Logical Replication WIP

Started by Petr Jelinekover 9 years ago222 messages
#1Petr Jelinek
petr@2ndquadrant.com
6 attachment(s)

Hi,

as promised here is WIP version of logical replication patch.

This is by no means anywhere close to be committable, but it should be
enough for discussion on the approaches chosen. I do plan to give this
some more time before September CF as well as during the CF itself.

You've seen some preview of ideas in the doc Simon posted [1]/messages/by-id/CANP8+j+NMHP-yFvoG03tpb4_s7GdmnCriEEOJeKkXWmUu_=-HA@mail.gmail.com, not all
of them are implemented yet in this patch though.

I'll start with the overview of the state of things.

What works:
- Replication of INSERT/UPDATE/DELETE operations on tables in
publication.
- Initial copy of data in publication.
- Automatic management of things like slots and origin tracking.
- Some psql support (\drp, \drs and additional info in \d for
tables, it's mainly missing ACLs as those are not implemented
(see bellow) yet and tab completion.

What's missing:
- sequences, I'd like to have them in 10.0 but I don't have good
way to implement it. PGLogical uses periodical syncing with some
buffer value but that's suboptimal. I would like to decode them
but that has proven to be complicated due to their sometimes
transactional sometimes nontransactional nature, so I probably
won't have time to do it within 10.0 by myself.
- ACLs, I still expect to have it the way it's documented in the
logical replication docs, but currently the code just assumes
superuser/REPLICATION role. This can be probably discussed in the
design thread more [1]/messages/by-id/CANP8+j+NMHP-yFvoG03tpb4_s7GdmnCriEEOJeKkXWmUu_=-HA@mail.gmail.com.
- pg_dump, same as above, I want to have publications and membership
in those dumped unconditionally and potentially dump also
subscription definitions if user asks for it using commandline
option as I don't think subscriptions should be dumped by default as
automatically starting replication when somebody dumps and restores
the db goes against POLA.
- DDL, I see several approaches we could do here for 10.0. a) don't
deal with DDL at all yet, b) provide function which pushes the DDL
into replication queue and then executes on downstream (like
londiste, slony, pglogical do), c) capture the DDL query as text
and allow user defined function to be called with that DDL text on
the subscriber (that's what oracle did with CDC)
- FDW support on downstream, currently only INSERTs should work
there but that should be easy to fix.
- Monitoring, I'd like to add some pg_stat_subscription view on the
downstream (the rest of monitoring is very similar to physical
streaming so that needs mostly docs).
- TRUNCATE, this is handled using triggers in BDR and pglogical but
I am not convinced that's the right way to do it for incore as it
brings limitations (fe. inability to use restart identity).

The parts I am not overly happy with:
- The fact that subscription handles slot creation/drop means we do
some automagic that might fail and user might need to fix that up
manually. I am not saying this is necessarily problem as that's how
most of the publish/subscribe replication systems work but I wonder
if there is better way of doing this that I missed.
- The initial copy patch adds some interfaces for getting table list
and data into the DecodingContext and I wonder if that's good place
for those or if we should create some TableSync API instead that
would load plugin as well and have these two new interfaces and put
into the tablesync module. One reason why I didn't do it is that
the interface would be almost the same and the plugin then would
have to do separate init for DecodingContext and TableSync.
- The initial copy uses the snapshot from slot creation in the
walsender. I currently just push it as active snapshot inside
snapbuilder which is probably not the right thing to do (tm). That
is mostly because I don't really know what the right thing is there.

About individual pathes:
0001-Add-PUBLICATION-catalogs-and-DDL.patch: This patch defines a
Publication which his basically same thing as replication set. It adds
database local catalog pg_publication which stores the publications and
DML filters, and pg_publication_rel catalog for storing membership of
relation in the publication. Adds the DDL, dependency handling and all
the necessary boilerplate around that including some basic regression
tests for the DDL.

0002-Add-SUBSCRIPTION-catalog-and-DDL.patch: Adds Subscriptions with
shared nailed (!) catalog pg_subscription which stores the individual
subscriptions for each database. The reason why this is nailed is that
it needs to be accessible without connection to database so that the
logical replication launcher can read it and start/stop workers as
necessary. This does not include regression tests as I am usure how to
test this within regression testing framework given that it is
supposed to start workers (those are added in later patches).

0003-Define-logical-replication-protocol-and-output-plugi.patch:
Adds the logical replication protocol (api and docs) and "standard"
output plugin for logical decoding that produces output based on that
protocol and the publication definitions.

0004-Make-libpqwalreceiver-reentrant.patch: Redesigns the
libpqwalreceiver to be reusable outside of walreceiver by exporting
the api as struct and opaque connection handle. Also adds couple of
additional functions for logical replication.

0005-Add-logical-replication-workers.patch: This patch adds the actual
logical replication workers that use all above to implement the data
change replication from publisher to subscriber. It adds two different
background workers. First is Launcher which works like the autovacuum
laucnher in that it gets list of subscriptions and starts/stops the
apply workers for those subscriptions as needed. Apply workers connect
to the output plugin via streaming protocol and handle the actual data
replication. I exported the ExecUpdate/ExecInsert/ExecDelete functions
from nodeModifyTable to handle the actual database updates so that
things like triggers, etc are handled automatically without special
code. This also adds couple of TAP tests that test basic replication
setup and also wide variety of type support. Also the overview doc for
logical replication that Simon previously posted to the list is part
of this one.

0006-Logical-replication-support-for-initial-data-copy.patch: PoC of
initial sync. It adds another mode into apply worker which just applies
updates for single table and some handover logic for when the table is
given synchronized and can be replicated normally. It also adds new
catalog pg_subscription_rel which keeps information about
synchronization status of individual tables. Note that tables added to
publications at later time are not yet synchronized, there is also no
resynchronization UI yet.

On the upstream side it adds two new commands into replication protocol
for getting list of tables and for streaming existing table data. I
discussed this part as suboptimal above so won't repeat here.

Feedback is welcome.

[1]: /messages/by-id/CANP8+j+NMHP-yFvoG03tpb4_s7GdmnCriEEOJeKkXWmUu_=-HA@mail.gmail.com
/messages/by-id/CANP8+j+NMHP-yFvoG03tpb4_s7GdmnCriEEOJeKkXWmUu_=-HA@mail.gmail.com

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

Attachments:

0001-Add-PUBLICATION-catalogs-and-DDL.patchapplication/x-patch; name=0001-Add-PUBLICATION-catalogs-and-DDL.patchDownload
From 21bfbbe60ec81eb028ede812216c3ca2f035499c Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Wed, 13 Jul 2016 18:11:54 +0200
Subject: [PATCH 1/6] Add PUBLICATION catalogs and DDL

---
 doc/src/sgml/catalogs.sgml                    | 128 ++++++
 doc/src/sgml/ref/allfiles.sgml                |   3 +
 doc/src/sgml/ref/alter_publication.sgml       | 159 ++++++++
 doc/src/sgml/ref/create_publication.sgml      | 164 ++++++++
 doc/src/sgml/ref/drop_publication.sgml        |  86 ++++
 src/backend/catalog/Makefile                  |   1 +
 src/backend/catalog/dependency.c              |  19 +
 src/backend/catalog/objectaddress.c           | 162 ++++++++
 src/backend/commands/Makefile                 |   5 +-
 src/backend/commands/event_trigger.c          |   5 +
 src/backend/commands/publicationcmds.c        | 550 ++++++++++++++++++++++++++
 src/backend/nodes/copyfuncs.c                 |  31 ++
 src/backend/nodes/equalfuncs.c                |  29 ++
 src/backend/parser/gram.y                     | 117 +++++-
 src/backend/replication/logical/Makefile      |   4 +-
 src/backend/replication/logical/publication.c | 343 ++++++++++++++++
 src/backend/tcop/utility.c                    |  38 ++
 src/backend/utils/cache/relcache.c            |   9 +
 src/backend/utils/cache/syscache.c            |  46 +++
 src/bin/psql/command.c                        |  27 +-
 src/bin/psql/describe.c                       |  96 +++++
 src/bin/psql/describe.h                       |   3 +
 src/bin/psql/help.c                           |   1 +
 src/include/catalog/dependency.h              |   2 +
 src/include/catalog/indexing.h                |  12 +
 src/include/catalog/pg_publication.h          |  64 +++
 src/include/catalog/pg_publication_rel.h      |  53 +++
 src/include/commands/replicationcmds.h        |  26 ++
 src/include/nodes/nodes.h                     |   2 +
 src/include/nodes/parsenodes.h                |  24 ++
 src/include/parser/kwlist.h                   |   1 +
 src/include/replication/publication.h         |  47 +++
 src/include/utils/rel.h                       |   4 +
 src/include/utils/syscache.h                  |   4 +
 src/test/regress/expected/publication.out     |  76 ++++
 src/test/regress/expected/sanity_check.out    |   2 +
 src/test/regress/parallel_schedule            |   2 +-
 src/test/regress/sql/publication.sql          |  40 ++
 38 files changed, 2370 insertions(+), 15 deletions(-)
 create mode 100644 doc/src/sgml/ref/alter_publication.sgml
 create mode 100644 doc/src/sgml/ref/create_publication.sgml
 create mode 100644 doc/src/sgml/ref/drop_publication.sgml
 create mode 100644 src/backend/commands/publicationcmds.c
 create mode 100644 src/backend/replication/logical/publication.c
 create mode 100644 src/include/catalog/pg_publication.h
 create mode 100644 src/include/catalog/pg_publication_rel.h
 create mode 100644 src/include/commands/replicationcmds.h
 create mode 100644 src/include/replication/publication.h
 create mode 100644 src/test/regress/expected/publication.out
 create mode 100644 src/test/regress/sql/publication.sql

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 8f5332a..6d505ae 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -236,6 +236,16 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-publication"><structname>pg_publication</structname></link></entry>
+      <entry>publications for logical replication</entry>
+     </row>
+
+     <row>
+      <entry><link linkend="catalog-pg-publication-rel"><structname>pg_publication_rel</structname></link></entry>
+      <entry>relation to publication mapping</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-range"><structname>pg_range</structname></link></entry>
       <entry>information about range types</entry>
      </row>
@@ -5110,6 +5120,124 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-publication">
+  <title><structname>pg_publication</structname></title>
+
+  <indexterm zone="catalog-pg-publication">
+   <primary>pg_publication</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_publication</structname> catalog contains
+   all publications created in the database.
+  </para>
+
+  <table>
+
+   <title><structname>pg_publication</structname> Columns</title>
+
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry><structfield>oid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry></entry>
+      <entry>Row identifier (hidden attribute; must be explicitly selected)</entry>
+     </row>
+
+     <row>
+      <entry><structfield>pubname</structfield></entry>
+      <entry><type>Name</type></entry>
+      <entry></entry>
+      <entry>A unique, database-wide identifier for the publication.</entry>
+     </row>
+
+     <row>
+      <entry><structfield>pubreplins</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>If true, INSERT operations are replicated for tables in the
+       publication.</entry>
+     </row>
+
+     <row>
+      <entry><structfield>pubreplupd</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>If true, UPDATE operations are replicated for tables in the
+       publication.</entry>
+     </row>
+
+     <row>
+      <entry><structfield>pubrepldel</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>If true, DELETE operations are replicated for tables in the
+       publication.</entry>
+     </row>
+
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
+ <sect1 id="catalog-pg-publication-rel">
+  <title><structname>pg_publication_rel</structname></title>
+
+  <indexterm zone="catalog-pg-publication-rel">
+   <primary>pg_publication_rel</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_publication_rel</structname> catalog contains
+   mapping between tables and publications in the database. This is many to
+   many mapping.
+  </para>
+
+  <table>
+
+   <title><structname>pg_publication_rel</structname> Columns</title>
+
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+    <tbody>
+
+     <row>
+      <entry><structfield>pubid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.oid</literal></entry>
+      <entry>Publication reference.</entry>
+     </row>
+
+     <row>
+      <entry><structfield>relid</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+      <entry>Relation reference.</entry>
+     </row>
+
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="catalog-pg-range">
   <title><structname>pg_range</structname></title>
 
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index 77667bd..371a7b7 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -26,6 +26,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY alterOperatorClass SYSTEM "alter_opclass.sgml">
 <!ENTITY alterOperatorFamily SYSTEM "alter_opfamily.sgml">
 <!ENTITY alterPolicy        SYSTEM "alter_policy.sgml">
+<!ENTITY alterPublication   SYSTEM "alter_publication.sgml">
 <!ENTITY alterRole          SYSTEM "alter_role.sgml">
 <!ENTITY alterRule          SYSTEM "alter_rule.sgml">
 <!ENTITY alterSchema        SYSTEM "alter_schema.sgml">
@@ -72,6 +73,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY createOperatorClass SYSTEM "create_opclass.sgml">
 <!ENTITY createOperatorFamily SYSTEM "create_opfamily.sgml">
 <!ENTITY createPolicy       SYSTEM "create_policy.sgml">
+<!ENTITY createPublication  SYSTEM "create_publication.sgml">
 <!ENTITY createRole         SYSTEM "create_role.sgml">
 <!ENTITY createRule         SYSTEM "create_rule.sgml">
 <!ENTITY createSchema       SYSTEM "create_schema.sgml">
@@ -116,6 +118,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY dropOperatorFamily  SYSTEM "drop_opfamily.sgml">
 <!ENTITY dropOwned          SYSTEM "drop_owned.sgml">
 <!ENTITY dropPolicy         SYSTEM "drop_policy.sgml">
+<!ENTITY dropPublication    SYSTEM "drop_publication.sgml">
 <!ENTITY dropRole           SYSTEM "drop_role.sgml">
 <!ENTITY dropRule           SYSTEM "drop_rule.sgml">
 <!ENTITY dropSchema         SYSTEM "drop_schema.sgml">
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
new file mode 100644
index 0000000..246b39b
--- /dev/null
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -0,0 +1,159 @@
+<!--
+doc/src/sgml/ref/alter_publication.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-ALTERPUBLICATION">
+ <indexterm zone="sql-alterpublication">
+  <primary>ALTER PUBLICATION</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>ALTER PUBLICATION</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>ALTER PUBLICATION</refname>
+  <refpurpose>change the definition of a publication</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+ALTER PUBLICATION <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replaceable class="PARAMETER">option</replaceable> [ ... ] ]
+
+<phrase>where <replaceable class="PARAMETER">option</replaceable> can be:</phrase>
+
+      REPLICATE_INSERT | NOREPLICATE_INSERT
+    | REPLICATE_UPDATE | NOREPLICATE_UPDATE
+    | REPLICATE_DELETE | NOREPLICATE_DELETE
+
+ALTER PUBLICATION <replaceable class="PARAMETER">name</replaceable> ADD TABLE <replaceable class="PARAMETER">table_name</replaceable> [, ...]
+ALTER PUBLICATION <replaceable class="PARAMETER">name</replaceable> ADD TABLE ALL IN SCHEMA <replaceable class="PARAMETER">schema_name</replaceable>
+ALTER PUBLICATION <replaceable class="PARAMETER">name</replaceable> DROP TABLE <replaceable class="PARAMETER">table_name</replaceable> [, ...]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   The first variant of this command listed in the synopsis can change
+   all of the publication attributes that can be specified in
+   <xref linkend="sql-createpublication">.
+   Attributes not mentioned in the command retain their previous settings.
+   Database superusers can change any of these settings for any role.
+  </para>
+
+  <para>
+   The other variants this command deal with table membership in the
+   publication. The <literal>ADD TABLE</literal> subcommand will add one
+   or more tables to the publication. If the optional
+   <literal>ALL IN SCHEMA</literal> is specified all tables in that schema
+   will be added. The <literal>ALL IN SCHEMA</literal> variant of this
+   command will not complain about tables that are already present in the
+   publication, it only adds the missing ones.
+   The <literal>DROP TABLE</literal> will remove one or more table from
+   publication.
+  </para>
+
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name of an existing publication whose attributes are to be
+      altered.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>REPLICATE_INSERT</literal></term>
+    <term><literal>NOREPLICATE_INSERT</literal></term>
+    <term><literal>REPLICATE_UPDATE</literal></term>
+    <term><literal>NOREPLICATE_UPDATE</literal></term>
+    <term><literal>REPLICATE_DELETE</literal></term>
+    <term><literal>NOREPLICATE_DELETE</literal></term>
+    <listitem>
+     <para>
+      These clauses alter attributes originally set by
+      <xref linkend="SQL-CREATEPUBLICATION">. For more information, see the
+      <command>CREATE PUBLICATION</command> reference page.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">table_name</replaceable></term>
+    <listitem>
+     <para>
+      Name of an existing table.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">schema_name</replaceable></term>
+    <listitem>
+     <para>
+      Name of a schema.
+     </para>
+    </listitem>
+   </varlistentry>
+
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   Change the publication to not replicate inserts:
+<programlisting>
+ALTER PUBLICATION noinsert NOREPLICATE_INSERT;
+</programlisting>
+  </para>
+
+  <para>
+   Add some tables to the publication:
+<programlisting>
+ALTER PUBLICATION mypublication ADD TABLE users, departments;
+</programlisting>
+  </para>
+
+  <para>
+   Add all tables from public schema to the publication:
+<programlisting>
+ALTER PUBLICATION mypublication ADD TABLE ALL IN SCHEMA public;
+</programlisting>
+  </para>
+
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <command>ALTER PUBLICATION</command> is a <productname>PostgreSQL</>
+   extension.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-createpublication"></member>
+   <member><xref linkend="sql-droppublication"></member>
+   <member><xref linkend="sql-createsubscription"></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
new file mode 100644
index 0000000..ac8b71e
--- /dev/null
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -0,0 +1,164 @@
+<!--
+doc/src/sgml/ref/create_publication.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-CREATEPUBLICATION">
+ <indexterm zone="sql-createpublication">
+  <primary>CREATE PUBLICATION</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>CREATE PUBLICATION</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>CREATE PUBLICATION</refname>
+  <refpurpose>define new publication</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+CREATE PUBLICATION <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replaceable class="PARAMETER">option</replaceable> [ ... ] ]
+
+<phrase>where <replaceable class="PARAMETER">option</replaceable> can be:</phrase>
+
+      REPLICATE_INSERT | NOREPLICATE_INSERT
+    | REPLICATE_UPDATE | NOREPLICATE_UPDATE
+    | REPLICATE_DELETE | NOREPLICATE_DELETE
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>CREATE PUBLICATION</command> adds a new publication
+   into the current database. The publication name must be distinct from
+   the name of any existing publication in the current database.
+  </para>
+
+  <para>
+   A publication is essentially a group of tables intended for managing
+   logical replication. See
+   <xref linkend="logical-replication-publication"> for details about how
+   publications fit into logical replication setup.
+   </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name of the new publication.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>REPLICATE_INSERT</literal></term>
+    <term><literal>NOREPLICATE_INSERT</literal></term>
+    <listitem>
+     <para>
+      These clauses determine whether the new publication will send
+      the <command>INSERT</command> operations to the subscribers.
+      <literal>REPLICATE_INSERT</literal> is the default.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>REPLICATE_UPDATE</literal></term>
+    <term><literal>NOREPLICATE_UPDATE</literal></term>
+    <listitem>
+     <para>
+      These clauses determine whether the new publication will send
+      the <command>UPDATE</command> operations to the subscribers.
+      <literal>REPLICATE_UPDATE</literal> is the default.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>REPLICATE_DELETE</literal></term>
+    <term><literal>NOREPLICATE_DELETE</literal></term>
+    <listitem>
+     <para>
+      These clauses determine whether the new publication will send
+      the <command>DELETE</command> operations to the subscribers.
+      <literal>REPLICATE_DELETE</literal> is the default.
+     </para>
+    </listitem>
+   </varlistentry>
+
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+   This operation does not reserve any resources on the server. It only
+   defines grouping and filtering logic for future subscribers.
+  </para>
+
+  <para>
+   To create a publication, the invoking user must have the
+   <literal>CREATE</> privilege for the current database and the
+   <literal>SUBSCRIPTION</> role.
+   (Of course, superusers bypass this check.)
+  </para>
+
+  <para>
+   Replication of <command>UPDATE</command> and <command>DELETE</command>
+   operations requires the tables added to the publication to have a
+   <literal>REPLICA IDENTITY</> index specified.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   Create a simple publication that just replicates all DML for tables in it:
+<programlisting>
+CREATE PUBLICATION mypublication;
+</programlisting>
+  </para>
+
+  <para>
+   Create an insert only publication (for example for tables without a
+   primary key):
+<programlisting>
+CREATE PUBLICATION insert_only NOREPLICATE_UPDATE NOREPLICATE_DELETE;
+</programlisting>
+  </para>
+
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <command>CREATE PUBLICATION</command> is a <productname>PostgreSQL</>
+   extension.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-alterpublication"></member>
+   <member><xref linkend="sql-droppublication"></member>
+   <member><xref linkend="sql-createsubscription"></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/drop_publication.sgml b/doc/src/sgml/ref/drop_publication.sgml
new file mode 100644
index 0000000..c5b0c78
--- /dev/null
+++ b/doc/src/sgml/ref/drop_publication.sgml
@@ -0,0 +1,86 @@
+<!--
+doc/src/sgml/ref/drop_publication.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-DROPPUBLICATION">
+ <indexterm zone="sql-droppublication">
+  <primary>DROP PUBLICATION</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>DROP PUBLICATION</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>DROP PUBLICATION</refname>
+  <refpurpose>remove an existing publication</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+DROP PUBLCATION <replaceable class="PARAMETER">name</replaceable> [, ...]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>DROP PUBLCATION</command> removes publications from the database.
+  </para>
+
+  <para>
+   A publication can only be dropped by its owner or a superuser.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name of an existing publication.
+     </para>
+    </listitem>
+   </varlistentry>
+
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   Drop a publication:
+<programlisting>
+DROP PUBLICATION mypublication;
+</programlisting>
+  </para>
+
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <command>DROP PUBLICATION</command> is a <productname>PostgreSQL</>
+   extension.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-createpublication"></member>
+   <member><xref linkend="sql-alterpublication"></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..6bb3683 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -42,6 +42,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
 	pg_foreign_table.h pg_policy.h pg_replication_origin.h \
 	pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
 	pg_collation.h pg_range.h pg_transform.h \
+	pg_publication.h pg_publication_rel.h \
 	toasting.h indexing.h \
     )
 
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..359e7bb 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -48,6 +48,8 @@
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_policy.h"
 #include "catalog/pg_proc.h"
+#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_rel.h"
 #include "catalog/pg_rewrite.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_transform.h"
@@ -64,6 +66,7 @@
 #include "commands/extension.h"
 #include "commands/policy.h"
 #include "commands/proclang.h"
+#include "commands/replicationcmds.h"
 #include "commands/schemacmds.h"
 #include "commands/seclabel.h"
 #include "commands/trigger.h"
@@ -163,6 +166,8 @@ static const Oid object_classes[] = {
 	ExtensionRelationId,		/* OCLASS_EXTENSION */
 	EventTriggerRelationId,		/* OCLASS_EVENT_TRIGGER */
 	PolicyRelationId,			/* OCLASS_POLICY */
+	PublicationRelationId,		/* OCLASS_PUBCLICATION */
+	PublicationRelRelationId,	/* OCLASS_PUBCLICATION_REL */
 	TransformRelationId			/* OCLASS_TRANSFORM */
 };
 
@@ -1279,6 +1284,14 @@ doDeletion(const ObjectAddress *object, int flags)
 			RemovePolicyById(object->objectId);
 			break;
 
+		case OCLASS_PUBLICATION:
+			DropPublicationById(object->objectId);
+			break;
+
+		case OCLASS_PUBLICATION_REL:
+			RemovePublicationRelById(object->objectId);
+			break;
+
 		case OCLASS_TRANSFORM:
 			DropTransformById(object->objectId);
 			break;
@@ -2436,6 +2449,12 @@ getObjectClass(const ObjectAddress *object)
 		case PolicyRelationId:
 			return OCLASS_POLICY;
 
+		case PublicationRelationId:
+			return OCLASS_PUBLICATION;
+
+		case PublicationRelRelationId:
+			return OCLASS_PUBLICATION_REL;
+
 		case TransformRelationId:
 			return OCLASS_TRANSFORM;
 	}
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 8068b82..375f4b0 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -45,6 +45,8 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_policy.h"
+#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_rel.h"
 #include "catalog/pg_rewrite.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_transform.h"
@@ -71,6 +73,7 @@
 #include "parser/parse_func.h"
 #include "parser/parse_oper.h"
 #include "parser/parse_type.h"
+#include "replication/publication.h"
 #include "rewrite/rewriteSupport.h"
 #include "storage/lmgr.h"
 #include "storage/sinval.h"
@@ -450,6 +453,18 @@ static const ObjectPropertyType ObjectProperty[] =
 		Anum_pg_type_typacl,
 		ACL_KIND_TYPE,
 		true
+	},
+	{
+		PublicationRelationId,
+		PublicationObjectIndexId,
+		PUBLICATIONOID,
+		PUBLICATIONNAME,
+		Anum_pg_publication_pubname,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		-1,
+		true
 	}
 };
 
@@ -653,6 +668,14 @@ static const struct object_type_map
 	{
 		"policy", OBJECT_POLICY
 	},
+	/* OCLASS_PUBLICATION */
+	{
+		"publication", OBJECT_PUBLICATION
+	},
+	/* OCLASS_PUBLICATION_REL */
+	{
+		"publication relation", OBJECT_PUBLICATION_REL
+	},
 	/* OCLASS_TRANSFORM */
 	{
 		"transform", OBJECT_TRANSFORM
@@ -688,6 +711,9 @@ static ObjectAddress get_object_address_opf_member(ObjectType objtype,
 
 static ObjectAddress get_object_address_usermapping(List *objname,
 							   List *objargs, bool missing_ok);
+static ObjectAddress get_object_address_publication_rel(List *objname,
+								   List *objargs, Relation *relation,
+								   bool missing_ok);
 static ObjectAddress get_object_address_defacl(List *objname, List *objargs,
 						  bool missing_ok);
 static const ObjectPropertyType *get_object_property_data(Oid class_id);
@@ -812,6 +838,7 @@ get_object_address(ObjectType objtype, List *objname, List *objargs,
 			case OBJECT_FOREIGN_SERVER:
 			case OBJECT_EVENT_TRIGGER:
 			case OBJECT_ACCESS_METHOD:
+			case OBJECT_PUBLICATION:
 				address = get_object_address_unqualified(objtype,
 														 objname, missing_ok);
 				break;
@@ -926,6 +953,10 @@ get_object_address(ObjectType objtype, List *objname, List *objargs,
 				address = get_object_address_usermapping(objname, objargs,
 														 missing_ok);
 				break;
+			case OBJECT_PUBLICATION_REL:
+				address = get_object_address_publication_rel(objname, objargs,
+															 &relation,
+															 missing_ok);
 			case OBJECT_DEFACL:
 				address = get_object_address_defacl(objname, objargs,
 													missing_ok);
@@ -1091,6 +1122,9 @@ get_object_address_unqualified(ObjectType objtype,
 			case OBJECT_EVENT_TRIGGER:
 				msg = gettext_noop("event trigger name cannot be qualified");
 				break;
+			case OBJECT_PUBLICATION:
+				msg = gettext_noop("publication name cannot be qualified");
+				break;
 			default:
 				elog(ERROR, "unrecognized objtype: %d", (int) objtype);
 				msg = NULL;		/* placate compiler */
@@ -1156,6 +1190,11 @@ get_object_address_unqualified(ObjectType objtype,
 			address.objectId = get_event_trigger_oid(name, missing_ok);
 			address.objectSubId = 0;
 			break;
+		case OBJECT_PUBLICATION:
+			address.classId = PublicationRelationId;
+			address.objectId = get_publication_oid(name, missing_ok);
+			address.objectSubId = 0;
+			break;
 		default:
 			elog(ERROR, "unrecognized objtype: %d", (int) objtype);
 			/* placate compiler, which doesn't know elog won't return */
@@ -1743,6 +1782,50 @@ get_object_address_usermapping(List *objname, List *objargs, bool missing_ok)
 }
 
 /*
+ * Find the ObjectAddress for a publication relation.
+ */
+static ObjectAddress
+get_object_address_publication_rel(List *objname, List *objargs,
+								   Relation *relation, bool missing_ok)
+{
+	ObjectAddress address;
+	char	   *pubname;
+	Publication *pub;
+
+	ObjectAddressSet(address, UserMappingRelationId, InvalidOid);
+
+	*relation = relation_openrv_extended(makeRangeVarFromNameList(objname),
+										 AccessShareLock, missing_ok);
+	if (!relation)
+		return address;
+
+	/* fetch publication name from input list */
+	pubname = strVal(linitial(objargs));
+
+	/* Now look up the pg_publication tuple */
+	pub = GetPublicationByName(pubname, missing_ok);
+	if (!pub)
+		return address;
+
+	/* Find the publication relation mapping in syscache. */
+	address.objectId =
+		GetSysCacheOid2(PUBLICATIONRELMAP,
+						ObjectIdGetDatum(RelationGetRelid(*relation)),
+						ObjectIdGetDatum(pub->oid));
+	if (!OidIsValid(address.objectId))
+	{
+		if (!missing_ok)
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("publication relation \"%s\" in publication \"%s\" does not exist",
+							RelationGetRelationName(*relation), pubname)));
+		return address;
+	}
+
+	return address;
+}
+
+/*
  * Find the ObjectAddress for a default ACL.
  */
 static ObjectAddress
@@ -2001,6 +2084,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
 		case OBJECT_DOMCONSTRAINT:
 		case OBJECT_CAST:
 		case OBJECT_USER_MAPPING:
+		case OBJECT_PUBLICATION_REL:
 		case OBJECT_DEFACL:
 		case OBJECT_TRANSFORM:
 			if (list_length(args) != 1)
@@ -2230,6 +2314,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 		case OBJECT_TSPARSER:
 		case OBJECT_TSTEMPLATE:
 		case OBJECT_ACCESS_METHOD:
+		case OBJECT_PUBLICATION:
 			/* We treat these object types as being owned by superusers */
 			if (!superuser_arg(roleid))
 				ereport(ERROR,
@@ -3195,6 +3280,42 @@ getObjectDescription(const ObjectAddress *object)
 				break;
 			}
 
+		case OCLASS_PUBLICATION:
+			{
+				HeapTuple	tup;
+
+				tup = SearchSysCache1(PUBLICATIONOID,
+									  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "cache lookup failed for publication %u",
+						 object->objectId);
+				appendStringInfo(&buffer, _("publicaton %s"),
+				   NameStr(((Form_pg_publication) GETSTRUCT(tup))->pubname));
+				ReleaseSysCache(tup);
+				break;
+			}
+
+		case OCLASS_PUBLICATION_REL:
+			{
+				HeapTuple	tup;
+				Publication *pub;
+				Form_pg_publication_rel	prform;
+
+				tup = SearchSysCache1(PUBLICATIONREL,
+									  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "cache lookup failed for publication table %u",
+						 object->objectId);
+
+				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
+				pub = GetPublication(prform->pubid);
+
+				appendStringInfo(&buffer, _("publication table %s in publication %s"),
+								 get_rel_name(prform->relid), pub->name);
+				ReleaseSysCache(tup);
+				break;
+			}
+
 		default:
 			appendStringInfo(&buffer, "unrecognized object %u %u %d",
 							 object->classId,
@@ -3680,6 +3801,14 @@ getObjectTypeDescription(const ObjectAddress *object)
 			appendStringInfoString(&buffer, "access method");
 			break;
 
+		case OCLASS_PUBLICATION:
+			appendStringInfoString(&buffer, "publication");
+			break;
+
+		case OCLASS_PUBLICATION_REL:
+			appendStringInfoString(&buffer, "publication table");
+			break;
+
 		default:
 			appendStringInfo(&buffer, "unrecognized %u", object->classId);
 			break;
@@ -4650,6 +4779,39 @@ getObjectIdentityParts(const ObjectAddress *object,
 			}
 			break;
 
+		case OCLASS_PUBLICATION:
+			{
+				Publication *pub;
+
+				pub = GetPublication(object->objectId);
+				appendStringInfoString(&buffer,
+									   quote_identifier(pub->name));
+				if (objname)
+					*objname = list_make1(pstrdup(pub->name));
+				break;
+			}
+
+		case OCLASS_PUBLICATION_REL:
+			{
+				HeapTuple	tup;
+				Publication *pub;
+				Form_pg_publication_rel	prform;
+
+				tup = SearchSysCache1(PUBLICATIONREL,
+									  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "cache lookup failed for publication table %u",
+						 object->objectId);
+
+				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
+				pub = GetPublication(prform->pubid);
+
+				appendStringInfo(&buffer, _("%s in publication %s"),
+								 get_rel_name(prform->relid), pub->name);
+				ReleaseSysCache(tup);
+				break;
+			}
+
 		default:
 			appendStringInfo(&buffer, "unrecognized object %u %u %d",
 							 object->classId,
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 6b3742c..cb580377 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -17,9 +17,8 @@ OBJS = amcmds.o aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \
 	dbcommands.o define.o discard.o dropcmds.o \
 	event_trigger.o explain.o extension.o foreigncmds.o functioncmds.o \
 	indexcmds.o lockcmds.o matview.o operatorcmds.o opclasscmds.o \
-	policy.o portalcmds.o prepare.o proclang.o \
+	policy.o portalcmds.o prepare.o proclang.o publicationcmds.o \
 	schemacmds.o seclabel.o sequence.o tablecmds.o tablespace.o trigger.o \
-	tsearchcmds.o typecmds.o user.o vacuum.o vacuumlazy.o \
-	variable.o view.o
+	tsearchcmds.o typecmds.o user.o vacuum.o vacuumlazy.o variable.o view.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 50c89b8..8aaf1a7 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -122,6 +122,7 @@ static event_trigger_support_data event_trigger_support[] = {
 	{"TYPE", true},
 	{"USER MAPPING", true},
 	{"VIEW", true},
+	{"PUBLICATION", true},
 	{NULL, false}
 };
 
@@ -1119,6 +1120,8 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 		case OBJECT_TYPE:
 		case OBJECT_USER_MAPPING:
 		case OBJECT_VIEW:
+		case OBJECT_PUBLICATION:
+		case OBJECT_PUBLICATION_REL:
 			return true;
 	}
 	return true;
@@ -1170,6 +1173,8 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
 		case OCLASS_EXTENSION:
 		case OCLASS_POLICY:
 		case OCLASS_AM:
+		case OCLASS_PUBLICATION:
+		case OCLASS_PUBLICATION_REL:
 			return true;
 	}
 
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
new file mode 100644
index 0000000..19c6a97
--- /dev/null
+++ b/src/backend/commands/publicationcmds.c
@@ -0,0 +1,550 @@
+/*-------------------------------------------------------------------------
+ *
+ * publicationcmds.c
+ *		publication manipulation
+ *
+ * Copyright (c) 2015, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		publicationcmds.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "funcapi.h"
+#include "miscadmin.h"
+
+#include "access/genam.h"
+#include "access/hash.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/xact.h"
+
+#include "catalog/indexing.h"
+#include "catalog/namespace.h"
+#include "catalog/objectaddress.h"
+#include "catalog/pg_inherits_fn.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_rel.h"
+
+#include "commands/defrem.h"
+#include "commands/event_trigger.h"
+#include "commands/replicationcmds.h"
+
+#include "executor/spi.h"
+
+#include "nodes/makefuncs.h"
+
+#include "parser/parse_clause.h"
+
+#include "replication/publication.h"
+#include "replication/reorderbuffer.h"
+
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/catcache.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+static void
+check_replication_permissions(void)
+{
+	if (!superuser() && !has_rolreplication(GetUserId()))
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 (errmsg("must be superuser or replication role to manipulate publications"))));
+}
+
+static void
+parse_publication_options(List *options,
+						  bool *replicate_insert_given,
+						  bool *replicate_insert,
+						  bool *replicate_update_given,
+						  bool *replicate_update,
+						  bool *replicate_delete_given,
+						  bool *replicate_delete)
+{
+	ListCell   *lc;
+
+	*replicate_insert_given = false;
+	*replicate_update_given = false;
+	*replicate_delete_given = false;
+
+	/* Defaults are true */
+	*replicate_insert = true;
+	*replicate_update = true;
+	*replicate_delete = true;
+
+	/* Parse options */
+	foreach (lc, options)
+	{
+		DefElem    *defel = (DefElem *) lfirst(lc);
+
+		if (strcmp(defel->defname, "replicate_insert") == 0)
+		{
+			if (*replicate_insert_given)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+
+			*replicate_insert_given = true;
+			*replicate_insert = defGetBoolean(defel);
+		}
+		else if (strcmp(defel->defname, "replicate_update") == 0)
+		{
+			if (*replicate_update_given)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+
+			*replicate_update_given = true;
+			*replicate_update = defGetBoolean(defel);
+		}
+		else if (strcmp(defel->defname, "replicate_delete") == 0)
+		{
+			if (*replicate_delete_given)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+
+			*replicate_delete_given = true;
+			*replicate_delete = defGetBoolean(defel);
+		}
+		else
+			elog(ERROR, "unrecognized option: %s", defel->defname);
+	}
+}
+
+/*
+ * Create new publication.
+ * TODO ACL check
+ */
+ObjectAddress
+CreatePublication(CreatePublicationStmt *stmt)
+{
+	Relation	rel;
+	ObjectAddress myself;
+	Oid			puboid;
+	bool		nulls[Natts_pg_publication];
+	Datum		values[Natts_pg_publication];
+	HeapTuple	tup;
+	bool		replicate_insert_given;
+	bool		replicate_update_given;
+	bool		replicate_delete_given;
+	bool		replicate_insert;
+	bool		replicate_update;
+	bool		replicate_delete;
+
+	check_replication_permissions();
+
+	rel = heap_open(PublicationRelationId, RowExclusiveLock);
+
+	/* Check if name is used */
+	puboid = GetSysCacheOid1(PUBLICATIONNAME, CStringGetDatum(stmt->pubname));
+	if (OidIsValid(puboid))
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("publication \"%s\" already exists",
+						stmt->pubname)));
+	}
+
+	/* Form a tuple. */
+	memset(values, 0, sizeof(values));
+	memset(nulls, false, sizeof(nulls));
+
+	values[Anum_pg_publication_pubname - 1] =
+		DirectFunctionCall1(namein, CStringGetDatum(stmt->pubname));
+
+	parse_publication_options(stmt->options,
+							  &replicate_insert_given, &replicate_insert,
+							  &replicate_update_given, &replicate_update,
+							  &replicate_delete_given, &replicate_delete);
+
+	values[Anum_pg_publication_pubreplins - 1] =
+		BoolGetDatum(replicate_insert);
+	values[Anum_pg_publication_pubreplupd - 1] =
+		BoolGetDatum(replicate_update);
+	values[Anum_pg_publication_pubrepldel - 1] =
+		BoolGetDatum(replicate_delete);
+
+	tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+	/* Insert tuple into catalog. */
+	puboid = simple_heap_insert(rel, tup);
+	CatalogUpdateIndexes(rel, tup);
+	heap_freetuple(tup);
+
+	ObjectAddressSet(myself, PublicationRelationId, puboid);
+
+	heap_close(rel, RowExclusiveLock);
+
+	/* Make the changes visible. */
+	CommandCounterIncrement();
+
+	return myself;
+}
+
+/*
+ * Change options of a publication.
+ */
+static void
+AlterPublicationOptions(AlterPublicationStmt *stmt, Relation rel,
+					   HeapTuple tup)
+{
+	bool		nulls[Natts_pg_publication];
+	bool		replaces[Natts_pg_publication];
+	Datum		values[Natts_pg_publication];
+	bool		replicate_insert_given;
+	bool		replicate_update_given;
+	bool		replicate_delete_given;
+	bool		replicate_insert;
+	bool		replicate_update;
+	bool		replicate_delete;
+	ObjectAddress		obj;
+	Form_pg_publication pub = (Form_pg_publication) GETSTRUCT(tup);
+
+	parse_publication_options(stmt->options,
+							  &replicate_insert_given, &replicate_insert,
+							  &replicate_update_given, &replicate_update,
+							  &replicate_delete_given, &replicate_delete);
+
+	/*
+	 * Validate that replication is not being changed to replicate UPDATEs
+	 * and DELETEs if it contains any tables without replication identity.
+	 */
+	if ((replicate_update_given && replicate_update) ||
+		(replicate_delete_given && replicate_delete))
+	{
+		Relation		pubrelsrel;
+		ScanKeyData		scankey;
+		SysScanDesc		scan;
+		HeapTuple		reltup;
+
+		pubrelsrel = heap_open(PublicationRelRelationId, AccessShareLock);
+
+		/* Loop over all relations in the publication. */
+		ScanKeyInit(&scankey,
+					Anum_pg_publication_rel_pubid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(HeapTupleGetOid(tup)));
+
+		scan = systable_beginscan(pubrelsrel, 0, true, NULL, 1, &scankey);
+
+		/* Process every individual table in the publication. */
+		while (HeapTupleIsValid(reltup = systable_getnext(scan)))
+		{
+			Form_pg_publication_rel	t;
+			Relation				pubrel;
+
+			t = (Form_pg_publication_rel) GETSTRUCT(reltup);
+
+			pubrel = heap_open(t->relid, AccessShareLock);
+
+			/* Check if relation has replication index. */
+			if (RelationGetForm(pubrel)->relkind == RELKIND_RELATION)
+			{
+
+				if (pubrel->rd_indexvalid == 0)
+					RelationGetIndexList(pubrel);
+				if (!OidIsValid(pubrel->rd_replidindex))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("publication %s cannot be altered to "
+									"replicate UPDATEs or DELETEs because it "
+									"contains tables without PRIMARY KEY",
+									NameStr(pub->pubname))));
+			}
+
+			heap_close(pubrel, NoLock);
+		}
+
+		systable_endscan(scan);
+		heap_close(pubrelsrel, NoLock);
+	}
+
+	/* Everything ok, form a new tuple. */
+	memset(values, 0, sizeof(values));
+	memset(nulls, false, sizeof(nulls));
+	memset(replaces, false, sizeof(replaces));
+
+	if (replicate_insert_given)
+	{
+		values[Anum_pg_publication_pubreplins - 1] =
+			BoolGetDatum(replicate_insert);
+		replaces[Anum_pg_publication_pubreplins - 1] = true;
+	}
+	if (replicate_update_given)
+	{
+		values[Anum_pg_publication_pubreplupd - 1] =
+			BoolGetDatum(replicate_update);
+		replaces[Anum_pg_publication_pubreplupd - 1] = true;
+	}
+	if (replicate_delete_given)
+	{
+		values[Anum_pg_publication_pubrepldel - 1] =
+			BoolGetDatum(replicate_delete);
+		replaces[Anum_pg_publication_pubrepldel - 1] = true;
+	}
+
+	tup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls,
+							replaces);
+
+	/* Update the catalog. */
+	simple_heap_update(rel, &tup->t_self, tup);
+	CatalogUpdateIndexes(rel, tup);
+
+	ObjectAddressSet(obj, PublicationRelationId, HeapTupleGetOid(tup));
+	EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+									 (Node *) stmt);
+}
+
+/*
+ * Add or removes table to/from publication.
+ */
+static void
+AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
+					   HeapTuple tup)
+{
+	Oid			pubid = HeapTupleGetOid(tup);
+	Oid			prid;
+	bool		missing_ok = false;
+	bool		if_not_exists = false;
+	List	   *rels = NIL;
+	ListCell   *lc;
+	ObjectAddress	obj;
+
+	if (stmt->schema)
+	{
+		Oid			nspoid;
+		Relation	rel;
+		SysScanDesc scan;
+		ScanKeyData key[2];
+		HeapTuple	tup;
+
+		Assert(list_length(stmt->tables) == 0);
+
+		/*
+		 * Open, share-lock, and check all relation in the specified schema
+		 */
+
+		nspoid = LookupExplicitNamespace(stmt->schema, false);
+		rel = heap_open(RelationRelationId, AccessShareLock);
+
+		ScanKeyInit(&key[0],
+					Anum_pg_class_relnamespace,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(nspoid));
+		ScanKeyInit(&key[1],
+					Anum_pg_class_relkind,
+					BTEqualStrategyNumber, F_CHAREQ,
+					CharGetDatum(RELKIND_RELATION));
+
+		scan = systable_beginscan(rel, InvalidOid, false,
+								  NULL, 2, key);
+
+		while ((tup = systable_getnext(scan)) != NULL)
+		{
+			Oid			schemarelid = HeapTupleGetOid(tup);
+			Relation	schemarel;
+
+			schemarel = heap_open(schemarelid, AccessShareLock);
+			rels = lappend(rels, schemarel);
+		}
+
+		systable_endscan(scan);
+		heap_close(rel, AccessShareLock);
+
+		/* Don't error on missing tables on DROP */
+		missing_ok = true;
+
+		/* Don't error on already existing tables on ADD. */
+		if_not_exists = true;
+	}
+	else
+	{
+		List	   *relids = NIL;
+
+		Assert(list_length(stmt->tables) > 0);
+
+		/*
+		 * Open, share-lock, and check all the explicitly-specified relations
+		 */
+
+		foreach(lc, stmt->tables)
+		{
+			RangeVar   *rv = lfirst(lc);
+			Relation	rel;
+			bool		recurse = interpretInhOption(rv->inhOpt);
+			Oid			myrelid;
+
+			rel = heap_openrv(rv, AccessShareLock);
+			myrelid = RelationGetRelid(rel);
+			/* don't throw error for "foo, foo" */
+			if (list_member_oid(relids, myrelid))
+			{
+				heap_close(rel, AccessShareLock);
+				continue;
+			}
+			rels = lappend(rels, rel);
+			relids = lappend_oid(relids, myrelid);
+
+			if (recurse)
+			{
+				ListCell   *child;
+				List	   *children;
+
+				children = find_all_inheritors(myrelid, AccessShareLock,
+											   NULL);
+
+				foreach(child, children)
+				{
+					Oid			childrelid = lfirst_oid(child);
+
+					if (list_member_oid(relids, childrelid))
+						continue;
+
+					/* find_all_inheritors already got lock */
+					rel = heap_open(childrelid, NoLock);
+					rels = lappend(rels, rel);
+					relids = lappend_oid(relids, childrelid);
+				}
+			}
+		}
+	}
+
+	/* Do the operation which was requested on all found relations. */
+	if (stmt->isDrop)
+	{
+		foreach(lc, rels)
+		{
+			Relation	rel = (Relation) lfirst(lc);
+			Oid			relid = RelationGetRelid(rel);
+
+			prid = GetSysCacheOid2(PUBLICATIONRELMAP, relid, pubid);
+			if (!OidIsValid(prid))
+			{
+				if (missing_ok)
+					continue;
+
+				ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("relation \"%s\" is not part of the publication",
+							RelationGetRelationName(rel))));
+			}
+
+			ObjectAddressSet(obj, PublicationRelRelationId, prid);
+			performDeletion(&obj, DROP_CASCADE, 0);
+		}
+	}
+	else
+	{
+		foreach(lc, rels)
+		{
+			Relation	rel = (Relation) lfirst(lc);
+
+			prid = publication_add_relation(pubid, rel, if_not_exists);
+			ObjectAddressSet(obj, PublicationRelRelationId, prid);
+			EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+											 (Node *) stmt);
+		}
+	}
+
+	/* And close the rels */
+	foreach(lc, rels)
+	{
+		Relation	rel = (Relation) lfirst(lc);
+
+		heap_close(rel, NoLock);
+	}
+}
+
+/*
+ * Alter the existing publication.
+ *
+ * This is dispatcher function for AlterPublicationOptions and
+ * AlterPublicationTables.
+ */
+void
+AlterPublication(AlterPublicationStmt *stmt)
+{
+	Relation		rel;
+	HeapTuple		tup;
+
+	check_replication_permissions();
+
+	rel = heap_open(PublicationRelationId, RowExclusiveLock);
+
+	tup = SearchSysCacheCopy1(PUBLICATIONNAME,
+							  CStringGetDatum(stmt->pubname));
+
+	if (!HeapTupleIsValid(tup))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("publication \"%s\" does not exist",
+						stmt->pubname)));
+
+	if (stmt->options)
+		AlterPublicationOptions(stmt, rel, tup);
+	else
+		AlterPublicationTables(stmt, rel, tup);
+
+	/* Cleanup. */
+	heap_freetuple(tup);
+	heap_close(rel, RowExclusiveLock);
+}
+
+/*
+ * Drop publication by OID
+ */
+void
+DropPublicationById(Oid pubid)
+{
+	Relation	rel;
+	HeapTuple	tup;
+
+	check_replication_permissions();
+
+	rel = heap_open(PublicationRelationId, RowExclusiveLock);
+
+	tup = SearchSysCache1(PUBLICATIONOID, ObjectIdGetDatum(pubid));
+
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for publication %u", pubid);
+
+	simple_heap_delete(rel, &tup->t_self);
+
+	ReleaseSysCache(tup);
+
+	heap_close(rel, RowExclusiveLock);
+}
+
+/*
+ * Remove relation from publication by mapping OID.
+ */
+void
+RemovePublicationRelById(Oid prid)
+{
+	Relation        rel;
+	HeapTuple       tup;
+
+	rel = heap_open(PublicationRelRelationId, RowExclusiveLock);
+
+	tup = SearchSysCache1(PUBLICATIONREL, ObjectIdGetDatum(prid));
+
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for publication table %u",
+			 prid);
+
+	simple_heap_delete(rel, &tup->t_self);
+
+	ReleaseSysCache(tup);
+
+	heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3244c76..bf76742 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4154,6 +4154,31 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
 	return newnode;
 }
 
+static CreatePublicationStmt *
+_copyCreatePublicationStmt(const CreatePublicationStmt *from)
+{
+	CreatePublicationStmt *newnode = makeNode(CreatePublicationStmt);
+
+	COPY_STRING_FIELD(pubname);
+	COPY_NODE_FIELD(options);
+
+	return newnode;
+}
+
+static AlterPublicationStmt *
+_copyAlterPublicationStmt(const AlterPublicationStmt *from)
+{
+	AlterPublicationStmt *newnode = makeNode(AlterPublicationStmt);
+
+	COPY_STRING_FIELD(pubname);
+	COPY_NODE_FIELD(options);
+	COPY_SCALAR_FIELD(isDrop);
+	COPY_STRING_FIELD(schema);
+	COPY_NODE_FIELD(tables);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -4945,6 +4970,12 @@ copyObject(const void *from)
 		case T_AlterPolicyStmt:
 			retval = _copyAlterPolicyStmt(from);
 			break;
+		case T_CreatePublicationStmt:
+			retval = _copyCreatePublicationStmt(from);
+			break;
+		case T_AlterPublicationStmt:
+			retval = _copyAlterPublicationStmt(from);
+			break;
 		case T_A_Expr:
 			retval = _copyAExpr(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 1eb6799..0c5f1d0 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2106,6 +2106,29 @@ _equalAlterTSConfigurationStmt(const AlterTSConfigurationStmt *a,
 }
 
 static bool
+_equalCreatePublicationStmt(const CreatePublicationStmt *a,
+							const CreatePublicationStmt *b)
+{
+	COMPARE_STRING_FIELD(pubname);
+	COMPARE_NODE_FIELD(options);
+
+	return true;
+}
+
+static bool
+_equalAlterPublicationStmt(const AlterPublicationStmt *a,
+						   const AlterPublicationStmt *b)
+{
+	COMPARE_STRING_FIELD(pubname);
+	COMPARE_NODE_FIELD(options);
+	COMPARE_SCALAR_FIELD(isDrop);
+	COMPARE_STRING_FIELD(schema);
+	COMPARE_NODE_FIELD(tables);
+
+	return true;
+}
+
+static bool
 _equalCreatePolicyStmt(const CreatePolicyStmt *a, const CreatePolicyStmt *b)
 {
 	COMPARE_STRING_FIELD(policy_name);
@@ -3249,6 +3272,12 @@ equal(const void *a, const void *b)
 		case T_AlterPolicyStmt:
 			retval = _equalAlterPolicyStmt(a, b);
 			break;
+		case T_CreatePublicationStmt:
+			retval = _equalCreatePublicationStmt(a, b);
+			break;
+		case T_AlterPublicationStmt:
+			retval = _equalAlterPublicationStmt(a, b);
+			break;
 		case T_A_Expr:
 			retval = _equalAExpr(a, b);
 			break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0cae446..b91e75a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -266,6 +266,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		DropOwnedStmt ReassignOwnedStmt
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
 		CreateMatViewStmt RefreshMatViewStmt CreateAmStmt
+		CreatePublicationStmt AlterPublicationStmt
 
 %type <node>	select_no_parens select_with_parens select_clause
 				simple_select values_clause
@@ -372,13 +373,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				create_generic_options alter_generic_options
 				relation_expr_list dostmt_opt_list
 				transform_element_list transform_type_list
+				publication_opt_list publication_opt_items
 
 %type <list>	group_by_list
 %type <node>	group_by_item empty_grouping_set rollup_clause cube_clause
 %type <node>	grouping_sets_clause
 
 %type <list>	opt_fdw_options fdw_options
-%type <defelt>	fdw_option
+%type <defelt>	fdw_option publication_opt_item
 
 %type <range>	OptTempTableName
 %type <into>	into_clause create_as_target create_mv_target
@@ -617,7 +619,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
 	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
-	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROGRAM
+	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROGRAM PUBLICATION
 
 	QUOTE
 
@@ -778,6 +780,7 @@ stmt :
 			| AlterTableStmt
 			| AlterTblSpcStmt
 			| AlterCompositeTypeStmt
+			| AlterPublicationStmt
 			| AlterRoleSetStmt
 			| AlterRoleStmt
 			| AlterTSConfigurationStmt
@@ -807,6 +810,7 @@ stmt :
 			| CreateMatViewStmt
 			| CreateOpClassStmt
 			| CreateOpFamilyStmt
+			| CreatePublicationStmt
 			| AlterOpFamilyStmt
 			| CreatePolicyStmt
 			| CreatePLangStmt
@@ -5643,6 +5647,7 @@ drop_type:	TABLE									{ $$ = OBJECT_TABLE; }
 			| TEXT_P SEARCH DICTIONARY				{ $$ = OBJECT_TSDICTIONARY; }
 			| TEXT_P SEARCH TEMPLATE				{ $$ = OBJECT_TSTEMPLATE; }
 			| TEXT_P SEARCH CONFIGURATION			{ $$ = OBJECT_TSCONFIGURATION; }
+			| PUBLICATION							{ $$ = OBJECT_PUBLICATION; }
 		;
 
 any_name_list:
@@ -8498,6 +8503,113 @@ AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleSpec
 
 /*****************************************************************************
  *
+ * CREATE PUBLICATION name [ WITH options ]
+ *
+ *****************************************************************************/
+
+CreatePublicationStmt:
+			CREATE PUBLICATION name opt_with publication_opt_list
+				{
+					CreatePublicationStmt *n = makeNode(CreatePublicationStmt);
+					n->pubname = $3;
+					n->options = $5;
+					$$ = (Node *)n;
+				}
+		;
+
+publication_opt_list:
+			publication_opt_items					{ $$ = $1; }
+			| /* EMPTY */							{ $$ = NIL; }
+		;
+
+publication_opt_items:
+			publication_opt_item							{ $$ = list_make1($1); }
+			| publication_opt_items publication_opt_item	{ $$ = lappend($1, $2); }
+		;
+
+publication_opt_item:
+			IDENT
+				{
+					/*
+					 * We handle identifiers that aren't parser keywords with
+					 * the following special-case codes, to avoid bloating the
+					 * size of the main parser.
+					 */
+					if (strcmp($1, "replicate_insert") == 0)
+						$$ = makeDefElem("replicate_insert", (Node *)makeInteger(TRUE));
+					else if (strcmp($1, "noreplicate_insert") == 0)
+						$$ = makeDefElem("replicate_insert", (Node *)makeInteger(FALSE));
+					else if (strcmp($1, "replicate_update") == 0)
+						$$ = makeDefElem("replicate_update", (Node *)makeInteger(TRUE));
+					else if (strcmp($1, "noreplicate_update") == 0)
+						$$ = makeDefElem("replicate_update", (Node *)makeInteger(FALSE));
+					else if (strcmp($1, "replicate_delete") == 0)
+						$$ = makeDefElem("replicate_delete", (Node *)makeInteger(TRUE));
+					else if (strcmp($1, "noreplicate_delete") == 0)
+						$$ = makeDefElem("replicate_delete", (Node *)makeInteger(FALSE));
+					else
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("unrecognized publication option \"%s\"", $1),
+									 parser_errposition(@1)));
+				}
+		;
+
+/*****************************************************************************
+ *
+ * ALTER PUBLICATION name [ WITH ] options
+ *
+ * ALTER PUBLICATION name ADD TABLE table [, table2]
+ *
+ * ALTER PUBLICATION name DROP TABLE table [, table2]
+ *
+ *****************************************************************************/
+
+AlterPublicationStmt:
+			ALTER PUBLICATION name opt_with publication_opt_items
+				{
+					AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+					n->pubname = $3;
+					n->options = $5;
+					n->isDrop = FALSE;
+					n->schema = NULL;
+					n->tables = NIL;
+					$$ = (Node *)n;
+				}
+			| ALTER PUBLICATION name ADD_P TABLE relation_expr_list
+				{
+					AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+					n->pubname = $3;
+					n->options = NIL;
+					n->isDrop = FALSE;
+					n->schema = NULL;
+					n->tables = $6;
+					$$ = (Node *)n;
+				}
+			| ALTER PUBLICATION name ADD_P TABLE ALL IN_P SCHEMA name
+				{
+					AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+					n->pubname = $3;
+					n->options = NIL;
+					n->isDrop = FALSE;
+					n->schema = $9;
+					n->tables = NIL;
+					$$ = (Node *)n;
+				}
+			| ALTER PUBLICATION name DROP TABLE relation_expr_list
+				{
+					AlterPublicationStmt *n = makeNode(AlterPublicationStmt);
+					n->pubname = $3;
+					n->options = NIL;
+					n->isDrop = TRUE;
+					n->schema = NULL;
+					n->tables = $6;
+					$$ = (Node *)n;
+				}
+		;
+
+/*****************************************************************************
+ *
  *		QUERY:	Define Rewrite Rule
  *
  *****************************************************************************/
@@ -13899,6 +14011,7 @@ unreserved_keyword:
 			| PROCEDURAL
 			| PROCEDURE
 			| PROGRAM
+			| PUBLICATION
 			| QUOTE
 			| RANGE
 			| READ
diff --git a/src/backend/replication/logical/Makefile b/src/backend/replication/logical/Makefile
index 1d7ca06..3b3e90c 100644
--- a/src/backend/replication/logical/Makefile
+++ b/src/backend/replication/logical/Makefile
@@ -14,7 +14,7 @@ include $(top_builddir)/src/Makefile.global
 
 override CPPFLAGS := -I$(srcdir) $(CPPFLAGS)
 
-OBJS = decode.o logical.o logicalfuncs.o message.o origin.o reorderbuffer.o \
-	snapbuild.o
+OBJS = decode.o logical.o logicalfuncs.o message.o origin.o publication.o \
+	   reorderbuffer.o snapbuild.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/replication/logical/publication.c b/src/backend/replication/logical/publication.c
new file mode 100644
index 0000000..b86611e
--- /dev/null
+++ b/src/backend/replication/logical/publication.c
@@ -0,0 +1,343 @@
+/*-------------------------------------------------------------------------
+ *
+ * publication.c
+ *		publication C api manipulation
+ *
+ * Copyright (c) 2015, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		publication.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "funcapi.h"
+#include "miscadmin.h"
+
+#include "access/genam.h"
+#include "access/hash.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/xact.h"
+
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/namespace.h"
+#include "catalog/objectaddress.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_rel.h"
+
+#include "executor/spi.h"
+
+#include "nodes/makefuncs.h"
+
+#include "replication/publication.h"
+#include "replication/reorderbuffer.h"
+
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/catcache.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+
+/*
+ * Check if an action on a specified relation should be replicated for
+ * the publication list
+ */
+bool
+publication_change_is_replicated(Relation rel,
+								 PublicationChangeType change_type,
+								 List *pubnames)
+{
+	List	   *relpublications = GetRelationPublications(rel);
+	ListCell   *rellc;
+	bool		result = false;
+
+	/* TODO: optimize */
+	foreach (rellc, relpublications)
+	{
+		Oid	relpuboid = lfirst_oid(rellc);
+		ListCell   *namelc;
+
+		foreach (namelc, pubnames)
+		{
+			char	   *pubname = (char *) lfirst(namelc);
+			HeapTuple	tup;
+			Form_pg_publication	pub;
+
+			tup = SearchSysCache1(PUBLICATIONNAME, CStringGetDatum(pubname));
+			pub = (Form_pg_publication) GETSTRUCT(tup);
+
+			if (HeapTupleGetOid(tup) == relpuboid)
+			{
+				switch (change_type)
+				{
+					case PublicationChangeInsert:
+						result = pub->pubreplins;
+						break;
+					case PublicationChangeUpdate:
+						result = pub->pubreplupd;
+						break;
+					case PublicationChangeDelete:
+						result = pub->pubrepldel;
+						break;
+					default:
+						elog(ERROR, "unknown change_type %d", change_type);
+				}
+
+				/*
+				 * We don't need to search more once we found a publication
+				 * that replicates the change type.
+				 */
+				if (result)
+				{
+					list_free(relpublications);
+					return result;
+				}
+			}
+		}
+	}
+
+	list_free(relpublications);
+	return result;
+}
+
+
+/*
+ * Insert new publication / relation mapping.
+ */
+Oid
+publication_add_relation(Oid pubid, Relation targetrel,
+						 bool if_not_exists)
+{
+	Relation	rel;
+	HeapTuple	tup;
+	Datum		values[Natts_pg_publication_rel];
+	bool		nulls[Natts_pg_publication_rel];
+	Oid			relid = RelationGetRelid(targetrel);
+	Oid			prid;
+	Publication *pub = GetPublication(pubid);
+	ObjectAddress	myself,
+					referenced;
+
+	rel = heap_open(PublicationRelRelationId, RowExclusiveLock);
+
+	/* Check for duplicates */
+	if (SearchSysCacheExists2(PUBLICATIONRELMAP, relid, pubid))
+	{
+		heap_close(rel, RowExclusiveLock);
+
+		if (if_not_exists)
+			return InvalidOid;
+
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("relation %s is already member of publication %s",
+						RelationGetRelationName(rel), pub->name)));
+	}
+
+	/* Must be table */
+	if (RelationGetForm(rel)->relkind != RELKIND_RELATION)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("only tables can be added to publication"),
+				 errdetail("%s is not a table",
+						   RelationGetRelationName(rel))));
+
+	/* UNLOGGED and TEMP relations cannot be part of publication. */
+	if (!RelationNeedsWAL(targetrel))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("UNLOGGED and TEMP relations cannot be replicated")));
+
+	/* Must have replica identity index. */
+	if (targetrel->rd_indexvalid == 0)
+		RelationGetIndexList(targetrel);
+	if (!OidIsValid(targetrel->rd_replidindex) &&
+		(pub->replicate_update || pub->replicate_delete))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("table %s cannot be added to publication %s",
+						RelationGetRelationName(targetrel), pub->name),
+				 errdetail("table does not have REPLICA IDENTITY index "
+						   "and given publication is configured to "
+						   "replicate UPDATEs and/or DELETEs"),
+				 errhint("Add a PRIMARY KEY to the table")));
+
+	/* Form a tuple. */
+	memset(values, 0, sizeof(values));
+	memset(nulls, false, sizeof(nulls));
+
+	values[Anum_pg_publication_rel_pubid - 1] =
+		ObjectIdGetDatum(pubid);
+	values[Anum_pg_publication_rel_relid - 1] =
+		ObjectIdGetDatum(RelationGetRelid(targetrel));
+
+	tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+	/* Insert tuple into catalog. */
+	prid = simple_heap_insert(rel, tup);
+	CatalogUpdateIndexes(rel, tup);
+	heap_freetuple(tup);
+
+	/* Add dependency on the publication */
+	ObjectAddressSet(myself, PublicationRelRelationId, prid);
+	ObjectAddressSet(referenced, PublicationRelationId, pubid);
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+	/* Add dependency on the relation */
+	ObjectAddressSet(referenced, RelationRelationId, relid);
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+	/*
+	 * In case the publication replicates updates and deletes we also
+	 * need to record dependency on the replica index.
+	 *
+	 * XXX: this has unpleasant sideeffect that replacing replica index
+	 * (PKey in most cases) means removal of table from publication. We
+	 * could habdle is better if we checked specifically for this during
+	 * execution of the related commands.
+	 */
+	if (pub->replicate_update || pub->replicate_delete)
+	{
+		ObjectAddressSet(referenced, IndexRelationId,
+						 targetrel->rd_replidindex);
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	}
+
+	/* Close the table. */
+	heap_close(rel, RowExclusiveLock);
+
+	return prid;
+}
+
+
+/*
+ * Gets list of publication oids for a relation.
+ */
+List *
+GetRelationPublications(Relation rel)
+{
+	List		   *result;
+	List		   *oldlist;
+	Relation		pubrelsrel;
+	ScanKeyData		scankey;
+	SysScanDesc		scan;
+	HeapTuple		tup;
+	MemoryContext	oldcxt;
+
+	/* Quick exit if we already computed the list. */
+	if (rel->rd_publicationsvalid)
+		return list_copy(rel->rd_publications);
+
+	/* Find all publications associated with the relation. */
+	pubrelsrel = heap_open(PublicationRelRelationId, AccessShareLock);
+
+	ScanKeyInit(&scankey,
+				Anum_pg_publication_rel_relid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(rel)));
+
+	scan = systable_beginscan(pubrelsrel, PublicationRelMapIndexId, true,
+							  NULL, 1, &scankey);
+
+	result = NIL;
+	while (HeapTupleIsValid(tup = systable_getnext(scan)))
+	{
+		Form_pg_publication_rel		pubrel;
+
+		pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
+
+		result = lappend_oid(result, pubrel->pubid);
+	}
+
+	systable_endscan(scan);
+	heap_close(pubrelsrel, NoLock);
+
+	/* Now save a copy of the completed list in the relcache entry. */
+	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	oldlist = rel->rd_publications;
+	rel->rd_publications = list_copy(result);
+	rel->rd_publicationsvalid = true;
+	MemoryContextSwitchTo(oldcxt);
+
+	/* Don't leak the old list, if there is one */
+	list_free(oldlist);
+
+	return result;
+}
+
+Publication *
+GetPublication(Oid pubid)
+{
+	HeapTuple		tup;
+	Publication	   *pub;
+	Form_pg_publication	pubform;
+
+	tup = SearchSysCache1(PUBLICATIONOID, ObjectIdGetDatum(pubid));
+
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for publication %u", pubid);
+
+	pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+	pub = (Publication *) palloc(sizeof(Publication));
+	pub->oid = pubid;
+	pub->name = NameStr(pubform->pubname);
+	pub->replicate_insert = pubform->pubreplins;
+	pub->replicate_update = pubform->pubreplupd;
+	pub->replicate_delete = pubform->pubrepldel;
+
+	ReleaseSysCache(tup);
+
+	return pub;
+}
+
+
+/*
+ * Get Publication using name.
+ */
+Publication *
+GetPublicationByName(const char *pubname, bool missing_ok)
+{
+	Oid			oid;
+
+	oid = GetSysCacheOid1(PUBLICATIONNAME, CStringGetDatum(pubname));
+	if (!OidIsValid(oid))
+	{
+		if (missing_ok)
+			return NULL;
+
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("publication \"%s\" does not exist", pubname)));
+	}
+
+	return GetPublication(oid);
+}
+
+/*
+ * get_publication_oid - given a publication name, look up the OID
+ *
+ * If missing_ok is false, throw an error if name not found.  If true, just
+ * return InvalidOid.
+ */
+Oid
+get_publication_oid(const char *pubname, bool missing_ok)
+{
+	Oid			oid;
+
+	oid = GetSysCacheOid1(PUBLICATIONNAME, CStringGetDatum(pubname));
+	if (!OidIsValid(oid) && !missing_ok)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("publication \"%s\" does not exist", pubname)));
+	return oid;
+}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index ac50c2a..886d2ff 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -44,6 +44,7 @@
 #include "commands/portalcmds.h"
 #include "commands/prepare.h"
 #include "commands/proclang.h"
+#include "commands/replicationcmds.h"
 #include "commands/schemacmds.h"
 #include "commands/seclabel.h"
 #include "commands/sequence.h"
@@ -210,6 +211,8 @@ check_xact_readonly(Node *parsetree)
 		case T_CreateForeignTableStmt:
 		case T_ImportForeignSchemaStmt:
 		case T_SecLabelStmt:
+		case T_CreatePublicationStmt:
+		case T_AlterPublicationStmt:
 			PreventCommandIfReadOnly(CreateCommandTag(parsetree));
 			PreventCommandIfParallelMode(CreateCommandTag(parsetree));
 			break;
@@ -1544,6 +1547,19 @@ ProcessUtilitySlow(Node *parsetree,
 				address = CreateAccessMethod((CreateAmStmt *) parsetree);
 				break;
 
+			case T_CreatePublicationStmt:
+				address = CreatePublication((CreatePublicationStmt *) parsetree);
+				break;
+
+			case T_AlterPublicationStmt:
+				AlterPublication((AlterPublicationStmt *) parsetree);
+				/*
+				 * AlterPublication calls EventTriggerCollectSimpleCommand
+				 * directly
+				 */
+				commandCollected = true;
+				break;
+
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(parsetree));
@@ -1902,6 +1918,9 @@ AlterObjectTypeCommandTag(ObjectType objtype)
 		case OBJECT_MATVIEW:
 			tag = "ALTER MATERIALIZED VIEW";
 			break;
+		case OBJECT_PUBLICATION:
+			tag = "PUBLICATION";
+			break;
 		default:
 			tag = "???";
 			break;
@@ -2187,6 +2206,9 @@ CreateCommandTag(Node *parsetree)
 				case OBJECT_ACCESS_METHOD:
 					tag = "DROP ACCESS METHOD";
 					break;
+				case OBJECT_PUBLICATION:
+					tag = "DROP PUBLICATION";
+					break;
 				default:
 					tag = "???";
 			}
@@ -2557,6 +2579,14 @@ CreateCommandTag(Node *parsetree)
 			tag = "CREATE ACCESS METHOD";
 			break;
 
+		case T_CreatePublicationStmt:
+			tag = "CREATE PUBLICATION";
+			break;
+
+		case T_AlterPublicationStmt:
+			tag = "ALTER PUBLICATION";
+			break;
+
 		case T_PrepareStmt:
 			tag = "PREPARE";
 			break;
@@ -3122,6 +3152,14 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_CreatePublicationStmt:
+			lev = LOGSTMT_DDL;
+			break;
+
+		case T_AlterPublicationStmt:
+			lev = LOGSTMT_DDL;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 8d2ad01..3f7027f 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -2052,6 +2052,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_rsdesc->rscxt);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
+	if (relation->rd_publications)
+		list_free(relation->rd_publications);
 	pfree(relation);
 }
 
@@ -5053,6 +5055,13 @@ load_relcache_init_file(bool shared)
 		rel->rd_fdwroutine = NULL;
 
 		/*
+		 * Publications are not needed by most backends so we load them on
+		 * demand.
+		 */
+		rel->rd_publicationsvalid = false;
+		rel->rd_publications = NIL;
+
+		/*
 		 * Reset transient-state fields in the relcache entry
 		 */
 		rel->rd_smgr = NULL;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 65ffe84..d575b51 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -50,6 +50,8 @@
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_range.h"
+#include "catalog/pg_publication.h"
+#include "catalog/pg_publication_rel.h"
 #include "catalog/pg_rewrite.h"
 #include "catalog/pg_seclabel.h"
 #include "catalog/pg_shdepend.h"
@@ -645,6 +647,50 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		16
 	},
+	{PublicationRelationId,			/* PUBLICATIONOID */
+		PublicationObjectIndexId,
+		1,
+		{
+			ObjectIdAttributeNumber,
+			0,
+			0,
+			0
+		},
+		8
+	},
+	{PublicationRelationId,			/* PUBLICATIONNAME */
+		PublicationNameIndexId,
+		1,
+		{
+			Anum_pg_publication_pubname,
+			0,
+			0,
+			0
+		},
+		8
+	},
+	{PublicationRelRelationId,		/* PUBLICATIONREL */
+		PublicationRelObjectIndexId,
+		1,
+		{
+			ObjectIdAttributeNumber,
+			0,
+			0,
+			0
+		},
+		64
+	},
+	{PublicationRelRelationId,		/* PUBLICATIONRELMAP */
+		PublicationRelMapIndexId,
+		2,
+		{
+			Anum_pg_publication_rel_relid,
+			Anum_pg_publication_rel_pubid,
+			0,
+			0
+		},
+		64
+	},
 	{RewriteRelationId,			/* RULERELNAME */
 		RewriteRelRulenameIndexId,
 		2,
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 3f2cebf..a379c19 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -477,17 +477,30 @@ exec_command(const char *cmd,
 				success = listTables(&cmd[1], pattern, show_verbose, show_system);
 				break;
 			case 'r':
-				if (cmd[2] == 'd' && cmd[3] == 's')
+				switch (cmd[2])
 				{
-					char	   *pattern2 = NULL;
+					case 'd':
+					{
+						if (cmd[3] == 's')
+						{
+							char	   *pattern2 = NULL;
 
-					if (pattern)
-						pattern2 = psql_scan_slash_option(scan_state,
+							if (pattern)
+								pattern2 = psql_scan_slash_option(scan_state,
 													  OT_NORMAL, NULL, true);
-					success = listDbRoleSettings(pattern, pattern2);
+							success = listDbRoleSettings(pattern, pattern2);
+						}
+						else
+							success = PSQL_CMD_UNKNOWN;
+						break;
+					}
+					case 'p':
+						success = describePublications(pattern, show_verbose);
+						break;
+					default:
+						status = PSQL_CMD_UNKNOWN;
+						break;
 				}
-				else
-					success = PSQL_CMD_UNKNOWN;
 				break;
 			case 'u':
 				success = describeRoles(pattern, show_verbose, show_system);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 27be102..573d980 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2290,6 +2290,38 @@ describeOneTableDetails(const char *schemaname,
 			}
 			PQclear(result);
 		}
+
+		/* print any publications */
+		/* TODO: bump version */
+		if (pset.sversion >= 90600)
+		{
+			printfPQExpBuffer(&buf,
+							  "SELECT pub.pubname\n"
+							  "FROM pg_catalog.pg_publication pub,\n"
+							  "     pg_publication_rel pr\n"
+							  "WHERE pr.relid = '%s' AND pr.pubid = pub.oid\n"
+							  "ORDER BY 1;",
+							  oid);
+
+			result = PSQLexec(buf.data);
+			if (!result)
+				goto error_return;
+			else
+				tuples = PQntuples(result);
+
+			if (tuples > 0)
+				printTableAddFooter(&cont, _("Publications:"));
+
+			/* Might be an empty set - that's ok */
+			for (i = 0; i < tuples; i++)
+			{
+				printfPQExpBuffer(&buf, "    \"%s\"",
+								  PQgetvalue(result, i, 0));
+
+				printTableAddFooter(&cont, buf.data);
+			}
+			PQclear(result);
+		}
 	}
 
 	if (view_def)
@@ -4688,6 +4720,70 @@ listOneExtensionContents(const char *extname, const char *oid)
 	return true;
 }
 
+/* \drp
+ * Describes publications.
+ *
+ * Takes an optional regexp to select particular publications
+ */
+bool
+describePublications(const char *pattern, bool verbose)
+{
+	PQExpBufferData buf;
+	PGresult   *res;
+	printQueryOpt myopt = pset.popt;
+	static const bool translate_columns[] = {false, true, false, false, false};
+
+	/* TODO bump */
+	if (pset.sversion < 90600)
+	{
+		psql_error("The server (version %d.%d) does not support publications.\n",
+				   pset.sversion / 10000, (pset.sversion / 100) % 100);
+		return true;
+	}
+
+	initPQExpBuffer(&buf);
+
+	printfPQExpBuffer(&buf,
+					  "SELECT pubname AS \"%s\",\n"
+					  "  pubreplins AS \"%s\",\n"
+					  "  pubreplupd AS \"%s\",\n"
+					  "  pubrepldel AS \"%s\"\n",
+					  gettext_noop("Name"),
+					  gettext_noop("Inserts"),
+					  gettext_noop("Updates"),
+					  gettext_noop("Deletes"));
+
+	/* TODO Show owner and ACL */
+	if (verbose)
+	{
+	}
+
+	appendPQExpBufferStr(&buf,
+						 "\nFROM pg_catalog.pg_publication\n");
+
+	processSQLNamePattern(pset.db, &buf, pattern, false, false,
+						  NULL, "pubname", NULL,
+						  NULL);
+
+	appendPQExpBufferStr(&buf, "ORDER BY 1;");
+
+	res = PSQLexec(buf.data);
+	termPQExpBuffer(&buf);
+	if (!res)
+		return false;
+
+	myopt.nullPrint = NULL;
+	myopt.title = _("List of publications");
+	myopt.translate_header = true;
+	myopt.translate_columns = translate_columns;
+	myopt.n_translate_columns = lengthof(translate_columns);
+
+	printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+
+	PQclear(res);
+	return true;
+}
+
 /*
  * printACLColumn
  *
diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h
index 20a6508..c4457da 100644
--- a/src/bin/psql/describe.h
+++ b/src/bin/psql/describe.h
@@ -102,4 +102,7 @@ extern bool listExtensionContents(const char *pattern);
 /* \dy */
 extern bool listEventTriggers(const char *pattern, bool verbose);
 
+/* \drp */
+bool describePublications(const char *pattern, bool verbose);
+
 #endif   /* DESCRIBE_H */
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index efc8454..5cc5bce 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -241,6 +241,7 @@ slashUsage(unsigned short int pager)
 	fprintf(output, _("  \\dO[S+] [PATTERN]      list collations\n"));
 	fprintf(output, _("  \\dp     [PATTERN]      list table, view, and sequence access privileges\n"));
 	fprintf(output, _("  \\drds [PATRN1 [PATRN2]] list per-database role settings\n"));
+	fprintf(output, _("  \\drp[+] [PATTERN]      list replication publications\n"));
 	fprintf(output, _("  \\ds[S+] [PATTERN]      list sequences\n"));
 	fprintf(output, _("  \\dt[S+] [PATTERN]      list tables\n"));
 	fprintf(output, _("  \\dT[S+] [PATTERN]      list data types\n"));
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..01fd16a 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -161,6 +161,8 @@ typedef enum ObjectClass
 	OCLASS_EXTENSION,			/* pg_extension */
 	OCLASS_EVENT_TRIGGER,		/* pg_event_trigger */
 	OCLASS_POLICY,				/* pg_policy */
+	OCLASS_PUBLICATION,			/* pg_publication */
+	OCLASS_PUBLICATION_REL,		/* pg_publication_rel */
 	OCLASS_TRANSFORM			/* pg_transform */
 } ObjectClass;
 
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..ae46ed6 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,18 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
 DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
 #define ReplicationOriginNameIndex 6002
 
+DECLARE_UNIQUE_INDEX(pg_publication_oid_index, 6110, on pg_publication using btree(oid oid_ops));
+#define PublicationObjectIndexId 6110
+
+DECLARE_UNIQUE_INDEX(pg_publication_pubname_index, 6111, on pg_publication using btree(pubname name_ops));
+#define PublicationNameIndexId 6111
+
+DECLARE_UNIQUE_INDEX(pg_publication_rel_object_index, 6112, on pg_publication_rel using btree(oid oid_ops));
+#define PublicationRelObjectIndexId 6112
+
+DECLARE_UNIQUE_INDEX(pg_publication_rel_map_index, 6113, on pg_publication_rel using btree(relid oid_ops, pubid oid_ops));
+#define PublicationRelMapIndexId 6113
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
new file mode 100644
index 0000000..6b5263a
--- /dev/null
+++ b/src/include/catalog/pg_publication.h
@@ -0,0 +1,64 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication.h
+ *	  definition of the relation sets relation (pg_publication)
+  *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication.h
+ *
+ * NOTES
+ *	  the genbki.pl script reads this file and generates .bki
+ *	  information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_H
+#define PG_PUBLICATION_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *		pg_publication definition.  cpp turns this into
+ *		typedef struct FormData_pg_publication
+ *
+ * ----------------
+ */
+#define PublicationRelationId			6104
+#define PublicationRelation_Rowtype_Id	6105
+
+CATALOG(pg_publication,6104) BKI_ROWTYPE_OID(6105)
+{
+	NameData	pubname;			/* name of the publication */
+
+	/* true if inserts are replicated */
+	bool		pubreplins;
+
+	/* true if inserts are replicated */
+	bool		pubreplupd;
+
+	/* true if inserts are replicated */
+	bool		pubrepldel;
+
+} FormData_pg_publication;
+
+/* ----------------
+ *		Form_pg_publication corresponds to a pointer to a tuple with
+ *		the format of pg_publication relation.
+ * ----------------
+ */
+typedef FormData_pg_publication *Form_pg_publication;
+
+/* ----------------
+ *		compiler constants for pg_publication
+ * ----------------
+ */
+
+#define Natts_pg_publication				4
+#define Anum_pg_publication_pubname			1
+#define Anum_pg_publication_pubreplins		2
+#define Anum_pg_publication_pubreplupd		3
+#define Anum_pg_publication_pubrepldel		4
+
+#endif   /* PG_PUBLICATION_H */
diff --git a/src/include/catalog/pg_publication_rel.h b/src/include/catalog/pg_publication_rel.h
new file mode 100644
index 0000000..976c1a9
--- /dev/null
+++ b/src/include/catalog/pg_publication_rel.h
@@ -0,0 +1,53 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_publication_rel.h
+ *	  definition of the publication to relation map (pg_publication_rel)
+  *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_publication_rel.h
+ *
+ * NOTES
+ *	  the genbki.pl script reads this file and generates .bki
+ *	  information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PUBLICATION_REL_H
+#define PG_PUBLICATION_REL_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *		pg_publication_rel definition.  cpp turns this into
+ *		typedef struct FormData_pg_publication_rel
+ *
+ * ----------------
+ */
+#define PublicationRelRelationId				6106
+#define PublicationRelRelation_Rowtype_Id	6107
+
+CATALOG(pg_publication_rel,6106) BKI_ROWTYPE_OID(6107)
+{
+	Oid		pubid;				/* Oid of the publication */
+	Oid		relid;				/* Oid of the relation */
+} FormData_pg_publication_rel;
+
+/* ----------------
+ *		Form_pg_publication_rel corresponds to a pointer to a tuple with
+ *		the format of pg_publication_rel relation.
+ * ----------------
+ */
+typedef FormData_pg_publication_rel *Form_pg_publication_rel;
+
+/* ----------------
+ *		compiler constants for pg_publication_rel
+ * ----------------
+ */
+
+#define Natts_pg_publication_rel				2
+#define Anum_pg_publication_rel_pubid			1
+#define Anum_pg_publication_rel_relid			2
+
+#endif   /* PG_PUBLICATION_REL_H */
diff --git a/src/include/commands/replicationcmds.h b/src/include/commands/replicationcmds.h
new file mode 100644
index 0000000..717485f
--- /dev/null
+++ b/src/include/commands/replicationcmds.h
@@ -0,0 +1,26 @@
+/*-------------------------------------------------------------------------
+ *
+ * replicationcmds.h
+ *	  prototypes for publicationcmds.c.
+ *
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/commands/replicationcmds.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef REPLICATIONCMDS_H
+#define REPLICATIONCMDS_H
+
+#include "catalog/objectaddress.h"
+#include "nodes/parsenodes.h"
+
+extern ObjectAddress CreatePublication(CreatePublicationStmt *stmt);
+extern void AlterPublication(AlterPublicationStmt *stmt);
+extern void DropPublicationById(Oid pubid);
+extern void RemovePublicationRelById(Oid prid);
+
+#endif   /* REPLICATIONCMDS_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 6b850e4..3cce3d9 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -405,6 +405,8 @@ typedef enum NodeTag
 	T_AlterPolicyStmt,
 	T_CreateTransformStmt,
 	T_CreateAmStmt,
+	T_CreatePublicationStmt,
+	T_AlterPublicationStmt,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 1481fff..c10e6e7 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1408,6 +1408,8 @@ typedef enum ObjectType
 	OBJECT_OPERATOR,
 	OBJECT_OPFAMILY,
 	OBJECT_POLICY,
+	OBJECT_PUBLICATION,
+	OBJECT_PUBLICATION_REL,
 	OBJECT_ROLE,
 	OBJECT_RULE,
 	OBJECT_SCHEMA,
@@ -3101,4 +3103,26 @@ typedef struct AlterTSConfigurationStmt
 	bool		missing_ok;		/* for DROP - skip error if missing? */
 } AlterTSConfigurationStmt;
 
+
+typedef struct CreatePublicationStmt
+{
+	NodeTag		type;
+	char	   *pubname;		/* Name of of the publication */
+	List	   *options;		/* List of DefElem nodes */
+} CreatePublicationStmt;
+
+typedef struct AlterPublicationStmt
+{
+	NodeTag		type;
+	char	   *pubname;		/* Name of of the publication */
+
+	/* parameters used for ALTER PUBLICATION ... WITH */
+	List	   *options;		/* List of DefElem nodes */
+
+	/* parameters used for ALTER PUBLICATION ... ADD/DROP TABLE */
+	bool		isDrop;			/* Are tables to be added or dropped? */
+	char	   *schema;			/* ALL IN SCHEMA ... */
+	List	   *tables;			/* List of tables to add/drop */
+} AlterPublicationStmt;
+
 #endif   /* PARSENODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 17ffef5..9430ff0 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -304,6 +304,7 @@ PG_KEYWORD("privileges", PRIVILEGES, UNRESERVED_KEYWORD)
 PG_KEYWORD("procedural", PROCEDURAL, UNRESERVED_KEYWORD)
 PG_KEYWORD("procedure", PROCEDURE, UNRESERVED_KEYWORD)
 PG_KEYWORD("program", PROGRAM, UNRESERVED_KEYWORD)
+PG_KEYWORD("publication", PUBLICATION, UNRESERVED_KEYWORD)
 PG_KEYWORD("quote", QUOTE, UNRESERVED_KEYWORD)
 PG_KEYWORD("range", RANGE, UNRESERVED_KEYWORD)
 PG_KEYWORD("read", READ, UNRESERVED_KEYWORD)
diff --git a/src/include/replication/publication.h b/src/include/replication/publication.h
new file mode 100644
index 0000000..08245ee
--- /dev/null
+++ b/src/include/replication/publication.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * publication.h
+ *		publication support structures and interfaces
+ *
+ * Copyright (c) 2015, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		publication.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PUBLICATION_H
+#define PUBLICATION_H
+
+#include "postgres.h"
+
+
+typedef enum PublicationChangeType
+{
+	PublicationChangeInsert,
+	PublicationChangeUpdate,
+	PublicationChangeDelete
+} PublicationChangeType;
+
+typedef struct Publication
+{
+	Oid		oid;
+	char   *name;
+	bool	replicate_insert;
+	bool	replicate_update;
+	bool	replicate_delete;
+} Publication;
+
+extern Publication *GetPublication(Oid pubid);
+extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
+extern List *GetRelationPublications(Relation rel);
+
+extern bool publication_change_is_replicated(Relation rel,
+								 PublicationChangeType change_type,
+								 List *inpublications);
+extern Oid publication_add_relation(Oid pubid, Relation targetrel,
+							 bool if_not_exists);
+
+extern Oid get_publication_oid(const char *pubname, bool missing_ok);
+
+#endif		/* PUBLICATION_H */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index ed14442..ca2283a 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -159,6 +159,10 @@ typedef struct RelationData
 	/* use "struct" here to avoid needing to include fdwapi.h: */
 	struct FdwRoutine *rd_fdwroutine;	/* cached function pointers, or NULL */
 
+	/* Publication support */
+	bool		rd_publicationsvalid;		/* state of the rd_publications */
+	List	   *rd_publications;
+
 	/*
 	 * Hack for CLUSTER, rewriting ALTER TABLE, etc: when writing a new
 	 * version of a table, we need to make any toast pointers inserted into it
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 256615b..632fcbc 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,10 @@ enum SysCacheIdentifier
 	RELOID,
 	REPLORIGIDENT,
 	REPLORIGNAME,
+	PUBLICATIONOID,
+	PUBLICATIONNAME,
+	PUBLICATIONREL,
+	PUBLICATIONRELMAP,
 	RULERELNAME,
 	STATRELATTINH,
 	TABLESPACEOID,
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
new file mode 100644
index 0000000..917408e
--- /dev/null
+++ b/src/test/regress/expected/publication.out
@@ -0,0 +1,76 @@
+--
+-- PUBLICATION
+--
+CREATE PUBLICATION testpub_default;
+CREATE PUBLICATION testpib_ins_trunct WITH noreplicate_delete noreplicate_update;
+ALTER PUBLICATION testpub_default WITH noreplicate_insert noreplicate_delete;
+\drp
+               List of publications
+        Name        | Inserts | Updates | Deletes 
+--------------------+---------+---------+---------
+ testpib_ins_trunct | t       | f       | f
+ testpub_default    | f       | t       | f
+(2 rows)
+
+ALTER PUBLICATION testpub_default WITH replicate_insert replicate_delete;
+\drp
+               List of publications
+        Name        | Inserts | Updates | Deletes 
+--------------------+---------+---------+---------
+ testpib_ins_trunct | t       | f       | f
+ testpub_default    | t       | t       | t
+(2 rows)
+
+--- adding tables
+CREATE TABLE testpub_tbl1 (id serial primary key, data text);
+CREATE TABLE testpub_nopk (foo int, bar int);
+ALTER PUBLICATION testpub_default ADD TABLE testpub_tbl1;
+ALTER PUBLICATION testpub_default ADD TABLE testpub_nopk;
+ERROR:  table testpub_nopk cannot be added to publication testpub_default
+DETAIL:  table does not have REPLICA IDENTITY index and given publication is configured to replicate UPDATEs and/or DELETEs
+HINT:  Add a PRIMARY KEY to the table
+ALTER PUBLICATION testpib_ins_trunct ADD TABLE testpub_nopk, testpub_tbl1;
+\d+ testpub_nopk
+                     Table "public.testpub_nopk"
+ Column |  Type   | Modifiers | Storage | Stats target | Description 
+--------+---------+-----------+---------+--------------+-------------
+ foo    | integer |           | plain   |              | 
+ bar    | integer |           | plain   |              | 
+Publications:
+    "testpib_ins_trunct"
+
+\d+ testpub_tbl1
+                                             Table "public.testpub_tbl1"
+ Column |  Type   |                         Modifiers                         | Storage  | Stats target | Description 
+--------+---------+-----------------------------------------------------------+----------+--------------+-------------
+ id     | integer | not null default nextval('testpub_tbl1_id_seq'::regclass) | plain    |              | 
+ data   | text    |                                                           | extended |              | 
+Indexes:
+    "testpub_tbl1_pkey" PRIMARY KEY, btree (id)
+Publications:
+    "testpib_ins_trunct"
+    "testpub_default"
+
+ALTER PUBLICATION testpub_default DROP TABLE testpub_tbl1, testpub_nopk;
+ERROR:  relation "testpub_nopk" is not part of the publication
+ALTER PUBLICATION testpub_default DROP TABLE testpub_tbl1;
+\d+ testpub_tbl1
+                                             Table "public.testpub_tbl1"
+ Column |  Type   |                         Modifiers                         | Storage  | Stats target | Description 
+--------+---------+-----------------------------------------------------------+----------+--------------+-------------
+ id     | integer | not null default nextval('testpub_tbl1_id_seq'::regclass) | plain    |              | 
+ data   | text    |                                                           | extended |              | 
+Indexes:
+    "testpub_tbl1_pkey" PRIMARY KEY, btree (id)
+Publications:
+    "testpib_ins_trunct"
+
+DROP TABLE testpub_tbl1;
+ERROR:  cannot drop table testpub_tbl1 because other objects depend on it
+DETAIL:  publication table testpub_tbl1 in publication testpib_ins_trunct depends on table testpub_tbl1
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE testpub_tbl1 CASCADE;
+NOTICE:  drop cascades to publication table testpub_tbl1 in publication testpib_ins_trunct
+DROP PUBLICATION testpub_default;
+DROP PUBLICATION testpib_ins_trunct;
+DROP TABLE testpub_nopk;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 1c087a3..5ab04ae 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -121,6 +121,8 @@ pg_opfamily|t
 pg_pltemplate|t
 pg_policy|t
 pg_proc|t
+pg_publication|t
+pg_publication_rel|t
 pg_range|t
 pg_replication_origin|t
 pg_rewrite|t
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 4ebad04..871a93d 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -84,7 +84,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
 # ----------
 # Another group of parallel tests
 # ----------
-test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator
+test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator publication
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
new file mode 100644
index 0000000..9f85081
--- /dev/null
+++ b/src/test/regress/sql/publication.sql
@@ -0,0 +1,40 @@
+--
+-- PUBLICATION
+--
+
+CREATE PUBLICATION testpub_default;
+
+CREATE PUBLICATION testpib_ins_trunct WITH noreplicate_delete noreplicate_update;
+
+ALTER PUBLICATION testpub_default WITH noreplicate_insert noreplicate_delete;
+
+\drp
+
+ALTER PUBLICATION testpub_default WITH replicate_insert replicate_delete;
+
+\drp
+
+--- adding tables
+CREATE TABLE testpub_tbl1 (id serial primary key, data text);
+CREATE TABLE testpub_nopk (foo int, bar int);
+
+ALTER PUBLICATION testpub_default ADD TABLE testpub_tbl1;
+ALTER PUBLICATION testpub_default ADD TABLE testpub_nopk;
+
+ALTER PUBLICATION testpib_ins_trunct ADD TABLE testpub_nopk, testpub_tbl1;
+
+\d+ testpub_nopk
+\d+ testpub_tbl1
+
+ALTER PUBLICATION testpub_default DROP TABLE testpub_tbl1, testpub_nopk;
+ALTER PUBLICATION testpub_default DROP TABLE testpub_tbl1;
+
+\d+ testpub_tbl1
+
+DROP TABLE testpub_tbl1;
+DROP TABLE testpub_tbl1 CASCADE;
+
+DROP PUBLICATION testpub_default;
+DROP PUBLICATION testpib_ins_trunct;
+
+DROP TABLE testpub_nopk;
-- 
2.7.4

0002-Add-SUBSCRIPTION-catalog-and-DDL.patchapplication/x-patch; name=0002-Add-SUBSCRIPTION-catalog-and-DDL.patchDownload
From ef8ed0bed60f2cf5123a2cdc1335d0d02891d82c Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Wed, 13 Jul 2016 18:12:05 +0200
Subject: [PATCH 2/6] Add SUBSCRIPTION catalog and DDL

---
 doc/src/sgml/catalogs.sgml                     |  90 +++++++
 doc/src/sgml/ref/allfiles.sgml                 |   3 +
 doc/src/sgml/ref/alter_subscription.sgml       | 135 ++++++++++
 doc/src/sgml/ref/create_subscription.sgml      | 159 ++++++++++++
 doc/src/sgml/ref/drop_subscription.sgml        | 101 ++++++++
 src/backend/catalog/Makefile                   |   2 +-
 src/backend/catalog/catalog.c                  |   8 +-
 src/backend/catalog/dependency.c               |   9 +
 src/backend/catalog/objectaddress.c            |  59 +++++
 src/backend/commands/Makefile                  |   5 +-
 src/backend/commands/event_trigger.c           |   3 +
 src/backend/commands/subscriptioncmds.c        | 331 +++++++++++++++++++++++++
 src/backend/nodes/copyfuncs.c                  |  28 +++
 src/backend/nodes/equalfuncs.c                 |  26 ++
 src/backend/parser/gram.y                      | 127 +++++++++-
 src/backend/replication/logical/Makefile       |   2 +-
 src/backend/replication/logical/subscription.c | 146 +++++++++++
 src/backend/tcop/utility.c                     |  32 +++
 src/backend/utils/cache/relcache.c             |   6 +-
 src/backend/utils/cache/syscache.c             |  23 ++
 src/bin/psql/command.c                         |   3 +
 src/bin/psql/describe.c                        |  66 +++++
 src/bin/psql/describe.h                        |   3 +
 src/bin/psql/help.c                            |   1 +
 src/include/catalog/dependency.h               |   1 +
 src/include/catalog/indexing.h                 |   6 +
 src/include/catalog/pg_subscription.h          |  52 ++++
 src/include/commands/replicationcmds.h         |   6 +-
 src/include/nodes/nodes.h                      |   2 +
 src/include/nodes/parsenodes.h                 |  15 ++
 src/include/parser/kwlist.h                    |   1 +
 src/include/replication/subscription.h         |  33 +++
 src/include/utils/rel.h                        |   1 +
 src/include/utils/syscache.h                   |   2 +
 src/test/regress/expected/sanity_check.out     |   1 +
 35 files changed, 1478 insertions(+), 10 deletions(-)
 create mode 100644 doc/src/sgml/ref/alter_subscription.sgml
 create mode 100644 doc/src/sgml/ref/create_subscription.sgml
 create mode 100644 doc/src/sgml/ref/drop_subscription.sgml
 create mode 100644 src/backend/commands/subscriptioncmds.c
 create mode 100644 src/backend/replication/logical/subscription.c
 create mode 100644 src/include/catalog/pg_subscription.h
 create mode 100644 src/include/replication/subscription.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 6d505ae..84211c1 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -286,6 +286,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-subscription"><structname>pg_subscription</structname></link></entry>
+      <entry>logical replication subscriptions</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-tablespace"><structname>pg_tablespace</structname></link></entry>
       <entry>tablespaces within this database cluster</entry>
      </row>
@@ -6037,6 +6042,91 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-subscription">
+  <title><structname>pg_subscription</structname></title>
+
+  <indexterm zone="catalog-pg-subscription">
+   <primary>pg_subscription</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_subscription</structname> catalog contains
+   all existing logical replication subscriptions.
+  </para>
+
+  <para>
+   Unlike most system catalogs, <structname>pg_subscription</structname>
+   is shared across all databases of a cluster: there is only one
+   copy of <structname>pg_subscription</structname> per cluster, not
+   one per database.
+  </para>
+
+  <table>
+
+   <title><structname>pg_subscription</structname> Columns</title>
+
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry><structfield>oid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry></entry>
+      <entry>Row identifier (hidden attribute; must be explicitly selected)</entry>
+     </row>
+
+     <row>
+      <entry><structfield>subname</structfield></entry>
+      <entry><type>Name</type></entry>
+      <entry></entry>
+      <entry>A unique, database-wide identifier for the replication
+      subscription.</entry>
+     </row>
+
+     <row>
+      <entry><structfield>subenabled</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>If true, the subscription is enabled and should be replicating.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>subconninfo</structfield></entry>
+      <entry><type>text</type></entry>
+      <entry></entry>
+      <entry>Connection string to the upstream database.
+      </entry>
+     </row>
+
+     <row>
+      <entry><structfield>subslotname</structfield></entry>
+      <entry><type>text</type></entry>
+      <entry></entry>
+      <entry>Name of the replication slot in the upstream database. Also used
+       for local replication origin name.</entry>
+     </row>
+
+     <row>
+      <entry><structfield>subpublications</structfield></entry>
+      <entry><type>text[]</type></entry>
+      <entry></entry>
+      <entry>Array of subscribed publication names. For more on publications
+       see <xref linkend="publications">.
+      </entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
 
  <sect1 id="catalog-pg-tablespace">
   <title><structname>pg_tablespace</structname></title>
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index 371a7b7..0d09f81 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -32,6 +32,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY alterSchema        SYSTEM "alter_schema.sgml">
 <!ENTITY alterServer        SYSTEM "alter_server.sgml">
 <!ENTITY alterSequence      SYSTEM "alter_sequence.sgml">
+<!ENTITY alterSubscription  SYSTEM "alter_subscription.sgml">
 <!ENTITY alterSystem        SYSTEM "alter_system.sgml">
 <!ENTITY alterTable         SYSTEM "alter_table.sgml">
 <!ENTITY alterTableSpace    SYSTEM "alter_tablespace.sgml">
@@ -79,6 +80,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY createSchema       SYSTEM "create_schema.sgml">
 <!ENTITY createSequence     SYSTEM "create_sequence.sgml">
 <!ENTITY createServer       SYSTEM "create_server.sgml">
+<!ENTITY createSubscription SYSTEM "create_subscription.sgml">
 <!ENTITY createTable        SYSTEM "create_table.sgml">
 <!ENTITY createTableAs      SYSTEM "create_table_as.sgml">
 <!ENTITY createTableSpace   SYSTEM "create_tablespace.sgml">
@@ -124,6 +126,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY dropSchema         SYSTEM "drop_schema.sgml">
 <!ENTITY dropSequence       SYSTEM "drop_sequence.sgml">
 <!ENTITY dropServer         SYSTEM "drop_server.sgml">
+<!ENTITY dropSubscription   SYSTEM "drop_subscription.sgml">
 <!ENTITY dropTable          SYSTEM "drop_table.sgml">
 <!ENTITY dropTableSpace     SYSTEM "drop_tablespace.sgml">
 <!ENTITY dropTransform      SYSTEM "drop_transform.sgml">
diff --git a/doc/src/sgml/ref/alter_subscription.sgml b/doc/src/sgml/ref/alter_subscription.sgml
new file mode 100644
index 0000000..467e71f
--- /dev/null
+++ b/doc/src/sgml/ref/alter_subscription.sgml
@@ -0,0 +1,135 @@
+<!--
+doc/src/sgml/ref/alter_subscription.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-ALTERSUBSCRIPTION">
+ <indexterm zone="sql-altersubscription">
+  <primary>ALTER SUBSCRIPTION</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>ALTER SUBSCRIPTION</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>ALTER SUBSCRIPTION</refname>
+  <refpurpose>change the definition of a subscription</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+ALTER SUBSCRIPTION <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replaceable class="PARAMETER">option</replaceable> [ ... ] ]
+
+<phrase>where <replaceable class="PARAMETER">option</replaceable> can be:</phrase>
+
+    CONNECTION 'conninfo'
+    | PUBLICATION publication_name [, ...]
+
+ALTER SUBSCRIPTION <replaceable class="PARAMETER">name</replaceable> ENABLE
+ALTER SUBSCRIPTION <replaceable class="PARAMETER">name</replaceable> DISABLE
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>ALTER SUBSCRIPTION</command> can change most of the subscription
+   attributes that can be specified in
+   <xref linkend="sql-createsubscription">.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name of a subscription whose attributes are to be altered.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term>CONNECTION '<replaceable class="parameter">conninfo</replaceable>'</term>
+    <term>PUBLICATION <replaceable class="parameter">publication_name</replaceable></term>
+    <listitem>
+     <para>
+      These clauses alter attributes originally set by
+      <xref linkend="SQL-CREATESUBSCRIPTION">. For more information, see the
+      <command>CREATE SUBSCRIPTION</command> reference page.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term>ENABLE</term>
+    <listitem>
+     <para>
+      Enables the previously disabled subscription, starting the logical
+      replication worker at the end of transaction.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term>DISABLE</term>
+    <listitem>
+     <para>
+      Disables the running subscription, stopping the logical replication
+      worker at the end of transaction.
+     </para>
+    </listitem>
+   </varlistentry>
+
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   Change the publication subscribed by a subscription to
+   <literal>insert_only</literal>:
+<programlisting>
+ALTER SUBSCRIPTION mysub
+        PUBLICATION insert_only;
+</programlisting>
+  </para>
+
+  <para>
+   Disable (stop) the subscription:
+<programlisting>
+ALTER SUBSCRIPTION mysub DISABLE;
+</programlisting>
+  </para>
+
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <command>ALTER SUBSCRIPTION</command> is a <productname>PostgreSQL</>
+   extension.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-createsubscription"></member>
+   <member><xref linkend="sql-dropsubscription"></member>
+   <member><xref linkend="sql-createpublication"></member>
+   <member><xref linkend="sql-alterpublication"></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/create_subscription.sgml b/doc/src/sgml/ref/create_subscription.sgml
new file mode 100644
index 0000000..a2cb459
--- /dev/null
+++ b/doc/src/sgml/ref/create_subscription.sgml
@@ -0,0 +1,159 @@
+<!--
+doc/src/sgml/ref/create_subscription.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-CREATESUBSCRIPTION">
+ <indexterm zone="sql-altersubscription">
+  <primary>CREATE SUBSCRIPTION</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>CREATE SUBSCRIPTION</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>CREATE SUBSCRIPTION</refname>
+  <refpurpose>define new subscription</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+CREATE SUBSCRIPTION <replaceable class="PARAMETER">subscription_name</replaceable> [ [ WITH ] <replaceable class="PARAMETER">option</replaceable> [ ... ] ]
+
+<phrase>where <replaceable class="PARAMETER">option</replaceable> can be:</phrase>
+
+    CONNECTION 'conninfo'
+    | PUBLICATION publication_name [, ...]
+    | INITIALLY ( ENABLED | DISABLED )
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>CREATE SUBSCRIPTION</command> adds a new subscription for
+   a current database. The subscription name must be distinct from
+   the name of any existing subscription in the database cluster.
+  </para>
+
+  <para>
+   The subscription represents a replication connection to the provider.
+   As such this command does not only add definition in the local catalogs
+   but also creates a replication slot on the provider.
+  </para>
+
+  <para>
+   A logical replication worker will be started to replicate data for the
+   new subscription at the commit of the transaction where this command
+   was run.
+  </para>
+
+  <para>
+   Additional info about subscriptions and logical replication as a whole
+   can is available at <xref linkend="logical-replication-subscription"> and
+   <xref linkend="logical-replication">.
+  </para>
+
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">subscription_name</replaceable></term>
+    <listitem>
+     <para>
+      The name of the new subscription.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term>CONNECTION '<replaceable class="parameter">conninfo</replaceable>'</term>
+    <listitem>
+     <para>
+      The connection string to the provider.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term>PUBLICATION <replaceable class="parameter">publication_name</replaceable></term>
+    <listitem>
+     <para>
+      Name(s) of the publications on the provider to subscribe to.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term>INITIALLY ENABLED</term>
+    <term>INITIALLY DISABLED</term>
+    <listitem>
+     <para>
+      Specifies if the subscription should be actively replicating or
+      if it should be just setup but not started yet.  Note that the
+      replication slot as described above is created in either case.
+      <literal>INITIALLY ENABLED</literal> is the default.
+     </para>
+    </listitem>
+   </varlistentry>
+
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   Create a subscription to a different server which replicates tables in
+   the publications <literal>mypubclication</literal> and
+   <literal>insert_only</literal> and starts replicating immediately on
+   commit:
+<programlisting>
+CREATE SUBSCRIPTION mysub
+         CONNECTION 'host=192.168.1.50 port=5432 user=foo dbname=foodb password=foopass'
+        PUBLICATION mypublication, insert_only;
+</programlisting>
+  </para>
+
+  <para>
+   Create a subscription to a different server which replicates tables in
+   the <literal>insert_only</literal> publication and does not replicate
+   until enabled at a later time.
+<programlisting>
+CREATE SUBSCRIPTION mysub
+         CONNECTION 'host=192.168.1.50 port=5432 user=foo dbname=foodb password=foopass'
+        PUBLICATION insert_only
+ INITIALLY DISABLED;
+</programlisting>
+  </para>
+
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <command>CREATE SUBSCRIPTION</command> is a <productname>PostgreSQL</>
+   extension.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-altersubscription"></member>
+   <member><xref linkend="sql-dropsubscription"></member>
+   <member><xref linkend="sql-createpublication"></member>
+   <member><xref linkend="sql-alterpublication"></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/drop_subscription.sgml b/doc/src/sgml/ref/drop_subscription.sgml
new file mode 100644
index 0000000..38fb4b0
--- /dev/null
+++ b/doc/src/sgml/ref/drop_subscription.sgml
@@ -0,0 +1,101 @@
+<!--
+doc/src/sgml/ref/drop_subscription.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-DROPSUBSCRIPTION">
+ <indexterm zone="sql-dropsubscription">
+  <primary>DROP SUBSCRIPTION</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>DROP SUBSCRIPTION</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>DROP SUBSCRIPTION</refname>
+  <refpurpose>remove an existing subscription</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+DROP SUBSCRIPTION <replaceable class="PARAMETER">name</replaceable> [, ...]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>DROP SUBSCRIPTION</command> removes subscriptions from the
+   cluster.
+  </para>
+
+  <para>
+   This command cannot be performed inside a transaction block.
+  </para>
+
+  <para>
+   A subscription can only be dropped by its owner or a superuser.
+  </para>
+
+  <warning>
+   <para>
+    While the <command>DROP SUBSCRIPTION</command> will try to remove the
+    replication slot on the provider, it will not fail when said action
+    is not successfull. This means the replication slot may need to be
+    dropped manually to not hold back removal of WAL on provider.
+   </para>
+  </warning>
+
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name of a subscription to be dropped.
+     </para>
+    </listitem>
+   </varlistentry>
+
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   Drop a subscription:
+<programlisting>
+DROP SUBSCRIPTION mysub;
+</programlisting>
+  </para>
+
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <command>DROP SUBSCRIPTION</command> is a <productname>PostgreSQL</>
+   extension.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-createsubscription"></member>
+   <member><xref linkend="sql-altersubscription"></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 6bb3683..60737d4 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -42,7 +42,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
 	pg_foreign_table.h pg_policy.h pg_replication_origin.h \
 	pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
 	pg_collation.h pg_range.h pg_transform.h \
-	pg_publication.h pg_publication_rel.h \
+	pg_publication.h pg_publication_rel.h pg_subscription.h \
 	toasting.h indexing.h \
     )
 
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 1baaa0b..ba758bb 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -36,6 +36,7 @@
 #include "catalog/pg_shdepend.h"
 #include "catalog/pg_shdescription.h"
 #include "catalog/pg_shseclabel.h"
+#include "catalog/pg_subscription.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/toasting.h"
 #include "miscadmin.h"
@@ -227,7 +228,8 @@ IsSharedRelation(Oid relationId)
 		relationId == SharedSecLabelRelationId ||
 		relationId == TableSpaceRelationId ||
 		relationId == DbRoleSettingRelationId ||
-		relationId == ReplicationOriginRelationId)
+		relationId == ReplicationOriginRelationId ||
+		relationId == SubscriptionRelationId)
 		return true;
 	/* These are their indexes (see indexing.h) */
 	if (relationId == AuthIdRolnameIndexId ||
@@ -245,7 +247,9 @@ IsSharedRelation(Oid relationId)
 		relationId == TablespaceNameIndexId ||
 		relationId == DbRoleSettingDatidRolidIndexId ||
 		relationId == ReplicationOriginIdentIndex ||
-		relationId == ReplicationOriginNameIndex)
+		relationId == ReplicationOriginNameIndex ||
+		relationId == SubscriptionObjectIndexId ||
+		relationId == SubscriptionNameIndexId)
 		return true;
 	/* These are their toast tables and toast indexes (see toasting.h) */
 	if (relationId == PgShdescriptionToastTable ||
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 359e7bb..91564b2 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -51,6 +51,7 @@
 #include "catalog/pg_publication.h"
 #include "catalog/pg_publication_rel.h"
 #include "catalog/pg_rewrite.h"
+#include "catalog/pg_subscription.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_trigger.h"
@@ -168,6 +169,7 @@ static const Oid object_classes[] = {
 	PolicyRelationId,			/* OCLASS_POLICY */
 	PublicationRelationId,		/* OCLASS_PUBCLICATION */
 	PublicationRelRelationId,	/* OCLASS_PUBCLICATION_REL */
+	SubscriptionRelationId,		/* OCLASS_SUBSCRIPTION */
 	TransformRelationId			/* OCLASS_TRANSFORM */
 };
 
@@ -1292,6 +1294,10 @@ doDeletion(const ObjectAddress *object, int flags)
 			RemovePublicationRelById(object->objectId);
 			break;
 
+		case OCLASS_SUBSCRIPTION:
+			DropSubscriptionById(object->objectId);
+			break;
+
 		case OCLASS_TRANSFORM:
 			DropTransformById(object->objectId);
 			break;
@@ -2455,6 +2461,9 @@ getObjectClass(const ObjectAddress *object)
 		case PublicationRelRelationId:
 			return OCLASS_PUBLICATION_REL;
 
+		case SubscriptionRelationId:
+			return OCLASS_SUBSCRIPTION;
+
 		case TransformRelationId:
 			return OCLASS_TRANSFORM;
 	}
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 375f4b0..f25c69b 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_publication.h"
 #include "catalog/pg_publication_rel.h"
 #include "catalog/pg_rewrite.h"
+#include "catalog/pg_subscription.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_trigger.h"
@@ -74,6 +75,7 @@
 #include "parser/parse_oper.h"
 #include "parser/parse_type.h"
 #include "replication/publication.h"
+#include "replication/subscription.h"
 #include "rewrite/rewriteSupport.h"
 #include "storage/lmgr.h"
 #include "storage/sinval.h"
@@ -465,6 +467,18 @@ static const ObjectPropertyType ObjectProperty[] =
 		InvalidAttrNumber,
 		-1,
 		true
+	},
+	{
+		SubscriptionRelationId,
+		SubscriptionObjectIndexId,
+		SUBSCRIPTIONOID,
+		SUBSCRIPTIONNAME,
+		Anum_pg_subscription_subname,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		-1,
+		true
 	}
 };
 
@@ -676,6 +690,10 @@ static const struct object_type_map
 	{
 		"publication relation", OBJECT_PUBLICATION_REL
 	},
+	/* OCLASS_SUBSCRIPTION */
+	{
+		"subscription", OBJECT_SUBSCRIPTION
+	},
 	/* OCLASS_TRANSFORM */
 	{
 		"transform", OBJECT_TRANSFORM
@@ -839,6 +857,7 @@ get_object_address(ObjectType objtype, List *objname, List *objargs,
 			case OBJECT_EVENT_TRIGGER:
 			case OBJECT_ACCESS_METHOD:
 			case OBJECT_PUBLICATION:
+			case OBJECT_SUBSCRIPTION:
 				address = get_object_address_unqualified(objtype,
 														 objname, missing_ok);
 				break;
@@ -1125,6 +1144,9 @@ get_object_address_unqualified(ObjectType objtype,
 			case OBJECT_PUBLICATION:
 				msg = gettext_noop("publication name cannot be qualified");
 				break;
+			case OBJECT_SUBSCRIPTION:
+				msg = gettext_noop("subscription name cannot be qualified");
+				break;
 			default:
 				elog(ERROR, "unrecognized objtype: %d", (int) objtype);
 				msg = NULL;		/* placate compiler */
@@ -1195,6 +1217,11 @@ get_object_address_unqualified(ObjectType objtype,
 			address.objectId = get_publication_oid(name, missing_ok);
 			address.objectSubId = 0;
 			break;
+		case OBJECT_SUBSCRIPTION:
+			address.classId = SubscriptionRelationId;
+			address.objectId = get_subscription_oid(name, missing_ok);
+			address.objectSubId = 0;
+			break;
 		default:
 			elog(ERROR, "unrecognized objtype: %d", (int) objtype);
 			/* placate compiler, which doesn't know elog won't return */
@@ -2315,6 +2342,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 		case OBJECT_TSTEMPLATE:
 		case OBJECT_ACCESS_METHOD:
 		case OBJECT_PUBLICATION:
+		case OBJECT_SUBSCRIPTION:
 			/* We treat these object types as being owned by superusers */
 			if (!superuser_arg(roleid))
 				ereport(ERROR,
@@ -3316,6 +3344,21 @@ getObjectDescription(const ObjectAddress *object)
 				break;
 			}
 
+		case OCLASS_SUBSCRIPTION:
+			{
+				HeapTuple	tup;
+
+				tup = SearchSysCache1(SUBSCRIPTIONOID,
+									  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "cache lookup failed for subscription %u",
+						 object->objectId);
+				appendStringInfo(&buffer, _("subscription %s"),
+				   NameStr(((Form_pg_subscription) GETSTRUCT(tup))->subname));
+				ReleaseSysCache(tup);
+				break;
+			}
+
 		default:
 			appendStringInfo(&buffer, "unrecognized object %u %u %d",
 							 object->classId,
@@ -3809,6 +3852,10 @@ getObjectTypeDescription(const ObjectAddress *object)
 			appendStringInfoString(&buffer, "publication table");
 			break;
 
+		case OCLASS_SUBSCRIPTION:
+			appendStringInfoString(&buffer, "subscription");
+			break;
+
 		default:
 			appendStringInfo(&buffer, "unrecognized %u", object->classId);
 			break;
@@ -4812,6 +4859,18 @@ getObjectIdentityParts(const ObjectAddress *object,
 				break;
 			}
 
+		case OCLASS_SUBSCRIPTION:
+			{
+				Subscription *sub;
+
+				sub = GetSubscription(object->objectId);
+				appendStringInfoString(&buffer,
+									   quote_identifier(sub->name));
+				if (objname)
+					*objname = list_make1(pstrdup(sub->name));
+				break;
+			}
+
 		default:
 			appendStringInfo(&buffer, "unrecognized object %u %u %d",
 							 object->classId,
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index cb580377..e0fab38 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -18,7 +18,8 @@ OBJS = amcmds.o aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \
 	event_trigger.o explain.o extension.o foreigncmds.o functioncmds.o \
 	indexcmds.o lockcmds.o matview.o operatorcmds.o opclasscmds.o \
 	policy.o portalcmds.o prepare.o proclang.o publicationcmds.o \
-	schemacmds.o seclabel.o sequence.o tablecmds.o tablespace.o trigger.o \
-	tsearchcmds.o typecmds.o user.o vacuum.o vacuumlazy.o variable.o view.o
+	schemacmds.o seclabel.o sequence.o subscriptioncmds.o tablecmds.o \
+	tablespace.o trigger.o tsearchcmds.o typecmds.o user.o vacuum.o \
+	vacuumlazy.o variable.o view.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 8aaf1a7..2a7951d 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -123,6 +123,7 @@ static event_trigger_support_data event_trigger_support[] = {
 	{"USER MAPPING", true},
 	{"VIEW", true},
 	{"PUBLICATION", true},
+	{"SUBSCRIPTION", true},
 	{NULL, false}
 };
 
@@ -1122,6 +1123,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 		case OBJECT_VIEW:
 		case OBJECT_PUBLICATION:
 		case OBJECT_PUBLICATION_REL:
+		case OBJECT_SUBSCRIPTION:
 			return true;
 	}
 	return true;
@@ -1175,6 +1177,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
 		case OCLASS_AM:
 		case OCLASS_PUBLICATION:
 		case OCLASS_PUBLICATION_REL:
+		case OCLASS_SUBSCRIPTION:
 			return true;
 	}
 
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
new file mode 100644
index 0000000..54d66d5
--- /dev/null
+++ b/src/backend/commands/subscriptioncmds.c
@@ -0,0 +1,331 @@
+/*-------------------------------------------------------------------------
+ *
+ * subscriptioncmds.c
+ *		subscription catalog manipulation functions
+ *
+ * Copyright (c) 2015, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		subscriptioncmds.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "funcapi.h"
+#include "miscadmin.h"
+
+#include "access/genam.h"
+#include "access/hash.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/xact.h"
+
+#include "catalog/indexing.h"
+#include "catalog/namespace.h"
+#include "catalog/objectaddress.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_subscription.h"
+
+#include "commands/defrem.h"
+
+#include "executor/spi.h"
+
+#include "nodes/makefuncs.h"
+
+#include "replication/reorderbuffer.h"
+#include "commands/replicationcmds.h"
+
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/catcache.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+
+static void
+check_replication_permissions(void)
+{
+	if (!superuser() && !has_rolreplication(GetUserId()))
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 (errmsg("must be superuser or replication role to manipulate subscriptions"))));
+}
+
+static void
+parse_subscription_options(List *options,
+						   bool *enabled_given, bool *enabled,
+						   char **conninfo, List **publications)
+{
+	ListCell   *lc;
+
+	*enabled_given = false;
+	*enabled = true;
+	*conninfo = NULL;
+	*publications = NIL;
+
+	/* Parse options */
+	foreach (lc, options)
+	{
+		DefElem    *defel = (DefElem *) lfirst(lc);
+
+		if (strcmp(defel->defname, "enabled") == 0)
+		{
+			if (*enabled_given)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+
+			*enabled_given = true;
+			*enabled = defGetBoolean(defel);
+		}
+		else if (strcmp(defel->defname, "conninfo") == 0)
+		{
+			if (*conninfo)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+
+			*conninfo = defGetString(defel);
+		}
+		else if (strcmp(defel->defname, "publication") == 0)
+		{
+			if (*publications)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+
+			if (defel->arg == NULL)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("%s requires a parameter", defel->defname)));
+
+			*publications = (List *) defel->arg;
+		}
+		else
+			elog(ERROR, "unrecognized option: %s", defel->defname);
+	}
+}
+
+/*
+ * Auxiliary function to return a TEXT array out of a list of String nodes.
+ */
+static Datum
+publicationListToArray(List *list)
+{
+	ArrayType  *arr;
+	Datum	   *datums;
+	int			j = 0;
+	ListCell   *cell;
+	MemoryContext memcxt;
+	MemoryContext oldcxt;
+
+	memcxt = AllocSetContextCreate(CurrentMemoryContext,
+								   "publicationListToArray to array",
+								   ALLOCSET_DEFAULT_MINSIZE,
+								   ALLOCSET_DEFAULT_INITSIZE,
+								   ALLOCSET_DEFAULT_MAXSIZE);
+	oldcxt = MemoryContextSwitchTo(memcxt);
+
+	datums = palloc(sizeof(text *) * list_length(list));
+	foreach(cell, list)
+	{
+		char	   *name = strVal(lfirst(cell));
+
+		datums[j++] = CStringGetTextDatum(name);
+	}
+
+	MemoryContextSwitchTo(oldcxt);
+
+	arr = construct_array(datums, list_length(list),
+						  TEXTOID, -1, false, 'i');
+	MemoryContextDelete(memcxt);
+
+	return PointerGetDatum(arr);
+}
+
+/*
+ * Create new subscription.
+ */
+ObjectAddress
+CreateSubscription(CreateSubscriptionStmt *stmt)
+{
+	Relation	rel;
+	ObjectAddress myself;
+	Oid			subid;
+	bool		nulls[Natts_pg_subscription];
+	Datum		values[Natts_pg_subscription];
+	HeapTuple	tup;
+	bool		enabled_given;
+	bool		enabled;
+	char	   *conninfo;
+	List	   *publications;
+
+	check_replication_permissions();
+
+	rel = heap_open(SubscriptionRelationId, RowExclusiveLock);
+
+	/* Check if name is used */
+	subid = GetSysCacheOid1(SUBSCRIPTIONNAME,
+							CStringGetDatum(stmt->subname));
+	if (OidIsValid(subid))
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("subscription \"%s\" already exists",
+						stmt->subname)));
+	}
+
+	/* Parse and check options. */
+	parse_subscription_options(stmt->options, &enabled_given, &enabled,
+							   &conninfo, &publications);
+
+	/* TODO: improve error messages here. */
+	if (conninfo == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("connection not specified")));
+
+	if (list_length(publications) == 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("publication not specified")));
+
+	/* Everything ok, form a new tuple. */
+	memset(values, 0, sizeof(values));
+	memset(nulls, false, sizeof(nulls));
+
+	values[Anum_pg_subscription_dbid - 1] = ObjectIdGetDatum(MyDatabaseId);
+	values[Anum_pg_subscription_subname - 1] =
+		DirectFunctionCall1(namein, CStringGetDatum(stmt->subname));
+	values[Anum_pg_subscription_subenabled - 1] = BoolGetDatum(enabled);
+	values[Anum_pg_subscription_subconninfo - 1] =
+		CStringGetTextDatum(conninfo);
+	values[Anum_pg_subscription_subslotname - 1] =
+		DirectFunctionCall1(namein, CStringGetDatum(stmt->subname));
+	values[Anum_pg_subscription_subpublications - 1] =
+		 publicationListToArray(publications);
+
+	tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+	/* Insert tuple into catalog. */
+	subid = simple_heap_insert(rel, tup);
+	CatalogUpdateIndexes(rel, tup);
+	heap_freetuple(tup);
+
+	ObjectAddressSet(myself, SubscriptionRelationId, suboid);
+
+	heap_close(rel, RowExclusiveLock);
+
+	/* Make the changes visible. */
+	CommandCounterIncrement();
+
+	return myself;
+}
+
+/*
+ * Alter the existing subscription.
+ */
+ObjectAddress
+AlterSubscription(AlterSubscriptionStmt *stmt)
+{
+	Relation	rel;
+	ObjectAddress myself;
+	bool		nulls[Natts_pg_subscription];
+	bool		replaces[Natts_pg_subscription];
+	Datum		values[Natts_pg_subscription];
+	HeapTuple	tup;
+	Oid			subid;
+	bool		enabled_given;
+	bool		enabled;
+	char	   *conninfo;
+	List	   *publications;
+
+	check_replication_permissions();
+
+	rel = heap_open(SubscriptionRelationId, RowExclusiveLock);
+
+	/* Fetch the existing tuple. */
+	tup = SearchSysCacheCopy1(SUBSCRIPTIONNAME,
+							  CStringGetDatum(stmt->subname));
+
+	if (!HeapTupleIsValid(tup))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("subscription \"%s\" does not exist",
+						stmt->subname)));
+
+	subid = HeapTupleGetOid(tup);
+
+	/* Parse options. */
+	parse_subscription_options(stmt->options, &enabled_given, &enabled,
+							   &conninfo, &publications);
+
+	/* Form a new tuple. */
+	memset(values, 0, sizeof(values));
+	memset(nulls, false, sizeof(nulls));
+	memset(replaces, false, sizeof(replaces));
+
+	if (enabled_given)
+	{
+		values[Anum_pg_subscription_subenabled - 1] = BoolGetDatum(enabled);
+		replaces[Anum_pg_subscription_subenabled - 1] = true;
+	}
+	if (conninfo)
+	{
+		values[Anum_pg_subscription_subconninfo - 1] =
+			CStringGetTextDatum(conninfo);
+		replaces[Anum_pg_subscription_subenabled - 1] = true;
+	}
+	if (publications != NIL)
+	{
+		values[Anum_pg_subscription_subpublications - 1] =
+			 publicationListToArray(publications);
+		replaces[Anum_pg_subscription_subpublications - 1] = true;
+	}
+
+	tup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls,
+							replaces);
+
+	/* Update the catalog. */
+	simple_heap_update(rel, &tup->t_self, tup);
+	CatalogUpdateIndexes(rel, tup);
+
+	ObjectAddressSet(myself, SubscriptionRelationId, subid);
+
+	/* Cleanup. */
+	heap_freetuple(tup);
+	heap_close(rel, RowExclusiveLock);
+
+	return myself;
+}
+
+/*
+ * Drop subscription by OID
+ */
+void
+DropSubscriptionById(Oid subid)
+{
+	Relation	rel;
+	HeapTuple	tup;
+
+	check_replication_permissions();
+
+	rel = heap_open(SubscriptionRelationId, RowExclusiveLock);
+
+	tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for subscription %u", subid);
+
+	simple_heap_delete(rel, &tup->t_self);
+
+	ReleaseSysCache(tup);
+
+	heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index bf76742..3c39b7e 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4179,6 +4179,28 @@ _copyAlterPublicationStmt(const AlterPublicationStmt *from)
 	return newnode;
 }
 
+static CreateSubscriptionStmt *
+_copyCreateSubscriptionStmt(const CreateSubscriptionStmt *from)
+{
+	CreateSubscriptionStmt *newnode = makeNode(CreateSubscriptionStmt);
+
+	COPY_STRING_FIELD(subname);
+	COPY_NODE_FIELD(options);
+
+	return newnode;
+}
+
+static AlterSubscriptionStmt *
+_copyAlterSubscriptionStmt(const AlterSubscriptionStmt *from)
+{
+	AlterSubscriptionStmt *newnode = makeNode(AlterSubscriptionStmt);
+
+	COPY_STRING_FIELD(subname);
+	COPY_NODE_FIELD(options);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -4976,6 +4998,12 @@ copyObject(const void *from)
 		case T_AlterPublicationStmt:
 			retval = _copyAlterPublicationStmt(from);
 			break;
+		case T_CreateSubscriptionStmt:
+			retval = _copyCreateSubscriptionStmt(from);
+			break;
+		case T_AlterSubscriptionStmt:
+			retval = _copyAlterSubscriptionStmt(from);
+			break;
 		case T_A_Expr:
 			retval = _copyAExpr(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 0c5f1d0..2a5ed58 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2129,6 +2129,26 @@ _equalAlterPublicationStmt(const AlterPublicationStmt *a,
 }
 
 static bool
+_equalCreateSubscriptionStmt(const CreateSubscriptionStmt *a,
+							 const CreateSubscriptionStmt *b)
+{
+	COMPARE_STRING_FIELD(subname);
+	COMPARE_NODE_FIELD(options);
+
+	return true;
+}
+
+static bool
+_equalAlterSubscriptionStmt(const AlterSubscriptionStmt *a,
+							const AlterSubscriptionStmt *b)
+{
+	COMPARE_STRING_FIELD(subname);
+	COMPARE_NODE_FIELD(options);
+
+	return true;
+}
+
+static bool
 _equalCreatePolicyStmt(const CreatePolicyStmt *a, const CreatePolicyStmt *b)
 {
 	COMPARE_STRING_FIELD(policy_name);
@@ -3278,6 +3298,12 @@ equal(const void *a, const void *b)
 		case T_AlterPublicationStmt:
 			retval = _equalAlterPublicationStmt(a, b);
 			break;
+		case T_CreateSubscriptionStmt:
+			retval = _equalCreateSubscriptionStmt(a, b);
+			break;
+		case T_AlterSubscriptionStmt:
+			retval = _equalAlterSubscriptionStmt(a, b);
+			break;
 		case T_A_Expr:
 			retval = _equalAExpr(a, b);
 			break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index b91e75a..03640eb 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -267,6 +267,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
 		CreateMatViewStmt RefreshMatViewStmt CreateAmStmt
 		CreatePublicationStmt AlterPublicationStmt
+		CreateSubscriptionStmt AlterSubscriptionStmt
 
 %type <node>	select_no_parens select_with_parens select_clause
 				simple_select values_clause
@@ -374,13 +375,17 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				relation_expr_list dostmt_opt_list
 				transform_element_list transform_type_list
 				publication_opt_list publication_opt_items
+				subscription_create_opt_items subscription_opt_items
 
 %type <list>	group_by_list
 %type <node>	group_by_item empty_grouping_set rollup_clause cube_clause
 %type <node>	grouping_sets_clause
 
 %type <list>	opt_fdw_options fdw_options
+				publication_list
 %type <defelt>	fdw_option publication_opt_item
+				subscription_opt_item subscription_create_opt_item
+%type <value>	publication_item
 
 %type <range>	OptTempTableName
 %type <into>	into_clause create_as_target create_mv_target
@@ -631,8 +636,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
 	SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
 	SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P START
-	STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING
-	SYMMETRIC SYSID SYSTEM_P
+	STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSCRIPTION
+	SUBSTRING SYMMETRIC SYSID SYSTEM_P
 
 	TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
 	TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM TREAT TRIGGER TRIM TRUE_P
@@ -783,6 +788,7 @@ stmt :
 			| AlterPublicationStmt
 			| AlterRoleSetStmt
 			| AlterRoleStmt
+			| AlterSubscriptionStmt
 			| AlterTSConfigurationStmt
 			| AlterTSDictionaryStmt
 			| AlterUserMappingStmt
@@ -817,6 +823,7 @@ stmt :
 			| CreateSchemaStmt
 			| CreateSeqStmt
 			| CreateStmt
+			| CreateSubscriptionStmt
 			| CreateTableSpaceStmt
 			| CreateTransformStmt
 			| CreateTrigStmt
@@ -5648,6 +5655,7 @@ drop_type:	TABLE									{ $$ = OBJECT_TABLE; }
 			| TEXT_P SEARCH TEMPLATE				{ $$ = OBJECT_TSTEMPLATE; }
 			| TEXT_P SEARCH CONFIGURATION			{ $$ = OBJECT_TSCONFIGURATION; }
 			| PUBLICATION							{ $$ = OBJECT_PUBLICATION; }
+			| SUBSCRIPTION							{ $$ = OBJECT_SUBSCRIPTION; }
 		;
 
 any_name_list:
@@ -8610,6 +8618,120 @@ AlterPublicationStmt:
 
 /*****************************************************************************
  *
+ * CREATE SUBSCRIPTION name [ WITH ] options
+ *
+ *****************************************************************************/
+
+CreateSubscriptionStmt:
+			CREATE SUBSCRIPTION name opt_with subscription_create_opt_items
+				{
+					CreateSubscriptionStmt *n =
+						makeNode(CreateSubscriptionStmt);
+					n->subname = $3;
+					n->options = $5;
+					$$ = (Node *)n;
+				}
+		;
+
+subscription_create_opt_items:
+			subscription_create_opt_item
+				{
+					$$ = list_make1($1);
+				}
+			| subscription_create_opt_items subscription_create_opt_item
+				{
+					$$ = lappend($1, $2);
+				}
+		;
+
+subscription_create_opt_item:
+			subscription_opt_item
+			| INITIALLY IDENT
+				{
+					if (strcmp($2, "enabled") == 0)
+						$$ = makeDefElem("enabled", (Node *)makeInteger(TRUE));
+					else if (strcmp($2, "disabled") == 0)
+						$$ = makeDefElem("enabled", (Node *)makeInteger(FALSE));
+					else
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("unrecognized subscription option \"%s\"", $1),
+									 parser_errposition(@2)));
+				}
+		;
+
+subscription_opt_item:
+			CONNECTION Sconst
+				{
+					$$ = makeDefElem("conninfo", (Node *)makeString($2));
+				}
+			| PUBLICATION publication_list
+				{
+					$$ = makeDefElem("publication", (Node *)$2);
+				}
+		;
+
+publication_list:
+			publication_item
+				{
+					$$ = list_make1($1);
+				}
+			| publication_list ',' publication_item
+				{
+					$$ = lappend($1, $3);
+				}
+		;
+
+publication_item:
+			ColLabel			{ $$ = makeString($1); };
+
+/*****************************************************************************
+ *
+ * ALTER SUBSCRIPTION name [ WITH ] options
+ *
+ *****************************************************************************/
+
+AlterSubscriptionStmt:
+			ALTER SUBSCRIPTION name opt_with subscription_opt_items
+				{
+					AlterSubscriptionStmt *n =
+						makeNode(AlterSubscriptionStmt);
+					n->subname = $3;
+					n->options = $5;
+					$$ = (Node *)n;
+				}
+			| ALTER SUBSCRIPTION name ENABLE_P
+				{
+					AlterSubscriptionStmt *n =
+						makeNode(AlterSubscriptionStmt);
+					n->subname = $3;
+					n->options = list_make1(makeDefElem("enabled",
+												   (Node *)makeInteger(TRUE)));
+					$$ = (Node *)n;
+				}
+			| ALTER SUBSCRIPTION name DISABLE_P
+				{
+					AlterSubscriptionStmt *n =
+						makeNode(AlterSubscriptionStmt);
+					n->subname = $3;
+					n->options = list_make1(makeDefElem("enabled",
+												  (Node *)makeInteger(FALSE)));
+					$$ = (Node *)n;
+				}		;
+
+subscription_opt_items:
+			subscription_opt_item
+				{
+					$$ = list_make1($1);
+				}
+			| subscription_opt_items subscription_opt_item
+				{
+					$$ = lappend($1, $2);
+				}
+		;
+
+/*****************************************************************************
+ *
  *		QUERY:	Define Rewrite Rule
  *
  *****************************************************************************/
@@ -14066,6 +14188,7 @@ unreserved_keyword:
 			| STORAGE
 			| STRICT_P
 			| STRIP_P
+			| SUBSCRIPTION
 			| SYSID
 			| SYSTEM_P
 			| TABLES
diff --git a/src/backend/replication/logical/Makefile b/src/backend/replication/logical/Makefile
index 3b3e90c..e4b093b 100644
--- a/src/backend/replication/logical/Makefile
+++ b/src/backend/replication/logical/Makefile
@@ -15,6 +15,6 @@ include $(top_builddir)/src/Makefile.global
 override CPPFLAGS := -I$(srcdir) $(CPPFLAGS)
 
 OBJS = decode.o logical.o logicalfuncs.o message.o origin.o publication.o \
-	   reorderbuffer.o snapbuild.o
+	   reorderbuffer.o snapbuild.o subscription.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/replication/logical/subscription.c b/src/backend/replication/logical/subscription.c
new file mode 100644
index 0000000..7d1de2c
--- /dev/null
+++ b/src/backend/replication/logical/subscription.c
@@ -0,0 +1,146 @@
+/*-------------------------------------------------------------------------
+ *
+ * subscription.c
+ *		replication subscriptions
+ *
+ * Copyright (c) 2015, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		subscription.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "funcapi.h"
+#include "miscadmin.h"
+
+#include "access/genam.h"
+#include "access/hash.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/xact.h"
+
+#include "catalog/indexing.h"
+#include "catalog/namespace.h"
+#include "catalog/objectaddress.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_subscription.h"
+
+#include "executor/spi.h"
+
+#include "nodes/makefuncs.h"
+
+#include "replication/reorderbuffer.h"
+#include "replication/subscription.h"
+
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/catcache.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+
+static List *textarray_to_stringlist(ArrayType *textarray);
+
+/*
+ * Fetch the subscription from the syscache.
+ */
+Subscription *
+GetSubscription(Oid subid)
+{
+	HeapTuple		tup;
+	Subscription   *sub;
+	Form_pg_subscription	subform;
+	Datum			datum;
+	bool			isnull;
+
+	tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for subscription %u", subid);
+
+	subform = (Form_pg_subscription) GETSTRUCT(tup);
+
+	sub = (Subscription *) palloc(sizeof(Subscription));
+	sub->oid = subid;
+	sub->dbid = subform->dbid;
+	sub->name = NameStr(subform->subname);
+	sub->enabled = subform->subenabled;
+
+	/* Get conninfo */
+	datum = SysCacheGetAttr(SUBSCRIPTIONOID,
+							tup,
+							Anum_pg_subscription_subconninfo,
+							&isnull);
+	Assert(!isnull);
+	sub->conninfo = pstrdup(TextDatumGetCString(datum));
+
+	/* Get slotname */
+	datum = SysCacheGetAttr(SUBSCRIPTIONOID,
+							tup,
+							Anum_pg_subscription_subslotname,
+							&isnull);
+	Assert(!isnull);
+	sub->slotname = pstrdup(NameStr(*DatumGetName(datum)));
+
+	/* Get publications */
+	datum = SysCacheGetAttr(SUBSCRIPTIONOID,
+							tup,
+							Anum_pg_subscription_subpublications,
+							&isnull);
+	Assert(!isnull);
+	sub->publications = textarray_to_stringlist(DatumGetArrayTypeP(datum));
+
+	ReleaseSysCache(tup);
+
+	return sub;
+}
+
+/*
+ * get_subscription_oid - given a subscription name, look up the OID
+ *
+ * If missing_ok is false, throw an error if name not found.  If true, just
+ * return InvalidOid.
+ */
+Oid
+get_subscription_oid(const char *subname, bool missing_ok)
+{
+	Oid			oid;
+
+	oid = GetSysCacheOid1(SUBSCRIPTIONNAME, CStringGetDatum(subname));
+	if (!OidIsValid(oid) && !missing_ok)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("subscription \"%s\" does not exist", subname)));
+	return oid;
+}
+
+/*
+ * Convert text array to list of strings.
+ *
+ * Note: the resulting list of strings is pallocated here.
+ */
+static List *
+textarray_to_stringlist(ArrayType *textarray)
+{
+	Datum		   *elems;
+	int				nelems, i;
+	List		   *res = NIL;
+
+	deconstruct_array(textarray,
+					  TEXTOID, -1, false, 'i',
+					  &elems, NULL, &nelems);
+
+	if (nelems == 0)
+		return NIL;
+
+	for (i = 0; i < nelems; i++)
+		res = lappend(res, makeString(pstrdup(TextDatumGetCString(elems[i]))));
+
+	return res;
+}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 886d2ff..4cb2366 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -213,6 +213,8 @@ check_xact_readonly(Node *parsetree)
 		case T_SecLabelStmt:
 		case T_CreatePublicationStmt:
 		case T_AlterPublicationStmt:
+		case T_CreateSubscriptionStmt:
+		case T_AlterSubscriptionStmt:
 			PreventCommandIfReadOnly(CreateCommandTag(parsetree));
 			PreventCommandIfParallelMode(CreateCommandTag(parsetree));
 			break;
@@ -1560,6 +1562,14 @@ ProcessUtilitySlow(Node *parsetree,
 				commandCollected = true;
 				break;
 
+			case T_CreateSubscriptionStmt:
+				address = CreateSubscription((CreateSubscriptionStmt *) parsetree);
+				break;
+
+			case T_AlterSubscriptionStmt:
+				address = AlterSubscription((AlterSubscriptionStmt *) parsetree);
+				break;
+
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(parsetree));
@@ -1921,6 +1931,9 @@ AlterObjectTypeCommandTag(ObjectType objtype)
 		case OBJECT_PUBLICATION:
 			tag = "PUBLICATION";
 			break;
+		case OBJECT_SUBSCRIPTION:
+			tag = "SUBSCRIPTION";
+			break;
 		default:
 			tag = "???";
 			break;
@@ -2209,6 +2222,9 @@ CreateCommandTag(Node *parsetree)
 				case OBJECT_PUBLICATION:
 					tag = "DROP PUBLICATION";
 					break;
+				case OBJECT_SUBSCRIPTION:
+					tag = "DROP SUBSCRIPTION";
+					break;
 				default:
 					tag = "???";
 			}
@@ -2587,6 +2603,14 @@ CreateCommandTag(Node *parsetree)
 			tag = "ALTER PUBLICATION";
 			break;
 
+		case T_CreateSubscriptionStmt:
+			tag = "CREATE SUBSCRIPTION";
+			break;
+
+		case T_AlterSubscriptionStmt:
+			tag = "ALTER SUBSCRIPTION";
+			break;
+
 		case T_PrepareStmt:
 			tag = "PREPARE";
 			break;
@@ -3160,6 +3184,14 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_CreateSubscriptionStmt:
+			lev = LOGSTMT_DDL;
+			break;
+
+		case T_AlterSubscriptionStmt:
+			lev = LOGSTMT_DDL;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 3f7027f..7c3121b 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -52,6 +52,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
 #include "catalog/pg_shseclabel.h"
+#include "catalog/pg_subscription.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -100,6 +101,7 @@ static const FormData_pg_attribute Desc_pg_authid[Natts_pg_authid] = {Schema_pg_
 static const FormData_pg_attribute Desc_pg_auth_members[Natts_pg_auth_members] = {Schema_pg_auth_members};
 static const FormData_pg_attribute Desc_pg_index[Natts_pg_index] = {Schema_pg_index};
 static const FormData_pg_attribute Desc_pg_shseclabel[Natts_pg_shseclabel] = {Schema_pg_shseclabel};
+static const FormData_pg_attribute Desc_pg_subscription[Natts_pg_subscription] = {Schema_pg_subscription};
 
 /*
  *		Hash tables that index the relation cache
@@ -3266,8 +3268,10 @@ RelationCacheInitializePhase2(void)
 				  false, Natts_pg_auth_members, Desc_pg_auth_members);
 		formrdesc("pg_shseclabel", SharedSecLabelRelation_Rowtype_Id, true,
 				  false, Natts_pg_shseclabel, Desc_pg_shseclabel);
+		formrdesc("pg_subscription", SubscriptionRelation_Rowtype_Id, true,
+				  true, Natts_pg_subscription, Desc_pg_subscription);
 
-#define NUM_CRITICAL_SHARED_RELS	4	/* fix if you change list above */
+#define NUM_CRITICAL_SHARED_RELS	5	/* fix if you change list above */
 	}
 
 	MemoryContextSwitchTo(oldcxt);
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index d575b51..03c8916 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -59,6 +59,7 @@
 #include "catalog/pg_shseclabel.h"
 #include "catalog/pg_replication_origin.h"
 #include "catalog/pg_statistic.h"
+#include "catalog/pg_subscription.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_ts_config.h"
@@ -713,6 +714,28 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		128
 	},
+	{SubscriptionRelationId,		/* SUBSCRIPTIONOID */
+		SubscriptionObjectIndexId,
+		1,
+		{
+			ObjectIdAttributeNumber,
+			0,
+			0,
+			0
+		},
+		4
+	},
+	{SubscriptionRelationId,		/* SUBSCRIPTIONNAME */
+		SubscriptionNameIndexId,
+		1,
+		{
+			Anum_pg_subscription_subname,
+			0,
+			0,
+			0
+		},
+		4
+	},
 	{TableSpaceRelationId,		/* TABLESPACEOID */
 		TablespaceOidIndexId,
 		1,
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index a379c19..225ea25 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -497,6 +497,9 @@ exec_command(const char *cmd,
 					case 'p':
 						success = describePublications(pattern, show_verbose);
 						break;
+					case 's':
+						success = describeSubscriptions(pattern, show_verbose);
+						break;
 					default:
 						status = PSQL_CMD_UNKNOWN;
 						break;
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 573d980..823cda4 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -4784,6 +4784,72 @@ describePublications(const char *pattern, bool verbose)
 	return true;
 }
 
+/* \drs
+ * Describes subscriptions.
+ *
+ * Takes an optional regexp to select particular subscriptions
+ */
+bool
+describeSubscriptions(const char *pattern, bool verbose)
+{
+	PQExpBufferData buf;
+	PGresult   *res;
+	printQueryOpt myopt = pset.popt;
+	static const bool translate_columns[] = {false, true, false, false, false};
+
+	/* TODO bump */
+	if (pset.sversion < 90600)
+	{
+		psql_error("The server (version %d.%d) does not support subscriptions.\n",
+				   pset.sversion / 10000, (pset.sversion / 100) % 100);
+		return true;
+	}
+
+	initPQExpBuffer(&buf);
+
+	printfPQExpBuffer(&buf,
+					  "SELECT subname AS \"%s\",\n"
+					  "  (SELECT datname FROM pg_catalog.pg_database WHERE oid = dbid) AS \"%s\",\n"
+					  "  subenabled AS \"%s\",\n"
+					  "  subpublications AS \"%s\",\n"
+					  "  subconninfo AS \"%s\"\n",
+					  gettext_noop("Name"),
+					  gettext_noop("Database"),
+					  gettext_noop("Enabled"),
+					  gettext_noop("Publication"),
+					  gettext_noop("Conninfo"));
+
+	/* TODO Show owner and ACL */
+	if (verbose)
+	{
+	}
+
+	appendPQExpBufferStr(&buf,
+						 "\nFROM pg_catalog.pg_subscription\n");
+
+	processSQLNamePattern(pset.db, &buf, pattern, false, false,
+						  NULL, "subname", NULL,
+						  NULL);
+
+	appendPQExpBufferStr(&buf, "ORDER BY 1;");
+
+	res = PSQLexec(buf.data);
+	termPQExpBuffer(&buf);
+	if (!res)
+		return false;
+
+	myopt.nullPrint = NULL;
+	myopt.title = _("List of subscriptions");
+	myopt.translate_header = true;
+	myopt.translate_columns = translate_columns;
+	myopt.n_translate_columns = lengthof(translate_columns);
+
+	printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
+
+	PQclear(res);
+	return true;
+}
+
 /*
  * printACLColumn
  *
diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h
index c4457da..a754fb8 100644
--- a/src/bin/psql/describe.h
+++ b/src/bin/psql/describe.h
@@ -105,4 +105,7 @@ extern bool listEventTriggers(const char *pattern, bool verbose);
 /* \drp */
 bool describePublications(const char *pattern, bool verbose);
 
+/* \drs */
+bool describeSubscriptions(const char *pattern, bool verbose);
+
 #endif   /* DESCRIBE_H */
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 5cc5bce..88c3932 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -242,6 +242,7 @@ slashUsage(unsigned short int pager)
 	fprintf(output, _("  \\dp     [PATTERN]      list table, view, and sequence access privileges\n"));
 	fprintf(output, _("  \\drds [PATRN1 [PATRN2]] list per-database role settings\n"));
 	fprintf(output, _("  \\drp[+] [PATTERN]      list replication publications\n"));
+	fprintf(output, _("  \\drs[+] [PATTERN]      list replication subscriptions\n"));
 	fprintf(output, _("  \\ds[S+] [PATTERN]      list sequences\n"));
 	fprintf(output, _("  \\dt[S+] [PATTERN]      list tables\n"));
 	fprintf(output, _("  \\dT[S+] [PATTERN]      list data types\n"));
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 01fd16a..789ce00 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -163,6 +163,7 @@ typedef enum ObjectClass
 	OCLASS_POLICY,				/* pg_policy */
 	OCLASS_PUBLICATION,			/* pg_publication */
 	OCLASS_PUBLICATION_REL,		/* pg_publication_rel */
+	OCLASS_SUBSCRIPTION,		/* pg_subscription */
 	OCLASS_TRANSFORM			/* pg_transform */
 } ObjectClass;
 
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ae46ed6..86e2939 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -331,6 +331,12 @@ DECLARE_UNIQUE_INDEX(pg_publication_rel_object_index, 6112, on pg_publication_re
 DECLARE_UNIQUE_INDEX(pg_publication_rel_map_index, 6113, on pg_publication_rel using btree(relid oid_ops, pubid oid_ops));
 #define PublicationRelMapIndexId 6113
 
+DECLARE_UNIQUE_INDEX(pg_subscription_oid_index, 6114, on pg_subscription using btree(oid oid_ops));
+#define SubscriptionObjectIndexId 6114
+
+DECLARE_UNIQUE_INDEX(pg_subscription_subname_index, 6115, on pg_subscription using btree(subname name_ops));
+#define SubscriptionNameIndexId 6115
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h
new file mode 100644
index 0000000..254d509
--- /dev/null
+++ b/src/include/catalog/pg_subscription.h
@@ -0,0 +1,52 @@
+/* -------------------------------------------------------------------------
+ *
+ * pg_subscription.h
+ *		Definition of the subscription catalog (pg_subscription).
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+#ifndef PG_SUBSCRIPTION_H
+#define PG_SUBSCRIPTION_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *		pg_subscription definition. cpp turns this into
+ *		typedef struct FormData_pg_subscription
+ * ----------------
+ */
+#define SubscriptionRelationId			6100
+#define SubscriptionRelation_Rowtype_Id	6101
+
+CATALOG(pg_subscription,6100) BKI_SHARED_RELATION BKI_ROWTYPE_OID(6101) BKI_SCHEMA_MACRO
+{
+	Oid			dbid;			/* Database the subscription is in. */
+	NameData	subname;		/* Name of the subscription */
+	bool		subenabled;		/* True if the subsription is enabled (running) */
+
+#ifdef CATALOG_VARLEN			/* variable-length fields start here */
+	text		subconninfo;	/* Connection string to the provider */
+	NameData	subslotname;	/* Slot name on provider */
+
+	text		subpublications[1];	/* List of publications subscribed to */
+#endif
+} FormData_pg_subscription;
+
+typedef FormData_pg_subscription *Form_pg_subscription;
+
+/* ----------------
+ *		compiler constants for pg_subscription
+ * ----------------
+ */
+#define Natts_pg_subscription				6
+#define Anum_pg_subscription_dbid			1
+#define Anum_pg_subscription_subname		2
+#define Anum_pg_subscription_subenabled		3
+#define Anum_pg_subscription_subconninfo	4
+#define Anum_pg_subscription_subslotname	5
+#define Anum_pg_subscription_subpublications	6
+
+#endif   /* PG_SUBSCRIPTION_H */
diff --git a/src/include/commands/replicationcmds.h b/src/include/commands/replicationcmds.h
index 717485f..7c35d72 100644
--- a/src/include/commands/replicationcmds.h
+++ b/src/include/commands/replicationcmds.h
@@ -1,7 +1,7 @@
 /*-------------------------------------------------------------------------
  *
  * replicationcmds.h
- *	  prototypes for publicationcmds.c.
+ *	  prototypes for publicationcmds.c and subscriptioncmds.c.
  *
  *
  * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
@@ -23,4 +23,8 @@ extern void AlterPublication(AlterPublicationStmt *stmt);
 extern void DropPublicationById(Oid pubid);
 extern void RemovePublicationRelById(Oid prid);
 
+extern ObjectAddress CreateSubscription(CreateSubscriptionStmt *stmt);
+extern ObjectAddress AlterSubscription(AlterSubscriptionStmt *stmt);
+extern void DropSubscriptionById(Oid subid);
+
 #endif   /* REPLICATIONCMDS_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 3cce3d9..322286b 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -407,6 +407,8 @@ typedef enum NodeTag
 	T_CreateAmStmt,
 	T_CreatePublicationStmt,
 	T_AlterPublicationStmt,
+	T_CreateSubscriptionStmt,
+	T_AlterSubscriptionStmt,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index c10e6e7..247234c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1414,6 +1414,7 @@ typedef enum ObjectType
 	OBJECT_RULE,
 	OBJECT_SCHEMA,
 	OBJECT_SEQUENCE,
+	OBJECT_SUBSCRIPTION,
 	OBJECT_TABCONSTRAINT,
 	OBJECT_TABLE,
 	OBJECT_TABLESPACE,
@@ -3125,4 +3126,18 @@ typedef struct AlterPublicationStmt
 	List	   *tables;			/* List of tables to add/drop */
 } AlterPublicationStmt;
 
+typedef struct CreateSubscriptionStmt
+{
+	NodeTag		type;
+	char	   *subname;		/* Name of of the subscription */
+	List	   *options;		/* List of DefElem nodes */
+} CreateSubscriptionStmt;
+
+typedef struct AlterSubscriptionStmt
+{
+	NodeTag		type;
+	char	   *subname;		/* Name of of the subscription */
+	List	   *options;		/* List of DefElem nodes */
+} AlterSubscriptionStmt;
+
 #endif   /* PARSENODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 9430ff0..c11a3f4 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -370,6 +370,7 @@ PG_KEYWORD("stdout", STDOUT, UNRESERVED_KEYWORD)
 PG_KEYWORD("storage", STORAGE, UNRESERVED_KEYWORD)
 PG_KEYWORD("strict", STRICT_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("strip", STRIP_P, UNRESERVED_KEYWORD)
+PG_KEYWORD("subscription", SUBSCRIPTION, UNRESERVED_KEYWORD)
 PG_KEYWORD("substring", SUBSTRING, COL_NAME_KEYWORD)
 PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD)
 PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD)
diff --git a/src/include/replication/subscription.h b/src/include/replication/subscription.h
new file mode 100644
index 0000000..a937f4b
--- /dev/null
+++ b/src/include/replication/subscription.h
@@ -0,0 +1,33 @@
+/*-------------------------------------------------------------------------
+ *
+ * subscription.h
+ *		replication subscription support struncture/function definition
+ *
+ * Copyright (c) 2015, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		subscription.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef SUBSCRIPTION_H
+#define SUBSCRIPTION_H
+
+#include "postgres.h"
+
+
+typedef struct Subscription
+{
+	Oid		oid;			/* Oid of the subscription */
+	Oid		dbid;			/* Oid of the database which dubscription is in */
+	char   *name;			/* Name of the subscription */
+	bool	enabled;		/* Indicates if the subscription is enabled */
+	char   *conninfo;		/* Connection string to the provider */
+	char   *slotname;		/* Name of the replication slot */
+	List   *publications;	/* List of publication names to subscribe to */
+} Subscription;
+
+extern Subscription *GetSubscription(Oid subid);
+extern Oid get_subscription_oid(const char *subname, bool missing_ok);
+
+#endif		/* SUBSCRIPTION_H */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index ca2283a..7fcd139 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -540,5 +540,6 @@ typedef struct ViewOptions
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
 extern bool RelationHasUnloggedIndex(Relation rel);
+extern List *RelationGetRepsetList(Relation rel);
 
 #endif   /* REL_H */
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 632fcbc..b1d03a5 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -85,6 +85,8 @@ enum SysCacheIdentifier
 	PUBLICATIONRELMAP,
 	RULERELNAME,
 	STATRELATTINH,
+	SUBSCRIPTIONOID,
+	SUBSCRIPTIONNAME,
 	TABLESPACEOID,
 	TRFOID,
 	TRFTYPELANG,
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 5ab04ae..ceac2c8 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -131,6 +131,7 @@ pg_shdepend|t
 pg_shdescription|t
 pg_shseclabel|t
 pg_statistic|t
+pg_subscription|t
 pg_tablespace|t
 pg_transform|t
 pg_trigger|t
-- 
2.7.4

0003-Define-logical-replication-protocol-and-output-plugi.patchapplication/x-patch; name=0003-Define-logical-replication-protocol-and-output-plugi.patchDownload
From 0aa5eb65e4e8eb6a14654e4f877ad8d1541d6049 Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Sat, 4 Jun 2016 17:57:09 +0200
Subject: [PATCH 3/6] Define logical replication protocol and output plugin

---
 doc/src/sgml/protocol.sgml                         | 712 +++++++++++++++++++++
 src/Makefile                                       |   1 +
 src/backend/replication/logical/Makefile           |   4 +-
 src/backend/replication/logical/proto.c            | 641 +++++++++++++++++++
 src/backend/replication/pgoutput/Makefile          |  33 +
 src/backend/replication/pgoutput/pgoutput.c        | 445 +++++++++++++
 src/backend/replication/pgoutput/pgoutput_config.c | 188 ++++++
 src/include/replication/logicalproto.h             |  76 +++
 src/include/replication/pgoutput.h                 |  32 +
 9 files changed, 2130 insertions(+), 2 deletions(-)
 create mode 100644 src/backend/replication/logical/proto.c
 create mode 100644 src/backend/replication/pgoutput/Makefile
 create mode 100644 src/backend/replication/pgoutput/pgoutput.c
 create mode 100644 src/backend/replication/pgoutput/pgoutput_config.c
 create mode 100644 src/include/replication/logicalproto.h
 create mode 100644 src/include/replication/pgoutput.h

diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 8e701aa..6dd0e55 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -2104,6 +2104,136 @@ The commands accepted in walsender mode are:
 
 </sect1>
 
+<sect1 id="protocol-logical-replication">
+ <title>Logical Streaming Replication Protocol</title>
+
+ <para>
+  This section describes the logical replication protocol which is the message
+  flow started by the <literal>START_REPLICATION</literal>
+  <literal>SLOT</literal> <replaceable class="parameter">slot_name</>
+  <literal>LOGICAL</literal> replication command.
+ </para>
+
+ <para>
+  The logical streaming replication protocol builds on the primitives of
+  physical streaming replication protocol.
+ </para>
+
+ <sect2 id="protocol-logical-replication-params">
+  <title>Logical Streaming Replication Parameters</title>
+
+  <para>
+   The logical replication  <literal>START_REPLICATION</literal> command
+   accepts following parameters:
+
+   <variablelist>
+    <varlistentry>
+     <term>
+      proto_version
+     </term>
+     <listitem>
+      <para>
+       Protocol version. Currently only version <literal>1</literal> is
+       supported.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term>
+      encoding
+     </term>
+     <listitem>
+      <para>
+       Name of the encoding. This currently correspond to the encoding of the
+       server.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term>
+      publication_names
+     </term>
+     <listitem>
+      <para>
+       Comma separated list of publication names for which to subscribe
+       (receive changes). See
+       <xref linkend="logical-replication-publication"> for more info.
+      </para>
+     </listitem>
+    </varlistentry>
+   </variablelist>
+
+  </para>
+ </sect2>
+
+ <sect2 id="protocol-logical-messages">
+  <title>Logical Replication Protocol Messages</title>
+
+  <para>
+   The individual protocol messages are discussed in the following
+   sub-sections. Individual messages are describer in
+   <xref linkend="protocol-logicalrep-message-formats"> section.
+  </para>
+
+  <para>
+   All top-level protocol messages begin with a message type byte.
+   While represented in code as a character, this is a signed byte with no
+   associated encoding.
+  </para>
+
+  <para>
+   Since the streaming replication protocol supplies a message length there
+   is no need for top-level protocol messages to embed a length in their
+   header.
+  </para>
+
+ </sect2>
+
+ <sect2 id="protocol-logical-messages-flow">
+  <title>Logical Replication Protocol Message Flow</title>
+
+  <para>
+   With the exception of the <literal>START_REPLICATION</literal> command and
+   the replay progress messages, all information flows only from the backend
+   to the frontend.
+  </para>
+
+  <para>
+   The logical replication protocol sends individual transactions one by one.
+   This means that all messages between two Begin and Commit messages all
+   belong to the same transaction.
+  </para>
+
+  <para>
+   Every sent transaction contains zero or more DML messages (Insert,
+   Update, Delete) and in case of cascaded setup it can also contain Origin
+   messages. The origin message indicated that the transaction originated on
+   different replication node. Since replication node in the scope of logical
+   replication protocol can be pretty much anything, the only identifier
+   is the origin name. It's downstream responsibility to handle this as
+   needed (if needed). The Origin message is always sent before any DML
+   messages in the transaction.
+  </para>
+
+  <para>
+   Every DML message contains arbitraty relation id, which can be mapped to
+   an id in the Relation messages. The Relation describe the schema of the
+   given relation. The Relation message is sent for a given relation either
+   because it's the first time we send DML message for given relation in
+   current session or because the relation definition has changed since the
+   last Relation message was sent for it. The protocol assumes that the client
+   is capable of caching the metadata for as many relations as needed.
+  </para>
+
+  <para>
+  </para>
+
+ </sect2>
+
+</sect1>
+
 <sect1 id="protocol-message-types">
 <title>Message Data Types</title>
 
@@ -5112,6 +5242,588 @@ not line breaks.
 
 </sect1>
 
+<sect1 id="protocol-logicalrep-message-formats">
+<title>Logical Replication Message Formats</title>
+
+<para>
+This section describes the detailed format of each logical replication message.
+These messages are returned either by replication slot SQL interface or by
+WalSender. In case of WalSender they are encapsulated inside the replication
+protocol WAL messages as described in <xref linkend="protocol-replication">
+section and generaly obey same message flow as physical replication.
+</para>
+
+<variablelist>
+
+<varlistentry>
+<term>
+Begin
+</term>
+<listitem>
+<para>
+
+<variablelist>
+<varlistentry>
+<term>
+        Byte1('B')
+</term>
+<listitem>
+<para>
+                Identifies the message as a begin message.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int64
+</term>
+<listitem>
+<para>
+                The final LSN of the transaction.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int64
+</term>
+<listitem>
+<para>
+                Commit timestamp of the transaction.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int32
+</term>
+<listitem>
+<para>
+                Xid of the transaction.
+</para>
+</listitem>
+</varlistentry>
+
+</variablelist>
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+Commit
+</term>
+<listitem>
+<para>
+
+<variablelist>
+<varlistentry>
+<term>
+        Byte1('C')
+</term>
+<listitem>
+<para>
+                Identifies the message as a commit message.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int64
+</term>
+<listitem>
+<para>
+                The LSN of the commit.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int64
+</term>
+<listitem>
+<para>
+                The end LSN of the transaction.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int64
+</term>
+<listitem>
+<para>
+                Commit timestamp of the transaction.
+</para>
+</listitem>
+</varlistentry>
+
+</variablelist>
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+Origin
+</term>
+<listitem>
+<para>
+
+<variablelist>
+<varlistentry>
+<term>
+        Byte1('O')
+</term>
+<listitem>
+<para>
+                Identifies the message as an origin message.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int64
+</term>
+<listitem>
+<para>
+                The LSN of the commit on the origin server.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int8
+</term>
+<listitem>
+<para>
+                Length of the origin name (including the NULL-termination
+                character).
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        String
+</term>
+<listitem>
+<para>
+                Name of the origin.
+</para>
+</listitem>
+</varlistentry>
+
+</variablelist>
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+Relation
+</term>
+<listitem>
+<para>
+
+<variablelist>
+<varlistentry>
+<term>
+        Byte1('R')
+</term>
+<listitem>
+<para>
+                Identifies the message as an relation message.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int32
+</term>
+<listitem>
+<para>
+                Id of the relation.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int8
+</term>
+<listitem>
+<para>
+                Length of the namespace name (including the NULL-termination
+                character).
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        String
+</term>
+<listitem>
+<para>
+                Namespace.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int8
+</term>
+<listitem>
+<para>
+                Length of the relation name (including the NULL-termination
+                character).
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        String
+</term>
+<listitem>
+<para>
+                Relation name.
+</para>
+</listitem>
+</varlistentry>
+</variablelist>
+
+</para>
+
+<para>
+This message is always followed by Attributes message.
+</para>
+
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+Attrinutes
+</term>
+<listitem>
+<para>
+
+<variablelist>
+<varlistentry>
+<term>
+        Byte1('A')
+</term>
+<listitem>
+<para>
+                Identifies the message as an attributes message.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int16
+</term>
+<listitem>
+<para>
+                Number of columns.
+</para>
+</listitem>
+</varlistentry>
+</variablelist>
+        Next, the following submessage appears for each column:
+<variablelist>
+<varlistentry>
+<term>
+        Byte1('C')
+</term>
+<listitem>
+<para>
+                Start of column block.
+</para>
+</listitem>
+</varlistentry><varlistentry>
+<term>
+        Int8
+</term>
+<listitem>
+<para>
+                Flags for the column. Currently can be either 0 for no flags
+                or one which marks the column as part of the key.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int8
+</term>
+<listitem>
+<para>
+                Length of column name (including the NULL-termination
+                character).
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        String
+</term>
+<listitem>
+<para>
+                Name of the column.
+</para>
+</listitem>
+</varlistentry>
+
+</variablelist>
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+Insert
+</term>
+<listitem>
+<para>
+
+<variablelist>
+<varlistentry>
+<term>
+        Byte1('I')
+</term>
+<listitem>
+<para>
+                Identifies the message as an insert message.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int32
+</term>
+<listitem>
+<para>
+                Id of the relation corresponding to the id in the relation
+                message.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Byte1('N')
+</term>
+<listitem>
+<para>
+                Identifies the following TupleData message as a new tuple.
+</para>
+</listitem>
+</varlistentry>
+
+</variablelist>
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+Update
+</term>
+<listitem>
+<para>
+
+<variablelist>
+<varlistentry>
+<term>
+        Byte1('U')
+</term>
+<listitem>
+<para>
+                Identifies the message as an update message.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int32
+</term>
+<listitem>
+<para>
+                Id of the relation corresponding to the id in the relation
+                message.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Byte1('O')
+</term>
+<listitem>
+<para>
+                Identifies the following TupleData message as a old tuple.
+                This field is optional and is only present if table in which
+                the update happened has REPLICA IDENTITY set to FULL or when
+                the REPLICA IDENTITY index values have changed.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Byte1('N')
+</term>
+<listitem>
+<para>
+                Identifies the following TupleData message as a new tuple.
+</para>
+</listitem>
+</varlistentry>
+
+</variablelist>
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+Delete
+</term>
+<listitem>
+<para>
+
+<variablelist>
+<varlistentry>
+<term>
+        Byte1('D')
+</term>
+<listitem>
+<para>
+                Identifies the message as a delete message.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int32
+</term>
+<listitem>
+<para>
+                Id of the relation corresponding to the id in the relation
+                message.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Byte1('O')
+</term>
+<listitem>
+<para>
+                Identifies the following TupleData message as the old tuple
+                (deleted tuple).
+</para>
+</listitem>
+</varlistentry>
+
+</variablelist>
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+TupleData
+</term>
+<listitem>
+<para>
+
+<variablelist>
+<varlistentry>
+<term>
+        Byte1('T')
+</term>
+<listitem>
+<para>
+                Identifies the message as an tuple data message.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int16
+</term>
+<listitem>
+<para>
+                Number of columns.
+</para>
+</listitem>
+</varlistentry>
+</variablelist>
+        Next, one of the following submessages appears for each column:
+<variablelist>
+<varlistentry>
+<term>
+        Byte1('n')
+</term>
+<listitem>
+<para>
+                Idenfifies the data as NULL value.
+</para>
+</listitem>
+</varlistentry>
+</variablelist>
+        Or
+<variablelist>
+<varlistentry>
+<term>
+        Byte1('u')
+</term>
+<listitem>
+<para>
+                Idenfifies unchanged TOASTed value (the actual value is not
+                sent).
+</para>
+</listitem>
+</varlistentry>
+</variablelist>
+        Or
+<variablelist>
+<varlistentry>
+<term>
+        Byte1('t')
+</term>
+<listitem>
+<para>
+                Idenfifies the data as text formatted value.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int32
+</term>
+<listitem>
+<para>
+                Length of the column value.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        String
+</term>
+<listitem>
+<para>
+                The text value.
+</para>
+</listitem>
+</varlistentry>
+
+</variablelist>
+</para>
+</listitem>
+</varlistentry>
+
+</variablelist>
+
+</sect1>
+
 <sect1 id="protocol-changes">
 <title>Summary of Changes since Protocol 2.0</title>
 
diff --git a/src/Makefile b/src/Makefile
index b526be7..9723a1e 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -22,6 +22,7 @@ SUBDIRS = \
 	include \
 	interfaces \
 	backend/replication/libpqwalreceiver \
+	backend/replication/pgoutput \
 	fe_utils \
 	bin \
 	pl \
diff --git a/src/backend/replication/logical/Makefile b/src/backend/replication/logical/Makefile
index e4b093b..438811e 100644
--- a/src/backend/replication/logical/Makefile
+++ b/src/backend/replication/logical/Makefile
@@ -14,7 +14,7 @@ include $(top_builddir)/src/Makefile.global
 
 override CPPFLAGS := -I$(srcdir) $(CPPFLAGS)
 
-OBJS = decode.o logical.o logicalfuncs.o message.o origin.o publication.o \
-	   reorderbuffer.o snapbuild.o subscription.o
+OBJS = decode.o logical.o logicalfuncs.o message.o origin.o proto.o \
+	   publication.o reorderbuffer.o snapbuild.o subscription.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/replication/logical/proto.c b/src/backend/replication/logical/proto.c
new file mode 100644
index 0000000..2b82495
--- /dev/null
+++ b/src/backend/replication/logical/proto.c
@@ -0,0 +1,641 @@
+/*-------------------------------------------------------------------------
+ *
+ * proto.c
+ * 		logical replication protocol functions
+ *
+ * Copyright (c) 2015, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		src/backend/replication/logical/proto.c
+ *
+ * TODO
+ *		unaligned access
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "miscadmin.h"
+
+#include "access/htup_details.h"
+#include "access/heapam.h"
+
+#include "access/sysattr.h"
+#include "access/tuptoaster.h"
+#include "access/xact.h"
+
+#include "catalog/catversion.h"
+#include "catalog/index.h"
+
+#include "catalog/namespace.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_database.h"
+#include "catalog/pg_namespace.h"
+#include "catalog/pg_type.h"
+
+#include "commands/dbcommands.h"
+
+#include "executor/spi.h"
+
+#include "libpq/pqformat.h"
+
+#include "mb/pg_wchar.h"
+
+#include "nodes/makefuncs.h"
+
+#include "replication/logicalproto.h"
+#include "replication/reorderbuffer.h"
+
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "utils/timestamp.h"
+#include "utils/typcache.h"
+
+#define IS_REPLICA_IDENTITY	1
+
+static void logicalrep_write_attrs(StringInfo out, Relation rel);
+static void logicalrep_write_tuple(StringInfo out, Relation rel,
+								   HeapTuple tuple);
+
+static void logicalrep_read_attrs(StringInfo in, char ***attrnames,
+								  int *nattrnames);
+static void logicalrep_read_tuple(StringInfo in, LogicalRepTupleData *tuple);
+
+
+/*
+ * Given a List of strings, return it as single comma separated
+ * string, quoting identifiers as needed.
+ *
+ * This is essentially the reverse of SplitIdentifierString.
+ *
+ * The caller should free the result.
+ */
+static char *
+stringlist_to_identifierstr(List *strings)
+{
+	ListCell *lc;
+	StringInfoData res;
+	bool first = true;
+
+	initStringInfo(&res);
+
+	foreach (lc, strings)
+	{
+		if (first)
+			first = false;
+		else
+			appendStringInfoChar(&res, ',');
+
+		appendStringInfoString(&res, quote_identifier(strVal(lfirst(lc))));
+	}
+
+	return res.data;
+}
+
+/*
+ * Build string of options for logical replication plugin.
+ */
+char *
+logicalrep_build_options(List *publications)
+{
+	StringInfoData	options;
+	char		   *publicationstr;
+
+	initStringInfo(&options);
+	appendStringInfo(&options, "proto_version '%u'", LOGICALREP_PROTO_VERSION_NUM);
+	appendStringInfo(&options, ", encoding %s",
+					 quote_literal_cstr(GetDatabaseEncodingName()));
+	appendStringInfo(&options, ", pg_version '%u'", PG_VERSION_NUM);
+	publicationstr = stringlist_to_identifierstr(publications);
+	appendStringInfo(&options, ", publication_names %s",
+					 quote_literal_cstr(publicationstr));
+	pfree(publicationstr);
+
+	return options.data;
+}
+
+/*`
+ * Write BEGIN to the output stream.
+ */
+void
+logicalrep_write_begin(StringInfo out, ReorderBufferTXN *txn)
+{
+	pq_sendbyte(out, 'B');		/* BEGIN */
+
+	/* fixed fields */
+	pq_sendint64(out, txn->final_lsn);
+	pq_sendint64(out, txn->commit_time);
+	pq_sendint(out, txn->xid, 4);
+}
+
+/*
+ * Read transaction BEGIN from the stream.
+ */
+void
+logicalrep_read_begin(StringInfo in, XLogRecPtr *remote_lsn,
+					  TimestampTz *committime, TransactionId *remote_xid)
+{
+	/* read fields */
+	*remote_lsn = pq_getmsgint64(in);
+	Assert(*remote_lsn != InvalidXLogRecPtr);
+	*committime = pq_getmsgint64(in);
+	*remote_xid = pq_getmsgint(in, 4);
+}
+
+
+/*
+ * Write COMMIT to the output stream.
+ */
+void
+logicalrep_write_commit(StringInfo out, ReorderBufferTXN *txn,
+						XLogRecPtr commit_lsn)
+{
+	pq_sendbyte(out, 'C');		/* sending COMMIT */
+
+	/* send fixed fields */
+	pq_sendint64(out, commit_lsn);
+	pq_sendint64(out, txn->end_lsn);
+	pq_sendint64(out, txn->commit_time);
+}
+
+/*
+ * Read transaction COMMIT from the stream.
+ */
+void
+logicalrep_read_commit(StringInfo in, XLogRecPtr *commit_lsn,
+					   XLogRecPtr *end_lsn, TimestampTz *committime)
+{
+	/* read fields */
+	*commit_lsn = pq_getmsgint64(in);
+	*end_lsn = pq_getmsgint64(in);
+	*committime = pq_getmsgint64(in);
+}
+
+/*
+ * Write ORIGIN to the output stream.
+ */
+void
+logicalrep_write_origin(StringInfo out, const char *origin,
+						XLogRecPtr origin_lsn)
+{
+	uint8	len;
+
+	Assert(strlen(origin) < 255);
+
+	pq_sendbyte(out, 'O');		/* ORIGIN */
+
+	/* fixed fields */
+	pq_sendint64(out, origin_lsn);
+
+	/* origin */
+	len = strlen(origin) + 1;
+	pq_sendbyte(out, len);
+	pq_sendbytes(out, origin, len);
+}
+
+
+/*
+ * Read ORIGIN from the output stream.
+ */
+char *
+logicalrep_read_origin(StringInfo in, XLogRecPtr *origin_lsn)
+{
+	uint8	len;
+
+	/* fixed fields */
+	*origin_lsn = pq_getmsgint64(in);
+
+	/* origin */
+	len = pq_getmsgbyte(in);
+
+	return pnstrdup(pq_getmsgbytes(in, len), len);
+}
+
+
+/*
+ * Write INSERT to the output stream.
+ */
+void
+logicalrep_write_insert(StringInfo out,	Relation rel, HeapTuple newtuple)
+{
+	pq_sendbyte(out, 'I');		/* action INSERT */
+
+	/* use Oid as relation identifier */
+	pq_sendint(out, RelationGetRelid(rel), 4);
+
+	pq_sendbyte(out, 'N');		/* new tuple follows */
+	logicalrep_write_tuple(out, rel, newtuple);
+}
+
+/*
+ * Read INSERT from stream.
+ *
+ * Fills the new tuple.
+ */
+LogicalRepRelId
+logicalrep_read_insert(StringInfo in, LogicalRepTupleData *newtup)
+{
+	char		action;
+	LogicalRepRelId		relid;
+
+	/* read the relation id */
+	relid = pq_getmsgint(in, 4);
+
+	action = pq_getmsgbyte(in);
+	if (action != 'N')
+		elog(ERROR, "expected new tuple but got %d",
+			 action);
+
+	logicalrep_read_tuple(in, newtup);
+
+	return relid;
+}
+
+/*
+ * Write UPDATE to the output stream.
+ */
+void
+logicalrep_write_update(StringInfo out, Relation rel, HeapTuple oldtuple,
+					   HeapTuple newtuple)
+{
+	pq_sendbyte(out, 'U');		/* action UPDATE */
+
+	/* use Oid as relation identifier */
+	pq_sendint(out, RelationGetRelid(rel), 4);
+
+	if (oldtuple != NULL)
+	{
+		pq_sendbyte(out, 'O');	/* old tuple follows */
+		logicalrep_write_tuple(out, rel, oldtuple);
+	}
+
+	pq_sendbyte(out, 'N');		/* new tuple follows */
+	logicalrep_write_tuple(out, rel, newtuple);
+}
+
+/*
+ * Read UPDATE from stream.
+ */
+LogicalRepRelId
+logicalrep_read_update(StringInfo in, bool *hasoldtup,
+					   LogicalRepTupleData *oldtup,
+					   LogicalRepTupleData *newtup)
+{
+	char		action;
+	LogicalRepRelId		relid;
+
+	/* read the relation id */
+	relid = pq_getmsgint(in, 4);
+
+	/* read and verify action */
+	action = pq_getmsgbyte(in);
+	if (action != 'O' && action != 'N')
+		elog(ERROR, "expected action 'N' or 'O', got %c",
+			 action);
+
+	/* check for old tuple */
+	if (action == 'O')
+	{
+		logicalrep_read_tuple(in, oldtup);
+		*hasoldtup = true;
+		action = pq_getmsgbyte(in);
+	}
+	else
+		*hasoldtup = false;
+
+	/* check for new  tuple */
+	if (action != 'N')
+		elog(ERROR, "expected action 'N', got %c",
+			 action);
+
+	logicalrep_read_tuple(in, newtup);
+
+	return relid;
+}
+
+/*
+ * Write DELETE to the output stream.
+ */
+void
+logicalrep_write_delete(StringInfo out, Relation rel, HeapTuple oldtuple)
+{
+	pq_sendbyte(out, 'D');		/* action DELETE */
+
+	/* use Oid as relation identifier */
+	pq_sendint(out, RelationGetRelid(rel), 4);
+
+	pq_sendbyte(out, 'O');	/* old tuple follows */
+	logicalrep_write_tuple(out, rel, oldtuple);
+}
+
+/*
+ * Read DELETE from stream.
+ *
+ * Fills the old tuple.
+ */
+LogicalRepRelId
+logicalrep_read_delete(StringInfo in, LogicalRepTupleData *oldtup)
+{
+	char		action;
+	LogicalRepRelId		relid;
+
+	/* read the relation id */
+	relid = pq_getmsgint(in, 4);
+
+	/* read and verify action */
+	action = pq_getmsgbyte(in);
+	if (action != 'O')
+		elog(ERROR, "expected action 'O', got %c", action);
+
+	logicalrep_read_tuple(in, oldtup);
+
+	return relid;
+}
+
+/*
+ * Write relation description to the output stream.
+ */
+void
+logicalrep_write_rel(StringInfo out, Relation rel)
+{
+	char	   *nspname;
+	uint8		nspnamelen;
+	const char *relname;
+	uint8		relnamelen;
+
+	pq_sendbyte(out, 'R');		/* sending RELATION */
+
+	/* use Oid as relation identifier */
+	pq_sendint(out, RelationGetRelid(rel), 4);
+
+	nspname = get_namespace_name(RelationGetNamespace(rel));
+	if (nspname == NULL)
+		elog(ERROR, "cache lookup failed for namespace %u",
+			 rel->rd_rel->relnamespace);
+	nspnamelen = strlen(nspname) + 1;
+
+	relname = RelationGetRelationName(rel);
+	relnamelen = strlen(relname) + 1;
+
+	pq_sendbyte(out, nspnamelen);		/* schema name length */
+	pq_sendbytes(out, nspname, nspnamelen);
+
+	pq_sendbyte(out, relnamelen);		/* table name length */
+	pq_sendbytes(out, relname, relnamelen);
+
+	/* send the attribute info */
+	logicalrep_write_attrs(out, rel);
+
+	pfree(nspname);
+}
+
+/*
+ * Read schema.relation from stream and return as LogicalRepRelation opened in
+ * lockmode.
+ */
+LogicalRepRelation *
+logicalrep_read_rel(StringInfo in)
+{
+	LogicalRepRelation	*rel = palloc(sizeof(LogicalRepRelation));
+	int			len;
+
+	rel->remoteid = pq_getmsgint(in, 4);
+
+	/* Read relation from stream */
+	len = pq_getmsgbyte(in);
+	rel->nspname = (char *) pq_getmsgbytes(in, len);
+
+	len = pq_getmsgbyte(in);
+	rel->relname = (char *) pq_getmsgbytes(in, len);
+
+	/* Get attribute description */
+	logicalrep_read_attrs(in, &rel->attnames, &rel->natts);
+
+	return rel;
+}
+
+/*
+ * Write a tuple to the outputstream, in the most efficient format possible.
+ */
+static void
+logicalrep_write_tuple(StringInfo out, Relation rel, HeapTuple tuple)
+{
+	TupleDesc	desc;
+	Datum		values[MaxTupleAttributeNumber];
+	bool		isnull[MaxTupleAttributeNumber];
+	int			i;
+	uint16		nliveatts = 0;
+
+	desc = RelationGetDescr(rel);
+
+	pq_sendbyte(out, 'T');			/* sending TUPLE */
+
+	for (i = 0; i < desc->natts; i++)
+	{
+		if (desc->attrs[i]->attisdropped)
+			continue;
+		nliveatts++;
+	}
+	pq_sendint(out, nliveatts, 2);
+
+	/* try to allocate enough memory from the get go */
+	enlargeStringInfo(out, tuple->t_len +
+					  nliveatts * (1 + 4));
+
+	heap_deform_tuple(tuple, desc, values, isnull);
+
+	/* Write the values */
+	for (i = 0; i < desc->natts; i++)
+	{
+		HeapTuple	typtup;
+		Form_pg_type typclass;
+		Form_pg_attribute att = desc->attrs[i];
+		char   	   *outputstr;
+		int			len;
+
+		/* skip dropped columns */
+		if (att->attisdropped)
+			continue;
+
+		if (isnull[i])
+		{
+			pq_sendbyte(out, 'n');	/* null column */
+			continue;
+		}
+		else if (att->attlen == -1 && VARATT_IS_EXTERNAL_ONDISK(values[i]))
+		{
+			pq_sendbyte(out, 'u');	/* unchanged toast column */
+			continue;
+		}
+
+		typtup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(att->atttypid));
+		if (!HeapTupleIsValid(typtup))
+			elog(ERROR, "cache lookup failed for type %u", att->atttypid);
+		typclass = (Form_pg_type) GETSTRUCT(typtup);
+
+		pq_sendbyte(out, 't');	/* 'text' data follows */
+
+		outputstr =	OidOutputFunctionCall(typclass->typoutput, values[i]);
+		len = strlen(outputstr) + 1;	/* null terminated */
+		pq_sendint(out, len, 4);		/* length */
+		appendBinaryStringInfo(out, outputstr, len); /* data */
+
+		pfree(outputstr);
+
+		ReleaseSysCache(typtup);
+	}
+}
+
+/*
+ * Read tuple in remote format from stream.
+ *
+ * The returned tuple points into the input stringinfo.
+ */
+static void
+logicalrep_read_tuple(StringInfo in, LogicalRepTupleData *tuple)
+{
+	int			i;
+	int			natts;
+	char		action;
+
+	/* Check that the action is what we expect. */
+	action = pq_getmsgbyte(in);
+	if (action != 'T')
+		elog(ERROR, "expected TUPLE, got %c", action);
+
+	/* Get of attributes. */
+	natts = pq_getmsgint(in, 2);
+
+	memset(tuple->changed, 0, sizeof(tuple->changed));
+
+	/* Read the data */
+	for (i = 0; i < natts; i++)
+	{
+		char		kind;
+		int			len;
+
+		kind = pq_getmsgbyte(in);
+
+		switch (kind)
+		{
+			case 'n': /* null */
+				tuple->values[i] = NULL;
+				tuple->changed[i] = true;
+				break;
+			case 'u': /* unchanged column */
+				tuple->values[i] = (char *) 0xdeadbeef; /* make bad usage more obvious */
+				break;
+			case 't': /* text formatted value */
+				{
+					tuple->changed[i] = true;
+
+					len = pq_getmsgint(in, 4); /* read length */
+
+					/* and data */
+					tuple->values[i] = (char *) pq_getmsgbytes(in, len);
+				}
+				break;
+			default:
+				elog(ERROR, "unknown data representation type '%c'", kind);
+		}
+	}
+}
+
+
+/*
+ * Write relation attributes to the outputstream.
+ */
+static void
+logicalrep_write_attrs(StringInfo out, Relation rel)
+{
+	TupleDesc	desc;
+	int			i;
+	uint16		nliveatts = 0;
+	Bitmapset  *idattrs;
+
+	desc = RelationGetDescr(rel);
+
+	pq_sendbyte(out, 'A');			/* sending ATTRS */
+
+	/* send number of live attributes */
+	for (i = 0; i < desc->natts; i++)
+	{
+		if (desc->attrs[i]->attisdropped)
+			continue;
+		nliveatts++;
+	}
+	pq_sendint(out, nliveatts, 2);
+
+	/* fetch bitmap of REPLICATION IDENTITY attributes */
+	idattrs = RelationGetIndexAttrBitmap(rel, INDEX_ATTR_BITMAP_IDENTITY_KEY);
+
+	/* send the attributes */
+	for (i = 0; i < desc->natts; i++)
+	{
+		Form_pg_attribute att = desc->attrs[i];
+		uint8			flags = 0;
+		uint8			len;
+		const char	   *attname;
+
+		if (att->attisdropped)
+			continue;
+
+		if (bms_is_member(att->attnum - FirstLowInvalidHeapAttributeNumber,
+						  idattrs))
+			flags |= IS_REPLICA_IDENTITY;
+
+		pq_sendbyte(out, 'C');		/* column definition follows */
+		pq_sendbyte(out, flags);
+
+		attname = NameStr(att->attname);
+		len = strlen(attname) + 1;
+		pq_sendbyte(out, len);
+		pq_sendbytes(out, attname, len); /* data */
+	}
+
+	bms_free(idattrs);
+}
+
+
+/*
+ * Read relation attribute names from the outputstream.
+ */
+static void
+logicalrep_read_attrs(StringInfo in, char ***attrnames, int *nattrnames)
+{
+	int			i;
+	uint16		nattrs;
+	char	  **attrs;
+	char		blocktype;
+
+	blocktype = pq_getmsgbyte(in);
+	if (blocktype != 'A')
+		elog(ERROR, "expected ATTRS, got %c", blocktype);
+
+	nattrs = pq_getmsgint(in, 2);
+	attrs = palloc(nattrs * sizeof(char *));
+
+	/* read the attributes */
+	for (i = 0; i < nattrs; i++)
+	{
+		uint8			len;
+
+		blocktype = pq_getmsgbyte(in);		/* column definition follows */
+		if (blocktype != 'C')
+			elog(ERROR, "expected COLUMN, got %c", blocktype);
+
+		/* We ignore flags atm. */
+		(void) pq_getmsgbyte(in);
+
+		/* attribute name */
+		len = pq_getmsgbyte(in);
+		/* the string is NULL terminated */
+		attrs[i] = (char *) pq_getmsgbytes(in, len);
+	}
+
+	*attrnames = attrs;
+	*nattrnames = nattrs;
+}
diff --git a/src/backend/replication/pgoutput/Makefile b/src/backend/replication/pgoutput/Makefile
new file mode 100644
index 0000000..f25ed20
--- /dev/null
+++ b/src/backend/replication/pgoutput/Makefile
@@ -0,0 +1,33 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+#    Makefile for src/backend/replication/pgoutput
+#
+# IDENTIFICATION
+#    src/backend/replication/pgoutput
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/replication/pgoutput
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+
+override CPPFLAGS := -I$(srcdir) -I$(libpq_srcdir) $(CPPFLAGS)
+
+OBJS = pgoutput.o pgoutput_config.o $(WIN32RES)
+SHLIB_LINK = $(libpq)
+PGFILEDESC = "pgoutput - standard logical replication output plugin"
+NAME = pgoutput
+
+all: all-shared-lib
+
+include $(top_srcdir)/src/Makefile.shlib
+
+install: all installdirs install-lib
+
+installdirs: installdirs-lib
+
+uninstall: uninstall-lib
+
+clean distclean maintainer-clean: clean-lib
+	rm -f $(OBJS)
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
new file mode 100644
index 0000000..d74c7e9
--- /dev/null
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -0,0 +1,445 @@
+/*-------------------------------------------------------------------------
+ *
+ * pgoutput.c
+ *		  Logical Replication output plugin
+ *
+ * Copyright (c) 2012-2015, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		  pgoutput.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/xact.h"
+
+#include "mb/pg_wchar.h"
+
+#include "replication/logical.h"
+#include "replication/logicalproto.h"
+#include "replication/origin.h"
+#include "replication/pgoutput.h"
+#include "replication/publication.h"
+
+#include "utils/builtins.h"
+#include "utils/inval.h"
+#include "utils/memutils.h"
+
+PG_MODULE_MAGIC;
+
+extern void		_PG_output_plugin_init(OutputPluginCallbacks *cb);
+
+static void pgoutput_startup(LogicalDecodingContext * ctx,
+							  OutputPluginOptions *opt, bool is_init);
+static void pgoutput_shutdown(LogicalDecodingContext * ctx);
+static void pgoutput_begin_txn(LogicalDecodingContext *ctx,
+					ReorderBufferTXN *txn);
+static void pgoutput_commit_txn(LogicalDecodingContext *ctx,
+					 ReorderBufferTXN *txn, XLogRecPtr commit_lsn);
+static void pgoutput_change(LogicalDecodingContext *ctx,
+				 ReorderBufferTXN *txn, Relation rel,
+				 ReorderBufferChange *change);
+static bool pgoutput_origin_filter(LogicalDecodingContext *ctx,
+						RepOriginId origin_id);
+
+/* Entry in the map used to remember which relation schemas we sent. */
+typedef struct RelSchemaSyncEntry
+{
+	Oid		relid;			/* relation oid */
+	bool	schema_sent;	/* did we send the schema? */
+} RelSchemaSyncEntry;
+
+/* Map used to remember which relation schemas we sent. */
+static HTAB *RelSchemaSyncCache = NULL;
+
+static void rel_schema_sync_cache_cb(Datum arg, Oid relid);
+
+static void init_rel_schema_sync_cache(MemoryContext decoding_context);
+static void destroy_rel_schema_sync_cache(void);
+static RelSchemaSyncEntry *get_rel_schema_sync_entry(Oid relid);
+static void rel_schema_sync_cache_cb(Datum arg, Oid relid);
+
+/*
+ * Specify output plugin callbacks
+ */
+void
+_PG_output_plugin_init(OutputPluginCallbacks *cb)
+{
+	AssertVariableIsOfType(&_PG_output_plugin_init, LogicalOutputPluginInit);
+
+	cb->startup_cb = pgoutput_startup;
+	cb->begin_cb = pgoutput_begin_txn;
+	cb->change_cb = pgoutput_change;
+	cb->commit_cb = pgoutput_commit_txn;
+	cb->filter_by_origin_cb = pgoutput_origin_filter;
+	cb->shutdown_cb = pgoutput_shutdown;
+}
+
+/*
+ * Initialize this plugin
+ */
+static void
+pgoutput_startup(LogicalDecodingContext * ctx, OutputPluginOptions *opt,
+				  bool is_init)
+{
+	PGOutputData   *data = palloc0(sizeof(PGOutputData));
+	int				client_encoding;
+
+	/* Create our memory context for private allocations. */
+	data->context = AllocSetContextCreate(ctx->context,
+										  "logical replication output context",
+										  ALLOCSET_DEFAULT_MINSIZE,
+										  ALLOCSET_DEFAULT_INITSIZE,
+										  ALLOCSET_DEFAULT_MAXSIZE);
+
+	ctx->output_plugin_private = data;
+
+	/*
+	 * This is replication start and not slot initialization.
+	 *
+	 * Parse and validate options passed by the client.
+	 */
+	if (!is_init)
+	{
+		/* We can only do binary */
+		if (opt->output_type != OUTPUT_PLUGIN_BINARY_OUTPUT)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("only binary mode is supported for logical replication protocol")));
+
+		/* Parse the params and ERROR if we see any we don't recognise */
+		pgoutput_process_parameters(ctx->output_plugin_options, data);
+
+		/* Check if we support requested protol */
+		if (data->protocol_version > LOGICALREP_PROTO_VERSION_NUM)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("client sent protocol_version=%d but we only support protocol %d or lower",
+					 data->protocol_version, LOGICALREP_PROTO_VERSION_NUM)));
+
+		if (data->protocol_version < LOGICALREP_PROTO_MIN_VERSION_NUM)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("client sent protocol_version=%d but we only support protocol %d or higher",
+				 	data->protocol_version, LOGICALREP_PROTO_MIN_VERSION_NUM)));
+
+		/* Check for encoding match */
+		if (data->client_encoding == NULL ||
+			strlen(data->client_encoding) == 0)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("encoding parameter missing")));
+
+		client_encoding = pg_char_to_encoding(data->client_encoding);
+
+		if (client_encoding == -1)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("unrecognised encoding name %s passed to expected_encoding",
+							data->client_encoding)));
+
+		if (client_encoding != GetDatabaseEncoding())
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("encoding conversion for logical replication is not supported yet"),
+						 errdetail("encoding %s must be unset or match server_encoding %s",
+							 data->client_encoding, GetDatabaseEncodingName())));
+
+		if (list_length(data->publication_names) < 1)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("publication_names parameter missing")));
+
+		/* Initialize relation schema cache. */
+		init_rel_schema_sync_cache(CacheMemoryContext);
+	}
+}
+
+/*
+ * BEGIN callback
+ */
+static void
+pgoutput_begin_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn)
+{
+	bool	send_replication_origin = txn->origin_id != InvalidRepOriginId;
+
+	OutputPluginPrepareWrite(ctx, !send_replication_origin);
+	logicalrep_write_begin(ctx->out, txn);
+
+	if (send_replication_origin)
+	{
+		char *origin;
+
+		/* Message boundary */
+		OutputPluginWrite(ctx, false);
+		OutputPluginPrepareWrite(ctx, true);
+
+		/*
+		 * XXX: which behaviour we want here?
+		 *
+		 * Alternatives:
+		 *  - don't send origin message if origin name not found
+		 *    (that's what we do now)
+		 *  - throw error - that will break replication, not good
+		 *  - send some special "unknown" origin
+		 */
+		if (replorigin_by_oid(txn->origin_id, true, &origin))
+			logicalrep_write_origin(ctx->out, origin, txn->origin_lsn);
+	}
+
+	OutputPluginWrite(ctx, true);
+}
+
+/*
+ * COMMIT callback
+ */
+static void
+pgoutput_commit_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
+					 XLogRecPtr commit_lsn)
+{
+	OutputPluginPrepareWrite(ctx, true);
+	logicalrep_write_commit(ctx->out, txn, commit_lsn);
+	OutputPluginWrite(ctx, true);
+}
+
+/*
+ * Convert ReorderBufferChange to PublicationChangeType
+ */
+static PublicationChangeType
+get_publication_change_type(ReorderBufferChange *change)
+{
+	switch (change->action)
+	{
+		case REORDER_BUFFER_CHANGE_INSERT:
+			return PublicationChangeInsert;
+		case REORDER_BUFFER_CHANGE_UPDATE:
+			return PublicationChangeUpdate;
+		case REORDER_BUFFER_CHANGE_DELETE:
+			return PublicationChangeDelete;
+		default:
+			elog(ERROR, "unexpected action %d", change->action);
+	}
+}
+
+/*
+ * Sends the decoded DML over wire.
+ */
+static void
+pgoutput_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
+				Relation relation, ReorderBufferChange *change)
+{
+	PGOutputData	   *data = (PGOutputData *) ctx->output_plugin_private;
+	MemoryContext		old;
+	RelSchemaSyncEntry *relentry = NULL;
+
+	/* First check the table filter */
+	if (!publication_change_is_replicated(relation,
+										  get_publication_change_type(change),
+										  data->publication_names))
+		return;
+
+	/* Avoid leaking memory by using and resetting our own context */
+	old = MemoryContextSwitchTo(data->context);
+
+	/*
+	 * Write the relation schema if the current schema haven't been sent yet.
+	 */
+	relentry = get_rel_schema_sync_entry(RelationGetRelid(relation));
+	if (!relentry->schema_sent)
+	{
+		OutputPluginPrepareWrite(ctx, false);
+		logicalrep_write_rel(ctx->out, relation);
+		OutputPluginWrite(ctx, false);
+		relentry->schema_sent = true;
+	}
+
+	/* Send the data */
+	switch (change->action)
+	{
+		case REORDER_BUFFER_CHANGE_INSERT:
+			OutputPluginPrepareWrite(ctx, true);
+			logicalrep_write_insert(ctx->out, relation,
+									&change->data.tp.newtuple->tuple);
+			OutputPluginWrite(ctx, true);
+			break;
+		case REORDER_BUFFER_CHANGE_UPDATE:
+			{
+				HeapTuple oldtuple = change->data.tp.oldtuple ?
+					&change->data.tp.oldtuple->tuple : NULL;
+
+				OutputPluginPrepareWrite(ctx, true);
+				logicalrep_write_update(ctx->out, relation, oldtuple,
+										&change->data.tp.newtuple->tuple);
+				OutputPluginWrite(ctx, true);
+				break;
+			}
+		case REORDER_BUFFER_CHANGE_DELETE:
+			if (change->data.tp.oldtuple)
+			{
+				OutputPluginPrepareWrite(ctx, true);
+				logicalrep_write_delete(ctx->out, relation,
+										&change->data.tp.oldtuple->tuple);
+				OutputPluginWrite(ctx, true);
+			}
+			else
+				elog(DEBUG1, "didn't send DELETE change because of missing oldtuple");
+			break;
+		default:
+			Assert(false);
+	}
+
+	/* Cleanup */
+	MemoryContextSwitchTo(old);
+	MemoryContextReset(data->context);
+}
+
+/*
+ * Currently we always forward.
+ */
+static bool
+pgoutput_origin_filter(LogicalDecodingContext *ctx,
+						RepOriginId origin_id)
+{
+	return false;
+}
+
+/*
+ * Shutdown the output plugin.
+ *
+ * Note, we don't need to clean the data->context as it's child context
+ * of the ctx->context so it will be cleaned up by logical decoding machinery.
+ */
+static void
+pgoutput_shutdown(LogicalDecodingContext * ctx)
+{
+	destroy_rel_schema_sync_cache();
+}
+
+
+/*
+ * Initialize the relation schema sync cache for a decoding session.
+ *
+ * The hash table is destoyed at the end of a decoding session. While
+ * relcache invalidations still exist and will still be invoked, they
+ * will just see the null hash table global and take no action.
+ */
+static void
+init_rel_schema_sync_cache(MemoryContext cachectx)
+{
+	HASHCTL	ctl;
+	int		hash_flags;
+	MemoryContext old_ctxt;
+
+	if (RelSchemaSyncCache != NULL)
+		return;
+
+	/* Make a new hash table for the cache */
+	hash_flags = HASH_ELEM | HASH_CONTEXT;
+
+	MemSet(&ctl, 0, sizeof(ctl));
+	ctl.keysize = sizeof(Oid);
+	ctl.entrysize = sizeof(struct RelSchemaSyncEntry);
+	ctl.hcxt = cachectx;
+
+	hash_flags |= HASH_BLOBS;
+
+	old_ctxt = MemoryContextSwitchTo(cachectx);
+	RelSchemaSyncCache = hash_create("logical replication output relation cache",
+									 128, &ctl, hash_flags);
+	(void) MemoryContextSwitchTo(old_ctxt);
+
+	Assert(RelSchemaSyncCache != NULL);
+
+	CacheRegisterRelcacheCallback(rel_schema_sync_cache_cb, (Datum) 0);
+}
+
+/*
+ * Remove all the entries from our relation cache.
+ */
+static void
+destroy_rel_schema_sync_cache(void)
+{
+	HASH_SEQ_STATUS		status;
+	RelSchemaSyncEntry *entry;
+
+	if (RelSchemaSyncCache == NULL)
+		return;
+
+	hash_seq_init(&status, RelSchemaSyncCache);
+
+	while ((entry = (RelSchemaSyncEntry *) hash_seq_search(&status)) != NULL)
+	{
+		if (hash_search(RelSchemaSyncCache,	(void *) &entry->relid,
+						HASH_REMOVE, NULL) == NULL)
+			elog(ERROR, "hash table corrupted");
+	}
+
+	RelSchemaSyncCache = NULL;
+}
+
+/*
+ * Find or create entry in the relation schema cache.
+ */
+static RelSchemaSyncEntry *
+get_rel_schema_sync_entry(Oid relid)
+{
+	RelSchemaSyncEntry *entry;
+	bool				found;
+	MemoryContext		oldctx;
+
+	Assert(RelSchemaSyncCache != NULL);
+
+	/* Find cached function info, creating if not found */
+	oldctx = MemoryContextSwitchTo(CacheMemoryContext);
+	entry = (RelSchemaSyncEntry *) hash_search(RelSchemaSyncCache,
+											   (void *) &relid,
+											   HASH_ENTER, &found);
+	Assert(entry != NULL);
+	(void) MemoryContextSwitchTo(oldctx);
+
+	/* Not found means schema wasn't sent */
+	if (!found)
+		entry->schema_sent = false;
+
+	return entry;
+}
+
+/*
+ * Relcache invalidation callback
+ */
+static void
+rel_schema_sync_cache_cb(Datum arg, Oid relid)
+{
+	RelSchemaSyncEntry *entry;
+
+	/*
+	 * We can get here if the plugin was used in SQL interface as the
+	 * RelSchemaSyncCache is detroyed when the decoding finishes, but there
+	 * is no way to unregister the relcache invalidation callback.
+	 */
+	if (RelSchemaSyncCache == NULL)
+		return;
+
+	/*
+	 * Nobody keeps pointers to entries in this hash table around outside
+	 * logical decoding callback calls - but invalidation events can come in
+	 * *during* a callback if we access the relcache in the callback. Because
+	 * of that we must mark the cache entry as invalid but not remove it from
+	 * the hash while it could still be referenced, then prune it at a later
+	 * safe point.
+	 *
+	 * Getting invalidations for relations that aren't in the table is
+	 * entirely normal, since there's no way to unregister for an
+	 * invalidation event. So we don't care if it's found or not.
+	 */
+	entry = (RelSchemaSyncEntry *) hash_search(RelSchemaSyncCache, &relid,
+											   HASH_FIND, NULL);
+
+	/*
+	 * Reset schema sent status as the relation definition may have
+	 * changed.
+	 */
+	if (entry != NULL)
+		entry->schema_sent = false;
+}
diff --git a/src/backend/replication/pgoutput/pgoutput_config.c b/src/backend/replication/pgoutput/pgoutput_config.c
new file mode 100644
index 0000000..335b971
--- /dev/null
+++ b/src/backend/replication/pgoutput/pgoutput_config.c
@@ -0,0 +1,188 @@
+/*-------------------------------------------------------------------------
+ *
+ * pgoutput_param.c
+ *		  Logical Replication output plugin parameter parsing
+ *
+ * Copyright (c) 2012-2015, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		  pgoutput_param.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "catalog/catversion.h"
+
+#include "mb/pg_wchar.h"
+
+#include "nodes/makefuncs.h"
+
+#include "replication/pgoutput.h"
+
+#include "utils/builtins.h"
+#include "utils/int8.h"
+
+
+typedef enum OutputParamType
+{
+	OUTPUT_PARAM_TYPE_UNDEFINED,
+	OUTPUT_PARAM_TYPE_UINT32,
+	OUTPUT_PARAM_TYPE_STRING
+} OutputParamType;
+
+/* param parsing */
+static int get_param_key(const char * const param_name);
+static Datum get_param_value(DefElem *elem, OutputParamType type,
+							 bool null_ok);
+static uint32 parse_param_uint32(DefElem *elem);
+
+enum {
+	PARAM_UNRECOGNISED,
+	PARAM_PROTOCOL_VERSION,
+	PARAM_ENCODING,
+	PARAM_PG_VERSION,
+	PARAM_PUBLICATION_NAMES,
+} OutputPluginParamKey;
+
+typedef struct {
+	const char * const paramname;
+	int	paramkey;
+} OutputPluginParam;
+
+/* Oh, if only C had switch on strings */
+static OutputPluginParam param_lookup[] = {
+	{"proto_version", PARAM_PROTOCOL_VERSION},
+	{"encoding", PARAM_ENCODING},
+	{"pg_version", PARAM_PG_VERSION},
+	{"publication_names", PARAM_PUBLICATION_NAMES},
+	{NULL, PARAM_UNRECOGNISED}
+};
+
+
+/*
+ * Read parameters sent by client at startup and store recognised
+ * ones in the parameters PGOutputData.
+ *
+ * The data must have all client-supplied parameter fields zeroed,
+ * such as by memset or palloc0, since values not supplied
+ * by the client are not set.
+ */
+void
+pgoutput_process_parameters(List *options, PGOutputData *data)
+{
+	ListCell	*lc;
+
+	/* Examine all the other params in the message. */
+	foreach(lc, options)
+	{
+		DefElem    *elem = lfirst(lc);
+		Datum		val;
+
+		Assert(elem->arg == NULL || IsA(elem->arg, String));
+
+		/* Check each param, whether or not we recognise it */
+		switch(get_param_key(elem->defname))
+		{
+			case PARAM_PROTOCOL_VERSION:
+				val = get_param_value(elem, OUTPUT_PARAM_TYPE_UINT32, false);
+				data->protocol_version = DatumGetUInt32(val);
+				break;
+
+			case PARAM_ENCODING:
+				val = get_param_value(elem, OUTPUT_PARAM_TYPE_STRING, false);
+				data->client_encoding = DatumGetCString(val);
+				break;
+
+			case PARAM_PG_VERSION:
+				val = get_param_value(elem, OUTPUT_PARAM_TYPE_UINT32, false);
+				data->client_pg_version = DatumGetUInt32(val);
+				break;
+
+			case PARAM_PUBLICATION_NAMES:
+				val = get_param_value(elem, OUTPUT_PARAM_TYPE_STRING, false);
+				if (!SplitIdentifierString(DatumGetCString(val), ',',
+										   &data->publication_names))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_NAME),
+							 errmsg("invalid publication name syntax")));
+
+				break;
+
+			default:
+				ereport(ERROR,
+						(errmsg("Unrecognised pgoutput parameter %s",
+								elem->defname)));
+				break;
+		}
+	}
+}
+
+/*
+ * Look up a param name to find the enum value for the
+ * param, or PARAM_UNRECOGNISED if not found.
+ */
+static int
+get_param_key(const char * const param_name)
+{
+	OutputPluginParam *param = &param_lookup[0];
+
+	do {
+		if (strcmp(param->paramname, param_name) == 0)
+			return param->paramkey;
+		param++;
+	} while (param->paramname != NULL);
+
+	return PARAM_UNRECOGNISED;
+}
+
+/*
+ * Parse parameter as given type and return the value as Datum.
+ */
+static Datum
+get_param_value(DefElem *elem, OutputParamType type, bool null_ok)
+{
+	/* Check for NULL value */
+	if (elem->arg == NULL || strVal(elem->arg) == NULL)
+	{
+		if (null_ok)
+			return (Datum) 0;
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("parameter \"%s\" cannot be NULL", elem->defname)));
+	}
+
+	switch (type)
+	{
+		case OUTPUT_PARAM_TYPE_UINT32:
+			return UInt32GetDatum(parse_param_uint32(elem));
+		case OUTPUT_PARAM_TYPE_STRING:
+			return CStringGetDatum(pstrdup(strVal(elem->arg)));
+		default:
+			elog(ERROR, "unknown parameter type %d", type);
+	}
+}
+
+/*
+ * Parse string DefElem as uint32.
+ */
+static uint32
+parse_param_uint32(DefElem *elem)
+{
+	int64		res;
+
+	if (!scanint8(strVal(elem->arg), true, &res))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("could not parse integer value \"%s\" for parameter \"%s\"",
+						strVal(elem->arg), elem->defname)));
+
+	if (res > PG_UINT32_MAX || res < 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("value \"%s\" out of range for parameter \"%s\"",
+						strVal(elem->arg), elem->defname)));
+
+	return (uint32) res;
+}
diff --git a/src/include/replication/logicalproto.h b/src/include/replication/logicalproto.h
new file mode 100644
index 0000000..b69d015
--- /dev/null
+++ b/src/include/replication/logicalproto.h
@@ -0,0 +1,76 @@
+/*-------------------------------------------------------------------------
+ *
+ * logicalproto.h
+ *		logical replication protocol
+ *
+ * Copyright (c) 2015, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		src/include/replication/logicalproto.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef LOGICAL_PROTO_H
+#define LOGICAL_PROTO_H
+
+#include "replication/reorderbuffer.h"
+#include "utils/rel.h"
+
+/*
+ * Protocol capabilities
+ *
+ * LOGICAL_PROTO_VERSION_NUM is our native protocol and the greatest version
+ * we can support. PGLOGICAL_PROTO_MIN_VERSION_NUM is the oldest version we
+ * have backwards compatibility for. The client requests protocol version at
+ * connect time.
+ */
+#define LOGICALREP_PROTO_MIN_VERSION_NUM 1
+#define LOGICALREP_PROTO_VERSION_NUM 1
+
+/* Tuple coming via logical replication. */
+typedef struct LogicalRepTupleData
+{
+        char   *values[MaxTupleAttributeNumber];	/* value in out function format or NULL if values is NULL */
+        bool    changed[MaxTupleAttributeNumber];	/* marker for changed/unchanged values */
+} LogicalRepTupleData;
+
+typedef uint32	LogicalRepRelId;
+
+/* Relation information */
+typedef struct LogicalRepRelation
+{
+	/* Info coming from the remote side. */
+	LogicalRepRelId		remoteid;	/* unique id of the relation */
+	char       *nspname;			/* schema name */
+	char       *relname;			/* relation name */
+    int         natts;				/* number of columns */
+    char      **attnames;			/* column names */
+} LogicalRepRelation;
+
+extern char *logicalrep_build_options(List *publications);
+extern void logicalrep_write_begin(StringInfo out,  ReorderBufferTXN *txn);
+extern void logicalrep_read_begin(StringInfo in, XLogRecPtr *remote_lsn,
+					  TimestampTz *committime, TransactionId *remote_xid);
+extern void logicalrep_write_commit(StringInfo out, ReorderBufferTXN *txn,
+						XLogRecPtr commit_lsn);
+extern void logicalrep_read_commit(StringInfo in, XLogRecPtr *commit_lsn,
+					   XLogRecPtr *end_lsn, TimestampTz *committime);
+extern void logicalrep_write_origin(StringInfo out, const char *origin,
+						XLogRecPtr origin_lsn);
+extern char *logicalrep_read_origin(StringInfo in, XLogRecPtr *origin_lsn);
+extern void logicalrep_write_insert(StringInfo out, Relation rel,
+							 HeapTuple newtuple);
+extern LogicalRepRelId logicalrep_read_insert(StringInfo in, LogicalRepTupleData *newtup);
+extern void logicalrep_write_update(StringInfo out, Relation rel, HeapTuple oldtuple,
+					   HeapTuple newtuple);
+extern LogicalRepRelId logicalrep_read_update(StringInfo in, bool *hasoldtup,
+					   LogicalRepTupleData *oldtup,
+					   LogicalRepTupleData *newtup);
+extern void logicalrep_write_delete(StringInfo out, Relation rel,
+							 HeapTuple oldtuple);
+extern LogicalRepRelId logicalrep_read_delete(StringInfo in, LogicalRepTupleData *oldtup);
+extern void logicalrep_write_rel(StringInfo out, Relation rel);
+
+extern LogicalRepRelation *logicalrep_read_rel(StringInfo in);
+
+#endif /* LOGICALREP_PROTO_H */
diff --git a/src/include/replication/pgoutput.h b/src/include/replication/pgoutput.h
new file mode 100644
index 0000000..8cd82e7
--- /dev/null
+++ b/src/include/replication/pgoutput.h
@@ -0,0 +1,32 @@
+/*-------------------------------------------------------------------------
+ *
+ * pgoutput.h
+ *		Logical Replication output plugin
+ *
+ * Copyright (c) 2015, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		pgoutput.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PGOUTPUT_H
+#define PGOUTPUT_H
+
+
+typedef struct PGOutputData
+{
+	MemoryContext	context;			/* pricate memory context for transient
+										 * allocations */
+
+	/* client info */
+	uint32			protocol_version;
+	const char	   *client_encoding;
+	uint32			client_pg_version;
+
+	List		   *publication_names;
+} PGOutputData;
+
+extern void pgoutput_process_parameters(List *options, PGOutputData *data);
+
+#endif /* PGOUTPUT_H */
-- 
2.7.4

0004-Make-libpqwalreceiver-reentrant.patchapplication/x-patch; name=0004-Make-libpqwalreceiver-reentrant.patchDownload
From a1ff9a936566af8486179d7add4c2be0860cef18 Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Wed, 6 Jul 2016 13:59:23 +0200
Subject: [PATCH 4/6] Make libpqwalreceiver reentrant

---
 .../libpqwalreceiver/libpqwalreceiver.c            | 328 ++++++++++++++-------
 src/backend/replication/walreceiver.c              |  67 +++--
 src/include/replication/walreceiver.h              |  75 +++--
 3 files changed, 306 insertions(+), 164 deletions(-)

diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 45dccb3..f28a792 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -25,6 +25,7 @@
 #include "miscadmin.h"
 #include "replication/walreceiver.h"
 #include "utils/builtins.h"
+#include "utils/pg_lsn.h"
 
 #ifdef HAVE_POLL_H
 #include <poll.h>
@@ -38,62 +39,83 @@
 
 PG_MODULE_MAGIC;
 
-void		_PG_init(void);
+typedef struct WalReceiverConnHandle {
+	/* Current connection to the primary, if any */
+	PGconn *streamConn;
+	/* Buffer for currently read records */
+	char   *recvBuf;
+} WalReceiverConnHandle;
 
-/* Current connection to the primary, if any */
-static PGconn *streamConn = NULL;
-
-/* Buffer for currently read records */
-static char *recvBuf = NULL;
+PGDLLEXPORT WalReceiverConnHandle *_PG_walreceirver_conn_init(WalReceiverConnAPI *wrcapi);
 
 /* Prototypes for interface functions */
-static void libpqrcv_connect(char *conninfo);
-static char *libpqrcv_get_conninfo(void);
-static void libpqrcv_identify_system(TimeLineID *primary_tli);
-static void libpqrcv_readtimelinehistoryfile(TimeLineID tli, char **filename, char **content, int *len);
-static bool libpqrcv_startstreaming(TimeLineID tli, XLogRecPtr startpoint,
-						char *slotname);
-static void libpqrcv_endstreaming(TimeLineID *next_tli);
-static int	libpqrcv_receive(char **buffer, pgsocket *wait_fd);
-static void libpqrcv_send(const char *buffer, int nbytes);
-static void libpqrcv_disconnect(void);
+static void libpqrcv_connect(WalReceiverConnHandle *handle, char *conninfo,
+							 bool logical, const char *connname);
+static char *libpqrcv_get_conninfo(WalReceiverConnHandle *handle);
+static void libpqrcv_identify_system(WalReceiverConnHandle *handle,
+									 TimeLineID *primary_tli);
+static void libpqrcv_readtimelinehistoryfile(WalReceiverConnHandle *handle,
+											 TimeLineID tli, char **filename,
+											 char **content, int *len);
+static char *libpqrcv_create_slot(WalReceiverConnHandle *handle,
+								  char *slotname, bool logical,
+								  XLogRecPtr *lsn);
+static bool libpqrcv_startstreaming_physical(WalReceiverConnHandle *handle,
+								 TimeLineID tli, XLogRecPtr startpoint,
+								 char *slotname);
+static bool libpqrcv_startstreaming_logical(WalReceiverConnHandle *handle,
+								XLogRecPtr startpoint, char *slotname,
+								char *options);
+static void libpqrcv_endstreaming(WalReceiverConnHandle *handle,
+								  TimeLineID *next_tli);
+static int	libpqrcv_receive(WalReceiverConnHandle *handle, char **buffer,
+							 pgsocket *wait_fd);
+static void libpqrcv_send(WalReceiverConnHandle *handle, const char *buffer,
+						  int nbytes);
+static void libpqrcv_disconnect(WalReceiverConnHandle *handle);
 
 /* Prototypes for private functions */
-static bool libpq_select(int timeout_ms);
-static PGresult *libpqrcv_PQexec(const char *query);
+static bool libpq_select(WalReceiverConnHandle *handle,
+						 int timeout_ms);
+static PGresult *libpqrcv_PQexec(WalReceiverConnHandle *handle,
+								 const char *query);
 
 /*
- * Module load callback
+ * Module initialization callback
  */
-void
-_PG_init(void)
+WalReceiverConnHandle *
+_PG_walreceirver_conn_init(WalReceiverConnAPI *wrcapi)
 {
-	/* Tell walreceiver how to reach us */
-	if (walrcv_connect != NULL || walrcv_identify_system != NULL ||
-		walrcv_readtimelinehistoryfile != NULL ||
-		walrcv_startstreaming != NULL || walrcv_endstreaming != NULL ||
-		walrcv_receive != NULL || walrcv_send != NULL ||
-		walrcv_disconnect != NULL)
-		elog(ERROR, "libpqwalreceiver already loaded");
-	walrcv_connect = libpqrcv_connect;
-	walrcv_get_conninfo = libpqrcv_get_conninfo;
-	walrcv_identify_system = libpqrcv_identify_system;
-	walrcv_readtimelinehistoryfile = libpqrcv_readtimelinehistoryfile;
-	walrcv_startstreaming = libpqrcv_startstreaming;
-	walrcv_endstreaming = libpqrcv_endstreaming;
-	walrcv_receive = libpqrcv_receive;
-	walrcv_send = libpqrcv_send;
-	walrcv_disconnect = libpqrcv_disconnect;
+	WalReceiverConnHandle *handle;
+
+	handle = palloc0(sizeof(WalReceiverConnHandle));
+
+	/* Tell caller how to reach us */
+	wrcapi->connect = libpqrcv_connect;
+	wrcapi->get_conninfo = libpqrcv_get_conninfo;
+	wrcapi->identify_system = libpqrcv_identify_system;
+	wrcapi->readtimelinehistoryfile = libpqrcv_readtimelinehistoryfile;
+	wrcapi->create_slot = libpqrcv_create_slot;
+	wrcapi->startstreaming_physical = libpqrcv_startstreaming_physical;
+	wrcapi->startstreaming_logical = libpqrcv_startstreaming_logical;
+	wrcapi->endstreaming = libpqrcv_endstreaming;
+	wrcapi->receive = libpqrcv_receive;
+	wrcapi->send = libpqrcv_send;
+	wrcapi->disconnect = libpqrcv_disconnect;
+
+	return handle;
 }
 
 /*
  * Establish the connection to the primary server for XLOG streaming
  */
 static void
-libpqrcv_connect(char *conninfo)
+libpqrcv_connect(WalReceiverConnHandle *handle, char *conninfo, bool logical,
+				 const char *connname)
 {
 	const char *keys[5];
 	const char *vals[5];
+	int			i = 0;
 
 	/*
 	 * We use the expand_dbname parameter to process the connection string (or
@@ -102,22 +124,26 @@ libpqrcv_connect(char *conninfo)
 	 * database name is ignored by the server in replication mode, but specify
 	 * "replication" for .pgpass lookup.
 	 */
-	keys[0] = "dbname";
-	vals[0] = conninfo;
-	keys[1] = "replication";
-	vals[1] = "true";
-	keys[2] = "dbname";
-	vals[2] = "replication";
-	keys[3] = "fallback_application_name";
-	vals[3] = "walreceiver";
-	keys[4] = NULL;
-	vals[4] = NULL;
-
-	streamConn = PQconnectdbParams(keys, vals, /* expand_dbname = */ true);
-	if (PQstatus(streamConn) != CONNECTION_OK)
+	keys[i] = "dbname";
+	vals[i] = conninfo;
+	keys[++i] = "replication";
+	vals[i] = logical ? "database" : "true";
+	if (!logical)
+	{
+		keys[++i] = "dbname";
+		vals[i] = "replication";
+	}
+	keys[++i] = "fallback_application_name";
+	vals[i] = connname;
+	keys[++i] = NULL;
+	vals[i] = NULL;
+
+	handle->streamConn = PQconnectdbParams(keys, vals,
+											   /* expand_dbname = */ true);
+	if (PQstatus(handle->streamConn) != CONNECTION_OK)
 		ereport(ERROR,
 				(errmsg("could not connect to the primary server: %s",
-						PQerrorMessage(streamConn))));
+						PQerrorMessage(handle->streamConn))));
 }
 
 /*
@@ -125,17 +151,17 @@ libpqrcv_connect(char *conninfo)
  * are obfuscated.
  */
 static char *
-libpqrcv_get_conninfo(void)
+libpqrcv_get_conninfo(WalReceiverConnHandle *handle)
 {
 	PQconninfoOption *conn_opts;
 	PQconninfoOption *conn_opt;
 	PQExpBufferData	buf;
 	char	   *retval;
 
-	Assert(streamConn != NULL);
+	Assert(handle->streamConn != NULL);
 
 	initPQExpBuffer(&buf);
-	conn_opts = PQconninfo(streamConn);
+	conn_opts = PQconninfo(handle->streamConn);
 
 	if (conn_opts == NULL)
 		ereport(ERROR,
@@ -174,7 +200,8 @@ libpqrcv_get_conninfo(void)
  * timeline ID of the primary.
  */
 static void
-libpqrcv_identify_system(TimeLineID *primary_tli)
+libpqrcv_identify_system(WalReceiverConnHandle *handle,
+						 TimeLineID *primary_tli)
 {
 	PGresult   *res;
 	char	   *primary_sysid;
@@ -184,14 +211,14 @@ libpqrcv_identify_system(TimeLineID *primary_tli)
 	 * Get the system identifier and timeline ID as a DataRow message from the
 	 * primary server.
 	 */
-	res = libpqrcv_PQexec("IDENTIFY_SYSTEM");
+	res = libpqrcv_PQexec(handle, "IDENTIFY_SYSTEM");
 	if (PQresultStatus(res) != PGRES_TUPLES_OK)
 	{
 		PQclear(res);
 		ereport(ERROR,
 				(errmsg("could not receive database system identifier and timeline ID from "
 						"the primary server: %s",
-						PQerrorMessage(streamConn))));
+						PQerrorMessage(handle->streamConn))));
 	}
 	if (PQnfields(res) < 3 || PQntuples(res) != 1)
 	{
@@ -225,6 +252,43 @@ libpqrcv_identify_system(TimeLineID *primary_tli)
 }
 
 /*
+ * Create new replication slot.
+ */
+static char *
+libpqrcv_create_slot(WalReceiverConnHandle *handle, char *slotname,
+					 bool logical, XLogRecPtr *lsn)
+{
+	PGresult	   *res;
+	char			cmd[256];
+	char		   *snapshot;
+
+	if (logical)
+		snprintf(cmd, sizeof(cmd),
+				 "CREATE_REPLICATION_SLOT \"%s\" LOGICAL %s",
+				 slotname, "pgoutput");
+	else
+		snprintf(cmd, sizeof(cmd),
+				 "CREATE_REPLICATION_SLOT \"%s\"", slotname);
+
+	res = libpqrcv_PQexec(handle, cmd);
+
+	if (PQresultStatus(res) != PGRES_TUPLES_OK)
+	{
+		elog(FATAL, "could not crate replication slot \"%s\": %s\n",
+			 slotname, PQerrorMessage(handle->streamConn));
+	}
+
+	*lsn = DatumGetLSN(DirectFunctionCall1Coll(pg_lsn_in, InvalidOid,
+					  CStringGetDatum(PQgetvalue(res, 0, 1))));
+	snapshot = pstrdup(PQgetvalue(res, 0, 2));
+
+	PQclear(res);
+
+	return snapshot;
+}
+
+
+/*
  * Start streaming WAL data from given startpoint and timeline.
  *
  * Returns true if we switched successfully to copy-both mode. False
@@ -235,7 +299,9 @@ libpqrcv_identify_system(TimeLineID *primary_tli)
  * throws an ERROR.
  */
 static bool
-libpqrcv_startstreaming(TimeLineID tli, XLogRecPtr startpoint, char *slotname)
+libpqrcv_startstreaming_physical(WalReceiverConnHandle *handle,
+								 TimeLineID tli, XLogRecPtr startpoint,
+								 char *slotname)
 {
 	char		cmd[256];
 	PGresult   *res;
@@ -249,7 +315,49 @@ libpqrcv_startstreaming(TimeLineID tli, XLogRecPtr startpoint, char *slotname)
 		snprintf(cmd, sizeof(cmd),
 				 "START_REPLICATION %X/%X TIMELINE %u",
 				 (uint32) (startpoint >> 32), (uint32) startpoint, tli);
-	res = libpqrcv_PQexec(cmd);
+	res = libpqrcv_PQexec(handle, cmd);
+
+	if (PQresultStatus(res) == PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		return false;
+	}
+	else if (PQresultStatus(res) != PGRES_COPY_BOTH)
+	{
+		PQclear(res);
+		ereport(ERROR,
+				(errmsg("could not start WAL streaming: %s",
+						PQerrorMessage(handle->streamConn))));
+	}
+	PQclear(res);
+	return true;
+}
+
+/*
+ * Same as above but for logical stream.
+ *
+ * The ERROR scenario can be that the options were incorrect for given
+ * slot.
+ */
+static bool
+libpqrcv_startstreaming_logical(WalReceiverConnHandle *handle,
+								XLogRecPtr startpoint, char *slotname,
+								char *options)
+{
+	StringInfoData	cmd;
+	PGresult	   *res;
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd, "START_REPLICATION SLOT \"%s\" LOGICAL %X/%X",
+					 slotname,
+					 (uint32) (startpoint >> 32),
+					 (uint32) startpoint);
+
+	/* Send options */
+	if (options)
+		appendStringInfo(&cmd, "( %s )", options);
+
+	res = libpqrcv_PQexec(handle, cmd.data);
 
 	if (PQresultStatus(res) == PGRES_COMMAND_OK)
 	{
@@ -261,25 +369,28 @@ libpqrcv_startstreaming(TimeLineID tli, XLogRecPtr startpoint, char *slotname)
 		PQclear(res);
 		ereport(ERROR,
 				(errmsg("could not start WAL streaming: %s",
-						PQerrorMessage(streamConn))));
+						PQerrorMessage(handle->streamConn))));
 	}
 	PQclear(res);
+	pfree(cmd.data);
 	return true;
 }
 
+
 /*
  * Stop streaming WAL data. Returns the next timeline's ID in *next_tli, as
  * reported by the server, or 0 if it did not report it.
  */
 static void
-libpqrcv_endstreaming(TimeLineID *next_tli)
+libpqrcv_endstreaming(WalReceiverConnHandle *handle, TimeLineID *next_tli)
 {
 	PGresult   *res;
 
-	if (PQputCopyEnd(streamConn, NULL) <= 0 || PQflush(streamConn))
+	if (PQputCopyEnd(handle->streamConn, NULL) <= 0 ||
+		PQflush(handle->streamConn))
 		ereport(ERROR,
 			(errmsg("could not send end-of-streaming message to primary: %s",
-					PQerrorMessage(streamConn))));
+					PQerrorMessage(handle->streamConn))));
 
 	/*
 	 * After COPY is finished, we should receive a result set indicating the
@@ -291,7 +402,7 @@ libpqrcv_endstreaming(TimeLineID *next_tli)
 	 * called after receiving CopyDone from the backend - the walreceiver
 	 * never terminates replication on its own initiative.
 	 */
-	res = PQgetResult(streamConn);
+	res = PQgetResult(handle->streamConn);
 	if (PQresultStatus(res) == PGRES_TUPLES_OK)
 	{
 		/*
@@ -305,7 +416,7 @@ libpqrcv_endstreaming(TimeLineID *next_tli)
 		PQclear(res);
 
 		/* the result set should be followed by CommandComplete */
-		res = PQgetResult(streamConn);
+		res = PQgetResult(handle->streamConn);
 	}
 	else
 		*next_tli = 0;
@@ -313,23 +424,24 @@ libpqrcv_endstreaming(TimeLineID *next_tli)
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
 		ereport(ERROR,
 				(errmsg("error reading result of streaming command: %s",
-						PQerrorMessage(streamConn))));
+						PQerrorMessage(handle->streamConn))));
 	PQclear(res);
 
 	/* Verify that there are no more results */
-	res = PQgetResult(streamConn);
+	res = PQgetResult(handle->streamConn);
 	if (res != NULL)
 		ereport(ERROR,
 				(errmsg("unexpected result after CommandComplete: %s",
-						PQerrorMessage(streamConn))));
+						PQerrorMessage(handle->streamConn))));
 }
 
 /*
  * Fetch the timeline history file for 'tli' from primary.
  */
 static void
-libpqrcv_readtimelinehistoryfile(TimeLineID tli,
-								 char **filename, char **content, int *len)
+libpqrcv_readtimelinehistoryfile(WalReceiverConnHandle *handle,
+								 TimeLineID tli, char **filename,
+								 char **content, int *len)
 {
 	PGresult   *res;
 	char		cmd[64];
@@ -338,14 +450,14 @@ libpqrcv_readtimelinehistoryfile(TimeLineID tli,
 	 * Request the primary to send over the history file for given timeline.
 	 */
 	snprintf(cmd, sizeof(cmd), "TIMELINE_HISTORY %u", tli);
-	res = libpqrcv_PQexec(cmd);
+	res = libpqrcv_PQexec(handle, cmd);
 	if (PQresultStatus(res) != PGRES_TUPLES_OK)
 	{
 		PQclear(res);
 		ereport(ERROR,
 				(errmsg("could not receive timeline history file from "
 						"the primary server: %s",
-						PQerrorMessage(streamConn))));
+						PQerrorMessage(handle->streamConn))));
 	}
 	if (PQnfields(res) != 2 || PQntuples(res) != 1)
 	{
@@ -375,22 +487,23 @@ libpqrcv_readtimelinehistoryfile(TimeLineID tli,
  * This is based on pqSocketCheck.
  */
 static bool
-libpq_select(int timeout_ms)
+libpq_select(WalReceiverConnHandle *handle, int timeout_ms)
 {
 	int			ret;
 
-	Assert(streamConn != NULL);
-	if (PQsocket(streamConn) < 0)
+	Assert(handle->streamConn != NULL);
+	if (PQsocket(handle->streamConn) < 0)
 		ereport(ERROR,
 				(errcode_for_socket_access(),
-				 errmsg("invalid socket: %s", PQerrorMessage(streamConn))));
+				 errmsg("invalid socket: %s",
+						PQerrorMessage(handle->streamConn))));
 
 	/* We use poll(2) if available, otherwise select(2) */
 	{
 #ifdef HAVE_POLL
 		struct pollfd input_fd;
 
-		input_fd.fd = PQsocket(streamConn);
+		input_fd.fd = PQsocket(handle->streamConn);
 		input_fd.events = POLLIN | POLLERR;
 		input_fd.revents = 0;
 
@@ -402,7 +515,7 @@ libpq_select(int timeout_ms)
 		struct timeval *ptr_timeout;
 
 		FD_ZERO(&input_mask);
-		FD_SET(PQsocket(streamConn), &input_mask);
+		FD_SET(PQsocket(handle->streamConn), &input_mask);
 
 		if (timeout_ms < 0)
 			ptr_timeout = NULL;
@@ -413,7 +526,7 @@ libpq_select(int timeout_ms)
 			ptr_timeout = &timeout;
 		}
 
-		ret = select(PQsocket(streamConn) + 1, &input_mask,
+		ret = select(PQsocket(handle->streamConn) + 1, &input_mask,
 					 NULL, NULL, ptr_timeout);
 #endif   /* HAVE_POLL */
 	}
@@ -444,7 +557,7 @@ libpq_select(int timeout_ms)
  * Queries are always executed on the connection in streamConn.
  */
 static PGresult *
-libpqrcv_PQexec(const char *query)
+libpqrcv_PQexec(WalReceiverConnHandle *handle, const char *query)
 {
 	PGresult   *result = NULL;
 	PGresult   *lastResult = NULL;
@@ -459,7 +572,7 @@ libpqrcv_PQexec(const char *query)
 	 * Submit a query. Since we don't use non-blocking mode, this also can
 	 * block. But its risk is relatively small, so we ignore that for now.
 	 */
-	if (!PQsendQuery(streamConn, query))
+	if (!PQsendQuery(handle->streamConn, query))
 		return NULL;
 
 	for (;;)
@@ -468,7 +581,7 @@ libpqrcv_PQexec(const char *query)
 		 * Receive data until PQgetResult is ready to get the result without
 		 * blocking.
 		 */
-		while (PQisBusy(streamConn))
+		while (PQisBusy(handle->streamConn))
 		{
 			/*
 			 * We don't need to break down the sleep into smaller increments,
@@ -476,9 +589,9 @@ libpqrcv_PQexec(const char *query)
 			 * elog(FATAL) within SIGTERM signal handler if the signal arrives
 			 * in the middle of establishment of replication connection.
 			 */
-			if (!libpq_select(-1))
+			if (!libpq_select(handle, -1))
 				continue;		/* interrupted */
-			if (PQconsumeInput(streamConn) == 0)
+			if (PQconsumeInput(handle->streamConn) == 0)
 				return NULL;	/* trouble */
 		}
 
@@ -487,7 +600,7 @@ libpqrcv_PQexec(const char *query)
 		 * there are many. Since walsender will never generate multiple
 		 * results, we skip the concatenation of error messages.
 		 */
-		result = PQgetResult(streamConn);
+		result = PQgetResult(handle->streamConn);
 		if (result == NULL)
 			break;				/* query is complete */
 
@@ -497,7 +610,7 @@ libpqrcv_PQexec(const char *query)
 		if (PQresultStatus(lastResult) == PGRES_COPY_IN ||
 			PQresultStatus(lastResult) == PGRES_COPY_OUT ||
 			PQresultStatus(lastResult) == PGRES_COPY_BOTH ||
-			PQstatus(streamConn) == CONNECTION_BAD)
+			PQstatus(handle->streamConn) == CONNECTION_BAD)
 			break;
 	}
 
@@ -508,10 +621,10 @@ libpqrcv_PQexec(const char *query)
  * Disconnect connection to primary, if any.
  */
 static void
-libpqrcv_disconnect(void)
+libpqrcv_disconnect(WalReceiverConnHandle *handle)
 {
-	PQfinish(streamConn);
-	streamConn = NULL;
+	PQfinish(handle->streamConn);
+	handle->streamConn = NULL;
 }
 
 /*
@@ -531,30 +644,31 @@ libpqrcv_disconnect(void)
  * ereports on error.
  */
 static int
-libpqrcv_receive(char **buffer, pgsocket *wait_fd)
+libpqrcv_receive(WalReceiverConnHandle *handle, char **buffer,
+				 pgsocket *wait_fd)
 {
 	int			rawlen;
 
-	if (recvBuf != NULL)
-		PQfreemem(recvBuf);
-	recvBuf = NULL;
+	if (handle->recvBuf != NULL)
+		PQfreemem(handle->recvBuf);
+	handle->recvBuf = NULL;
 
 	/* Try to receive a CopyData message */
-	rawlen = PQgetCopyData(streamConn, &recvBuf, 1);
+	rawlen = PQgetCopyData(handle->streamConn, &handle->recvBuf, 1);
 	if (rawlen == 0)
 	{
 		/* Try consuming some data. */
-		if (PQconsumeInput(streamConn) == 0)
+		if (PQconsumeInput(handle->streamConn) == 0)
 			ereport(ERROR,
 					(errmsg("could not receive data from WAL stream: %s",
-							PQerrorMessage(streamConn))));
+							PQerrorMessage(handle->streamConn))));
 
 		/* Now that we've consumed some input, try again */
-		rawlen = PQgetCopyData(streamConn, &recvBuf, 1);
+		rawlen = PQgetCopyData(handle->streamConn, &handle->recvBuf, 1);
 		if (rawlen == 0)
 		{
 			/* Tell caller to try again when our socket is ready. */
-			*wait_fd = PQsocket(streamConn);
+			*wait_fd = PQsocket(handle->streamConn);
 			return 0;
 		}
 	}
@@ -562,7 +676,7 @@ libpqrcv_receive(char **buffer, pgsocket *wait_fd)
 	{
 		PGresult   *res;
 
-		res = PQgetResult(streamConn);
+		res = PQgetResult(handle->streamConn);
 		if (PQresultStatus(res) == PGRES_COMMAND_OK ||
 			PQresultStatus(res) == PGRES_COPY_IN)
 		{
@@ -574,16 +688,16 @@ libpqrcv_receive(char **buffer, pgsocket *wait_fd)
 			PQclear(res);
 			ereport(ERROR,
 					(errmsg("could not receive data from WAL stream: %s",
-							PQerrorMessage(streamConn))));
+							PQerrorMessage(handle->streamConn))));
 		}
 	}
 	if (rawlen < -1)
 		ereport(ERROR,
 				(errmsg("could not receive data from WAL stream: %s",
-						PQerrorMessage(streamConn))));
+						PQerrorMessage(handle->streamConn))));
 
 	/* Return received messages to caller */
-	*buffer = recvBuf;
+	*buffer = handle->recvBuf;
 	return rawlen;
 }
 
@@ -593,11 +707,11 @@ libpqrcv_receive(char **buffer, pgsocket *wait_fd)
  * ereports on error.
  */
 static void
-libpqrcv_send(const char *buffer, int nbytes)
+libpqrcv_send(WalReceiverConnHandle *handle, const char *buffer, int nbytes)
 {
-	if (PQputCopyData(streamConn, buffer, nbytes) <= 0 ||
-		PQflush(streamConn))
+	if (PQputCopyData(handle->streamConn, buffer, nbytes) <= 0 ||
+		PQflush(handle->streamConn))
 		ereport(ERROR,
 				(errmsg("could not send data to WAL stream: %s",
-						PQerrorMessage(streamConn))));
+						PQerrorMessage(handle->streamConn))));
 }
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 413ee3a..68e3df5 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -51,6 +51,7 @@
 #include "access/transam.h"
 #include "access/xlog_internal.h"
 #include "catalog/pg_type.h"
+#include "fmgr.h"
 #include "funcapi.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqsignal.h"
@@ -73,16 +74,9 @@ int			wal_receiver_status_interval;
 int			wal_receiver_timeout;
 bool		hot_standby_feedback;
 
-/* libpqreceiver hooks to these when loaded */
-walrcv_connect_type walrcv_connect = NULL;
-walrcv_get_conninfo_type walrcv_get_conninfo = NULL;
-walrcv_identify_system_type walrcv_identify_system = NULL;
-walrcv_startstreaming_type walrcv_startstreaming = NULL;
-walrcv_endstreaming_type walrcv_endstreaming = NULL;
-walrcv_readtimelinehistoryfile_type walrcv_readtimelinehistoryfile = NULL;
-walrcv_receive_type walrcv_receive = NULL;
-walrcv_send_type walrcv_send = NULL;
-walrcv_disconnect_type walrcv_disconnect = NULL;
+/* filled by libpqreceiver when loaded */
+static WalReceiverConnAPI *wrcapi = NULL;
+static WalReceiverConnHandle *wrchandle = NULL;
 
 #define NAPTIME_PER_CYCLE 100	/* max sleep time between cycles (100ms) */
 
@@ -202,6 +196,7 @@ WalReceiverMain(void)
 	WalRcvData *walrcv = WalRcv;
 	TimestampTz last_recv_timestamp;
 	bool		ping_sent;
+	walrcvconn_init_fn walrcvconn_init;
 
 	/*
 	 * WalRcv should be set up already (if we are a backend, we inherit this
@@ -284,15 +279,24 @@ WalReceiverMain(void)
 	sigdelset(&BlockSig, SIGQUIT);
 
 	/* Load the libpq-specific functions */
-	load_file("libpqwalreceiver", false);
-	if (walrcv_connect == NULL ||
-		walrcv_get_conninfo == NULL ||
-		walrcv_startstreaming == NULL ||
-		walrcv_endstreaming == NULL ||
-		walrcv_identify_system == NULL ||
-		walrcv_readtimelinehistoryfile == NULL ||
-		walrcv_receive == NULL || walrcv_send == NULL ||
-		walrcv_disconnect == NULL)
+	wrcapi = palloc0(sizeof(WalReceiverConnAPI));
+
+	walrcvconn_init = (walrcvconn_init_fn)
+		load_external_function("libpqwalreceiver",
+							   "_PG_walreceirver_conn_init", false, NULL);
+
+	if (walrcvconn_init == NULL)
+		elog(ERROR, "libpqwalreceiver does not declare _PG_walreceirver_conn_init symbol");
+
+	wrchandle = walrcvconn_init(wrcapi);
+	if (wrcapi->connect == NULL ||
+		wrcapi->get_conninfo == NULL ||
+		wrcapi->startstreaming_physical == NULL ||
+		wrcapi->endstreaming == NULL ||
+		wrcapi->identify_system == NULL ||
+		wrcapi->readtimelinehistoryfile == NULL ||
+		wrcapi->receive == NULL || wrcapi->send == NULL ||
+		wrcapi->disconnect == NULL)
 		elog(ERROR, "libpqwalreceiver didn't initialize correctly");
 
 	/*
@@ -306,14 +310,14 @@ WalReceiverMain(void)
 
 	/* Establish the connection to the primary for XLOG streaming */
 	EnableWalRcvImmediateExit();
-	walrcv_connect(conninfo);
+	wrcapi->connect(wrchandle, conninfo, false, "walreceiver");
 	DisableWalRcvImmediateExit();
 
 	/*
 	 * Save user-visible connection string.  This clobbers the original
 	 * conninfo, for security.
 	 */
-	tmp_conninfo = walrcv_get_conninfo();
+	tmp_conninfo = wrcapi->get_conninfo(wrchandle);
 	SpinLockAcquire(&walrcv->mutex);
 	memset(walrcv->conninfo, 0, MAXCONNINFO);
 	if (tmp_conninfo)
@@ -332,7 +336,7 @@ WalReceiverMain(void)
 		 * IDENTIFY_SYSTEM replication command,
 		 */
 		EnableWalRcvImmediateExit();
-		walrcv_identify_system(&primaryTLI);
+		wrcapi->identify_system(wrchandle, &primaryTLI);
 		DisableWalRcvImmediateExit();
 
 		/*
@@ -369,7 +373,8 @@ WalReceiverMain(void)
 		 * on the new timeline.
 		 */
 		ThisTimeLineID = startpointTLI;
-		if (walrcv_startstreaming(startpointTLI, startpoint,
+		if (wrcapi->startstreaming_physical(wrchandle, startpointTLI,
+											startpoint,
 								  slotname[0] != '\0' ? slotname : NULL))
 		{
 			if (first_stream)
@@ -421,7 +426,7 @@ WalReceiverMain(void)
 				}
 
 				/* See if we can read data immediately */
-				len = walrcv_receive(&buf, &wait_fd);
+				len = wrcapi->receive(wrchandle, &buf, &wait_fd);
 				if (len != 0)
 				{
 					/*
@@ -452,7 +457,7 @@ WalReceiverMain(void)
 							endofwal = true;
 							break;
 						}
-						len = walrcv_receive(&buf, &wait_fd);
+						len = wrcapi->receive(wrchandle, &buf, &wait_fd);
 					}
 
 					/* Let the master know that we received some data. */
@@ -568,7 +573,7 @@ WalReceiverMain(void)
 			 * our side, too.
 			 */
 			EnableWalRcvImmediateExit();
-			walrcv_endstreaming(&primaryTLI);
+			wrcapi->endstreaming(wrchandle, &primaryTLI);
 			DisableWalRcvImmediateExit();
 
 			/*
@@ -723,7 +728,7 @@ WalRcvFetchTimeLineHistoryFiles(TimeLineID first, TimeLineID last)
 							tli)));
 
 			EnableWalRcvImmediateExit();
-			walrcv_readtimelinehistoryfile(tli, &fname, &content, &len);
+			wrcapi->readtimelinehistoryfile(wrchandle, tli, &fname, &content, &len);
 			DisableWalRcvImmediateExit();
 
 			/*
@@ -775,8 +780,8 @@ WalRcvDie(int code, Datum arg)
 	SpinLockRelease(&walrcv->mutex);
 
 	/* Terminate the connection gracefully. */
-	if (walrcv_disconnect != NULL)
-		walrcv_disconnect();
+	if (wrcapi->disconnect != NULL)
+		wrcapi->disconnect(wrchandle);
 
 	/* Wake up the startup process to notice promptly that we're gone */
 	WakeupRecovery();
@@ -1146,7 +1151,7 @@ XLogWalRcvSendReply(bool force, bool requestReply)
 		 (uint32) (applyPtr >> 32), (uint32) applyPtr,
 		 requestReply ? " (reply requested)" : "");
 
-	walrcv_send(reply_message.data, reply_message.len);
+	wrcapi->send(wrchandle, reply_message.data, reply_message.len);
 }
 
 /*
@@ -1224,7 +1229,7 @@ XLogWalRcvSendHSFeedback(bool immed)
 	pq_sendint64(&reply_message, GetCurrentIntegerTimestamp());
 	pq_sendint(&reply_message, xmin, 4);
 	pq_sendint(&reply_message, nextEpoch, 4);
-	walrcv_send(reply_message.data, reply_message.len);
+	wrcapi->send(wrchandle, reply_message.data, reply_message.len);
 	if (TransactionIdIsValid(xmin))
 		master_has_standby_xmin = true;
 	else
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index cd787c9..0f42008 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -133,33 +133,56 @@ typedef struct
 
 extern WalRcvData *WalRcv;
 
-/* libpqwalreceiver hooks */
-typedef void (*walrcv_connect_type) (char *conninfo);
-extern PGDLLIMPORT walrcv_connect_type walrcv_connect;
-
-typedef char *(*walrcv_get_conninfo_type) (void);
-extern PGDLLIMPORT walrcv_get_conninfo_type walrcv_get_conninfo;
-
-typedef void (*walrcv_identify_system_type) (TimeLineID *primary_tli);
-extern PGDLLIMPORT walrcv_identify_system_type walrcv_identify_system;
-
-typedef void (*walrcv_readtimelinehistoryfile_type) (TimeLineID tli, char **filename, char **content, int *size);
-extern PGDLLIMPORT walrcv_readtimelinehistoryfile_type walrcv_readtimelinehistoryfile;
-
-typedef bool (*walrcv_startstreaming_type) (TimeLineID tli, XLogRecPtr startpoint, char *slotname);
-extern PGDLLIMPORT walrcv_startstreaming_type walrcv_startstreaming;
+struct WalReceiverConnHandle;
+typedef struct WalReceiverConnHandle WalReceiverConnHandle;
 
-typedef void (*walrcv_endstreaming_type) (TimeLineID *next_tli);
-extern PGDLLIMPORT walrcv_endstreaming_type walrcv_endstreaming;
-
-typedef int (*walrcv_receive_type) (char **buffer, pgsocket *wait_fd);
-extern PGDLLIMPORT walrcv_receive_type walrcv_receive;
-
-typedef void (*walrcv_send_type) (const char *buffer, int nbytes);
-extern PGDLLIMPORT walrcv_send_type walrcv_send;
-
-typedef void (*walrcv_disconnect_type) (void);
-extern PGDLLIMPORT walrcv_disconnect_type walrcv_disconnect;
+/* libpqwalreceiver hooks */
+typedef void (*walrcvconn_connect_fn) (
+									WalReceiverConnHandle *handle,
+									char *conninfo, bool logical,
+									const char *connname);
+typedef char *(*walrcvconn_get_conninfo_fn) (WalReceiverConnHandle *handle);
+typedef void (*walrcvconn_identify_system_fn) (WalReceiverConnHandle *handle,
+											   TimeLineID *primary_tli);
+typedef void (*walrcvconn_readtimelinehistoryfile_fn) (
+									WalReceiverConnHandle *handle,
+									TimeLineID tli, char **filename,
+									char **content, int *size);
+typedef char *(*walrcvconn_create_slot_fn) (
+									WalReceiverConnHandle *handle,
+									char *slotname, bool logical,
+									XLogRecPtr *lsn);
+typedef bool (*walrcvconn_startstreaming_physical_fn) (
+									WalReceiverConnHandle *handle,
+									TimeLineID tli, XLogRecPtr startpoint,
+									char *slotname);
+typedef bool (*walrcvconn_startstreaming_logical_fn) (
+									WalReceiverConnHandle *handle,
+									XLogRecPtr startpoint, char *slotname,
+									char *options);
+typedef void (*walrcvconn_endstreaming_fn) (WalReceiverConnHandle *handle,
+											TimeLineID *next_tli);
+typedef int (*walrcvconn_receive_fn) (WalReceiverConnHandle *handle,
+									  char **buffer, pgsocket *wait_fd);
+typedef void (*walrcvconn_send_fn) (WalReceiverConnHandle *handle,
+									const char *buffer, int nbytes);
+typedef void (*walrcvconn_disconnect_fn) (WalReceiverConnHandle *handle);
+
+typedef struct WalReceiverConnAPI {
+	walrcvconn_connect_fn					connect;
+	walrcvconn_get_conninfo_fn				get_conninfo;
+	walrcvconn_identify_system_fn			identify_system;
+	walrcvconn_readtimelinehistoryfile_fn	readtimelinehistoryfile;
+	walrcvconn_create_slot_fn				create_slot;
+	walrcvconn_startstreaming_physical_fn	startstreaming_physical;
+	walrcvconn_startstreaming_logical_fn	startstreaming_logical;
+	walrcvconn_endstreaming_fn				endstreaming;
+	walrcvconn_receive_fn					receive;
+	walrcvconn_send_fn						send;
+	walrcvconn_disconnect_fn				disconnect;
+} WalReceiverConnAPI;
+
+typedef WalReceiverConnHandle *(*walrcvconn_init_fn)(WalReceiverConnAPI *wrconn);
 
 /* prototypes for functions in walreceiver.c */
 extern void WalReceiverMain(void) pg_attribute_noreturn();
-- 
2.7.4

0005-Add-logical-replication-workers.patchapplication/x-patch; name=0005-Add-logical-replication-workers.patchDownload
From f3a729fad9e2a54e0efd83c57562791ab49b4317 Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Wed, 13 Jul 2016 20:00:06 +0200
Subject: [PATCH 5/6] Add logical replication workers

---
 doc/src/sgml/catalogs.sgml                         |    8 +-
 doc/src/sgml/filelist.sgml                         |    1 +
 doc/src/sgml/logical-replication.sgml              |  361 +++++
 doc/src/sgml/postgres.sgml                         |    1 +
 doc/src/sgml/reference.sgml                        |    6 +
 src/backend/commands/subscriptioncmds.c            |  175 ++-
 src/backend/executor/nodeModifyTable.c             |    6 +-
 src/backend/postmaster/bgworker.c                  |    6 +-
 src/backend/postmaster/postmaster.c                |   41 +
 .../libpqwalreceiver/libpqwalreceiver.c            |   28 +-
 src/backend/replication/logical/Makefile           |    5 +-
 src/backend/replication/logical/apply.c            | 1435 ++++++++++++++++++++
 src/backend/replication/logical/launcher.c         |  542 ++++++++
 src/backend/storage/ipc/ipci.c                     |    3 +
 src/backend/storage/lmgr/lwlocknames.txt           |    1 +
 src/backend/utils/misc/guc.c                       |   22 +
 src/include/executor/nodeModifyTable.h             |   20 +
 src/include/postmaster/bgworker_internals.h        |    2 +
 src/include/replication/logicalworker.h            |   41 +
 src/include/replication/walreceiver.h              |    4 +
 src/test/perl/PostgresNode.pm                      |   10 +-
 src/test/subscription/.gitignore                   |    2 +
 src/test/subscription/Makefile                     |   20 +
 src/test/subscription/README                       |   16 +
 src/test/subscription/t/001_rep_changes.pl         |   89 ++
 src/test/subscription/t/002_types.pl               |  509 +++++++
 26 files changed, 3338 insertions(+), 16 deletions(-)
 create mode 100644 doc/src/sgml/logical-replication.sgml
 create mode 100644 src/backend/replication/logical/apply.c
 create mode 100644 src/backend/replication/logical/launcher.c
 create mode 100644 src/include/replication/logicalworker.h
 create mode 100644 src/test/subscription/.gitignore
 create mode 100644 src/test/subscription/Makefile
 create mode 100644 src/test/subscription/README
 create mode 100644 src/test/subscription/t/001_rep_changes.pl
 create mode 100644 src/test/subscription/t/002_types.pl

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 84211c1..5951716 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -5134,7 +5134,8 @@
 
   <para>
    The <structname>pg_publication</structname> catalog contains
-   all publications created in the database.
+   all publications created in the database.  For more on publications
+   see <xref linkend="logical-replication-publication">.
   </para>
 
   <table>
@@ -6051,7 +6052,8 @@
 
   <para>
    The <structname>pg_subscription</structname> catalog contains
-   all existing logical replication subscriptions.
+   all existing logical replication subscriptions.  For more information
+   about logical replication see <xref linkend="logical-replication">.
   </para>
 
   <para>
@@ -6120,7 +6122,7 @@
       <entry><type>text[]</type></entry>
       <entry></entry>
       <entry>Array of subscribed publication names. For more on publications
-       see <xref linkend="publications">.
+       see <xref linkend="logical-replication-publication">.
       </entry>
      </row>
     </tbody>
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 4383711..7067a21 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -49,6 +49,7 @@
 <!ENTITY config        SYSTEM "config.sgml">
 <!ENTITY user-manag    SYSTEM "user-manag.sgml">
 <!ENTITY wal           SYSTEM "wal.sgml">
+<!ENTITY logical-replication    SYSTEM "logical-replication.sgml">
 
 <!-- programmer's guide -->
 <!ENTITY bgworker   SYSTEM "bgworker.sgml">
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
new file mode 100644
index 0000000..3179add
--- /dev/null
+++ b/doc/src/sgml/logical-replication.sgml
@@ -0,0 +1,361 @@
+<!-- doc/src/sgml/logical-replication.sgml -->
+
+<chapter id="logical-replication">
+
+  <title>Logical Replication</title>
+  <para>
+    Logical Replication is a method of replicating data objects and their
+    changes, based upon their Primary Keys (or Replication Identity). We
+    use the term Logical in contrast to Physical replication which
+    uses exact block addresses and byte-by-byte replication.
+    PostgreSQL supports both mechanisms concurrently, see
+    <xref linkend="high-availability">. Logical Replication allows
+    fine-grained control over both data replication and security.
+  </para>
+  <para>
+    Logical Replication uses a Publish and Subscribe model with one or
+    more Subscribers subscribing to one or more Publications on a
+    Provider node. Subscribers pull data from the Publications they
+    subscribe to and may subsequently re-publish data to allow
+    cascading replication or more complex configurations.
+  </para>
+  <para>
+    Logical replication typically starts with snapshot of the data on
+    the Provider database. Once that is done, the changes on Provider
+    are sent to Subscriber as they occur in real-time. The Subscriber
+    applies the data in same order as the Provider so that the
+    transactional consistency is guaranteed for the Publications within
+    single Subscription. This method of data replication is sometimes
+    referred to as transactional replication.
+  </para>
+  <para>
+    The typical use-cases for logical replication are:
+  </para>
+  <itemizedlist>
+    <listitem>
+      <para>
+        Sending incremental changes in a single database or a subset of
+        a database to Subscribers as they occur.
+      </para>
+    </listitem>
+    <listitem>
+      <para>
+        Firing triggers for individual changes as they are incoming to
+        subscriber.
+      </para>
+    </listitem>
+    <listitem>
+      <para>
+        Consolidating multiple databases into a single one (for example
+        for analytical purposes).
+      </para>
+    </listitem>
+    <listitem>
+      <para>
+        Replicating between different major versions of the PostgreSQL
+      </para>
+    </listitem>
+    <listitem>
+      <para>
+        Giving access to the replicated data to different groups of
+        users.
+      </para>
+    </listitem>
+    <listitem>
+      <para>
+        Sharing subset of the database between multiple databases.
+      </para>
+    </listitem>
+  </itemizedlist>
+  <para>
+    The Subscriber database behaves in a same way as any other
+    PostgreSQL instance and can be used as a Provider for other
+    databases by defining its own Publications. When the Subscriber is
+    treated as read-only by application, there will be no conflicts from
+    a single Subscription. On the other hand if there are other writes
+    done either by application or other Subscribers to the same set of
+    tables conflicts can arise.
+  </para>
+
+<sect1 id="logical-replication-publication">
+  <title>Publication</title>
+  <para>
+    A Publication object can be defined on any master node, owned by one
+    user. A Publication is a set of changes generated from a group of
+    tables, and might also be described as a Change Set or Replication Set.
+    Each Publication exists in only one database.
+  </para>
+  <para>
+    Publications are different from table schema and do not affect
+    how the table is accessed. Each table can be added to multiple
+    Publications if needed.  Publications may include both tables
+    and materialized views. Objects must be added explicitly, except
+    when a Publication is created for "ALL TABLES". There is no
+    default name for a Publication which specifies all tables.
+  </para>
+  <para>
+    The Publication is different from table schema, it does not affect
+    how the table is accessed and each table can be added to multiple
+    Publications as needed. All tables in the database can be added to
+    single Publication. Since the logical replication requires REPLICA
+    IDENTITY index to be present on a table for replication of UPDATEs
+    and DELETEs, the table without such index can't be added to
+    Publication which replicates UPDATEs and DELETEs.
+  </para>
+  <para>
+    Publications can choose to limit the changes they produce to show
+    any combination of INSERT, UPDATE, DELETE and TRUNCATE in a similar
+    way to the way triggers are fired by particular event types.
+  </para>
+  <para>
+    All tables added to the Publication must be accessible via SELECT
+    privilege for the user owning the Publication. Usage on the
+    Publication can be GRANTed to other users.
+  </para>
+  <para>
+    The definition of a Publication object will be included within
+    pg_dump.
+  </para>
+  <para>
+    Every Publication can have multiple Subscribers.
+  </para>
+  <para>
+    Publication is created using the <xref linkend="sql-createpublication">
+    command and may be later altered or dropped using corresponding commands.
+  </para>
+  <para>
+    The individual tables can be added and removed dynamically using
+    <xref linkend="sql-alterpublication">. Both the ADD TABLE and DROP
+    TABLE operations are transactional so the table will start or stop
+    replicating at the correct snapshot once the transaction has committed.
+  </para>
+</sect1>
+<sect1 id="logical-replication-subscription">
+  <title>Subscription</title>
+  <para>
+    A Subscription is the downstream side of the Logical Replication. The
+    node where Subscription is defined is referred to as a Subscriber.
+    Subscription defines the connection to another database and set of
+    Publications (one or more) to which it wants to be subscribed.
+  </para>
+  <para>
+    The Subscriber database behaves in a same way as any other
+    PostgreSQL instance and can be used as a Provider for other
+    databases by defining its own Publications.
+  </para>
+  <para>
+    A Subscriber may have multiple Subscriptions if desired. It is
+    possible to define multiple Subscriptions between single
+    Provider-Subscriber pair, provided that each Publications can only
+    be subscribed to from one Subscriber.
+  </para>
+  <para>
+    Each Subscription will receive changes via one replication slot (see
+    <xref linkend="streaming-replication-slots">). Additional temporary
+    replication slots may be required for the initial data synchronizations
+    of pre-existing table data.
+  </para>
+  <para>
+    Subscriptions are not dumped by pg_dump by default, but can be
+    requested using --subscriptions parameter.
+  </para>
+  <para>
+    The Subscription is added using <xref linkend="sql-createsubscription">
+    and can be stopped/resumed at any time using
+    <xref linkend="sql-altersubscription"> command or removed using
+    <xref linkend="sql-dropsubscription">.
+  </para>
+  <para>
+    When subscription is dropped and recreated the synchronization
+    information is lost. This means that the data has to be
+    resynchronized afterwards.
+  </para>
+</sect1>
+<sect1 id="logical-replication-conflicts">
+  <title>Conflicts</title>
+  <para>
+    Conflicts happen when the replicated changes is breaking any
+    specified constraints (with the exception of foreign keys which are
+    not checked). Currently conflicts are not resolved automatically and
+    cause replication to be stopped with an error until the conflict is
+    manually resolved.
+  </para>
+</sect1>
+<sect1 id="logical-replication-architecture">
+  <title>Architecture</title>
+  <para>
+    Logical replication starts by copying a snapshot of the data on
+    the Provider database. Once that is done, the changes on Provider
+    are sent to Subscriber as they occur in real-time. The Subscriber
+    applies the data in the order in which commits were made on the
+    Provider so that transactional consistency is guaranteed for the
+    Publications within any single Subscription.
+  </para>
+  <para>
+    The Logical Replication is built on the similar architecture as the
+    physical streaming replication
+    (see <xref linkend="streaming-replication">). It is implemented by
+    WalSender and the Apply processes. The WalSender starts the logical
+    decoding (described in <xref linkend="logicaldecoding">) of the WAL and
+    loads the standard logical decoding plugin (pgoutput). The plugin
+    transforms the changes read from WAL to the logical replication protocol
+    (see <xref linkend="protocol-logical-replication">) and filters the data
+    according to Publication specifications. The data are then continuously
+    transferred using the streaming replication protocol to the Apply worker
+    which maps them to the local tables and applies the individual changes as
+    they are received in exact transactional order.
+  </para>
+  <para>
+    The Apply process on Subscriber database always runs with
+    session_replication_role set to replica, which produces the normal effects
+    on triggers and constraints.
+  </para>
+  <sect2 id="logical-replication-snapshot">
+    <title>Initial snapshot</title>
+    <para>
+      The initial snapshot is taken when the replication slot for
+      Subscription is created. The existing data at that snapshot are
+      then sent over using the streaming replication protocol between
+      WalSender and Apply processes in similar way the changes are sent.
+      Once the initial data are copied, the Apply enters catch up phase
+      where it replays the changes which happened on the Provider while
+      the initial snapshot was being copied. Once the replication catches
+      up the Apply switches to normal replication streaming mode and
+      replicates transactions as they happen.
+    </para>
+  </sect2>
+  <sect2 id="logical-replication-table-resync">
+    <title>Individual table resynchronization</title>
+    <para>
+      The table can be resynchronized at any point during the normal
+      replication operation. When the table resynchronization is
+      requested a parallel instance of special kind of the Apply process
+      is started which registers its own temporary replication slot and
+      does new snapshot. Then it works same way as the initial snapshot
+      <xref linkend="logical-replication-snapshot"> with the exception that
+      it only does data copy of single table and once the catchup phase is
+      finished the control of the replication of the table is given back to
+      the main Apply process.
+    </para>
+  </sect2>
+</sect1>
+<sect1 id="logical-replication-monitoring">
+  <title>Monitoring</title>
+  <para>
+    pg_stat_replication
+  </para>
+  <para>
+    pg_stat_subscription
+  </para>
+</sect1>
+<sect1 id="logical-replication-security">
+  <title>Security</title>
+  <para>
+    Replication connection can occur in the same way as physical streaming
+    replication. It requires access to be specifically given using
+    pg_hba.conf. The role used for the replication must have
+    <literal>REPLICATION</literal> privilege <command>GRANTED</command>.
+    This gives a role access to both logical and physical replication.
+  </para>
+  <para>
+    In addition, logical replication can be accessed with the
+    <literal>SUBSCRIPTION</literal> privilege. This allows you to create
+    roles which can pull data from Publications yet cannot request
+    physical replication.
+  </para>
+  <para>
+    To create or subscribe to a Publication the user must have the
+    REPLICATION role, the SUBSCRIPTION role or be a superuser.
+  </para>
+  <para>
+    <literal>SELECT</literal> privilege is required when the user
+    adds a table to a Publication.
+    To subscribe to a Publication, user must be owner or have USAGE
+    privileges granted to the Publication.
+  </para>
+  <para>
+    To create a Subscription the user must have the
+    REPLICATION role, the SUBSCRIPTION role or be a superuser.
+    The Subscription Apply process will run in local database
+    with the privileges of the owner of the Subscription. In practice this
+    means that the owner of the Subscription must have <literal>INSERT</>,
+    <literal>UPDATE</>, <literal>DELETE</> and <literal>TRUNCATE</>
+    privileges on Subscriber to the tables that are being replicated by the
+    Subscription, or be superuser, though this is not recommended.
+  </para>
+  <para>
+    In particular, note that privileges are not re-checked as each change
+    record is read from the Provider, nor are they re-checked for each change
+    when applied. Security is checked once at startup. Concurrent REVOKEs
+    of privilege will interrupt logical replication if they have a material
+    affect on the security of the change stream.
+  </para>
+</sect1>
+<sect1 id="logical-replication-gucs">
+  <title>Logical replication related configuration parameters</title>
+  <para>
+    The Logical Replication requires several configuration options to be
+    set.
+  </para>
+  <para>
+    On the provider side the <varname>wal_level</> must be set to
+    <literal>logical</>, <varname>max_replication_slots</> has to be set to
+    at least number of Subscriptions expected to connect with some reserve
+    for table synchronization as well. And <varname>max_wal_senders</>
+    should be set to at least same as <varname>max_replication_slots</> plus
+    the number of physical replicas that are connected at the same time.
+  </para>
+  <para>
+    The Subscriber also requires the <varname>max_replication_slots</> to
+    be set. In this case it should be set to at least the number of
+    Subscriptions that will be added to the Subscriber. The
+    <varname>max_logical_replication_workers</> has to be set to at least
+    the number of Subscriptions again with some reserve for the table
+    synchronization. Additionally the <varname>max_worker_processes</> may
+    need to be adjusted to accommodate for replication workers at least
+    (<varname>max_logical_replication_workers</> + <literal>1</>). Please
+    note that some extensions and parallel queries also take worker slots
+    from <varname>max_worker_processes</>.
+  </para>
+</sect1>
+<sect1 id="logical-replication-quick-setup">
+  <title>Quick setup</title>
+  <para>
+    First set the configuration options in the postgresql.conf:
+<programlisting>
+wal_level = logical
+max_worker_processes = 10 # one per subscription + one per instance needed on subscriber
+max_logical_replication_workers = 10 # one per subscription + one per instance needed on subscriber
+max_replication_slots = 10 # one per subscription needed both provider and subscriber
+max_wal_senders = 10 # one per subscription needed on provider
+</programlisting>
+  </para>
+  <para>
+    The pg_hba.conf needs to be adjusted to allow replication (the
+    values here depend on your actual network configuration and user you
+    want to use for connecting):
+<programlisting>
+host    replication     repuser     0.0.0.0/0       md5
+</programlisting>
+  </para>
+  <para>
+    Then on Provider database:
+<programlisting>
+CREATE PUBLICATION mypub;
+ALTER PUBLICATION mypub ADD TABLE users, departments;
+</programlisting>
+  </para>
+  <para>
+    And on Subscriber database:
+<programlisting>
+CREATE SUBSCRIPTION mysub WITH CONNECTION <quote>dbname=foo host=bar user=repuser</quote> PUBLICATION mypub;
+</programlisting>
+  </para>
+  <para>
+    The above will start the replication process which synchronizes the
+    initial table contents of <literal>users</literal> and
+    <literal>departments</literal> tables and then starts replicating
+    incremental changes to those tables.
+  </para>
+</sect1>
+</chapter>
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index 0346d36..1c94015 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -159,6 +159,7 @@
   &monitoring;
   &diskusage;
   &wal;
+  &logical-replication;
   &regress;
 
  </part>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index 8acdff1..34007d3 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -54,11 +54,13 @@
    &alterOperatorClass;
    &alterOperatorFamily;
    &alterPolicy;
+   &alterPublication;
    &alterRole;
    &alterRule;
    &alterSchema;
    &alterSequence;
    &alterServer;
+   &alterSubscription;
    &alterSystem;
    &alterTable;
    &alterTableSpace;
@@ -100,11 +102,13 @@
    &createOperatorClass;
    &createOperatorFamily;
    &createPolicy;
+   &createPublication;
    &createRole;
    &createRule;
    &createSchema;
    &createSequence;
    &createServer;
+   &createSubscription;
    &createTable;
    &createTableAs;
    &createTableSpace;
@@ -144,11 +148,13 @@
    &dropOperatorFamily;
    &dropOwned;
    &dropPolicy;
+   &dropPublication;
    &dropRole;
    &dropRule;
    &dropSchema;
    &dropSequence;
    &dropServer;
+   &dropSubscription;
    &dropTable;
    &dropTableSpace;
    &dropTSConfig;
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 54d66d5..43e2853 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -29,13 +29,19 @@
 #include "catalog/pg_subscription.h"
 
 #include "commands/defrem.h"
+#include "commands/replicationcmds.h"
 
 #include "executor/spi.h"
 
 #include "nodes/makefuncs.h"
 
+#include "replication/logical.h"
+#include "replication/logicalproto.h"
+#include "replication/logicalworker.h"
+#include "replication/origin.h"
 #include "replication/reorderbuffer.h"
-#include "commands/replicationcmds.h"
+#include "replication/logicalworker.h"
+#include "replication/walreceiver.h"
 
 #include "utils/array.h"
 #include "utils/builtins.h"
@@ -164,7 +170,12 @@ CreateSubscription(CreateSubscriptionStmt *stmt)
 	bool		enabled_given;
 	bool		enabled;
 	char	   *conninfo;
+	char	   *slotname;
 	List	   *publications;
+	WalReceiverConnHandle  *wrchandle = NULL;
+	WalReceiverConnAPI	   *wrcapi = NULL;
+	walrcvconn_init_fn		walrcvconn_init;
+	XLogRecPtr	lsn;
 
 	check_replication_permissions();
 
@@ -184,6 +195,7 @@ CreateSubscription(CreateSubscriptionStmt *stmt)
 	/* Parse and check options. */
 	parse_subscription_options(stmt->options, &enabled_given, &enabled,
 							   &conninfo, &publications);
+	slotname = stmt->subname;
 
 	/* TODO: improve error messages here. */
 	if (conninfo == NULL)
@@ -202,7 +214,7 @@ CreateSubscription(CreateSubscriptionStmt *stmt)
 
 	values[Anum_pg_subscription_dbid - 1] = ObjectIdGetDatum(MyDatabaseId);
 	values[Anum_pg_subscription_subname - 1] =
-		DirectFunctionCall1(namein, CStringGetDatum(stmt->subname));
+		DirectFunctionCall1(namein, CStringGetDatum(slotname));
 	values[Anum_pg_subscription_subenabled - 1] = BoolGetDatum(enabled);
 	values[Anum_pg_subscription_subconninfo - 1] =
 		CStringGetTextDatum(conninfo);
@@ -218,13 +230,47 @@ CreateSubscription(CreateSubscriptionStmt *stmt)
 	CatalogUpdateIndexes(rel, tup);
 	heap_freetuple(tup);
 
-	ObjectAddressSet(myself, SubscriptionRelationId, suboid);
+	/*
+	 * Now that the catalog update is done, try to reserve slot at the
+	 * provider node using replication connection.
+	 */
+	wrcapi = palloc0(sizeof(WalReceiverConnAPI));
+
+	walrcvconn_init = (walrcvconn_init_fn)
+		load_external_function("libpqwalreceiver",
+							   "_PG_walreceirver_conn_init", false, NULL);
+
+	if (walrcvconn_init == NULL)
+		elog(ERROR, "libpqwalreceiver does not declare _PG_walreceirver_conn_init symbol");
+
+	wrchandle = walrcvconn_init(wrcapi);
+	if (wrcapi->connect == NULL ||
+		wrcapi->create_slot == NULL)
+		elog(ERROR, "libpqwalreceiver didn't initialize correctly");
+
+	wrcapi->connect(wrchandle, conninfo, true, stmt->subname);
+	wrcapi->create_slot(wrchandle, slotname, true, &lsn);
+	ereport(NOTICE,
+			(errmsg("created replication slot \"%s\" on provider",
+					slotname)));
+	/*
+	 * Setup replication origin tracking.
+	 * TODO: do this only when it does not already exist?
+	 */
+	replorigin_create(slotname);
+
+	/* And we are done with the remote side. */
+	wrcapi->disconnect(wrchandle);
 
 	heap_close(rel, RowExclusiveLock);
 
 	/* Make the changes visible. */
 	CommandCounterIncrement();
 
+	ApplyLauncherWakeupOnCommit();
+
+	ObjectAddressSet(myself, SubscriptionRelationId, subid);
+
 	return myself;
 }
 
@@ -302,6 +348,11 @@ AlterSubscription(AlterSubscriptionStmt *stmt)
 	heap_freetuple(tup);
 	heap_close(rel, RowExclusiveLock);
 
+	/* Make the changes visible. */
+	CommandCounterIncrement();
+
+	ApplyLauncherWakeupOnCommit();
+
 	return myself;
 }
 
@@ -313,19 +364,135 @@ DropSubscriptionById(Oid subid)
 {
 	Relation	rel;
 	HeapTuple	tup;
+	Datum		datum;
+	bool		isnull;
+	char	   *subname;
+	char	   *conninfo;
+	char	   *slotname;
+	TupleDesc	tupdesc;
+	RepOriginId	originid;
+	MemoryContext			tmpctx,
+							oldctx;
+	WalReceiverConnHandle  *wrchandle = NULL;
+	WalReceiverConnAPI	   *wrcapi = NULL;
+	walrcvconn_init_fn		walrcvconn_init;
 
 	check_replication_permissions();
 
 	rel = heap_open(SubscriptionRelationId, RowExclusiveLock);
 
+	if (GetTopTransactionIdIfAny() != InvalidTransactionId)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("DROP SUBSCRIPTION must be first action in transaction")));
+
 	tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
 
 	if (!HeapTupleIsValid(tup))
 		elog(ERROR, "cache lookup failed for subscription %u", subid);
 
+	tupdesc = RelationGetDescr(rel);
+
+	/*
+	 * Create temporary memory context to keep copy of subscription
+	 * info needed later in the execution.
+	 */
+	tmpctx = AllocSetContextCreate(TopMemoryContext,
+										  "DropSubscription Ctx",
+										  ALLOCSET_DEFAULT_MINSIZE,
+										  ALLOCSET_DEFAULT_INITSIZE,
+										  ALLOCSET_DEFAULT_MAXSIZE);
+	oldctx = MemoryContextSwitchTo(tmpctx);
+
+	/* Get subname */
+	datum = heap_getattr(tup, Anum_pg_subscription_subname, tupdesc,
+						 &isnull);
+	Assert(!isnull);
+	subname = pstrdup(NameStr(*DatumGetName(datum)));
+
+	/* Get conninfo */
+	datum = heap_getattr(tup, Anum_pg_subscription_subconninfo, tupdesc,
+						 &isnull);
+	Assert(!isnull);
+	conninfo = pstrdup(TextDatumGetCString(datum));
+
+	/* Get slotname */
+	datum = heap_getattr(tup, Anum_pg_subscription_subslotname, tupdesc,
+						 &isnull);
+	Assert(!isnull);
+	slotname = pstrdup(NameStr(*DatumGetName(datum)));
+
+	MemoryContextSwitchTo(oldctx);
+
+	/* Remove the tuple from catalog. */
 	simple_heap_delete(rel, &tup->t_self);
 
 	ReleaseSysCache(tup);
 
-	heap_close(rel, RowExclusiveLock);
+	heap_close(rel, NoLock);
+
+	originid = replorigin_by_name(slotname, true);
+	if (originid != InvalidRepOriginId)
+		replorigin_drop(originid);
+
+	/* Commit the transaction to make the change visible to laucnher. */
+	PopActiveSnapshot();
+	CommitTransactionCommand();
+
+	/* Signal the launcher so that it kills the apply proccess. */
+	ApplyLauncherWakeup();
+
+	StartTransactionCommand();
+
+	/*
+	 * Now that the catalog update is done, try to reserve slot at the
+	 * provider node using replication connection.
+	 */
+	wrcapi = palloc0(sizeof(WalReceiverConnAPI));
+
+	walrcvconn_init = (walrcvconn_init_fn)
+		load_external_function("libpqwalreceiver",
+							   "_PG_walreceirver_conn_init", false, NULL);
+
+	if (walrcvconn_init == NULL)
+		elog(ERROR, "libpqwalreceiver does not declare _PG_walreceirver_conn_init symbol");
+
+	wrchandle = walrcvconn_init(wrcapi);
+	if (wrcapi->connect == NULL ||
+		wrcapi->drop_slot == NULL)
+		elog(ERROR, "libpqwalreceiver didn't initialize correctly");
+
+	/*
+	 * We must ignore error as that would make it impossible to drop
+	 * subscription when provider is down.
+	 */
+	oldctx = CurrentMemoryContext;
+	PG_TRY();
+	{
+		wrcapi->connect(wrchandle, conninfo, true, subname);
+		wrcapi->drop_slot(wrchandle, slotname);
+		ereport(NOTICE,
+				(errmsg("dropped replication slot \"%s\" on provider",
+						slotname)));
+		wrcapi->disconnect(wrchandle);
+	}
+	PG_CATCH();
+	{
+		MemoryContext	ectx;
+		ErrorData	   *edata;
+
+		ectx = MemoryContextSwitchTo(oldctx);
+		/* Save error info */
+		edata = CopyErrorData();
+		MemoryContextSwitchTo(ectx);
+		FlushErrorState();
+
+		ereport(WARNING,
+				(errmsg("there was problem dropping the replication slot "
+						"\"%s\" on provider", slotname),
+				 errdetail("The error was: %s", edata->message),
+				 errhint("You may have to drop it manually")));
+		FreeErrorData(edata);
+	}
+	PG_END_TRY();
 }
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index af7b26c..907623b 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -232,7 +232,7 @@ ExecCheckTIDVisible(EState *estate,
  *		Returns RETURNING result if any, otherwise NULL.
  * ----------------------------------------------------------------
  */
-static TupleTableSlot *
+TupleTableSlot *
 ExecInsert(ModifyTableState *mtstate,
 		   TupleTableSlot *slot,
 		   TupleTableSlot *planSlot,
@@ -536,7 +536,7 @@ ExecInsert(ModifyTableState *mtstate,
  *		Returns RETURNING result if any, otherwise NULL.
  * ----------------------------------------------------------------
  */
-static TupleTableSlot *
+TupleTableSlot *
 ExecDelete(ItemPointer tupleid,
 		   HeapTuple oldtuple,
 		   TupleTableSlot *planSlot,
@@ -794,7 +794,7 @@ ldelete:;
  *		Returns RETURNING result if any, otherwise NULL.
  * ----------------------------------------------------------------
  */
-static TupleTableSlot *
+TupleTableSlot *
 ExecUpdate(ItemPointer tupleid,
 		   HeapTuple oldtuple,
 		   TupleTableSlot *slot,
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 699c934..fc998cd 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -93,6 +93,9 @@ struct BackgroundWorkerHandle
 
 static BackgroundWorkerArray *BackgroundWorkerData;
 
+/* Enables registration of internal background workers. */
+bool internal_bgworker_registration_in_progress = false;
+
 /*
  * Calculate shared memory needed.
  */
@@ -745,7 +748,8 @@ RegisterBackgroundWorker(BackgroundWorker *worker)
 		ereport(DEBUG1,
 		 (errmsg("registering background worker \"%s\"", worker->bgw_name)));
 
-	if (!process_shared_preload_libraries_in_progress)
+	if (!process_shared_preload_libraries_in_progress &&
+		!internal_bgworker_registration_in_progress)
 	{
 		if (!IsUnderPostmaster)
 			ereport(LOG,
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index f5c8e9d..2211532 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -113,6 +113,7 @@
 #include "postmaster/pgarch.h"
 #include "postmaster/postmaster.h"
 #include "postmaster/syslogger.h"
+#include "replication/logicalworker.h"
 #include "replication/walsender.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
@@ -416,6 +417,7 @@ static void maybe_start_bgworker(void);
 static bool CreateOptsFile(int argc, char *argv[], char *fullprogname);
 static pid_t StartChildProcess(AuxProcType type);
 static void StartAutovacuumWorker(void);
+static void register_internal_bgworkers(void);
 static void InitPostmasterDeathWatchHandle(void);
 
 /*
@@ -925,6 +927,12 @@ PostmasterMain(int argc, char *argv[])
 #endif
 
 	/*
+	 * Register internal bgworkers before we give external modules chance
+	 * to do the same.
+	 */
+	register_internal_bgworkers();
+
+	/*
 	 * process any libraries that should be preloaded at postmaster start
 	 */
 	process_shared_preload_libraries();
@@ -5641,6 +5649,39 @@ assign_backendlist_entry(RegisteredBgWorker *rw)
 }
 
 /*
+ * Register internal background workers.
+ *
+ * This is here mainly because the permanent bgworkers are normally allowed
+ * to be registered only when share preload libraries are loaded which does
+ * not work for the internal ones.
+ */
+static void
+register_internal_bgworkers(void)
+{
+	internal_bgworker_registration_in_progress = true;
+
+	/* Register the logical replication worker launcher if appropriate. */
+	if (!IsBinaryUpgrade && max_logical_replication_workers > 0)
+	{
+		BackgroundWorker bgw;
+
+		bgw.bgw_flags =	BGWORKER_SHMEM_ACCESS |
+			BGWORKER_BACKEND_DATABASE_CONNECTION;
+		bgw.bgw_start_time = BgWorkerStart_RecoveryFinished;
+		bgw.bgw_main = ApplyLauncherMain;
+		snprintf(bgw.bgw_name, BGW_MAXLEN,
+				 "logical replication launcher");
+		bgw.bgw_restart_time = 5;
+		bgw.bgw_notify_pid = 0;
+		bgw.bgw_main_arg = (Datum) 0;
+
+		RegisterBackgroundWorker(&bgw);
+	}
+
+	internal_bgworker_registration_in_progress = false;
+}
+
+/*
  * If the time is right, start one background worker.
  *
  * As a side effect, the bgworker control variables are set or reset whenever
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index f28a792..4c4d441 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -60,6 +60,7 @@ static void libpqrcv_readtimelinehistoryfile(WalReceiverConnHandle *handle,
 static char *libpqrcv_create_slot(WalReceiverConnHandle *handle,
 								  char *slotname, bool logical,
 								  XLogRecPtr *lsn);
+static void libpqrcv_drop_slot(WalReceiverConnHandle *handle, char *slotname);
 static bool libpqrcv_startstreaming_physical(WalReceiverConnHandle *handle,
 								 TimeLineID tli, XLogRecPtr startpoint,
 								 char *slotname);
@@ -96,6 +97,7 @@ _PG_walreceirver_conn_init(WalReceiverConnAPI *wrcapi)
 	wrcapi->identify_system = libpqrcv_identify_system;
 	wrcapi->readtimelinehistoryfile = libpqrcv_readtimelinehistoryfile;
 	wrcapi->create_slot = libpqrcv_create_slot;
+	wrcapi->drop_slot = libpqrcv_drop_slot;
 	wrcapi->startstreaming_physical = libpqrcv_startstreaming_physical;
 	wrcapi->startstreaming_logical = libpqrcv_startstreaming_logical;
 	wrcapi->endstreaming = libpqrcv_endstreaming;
@@ -274,7 +276,7 @@ libpqrcv_create_slot(WalReceiverConnHandle *handle, char *slotname,
 
 	if (PQresultStatus(res) != PGRES_TUPLES_OK)
 	{
-		elog(FATAL, "could not crate replication slot \"%s\": %s\n",
+		elog(ERROR, "could not crate replication slot \"%s\": %s\n",
 			 slotname, PQerrorMessage(handle->streamConn));
 	}
 
@@ -287,6 +289,28 @@ libpqrcv_create_slot(WalReceiverConnHandle *handle, char *slotname,
 	return snapshot;
 }
 
+/*
+ * Drop replication slot.
+ */
+static void
+libpqrcv_drop_slot(WalReceiverConnHandle *handle, char *slotname)
+{
+	PGresult	   *res;
+	char			cmd[256];
+
+	snprintf(cmd, sizeof(cmd),
+			 "DROP_REPLICATION_SLOT \"%s\"", slotname);
+
+	res = libpqrcv_PQexec(handle, cmd);
+
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		elog(ERROR, "could not drop replication slot \"%s\": %s\n",
+			 slotname, PQerrorMessage(handle->streamConn));
+	}
+
+	PQclear(res);
+}
 
 /*
  * Start streaming WAL data from given startpoint and timeline.
@@ -353,7 +377,7 @@ libpqrcv_startstreaming_logical(WalReceiverConnHandle *handle,
 					 (uint32) (startpoint >> 32),
 					 (uint32) startpoint);
 
-	/* Send options */
+	/* Add options */
 	if (options)
 		appendStringInfo(&cmd, "( %s )", options);
 
diff --git a/src/backend/replication/logical/Makefile b/src/backend/replication/logical/Makefile
index 438811e..ab6e11e 100644
--- a/src/backend/replication/logical/Makefile
+++ b/src/backend/replication/logical/Makefile
@@ -14,7 +14,8 @@ include $(top_builddir)/src/Makefile.global
 
 override CPPFLAGS := -I$(srcdir) $(CPPFLAGS)
 
-OBJS = decode.o logical.o logicalfuncs.o message.o origin.o proto.o \
-	   publication.o reorderbuffer.o snapbuild.o subscription.o
+OBJS = apply.o decode.o launcher.o logical.o logicalfuncs.o message.o \
+	   origin.o proto.o publication.o reorderbuffer.o snapbuild.o \
+	   subscription.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/replication/logical/apply.c b/src/backend/replication/logical/apply.c
new file mode 100644
index 0000000..eb7af19
--- /dev/null
+++ b/src/backend/replication/logical/apply.c
@@ -0,0 +1,1435 @@
+/*-------------------------------------------------------------------------
+ * apply.c
+ *	   PostgreSQL logical replication
+ *
+ * Copyright (c) 2012-2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/replication/logical/apply.c
+ *
+ * NOTES
+ *	  This file contains the worker which applies logical changes as they come
+ *	  from remote logical replication stream.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "funcapi.h"
+
+#include "access/xact.h"
+#include "access/xlog_internal.h"
+
+#include "catalog/namespace.h"
+
+#include "commands/trigger.h"
+
+#include "executor/executor.h"
+#include "executor/nodeModifyTable.h"
+
+#include "libpq/pqformat.h"
+#include "libpq/pqsignal.h"
+
+#include "mb/pg_wchar.h"
+
+#include "optimizer/planner.h"
+
+#include "parser/parse_relation.h"
+
+#include "postmaster/bgworker.h"
+#include "postmaster/postmaster.h"
+
+#include "replication/decode.h"
+#include "replication/logical.h"
+#include "replication/logicalproto.h"
+#include "replication/logicalworker.h"
+#include "replication/reorderbuffer.h"
+#include "replication/origin.h"
+#include "replication/snapbuild.h"
+#include "replication/subscription.h"
+#include "replication/walreceiver.h"
+
+#include "rewrite/rewriteHandler.h"
+
+#include "storage/bufmgr.h"
+#include "storage/ipc.h"
+#include "storage/lmgr.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+
+#include "utils/builtins.h"
+#include "utils/catcache.h"
+#include "utils/guc.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/timeout.h"
+#include "utils/tqual.h"
+#include "utils/syscache.h"
+
+typedef struct FlushPosition
+{
+	dlist_node node;
+	XLogRecPtr local_end;
+	XLogRecPtr remote_end;
+} FlushPosition;
+
+static dlist_head lsn_mapping = DLIST_STATIC_INIT(lsn_mapping);
+
+static MemoryContext	ApplyContext;
+static bool				in_remote_transaction = false;
+
+static Subscription	   *MySubscription = NULL;
+static bool				got_SIGTERM = false;
+
+typedef struct LogicalRepRelMapEntry {
+	LogicalRepRelation	remoterel;		/* key is remoterel.remoteid */
+
+	/* Mapping to local relation, filled as needed. */
+    Oid					reloid;			/* local relation id */
+    Relation			rel;			/* relcache entry */
+	int                *attmap;			/* map of remote attributes to
+										 * local ones */
+	AttInMetadata	   *attin;			/* cached info used in type
+										 * conversion */
+} LogicalRepRelMapEntry;
+
+static HTAB *LogicalRepRelMap = NULL;
+
+/* filled by libpqreceiver when loaded */
+static WalReceiverConnAPI *wrcapi = NULL;
+static WalReceiverConnHandle *wrchandle = NULL;
+
+static void send_feedback(XLogRecPtr recvpos, int64 now, bool force);
+void pglogical_apply_main(Datum main_arg);
+
+static bool tuple_find_by_replidx(Relation rel, LockTupleMode lockmode,
+					  TupleTableSlot *searchslot, TupleTableSlot *slot);
+
+
+
+/*
+ * Relcache invalidation callback for our relation map cache.
+ */
+static void
+logicalreprelmap_invalidate_cb(Datum arg, Oid reloid)
+{
+	LogicalRepRelMapEntry  *entry;
+
+	/* Just to be sure. */
+	if (LogicalRepRelMap == NULL)
+		return;
+
+	if (reloid != InvalidOid)
+	{
+		HASH_SEQ_STATUS status;
+
+		hash_seq_init(&status, LogicalRepRelMap);
+
+		/* TODO, use inverse lookup hastable? */
+		while ((entry = (LogicalRepRelMapEntry *) hash_seq_search(&status)) != NULL)
+		{
+			if (entry->reloid == reloid)
+				entry->reloid = InvalidOid;
+		}
+	}
+	else
+	{
+		/* invalidate all cache entries */
+		HASH_SEQ_STATUS status;
+
+		hash_seq_init(&status, LogicalRepRelMap);
+
+		while ((entry = (LogicalRepRelMapEntry *) hash_seq_search(&status)) != NULL)
+			entry->reloid = InvalidOid;
+	}
+}
+
+/*
+ * Initialize the relation map cache.
+ */
+static void
+remoterelmap_init(void)
+{
+	HASHCTL		ctl;
+
+	/* Make sure we've initialized CacheMemoryContext. */
+	if (CacheMemoryContext == NULL)
+		CreateCacheMemoryContext();
+
+	/* Initialize the hash table. */
+	MemSet(&ctl, 0, sizeof(ctl));
+	ctl.keysize = sizeof(uint32);
+	ctl.entrysize = sizeof(LogicalRepRelMapEntry);
+	ctl.hcxt = CacheMemoryContext;
+
+	LogicalRepRelMap = hash_create("logicalrep relation map cache", 128, &ctl,
+								   HASH_ELEM | HASH_CONTEXT);
+
+	/* Watch for invalidation events. */
+	CacheRegisterRelcacheCallback(logicalreprelmap_invalidate_cb,
+								  (Datum) 0);
+}
+
+/*
+ * Free the entry of a relation map cache.
+ */
+static void
+remoterelmap_free_entry(LogicalRepRelMapEntry *entry)
+{
+	LogicalRepRelation *remoterel;
+
+	remoterel = &entry->remoterel;
+
+	pfree(remoterel->nspname);
+	pfree(remoterel->relname);
+
+	if (remoterel->natts > 0)
+	{
+		int	i;
+
+		for (i = 0; i < remoterel->natts; i++)
+			pfree(remoterel->attnames[i]);
+
+		pfree(remoterel->attnames);
+	}
+
+	if (entry->attmap)
+		pfree(entry->attmap);
+
+	remoterel->natts = 0;
+	entry->reloid = InvalidOid;
+	entry->rel = NULL;
+}
+
+/*
+ * Add new entry or update existing entry in the relation map cache.
+ *
+ * Called when new relation mapping is sent by the provider to update
+ * our expected view of incoming data from said provider.
+ */
+static void
+remoterelmap_update(LogicalRepRelation *remoterel)
+{
+	MemoryContext			oldctx;
+	LogicalRepRelMapEntry  *entry;
+	bool					found;
+	int						i;
+
+	if (LogicalRepRelMap == NULL)
+		remoterelmap_init();
+
+	/*
+	 * HASH_ENTER returns the existing entry if present or creates a new one.
+	 */
+	entry = hash_search(LogicalRepRelMap, (void *) &remoterel->remoteid,
+						HASH_ENTER, &found);
+
+	if (found)
+		remoterelmap_free_entry(entry);
+
+	/* Make cached copy of the data */
+	oldctx = MemoryContextSwitchTo(CacheMemoryContext);
+	entry->remoterel.remoteid = remoterel->remoteid;
+	entry->remoterel.nspname = pstrdup(remoterel->nspname);
+	entry->remoterel.relname = pstrdup(remoterel->relname);
+	entry->remoterel.natts = remoterel->natts;
+	entry->remoterel.attnames = palloc(remoterel->natts * sizeof(char*));
+	for (i = 0; i < remoterel->natts; i++)
+		entry->remoterel.attnames[i] = pstrdup(remoterel->attnames[i]);
+	entry->attmap = palloc(remoterel->natts * sizeof(int));
+	entry->reloid = InvalidOid;
+	MemoryContextSwitchTo(oldctx);
+}
+
+/*
+ * Find attribute index in TupleDesc struct by attribute name.
+ */
+static int
+tupdesc_get_att_by_name(TupleDesc desc, const char *attname)
+{
+	int		i;
+
+	for (i = 0; i < desc->natts; i++)
+	{
+		Form_pg_attribute att = desc->attrs[i];
+
+		if (strcmp(NameStr(att->attname), attname) == 0)
+			return i;
+	}
+
+	elog(ERROR, "unknown column name %s", attname);
+}
+
+/*
+ * Open the local relation associated with the remote one.
+ */
+static LogicalRepRelMapEntry *
+logicalreprel_open(uint32 remoteid, LOCKMODE lockmode)
+{
+	LogicalRepRelMapEntry  *entry;
+	bool		found;
+
+	if (LogicalRepRelMap == NULL)
+		remoterelmap_init();
+
+	/* Search for existing entry. */
+	entry = hash_search(LogicalRepRelMap, (void *) &remoteid,
+						HASH_FIND, &found);
+
+	if (!found)
+		elog(ERROR, "cache lookup failed for remote relation %u",
+			 remoteid);
+
+	/* Need to update the local cache? */
+	if (!OidIsValid(entry->reloid))
+	{
+		Oid			nspid;
+		Oid			relid;
+		int			i;
+		TupleDesc	desc;
+		LogicalRepRelation *remoterel;
+
+		remoterel = &entry->remoterel;
+
+		nspid = LookupExplicitNamespace(remoterel->nspname, false);
+		relid = get_relname_relid(remoterel->relname, nspid);
+		entry->rel = heap_open(relid, lockmode);
+
+		desc = RelationGetDescr(entry->rel);
+		for (i = 0; i < remoterel->natts; i++)
+			entry->attmap[i] = tupdesc_get_att_by_name(desc,
+													   remoterel->attnames[i]);
+
+		entry->reloid = RelationGetRelid(entry->rel);
+	}
+	else
+		entry->rel = heap_open(entry->reloid, lockmode);
+
+	return entry;
+}
+
+/*
+ * Close the previously opened logical relation.
+ */
+static void
+logicalreprel_close(LogicalRepRelMapEntry *rel, LOCKMODE lockmode)
+{
+	heap_close(rel->rel, lockmode);
+	rel->rel = NULL;
+}
+
+
+/*
+ * Make sure that we started local transaction.
+ *
+ * Also switches to ApplyContext as necessary.
+ */
+static bool
+ensure_transaction(void)
+{
+	if (IsTransactionState())
+	{
+		if (CurrentMemoryContext != ApplyContext)
+			MemoryContextSwitchTo(ApplyContext);
+		return false;
+	}
+
+	StartTransactionCommand();
+	MemoryContextSwitchTo(ApplyContext);
+	return true;
+}
+
+
+/*
+ * Executor state preparation for evaluation of constraint expressions,
+ * indexes and triggers.
+ *
+ * This is based on similar code in copy.c
+ */
+static EState *
+create_estate_for_relation(LogicalRepRelMapEntry *rel)
+{
+	EState	   *estate;
+	ResultRelInfo *resultRelInfo;
+	RangeTblEntry *rte;
+
+	estate = CreateExecutorState();
+
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(rel->rel);
+	rte->relkind = rel->rel->rd_rel->relkind;
+	estate->es_range_table = list_make1(rte);
+
+	resultRelInfo = makeNode(ResultRelInfo);
+	InitResultRelInfo(resultRelInfo, rel->rel, 1, 0);
+
+	estate->es_result_relations = resultRelInfo;
+	estate->es_num_result_relations = 1;
+	estate->es_result_relation_info = resultRelInfo;
+
+	/* Triggers might need a slot */
+	if (resultRelInfo->ri_TrigDesc)
+		estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate);
+
+	return estate;
+}
+
+/*
+ * Check if the local attribute is present in relation definition used
+ * by upstream and hence updated by the replication.
+ */
+static bool
+physatt_in_attmap(LogicalRepRelMapEntry *rel, int attid)
+{
+	AttrNumber	i;
+
+	/* Fast path for tables that are same on upstream and downstream. */
+	if (attid < rel->remoterel.natts && rel->attmap[attid] == attid)
+		return true;
+
+	/* Try to find the attribute in the map. */
+	for (i = 0; i < rel->remoterel.natts; i++)
+		if (rel->attmap[i] == attid)
+			return true;
+
+	return false;
+}
+
+/*
+ * Executes default values for columns for which we can't map to remote
+ * relation columns.
+ *
+ * This allows us to support tables which have more columns on the downstream
+ * than on the upsttream.
+ */
+static void
+FillSlotDefaults(LogicalRepRelMapEntry *rel, EState *estate,
+				 TupleTableSlot *slot)
+{
+	TupleDesc	desc = RelationGetDescr(rel->rel);
+	AttrNumber	num_phys_attrs = desc->natts;
+	int			i;
+	AttrNumber	attnum,
+				num_defaults = 0;
+	int		   *defmap;
+	ExprState **defexprs;
+	ExprContext *econtext;
+
+	econtext = GetPerTupleExprContext(estate);
+
+	/* We got all the data via replication, no need to evaluate anything. */
+	if (num_phys_attrs == rel->remoterel.natts)
+		return;
+
+	defmap = (int *) palloc(num_phys_attrs * sizeof(int));
+	defexprs = (ExprState **) palloc(num_phys_attrs * sizeof(ExprState *));
+
+	for (attnum = 0; attnum < num_phys_attrs; attnum++)
+	{
+		Expr	   *defexpr;
+
+		if (desc->attrs[attnum]->attisdropped)
+			continue;
+
+		if (physatt_in_attmap(rel, attnum))
+			continue;
+
+		defexpr = (Expr *) build_column_default(rel->rel, attnum + 1);
+
+		if (defexpr != NULL)
+		{
+			/* Run the expression through planner */
+			defexpr = expression_planner(defexpr);
+
+			/* Initialize executable expression in copycontext */
+			defexprs[num_defaults] = ExecInitExpr(defexpr, NULL);
+			defmap[num_defaults] = attnum;
+			num_defaults++;
+		}
+
+	}
+
+	for (i = 0; i < num_defaults; i++)
+		slot->tts_values[defmap[i]] =
+			ExecEvalExpr(defexprs[i], econtext, &slot->tts_isnull[defmap[i]],
+						 NULL);
+}
+
+/*
+ * Store data in C string form into slot.
+ * This is similar to BuildTupleFromCStrings but TupleTableSlot fits our
+ * use better.
+ */
+static void
+SlotStoreCStrings(TupleTableSlot *slot, char **values)
+{
+	int		natts = slot->tts_tupleDescriptor->natts;
+	int		i;
+
+	ExecClearTuple(slot);
+
+	/* Call the "in" function for each non-dropped attribute */
+	for (i = 0; i < natts; i++)
+	{
+		Form_pg_attribute att = slot->tts_tupleDescriptor->attrs[i];
+
+		if (!att->attisdropped && values[i] != NULL)
+		{
+			Oid typinput;
+			Oid typioparam;
+
+			getTypeInputInfo(att->atttypid, &typinput, &typioparam);
+			slot->tts_values[i] = OidInputFunctionCall(typinput, values[i],
+													   typioparam,
+													   att->atttypmod);
+			slot->tts_isnull[i] = false;
+		}
+		else
+		{
+			/* We assign NULL for both NULL values and dropped attributes. */
+			slot->tts_values[i] = (Datum) 0;
+			slot->tts_isnull[i] = true;
+		}
+	}
+
+	ExecStoreVirtualTuple(slot);
+}
+
+/*
+ * Modify slot with user data provided as C strigs.
+ * This is somewhat similar to heap_modify_tuple but also calls the type
+ * input fuction on the user data as the input is the rext representation
+ * of the types.
+ */
+static void
+SlotModifyCStrings(TupleTableSlot *slot, char **values, bool *replaces)
+{
+	int		natts = slot->tts_tupleDescriptor->natts;
+	int		i;
+
+	slot_getallattrs(slot);
+	ExecClearTuple(slot);
+
+	/* Call the "in" function for each replaced attribute */
+	for (i = 0; i < natts; i++)
+	{
+		Form_pg_attribute att = slot->tts_tupleDescriptor->attrs[i];
+
+		if (!replaces[i])
+			continue;
+
+		if (values[i] != NULL)
+		{
+			Oid typinput;
+			Oid typioparam;
+
+			getTypeInputInfo(att->atttypid, &typinput, &typioparam);
+			slot->tts_values[i] = OidInputFunctionCall(typinput, values[i],
+													   typioparam,
+													   att->atttypmod);
+			slot->tts_isnull[i] = false;
+		}
+		else
+		{
+			slot->tts_values[i] = (Datum) 0;
+			slot->tts_isnull[i] = true;
+		}
+	}
+
+	ExecStoreVirtualTuple(slot);
+}
+
+/*
+ * Handle BEGIN message.
+ */
+static void
+handle_begin(StringInfo s)
+{
+	XLogRecPtr		commit_lsn;
+	TimestampTz		commit_time;
+	TransactionId	remote_xid;
+
+	logicalrep_read_begin(s, &commit_lsn, &commit_time, &remote_xid);
+
+	replorigin_session_origin_timestamp = commit_time;
+	replorigin_session_origin_lsn = commit_lsn;
+
+	in_remote_transaction = true;
+
+	pgstat_report_activity(STATE_RUNNING, NULL);
+}
+
+/*
+ * Handle COMMIT message.
+ *
+ * TODO, support tracking of multiple origins
+ */
+static void
+handle_commit(StringInfo s)
+{
+	XLogRecPtr		commit_lsn;
+	XLogRecPtr		end_lsn;
+	TimestampTz		commit_time;
+
+	logicalrep_read_commit(s, &commit_lsn, &end_lsn, &commit_time);
+
+	Assert(commit_lsn == replorigin_session_origin_lsn);
+	Assert(commit_time == replorigin_session_origin_timestamp);
+
+	if (IsTransactionState())
+	{
+		FlushPosition *flushpos;
+
+		CommitTransactionCommand();
+		MemoryContextSwitchTo(CacheMemoryContext);
+
+		/* Track commit lsn  */
+		flushpos = (FlushPosition *) palloc(sizeof(FlushPosition));
+		flushpos->local_end = XactLastCommitEnd;
+		flushpos->remote_end = end_lsn;
+
+		dlist_push_tail(&lsn_mapping, &flushpos->node);
+		MemoryContextSwitchTo(ApplyContext);
+	}
+
+	in_remote_transaction = false;
+
+	pgstat_report_activity(STATE_IDLE, NULL);
+}
+
+/*
+ * Handle ORIGIN message.
+ *
+ * TODO, support tracking of multiple origins
+ */
+static void
+handle_origin(StringInfo s)
+{
+	/*
+	 * ORIGIN message can only come inside remote transaction and before
+	 * any actual writes.
+	 */
+	if (!in_remote_transaction || IsTransactionState())
+		elog(ERROR, "ORIGIN message sent out of order");
+}
+
+/*
+ * Handle RELATION message.
+ *
+ * Note we don't do validation against local schema here. The validation is
+ * posponed until first change for given relation comes.
+ */
+static void
+handle_relation(StringInfo s)
+{
+	LogicalRepRelation  *rel;
+
+	rel = logicalrep_read_rel(s);
+	remoterelmap_update(rel);
+}
+
+
+/*
+ * Handle INSERT message.
+ */
+static void
+handle_insert(StringInfo s)
+{
+	LogicalRepRelMapEntry *rel;
+	LogicalRepTupleData	newtup;
+	LogicalRepRelId		relid;
+	EState			   *estate;
+	TupleTableSlot	   *remoteslot;
+	MemoryContext		oldctx;
+
+	ensure_transaction();
+
+	relid = logicalrep_read_insert(s, &newtup);
+	rel = logicalreprel_open(relid, RowExclusiveLock);
+
+	/* Initialize the executor state. */
+	estate = create_estate_for_relation(rel);
+	remoteslot = ExecInitExtraTupleSlot(estate);
+	ExecSetSlotDescriptor(remoteslot, RelationGetDescr(rel->rel));
+
+	/* Process and store remote tuple in the slot */
+	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+	SlotStoreCStrings(remoteslot, newtup.values);
+	FillSlotDefaults(rel, estate, remoteslot);
+	MemoryContextSwitchTo(oldctx);
+
+	PushActiveSnapshot(GetTransactionSnapshot());
+	ExecOpenIndices(estate->es_result_relation_info, false);
+
+	ExecInsert(NULL, /* mtstate is only used for onconflict handling which we don't support atm */
+			   remoteslot,
+			   remoteslot,
+			   NIL,
+			   ONCONFLICT_NONE,
+			   estate,
+			   false);
+
+	/* Cleanup. */
+	ExecCloseIndices(estate->es_result_relation_info);
+	PopActiveSnapshot();
+	ExecResetTupleTable(estate->es_tupleTable, false);
+	FreeExecutorState(estate);
+
+	logicalreprel_close(rel, NoLock);
+
+	CommandCounterIncrement();
+}
+
+/*
+ * Handle UPDATE message.
+ *
+ * TODO: FDW support
+ */
+static void
+handle_update(StringInfo s)
+{
+	LogicalRepRelMapEntry *rel;
+	LogicalRepRelId		relid;
+	EState			   *estate;
+	EPQState			epqstate;
+	LogicalRepTupleData	oldtup;
+	LogicalRepTupleData	newtup;
+	bool				hasoldtup;
+	TupleTableSlot	   *localslot;
+	TupleTableSlot	   *remoteslot;
+	bool				found;
+	MemoryContext		oldctx;
+
+	ensure_transaction();
+
+	relid = logicalrep_read_update(s, &hasoldtup, &oldtup,
+								   &newtup);
+	rel = logicalreprel_open(relid, RowExclusiveLock);
+
+	/* Initialize the executor state. */
+	estate = create_estate_for_relation(rel);
+	remoteslot = ExecInitExtraTupleSlot(estate);
+	ExecSetSlotDescriptor(remoteslot, RelationGetDescr(rel->rel));
+	localslot = ExecInitExtraTupleSlot(estate);
+	ExecSetSlotDescriptor(localslot, RelationGetDescr(rel->rel));
+	EvalPlanQualInit(&epqstate, estate, NULL, NIL, -1);
+
+	PushActiveSnapshot(GetTransactionSnapshot());
+	ExecOpenIndices(estate->es_result_relation_info, false);
+
+	/* Find the tuple using the replica identity index. */
+	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+	SlotStoreCStrings(remoteslot, hasoldtup ? oldtup.values : newtup.values);
+	MemoryContextSwitchTo(oldctx);
+	found = tuple_find_by_replidx(rel->rel, LockTupleExclusive,
+								  remoteslot, localslot);
+	ExecClearTuple(remoteslot);
+
+	/*
+	 * Tuple found.
+	 *
+	 * Note this will fail if there are other conflicting unique indexes.
+	 */
+	if (found)
+	{
+		/* Process and store remote tuple in the slot */
+		oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+		ExecStoreTuple(localslot->tts_tuple, remoteslot, InvalidBuffer, false);
+		SlotModifyCStrings(remoteslot, newtup.values, newtup.changed);
+		MemoryContextSwitchTo(oldctx);
+
+		EvalPlanQualSetSlot(&epqstate, remoteslot);
+
+		ExecUpdate(&localslot->tts_tuple->t_self,
+				   localslot->tts_tuple,
+				   remoteslot,
+				   localslot,
+				   &epqstate,
+				   estate,
+				   false);
+	}
+	else
+	{
+		/*
+		 * The tuple to be updated could not be found.
+		 *
+		 * TODO what to do here?
+		 */
+	}
+
+	/* Cleanup. */
+	ExecCloseIndices(estate->es_result_relation_info);
+	PopActiveSnapshot();
+	EvalPlanQualEnd(&epqstate);
+	ExecResetTupleTable(estate->es_tupleTable, false);
+	FreeExecutorState(estate);
+
+	logicalreprel_close(rel, NoLock);
+
+	CommandCounterIncrement();
+}
+
+/*
+ * Handle DELETE message.
+ *
+ * TODO: FDW support
+ */
+static void
+handle_delete(StringInfo s)
+{
+	LogicalRepRelMapEntry *rel;
+	LogicalRepTupleData	oldtup;
+	LogicalRepRelId		relid;
+	EState			   *estate;
+	EPQState			epqstate;
+	TupleTableSlot	   *remoteslot;
+	TupleTableSlot	   *localslot;
+	bool				found;
+	MemoryContext		oldctx;
+
+	ensure_transaction();
+
+	relid = logicalrep_read_delete(s, &oldtup);
+	rel = logicalreprel_open(relid, RowExclusiveLock);
+
+	/* Initialize the executor state. */
+	estate = create_estate_for_relation(rel);
+	remoteslot = ExecInitExtraTupleSlot(estate);
+	ExecSetSlotDescriptor(remoteslot, RelationGetDescr(rel->rel));
+	localslot = ExecInitExtraTupleSlot(estate);
+	ExecSetSlotDescriptor(localslot, RelationGetDescr(rel->rel));
+	EvalPlanQualInit(&epqstate, estate, NULL, NIL, -1);
+
+	PushActiveSnapshot(GetTransactionSnapshot());
+	ExecOpenIndices(estate->es_result_relation_info, false);
+
+	/* Find the tuple using the replica identity index. */
+	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+	SlotStoreCStrings(remoteslot, oldtup.values);
+	MemoryContextSwitchTo(oldctx);
+	found = tuple_find_by_replidx(rel->rel, LockTupleExclusive,
+								  remoteslot, localslot);
+	/* If found delete it. */
+	if (found)
+	{
+		EvalPlanQualSetSlot(&epqstate, localslot);
+		ExecDelete(&localslot->tts_tuple->t_self,
+				   localslot->tts_tuple,
+				   localslot,
+				   &epqstate,
+				   estate,
+				   false);
+	}
+	else
+	{
+		/* The tuple to be deleted could not be found.*/
+	}
+
+	/* Cleanup. */
+	ExecCloseIndices(estate->es_result_relation_info);
+	PopActiveSnapshot();
+	EvalPlanQualEnd(&epqstate);
+	ExecResetTupleTable(estate->es_tupleTable, false);
+	FreeExecutorState(estate);
+
+	logicalreprel_close(rel, NoLock);
+
+	CommandCounterIncrement();
+}
+
+
+/*
+ * Logical replication protocol message dispatcher.
+ */
+static void
+handle_message(StringInfo s)
+{
+	char action = pq_getmsgbyte(s);
+
+	switch (action)
+	{
+		/* BEGIN */
+		case 'B':
+			handle_begin(s);
+			break;
+		/* COMMIT */
+		case 'C':
+			handle_commit(s);
+			break;
+		/* INSERT */
+		case 'I':
+			handle_insert(s);
+			break;
+		/* UPDATE */
+		case 'U':
+			handle_update(s);
+			break;
+		/* DELETE */
+		case 'D':
+			handle_delete(s);
+			break;
+		/* RELATION */
+		case 'R':
+			handle_relation(s);
+			break;
+		/* ORIGIN */
+		case 'O':
+			handle_origin(s);
+			break;
+		default:
+			elog(ERROR, "unknown action of type %c", action);
+	}
+}
+
+/*
+ * Figure out which write/flush positions to report to the walsender process.
+ *
+ * We can't simply report back the last LSN the walsender sent us because the
+ * local transaction might not yet be flushed to disk locally. Instead we
+ * build a list that associates local with remote LSNs for every commit. When
+ * reporting back the flush position to the sender we iterate that list and
+ * check which entries on it are already locally flushed. Those we can report
+ * as having been flushed.
+ *
+ * Returns true if there's no outstanding transactions that need to be
+ * flushed.
+ */
+static bool
+get_flush_position(XLogRecPtr *write, XLogRecPtr *flush)
+{
+	dlist_mutable_iter iter;
+	XLogRecPtr	local_flush = GetFlushRecPtr();
+
+	*write = InvalidXLogRecPtr;
+	*flush = InvalidXLogRecPtr;
+
+	dlist_foreach_modify(iter, &lsn_mapping)
+	{
+		FlushPosition *pos =
+			dlist_container(FlushPosition, node, iter.cur);
+
+		*write = pos->remote_end;
+
+		if (pos->local_end <= local_flush)
+		{
+			*flush = pos->remote_end;
+			dlist_delete(iter.cur);
+			pfree(pos);
+		}
+		else
+		{
+			/*
+			 * Don't want to uselessly iterate over the rest of the list which
+			 * could potentially be long. Instead get the last element and
+			 * grab the write position from there.
+			 */
+			pos = dlist_tail_element(FlushPosition, node,
+									 &lsn_mapping);
+			*write = pos->remote_end;
+			return false;
+		}
+	}
+
+	return dlist_is_empty(&lsn_mapping);
+}
+
+
+/*
+ * Apply main loop.
+ */
+static void
+ApplyLoop(void)
+{
+	XLogRecPtr	last_received = InvalidXLogRecPtr;
+
+	/* Init the ApplyContext which we use for easier cleanup. */
+	ApplyContext = AllocSetContextCreate(TopMemoryContext,
+										 "ApplyContext",
+										 ALLOCSET_DEFAULT_MINSIZE,
+										 ALLOCSET_DEFAULT_INITSIZE,
+										 ALLOCSET_DEFAULT_MAXSIZE);
+
+	/* mark as idle, before starting to loop */
+	pgstat_report_activity(STATE_IDLE, NULL);
+
+	while (!got_SIGTERM)
+	{
+		pgsocket	fd = PGINVALID_SOCKET;
+		int			rc;
+		int			len;
+		char	   *buf = NULL;
+		bool		endofstream = false;
+
+		CHECK_FOR_INTERRUPTS();
+
+		MemoryContextSwitchTo(ApplyContext);
+
+		len = wrcapi->receive(wrchandle, &buf, &fd);
+
+		if (len != 0)
+		{
+			/* Process the data */
+			for (;;)
+			{
+				CHECK_FOR_INTERRUPTS();
+
+				if (len == 0)
+				{
+					break;
+				}
+				else if (len < 0)
+				{
+					elog(NOTICE, "data stream from provider has ended");
+					endofstream = true;
+					break;
+				}
+				else
+				{
+					int c;
+					StringInfoData s;
+
+					/* Ensure we are reading the data into our memory context. */
+					MemoryContextSwitchTo(ApplyContext);
+
+					initStringInfo(&s);
+					s.data = buf;
+					s.len = len;
+					s.maxlen = -1;
+
+					c = pq_getmsgbyte(&s);
+
+					if (c == 'w')
+					{
+						XLogRecPtr	start_lsn;
+						XLogRecPtr	end_lsn;
+
+						start_lsn = pq_getmsgint64(&s);
+						end_lsn = pq_getmsgint64(&s);
+						pq_getmsgint64(&s); /* sendTime */
+
+						if (last_received < start_lsn)
+							last_received = start_lsn;
+
+						if (last_received < end_lsn)
+							last_received = end_lsn;
+
+						handle_message(&s);
+					}
+					else if (c == 'k')
+					{
+						XLogRecPtr endpos;
+						bool reply_requested;
+
+						endpos = pq_getmsgint64(&s);
+						/* timestamp = */ pq_getmsgint64(&s);
+						reply_requested = pq_getmsgbyte(&s);
+
+						send_feedback(endpos,
+									  GetCurrentTimestamp(),
+									  reply_requested);
+					}
+					/* other message types are purposefully ignored */
+				}
+
+				len = wrcapi->receive(wrchandle, &buf, &fd);
+			}
+		}
+
+		/* confirm all writes at once */
+		send_feedback(last_received, GetCurrentTimestamp(), false);
+
+		/* Cleanup the memory. */
+		MemoryContextResetAndDeleteChildren(ApplyContext);
+		MemoryContextSwitchTo(TopMemoryContext);
+
+		/* Check if we need to exit the streaming loop. */
+		if (endofstream)
+			break;
+
+		/*
+		 * Wait for more data or latch.
+		 */
+		rc = WaitLatchOrSocket(&MyProc->procLatch,
+							   WL_SOCKET_READABLE | WL_LATCH_SET |
+							   WL_TIMEOUT | WL_POSTMASTER_DEATH,
+							   fd, 1000L);
+
+		/* Emergency bailout if postmaster has died */
+		if (rc & WL_POSTMASTER_DEATH)
+			proc_exit(1);
+
+		ResetLatch(&MyProc->procLatch);
+	}
+}
+
+/*
+ * Send a Standby Status Update message to server.
+ *
+ * 'recvpos' is the latest LSN we've received data to, force is set if we need
+ * to send a response to avoid timeouts.
+ */
+static void
+send_feedback(XLogRecPtr recvpos, int64 now, bool force)
+{
+	static StringInfo	reply_message = NULL;
+
+	static XLogRecPtr last_recvpos = InvalidXLogRecPtr;
+	static XLogRecPtr last_writepos = InvalidXLogRecPtr;
+	static XLogRecPtr last_flushpos = InvalidXLogRecPtr;
+
+	XLogRecPtr writepos;
+	XLogRecPtr flushpos;
+
+	/* It's legal to not pass a recvpos */
+	if (recvpos < last_recvpos)
+		recvpos = last_recvpos;
+
+	if (get_flush_position(&writepos, &flushpos))
+	{
+		/*
+		 * No outstanding transactions to flush, we can report the latest
+		 * received position. This is important for synchronous replication.
+		 */
+		flushpos = writepos = recvpos;
+	}
+
+	if (writepos < last_writepos)
+		writepos = last_writepos;
+
+	if (flushpos < last_flushpos)
+		flushpos = last_flushpos;
+
+	/* if we've already reported everything we're good */
+	if (!force &&
+		writepos == last_writepos &&
+		flushpos == last_flushpos)
+		return;
+
+	if (!reply_message)
+	{
+		MemoryContext	oldctx = MemoryContextSwitchTo(CacheMemoryContext);
+		reply_message = makeStringInfo();
+		MemoryContextSwitchTo(oldctx);
+	}
+	else
+		resetStringInfo(reply_message);
+
+	pq_sendbyte(reply_message, 'r');
+	pq_sendint64(reply_message, recvpos);		/* write */
+	pq_sendint64(reply_message, flushpos);		/* flush */
+	pq_sendint64(reply_message, writepos);		/* apply */
+	pq_sendint64(reply_message, now);			/* sendTime */
+	pq_sendbyte(reply_message, false);			/* replyRequested */
+
+	elog(DEBUG2, "sending feedback (force %d) to recv %X/%X, write %X/%X, flush %X/%X",
+		 force,
+		 (uint32) (recvpos >> 32), (uint32) recvpos,
+		 (uint32) (writepos >> 32), (uint32) writepos,
+		 (uint32) (flushpos >> 32), (uint32) flushpos
+		);
+
+	wrcapi->send(wrchandle, reply_message->data, reply_message->len);
+
+	if (recvpos > last_recvpos)
+		last_recvpos = recvpos;
+	if (writepos > last_writepos)
+		last_writepos = writepos;
+	if (flushpos > last_flushpos)
+		last_flushpos = flushpos;
+}
+
+/* SIGTERM: set flag to exit at next convenient time */
+static void
+LogicalWorkerSigTermHandler(SIGNAL_ARGS)
+{
+	got_SIGTERM = true;
+}
+
+/* Logical Replication Apply worker entry point */
+void
+ApplyWorkerMain(Datum main_arg)
+{
+	int				worker_slot = DatumGetObjectId(main_arg);
+	MemoryContext	oldctx;
+	RepOriginId		originid;
+	XLogRecPtr		origin_startpos;
+	char		   *options;
+	walrcvconn_init_fn walrcvconn_init;
+
+	/* Attach to slot */
+	logicalrep_worker_attach(worker_slot);
+
+	/* Setup signal handling */
+	pqsignal(SIGTERM, LogicalWorkerSigTermHandler);
+	BackgroundWorkerUnblockSignals();
+
+	/* Make it easy to identify our processes. */
+	SetConfigOption("application_name", MyBgworkerEntry->bgw_name,
+					PGC_USERSET, PGC_S_SESSION);
+
+	/* Load the libpq-specific functions */
+	wrcapi = palloc0(sizeof(WalReceiverConnAPI));
+
+	walrcvconn_init = (walrcvconn_init_fn)
+		load_external_function("libpqwalreceiver",
+							   "_PG_walreceirver_conn_init", false, NULL);
+
+	if (walrcvconn_init == NULL)
+		elog(ERROR, "libpqwalreceiver does not declare _PG_walreceirver_conn_init symbol");
+
+	wrchandle = walrcvconn_init(wrcapi);
+	if (wrcapi->connect == NULL ||
+		wrcapi->startstreaming_logical == NULL ||
+		wrcapi->identify_system == NULL ||
+		wrcapi->receive == NULL || wrcapi->send == NULL ||
+		wrcapi->disconnect == NULL)
+		elog(ERROR, "libpqwalreceiver didn't initialize correctly");
+
+	Assert(CurrentResourceOwner == NULL);
+	CurrentResourceOwner = ResourceOwnerCreate(NULL,
+											   "logical replication apply");
+
+	/* Setup synchronous commit according to the user's wishes */
+/*	SetConfigOption("synchronous_commit",
+					logical_apply_synchronous_commit,
+					PGC_BACKEND, PGC_S_OVERRIDE);
+*/
+	/* Run as replica session replication role. */
+	SetConfigOption("session_replication_role", "replica",
+					PGC_SUSET, PGC_S_OVERRIDE);
+
+	/* Connect to our database. */
+	BackgroundWorkerInitializeConnectionByOid(MyLogicalRepWorker->dbid,
+											  InvalidOid);
+
+	StartTransactionCommand();
+
+	/* Load the subscription into persistent memory context. */
+	oldctx = MemoryContextSwitchTo(CacheMemoryContext);
+	MySubscription = GetSubscription(MyLogicalRepWorker->subid);
+	MemoryContextSwitchTo(oldctx);
+
+	elog(LOG, "logical replication apply for subscription %s started",
+		 MySubscription->name);
+
+	/* Setup replication origin tracking. */
+	originid = replorigin_by_name(MySubscription->slotname, true);
+	if (!OidIsValid(originid))
+		originid = replorigin_create(MySubscription->slotname);
+	replorigin_session_setup(originid);
+	replorigin_session_origin = originid;
+	origin_startpos = replorigin_session_get_progress(false);
+
+	CommitTransactionCommand();
+
+	/* Connect to the origin and start the replication. */
+	elog(DEBUG1, "connecting to provider using connection string %s",
+		 MySubscription->conninfo);
+	wrcapi->connect(wrchandle, MySubscription->conninfo, true,
+					MySubscription->name);
+
+	/* Build option string for the plugin. */
+	options = logicalrep_build_options(MySubscription->publications);
+
+	/* Start streaming from the slot. */
+	wrcapi->startstreaming_logical(wrchandle, origin_startpos,
+								   MySubscription->slotname, options);
+
+	/* Run the main loop. */
+	ApplyLoop();
+
+	wrcapi->disconnect(wrchandle);
+
+	/* We should only get here if we received sigTERM */
+	proc_exit(0);
+}
+
+/*
+ * Setup a ScanKey for a search in the relation 'rel' for a tuple 'key' that
+ * is setup to match 'rel' (*NOT* idxrel!).
+ *
+ * Returns whether any column contains NULLs.
+ *
+ * This is not generic routine, it expects the idxrel to be replication
+ * identity of a rel and meet all limitations associated with that.
+ */
+static bool
+build_replindex_scan_key(ScanKey skey, Relation rel, Relation idxrel,
+						 TupleTableSlot *searchslot)
+{
+	int			attoff;
+	bool		isnull;
+	Datum		indclassDatum;
+	oidvector  *opclass;
+	int2vector *indkey = &idxrel->rd_index->indkey;
+	bool		hasnulls = false;
+
+	Assert(RelationGetReplicaIndex(rel) == RelationGetRelid(idxrel));
+
+	indclassDatum = SysCacheGetAttr(INDEXRELID, idxrel->rd_indextuple,
+									Anum_pg_index_indclass, &isnull);
+	Assert(!isnull);
+	opclass = (oidvector *) DatumGetPointer(indclassDatum);
+
+	/* Build scankey for every attribute in the index. */
+	for (attoff = 0; attoff < RelationGetNumberOfAttributes(idxrel); attoff++)
+	{
+		Oid			operator;
+		Oid			opfamily;
+		RegProcedure regop;
+		int			pkattno = attoff + 1;
+		int			mainattno = indkey->values[attoff];
+		Oid			atttype = attnumTypeId(rel, mainattno);
+		Oid			optype = get_opclass_input_type(opclass->values[attoff]);
+
+		/*
+		 * Load the operator info, we need this to get the equality operator
+		 * function for the scankey.
+		 */
+		opfamily = get_opclass_family(opclass->values[attoff]);
+
+		operator = get_opfamily_member(opfamily, optype,
+									   optype,
+									   BTEqualStrategyNumber);
+
+		if (!OidIsValid(operator))
+			elog(ERROR,
+				 "could not lookup equality operator for type %u, optype %u in opfamily %u",
+				 atttype, optype, opfamily);
+
+		regop = get_opcode(operator);
+
+		/* Initialize the scankey. */
+		ScanKeyInit(&skey[attoff],
+					pkattno,
+					BTEqualStrategyNumber,
+					regop,
+					searchslot->tts_values[mainattno - 1]);
+
+		/* Check for null value. */
+		if (searchslot->tts_isnull[mainattno - 1])
+		{
+			hasnulls = true;
+			skey[attoff].sk_flags |= SK_ISNULL;
+		}
+	}
+
+	return hasnulls;
+}
+
+/*
+ * Search the relation 'rel' for tuple using the replication index.
+ *
+ * If a matching tuple is found lock it with lockmode, fill the slot with its
+ * contents and return true, return false is returned otherwise.
+ */
+static bool
+tuple_find_by_replidx(Relation rel, LockTupleMode lockmode,
+					  TupleTableSlot *searchslot, TupleTableSlot *slot)
+{
+	HeapTuple		scantuple;
+	ScanKeyData		skey[INDEX_MAX_KEYS];
+	IndexScanDesc	scan;
+	SnapshotData	snap;
+	TransactionId	xwait;
+	Oid				idxoid;
+	Relation		idxrel;
+	bool			found;
+
+	/* Open REPLICA IDENTITY index.*/
+	idxoid = RelationGetReplicaIndex(rel);
+	if (!OidIsValid(idxoid))
+	{
+		elog(ERROR, "could not find configured replica identity for table \"%s\"",
+			 RelationGetRelationName(rel));
+		return false;
+	}
+	idxrel = index_open(idxoid, RowExclusiveLock);
+
+	/* Start an index scan. */
+	InitDirtySnapshot(snap);
+	scan = index_beginscan(rel, idxrel, &snap,
+						   RelationGetNumberOfAttributes(idxrel),
+						   0);
+
+	/* Build scan key. */
+	build_replindex_scan_key(skey, rel, idxrel, searchslot);
+
+retry:
+	found = false;
+
+	index_rescan(scan, skey, RelationGetNumberOfAttributes(idxrel), NULL, 0);
+
+	/* Try to find the tuple */
+	if ((scantuple = index_getnext(scan, ForwardScanDirection)) != NULL)
+	{
+		found = true;
+		ExecStoreTuple(scantuple, slot, InvalidBuffer, false);
+		ExecMaterializeSlot(slot);
+
+		xwait = TransactionIdIsValid(snap.xmin) ?
+			snap.xmin : snap.xmax;
+
+		/*
+		 * If the tuple is locked, wait for locking transaction to finish
+		 * and retry.
+		 */
+		if (TransactionIdIsValid(xwait))
+		{
+			XactLockTableWait(xwait, NULL, NULL, XLTW_None);
+			goto retry;
+		}
+	}
+
+	/* Found tuple, try to lock it in the lockmode. */
+	if (found)
+	{
+		Buffer buf;
+		HeapUpdateFailureData hufd;
+		HTSU_Result res;
+		HeapTupleData locktup;
+
+		ItemPointerCopy(&slot->tts_tuple->t_self, &locktup.t_self);
+
+		PushActiveSnapshot(GetLatestSnapshot());
+
+		res = heap_lock_tuple(rel, &locktup, GetCurrentCommandId(false),
+							  lockmode,
+							  false /* wait */,
+							  false /* don't follow updates */,
+							  &buf, &hufd);
+		/* the tuple slot already has the buffer pinned */
+		ReleaseBuffer(buf);
+
+		PopActiveSnapshot();
+
+		switch (res)
+		{
+			case HeapTupleMayBeUpdated:
+				break;
+			case HeapTupleUpdated:
+				/* XXX: Improve handling here */
+				ereport(LOG,
+						(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
+						 errmsg("concurrent update, retrying")));
+				goto retry;
+			case HeapTupleInvisible:
+				elog(ERROR, "attempted to lock invisible tuple");
+			default:
+				elog(ERROR, "unexpected heap_lock_tuple status: %u", res);
+				break;
+		}
+	}
+
+	index_endscan(scan);
+
+	/* Don't release lock until commit. */
+	index_close(idxrel, NoLock);
+
+	return found;
+}
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
new file mode 100644
index 0000000..385260e
--- /dev/null
+++ b/src/backend/replication/logical/launcher.c
@@ -0,0 +1,542 @@
+/*-------------------------------------------------------------------------
+ * launcher.c
+ *	   PostgreSQL logical replication apply launcher process
+ *
+ * Copyright (c) 2012-2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/replication/logical/launcher.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "miscadmin.h"
+#include "pgstat.h"
+
+#include "access/heapam.h"
+#include "access/htup.h"
+#include "access/htup_details.h"
+#include "access/xact.h"
+
+#include "catalog/pg_subscription.h"
+
+#include "libpq/pqsignal.h"
+
+#include "postmaster/bgworker.h"
+#include "postmaster/fork_process.h"
+#include "postmaster/postmaster.h"
+
+#include "replication/logicalworker.h"
+#include "replication/subscription.h"
+
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/procsignal.h"
+
+#include "tcop/tcopprot.h"
+
+#include "utils/memutils.h"
+#include "utils/ps_status.h"
+#include "utils/timeout.h"
+#include "utils/snapmgr.h"
+
+int	max_logical_replication_workers = 4;
+LogicalRepWorker *MyLogicalRepWorker = NULL;
+
+typedef struct LogicalRepCtxStruct
+{
+	/* Supervisor process. */
+	pid_t		launcher_pid;
+
+	/* Background workers. */
+	LogicalRepWorker	workers[FLEXIBLE_ARRAY_MEMBER];
+} LogicalRepCtxStruct;
+
+LogicalRepCtxStruct *LogicalRepCtx;
+
+static LogicalRepWorker *logicalrep_worker_find(Oid subid);
+static void logicalrep_worker_launch(Oid dbid, Oid subid);
+static void logicalrep_worker_stop(LogicalRepWorker *worker);
+static void logicalrep_worker_onexit(int code, Datum arg);
+static void logicalrep_worker_detach(void);
+
+static bool xacthook_do_signal_launcher = false;
+
+/*
+ * Load the list of subscriptions.
+ *
+ * Only the fields interesting for worker start/stop functions are filled for
+ * each subscription.
+ */
+static List *
+get_subscription_list(void)
+{
+	List	   *res = NIL;
+	Relation	rel;
+	HeapScanDesc scan;
+	HeapTuple	tup;
+	MemoryContext resultcxt;
+
+	/* This is the context that we will allocate our output data in */
+	resultcxt = CurrentMemoryContext;
+
+	/*
+	 * Start a transaction so we can access pg_database, and get a snapshot.
+	 * We don't have a use for the snapshot itself, but we're interested in
+	 * the secondary effect that it sets RecentGlobalXmin.  (This is critical
+	 * for anything that reads heap pages, because HOT may decide to prune
+	 * them even if the process doesn't attempt to modify any tuples.)
+	 */
+	StartTransactionCommand();
+	(void) GetTransactionSnapshot();
+
+	rel = heap_open(SubscriptionRelationId, AccessShareLock);
+	scan = heap_beginscan_catalog(rel, 0, NULL);
+
+	while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
+	{
+		Form_pg_subscription subform = (Form_pg_subscription) GETSTRUCT(tup);
+		Subscription   *sub;
+		MemoryContext	oldcxt;
+
+		/*
+		 * Allocate our results in the caller's context, not the
+		 * transaction's. We do this inside the loop, and restore the original
+		 * context at the end, so that leaky things like heap_getnext() are
+		 * not called in a potentially long-lived context.
+		 */
+		oldcxt = MemoryContextSwitchTo(resultcxt);
+
+		sub = (Subscription *) palloc(sizeof(Subscription));
+		sub->oid = HeapTupleGetOid(tup);
+		sub->dbid = subform->dbid;
+		sub->enabled = subform->subenabled;
+
+		/* We don't fill fields we are not intereste in. */
+		sub->name = NULL;
+		sub->conninfo = NULL;
+		sub->slotname = NULL;
+		sub->publications = NIL;
+
+		res = lappend(res, sub);
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	heap_endscan(scan);
+	heap_close(rel, AccessShareLock);
+
+	CommitTransactionCommand();
+
+	return res;
+}
+
+/*
+ * Wait for a background worker to start up and attach to the shmem context.
+ *
+ * This is like WaitForBackgroundWorkerStartup(), except that we wait for
+ * attaching, not just start and we also just exit if postmaster died.
+ */
+static bool
+WaitForReplicationWorkerAttach(LogicalRepWorker *worker,
+							   BackgroundWorkerHandle *handle)
+{
+	BgwHandleStatus status;
+	int			rc;
+
+	for (;;)
+	{
+		pid_t		pid;
+
+		CHECK_FOR_INTERRUPTS();
+
+		status = GetBackgroundWorkerPid(handle, &pid);
+
+		/*
+		 * Worker started and attached to our shmem. This check is safe
+		 * because only laucher ever starts the workers, so nobody can steal
+		 * the worker slot.
+		 */
+		if (status == BGWH_STARTED && worker->proc)
+			return true;
+		/* Worker didn't start or died before attaching to our shmem. */
+		if (status == BGWH_STOPPED)
+			return false;
+
+		/*
+		 * We need timeout because we generaly don't get notified via latch
+		 * about the worker attach.
+		 */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH, 1000L);
+
+		if (rc & WL_POSTMASTER_DEATH)
+			proc_exit(1);
+
+		ResetLatch(MyLatch);
+	}
+
+	return status;
+}
+
+/*
+ * Walks the workers array and searches for one that matches given
+ * subscription id.
+ */
+static LogicalRepWorker *
+logicalrep_worker_find(Oid subid)
+{
+	int	i;
+	LogicalRepWorker   *res = NULL;
+
+	/* Block concurrent modification. */
+	LWLockAcquire(LogicalRepLauncherLock, LW_SHARED);
+
+	/* Search for attached worker for a given subscription id. */
+	for (i = 0; i < max_logical_replication_workers; i++)
+	{
+		LogicalRepWorker   *w = &LogicalRepCtx->workers[i];
+		if (w->subid == subid && w->proc && IsBackendPid(w->proc->pid))
+		{
+			res = w;
+			break;
+		}
+	}
+
+	LWLockRelease(LogicalRepLauncherLock);
+
+	return res;
+}
+
+/*
+ * Start new apply background worker.
+ */
+static void
+logicalrep_worker_launch(Oid dbid, Oid subid)
+{
+	BackgroundWorker	bgw;
+	BackgroundWorkerHandle *bgw_handle;
+	int					slot;
+	LogicalRepWorker   *worker = NULL;
+
+	ereport(LOG,
+			(errmsg("starting logical replication worker for subscription %u",
+					subid)));
+
+	/*
+	 * We need to do the modification of the shared memory under lock so that
+	 * we have consistent view.
+	 */
+	LWLockAcquire(LogicalRepLauncherLock, LW_EXCLUSIVE);
+
+	/* Find unused worker slot. */
+	for (slot = 0; slot < max_logical_replication_workers; slot++)
+	{
+		if (!LogicalRepCtx->workers[slot].proc)
+		{
+			worker = &LogicalRepCtx->workers[slot];
+			break;
+		}
+	}
+
+	/* Bail if not found */
+	if (worker == NULL)
+	{
+		ereport(WARNING,
+				(errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
+				 errmsg("logical replication worker registration failed, "
+						"you might want to increase "
+						"max_logical_replication_workers setting")));
+		return;
+	}
+
+	/* Prepare the worker info. */
+	memset(worker, 0, sizeof(LogicalRepWorker));
+	worker->dbid = dbid;
+	worker->subid = subid;
+
+	LWLockRelease(LogicalRepLauncherLock);
+
+	/* Register the new dynamic worker. */
+	bgw.bgw_flags =	BGWORKER_SHMEM_ACCESS |
+		BGWORKER_BACKEND_DATABASE_CONNECTION;
+	bgw.bgw_start_time = BgWorkerStart_RecoveryFinished;
+	bgw.bgw_main = ApplyWorkerMain;
+
+	bgw.bgw_restart_time = BGW_NEVER_RESTART;
+	bgw.bgw_notify_pid = MyProcPid;
+	bgw.bgw_main_arg = slot;
+
+	if (!RegisterDynamicBackgroundWorker(&bgw, &bgw_handle))
+	{
+		ereport(WARNING,
+				(errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
+				 errmsg("logical replication worker registration failed, "
+						"you might want to increase "
+						"max_logical_replication_workers setting")));
+		return;
+	}
+
+	/* Now wait until it attaches. */
+	if (!WaitForReplicationWorkerAttach(worker, bgw_handle))
+	{
+		ereport(WARNING,
+				(errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
+				 errmsg("could not launch logical replication worker")));
+		return;
+	}
+}
+
+/*
+ * Stop the logical replication worker and wait until it detaches from the
+ * slot.
+ */
+static void
+logicalrep_worker_stop(LogicalRepWorker *worker)
+{
+	LWLockAcquire(LogicalRepLauncherLock, LW_EXCLUSIVE);
+
+	/* Check that the worker is up and what we expect. */
+	if (!worker->proc)
+		return;
+	if (!IsBackendPid(worker->proc->pid))
+		return;
+
+	/* Terminate the worker. */
+	kill(worker->proc->pid, SIGTERM);
+
+	LWLockRelease(LogicalRepLauncherLock);
+
+	/* Wait for it to detach. */
+	for (;;)
+	{
+		int	rc = WaitLatch(&MyProc->procLatch,
+						   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+						   1000L);
+
+        /* emergency bailout if postmaster has died */
+        if (rc & WL_POSTMASTER_DEATH)
+			proc_exit(1);
+
+        ResetLatch(&MyProc->procLatch);
+
+		CHECK_FOR_INTERRUPTS();
+
+		if (!worker->proc)
+			return;
+	}
+}
+
+/*
+ * Attach to a slot.
+ */
+void
+logicalrep_worker_attach(int slot)
+{
+	/* Block concurrent access. */
+	LWLockAcquire(LogicalRepLauncherLock, LW_EXCLUSIVE);
+
+	Assert(slot >= 0 && slot < max_logical_replication_workers);
+	MyLogicalRepWorker = &LogicalRepCtx->workers[slot];
+
+	if (MyLogicalRepWorker->proc)
+		ereport(ERROR,
+			   (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				errmsg("logical replication worker slot %d already used by "
+					   "another worker", slot)));
+
+	MyLogicalRepWorker->proc = MyProc;
+	before_shmem_exit(logicalrep_worker_onexit, (Datum) 0);
+
+	LWLockRelease(LogicalRepLauncherLock);
+}
+
+/*
+ * Detach the worker (cleans up the worker info).
+ */
+static void
+logicalrep_worker_detach(void)
+{
+	/* Block concurrent access. */
+	LWLockAcquire(LogicalRepLauncherLock, LW_EXCLUSIVE);
+
+	MyLogicalRepWorker->dbid = InvalidOid;
+	MyLogicalRepWorker->subid = InvalidOid;
+	MyLogicalRepWorker->proc = NULL;
+
+	LWLockRelease(LogicalRepLauncherLock);
+}
+
+/*
+ * Cleanup function.
+ *
+ * Called on logical replication worker exit.
+ */
+static void
+logicalrep_worker_onexit(int code, Datum arg)
+{
+	logicalrep_worker_detach();
+}
+
+/*
+ * ApplyLauncherShmemSize
+ *		Compute space needed for replication launcher shared memory
+ */
+Size
+ApplyLauncherShmemSize(void)
+{
+	Size		size;
+
+	/*
+	 * Need the fixed struct and the array of LogicalRepWorker.
+	 */
+	size = sizeof(LogicalRepCtxStruct);
+	size = MAXALIGN(size);
+	size = add_size(size, mul_size(max_logical_replication_workers,
+								   sizeof(LogicalRepWorker)));
+	return size;
+}
+
+/*
+ * ApplyLauncherShmemInit
+ *		Allocate and initialize replication launcher shared memory
+ */
+void
+ApplyLauncherShmemInit(void)
+{
+	bool		found;
+
+	LogicalRepCtx = (LogicalRepCtxStruct *)
+		ShmemInitStruct("Logical Replication Launcher Data",
+						ApplyLauncherShmemSize(),
+						&found);
+
+	if (IsUnderPostmaster)
+	{
+		Assert(found);
+		return;
+	}
+
+	memset(LogicalRepCtx, 0, ApplyLauncherShmemSize());
+}
+
+static void
+xacthook_signal_launcher(XactEvent event, void *arg)
+{
+	switch (event)
+	{
+		case XACT_EVENT_COMMIT:
+			if (xacthook_do_signal_launcher)
+				ApplyLauncherWakeup();
+			break;
+		default:
+			/* We're not interested in other tx events */
+			break;
+	}
+}
+
+void
+ApplyLauncherWakeupOnCommit(void)
+{
+	if (!xacthook_do_signal_launcher)
+	{
+		RegisterXactCallback(xacthook_signal_launcher, NULL);
+		xacthook_do_signal_launcher = true;
+	}
+}
+
+void
+ApplyLauncherWakeup(void)
+{
+	if (IsBackendPid(LogicalRepCtx->launcher_pid))
+		kill(LogicalRepCtx->launcher_pid, SIGUSR1);
+}
+
+/*
+ * Main loop for the apply launcher process.
+ */
+void
+ApplyLauncherMain(Datum main_arg)
+{
+	ereport(LOG,
+			(errmsg("logical replication launcher started")));
+
+	/* Establish signal handlers. */
+	pqsignal(SIGTERM, die);
+	BackgroundWorkerUnblockSignals();
+
+	/* Make it easy to identify our processes. */
+	SetConfigOption("application_name", MyBgworkerEntry->bgw_name,
+					PGC_USERSET, PGC_S_SESSION);
+
+	LogicalRepCtx->launcher_pid = MyProcPid;
+
+	/*
+	 * Establish connection to nailed catalogs (we only ever access
+	 * pg_subscription).
+	 */
+	BackgroundWorkerInitializeConnection(NULL, NULL);
+
+	/* Enter main loop */
+	for (;;)
+    {
+		int			rc;
+		List	   *sublist;
+		ListCell   *lc;
+		Subscription   *startsub = NULL;
+		MemoryContext	subctx;
+		MemoryContext	oldctx;
+
+		CHECK_FOR_INTERRUPTS();
+
+		/* Use temporary context for the database list and worker info. */
+		subctx = AllocSetContextCreate(TopMemoryContext,
+									   "Logical Replication Launcher sublist",
+									   ALLOCSET_DEFAULT_MINSIZE,
+									   ALLOCSET_DEFAULT_INITSIZE,
+									   ALLOCSET_DEFAULT_MAXSIZE);
+		oldctx = MemoryContextSwitchTo(subctx);
+
+		/* Search for subscriptions to start or stop. */
+		sublist = get_subscription_list();
+		foreach(lc, sublist)
+		{
+			Subscription	   *sub = (Subscription *) lfirst(lc);
+			LogicalRepWorker   *w = logicalrep_worker_find(sub->oid);
+
+			if (sub->enabled && w == NULL && startsub == NULL)
+				startsub = sub;
+			else if (!sub->enabled && w != NULL)
+				logicalrep_worker_stop(w);
+		}
+
+		if (startsub)
+			logicalrep_worker_launch(startsub->dbid, startsub->oid);
+
+		/* Switch back to original memory context. */
+		MemoryContextSwitchTo(oldctx);
+		/* Clean the temporary memory. */
+		MemoryContextDelete(subctx);
+
+		/* Wait for more work. */
+		rc = WaitLatch(&MyProc->procLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+					   startsub ? 5000L : 180000L);
+
+        /* emergency bailout if postmaster has died */
+        if (rc & WL_POSTMASTER_DEATH)
+			proc_exit(1);
+
+		ResetLatch(&MyProc->procLatch);
+	}
+
+	LogicalRepCtx->launcher_pid = 0;
+
+	/* ... and if it returns, we're done */
+	ereport(LOG,
+			(errmsg("logical replication launcher shutting down")));
+
+	proc_exit(0);
+}
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index c04b17f..423cb0f 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -28,6 +28,7 @@
 #include "postmaster/bgworker_internals.h"
 #include "postmaster/bgwriter.h"
 #include "postmaster/postmaster.h"
+#include "replication/logicalworker.h"
 #include "replication/slot.h"
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
@@ -137,6 +138,7 @@ CreateSharedMemoryAndSemaphores(bool makePrivate, int port)
 		size = add_size(size, ReplicationOriginShmemSize());
 		size = add_size(size, WalSndShmemSize());
 		size = add_size(size, WalRcvShmemSize());
+		size = add_size(size, ApplyLauncherShmemSize());
 		size = add_size(size, SnapMgrShmemSize());
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
@@ -245,6 +247,7 @@ CreateSharedMemoryAndSemaphores(bool makePrivate, int port)
 	ReplicationOriginShmemInit();
 	WalSndShmemInit();
 	WalRcvShmemInit();
+	ApplyLauncherShmemInit();
 
 	/*
 	 * Set up other modules that need some shared memory space
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
index f8996cd..4488ff7 100644
--- a/src/backend/storage/lmgr/lwlocknames.txt
+++ b/src/backend/storage/lmgr/lwlocknames.txt
@@ -47,3 +47,4 @@ CommitTsLock						39
 ReplicationOriginLock				40
 MultiXactTruncationLock				41
 OldSnapshotTimeMapLock				42
+LogicalRepLauncherLock				43
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 9c93df0..32856db 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -58,6 +58,7 @@
 #include "postmaster/postmaster.h"
 #include "postmaster/syslogger.h"
 #include "postmaster/walwriter.h"
+#include "replication/logicalworker.h"
 #include "replication/slot.h"
 #include "replication/syncrep.h"
 #include "replication/walreceiver.h"
@@ -171,6 +172,7 @@ static const char *show_tcp_keepalives_interval(void);
 static const char *show_tcp_keepalives_count(void);
 static bool check_maxconnections(int *newval, void **extra, GucSource source);
 static bool check_max_worker_processes(int *newval, void **extra, GucSource source);
+static bool check_max_logical_replication_workers(int *newval, void **extra, GucSource source);
 static bool check_autovacuum_max_workers(int *newval, void **extra, GucSource source);
 static bool check_autovacuum_work_mem(int *newval, void **extra, GucSource source);
 static bool check_effective_io_concurrency(int *newval, void **extra, GucSource source);
@@ -2474,6 +2476,18 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
+		{"max_logical_replication_processes",
+			PGC_POSTMASTER,
+			RESOURCES_ASYNCHRONOUS,
+			gettext_noop("Maximum number of logical replication worker processes."),
+			NULL,
+		},
+		&max_logical_replication_workers,
+		4, 1, MAX_BACKENDS,
+		check_max_logical_replication_workers, NULL, NULL
+	},
+
+	{
 		{"log_rotation_age", PGC_SIGHUP, LOGGING_WHERE,
 			gettext_noop("Automatic log file rotation will occur after N minutes."),
 			NULL,
@@ -10184,6 +10198,14 @@ check_max_worker_processes(int *newval, void **extra, GucSource source)
 }
 
 static bool
+check_max_logical_replication_workers(int *newval, void **extra, GucSource source)
+{
+	if (*newval > max_worker_processes)
+		return false;
+	return true;
+}
+
+static bool
 check_effective_io_concurrency(int *newval, void **extra, GucSource source)
 {
 #ifdef USE_PREFETCH
diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h
index 6b66353..dfb7e7c 100644
--- a/src/include/executor/nodeModifyTable.h
+++ b/src/include/executor/nodeModifyTable.h
@@ -19,5 +19,25 @@ extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate,
 extern TupleTableSlot *ExecModifyTable(ModifyTableState *node);
 extern void ExecEndModifyTable(ModifyTableState *node);
 extern void ExecReScanModifyTable(ModifyTableState *node);
+extern TupleTableSlot *ExecInsert(ModifyTableState *mtstate,
+		   TupleTableSlot *slot,
+		   TupleTableSlot *planSlot,
+		   List *arbiterIndexes,
+		   OnConflictAction onconflict,
+		   EState *estate,
+		   bool canSetTag);
+extern TupleTableSlot *ExecDelete(ItemPointer tupleid,
+		   HeapTuple oldtuple,
+		   TupleTableSlot *planSlot,
+		   EPQState *epqstate,
+		   EState *estate,
+		   bool canSetTag);
+extern TupleTableSlot *ExecUpdate(ItemPointer tupleid,
+		   HeapTuple oldtuple,
+		   TupleTableSlot *slot,
+		   TupleTableSlot *planSlot,
+		   EPQState *epqstate,
+		   EState *estate,
+		   bool canSetTag);
 
 #endif   /* NODEMODIFYTABLE_H */
diff --git a/src/include/postmaster/bgworker_internals.h b/src/include/postmaster/bgworker_internals.h
index cd6cd44..3f8e764 100644
--- a/src/include/postmaster/bgworker_internals.h
+++ b/src/include/postmaster/bgworker_internals.h
@@ -52,4 +52,6 @@ extern void StartBackgroundWorker(void) pg_attribute_noreturn();
 extern BackgroundWorker *BackgroundWorkerEntry(int slotno);
 #endif
 
+extern bool internal_bgworker_registration_in_progress;
+
 #endif   /* BGWORKER_INTERNALS_H */
diff --git a/src/include/replication/logicalworker.h b/src/include/replication/logicalworker.h
new file mode 100644
index 0000000..64f36d3
--- /dev/null
+++ b/src/include/replication/logicalworker.h
@@ -0,0 +1,41 @@
+/*-------------------------------------------------------------------------
+ *
+ * logicalworker.h
+ *	  Exports for logical replication workers.
+ *
+ * Portions Copyright (c) 2010-2016, PostgreSQL Global Development Group
+ *
+ * src/include/replication/logicalworker.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef LOGICALWORKER_H
+#define LOGICALWORKER_H
+
+typedef struct LogicalRepWorker
+{
+	/* Pointer to proc array. NULL if not running. */
+	PGPROC *proc;
+
+	/* Database id to connect to. */
+	Oid		dbid;
+
+	/* Subscription id for the worker. */
+	Oid		subid;
+} LogicalRepWorker;
+
+extern int max_logical_replication_workers;
+extern LogicalRepWorker *MyLogicalRepWorker;
+
+extern void ApplyLauncherMain(Datum main_arg);
+extern void ApplyWorkerMain(Datum main_arg);
+
+extern Size ApplyLauncherShmemSize(void);
+extern void ApplyLauncherShmemInit(void);
+
+extern void ApplyLauncherWakeupOnCommit(void);
+extern void ApplyLauncherWakeup(void);
+
+extern void logicalrep_worker_attach(int slot);
+
+#endif   /* LOGICALWORKER_H */
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index 0f42008..3801949 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -152,6 +152,9 @@ typedef char *(*walrcvconn_create_slot_fn) (
 									WalReceiverConnHandle *handle,
 									char *slotname, bool logical,
 									XLogRecPtr *lsn);
+typedef void (*walrcvconn_drop_slot_fn) (
+									WalReceiverConnHandle *handle,
+									char *slotname);
 typedef bool (*walrcvconn_startstreaming_physical_fn) (
 									WalReceiverConnHandle *handle,
 									TimeLineID tli, XLogRecPtr startpoint,
@@ -174,6 +177,7 @@ typedef struct WalReceiverConnAPI {
 	walrcvconn_identify_system_fn			identify_system;
 	walrcvconn_readtimelinehistoryfile_fn	readtimelinehistoryfile;
 	walrcvconn_create_slot_fn				create_slot;
+	walrcvconn_drop_slot_fn					drop_slot;
 	walrcvconn_startstreaming_physical_fn	startstreaming_physical;
 	walrcvconn_startstreaming_logical_fn	startstreaming_logical;
 	walrcvconn_endstreaming_fn				endstreaming;
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index e629373..408a2c1 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -407,8 +407,16 @@ sub init
 
 	if ($params{allows_streaming})
 	{
-		print $conf "wal_level = replica\n";
+		if ($params{allows_streaming} eq "logical")
+		{
+			print $conf "wal_level = logical\n";
+		}
+		else
+		{
+			print $conf "wal_level = replica\n";
+		}
 		print $conf "max_wal_senders = 5\n";
+		print $conf "max_replication_slots = 5\n";
 		print $conf "wal_keep_segments = 20\n";
 		print $conf "max_wal_size = 128MB\n";
 		print $conf "shared_buffers = 1MB\n";
diff --git a/src/test/subscription/.gitignore b/src/test/subscription/.gitignore
new file mode 100644
index 0000000..871e943
--- /dev/null
+++ b/src/test/subscription/.gitignore
@@ -0,0 +1,2 @@
+# Generated by test suite
+/tmp_check/
diff --git a/src/test/subscription/Makefile b/src/test/subscription/Makefile
new file mode 100644
index 0000000..54c4d19
--- /dev/null
+++ b/src/test/subscription/Makefile
@@ -0,0 +1,20 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for src/test/subscription
+#
+# Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/test/subscription/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/test/subscription
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+check:
+	$(prove_check)
+
+clean distclean maintainer-clean:
+	rm -rf tmp_check
diff --git a/src/test/subscription/README b/src/test/subscription/README
new file mode 100644
index 0000000..e9e9375
--- /dev/null
+++ b/src/test/subscription/README
@@ -0,0 +1,16 @@
+src/test/subscription/README
+
+Regression tests for subscription/logical replication
+=====================================================
+
+This directory contains a test suite for subscription/logical replication.
+
+Running the tests
+=================
+
+    make check
+
+NOTE: This creates a temporary installation, and some tests may
+create one or multiple nodes, for the purpose of the tests.
+
+NOTE: This requires the --enable-tap-tests argument to configure.
diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl
new file mode 100644
index 0000000..dca19c4
--- /dev/null
+++ b/src/test/subscription/t/001_rep_changes.pl
@@ -0,0 +1,89 @@
+# Basic logical replication test
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 3;
+
+# Initialize provider node
+my $node_provider = get_new_node('provider');
+$node_provider->init(allows_streaming => 'logical');
+$node_provider->start;
+
+# Create subscriber node
+my $node_subscriber = get_new_node('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Create some preexisting content on provider
+$node_provider->safe_psql('postgres',
+	"CREATE TABLE tab_notrep AS SELECT generate_series(1,10) AS a");
+$node_provider->safe_psql('postgres',
+	"CREATE TABLE tab_ins (a int)");
+$node_provider->safe_psql('postgres',
+	"CREATE TABLE tab_rep (a int primary key)");
+
+# Setup structure on subscriber
+$node_subscriber->safe_psql('postgres',
+	"CREATE TABLE tab_notrep (a int)");
+$node_subscriber->safe_psql('postgres',
+	"CREATE TABLE tab_ins (a int)");
+$node_subscriber->safe_psql('postgres',
+	"CREATE TABLE tab_rep (a int primary key)");
+
+# Setup logical replication
+my $provider_connstr = $node_provider->connstr . ' dbname=postgres';
+$node_provider->safe_psql('postgres',
+	"CREATE PUBLICATION tap_pub");
+$node_provider->safe_psql('postgres',
+	"CREATE PUBLICATION tap_pub_ins_only WITH noreplicate_delete noreplicate_update");
+$node_provider->safe_psql('postgres',
+	"ALTER PUBLICATION tap_pub ADD TABLE tab_rep");
+$node_provider->safe_psql('postgres',
+	"ALTER PUBLICATION tap_pub_ins_only ADD TABLE tab_ins");
+
+$node_subscriber->safe_psql('postgres',
+	"CREATE SUBSCRIPTION tap_sub WITH CONNECTION '$provider_connstr' PUBLICATION tap_pub, tap_pub_ins_only");
+
+# Wait for subscriber to finish table sync
+my $appname = 'tap_sub';
+my $caughtup_query =
+"SELECT pg_current_xlog_location() <= write_location FROM pg_stat_replication WHERE application_name = '$appname';";
+$node_provider->poll_query_until('postgres', $caughtup_query)
+  or die "Timed out while waiting for subscriber to catch up";
+
+my $result =
+  $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM tab_notrep");
+print "node_subscriber: $result\n";
+is($result, qq(0), 'check non-replicated table is empty on subscriber');
+
+
+$node_provider->safe_psql('postgres',
+	"INSERT INTO tab_ins SELECT generate_series(1,50)");
+$node_provider->safe_psql('postgres',
+	"DELETE FROM tab_ins WHERE a > 20");
+$node_provider->safe_psql('postgres',
+	"UPDATE tab_ins SET a = -a");
+
+$node_provider->safe_psql('postgres',
+	"INSERT INTO tab_rep SELECT generate_series(1,50)");
+$node_provider->safe_psql('postgres',
+	"DELETE FROM tab_rep WHERE a > 20");
+$node_provider->safe_psql('postgres',
+	"UPDATE tab_rep SET a = -a");
+
+$node_provider->poll_query_until('postgres', $caughtup_query)
+  or die "Timed out while waiting for subscriber to catch up";
+
+$result =
+  $node_subscriber->safe_psql('postgres', "SELECT count(*), min(a), max(a) FROM tab_ins");
+print "node_subscriber: $result\n";
+is($result, qq(50|1|50), 'check replicated inserts on subscriber');
+
+$result =
+  $node_subscriber->safe_psql('postgres', "SELECT count(*), min(a), max(a) FROM tab_rep");
+print "node_subscriber: $result\n";
+is($result, qq(20|-20|-1), 'check replicated changes on subscriber');
+
+$node_subscriber->stop('fast');
+$node_provider->stop('fast');
diff --git a/src/test/subscription/t/002_types.pl b/src/test/subscription/t/002_types.pl
new file mode 100644
index 0000000..a126201
--- /dev/null
+++ b/src/test/subscription/t/002_types.pl
@@ -0,0 +1,509 @@
+# This tests that more complex datatypes are replicated correctly
+# by logical replication
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 3;
+
+# Initialize provider node
+my $node_provider = get_new_node('provider');
+$node_provider->init(allows_streaming => 'logical');
+$node_provider->start;
+
+# Create subscriber node
+my $node_subscriber = get_new_node('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Create some preexisting content on provider
+my $ddl =  qq(
+	CREATE TABLE public.tst_one_array (
+		a INTEGER PRIMARY KEY,
+		b INTEGER[]
+		);
+	CREATE TABLE public.tst_arrays (
+		a INTEGER[] PRIMARY KEY,
+		b TEXT[],
+		c FLOAT[],
+		d INTERVAL[]
+		);
+
+	CREATE TYPE public.tst_enum_t AS ENUM ('a', 'b', 'c', 'd', 'e');
+	CREATE TABLE public.tst_one_enum (
+		a INTEGER PRIMARY KEY,
+		b public.tst_enum_t
+		);
+	CREATE TABLE public.tst_enums (
+		a public.tst_enum_t PRIMARY KEY,
+		b public.tst_enum_t[]
+		);
+
+	CREATE TYPE public.tst_comp_basic_t AS (a FLOAT, b TEXT, c INTEGER);
+	CREATE TYPE public.tst_comp_enum_t AS (a FLOAT, b public.tst_enum_t, c INTEGER);
+	CREATE TYPE public.tst_comp_enum_array_t AS (a FLOAT, b public.tst_enum_t[], c INTEGER);
+	CREATE TABLE public.tst_one_comp (
+		a INTEGER PRIMARY KEY,
+		b public.tst_comp_basic_t
+		);
+	CREATE TABLE public.tst_comps (
+		a public.tst_comp_basic_t PRIMARY KEY,
+		b public.tst_comp_basic_t[]
+		);
+	CREATE TABLE public.tst_comp_enum (
+		a INTEGER PRIMARY KEY,
+		b public.tst_comp_enum_t
+		);
+	CREATE TABLE public.tst_comp_enum_array (
+		a public.tst_comp_enum_t PRIMARY KEY,
+		b public.tst_comp_enum_t[]
+		);
+	CREATE TABLE public.tst_comp_one_enum_array (
+		a INTEGER PRIMARY KEY,
+		b public.tst_comp_enum_array_t
+		);
+	CREATE TABLE public.tst_comp_enum_what (
+		a public.tst_comp_enum_array_t PRIMARY KEY,
+		b public.tst_comp_enum_array_t[]
+		);
+
+	CREATE TYPE public.tst_comp_mix_t AS (
+		a public.tst_comp_basic_t,
+		b public.tst_comp_basic_t[],
+		c public.tst_enum_t,
+		d public.tst_enum_t[]
+		);
+	CREATE TABLE public.tst_comp_mix_array (
+		a public.tst_comp_mix_t PRIMARY KEY,
+		b public.tst_comp_mix_t[]
+		);
+	CREATE TABLE public.tst_range (
+		a INTEGER PRIMARY KEY,
+		b int4range
+	);
+	CREATE TABLE public.tst_range_array (
+		a INTEGER PRIMARY KEY,
+		b TSTZRANGE,
+		c int8range[]
+	););
+
+# Setup structure on both nodes
+$node_provider->safe_psql('postgres', $ddl);
+$node_subscriber->safe_psql('postgres', $ddl);
+
+# Setup logical replication
+my $provider_connstr = $node_provider->connstr . ' dbname=postgres';
+$node_provider->safe_psql('postgres',
+	"CREATE PUBLICATION tap_pub");
+$node_provider->safe_psql('postgres',
+	"ALTER PUBLICATION tap_pub ADD TABLE ALL IN SCHEMA public");
+
+$node_subscriber->safe_psql('postgres',
+	"CREATE SUBSCRIPTION tap_sub WITH CONNECTION '$provider_connstr' PUBLICATION tap_pub");
+
+# Wait for subscriber to finish table sync
+my $appname = 'tap_sub';
+my $caughtup_query =
+"SELECT pg_current_xlog_location() <= write_location FROM pg_stat_replication WHERE application_name = '$appname';";
+$node_provider->poll_query_until('postgres', $caughtup_query)
+  or die "Timed out while waiting for subscriber to catch up";
+
+# Insert initial test data
+$node_provider->safe_psql('postgres', qq(
+	-- test_tbl_one_array_col
+	INSERT INTO tst_one_array (a, b) VALUES
+		(1, '{1, 2, 3}'),
+		(2, '{2, 3, 1}'),
+		(3, '{3, 2, 1}'),
+		(4, '{4, 3, 2}'),
+		(5, '{5, NULL, 3}');
+
+	-- test_tbl_arrays
+	INSERT INTO tst_arrays (a, b, c, d) VALUES
+		('{1, 2, 3}', '{"a", "b", "c"}', '{1.1, 2.2, 3.3}', '{"1 day", "2 days", "3 days"}'),
+		('{2, 3, 1}', '{"b", "c", "a"}', '{2.2, 3.3, 1.1}', '{"2 minutes", "3 minutes", "1 minute"}'),
+		('{3, 1, 2}', '{"c", "a", "b"}', '{3.3, 1.1, 2.2}', '{"3 years", "1 year", "2 years"}'),
+		('{4, 1, 2}', '{"d", "a", "b"}', '{4.4, 1.1, 2.2}', '{"4 years", "1 year", "2 years"}'),
+		('{5, NULL, NULL}', '{"e", NULL, "b"}', '{5.5, 1.1, NULL}', '{"5 years", NULL, NULL}');
+
+	-- test_tbl_single_enum
+	INSERT INTO tst_one_enum (a, b) VALUES
+		(1, 'a'),
+		(2, 'b'),
+		(3, 'c'),
+		(4, 'd'),
+		(5, NULL);
+
+	-- test_tbl_enums
+	INSERT INTO tst_enums (a, b) VALUES
+		('a', '{b, c}'),
+		('b', '{c, a}'),
+		('c', '{b, a}'),
+		('d', '{c, b}'),
+		('e', '{d, NULL}');
+
+	-- test_tbl_single_composites
+	INSERT INTO tst_one_comp (a, b) VALUES
+		(1, ROW(1.0, 'a', 1)),
+		(2, ROW(2.0, 'b', 2)),
+		(3, ROW(3.0, 'c', 3)),
+		(4, ROW(4.0, 'd', 4)),
+		(5, ROW(NULL, NULL, 5));
+
+	-- test_tbl_composites
+	INSERT INTO tst_comps (a, b) VALUES
+		(ROW(1.0, 'a', 1), ARRAY[ROW(1, 'a', 1)::tst_comp_basic_t]),
+		(ROW(2.0, 'b', 2), ARRAY[ROW(2, 'b', 2)::tst_comp_basic_t]),
+		(ROW(3.0, 'c', 3), ARRAY[ROW(3, 'c', 3)::tst_comp_basic_t]),
+		(ROW(4.0, 'd', 4), ARRAY[ROW(4, 'd', 3)::tst_comp_basic_t]),
+		(ROW(5.0, 'e', NULL), ARRAY[NULL, ROW(5, NULL, 5)::tst_comp_basic_t]);
+
+	-- test_tbl_composite_with_enums
+	INSERT INTO tst_comp_enum (a, b) VALUES
+		(1, ROW(1.0, 'a', 1)),
+		(2, ROW(2.0, 'b', 2)),
+		(3, ROW(3.0, 'c', 3)),
+		(4, ROW(4.0, 'd', 4)),
+		(5, ROW(NULL, 'e', NULL));
+
+	-- test_tbl_composite_with_enums_array
+	INSERT INTO tst_comp_enum_array (a, b) VALUES
+		(ROW(1.0, 'a', 1), ARRAY[ROW(1, 'a', 1)::tst_comp_enum_t]),
+		(ROW(2.0, 'b', 2), ARRAY[ROW(2, 'b', 2)::tst_comp_enum_t]),
+		(ROW(3.0, 'c', 3), ARRAY[ROW(3, 'c', 3)::tst_comp_enum_t]),
+		(ROW(4.0, 'd', 3), ARRAY[ROW(3, 'd', 3)::tst_comp_enum_t]),
+		(ROW(5.0, 'e', 3), ARRAY[ROW(3, 'e', 3)::tst_comp_enum_t, NULL]);
+
+	-- test_tbl_composite_with_single_enums_array_in_composite
+	INSERT INTO tst_comp_one_enum_array (a, b) VALUES
+		(1, ROW(1.0, '{a, b, c}', 1)),
+		(2, ROW(2.0, '{a, b, c}', 2)),
+		(3, ROW(3.0, '{a, b, c}', 3)),
+		(4, ROW(4.0, '{c, b, d}', 4)),
+		(5, ROW(5.0, '{NULL, e, NULL}', 5));
+
+	-- test_tbl_composite_with_enums_array_in_composite
+	INSERT INTO tst_comp_enum_what (a, b) VALUES
+		(ROW(1.0, '{a, b, c}', 1), ARRAY[ROW(1, '{a, b, c}', 1)::tst_comp_enum_array_t]),
+		(ROW(2.0, '{b, c, a}', 2), ARRAY[ROW(2, '{b, c, a}', 1)::tst_comp_enum_array_t]),
+		(ROW(3.0, '{c, a, b}', 1), ARRAY[ROW(3, '{c, a, b}', 1)::tst_comp_enum_array_t]),
+		(ROW(4.0, '{c, b, d}', 4), ARRAY[ROW(4, '{c, b, d}', 4)::tst_comp_enum_array_t]),
+		(ROW(5.0, '{c, NULL, b}', NULL), ARRAY[ROW(5, '{c, e, b}', 1)::tst_comp_enum_array_t]);
+
+	-- test_tbl_mixed_composites
+	INSERT INTO tst_comp_mix_array (a, b) VALUES
+		(ROW(
+			ROW(1,'a',1),
+			ARRAY[ROW(1,'a',1)::tst_comp_basic_t, ROW(2,'b',2)::tst_comp_basic_t],
+			'a',
+			'{a,b,NULL,c}'),
+		ARRAY[
+			ROW(
+				ROW(1,'a',1),
+				ARRAY[
+					ROW(1,'a',1)::tst_comp_basic_t,
+					ROW(2,'b',2)::tst_comp_basic_t,
+					NULL
+					],
+				'a',
+				'{a,b,c}'
+				)::tst_comp_mix_t
+			]
+		);
+
+	-- test_tbl_range
+	INSERT INTO tst_range (a, b) VALUES
+		(1, '[1, 10]'),
+		(2, '[2, 20]'),
+		(3, '[3, 30]'),
+		(4, '[4, 40]'),
+		(5, '[5, 50]');
+
+	-- test_tbl_range_array
+	INSERT INTO tst_range_array (a, b, c) VALUES
+		(1, tstzrange('Mon Aug 04 00:00:00 2014 CEST'::timestamptz, 'infinity'), '{"[1,2]", "[10,20]"}'),
+		(2, tstzrange('Mon Aug 04 00:00:00 2014 CEST'::timestamptz - interval '2 days', 'Mon Aug 04 00:00:00 2014 CEST'::timestamptz), '{"[2,3]", "[20,30]"}'),
+		(3, tstzrange('Mon Aug 04 00:00:00 2014 CEST'::timestamptz - interval '3 days', 'Mon Aug 04 00:00:00 2014 CEST'::timestamptz), '{"[3,4]"}'),
+		(4, tstzrange('Mon Aug 04 00:00:00 2014 CEST'::timestamptz - interval '4 days', 'Mon Aug 04 00:00:00 2014 CEST'::timestamptz), '{"[4,5]", NULL, "[40,50]"}'),
+		(5, NULL, NULL);
+));
+
+$node_provider->poll_query_until('postgres', $caughtup_query)
+  or die "Timed out while waiting for subscriber to catch up";
+
+# Check the data on subscriber
+my $result = $node_subscriber->safe_psql('postgres', qq(
+	SELECT a, b FROM tst_one_array ORDER BY a;
+	SELECT a, b, c, d FROM tst_arrays ORDER BY a;
+	SELECT a, b FROM tst_one_enum ORDER BY a;
+	SELECT a, b FROM tst_enums ORDER BY a;
+	SELECT a, b FROM tst_one_comp ORDER BY a;
+	SELECT a, b FROM tst_comps ORDER BY a;
+	SELECT a, b FROM tst_comp_enum ORDER BY a;
+	SELECT a, b FROM tst_comp_enum_array ORDER BY a;
+	SELECT a, b FROM tst_comp_one_enum_array ORDER BY a;
+	SELECT a, b FROM tst_comp_enum_what ORDER BY a;
+	SELECT a, b FROM tst_comp_mix_array ORDER BY a;
+	SELECT a, b FROM tst_range ORDER BY a;
+	SELECT a, b, c FROM tst_range_array ORDER BY a;
+));
+
+is($result, '1|{1,2,3}
+2|{2,3,1}
+3|{3,2,1}
+4|{4,3,2}
+5|{5,NULL,3}
+{1,2,3}|{a,b,c}|{1.1,2.2,3.3}|{"1 day","2 days","3 days"}
+{2,3,1}|{b,c,a}|{2.2,3.3,1.1}|{00:02:00,00:03:00,00:01:00}
+{3,1,2}|{c,a,b}|{3.3,1.1,2.2}|{"3 years","1 year","2 years"}
+{4,1,2}|{d,a,b}|{4.4,1.1,2.2}|{"4 years","1 year","2 years"}
+{5,NULL,NULL}|{e,NULL,b}|{5.5,1.1,NULL}|{"5 years",NULL,NULL}
+1|a
+2|b
+3|c
+4|d
+5|
+a|{b,c}
+b|{c,a}
+c|{b,a}
+d|{c,b}
+e|{d,NULL}
+1|(1,a,1)
+2|(2,b,2)
+3|(3,c,3)
+4|(4,d,4)
+5|(,,5)
+(1,a,1)|{"(1,a,1)"}
+(2,b,2)|{"(2,b,2)"}
+(3,c,3)|{"(3,c,3)"}
+(4,d,4)|{"(4,d,3)"}
+(5,e,)|{NULL,"(5,,5)"}
+1|(1,a,1)
+2|(2,b,2)
+3|(3,c,3)
+4|(4,d,4)
+5|(,e,)
+(1,a,1)|{"(1,a,1)"}
+(2,b,2)|{"(2,b,2)"}
+(3,c,3)|{"(3,c,3)"}
+(4,d,3)|{"(3,d,3)"}
+(5,e,3)|{"(3,e,3)",NULL}
+1|(1,"{a,b,c}",1)
+2|(2,"{a,b,c}",2)
+3|(3,"{a,b,c}",3)
+4|(4,"{c,b,d}",4)
+5|(5,"{NULL,e,NULL}",5)
+(1,"{a,b,c}",1)|{"(1,\"{a,b,c}\",1)"}
+(2,"{b,c,a}",2)|{"(2,\"{b,c,a}\",1)"}
+(3,"{c,a,b}",1)|{"(3,\"{c,a,b}\",1)"}
+(4,"{c,b,d}",4)|{"(4,\"{c,b,d}\",4)"}
+(5,"{c,NULL,b}",)|{"(5,\"{c,e,b}\",1)"}
+("(1,a,1)","{""(1,a,1)"",""(2,b,2)""}",a,"{a,b,NULL,c}")|{"(\"(1,a,1)\",\"{\"\"(1,a,1)\"\",\"\"(2,b,2)\"\",NULL}\",a,\"{a,b,c}\")"}
+1|[1,11)
+2|[2,21)
+3|[3,31)
+4|[4,41)
+5|[5,51)
+1|["2014-08-04 00:00:00+02",infinity)|{"[1,3)","[10,21)"}
+2|["2014-08-02 00:00:00+02","2014-08-04 00:00:00+02")|{"[2,4)","[20,31)"}
+3|["2014-08-01 00:00:00+02","2014-08-04 00:00:00+02")|{"[3,5)"}
+4|["2014-07-31 00:00:00+02","2014-08-04 00:00:00+02")|{"[4,6)",NULL,"[40,51)"}
+5||',
+'check replicated inserts on subscriber');
+
+# Run batch of updates
+$node_provider->safe_psql('postgres', qq(
+	UPDATE tst_one_array SET b = '{4, 5, 6}' WHERE a = 1;
+	UPDATE tst_one_array SET b = '{4, 5, 6, 1}' WHERE a > 3;
+	UPDATE tst_arrays SET b = '{"1a", "2b", "3c"}', c = '{1.0, 2.0, 3.0}', d = '{"1 day 1 second", "2 days 2 seconds", "3 days 3 second"}' WHERE a = '{1, 2, 3}';
+	UPDATE tst_arrays SET b = '{"c", "d", "e"}', c = '{3.0, 4.0, 5.0}', d = '{"3 day 1 second", "4 days 2 seconds", "5 days 3 second"}' WHERE a[1] > 3;
+	UPDATE tst_one_enum SET b = 'c' WHERE a = 1;
+	UPDATE tst_one_enum SET b = NULL WHERE a > 3;
+	UPDATE tst_enums SET b = '{e, NULL}' WHERE a = 'a';
+	UPDATE tst_enums SET b = '{e, d}' WHERE a > 'c';
+	UPDATE tst_one_comp SET b = ROW(1.0, 'A', 1) WHERE a = 1;
+	UPDATE tst_one_comp SET b = ROW(NULL, 'x', -1) WHERE a > 3;
+	UPDATE tst_comps SET b = ARRAY[ROW(9, 'x', -1)::tst_comp_basic_t] WHERE (a).a = 1.0;
+	UPDATE tst_comps SET b = ARRAY[NULL, ROW(9, 'x', NULL)::tst_comp_basic_t] WHERE (a).a > 3.9;
+	UPDATE tst_comp_enum SET b = ROW(1.0, NULL, NULL) WHERE a = 1;
+	UPDATE tst_comp_enum SET b = ROW(4.0, 'd', 44) WHERE a > 3;
+	UPDATE tst_comp_enum_array SET b = ARRAY[NULL, ROW(3, 'd', 3)::tst_comp_enum_t] WHERE a = ROW(1.0, 'a', 1)::tst_comp_enum_t;
+	UPDATE tst_comp_enum_array SET b = ARRAY[ROW(1, 'a', 1)::tst_comp_enum_t, ROW(2, 'b', 2)::tst_comp_enum_t] WHERE (a).a > 3;
+	UPDATE tst_comp_one_enum_array SET b = ROW(1.0, '{a, e, c}', NULL) WHERE a = 1;
+	UPDATE tst_comp_one_enum_array SET b = ROW(4.0, '{c, b, d}', 4) WHERE a > 3;
+	UPDATE tst_comp_enum_what SET b = ARRAY[NULL, ROW(1, '{a, b, c}', 1)::tst_comp_enum_array_t, ROW(NULL, '{a, e, c}', 2)::tst_comp_enum_array_t] WHERE (a).a = 1;
+	UPDATE tst_comp_enum_what SET b = ARRAY[ROW(5, '{a, b, c}', 5)::tst_comp_enum_array_t] WHERE (a).a > 3;
+	UPDATE tst_comp_mix_array SET b[2] = NULL WHERE ((a).a).a = 1;
+	UPDATE tst_range SET b = '[100, 1000]' WHERE a = 1;
+	UPDATE tst_range SET b = '(1, 90)' WHERE a > 3;
+	UPDATE tst_range_array SET c = '{"[100, 1000]"}' WHERE a = 1;
+	UPDATE tst_range_array SET b = tstzrange('Mon Aug 04 00:00:00 2014 CEST'::timestamptz, 'infinity'), c = '{NULL, "[11,9999999]"}' WHERE a > 3;
+));
+
+$node_provider->poll_query_until('postgres', $caughtup_query)
+  or die "Timed out while waiting for subscriber to catch up";
+
+# Check the data on subscriber
+$result = $node_subscriber->safe_psql('postgres', qq(
+	SELECT a, b FROM tst_one_array ORDER BY a;
+	SELECT a, b, c, d FROM tst_arrays ORDER BY a;
+	SELECT a, b FROM tst_one_enum ORDER BY a;
+	SELECT a, b FROM tst_enums ORDER BY a;
+	SELECT a, b FROM tst_one_comp ORDER BY a;
+	SELECT a, b FROM tst_comps ORDER BY a;
+	SELECT a, b FROM tst_comp_enum ORDER BY a;
+	SELECT a, b FROM tst_comp_enum_array ORDER BY a;
+	SELECT a, b FROM tst_comp_one_enum_array ORDER BY a;
+	SELECT a, b FROM tst_comp_enum_what ORDER BY a;
+	SELECT a, b FROM tst_comp_mix_array ORDER BY a;
+	SELECT a, b FROM tst_range ORDER BY a;
+	SELECT a, b, c FROM tst_range_array ORDER BY a;
+));
+
+is($result, '1|{4,5,6}
+2|{2,3,1}
+3|{3,2,1}
+4|{4,5,6,1}
+5|{4,5,6,1}
+{1,2,3}|{1a,2b,3c}|{1,2,3}|{"1 day 00:00:01","2 days 00:00:02","3 days 00:00:03"}
+{2,3,1}|{b,c,a}|{2.2,3.3,1.1}|{00:02:00,00:03:00,00:01:00}
+{3,1,2}|{c,a,b}|{3.3,1.1,2.2}|{"3 years","1 year","2 years"}
+{4,1,2}|{c,d,e}|{3,4,5}|{"3 days 00:00:01","4 days 00:00:02","5 days 00:00:03"}
+{5,NULL,NULL}|{c,d,e}|{3,4,5}|{"3 days 00:00:01","4 days 00:00:02","5 days 00:00:03"}
+1|c
+2|b
+3|c
+4|
+5|
+a|{e,NULL}
+b|{c,a}
+c|{b,a}
+d|{e,d}
+e|{e,d}
+1|(1,A,1)
+2|(2,b,2)
+3|(3,c,3)
+4|(,x,-1)
+5|(,x,-1)
+(1,a,1)|{"(9,x,-1)"}
+(2,b,2)|{"(2,b,2)"}
+(3,c,3)|{"(3,c,3)"}
+(4,d,4)|{NULL,"(9,x,)"}
+(5,e,)|{NULL,"(9,x,)"}
+1|(1,,)
+2|(2,b,2)
+3|(3,c,3)
+4|(4,d,44)
+5|(4,d,44)
+(1,a,1)|{NULL,"(3,d,3)"}
+(2,b,2)|{"(2,b,2)"}
+(3,c,3)|{"(3,c,3)"}
+(4,d,3)|{"(1,a,1)","(2,b,2)"}
+(5,e,3)|{"(1,a,1)","(2,b,2)"}
+1|(1,"{a,e,c}",)
+2|(2,"{a,b,c}",2)
+3|(3,"{a,b,c}",3)
+4|(4,"{c,b,d}",4)
+5|(4,"{c,b,d}",4)
+(1,"{a,b,c}",1)|{NULL,"(1,\"{a,b,c}\",1)","(,\"{a,e,c}\",2)"}
+(2,"{b,c,a}",2)|{"(2,\"{b,c,a}\",1)"}
+(3,"{c,a,b}",1)|{"(3,\"{c,a,b}\",1)"}
+(4,"{c,b,d}",4)|{"(5,\"{a,b,c}\",5)"}
+(5,"{c,NULL,b}",)|{"(5,\"{a,b,c}\",5)"}
+("(1,a,1)","{""(1,a,1)"",""(2,b,2)""}",a,"{a,b,NULL,c}")|{"(\"(1,a,1)\",\"{\"\"(1,a,1)\"\",\"\"(2,b,2)\"\",NULL}\",a,\"{a,b,c}\")",NULL}
+1|[100,1001)
+2|[2,21)
+3|[3,31)
+4|[2,90)
+5|[2,90)
+1|["2014-08-04 00:00:00+02",infinity)|{"[100,1001)"}
+2|["2014-08-02 00:00:00+02","2014-08-04 00:00:00+02")|{"[2,4)","[20,31)"}
+3|["2014-08-01 00:00:00+02","2014-08-04 00:00:00+02")|{"[3,5)"}
+4|["2014-08-04 00:00:00+02",infinity)|{NULL,"[11,10000000)"}
+5|["2014-08-04 00:00:00+02",infinity)|{NULL,"[11,10000000)"}',
+'check replicated updates on subscriber');
+
+# Run batch of deletes
+$node_provider->safe_psql('postgres', qq(
+	DELETE FROM tst_one_array WHERE a = 1;
+	DELETE FROM tst_one_array WHERE b = '{2, 3, 1}';
+	DELETE FROM tst_arrays WHERE a = '{1, 2, 3}';
+	DELETE FROM tst_arrays WHERE a[1] = 2;
+	DELETE FROM tst_one_enum WHERE a = 1;
+	DELETE FROM tst_one_enum WHERE b = 'b';
+	DELETE FROM tst_enums WHERE a = 'a';
+	DELETE FROM tst_enums WHERE b[1] = 'b';
+	DELETE FROM tst_one_comp WHERE a = 1;
+	DELETE FROM tst_one_comp WHERE (b).a = 2.0;
+	DELETE FROM tst_comps WHERE (a).b = 'a';
+	DELETE FROM tst_comps WHERE ROW(3, 'c', 3)::tst_comp_basic_t = ANY(b);
+	DELETE FROM tst_comp_enum WHERE a = 1;
+	DELETE FROM tst_comp_enum WHERE (b).a = 2.0;
+	DELETE FROM tst_comp_enum_array WHERE a = ROW(1.0, 'a', 1)::tst_comp_enum_t;
+	DELETE FROM tst_comp_enum_array WHERE ROW(3, 'c', 3)::tst_comp_enum_t = ANY(b);
+	DELETE FROM tst_comp_one_enum_array WHERE a = 1;
+	DELETE FROM tst_comp_one_enum_array WHERE 'a' = ANY((b).b);
+	DELETE FROM tst_comp_enum_what WHERE (a).a = 1;
+	DELETE FROM tst_comp_enum_what WHERE (b[1]).b = '{c, a, b}';
+	DELETE FROM tst_comp_mix_array WHERE ((a).a).a = 1;
+	DELETE FROM tst_range WHERE a = 1;
+	DELETE FROM tst_range WHERE '[10,20]' && b;
+	DELETE FROM tst_range_array WHERE a = 1;
+	DELETE FROM tst_range_array WHERE tstzrange('Mon Aug 04 00:00:00 2014 CEST'::timestamptz, 'Mon Aug 05 00:00:00 2014 CEST'::timestamptz) && b;
+));
+
+$node_provider->poll_query_until('postgres', $caughtup_query)
+  or die "Timed out while waiting for subscriber to catch up";
+
+# Check the data on subscriber
+$result = $node_subscriber->safe_psql('postgres', qq(
+	SELECT a, b FROM tst_one_array ORDER BY a;
+	SELECT a, b, c, d FROM tst_arrays ORDER BY a;
+	SELECT a, b FROM tst_one_enum ORDER BY a;
+	SELECT a, b FROM tst_enums ORDER BY a;
+	SELECT a, b FROM tst_one_comp ORDER BY a;
+	SELECT a, b FROM tst_comps ORDER BY a;
+	SELECT a, b FROM tst_comp_enum ORDER BY a;
+	SELECT a, b FROM tst_comp_enum_array ORDER BY a;
+	SELECT a, b FROM tst_comp_one_enum_array ORDER BY a;
+	SELECT a, b FROM tst_comp_enum_what ORDER BY a;
+	SELECT a, b FROM tst_comp_mix_array ORDER BY a;
+	SELECT a, b FROM tst_range ORDER BY a;
+	SELECT a, b, c FROM tst_range_array ORDER BY a;
+));
+
+is($result, '3|{3,2,1}
+4|{4,5,6,1}
+5|{4,5,6,1}
+{3,1,2}|{c,a,b}|{3.3,1.1,2.2}|{"3 years","1 year","2 years"}
+{4,1,2}|{c,d,e}|{3,4,5}|{"3 days 00:00:01","4 days 00:00:02","5 days 00:00:03"}
+{5,NULL,NULL}|{c,d,e}|{3,4,5}|{"3 days 00:00:01","4 days 00:00:02","5 days 00:00:03"}
+3|c
+4|
+5|
+b|{c,a}
+d|{e,d}
+e|{e,d}
+3|(3,c,3)
+4|(,x,-1)
+5|(,x,-1)
+(2,b,2)|{"(2,b,2)"}
+(4,d,4)|{NULL,"(9,x,)"}
+(5,e,)|{NULL,"(9,x,)"}
+3|(3,c,3)
+4|(4,d,44)
+5|(4,d,44)
+(2,b,2)|{"(2,b,2)"}
+(4,d,3)|{"(1,a,1)","(2,b,2)"}
+(5,e,3)|{"(1,a,1)","(2,b,2)"}
+4|(4,"{c,b,d}",4)
+5|(4,"{c,b,d}",4)
+(2,"{b,c,a}",2)|{"(2,\"{b,c,a}\",1)"}
+(4,"{c,b,d}",4)|{"(5,\"{a,b,c}\",5)"}
+(5,"{c,NULL,b}",)|{"(5,\"{a,b,c}\",5)"}
+2|["2014-08-02 00:00:00+02","2014-08-04 00:00:00+02")|{"[2,4)","[20,31)"}
+3|["2014-08-01 00:00:00+02","2014-08-04 00:00:00+02")|{"[3,5)"}',
+'check replicated deletes on subscriber');
+
+$node_subscriber->stop('fast');
+$node_provider->stop('fast');
-- 
2.7.4

0006-Logical-replication-support-for-initial-data-copy.patchapplication/x-patch; name=0006-Logical-replication-support-for-initial-data-copy.patchDownload
From 2b2bc8c5d670c765ed3eb771dee266f6b9b3f3c2 Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Tue, 19 Jul 2016 01:55:25 +0200
Subject: [PATCH 6/6] Logical replication support for initial data copy

---
 src/backend/catalog/Makefile                       |   2 +-
 src/backend/commands/subscriptioncmds.c            |  34 +-
 .../libpqwalreceiver/libpqwalreceiver.c            | 126 +++-
 src/backend/replication/logical/Makefile           |   2 +-
 src/backend/replication/logical/apply.c            | 173 +++++-
 src/backend/replication/logical/launcher.c         |  92 +--
 src/backend/replication/logical/logical.c          | 190 +++++-
 src/backend/replication/logical/proto.c            |  63 +-
 src/backend/replication/logical/publication.c      |  40 ++
 src/backend/replication/logical/snapbuild.c        |   5 +-
 src/backend/replication/logical/subscription.c     | 180 ++++++
 src/backend/replication/logical/tablesync.c        | 672 +++++++++++++++++++++
 src/backend/replication/pgoutput/pgoutput.c        | 108 ++++
 src/backend/replication/repl_gram.y                |  31 +-
 src/backend/replication/repl_scanner.l             |   3 +
 src/backend/replication/walsender.c                | 199 +++++-
 src/backend/utils/cache/syscache.c                 |  23 +
 src/include/catalog/indexing.h                     |   6 +
 src/include/catalog/pg_subscription_rel.h          |  61 ++
 src/include/commands/replicationcmds.h             |   1 +
 src/include/nodes/nodes.h                          |   4 +-
 src/include/nodes/replnodes.h                      |  23 +
 src/include/replication/logical.h                  |  22 +-
 src/include/replication/logicalproto.h             |   3 +-
 src/include/replication/logicalworker.h            |   9 +-
 src/include/replication/output_plugin.h            |  18 +
 src/include/replication/publication.h              |   1 +
 src/include/replication/subscription.h             |   6 +
 src/include/replication/walreceiver.h              |   9 +
 src/include/replication/worker_internal.h          |  32 +
 src/include/utils/syscache.h                       |   2 +
 src/test/Makefile                                  |   2 +-
 src/test/README                                    |   3 +
 src/test/regress/expected/sanity_check.out         |   1 +
 src/test/subscription/t/001_rep_changes.pl         |  18 +-
 src/test/subscription/t/002_types.pl               |   8 +-
 36 files changed, 2043 insertions(+), 129 deletions(-)
 create mode 100644 src/backend/replication/logical/tablesync.c
 create mode 100644 src/include/catalog/pg_subscription_rel.h
 create mode 100644 src/include/replication/worker_internal.h

diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 60737d4..22f78f8 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,7 +43,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
 	pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
 	pg_collation.h pg_range.h pg_transform.h \
 	pg_publication.h pg_publication_rel.h pg_subscription.h \
-	toasting.h indexing.h \
+	pg_subscription_rel.h toasting.h indexing.h \
     )
 
 # location of Catalog.pm
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 43e2853..bfef492 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -22,11 +22,13 @@
 #include "access/htup_details.h"
 #include "access/xact.h"
 
+#include "catalog/dependency.h"
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/objectaddress.h"
 #include "catalog/pg_type.h"
 #include "catalog/pg_subscription.h"
+#include "catalog/pg_subscription_rel.h"
 
 #include "commands/defrem.h"
 #include "commands/replicationcmds.h"
@@ -40,9 +42,12 @@
 #include "replication/logicalworker.h"
 #include "replication/origin.h"
 #include "replication/reorderbuffer.h"
-#include "replication/logicalworker.h"
+#include "replication/subscription.h"
 #include "replication/walreceiver.h"
 
+#include "storage/ipc.h"
+#include "storage/proc.h"
+
 #include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/catcache.h"
@@ -172,10 +177,13 @@ CreateSubscription(CreateSubscriptionStmt *stmt)
 	char	   *conninfo;
 	char	   *slotname;
 	List	   *publications;
+	char	   *options;
 	WalReceiverConnHandle  *wrchandle = NULL;
 	WalReceiverConnAPI	   *wrcapi = NULL;
 	walrcvconn_init_fn		walrcvconn_init;
 	XLogRecPtr	lsn;
+	List	   *tables;
+	ListCell   *lc;
 
 	check_replication_permissions();
 
@@ -248,6 +256,12 @@ CreateSubscription(CreateSubscriptionStmt *stmt)
 		wrcapi->create_slot == NULL)
 		elog(ERROR, "libpqwalreceiver didn't initialize correctly");
 
+	/*
+	 * Create the replication slot on remote side for our newly created
+	 * subscription.
+	 *
+	 * TODO: ensure drop of the slot on subsequent failure/rollback?
+	 */
 	wrcapi->connect(wrchandle, conninfo, true, stmt->subname);
 	wrcapi->create_slot(wrchandle, slotname, true, &lsn);
 	ereport(NOTICE,
@@ -259,6 +273,24 @@ CreateSubscription(CreateSubscriptionStmt *stmt)
 	 */
 	replorigin_create(slotname);
 
+	/* Build option string for the plugin. */
+	options = logicalrep_build_options(publications);
+
+	/* Get the table list from provider and build local table status info. */
+	tables = wrcapi->list_tables(wrchandle, slotname, options);
+	foreach (lc, tables)
+	{
+		LogicalRepTableListEntry *entry = lfirst(lc);
+		Oid		nspid = LookupExplicitNamespace(entry->nspname, false);
+		Oid		relid = get_relname_relid(entry->relname, nspid);
+
+		SetSubscriptionRelState(subid, relid, SUBREL_STATE_INIT,
+								InvalidXLogRecPtr);
+	}
+
+	ereport(NOTICE,
+			(errmsg("synchronized table states")));
+
 	/* And we are done with the remote side. */
 	wrcapi->disconnect(wrchandle);
 
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 4c4d441..94648c7 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -23,6 +23,7 @@
 #include "pqexpbuffer.h"
 #include "access/xlog.h"
 #include "miscadmin.h"
+#include "replication/logical.h"
 #include "replication/walreceiver.h"
 #include "utils/builtins.h"
 #include "utils/pg_lsn.h"
@@ -73,6 +74,11 @@ static int	libpqrcv_receive(WalReceiverConnHandle *handle, char **buffer,
 							 pgsocket *wait_fd);
 static void libpqrcv_send(WalReceiverConnHandle *handle, const char *buffer,
 						  int nbytes);
+static List *libpqrcv_list_tables(WalReceiverConnHandle *handle,
+								 char *slotname, char *options);
+static bool libpqrcv_copy_table(WalReceiverConnHandle *handle,
+								char *slotname, char *nspname,
+								char *relname, char *options);
 static void libpqrcv_disconnect(WalReceiverConnHandle *handle);
 
 /* Prototypes for private functions */
@@ -104,6 +110,8 @@ _PG_walreceirver_conn_init(WalReceiverConnAPI *wrcapi)
 	wrcapi->receive = libpqrcv_receive;
 	wrcapi->send = libpqrcv_send;
 	wrcapi->disconnect = libpqrcv_disconnect;
+	wrcapi->copy_table = libpqrcv_copy_table;
+	wrcapi->list_tables = libpqrcv_list_tables;
 
 	return handle;
 }
@@ -416,15 +424,15 @@ libpqrcv_endstreaming(WalReceiverConnHandle *handle, TimeLineID *next_tli)
 			(errmsg("could not send end-of-streaming message to primary: %s",
 					PQerrorMessage(handle->streamConn))));
 
+	*next_tli = 0;
+
 	/*
 	 * After COPY is finished, we should receive a result set indicating the
 	 * next timeline's ID, or just CommandComplete if the server was shut
 	 * down.
 	 *
-	 * If we had not yet received CopyDone from the backend, PGRES_COPY_IN
-	 * would also be possible. However, at the moment this function is only
-	 * called after receiving CopyDone from the backend - the walreceiver
-	 * never terminates replication on its own initiative.
+	 * If we had not yet received CopyDone from the backend, PGRES_COPY_OUT
+	 * is also possible in case we aborted the copy in mid-stream.
 	 */
 	res = PQgetResult(handle->streamConn);
 	if (PQresultStatus(res) == PGRES_TUPLES_OK)
@@ -442,8 +450,16 @@ libpqrcv_endstreaming(WalReceiverConnHandle *handle, TimeLineID *next_tli)
 		/* the result set should be followed by CommandComplete */
 		res = PQgetResult(handle->streamConn);
 	}
-	else
-		*next_tli = 0;
+	else if (PQresultStatus(res) == PGRES_COPY_OUT)
+	{
+		PQclear(res);
+
+		/* End the copy */
+		PQendcopy(handle->streamConn);
+
+		/* CommandComplete should follow */
+		res = PQgetResult(handle->streamConn);
+	}
 
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
 		ereport(ERROR,
@@ -642,6 +658,104 @@ libpqrcv_PQexec(WalReceiverConnHandle *handle, const char *query)
 }
 
 /*
+ * Run the LIST_TABLES command which will send list of the tables to copy
+ * in whatever format the plugin choses.
+ */
+static List *
+libpqrcv_list_tables(WalReceiverConnHandle *handle, char *slotname,
+					 char *options)
+{
+	StringInfoData	cmd;
+	PGresult	   *res;
+	int				i;
+	List		   *tablelist = NIL;
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd, "LIST_TABLES SLOT \"%s\"",
+					 slotname);
+
+	/* Add options */
+	if (options)
+		appendStringInfo(&cmd, "( %s )", options);
+
+	res = libpqrcv_PQexec(handle, cmd.data);
+	pfree(cmd.data);
+
+	if (PQresultStatus(res) != PGRES_TUPLES_OK)
+	{
+		PQclear(res);
+		ereport(ERROR,
+				(errmsg("could not receive list of replicated tables from the provider: %s",
+						PQerrorMessage(handle->streamConn))));
+	}
+	if (PQnfields(res) != 3)
+	{
+		int nfields = PQnfields(res);
+		PQclear(res);
+		ereport(ERROR,
+				(errmsg("invalid response from provider"),
+				 errdetail("Expected 3 fields, got %d fields.", nfields)));
+	}
+
+	for (i = 0; i < PQntuples(res); i++)
+	{
+		LogicalRepTableListEntry *entry;
+
+		entry = palloc(sizeof(LogicalRepTableListEntry));
+		entry->nspname = pstrdup(PQgetvalue(res, i, 0));
+		entry->relname = pstrdup(PQgetvalue(res, i, 1));
+		if (!PQgetisnull(res, i, 2))
+			entry->info = pstrdup(PQgetvalue(res, i, 2));
+		else
+			entry->info = NULL;
+
+		tablelist = lappend(tablelist, entry);
+	}
+
+	PQclear(res);
+
+	return tablelist;
+}
+
+/*
+ * Run the COPY_TABLE command which will start streaming the existing data
+ * in the table.
+ */
+static bool
+libpqrcv_copy_table(WalReceiverConnHandle *handle, char *slotname,
+					char *nspname, char *relname, char *options)
+{
+	StringInfoData	cmd;
+	PGresult	   *res;
+
+	initStringInfo(&cmd);
+	appendStringInfo(&cmd, "COPY_TABLE SLOT \"%s\" TABLE \"%s\" \"%s\"",
+					 slotname, nspname, relname);
+
+	/* Add options */
+	if (options)
+		appendStringInfo(&cmd, "( %s )", options);
+
+	res = libpqrcv_PQexec(handle, cmd.data);
+
+	if (PQresultStatus(res) == PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		return false;
+	}
+	else if (PQresultStatus(res) != PGRES_COPY_BOTH)
+	{
+		PQclear(res);
+		ereport(ERROR,
+				(errmsg("could not start initial table contents streaming: %s",
+						PQerrorMessage(handle->streamConn))));
+	}
+	PQclear(res);
+	pfree(cmd.data);
+	return true;
+}
+
+/*
  * Disconnect connection to primary, if any.
  */
 static void
diff --git a/src/backend/replication/logical/Makefile b/src/backend/replication/logical/Makefile
index ab6e11e..a1e1d81 100644
--- a/src/backend/replication/logical/Makefile
+++ b/src/backend/replication/logical/Makefile
@@ -16,6 +16,6 @@ override CPPFLAGS := -I$(srcdir) $(CPPFLAGS)
 
 OBJS = apply.o decode.o launcher.o logical.o logicalfuncs.o message.o \
 	   origin.o proto.o publication.o reorderbuffer.o snapbuild.o \
-	   subscription.o
+	   subscription.o tablesync.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/replication/logical/apply.c b/src/backend/replication/logical/apply.c
index eb7af19..809b77b 100644
--- a/src/backend/replication/logical/apply.c
+++ b/src/backend/replication/logical/apply.c
@@ -24,6 +24,7 @@
 #include "access/xlog_internal.h"
 
 #include "catalog/namespace.h"
+#include "catalog/pg_subscription_rel.h"
 
 #include "commands/trigger.h"
 
@@ -51,6 +52,7 @@
 #include "replication/snapbuild.h"
 #include "replication/subscription.h"
 #include "replication/walreceiver.h"
+#include "replication/worker_internal.h"
 
 #include "rewrite/rewriteHandler.h"
 
@@ -62,6 +64,7 @@
 
 #include "utils/builtins.h"
 #include "utils/catcache.h"
+#include "utils/fmgroids.h"
 #include "utils/guc.h"
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
@@ -72,17 +75,27 @@
 
 typedef struct FlushPosition
 {
-	dlist_node node;
-	XLogRecPtr local_end;
-	XLogRecPtr remote_end;
+	dlist_node	node;
+	XLogRecPtr	local_end;
+	XLogRecPtr	remote_end;
 } FlushPosition;
 
 static dlist_head lsn_mapping = DLIST_STATIC_INIT(lsn_mapping);
 
+
+typedef struct TableState
+{
+	dlist_node	node;
+	Oid			relid;
+	XLogRecPtr	lsn;
+	char		state;
+} TableState;
+
+static dlist_head table_states = DLIST_STATIC_INIT(table_states);
+static XLogRecPtr		last_commit_lsn;
+
 static MemoryContext	ApplyContext;
-static bool				in_remote_transaction = false;
 
-static Subscription	   *MySubscription = NULL;
 static bool				got_SIGTERM = false;
 
 typedef struct LogicalRepRelMapEntry {
@@ -95,13 +108,19 @@ typedef struct LogicalRepRelMapEntry {
 										 * local ones */
 	AttInMetadata	   *attin;			/* cached info used in type
 										 * conversion */
+	char				state;
 } LogicalRepRelMapEntry;
 
 static HTAB *LogicalRepRelMap = NULL;
 
-/* filled by libpqreceiver when loaded */
-static WalReceiverConnAPI *wrcapi = NULL;
-static WalReceiverConnHandle *wrchandle = NULL;
+WalReceiverConnAPI	   *wrcapi = NULL;
+WalReceiverConnHandle  *wrchandle = NULL;
+
+LogicalRepWorker   *MyLogicalRepWorker = NULL;
+Subscription	   *MySubscription = NULL;
+
+static char	   *myslotname = NULL;
+bool			in_remote_transaction = false;
 
 static void send_feedback(XLogRecPtr recvpos, int64 now, bool force);
 void pglogical_apply_main(Datum main_arg);
@@ -109,6 +128,19 @@ void pglogical_apply_main(Datum main_arg);
 static bool tuple_find_by_replidx(Relation rel, LockTupleMode lockmode,
 					  TupleTableSlot *searchslot, TupleTableSlot *slot);
 
+/*
+ * Should this worker apply changes for given relation.
+ *
+ * This is mainly needed for initial relation data sync as that runs in
+ * parallel worker and we need some way to skip changes coming to the main
+ * apply worker during the sync of a table.
+ */
+static bool
+interesting_relation(LogicalRepRelMapEntry *rel)
+{
+	return rel->state == SUBREL_STATE_READY ||
+		rel->reloid == MyLogicalRepWorker->relid;
+}
 
 
 /*
@@ -266,12 +298,16 @@ tupdesc_get_att_by_name(TupleDesc desc, const char *attname)
 
 /*
  * Open the local relation associated with the remote one.
+ *
+ * Optionally rebuilds the Relcache mapping if it was invalidated
+ * by local DDL.
  */
 static LogicalRepRelMapEntry *
 logicalreprel_open(uint32 remoteid, LOCKMODE lockmode)
 {
 	LogicalRepRelMapEntry  *entry;
 	bool		found;
+	XLogRecPtr	lsn;
 
 	if (LogicalRepRelMap == NULL)
 		remoterelmap_init();
@@ -309,6 +345,10 @@ logicalreprel_open(uint32 remoteid, LOCKMODE lockmode)
 	else
 		entry->rel = heap_open(entry->reloid, lockmode);
 
+	/* TODO cache this */
+	entry->state = GetSubscriptionRelState(MySubscription->oid,
+										   entry->reloid, &lsn, true);
+
 	return entry;
 }
 
@@ -322,7 +362,6 @@ logicalreprel_close(LogicalRepRelMapEntry *rel, LOCKMODE lockmode)
 	rel->rel = NULL;
 }
 
-
 /*
  * Make sure that we started local transaction.
  *
@@ -599,6 +638,9 @@ handle_commit(StringInfo s)
 
 	in_remote_transaction = false;
 
+	last_commit_lsn = end_lsn;
+	process_syncing_tables(myslotname, end_lsn);
+
 	pgstat_report_activity(STATE_IDLE, NULL);
 }
 
@@ -651,6 +693,15 @@ handle_insert(StringInfo s)
 
 	relid = logicalrep_read_insert(s, &newtup);
 	rel = logicalreprel_open(relid, RowExclusiveLock);
+	if (!interesting_relation(rel))
+	{
+		/*
+		 * The relation can't become interestin in the middle of the
+		 * transaction so it's safe to unlock it.
+		 */
+		logicalreprel_close(rel, RowExclusiveLock);
+		return;
+	}
 
 	/* Initialize the executor state. */
 	estate = create_estate_for_relation(rel);
@@ -710,6 +761,15 @@ handle_update(StringInfo s)
 	relid = logicalrep_read_update(s, &hasoldtup, &oldtup,
 								   &newtup);
 	rel = logicalreprel_open(relid, RowExclusiveLock);
+	if (!interesting_relation(rel))
+	{
+		/*
+		 * The relation can't become interestin in the middle of the
+		 * transaction so it's safe to unlock it.
+		 */
+		logicalreprel_close(rel, RowExclusiveLock);
+		return;
+	}
 
 	/* Initialize the executor state. */
 	estate = create_estate_for_relation(rel);
@@ -796,6 +856,15 @@ handle_delete(StringInfo s)
 
 	relid = logicalrep_read_delete(s, &oldtup);
 	rel = logicalreprel_open(relid, RowExclusiveLock);
+	if (!interesting_relation(rel))
+	{
+		/*
+		 * The relation can't become interestin in the middle of the
+		 * transaction so it's safe to unlock it.
+		 */
+		logicalreprel_close(rel, RowExclusiveLock);
+		return;
+	}
 
 	/* Initialize the executor state. */
 	estate = create_estate_for_relation(rel);
@@ -942,11 +1011,9 @@ get_flush_position(XLogRecPtr *write, XLogRecPtr *flush)
 /*
  * Apply main loop.
  */
-static void
-ApplyLoop(void)
+void
+LogicalRepApplyLoop(XLogRecPtr last_received)
 {
-	XLogRecPtr	last_received = InvalidXLogRecPtr;
-
 	/* Init the ApplyContext which we use for easier cleanup. */
 	ApplyContext = AllocSetContextCreate(TopMemoryContext,
 										 "ApplyContext",
@@ -1029,6 +1096,9 @@ ApplyLoop(void)
 						/* timestamp = */ pq_getmsgint64(&s);
 						reply_requested = pq_getmsgbyte(&s);
 
+						if (last_received < endpos)
+							last_received = endpos;
+
 						send_feedback(endpos,
 									  GetCurrentTimestamp(),
 									  reply_requested);
@@ -1040,6 +1110,18 @@ ApplyLoop(void)
 			}
 		}
 
+		if (!in_remote_transaction)
+		{
+			/*
+			 * If we didn't get any transactions for a while there might be
+			 * unconsumer invalidation messages in the queue, consume them now.
+			 */
+			AcceptInvalidationMessages();
+
+			/* Process any table synchronization changes. */
+			process_syncing_tables(myslotname, last_received);
+		}
+
 		/* confirm all writes at once */
 		send_feedback(last_received, GetCurrentTimestamp(), false);
 
@@ -1049,7 +1131,11 @@ ApplyLoop(void)
 
 		/* Check if we need to exit the streaming loop. */
 		if (endofstream)
+		{
+			TimeLineID	tli;
+			wrcapi->endstreaming(wrchandle, &tli);
 			break;
+		}
 
 		/*
 		 * Wait for more data or latch.
@@ -1156,7 +1242,6 @@ ApplyWorkerMain(Datum main_arg)
 {
 	int				worker_slot = DatumGetObjectId(main_arg);
 	MemoryContext	oldctx;
-	RepOriginId		originid;
 	XLogRecPtr		origin_startpos;
 	char		   *options;
 	walrcvconn_init_fn walrcvconn_init;
@@ -1207,41 +1292,65 @@ ApplyWorkerMain(Datum main_arg)
 	BackgroundWorkerInitializeConnectionByOid(MyLogicalRepWorker->dbid,
 											  InvalidOid);
 
-	StartTransactionCommand();
-
 	/* Load the subscription into persistent memory context. */
+	StartTransactionCommand();
 	oldctx = MemoryContextSwitchTo(CacheMemoryContext);
 	MySubscription = GetSubscription(MyLogicalRepWorker->subid);
 	MemoryContextSwitchTo(oldctx);
 
-	elog(LOG, "logical replication apply for subscription %s started",
-		 MySubscription->name);
-
-	/* Setup replication origin tracking. */
-	originid = replorigin_by_name(MySubscription->slotname, true);
-	if (!OidIsValid(originid))
-		originid = replorigin_create(MySubscription->slotname);
-	replorigin_session_setup(originid);
-	replorigin_session_origin = originid;
-	origin_startpos = replorigin_session_get_progress(false);
-
+	if (OidIsValid(MyLogicalRepWorker->relid))
+		elog(LOG, "logical replication sync for subscription %s, table %s started",
+			 MySubscription->name, get_rel_name(MyLogicalRepWorker->relid));
+	else
+		elog(LOG, "logical replication apply for subscription %s started",
+			 MySubscription->name);
 	CommitTransactionCommand();
 
 	/* Connect to the origin and start the replication. */
 	elog(DEBUG1, "connecting to provider using connection string %s",
 		 MySubscription->conninfo);
-	wrcapi->connect(wrchandle, MySubscription->conninfo, true,
-					MySubscription->name);
 
 	/* Build option string for the plugin. */
 	options = logicalrep_build_options(MySubscription->publications);
 
-	/* Start streaming from the slot. */
+	if (OidIsValid(MyLogicalRepWorker->relid))
+	{
+		/* This is table synchroniation worker, call initial sync. */
+		myslotname = LogicalRepSyncTableStart(&origin_startpos);
+	}
+	else
+	{
+		/* This is main apply worker */
+		RepOriginId		originid;
+
+		myslotname = MySubscription->slotname;
+
+		StartTransactionCommand();
+		originid = replorigin_by_name(myslotname, false);
+		replorigin_session_setup(originid);
+		replorigin_session_origin = originid;
+		CommitTransactionCommand();
+
+		wrcapi->connect(wrchandle, MySubscription->conninfo, true,
+						myslotname);
+	}
+
+	/*
+	 * Setup callback for syscache so that we know when something
+	 * changes in the subscription relation state.
+	 */
+	CacheRegisterSyscacheCallback(SUBSCRIPTIONRELOID,
+								  invalidate_syncing_table_states,
+								  (Datum) 0);
+
+	/* Start normal logical streaming replication. */
 	wrcapi->startstreaming_logical(wrchandle, origin_startpos,
-								   MySubscription->slotname, options);
+								   myslotname, options);
+
+	pfree(options);
 
 	/* Run the main loop. */
-	ApplyLoop();
+	LogicalRepApplyLoop(origin_startpos);
 
 	wrcapi->disconnect(wrchandle);
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 385260e..927af69 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -30,6 +30,7 @@
 
 #include "replication/logicalworker.h"
 #include "replication/subscription.h"
+#include "replication/worker_internal.h"
 
 #include "storage/ipc.h"
 #include "storage/proc.h"
@@ -44,7 +45,6 @@
 #include "utils/snapmgr.h"
 
 int	max_logical_replication_workers = 4;
-LogicalRepWorker *MyLogicalRepWorker = NULL;
 
 typedef struct LogicalRepCtxStruct
 {
@@ -57,8 +57,6 @@ typedef struct LogicalRepCtxStruct
 
 LogicalRepCtxStruct *LogicalRepCtx;
 
-static LogicalRepWorker *logicalrep_worker_find(Oid subid);
-static void logicalrep_worker_launch(Oid dbid, Oid subid);
 static void logicalrep_worker_stop(LogicalRepWorker *worker);
 static void logicalrep_worker_onexit(int code, Datum arg);
 static void logicalrep_worker_detach(void);
@@ -183,29 +181,50 @@ WaitForReplicationWorkerAttach(LogicalRepWorker *worker,
 
 /*
  * Walks the workers array and searches for one that matches given
- * subscription id.
+ * subscription id and relid.
  */
-static LogicalRepWorker *
-logicalrep_worker_find(Oid subid)
+LogicalRepWorker *
+logicalrep_worker_find(Oid subid, Oid relid)
 {
 	int	i;
 	LogicalRepWorker   *res = NULL;
 
-	/* Block concurrent modification. */
-	LWLockAcquire(LogicalRepLauncherLock, LW_SHARED);
+	Assert(LWLockHeldByMe(LogicalRepLauncherLock));
 
 	/* Search for attached worker for a given subscription id. */
 	for (i = 0; i < max_logical_replication_workers; i++)
 	{
 		LogicalRepWorker   *w = &LogicalRepCtx->workers[i];
-		if (w->subid == subid && w->proc && IsBackendPid(w->proc->pid))
+		if (w->subid == subid && w->relid == relid &&
+			w->proc && IsBackendPid(w->proc->pid))
 		{
 			res = w;
 			break;
 		}
 	}
 
-	LWLockRelease(LogicalRepLauncherLock);
+	return res;
+}
+
+/*
+ * Walks the workers array and searches for ones that matches given
+ * subscription id and counts them.
+ */
+int
+logicalrep_worker_count(Oid subid)
+{
+	int	i;
+	int	res = 0;
+
+	Assert(LWLockHeldByMe(LogicalRepLauncherLock));
+
+	/* Search for attached worker for a given subscription id. */
+	for (i = 0; i < max_logical_replication_workers; i++)
+	{
+		LogicalRepWorker   *w = &LogicalRepCtx->workers[i];
+		if (w->subid == subid && w->proc && IsBackendPid(w->proc->pid))
+			res++;
+	}
 
 	return res;
 }
@@ -213,17 +232,18 @@ logicalrep_worker_find(Oid subid)
 /*
  * Start new apply background worker.
  */
-static void
-logicalrep_worker_launch(Oid dbid, Oid subid)
+void
+logicalrep_worker_launch(Oid dbid, Oid subid, Oid relid)
 {
 	BackgroundWorker	bgw;
 	BackgroundWorkerHandle *bgw_handle;
 	int					slot;
 	LogicalRepWorker   *worker = NULL;
 
-	ereport(LOG,
-			(errmsg("starting logical replication worker for subscription %u",
-					subid)));
+	ereport(DEBUG1,
+			(errmsg("starting logical replication worker for "
+					"subscription %u, relation %u",
+					subid, relid)));
 
 	/*
 	 * We need to do the modification of the shared memory under lock so that
@@ -256,6 +276,7 @@ logicalrep_worker_launch(Oid dbid, Oid subid)
 	memset(worker, 0, sizeof(LogicalRepWorker));
 	worker->dbid = dbid;
 	worker->subid = subid;
+	worker->relid = relid;
 
 	LWLockRelease(LogicalRepLauncherLock);
 
@@ -265,6 +286,13 @@ logicalrep_worker_launch(Oid dbid, Oid subid)
 	bgw.bgw_start_time = BgWorkerStart_RecoveryFinished;
 	bgw.bgw_main = ApplyWorkerMain;
 
+	if (OidIsValid(relid))
+		snprintf(bgw.bgw_name, BGW_MAXLEN,
+				 "logical replication worker %u sync %u", subid, relid);
+	else
+		snprintf(bgw.bgw_name, BGW_MAXLEN,
+				 "logical replication worker %u", subid);
+
 	bgw.bgw_restart_time = BGW_NEVER_RESTART;
 	bgw.bgw_notify_pid = MyProcPid;
 	bgw.bgw_main_arg = slot;
@@ -283,7 +311,7 @@ logicalrep_worker_launch(Oid dbid, Oid subid)
 	if (!WaitForReplicationWorkerAttach(worker, bgw_handle))
 	{
 		ereport(WARNING,
-				(errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("could not launch logical replication worker")));
 		return;
 	}
@@ -296,7 +324,7 @@ logicalrep_worker_launch(Oid dbid, Oid subid)
 static void
 logicalrep_worker_stop(LogicalRepWorker *worker)
 {
-	LWLockAcquire(LogicalRepLauncherLock, LW_EXCLUSIVE);
+	Assert(LWLockHeldByMe(LogicalRepLauncherLock));
 
 	/* Check that the worker is up and what we expect. */
 	if (!worker->proc)
@@ -306,27 +334,6 @@ logicalrep_worker_stop(LogicalRepWorker *worker)
 
 	/* Terminate the worker. */
 	kill(worker->proc->pid, SIGTERM);
-
-	LWLockRelease(LogicalRepLauncherLock);
-
-	/* Wait for it to detach. */
-	for (;;)
-	{
-		int	rc = WaitLatch(&MyProc->procLatch,
-						   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
-						   1000L);
-
-        /* emergency bailout if postmaster has died */
-        if (rc & WL_POSTMASTER_DEATH)
-			proc_exit(1);
-
-        ResetLatch(&MyProc->procLatch);
-
-		CHECK_FOR_INTERRUPTS();
-
-		if (!worker->proc)
-			return;
-	}
 }
 
 /*
@@ -504,16 +511,21 @@ ApplyLauncherMain(Datum main_arg)
 		foreach(lc, sublist)
 		{
 			Subscription	   *sub = (Subscription *) lfirst(lc);
-			LogicalRepWorker   *w = logicalrep_worker_find(sub->oid);
+			LogicalRepWorker   *w;
+
+			LWLockAcquire(LogicalRepLauncherLock, LW_SHARED);
+			w = logicalrep_worker_find(sub->oid, InvalidOid);
 
 			if (sub->enabled && w == NULL && startsub == NULL)
 				startsub = sub;
 			else if (!sub->enabled && w != NULL)
 				logicalrep_worker_stop(w);
+			LWLockRelease(LogicalRepLauncherLock);
 		}
 
 		if (startsub)
-			logicalrep_worker_launch(startsub->dbid, startsub->oid);
+			logicalrep_worker_launch(startsub->dbid, startsub->oid,
+									 InvalidOid);
 
 		/* Switch back to original memory context. */
 		MemoryContextSwitchTo(oldctx);
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index ecf9a03..4f456f6 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -30,6 +30,8 @@
 
 #include "miscadmin.h"
 
+#include "access/heapam.h"
+#include "access/htup.h"
 #include "access/xact.h"
 #include "access/xlog_internal.h"
 
@@ -43,6 +45,7 @@
 #include "storage/procarray.h"
 
 #include "utils/memutils.h"
+#include "utils/tuplestore.h"
 
 /* data for errcontext callback */
 typedef struct LogicalErrorCallbackState
@@ -65,6 +68,9 @@ static void change_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
 static void message_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
 				   XLogRecPtr message_lsn, bool transactional,
 				 const char *prefix, Size message_size, const char *message);
+static List *list_tables_cb_wrapper(LogicalDecodingContext *ctx);
+static void tuple_cb_wrapper(LogicalDecodingContext *ctx, Relation relation,
+							 HeapTuple tup);
 
 static void LoadOutputPlugin(OutputPluginCallbacks *callbacks, char *plugin);
 
@@ -401,6 +407,127 @@ CreateDecodingContext(XLogRecPtr start_lsn,
 }
 
 /*
+ * Create a new limited decoding context for base copy.
+ *
+ * nspname:
+ *		name of a schema
+ *
+ * relname
+ *		name of a relation
+ *
+ * output_plugin_options
+ *		contains options passed to the output plugin.
+ *
+ * prepare_write, do_write
+ *		callbacks that have to be filled to perform the use-case dependent,
+ *		actual work.
+ *
+ * Needs to be called while in a memory context that's at least as long lived
+ * as the decoding context because further memory contexts will be created
+ * inside it.
+ *
+ * Needs to be called inside transaction.
+ *
+ * Returns an initialized decoding context after calling the output plugin's
+ * startup function.
+ */
+LogicalDecodingContext *
+CreateCopyDecodingContext(List *output_plugin_options,
+						  LogicalOutputPluginWriterPrepareWrite prepare_write,
+						  LogicalOutputPluginWriterWrite do_write)
+{
+	LogicalDecodingContext *ctx;
+	ReplicationSlot *slot;
+	MemoryContext	context,
+					old_context;
+
+	/* shorter lines... */
+	slot = MyReplicationSlot;
+
+	/* first some sanity checks that are unlikely to be violated */
+	if (slot == NULL)
+		elog(ERROR, "cannot perform logical base copy without an acquired slot");
+
+	/* make sure the passed slot is suitable, these are user facing errors */
+	if (SlotIsPhysical(slot))
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 (errmsg("cannot use physical replication slot for logical base copy"))));
+
+	if (slot->data.database != MyDatabaseId)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+		  (errmsg("replication slot \"%s\" was not created in this database",
+				  NameStr(slot->data.name)))));
+
+	if (!IsTransactionOrTransactionBlock())
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 (errmsg("cannot perform table copy without snapshot"))));
+
+
+	context = AllocSetContextCreate(CurrentMemoryContext,
+									"Table Copy Context",
+									ALLOCSET_DEFAULT_MINSIZE,
+									ALLOCSET_DEFAULT_INITSIZE,
+									ALLOCSET_DEFAULT_MAXSIZE);
+	old_context = MemoryContextSwitchTo(context);
+	ctx = palloc0(sizeof(LogicalDecodingContext));
+
+	ctx->context = context;
+
+	/*
+	 * (re-)load output plugins, so we detect a bad (removed) output plugin
+	 * now.
+	 */
+	LoadOutputPlugin(&ctx->callbacks, NameStr(slot->data.plugin));
+
+	/* CHeck if the output plugin actually supports the copy operations. */
+	if (ctx->callbacks.list_tables_cb == NULL ||
+		ctx->callbacks.tuple_cb == NULL)
+		elog(ERROR, "output plugin \"%s\" does not support LIST_TABLES and COPY_TABLE comamnds",
+			 NameStr(slot->data.plugin));
+
+	/* Initialize non NULL fields. */
+	ctx->slot = slot;
+	ctx->out = makeStringInfo();
+	ctx->prepare_write = prepare_write;
+	ctx->write = do_write;
+
+	/* Make sure plugin sees the options. */
+	ctx->output_plugin_options = output_plugin_options;
+
+	/* call output plugin initialization callback */
+	if (ctx->callbacks.startup_cb != NULL)
+		startup_cb_wrapper(ctx, &ctx->options, false);
+
+	MemoryContextSwitchTo(old_context);
+
+	return ctx;
+}
+
+/*
+ * Process the tuple tup of a relation rel - send it to the tuple
+ * callback of the plugin.
+ */
+void
+DecodingContextProccessTuple(LogicalDecodingContext *ctx, Relation rel,
+							 HeapTuple tup)
+{
+	tuple_cb_wrapper(ctx, rel, tup);
+}
+
+/*
+ * Process the tuple tup of a relation rel - send it to the tuple
+ * callback of the plugin.
+ */
+List *
+DecodingContextGetTableList(LogicalDecodingContext *ctx)
+{
+	return list_tables_cb_wrapper(ctx);
+}
+
+/*
  * Returns true if a consistent initial decoding snapshot has been built.
  */
 bool
@@ -461,9 +588,12 @@ FreeDecodingContext(LogicalDecodingContext *ctx)
 	if (ctx->callbacks.shutdown_cb != NULL)
 		shutdown_cb_wrapper(ctx);
 
-	ReorderBufferFree(ctx->reorder);
-	FreeSnapshotBuilder(ctx->snapshot_builder);
-	XLogReaderFree(ctx->reader);
+	if (ctx->reorder)
+		ReorderBufferFree(ctx->reorder);
+	if (ctx->snapshot_builder)
+		FreeSnapshotBuilder(ctx->snapshot_builder);
+	if (ctx->reader)
+		XLogReaderFree(ctx->reader);
 	MemoryContextDelete(ctx->context);
 }
 
@@ -748,6 +878,60 @@ message_cb_wrapper(ReorderBuffer *cache, ReorderBufferTXN *txn,
 	error_context_stack = errcallback.previous;
 }
 
+static List *
+list_tables_cb_wrapper(LogicalDecodingContext *ctx)
+{
+	LogicalErrorCallbackState state;
+	ErrorContextCallback errcallback;
+	List *res;
+
+	/* Push callback + info on the error context stack */
+	state.ctx = ctx;
+	state.callback_name = "list_tables";
+	state.report_location = InvalidXLogRecPtr;
+	errcallback.callback = output_plugin_error_callback;
+	errcallback.arg = (void *) &state;
+	errcallback.previous = error_context_stack;
+	error_context_stack = &errcallback;
+
+	/* set output state */
+	ctx->accept_writes = true;
+
+	/* do the actual work: call callback */
+	res = ctx->callbacks.list_tables_cb(ctx);
+
+	/* Pop the error context stack */
+	error_context_stack = errcallback.previous;
+
+	return res;
+}
+
+static void
+tuple_cb_wrapper(LogicalDecodingContext *ctx, Relation relation,
+				 HeapTuple tup)
+{
+	LogicalErrorCallbackState state;
+	ErrorContextCallback errcallback;
+
+	/* Push callback + info on the error context stack */
+	state.ctx = ctx;
+	state.callback_name = "tuple";
+	state.report_location = InvalidXLogRecPtr;
+	errcallback.callback = output_plugin_error_callback;
+	errcallback.arg = (void *) &state;
+	errcallback.previous = error_context_stack;
+	error_context_stack = &errcallback;
+
+	/* set output state */
+	ctx->accept_writes = true;
+
+	/* do the actual work: call callback */
+	ctx->callbacks.tuple_cb(ctx, relation, tup);
+
+	/* Pop the error context stack */
+	error_context_stack = errcallback.previous;
+}
+
 /*
  * Set the required catalog xmin horizon for historic snapshots in the current
  * replication slot.
diff --git a/src/backend/replication/logical/proto.c b/src/backend/replication/logical/proto.c
index 2b82495..e2f203b 100644
--- a/src/backend/replication/logical/proto.c
+++ b/src/backend/replication/logical/proto.c
@@ -356,60 +356,79 @@ logicalrep_read_delete(StringInfo in, LogicalRepTupleData *oldtup)
 }
 
 /*
+ * Write qualified relation name to the output stream.
+ */
+void
+logicalrep_write_rel_name(StringInfo out, char *nspname, char *relname)
+{
+	uint8		nspnamelen;
+	uint8		relnamelen;
+
+	nspnamelen = strlen(nspname) + 1;
+	relnamelen = strlen(relname) + 1;
+
+	pq_sendbyte(out, nspnamelen);		/* schema name length */
+	pq_sendbytes(out, nspname, nspnamelen);
+
+	pq_sendbyte(out, relnamelen);		/* table name length */
+	pq_sendbytes(out, relname, relnamelen);
+}
+
+/*
+ * Read qualified relation name from the stream.
+ */
+void
+logicalrep_read_rel_name(StringInfo in, char **nspname, char **relname)
+{
+	int			len;
+
+	len = pq_getmsgbyte(in);
+	*nspname = (char *) pq_getmsgbytes(in, len);
+
+	len = pq_getmsgbyte(in);
+	*relname = (char *) pq_getmsgbytes(in, len);
+}
+
+
+/*
  * Write relation description to the output stream.
  */
 void
 logicalrep_write_rel(StringInfo out, Relation rel)
 {
 	char	   *nspname;
-	uint8		nspnamelen;
-	const char *relname;
-	uint8		relnamelen;
+	char	   *relname;
 
 	pq_sendbyte(out, 'R');		/* sending RELATION */
 
 	/* use Oid as relation identifier */
 	pq_sendint(out, RelationGetRelid(rel), 4);
 
+	/* send the relation name */
 	nspname = get_namespace_name(RelationGetNamespace(rel));
 	if (nspname == NULL)
 		elog(ERROR, "cache lookup failed for namespace %u",
 			 rel->rd_rel->relnamespace);
-	nspnamelen = strlen(nspname) + 1;
-
 	relname = RelationGetRelationName(rel);
-	relnamelen = strlen(relname) + 1;
-
-	pq_sendbyte(out, nspnamelen);		/* schema name length */
-	pq_sendbytes(out, nspname, nspnamelen);
 
-	pq_sendbyte(out, relnamelen);		/* table name length */
-	pq_sendbytes(out, relname, relnamelen);
+	logicalrep_write_rel_name(out, nspname, relname);
 
 	/* send the attribute info */
 	logicalrep_write_attrs(out, rel);
-
-	pfree(nspname);
 }
 
 /*
- * Read schema.relation from stream and return as LogicalRepRelation opened in
- * lockmode.
+ * Read the relation info from stream and return as LogicalRepRelation.
  */
 LogicalRepRelation *
 logicalrep_read_rel(StringInfo in)
 {
 	LogicalRepRelation	*rel = palloc(sizeof(LogicalRepRelation));
-	int			len;
 
 	rel->remoteid = pq_getmsgint(in, 4);
 
-	/* Read relation from stream */
-	len = pq_getmsgbyte(in);
-	rel->nspname = (char *) pq_getmsgbytes(in, len);
-
-	len = pq_getmsgbyte(in);
-	rel->relname = (char *) pq_getmsgbytes(in, len);
+	/* Read relation name from stream */
+	logicalrep_read_rel_name(in, &rel->nspname, &rel->relname);
 
 	/* Get attribute description */
 	logicalrep_read_attrs(in, &rel->attnames, &rel->natts);
diff --git a/src/backend/replication/logical/publication.c b/src/backend/replication/logical/publication.c
index b86611e..62f40e9 100644
--- a/src/backend/replication/logical/publication.c
+++ b/src/backend/replication/logical/publication.c
@@ -274,6 +274,46 @@ GetRelationPublications(Relation rel)
 	return result;
 }
 
+/*
+ * Gets list of relation oids for a publication.
+ */
+List *
+GetPublicationRelations(Oid pubid)
+{
+	List		   *result;
+	Relation		pubrelsrel;
+	ScanKeyData		scankey;
+	SysScanDesc		scan;
+	HeapTuple		tup;
+
+	/* Find all publications associated with the relation. */
+	pubrelsrel = heap_open(PublicationRelRelationId, AccessShareLock);
+
+	ScanKeyInit(&scankey,
+				Anum_pg_publication_rel_pubid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(pubid));
+
+	scan = systable_beginscan(pubrelsrel, PublicationRelMapIndexId, true,
+							  NULL, 1, &scankey);
+
+	result = NIL;
+	while (HeapTupleIsValid(tup = systable_getnext(scan)))
+	{
+		Form_pg_publication_rel		pubrel;
+
+		pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
+
+		result = lappend_oid(result, pubrel->relid);
+	}
+
+	systable_endscan(scan);
+	heap_close(pubrelsrel, NoLock);
+
+	return result;
+}
+
+
 Publication *
 GetPublication(Oid pubid)
 {
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index b5fa3db..33e15ab 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -591,9 +591,10 @@ SnapBuildExportSnapshot(SnapBuild *builder)
 	snap->xip = newxip;
 
 	/*
-	 * now that we've built a plain snapshot, use the normal mechanisms for
-	 * exporting it
+	 * now that we've built a plain snapshot, make it active and use the
+	 * normal mechanisms for exporting it
 	 */
+	PushActiveSnapshot(snap);
 	snapname = ExportSnapshot(snap);
 
 	ereport(LOG,
diff --git a/src/backend/replication/logical/subscription.c b/src/backend/replication/logical/subscription.c
index 7d1de2c..3ba2b45 100644
--- a/src/backend/replication/logical/subscription.c
+++ b/src/backend/replication/logical/subscription.c
@@ -22,11 +22,15 @@
 #include "access/htup_details.h"
 #include "access/xact.h"
 
+#include "catalog/dependency.h"
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/objectaddress.h"
 #include "catalog/pg_type.h"
 #include "catalog/pg_subscription.h"
+#include "catalog/pg_subscription_rel.h"
+
+#include "commands/replicationcmds.h"
 
 #include "executor/spi.h"
 
@@ -41,6 +45,7 @@
 #include "utils/fmgroids.h"
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
+#include "utils/pg_lsn.h"
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
@@ -144,3 +149,178 @@ textarray_to_stringlist(ArrayType *textarray)
 
 	return res;
 }
+
+/*
+ * Set the state of a subscription table.
+ */
+Oid
+SetSubscriptionRelState(Oid subid, Oid relid, char state,
+						   XLogRecPtr sublsn)
+{
+	Relation	rel;
+	HeapTuple	tup;
+	Oid			subrelid;
+	bool		nulls[Natts_pg_subscription_rel];
+	Datum		values[Natts_pg_subscription_rel];
+
+	rel = heap_open(SubscriptionRelRelationId, RowExclusiveLock);
+
+	/* Try finding existing mapping. */
+	tup = SearchSysCacheCopy2(SUBSCRIPTIONRELMAP,
+							  ObjectIdGetDatum(relid),
+							  ObjectIdGetDatum(subid));
+
+	memset(values, 0, sizeof(values));
+
+	/*
+	 * If the record for given table does not exist yet create new
+	 * record, otherwise update the existing one.
+	 */
+	if (!HeapTupleIsValid(tup))
+	{
+		ObjectAddress	myself,
+						referenced;
+
+		/* Form the tuple. */
+		memset(nulls, false, sizeof(nulls));
+		values[Anum_pg_subscription_rel_subid - 1] = ObjectIdGetDatum(subid);
+		values[Anum_pg_subscription_rel_subrelid - 1] = ObjectIdGetDatum(relid);
+		values[Anum_pg_subscription_rel_substate - 1] = CharGetDatum(state);
+		if (sublsn != InvalidXLogRecPtr)
+			values[Anum_pg_subscription_rel_sublsn - 1] = LSNGetDatum(sublsn);
+		else
+			nulls[Anum_pg_subscription_rel_sublsn - 1] = true;
+
+		tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+		/* Insert tuple into catalog. */
+		subrelid = simple_heap_insert(rel, tup);
+		CatalogUpdateIndexes(rel, tup);
+
+		heap_freetuple(tup);
+
+		/* Add dependency on the publication */
+		ObjectAddressSet(myself, SubscriptionRelRelationId, subrelid);
+		ObjectAddressSet(referenced, SubscriptionRelationId, subid);
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+		/* Add dependency on the relation */
+		ObjectAddressSet(referenced, RelationRelationId, relid);
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+	}
+	else
+	{
+		bool		replaces[Natts_pg_subscription_rel];
+
+		/* Update the tuple. */
+		memset(nulls, true, sizeof(nulls));
+		memset(replaces, false, sizeof(replaces));
+
+		replaces[Anum_pg_subscription_rel_substate - 1] = true;
+		nulls[Anum_pg_subscription_rel_substate - 1] = false;
+		values[Anum_pg_subscription_rel_substate - 1] = CharGetDatum(state);
+
+		replaces[Anum_pg_subscription_rel_sublsn - 1] = true;
+		if (sublsn != InvalidXLogRecPtr)
+		{
+			nulls[Anum_pg_subscription_rel_sublsn - 1] = false;
+			values[Anum_pg_subscription_rel_sublsn - 1] = LSNGetDatum(sublsn);
+		}
+
+		tup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls,
+								replaces);
+
+		/* Update the catalog. */
+		simple_heap_update(rel, &tup->t_self, tup);
+		CatalogUpdateIndexes(rel, tup);
+
+		subrelid = HeapTupleGetOid(tup);
+	}
+
+	/* Cleanup. */
+	heap_close(rel, NoLock);
+
+	/* Make the changes visible. */
+	CommandCounterIncrement();
+
+	return subrelid;
+}
+
+/*
+ * Get state of subscription table.
+ *
+ * Returns SUBREL_STATE_UNKNOWN when not found and missing_ok is true.
+ */
+char
+GetSubscriptionRelState(Oid subid, Oid relid, XLogRecPtr *sublsn,
+						bool missing_ok)
+{
+	Relation	rel;
+	HeapTuple	tup;
+	char		substate;
+	bool		isnull;
+	Datum		d;
+
+	rel = heap_open(SubscriptionRelRelationId, RowExclusiveLock);
+
+	/* Try finding the mapping. */
+	tup = SearchSysCache2(SUBSCRIPTIONRELMAP,
+						  ObjectIdGetDatum(relid),
+						  ObjectIdGetDatum(subid));
+
+	if (!HeapTupleIsValid(tup))
+	{
+		if (missing_ok)
+		{
+			*sublsn = InvalidXLogRecPtr;
+			return '\0';
+		}
+
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("subscription table %u in subscription %d does not exist",
+						relid, subid)));
+	}
+
+	/* Get the state. */
+	d = SysCacheGetAttr(SUBSCRIPTIONRELMAP, tup,
+						Anum_pg_subscription_rel_substate, &isnull);
+	Assert(!isnull);
+	substate = DatumGetChar(d);
+	d = SysCacheGetAttr(SUBSCRIPTIONRELMAP, tup,
+						Anum_pg_subscription_rel_sublsn, &isnull);
+	if (isnull)
+		*sublsn = InvalidXLogRecPtr;
+	else
+		*sublsn = DatumGetLSN(d);
+
+	/* Cleanup */
+	ReleaseSysCache(tup);
+	heap_close(rel, RowExclusiveLock);
+
+	return substate;
+}
+
+/*
+ * Drop subscription table by OID
+ */
+void
+DropSubscriptionRelById(Oid subrelid)
+{
+	Relation	rel;
+	HeapTuple	tup;
+
+	rel = heap_open(SubscriptionRelRelationId, RowExclusiveLock);
+
+	tup = SearchSysCache1(SUBSCRIPTIONRELOID, ObjectIdGetDatum(subrelid));
+
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for subscription table %u",
+			 subrelid);
+
+	simple_heap_delete(rel, &tup->t_self);
+
+	ReleaseSysCache(tup);
+
+	heap_close(rel, RowExclusiveLock);
+}
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
new file mode 100644
index 0000000..7e844e8
--- /dev/null
+++ b/src/backend/replication/logical/tablesync.c
@@ -0,0 +1,672 @@
+/*-------------------------------------------------------------------------
+ * tablesync.c
+ *	   PostgreSQL logical replication
+ *
+ * Copyright (c) 2012-2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/replication/logical/tablesync.c
+ *
+ * NOTES
+ *	  This file contains code for initial table data synchronization for
+ *	  logical replication.
+ *
+ *    The initial data synchronization is done separately for each table,
+ *    in separate apply worker that only fetches the initial snapshot data
+ *    from the provider and then synchronizes the position in stream with
+ *    the main apply worker.
+ *
+ *    The stream position synchronization works in multiple steps.
+ *     - sync finishes copy and sets table state as SYNCWAIT and waits
+ *       for state to change in a loop
+ *     - apply periodically checks unsynced tables for SYNCWAIT, when it
+ *       appears it will compare its position in the stream with the
+ *       SYNCWAIT position and decides to either set it to CATCHUP when
+ *       the apply was infront (and wait for the sync to do the catchup),
+ *       or set the state to SYNCDONE if the sync was infront or in case
+ *       both sync and apply are at the same position it will set it to
+ *       READY and stops tracking it
+ *     - if the state was set to CATCHUP sync will read the stream and
+ *       apply changes until it catches up to the specified stream
+ *       position and then sets state to READY and signals apply that it
+ *       can stop waiting and exits, if the state was set to something
+ *       else than CATCHUP the sync process will simply end
+ *     - if the state was set to SYNCDONE by apply, the apply will
+ *       continue tracking the table until it reaches the SYNCDONE stream
+ *       position at which point it sets state to READY and stops tracking
+ *
+ *    Example flows look like this:
+ *     - Apply is infront:
+ *	      sync:8   -> set SYNCWAIT
+ *        apply:10 -> set CATCHUP
+ *        sync:10  -> set ready
+ *          exit
+ *        apply:10
+ *          stop tracking
+ *          continue rep
+ *    - Sync infront:
+ *        sync:10
+ *          set SYNCWAIT
+ *        apply:8
+ *          set SYNCDONE
+ *        sync:10
+ *          exit
+ *        apply:10
+ *          set READY
+ *          stop tracking
+ *          continue rep
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "funcapi.h"
+
+#include "access/xact.h"
+#include "access/xlog_internal.h"
+
+#include "catalog/namespace.h"
+#include "catalog/pg_subscription_rel.h"
+
+#include "commands/trigger.h"
+
+#include "executor/executor.h"
+#include "executor/nodeModifyTable.h"
+
+#include "libpq/pqformat.h"
+#include "libpq/pqsignal.h"
+
+#include "mb/pg_wchar.h"
+
+#include "optimizer/planner.h"
+
+#include "parser/parse_relation.h"
+
+#include "postmaster/bgworker.h"
+#include "postmaster/postmaster.h"
+
+#include "replication/decode.h"
+#include "replication/logical.h"
+#include "replication/logicalproto.h"
+#include "replication/logicalworker.h"
+#include "replication/reorderbuffer.h"
+#include "replication/origin.h"
+#include "replication/snapbuild.h"
+#include "replication/subscription.h"
+#include "replication/walreceiver.h"
+#include "replication/worker_internal.h"
+
+#include "rewrite/rewriteHandler.h"
+
+#include "storage/bufmgr.h"
+#include "storage/ipc.h"
+#include "storage/lmgr.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+
+#include "utils/builtins.h"
+#include "utils/catcache.h"
+#include "utils/fmgroids.h"
+#include "utils/guc.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/timeout.h"
+#include "utils/tqual.h"
+#include "utils/syscache.h"
+
+typedef struct TableState
+{
+	dlist_node	node;
+	Oid			relid;
+	XLogRecPtr	lsn;
+	char		state;
+} TableState;
+
+static dlist_head table_states = DLIST_STATIC_INIT(table_states);
+static bool table_states_valid = false;
+
+
+/*
+ * Exit routine for synchronization worker.
+ */
+static void
+finish_sync_worker(char *slotname)
+{
+	LogicalRepWorker   *worker;
+	RepOriginId			originid;
+	MemoryContext		oldctx = CurrentMemoryContext;
+
+	/*
+	 * Drop the replication slot on remote server.
+	 * We want to continue even in the case that the slot on remote side
+	 * is already gone. This means that we can leave slot on the remote
+	 * side but that can happen for other reasons as well so we can't
+	 * really protect against that.
+	 */
+	PG_TRY();
+	{
+		wrcapi->drop_slot(wrchandle, slotname);
+	}
+	PG_CATCH();
+	{
+		MemoryContext	ectx;
+		ErrorData	   *edata;
+
+		ectx = MemoryContextSwitchTo(oldctx);
+		/* Save error info */
+		edata = CopyErrorData();
+		MemoryContextSwitchTo(ectx);
+		FlushErrorState();
+
+		ereport(WARNING,
+				(errmsg("there was problem dropping the replication slot "
+						"\"%s\" on provider", slotname),
+				 errdetail("The error was: %s", edata->message),
+				 errhint("You may have to drop it manually")));
+		FreeErrorData(edata);
+	}
+	PG_END_TRY();
+
+	/* Also remove the origin tracking for the slot if it exists. */
+	StartTransactionCommand();
+	originid = replorigin_by_name(slotname, true);
+	if (originid != InvalidRepOriginId)
+	{
+		if (originid == replorigin_session_origin)
+		{
+			replorigin_session_reset();
+			replorigin_session_origin = InvalidRepOriginId;
+		}
+		replorigin_drop(originid);
+	}
+	CommitTransactionCommand();
+
+	/* Flush all writes. */
+	XLogFlush(GetXLogWriteRecPtr());
+
+	/* Find the main apply worker and signal it. */
+	LWLockAcquire(LogicalRepLauncherLock, LW_EXCLUSIVE);
+	worker = logicalrep_worker_find(MyLogicalRepWorker->subid, InvalidOid);
+	if (worker && worker->proc)
+		SetLatch(&worker->proc->procLatch);
+	LWLockRelease(LogicalRepLauncherLock);
+
+	ereport(LOG,
+			(errmsg("logical replication synchronization worker finished processing")));
+
+	/* Stop gracefully */
+	wrcapi->disconnect(wrchandle);
+	proc_exit(0);
+}
+
+/*
+ * Wait until the table synchronization change matches the desired state.
+ */
+static bool
+wait_for_sync_status_change(TableState *tstate)
+{
+	int		rc;
+	char	state = tstate->state;
+
+	for (;;)
+	{
+		CHECK_FOR_INTERRUPTS();
+
+		StartTransactionCommand();
+		tstate->state = GetSubscriptionRelState(MyLogicalRepWorker->subid,
+												tstate->relid,
+												&tstate->lsn,
+												true);
+		CommitTransactionCommand();
+
+		if (tstate->state != state)
+			return true;
+
+		rc = WaitLatch(&MyProc->procLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+					   10000L);
+
+		/* emergency bailout if postmaster has died */
+		if (rc & WL_POSTMASTER_DEATH)
+			proc_exit(1);
+
+        ResetLatch(&MyProc->procLatch);
+	}
+
+	return false;
+}
+
+/*
+ * Read the state of the tables in the subscription and update our table
+ * state list.
+ */
+static void
+reread_sync_state(Oid relid)
+{
+	dlist_mutable_iter	iter;
+	Relation	rel;
+	HeapTuple	tup;
+	ScanKeyData	skey[2];
+	HeapScanDesc	scan;
+
+	/* Clean the old list. */
+	dlist_foreach_modify(iter, &table_states)
+	{
+		TableState *tstate = dlist_container(TableState, node, iter.cur);
+
+		dlist_delete(iter.cur);
+		pfree(tstate);
+	}
+
+	/*
+	 * Fetch all the subscription relation states that are not marked as
+	 * ready and push them into our table state tracking list.
+	 */
+	rel = heap_open(SubscriptionRelRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&skey[0],
+				Anum_pg_subscription_rel_subid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(MyLogicalRepWorker->subid));
+
+	if (OidIsValid(relid))
+	{
+		ScanKeyInit(&skey[1],
+					Anum_pg_subscription_rel_subrelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(relid));
+	}
+	else
+	{
+		ScanKeyInit(&skey[1],
+					Anum_pg_subscription_rel_substate,
+					BTEqualStrategyNumber, F_CHARNE,
+					CharGetDatum(SUBREL_STATE_READY));
+	}
+
+	scan = heap_beginscan_catalog(rel, 2, skey);
+
+	while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
+	{
+		Form_pg_subscription_rel	subrel;
+		TableState	   *tstate;
+		MemoryContext	oldctx;
+
+		subrel = (Form_pg_subscription_rel) GETSTRUCT(tup);
+
+		/* Allocate the tracking info in a permament memory context. */
+		oldctx = MemoryContextSwitchTo(CacheMemoryContext);
+
+		tstate = (TableState *) palloc(sizeof(TableState));
+		tstate->relid = subrel->relid;
+		tstate->state = subrel->substate;
+		tstate->lsn = subrel->sublsn;
+
+		dlist_push_tail(&table_states, &tstate->node);
+		MemoryContextSwitchTo(oldctx);
+	}
+
+	/* Cleanup */
+	heap_endscan(scan);
+	heap_close(rel, RowExclusiveLock);
+
+	table_states_valid = true;
+}
+
+/*
+ * Callback from syscache invalidation.
+ */
+void
+invalidate_syncing_table_states(Datum arg, int cacheid, uint32 hashvalue)
+{
+	table_states_valid = false;
+}
+
+/*
+ * Handle table synchronization cooperation from the synchroniation
+ * worker.
+ */
+static void
+process_syncing_tables_sync(char *slotname, XLogRecPtr end_lsn)
+{
+	TableState *tstate;
+	TimeLineID	tli;
+
+	Assert(!IsTransactionState());
+
+	/*
+	 * Synchronization workers don't keep track of all synchronization
+	 * tables, they only care about their table.
+	 */
+	if (!table_states_valid)
+	{
+		StartTransactionCommand();
+		reread_sync_state(MyLogicalRepWorker->relid);
+		CommitTransactionCommand();
+	}
+
+	/* Somebody removed table underneath this worker, nothing more to do. */
+	if (dlist_is_empty(&table_states))
+	{
+		wrcapi->endstreaming(wrchandle, &tli);
+		finish_sync_worker(slotname);
+	}
+
+	/* Check if we are done with catchup now. */
+	tstate = dlist_container(TableState, node, dlist_head_node(&table_states));
+	if (tstate->state == SUBREL_STATE_CATCHUP)
+	{
+		Assert(tstate->lsn != InvalidXLogRecPtr);
+
+		if (tstate->lsn == end_lsn)
+		{
+			tstate->state = SUBREL_STATE_READY;
+			tstate->lsn = InvalidXLogRecPtr;
+			/* Update state of the synchronization. */
+			StartTransactionCommand();
+			SetSubscriptionRelState(MyLogicalRepWorker->subid,
+									tstate->relid, tstate->state,
+									tstate->lsn);
+			CommitTransactionCommand();
+
+			wrcapi->endstreaming(wrchandle, &tli);
+			finish_sync_worker(slotname);
+		}
+		return;
+	}
+}
+
+/*
+ * Handle table synchronization cooperation from the apply worker.
+ */
+static void
+process_syncing_tables_apply(char *slotname, XLogRecPtr end_lsn)
+{
+	dlist_mutable_iter	iter;
+
+	Assert(!IsTransactionState());
+
+	if (!table_states_valid)
+	{
+		StartTransactionCommand();
+		reread_sync_state(InvalidOid);
+		CommitTransactionCommand();
+	}
+
+	dlist_foreach_modify(iter, &table_states)
+	{
+		TableState *tstate = dlist_container(TableState, node, iter.cur);
+		bool		start_worker;
+		LogicalRepWorker   *worker;
+
+		/*
+		 * When the synchronization process is at the cachup phase we need
+		 * to ensure that we are not behind it (it's going to wait at this
+		 * point for the change of state). Once we are infront or at the same
+		 * position as the synchronization proccess we can signal it to
+		 * finish the catchup.
+		 */
+		if (tstate->state == SUBREL_STATE_SYNCWAIT)
+		{
+			if (end_lsn > tstate->lsn)
+			{
+				/*
+				 * Apply is infront, tell sync to catchup. and wait until
+				 * it does.
+				 */
+				tstate->state = SUBREL_STATE_CATCHUP;
+				tstate->lsn = end_lsn;
+				StartTransactionCommand();
+				SetSubscriptionRelState(MyLogicalRepWorker->subid,
+										tstate->relid, tstate->state,
+										tstate->lsn);
+				CommitTransactionCommand();
+
+				/* Signal the worker as it may be waiting for us. */
+				LWLockAcquire(LogicalRepLauncherLock, LW_SHARED);
+				worker = logicalrep_worker_find(MyLogicalRepWorker->subid,
+												tstate->relid);
+				if (worker && worker->proc)
+					SetLatch(&worker->proc->procLatch);
+				LWLockRelease(LogicalRepLauncherLock);
+
+				if (wait_for_sync_status_change(tstate));
+					Assert(SUBREL_STATE_READY);
+			}
+			else
+			{
+				/*
+				 * Apply is either behind in which case sync worker is done
+				 * but apply needs to keep tracking the table until it
+				 * catches up to where sync finished.
+				 * Or apply and sync are at the same position in which case
+				 * table can be switched to standard replication mode
+				 * immediately.
+				 */
+				if (end_lsn < tstate->lsn)
+					tstate->state = SUBREL_STATE_SYNCDONE;
+				else
+					tstate->state = SUBREL_STATE_READY;
+
+				StartTransactionCommand();
+				SetSubscriptionRelState(MyLogicalRepWorker->subid,
+										tstate->relid, tstate->state,
+										tstate->lsn);
+				CommitTransactionCommand();
+
+				/* Signal the worker as it may be waiting for us. */
+				LWLockAcquire(LogicalRepLauncherLock, LW_SHARED);
+				worker = logicalrep_worker_find(MyLogicalRepWorker->subid,
+												tstate->relid);
+				if (worker && worker->proc)
+					SetLatch(&worker->proc->procLatch);
+				LWLockRelease(LogicalRepLauncherLock);
+			}
+		}
+		else if (tstate->state == SUBREL_STATE_SYNCDONE &&
+				 end_lsn >= tstate->lsn)
+		{
+			/*
+			 * Apply catched up to the position where table sync finished,
+			 * mark the table as ready for normal replication.
+			 */
+			tstate->state = SUBREL_STATE_READY;
+			tstate->lsn = InvalidXLogRecPtr;
+			StartTransactionCommand();
+			SetSubscriptionRelState(MyLogicalRepWorker->subid,
+									tstate->relid, tstate->state,
+									tstate->lsn);
+			CommitTransactionCommand();
+		}
+
+		/*
+		 * In case table is supposed to be synchronizing but the
+		 * synchronization worker is not running, start it.
+		 * Limit the number of launched workers here to one (for now).
+		 */
+		if (tstate->state != SUBREL_STATE_READY &&
+			tstate->state != SUBREL_STATE_SYNCDONE)
+		{
+			LWLockAcquire(LogicalRepLauncherLock, LW_SHARED);
+			worker = logicalrep_worker_find(MyLogicalRepWorker->subid,
+											tstate->relid);
+			start_worker = !worker &&
+				logicalrep_worker_count(MyLogicalRepWorker->subid) < 2;
+			LWLockRelease(LogicalRepLauncherLock);
+			if (start_worker)
+				logicalrep_worker_launch(MyLogicalRepWorker->dbid,
+										 MyLogicalRepWorker->subid,
+										 tstate->relid);
+
+		}
+	}
+}
+
+/*
+ * Proccess state possible change(s) of tables that are being synchronized
+ * in parallel.
+ */
+void
+process_syncing_tables(char *slotname, XLogRecPtr end_lsn)
+{
+	if (OidIsValid(MyLogicalRepWorker->relid))
+		process_syncing_tables_sync(slotname, end_lsn);
+	else
+		process_syncing_tables_apply(slotname, end_lsn);
+}
+
+/*
+ * Setup replication origin tracking.
+ */
+static XLogRecPtr
+setup_origin_tracking(char *origin_name)
+{
+	RepOriginId		originid;
+
+	StartTransactionCommand();
+	originid = replorigin_by_name(origin_name, true);
+	if (!OidIsValid(originid))
+		originid = replorigin_create(origin_name);
+	replorigin_session_setup(originid);
+	replorigin_session_origin = originid;
+	CommitTransactionCommand();
+	return replorigin_session_get_progress(false);
+}
+
+
+/*
+ * Start syncing the table in the sync worker.
+ */
+char *
+LogicalRepSyncTableStart(XLogRecPtr *origin_startpos)
+{
+	StringInfoData	s;
+	TableState		tstate;
+	MemoryContext	oldctx;
+	char		   *slotname;
+
+	/* Check the state of the table synchronization. */
+	StartTransactionCommand();
+	tstate.relid = MyLogicalRepWorker->relid;
+	tstate.state = GetSubscriptionRelState(MySubscription->oid, tstate.relid,
+										   &tstate.lsn, false);
+
+	/*
+	 * Build unique slot name.
+	 * TODO: protect against too long slot name.
+	 */
+	oldctx = MemoryContextSwitchTo(CacheMemoryContext);
+	initStringInfo(&s);
+	appendStringInfo(&s, "%s_sync_%s", MySubscription->slotname,
+					 get_rel_name(tstate.relid));
+	slotname = s.data;
+	MemoryContextSwitchTo(oldctx);
+
+	CommitTransactionCommand();
+
+	wrcapi->connect(wrchandle, MySubscription->conninfo, true, slotname);
+
+	switch (tstate.state)
+	{
+		case SUBREL_STATE_INIT:
+		case SUBREL_STATE_DATA:
+			{
+				Relation	rel;
+				XLogRecPtr	lsn;
+				char	   *options;
+
+				/* Update the state and make it visible to others. */
+				StartTransactionCommand();
+				SetSubscriptionRelState(MySubscription->oid, tstate.relid,
+										SUBREL_STATE_DATA,
+										InvalidXLogRecPtr);
+				CommitTransactionCommand();
+
+				*origin_startpos = setup_origin_tracking(slotname);
+
+				/*
+				 * We want to do the table data sync in single
+				 * transaction so do not close the transaction opened
+				 * above.
+				 * There will be no BEGIN or COMMIT messages coming via
+				 * logical replication while the copy table command is
+				 * running so start the transaction here.
+				 * Note the memory context for data handling will still
+				 * be done using ensure_transaction called by the insert
+				 * handler.
+				 */
+				StartTransactionCommand();
+
+				/*
+				 * Don't allow parallel access other than SELECT while
+				 * the initial contents are being copied.
+				 */
+				rel = heap_open(tstate.relid, ExclusiveLock);
+
+				/* Create temporary slot for the sync proccess. */
+				wrcapi->create_slot(wrchandle, slotname, true,  &lsn);
+
+				/* Build option string for the plugin. */
+				options = logicalrep_build_options(MySubscription->publications);
+
+				wrcapi->copy_table(wrchandle, slotname,
+								   get_namespace_name(RelationGetNamespace(rel)),
+								   RelationGetRelationName(rel),
+								   options);
+
+				/*
+				 * Run the standard apply loop for the initial data
+				 * stream.
+				 */
+				in_remote_transaction = true;
+				LogicalRepApplyLoop(*origin_startpos);
+
+				/*
+				 * We are done with the initial data synchronization,
+				 * update the state.
+				 */
+				SetSubscriptionRelState(MySubscription->oid, tstate.relid,
+										SUBREL_STATE_SYNCWAIT, lsn);
+				heap_close(rel, NoLock);
+
+				/* End the transaction. */
+				CommitTransactionCommand();
+				in_remote_transaction = false;
+
+				/*
+				 * Wait for main apply worker to either tell us to
+				 * catchup or that we are done.
+				 */
+				wait_for_sync_status_change(&tstate);
+				if (tstate.state != SUBREL_STATE_CATCHUP)
+					finish_sync_worker(slotname);
+				break;
+			}
+
+		case SUBREL_STATE_SYNCWAIT:
+			*origin_startpos = setup_origin_tracking(slotname);
+			/*
+			 * Wait for main apply worker to either tell us to
+			 * catchup or that we are done.
+			 */
+			wait_for_sync_status_change(&tstate);
+			if (tstate.state != SUBREL_STATE_CATCHUP)
+				finish_sync_worker(slotname);
+			break;
+		case SUBREL_STATE_CATCHUP:
+			/* Catchup is handled by streaming loop. */
+			*origin_startpos = setup_origin_tracking(slotname);
+			break;
+		case SUBREL_STATE_SYNCDONE:
+		case SUBREL_STATE_READY:
+			/* Nothing to do here but finish. */
+			finish_sync_worker(slotname);
+		default:
+			elog(ERROR, "unknown relation state \"%c\"", tstate.state);
+	}
+
+	return slotname;
+}
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index d74c7e9..e18939e 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -14,6 +14,8 @@
 
 #include "access/xact.h"
 
+#include "catalog/pg_publication.h"
+
 #include "mb/pg_wchar.h"
 
 #include "replication/logical.h"
@@ -24,7 +26,10 @@
 
 #include "utils/builtins.h"
 #include "utils/inval.h"
+#include "utils/lsyscache.h"
 #include "utils/memutils.h"
+#include "utils/tuplestore.h"
+#include "utils/syscache.h"
 
 PG_MODULE_MAGIC;
 
@@ -40,6 +45,9 @@ static void pgoutput_commit_txn(LogicalDecodingContext *ctx,
 static void pgoutput_change(LogicalDecodingContext *ctx,
 				 ReorderBufferTXN *txn, Relation rel,
 				 ReorderBufferChange *change);
+static void pgoutput_tuple(LogicalDecodingContext *ctx, Relation relation,
+			   HeapTuple tuple);
+static List *pgoutput_list_tables(LogicalDecodingContext *ctx);
 static bool pgoutput_origin_filter(LogicalDecodingContext *ctx,
 						RepOriginId origin_id);
 
@@ -74,6 +82,8 @@ _PG_output_plugin_init(OutputPluginCallbacks *cb)
 	cb->commit_cb = pgoutput_commit_txn;
 	cb->filter_by_origin_cb = pgoutput_origin_filter;
 	cb->shutdown_cb = pgoutput_shutdown;
+	cb->tuple_cb = pgoutput_tuple;
+	cb->list_tables_cb = pgoutput_list_tables;
 }
 
 /*
@@ -295,6 +305,104 @@ pgoutput_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
 }
 
 /*
+ * Sends the tuple from relation over wire.
+ * Currenly this behaves same as the INSERT replication.
+ */
+static void
+pgoutput_tuple(LogicalDecodingContext *ctx, Relation relation,
+			   HeapTuple tuple)
+{
+	PGOutputData	   *data = (PGOutputData *) ctx->output_plugin_private;
+	MemoryContext		old;
+	RelSchemaSyncEntry *relentry = NULL;
+
+	/*
+	 * First check the table filter
+	 * TODO: do we actually need this?
+	 */
+	if (!publication_change_is_replicated(relation,
+										  PublicationChangeInsert,
+										  data->publication_names))
+		return;
+
+	/* Avoid leaking memory by using and resetting our own context */
+	old = MemoryContextSwitchTo(data->context);
+
+	/*
+	 * Write the relation schema if the current schema haven't been sent yet.
+	 */
+	relentry = get_rel_schema_sync_entry(RelationGetRelid(relation));
+	if (!relentry->schema_sent)
+	{
+		OutputPluginPrepareWrite(ctx, false);
+		logicalrep_write_rel(ctx->out, relation);
+		OutputPluginWrite(ctx, false);
+		relentry->schema_sent = true;
+	}
+
+	/* Send the data */
+	OutputPluginPrepareWrite(ctx, true);
+	logicalrep_write_insert(ctx->out, relation, tuple);
+	OutputPluginWrite(ctx, true);
+
+	/* Cleanup */
+	MemoryContextSwitchTo(old);
+	MemoryContextReset(data->context);
+}
+
+/*
+ * Get the list of tables replicated by current connection.
+ */
+static List *
+pgoutput_list_tables(LogicalDecodingContext *ctx)
+{
+	PGOutputData   *data = (PGOutputData *) ctx->output_plugin_private;
+	MemoryContext	old;
+	List		   *rellist = NIL,
+				   *res = NIL;
+	ListCell	   *lc;
+
+	/* Avoid leaking memory by using and resetting our own context */
+	old = MemoryContextSwitchTo(data->context);
+
+	/* Build unique list of relations in all subscribed publications. */
+	foreach(lc, data->publication_names)
+	{
+		char	   *pubname = (char *) lfirst(lc);
+		Oid			pubid;
+		List	   *pubrellist;
+
+		pubid = GetSysCacheOid1(PUBLICATIONNAME, CStringGetDatum(pubname));
+		if (!OidIsValid(pubid))
+			elog(ERROR, "cache lookup failed for publication %u", pubid);
+
+		pubrellist = GetPublicationRelations(pubid);
+		rellist = list_concat_unique_oid(rellist, pubrellist);
+	}
+
+	MemoryContextSwitchTo(old);
+
+	/* Put all the relations list of LogicalRepTableListEntry. */
+	foreach (lc, rellist)
+	{
+		Oid			relid = lfirst_oid(lc);
+		LogicalRepTableListEntry *entry;
+
+		entry = palloc(sizeof(LogicalRepTableListEntry));
+		entry->nspname = get_namespace_name(get_rel_namespace(relid));
+		entry->relname = get_rel_name(relid);
+		entry->info = NULL;
+
+		res = lappend(res, entry);
+	}
+
+	/* Cleanup our memory context. */
+	MemoryContextReset(data->context);
+
+	return res;
+}
+
+/*
  * Currently we always forward.
  */
 static bool
diff --git a/src/backend/replication/repl_gram.y b/src/backend/replication/repl_gram.y
index d93db88..65b8ea7 100644
--- a/src/backend/replication/repl_gram.y
+++ b/src/backend/replication/repl_gram.y
@@ -77,11 +77,14 @@ Node *replication_parse_result;
 %token K_LOGICAL
 %token K_SLOT
 %token K_RESERVE_WAL
+%token K_TABLE
+%token K_LIST_TABLES
+%token K_COPY_TABLE
 
 %type <node>	command
 %type <node>	base_backup start_replication start_logical_replication
 				create_replication_slot drop_replication_slot identify_system
-				timeline_history
+				timeline_history list_tables copy_table
 %type <list>	base_backup_opt_list
 %type <defelt>	base_backup_opt
 %type <uintval>	opt_timeline
@@ -111,6 +114,8 @@ command:
 			| create_replication_slot
 			| drop_replication_slot
 			| timeline_history
+			| list_tables
+			| copy_table
 			;
 
 /*
@@ -323,6 +328,30 @@ plugin_opt_arg:
 			SCONST							{ $$ = (Node *) makeString($1); }
 			| /* EMPTY */					{ $$ = NULL; }
 		;
+
+copy_table:
+			K_COPY_TABLE K_SLOT IDENT K_TABLE IDENT IDENT plugin_options
+				{
+					CopyTableCmd *cmd;
+					cmd = makeNode(CopyTableCmd);
+					cmd->slotname = $3;
+					cmd->relation = makeRangeVar($5, $6, -1);
+					cmd->options = $7;
+					$$ = (Node *) cmd;
+				}
+		;
+
+list_tables:
+			K_LIST_TABLES K_SLOT IDENT plugin_options
+				{
+					ListTablesCmd *cmd;
+					cmd = makeNode(ListTablesCmd);
+					cmd->slotname = $3;
+					cmd->options = $4;
+					$$ = (Node *) cmd;
+				}
+		;
+
 %%
 
 #include "repl_scanner.c"
diff --git a/src/backend/replication/repl_scanner.l b/src/backend/replication/repl_scanner.l
index f83ec53..69d6e86 100644
--- a/src/backend/replication/repl_scanner.l
+++ b/src/backend/replication/repl_scanner.l
@@ -98,6 +98,9 @@ PHYSICAL			{ return K_PHYSICAL; }
 RESERVE_WAL			{ return K_RESERVE_WAL; }
 LOGICAL				{ return K_LOGICAL; }
 SLOT				{ return K_SLOT; }
+LIST_TABLES			{ return K_LIST_TABLES; }
+COPY_TABLE			{ return K_COPY_TABLE; }
+TABLE				{ return K_TABLE; }
 
 ","				{ return ','; }
 ";"				{ return ';'; }
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index a0dba19..def88d3 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -43,6 +43,7 @@
 #include <signal.h>
 #include <unistd.h>
 
+#include "access/relscan.h"
 #include "access/timeline.h"
 #include "access/transam.h"
 #include "access/xact.h"
@@ -932,7 +933,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 	pq_endmessage(&buf);
 
 	/*
-	 * release active status again, START_REPLICATION will reacquire it
+	 * release active status again, subsequent commands will reacquire it
 	 */
 	ReplicationSlotRelease();
 }
@@ -1035,6 +1036,176 @@ StartLogicalReplication(StartReplicationCmd *cmd)
 }
 
 /*
+ * Handle LIST_TABLES command.
+ */
+static void
+SendTableList(ListTablesCmd *cmd)
+{
+	List		   *tables;
+	ListCell	   *lc;
+	StringInfoData	buf;
+
+	/* make sure that our requirements are still fulfilled */
+	CheckLogicalDecodingRequirements();
+
+	Assert(!MyReplicationSlot);
+
+	ReplicationSlotAcquire(cmd->slotname);
+
+	/* Initialize the decoding context for table copy. */
+	logical_decoding_ctx = CreateCopyDecodingContext(cmd->options,
+													 WalSndPrepareWrite,
+													 WalSndWriteData);
+
+	/* Send a RowDescription message */
+	pq_beginmessage(&buf, 'T');
+	pq_sendint(&buf, 3, 2);		/* 3 fields */
+
+	/* first field: namespace name */
+	pq_sendstring(&buf, "nspname");	/* 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 */
+
+	/* second field: relation name */
+	pq_sendstring(&buf, "relname");	/* 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 */
+
+	/* third field: freeform relation info (the only NULLable field) */
+	pq_sendstring(&buf, "info");		/* 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);
+
+	/* Let the decoding contex send the list. */
+	tables = DecodingContextGetTableList(logical_decoding_ctx);
+
+	/* Send the table list as tuples. */
+	foreach(lc, tables)
+	{
+		LogicalRepTableListEntry *entry = lfirst(lc);
+		Size		len;
+
+		Assert(entry->nspname != NULL);
+		Assert(entry->relname != NULL);
+
+		/* Send a DataRow message */
+		pq_beginmessage(&buf, 'D');
+		pq_sendint(&buf, 3, 2);		/* # of columns */
+
+		/* namespace name */
+		len = strlen(entry->nspname);
+		pq_sendint(&buf, len, 4);	/* col1 len */
+		pq_sendbytes(&buf, entry->nspname, len);
+
+		/* relation name name */
+		len = strlen(entry->relname);
+		pq_sendint(&buf, len, 4);	/* col1 len */
+		pq_sendbytes(&buf, entry->relname, len);
+
+		/* relation info, or NULL if none */
+		if (entry->info != NULL)
+		{
+			len = strlen(entry->info);
+			pq_sendint(&buf, len, 4);
+			pq_sendbytes(&buf, entry->info, len);
+		}
+		else
+			pq_sendint(&buf, -1, 4);
+
+		pq_endmessage(&buf);
+	}
+
+	/* Clean up the logical decoding context. */
+	FreeDecodingContext(logical_decoding_ctx);
+
+	ReplicationSlotRelease();
+}
+
+/*
+ * LogicalDecodingContext 'write' callback.
+ *
+ * Actually write out data previously prepared by WalSndPrepareWrite out
+ * to the network.
+ */
+static void
+CopyTableWriteData(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid,
+				bool last_write)
+{
+	/* output previously gathered data in a CopyData packet */
+	if (pq_putmessage('d', ctx->out->data, ctx->out->len))
+		ereport(ERROR,
+			(errmsg("copy table could not send data, aborting")));
+}
+
+/*
+ * Handle OPY_TABLE command.
+ */
+static void
+CopyTable(CopyTableCmd *cmd)
+{
+	StringInfoData	buf;
+	Relation		rel;
+	HeapScanDesc	scandesc;
+	HeapTuple	tup;
+
+	/* make sure that our requirements are still fulfilled */
+	CheckLogicalDecodingRequirements();
+
+	Assert(!MyReplicationSlot);
+
+	ReplicationSlotAcquire(cmd->slotname);
+
+	WalSndSetState(WALSNDSTATE_BACKUP);
+
+	/* 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();
+
+	/* Initialize the decoding context for table copy. */
+	logical_decoding_ctx = CreateCopyDecodingContext(cmd->options,
+													 WalSndPrepareWrite,
+													 CopyTableWriteData);
+
+	/* Open the relation and start the scan. */
+	rel = heap_openrv(cmd->relation, AccessShareLock);
+	scandesc = heap_beginscan(rel, GetActiveSnapshot(), 0, NULL);
+
+	/* Scan the whole table and pass the rows to the decoding context. */
+	while (HeapTupleIsValid(tup = heap_getnext(scandesc,
+											   ForwardScanDirection)))
+		DecodingContextProccessTuple(logical_decoding_ctx, rel, tup);
+
+	/* Close the scan and relation. */
+	heap_endscan(scandesc);
+	heap_close(rel, AccessShareLock);
+
+	/* Send CopyDone */
+	pq_putemptymessage('c');
+
+	FreeDecodingContext(logical_decoding_ctx);
+
+	ReplicationSlotRelease();
+}
+
+
+/*
  * LogicalDecodingContext 'prepare_write' callback.
  *
  * Prepare a write into a StringInfo.
@@ -1299,14 +1470,6 @@ exec_replication_command(const char *cmd_string)
 	ereport(log_replication_commands ? LOG : DEBUG1,
 			(errmsg("received replication command: %s", cmd_string)));
 
-	/*
-	 * CREATE_REPLICATION_SLOT ... LOGICAL exports a snapshot until the next
-	 * command arrives. Clean up the old stuff if there's anything.
-	 */
-	SnapBuildClearExportedSnapshot();
-
-	CHECK_FOR_INTERRUPTS();
-
 	cmd_context = AllocSetContextCreate(CurrentMemoryContext,
 										"Replication command context",
 										ALLOCSET_DEFAULT_MINSIZE,
@@ -1324,6 +1487,16 @@ exec_replication_command(const char *cmd_string)
 
 	cmd_node = replication_parse_result;
 
+	/*
+	 * CREATE_REPLICATION_SLOT ... LOGICAL exports a snapshot until the next
+	 * command arrives. Clean up the old stuff if there's anything unless
+	 * the command currently being executed needs the snapshot.
+	 */
+	if (cmd_node->type != T_ListTablesCmd && cmd_node->type != T_CopyTableCmd)
+		SnapBuildClearExportedSnapshot();
+
+	CHECK_FOR_INTERRUPTS();
+
 	switch (cmd_node->type)
 	{
 		case T_IdentifySystemCmd:
@@ -1357,6 +1530,14 @@ exec_replication_command(const char *cmd_string)
 			SendTimeLineHistory((TimeLineHistoryCmd *) cmd_node);
 			break;
 
+		case T_ListTablesCmd:
+			SendTableList((ListTablesCmd *) cmd_node);
+			break;
+
+		case T_CopyTableCmd:
+			CopyTable((CopyTableCmd *) cmd_node);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized replication command node tag: %u",
 				 cmd_node->type);
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 03c8916..c6e7207 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -60,6 +60,7 @@
 #include "catalog/pg_replication_origin.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_subscription.h"
+#include "catalog/pg_subscription_rel.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_ts_config.h"
@@ -736,6 +737,28 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		4
 	},
+	{SubscriptionRelRelationId,		/* SUBSCRIPTIONRELOID */
+		SubscriptionRelOidIndexId,
+		1,
+		{
+			ObjectIdAttributeNumber,
+			0,
+			0,
+			0
+		},
+		64
+	},
+	{SubscriptionRelRelationId,		/* SUBSCRIPTIONRELMAP */
+		SubscriptionRelMapIndexId,
+		2,
+		{
+			Anum_pg_subscription_rel_subrelid,
+			Anum_pg_subscription_rel_subid,
+			0,
+			0
+		},
+		64
+	},
 	{TableSpaceRelationId,		/* TABLESPACEOID */
 		TablespaceOidIndexId,
 		1,
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 86e2939..02ebd12 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -337,6 +337,12 @@ DECLARE_UNIQUE_INDEX(pg_subscription_oid_index, 6114, on pg_subscription using b
 DECLARE_UNIQUE_INDEX(pg_subscription_subname_index, 6115, on pg_subscription using btree(subname name_ops));
 #define SubscriptionNameIndexId 6115
 
+DECLARE_UNIQUE_INDEX(pg_subscription_rel_oid_index, 6116, on pg_subscription_rel using btree(oid oid_ops));
+#define SubscriptionRelOidIndexId 6116
+
+DECLARE_UNIQUE_INDEX(pg_subscription_rel_map_index, 6117, on pg_subscription_rel using btree(relid oid_ops, subid oid_ops));
+#define SubscriptionRelMapIndexId 6117
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_subscription_rel.h b/src/include/catalog/pg_subscription_rel.h
new file mode 100644
index 0000000..300ba17
--- /dev/null
+++ b/src/include/catalog/pg_subscription_rel.h
@@ -0,0 +1,61 @@
+/* -------------------------------------------------------------------------
+ *
+ * pg_subscription_rel.h
+ *		Local info about tables that come from the provider of a
+ *		subscription (pg_subscription_rel).
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+#ifndef PG_SUBSCRIPTION_REL_H
+#define PG_SUBSCRIPTION_REL_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *		pg_subscription_rel definition. cpp turns this into
+ *		typedef struct FormData_pg_subscription_rel
+ * ----------------
+ */
+#define SubscriptionRelRelationId			6102
+#define SubscriptionRelRelation_Rowtype_Id	6103
+
+/* Workaround for genbki not knowing about XLogRecPtr */
+#define pg_lsn XLogRecPtr
+
+CATALOG(pg_subscription_rel,6102) BKI_ROWTYPE_OID(6103)
+{
+	Oid			subid;			/* Oid of subscription */
+	Oid			relid;			/* Oid of relation */
+	char		substate;		/* state of the relation in subscription */
+	pg_lsn		sublsn;			/* remote lsn of the state change
+								 * used for synchronization coordination */
+} FormData_pg_subscription_rel;
+
+typedef FormData_pg_subscription_rel *Form_pg_subscription_rel;
+
+/* ----------------
+ *		compiler constants for pg_subscription_rel
+ * ----------------
+ */
+#define Natts_pg_subscription_rel			4
+#define Anum_pg_subscription_rel_subid		1
+#define Anum_pg_subscription_rel_subrelid	2
+#define Anum_pg_subscription_rel_substate	3
+#define Anum_pg_subscription_rel_sublsn		4
+
+/* ----------------
+ *		substate constants
+ * ----------------
+ */
+#define		  SUBREL_STATE_UNKNOWN			'\0'	/* unknown state (sublsn NULL) */
+#define		  SUBREL_STATE_INIT				'i'		/* initializing (sublsn NULL) */
+#define		  SUBREL_STATE_DATA				'd'		/* data copy (sublsn NULL) */
+#define		  SUBREL_STATE_SYNCWAIT			'w'		/* waiting for sync (sublsn set) */
+#define		  SUBREL_STATE_CATCHUP			'c'		/* catchup (sublsn set) */
+#define		  SUBREL_STATE_SYNCDONE			's'		/* synced (sublsn set) */
+#define		  SUBREL_STATE_READY			'r'		/* ready (sublsn NULL) */
+
+#endif   /* PG_SUBSCRIPTION_REL_H */
diff --git a/src/include/commands/replicationcmds.h b/src/include/commands/replicationcmds.h
index 7c35d72..a9895f6 100644
--- a/src/include/commands/replicationcmds.h
+++ b/src/include/commands/replicationcmds.h
@@ -26,5 +26,6 @@ extern void RemovePublicationRelById(Oid prid);
 extern ObjectAddress CreateSubscription(CreateSubscriptionStmt *stmt);
 extern ObjectAddress AlterSubscription(AlterSubscriptionStmt *stmt);
 extern void DropSubscriptionById(Oid subid);
+extern void DropSubscriptionRelById(Oid subrelid);
 
 #endif   /* REPLICATIONCMDS_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 322286b..21128a1 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -466,6 +466,8 @@ typedef enum NodeTag
 	T_DropReplicationSlotCmd,
 	T_StartReplicationCmd,
 	T_TimeLineHistoryCmd,
+	T_ListTablesCmd,
+	T_CopyTableCmd,
 
 	/*
 	 * TAGS FOR RANDOM OTHER STUFF
@@ -475,7 +477,7 @@ typedef enum NodeTag
 	 * purposes (usually because they are involved in APIs where we want to
 	 * pass multiple object types through the same pointer).
 	 */
-	T_TriggerData = 950,		/* in commands/trigger.h */
+	T_TriggerData = 970,		/* in commands/trigger.h */
 	T_EventTriggerData,			/* in commands/event_trigger.h */
 	T_ReturnSetInfo,			/* in nodes/execnodes.h */
 	T_WindowObjectData,			/* private in nodeWindowAgg.c */
diff --git a/src/include/nodes/replnodes.h b/src/include/nodes/replnodes.h
index d2f1edb..b8180c1 100644
--- a/src/include/nodes/replnodes.h
+++ b/src/include/nodes/replnodes.h
@@ -95,4 +95,27 @@ typedef struct TimeLineHistoryCmd
 	TimeLineID	timeline;
 } TimeLineHistoryCmd;
 
+/* ----------------------
+ *		LIST_TABLES command
+ * ----------------------
+ */
+typedef struct ListTablesCmd
+{
+	NodeTag		type;
+	char	   *slotname;
+	List	   *options;
+} ListTablesCmd;
+
+/* ----------------------
+ *		COPY_TABLE command
+ * ----------------------
+ */
+typedef struct CopyTableCmd
+{
+	NodeTag		type;
+	char	   *slotname;
+	struct RangeVar   *relation;
+	List	   *options;
+} CopyTableCmd;
+
 #endif   /* REPLNODES_H */
diff --git a/src/include/replication/logical.h b/src/include/replication/logical.h
index 947000e..a356b43 100644
--- a/src/include/replication/logical.h
+++ b/src/include/replication/logical.h
@@ -31,9 +31,11 @@ typedef struct LogicalDecodingContext
 	/* memory context this is all allocated in */
 	MemoryContext context;
 
-	/* infrastructure pieces */
-	XLogReaderState *reader;
+	/* The associated replication slot */
 	ReplicationSlot *slot;
+
+	/* infrastructure pieces for decoding */
+	XLogReaderState *reader;
 	struct ReorderBuffer *reorder;
 	struct SnapBuild *snapshot_builder;
 
@@ -75,6 +77,14 @@ typedef struct LogicalDecodingContext
 	TransactionId write_xid;
 } LogicalDecodingContext;
 
+
+/* Entry used for listing tables by logical decoding plugin. */
+typedef struct LogicalRepTableListEntry {
+	char *nspname;
+	char *relname;
+	char *info;
+} LogicalRepTableListEntry;
+
 extern void CheckLogicalDecodingRequirements(void);
 
 extern LogicalDecodingContext *CreateInitDecodingContext(char *plugin,
@@ -92,6 +102,14 @@ extern void DecodingContextFindStartpoint(LogicalDecodingContext *ctx);
 extern bool DecodingContextReady(LogicalDecodingContext *ctx);
 extern void FreeDecodingContext(LogicalDecodingContext *ctx);
 
+extern LogicalDecodingContext *CreateCopyDecodingContext(
+					  List *output_plugin_options,
+					  LogicalOutputPluginWriterPrepareWrite prepare_write,
+					  LogicalOutputPluginWriterWrite do_write);
+extern void DecodingContextProccessTuple(LogicalDecodingContext *ctx,
+							 Relation rel, HeapTuple tup);
+extern List *DecodingContextGetTableList(LogicalDecodingContext *ctx);
+
 extern void LogicalIncreaseXminForSlot(XLogRecPtr lsn, TransactionId xmin);
 extern void LogicalIncreaseRestartDecodingForSlot(XLogRecPtr current_lsn,
 									  XLogRecPtr restart_lsn);
diff --git a/src/include/replication/logicalproto.h b/src/include/replication/logicalproto.h
index b69d015..2491cc7 100644
--- a/src/include/replication/logicalproto.h
+++ b/src/include/replication/logicalproto.h
@@ -69,8 +69,9 @@ extern LogicalRepRelId logicalrep_read_update(StringInfo in, bool *hasoldtup,
 extern void logicalrep_write_delete(StringInfo out, Relation rel,
 							 HeapTuple oldtuple);
 extern LogicalRepRelId logicalrep_read_delete(StringInfo in, LogicalRepTupleData *oldtup);
+extern void logicalrep_write_rel_name(StringInfo out, char *nspname, char *relname);
 extern void logicalrep_write_rel(StringInfo out, Relation rel);
-
+extern void logicalrep_read_rel_name(StringInfo in, char **nspname, char **relname);
 extern LogicalRepRelation *logicalrep_read_rel(StringInfo in);
 
 #endif /* LOGICALREP_PROTO_H */
diff --git a/src/include/replication/logicalworker.h b/src/include/replication/logicalworker.h
index 64f36d3..6327067 100644
--- a/src/include/replication/logicalworker.h
+++ b/src/include/replication/logicalworker.h
@@ -22,20 +22,27 @@ typedef struct LogicalRepWorker
 
 	/* Subscription id for the worker. */
 	Oid		subid;
+
+	/* Used for initial table synchronization. */
+	Oid		relid;
 } LogicalRepWorker;
 
 extern int max_logical_replication_workers;
-extern LogicalRepWorker *MyLogicalRepWorker;
 
 extern void ApplyLauncherMain(Datum main_arg);
 extern void ApplyWorkerMain(Datum main_arg);
 
 extern Size ApplyLauncherShmemSize(void);
 extern void ApplyLauncherShmemInit(void);
+extern void ApplyLauncherWakeup(void);
+extern void ApplyLauncherWakeupOnCommit(void);
 
 extern void ApplyLauncherWakeupOnCommit(void);
 extern void ApplyLauncherWakeup(void);
 
 extern void logicalrep_worker_attach(int slot);
+extern LogicalRepWorker *logicalrep_worker_find(Oid subid, Oid relid);
+extern int logicalrep_worker_count(Oid subid);
+extern void logicalrep_worker_launch(Oid dbid, Oid subid, Oid relid);
 
 #endif   /* LOGICALWORKER_H */
diff --git a/src/include/replication/output_plugin.h b/src/include/replication/output_plugin.h
index 7911cc0..04ab611 100644
--- a/src/include/replication/output_plugin.h
+++ b/src/include/replication/output_plugin.h
@@ -93,6 +93,22 @@ typedef bool (*LogicalDecodeFilterByOriginCB) (
 													  RepOriginId origin_id);
 
 /*
+ * Called from the LIST_TABLES replication command.
+ */
+typedef List *(*BaseCopyListTablesCB) (
+											  struct LogicalDecodingContext *
+);
+
+/*
+ * Called for every individual tuple in a table during COPY_TABLE.
+ */
+typedef void (*BaseCopyTupleCB) (
+											  struct LogicalDecodingContext *,
+											  Relation relation,
+											  HeapTuple tup
+);
+
+/*
  * Called to shutdown an output plugin.
  */
 typedef void (*LogicalDecodeShutdownCB) (
@@ -111,6 +127,8 @@ typedef struct OutputPluginCallbacks
 	LogicalDecodeMessageCB message_cb;
 	LogicalDecodeFilterByOriginCB filter_by_origin_cb;
 	LogicalDecodeShutdownCB shutdown_cb;
+	BaseCopyListTablesCB list_tables_cb;
+	BaseCopyTupleCB tuple_cb;
 } OutputPluginCallbacks;
 
 void		OutputPluginPrepareWrite(struct LogicalDecodingContext *ctx, bool last_write);
diff --git a/src/include/replication/publication.h b/src/include/replication/publication.h
index 08245ee..78b4eb4 100644
--- a/src/include/replication/publication.h
+++ b/src/include/replication/publication.h
@@ -35,6 +35,7 @@ typedef struct Publication
 extern Publication *GetPublication(Oid pubid);
 extern Publication *GetPublicationByName(const char *pubname, bool missing_ok);
 extern List *GetRelationPublications(Relation rel);
+extern List * GetPublicationRelations(Oid pubid);
 
 extern bool publication_change_is_replicated(Relation rel,
 								 PublicationChangeType change_type,
diff --git a/src/include/replication/subscription.h b/src/include/replication/subscription.h
index a937f4b..18504da 100644
--- a/src/include/replication/subscription.h
+++ b/src/include/replication/subscription.h
@@ -30,4 +30,10 @@ typedef struct Subscription
 extern Subscription *GetSubscription(Oid subid);
 extern Oid get_subscription_oid(const char *subname, bool missing_ok);
 
+extern Oid SetSubscriptionRelState(Oid subid, Oid relid, char state,
+								   XLogRecPtr sublsn);
+extern char GetSubscriptionRelState(Oid subid, Oid relid,
+									XLogRecPtr *sublsn, bool missing_ok);
+extern void DropSubscriptionRelById(Oid subrelid);
+
 #endif		/* SUBSCRIPTION_H */
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index 3801949..d99cd72 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -169,6 +169,13 @@ typedef int (*walrcvconn_receive_fn) (WalReceiverConnHandle *handle,
 									  char **buffer, pgsocket *wait_fd);
 typedef void (*walrcvconn_send_fn) (WalReceiverConnHandle *handle,
 									const char *buffer, int nbytes);
+typedef List *(*walrcvconn_list_tables_fn) (
+									WalReceiverConnHandle *handle,
+									char *slotname, char *options);
+typedef bool (*walrcvconn_copy_table_fn) (
+									WalReceiverConnHandle *handle,
+									char *slotname, char *nspname,
+									char *relname, char *options);
 typedef void (*walrcvconn_disconnect_fn) (WalReceiverConnHandle *handle);
 
 typedef struct WalReceiverConnAPI {
@@ -183,6 +190,8 @@ typedef struct WalReceiverConnAPI {
 	walrcvconn_endstreaming_fn				endstreaming;
 	walrcvconn_receive_fn					receive;
 	walrcvconn_send_fn						send;
+	walrcvconn_list_tables_fn				list_tables;
+	walrcvconn_copy_table_fn				copy_table;
 	walrcvconn_disconnect_fn				disconnect;
 } WalReceiverConnAPI;
 
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
new file mode 100644
index 0000000..bd4402b
--- /dev/null
+++ b/src/include/replication/worker_internal.h
@@ -0,0 +1,32 @@
+/*-------------------------------------------------------------------------
+ *
+ * worker_internal.h
+ *	  Internal headers shared by logical replication workers.
+ *
+ * Portions Copyright (c) 2010-2016, PostgreSQL Global Development Group
+ *
+ * src/include/replication/worker_internal.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef WORKER_INTERNAL_H
+#define WORKER_INTERNAL_H
+
+/* filled by libpqreceiver when loaded */
+extern struct WalReceiverConnAPI	   *wrcapi;
+extern struct WalReceiverConnHandle    *wrchandle;
+
+/* Worker and subscription objects. */
+extern Subscription		   *MySubscription;
+extern LogicalRepWorker	   *MyLogicalRepWorker;
+
+extern bool					in_remote_transaction;
+
+extern void LogicalRepApplyLoop(XLogRecPtr last_received);
+extern char *LogicalRepSyncTableStart(XLogRecPtr *origin_startpos);
+void process_syncing_tables(char *slotname, XLogRecPtr end_lsn);
+void invalidate_syncing_table_states(Datum arg, int cacheid,
+									 uint32 hashvalue);
+
+
+#endif   /* WORKER_INTERNAL_H */
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index b1d03a5..5349c81 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -87,6 +87,8 @@ enum SysCacheIdentifier
 	STATRELATTINH,
 	SUBSCRIPTIONOID,
 	SUBSCRIPTIONNAME,
+	SUBSCRIPTIONRELOID,
+	SUBSCRIPTIONRELMAP,
 	TABLESPACEOID,
 	TRFOID,
 	TRFTYPELANG,
diff --git a/src/test/Makefile b/src/test/Makefile
index 7f7754f..8e8527a 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -12,7 +12,7 @@ subdir = src/test
 top_builddir = ../..
 include $(top_builddir)/src/Makefile.global
 
-SUBDIRS = regress isolation modules recovery
+SUBDIRS = regress isolation modules recovery subscription
 
 # We don't build or execute examples/, locale/, or thread/ by default,
 # but we do want "make clean" etc to recurse into them.  Likewise for ssl/,
diff --git a/src/test/README b/src/test/README
index 62395e7..74bab09 100644
--- a/src/test/README
+++ b/src/test/README
@@ -37,5 +37,8 @@ regress/
 ssl/
   Tests to exercise and verify SSL certificate handling
 
+subscription/
+  Test suite for subscriptions and logical replication
+
 thread/
   A thread-safety-testing utility used by configure
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index ceac2c8..9e402f6 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -132,6 +132,7 @@ pg_shdescription|t
 pg_shseclabel|t
 pg_statistic|t
 pg_subscription|t
+pg_subscription_rel|t
 pg_tablespace|t
 pg_transform|t
 pg_trigger|t
diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl
index dca19c4..f8121d9 100644
--- a/src/test/subscription/t/001_rep_changes.pl
+++ b/src/test/subscription/t/001_rep_changes.pl
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 use PostgresNode;
 use TestLib;
-use Test::More tests => 3;
+use Test::More tests => 4;
 
 # Initialize provider node
 my $node_provider = get_new_node('provider');
@@ -19,7 +19,7 @@ $node_subscriber->start;
 $node_provider->safe_psql('postgres',
 	"CREATE TABLE tab_notrep AS SELECT generate_series(1,10) AS a");
 $node_provider->safe_psql('postgres',
-	"CREATE TABLE tab_ins (a int)");
+	"CREATE TABLE tab_ins AS SELECT generate_series(1,1002) AS a");
 $node_provider->safe_psql('postgres',
 	"CREATE TABLE tab_rep (a int primary key)");
 
@@ -45,18 +45,28 @@ $node_provider->safe_psql('postgres',
 $node_subscriber->safe_psql('postgres',
 	"CREATE SUBSCRIPTION tap_sub WITH CONNECTION '$provider_connstr' PUBLICATION tap_pub, tap_pub_ins_only");
 
-# Wait for subscriber to finish table sync
+# Wait for subscriber to finish initialization
 my $appname = 'tap_sub';
 my $caughtup_query =
 "SELECT pg_current_xlog_location() <= write_location FROM pg_stat_replication WHERE application_name = '$appname';";
 $node_provider->poll_query_until('postgres', $caughtup_query)
   or die "Timed out while waiting for subscriber to catch up";
 
+# Also wait for initial table sync to finish
+my $synced_query =
+"SELECT count(1) = 0 FROM pg_subscription_rel WHERE substate != 'r';";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+  or die "Timed out while waiting for subscriber to synchronize data";
+
 my $result =
   $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM tab_notrep");
 print "node_subscriber: $result\n";
 is($result, qq(0), 'check non-replicated table is empty on subscriber');
 
+$result =
+  $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM tab_ins");
+print "node_subscriber: $result\n";
+is($result, qq(1002), 'check initial data was copied to subscriber');
 
 $node_provider->safe_psql('postgres',
 	"INSERT INTO tab_ins SELECT generate_series(1,50)");
@@ -78,7 +88,7 @@ $node_provider->poll_query_until('postgres', $caughtup_query)
 $result =
   $node_subscriber->safe_psql('postgres', "SELECT count(*), min(a), max(a) FROM tab_ins");
 print "node_subscriber: $result\n";
-is($result, qq(50|1|50), 'check replicated inserts on subscriber');
+is($result, qq(1052|1|1002), 'check replicated inserts on subscriber');
 
 $result =
   $node_subscriber->safe_psql('postgres', "SELECT count(*), min(a), max(a) FROM tab_rep");
diff --git a/src/test/subscription/t/002_types.pl b/src/test/subscription/t/002_types.pl
index a126201..a9c8526 100644
--- a/src/test/subscription/t/002_types.pl
+++ b/src/test/subscription/t/002_types.pl
@@ -101,13 +101,19 @@ $node_provider->safe_psql('postgres',
 $node_subscriber->safe_psql('postgres',
 	"CREATE SUBSCRIPTION tap_sub WITH CONNECTION '$provider_connstr' PUBLICATION tap_pub");
 
-# Wait for subscriber to finish table sync
+# Wait for subscriber to finish initialization
 my $appname = 'tap_sub';
 my $caughtup_query =
 "SELECT pg_current_xlog_location() <= write_location FROM pg_stat_replication WHERE application_name = '$appname';";
 $node_provider->poll_query_until('postgres', $caughtup_query)
   or die "Timed out while waiting for subscriber to catch up";
 
+# Wait for initial sync to finish as well
+my $synced_query =
+"SELECT count(1) = 0 FROM pg_subscription_rel WHERE substate != 'r';";
+$node_subscriber->poll_query_until('postgres', $synced_query)
+  or die "Timed out while waiting for subscriber to synchronize data";
+
 # Insert initial test data
 $node_provider->safe_psql('postgres', qq(
 	-- test_tbl_one_array_col
-- 
2.7.4

#2Andres Freund
andres@anarazel.de
In reply to: Petr Jelinek (#1)
Re: Logical Replication WIP

On 2016-08-05 17:00:13 +0200, Petr Jelinek wrote:

as promised here is WIP version of logical replication patch.

Yay!

I'm about to head out for a week of, desperately needed, holidays, but
after that I plan to spend a fair amount of time helping to review
etc. this.

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

#3Simon Riggs
simon@2ndquadrant.com
In reply to: Andres Freund (#2)
Re: Logical Replication WIP

On 5 August 2016 at 16:22, Andres Freund <andres@anarazel.de> wrote:

On 2016-08-05 17:00:13 +0200, Petr Jelinek wrote:

as promised here is WIP version of logical replication patch.

Yay!

Yay2

I'm about to head out for a week of, desperately needed, holidays, but
after that I plan to spend a fair amount of time helping to review
etc. this.

Have a good one.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#4Masahiko Sawada
sawada.mshk@gmail.com
In reply to: Simon Riggs (#3)
1 attachment(s)
Re: Logical Replication WIP

On Sat, Aug 6, 2016 at 2:04 AM, Simon Riggs <simon@2ndquadrant.com> wrote:

On 5 August 2016 at 16:22, Andres Freund <andres@anarazel.de> wrote:

On 2016-08-05 17:00:13 +0200, Petr Jelinek wrote:

as promised here is WIP version of logical replication patch.

Yay!

Yay2

Thank you for working on this!

I've applied these patches to current HEAD, but got the following error.

libpqwalreceiver.c:48: error: redefinition of typedef ‘WalReceiverConnHandle’
../../../../src/include/replication/walreceiver.h:137: note: previous
declaration of ‘WalReceiverConnHandle’ was here
make[2]: *** [libpqwalreceiver.o] Error 1
make[1]: *** [install-backend/replication/libpqwalreceiver-recurse] Error 2
make: *** [install-src-recurse] Error 2

After fixed this issue with attached patch, I used logical replication a little.
Some random comments and questions.

The logical replication launcher process and the apply process are
implemented as a bgworker. Isn't better to have them as an auxiliary
process like checkpointer, wal writer?
IMO the number of logical replication connections should not be
limited by max_worker_processes.

--
We need to set the publication up by at least CREATE PUBLICATION and
ALTER PUBLICATION command.
Can we make CREATE PUBLICATION possible to define tables as well?
For example,
CREATE PUBLICATION mypub [ TABLE table_name, ...] [WITH options]

--
This patch can not drop the subscription.

=# drop subscription sub;
ERROR: unrecognized object class: 6102

-- 
+/*-------------------------------------------------------------------------
+ *
+ * proto.c
+ *             logical replication protocol functions
+ *
+ * Copyright (c) 2015, PostgreSQL Global Development Group
+ *

The copyright of added files are old.

And this patch has some whitespace problems.
Please run "git show --check" or "git diff origin/master --check"

Regards,

--
Masahiko Sawada

Attachments:

fix_compile_error.patchapplication/x-patch; name=fix_compile_error.patchDownload
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 94648c7..e4aaba4 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -40,12 +40,12 @@
 
 PG_MODULE_MAGIC;
 
-typedef struct WalReceiverConnHandle {
+struct WalReceiverConnHandle {
 	/* Current connection to the primary, if any */
 	PGconn *streamConn;
 	/* Buffer for currently read records */
 	char   *recvBuf;
-} WalReceiverConnHandle;
+};
 
 PGDLLEXPORT WalReceiverConnHandle *_PG_walreceirver_conn_init(WalReceiverConnAPI *wrcapi);
 
#5Craig Ringer
craig@2ndquadrant.com
In reply to: Masahiko Sawada (#4)
Re: Logical Replication WIP

On 9 August 2016 at 15:59, Masahiko Sawada <sawada.mshk@gmail.com> wrote:

The logical replication launcher process and the apply process are
implemented as a bgworker. Isn't better to have them as an auxiliary
process like checkpointer, wal writer?

I don't think so. The checkpointer, walwriter, autovacuum, etc predate
bgworkers. I strongly suspect that if they were to be implemented now
they'd use bgworkers.

Now, perhaps we want a new bgworker "kind" for system workers or some other
minor tweaks. But basically I think bgworkers are exactly what we should be
using here.

IMO the number of logical replication connections should not be
limited by max_worker_processes.

Well, they *are* worker processes... but I take your point, that that
setting has been "number of bgworkers the user can run" and it might not be
expected that logical replication would use the same space.

max_worker_progresses isn't just a limit, it controls how many shmem slots
we allocate.

I guess we could have a separate max_logical_workers or something, but I'm
inclined to think that adds complexity without really making things any
nicer. We'd just add them together to decide how many shmem slots to
allocate and we'd have to keep track of how many slots were used by which
types of backend. Or create a near-duplicate of the bgworker facility for
logical rep.

Sure, you can go deeper down the rabbit hole here and say that we need to
add bgworker "categories" with reserved pools of worker slots for each
category. But do we really need that?

max_connections includes everything, both system and user backends. It's
not like we don't do this elsewhere. It's at worst a mild wart.

The only argument I can see for not using bgworkers is for the supervisor
worker. It's a singleton that launches the per-database workers, and
arguably is a job that the postmaster could do better. The current design
there stems from its origins as an extension. Maybe worker management could
be simplified a bit as a result. I'd really rather not invent yet another
new and mostly duplicate category of custom workers to achieve that though.

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

#6Michael Paquier
michael.paquier@gmail.com
In reply to: Craig Ringer (#5)
Re: Logical Replication WIP

On Tue, Aug 9, 2016 at 5:13 PM, Craig Ringer <craig@2ndquadrant.com> wrote:

On 9 August 2016 at 15:59, Masahiko Sawada <sawada.mshk@gmail.com> wrote:

The logical replication launcher process and the apply process are
implemented as a bgworker. Isn't better to have them as an auxiliary
process like checkpointer, wal writer?

I don't think so. The checkpointer, walwriter, autovacuum, etc predate
bgworkers. I strongly suspect that if they were to be implemented now they'd
use bgworkers.

+1. We could always get them now under the umbrella of the bgworker
infrastructure if this cleans up some code duplication.
-- 
Michael

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

#7Petr Jelinek
petr@2ndquadrant.com
In reply to: Masahiko Sawada (#4)
Re: Logical Replication WIP

On 09/08/16 09:59, Masahiko Sawada wrote:

On 2016-08-05 17:00:13 +0200, Petr Jelinek wrote:

as promised here is WIP version of logical replication patch.

Thank you for working on this!

Thanks for looking!

I've applied these patches to current HEAD, but got the following error.

libpqwalreceiver.c:48: error: redefinition of typedef ‘WalReceiverConnHandle’
../../../../src/include/replication/walreceiver.h:137: note: previous
declaration of ‘WalReceiverConnHandle’ was here
make[2]: *** [libpqwalreceiver.o] Error 1
make[1]: *** [install-backend/replication/libpqwalreceiver-recurse] Error 2
make: *** [install-src-recurse] Error 2

After fixed this issue with attached patch, I used logical replication a little.
Some random comments and questions.

Interesting, my compiler does have problem. Will investigate.

The logical replication launcher process and the apply process are
implemented as a bgworker. Isn't better to have them as an auxiliary
process like checkpointer, wal writer?
IMO the number of logical replication connections should not be
limited by max_worker_processes.

What Craig said reflects my rationale for doing this pretty well.

We need to set the publication up by at least CREATE PUBLICATION and
ALTER PUBLICATION command.
Can we make CREATE PUBLICATION possible to define tables as well?
For example,
CREATE PUBLICATION mypub [ TABLE table_name, ...] [WITH options]

Agreed, that just didn't make it to the first cut to -hackers. We've
been also thinking of having special ALL TABLES parameter there that
would encompass whole db.

--
This patch can not drop the subscription.

=# drop subscription sub;
ERROR: unrecognized object class: 6102

Yeah that's because of the patch 0006, I didn't finish all the
dependency tracking for the pg_subscription_rel catalog that it adds
(which is why I called it PoC). I expect to have this working in next
version (there is still quite a bit of polish work needed in general).

--
Petr Jelinek 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

#8Petr Jelinek
petr@2ndquadrant.com
In reply to: Craig Ringer (#5)
Re: Logical Replication WIP

On 09/08/16 10:13, Craig Ringer wrote:

On 9 August 2016 at 15:59, Masahiko Sawada <sawada.mshk@gmail.com
<mailto:sawada.mshk@gmail.com>> wrote:

The logical replication launcher process and the apply process are
implemented as a bgworker. Isn't better to have them as an auxiliary
process like checkpointer, wal writer?

I don't think so. The checkpointer, walwriter, autovacuum, etc predate
bgworkers. I strongly suspect that if they were to be implemented now
they'd use bgworkers.

Now, perhaps we want a new bgworker "kind" for system workers or some
other minor tweaks. But basically I think bgworkers are exactly what we
should be using here.

Agreed.

IMO the number of logical replication connections should not be
limited by max_worker_processes.

Well, they *are* worker processes... but I take your point, that that
setting has been "number of bgworkers the user can run" and it might not
be expected that logical replication would use the same space.

Again agree, I think we should ultimately go towards what PeterE
suggested in
/messages/by-id/a2fffd92-6e59-a4eb-dd85-c5865ebca1a0@2ndquadrant.com

The only argument I can see for not using bgworkers is for the
supervisor worker. It's a singleton that launches the per-database
workers, and arguably is a job that the postmaster could do better. The
current design there stems from its origins as an extension. Maybe
worker management could be simplified a bit as a result. I'd really
rather not invent yet another new and mostly duplicate category of
custom workers to achieve that though.

It is simplified compared to pglogical (there is only 2 worker types not
3). I don't think it's job of postmaster to scan catalogs however so it
can't really start workers for logical replication. I actually modeled
it more after autovacuum (using bgworkers though) than the original
extension.

--
Petr Jelinek 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

#9Masahiko Sawada
sawada.mshk@gmail.com
In reply to: Craig Ringer (#5)
Re: Logical Replication WIP

On Tue, Aug 9, 2016 at 5:13 PM, Craig Ringer <craig@2ndquadrant.com> wrote:

On 9 August 2016 at 15:59, Masahiko Sawada <sawada.mshk@gmail.com> wrote:

The logical replication launcher process and the apply process are
implemented as a bgworker. Isn't better to have them as an auxiliary
process like checkpointer, wal writer?

I don't think so. The checkpointer, walwriter, autovacuum, etc predate
bgworkers. I strongly suspect that if they were to be implemented now they'd
use bgworkers.

Now, perhaps we want a new bgworker "kind" for system workers or some other
minor tweaks. But basically I think bgworkers are exactly what we should be
using here.

I understood. Thanks!

IMO the number of logical replication connections should not be
limited by max_worker_processes.

Well, they *are* worker processes... but I take your point, that that
setting has been "number of bgworkers the user can run" and it might not be
expected that logical replication would use the same space.

max_worker_progresses isn't just a limit, it controls how many shmem slots
we allocate.

I guess we could have a separate max_logical_workers or something, but I'm
inclined to think that adds complexity without really making things any
nicer. We'd just add them together to decide how many shmem slots to
allocate and we'd have to keep track of how many slots were used by which
types of backend. Or create a near-duplicate of the bgworker facility for
logical rep.

Sure, you can go deeper down the rabbit hole here and say that we need to
add bgworker "categories" with reserved pools of worker slots for each
category. But do we really need that?

If we change these processes to bgworker, we can categorize them into
two, auxiliary process(check pointer and wal sender etc) and other
worker process.
And max_worker_processes controls the latter.

max_connections includes everything, both system and user backends. It's not
like we don't do this elsewhere. It's at worst a mild wart.

The only argument I can see for not using bgworkers is for the supervisor
worker. It's a singleton that launches the per-database workers, and
arguably is a job that the postmaster could do better. The current design
there stems from its origins as an extension. Maybe worker management could
be simplified a bit as a result. I'd really rather not invent yet another
new and mostly duplicate category of custom workers to achieve that though.

Regards,

--
Masahiko Sawada

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

#10Craig Ringer
craig@2ndquadrant.com
In reply to: Masahiko Sawada (#9)
Re: Logical Replication WIP

On 9 August 2016 at 17:28, Masahiko Sawada <sawada.mshk@gmail.com> wrote:

Sure, you can go deeper down the rabbit hole here and say that we need to
add bgworker "categories" with reserved pools of worker slots for each
category. But do we really need that?

If we change these processes to bgworker, we can categorize them into
two, auxiliary process(check pointer and wal sender etc) and other
worker process.
And max_worker_processes controls the latter.

Right. I think that's probably the direction we should be going eventually.
Personally I don't think such a change should block the logical replication
work from proceeding with bgworkers, though. It's been delayed a long time,
a lot of people want it, and I think we need to focus on meeting the core
requirements not getting too sidetracked on minor points.

Of course, everyone's idea of what's core and what's a minor sidetrack
differs ;)

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

#11Petr Jelinek
petr@2ndquadrant.com
In reply to: Craig Ringer (#10)
Re: Logical Replication WIP

On 09/08/16 12:16, Craig Ringer wrote:

On 9 August 2016 at 17:28, Masahiko Sawada <sawada.mshk@gmail.com
<mailto:sawada.mshk@gmail.com>> wrote:

Sure, you can go deeper down the rabbit hole here and say that we need to
add bgworker "categories" with reserved pools of worker slots for each
category. But do we really need that?

If we change these processes to bgworker, we can categorize them into
two, auxiliary process(check pointer and wal sender etc) and other
worker process.
And max_worker_processes controls the latter.

Right. I think that's probably the direction we should be going
eventually. Personally I don't think such a change should block the
logical replication work from proceeding with bgworkers, though. It's
been delayed a long time, a lot of people want it, and I think we need
to focus on meeting the core requirements not getting too sidetracked on
minor points.

Of course, everyone's idea of what's core and what's a minor sidetrack
differs ;)

Yeah that's why I added local max GUC that just handles the logical
worker limit within the max_worker_processes. I didn't want to also
write generic framework for managing the max workers using tags or
something as part of this, it's big enough as it is and we can always
move the limit to the more generic place once we have it.

--
Petr Jelinek 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

#12Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Petr Jelinek (#11)
Re: Logical Replication WIP

Petr Jelinek wrote:

On 09/08/16 12:16, Craig Ringer wrote:

Right. I think that's probably the direction we should be going
eventually. Personally I don't think such a change should block the
logical replication work from proceeding with bgworkers, though.

Yeah that's why I added local max GUC that just handles the logical worker
limit within the max_worker_processes. I didn't want to also write generic
framework for managing the max workers using tags or something as part of
this, it's big enough as it is and we can always move the limit to the more
generic place once we have it.

Parallel query does exactly that: the workers are allocated from the
bgworkers array, and if you want more, it's on you to increase that
limit (it doesn't even have the GUC for a maximum). As far as logical
replication and parallel query are concerned, that's fine. We can
improve this later, if it proves to be a problem.

I think there are far more pressing matters to review.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#13Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Petr Jelinek (#8)
Re: Logical Replication WIP

Petr Jelinek wrote:

On 09/08/16 10:13, Craig Ringer wrote:

The only argument I can see for not using bgworkers is for the
supervisor worker. It's a singleton that launches the per-database
workers, and arguably is a job that the postmaster could do better. The
current design there stems from its origins as an extension. Maybe
worker management could be simplified a bit as a result. I'd really
rather not invent yet another new and mostly duplicate category of
custom workers to achieve that though.

It is simplified compared to pglogical (there is only 2 worker types not 3).
I don't think it's job of postmaster to scan catalogs however so it can't
really start workers for logical replication. I actually modeled it more
after autovacuum (using bgworkers though) than the original extension.

Yeah, it's a very bad idea to put postmaster on this task. We should
definitely stay away from that.

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#14Stas Kelvich
s.kelvich@postgrespro.ru
In reply to: Alvaro Herrera (#13)
1 attachment(s)
Re: Logical Replication WIP

On 05 Aug 2016, at 18:00, Petr Jelinek <petr@2ndquadrant.com> wrote:

Hi,

as promised here is WIP version of logical replication patch.

Great!

Proposed DDL about publication/subscriptions looks very nice to me.

Some notes and thoughts about patch:

* Clang grumbles at following pieces of code:

apply.c:1316:6: warning: variable 'origin_startpos' is used uninitialized whenever 'if' condition is false [-Wsometimes-uninitialized]

tablesync.c:436:45: warning: if statement has empty body [-Wempty-body]
if (wait_for_sync_status_change(tstate));

* max_logical_replication_workers mentioned everywhere in docs, but guc.c defines
variable called max_logical_replication_processes for postgresql.conf

* Since pg_subscription already shared across the cluster, it can be also handy to
share pg_publications too and allow publication of tables from different databases. That
is rare scenarios but quite important for virtual hosting use case — tons of small databases
in a single postgres cluster.

* There is no way to see attachecd tables/schemas to publication through \drp

* As far as I understand there is no way to add table/tablespace right in CREATE
PUBLICATION and one need explicitly do ALTER PUBLICATION right after creation.
May be add something like WITH TABLE/TABLESPACE to CREATE?

* So binary protocol goes into core. Is it still possible to use it as decoding plugin for
manually created walsender? May be also include json as it was in pglogical? While
i’m not arguing that it should be done, i’m interested about your opinion on that.

* Also I’ve noted that you got rid of reserved byte (flags) in protocol comparing to
pglogical_native. It was very handy to use it for two phase tx decoding (0 — usual
commit, 1 — prepare, 2 — commit prepared), because both prepare and commit
prepared generates commit record in xlog.

On 05 Aug 2016, at 18:00, Petr Jelinek <petr@2ndquadrant.com> wrote:

- DDL, I see several approaches we could do here for 10.0. a) don't
deal with DDL at all yet, b) provide function which pushes the DDL
into replication queue and then executes on downstream (like
londiste, slony, pglogical do), c) capture the DDL query as text
and allow user defined function to be called with that DDL text on
the subscriber

* Since here DDL is mostly ALTER / CREATE / DROP TABLE (or am I wrong?) may be
we can add something like WITH SUBSCRIBERS to statements?

* Talking about exact mechanism of DDL replication I like you variant b), but since we
have transactional DDL, we can do two phase commit here. That will require two phase
decoding and some logic about catching prepare responses through logical messages. If that
approach sounds interesting i can describe proposal in more details and create a patch.

* Also I wasn’t able actually to run replication itself =) While regression tests passes, TAP
tests and manual run stuck. pg_subscription_rel.substate never becomes ‘r’. I’ll investigate
that more and write again.

* As far as I understand sync starts automatically on enabling publication. May be split that
logic into a different command with some options? Like don’t sync at all for example.

* When I’m trying to create subscription to non-existent publication, CREATE SUBSRITION
creates replication slot and do not destroys it:

# create subscription sub connection 'host=127.0.0.1 dbname=postgres' publication mypub;
NOTICE: created replication slot "sub" on provider
ERROR: could not receive list of replicated tables from the provider: ERROR: cache lookup failed for publication 0
CONTEXT: slot "sub", output plugin "pgoutput", in the list_tables callback

after that:

postgres=# drop subscription sub;
ERROR: subscription "sub" does not exist
postgres=# create subscription sub connection 'host=127.0.0.1 dbname=postgres' publication pub;
ERROR: could not crate replication slot "sub": ERROR: replication slot "sub" already exists

* Also can’t drop subscription:

postgres=# \drs
List of subscriptions
Name | Database | Enabled | Publication | Conninfo
------+----------+---------+-------------+--------------------------------
sub | postgres | t | {mypub} | host=127.0.0.1 dbname=postgres
(1 row)

postgres=# drop subscription sub;
ERROR: unrecognized object class: 6102

* Several time i’ve run in a situation where provider's postmaster ignores Ctrl-C until subscribed
node is switched off.

* Patch with small typos fixed attached.

I’ll do more testing, just want to share what i have so far.

Attachments:

typos.diffapplication/octet-stream; name=typos.diffDownload
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index 3179add..f57068c 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -52,7 +52,7 @@
     </listitem>
     <listitem>
       <para>
-        Replicating between different major versions of the PostgreSQL
+        Replicating between different major versions of the PostgreSQL.
       </para>
     </listitem>
     <listitem>
@@ -325,7 +325,7 @@
 <programlisting>
 wal_level = logical
 max_worker_processes = 10 # one per subscription + one per instance needed on subscriber
-max_logical_replication_workers = 10 # one per subscription + one per instance needed on subscriber
+max_logical_replication_processes = 10 # one per subscription + one per instance needed on subscriber
 max_replication_slots = 10 # one per subscription needed both provider and subscriber
 max_wal_senders = 10 # one per subscription needed on provider
 </programlisting>
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index bfef492..0100d43 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -254,7 +254,7 @@ CreateSubscription(CreateSubscriptionStmt *stmt)
        wrchandle = walrcvconn_init(wrcapi);
        if (wrcapi->connect == NULL ||
                wrcapi->create_slot == NULL)
-               elog(ERROR, "libpqwalreceiver didn't initialize correctly");
+               elog(ERROR, "libpqwalreceiver didn't initialized correctly");
 
        /*
         * Create the replication slot on remote side for our newly created
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index def88d3..cc60582 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1152,7 +1152,7 @@ CopyTableWriteData(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xi
 }
 
 /*
- * Handle OPY_TABLE command.
+ * Handle COPY_TABLE command.
  */
 static void
 CopyTable(CopyTableCmd *cmd)
#15Petr Jelinek
petr@2ndquadrant.com
In reply to: Stas Kelvich (#14)
Re: Logical Replication WIP

Hi,

On 11/08/16 13:34, Stas Kelvich wrote:

* max_logical_replication_workers mentioned everywhere in docs, but guc.c defines
variable called max_logical_replication_processes for postgresql.conf

Ah changed it in code but not in docs, will fix.

* Since pg_subscription already shared across the cluster, it can be also handy to
share pg_publications too and allow publication of tables from different databases. That
is rare scenarios but quite important for virtual hosting use case � tons of small databases
in a single postgres cluster.

You can't decode changes from multiple databases in one slot so I don't
see the usefulness there. The pg_subscription is currently shared
because it's technical necessity (as in I don't see how to solve the
need to access the catalog from launcher in any other way) not because I
think it's great design :)

* There is no way to see attachecd tables/schemas to publication through \drp

That's mostly intentional as publications for table are visible in \d,
but I am not against adding it to \drp.

* As far as I understand there is no way to add table/tablespace right in CREATE
PUBLICATION and one need explicitly do ALTER PUBLICATION right after creation.
May be add something like WITH TABLE/TABLESPACE to CREATE?

Yes, as I said to Masahiko Sawada, it's just not there yet but I plan to
have that.

* So binary protocol goes into core. Is it still possible to use it as decoding plugin for
manually created walsender? May be also include json as it was in pglogical? While
i�m not arguing that it should be done, i�m interested about your opinion on that.

Well the plugin is bit more integrated into the publication infra so if
somebody would want to use it directly they'd have to use that part as
well. OTOH the protocol itself is provided as API so it's reusable by
other plugins if needed.

JSON plugin is something that would be nice to have in core as well, but
I don't think it's part of this patch.

* Also I�ve noted that you got rid of reserved byte (flags) in protocol comparing to
pglogical_native. It was very handy to use it for two phase tx decoding (0 � usual
commit, 1 � prepare, 2 � commit prepared), because both prepare and commit
prepared generates commit record in xlog.

Hmm maybe commit message could get it back. PGLogical has them sprinkled
all around the protocol which I don't really like so I want to limit
them to the places where they are actually useful.

On 05 Aug 2016, at 18:00, Petr Jelinek <petr@2ndquadrant.com> wrote:

- DDL, I see several approaches we could do here for 10.0. a) don't
deal with DDL at all yet, b) provide function which pushes the DDL
into replication queue and then executes on downstream (like
londiste, slony, pglogical do), c) capture the DDL query as text
and allow user defined function to be called with that DDL text on
the subscriber

* Since here DDL is mostly ALTER / CREATE / DROP TABLE (or am I wrong?) may be
we can add something like WITH SUBSCRIBERS to statements?

Not sure I follow. How does that help?

* Talking about exact mechanism of DDL replication I like you variant b), but since we
have transactional DDL, we can do two phase commit here. That will require two phase
decoding and some logic about catching prepare responses through logical messages. If that
approach sounds interesting i can describe proposal in more details and create a patch.

I'd think that such approach is somewhat more interesting with c)
honestly. The difference between b) and c) is mostly about explicit vs
implicit. I definitely would like to see the 2PC patch updated to work
with this. But maybe it's wise to wait a while until the core of the
patch stabilizes during the discussion.

* Also I wasn�t able actually to run replication itself =) While regression tests passes, TAP
tests and manual run stuck. pg_subscription_rel.substate never becomes �r�. I�ll investigate
that more and write again.

Interesting, please keep me posted. It's possible for tables to stay in
's' state for some time if there is nothing happening on the server, but
that should not mean anything is stuck.

* As far as I understand sync starts automatically on enabling publication. May be split that
logic into a different command with some options? Like don�t sync at all for example.

I think SYNC should be option of subscription creation just like
INITIALLY ENABLED/DISABLED is. And then there should be interface to
resync a table manually (like pglogical has). Not yet sure how that
interface should look like in terms of DDL though.

* When I�m trying to create subscription to non-existent publication, CREATE SUBSRITION
creates replication slot and do not destroys it:

# create subscription sub connection 'host=127.0.0.1 dbname=postgres' publication mypub;
NOTICE: created replication slot "sub" on provider
ERROR: could not receive list of replicated tables from the provider: ERROR: cache lookup failed for publication 0
CONTEXT: slot "sub", output plugin "pgoutput", in the list_tables callback

after that:

postgres=# drop subscription sub;
ERROR: subscription "sub" does not exist
postgres=# create subscription sub connection 'host=127.0.0.1 dbname=postgres' publication pub;
ERROR: could not crate replication slot "sub": ERROR: replication slot "sub" already exists

See the TODO in CreateSubscription function :)

* Also can�t drop subscription:

postgres=# \drs
List of subscriptions
Name | Database | Enabled | Publication | Conninfo
------+----------+---------+-------------+--------------------------------
sub | postgres | t | {mypub} | host=127.0.0.1 dbname=postgres
(1 row)

postgres=# drop subscription sub;
ERROR: unrecognized object class: 6102

Yes that has been already reported.

* Several time i�ve run in a situation where provider's postmaster ignores Ctrl-C until subscribed
node is switched off.

Hmm I guess there is bug in signal processing code somewhere.

* Patch with small typos fixed attached.

I�ll do more testing, just want to share what i have so far.

Thanks for both.

--
Petr Jelinek 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

#16Steve Singer
steve@ssinger.info
In reply to: Petr Jelinek (#1)
Re: Logical Replication WIP

On 08/05/2016 11:00 AM, Petr Jelinek wrote:

Hi,

as promised here is WIP version of logical replication patch.

Thanks for keeping on this. This is important work

Feedback is welcome.

+<sect1 id="logical-replication-publication">
+  <title>Publication</title>
+  <para>
+    A Publication object can be defined on any master node, owned by one
+    user. A Publication is a set of changes generated from a group of
+    tables, and might also be described as a Change Set or Replication Set.
+    Each Publication exists in only one database.

'A publication object can be defined on *any master node*'. I found
this confusing the first time I read it because I thought it was
circular (what makes a node a 'master' node? Having a publication object
published from it?). On reflection I realized that you mean ' any
*physical replication master*'. I think this might be better worded as
'A publication object can be defined on any node other than a standby
node'. I think referring to 'master' in the context of logical
replication might confuse people.

I am raising this in the context of the larger terminology that we want
to use and potential confusion with the terminology we use for physical
replication. I like the publication / subscription terminology you've
gone with.

  <para>
+    Publications are different from table schema and do not affect
+    how the table is accessed. Each table can be added to multiple
+    Publications if needed.  Publications may include both tables
+    and materialized views. Objects must be added explicitly, except
+    when a Publication is created for "ALL TABLES". There is no
+    default name for a Publication which specifies all tables.
+  </para>
+  <para>
+    The Publication is different from table schema, it does not affect
+    how the table is accessed and each table can be added to multiple

Those 2 paragraphs seem to start the same way. I get the feeling that
there is some point your trying to express that I'm not catching onto.
Of course a publication is different than a tables schema, or different
than a function.

The definition of publication you have on the CREATE PUBLICATION page
seems better and should be repeated here (A publication is essentially a
group of tables intended for managing logical replication. See Section
30.1 <cid:part1.06040100.08080900@ssinger.info> for details about how
publications fit into logical replication setup. )

+  <para>
+    Conflicts happen when the replicated changes is breaking any
+    specified constraints (with the exception of foreign keys which are
+    not checked). Currently conflicts are not resolved automatically and
+    cause replication to be stopped with an error until the conflict is
+    manually resolved.

What options are there for manually resolving conflicts? Is the only
option to change the data on the subscriber to avoid the conflict?
I assume there isn't a way to flag a particular row coming from the
publisher and say ignore it. I don't think this is something we need to
support for the first version.

<sect1 id="logical-replication-architecture">
+  <title>Architecture</title>
+  <para>
+    Logical replication starts by copying a snapshot of the data on
+    the Provider database. Once that is done, the changes on Provider

I notice the user of 'Provider' above do you intend to update that to
'Publisher' or does provider mean something different. If we like the
'publication' terminology then I think 'publishers' should publish them
not providers.

I'm trying to test a basic subscription and I do the following

I did the following:

cluster 1:
create database test1;
create table a(id serial8 primary key,b text);
create publication testpub1;
alter publication testpub1 add table a;
insert into a(b) values ('1');

cluster2
create database test1;
create table a(id serial8 primary key,b text);
create subscription testsub2 publication testpub1 connection
'host=localhost port=5440 dbname=test1';
NOTICE: created replication slot "testsub2" on provider
NOTICE: synchronized table states
CREATE SUBSCRIPTION

This resulted in
LOG: logical decoding found consistent point at 0/15625E0
DETAIL: There are no running transactions.
LOG: exported logical decoding snapshot: "00000494-1" with 0
transaction IDs
LOG: logical replication apply for subscription testsub2 started
LOG: starting logical decoding for slot "testsub2"
DETAIL: streaming transactions committing after 0/1562618, reading WAL
from 0/15625E0
LOG: logical decoding found consistent point at 0/15625E0
DETAIL: There are no running transactions.
LOG: logical replication sync for subscription testsub2, table a started
LOG: logical decoding found consistent point at 0/1562640
DETAIL: There are no running transactions.
LOG: exported logical decoding snapshot: "00000495-1" with 0
transaction IDs
LOG: logical replication synchronization worker finished processing

The initial sync completed okay, then I did

insert into a(b) values ('2');

but the second insert never replicated.

I had the following output

LOG: terminating walsender process due to replication timeout

On cluster 1 I do

select * FROM pg_stat_replication;
pid | usesysid | usename | application_name | client_addr |
client_hostname | client_port | backend_start |
backend_xmin | state | sent_location | write_location | flush_location |
replay_location | sync_priority | sy
nc_state
-----+----------+---------+------------------+-------------+-----------------+-------------+---------------+-
-------------+-------+---------------+----------------+----------------+-----------------+---------------+---
---------
(0 rows)

If I then kill the cluster2 postmaster, I have to do a -9 or it won't die

I get

LOG: worker process: logical replication worker 16396 sync 16387 (PID
3677) exited with exit code 1
WARNING: could not launch logical replication worker
LOG: logical replication sync for subscription testsub2, table a started
ERROR: replication slot "testsub2_sync_a" does not exist
ERROR: could not start WAL streaming: ERROR: replication slot
"testsub2_sync_a" does not exist

I'm not really sure what I need to do to debug this, I suspect the
worker on cluster2 is having some issue.

[1]
/messages/by-id/CANP8+j+NMHP-yFvoG03tpb4_s7GdmnCriEEOJeKkXWmUu_=-HA@mail.gmail.com

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

#17Petr Jelinek
petr@2ndquadrant.com
In reply to: Steve Singer (#16)
Re: Logical Replication WIP

On 13/08/16 17:34, Steve Singer wrote:

On 08/05/2016 11:00 AM, Petr Jelinek wrote:

Hi,

as promised here is WIP version of logical replication patch.

Thanks for keeping on this. This is important work

Feedback is welcome.

+<sect1 id="logical-replication-publication">
+  <title>Publication</title>
+  <para>
+    A Publication object can be defined on any master node, owned by one
+    user. A Publication is a set of changes generated from a group of
+    tables, and might also be described as a Change Set or Replication
Set.
+    Each Publication exists in only one database.

'A publication object can be defined on *any master node*'. I found
this confusing the first time I read it because I thought it was
circular (what makes a node a 'master' node? Having a publication object
published from it?). On reflection I realized that you mean ' any
*physical replication master*'. I think this might be better worded as
'A publication object can be defined on any node other than a standby
node'. I think referring to 'master' in the context of logical
replication might confuse people.

Makes sense to me.

I am raising this in the context of the larger terminology that we want
to use and potential confusion with the terminology we use for physical
replication. I like the publication / subscription terminology you've
gone with.

<para>
+    Publications are different from table schema and do not affect
+    how the table is accessed. Each table can be added to multiple
+    Publications if needed.  Publications may include both tables
+    and materialized views. Objects must be added explicitly, except
+    when a Publication is created for "ALL TABLES". There is no
+    default name for a Publication which specifies all tables.
+  </para>
+  <para>
+    The Publication is different from table schema, it does not affect
+    how the table is accessed and each table can be added to multiple

Those 2 paragraphs seem to start the same way. I get the feeling that
there is some point your trying to express that I'm not catching onto.
Of course a publication is different than a tables schema, or different
than a function.

Ah that's relic of some editorialization, will fix. The reason why we
think it's important to mention the difference between publication and
schema is that they are the only objects that contain tables but they
affect them in very different ways which might confuse users.

The definition of publication you have on the CREATE PUBLICATION page
seems better and should be repeated here (A publication is essentially a
group of tables intended for managing logical replication. See Section
30.1 <cid:part1.06040100.08080900@ssinger.info> for details about how
publications fit into logical replication setup. )

+  <para>
+    Conflicts happen when the replicated changes is breaking any
+    specified constraints (with the exception of foreign keys which are
+    not checked). Currently conflicts are not resolved automatically and
+    cause replication to be stopped with an error until the conflict is
+    manually resolved.

What options are there for manually resolving conflicts? Is the only
option to change the data on the subscriber to avoid the conflict?
I assume there isn't a way to flag a particular row coming from the
publisher and say ignore it. I don't think this is something we need to
support for the first version.

Yes you have to update data on subscriber or skip the the replication of
whole transaction (for which the UI is not very friendly currently as
you either have to consume the transaction
pg_logical_slot_get_binary_changes or by moving origin on subscriber
using pg_replication_origin_advance).

It's relatively easy to add some automatic conflict resolution as well,
but it didn't seem absolutely necessary so I didn't do it for the
initial version.

<sect1 id="logical-replication-architecture">
+  <title>Architecture</title>
+  <para>
+    Logical replication starts by copying a snapshot of the data on
+    the Provider database. Once that is done, the changes on Provider

I notice the user of 'Provider' above do you intend to update that to
'Publisher' or does provider mean something different. If we like the
'publication' terminology then I think 'publishers' should publish them
not providers.

Okay, I am just used to 'provider' in general (I guess londiste habit),
but 'publisher' is fine as well.

I'm trying to test a basic subscription and I do the following

I did the following:

cluster 1:
create database test1;
create table a(id serial8 primary key,b text);
create publication testpub1;
alter publication testpub1 add table a;
insert into a(b) values ('1');

cluster2
create database test1;
create table a(id serial8 primary key,b text);
create subscription testsub2 publication testpub1 connection
'host=localhost port=5440 dbname=test1';
NOTICE: created replication slot "testsub2" on provider
NOTICE: synchronized table states
CREATE SUBSCRIPTION

[...]

The initial sync completed okay, then I did

insert into a(b) values ('2');

but the second insert never replicated.

I had the following output

LOG: terminating walsender process due to replication timeout

On cluster 1 I do

select * FROM pg_stat_replication;
pid | usesysid | usename | application_name | client_addr |
client_hostname | client_port | backend_start |
backend_xmin | state | sent_location | write_location | flush_location |
replay_location | sync_priority | sy
nc_state
-----+----------+---------+------------------+-------------+-----------------+-------------+---------------+-

-------------+-------+---------------+----------------+----------------+-----------------+---------------+---

---------
(0 rows)

If I then kill the cluster2 postmaster, I have to do a -9 or it won't die

That might explain why it didn't replicate. The wait loops in apply
worker clearly need some work. Thanks for the report.

--
Petr Jelinek 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

#18Stas Kelvich
s.kelvich@postgrespro.ru
In reply to: Petr Jelinek (#15)
Re: Logical Replication WIP

On 11 Aug 2016, at 17:43, Petr Jelinek <petr@2ndquadrant.com> wrote:

* Also I wasn’t able actually to run replication itself =) While regression tests passes, TAP
tests and manual run stuck. pg_subscription_rel.substate never becomes ‘r’. I’ll investigate
that more and write again.

Interesting, please keep me posted. It's possible for tables to stay in 's' state for some time if there is nothing happening on the server, but that should not mean anything is stuck.

Slightly played around, it seems that apply worker waits forever for substate change.

(lldb) bt
* thread #1: tid = 0x183e00, 0x00007fff88c7f2a2 libsystem_kernel.dylib`poll + 10, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
frame #0: 0x00007fff88c7f2a2 libsystem_kernel.dylib`poll + 10
frame #1: 0x00000001017ca8a3 postgres`WaitEventSetWaitBlock(set=0x00007fd2dc816b30, cur_timeout=10000, occurred_events=0x00007fff5e7f67d8, nevents=1) + 51 at latch.c:1108
frame #2: 0x00000001017ca438 postgres`WaitEventSetWait(set=0x00007fd2dc816b30, timeout=10000, occurred_events=0x00007fff5e7f67d8, nevents=1) + 248 at latch.c:941
frame #3: 0x00000001017c9fde postgres`WaitLatchOrSocket(latch=0x000000010ab208a4, wakeEvents=25, sock=-1, timeout=10000) + 254 at latch.c:347
frame #4: 0x00000001017c9eda postgres`WaitLatch(latch=0x000000010ab208a4, wakeEvents=25, timeout=10000) + 42 at latch.c:302
* frame #5: 0x0000000101793352 postgres`wait_for_sync_status_change(tstate=0x0000000101e409b0) + 178 at tablesync.c:228
frame #6: 0x0000000101792bbe postgres`process_syncing_tables_apply(slotname="subbi", end_lsn=140734778796592) + 430 at tablesync.c:436
frame #7: 0x00000001017928c1 postgres`process_syncing_tables(slotname="subbi", end_lsn=140734778796592) + 81 at tablesync.c:518
frame #8: 0x000000010177b620 postgres`LogicalRepApplyLoop(last_received=140734778796592) + 704 at apply.c:1122
frame #9: 0x000000010177bef4 postgres`ApplyWorkerMain(main_arg=0) + 1044 at apply.c:1353
frame #10: 0x000000010174cb5a postgres`StartBackgroundWorker + 826 at bgworker.c:729
frame #11: 0x0000000101762227 postgres`do_start_bgworker(rw=0x00007fd2db700000) + 343 at postmaster.c:5553
frame #12: 0x000000010175d42b postgres`maybe_start_bgworker + 427 at postmaster.c:5761
frame #13: 0x000000010175bccf postgres`sigusr1_handler(postgres_signal_arg=30) + 383 at postmaster.c:4979
frame #14: 0x00007fff9ab2352a libsystem_platform.dylib`_sigtramp + 26
frame #15: 0x00007fff88c7e07b libsystem_kernel.dylib`__select + 11
frame #16: 0x000000010175d5ac postgres`ServerLoop + 252 at postmaster.c:1665
frame #17: 0x000000010175b2e0 postgres`PostmasterMain(argc=3, argv=0x00007fd2db403840) + 5968 at postmaster.c:1309
frame #18: 0x000000010169507f postgres`main(argc=3, argv=0x00007fd2db403840) + 751 at main.c:228
frame #19: 0x00007fff8d45c5ad libdyld.dylib`start + 1
(lldb) p state
(char) $1 = 'c'
(lldb) p tstate->state
(char) $2 = ‘c’

Also I’ve noted that some lsn position looks wrong on publisher:

postgres=# select restart_lsn, confirmed_flush_lsn from pg_replication_slots;
restart_lsn | confirmed_flush_lsn
-------------+---------------------
0/1530EF8 | 7FFF/5E7F6A30
(1 row)

postgres=# select sent_location, write_location, flush_location, replay_location from pg_stat_replication;
sent_location | write_location | flush_location | replay_location
---------------+----------------+----------------+-----------------
0/1530F30 | 7FFF/5E7F6A30 | 7FFF/5E7F6A30 | 7FFF/5E7F6A30
(1 row)

--
Stas Kelvich
Postgres Professional: http://www.postgrespro.com
Russian Postgres Company

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

#19Petr Jelinek
petr@2ndquadrant.com
In reply to: Stas Kelvich (#18)
Re: Logical Replication WIP

On 15/08/16 15:51, Stas Kelvich wrote:

On 11 Aug 2016, at 17:43, Petr Jelinek <petr@2ndquadrant.com> wrote:

* Also I wasn�t able actually to run replication itself =) While regression tests passes, TAP
tests and manual run stuck. pg_subscription_rel.substate never becomes �r�. I�ll investigate
that more and write again.

Interesting, please keep me posted. It's possible for tables to stay in 's' state for some time if there is nothing happening on the server, but that should not mean anything is stuck.

Slightly played around, it seems that apply worker waits forever for substate change.

(lldb) bt
* thread #1: tid = 0x183e00, 0x00007fff88c7f2a2 libsystem_kernel.dylib`poll + 10, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
frame #0: 0x00007fff88c7f2a2 libsystem_kernel.dylib`poll + 10
frame #1: 0x00000001017ca8a3 postgres`WaitEventSetWaitBlock(set=0x00007fd2dc816b30, cur_timeout=10000, occurred_events=0x00007fff5e7f67d8, nevents=1) + 51 at latch.c:1108
frame #2: 0x00000001017ca438 postgres`WaitEventSetWait(set=0x00007fd2dc816b30, timeout=10000, occurred_events=0x00007fff5e7f67d8, nevents=1) + 248 at latch.c:941
frame #3: 0x00000001017c9fde postgres`WaitLatchOrSocket(latch=0x000000010ab208a4, wakeEvents=25, sock=-1, timeout=10000) + 254 at latch.c:347
frame #4: 0x00000001017c9eda postgres`WaitLatch(latch=0x000000010ab208a4, wakeEvents=25, timeout=10000) + 42 at latch.c:302
* frame #5: 0x0000000101793352 postgres`wait_for_sync_status_change(tstate=0x0000000101e409b0) + 178 at tablesync.c:228
frame #6: 0x0000000101792bbe postgres`process_syncing_tables_apply(slotname="subbi", end_lsn=140734778796592) + 430 at tablesync.c:436
frame #7: 0x00000001017928c1 postgres`process_syncing_tables(slotname="subbi", end_lsn=140734778796592) + 81 at tablesync.c:518
frame #8: 0x000000010177b620 postgres`LogicalRepApplyLoop(last_received=140734778796592) + 704 at apply.c:1122
frame #9: 0x000000010177bef4 postgres`ApplyWorkerMain(main_arg=0) + 1044 at apply.c:1353
frame #10: 0x000000010174cb5a postgres`StartBackgroundWorker + 826 at bgworker.c:729
frame #11: 0x0000000101762227 postgres`do_start_bgworker(rw=0x00007fd2db700000) + 343 at postmaster.c:5553
frame #12: 0x000000010175d42b postgres`maybe_start_bgworker + 427 at postmaster.c:5761
frame #13: 0x000000010175bccf postgres`sigusr1_handler(postgres_signal_arg=30) + 383 at postmaster.c:4979
frame #14: 0x00007fff9ab2352a libsystem_platform.dylib`_sigtramp + 26
frame #15: 0x00007fff88c7e07b libsystem_kernel.dylib`__select + 11
frame #16: 0x000000010175d5ac postgres`ServerLoop + 252 at postmaster.c:1665
frame #17: 0x000000010175b2e0 postgres`PostmasterMain(argc=3, argv=0x00007fd2db403840) + 5968 at postmaster.c:1309
frame #18: 0x000000010169507f postgres`main(argc=3, argv=0x00007fd2db403840) + 751 at main.c:228
frame #19: 0x00007fff8d45c5ad libdyld.dylib`start + 1
(lldb) p state
(char) $1 = 'c'
(lldb) p tstate->state
(char) $2 = �c�

Hmm, not sure why is that, it might be related to the lsn reported being
wrong. Could you check what is the lsn there (either in tstate or or in
pg_subscription_rel)? Especially in comparison with what the
sent_location is.

Also I�ve noted that some lsn position looks wrong on publisher:

postgres=# select restart_lsn, confirmed_flush_lsn from pg_replication_slots;
restart_lsn | confirmed_flush_lsn
-------------+---------------------
0/1530EF8 | 7FFF/5E7F6A30
(1 row)

postgres=# select sent_location, write_location, flush_location, replay_location from pg_stat_replication;
sent_location | write_location | flush_location | replay_location
---------------+----------------+----------------+-----------------
0/1530F30 | 7FFF/5E7F6A30 | 7FFF/5E7F6A30 | 7FFF/5E7F6A30
(1 row)

That's most likely result of the unitialized origin_startpos warning. I
am working on new version of patch where that part is fixed, if you want
to check this before I send it in, the patch looks like this:

diff --git a/src/backend/replication/logical/apply.c 
b/src/backend/replication/logical/apply.c
index 581299e..7a9e775 100644
--- a/src/backend/replication/logical/apply.c
+++ b/src/backend/replication/logical/apply.c
@@ -1353,6 +1353,7 @@ ApplyWorkerMain(Datum main_arg)
                 originid = replorigin_by_name(myslotname, false);
                 replorigin_session_setup(originid);
                 replorigin_session_origin = originid;
+               origin_startpos = replorigin_session_get_progress(false);
                 CommitTransactionCommand();

wrcapi->connect(wrchandle, MySubscription->conninfo, true,

--
Petr Jelinek 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

#20Petr Jelinek
petr@2ndquadrant.com
In reply to: Petr Jelinek (#19)
6 attachment(s)
Re: Logical Replication WIP

Hi all,

attaching updated version of the patch. Still very much WIP but it's
slowly getting there.

Changes since last time:
- Mostly rewrote publication handling in pgoutput which brings a)
ability to add FOR ALL TABLES publications, b) performs better (no need
to syscache lookup for every change like before), c) does correct
invalidation of publications on DDL
- added FOR TABLE and FOR ALL TABLES clause to both CREATE PUBLICATION
and ALTER PUBLICATION so that one can create publication directly with
table list, the FOR TABLE in ALTER PUBLICATION behaves like SET
operation (removes existing, adds new ones)
- fixed several issues with initial table synchronization (most of which
have been reported here)
- added pg_stat_subscription monitoring view
- updated docs to reflect all the changes, also removed the stuff that's
only planned from the docs (there is copy of the planned stuff docs in
the neighboring thread so no need to keep it in the patch)
- added documentation improvements suggested by Steve Singer and removed
the capitalization in the main doc
- added pg_dump support
- improved psql support (\drp+ shows list of tables)
- added flags to COMMIT message in the protocol so that we can add 2PC
support in the future
- fixed DROP SUBSCRIPTION issues and added tests for it

I decided to not deal with ACLs so far, assuming superuser/replication
role for now. We can always make it less restrictive later by adding the
grantable privileges.

FDW support is still TODO. I think TRUNCATE will have to be solved as
part of other DDL in the future. I do have some ideas what to do with
DDL but I don't plan to implement them in the initial patch.

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

Attachments:

0003-Define-logical-replication-protocol-and-output-plugi.patch.gzapplication/gzip; name=0003-Define-logical-replication-protocol-and-output-plugi.patch.gzDownload
���W0003-Define-logical-replication-protocol-and-output-plugi.patch�=iw�F���_������C�e���"K��YY��t�y�<.4)�AA��M��VUw���,�I��ML�U]wU_�7Q0e{���m��?���h�{�9������cwm~���}�����|��!�>e;;��lwg�[h��kG�[��>����O'������W�3+��Y���l�}���=|��>�y��w]�������9���dp���9����1�e^0qm�c=��@Rq`�|�I&1�d���z���3'�;"��&S��{�����+{��e�Uu���Y�"���P��X�)��e���1��(���"�>�n-&��v~����*F�c'R��){{��Ha����_���~��5P
�����+�1���^��*�H������-�N�C�����x�o-��&��=x�\_�q���&<c������3;������������z]����Agz�<�Tys�,W�Z����u��Y�5qcfuD������;�{f?;t�������8�4q�����������7�0���w�����3;�N!�	f�6c����;� wIyX^guv�=�����1}e��rC�R2m��xU�f��{������d=u�	�1b���r�����Z��(�VW0�:\��;��o�D��[��e��9�BX���^p�DlE��hF������^�'7�����E��d���<��&���]\
�&v�X6�F�l��L9t�x%� �����Gsq���00��(
�Q%��h2�T���%���G�o�S7v?��1b
ogb4%RP/�Km�E����lBO����=��I�J���%L�����N<�����V��z=W��z�?�G3����T���~D@���1�#�*�1x�?�Lai��$�`Lo���������+RD"	������1�B�)���+��};p@����dvI��1�e�S����0��,r]h�7�@D��t�������q��	D�SQ�Y2�3B��(�H��Q#�6W���lC��5�����h_nT�m6��+nD��8��8�3����S<2��.�0*�W�����)��A����u@��O=fD���N��-/
�4�R�J�Y/��G�2YH�T��(rz��@S+�u^l�$X�����@���?�*�F|����o����xr��1����kv�|4)Y��Dv��"�3o�<�M]|(��O)�+��2�
�.�v�����C)�I�Kr8�E��lx����I�����G{^$p*>AGK���v#�t�-G��\J\�z[X��m��
@�5��]�4��0�!O�I�L'H�
g�b��
�TLMf�}aH@����!S��Z������$�������3�s,a
��#���> �?�
���C ���t��;�A�w{M>�2�p5~���G�F��3S�1�B��!�������=��������l�F���MB�>t������|��E��eO���B(NB<����@���"w����M"�!�"�MKRC�M�eojWEN�8&���|�m����:�
;�|�uBW�y�f��,���&�-���4	Y������v�8��(D���`Np����dY!��1C
C�t<.C�%�:F������M)�����`������1��D����������\@�G��Rs��*���V�>UH���
������\J_�����@���#`
��@0[�]����l+{vQG5����w����9 �6e��r!����exK���K_o�t��JB�fxq��;����%��J������l�U~����B�=���h�j�[����/�M,Z�2�E�2/=����:����rk,�t~�)��`c��-9t����p����<8:�%?����xN�A��XV�,}b~�L���m�I-��
���U�A%��H��F�P�k<MI�5��hLE&,.���\��B�[6'�������>-�`��F��)R9����B�x�,���4[�a���	~b�0�6��������E��f�}, ������#�*�.,h�hz�Ig���9e��,J��~�S�����aS� ���n���'�KP��_O����,�eM�G�on�T�-������k�9����
.����g�/H�*�0������M�������shR�r�2�~���N��\���]�pI��'	����o>��������C��_�S}y�+9��P����G���d^�eE�1i�ym��*����E�44[i7�������G���O����������<��<�E�I��-zim��w�8�B�!�*�.C��������\�7&k�R��1�t���&�K���IT����*��X#�>�}��v����Z(�������N�Lqs
��/���3�#��?��������CnEr_�\�������g2}<�1�F^`X��G:o<k""�4$�<�Vs�����6F�26h��<���@K�5�{|���� �V��'��f���E?���X�����Q���}�a7��vT���.�zp���/?W�Y�$��i�?����;c���b�	�XV��a�8!b�c���
�R+<'��".�^t�/����W�oP�a����;<H����
�Q���B��p�Q��`����Ao�O��G���������8��!�i������Vz���Ox�ox�g++��=�J;]	�Ot��DZ������!�����N��S���IIf�1$�W�-��M�R��Y<���������H-�'���t������P�9%�>4�:���8b
�&�x�P>���qV�L>A�C�4�T�bb-�/�i���j������|��H\k�Y��jYv�!/u9�8��O�S+��HOe�:���A�m�'�r7�r�XG������`�p��������Y]�������r��F.��������g��>{��Ug5umO}W�����
�������]����veG}��0��0�]O��?C>J�X�^�h��J�|��lo�n��u�d��L�+uG�w�A���/����{#�!�1s�h3����F�����p�G�N���\�����/Y����G8��[6������W��M�v��e�����5@��T����W�Ucx�8��<���o�Du;�@J���g
N�K�&�
��A�,/b%���p�zO?�g}�b�r�W�qNF�j��U�#���C������,����?��
M��v���3k�E�o��ow�Z��z�cz8�^�-�e����)�i�@��1k������&�D<�8��|K6���jINq��m$a
/'�o��|T��*�cpuv%!���m��(R2Th���ik�%��}��Y&k��x�`
nPl��un�v�#����[�5�/f���AwX��������l��;���X��u�����~��p2�������Yb�����e�e��3�_K}�'n'quD��)Qv��e�W�������R^��)��������7�u���"B������-=�"<f_��l��)}�l��W�|!��]u�,�@���/&������^��u�`�]�}\�eg��wT�Ct��%k����f���o�X��f�Q4!��?�o������b���2�����:�����d��A��c[~��������
��0[��2H�<C��-]!���+��
�#��D?^���*�x�E
��]B ���$�q��]C*��P�\]����,���E1H�����5�HrfbBJXq$����wD��m������'����0��q� ���0��?�>;����<dV�$L�����(��IWu^�8J8���k2�����#���(\�hxv�e#�p��;f
B�I����]8Z��j��u'�/
�dO�O�X�^������ @�w���hX�o�p���2�����7��^cY��A%�M�VSYp�+�������:q��(���R^+5��F���9�8�V�$~�RK[�l#�������x�|��������j���M�����wK16���_�
	�v�E���V���g*o�+�jo(5-�V����%:���[�49m�@@�����#y���-/L�TCl��H����^���]�ey5��"�9b���Lt��"YP�~Mz��%��O�����!^����
���o^�j�-E���������pN�c
���g	
�[������Hu'y�y�������	�J�o<��J����4���DT��J����$�n_�amE|J�~jl�+��/lK2��7� #����@��2HG�	�����x*&RN������
���^����NF-�5���������g�Ne-
�������>)�Z�$����24&)��=������Ye+>�V,�j�Y������R�<�E����O�z��m��<CO ��5���a���)���~��/�k9FYaE��X�<#�Aq���/E� �WK^j���NM�t����"�L��2��{�b`~����[���nz���r)��C6�U<�.U$[�T��/����������������<�����xZ/G�zR�I��y�$h�u_T�}6
�&��k�*���=*uRQ�����R��e���5���I��7��#�B�I�F��hh�����e���^�_,�n-75��>��s�y�IO��
q����%W��`fg�qAW�E�l�f�P��W�i��\��J*.5���!��8gO��q�gUW<�]B�o\������5�Q���^������OiA�S����
���@h���;n� �P�����R�s�<�=U����'�p������M��Bn�nh����$��W������!�+�(%��������_�����"�$��%�E�!������/��"�Q����F��Y�tAaNv�����}k�s���������X�.�������Vq�n-!�3�_�����B^����&����L[#�������f�F�:�qNn��`W�������I3k$"
2^�A�(R)�E-Se�*W[�:XY������oE�������������������<��v��!�
RQ�;WK��Z1��KK
��y��BWYs�������!fU����*�c�-+=�p?<�w�]���/B\$}�Nlj�������)���'�d��7������\WTL�
�d{���a���	>��<+s�B�����������_%:������O)1�C�e�������/K�P�����8�v!
�d��3_IQZ_�5t4�zYgqM�@�8�G��S��;+� �(J����_z}�� +�J5VM*-�k�W�[)�vj�F��@�\�8�d�@^O�7�m1��'~*C��X�(���$���B�a�� ��
����q��O����>��W��FaL�D��h�Q2/m�����k�`�_�%�����1�o�x�mv��5�����������+#��*�\���k�+��/�]_l���B��,���a������d���P.(e����V��~|g}�.������~u��
<�����>W'��!d ���Q����~HW�0��z�z��3x}����.
�\vL�W
�����r�H���������|���C�w�����lo������.M���c��Q�o�b��dr�������_0i�G��r���	7�C�6
��*��l�S�L�[�������F�3bP���)��$����I*�(���D�krD���M-��B���/���W��l:��k�\���RS?��"cT���E-��G��
����p�*�^��%:�=���?Qb��4}5/�����-oG���YfRAa�%kuq������`0����?�o.O.�W�g��7��)iI-��:���,���������8���������w�dW���s���d����#���o���w�-���%���^d-Xv��(/�dE�L��do�������Ri�,�XI�	^Bx��Kd��B���^������o�a�S����|s����PWDm~�D���[j7�%
\M�J!�)[�����:A���Y1p���L��X�,�R)�J�������6n$���+�JE�J�#O{�����D�D*���+#�g�T8�e%��~��`�CE���J�����nt�xs��R�
5q��7�t���BQ��mQm�{��_���)!�M��DT$���Pv6�����Eub�Ofk��r�9�XT�q����D�Gms��k�Lw���Hm�gz�Pr�9y��Spe��.��������������6�)zPD�����d��������������$`����������vG&��������5��j��B���s��j�5+�>�8�
���6!��&����6	�~:�0�.2[]�����E������dlR"'�+� 16�g�#4���9_6�b��������r��_x�a1y?��L�q�l�%_:/}=D5Z����?���B��H����
N�ERx���;�_��� �����K�;�$
"�i'��3�!��M��d���}�"w���*}�����D�������0���X�����=�Vo��=�9�~��_��(�`�J�$c1�����{*�?e�#Z4]%�V�WYo���0���~�%�J^����F��P����&I��"r�?����
q��L��N�_P�����x8�_�����������S��~��1�L�q$1�e�D/��2!3��|��j�l��� � �,/3��X��%a��o���2��B�i�Lr"��$'��1.���j�5����������eT��Bo���������	��(��Y����KHF����MPj����_��Y����(��P�����!(��1��%)ZD���0���o6���3�6����v�D��-���V	$=�}:z�x�@��%J��E�~u�1�_���i�`��%	n�e]�J���TQ�o�q<�;���B���#�����o���Y%.Z]��)����k���$�$x
����~�����t��v{������T�O^�����<^�A�[�0v2��F��/A���o�;GM�� 0��h(��`���-hGw[2e�,tB�I�P<3���r���.g�/&�&���^��	&8�������J��-z�<���xm"�9����"�P��+lh�2�
�x���h0��n�C���&�b���E
���S^R#?
}a �����FOr��7���n�EG��K�+��Cq�!�����C��5��Q���zG��g�����Ak����#����Aa���z(]��|BO��[����skY,e�^\�;������0��G�������M����DnN���r�v�7w��c����
��(���c�����0���M�L����x$Ra�d���^Qs��n�(�AU���M���k�5Ve�@�k���h��5_��ZPq�=R�u��=��G'����F����1������NW?I���@���T,:k1��"|���� �7��^���
/�#��{�*����I.C1��&�j���NP�v2�X=['5�v�J��5����	���t3��&u�������p��R;����#/��8����P��c�wtW�������&��z��q&q �A7�^%{4��#��6�u$�eH|�_F��hz����CW4o��n^�#����0W�4�4���f8���`�P'�|�~�wC�
�$E[���(����I,O@�����1���C�~��Vv|��(�����=���%gj[��
(��m/�~{��B�(s���-��=W��3����pW����o����<�y� �������N��[�V;���x9b���'��	�k�zl<�t)�V���=��4g)z�L3��"��4�����/
�gf���Tq���H}:��s)W[���B�M�V�,�k�Ov��:�no��z����;j�;��l.+�j��U����bf�c���b��2��!��`J�$��1��0�+�4�5�B.}��������M-$�u?�8%������(��n��1��t����G�d@>�,�����Y����;9<;h�{�����?z�P��0��l.�C!h=��	���7������{';���w��=��Ys��w��U���F�������g������a��ml(�")�h3W����>7t(rzi�9�Na����d�+����jV=���YV�,�U��Au���M4��NDu�R�o%3&����$�p6��x�����6{��n�svrr|�m����3���s4��������H�p3(��5
�	&.�9�����`��	-����*��_������#OR�L���6��F�������������>����#W[����%�����h��8C�(����#2
�5`�<-��ZL
l=�"�ZL,c�y����-39�\��>�/>��������7Vc�n����fW1k�~V\�6��m��`��U�@��(��4YL����u��7{1$�rr�i��b`�4A	���^��#!��Y4!�Q�����S����1O�=\���j���)�x�a�����X���3�F��l��U�g����2��_��

b$�vN�e=�-��R�=5����DF�*��Y�J������%j;�^��X�cM;
GH�~�8Z]8�!�^JnLu����&��TF�!^T���g�4��I���t��@F���V��u�7��C�5�<�O���o�>�� �����O��>��p���pA)����?��f��Y�%��y���(��\����qX������G��Dk��q��/f�������]�f|y����f�
���t:rS��)4���x��|[��qY{�c}��PFkoa1��L%3�"L�0\���@!}�ed�����J;�������P,��J�.��@1y�'@�Z�*�kI!@@��d�����a���AA���KI�M���,�����9�FR�<#�<F]�M/���)P5�f��������l����96z�����S�v�]dSo-�k���z%.���G�i��t��������7;�<�,�=XI37���6�F�[Q�������4�`K�`k�]����%M����	��������������H�aC��6Y��#1� \,2|t6�'�/���x��CS���=S�]ltq1Oy�c,I38��y���������A�fs�G*u�`�N9�Q�c4 ������4^]�W����E�\~8����
�nd��^���w��������z��g
)��_y���1-���N�W�H��n���������B����d!cb�
E+�T��^���gC��/PE@2���i�N�z��������=;x�H�F��M�[�3�/�Z�A;kk����w��aiq��<�=�����>En2VO��y�oR��|v���r�
�IJ����r�X$t�(���%�>�C�LS��t��5#69Zn��0Iw���1������T����.D@�G�t�
R�������XR�5kr.��������?�6W���D������_
�6C��"�I�8@x�rW��N�%��;��XOb�A]U�#��;�(�`�i����/2����d8����G�t���2��������E��9Z�����h3�U��k`u����ZP��DJ|���5<�V��l�
R��l�*�W!��/�nJ����8c��5���Ck��_X�E�?�@����b���D	K����8��~&Z?�n�fi����
|�fL.��$��M���d�8�U����2R%��.�,��H��#�&���x�p@e��1����j�����5�D8�:��T��'S	;�.���"����7;�7{���p~i/����
�t=�z�LU�+�B2t��s�0,�s�)��rC���j\`�c'{p%����q��|��j��8�a:d����c�}z���g��K;�$q���������
����������~khV��9�k��5���U~�}����k�'Oj$L#�[jP"E
����V�`����y��S�*�p]������
���C/K)��Jz
�!�W��q!\a>��.T}}ZGwc����~�N�?z��N��������
�L0����`���g�������4�����sI�n�<�~�c�V�X��vc#�2��Ji*�O��u��7.�C��yt�[��u{c���k����k8����_�}�W��1���xI��&�#�"���7+{��B)[
�T���<n������V�O���r$ 4�"v�,��m�����J�`�h�7�]H�
��|���e��jU���,t���j["\����Uq��D��+���)yX�H������������`CO|����l�H�����GY�@:A�=[�����P�T�w�j����
Df������(M�c��&z�(6E������^pGk�)����%���Kf[qS�/���4����h�E�&O����w[��h#&��Z$��;3]^��#�i�X����Q`J/!������Y��Z�����K��j���S������\_R���Y��5����f��l!�;f������j��kN
_��h3����:w/?��6+���7��
@\����>���1�A�r_�\4�!J��t=�P��H'���2���.0�}�������7����8j��r�����.�:�����6y�0cx&L��K"��0�SOF�(�x$#lX���#K�h���)�A�4�|,���h������*aK���SE�:�G���S��Lq|wONFW��{��Q��<�u��K�c�����F�g�u���Jk'��,=��E�F����
B�	LM:F�\���W���z��>j�f�g<���.�d����� /��a����
�YN��Q�2#����g�S��t��,:��n!�'��5H���I�L�z�j�7|��lD�3���#5Av��i��	W@���=��M� �����l�e����
VR)2��]�����������6��JV�C5�{a�����;d����-�\L/
srl�����b�-�������"��G���}?���'-��5��������o�X����(�������{���7E��&����������?�w.|5E�,�+FBb��8�I��~�w��o�1�$_���V���i�]�{�j``i��VJ�h��>g��@���Oo�:�����&K����}KGM}?=o^�Wp|���|w�O�qS�n�����Z0�n*��#�z�����F�8k�6������R�����	�po����} �*{�R��"�t�������5�3���+"����W#��=������E��W1e�=H���C���6Z�pP�L'����(a���c�(�>7��eH|:�{�T��t��#����yr�x|�"z����E����X0�JL���I7_��S.��o�'FJuM���_��i([�e�������zdr����)J�(,`���n2`+
��iDyA�6�X)}�����ER��<�>	|�+z�Z�s��k���A��W�������f���^o[�4kt2)��a��r�1�F���j
�^l�~
��^�H���V��^4��������@1�F��
j@.�p,�Vq�������O�2�_�S��
��W�Q������Vl��@�<k���.[�N�
}�����\�e��'I�+t������tO%E��vT$�\���e3��b�l$��r��z����4cAO%aO8�
u��h'c[�;�K���QE:�h�C�v�nZ9��x��1[/x���������Q�����I��`�>��)�A�����~f'�$<������H|�WU.�D<PG7��/�u����L��4�7� ;���T��N�"j����,��X�\_����.����Jy�
�������C*��*���o�Ehc�HL�4�����j�j� ]:yd7Y�bG�����{�];��q-9�[���u3��hEK��}��s
�?�f)���n���MN���yC~�������(���-���W��FW��gfBj�Q�����Y��*H5�Gx�
��Yjh7��V(?�m�d�y������&5�p|6`lf�~�8�%K�E��}�
u�����F�+d5�Hn�9V�rd��Ha�+�Ze)!�n0|�7aY�:>op��u����iPf���o$<����!<{���'���+��?�<z��X�B3����_���k�,[e0#�v�{0�U��3��O� !��8�^��H�(2��8xp@��QC�,�;�az c���� �����oY`��M���`6�"r�#�)�DPg�W���m`~�9�
lCr�������x4'c4C�7����-n�m��E����u�������&����
����$yRR6W��D�=������l�"�E��������ksYF9��G�M���xI� #&������^��H��f)�Ii{x�"W�R�[��E%;��g�c��O�k,p6���p0c�>XHK���	w_���YHb%�Y+I$#/�y�`����#vb��c��Dg���B��3I[�ma,�fc����qq�QD�dS'���oJn�p�	2�{��[YR!^�m��\������_���]
p�+��
A]c�t�'C�]��.p�����$�k����tA���'�5��~d��1I{���$��V��Z6�����]]n�^D���E�X:ZA6��V���u�|P��h�a;��F+�wZ"^Ks}:���<5�����(l�0w�K!_�y�3]5�Zi@a�8 :06/�7�+����TE��y����N_q���|_����<����/�~L5|�)��1#N ��A�_I�q�%J�+T�������?>]I�QM�i6�(L��3$���7_Rq99���Y�������b���[��\�A�g�CG��	�2�gE��h�*���.����@�����Z�<�C�V�yp3�v��7n��9�E�k�$x���W��Uan���B���z�t����<�_���)�
0004-Make-libpqwalreceiver-reentrant.patch.gzapplication/gzip; name=0004-Make-libpqwalreceiver-reentrant.patch.gzDownload
0005-Add-logical-replication-workers.patch.gzapplication/gzip; name=0005-Add-logical-replication-workers.patch.gzDownload
0006-Logical-replication-support-for-initial-data-copy.patch.gzapplication/gzip; name=0006-Logical-replication-support-for-initial-data-copy.patch.gzDownload
0001-Add-PUBLICATION-catalogs-and-DDL.patch.gzapplication/gzip; name=0001-Add-PUBLICATION-catalogs-and-DDL.patch.gzDownload
0002-Add-SUBSCRIPTION-catalog-and-DDL.patch.gzapplication/gzip; name=0002-Add-SUBSCRIPTION-catalog-and-DDL.patch.gzDownload
#21Petr Jelinek
petr@2ndquadrant.com
In reply to: Petr Jelinek (#20)
6 attachment(s)
Re: Logical Replication WIP

Hi,

I found few bugs and missing docs and fixed those, here is updated
version of the patch.

No changes in terms of features.

On 20/08/16 19:24, Petr Jelinek wrote:

Hi all,

attaching updated version of the patch. Still very much WIP but it's
slowly getting there.

Changes since last time:
- Mostly rewrote publication handling in pgoutput which brings a)
ability to add FOR ALL TABLES publications, b) performs better (no need
to syscache lookup for every change like before), c) does correct
invalidation of publications on DDL
- added FOR TABLE and FOR ALL TABLES clause to both CREATE PUBLICATION
and ALTER PUBLICATION so that one can create publication directly with
table list, the FOR TABLE in ALTER PUBLICATION behaves like SET
operation (removes existing, adds new ones)
- fixed several issues with initial table synchronization (most of which
have been reported here)
- added pg_stat_subscription monitoring view
- updated docs to reflect all the changes, also removed the stuff that's
only planned from the docs (there is copy of the planned stuff docs in
the neighboring thread so no need to keep it in the patch)
- added documentation improvements suggested by Steve Singer and removed
the capitalization in the main doc
- added pg_dump support
- improved psql support (\drp+ shows list of tables)
- added flags to COMMIT message in the protocol so that we can add 2PC
support in the future
- fixed DROP SUBSCRIPTION issues and added tests for it

I decided to not deal with ACLs so far, assuming superuser/replication
role for now. We can always make it less restrictive later by adding the
grantable privileges.

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

Attachments:

0001-Add-PUBLICATION-catalogs-and-DDL.patch.gzapplication/gzip; name=0001-Add-PUBLICATION-catalogs-and-DDL.patch.gzDownload
����W0001-Add-PUBLICATION-catalogs-and-DDL.patch�=kW�H���W��������
!�p��!��t��I���R�V#K�$C�N����R�T�
!���e��-��u_u_%o�`Nhc�v��F���^c���jX���4������th��O�>��4���8d�'�F�����k�D���>�%G����	�����O���3+���������a�|�C���f�p�M*
��9\��vrH�_���~O���r�8���7��'7�W�����1�|���]ln�j�M�v=�������1���~>�f�G*�����ny�����3��G�`	�F�b��@��*<���T���#
2*��^G���(�fF��2�?z5�P�k���"���o��H���Y��d�.���94����v��le`3
�q"�$J��IV�s0���V$�R+��w�OFI�N�4Rgp%`�&������t:�T���84���d��q^^��=�g���W�p�\hE1������>��e�V�YDC�r����Qd���Mp�!���j2�$ ��"q=7)XT�l��,��p-{F��C�>����dp�_�#g1��m%B�G`�w	��1�i@0+�w��	�7��(1C�g�K��Fk$g
��>S��D.�2�	E80Z;r��$�l�x4h�p�Z:$6�^�B2�f,��5�3�hb���:����CM>pV��U��'��Y��J�@���i`�f������zU�G
p��)�����2a�����+�o�e�����*����*������81N��\����,N4���t����0�p�,�qsM1�����u�	�E�*��)�[>���l�JHM��r��bpz�`5�-���ip�K{�����3��b2���T��AoWv���
��]��l�H')i6�v{��k=���k=`c�e]/SX	�2`�bX�����j�R���t��B�����6�����N&�V��	��KJ��������6m�Fgww��LZ{AV5Kqo��_���kRk�u��$��[�_o�5yT����M"��o�R������[�������k!�?����Q�D;��9=������:�#&�����R�b2	""�(���
*��S9���[�Z�#8���$Q������a���D�>�@:���`������Hk��a �CH�� �j���D��o���f��Lk�����hZr�>w�� �G��xt}K������"�?'�
|�|"rF��B������z�SN�q�c��Q�&��SA��O,r�W��;�ADA�r��VLw9)bf.&k���H����[��X �F�"
���V[H	.���/YZ���d������	� �UKp�r�p�r��%}��q�<,#WHi�R�9\G��_(!���>�
+�5����.L�����7b%	)���$�E��1%�@g��C���j�	�N���gZ�M��'�yB��qA������o�wt�h�j�rYh@�^��qxO��|B``��r��� ��X�VD� �����4�Yb�
�/�����3�����M�/���E�?����E�k3f��0�,B���Y���`yv�V�5$:O���e����L�@�9����mrO�E����1fw�j�H�2�,0~q�.2�"R?b�?s��\�3��:��.�?��L��RDR�_���%�5<�jI�����O��@Pk���N��8MQMm�.ZSa%�(Y�(�'�W}�n��������n����K�E��@q���@���
|�hl��`B1��0��`�Tt������H3�v7�����7�7?���b�2�N�����M���}� dF��@��!�Zs��<��].��`m�{W:h��I�JPY��\{�8� P{�:t���K��`C{F��,f�$ ���Bw��U��j5�X�SK�������������-���T��\�h>F�t��i�XgT5���:W�u���s��{Vmc�;����/W��!C.p�!��L_�:X�����sp�4�#�������K����[�:��t���/�O���M��{fj'\����0d�wM����C����[h����6�
0�*>
��5j�V�����r�����.p+f1�������j,'8����a\����?P� b�s����`������U"�Y$��)T1H��&��I��)��>��@�qx�b~�=�g_$�����=Fi��$(kJ�DBQ21��cDfD	1�q��+Km���[r\��� ��|/��Mt��.[w�j,�e�
�$$�Z�?�A�1�x$���A8>�,�2�"�������;��[�����9&���<���|X��f
|ww`?03gT���3HR����|�G��8�4�'��5��?�����*����}0�X,��`~	yq�����7�U�,M�VE�x>rN..8I���yrv�Wb1#=�$�����NVa���@��Pu6��~.)A����Dt�!�(��bN�Wh}-��4q#H�����|�0��H�]�,*���3����`��� ��w������@����'G����d�6&��i��u��I���N�f$J�#��0��F$���,b�$q�i�q������-b���zq�p��dmO��R�b3Ln��n�����G��d��s��|��P���	o
�vB�m�02�%�{����`T�"�@div�5p5�q�f��@��Y�R�#s3
1��!�4�����#^n iw9f&�'{�d�?%��M�>Y$��s�����R��I���0�8�rB�pb�$(���sn�<�Y��t>�)s��3I����Ob�M�P�,�r����-`�G�W{~����,�����<Xu�.���l�U9y�CE��L�p�����B2�,�s�Qf���$
����6�
������������A&1���h��%�����KoMn<��M1��i1����T~W���b`)�O�'
��V��H�����6���K��\���2�]�����t�Rk��Lmn��P�\n=�|R�����
?�r��F3��0se��"w���a[�	?�Md=vb�w:�5Z��K����<�R\T��;�����!y/�S����cp��[Y���_V��g0T��>�#K����9��\m��������f?�*g������F�|l|�Ug����1A�>�P3�����<g�W��`�$��I��Ae0+SX�Y���LX��)$*�JA�P����V|�:d�e4���	��u�R�I=��O�:&H�,l^�����gGu��U~\�&��u��������f�Rj�_|����j�|*�'���U9Gu1|,�L��ge��(s�Ps�d�u�V�i�^�3�O�I_�Q����=z�^��l"�S��,���axJ����z�c�������n�	�_�][B��_����b�%=c�H��;E<$�F�?_���<�,�f�9�@~�w��1�fc�����K�Y�m���!p}�e[G}�K��X�)�uX�����N���\��[~�����!&4����J��g��FT��	�����)CdxAb�@@6u���2V���w�E���2��
�����0;b�pWd���#����
���n����|�C�f��]��	
��������2uJ��;+����vs�7�E���$s_�J��*6���j�^���czL���4�����oe�WsRc�S:pIM���G���\��R�d
y�;����K�(�|�f��I���%�����?�k�j��|�Z�|=�Z����%�1��}�+��b��b�;�	��l���$�V�K���C����gV��q*h~c����Y�����-}7	q���E����i����z��zx�{T?&P��A]��|[*�W2��?����y�|������	�A@����C��j��Wv*��2>J�C��@�J���RL7�nD���1p{����%&=K�\�69?�O�!���n,>����k+���B:��q�����������!�&�6���������Jj}�E�[�*��H�\�4��7V\��uBn������Gl�b�3�Vui����Q�O1������:3�6�'s��A���
��[��j�F��.o�����<��
�j����6N��	��6-b�K5M��[��������u����[�������������\���VmP�!�^(���C��a&�@�w�Cr��|��<��~[����mn�������6�������]���|����T��b�~�!�jD��Y��M�����E[���$u%�\����4�A7�w��/U��P��������0���"�
��0t ~q��}�yOro��\_��1����P_�]�k����ao��%�
�*8"B.\��A�z��l����V�I����'-����%O������?�+������v�8)������o���$��(NT�Q�x
l�u�����:�F4vf��%}O=O�Bs�&w]��
fERy��y?�s�*#)�*��Y����#��Ko��o�iB����:�=�!,E����!���UZ���\y}5��n�����|4��+�������A�O�9��"�q���� ��/�d#��������'�����G�YYp#��#\��7�l��7�F�N��w���5��n<S�v�y�@="_t����lpe�p����PD���U9��n;�7�W�m��:D�pm��k�����;��
I�h��-��-��=v���	�y�����;�*!�\z?
l�[1�U��� ����"z�B-��_����d���:m�S��0���l��RkzH�QH�]�g�}�(=o���?.�tD�r���1���fg�'f"$�
���i �^��G���2h�����`���8�Q�Nuc���\�^������/��+���p<���O]�S��ft38�����C�w���
��d�?�����6u	zH�O�������F����F�O�Qv38����c������=`�n���&�FH��L�"�-D��}�O�'/��q�7!�����`�+a�x�<�;�|\���>wv^�qcH�oq��������-��!��8��H/�Y�pV��DIq��PM�k!':�T���/$7U�`����)���A��B�Oi�����X������Mr�"�, ��,S���>��r��U���
�U��5��J����(������%�F�7�����I{�X�x��a~$���,"�/�����oU��v#b������������R�>Jr�}��DPz���E0]���0�?��A������4O������I���
n{��!2s�����&�]������_�� w���Uv��b���������}v���%�{��
��9����Y�q������:?�_�<y�g�$a�m�c8��s�������<�j��~1��wd�����s���}�l��U2�`��[!��dcc����*�z�C��FxX�������P���P�#+U`D$���')�" �LJ��_��2D�8.�����f�@�� �������~��a�|,3[�"�bh]L��
n��Fn9A�G�^��M�*�������������_~��F����l/�z��E���v����R�&*u(/�ICqc�{����,|�bv�F�k�R��'R*lR:YM=O���	����
�������}-o/�>9=������|u������2�	���0��/�5��3#��~H��.�
=<�����o<��V����K�vR^JYX"�,�^NI~���l����aE����w��o�%�J4�l��j�x���>r����j����SNyB?%#?��-�'�����>�m��0���t���u(�g����@�Le&�U4����`p5����Q;�����#�qH���h�
����&��d]�xq�����l�'x���	@h�'������f��"�xY�	�����(p������c���U���H	��������V�\	�Eh����SM�_�Q���~��3�X���	���{�s�����Qj�����n�U�/��Kr�U�$����_lV������6{f���3�����7+��>�^_�.�G��
��:�bfT�=�"D�B��/�n����!M��*�D���Z)$����X�R*���G,���n�z����uK�q�����i�k�(��w�p@Nl�.�E`���������vTS&��Oh���b�=����B���s!+`j��,o�����4�l���)j�Y����!����r<9I6_C71�	
v����u����jh�r2�g�%��^�V���b������m����i��9��,�H,W`f�Z�-�FKK:�/+��;;�r��4��UG���N�E��b�D���@9������$)��y���9�P�h��MY��|����e
�������@��t���N��V��������uoI�_������r�X��0��'�V{��\���vk@�Y�N���>*{�2������[���k~�-��Ze+b{U�+ny�8���Mk��h���fg��c���&<��]H��
����n���Q;�#^@�����8�x}~�Tb�w�O���N��:���%��9�|��=i��_K�` ���s~�l�%Jd��_J��y/�
���4�]-��x��_��m�~c�F��<}�W�G+,���LX�O$*��^�\������� �_\�_]�5/��WY>h-$q��H�Q��hf4Jy��a���0j�����G�6�x ��LS��f�>x����c���6�j�=[Hn��m�[���
��������%`����,/�w���Q�]+DW/{d^��`��~@��J�[��d�����EW!2��C�iHB��^M���*E%"G�Q"#��<<:�q\~8\��@v
RT�%[
�	�#�������m��������K����5��^%6��6�E�b6���VL���i��C��c����*� �*��r�DN��6n;���������F��)ScO(E�G!�2F���u8�J��0����9�M��G�e.�?�G����'��T�V��4�kGxA��{�����
p'����C
��7��:�La]���Mi��(e��AH����78#�eU�{��9<<��[����]���`�\������.k�[.�Zs��xg�-�%~Y��h�&$+����\�����#��B��zy.9�p5�4�$�o���w��|�e��g���g+�����^cT����G����5f�&<�2$#����,�$����
Zs�"��ZDW�2R���>g���"����F�s4]%���C~s�PE����S_&�(X��m��q(n�N�3H�Lf�M�����>�����5������-�	��&�{�MYS����[xRbs�X7L��|�"���s1�w/n��1b��.�^����\N1v.a�O�"].�2A��
JB����O,����F��s�5�?��e�����-�B�9d�{M�|���a�!%c��S���~mtt<<�V��`\��,��5�-Jvv
2�ch������,FP���m�{
I��Uj����`�����N�M�dG>1����?���f�D��D�^?fEV�P�i=��.IJ��:�g�r��6�K��z)EA�~����L��h��5�����������D'���g���<n�z��<\K+��[�t�H*�|e
�n4����5��p�n?������������ju���Hi"F�p2_M�������[	-�KQm�F��_�k��4S���|����hC���/�-��i:��^��h9�4������v������ ���2��}zI�������:����skI�o�1��%~
F��p��a�d�5zDG)e��yo�s�NNf��b�L�5����{0�����KRl�+�s�w���~�F+Ic=MJ��3����e+[�(Sf�UfN�E/�hA�~���R���`�\;Yz3\M�����fz){ ������jOBMm��y3��6�[���s��V/�v�4�xJ�>�*]��(O�
�,y�t&k���4J��s2�
�a�@����RK��*
�d<�������Z4�\��������t�6��{��a['�8��IB5�g�rvHwc���!FUIR�2�@�5�m������l���^��4��;ay����1�q���_���BL�.z��:g�������QE�����.���//K�+�r�!~�,�H�dE�j��W*��-x���8���D"����X�C���[S o���P1�e��I4��A�t��|S�;����"��s��3����E���!�;R�g!+�������M�Q��	3���
���tTQo�Z]��=�n�k���l�)�����b�	�dU%<���,%G>/c�'p����Imm���5<7$��9��5�J?{����_6�1�R�lJ+�G�l,��0x���,��9s}p�4, ��{���r�;|#�/
f�$���xBB�(�X������r��w�4��b�o\,�P�3,��$���O�����k��p,�@p�'���f�eK�\Q�D�},.��.���Ys����x��yb�	���q�d|�0$[ss�D�1>��$����|_*#��p,%�n��?A!��J�`[Ek�e�����G��������[�o�@l=��p�@�����~B��o�
�a9X0�KM�N�8��.�$ T���g�w0��,]���k���cdG����S�B�W��
�g�zyM���K%B#R7U�8CT'��3" {?����n�hY&���ZE$�4�c~B
p!`���r�P��5~W����Df|-�j��E�'�tZG�������F���T��%��0�V����eQ��9�,:0�������(���u�HRNYt�rT����r[u��`�r96�HkV�E�T�[���4��D@�7@�7M@�����P'��Y��QNd"�q�Y$Ru�	E]`o�R�pE(��	U{�SF_F�Z%h��(�q�c���g�i*~{|�f�Zs*�Z9������M��P����������;����)TL$����m����6���NM����t�����Vq�@aT�����,�b���b��=-b�M�j}5[��R|�����'�%t����<��I�/r"0

����Q
	�)��>�R<��z��1��y���$�#a���zaB�N��
��
d+�rGQ�jbnQL��	$k�A
�C;!�������
=e�����x�Pd�����L���������Kx�L��K�Q0�{t9�%�$��n��m��z��4���<M
v�V���&~v�E�%1�Y�E�.�\������q��Cg6Y����.S0�!Y&���
�h���kwu�������i��U�2�Hz1����5R3�q��x�P.�at%�D>,�k��f��+�.A�'`���"F��H.�-������r�l���0��AQX�E����������e:g�)��y�]K��S�R0�x�eAK(�f����l��4���@���>�@o����gPA0P��m��-�i|�����0|��D��d�)�,h���Z��@o�T�Q�|K��A?*���^��(�(N"��SS6�L�'���y�i
t������mX�y�/���SV���J�����B��hJ%��V%�.Ty���j��oo8��J����R�n�2h4)!Z8y�b"����h�IDh����@��.�Q���"��O��3�����v�����A���j�X��*���X�V�$�Cf������mn��Uh�i�kF�
-����6�s�F��j��fo�p2�h&+%��q���� ��+������C�'��'f���V2G��
s�>VP��|�����L���x:�k*������5�����`��x[(U��]A`�5c�v����S#pq.�Y�{b��t�v�"�,H��5i��R6��]���s����W���������h�Q����m��[���S]��-'w��h6���O0�6��TS���K�h��l���.��BXPl�_�L=O���'�h�����~!��W9��ur�T,a[hh75@k��%v;2����js�4P��I�$c9��7JhS��������i��*e3���tD�{����"K2�V����2��=8���������g1Vc���$)p8�1�q����T��ciZCM���gI �Z��ZK�uS/�6&'q���PM��Q�H/��Y���$�X�?\��bT�^F*��;%�2?Q����z r��r���p{��M-�0����B��a���FB*Y#X�JZz6��FAi+�}�Hn9�8]s+ gH�N�)~�<���#H:��
�)��U���<�0����G��kf������R�	����r�=M�N�9L6Zh�\��"�F�O�NT����\sc�M��4�Z��h�|�Q`��U���[1%���B
��`�ty�<
:��2�]Jy�f"y,��R���N�D����~���_QH�n	L3b.��Vt;-T}x��]����-�'�f�?��G)��r/L�8�� �)�ip�����g�v�J�\��]�v��d���P���4.,�c
�������������n����O�x��QF�&O$�����p��N�qpj��e��t�%s6�q1�!�JNoH�!3�
����TA���%����7����+a����S~�J
�I#��?��l�
xi�<W��e��R,D�.��)nVX��i�'�e����g���($���8��D������8yS�'/�=����h��B+���1 ������ H����T�����Z���A��b�>iSw�'PH�W����m��j����D��j
�/�uO��X��1��<��z�����G�RC������E��2���>+����y:���'�y�\�:����1�������loY��..�J����'`�1_����(������'��� ����EIe$&-��Z9�e���FI$���-��@pAm��T���p�*
���=�VEpO�v���R�3�������z�<��w�S�p^nc\&��$VS�DI?[�m���pBb:4�b��x��������B[�X�
l�"T�����_e+��k��M��V��NO 8avrZx%�8IJ�����>l���������m�#>�204\c#>Cn��|� /�ds}��9L��dV��(�+�J%U����u� ���)b-���;���}���,z�����1������u��dS���w�����3.��1)9E��4�OG�~42>����}*D��=�O�l�07�xPl�3*����1������4�o�+F�f]T\PI�H:?v��@DPX$e��B��?�����P�&�K ��I������`87�9����-9��2�9f��0��jm������:�-d
d�`l/�����me+bh@���y*�He;
#1��C7�)<�:�9�+v�'�[�/�&���5�D����w��u���~���u�O/:����&L�ox{M�������Q�'��f�_fm���^/��x���x�|/��Z�n��e���U�j���O]RO!C��n���"�DF5� Ev-`��.%�@����cJ��w|��j��;������DY��1��R��-��{��	ox����d�ThF����M����A��e�Q�6l,����|��9���wz�@p������E���Zm�����dk�&�0�U�1����>wNX���������C<�������K)%q6��<H�����
�w_]4{�u��
�b��k}p�E17��M�m.���t���\W���#!��z���b��E�����S��������������A�YwG*a%����;�9��5�%��lo�Ihw����?��Cw��M���B���r.��H9W���z� C��"�����H���Z���8������~�X�3������]L��������I��M��FM+@{*�2�TI������� ��v!�<p/
$�v5�O0�a���	�`q�jp�
�8.jx,y�k�j�`��R���&���N��p?}]Jp��Y<���D��6wQ�#
<��Dz�������J}W���J}�s��c���*�E��H�`�!���j(&wI�F�P�1�?���r:��@
<�l��0by�W0*�>�,����(�u�
�
�����?�T��|y;�g H�%�������b�Fz��
q%�U;fP�� �A���R���d��q�^o����.��d���I��w�n1�����|y)��L |���
������&H�Wr
���;~@I%��w�8�2��!�w��<;k�	�����}b���f�//B�|�����Y��5��B������B�}�na	����g������>[���������	�I6����^�`8�*vO{�W��T^��Da����^�9��!��?<�U+-��I�3��]U(R?N�����$��������z�����^,{����n�Y�N'k�����G�[��s���y�"��Lo��x��9<���XJ�����	���?�$��f������.�������7���\PaT��s����l��rp!��f���hu0���4O�~�6���������=�����J��><�������������g<����TD��.�k����R���o�S�z�9�<����yz�k�����������&�7���eW P��%}��?���j���a�)�q4?�7����
�'e�z�h�������^I��L����2�B�Kla�-6e�/v��j����D�_���"�h�u'i�"t�lo�~����x���z��~%X�������>���7��w��E��e�u[5��?�,(����Kd�7�6���<g_������..���#��i���L�,K�0`�-�B5{��U�����U�(�"ng�����������>�������,_������z���X��E�"�hlo!
C��M�%�x��`���N�P�c�&"P��n����\j����Kj:~&�h�L� ��4��E����4��
i�e�e7d������:dF8X��Q5L��s��r�FE�\�4^rc�a�������y�������:��2�{��nS�F���9�����s�������_4B����b����3�[�P�)����@��\(��O]{�x���*��Ct^Y��"�6�o�Z|����lP��iSM���������V��?$�E�9����H�b�9j�"
W�_���W��	&��r�ypQ�h�|��t`J��V�Q��&$V�����WM,�GM�.���sO�8��[��	�H������Z�L��6�R�+�o_n^��uM���������A���,���B�O�[M#��F���f��������u>�E//�wQ8`�]	�Rq�����fQ�#���,���������n���\f�i|%�N����+�:�zx8
j���p7:���u`k�������\�;*#���K�:)��V��\1 t��c�e����g���x��x������d;���,�����Py�

G�~#�X�DI\A�x1��|kv�J���9R�}J��
+%PC�A�G���J��s0���������{�����6�fd=j�!Y�|�ND0��)y�����>r�H�����s3�C�"���>)���/d��u#����?RP��7z�%���|1	�6D�k>�;�P@�\w�8��F�)R4��<Y�A�eN�C��|j�c���f~�/9���Y�P�\��T�m����p��_�Y�Pe�%o���f�fuy��Tu��v~�==m�p�@�gx_�����y^�E���'���M�����W����|-�2��`q��t�\+P'�J�!7����i|�t����.)v�f�M��!�I'3Q~�]8/�P������
���LL��n3����= M���-�C�6�f`���{��4����-��,��c�FNQ��
 *[y�����d�,o�#_`Un[��>���X�c�;)R���rt�	���+�~^Y����+`�p.��`������u�
N_�A����s�I����f�Y���8>?/�"��W�����%:�/�a�+�$�v��6n�f�5�QW0�>v�ntIg�M#0p��	��a�. ��L~����	������&@2��z���
�mY�	Q�|sKt|����\YQu>s.���������S&b^:^�4��9;�����)Y���k�m�����C��C��V��-O����������)��������9��K�cx���j��!>�8`�(��nM8B ^����D��$�6f��y��N�I4������?��L��8���fs��I�I�E�K-���l�������# ��M,�B�N�-�Y0K(tn0E��L2�s���Z��i(�1D[	�@����A��(�	D+�8&�`*�~�:p��
b�Q3����u�������	�H%��e-��y4�F��D+J�?
���BSUf`���f���`���%hn��Q�\SD����g�&�r�k�@�}���������$��2
���/'F^o_|H���g������K�x4AB��F��I�{H��&
��K���a��o�G57����z��a^�TA��Q�M����bcR<{y)-�s���H#���8;V<�
OozS�!8�������:Q�������T�f2�&��'I�������z�$r��CE��sv7'7�R��u[����Ge&M��D��-��*�Si�R���E���w��.�����T�f���)������Oq���-�������n���`���0���X���"'C�����k���br+��#�a��L��������z�������!����W�=	2y<��Lz�\E�V;O2G$�R�	�8��������|fll�m��Q�5y���j810~^P�Y����X����:�!��&�w����	^��I�\��r�������:^�%�n��~m�������!���p�g��N�ZA1+�
��0T[L�?������xK�5��U��x4
�*<��P[�������d?C�������S$�b�a����rR"Pw��
�Jn"2��D^���j���%:7�x\����E������7z��u	��E��8Xg��s��\�M~~\��i�;�[�"X��SR������� ��%�Y���R?����l�	���������eu�����4��hy�����Ld��+w<����3���</���������m��j����}����y����4
�Dl�����Tyz����H�x�����B��;�����	���k�@F�D(��x�K�<�-��e���k
���Q�8��C�<
pL�x�3t��Vv�{�����~C�]Wr^[d�(��{��Y���[�ly�<d]�h;�-��-Vp�Z(.����m+st�#
����3'����6ON����U��e����zE{S* ��NB�v�vc�y�3w�$;�4��}����q��W|����ff���(�n/wSw6�������	t��]A�lg!f���S�[�o��I*�W�,�:#'��M#�5|�=�_���Zg�^�F��L�������\R3�V��~B�,c�e����d��E���=�K��1�v�?GG{�j��?���h9�d	������4����%�R[�x>Fp�2���]��#*��=TNgdZ�_ ��3J\� ^v��~ I�����X�D^e#��E
!~�����	b�>�U?��~��7��`���dS��Ew���������4�������e���q<�#�C�22���CA@��r'"��q���g��>��N��kw��Q�{���������%t���.��@�~c�����^�������h��`�?3w^���8r2���n���YcF�+e�>��%><�GQp�������F�C~��rW���*�F�����������jY��x,W��_,x����'���'���c�����r��~T��r�$�E37�MW$'����0�����
�i{2u4��!��{�l��\O��>8��i��k-������4�,W��k��?���)��������`�~T������P�a���82�������4&6$�&���1(��<������$F5	�%U+q�q��������V����-����lp�n�+nf5,�n���$n��-�E���Y��i;���r��(��T��Ir�8%�4�� i,!��8�8�w[X	��JX.���o����A�j���q������<��4 ����7t:�!���;u�+��HVc(H����5j��uTS�>�HUBe�4!�4����9?��wYz���>q����yJ���e���j��H����x}'Q��_/�?�������DX����OB�
a��W2Qf�*�+�AC���$��&]g�n��x��>�<��W��T�w���w��w���Ee}|���y��_�������}% 
�H���o���dm�����L����5��x�u(�����.__�����yapOe���T
��cK�=�*#	���^^G��x�Yl�
���V);!hN�|���J���HX�>
�Q9��Q�1�gHLBy���-�O)��\{�����Q��\zJ��F�#�[�_:�!A{nq��)�N?��|����r^
nH�K;�Xd�����������X{.&�8�����������a<��*�G	�W���"p�
�;a5�T�`�:���+w��hb����o�v~]3iM	��N',?t��w�@l�r���.,����
M8c�#�!]~�|+M�6�a�P�\�H������4��[��MI�F?�Mka
��C
h�o�}���xJ	�mML���S����c	;�U���a<��Z��s��"�}��
F�P��>��271��{o���w%4X�J�������$B�o�Q-��av|��s[�[�'�5��7������:]�D�_0iQ];��rZTks{��r	�4�v��0������y�[;g<p&�2rc����s��c]����������-���������-5e��7��/e���C��X�w�d����V�oc,��E�9���T�,�����I���F��V
��(b��2��Z�h��an�h-��$H�
M��O��(���H��JS>�e����������G0Y�N��,
/]@�x�(��j1'?�I2Up�����^�W?�m�[$��M��nb-�|t� ^Z)��D��s��fO�a�6�vH�Q`.
M����0�1E�����D�����y��%���8���U`H��x$���
�O���t��g���a���>���0���cu���6Y���[/��t�����^6P1k`��5�������^��|}�zd��<o�\�����{z��2�������Z�������
T�kG@b����.��:�5�w�6)�=���T�����f���X��
f���FB�?��9e�&D�������
�0����*^�8�)����5��-��f��|��b^G��=�
e+�;��Q�����6#�n�e����3�xV>�Kdl���x��K��2���\���9�5��,�u1�P,���<&Y�����e9�P������4�B�FT���w����7��"x�o8�L+�y8��$-E�+�1���h�����i�E�8���j<��Yi�9��|����9����Y8�M���
�_�9��h�.�f��(�����BN5X�V���$`!-��?H��~8'�m��S���?T�5�����x��d�
��NN���-u���y�4�s�L�e�.�J�m���cKi_Yd�J�T��TK����]�
�m�v����szm��7����f��g�1������E��3 ��1�x,�*���1�:�IQ��7��!aF���m��[�]��A���VCk��e�sz��=�^I�����G��j�wJk6/w�b��&N�O��	���<7�fC��1� #���
y�l����4��u�b`��e\#y��8&�����\��Bc8�q�z��?�w�(\o0P��d8�����	H@Dr+�\��U�����^����_*����'%��p������\�	����A@>�<$��sy[H��a'D��^K�x2�V;�
c�C�<��}���?�4�5`���\#N�nxH[�0#���N[%�������+!����^J�/��q�U������z�
r���?��ynP��l
+8a���!'����>�.�I���&�R�����v��P���a�q�W���
A����
�0�D~�a;���h��"� c`8�dI
���1�qe_���32?��KK[5�����>�	��P�ZD�2������I2/��L���*d��l��y��E�rk���MV�..UU�b�`���/�k�Y��.N(7{	{X��x�\g����G���QX��FG�q����ob��
�E���>]�	3�?��)�XL��dy����^*��;�n�F�.g]!T��oqn(����;���B,B���x�p�d;��{?`�a�Aa}����q��h��}��5��-�nc����X��������_�]w����\4^��H\i�������Q��AN�d ��d9,�K�"MF���!�!�� l[��-�V��_���,�\m��a�
E�U��)����l��O�&3�8x���X��@v�dg�@R���p��>��,K���.��O�����G�Y�]�Ok��@������E�����Hb��a�F�������L1b���Q?�[��V|���<��e�~E��<%y�����uZ)nB�S���@����XD|����=�X HB���� ���b�_x�O�	~��2�n�z��B2����9�e���f~�6�J�� ��������&e��7��t�6�3(����h�������9%�1����	��m�gb�_"���M�%��b*y��F2�@�������1$��/.��a�)����0�4#JKP��G��]H2��Zn	c|��$�!��}��jf��nu����w
�E��0+���`$^���d��'sc�z��i7���F��s0�s���H�v���)���9������!lT�����mN����U�rd�W�^0�����j�5j��y<�]��[������R�|���8"
���gt2
��0���d���|�C]2���?�*"���+�����7�F�)�G�G�$S"Kh���<
Ahx�K�G�V0y?Y��Ua�~�F�
����2�K���T���D@u��fY�������^n���Qvc���4H�99}��HN��g���wk���.�����S�C�
�������/�+J���
$�	U`+����E�������������o�uE3��n�vv�w������Bq���I-��
�����h
T����I�"�4�'z(�~e�����g�iV{fl��s���TcY���Q�����d��p�p���p�zp��]3w)�~�����,B���L3������LC��r��}��e��f��k�y-��;6���+�
��q�g6u����fX��������Y`���jXqj����As
z��GdK�6dd��j�����7��zjy/b���	�w�@B�	B�F^|j�*^��n`�K�Jf�����n�K}�S��F t4��E/a+�8�"�-��?���V�X�C���LD7�����.����Q"������d�!h3V}������Hk���m��qO'91=�u�s�pi�w�@�����{7��$>�&��p�{�{��:����im���.�-�S��$��!@���!�����1\`v-���d5n��
�b�m��L�;vHi��]&��4X�|hAu�`'������jv��[�s^�s,�q/���;_��[��BL1g(K�����`�@XlI�MW7����p�F�����BB�%6Rx��[��4�RU�/����v�0Y�!����m/ ����a�>�_�����)g��{���Z�iaNJ
���c�CD��S��2�c�����2����H�u9�����u���yo)�@�_�r	�)����[��J�Qb]7Bw�m�����R"n�����pX��49a�����PmkYM��Go�5�ou��"�t��)}���4djkD\���2�f9Y�y��0��A������}�aj�w�������l��0�fW��x\��4��s�
�Cl�BG��^�&��K����B���>�l��x{�s��1ys>LT�K,4����q����3^'G�����������9��B
�H7�V�j�7@R&.Bp��Z.���s�M���r�J@)�N� ����#r_@P�I�c�5�a�C���t�>4���I �t��'2>������p�
g�� =A��v���6&�3��9����MJN{I����8� S����,��/�`;�8��u��Q�N�H;�Rf
��f�f�d���*?`���*���I5�G�pTpwIi�OhD�9�F2��D��j���6�dC{�����-Op�� ?�Ug���-`��	F� I�(O�`�L�jf^!��T���<B�S���c����e�t�_����)m�����ji1�?��(�Jt��;��d��[&�x�I�Q"��/����
��1B���Y�����"���;f_�6sw�D��PG��<T�Xr!-LZ/K"��!Er�����u��$�:�!K0D�F'����2�TxTXvdy�������d0����(J����\�(����A4O�N�y�GEd����j�P�kG���am���N�g�]''��z�Q�;�Q���(K��z�\������D�
�z�S�{(��sC����
�?��L�����e��j�Oz�o���{a]���<����*����h/����������=�z���7Z�N�%��MC�n�u\E���]!��5�� �����t��w�4�W�h$Z�1�����I�p���l��"u9�����4�H����F�X�����qp��Uz0�*��|m�e&�G������~�n��}y ��X�S`�I������:�!g��!�}k�������6��m����enh���3�H>|�����
�Y8������{��^T�FQ8���k�����{�:�Q������L��m��c	2t��a�����V��y���u���e��tp����
��/���*��W�U2��m�����{���A�,�  4��_$���/�o(�2�'[���N��5T�u��4�/��
��BZ�����b�LI����-<�(�L��j�`����*�����WH��X��;d��a����qL9svk�^P(cH#��G���U"s@{@�A����`)�H�zE/=�l�������/�L���B�0:��\��t���������<f���s�k
��e�3^�;<��/eU������X^�	M#L���m��Z����!�S��A���I��7�+���B�^���[�5*<X^��	���n��/{�'K�|��~���'�����<�F��1��e0�r�z��,g�%�fB��&=��R�}+������jp�1��|L����P�o���_����et��j��\���X0�2�C���$
OU��4O�&�����v<�=�W���8���jm#����R�H�����u�	8O���K�=9k����?.��}��-�A�� u-�5T-�\](���Hp�<���t����i�������3S>��J�S�
� .{���<��t����D��?d.
����;
��y������pWR�A�w��4k���{���9��9��:n}�-��9;;��j���iJ4�9���M����Z�fE�j�
$7�?�[�������ta��9�\>e�(P���� �'H��H���.���`OpS�
���x���55@����
��hv�h�������Xd,,=7�S�4���zkedF�v�	rw���1��gTx���RnBl�.#�	���v����x`, ,0�4+
��w��@F4����.�D`�|�~��J��F�{4
�{�1�K����Y��9i��  7oQzp�=�E����
��b�������p|t��I^.�������0Q�R�7P��y�$������>�-���������B�9%�"�-_�28���4�+��E����I�V?�`���E���L��q�hAR�x~���E��ohi�"1�H��0_�&�_b0�	I���M=�Bk�
�w/%$��`��h6|;�ELq!>d����,A�DTea�m /�l<(���G7�}��n�SH�5��rqj��/���y�K������u�^&�M��*�h> ��U��_L����X�N$�5y�"
�	��������=FY��e�}��\��Y����
��-��{g�������=�vZe���&p_�J*��Nx�0����4^�D(�F��d����d�$~��Q��:^I))XI�]����
D
!R!�����_G�|��f�fr���J �)����8D;z��Bv%eN��Z'���qN>�2D���*���<{��=���(^,�d������A�"��z&����R.@�2�)�W��`\���	���A����D_���O*�G/oH��:��f�t��~ZdM9Y������RccY�Ri�PQ	Z��^���pV��f�B�^�����R#"�h!z���`T�?����d��I��<���Y����j]�Z��@��+�oA���gk~{_�
�:�I�@�G�Xt�"�J:��a>�����K�2�����D%|���.�y~��J#g�)��%��{aXw�q���&C�{��I��3�{��d}���a�6\�:����Y'1�V���xj������<5�\��0��������������{1��Vl|�[1�/!��"^�[a$�,6���_���_��t��W���H=y���������!9_�%N*����)j��+_Ml�CR�&����eo�u�8��������e_�u��"6#�~s�	�����Y����"�2Jk,����2�����b9'�z�L����88�V��vG�q�� �kg����"k�5>�
�sY�2�z J*�=�h`�+��v�R�L�M~���8�E�|�XA7N�m�(���>�m��A\��mI!��}?��]J�KcP�${G��x\�������x��[M�m�U���8���S�A,?�]}��'���E����b\��E�U��{�Q�V=�C�f���,ml<�����������V�>����h�;}�?�g���j�w[�G���I�A���m��D$o's����k��F��LxW/,�t�@�aP�gB����Y��G6�o�"O��j)��V4nO���T`!����l�5Y��_h	�������r�#U����g���W��<���������L�YL��1}a���O`f
��w���j=Eh$��SO���x��2�s�B���V�.��6�)f+��.�������;���#%r�GT����N@�0��@ c��������,���}P�A��[����h�_������Z>Vv�gQ�[qY
�%���IR�����t{��Vj/�%)�^���Y��������v�����R��85�QJ�4����v�u"�����]I��Z���+��N��P��/6���x`_��{Yd|�t������i��QB�v��/�e��t��4�\!�+8l�����W~/��{�����h��"���P�j�n��@��8QBl�f�$��_�����V�����!,3���i�\��0_o8��)N'sA��9q�B��7��W��=;_�{���X�c��E�����X��
��t�||j�)��Ek�s��������"%������������!���W���L�A��'=��y��,��=�����*Z�1u?�IL��Y��0��>d�r���(F��������i�4�MF��;2H�}�8�������Z=�m�GCO����(,Sc�������z�y��&'P�6d��$ZHz_2�N���.����)b��y�U6�X�b=�B��}��y�mK�E��Z���l^bf����Kd]����y���2��%����<�~�cr�C�a����\�C�F��J��L��2�M+E�<����lp�����g
$.�Id��9$����Hc�,��t�m�����b��|aO�B��������7�����[�Je�v�~��x��w��=��o��1{���,\nt��Rp3��o��ia�����C�������8���r8�������-&7���mt[a?'yUl?��>��o!!q���!�}1!����h=V����0Y���u{�g��F9
f��g�]���e�s���Ab��wZ�������]A��I�8I@i�����U���OAX�>r�^v�/e��00i� ������gsW�.�bU�U�1��
d�
�Q�X���fIN
����(0�)��8�[NN/����T��G���\��������r4�8�����wc"�s�ja=��������j���h���������9K����
h(g+���-��K������;�����8��18�@k�7f����G�b
&� �~y����A0l����^����#r�������
p���W[��>�Xz�D2��}H�Z���*vEsh�<����,��rOC�1�d�F��?��?������L��0��S)(>������(X%���Y������Idb.y�mk2�@6�@�&�[_)�!kL'��I���<�8L�M~|K=�eWsWC�!W��{�9i�>��Q ���t�:7�}=��/b���;
Bz��$�M����u4z���v+���vt�V�����x������g*P>7��l�_��j����dz����HM�z�c���W?�H~�����O@��UP���g������	v�~+@�6�FS��W�<K�)��~��G��A�x�hx�_74�������G���9b�Tx��9`�������v~�[�����Mt��L��2L��WsF��
���4��������I���~ds�H�N�Q�)_�h���~�i0D�A���`	O�4�U�H�%�r���[�0��5�B��{`	�`a/����"����g=�C�S�IW<��)VL�7>������|�"���Z�_A��g����O���U�6���
�Y������$&YN�>y�O���c�/��6�G
j

%a�A�zX�{�����J�
0002-Add-SUBSCRIPTION-catalog-and-DDL.patch.gzapplication/gzip; name=0002-Add-SUBSCRIPTION-catalog-and-DDL.patch.gzDownload
0003-Define-logical-replication-protocol-and-output-plugi.patch.gzapplication/gzip; name=0003-Define-logical-replication-protocol-and-output-plugi.patch.gzDownload
0004-Make-libpqwalreceiver-reentrant.patch.gzapplication/gzip; name=0004-Make-libpqwalreceiver-reentrant.patch.gzDownload
0005-Add-logical-replication-workers.patch.gzapplication/gzip; name=0005-Add-logical-replication-workers.patch.gzDownload
0006-Logical-replication-support-for-initial-data-copy.patch.gzapplication/gzip; name=0006-Logical-replication-support-for-initial-data-copy.patch.gzDownload
#22Petr Jelinek
petr@2ndquadrant.com
In reply to: Petr Jelinek (#21)
6 attachment(s)
Re: Logical Replication WIP

Hi,

and one more version with bug fixes, improved code docs and couple more
tests, some general cleanup and also rebased on current master for the
start of CF.

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

Attachments:

0001-Add-PUBLICATION-catalogs-and-DDL.patch.gzapplication/gzip; name=0001-Add-PUBLICATION-catalogs-and-DDL.patch.gzDownload
0002-Add-SUBSCRIPTION-catalog-and-DDL.patch.gzapplication/gzip; name=0002-Add-SUBSCRIPTION-catalog-and-DDL.patch.gzDownload
0003-Define-logical-replication-protocol-and-output-plugi.patch.gzapplication/gzip; name=0003-Define-logical-replication-protocol-and-output-plugi.patch.gzDownload
��@�W0003-Define-logical-replication-protocol-and-output-plugi.patch�=iw�F���_������C�u���"K��YY��t�y�<.4)�AA��M��VUw���,�I��%!������/�ML��[�G;j��F��{�{�tt8����G��xw��>���u���s�����t�o�sv���}�=���q��i�����y��~f��9�[q���o���������gl{�����������O��`O;?�3>��&�my,��_bH
� ��c��� ��$f��L\�^o�Zu�vGD��d�ut�6�b��~e��]�]�Wg����(��\E|�u��Y��;���b�
/�����r`��m�>���bd>�p"�XA����"6RX�%��W��_�d
TC;���0���GG�D����8�J*R(�Eu���)y���1�`���O��d��;����hlo�3�p�����:�#^��+8���������`KZ�t����O�7�r����^w����Z7fVgA4-h����?�������n����?z��@���f!�:�b��|�Z����&&}v;<�rf�)D7�,��a�02vgy��#)+������;�g�|�>���u^n��ZJ�-CX����8vc���P�����?a7F��VX�;�w}!C+���
�cb_�;rG�����}w�����=�\k�������Y����scY�����f0�9�����zW�����Y���A�������"��YB�p��)����C~w�h.���8&���6����QM&���R���@���
#w���G�c0FL��L���D
�ew�-�HbC2��Mh���U���7i]I�Z��I:�`���@RR���r\v���\T���X@��'��h��{<��Rp���H�=:f�c�Te0�����),mv�D���@���<��k����H�0@�o�#u��*G�q����ls���8���]��jLm��u;�o"�>��\W���#��3&#m����~���e��C�G������F*��D2��1E������U)!6�Pig���">�>&���n�
������<1�-N��t&���L6��#�
��q�*��w
baP���~t����S��9��!d�D�K�M�T�m������L)��>���^4�-�����o��9	x:�<0���������`�w�[f��,���c�������h�MJ�m*�������� pS
w�S��������B�K��z�"��P�q�����sQ�)�'02G���2?`>�������OG���h����0�r�Qa%����$k�0{Ps��{W2��'Lm�@��u�#�	�����&`"S�Y`_�&q$�G��4���V���&�5��!��f���%�L�KX�e����%��@#���B�"��H�b"�����sP�]�^�O�0C�������0D����e���sHG3�<6A@�~l�D������`{��"���y`���p�&;�� �$j�+-A�>m�P����,y"�#��������h��jH��k�����a�p���U���#��:�4�E[�s����B"_i��Uj�a)��x��	�A��A��'MBE�:@�;vy��?���4
:~"���2�0YVw��C�P��?���f����m�c�uSJ�� 94�;k&��G|�::Q�9�k�gD�ud�cE#�����\��J%����O�����F�m"}�3���-�r�(�&Pr���XC.$��V�q���2�J��]�E
7�r(v���8yN���M��Di�)r�R������[+]��d��!�^\f��2�rc�z��(D2�"s!r�k����f���6Z������%%�mG����o���KO��~���f����Z�,��t
:C���&"FK���]\r��=�6���p��b,n ��}�5�*K���-�$){�yR�e���2�r�*G�pPIe"Rt�!�F�BSa
$-S�	����?`��x����*g���-�O�0X?�=}�T�h(��P$�%K2�.��j��vj����LG�M�?��,��h�a��pH�#��r`����C��6�^j����|bN�4��2����T�x.�y��4������x����������%��$bYSg����� �&G�h������d(j������.�Y�R��+�"���c�twM���94�G9�\r?Rh��	��	v�~���`8�$����E�_�7����q���!��/������Ao(B�e����hM2/������������t�|������q����
���q��#Z�[�'sq[r�e}H�b��n���������6H���M�g�!F�@�M��O.���5h)o��S:s�]^
��%r��$�GI�E�G�?�^�>V`;y��f-�g[{�`]�K��9��Q��L}�������f~�w�RA�!�"��I.r���V��3�>���f#/�?���#�7�5��ueP��j}i�z�#X4���\�Z�a����=>��g�x����GZ��S������Jz���{��(�?�Q�>�0���	f;*QZcq=8CJ���+�,@������[�������Z��},+~��V������mf���ZqU��
�����QX�F���7��0�I�����Z���(��b���B���N`������7�'n����@fct��WH��i���4�G�K��[+=���'��7����^�t����'��a�����RF���R�kT��a�)�e�����$3�����q��\�j�,~���KU�LU��N�d:zi\E�f(��_\���I�r
K<s(������+�u&�� ���T�M�c1����4�eY�T��t�k��{l$�5����^�,�����`��'���P���Q�~K� ��w�_��p�{���Ou�m��{0��������V��o������#���K��v��x�����Y���^��YM]�S���*A?+oC����;<bFW%"��]�Q_�#Lc>Lb�Sh��������,V��;Z���!��<{:j���u�d��L�+uG�w�@���_�����F�C�c���fNe���,�� ����^_��8y�g�_�V��
��p�G�lB����oQ����@����!:��k�@o��u���z��� q9<%x<�
��vz�&�����.�dM�hY��Y^�JP9���=��~X��������
������+�rG���������,����?��
M��v���3k�E�o��ow�Z��z�ez8�^�-�e����)�i�@��1k������&�D<�8��|K6���jINq��m$a
/'�o��|T��*�cpuv%!���m��(R2Th���ik�%��}��Y&k��x�`
nPl��un�v�#����[�5�/f���AwX��������l��;���X��u�����~��p2�������Yb�����e�e��3�_K}�'n'quD��)Qv��e�W�������R^��)��������7�u���"B������-=�"<f_��l��)}�l��W�|!��]u�,�@���/&������^��u�`�]�}\�eg��wT�Ct��%k����f���o�X��f�Q4!��?�o������b���2�����:�����d��A��c[~��������
��0[��2H�<C��-]!���+��
�#��D?^���*�x�E
��]B ���$�q��]C*��P�\]����,���E1H�����5�HrfbBJXq$����wD��m������'����0��q� ���0��?�>;����<dV�$L�����(��IWu^�8J8���k2�����#���(\�hxv�e#�p��;f
B�I����]8Z��j��u'�/
�dO�O�X�^������ @�w���hX�o�p���2�����7��^cY��A%�M�VSYp�+�������:q��(���R^+5��F���9�8�V�$~�RK[�l#�������x�|��������j���M�����wK16���_�
	�v�E���V���g*o�+�jo(5-�V����%:���[�49m�@@�����#y���-/L�TCl��H����^���]�ey5��"�9b���Lt��"YP�~Mz��%��O�����!^����
���o^�j�-E���������pN�c
���g	
�[������Hu'y�y�������	�J�m<��J����4���DT��J����$�n_�amE|J�~jl�+��/lK2��7� #����@��2HG�	�����x*&RN������
���^����NF-�5���������g�Ne-
�������>)�Z�$����24&)��=������Ye+>�V,�j�Y������R�<�E����O�z��m��<CO ��5���a���)���~��/�k9FYaE��X�<#�Aq���/E� �WK^j���NM�t����"�L��2��{�b`~����[���nz���r)��C6�U<�.U$[�T��/����������������<�����xZ/G�zR�I��y�$h�u_T�}6
�&��k�*���=*uRQ�����R��e���5���I��7��#�B�I�F��hh�����e���^�_,�n-75��>��s�y�IO��
q����%W��`fg�qAW�E�l�f�P��W�i��\��J*.5���!��8gO��q�gUW<�]B�o\������5�Q���^������OiA�S����
���@h���;n� �P�����R�s�<�=U����'�p������M��Bn�nh����$��W������!�+�(%��������_�����"�$��%�E�!������/��"�Q����F��Y�tAaNv�����}k�s���������X�.�������Vq�n-!�3�_�����B^����&����L[#�������f�F�:�qNn��`W�������I3k$"
2^�A�(R)�E-Se�*W[�:XY������oE�������������������<��v��!�
RQ�;WK��Z1��KK
��y��BWYs�������!fU����*�c�-+=�p?<�w�]���/B\$}�Nlj�������)���'�d��7������\WTL�
�d{���a���	>��<+s�B�����������_%:������O)1�C�e�������/K�P�����8�v!
�d��3_IQZ_�5t4�zYgqM�@�8�G��S��;+� �(J����_z}�� +�J5VM*-�k�W�[)�vj�F��@�\�8�d�@^O�7�m1��'~*C��X�(���$���B�a�� ��
����q��O����>��W��FaL�D��h�Q2/m�����k�`�_�%�����1�o�x�mv��5�����������+#��*�\���k�+��/�]_l���B��,���a������d���P.(e����V��~|g}�.������~u��
<�����>W'��d ���Q����~HW�0��z�z��3x}����.
�\vL�W
�����r�H���������|���C�w�����lo������.M���c��Q�o�b��dr�������_0i�G��r���	7�C�6
��*��l�S�L�[�������F�3bP���)��$����I*�(���D�krD���M-��B���/���W��l:��k�\���RS?��"cT���E-��G��
����p�*�^��%:�=���?Qb��4}5/�����-oG���YfRAa�%kuq������`0����?�o.O.�W�g��7��)iI-��:���,���������8���������w�dW����9�DA2MYP��������z�;�mH����j/�,�X~��V�"�
�ym���������@��Y�T�$�/!<a�%��|�RKc/kP�^��7���)�uCS�9ew�]�+�6�y�Q��-�����M��������J��x
���8��T�dL�je��Q�����=ks7���_1V*2�P�I��k���eV$J'RY��^�Hq$�,�
�������/�f8����p��M�`�Fh���6��,5�@a�Kh���f���n�J�����?���41��	{��s1����;�����n,��d-���@��"��(�r�%7��3����y��[&��eW$�
�39SYr�����g�)���tV��V�z�H�������E��("Wc��h<�Ro|Ql7�e7���a3E)0���9NB�j�=�	��/��27~m��b~�F�����d56��]�izF��}�`pNa���~2�0��3�]0����E������dn�"��+� 1:Eg�#T���5_4�b��������r��_x�a>~?���yp[l�9_�/}{�>j�;���9n?����,����������
,sm{4��_c��d}4����Io�4��������"4K����J��,E�0 l!��|�K��� �GcCP�a��I�j�w��z���v�{�ud��{�5�Q��XiKp����i�+�TJ�G,�H��������(�]a�����K���A'������!��?L���+d�?����2q��L��N�(K��j���/8�nZ�@�qq����)v��h���G&�8���2���Qg�����W��L5J�L�An0S��je������(��o^��2��5B�i��s"��8'��Q.��g�j�5�����a�����2�~��w��c�O��Qi���y��,�v�O�%(#Mp��c�Z���gW�r�����
y8� ��j|����LagI���	��dcE���������js�c��?W"
���w�Uc���=�1>}�T i8�
%}��"I�����o����E0����"P��|8(uT��{����`8���c��o�`s�{�������Q���o�@��"������@�$�	��'o��==n�'������~��|&�����7���n���Wl�����a4l�+������ASA�q}�����m:���qqt�ESva��M"���������W/t;�~>�=�����e�c��������T:��/�����������%	�*�]�@���U��?���y?��Pc)�����Z$�������W��/C_��t�~���X��x�������HTqI@q��cI�P��U/n��A�!ry�����e$e������f�`k��C�I�X�1G�����H�R=����>�'��������skI,U��_��n�ax;X��#�������M=���D�O�g�r���������^_�x�_SVo���sm<���k'S�����$�T8&��*�W4\$��o�I��+�[B�e�  ������{�\0[ ���*��Gb�n�1�����H�s���hqu9bQ��q:���'�O�Y$��h�b�Y�y��w^�J�a�����9��Px�s��:Aro[���42�e(ju�D@�Ai`~:�����,����k�l���Ffme�Yc����NtU_j�Zl���W�;G^��
�|���0Y�����d]wO���7�tS�e��G�\? ����C���C���6��D��|'$���|�ut��Q����SE�����Anf�����{;��[�s}��$):�Nt@�F7G�c��t�x>�K_C��9��w�v����e}5�����n[r���f��Z.1��b����R���7��i��2���&L�sM��a�11�
n
u�?�H����������w"a��O)o����&m������+���yQ�3��)�����3L��>a5.��3��Ms��w�d>5�)r�IB���>�k�b�0y&�8<.�}���'��t&3�n�0[�(wHVJ�E�b%������N����6�l��w{�v����E�Z�V�J���w���{��?.��@8�?T���)U�@�Vx���#�����o���>f3!Gh�sS����e\����OM�C�YX��Z:,�A����d@>J-��	���I����;�?�k�{�����?z�P��0��t&�CAh=�N1>�9�m���o�����A��6�{�����e�������B�	d$�b���5!� e��V���b���l3W���D����4�N������|<���c%�uw�4�G���U��A�~2&O��c7"L���T��G�����x]�$�p5��x����7{��n�srttx�m����+���k4�ay�����n�p�����-2�	.���y-�}b�3�J�;�����V=���[����0W}����W�����'�GL[��k��5�����j�a�(u��13
�50��yZ��Ll<�*���-a�u!k����o�.&u����V6! ��|?��e�Xz"�7�9�E��S?+�~��V��)�����[@����A�����}������X$Krr�i��b� i�R�"�z1�'��31|�M&;*;�3w��u�6f�����%��b��Pk��#��8w����}F���l�
�U<UR�;�� �fS��		��iY�2S����'���9/���J�j���TN�a��%�8�\��X�=��vT!��a�hq�h���Rrc�� d�2���QQ�E]=P|&��[�)?0���dxL
����[v�'�kn{�������{!z�Az�G��)��7}���~M���|�?,�!�^��dC�?R�����k��t�r�����	&���9�O`�1?��|v1���������7������4ilP�����}L0e:CE�R���"�q[{�c��PFkoc1��,%���6nc�t-�7�
�s�-#���$>�t�**��SV(�E;Xi��8�TL��	2I-������D��d�`����������m��H��e7�i�C%DMi!r���XxFy��V�\�5�R�jj��5�/	�i	Y/'u!r�� O5��������">Z���g��Z\�-D������.\��'o��_;o��{x�c��`%���Am�����D�����-��Y,���w�#��z��!<�^��^���lq�Z��$VD
n�9�=�	l ��E����F�D�����o_uh	���������..�)�r��h�5O�r^�4�4�lN�P��v��u9F����:�I��E�R.�v-:�����4T$���K��p���\5�!����M�f���>S�)5��g�/�"�i��e_�*�`�v��Yhj�`1D�d���1Hb�F+�T��^���gM���"I2�7��e���z�i��s��}�����GC��JZH�i���E������tw���|����X��c��!a�X>[��&u���7��0���������3�"��F���/����*�4�iOf�;[�1�&G�
;�"�� �,�lpz����)udj0)����,�e�����a9��3�cfM�@�E_��r�������D�_I��
�������jR^��d�L�P�����w������&XO$���\vt���8�����.m9���"!��z�}K�#�L]�|�
���Y��:w.-:���Z
�J�D�Q���;n��j��T� R���%���5X]��
�H1���E�1�>��J9~�tS�0��r|�2f�1��;|i
����gQ��s�9V���O��X�,����G��e��������Y��=����Z�9HLn15���2�,�I���H��K�-�$�!���x��[&��������N��������L
!P������F+�'v�z��E��#=o�:ow������5R��=
(���z�����BW���I#|�����Us�Ps�
����q0��G {p$����a��|��n�\��02�d�;��|���c������/��_"�m|qJfv�FAA�I���?���[��B���I^�6�A��)����������O���HGZP"E
��]H�p0�k��<�����.vj����
����W����x-���������0��LT��>��������^���Nw�{�A���ia�
&~�^=Z�,����Vy�Ft �w�t.����'<xm�N3JVa�][r�"���Z�����z]���K��E�:n��lp��\� *EZ������u��x���fDj��^�j���a���=�ni�:�(e�A2�j� ���,��x���������1
���9��U�X����>Ti�l-�����A����>`����T���!��74�U�"�
������~&B�L�����O��@�G��=6���5f�x�����Eys�q��|T�	�����uS�4���J�p���<J��@�@�/����izkg40Q�j ������^`���)$oO�bZ���WL���\��7�IV���R��M�le��6^��FD2��Hxgwf2^��#�i�5^X����Q�J/A�J��������-��S�� ���Y!������\]R���i�d4���f��d!�;b������j��kn
���H3�V�e���bJ��IL���dcm ����1p�VP���)�Cu��#Y2�b/�����U�q��������f`TJ#�.E��6�\;����5���d0�&��f��	K����0���|���O�v� 
e��H?�d���M!��b2(\�S4��D�_�Q[���]'�`�1py��\'�H�Yw�}�*�m��dt���Wr _���c�H�6����8��53b=��#�]�SY;�������T�o�]���!@�O`i���0�g�K{������Qr5{?����d���-w�	x��-Lcz��lH�r�l�
�)4�F=�_���3qj��>��}2�AU��e�K8�2��M�����"s�o�(j��Gb��V����N�,���{��I�i�����H�gG�@(S��d�Z�J�������N]�����W�����%����Hd�LY���\�����u!�>E�b~��������K����*E�?{�����O�,
�
�������W����%B��3)��H��������B����:�^�������;��b~-1,#"1�O<�$\��{���w��m���$����V���i�]�{�j�ajn���J�h������A5��Oo�������&��|�W������nz��L����?yp(��r��`9<���{���vn*��#�{f���F�8i7w���#��R8������po����]@�j��R���A�4��u��-��5�����s@;
=xO}D:7kyx� _WX�z,+;��#9���+�#���{��.!|����dps����=$>���@�z����X\�<����L���+�2���B����J2��If�l��,��f���R��V��6Br�"�Q2�p@�D�P���t�"{��������"'.A������&*;�o��B(�Ij���|
"Qrl���q��9W*�fu��!�c�j4v��	2u&��$`?�M�V�j�L=n��4��x��J����/
�l����VQRI+�r/	g�C���@n �J3`Y�%p8�����}����'r�Ov?S�4f#����
!-���`� 2
z%��Dz�":
]����@�}'��B����v�b}�-�o�	x���]����"��:p�g-[�GCd#y�x��NV�t�V��*���9�����FEL���/f��*,*��\:�(8u3���������"�j��9�����j�����I�����^�B8���&0�������g
��`�_U�,A��3���U�������$��V�N������aNzOj����������$��^��Y���KyME������|�!�D���-��M�`�PL�4��3�>�jKp��^�yT~oR?��&����K���Q��q#9�[/���I���F���|��s�E�g)A	[�4�����S����� ��dbk�VD����t���Vh��Wj9+!={�(������_+&�j�����|�f����f������Er���������=f�`(�����eSC�+�/�:.�����W|R���T�n�D%�-�Hu��n���,�����V�� x�����d����y�����)y{����9�_Kx]#z9�Cx�.?��(�7���������o�Q:��?}5�[nP�l��$)����J�"f����l�
	�P!�)�������"��#
iU�M�K���g�i�����p��4���GV�2P��v�v99��r����A���{,�3$e�&��\FC
�������xT'c�C�f�k��fp��|��~�%ML�5��9�x�qz�������_�9$yR�6���D��E�a���w����T���%��������s�1�$���9�I�!�&�����P�����a�J�xhw������|Q��>�f�)������
�
�E�6��HJ��|ayEJ�/�cc2�
��EK)��5!�Z��D����X�$},K������h�t*fk~�E������u0j�������I����2�d����2�{�2�����(����BS<0�%� �B�LA�����C���O����q���|��}W��?�uLGB�H��Ud����o?��^��$�=,oS}_+L�Z6�p���]_n�^L_�o��p��!?[-6Kb��T�,	E������k��8�[Z�iU\��	�T�HE�F�P���\��R�%(u�r�j|����qBta�_�3N
VR<}Q��	��W[M]a��������������%/.�L=|�����|N���E�_I�q����5��W�C����O��k�aM���S_��/��B���\��D��d�,bu�,��� ���zJ�0�T�3��������:����&���}.�$`+g�y����m?���>,�' ������_�&M�� }��L�w2�N�[)D	��O7�o�����v�>�V�
0004-Make-libpqwalreceiver-reentrant.patch.gzapplication/gzip; name=0004-Make-libpqwalreceiver-reentrant.patch.gzDownload
0005-Add-logical-replication-workers.patch.gzapplication/gzip; name=0005-Add-logical-replication-workers.patch.gzDownload
0006-Logical-replication-support-for-initial-data-copy.patch.gzapplication/gzip; name=0006-Logical-replication-support-for-initial-data-copy.patch.gzDownload
#23Erik Rijkers
er@xs4all.nl
In reply to: Petr Jelinek (#22)
1 attachment(s)
Re: Logical Replication WIP

On 2016-08-31 22:51, Petr Jelinek wrote:

Hi,

and one more version with bug fixes, improved code docs and couple
more tests, some general cleanup and also rebased on current master
for the start of CF.

Clear, well-written docs, thanks.

Here are some small changes to logical-replication.sgml

Erik Rijkers

Attachments:

logical-replication-20160831.sgml.difftext/x-diff; name=logical-replication-20160831.sgml.diffDownload
--- doc/src/sgml/logical-replication.sgml.orig	2016-09-01 00:04:11.484729675 +0200
+++ doc/src/sgml/logical-replication.sgml	2016-09-01 00:54:07.817799915 +0200
@@ -22,11 +22,11 @@
     cascading replication or more complex configurations.
   </para>
   <para>
-    Logical replication typically starts with snapshot of the data on
-    the publisher database. Once that is done, the changes on publisher
-    are sent to subscriber as they occur in real-time. The subscriber
-    applies the data in same order as the publisher so that the
-    transactional consistency is guaranteed for the publications within
+    Logical replication typically starts with a snapshot of the data on
+    the publisher database. Once that is done, the changes on the publisher
+    are sent to the subscriber as they occur in real-time. The subscriber
+    applies the data in the same order as the publisher so that
+    transactional consistency is guaranteed for publications within a
     single subscription. This method of data replication is sometimes
     referred to as transactional replication.
   </para>
@@ -54,23 +54,23 @@
     </listitem>
     <listitem>
       <para>
-        Replicating between different major versions of the PostgreSQL
+        Replicating between different major versions of PostgreSQL
       </para>
     </listitem>
     <listitem>
       <para>
-        Giving access to the replicated data to different groups of
+        Giving access to replicated data to different groups of
         users.
       </para>
     </listitem>
     <listitem>
       <para>
-        Sharing subset of the database between multiple databases.
+        Sharing a subset of the database between multiple databases.
       </para>
     </listitem>
   </itemizedlist>
   <para>
-    The subscriber database behaves in a same way as any other
+    The subscriber database behaves in the same way as any other
     PostgreSQL instance and can be used as a publisher for other
     databases by defining its own publications. When the subscriber is
     treated as read-only by application, there will be no conflicts from
@@ -83,9 +83,9 @@
   <title>Publication</title>
   <para>
     A <firstterm>publication</> object can be defined on any physical
-    replication master, the node where publication is deined is referred to
-    as <firstterm>publisher</>. Only superusers or members of
-    <literal>REPLICATION</> role can define publication. A publication is
+    replication master. The node where a publication is defined is referred
+    to as <firstterm>publisher</>. Only superusers or members of the
+    <literal>REPLICATION</> role can define a publication. A publication is
     a set of changes generated from a group of tables, and might also be
     described as a <firstterm>change set</> or <firstterm>replication set</>.
     Each publication exists in only one database.
@@ -93,7 +93,7 @@
   <para>
     Publications are different from table schema and do not affect
     how the table is accessed. Each table can be added to multiple
-    publications if needed.  Publications may currenly only contain
+    publications if needed.  Publications may currently only contain
     tables. Objects must be added explicitly, except when a publication
     is created for <literal>ALL TABLES</>. There is no default name for
     a publication which specifies all tables.
@@ -103,8 +103,8 @@
     any combination of <command>INSERT</>, <command>UPDATE</>,
     <command>DELETE</> and <command>TRUNCATE</> in a similar
     way to the way triggers are fired by particular event types. Only
-    tables with <literal>REPLICA IDENTITY</> index can be added to
-    publication which replicate <command>UPDATE</> and <command>DELETE</>
+    tables with a <literal>REPLICA IDENTITY</> index can be added to a
+    publication which replicates <command>UPDATE</> and <command>DELETE</>
     operation.
   </para>
   <para>
@@ -129,20 +129,20 @@
 <sect1 id="logical-replication-subscription">
   <title>Subscription</title>
   <para>
-    A <firstterm>subscription</> is the downstream side of the logical
+    A <firstterm>subscription</> is the downstream side of logical
     replication. The node where subscription is defined is referred to as
-    a <firstterm>subscriber</>. Subscription defines the connection to
+    the <firstterm>subscriber</>. Subscription defines the connection to
     another database and set of publications (one or more) to which it
     wants to be subscribed.
   </para>
   <para>
-    The subscriber database behaves in a same way as any other
+    The subscriber database behaves in the same way as any other
     PostgreSQL instance and can be used as a publisher for other
     databases by defining its own publications.
   </para>
   <para>
     A subscriber may have multiple subscriptions if desired. It is
-    possible to define multiple subscriptions between single
+    possible to define multiple subscriptions between a single
     publisher-subscriber pair, in which case extra care must be taken
     to ensure that the subscribed publication objects don't overlap.
   </para>
@@ -153,17 +153,17 @@
     of pre-existing table data.
   </para>
   <para>
-    Subscriptions are not dumped by pg_dump by default, but can be
-    requested using --subscriptions parameter.
+    Subscriptions are not dumped by pg_dump by default but can be
+    requested using the --subscriptions parameter.
   </para>
   <para>
     The subscription is added using <xref linkend="sql-createsubscription">
-    and can be stopped/resumed at any time using
+    and can be stopped/resumed at any time using the
     <xref linkend="sql-altersubscription"> command or removed using
     <xref linkend="sql-dropsubscription">.
   </para>
   <para>
-    When subscription is dropped and recreated the synchronization
+    When subscription is dropped and recreated, the synchronization
     information is lost. This means that the data has to be
     resynchronized afterwards.
   </para>
@@ -173,25 +173,25 @@
   <para>
     The logical replication behaves similarly to normal <literal>DML</>
     operations in that the data will be updated even if it was changed
-    locally on the subscriber node. In case when the incoming data
-    violates any constraints the replication will stop. This is refered
+    locally on the subscriber node. If the incoming data
+    violates any constraints the replication will stop. This is referred
     to as a <firstterm>conflict</>. When replicating <command>UPDATE</>
-    or <command>DELETE</> operations any missing data will not produce
-    conflict and such operation will simply be skipped.
+    or <command>DELETE</> operations, missing data will not produce a
+    conflict and such operations will simply be skipped.
   </para>
   <para>
-    The conflicts will produce error and stop the replication and must
-    be resolved manually by user.
+    A conflict will produce an error and will stop the replication; it
+    must be resolved manually by the user.
   </para>
   <para>
-    The resolution can be done either by changing dota on the subscriber
+    The resolution can be done either by changing data on the subscriber
     so that it does not conflict with incoming change or by skipping the
-    transaction which conflicts with the existing data. The transaction
+    transaction that conflicts with the existing data. The transaction
     can be skipped by calling the
     <link linkend="pg-replication-origin-advance">
     <function>pg_replication_origin_advance()</function></link> function
-    with <literal>node_name</> corresponding to the subscription name. The
-    current position of origins can be seen in
+    with a <literal>node_name</> corresponding to the subscription name. The
+    current position of origins can be seen in the
     <link linkend="view-pg-replication-origin-status">
     <structname>pg_replication_origin_status</structname></link> system view.
   </para>
@@ -200,28 +200,28 @@
   <title>Architecture</title>
   <para>
     Logical replication starts by copying a snapshot of the data on
-    the publisher database. Once that is done, the changes on publisher
-    are sent to subscriber as they occur in real-time. The subscriber
-    applies the data in the order in which commits were made on the
+    the publisher database. Once that is done, changes on the publisher
+    are sent to the subscriber as they occur in real-time. The subscriber
+    applies data in the order in which commits were made on the
     publisher so that transactional consistency is guaranteed for the
     publications within any single Subscription.
   </para>
   <para>
-    The logical replication is built on the similar architecture as the
+    Logical replication is built with an architecture similar to
     physical streaming replication
     (see <xref linkend="streaming-replication">). It is implemented by
-    WalSender and the Apply processes. The WalSender starts the logical
+    WalSender and the Apply processes. The WalSender starts logical
     decoding (described in <xref linkend="logicaldecoding">) of the WAL and
     loads the standard logical decoding plugin (pgoutput). The plugin
     transforms the changes read from WAL to the logical replication protocol
     (see <xref linkend="protocol-logical-replication">) and filters the data
-    according to publication specifications. The data are then continuously
+    according to publication specification. The data are then continuously
     transferred using the streaming replication protocol to the Apply worker
     which maps them to the local tables and applies the individual changes as
     they are received in exact transactional order.
   </para>
   <para>
-    The Apply process on subscriber database always runs with
+    The Apply process on the subscriber database always runs with
     session_replication_role set to replica, which produces the usual effects
     on triggers and constraints.
   </para>
@@ -229,15 +229,15 @@
     <title>Initial snapshot</title>
     <para>
       The initial data in existing subscribed tables are snapshotted and
-      copied in a parallel instance of special kind of Apply process.
-      This process will create it's own temporary replication slot and
-      copies the existing data. Once existing data is copied, the worker
+      copied in a parallel instance of a special kind of Apply process.
+      This process will create its own temporary replication slot and
+      copy the existing data. Once existing data is copied, the worker
       enters synchronization mode which ensures that the table is brought
-      up to synchronized state with the main Apply proccess by streaming
+      up to synchronized state with the main Apply process by streaming
       any changes which happened during the initial data copy using standard
-      logical replication. Once the sycnhronization is done, the control
-      of of the replication of the table is given back to the main Apply
-      proccess where the replication continues as normal.
+      logical replication. Once the synchronization is done, the control
+      of the replication of the table is given back to the main Apply
+      process where the replication continues as normal.
     </para>
   </sect2>
 </sect1>
@@ -246,18 +246,18 @@
   <para>
     Because logical replication is based on similar architecture as
     <link linkend="streaming-replication">physical streaming
-    replication</link> the monitoring on publicasher is very similar to
-    monitoring of physical replication master(see
+    replication</link> the monitoring on publication is very similar to
+    monitoring of physical replication master (see
     <xref linkend="streaming-replication-monitoring">).
   </para>
   <para>
-    The monitoring information about subscription can is visible in
+    The monitoring information about subscription is visible in
     <link linkend="pg-stat-subscription"><literal>pg_stat_subscription</></link>.
-    This view contains one row per every subscription worker. Subscription
-    can have zero or more active subscription workers depending on it's state.
+    This view contains one row for every subscription worker. Subscription
+    can have zero or more active subscription workers depending on its state.
   </para>
   <para>
-    Normally there is single Apply process running for the enabled
+    Normally there is a single Apply process running for the enabled
     subscription. The disabled subscription of crashed subscription will
     have zero rows in this view. If the initial data synchronization of
     any table is in progress there will be additional worker(s) for the
@@ -270,7 +270,7 @@
     Replication connection can occur in the same way as physical streaming
     replication. It requires access to be specifically given using
     pg_hba.conf. The role used for the replication must have
-    <literal>REPLICATION</literal> privilege <command>GRANTED</command>.
+    <literal>REPLICATION</literal> privilege <command>GRANT</command>ED.
     This gives a role access to both logical and physical replication.
   </para>
   <para>
@@ -281,7 +281,7 @@
     To create a subscription the user must be a superuser.
   </para>
   <para>
-    The subscription apply process will run in local database
+    The subscription apply process will run in the local database
     with the privileges of a superuser.
   </para>
   <para>
@@ -298,10 +298,10 @@
   </para>
   <para>
     On the publisher side the <varname>wal_level</> must be set to
-    <literal>logical</>, <varname>max_replication_slots</> has to be set to
-    at least number of Subscriptions expected to connect with some reserve
+    <literal>logical</>, and <varname>max_replication_slots</> has to be set to
+    at least the number of Subscriptions expected to connect with some reserve
     for table synchronization as well. And <varname>max_wal_senders</>
-    should be set to at least same as <varname>max_replication_slots</> plus
+    should be set to at least the same as <varname>max_replication_slots</> plus
     the number of physical replicas that are connected at the same time.
   </para>
   <para>
@@ -311,7 +311,7 @@
     <varname>max_logical_replication_workers</> has to be set to at least
     the number of Subscriptions again with some reserve for the table
     synchronization. Additionally the <varname>max_worker_processes</> may
-    need to be adjusted to accommodate for replication workers at least
+    need to be adjusted to accommodate for replication workers, at least
     (<varname>max_logical_replication_workers</> + <literal>1</>). Please
     note that some extensions and parallel queries also take worker slots
     from <varname>max_worker_processes</>.
@@ -325,7 +325,7 @@
 wal_level = logical
 max_worker_processes = 10 # one per subscription + one per instance needed on subscriber
 max_logical_replication_workers = 10 # one per subscription + one per instance needed on subscriber
-max_replication_slots = 10 # one per subscription needed both publisher and subscriber
+max_replication_slots = 10 # one per subscription needed on both publisher and subscriber
 max_wal_senders = 10 # one per subscription needed on publisher
 </programlisting>
   </para>
@@ -338,13 +338,13 @@
 </programlisting>
   </para>
   <para>
-    Then on publisher database:
+    Then on the publisher database:
 <programlisting>
 CREATE PUBLICATION mypub FOR TABLE users, departments;
 </programlisting>
   </para>
   <para>
-    And on Subscriber database:
+    And on the Subscriber database:
 <programlisting>
 CREATE SUBSCRIPTION mysub WITH CONNECTION <quote>dbname=foo host=bar user=repuser</quote> PUBLICATION mypub;
 </programlisting>
#24Erik Rijkers
er@xs4all.nl
In reply to: Erik Rijkers (#23)
2 attachment(s)
Re: Logical Replication WIP

On 2016-09-01 01:04, Erik Rijkers wrote:

On 2016-08-31 22:51, Petr Jelinek wrote:

Here are some small changes to logical-replication.sgml

... and other .sgml files.

Erik Rijkers

Attachments:

alter_publication-20160831.sgml.difftext/x-diff; name=alter_publication-20160831.sgml.diffDownload
--- doc/src/sgml/ref/alter_publication.sgml.orig	2016-09-01 07:20:03.280295807 +0200
+++ doc/src/sgml/ref/alter_publication.sgml	2016-09-01 07:34:43.448625900 +0200
@@ -42,15 +42,15 @@
 
   <para>
    The first variant of this command listed in the synopsis can change
-   all of the publication attributes that can be specified in
+   all of the publication attributes specified in
    <xref linkend="sql-createpublication">.
    Attributes not mentioned in the command retain their previous settings.
    Database superusers can change any of these settings for any role.
   </para>
 
   <para>
-   The other variants this command deal with table membership in the
-   publication. The <literal>FOR TABLE</literal> subscommand will replace
+   The other variants of this command deal with table membership in the
+   publication. The <literal>FOR TABLE</literal> subcommand will replace
    the current list of tables in the publication with the specified one.
    The <literal>FOR ALL TABLES</literal> variant will mark the publication
    as one that replicates all tables in the database (including the future
drop_publication-20160831.sgml.difftext/x-diff; name=drop_publication-20160831.sgml.diffDownload
--- doc/src/sgml/ref/drop_publication.sgml.orig	2016-09-01 07:43:26.599160896 +0200
+++ doc/src/sgml/ref/drop_publication.sgml	2016-09-01 07:44:16.051797277 +0200
@@ -21,7 +21,7 @@
 
  <refsynopsisdiv>
 <synopsis>
-DROP PUBLCATION <replaceable class="PARAMETER">name</replaceable> [, ...]
+DROP PUBLICATION <replaceable class="PARAMETER">name</replaceable> [, ...]
 </synopsis>
  </refsynopsisdiv>
 
@@ -29,7 +29,7 @@
   <title>Description</title>
 
   <para>
-   <command>DROP PUBLCATION</command> removes publications from the database.
+   <command>DROP PUBLICATION</command> removes publications from the database.
   </para>
 
   <para>
#25Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Petr Jelinek (#22)
Re: Logical Replication WIP

Review of 0001-Add-PUBLICATION-catalogs-and-DDL.patch:

The new system catalog pg_publication_rel has columns pubid, relid,
and does not use the customary column name prefixes. Maybe that is OK
here. I can't actually think of a naming scheme that wouldn't make
things worse.

The hunk in patch 0006 for
src/backend/replication/logical/publication.c needs to be moved to
0001, for the definition of GetPublicationRelations().

The catalog column puballtables is not mentioned in the documentation.

Unrelated formatting changes in src/backend/commands/Makefile.

In psql, the code psql_error("The server (version %d.%d) does not
...") should be updated to use the new formatPGVersionNumber()
function.

In psql, psql \dr is already for "roles" (\drds). You are adding \drp
for publications. Maybe use big R for replication-related describes?

There should be some documentation about how TRUNCATE commands are
handled by publications. Patch 0005 mentions TRUNCATE in the general
documentation, but I would have questions when reading the CREATE
PUBLICATION reference page.

Also, document how publications deal with INSERT ON CONFLICT.

In some places, the new publication object type is just added to the
end of a list instead of some alphabetical place, e.g.,
event_trigger.c, gram.y (drop_type).

publication.h has /* true if inserts are replicated */ repeated
several times.

What are the BKI_ROWTYPE_OID assignments for? Are they necessary
here? (Maybe this was just copied from pg_subscription?)

I think some or all of replication/logical/publication.c should be
catalog/pg_publication.c. There are various different precedents in
how this can be split up, but I kind of like having command/foocmds.c
call into catalog/pg_foo.c.

Also, some things could be in lsyscache.c, although not too many new
things go in there now.

Most calls of the GetPublication() function could be changed to a
simpler get_publication_name(Oid), because that is all it is used for
so far. (It will be used later in 0003, but only in one specific
case.)

In get_object_address_publication_rel() you are calling
ObjectAddressSet(address, UserMappingRelationId, InvalidOid). That is
probably a typo.

Also, document somewhere around get_object_address_publication_rel()
what objname (relation) and objargs (publication) are, otherwise one
has to guess. (Existing similar functions are also not good about that.)

The code for OCLASS_PUBLICATION_REL in getObjectIdentityParts() does
not fill in objname and objargs, as it is supposed to.

If I add a table to a publication, it requires a primary key. But
after the table is added, I can remove the primary key. There is code
in publication_add_relation() to record dependencies for that, but it
doesn't seem to do its job right.

Relatedly, the error messages in check_publication_add_relation() and
AlterPublicationOptions() conflate replica identity index and primary
key. (I suppose the whole user-facing presentation of what replica
identity indexes are, which have so far been a rather obscure feature,
will need some polishing during this.)

I think the syntax could be made prettier. For example, instead of

CREATE PUBLICATION testpib_ins_trunct WITH noreplicate_delete
noreplicate_update;

how about something like

CREATE PUBLICATION foo (REPLICATE DELETE, NO REPLICATE UPDATE);

Not that important right now, but something to keep in mind.

I also found ALTER PUBLICATION FOR TABLE / FOR ALL TABLES confusing.
Maybe that should be SET TABLE or something.

Finally, I'd like some more test coverage of DDL error cases, like
adding a view to a publication, trying to drop a primary key (as per
above), and so on.

(Various small typos and such I didn't bother with at this time.)

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#26Petr Jelinek
petr@2ndquadrant.com
In reply to: Peter Eisentraut (#25)
Re: Logical Replication WIP

On 02/09/16 22:57, Peter Eisentraut wrote:

Review of 0001-Add-PUBLICATION-catalogs-and-DDL.patch:

Thanks!

The new system catalog pg_publication_rel has columns pubid, relid,
and does not use the customary column name prefixes. Maybe that is OK
here. I can't actually think of a naming scheme that wouldn't make
things worse.

Yeah, well I could not either and thee are some catalogs that don't use
the prefixes so I figured it's probably not big deal.

In psql, the code psql_error("The server (version %d.%d) does not
...") should be updated to use the new formatPGVersionNumber()
function.

Right, same thing will be in the 2nd patch.

In psql, psql \dr is already for "roles" (\drds). You are adding \drp
for publications. Maybe use big R for replication-related describes?

Seems reasonable.

There should be some documentation about how TRUNCATE commands are
handled by publications. Patch 0005 mentions TRUNCATE in the general
documentation, but I would have questions when reading the CREATE
PUBLICATION reference page.

That's actually bug in the 0005 patch, TRUNCATE is not handled ATM, but
that should be probably documented as well.

Also, document how publications deal with INSERT ON CONFLICT.

Okay, they just replicate whatever was the result of that operation (if
any).

In some places, the new publication object type is just added to the
end of a list instead of some alphabetical place, e.g.,
event_trigger.c, gram.y (drop_type).

Hmm, what is and what isn't alphabetically sorted is quite unclear for
me as we have mix of both everywhere. For example, if you consider
drop_type to be alphabetically sorted then our locales are much more
different than I thought :)

What are the BKI_ROWTYPE_OID assignments for? Are they necessary
here? (Maybe this was just copied from pg_subscription?)

Yes they are.

I think some or all of replication/logical/publication.c should be
catalog/pg_publication.c. There are various different precedents in
how this can be split up, but I kind of like having command/foocmds.c
call into catalog/pg_foo.c.

Okay, I prefer grouping the code by functionality (as in terms of this
is replication) rather than architectures (as in terms this is catalog)
but no problem moving it. Again same thing will be in 2nd patch.

Also, some things could be in lsyscache.c, although not too many new
things go in there now.

TBH I dislike the whole lsyscache concept of just random lookup
functions piled in one huge module and would rather not add to it.

Most calls of the GetPublication() function could be changed to a
simpler get_publication_name(Oid), because that is all it is used for
so far. (It will be used later in 0003, but only in one specific
case.)

You mean the calls from objectaddress? Will change that - I actually
added the get_publication_name much later in the development and didn't
go back to use it in preexisting code.

If I add a table to a publication, it requires a primary key. But
after the table is added, I can remove the primary key. There is code
in publication_add_relation() to record dependencies for that, but it
doesn't seem to do its job right.

I need to rewrite that part. That's actually something I could use other
people opinion on - currently the pg_publication_rel does not have
records for the alltables publication as that seemed redundant so it
will need some special handling in tablecmds.c for the "dependency"
tracking and possibly elsewhere for other things. I do wonder though if
we should instead just add records to the pg_publication_rel catalog.

Relatedly, the error messages in check_publication_add_relation() and
AlterPublicationOptions() conflate replica identity index and primary
key. (I suppose the whole user-facing presentation of what replica
identity indexes are, which have so far been a rather obscure feature,
will need some polishing during this.)

Those are copy/paste issues from pglogical. It should say replica
identity index everywhere. But yes it might be needed to make it more
obvious what replica identity indexes are.

I think the syntax could be made prettier. For example, instead of

CREATE PUBLICATION testpib_ins_trunct WITH noreplicate_delete
noreplicate_update;

how about something like

CREATE PUBLICATION foo (REPLICATE DELETE, NO REPLICATE UPDATE);

I went with the same syntax style as CREATE ROLE, but I am open to changes.

I also found ALTER PUBLICATION FOR TABLE / FOR ALL TABLES confusing.
Maybe that should be SET TABLE or something.

Yeah I am not sure what is the best option there. SET was also what I
was thinking but then it does not map well to the CREATE PUBLICATION
syntax and I would like to have some harmony there.

--
Petr Jelinek 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

#27Tom Lane
tgl@sss.pgh.pa.us
In reply to: Petr Jelinek (#26)
Re: Logical Replication WIP

Petr Jelinek <petr@2ndquadrant.com> writes:

On 02/09/16 22:57, Peter Eisentraut wrote:

The new system catalog pg_publication_rel has columns pubid, relid,
and does not use the customary column name prefixes. Maybe that is OK
here. I can't actually think of a naming scheme that wouldn't make
things worse.

Yeah, well I could not either and thee are some catalogs that don't use
the prefixes so I figured it's probably not big deal.

The ones that don't are not models to be emulated. They are cases
where somebody ignored project convention and it wasn't caught until
too late.

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

#28Petr Jelinek
petr@2ndquadrant.com
In reply to: Tom Lane (#27)
Re: Logical Replication WIP

On 03/09/16 18:04, Tom Lane wrote:

Petr Jelinek <petr@2ndquadrant.com> writes:

On 02/09/16 22:57, Peter Eisentraut wrote:

The new system catalog pg_publication_rel has columns pubid, relid,
and does not use the customary column name prefixes. Maybe that is OK
here. I can't actually think of a naming scheme that wouldn't make
things worse.

Yeah, well I could not either and thee are some catalogs that don't use
the prefixes so I figured it's probably not big deal.

The ones that don't are not models to be emulated. They are cases
where somebody ignored project convention and it wasn't caught until
too late.

Okay but if I follow the convention the names of those fields would be
something like pubrelpubid and pubrelrelid which does not seem like
improvement to me. Maybe the catalog should be pg_publication_map then
as that would make it seem less ugly although less future proof (as
we'll want to add more things to publications than just tables and they
might need different catalogs).

--
Petr Jelinek 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

#29Steve Singer
steve@ssinger.info
In reply to: Petr Jelinek (#22)
Re: Logical Replication WIP

On 08/31/2016 04:51 PM, Petr Jelinek wrote:

Hi,

and one more version with bug fixes, improved code docs and couple
more tests, some general cleanup and also rebased on current master
for the start of CF.

To get the 'subscription' TAP tests to pass I need to set

export PGTZ=+02

Shouldn't the expected output be with reference to PST8PDT?

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

#30Steve Singer
steve@ssinger.info
In reply to: Steve Singer (#29)
Re: Logical Replication WIP

On 09/05/2016 03:58 PM, Steve Singer wrote:

On 08/31/2016 04:51 PM, Petr Jelinek wrote:

Hi,

and one more version with bug fixes, improved code docs and couple
more tests, some general cleanup and also rebased on current master
for the start of CF.

A few more things I noticed when playing with the patches

1, Creating a subscription to yourself ends pretty badly,
the 'CREATE SUBSCRIPTION' command seems to get stuck, and you can't kill
it. The background process seems to be waiting for a transaction to
commit (I assume the create subscription command). I had to kill -9 the
various processes to get things to stop. Getting confused about
hostnames and ports is a common operator error.

2. Failures during the initial subscription aren't recoverable

For example

on db1
create table a(id serial4 primary key,b text);
insert into a(b) values ('1');
create publication testpub for table a;

on db2
create table a(id serial4 primary key,b text);
insert into a(b) values ('1');
create subscription testsub connection 'host=localhost port=5440
dbname=test' publication testpub;

I then get in my db2 log

ERROR: duplicate key value violates unique constraint "a_pkey"
DETAIL: Key (id)=(1) already exists.
LOG: worker process: logical replication worker 16396 sync 16387 (PID
10583) exited with exit code 1
LOG: logical replication sync for subscription testsub, table a started
ERROR: could not crate replication slot "testsub_sync_a": ERROR:
replication slot "testsub_sync_a" already exists

LOG: worker process: logical replication worker 16396 sync 16387 (PID
10585) exited with exit code 1
LOG: logical replication sync for subscription testsub, table a started
ERROR: could not crate replication slot "testsub_sync_a": ERROR:
replication slot "testsub_sync_a" already exists

and it keeps looping.
If I then truncate "a" on db2 it doesn't help. (I'd expect at that point
the initial subscription to work)

If I then do on db2
drop subscription testsub cascade;

I still see a slot in use on db1

select * FROM pg_replication_slots ;
slot_name | plugin | slot_type | datoid | database | active |
active_pid | xmin | catalog_xmin | rest
art_lsn | confirmed_flush_lsn
----------------+----------+-----------+--------+----------+--------+------------+------+--------------+-----
--------+---------------------
testsub_sync_a | pgoutput | logical | 16384 | test | f
| | | 1173 | 0/15
66E08 | 0/1566E40

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

#31Petr Jelinek
petr@2ndquadrant.com
In reply to: Steve Singer (#29)
Re: Logical Replication WIP

On 05/09/16 21:58, Steve Singer wrote:

On 08/31/2016 04:51 PM, Petr Jelinek wrote:

Hi,

and one more version with bug fixes, improved code docs and couple
more tests, some general cleanup and also rebased on current master
for the start of CF.

To get the 'subscription' TAP tests to pass I need to set

export PGTZ=+02

Shouldn't the expected output be with reference to PST8PDT?

That would break it for other timezones, the expected output should be
whatever will work for everybody. I think the connection just needs to
set the timezone so that it's stable across environments.

--
Petr Jelinek 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

#32Petr Jelinek
petr@2ndquadrant.com
In reply to: Steve Singer (#30)
Re: Logical Replication WIP

On 05/09/16 23:35, Steve Singer wrote:

On 09/05/2016 03:58 PM, Steve Singer wrote:

On 08/31/2016 04:51 PM, Petr Jelinek wrote:

Hi,

and one more version with bug fixes, improved code docs and couple
more tests, some general cleanup and also rebased on current master
for the start of CF.

A few more things I noticed when playing with the patches

1, Creating a subscription to yourself ends pretty badly,
the 'CREATE SUBSCRIPTION' command seems to get stuck, and you can't kill
it. The background process seems to be waiting for a transaction to
commit (I assume the create subscription command). I had to kill -9 the
various processes to get things to stop. Getting confused about
hostnames and ports is a common operator error.

Hmm I guess there is missing interrupts check, will look. It would be
great to detect it properly but I am not really sure how to do that as
afaik there is no accurate way to detect that the connection is to yourself.

2. Failures during the initial subscription aren't recoverable

For example

on db1
create table a(id serial4 primary key,b text);
insert into a(b) values ('1');
create publication testpub for table a;

on db2
create table a(id serial4 primary key,b text);
insert into a(b) values ('1');
create subscription testsub connection 'host=localhost port=5440
dbname=test' publication testpub;

I then get in my db2 log

ERROR: duplicate key value violates unique constraint "a_pkey"
DETAIL: Key (id)=(1) already exists.
LOG: worker process: logical replication worker 16396 sync 16387 (PID
10583) exited with exit code 1
LOG: logical replication sync for subscription testsub, table a started
ERROR: could not crate replication slot "testsub_sync_a": ERROR:
replication slot "testsub_sync_a" already exists

LOG: worker process: logical replication worker 16396 sync 16387 (PID
10585) exited with exit code 1
LOG: logical replication sync for subscription testsub, table a started
ERROR: could not crate replication slot "testsub_sync_a": ERROR:
replication slot "testsub_sync_a" already exists

and it keeps looping.
If I then truncate "a" on db2 it doesn't help. (I'd expect at that point
the initial subscription to work)

Hmm, looks like the error case does not cleanup correctly after itself.

If I then do on db2
drop subscription testsub cascade;

I still see a slot in use on db1

select * FROM pg_replication_slots ;
slot_name | plugin | slot_type | datoid | database | active |
active_pid | xmin | catalog_xmin | rest
art_lsn | confirmed_flush_lsn
----------------+----------+-----------+--------+----------+--------+------------+------+--------------+-----

--------+---------------------
testsub_sync_a | pgoutput | logical | 16384 | test | f
| | | 1173 | 0/15
66E08 | 0/1566E40

Same as above.

--
Petr Jelinek 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

#33Petr Jelinek
petr@2ndquadrant.com
In reply to: Erik Rijkers (#24)
Re: Logical Replication WIP

On 01/09/16 08:29, Erik Rijkers wrote:

On 2016-09-01 01:04, Erik Rijkers wrote:

On 2016-08-31 22:51, Petr Jelinek wrote:

Here are some small changes to logical-replication.sgml

... and other .sgml files.

Thanks I'll integrate these into next iteration of the patch,

--
Petr Jelinek 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

#34Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Petr Jelinek (#26)
Re: Logical Replication WIP

On 9/3/16 5:14 AM, Petr Jelinek wrote:

What are the BKI_ROWTYPE_OID assignments for? Are they necessary

here? (Maybe this was just copied from pg_subscription?)

Yes they are.

Please explain/document why. It does not match other catalogs, which
either use it for relcache initialization or because they are shared
catalogs. (I'm not sure of the details, but this one clearly looks
different.)

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#35Petr Jelinek
petr@2ndquadrant.com
In reply to: Peter Eisentraut (#34)
Re: Logical Replication WIP

On 06/09/16 20:14, Peter Eisentraut wrote:

On 9/3/16 5:14 AM, Petr Jelinek wrote:

What are the BKI_ROWTYPE_OID assignments for? Are they necessary

here? (Maybe this was just copied from pg_subscription?)

Yes they are.

Please explain/document why. It does not match other catalogs, which
either use it for relcache initialization or because they are shared
catalogs. (I'm not sure of the details, but this one clearly looks
different.)

Erm, I meant yes they are just copied and I will remove them (I see how
my answer might been confusing given that you asked multiple questions,
sorry).

--
Petr Jelinek 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

#36Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Petr Jelinek (#22)
Re: Logical Replication WIP

Review of 0002-Add-SUBSCRIPTION-catalog-and-DDL.patch:

(As you had already mentioned, some of the review items in 0001 apply
analogously here.)

Changes needed to compile:

--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -218,7 +218,7 @@ CreateSubscription(CreateSubscriptionStmt *stmt)
    CatalogUpdateIndexes(rel, tup);
        heap_freetuple(tup);
-   ObjectAddressSet(myself, SubscriptionRelationId, suboid);
+   ObjectAddressSet(myself, SubscriptionRelationId, subid);

heap_close(rel, RowExclusiveLock);

This is fixed later in patch 0005.

--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -6140,8 +6140,7 @@ <title><structname>pg_subscription</structname>
Columns</title>
       <entry><structfield>subpublications</structfield></entry>
              <entry><type>text[]</type></entry>
             <entry></entry>
-      <entry>Array of subscribed publication names. For more on
publications
-       see <xref linkend="publications">.
+      <entry>Array of subscribed publication names.
            </entry>
          </row>
       </tbody>

I don't see that id defined in any later patch.

Minor problems:

Probably unintentional change in pg_dump.h:

- * The PublicationInfo struct is used to represent publications.
+ * The PublicationInfo struct is used to represent publication.

pg_subscription column "dbid" rename to "subdbid".

I think subpublications ought to be of type name[], not text[].

It says, a subscription can only be dropped by its owner or a
superuser. But subscriptions don't have owners. Maybe they should.

On the CREATE SUBSCRIPTION ref page,

| INITIALLY ( ENABLED | DISABLED )

should use {} instead.

We might want to add ALTER commands to rename subscriptions and
publications.

Similar concerns as before about ALTER syntax, e.g., does ALTER
SUBSCRIPTION ... PUBLICATION add to or replace the publication set?

For that matter, why is there no way to add?

Document why publicationListToArray() creates its own memory context.

I think we should allow creating subscriptions initally without
publications. This could be useful for example to test connections,
or create slots before later on adding publications. Seeing that
there is support for changing the publications later, this shouldn't
be a problem.

The synopsis of CREATE SUBSCRIPTION indicates that options are
optional, but it actually requires at least one option.

At the end of CreateSubscription(), the CommandCounterIncrement()
doesn't appear to be necessary (yet, see patch 0005?).

Maybe check for duplicates in the publications list.

Larger conceptual issues:

I haven't read the rest of the code yet to understand why
pg_subscription needs to be a shared catalog, but be that as it may, I
would still make it so that subscription names appear local to the
database. We already have the database OID in the pg_subscription
catalog, so I would make the key (subname, subdatid). DDL commands
would only operate on subscriptions in the current database (or you
could use "datname"."subname" syntax), and \drs would only show
subscriptions in the current database. That way the shared catalog is
an implementation detail that can be changed in the future. I think
it would be very helpful for users if publications and subscriptions
appear to work in a parallel way. If I have two databases that I want
to replicate between two servers, I might want to have a publication
"mypub" in each database and a subscription "mysub" in each database.
If I learn that the subscriptions can't be named that way, then I have
to go back to rename to the publications, and it'll all be a bit
frustrating.

Some thoughts on pg_dump and such:

Even an INITIALLY DISABLED subscription needs network access to create
the replication slot. So restoring a dump when the master is not
available will have some difficulties. And restoring master and slave
at the same time (say disaster recovery) will not necessarily work
well either. Also, the general idea of doing network access during a
backup restore without obvious prior warning sounds a bit unsafe.

I imagine maybe having three states for subscriptions: DISABLED,
PREPARED, ENABLED (to use existing key words). DISABLED just exists
in the catalog, PREPARED has the slots set up, ENABLED starts
replicating. So you can restore a dump with all slots disabled. And
then it might be good to have a command to "prepare" or "enable" all
subscriptions at once.

That command would also help if you restore a dump not in a
transaction but you want to enable all subscriptions in the same
transaction.

I'd also prefer having subscriptions dumped by default, just to keep
it so that pg_dump by default backs up everything.

Finally, having disabled subscriptions without network access would
also allow writing some DDL command tests.

As I had mentioned privately before, I would perhaps have CREATE
SUBSCRIPTION use the foreign server infrastructure for storing
connection information.

We'll have to keep thinking about ways to handle abandonded
replication slots. I imagine that people will want to create
subscriber instances in fully automated ways. If that fails every so
often and requires manual cleanup of replication slots on the master
some of the time, that will get messy. I don't have well-formed ideas
about this, though.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#37Petr Jelinek
petr@2ndquadrant.com
In reply to: Peter Eisentraut (#36)
Re: Logical Replication WIP

On 07/09/16 02:56, Peter Eisentraut wrote:

Review of 0002-Add-SUBSCRIPTION-catalog-and-DDL.patch:

Similar concerns as before about ALTER syntax, e.g., does ALTER
SUBSCRIPTION ... PUBLICATION add to or replace the publication set?

It sets.

For that matter, why is there no way to add?

Didn't seem all that useful, the expectation here is that most
subscriptions will use one couple of publications.

I think we should allow creating subscriptions initally without
publications. This could be useful for example to test connections,
or create slots before later on adding publications. Seeing that
there is support for changing the publications later, this shouldn't
be a problem.

Sure, but they need to be created disabled then.

Larger conceptual issues:

I haven't read the rest of the code yet to understand why
pg_subscription needs to be a shared catalog, but be that as it may, I
would still make it so that subscription names appear local to the
database. We already have the database OID in the pg_subscription
catalog, so I would make the key (subname, subdatid). DDL commands
would only operate on subscriptions in the current database (or you
could use "datname"."subname" syntax), and \drs would only show
subscriptions in the current database. That way the shared catalog is
an implementation detail that can be changed in the future. I think
it would be very helpful for users if publications and subscriptions
appear to work in a parallel way. If I have two databases that I want
to replicate between two servers, I might want to have a publication
"mypub" in each database and a subscription "mysub" in each database.
If I learn that the subscriptions can't be named that way, then I have
to go back to rename to the publications, and it'll all be a bit
frustrating.

Okay that makes sense. The pg_subscription is shared catalog so that we
can have one launcher per cluster instead one per database. Otherwise
there is no reason why it could not behave like local catalog.

Some thoughts on pg_dump and such:

Even an INITIALLY DISABLED subscription needs network access to create
the replication slot. So restoring a dump when the master is not
available will have some difficulties. And restoring master and slave
at the same time (say disaster recovery) will not necessarily work
well either. Also, the general idea of doing network access during a
backup restore without obvious prior warning sounds a bit unsafe.

I imagine maybe having three states for subscriptions: DISABLED,
PREPARED, ENABLED (to use existing key words). DISABLED just exists
in the catalog, PREPARED has the slots set up, ENABLED starts
replicating. So you can restore a dump with all slots disabled. And
then it might be good to have a command to "prepare" or "enable" all
subscriptions at once.

Well the DISABLED keyword is also used in alter to stop the subscription
but not remove it, that would not longer map well if we used the
behavior you described. That being said I agree with the idea of having
subscription that exists just locally in catalog, we just need to figure
out better naming.

As I had mentioned privately before, I would perhaps have CREATE
SUBSCRIPTION use the foreign server infrastructure for storing
connection information.

Hmm, yeah it's an idea. My worry there is that it will make it bit more
complex to setup as user will have to first create server and user
mapping before creating subscription.

We'll have to keep thinking about ways to handle abandonded
replication slots. I imagine that people will want to create
subscriber instances in fully automated ways. If that fails every so
often and requires manual cleanup of replication slots on the master
some of the time, that will get messy. I don't have well-formed ideas
about this, though.

Yes it's potential issue, don't have good solution for it either. It's
loosely coupled system so we can't have 100% control over everything.

--
Petr Jelinek 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

#38Erik Rijkers
er@xs4all.nl
In reply to: Petr Jelinek (#22)
Re: Logical Replication WIP

On 2016-08-31 22:51, Petr Jelinek wrote:

and one more version with bug fixes, improved code docs and couple

I am not able to get the replication to work. Would you (or anyone) be
so kind to point out what I am doing wrong?

Patches applied, compiled, make-checked, installed OK.

I have 2 copies compiled and installed, logical_replication and
logical_replication2, to be publisher and subscriber, ports 6972 and
6973 respectively.

( BTW, there is no postgres user; OS user is 'aardvark'. 'aardvark is
also db superuser, and
it is also the user as which the two installations are installed. )

PGPASSFILE is set up and works for both instances.

both pg_hba.conf's changed to have:
local replication aardvark md5

instances.sh
--------------------------------------------------------------------
#!/bin/sh
project1=logical_replication # publisher
project2=logical_replication2 # subscriber
pg_stuff_dir=$HOME/pg_stuff
PATH1=$pg_stuff_dir/pg_installations/pgsql.$project1/bin:$PATH
PATH2=$pg_stuff_dir/pg_installations/pgsql.$project2/bin:$PATH
server_dir1=$pg_stuff_dir/pg_installations/pgsql.$project1
server_dir2=$pg_stuff_dir/pg_installations/pgsql.$project2
port1=6972
port2=6973
data_dir1=$server_dir1/data
data_dir2=$server_dir2/data
options1="
-c wal_level=logical
-c max_replication_slots=10
-c max_worker_processes=12
-c max_logical_replication_workers=10
-c max_wal_senders=10
-c logging_collector=on
-c log_directory=$server_dir1
-c log_filename=logfile.${project1} "

options2="
-c wal_level=logical
-c max_replication_slots=10
-c max_worker_processes=12
-c max_logical_replication_workers=10
-c max_wal_senders=10
-c logging_collector=on
-c log_directory=$server_dir2
-c log_filename=logfile.${project2} "

# start two instances:
export PATH=$PATH1; postgres -D $data_dir1 -p $port1 ${options1} &

export PATH=$PATH2; postgres -D $data_dir2 -p $port2 ${options2} &
--------------------------------------------------------------------

Both instances run fine.

On publisher db:
Create a table testt, with 20 rows.

CREATE PUBLICATION pub1 FOR TABLE testt ;
No problem.

On Subscriber db:
CREATE SUBSCRIPTION sub1 WITH CONNECTION 'host=/tmp dbname=testdb
port=6972' PUBLICATION pub1 INITIALLY DISABLED;
ALTER SUBSCRIPTION sub1 enable;

Adding rows to the table (publisher-side) gets activity going. I give
the resulting logs of both sides:

Logfile publisher side:
[...]
2016-09-07 13:47:44.287 CEST 21995 LOG: logical replication launcher
started
2016-09-07 13:51:42.601 CEST 22141 LOG: logical decoding found
consistent point at 0/230F478
2016-09-07 13:51:42.601 CEST 22141 DETAIL: There are no running
transactions.
2016-09-07 13:51:42.601 CEST 22141 LOG: exported logical decoding
snapshot: "00000702-1" with 0 transaction IDs
2016-09-07 13:52:11.326 CEST 22144 LOG: starting logical decoding for
slot "sub1"
2016-09-07 13:52:11.326 CEST 22144 DETAIL: streaming transactions
committing after 0/230F4B0, reading WAL from 0/230F478
2016-09-07 13:52:11.326 CEST 22144 LOG: logical decoding found
consistent point at 0/230F478
2016-09-07 13:52:11.326 CEST 22144 DETAIL: There are no running
transactions.
2016-09-07 13:53:47.012 CEST 22144 LOG: could not receive data from
client: Connection reset by peer
2016-09-07 13:53:47.012 CEST 22144 LOG: unexpected EOF on standby
connection
2016-09-07 13:53:47.025 CEST 22185 LOG: starting logical decoding for
slot "sub1"
2016-09-07 13:53:47.025 CEST 22185 DETAIL: streaming transactions
committing after 0/230F628, reading WAL from 0/230F5F0
2016-09-07 13:53:47.025 CEST 22185 LOG: logical decoding found
consistent point at 0/230F5F0
2016-09-07 13:53:47.025 CEST 22185 DETAIL: There are no running
transactions.
2016-09-07 13:53:47.030 CEST 22185 LOG: could not receive data from
client: Connection reset by peer
2016-09-07 13:53:47.030 CEST 22185 LOG: unexpected EOF on standby
connection
2016-09-07 13:53:52.044 CEST 22188 LOG: starting logical decoding for
slot "sub1"
2016-09-07 13:53:52.044 CEST 22188 DETAIL: streaming transactions
committing after 0/230F628, reading WAL from 0/230F5F0
2016-09-07 13:53:52.044 CEST 22188 LOG: logical decoding found
consistent point at 0/230F5F0
2016-09-07 13:53:52.044 CEST 22188 DETAIL: There are no running
transactions.
2016-09-07 13:53:52.195 CEST 22188 LOG: could not receive data from
client: Connection reset by peer
2016-09-07 13:53:52.195 CEST 22188 LOG: unexpected EOF on standby
connection
(repeat every few seconds)

Logfile subscriber-side:
[...]
2016-09-07 13:47:44.441 CEST 21997 LOG: MultiXact member wraparound
protections are now enabled
2016-09-07 13:47:44.528 CEST 21986 LOG: database system is ready to
accept connections
2016-09-07 13:47:44.529 CEST 22002 LOG: logical replication launcher
started
2016-09-07 13:52:11.319 CEST 22143 LOG: logical replication apply for
subscription sub1 started
2016-09-07 13:53:47.010 CEST 22143 ERROR: could not open relation with
OID 0
2016-09-07 13:53:47.012 CEST 21986 LOG: worker process: logical
replication worker 24048 (PID 22143) exited with exit code 1
2016-09-07 13:53:47.018 CEST 22184 LOG: logical replication apply for
subscription sub1 started
2016-09-07 13:53:47.028 CEST 22184 ERROR: could not open relation with
OID 0
2016-09-07 13:53:47.030 CEST 21986 LOG: worker process: logical
replication worker 24048 (PID 22184) exited with exit code 1
2016-09-07 13:53:52.041 CEST 22187 LOG: logical replication apply for
subscription sub1 started
2016-09-07 13:53:52.045 CEST 22187 ERROR: could not open relation with
OID 0
2016-09-07 13:53:52.046 CEST 21986 LOG: worker process: logical
replication worker 24048 (PID 22187) exited with exit code 1
(repeat every few seconds)

Any hints welcome.

Thanks!

Erik Rijkers

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

#39Petr Jelinek
petr@2ndquadrant.com
In reply to: Erik Rijkers (#38)
Re: Logical Replication WIP

Hi,

On 07/09/16 14:10, Erik Rijkers wrote:

On 2016-08-31 22:51, Petr Jelinek wrote:

and one more version with bug fixes, improved code docs and couple

I am not able to get the replication to work. Would you (or anyone) be
so kind to point out what I am doing wrong?

Patches applied, compiled, make-checked, installed OK.

I have 2 copies compiled and installed, logical_replication and
logical_replication2, to be publisher and subscriber, ports 6972 and
6973 respectively.

Logfile subscriber-side:
[...]
2016-09-07 13:47:44.441 CEST 21997 LOG: MultiXact member wraparound
protections are now enabled
2016-09-07 13:47:44.528 CEST 21986 LOG: database system is ready to
accept connections
2016-09-07 13:47:44.529 CEST 22002 LOG: logical replication launcher
started
2016-09-07 13:52:11.319 CEST 22143 LOG: logical replication apply for
subscription sub1 started
2016-09-07 13:53:47.010 CEST 22143 ERROR: could not open relation with
OID 0
2016-09-07 13:53:47.012 CEST 21986 LOG: worker process: logical
replication worker 24048 (PID 22143) exited with exit code 1
2016-09-07 13:53:47.018 CEST 22184 LOG: logical replication apply for
subscription sub1 started
2016-09-07 13:53:47.028 CEST 22184 ERROR: could not open relation with
OID 0
2016-09-07 13:53:47.030 CEST 21986 LOG: worker process: logical
replication worker 24048 (PID 22184) exited with exit code 1
2016-09-07 13:53:52.041 CEST 22187 LOG: logical replication apply for
subscription sub1 started
2016-09-07 13:53:52.045 CEST 22187 ERROR: could not open relation with
OID 0
2016-09-07 13:53:52.046 CEST 21986 LOG: worker process: logical
replication worker 24048 (PID 22187) exited with exit code 1
(repeat every few seconds)

It means the tables don't exist on subscriber. I added check and proper
error message in my local dev branch, it will be part of the next update.

--
Petr Jelinek 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

#40Petr Jelinek
petr@2ndquadrant.com
In reply to: Peter Eisentraut (#36)
6 attachment(s)
Re: Logical Replication WIP

Hi,

Updated version, this should address most of the things in Peter's
reviews so far, not all though as some of it needs more discussion.

Changes:
- I moved the publication.c to pg_publication.c, subscription.c to
pg_subscription.c.
- changed \drp and \drs to \dRp and \dRs
- fixed definitions of the catalogs (BKI_ROWTYPE_OID)
- changed some GetPublication calls to get_publication_name
- fixed getObjectIdentityParts for OCLASS_PUBLICATION_REL
- fixed get_object_address_publication_rel
- fixed the dependencies between pkeys and publications, for this I
actually had to add new interface to depenency.c that allows dropping
single dependency
- fixed the 'for all tables' and 'for tables all in schema' publications
- changed the alter publication from FOR to SET
- added more test cases for the publication DDL
- fixed compilation of subscription patch alone and docs
- changed subpublications to name[]
- added check for publication list duplicates
- made the subscriptions behave more like they are inside the database
instead of shared catalog (even though the catalog is still shared)
- added options for for CREATE SUBSCRIPTION to optionally not create
slot and not do the initial data sync - that should solve the complaint
about CREATE SUBSCRIPTION always connecting
- the CREATE SUBSCRIPTION also tries to check if the specified
connection connects back to same db (although that check is somewhat
imperfect) and if it gets stuck on create slot it should be normally
cancelable (that should solve the issue Steve Singer had)
- fixed the tests to work in any timezone
- added DDL regress tests for subscription
- added proper detection of missing schemas and tables on subscriber
- rebased on top of 19acee8 as the DefElem changes broke the patch

The table sync is still far from ready.

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

Attachments:

0001-Add-PUBLICATION-catalogs-and-DDL.patch.gzapplication/gzip; name=0001-Add-PUBLICATION-catalogs-and-DDL.patch.gzDownload
0002-Add-SUBSCRIPTION-catalog-and-DDL.patch.gzapplication/gzip; name=0002-Add-SUBSCRIPTION-catalog-and-DDL.patch.gzDownload
0003-Define-logical-replication-protocol-and-output-plugi.patch.gzapplication/gzip; name=0003-Define-logical-replication-protocol-and-output-plugi.patch.gzDownload
0004-Make-libpqwalreceiver-reentrant.patch.gzapplication/gzip; name=0004-Make-libpqwalreceiver-reentrant.patch.gzDownload
0005-Add-logical-replication-workers.patch.gzapplication/gzip; name=0005-Add-logical-replication-workers.patch.gzDownload
0006-Logical-replication-support-for-initial-data-copy.patch.gzapplication/gzip; name=0006-Logical-replication-support-for-initial-data-copy.patch.gzDownload
#41Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Petr Jelinek (#22)
Re: Logical Replication WIP

Review of 0003-Define-logical-replication-protocol-and-output-plugi.patch:

(This is still based on the Aug 31 patch set, but at quick glance I
didn't see any significant changes in the Sep 8 set.)

Generally, this all seems mostly fine. Everything is encapsulated
well enough that problems are localized and any tweaks don't affect
the overall work.

Changes needed to build:

--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -2158,8 +2158,8 @@ <title>Logical Streaming Replication
Parameters</title>
      <listitem>
             <para>
             Comma separated list of publication names for which to
subscribe
-       (receive changes). See
-       <xref linkend="logical-replication-publication"> for more info.
+       (receive changes). <!-- See
+       <xref linkend="logical-replication-publication"> for more info. -->
            </para>
          </listitem>
       </varlistentry>
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -25,6 +25,7 @@
 #include "utils/builtins.h"
 #include "utils/inval.h"
 #include "utils/memutils.h"
+#include "utils/syscache.h"

PG_MODULE_MAGIC;

This is all fixed in later patches.

AFAICT, pgoutput does not use libpq, so the mentions in
src/backend/replication/pgoutput/Makefile are not needed (perhaps
copied from libpqwalreceiver?).

The start_replication option pg_version option is not documented and
not used in any later patch. We can probably do without it and just
rely on the protocol version.

In pgoutput_startup(), you check opt->output_type. But it is not set
anywhere. Actually, the startup callback is supposed to set it
itself.

In init_rel_sync_cache(), the way hash_flags is set seems kind of
weird. I think that variable could be removed and the flags put
directly into the hash_create() call.

pgoutput_config.c seems over-engineered, e.g., converting cstring to
Datum and back. Just do normal DefElem list parsing in pgoutput.c.
That's not pretty either, but at least it's a common coding pattern.

In the protocol documentation, explain the meaning of int64 as a
commit timestamp.

Also, the documentation should emphasize more clearly that all the
messages are not actually top-level protocol messages but are
contained inside binary copy data.

On the actual protocol messages:

Why do strings have a length byte? That is not how other strings in
the protocol work. As a minor side-effect, this would limit for
example column names to 255 characters.

The message structure doesn't match the documentation in some ways.
For example Attributes and TupleData are not separate messages but are
contained in Relation and Insert/Update/Delete messages. So the
documentation needs to be structured a bit differently.

In the Attributes message (or actually Relation message), we don't
need the 'A' and 'C' bytes.

I'm not sure that pgoutput should concern itself with the client
encoding. The client encoding should already be set by the initial
FE/BE protocol handshake. I haven't checked that further yet, so it
might already work, or it should be made to work that way, or I might
be way off.

Slight abuse of pqformat functions. We're not composing messages
using pq_beginmessage()/pq_endmessage(), and we're not using
pq_getmsgend() when reading. The "proper" way to do this is probably
to define a custom set of PQcommMethods. (low priority)

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#42Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Peter Eisentraut (#36)
Re: Logical Replication WIP

On 9/6/16 8:56 PM, Peter Eisentraut wrote:

Some thoughts on pg_dump and such:

Another issue to add to this list:

With the current patch set, pg_dump will fail for unprivileged users,
because it can't read pg_subscription. The include_subscription flag
ought to be checked in getSubscriptions() already, not (only) in
dumpSubscription(). The test suite for pg_dump fails because of this.

We might make further changes in this area, per ongoing discussion, but
it would be good to put in a quick fix for this in the next patch set so
that the global test suite doesn't fail.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#43Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Petr Jelinek (#40)
Re: Logical Replication WIP

Review of 0004-Make-libpqwalreceiver-reentrant.patch:

This looks like a good change.

typo: _PG_walreceirver_conn_init

For libpqrcv_create_slot(), slotname should be const char *.
Similarly, for slotname in libpqrcv_startstreaming*() and conninfo in
libpqrcv_connect(). (the latter two pre-existing)

The connection handle should record in libpqrcv_connect() whether a
connection is a logical or physical replication stream. Then that
parameter doesn't have to be passed around later (or at least some
asserts could double-check it).

In libpqrcv_connect(), the new argument connname is actually just the
application name, for which in later patches the subscription name is
passed in. Does this have a deeper meaning, or should we call the
argument appname to avoid introducing another term?

New function libpqrcv_create_slot(): Hardcoded cmd length (hmm, other
functions do that too), should used StringInfo. ereport instead of
elog. No newline at the end of error message, since PQerrorMessage()
already supplies it. Typo "could not crate". Briefly document return
value.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#44Petr Jelinek
petr@2ndquadrant.com
In reply to: Peter Eisentraut (#41)
Re: Logical Replication WIP

On 09/09/16 06:33, Peter Eisentraut wrote:

Review of 0003-Define-logical-replication-protocol-and-output-plugi.patch:

(This is still based on the Aug 31 patch set, but at quick glance I
didn't see any significant changes in the Sep 8 set.)

Yep.

The start_replication option pg_version option is not documented and
not used in any later patch. We can probably do without it and just
rely on the protocol version.

That's leftover from binary type data transfer which is not part of this
initial approach as it adds a lot of complications to both protocol and
apply side. So yes can do without.

In pgoutput_startup(), you check opt->output_type. But it is not set
anywhere. Actually, the startup callback is supposed to set it
itself.

Leftover from pglogical which actually supports both output types.

In init_rel_sync_cache(), the way hash_flags is set seems kind of
weird. I think that variable could be removed and the flags put
directly into the hash_create() call.

Eh, yes no idea how that came to be.

pgoutput_config.c seems over-engineered, e.g., converting cstring to
Datum and back. Just do normal DefElem list parsing in pgoutput.c.
That's not pretty either, but at least it's a common coding pattern.

Yes now that we have only couple of options I agree.

In the protocol documentation, explain the meaning of int64 as a
commit timestamp.

You mean that it's milliseconds since postgres epoch?

On the actual protocol messages:

Why do strings have a length byte? That is not how other strings in
the protocol work. As a minor side-effect, this would limit for
example column names to 255 characters.

Because I originally sent them without the null termination but I guess
they don't really need it anymore. (the 255 char limit is not really
important in practice given the column length is limited to 64
characters anyway)

The message structure doesn't match the documentation in some ways.
For example Attributes and TupleData are not separate messages but are
contained in Relation and Insert/Update/Delete messages. So the
documentation needs to be structured a bit differently.

In the Attributes message (or actually Relation message), we don't
need the 'A' and 'C' bytes.

Hmm okay will look into it. I guess if we remove the 'A' then rest of
the Attribute message neatly merges into the Relation message. The more
interesting part will be the TupleData as it's common part of other
messages.

I'm not sure that pgoutput should concern itself with the client
encoding. The client encoding should already be set by the initial
FE/BE protocol handshake. I haven't checked that further yet, so it
might already work, or it should be made to work that way, or I might
be way off.

Yes, I think you are right, that was there mostly for same reason as the
pg_version.

Slight abuse of pqformat functions. We're not composing messages
using pq_beginmessage()/pq_endmessage(), and we're not using
pq_getmsgend() when reading. The "proper" way to do this is probably
to define a custom set of PQcommMethods. (low priority)

If we change that, I'd probably rather go with direct use of StringInfo
functions.

--
Petr Jelinek 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

#45Andres Freund
andres@anarazel.de
In reply to: Petr Jelinek (#44)
Re: Logical Replication WIP

On 2016-09-12 21:47:08 +0200, Petr Jelinek wrote:

On 09/09/16 06:33, Peter Eisentraut wrote:

The start_replication option pg_version option is not documented and
not used in any later patch. We can probably do without it and just
rely on the protocol version.

That's leftover from binary type data transfer which is not part of this
initial approach as it adds a lot of complications to both protocol and
apply side. So yes can do without.

FWIW, I don't think we can leave this out of the initial protocol
design. We don't have to implement it, but it has to be part of the
design.

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

#46Petr Jelinek
petr@2ndquadrant.com
In reply to: Andres Freund (#45)
Re: Logical Replication WIP

On 12/09/16 21:54, Andres Freund wrote:

On 2016-09-12 21:47:08 +0200, Petr Jelinek wrote:

On 09/09/16 06:33, Peter Eisentraut wrote:

The start_replication option pg_version option is not documented and
not used in any later patch. We can probably do without it and just
rely on the protocol version.

That's leftover from binary type data transfer which is not part of this
initial approach as it adds a lot of complications to both protocol and
apply side. So yes can do without.

FWIW, I don't think we can leave this out of the initial protocol
design. We don't have to implement it, but it has to be part of the
design.

I don't think it's a good idea to have unimplemented parts of protocol,
we have protocol version so it can be added in v2 when we have code that
is able to handle it.

--
Petr Jelinek 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

#47Andres Freund
andres@anarazel.de
In reply to: Petr Jelinek (#46)
Re: Logical Replication WIP

On 2016-09-12 21:57:39 +0200, Petr Jelinek wrote:

On 12/09/16 21:54, Andres Freund wrote:

On 2016-09-12 21:47:08 +0200, Petr Jelinek wrote:

On 09/09/16 06:33, Peter Eisentraut wrote:

The start_replication option pg_version option is not documented and
not used in any later patch. We can probably do without it and just
rely on the protocol version.

That's leftover from binary type data transfer which is not part of this
initial approach as it adds a lot of complications to both protocol and
apply side. So yes can do without.

FWIW, I don't think we can leave this out of the initial protocol
design. We don't have to implement it, but it has to be part of the
design.

I don't think it's a good idea to have unimplemented parts of protocol, we
have protocol version so it can be added in v2 when we have code that is
able to handle it.

I don't think we have to have it part of the protocol. But it has to be
forseen, otherwise introducing it later will end up requiring more
invasive changes than acceptable. I don't want to repeat the "libpq v3
protocol" evolution story here.

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

#48Petr Jelinek
petr@2ndquadrant.com
In reply to: Andres Freund (#47)
Re: Logical Replication WIP

On 12/09/16 22:21, Andres Freund wrote:

On 2016-09-12 21:57:39 +0200, Petr Jelinek wrote:

On 12/09/16 21:54, Andres Freund wrote:

On 2016-09-12 21:47:08 +0200, Petr Jelinek wrote:

On 09/09/16 06:33, Peter Eisentraut wrote:

The start_replication option pg_version option is not documented and
not used in any later patch. We can probably do without it and just
rely on the protocol version.

That's leftover from binary type data transfer which is not part of this
initial approach as it adds a lot of complications to both protocol and
apply side. So yes can do without.

FWIW, I don't think we can leave this out of the initial protocol
design. We don't have to implement it, but it has to be part of the
design.

I don't think it's a good idea to have unimplemented parts of protocol, we
have protocol version so it can be added in v2 when we have code that is
able to handle it.

I don't think we have to have it part of the protocol. But it has to be
forseen, otherwise introducing it later will end up requiring more
invasive changes than acceptable. I don't want to repeat the "libpq v3
protocol" evolution story here.

Oh sure, I don't see that as big problem, the TupleData already contains
type of the data it sends (to distinguish between nulls and text data)
so that's mostly about adding some different type there and we'll also
need type info in the column part of the Relation message but that
should be easy to fence with one if for different protocol version.

--
Petr Jelinek 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

#49Craig Ringer
craig@2ndquadrant.com
In reply to: Petr Jelinek (#48)
Re: Logical Replication WIP

On 13 September 2016 at 06:03, Petr Jelinek <petr@2ndquadrant.com> wrote:

Oh sure, I don't see that as big problem, the TupleData already contains
type of the data it sends (to distinguish between nulls and text data) so
that's mostly about adding some different type there and we'll also need
type info in the column part of the Relation message but that should be easy
to fence with one if for different protocol version.

The missing piece seems to be negotiation.

If a binary-aware client connects to a non-binary aware server, the
non-binary-aware server needs a way to say "you requested this option
I don't understand, go away" or "you asked for binary but I don't
support that".

--
Craig Ringer 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

#50Petr Jelinek
petr@2ndquadrant.com
In reply to: Craig Ringer (#49)
Re: Logical Replication WIP

On 13/09/16 02:55, Craig Ringer wrote:

On 13 September 2016 at 06:03, Petr Jelinek <petr@2ndquadrant.com> wrote:

Oh sure, I don't see that as big problem, the TupleData already contains
type of the data it sends (to distinguish between nulls and text data) so
that's mostly about adding some different type there and we'll also need
type info in the column part of the Relation message but that should be easy
to fence with one if for different protocol version.

The missing piece seems to be negotiation.

If a binary-aware client connects to a non-binary aware server, the
non-binary-aware server needs a way to say "you requested this option
I don't understand, go away" or "you asked for binary but I don't
support that".

Not sure what you mean by negotiation. Why would that be needed? You
know server version when you connect and when you know that you also
know what capabilities that version of Postgres has. If you send
unrecognized option you get corresponding error.

--
Petr Jelinek 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

#51Andres Freund
andres@anarazel.de
In reply to: Petr Jelinek (#40)
Re: Logical Replication WIP

Hi,

First read through the current version. Hence no real architectural
comments.

On 2016-09-09 00:59:26 +0200, Petr Jelinek wrote:

diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
new file mode 100644
index 0000000..e0c719d
--- /dev/null
+++ b/src/backend/commands/publicationcmds.c
@@ -0,0 +1,761 @@
+/*-------------------------------------------------------------------------
+ *
+ * publicationcmds.c
+ *		publication manipulation
+ *
+ * Copyright (c) 2015, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		publicationcmds.c

Not that I'm a fan of this line in the first place, but usually it does
include the path.

+static void
+check_replication_permissions(void)
+{
+	if (!superuser() && !has_rolreplication(GetUserId()))
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 (errmsg("must be superuser or replication role to manipulate publications"))));
+}

Do we want to require owner privileges for replication roles? I'd say
no, but want to raise the question.

+ObjectAddress
+CreatePublication(CreatePublicationStmt *stmt)
+{
+	Relation	rel;
+	ObjectAddress myself;
+	Oid			puboid;
+	bool		nulls[Natts_pg_publication];
+	Datum		values[Natts_pg_publication];
+	HeapTuple	tup;
+	bool		replicate_insert_given;
+	bool		replicate_update_given;
+	bool		replicate_delete_given;
+	bool		replicate_insert;
+	bool		replicate_update;
+	bool		replicate_delete;
+
+	check_replication_permissions();
+
+	rel = heap_open(PublicationRelationId, RowExclusiveLock);
+
+	/* Check if name is used */
+	puboid = GetSysCacheOid1(PUBLICATIONNAME, CStringGetDatum(stmt->pubname));
+	if (OidIsValid(puboid))
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("publication \"%s\" already exists",
+						stmt->pubname)));
+	}
+
+	/* Form a tuple. */
+	memset(values, 0, sizeof(values));
+	memset(nulls, false, sizeof(nulls));
+
+	values[Anum_pg_publication_pubname - 1] =
+		DirectFunctionCall1(namein, CStringGetDatum(stmt->pubname));
+
+	parse_publication_options(stmt->options,
+							  &replicate_insert_given, &replicate_insert,
+							  &replicate_update_given, &replicate_update,
+							  &replicate_delete_given, &replicate_delete);
+
+	values[Anum_pg_publication_puballtables - 1] =
+		BoolGetDatum(stmt->for_all_tables);
+	values[Anum_pg_publication_pubreplins - 1] =
+		BoolGetDatum(replicate_insert);
+	values[Anum_pg_publication_pubreplupd - 1] =
+		BoolGetDatum(replicate_update);
+	values[Anum_pg_publication_pubrepldel - 1] =
+		BoolGetDatum(replicate_delete);
+
+	tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+	/* Insert tuple into catalog. */
+	puboid = simple_heap_insert(rel, tup);
+	CatalogUpdateIndexes(rel, tup);
+	heap_freetuple(tup);
+
+	ObjectAddressSet(myself, PublicationRelationId, puboid);
+
+	/* Make the changes visible. */
+	CommandCounterIncrement();
+
+	if (stmt->tables)
+	{
+		List	   *rels;
+
+		Assert(list_length(stmt->tables) > 0);
+
+		rels = GatherTableList(stmt->tables);
+		PublicationAddTables(puboid, rels, true, NULL);
+		CloseTables(rels);
+	}
+	else if (stmt->for_all_tables || stmt->schema)
+	{
+		List	   *rels;
+
+		rels = GatherTables(stmt->schema);
+		PublicationAddTables(puboid, rels, true, NULL);
+		CloseTables(rels);
+	}

Isn't this (and ALTER) racy? What happens if tables are concurrently
created? This session wouldn't necessarily see the tables, and other
sessions won't see for_all_tables/schema. Evaluating
for_all_tables/all_in_schema when the publication is used, would solve
that problem.

+/*
+ * Gather all tables optinally filtered by schema name.
+ * The gathered tables are locked in access share lock mode.
+ */
+static List *
+GatherTables(char *nspname)
+{
+	Oid			nspid = InvalidOid;
+	List	   *rels = NIL;
+	Relation	rel;
+	SysScanDesc scan;
+	ScanKeyData key[1];
+	HeapTuple	tup;
+
+	/* Resolve and validate the schema if specified */
+	if (nspname)
+	{
+		nspid = LookupExplicitNamespace(nspname, false);
+		if (IsSystemNamespace(nspid) || IsToastNamespace(nspid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("only tables in user schemas can be added to publication"),
+					 errdetail("%s is a system schema", strVal(nspname))));
+	}

Why are we restricting pg_catalog here? There's a bunch of extensions
creating objects therein, and we allow that. Seems better to just rely
on the IsSystemClass check for that below.

+/*
+ * Gather Relations based o provided by RangeVar list.
+ * The gathered tables are locked in access share lock mode.
+ */

Why access share? Shouldn't we make this ShareUpdateExclusive or
similar, to prevent schema changes?

+static List *
+GatherTableList(List *tables)
+{
+	List	   *relids = NIL;
+	List	   *rels = NIL;
+	ListCell   *lc;
+
+	/*
+	 * Open, share-lock, and check all the explicitly-specified relations
+	 */
+	foreach(lc, tables)
+	{
+		RangeVar   *rv = lfirst(lc);
+		Relation	rel;
+		bool		recurse = interpretInhOption(rv->inhOpt);
+		Oid			myrelid;
+
+		rel = heap_openrv(rv, AccessShareLock);
+		myrelid = RelationGetRelid(rel);
+		/* don't throw error for "foo, foo" */
+		if (list_member_oid(relids, myrelid))
+		{
+			heap_close(rel, AccessShareLock);
+			continue;
+		}
+		rels = lappend(rels, rel);
+		relids = lappend_oid(relids, myrelid);
+
+		if (recurse)
+		{
+			ListCell   *child;
+			List	   *children;
+
+			children = find_all_inheritors(myrelid, AccessShareLock,
+										   NULL);
+
+			foreach(child, children)
+			{
+				Oid			childrelid = lfirst_oid(child);
+
+				if (list_member_oid(relids, childrelid))
+					continue;
+
+				/* find_all_inheritors already got lock */
+				rel = heap_open(childrelid, NoLock);
+				rels = lappend(rels, rel);
+				relids = lappend_oid(relids, childrelid);
+			}
+		}
+	}

Hm, can't this yield duplicates, when both an inherited and a top level
relation are specified?

@@ -713,6 +714,25 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
ObjectAddressSet(address, RelationRelationId, relationId);

/*
+	 * If the newly created relation is a table and there are publications
+	 * which were created as FOR ALL TABLES, we want to add the relation
+	 * membership to those publications.
+	 */
+
+	if (relkind == RELKIND_RELATION)
+	{
+		List	   *pubids = GetAllTablesPublications();
+		ListCell   *lc;
+
+		foreach(lc, pubids)
+		{
+			Oid	pubid = lfirst_oid(lc);
+
+			publication_add_relation(pubid, rel, false);
+		}
+	}
+

Hm, this has the potential to noticeably slow down table creation.

+publication_opt_item:
+			IDENT
+				{
+					/*
+					 * We handle identifiers that aren't parser keywords with
+					 * the following special-case codes, to avoid bloating the
+					 * size of the main parser.
+					 */
+					if (strcmp($1, "replicate_insert") == 0)
+						$$ = makeDefElem("replicate_insert",
+										 (Node *)makeInteger(TRUE), @1);
+					else if (strcmp($1, "noreplicate_insert") == 0)
+						$$ = makeDefElem("replicate_insert",
+										 (Node *)makeInteger(FALSE), @1);
+					else if (strcmp($1, "replicate_update") == 0)
+						$$ = makeDefElem("replicate_update",
+										 (Node *)makeInteger(TRUE), @1);
+					else if (strcmp($1, "noreplicate_update") == 0)
+						$$ = makeDefElem("replicate_update",
+										 (Node *)makeInteger(FALSE), @1);
+					else if (strcmp($1, "replicate_delete") == 0)
+						$$ = makeDefElem("replicate_delete",
+										 (Node *)makeInteger(TRUE), @1);
+					else if (strcmp($1, "noreplicate_delete") == 0)
+						$$ = makeDefElem("replicate_delete",
+										 (Node *)makeInteger(FALSE), @1);
+					else
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("unrecognized publication option \"%s\"", $1),
+									 parser_errposition(@1)));
+				}
+		;

I'm kind of inclined to do this checking at execution (or transform)
time instead. That allows extension to add options, and handle them in
utility hooks.

+
+/* ----------------
+ *		pg_publication_rel definition.  cpp turns this into
+ *		typedef struct FormData_pg_publication_rel
+ *
+ * ----------------
+ */
+#define PublicationRelRelationId				6106
+
+CATALOG(pg_publication_rel,6106)
+{
+	Oid		pubid;				/* Oid of the publication */
+	Oid		relid;				/* Oid of the relation */
+} FormData_pg_publication_rel;

Hm. Do we really want this to have an oid? Won't that significantly,
especially if multiple publications are present, increase our oid
consumption? It seems entirely sufficient to identify rows in here
using (pubid, relid).

+ObjectAddress
+CreateSubscription(CreateSubscriptionStmt *stmt)
+{
+	Relation	rel;
+	ObjectAddress myself;
+	Oid			subid;
+	bool		nulls[Natts_pg_subscription];
+	Datum		values[Natts_pg_subscription];
+	HeapTuple	tup;
+	bool		enabled_given;
+	bool		enabled;
+	char	   *conninfo;
+	List	   *publications;
+
+	check_subscription_permissions();
+
+	rel = heap_open(SubscriptionRelationId, RowExclusiveLock);
+
+	/* Check if name is used */
+	subid = GetSysCacheOid2(SUBSCRIPTIONNAME, MyDatabaseId,
+							CStringGetDatum(stmt->subname));
+	if (OidIsValid(subid))
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("subscription \"%s\" already exists",
+						stmt->subname)));
+	}
+
+	/* Parse and check options. */
+	parse_subscription_options(stmt->options, &enabled_given, &enabled,
+							   &conninfo, &publications);
+
+	/* TODO: improve error messages here. */
+	if (conninfo == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("connection not specified")));

Probably also makes sense to parse the conninfo here to verify it looks
saen. Although that's fairly annoying to do, because the relevant code
is libpq :(

diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 65230e2..f3d54c8 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c

I think you might be missing outfuncs support.

+
+CATALOG(pg_subscription,6100) BKI_SHARED_RELATION BKI_ROWTYPE_OID(6101) BKI_SCHEMA_MACRO
+{
+	Oid			subdbid;			/* Database the subscription is in. */
+	NameData	subname;		/* Name of the subscription */
+	bool		subenabled;		/* True if the subsription is enabled (running) */

Not sure what "running" means here.

+#ifdef CATALOG_VARLEN			/* variable-length fields start here */
+	text		subconninfo;	/* Connection string to the provider */
+	NameData	subslotname;	/* Slot name on provider */
+
+	name		subpublications[1];	/* List of publications subscribed to */
+#endif
+} FormData_pg_subscription;
+    <varlistentry>
+     <term>
+      publication_names
+     </term>
+     <listitem>
+      <para>
+       Comma separated list of publication names for which to subscribe
+       (receive changes). See
+       <xref linkend="logical-replication-publication"> for more info.
+      </para>
+     </listitem>
+    </varlistentry>
+   </variablelist>

Do we need to specify an escaping scheme here?

+  <para>
+   Every DML message contains arbitraty relation id, which can be mapped to

Typo: "arbitraty"

+<listitem>
+<para>
+                Commit timestamp of the transaction.
+</para>
+</listitem>
+</varlistentry>

Perhaps mention it's relative to postgres epoch?

+<variablelist>
+<varlistentry>
+<term>
+        Byte1('O')
+</term>
+<listitem>
+<para>
+                Identifies the message as an origin message.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int64
+</term>
+<listitem>
+<para>
+                The LSN of the commit on the origin server.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int8
+</term>
+<listitem>
+<para>
+                Length of the origin name (including the NULL-termination
+                character).
+</para>
+</listitem>
+</varlistentry>

Should this explain that there could be mulitple origin messages (when
replay switched origins during an xact)?

+<para>
+                Relation name.
+</para>
+</listitem>
+</varlistentry>
+</variablelist>
+
+</para>
+
+<para>
+This message is always followed by Attributes message.
+</para>

What's the point of having this separate from the relation message?

+<varlistentry>
+<term>
+        Byte1('C')
+</term>
+<listitem>
+<para>
+                Start of column block.
+</para>
+</listitem>

"block"?

+</varlistentry><varlistentry>
+<term>
+        Int8
+</term>
+<listitem>
+<para>
+                Flags for the column. Currently can be either 0 for no flags
+                or one which marks the column as part of the key.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int8
+</term>
+<listitem>
+<para>
+                Length of column name (including the NULL-termination
+                character).
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        String
+</term>
+<listitem>
+<para>
+                Name of the column.
+</para>
+</listitem>
+</varlistentry>

Huh, no type information?

+<varlistentry>
+<term>
+        Byte1('O')
+</term>
+<listitem>
+<para>
+                Identifies the following TupleData message as the old tuple
+                (deleted tuple).
+</para>
+</listitem>
+</varlistentry>

Should we discern between old key and old tuple?

+#define IS_REPLICA_IDENTITY 1

Defining this in the c file doesn't seem particularly useful?

+/*
+ * Read transaction BEGIN from the stream.
+ */
+void
+logicalrep_read_begin(StringInfo in, XLogRecPtr *remote_lsn,
+					  TimestampTz *committime, TransactionId *remote_xid)
+{
+	/* read fields */
+	*remote_lsn = pq_getmsgint64(in);
+	Assert(*remote_lsn != InvalidXLogRecPtr);
+	*committime = pq_getmsgint64(in);
+	*remote_xid = pq_getmsgint(in, 4);
+}

In network exposed stuff it seems better not to use assert, and error
out instead.

+/*
+ * Write UPDATE to the output stream.
+ */
+void
+logicalrep_write_update(StringInfo out, Relation rel, HeapTuple oldtuple,
+					   HeapTuple newtuple)
+{
+	pq_sendbyte(out, 'U');		/* action UPDATE */
+
+	/* use Oid as relation identifier */
+	pq_sendint(out, RelationGetRelid(rel), 4);

Wonder if there's a way that could screw us. What happens if there's an
oid wraparound, and a relation is dropped? Then a new relation could end
up with same id. Maybe answered somewhere further down.

+/*
+ * Write a tuple to the outputstream, in the most efficient format possible.
+ */
+static void
+logicalrep_write_tuple(StringInfo out, Relation rel, HeapTuple tuple)
+{
+	/* Write the values */
+	for (i = 0; i < desc->natts; i++)
+	{
+		outputstr =	OidOutputFunctionCall(typclass->typoutput, values[i]);

Odd spacing.

+/*
+ * Initialize this plugin
+ */
+static void
+pgoutput_startup(LogicalDecodingContext * ctx, OutputPluginOptions *opt,
+				  bool is_init)
+{
+	PGOutputData   *data = palloc0(sizeof(PGOutputData));
+	int				client_encoding;
+
+	/* Create our memory context for private allocations. */
+	data->context = AllocSetContextCreate(ctx->context,
+										  "logical replication output context",
+										  ALLOCSET_DEFAULT_MINSIZE,
+										  ALLOCSET_DEFAULT_INITSIZE,
+										  ALLOCSET_DEFAULT_MAXSIZE);
+
+	ctx->output_plugin_private = data;
+
+	/*
+	 * This is replication start and not slot initialization.
+	 *
+	 * Parse and validate options passed by the client.
+	 */
+	if (!is_init)
+	{
+		/* We can only do binary */
+		if (opt->output_type != OUTPUT_PLUGIN_BINARY_OUTPUT)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("only binary mode is supported for logical replication protocol")));

Shouldn't you just set
opt->output_type = OUTPUT_PLUGIN_BINARY_OUTPUT;
or is the goal just to output a better message?

+
+/*
+ * COMMIT callback
+ */
+static void
+pgoutput_commit_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
+					 XLogRecPtr commit_lsn)
+{
+	OutputPluginPrepareWrite(ctx, true);
+	logicalrep_write_commit(ctx->out, txn, commit_lsn);
+	OutputPluginWrite(ctx, true);
+}

Hm, so we don't reset the context for these...

+/*
+ * Sends the decoded DML over wire.
+ */
+static void
+pgoutput_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
+				Relation relation, ReorderBufferChange *change)
+{
+	/* Avoid leaking memory by using and resetting our own context */
+	old = MemoryContextSwitchTo(data->context);
+
+	/*
+	 * Write the relation schema if the current schema haven't been sent yet.
+	 */
+	if (!relentry->schema_sent)
+	{
+		OutputPluginPrepareWrite(ctx, false);
+		logicalrep_write_rel(ctx->out, relation);
+		OutputPluginWrite(ctx, false);
+		relentry->schema_sent = true;
+	}
+
+	/* Send the data */
+	switch (change->action)
+	{

...

+	/* Cleanup */
+	MemoryContextSwitchTo(old);
+	MemoryContextReset(data->context);
+}

IIRC there were some pfree's in called functions. It's probably better
to remove those and rely on this.

+/*
+ * Load publications from the list of publication names.
+ */
+static List *
+LoadPublications(List *pubnames)
+{
+	List	   *result = NIL;
+	ListCell   *lc;
+
+	foreach (lc, pubnames)
+	{
+		char		   *pubname = (char *) lfirst(lc);
+		Publication	   *pub = GetPublicationByName(pubname, false);
+
+		result = lappend(result, pub);
+	}
+
+	return result;
+}

Why are we doing this eagerly? On systems with a lot of relations
this'll suck up a fair amount of memory, without much need?

+/*
+ * Remove all the entries from our relation cache.
+ */
+static void
+destroy_rel_sync_cache(void)
+{
+	HASH_SEQ_STATUS		status;
+	RelationSyncEntry  *entry;
+
+	if (RelationSyncCache == NULL)
+		return;
+
+	hash_seq_init(&status, RelationSyncCache);
+
+	while ((entry = (RelationSyncEntry *) hash_seq_search(&status)) != NULL)
+	{
+		if (hash_search(RelationSyncCache, (void *) &entry->relid,
+						HASH_REMOVE, NULL) == NULL)
+			elog(ERROR, "hash table corrupted");
+	}
+
+	RelationSyncCache = NULL;
+}

Any reason not to just destroy the hash table instead?

+enum {
+	PARAM_UNRECOGNISED,
+	PARAM_PROTOCOL_VERSION,
+	PARAM_ENCODING,
+	PARAM_PG_VERSION,
+	PARAM_PUBLICATION_NAMES,
+} OutputPluginParamKey;
+
+typedef struct {
+	const char * const paramname;
+	int	paramkey;
+} OutputPluginParam;
+
+/* Oh, if only C had switch on strings */
+static OutputPluginParam param_lookup[] = {
+	{"proto_version", PARAM_PROTOCOL_VERSION},
+	{"encoding", PARAM_ENCODING},
+	{"pg_version", PARAM_PG_VERSION},
+	{"publication_names", PARAM_PUBLICATION_NAMES},
+	{NULL, PARAM_UNRECOGNISED}
+};
+
+
+/*
+ * Read parameters sent by client at startup and store recognised
+ * ones in the parameters PGOutputData.
+ *
+ * The data must have all client-supplied parameter fields zeroed,
+ * such as by memset or palloc0, since values not supplied
+ * by the client are not set.
+ */
+void
+pgoutput_process_parameters(List *options, PGOutputData *data)
+{
+	ListCell	*lc;
+
+	/* Examine all the other params in the message. */
+	foreach(lc, options)
+	{
+		DefElem    *elem = lfirst(lc);
+		Datum		val;
+
+		Assert(elem->arg == NULL || IsA(elem->arg, String));
+
+		/* Check each param, whether or not we recognise it */
+		switch(get_param_key(elem->defname))
+		{
+			case PARAM_PROTOCOL_VERSION:
+				val = get_param_value(elem, OUTPUT_PARAM_TYPE_UINT32, false);
+				data->protocol_version = DatumGetUInt32(val);
+				break;
+
+			case PARAM_ENCODING:
+				val = get_param_value(elem, OUTPUT_PARAM_TYPE_STRING, false);
+				data->client_encoding = DatumGetCString(val);
+				break;
+
+			case PARAM_PG_VERSION:
+				val = get_param_value(elem, OUTPUT_PARAM_TYPE_UINT32, false);
+				data->client_pg_version = DatumGetUInt32(val);
+				break;
+
+			case PARAM_PUBLICATION_NAMES:
+				val = get_param_value(elem, OUTPUT_PARAM_TYPE_STRING, false);
+				if (!SplitIdentifierString(DatumGetCString(val), ',',
+										   &data->publication_names))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_NAME),
+							 errmsg("invalid publication name syntax")));
+
+				break;
+
+			default:
+				ereport(ERROR,
+						(errmsg("Unrecognised pgoutput parameter %s",
+								elem->defname)));
+				break;
+		}
+	}
+}
+
+/*
+ * Look up a param name to find the enum value for the
+ * param, or PARAM_UNRECOGNISED if not found.
+ */
+static int
+get_param_key(const char * const param_name)
+{
+	OutputPluginParam *param = &param_lookup[0];
+
+	do {
+		if (strcmp(param->paramname, param_name) == 0)
+			return param->paramkey;
+		param++;
+	} while (param->paramname != NULL);
+
+	return PARAM_UNRECOGNISED;
+}

I'm not following why this isn't just one routine with a chain of
else if (strmcp() == 0)
blocks?

From 2241471aec03de553126c2d5fc012fcba1ecf50d Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Wed, 6 Jul 2016 13:59:23 +0200
Subject: [PATCH 4/6] Make libpqwalreceiver reentrant

---
.../libpqwalreceiver/libpqwalreceiver.c | 328 ++++++++++++++-------
src/backend/replication/walreceiver.c | 67 +++--
src/include/replication/walreceiver.h | 75 +++--
3 files changed, 306 insertions(+), 164 deletions(-)

diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index f1c843e..5da4474 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -25,6 +25,7 @@
#include "miscadmin.h"
#include "replication/walreceiver.h"
#include "utils/builtins.h"
+#include "utils/pg_lsn.h"

#ifdef HAVE_POLL_H
#include <poll.h>
@@ -38,62 +39,83 @@

PG_MODULE_MAGIC;

-void		_PG_init(void);
+struct WalReceiverConnHandle {
+	/* Current connection to the primary, if any */
+	PGconn *streamConn;
+	/* Buffer for currently read records */
+	char   *recvBuf;
+};

newline before {

-/* Current connection to the primary, if any */
-static PGconn *streamConn = NULL;
-
-/* Buffer for currently read records */
-static char *recvBuf = NULL;

Yuck, this indeed seems better.

/*
- * Module load callback
+ * Module initialization callback
*/
-void
-_PG_init(void)
+WalReceiverConnHandle *
+_PG_walreceirver_conn_init(WalReceiverConnAPI *wrcapi)
{
-	/* Tell walreceiver how to reach us */
-	if (walrcv_connect != NULL || walrcv_identify_system != NULL ||
-		walrcv_readtimelinehistoryfile != NULL ||
-		walrcv_startstreaming != NULL || walrcv_endstreaming != NULL ||
-		walrcv_receive != NULL || walrcv_send != NULL ||
-		walrcv_disconnect != NULL)
-		elog(ERROR, "libpqwalreceiver already loaded");
-	walrcv_connect = libpqrcv_connect;
-	walrcv_get_conninfo = libpqrcv_get_conninfo;
-	walrcv_identify_system = libpqrcv_identify_system;
-	walrcv_readtimelinehistoryfile = libpqrcv_readtimelinehistoryfile;
-	walrcv_startstreaming = libpqrcv_startstreaming;
-	walrcv_endstreaming = libpqrcv_endstreaming;
-	walrcv_receive = libpqrcv_receive;
-	walrcv_send = libpqrcv_send;
-	walrcv_disconnect = libpqrcv_disconnect;
+	WalReceiverConnHandle *handle;
+
+	handle = palloc0(sizeof(WalReceiverConnHandle));
+
+	/* Tell caller how to reach us */
+	wrcapi->connect = libpqrcv_connect;
+	wrcapi->get_conninfo = libpqrcv_get_conninfo;
+	wrcapi->identify_system = libpqrcv_identify_system;
+	wrcapi->readtimelinehistoryfile = libpqrcv_readtimelinehistoryfile;
+	wrcapi->create_slot = libpqrcv_create_slot;
+	wrcapi->startstreaming_physical = libpqrcv_startstreaming_physical;
+	wrcapi->startstreaming_logical = libpqrcv_startstreaming_logical;
+	wrcapi->endstreaming = libpqrcv_endstreaming;
+	wrcapi->receive = libpqrcv_receive;
+	wrcapi->send = libpqrcv_send;
+	wrcapi->disconnect = libpqrcv_disconnect;
+
+	return handle;
}

This however I'm not following. Why do we need multiple copies of this?
And why aren't we doing the assignments in _PG_init? Seems better to
just allocate one WalRcvCalllbacks globally and assign all these as
constants. Then the establishment function can just return all these
(as part of a bigger struct).

(skipped logical rep docs)

diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index 8acdff1..34007d3 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -54,11 +54,13 @@
&alterOperatorClass;
&alterOperatorFamily;
&alterPolicy;
+   &alterPublication;
&alterRole;
&alterRule;
&alterSchema;
&alterSequence;
&alterServer;
+   &alterSubscription;
&alterSystem;
&alterTable;
&alterTableSpace;
@@ -100,11 +102,13 @@
&createOperatorClass;
&createOperatorFamily;
&createPolicy;
+   &createPublication;
&createRole;
&createRule;
&createSchema;
&createSequence;
&createServer;
+   &createSubscription;
&createTable;
&createTableAs;
&createTableSpace;
@@ -144,11 +148,13 @@
&dropOperatorFamily;
&dropOwned;
&dropPolicy;
+   &dropPublication;
&dropRole;
&dropRule;
&dropSchema;
&dropSequence;
&dropServer;
+   &dropSubscription;
&dropTable;
&dropTableSpace;
&dropTSConfig;

Hm, shouldn't all these have been registered in the earlier patch?

diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index d29d3f9..f2052b8 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c

This sure is a lot of yanking around of previously added code. At least
some of it looks like it should really have been part of the earlier
commit.

@@ -327,6 +431,18 @@ DropSubscriptionById(Oid subid)
{
Relation	rel;
HeapTuple	tup;
+	Datum		datum;
+	bool		isnull;
+	char	   *subname;
+	char	   *conninfo;
+	char	   *slotname;
+	RepOriginId	originid;
+	MemoryContext			tmpctx,
+							oldctx;
+	WalReceiverConnHandle  *wrchandle = NULL;
+	WalReceiverConnAPI	   *wrcapi = NULL;
+	walrcvconn_init_fn		walrcvconn_init;
+	LogicalRepWorker	   *worker;

check_subscription_permissions();

@@ -337,9 +453,135 @@ DropSubscriptionById(Oid subid)
if (!HeapTupleIsValid(tup))
elog(ERROR, "cache lookup failed for subscription %u", subid);

+	/*
+	 * Create temporary memory context to keep copy of subscription
+	 * info needed later in the execution.
+	 */
+	tmpctx = AllocSetContextCreate(TopMemoryContext,
+										  "DropSubscription Ctx",
+										  ALLOCSET_DEFAULT_MINSIZE,
+										  ALLOCSET_DEFAULT_INITSIZE,
+										  ALLOCSET_DEFAULT_MAXSIZE);
+	oldctx = MemoryContextSwitchTo(tmpctx);
+
+	/* Get subname */
+	datum = SysCacheGetAttr(SUBSCRIPTIONOID, tup,
+							Anum_pg_subscription_subname, &isnull);
+	Assert(!isnull);
+	subname = pstrdup(NameStr(*DatumGetName(datum)));
+
+	/* Get conninfo */
+	datum = SysCacheGetAttr(SUBSCRIPTIONOID, tup,
+							Anum_pg_subscription_subconninfo, &isnull);
+	Assert(!isnull);
+	conninfo = pstrdup(TextDatumGetCString(datum));
+
+	/* Get slotname */
+	datum = SysCacheGetAttr(SUBSCRIPTIONOID, tup,
+							Anum_pg_subscription_subslotname, &isnull);
+	Assert(!isnull);
+	slotname = pstrdup(NameStr(*DatumGetName(datum)));
+
+	MemoryContextSwitchTo(oldctx);
+
+	/* Remove the tuple from catalog. */
simple_heap_delete(rel, &tup->t_self);
-	ReleaseSysCache(tup);
+	/* Protect against launcher restarting the worker. */
+	LWLockAcquire(LogicalRepLauncherLock, LW_EXCLUSIVE);
-	heap_close(rel, RowExclusiveLock);
+	/* Kill the apply worker so that the slot becomes accessible. */
+	LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
+	worker = logicalrep_worker_find(subid);
+	if (worker)
+		logicalrep_worker_stop(worker);
+	LWLockRelease(LogicalRepWorkerLock);
+
+	/* Wait for apply process to die. */
+	for (;;)
+	{
+		int	rc;
+
+		CHECK_FOR_INTERRUPTS();
+
+		LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
+		if (logicalrep_worker_count(subid) < 1)
+		{
+			LWLockRelease(LogicalRepWorkerLock);
+			break;
+		}
+		LWLockRelease(LogicalRepWorkerLock);
+
+		/* Wait for more work. */
+		rc = WaitLatch(&MyProc->procLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+					   1000L);
+
+		/* emergency bailout if postmaster has died */
+		if (rc & WL_POSTMASTER_DEATH)
+			proc_exit(1);
+
+		ResetLatch(&MyProc->procLatch);
+	}

I'm really far from convinced this is the right layer to perform these
operations. Previously these routines were low level catalog
manipulation routines. Now they're certainly not.

+ /* Remove the origin trakicking. */

typo.

+	/*
+	 * Now that the catalog update is done, try to reserve slot at the
+	 * provider node using replication connection.
+	 */
+	wrcapi = palloc0(sizeof(WalReceiverConnAPI));
+
+	walrcvconn_init = (walrcvconn_init_fn)
+		load_external_function("libpqwalreceiver",
+							   "_PG_walreceirver_conn_init", false, NULL);
+
+	if (walrcvconn_init == NULL)
+		elog(ERROR, "libpqwalreceiver does not declare _PG_walreceirver_conn_init symbol");

This does rather reinforce my opinion that the _PG_init removal in
libpqwalreceiver isn't useful.

diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 699c934..fc998cd 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -93,6 +93,9 @@ struct BackgroundWorkerHandle

static BackgroundWorkerArray *BackgroundWorkerData;

+/* Enables registration of internal background workers. */
+bool internal_bgworker_registration_in_progress = false;
+
/*
* Calculate shared memory needed.
*/
@@ -745,7 +748,8 @@ RegisterBackgroundWorker(BackgroundWorker *worker)
ereport(DEBUG1,
(errmsg("registering background worker \"%s\"", worker->bgw_name)));
-	if (!process_shared_preload_libraries_in_progress)
+	if (!process_shared_preload_libraries_in_progress &&
+		!internal_bgworker_registration_in_progress)
{
if (!IsUnderPostmaster)
ereport(LOG,

Ugh.

/*
+ * Register internal background workers.
+ *
+ * This is here mainly because the permanent bgworkers are normally allowed
+ * to be registered only when share preload libraries are loaded which does
+ * not work for the internal ones.
+ */
+static void
+register_internal_bgworkers(void)
+{
+	internal_bgworker_registration_in_progress = true;
+
+	/* Register the logical replication worker launcher if appropriate. */
+	if (!IsBinaryUpgrade && max_logical_replication_workers > 0)
+	{
+		BackgroundWorker bgw;
+
+		bgw.bgw_flags =	BGWORKER_SHMEM_ACCESS |
+			BGWORKER_BACKEND_DATABASE_CONNECTION;
+		bgw.bgw_start_time = BgWorkerStart_RecoveryFinished;
+		bgw.bgw_main = ApplyLauncherMain;
+		snprintf(bgw.bgw_name, BGW_MAXLEN,
+				 "logical replication launcher");
+		bgw.bgw_restart_time = 5;
+		bgw.bgw_notify_pid = 0;
+		bgw.bgw_main_arg = (Datum) 0;
+
+		RegisterBackgroundWorker(&bgw);
+	}
+
+	internal_bgworker_registration_in_progress = false;
+}

Who says these flags are right for everyone? If we indeed want to go
through bgworkers here, I think you'll have to generallize this a bit,
so we don't check for max_logical_replication_workers and such here. We
could e.g. have the shared memory sizing hooks set up a chain of
registrations.

-static void
+static char *
libpqrcv_identify_system(WalReceiverConnHandle *handle,
-						 TimeLineID *primary_tli)
+						 TimeLineID *primary_tli,
+						 char **dbname)
{
+	char	   *sysid;
PGresult   *res;
-	char	   *primary_sysid;
-	char		standby_sysid[32];
/*
* Get the system identifier and timeline ID as a DataRow message from the
@@ -231,24 +234,19 @@ libpqrcv_identify_system(WalReceiverConnHandle *handle,
errdetail("Could not identify system: got %d rows and %d fields, expected %d rows and %d or more fields.",
ntuples, nfields, 3, 1)));
}
-	primary_sysid = PQgetvalue(res, 0, 0);
+	sysid = pstrdup(PQgetvalue(res, 0, 0));
*primary_tli = pg_atoi(PQgetvalue(res, 0, 1), 4, 0);
-
-	/*
-	 * Confirm that the system identifier of the primary is the same as ours.
-	 */
-	snprintf(standby_sysid, sizeof(standby_sysid), UINT64_FORMAT,
-			 GetSystemIdentifier());
-	if (strcmp(primary_sysid, standby_sysid) != 0)
+	if (dbname)
{
-		primary_sysid = pstrdup(primary_sysid);
-		PQclear(res);
-		ereport(ERROR,
-				(errmsg("database system identifier differs between the primary and standby"),
-				 errdetail("The primary's identifier is %s, the standby's identifier is %s.",
-						   primary_sysid, standby_sysid)));
+		if (PQgetisnull(res, 0, 3))
+			*dbname = NULL;
+		else
+			*dbname = pstrdup(PQgetvalue(res, 0, 3));
}
+
PQclear(res);
+
+	return sysid;
}

/*
@@ -274,7 +272,7 @@ libpqrcv_create_slot(WalReceiverConnHandle *handle, char *slotname,

if (PQresultStatus(res) != PGRES_TUPLES_OK)
{
-		elog(FATAL, "could not crate replication slot \"%s\": %s\n",
+		elog(ERROR, "could not crate replication slot \"%s\": %s\n",
slotname, PQerrorMessage(handle->streamConn));
}

@@ -287,6 +285,28 @@ libpqrcv_create_slot(WalReceiverConnHandle *handle, char *slotname,
return snapshot;
}

+/*
+ * Drop replication slot.
+ */
+static void
+libpqrcv_drop_slot(WalReceiverConnHandle *handle, char *slotname)
+{
+	PGresult	   *res;
+	char			cmd[256];
+
+	snprintf(cmd, sizeof(cmd),
+			 "DROP_REPLICATION_SLOT \"%s\"", slotname);
+
+	res = libpqrcv_PQexec(handle, cmd);
+
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		elog(ERROR, "could not drop replication slot \"%s\": %s\n",
+			 slotname, PQerrorMessage(handle->streamConn));
+	}
+
+	PQclear(res);
+}

Given that the earlier commit to libpqwalreciever added a lot of this
information, it doesn't seem right to change it again here.

+typedef struct LogicalRepRelMapEntry {

early {

Ok, running out of time. See you soon I guess ;)

Andres

--
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@anarazel.de
In reply to: Petr Jelinek (#40)
Re: Logical Replication WIP

(continuing, uh, a bit happier)

On 2016-09-09 00:59:26 +0200, Petr Jelinek wrote:

+/*
+ * Relcache invalidation callback for our relation map cache.
+ */
+static void
+logicalreprelmap_invalidate_cb(Datum arg, Oid reloid)
+{
+	LogicalRepRelMapEntry  *entry;
+
+	/* Just to be sure. */
+	if (LogicalRepRelMap == NULL)
+		return;
+
+	if (reloid != InvalidOid)
+	{
+		HASH_SEQ_STATUS status;
+
+		hash_seq_init(&status, LogicalRepRelMap);
+
+		/* TODO, use inverse lookup hastable? */

*hashtable

+		while ((entry = (LogicalRepRelMapEntry *) hash_seq_search(&status)) != NULL)
+		{
+			if (entry->reloid == reloid)
+				entry->reloid = InvalidOid;

can't we break here?

+/*
+ * Initialize the relation map cache.
+ */
+static void
+remoterelmap_init(void)
+{
+	HASHCTL		ctl;
+
+	/* Make sure we've initialized CacheMemoryContext. */
+	if (CacheMemoryContext == NULL)
+		CreateCacheMemoryContext();
+
+	/* Initialize the hash table. */
+	MemSet(&ctl, 0, sizeof(ctl));
+	ctl.keysize = sizeof(uint32);
+	ctl.entrysize = sizeof(LogicalRepRelMapEntry);
+	ctl.hcxt = CacheMemoryContext;

Wonder if this (and similar code earlier) should try to do everything in
a sub-context of CacheMemoryContext instead. That'd make some issues
easier to track down.

+/*
+ * Open the local relation associated with the remote one.
+ */
+static LogicalRepRelMapEntry *
+logicalreprel_open(uint32 remoteid, LOCKMODE lockmode)
+{
+	LogicalRepRelMapEntry  *entry;
+	bool		found;
+
+	if (LogicalRepRelMap == NULL)
+		remoterelmap_init();
+
+	/* Search for existing entry. */
+	entry = hash_search(LogicalRepRelMap, (void *) &remoteid,
+						HASH_FIND, &found);
+
+	if (!found)
+		elog(FATAL, "cache lookup failed for remote relation %u",
+			 remoteid);
+
+	/* Need to update the local cache? */
+	if (!OidIsValid(entry->reloid))
+	{
+		Oid			nspid;
+		Oid			relid;
+		int			i;
+		TupleDesc	desc;
+		LogicalRepRelation *remoterel;
+
+		remoterel = &entry->remoterel;
+
+		nspid = LookupExplicitNamespace(remoterel->nspname, false);
+		if (!OidIsValid(nspid))
+			ereport(FATAL,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("the logical replication target %s not found",
+							quote_qualified_identifier(remoterel->nspname,

remoterel->relname))));

+		relid = get_relname_relid(remoterel->relname, nspid);
+		if (!OidIsValid(relid))
+			ereport(FATAL,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("the logical replication target %s not found",
+							quote_qualified_identifier(remoterel->nspname,
+													   remoterel->relname))));
+
+		entry->rel = heap_open(relid, lockmode);

This seems rather racy. I think this really instead needs something akin
to RangeVarGetRelidExtended().

+/*
+ * Executor state preparation for evaluation of constraint expressions,
+ * indexes and triggers.
+ *
+ * This is based on similar code in copy.c
+ */
+static EState *
+create_estate_for_relation(LogicalRepRelMapEntry *rel)
+{
+	EState	   *estate;
+	ResultRelInfo *resultRelInfo;
+	RangeTblEntry *rte;
+
+	estate = CreateExecutorState();
+
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(rel->rel);
+	rte->relkind = rel->rel->rd_rel->relkind;
+	estate->es_range_table = list_make1(rte);
+
+	resultRelInfo = makeNode(ResultRelInfo);
+	InitResultRelInfo(resultRelInfo, rel->rel, 1, 0);
+
+	estate->es_result_relations = resultRelInfo;
+	estate->es_num_result_relations = 1;
+	estate->es_result_relation_info = resultRelInfo;
+
+	/* Triggers might need a slot */
+	if (resultRelInfo->ri_TrigDesc)
+		estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate);
+
+	return estate;
+}

Ugh, we do this for every single change? That's pretty darn heavy.

+/*
+ * Check if the local attribute is present in relation definition used
+ * by upstream and hence updated by the replication.
+ */
+static bool
+physatt_in_attmap(LogicalRepRelMapEntry *rel, int attid)
+{
+	AttrNumber	i;
+
+	/* Fast path for tables that are same on upstream and downstream. */
+	if (attid < rel->remoterel.natts && rel->attmap[attid] == attid)
+		return true;
+
+	/* Try to find the attribute in the map. */
+	for (i = 0; i < rel->remoterel.natts; i++)
+		if (rel->attmap[i] == attid)
+			return true;
+
+	return false;
+}

Shouldn't we rather try to keep an attribute map that always can map
remote attribute numbers to local ones? That doesn't seem hard on a
first blush? But I might be missing something here.

+/*
+ * Executes default values for columns for which we can't map to remote
+ * relation columns.
+ *
+ * This allows us to support tables which have more columns on the downstream
+ * than on the upsttream.
+ */

Typo: upsttream.

+static void
+FillSlotDefaults(LogicalRepRelMapEntry *rel, EState *estate,
+				 TupleTableSlot *slot)
+{

Why is this using a different naming scheme?

+/*
+ * Handle COMMIT message.
+ *
+ * TODO, support tracking of multiple origins
+ */
+static void
+handle_commit(StringInfo s)
+{
+	XLogRecPtr		commit_lsn;
+	XLogRecPtr		end_lsn;
+	TimestampTz		commit_time;
+
+	logicalrep_read_commit(s, &commit_lsn, &end_lsn, &commit_time);

Perhaps this (and related routines) should rather be
LogicalRepCommitdata commit_data;
logicalrep_read_commit(s, &commit_data);
etc? That way the data can transparently be enhanced.

+	Assert(commit_lsn == replorigin_session_origin_lsn);
+	Assert(commit_time == replorigin_session_origin_timestamp);
+
+	if (IsTransactionState())
+	{
+		FlushPosition *flushpos;
+
+		CommitTransactionCommand();
+		MemoryContextSwitchTo(CacheMemoryContext);
+
+		/* Track commit lsn  */
+		flushpos = (FlushPosition *) palloc(sizeof(FlushPosition));
+		flushpos->local_end = XactLastCommitEnd;
+		flushpos->remote_end = end_lsn;
+
+		dlist_push_tail(&lsn_mapping, &flushpos->node);
+		MemoryContextSwitchTo(ApplyContext);

Seems like it should be in a separate function.

+/*
+ * Handle INSERT message.
+ */
+static void
+handle_insert(StringInfo s)
+{
+	LogicalRepRelMapEntry *rel;
+	LogicalRepTupleData	newtup;
+	LogicalRepRelId		relid;
+	EState			   *estate;
+	TupleTableSlot	   *remoteslot;
+	MemoryContext		oldctx;
+
+	ensure_transaction();
+
+	relid = logicalrep_read_insert(s, &newtup);
+	rel = logicalreprel_open(relid, RowExclusiveLock);
+
+	/* Initialize the executor state. */
+	estate = create_estate_for_relation(rel);
+	remoteslot = ExecInitExtraTupleSlot(estate);
+	ExecSetSlotDescriptor(remoteslot, RelationGetDescr(rel->rel));

This seems incredibly expensive for replicating a lot of rows.

+	/* Process and store remote tuple in the slot */
+	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+	SlotStoreCStrings(remoteslot, newtup.values);
+	FillSlotDefaults(rel, estate, remoteslot);
+	MemoryContextSwitchTo(oldctx);
+
+	PushActiveSnapshot(GetTransactionSnapshot());
+	ExecOpenIndices(estate->es_result_relation_info, false);
+
+	ExecInsert(NULL, /* mtstate is only used for onconflict handling which we don't support atm */
+			   remoteslot,
+			   remoteslot,
+			   NIL,
+			   ONCONFLICT_NONE,
+			   estate,
+			   false);

I have *severe* doubts about just using the (newly) exposed functions
1:1 here.

+/*
+ * Search the relation 'rel' for tuple using the replication index.
+ *
+ * If a matching tuple is found lock it with lockmode, fill the slot with its
+ * contents and return true, return false is returned otherwise.
+ */
+static bool
+tuple_find_by_replidx(Relation rel, LockTupleMode lockmode,
+					  TupleTableSlot *searchslot, TupleTableSlot *slot)
+{
+	HeapTuple		scantuple;
+	ScanKeyData		skey[INDEX_MAX_KEYS];
+	IndexScanDesc	scan;
+	SnapshotData	snap;
+	TransactionId	xwait;
+	Oid				idxoid;
+	Relation		idxrel;
+	bool			found;
+
+	/* Open REPLICA IDENTITY index.*/
+	idxoid = RelationGetReplicaIndex(rel);
+	if (!OidIsValid(idxoid))
+	{
+		elog(ERROR, "could not find configured replica identity for table \"%s\"",
+			 RelationGetRelationName(rel));
+		return false;
+	}
+	idxrel = index_open(idxoid, RowExclusiveLock);
+
+	/* Start an index scan. */
+	InitDirtySnapshot(snap);
+	scan = index_beginscan(rel, idxrel, &snap,
+						   RelationGetNumberOfAttributes(idxrel),
+						   0);
+
+	/* Build scan key. */
+	build_replindex_scan_key(skey, rel, idxrel, searchslot);
+
+retry:
+	found = false;
+
+	index_rescan(scan, skey, RelationGetNumberOfAttributes(idxrel), NULL, 0);
+
+	/* Try to find the tuple */
+	if ((scantuple = index_getnext(scan, ForwardScanDirection)) != NULL)
+	{
+		found = true;
+		ExecStoreTuple(scantuple, slot, InvalidBuffer, false);
+		ExecMaterializeSlot(slot);
+
+		xwait = TransactionIdIsValid(snap.xmin) ?
+			snap.xmin : snap.xmax;
+
+		/*
+		 * If the tuple is locked, wait for locking transaction to finish
+		 * and retry.
+		 */
+		if (TransactionIdIsValid(xwait))
+		{
+			XactLockTableWait(xwait, NULL, NULL, XLTW_None);
+			goto retry;
+		}
+	}

Hm. So we potentially find multiple tuples here, and lock all of
them. but then only use one for the update.

+static List *
+get_subscription_list(void)
+{
+	List	   *res = NIL;
+	Relation	rel;
+	HeapScanDesc scan;
+	HeapTuple	tup;
+	MemoryContext resultcxt;
+
+	/* This is the context that we will allocate our output data in */
+	resultcxt = CurrentMemoryContext;
+
+	/*
+	 * Start a transaction so we can access pg_database, and get a snapshot.
+	 * We don't have a use for the snapshot itself, but we're interested in
+	 * the secondary effect that it sets RecentGlobalXmin.  (This is critical
+	 * for anything that reads heap pages, because HOT may decide to prune
+	 * them even if the process doesn't attempt to modify any tuples.)
+	 */
+	StartTransactionCommand();
+	(void) GetTransactionSnapshot();
+
+	rel = heap_open(SubscriptionRelationId, AccessShareLock);
+	scan = heap_beginscan_catalog(rel, 0, NULL);
+
+	while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
+	{
+		Form_pg_subscription subform = (Form_pg_subscription) GETSTRUCT(tup);
+		Subscription   *sub;
+		MemoryContext	oldcxt;
+
+		/*
+		 * Allocate our results in the caller's context, not the
+		 * transaction's. We do this inside the loop, and restore the original
+		 * context at the end, so that leaky things like heap_getnext() are
+		 * not called in a potentially long-lived context.
+		 */
+		oldcxt = MemoryContextSwitchTo(resultcxt);
+
+		sub = (Subscription *) palloc(sizeof(Subscription));
+		sub->oid = HeapTupleGetOid(tup);
+		sub->dbid = subform->subdbid;
+		sub->enabled = subform->subenabled;
+
+		/* We don't fill fields we are not intereste in. */
+		sub->name = NULL;
+		sub->conninfo = NULL;
+		sub->slotname = NULL;
+		sub->publications = NIL;
+
+		res = lappend(res, sub);
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	heap_endscan(scan);
+	heap_close(rel, AccessShareLock);
+
+	CommitTransactionCommand();

Hm. this doesn't seem quite right from a locking pov. What if, in the
middle of this, a new subscription is created?

+void
+logicalrep_worker_stop(LogicalRepWorker *worker)
+{
+	Assert(LWLockHeldByMe(LogicalRepWorkerLock));
+
+	/* Check that the worker is up and what we expect. */
+	if (!worker->proc)
+		return;
+	if (!IsBackendPid(worker->proc->pid))
+		return;
+
+	/* Terminate the worker. */
+	kill(worker->proc->pid, SIGTERM);
+
+	LWLockRelease(LogicalRepLauncherLock);
+
+	/* Wait for it to detach. */
+	for (;;)
+	{
+		int	rc = WaitLatch(&MyProc->procLatch,
+						   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+						   1000L);
+
+        /* emergency bailout if postmaster has died */
+        if (rc & WL_POSTMASTER_DEATH)
+			proc_exit(1);
+
+        ResetLatch(&MyProc->procLatch);
+
+		CHECK_FOR_INTERRUPTS();
+
+		if (!worker->proc)
+			return;
+	}
+}

indentation here seems scfrewed.

+static void
+xacthook_signal_launcher(XactEvent event, void *arg)
+{
+	switch (event)
+	{
+		case XACT_EVENT_COMMIT:
+			if (xacthook_do_signal_launcher)
+				ApplyLauncherWakeup();
+			break;
+		default:
+			/* We're not interested in other tx events */
+			break;
+	}
+}
+void
+ApplyLauncherWakeupOnCommit(void)
+{
+	if (!xacthook_do_signal_launcher)
+	{
+		RegisterXactCallback(xacthook_signal_launcher, NULL);
+		xacthook_do_signal_launcher = true;
+	}
+}

Hm. This seems like it really should be an AtCommit_* routine instead.
This also needs more docs.

Hadn't I previously read about always streaming data to disk first?

@@ -0,0 +1,674 @@
+/*-------------------------------------------------------------------------
+ * tablesync.c
+ *	   PostgreSQL logical replication
+ *
+ * Copyright (c) 2012-2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/replication/logical/tablesync.c
+ *
+ * NOTES
+ *	  This file contains code for initial table data synchronization for
+ *	  logical replication.
+ *
+ *    The initial data synchronization is done separately for each table,
+ *    in separate apply worker that only fetches the initial snapshot data
+ *    from the provider and then synchronizes the position in stream with
+ *    the main apply worker.

Why? I guess that's because it allows to incrementally add tables, with
acceptable overhead.

+ *    The stream position synchronization works in multiple steps.
+ *     - sync finishes copy and sets table state as SYNCWAIT and waits
+ *       for state to change in a loop
+ *     - apply periodically checks unsynced tables for SYNCWAIT, when it
+ *       appears it will compare its position in the stream with the
+ *       SYNCWAIT position and decides to either set it to CATCHUP when
+ *       the apply was infront (and wait for the sync to do the catchup),
+ *       or set the state to SYNCDONE if the sync was infront or in case
+ *       both sync and apply are at the same position it will set it to
+ *       READY and stops tracking it

I'm not quite following here.

+ *     - if the state was set to CATCHUP sync will read the stream and
+ *       apply changes until it catches up to the specified stream
+ *       position and then sets state to READY and signals apply that it
+ *       can stop waiting and exits, if the state was set to something
+ *       else than CATCHUP the sync process will simply end
+ *     - if the state was set to SYNCDONE by apply, the apply will
+ *       continue tracking the table until it reaches the SYNCDONE stream
+ *       position at which point it sets state to READY and stops tracking
+ *
+ *    Example flows look like this:
+ *     - Apply is infront:
+ *	      sync:8   -> set SYNCWAIT
+ *        apply:10 -> set CATCHUP
+ *        sync:10  -> set ready
+ *          exit
+ *        apply:10
+ *          stop tracking
+ *          continue rep
+ *    - Sync infront:
+ *        sync:10
+ *          set SYNCWAIT
+ *        apply:8
+ *          set SYNCDONE
+ *        sync:10
+ *          exit
+ *        apply:10
+ *          set READY
+ *          stop tracking
+ *          continue rep

This definitely needs to be expanded a bit. Where are we tracking how
far replication has progressed on individual tables? Are we creating new
slots for syncing? Is there any parallelism in syncing?

+/*
+ * Exit routine for synchronization worker.
+ */
+static void
+finish_sync_worker(char *slotname)
+{
+	LogicalRepWorker   *worker;
+	RepOriginId			originid;
+	MemoryContext		oldctx = CurrentMemoryContext;
+
+	/*
+	 * Drop the replication slot on remote server.
+	 * We want to continue even in the case that the slot on remote side
+	 * is already gone. This means that we can leave slot on the remote
+	 * side but that can happen for other reasons as well so we can't
+	 * really protect against that.
+	 */
+	PG_TRY();
+	{
+		wrcapi->drop_slot(wrchandle, slotname);
+	}
+	PG_CATCH();
+	{
+		MemoryContext	ectx;
+		ErrorData	   *edata;
+
+		ectx = MemoryContextSwitchTo(oldctx);
+		/* Save error info */
+		edata = CopyErrorData();
+		MemoryContextSwitchTo(ectx);
+		FlushErrorState();
+
+		ereport(WARNING,
+				(errmsg("there was problem dropping the replication slot "
+						"\"%s\" on provider", slotname),
+				 errdetail("The error was: %s", edata->message),
+				 errhint("You may have to drop it manually")));
+		FreeErrorData(edata);

ISTM we really should rather return success/failure here, and not throw
an error inside the libpqwalreceiver stuff. I kind of wonder if we
actually can get rid of this indirection.

+	/* Find the main apply worker and signal it. */
+	LWLockAcquire(LogicalRepWorkerLock, LW_EXCLUSIVE);
+	worker = logicalrep_worker_find(MyLogicalRepWorker->subid, InvalidOid);
+	if (worker && worker->proc)
+		SetLatch(&worker->proc->procLatch);
+	LWLockRelease(LogicalRepWorkerLock);

I'd rather do the SetLatch outside of the critical section.

+static bool
+wait_for_sync_status_change(TableState *tstate)
+{
+	int		rc;
+	char	state = tstate->state;
+
+	while (!got_SIGTERM)
+	{
+		StartTransactionCommand();
+		tstate->state = GetSubscriptionRelState(MyLogicalRepWorker->subid,
+												tstate->relid,
+												&tstate->lsn,
+												true);
+		CommitTransactionCommand();
+
+		/* Status record was removed. */
+		if (tstate->state == SUBREL_STATE_UNKNOWN)
+			return false;
+
+		if (tstate->state != state)
+			return true;
+
+		rc = WaitLatch(&MyProc->procLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+					   10000L);
+
+		/* emergency bailout if postmaster has died */
+		if (rc & WL_POSTMASTER_DEATH)
+			proc_exit(1);
+
+        ResetLatch(&MyProc->procLatch);

broken indentation.

+/*
+ * Read the state of the tables in the subscription and update our table
+ * state list.
+ */
+static void
+reread_sync_state(Oid relid)
+{
+	dlist_mutable_iter	iter;
+	Relation	rel;
+	HeapTuple	tup;
+	ScanKeyData	skey[2];
+	HeapScanDesc	scan;
+
+	/* Clean the old list. */
+	dlist_foreach_modify(iter, &table_states)
+	{
+		TableState *tstate = dlist_container(TableState, node, iter.cur);
+
+		dlist_delete(iter.cur);
+		pfree(tstate);
+	}
+
+	/*
+	 * Fetch all the subscription relation states that are not marked as
+	 * ready and push them into our table state tracking list.
+	 */
+	rel = heap_open(SubscriptionRelRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&skey[0],
+				Anum_pg_subscription_rel_subid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(MyLogicalRepWorker->subid));
+
+	if (OidIsValid(relid))
+	{
+		ScanKeyInit(&skey[1],
+					Anum_pg_subscription_rel_subrelid,
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(relid));
+	}
+	else
+	{
+		ScanKeyInit(&skey[1],
+					Anum_pg_subscription_rel_substate,
+					BTEqualStrategyNumber, F_CHARNE,
+					CharGetDatum(SUBREL_STATE_READY));
+	}
+
+	scan = heap_beginscan_catalog(rel, 2, skey);

Hm. So this is a seqscan. Shouldn't we make this use an index (depending
on which branch is taken above)?

+	while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
+	{
+		Form_pg_subscription_rel	subrel;
+		TableState	   *tstate;
+		MemoryContext	oldctx;
+
+		subrel = (Form_pg_subscription_rel) GETSTRUCT(tup);
+
+		/* Allocate the tracking info in a permament memory context. */

s/permament/permanent/

+/*
+ * Handle table synchronization cooperation from the synchroniation
+ * worker.
+ */
+static void
+process_syncing_tables_sync(char *slotname, XLogRecPtr end_lsn)
+{
+	TableState *tstate;
+	TimeLineID	tli;
+
+	Assert(!IsTransactionState());
+
+	/*
+	 * Synchronization workers don't keep track of all synchronization
+	 * tables, they only care about their table.
+	 */
+	if (!table_states_valid)
+	{
+		StartTransactionCommand();
+		reread_sync_state(MyLogicalRepWorker->relid);
+		CommitTransactionCommand();
+	}
+
+	/* Somebody removed table underneath this worker, nothing more to do. */
+	if (dlist_is_empty(&table_states))
+	{
+		wrcapi->endstreaming(wrchandle, &tli);
+		finish_sync_worker(slotname);
+	}
+
+	/* Check if we are done with catchup now. */
+	tstate = dlist_container(TableState, node, dlist_head_node(&table_states));
+	if (tstate->state == SUBREL_STATE_CATCHUP)
+	{
+		Assert(tstate->lsn != InvalidXLogRecPtr);
+
+		if (tstate->lsn == end_lsn)
+		{
+			tstate->state = SUBREL_STATE_READY;
+			tstate->lsn = InvalidXLogRecPtr;
+			/* Update state of the synchronization. */
+			StartTransactionCommand();
+			SetSubscriptionRelState(MyLogicalRepWorker->subid,
+									tstate->relid, tstate->state,
+									tstate->lsn);
+			CommitTransactionCommand();
+
+			wrcapi->endstreaming(wrchandle, &tli);
+			finish_sync_worker(slotname);
+		}
+		return;
+	}
+}

The return inside the if is a bit weird. Makes one think it might be a
loop or such.

+/*
+ * Handle table synchronization cooperation from the apply worker.
+ */
+static void
+process_syncing_tables_apply(char *slotname, XLogRecPtr end_lsn)
+{
+	dlist_mutable_iter	iter;
+
+	Assert(!IsTransactionState());
+
+	if (!table_states_valid)
+	{
+		StartTransactionCommand();
+		reread_sync_state(InvalidOid);
+		CommitTransactionCommand();
+	}

So this pattern is repeated a bunch of times, maybe we can encapsulate
that somewhat? Maybe like ensure_sync_state_valid() or such?

+	dlist_foreach_modify(iter, &table_states)
+	{
+		TableState *tstate = dlist_container(TableState, node, iter.cur);
+		bool		start_worker;
+		LogicalRepWorker   *worker;
+
+		/*
+		 * When the synchronization process is at the cachup phase we need

s/cachup/catchup/

+		 * to ensure that we are not behind it (it's going to wait at this
+		 * point for the change of state). Once we are infront or at the same
+		 * position as the synchronization proccess we can signal it to
+		 * finish the catchup.
+		 */
+		if (tstate->state == SUBREL_STATE_SYNCWAIT)
+		{
+			if (end_lsn > tstate->lsn)
+			{
+				/*
+				 * Apply is infront, tell sync to catchup. and wait until
+				 * it does.
+				 */
+				tstate->state = SUBREL_STATE_CATCHUP;
+				tstate->lsn = end_lsn;
+				StartTransactionCommand();
+				SetSubscriptionRelState(MyLogicalRepWorker->subid,
+										tstate->relid, tstate->state,
+										tstate->lsn);
+				CommitTransactionCommand();
+
+				/* Signal the worker as it may be waiting for us. */
+				LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
+				worker = logicalrep_worker_find(MyLogicalRepWorker->subid,
+												tstate->relid);
+				if (worker && worker->proc)
+					SetLatch(&worker->proc->procLatch);
+				LWLockRelease(LogicalRepWorkerLock);

Different parts of this file use different lock level to set the
latch. Why?

+				if (wait_for_sync_status_change(tstate))
+					Assert(tstate->state == SUBREL_STATE_READY);
+			}
+			else
+			{
+				/*
+				 * Apply is either behind in which case sync worker is done
+				 * but apply needs to keep tracking the table until it
+				 * catches up to where sync finished.
+				 * Or apply and sync are at the same position in which case
+				 * table can be switched to standard replication mode
+				 * immediately.
+				 */
+				if (end_lsn < tstate->lsn)
+					tstate->state = SUBREL_STATE_SYNCDONE;
+				else
+					tstate->state = SUBREL_STATE_READY;
+

What I'm failing to understand is how this can be done under
concurrency. You probably thought about this, but it should really be
explained somewhere.

+				StartTransactionCommand();
+				SetSubscriptionRelState(MyLogicalRepWorker->subid,
+										tstate->relid, tstate->state,
+										tstate->lsn);
+				CommitTransactionCommand();
+
+				/* Signal the worker as it may be waiting for us. */
+				LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
+				worker = logicalrep_worker_find(MyLogicalRepWorker->subid,
+												tstate->relid);
+				if (worker && worker->proc)
+					SetLatch(&worker->proc->procLatch);
+				LWLockRelease(LogicalRepWorkerLock);

Oh, and again, please set latches outside of the lock.

+		else if (tstate->state == SUBREL_STATE_SYNCDONE &&
+				 end_lsn >= tstate->lsn)
+		{
+			/*
+			 * Apply catched up to the position where table sync finished,
+			 * mark the table as ready for normal replication.
+			 */

Sentence needs to be rephrased a bit.

+		/*
+		 * In case table is supposed to be synchronizing but the
+		 * synchronization worker is not running, start it.
+		 * Limit the number of launched workers here to one (for now).
+		 */

Hm. That seems problematic for online upgrade type cases, we might never
be catch up that way...

+/*
+ * Start syncing the table in the sync worker.
+ */
+char *
+LogicalRepSyncTableStart(XLogRecPtr *origin_startpos)
+{
+	StringInfoData	s;
+	TableState		tstate;
+	MemoryContext	oldctx;
+	char		   *slotname;
+
+	/* Check the state of the table synchronization. */
+	StartTransactionCommand();
+	tstate.relid = MyLogicalRepWorker->relid;
+	tstate.state = GetSubscriptionRelState(MySubscription->oid, tstate.relid,
+										   &tstate.lsn, false);
+
+	/*
+	 * Build unique slot name.
+	 * TODO: protect against too long slot name.
+	 */
+	oldctx = MemoryContextSwitchTo(CacheMemoryContext);
+	initStringInfo(&s);
+	appendStringInfo(&s, "%s_sync_%s", MySubscription->slotname,
+					 get_rel_name(tstate.relid));
+	slotname = s.data;

Is this memory freed somewhere?

+				/*
+				 * We want to do the table data sync in single
+				 * transaction so do not close the transaction opened
+				 * above.
+				 * There will be no BEGIN or COMMIT messages coming via
+				 * logical replication while the copy table command is
+				 * running so start the transaction here.
+				 * Note the memory context for data handling will still
+				 * be done using ensure_transaction called by the insert
+				 * handler.
+				 */
+				StartTransactionCommand();
+
+				/*
+				 * Don't allow parallel access other than SELECT while
+				 * the initial contents are being copied.
+				 */
+				rel = heap_open(tstate.relid, ExclusiveLock);

Why do we want to allow access at all?

@@ -87,6 +92,8 @@ _PG_output_plugin_init(OutputPluginCallbacks *cb)
cb->commit_cb = pgoutput_commit_txn;
cb->filter_by_origin_cb = pgoutput_origin_filter;
cb->shutdown_cb = pgoutput_shutdown;
+	cb->tuple_cb = pgoutput_tuple;
+	cb->list_tables_cb = pgoutput_list_tables;
}

What are these new, and undocumented callbacks actually doing? And why
is this integrated into logical decoding?

/*
+ * Handle LIST_TABLES command.
+ */
+static void
+SendTableList(ListTablesCmd *cmd)
+{

Ugh.

I really dislike this kind of command. I think we should instead change
things around, allowing to issue normal SQL via the replication
command. We'll have to error out for running sql for non-database
connected replication connections, but that seems fine.

Andres

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

#53Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Andres Freund (#52)
Re: Logical Replication WIP

On 9/14/16 11:21 AM, Andres Freund wrote:

+ ExecInsert(NULL, /* mtstate is only used for onconflict handling which we don't support atm */

+			   remoteslot,
+			   remoteslot,
+			   NIL,
+			   ONCONFLICT_NONE,
+			   estate,
+			   false);

I have *severe* doubts about just using the (newly) exposed functions
1:1 here.

It is a valid concern, but what is the alternative? ExecInsert() and
the others appear to do exactly the right things that are required.

Are your concerns mainly philosophical about calling into internal
executor code, or do you have technical concerns that this will not do
the right thing in some cases?

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#54Andres Freund
andres@anarazel.de
In reply to: Peter Eisentraut (#53)
Re: Logical Replication WIP

On 2016-09-14 13:20:02 -0500, Peter Eisentraut wrote:

On 9/14/16 11:21 AM, Andres Freund wrote:

+ ExecInsert(NULL, /* mtstate is only used for onconflict handling which we don't support atm */

+			   remoteslot,
+			   remoteslot,
+			   NIL,
+			   ONCONFLICT_NONE,
+			   estate,
+			   false);

I have *severe* doubts about just using the (newly) exposed functions
1:1 here.

It is a valid concern, but what is the alternative? ExecInsert() and
the others appear to do exactly the right things that are required.

They're actually a lot more heavyweight than what's required. If you
e.g. do a large COPY on the source side, we create a single executor
state (if at all), and then insert the rows using lower level
routines. And that's *vastly* faster, than going through all the setup
costs here for each row.

Are your concerns mainly philosophical about calling into internal
executor code, or do you have technical concerns that this will not do
the right thing in some cases?

Well, not about it being wrong in the sene of returning wrong results,
but wrong in the sense of not even remotely being able to keep up in
common cases.

Andres

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

#55Petr Jelinek
petr@2ndquadrant.com
In reply to: Andres Freund (#51)
Re: Logical Replication WIP

On 14/09/16 00:48, Andres Freund wrote:

First read through the current version. Hence no real architectural
comments.

Hi,

Thanks for looking!

On 2016-09-09 00:59:26 +0200, Petr Jelinek wrote:

diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
new file mode 100644
index 0000000..e0c719d
--- /dev/null
+++ b/src/backend/commands/publicationcmds.c
@@ -0,0 +1,761 @@
+/*-------------------------------------------------------------------------
+ *
+ * publicationcmds.c
+ *		publication manipulation
+ *
+ * Copyright (c) 2015, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		publicationcmds.c

Not that I'm a fan of this line in the first place, but usually it does
include the path.

Yes, I don't bother with it in WIP version though, because this way I
won't forget to change it when it's getting close to ready if there were
renames.

+static void
+check_replication_permissions(void)
+{
+	if (!superuser() && !has_rolreplication(GetUserId()))
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 (errmsg("must be superuser or replication role to manipulate publications"))));
+}

Do we want to require owner privileges for replication roles? I'd say
no, but want to raise the question.

No, we might want to invent some publish role for which we will so that
we can do logical replication with higher granularity but for
replication role it does not make sense. And I think the higher
granularity ACLs is something for followup patches.

+ObjectAddress
+CreatePublication(CreatePublicationStmt *stmt)
+{
+	Relation	rel;
+	ObjectAddress myself;
+	Oid			puboid;
+	bool		nulls[Natts_pg_publication];
+	Datum		values[Natts_pg_publication];
+	HeapTuple	tup;
+	bool		replicate_insert_given;
+	bool		replicate_update_given;
+	bool		replicate_delete_given;
+	bool		replicate_insert;
+	bool		replicate_update;
+	bool		replicate_delete;
+
+	check_replication_permissions();
+
+	rel = heap_open(PublicationRelationId, RowExclusiveLock);
+
+	/* Check if name is used */
+	puboid = GetSysCacheOid1(PUBLICATIONNAME, CStringGetDatum(stmt->pubname));
+	if (OidIsValid(puboid))
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("publication \"%s\" already exists",
+						stmt->pubname)));
+	}
+
+	/* Form a tuple. */
+	memset(values, 0, sizeof(values));
+	memset(nulls, false, sizeof(nulls));
+
+	values[Anum_pg_publication_pubname - 1] =
+		DirectFunctionCall1(namein, CStringGetDatum(stmt->pubname));
+
+	parse_publication_options(stmt->options,
+							  &replicate_insert_given, &replicate_insert,
+							  &replicate_update_given, &replicate_update,
+							  &replicate_delete_given, &replicate_delete);
+
+	values[Anum_pg_publication_puballtables - 1] =
+		BoolGetDatum(stmt->for_all_tables);
+	values[Anum_pg_publication_pubreplins - 1] =
+		BoolGetDatum(replicate_insert);
+	values[Anum_pg_publication_pubreplupd - 1] =
+		BoolGetDatum(replicate_update);
+	values[Anum_pg_publication_pubrepldel - 1] =
+		BoolGetDatum(replicate_delete);
+
+	tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
+
+	/* Insert tuple into catalog. */
+	puboid = simple_heap_insert(rel, tup);
+	CatalogUpdateIndexes(rel, tup);
+	heap_freetuple(tup);
+
+	ObjectAddressSet(myself, PublicationRelationId, puboid);
+
+	/* Make the changes visible. */
+	CommandCounterIncrement();
+
+	if (stmt->tables)
+	{
+		List	   *rels;
+
+		Assert(list_length(stmt->tables) > 0);
+
+		rels = GatherTableList(stmt->tables);
+		PublicationAddTables(puboid, rels, true, NULL);
+		CloseTables(rels);
+	}
+	else if (stmt->for_all_tables || stmt->schema)
+	{
+		List	   *rels;
+
+		rels = GatherTables(stmt->schema);
+		PublicationAddTables(puboid, rels, true, NULL);
+		CloseTables(rels);
+	}

Isn't this (and ALTER) racy? What happens if tables are concurrently
created? This session wouldn't necessarily see the tables, and other
sessions won't see for_all_tables/schema. Evaluating
for_all_tables/all_in_schema when the publication is used, would solve
that problem.

Well, yes it is. It's technically not problem for all_in_schema as
that's just shorthand for TABLE a,b,c,d etc where future tables don't
matter (and should be added manually, unless we want to change that
behavior to act more like for_all_tables just with schema filter which I
wouldn't be against). But for for_all_tables it's problem I agree.

Based on discussion offline I'll move the check to the actual DML
operation instead of DDL and have for_all_tables be evaluated when used
not when defined.

+/*
+ * Gather all tables optinally filtered by schema name.
+ * The gathered tables are locked in access share lock mode.
+ */
+static List *
+GatherTables(char *nspname)
+{
+	Oid			nspid = InvalidOid;
+	List	   *rels = NIL;
+	Relation	rel;
+	SysScanDesc scan;
+	ScanKeyData key[1];
+	HeapTuple	tup;
+
+	/* Resolve and validate the schema if specified */
+	if (nspname)
+	{
+		nspid = LookupExplicitNamespace(nspname, false);
+		if (IsSystemNamespace(nspid) || IsToastNamespace(nspid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("only tables in user schemas can be added to publication"),
+					 errdetail("%s is a system schema", strVal(nspname))));
+	}

Why are we restricting pg_catalog here? There's a bunch of extensions
creating objects therein, and we allow that. Seems better to just rely
on the IsSystemClass check for that below.

Makes sense.

+/*
+ * Gather Relations based o provided by RangeVar list.
+ * The gathered tables are locked in access share lock mode.
+ */

Why access share? Shouldn't we make this ShareUpdateExclusive or
similar, to prevent schema changes?

Hm, I thought AccessShare would be enough to prevent schema changes that
matter to us (which is basically just drop afaik).

+static List *
+GatherTableList(List *tables)
+{
+	List	   *relids = NIL;
+	List	   *rels = NIL;
+	ListCell   *lc;
+
+	/*
+	 * Open, share-lock, and check all the explicitly-specified relations
+	 */
+	foreach(lc, tables)
+	{
+		RangeVar   *rv = lfirst(lc);
+		Relation	rel;
+		bool		recurse = interpretInhOption(rv->inhOpt);
+		Oid			myrelid;
+
+		rel = heap_openrv(rv, AccessShareLock);
+		myrelid = RelationGetRelid(rel);
+		/* don't throw error for "foo, foo" */
+		if (list_member_oid(relids, myrelid))
+		{
+			heap_close(rel, AccessShareLock);
+			continue;
+		}
+		rels = lappend(rels, rel);
+		relids = lappend_oid(relids, myrelid);
+
+		if (recurse)
+		{
+			ListCell   *child;
+			List	   *children;
+
+			children = find_all_inheritors(myrelid, AccessShareLock,
+										   NULL);
+
+			foreach(child, children)
+			{
+				Oid			childrelid = lfirst_oid(child);
+
+				if (list_member_oid(relids, childrelid))
+					continue;
+
+				/* find_all_inheritors already got lock */
+				rel = heap_open(childrelid, NoLock);
+				rels = lappend(rels, rel);
+				relids = lappend_oid(relids, childrelid);
+			}
+		}
+	}

Hm, can't this yield duplicates, when both an inherited and a top level
relation are specified?

Hmm possible, I'll do the same check as I do above.

@@ -713,6 +714,25 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
ObjectAddressSet(address, RelationRelationId, relationId);

/*
+	 * If the newly created relation is a table and there are publications
+	 * which were created as FOR ALL TABLES, we want to add the relation
+	 * membership to those publications.
+	 */
+
+	if (relkind == RELKIND_RELATION)
+	{
+		List	   *pubids = GetAllTablesPublications();
+		ListCell   *lc;
+
+		foreach(lc, pubids)
+		{
+			Oid	pubid = lfirst_oid(lc);
+
+			publication_add_relation(pubid, rel, false);
+		}
+	}
+

Hm, this has the potential to noticeably slow down table creation.

I doubt it's going to be noticeable given all the work CREATE TABLE
already does, but it certainly won't make it any faster. But since we
agreed to move the check to DML this will be removed as well.

+publication_opt_item:
+			IDENT
+				{
+					/*
+					 * We handle identifiers that aren't parser keywords with
+					 * the following special-case codes, to avoid bloating the
+					 * size of the main parser.
+					 */
+					if (strcmp($1, "replicate_insert") == 0)
+						$$ = makeDefElem("replicate_insert",
+										 (Node *)makeInteger(TRUE), @1);
+					else if (strcmp($1, "noreplicate_insert") == 0)
+						$$ = makeDefElem("replicate_insert",
+										 (Node *)makeInteger(FALSE), @1);
+					else if (strcmp($1, "replicate_update") == 0)
+						$$ = makeDefElem("replicate_update",
+										 (Node *)makeInteger(TRUE), @1);
+					else if (strcmp($1, "noreplicate_update") == 0)
+						$$ = makeDefElem("replicate_update",
+										 (Node *)makeInteger(FALSE), @1);
+					else if (strcmp($1, "replicate_delete") == 0)
+						$$ = makeDefElem("replicate_delete",
+										 (Node *)makeInteger(TRUE), @1);
+					else if (strcmp($1, "noreplicate_delete") == 0)
+						$$ = makeDefElem("replicate_delete",
+										 (Node *)makeInteger(FALSE), @1);
+					else
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("unrecognized publication option \"%s\"", $1),
+									 parser_errposition(@1)));
+				}
+		;

I'm kind of inclined to do this checking at execution (or transform)
time instead. That allows extension to add options, and handle them in
utility hooks.

Thant's interesting point, I prefer the parsing to be done in gram.y,
but it might be worth moving it for extensibility. Although there are so
far other barriers for that.

+
+/* ----------------
+ *		pg_publication_rel definition.  cpp turns this into
+ *		typedef struct FormData_pg_publication_rel
+ *
+ * ----------------
+ */
+#define PublicationRelRelationId				6106
+
+CATALOG(pg_publication_rel,6106)
+{
+	Oid		pubid;				/* Oid of the publication */
+	Oid		relid;				/* Oid of the relation */
+} FormData_pg_publication_rel;

Hm. Do we really want this to have an oid? Won't that significantly,
especially if multiple publications are present, increase our oid
consumption? It seems entirely sufficient to identify rows in here
using (pubid, relid).

It could, but I'll have to check and possibly fix dependency code, I
vaguely remember that there is some part of it that assumes that suboid
is only used for relation column and nothing else.

+ObjectAddress
+CreateSubscription(CreateSubscriptionStmt *stmt)
+{
+	Relation	rel;
+	ObjectAddress myself;
+	Oid			subid;
+	bool		nulls[Natts_pg_subscription];
+	Datum		values[Natts_pg_subscription];
+	HeapTuple	tup;
+	bool		enabled_given;
+	bool		enabled;
+	char	   *conninfo;
+	List	   *publications;
+
+	check_subscription_permissions();
+
+	rel = heap_open(SubscriptionRelationId, RowExclusiveLock);
+
+	/* Check if name is used */
+	subid = GetSysCacheOid2(SUBSCRIPTIONNAME, MyDatabaseId,
+							CStringGetDatum(stmt->subname));
+	if (OidIsValid(subid))
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("subscription \"%s\" already exists",
+						stmt->subname)));
+	}
+
+	/* Parse and check options. */
+	parse_subscription_options(stmt->options, &enabled_given, &enabled,
+							   &conninfo, &publications);
+
+	/* TODO: improve error messages here. */
+	if (conninfo == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("connection not specified")));

Probably also makes sense to parse the conninfo here to verify it looks
saen. Although that's fairly annoying to do, because the relevant code
is libpq :(

Well the connection is eventually used (in later patches) so maybe
that's not problem.

diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 65230e2..f3d54c8 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c

I think you might be missing outfuncs support.

I thought that we don't do outfuncs for DDL?

+
+CATALOG(pg_subscription,6100) BKI_SHARED_RELATION BKI_ROWTYPE_OID(6101) BKI_SCHEMA_MACRO
+{
+	Oid			subdbid;			/* Database the subscription is in. */
+	NameData	subname;		/* Name of the subscription */
+	bool		subenabled;		/* True if the subsription is enabled (running) */

Not sure what "running" means here.

It's very terse way of saying that enabled means worker should be running.

+    <varlistentry>
+     <term>
+      publication_names
+     </term>
+     <listitem>
+      <para>
+       Comma separated list of publication names for which to subscribe
+       (receive changes). See
+       <xref linkend="logical-replication-publication"> for more info.
+      </para>
+     </listitem>
+    </varlistentry>
+   </variablelist>

Do we need to specify an escaping scheme here?

Probably as we allow whatever Name allows.

+<listitem>
+<para>
+                Commit timestamp of the transaction.
+</para>
+</listitem>
+</varlistentry>

Perhaps mention it's relative to postgres epoch?

Already done in my local working copy.

+<variablelist>
+<varlistentry>
+<term>
+        Byte1('O')
+</term>
+<listitem>
+<para>
+                Identifies the message as an origin message.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int64
+</term>
+<listitem>
+<para>
+                The LSN of the commit on the origin server.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int8
+</term>
+<listitem>
+<para>
+                Length of the origin name (including the NULL-termination
+                character).
+</para>
+</listitem>
+</varlistentry>

Should this explain that there could be mulitple origin messages (when
replay switched origins during an xact)?

Makes sense.

+<para>
+                Relation name.
+</para>
+</listitem>
+</varlistentry>
+</variablelist>
+
+</para>
+
+<para>
+This message is always followed by Attributes message.
+</para>

What's the point of having this separate from the relation message?

It's not, it part of it, but the documentation does not make that very
clear.

+<varlistentry>
+<term>
+        Byte1('C')
+</term>
+<listitem>
+<para>
+                Start of column block.
+</para>
+</listitem>

"block"?

Block, message part, sub-message, I am not sure how to call something
that's repeating inside of a message.

+</varlistentry><varlistentry>
+<term>
+        Int8
+</term>
+<listitem>
+<para>
+                Flags for the column. Currently can be either 0 for no flags
+                or one which marks the column as part of the key.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        Int8
+</term>
+<listitem>
+<para>
+                Length of column name (including the NULL-termination
+                character).
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        String
+</term>
+<listitem>
+<para>
+                Name of the column.
+</para>
+</listitem>
+</varlistentry>

Huh, no type information?

It's not necessary for the text transfer, it will be if we ever add
binary data transfer but that will require protocol version bump anyway.

+<varlistentry>
+<term>
+        Byte1('O')
+</term>
+<listitem>
+<para>
+                Identifies the following TupleData message as the old tuple
+                (deleted tuple).
+</para>
+</listitem>
+</varlistentry>

Should we discern between old key and old tuple?

Yes, otherwise it will be hard to support REPLICA IDENTITY FULL.

+/*
+ * Read transaction BEGIN from the stream.
+ */
+void
+logicalrep_read_begin(StringInfo in, XLogRecPtr *remote_lsn,
+					  TimestampTz *committime, TransactionId *remote_xid)
+{
+	/* read fields */
+	*remote_lsn = pq_getmsgint64(in);
+	Assert(*remote_lsn != InvalidXLogRecPtr);
+	*committime = pq_getmsgint64(in);
+	*remote_xid = pq_getmsgint(in, 4);
+}

In network exposed stuff it seems better not to use assert, and error
out instead.

Okay

+/*
+ * Write UPDATE to the output stream.
+ */
+void
+logicalrep_write_update(StringInfo out, Relation rel, HeapTuple oldtuple,
+					   HeapTuple newtuple)
+{
+	pq_sendbyte(out, 'U');		/* action UPDATE */
+
+	/* use Oid as relation identifier */
+	pq_sendint(out, RelationGetRelid(rel), 4);

Wonder if there's a way that could screw us. What happens if there's an
oid wraparound, and a relation is dropped? Then a new relation could end
up with same id. Maybe answered somewhere further down.

Should not, we'll know we didn't send the message for the new table yet
so we'll send new Relation message.

+
+/*
+ * COMMIT callback
+ */
+static void
+pgoutput_commit_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
+					 XLogRecPtr commit_lsn)
+{
+	OutputPluginPrepareWrite(ctx, true);
+	logicalrep_write_commit(ctx->out, txn, commit_lsn);
+	OutputPluginWrite(ctx, true);
+}

Hm, so we don't reset the context for these...

What?

+/*
+ * Sends the decoded DML over wire.
+ */
+static void
+pgoutput_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
+				Relation relation, ReorderBufferChange *change)
+{
+	/* Avoid leaking memory by using and resetting our own context */
+	old = MemoryContextSwitchTo(data->context);
+
+	/*
+	 * Write the relation schema if the current schema haven't been sent yet.
+	 */
+	if (!relentry->schema_sent)
+	{
+		OutputPluginPrepareWrite(ctx, false);
+		logicalrep_write_rel(ctx->out, relation);
+		OutputPluginWrite(ctx, false);
+		relentry->schema_sent = true;
+	}
+
+	/* Send the data */
+	switch (change->action)
+	{

...

+	/* Cleanup */
+	MemoryContextSwitchTo(old);
+	MemoryContextReset(data->context);
+}

IIRC there were some pfree's in called functions. It's probably better
to remove those and rely on this.

Only write_tuple calls pfree, that's mostly because we may call it twice
for single tuple and it might allocate a lot of data.

+/*
+ * Load publications from the list of publication names.
+ */
+static List *
+LoadPublications(List *pubnames)
+{
+	List	   *result = NIL;
+	ListCell   *lc;
+
+	foreach (lc, pubnames)
+	{
+		char		   *pubname = (char *) lfirst(lc);
+		Publication	   *pub = GetPublicationByName(pubname, false);
+
+		result = lappend(result, pub);
+	}
+
+	return result;
+}

Why are we doing this eagerly? On systems with a lot of relations
this'll suck up a fair amount of memory, without much need?

Don't follow, it only reads publications not relations in them, reason
why we do it eagerly is to validate that the requested publications
actually exist.

+/*
+ * Remove all the entries from our relation cache.
+ */
+static void
+destroy_rel_sync_cache(void)
+{
+	HASH_SEQ_STATUS		status;
+	RelationSyncEntry  *entry;
+
+	if (RelationSyncCache == NULL)
+		return;
+
+	hash_seq_init(&status, RelationSyncCache);
+
+	while ((entry = (RelationSyncEntry *) hash_seq_search(&status)) != NULL)
+	{
+		if (hash_search(RelationSyncCache, (void *) &entry->relid,
+						HASH_REMOVE, NULL) == NULL)
+			elog(ERROR, "hash table corrupted");
+	}
+
+	RelationSyncCache = NULL;
+}

Any reason not to just destroy the hash table instead?

Missed that we have AOI for that.

/*
- * Module load callback
+ * Module initialization callback
*/
-void
-_PG_init(void)
+WalReceiverConnHandle *
+_PG_walreceirver_conn_init(WalReceiverConnAPI *wrcapi)
{
-	/* Tell walreceiver how to reach us */
-	if (walrcv_connect != NULL || walrcv_identify_system != NULL ||
-		walrcv_readtimelinehistoryfile != NULL ||
-		walrcv_startstreaming != NULL || walrcv_endstreaming != NULL ||
-		walrcv_receive != NULL || walrcv_send != NULL ||
-		walrcv_disconnect != NULL)
-		elog(ERROR, "libpqwalreceiver already loaded");
-	walrcv_connect = libpqrcv_connect;
-	walrcv_get_conninfo = libpqrcv_get_conninfo;
-	walrcv_identify_system = libpqrcv_identify_system;
-	walrcv_readtimelinehistoryfile = libpqrcv_readtimelinehistoryfile;
-	walrcv_startstreaming = libpqrcv_startstreaming;
-	walrcv_endstreaming = libpqrcv_endstreaming;
-	walrcv_receive = libpqrcv_receive;
-	walrcv_send = libpqrcv_send;
-	walrcv_disconnect = libpqrcv_disconnect;
+	WalReceiverConnHandle *handle;
+
+	handle = palloc0(sizeof(WalReceiverConnHandle));
+
+	/* Tell caller how to reach us */
+	wrcapi->connect = libpqrcv_connect;
+	wrcapi->get_conninfo = libpqrcv_get_conninfo;
+	wrcapi->identify_system = libpqrcv_identify_system;
+	wrcapi->readtimelinehistoryfile = libpqrcv_readtimelinehistoryfile;
+	wrcapi->create_slot = libpqrcv_create_slot;
+	wrcapi->startstreaming_physical = libpqrcv_startstreaming_physical;
+	wrcapi->startstreaming_logical = libpqrcv_startstreaming_logical;
+	wrcapi->endstreaming = libpqrcv_endstreaming;
+	wrcapi->receive = libpqrcv_receive;
+	wrcapi->send = libpqrcv_send;
+	wrcapi->disconnect = libpqrcv_disconnect;
+
+	return handle;
}

This however I'm not following. Why do we need multiple copies of this?
And why aren't we doing the assignments in _PG_init? Seems better to
just allocate one WalRcvCalllbacks globally and assign all these as
constants. Then the establishment function can just return all these
(as part of a bigger struct).

Meh, If I understand you correctly that will make the access bit more
ugly (multiple layers of structs).

(skipped logical rep docs)

diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index 8acdff1..34007d3 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -54,11 +54,13 @@
&alterOperatorClass;
&alterOperatorFamily;
&alterPolicy;
+   &alterPublication;
&alterRole;
&alterRule;
&alterSchema;
&alterSequence;
&alterServer;
+   &alterSubscription;
&alterSystem;
&alterTable;
&alterTableSpace;
@@ -100,11 +102,13 @@
&createOperatorClass;
&createOperatorFamily;
&createPolicy;
+   &createPublication;
&createRole;
&createRule;
&createSchema;
&createSequence;
&createServer;
+   &createSubscription;
&createTable;
&createTableAs;
&createTableSpace;
@@ -144,11 +148,13 @@
&dropOperatorFamily;
&dropOwned;
&dropPolicy;
+   &dropPublication;
&dropRole;
&dropRule;
&dropSchema;
&dropSequence;
&dropServer;
+   &dropSubscription;
&dropTable;
&dropTableSpace;
&dropTSConfig;

Hm, shouldn't all these have been registered in the earlier patch?

Yeah, all the rebasing sometimes produces artefacts.

diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index d29d3f9..f2052b8 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c

This sure is a lot of yanking around of previously added code. At least
some of it looks like it should really have been part of the earlier
commit.

True, but it depends on the previous patch ... scratches head ... hmm
although the libpqwalreceiver actually does not depend on anything so it
could be first patch in series, then this code could be moved to the
patch which adds subscriptions.

@@ -327,6 +431,18 @@ DropSubscriptionById(Oid subid)
{
Relation	rel;
HeapTuple	tup;
+	Datum		datum;
+	bool		isnull;
+	char	   *subname;
+	char	   *conninfo;
+	char	   *slotname;
+	RepOriginId	originid;
+	MemoryContext			tmpctx,
+							oldctx;
+	WalReceiverConnHandle  *wrchandle = NULL;
+	WalReceiverConnAPI	   *wrcapi = NULL;
+	walrcvconn_init_fn		walrcvconn_init;
+	LogicalRepWorker	   *worker;

check_subscription_permissions();

@@ -337,9 +453,135 @@ DropSubscriptionById(Oid subid)
if (!HeapTupleIsValid(tup))
elog(ERROR, "cache lookup failed for subscription %u", subid);

+	/*
+	 * Create temporary memory context to keep copy of subscription
+	 * info needed later in the execution.
+	 */
+	tmpctx = AllocSetContextCreate(TopMemoryContext,
+										  "DropSubscription Ctx",
+										  ALLOCSET_DEFAULT_MINSIZE,
+										  ALLOCSET_DEFAULT_INITSIZE,
+										  ALLOCSET_DEFAULT_MAXSIZE);
+	oldctx = MemoryContextSwitchTo(tmpctx);
+
+	/* Get subname */
+	datum = SysCacheGetAttr(SUBSCRIPTIONOID, tup,
+							Anum_pg_subscription_subname, &isnull);
+	Assert(!isnull);
+	subname = pstrdup(NameStr(*DatumGetName(datum)));
+
+	/* Get conninfo */
+	datum = SysCacheGetAttr(SUBSCRIPTIONOID, tup,
+							Anum_pg_subscription_subconninfo, &isnull);
+	Assert(!isnull);
+	conninfo = pstrdup(TextDatumGetCString(datum));
+
+	/* Get slotname */
+	datum = SysCacheGetAttr(SUBSCRIPTIONOID, tup,
+							Anum_pg_subscription_subslotname, &isnull);
+	Assert(!isnull);
+	slotname = pstrdup(NameStr(*DatumGetName(datum)));
+
+	MemoryContextSwitchTo(oldctx);
+
+	/* Remove the tuple from catalog. */
simple_heap_delete(rel, &tup->t_self);
-	ReleaseSysCache(tup);
+	/* Protect against launcher restarting the worker. */
+	LWLockAcquire(LogicalRepLauncherLock, LW_EXCLUSIVE);
-	heap_close(rel, RowExclusiveLock);
+	/* Kill the apply worker so that the slot becomes accessible. */
+	LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
+	worker = logicalrep_worker_find(subid);
+	if (worker)
+		logicalrep_worker_stop(worker);
+	LWLockRelease(LogicalRepWorkerLock);
+
+	/* Wait for apply process to die. */
+	for (;;)
+	{
+		int	rc;
+
+		CHECK_FOR_INTERRUPTS();
+
+		LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
+		if (logicalrep_worker_count(subid) < 1)
+		{
+			LWLockRelease(LogicalRepWorkerLock);
+			break;
+		}
+		LWLockRelease(LogicalRepWorkerLock);
+
+		/* Wait for more work. */
+		rc = WaitLatch(&MyProc->procLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+					   1000L);
+
+		/* emergency bailout if postmaster has died */
+		if (rc & WL_POSTMASTER_DEATH)
+			proc_exit(1);
+
+		ResetLatch(&MyProc->procLatch);
+	}

I'm really far from convinced this is the right layer to perform these
operations. Previously these routines were low level catalog
manipulation routines. Now they're certainly not.

Well I do want to have this happen when the DDL is executed so that I
can inform user about failure. I can move this code to a separate
function but it will still be executed in this layer.

+	/*
+	 * Now that the catalog update is done, try to reserve slot at the
+	 * provider node using replication connection.
+	 */
+	wrcapi = palloc0(sizeof(WalReceiverConnAPI));
+
+	walrcvconn_init = (walrcvconn_init_fn)
+		load_external_function("libpqwalreceiver",
+							   "_PG_walreceirver_conn_init", false, NULL);
+
+	if (walrcvconn_init == NULL)
+		elog(ERROR, "libpqwalreceiver does not declare _PG_walreceirver_conn_init symbol");

This does rather reinforce my opinion that the _PG_init removal in
libpqwalreceiver isn't useful.

I don't see how it helps, you said we'd still return struct from some
interface so this would be more or less the same?

diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 699c934..fc998cd 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -93,6 +93,9 @@ struct BackgroundWorkerHandle

static BackgroundWorkerArray *BackgroundWorkerData;

+/* Enables registration of internal background workers. */
+bool internal_bgworker_registration_in_progress = false;
+
/*
* Calculate shared memory needed.
*/
@@ -745,7 +748,8 @@ RegisterBackgroundWorker(BackgroundWorker *worker)
ereport(DEBUG1,
(errmsg("registering background worker \"%s\"", worker->bgw_name)));
-	if (!process_shared_preload_libraries_in_progress)
+	if (!process_shared_preload_libraries_in_progress &&
+		!internal_bgworker_registration_in_progress)
{
if (!IsUnderPostmaster)
ereport(LOG,

Ugh.

/*
+ * Register internal background workers.
+ *
+ * This is here mainly because the permanent bgworkers are normally allowed
+ * to be registered only when share preload libraries are loaded which does
+ * not work for the internal ones.
+ */
+static void
+register_internal_bgworkers(void)
+{
+	internal_bgworker_registration_in_progress = true;
+
+	/* Register the logical replication worker launcher if appropriate. */
+	if (!IsBinaryUpgrade && max_logical_replication_workers > 0)
+	{
+		BackgroundWorker bgw;
+
+		bgw.bgw_flags =	BGWORKER_SHMEM_ACCESS |
+			BGWORKER_BACKEND_DATABASE_CONNECTION;
+		bgw.bgw_start_time = BgWorkerStart_RecoveryFinished;
+		bgw.bgw_main = ApplyLauncherMain;
+		snprintf(bgw.bgw_name, BGW_MAXLEN,
+				 "logical replication launcher");
+		bgw.bgw_restart_time = 5;
+		bgw.bgw_notify_pid = 0;
+		bgw.bgw_main_arg = (Datum) 0;
+
+		RegisterBackgroundWorker(&bgw);
+	}
+
+	internal_bgworker_registration_in_progress = false;
+}

Who says these flags are right for everyone? If we indeed want to go
through bgworkers here, I think you'll have to generallize this a bit,
so we don't check for max_logical_replication_workers and such here. We
could e.g. have the shared memory sizing hooks set up a chain of
registrations.

It could be more generalized, I agree, this is more of a WIP hack.

I would like to make special version of RegisterBackgroundWorker called
something like RegisterInternalBackgroundWorker that does something
similar as the above function (obviously the if should be moved to the
caller of that function). The main point here is to be able to register
static worker without extension.

-static void
+static char *
libpqrcv_identify_system(WalReceiverConnHandle *handle,
-						 TimeLineID *primary_tli)
+						 TimeLineID *primary_tli,
+						 char **dbname)
{
+	char	   *sysid;
PGresult   *res;
-	char	   *primary_sysid;
-	char		standby_sysid[32];
/*
* Get the system identifier and timeline ID as a DataRow message from the
@@ -231,24 +234,19 @@ libpqrcv_identify_system(WalReceiverConnHandle *handle,
errdetail("Could not identify system: got %d rows and %d fields, expected %d rows and %d or more fields.",
ntuples, nfields, 3, 1)));
}
-	primary_sysid = PQgetvalue(res, 0, 0);
+	sysid = pstrdup(PQgetvalue(res, 0, 0));
*primary_tli = pg_atoi(PQgetvalue(res, 0, 1), 4, 0);
-
-	/*
-	 * Confirm that the system identifier of the primary is the same as ours.
-	 */
-	snprintf(standby_sysid, sizeof(standby_sysid), UINT64_FORMAT,
-			 GetSystemIdentifier());
-	if (strcmp(primary_sysid, standby_sysid) != 0)
+	if (dbname)
{
-		primary_sysid = pstrdup(primary_sysid);
-		PQclear(res);
-		ereport(ERROR,
-				(errmsg("database system identifier differs between the primary and standby"),
-				 errdetail("The primary's identifier is %s, the standby's identifier is %s.",
-						   primary_sysid, standby_sysid)));
+		if (PQgetisnull(res, 0, 3))
+			*dbname = NULL;
+		else
+			*dbname = pstrdup(PQgetvalue(res, 0, 3));
}
+
PQclear(res);
+
+	return sysid;
}

/*
@@ -274,7 +272,7 @@ libpqrcv_create_slot(WalReceiverConnHandle *handle, char *slotname,

if (PQresultStatus(res) != PGRES_TUPLES_OK)
{
-		elog(FATAL, "could not crate replication slot \"%s\": %s\n",
+		elog(ERROR, "could not crate replication slot \"%s\": %s\n",
slotname, PQerrorMessage(handle->streamConn));
}

@@ -287,6 +285,28 @@ libpqrcv_create_slot(WalReceiverConnHandle *handle, char *slotname,
return snapshot;
}

+/*
+ * Drop replication slot.
+ */
+static void
+libpqrcv_drop_slot(WalReceiverConnHandle *handle, char *slotname)
+{
+	PGresult	   *res;
+	char			cmd[256];
+
+	snprintf(cmd, sizeof(cmd),
+			 "DROP_REPLICATION_SLOT \"%s\"", slotname);
+
+	res = libpqrcv_PQexec(handle, cmd);
+
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		elog(ERROR, "could not drop replication slot \"%s\": %s\n",
+			 slotname, PQerrorMessage(handle->streamConn));
+	}
+
+	PQclear(res);
+}

Given that the earlier commit to libpqwalreciever added a lot of this
information, it doesn't seem right to change it again here.

Why? It's pretty unrelated to the previous change which is basically
just refactoring, this actually adds new functionality.

--
Petr Jelinek 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

#56Petr Jelinek
petr@2ndquadrant.com
In reply to: Andres Freund (#54)
Re: Logical Replication WIP

On 14/09/16 20:50, Andres Freund wrote:

On 2016-09-14 13:20:02 -0500, Peter Eisentraut wrote:

On 9/14/16 11:21 AM, Andres Freund wrote:

+ ExecInsert(NULL, /* mtstate is only used for onconflict handling which we don't support atm */

+			   remoteslot,
+			   remoteslot,
+			   NIL,
+			   ONCONFLICT_NONE,
+			   estate,
+			   false);

I have *severe* doubts about just using the (newly) exposed functions
1:1 here.

It is a valid concern, but what is the alternative? ExecInsert() and
the others appear to do exactly the right things that are required.

They're actually a lot more heavyweight than what's required. If you
e.g. do a large COPY on the source side, we create a single executor
state (if at all), and then insert the rows using lower level
routines. And that's *vastly* faster, than going through all the setup
costs here for each row.

Are your concerns mainly philosophical about calling into internal
executor code, or do you have technical concerns that this will not do
the right thing in some cases?

Well, not about it being wrong in the sene of returning wrong results,
but wrong in the sense of not even remotely being able to keep up in
common cases.

I'd say in common case they will. I don't plan to use these forever btw,
but it's simplest to just use them in v1 IMHO instead of trying to
reinvent new versions of these that perform better but also behave
correctly (in terms of triggers and stuff for example).

--
Petr Jelinek 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

#57Andres Freund
andres@anarazel.de
In reply to: Petr Jelinek (#55)
Re: Logical Replication WIP

Hi,

On 2016-09-14 21:17:42 +0200, Petr Jelinek wrote:

+/*
+ * Gather Relations based o provided by RangeVar list.
+ * The gathered tables are locked in access share lock mode.
+ */

Why access share? Shouldn't we make this ShareUpdateExclusive or
similar, to prevent schema changes?

Hm, I thought AccessShare would be enough to prevent schema changes that
matter to us (which is basically just drop afaik).

Doesn't e.g. dropping an index matter as well?

+					if (strcmp($1, "replicate_insert") == 0)
+						$$ = makeDefElem("replicate_insert",
+										 (Node *)makeInteger(TRUE), @1);
+					else if (strcmp($1, "noreplicate_insert") == 0)
+						$$ = makeDefElem("replicate_insert",
+										 (Node *)makeInteger(FALSE), @1);
+					else if (strcmp($1, "replicate_update") == 0)
+						$$ = makeDefElem("replicate_update",
+										 (Node *)makeInteger(TRUE), @1);
+					else if (strcmp($1, "noreplicate_update") == 0)
+						$$ = makeDefElem("replicate_update",
+										 (Node *)makeInteger(FALSE), @1);
+					else if (strcmp($1, "replicate_delete") == 0)
+						$$ = makeDefElem("replicate_delete",
+										 (Node *)makeInteger(TRUE), @1);
+					else if (strcmp($1, "noreplicate_delete") == 0)
+						$$ = makeDefElem("replicate_delete",
+										 (Node *)makeInteger(FALSE), @1);
+					else
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("unrecognized publication option \"%s\"", $1),
+									 parser_errposition(@1)));
+				}
+		;

I'm kind of inclined to do this checking at execution (or transform)
time instead. That allows extension to add options, and handle them in
utility hooks.

Thant's interesting point, I prefer the parsing to be done in gram.y, but it
might be worth moving it for extensibility. Although there are so far other
barriers for that.

Citus uses the lack of such check for COPY to implement copy over it's
distributed tables for example. So there's some benefit.

+	check_subscription_permissions();
+
+	rel = heap_open(SubscriptionRelationId, RowExclusiveLock);
+
+	/* Check if name is used */
+	subid = GetSysCacheOid2(SUBSCRIPTIONNAME, MyDatabaseId,
+							CStringGetDatum(stmt->subname));
+	if (OidIsValid(subid))
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("subscription \"%s\" already exists",
+						stmt->subname)));
+	}
+
+	/* Parse and check options. */
+	parse_subscription_options(stmt->options, &enabled_given, &enabled,
+							   &conninfo, &publications);
+
+	/* TODO: improve error messages here. */
+	if (conninfo == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("connection not specified")));

Probably also makes sense to parse the conninfo here to verify it looks
saen. Although that's fairly annoying to do, because the relevant code
is libpq :(

Well the connection is eventually used (in later patches) so maybe that's
not problem.

Well, it's nicer if it's immediately parsed, before doing complex and
expensive stuff, especially if that happens outside of the transaction.

diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 65230e2..f3d54c8 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c

I think you might be missing outfuncs support.

I thought that we don't do outfuncs for DDL?

I think it's just readfuncs that's skipped.

+                Length of column name (including the NULL-termination
+                character).
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        String
+</term>
+<listitem>
+<para>
+                Name of the column.
+</para>
+</listitem>
+</varlistentry>

Huh, no type information?

It's not necessary for the text transfer, it will be if we ever add binary
data transfer but that will require protocol version bump anyway.

I'm *hugely* unconvinced of this. For one type information is useful for
error reporting and such as well. For another, it's one thing to add a
new protocol message (for differently encoded tuples), and something
entirely different to change the format of existing messages.

+
+/*
+ * COMMIT callback
+ */
+static void
+pgoutput_commit_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
+					 XLogRecPtr commit_lsn)
+{
+	OutputPluginPrepareWrite(ctx, true);
+	logicalrep_write_commit(ctx->out, txn, commit_lsn);
+	OutputPluginWrite(ctx, true);
+}

Hm, so we don't reset the context for these...

What?

We only use & reset the data-> memory context in the change
callback. I'm not sure that's good.

This however I'm not following. Why do we need multiple copies of this?
And why aren't we doing the assignments in _PG_init? Seems better to
just allocate one WalRcvCalllbacks globally and assign all these as
constants. Then the establishment function can just return all these
(as part of a bigger struct).

Meh, If I understand you correctly that will make the access bit more ugly
(multiple layers of structs).

On the other hand, you right now need to access one struct, and pass the
other...

This does rather reinforce my opinion that the _PG_init removal in
libpqwalreceiver isn't useful.

I don't see how it helps, you said we'd still return struct from some
interface so this would be more or less the same?

Or we just set some global vars and use them directly.

Andres

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

#58Petr Jelinek
petr@2ndquadrant.com
In reply to: Andres Freund (#52)
Re: Logical Replication WIP

On 14/09/16 18:21, Andres Freund wrote:

(continuing, uh, a bit happier)

On 2016-09-09 00:59:26 +0200, Petr Jelinek wrote:

+/*
+ * Relcache invalidation callback for our relation map cache.
+ */
+static void
+logicalreprelmap_invalidate_cb(Datum arg, Oid reloid)
+{
+	LogicalRepRelMapEntry  *entry;
+
+	/* Just to be sure. */
+	if (LogicalRepRelMap == NULL)
+		return;
+
+	if (reloid != InvalidOid)
+	{
+		HASH_SEQ_STATUS status;
+
+		hash_seq_init(&status, LogicalRepRelMap);
+
+		/* TODO, use inverse lookup hastable? */

*hashtable

+		while ((entry = (LogicalRepRelMapEntry *) hash_seq_search(&status)) != NULL)
+		{
+			if (entry->reloid == reloid)
+				entry->reloid = InvalidOid;

can't we break here?

Probably.

+/*
+ * Initialize the relation map cache.
+ */
+static void
+remoterelmap_init(void)
+{
+	HASHCTL		ctl;
+
+	/* Make sure we've initialized CacheMemoryContext. */
+	if (CacheMemoryContext == NULL)
+		CreateCacheMemoryContext();
+
+	/* Initialize the hash table. */
+	MemSet(&ctl, 0, sizeof(ctl));
+	ctl.keysize = sizeof(uint32);
+	ctl.entrysize = sizeof(LogicalRepRelMapEntry);
+	ctl.hcxt = CacheMemoryContext;

Wonder if this (and similar code earlier) should try to do everything in
a sub-context of CacheMemoryContext instead. That'd make some issues
easier to track down.

Sure. don't see why not.

+/*
+ * Open the local relation associated with the remote one.
+ */
+static LogicalRepRelMapEntry *
+logicalreprel_open(uint32 remoteid, LOCKMODE lockmode)
+{
+	LogicalRepRelMapEntry  *entry;
+	bool		found;
+
+	if (LogicalRepRelMap == NULL)
+		remoterelmap_init();
+
+	/* Search for existing entry. */
+	entry = hash_search(LogicalRepRelMap, (void *) &remoteid,
+						HASH_FIND, &found);
+
+	if (!found)
+		elog(FATAL, "cache lookup failed for remote relation %u",
+			 remoteid);
+
+	/* Need to update the local cache? */
+	if (!OidIsValid(entry->reloid))
+	{
+		Oid			nspid;
+		Oid			relid;
+		int			i;
+		TupleDesc	desc;
+		LogicalRepRelation *remoterel;
+
+		remoterel = &entry->remoterel;
+
+		nspid = LookupExplicitNamespace(remoterel->nspname, false);
+		if (!OidIsValid(nspid))
+			ereport(FATAL,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("the logical replication target %s not found",
+							quote_qualified_identifier(remoterel->nspname,

remoterel->relname))));

+		relid = get_relname_relid(remoterel->relname, nspid);
+		if (!OidIsValid(relid))
+			ereport(FATAL,
+					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+					 errmsg("the logical replication target %s not found",
+							quote_qualified_identifier(remoterel->nspname,
+													   remoterel->relname))));
+
+		entry->rel = heap_open(relid, lockmode);

This seems rather racy. I think this really instead needs something akin
to RangeVarGetRelidExtended().

Maybe, I am not sure if it really matters here given how it's used, but
I can change that.

+/*
+ * Executor state preparation for evaluation of constraint expressions,
+ * indexes and triggers.
+ *
+ * This is based on similar code in copy.c
+ */
+static EState *
+create_estate_for_relation(LogicalRepRelMapEntry *rel)
+{
+	EState	   *estate;
+	ResultRelInfo *resultRelInfo;
+	RangeTblEntry *rte;
+
+	estate = CreateExecutorState();
+
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(rel->rel);
+	rte->relkind = rel->rel->rd_rel->relkind;
+	estate->es_range_table = list_make1(rte);
+
+	resultRelInfo = makeNode(ResultRelInfo);
+	InitResultRelInfo(resultRelInfo, rel->rel, 1, 0);
+
+	estate->es_result_relations = resultRelInfo;
+	estate->es_num_result_relations = 1;
+	estate->es_result_relation_info = resultRelInfo;
+
+	/* Triggers might need a slot */
+	if (resultRelInfo->ri_TrigDesc)
+		estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate);
+
+	return estate;
+}

Ugh, we do this for every single change? That's pretty darn heavy.

I plan to add caching but didn't come up with good way of doing that yet.

+/*
+ * Check if the local attribute is present in relation definition used
+ * by upstream and hence updated by the replication.
+ */
+static bool
+physatt_in_attmap(LogicalRepRelMapEntry *rel, int attid)
+{
+	AttrNumber	i;
+
+	/* Fast path for tables that are same on upstream and downstream. */
+	if (attid < rel->remoterel.natts && rel->attmap[attid] == attid)
+		return true;
+
+	/* Try to find the attribute in the map. */
+	for (i = 0; i < rel->remoterel.natts; i++)
+		if (rel->attmap[i] == attid)
+			return true;
+
+	return false;
+}

Shouldn't we rather try to keep an attribute map that always can map
remote attribute numbers to local ones? That doesn't seem hard on a
first blush? But I might be missing something here.

+static void
+FillSlotDefaults(LogicalRepRelMapEntry *rel, EState *estate,
+				 TupleTableSlot *slot)
+{

Why is this using a different naming scheme?

Because I originally wanted to put it into executor.

+/*
+ * Handle INSERT message.
+ */
+static void
+handle_insert(StringInfo s)
+{
+	LogicalRepRelMapEntry *rel;
+	LogicalRepTupleData	newtup;
+	LogicalRepRelId		relid;
+	EState			   *estate;
+	TupleTableSlot	   *remoteslot;
+	MemoryContext		oldctx;
+
+	ensure_transaction();
+
+	relid = logicalrep_read_insert(s, &newtup);
+	rel = logicalreprel_open(relid, RowExclusiveLock);
+
+	/* Initialize the executor state. */
+	estate = create_estate_for_relation(rel);
+	remoteslot = ExecInitExtraTupleSlot(estate);
+	ExecSetSlotDescriptor(remoteslot, RelationGetDescr(rel->rel));

This seems incredibly expensive for replicating a lot of rows.

You mean because of create_estate_for_relation()?

+/*
+ * Search the relation 'rel' for tuple using the replication index.
+ *
+ * If a matching tuple is found lock it with lockmode, fill the slot with its
+ * contents and return true, return false is returned otherwise.
+ */
+static bool
+tuple_find_by_replidx(Relation rel, LockTupleMode lockmode,
+					  TupleTableSlot *searchslot, TupleTableSlot *slot)
+{
+	HeapTuple		scantuple;
+	ScanKeyData		skey[INDEX_MAX_KEYS];
+	IndexScanDesc	scan;
+	SnapshotData	snap;
+	TransactionId	xwait;
+	Oid				idxoid;
+	Relation		idxrel;
+	bool			found;
+
+	/* Open REPLICA IDENTITY index.*/
+	idxoid = RelationGetReplicaIndex(rel);
+	if (!OidIsValid(idxoid))
+	{
+		elog(ERROR, "could not find configured replica identity for table \"%s\"",
+			 RelationGetRelationName(rel));
+		return false;
+	}
+	idxrel = index_open(idxoid, RowExclusiveLock);
+
+	/* Start an index scan. */
+	InitDirtySnapshot(snap);
+	scan = index_beginscan(rel, idxrel, &snap,
+						   RelationGetNumberOfAttributes(idxrel),
+						   0);
+
+	/* Build scan key. */
+	build_replindex_scan_key(skey, rel, idxrel, searchslot);
+
+retry:
+	found = false;
+
+	index_rescan(scan, skey, RelationGetNumberOfAttributes(idxrel), NULL, 0);
+
+	/* Try to find the tuple */
+	if ((scantuple = index_getnext(scan, ForwardScanDirection)) != NULL)
+	{
+		found = true;
+		ExecStoreTuple(scantuple, slot, InvalidBuffer, false);
+		ExecMaterializeSlot(slot);
+
+		xwait = TransactionIdIsValid(snap.xmin) ?
+			snap.xmin : snap.xmax;
+
+		/*
+		 * If the tuple is locked, wait for locking transaction to finish
+		 * and retry.
+		 */
+		if (TransactionIdIsValid(xwait))
+		{
+			XactLockTableWait(xwait, NULL, NULL, XLTW_None);
+			goto retry;
+		}
+	}

Hm. So we potentially find multiple tuples here, and lock all of
them. but then only use one for the update.

That's not how that code reads for me.

+static List *
+get_subscription_list(void)
+{
+	List	   *res = NIL;
+	Relation	rel;
+	HeapScanDesc scan;
+	HeapTuple	tup;
+	MemoryContext resultcxt;
+
+	/* This is the context that we will allocate our output data in */
+	resultcxt = CurrentMemoryContext;
+
+	/*
+	 * Start a transaction so we can access pg_database, and get a snapshot.
+	 * We don't have a use for the snapshot itself, but we're interested in
+	 * the secondary effect that it sets RecentGlobalXmin.  (This is critical
+	 * for anything that reads heap pages, because HOT may decide to prune
+	 * them even if the process doesn't attempt to modify any tuples.)
+	 */
+	StartTransactionCommand();
+	(void) GetTransactionSnapshot();
+
+	rel = heap_open(SubscriptionRelationId, AccessShareLock);
+	scan = heap_beginscan_catalog(rel, 0, NULL);
+
+	while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
+	{
+		Form_pg_subscription subform = (Form_pg_subscription) GETSTRUCT(tup);
+		Subscription   *sub;
+		MemoryContext	oldcxt;
+
+		/*
+		 * Allocate our results in the caller's context, not the
+		 * transaction's. We do this inside the loop, and restore the original
+		 * context at the end, so that leaky things like heap_getnext() are
+		 * not called in a potentially long-lived context.
+		 */
+		oldcxt = MemoryContextSwitchTo(resultcxt);
+
+		sub = (Subscription *) palloc(sizeof(Subscription));
+		sub->oid = HeapTupleGetOid(tup);
+		sub->dbid = subform->subdbid;
+		sub->enabled = subform->subenabled;
+
+		/* We don't fill fields we are not intereste in. */
+		sub->name = NULL;
+		sub->conninfo = NULL;
+		sub->slotname = NULL;
+		sub->publications = NIL;
+
+		res = lappend(res, sub);
+		MemoryContextSwitchTo(oldcxt);
+	}
+
+	heap_endscan(scan);
+	heap_close(rel, AccessShareLock);
+
+	CommitTransactionCommand();

Hm. this doesn't seem quite right from a locking pov. What if, in the
middle of this, a new subscription is created?

So it will be called again eventually in the next iteration of main
loop. We don't perfectly stable world view here, just snapshot of it to
work with.

Hadn't I previously read about always streaming data to disk first?

@@ -0,0 +1,674 @@
+/*-------------------------------------------------------------------------
+ * tablesync.c
+ *	   PostgreSQL logical replication
+ *
+ * Copyright (c) 2012-2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/replication/logical/tablesync.c
+ *
+ * NOTES
+ *	  This file contains code for initial table data synchronization for
+ *	  logical replication.
+ *
+ *    The initial data synchronization is done separately for each table,
+ *    in separate apply worker that only fetches the initial snapshot data
+ *    from the provider and then synchronizes the position in stream with
+ *    the main apply worker.

Why? I guess that's because it allows to incrementally add tables, with
acceptable overhead.

Yes I need to document why's more here. It enables us to copy multiple
tables in parallel (in the future). It also is needed for adding tables
after the initial sync as you say.

+ *    The stream position synchronization works in multiple steps.
+ *     - sync finishes copy and sets table state as SYNCWAIT and waits
+ *       for state to change in a loop
+ *     - apply periodically checks unsynced tables for SYNCWAIT, when it
+ *       appears it will compare its position in the stream with the
+ *       SYNCWAIT position and decides to either set it to CATCHUP when
+ *       the apply was infront (and wait for the sync to do the catchup),
+ *       or set the state to SYNCDONE if the sync was infront or in case
+ *       both sync and apply are at the same position it will set it to
+ *       READY and stops tracking it

I'm not quite following here.

It's hard for me to explain I guess, that's why the flow diagram is
underneath. The point is to reach same LSN for the table before the main
apply process can take over the replication of that table. There are 2
possible scenarios
a) either apply has replayed more of the stream than sync did and then
the sync needs to ask apply to wait for it a bit (which blocks
replication for short while)
b) or the sync has replayed more of the stream than sync and then apply
needs to track the table for a while (and don't apply changes to it)
until it reaches the same position where sync stopped and once it
reaches that point it can just apply changes to it same as to any old table

+ *     - if the state was set to CATCHUP sync will read the stream and
+ *       apply changes until it catches up to the specified stream
+ *       position and then sets state to READY and signals apply that it
+ *       can stop waiting and exits, if the state was set to something
+ *       else than CATCHUP the sync process will simply end
+ *     - if the state was set to SYNCDONE by apply, the apply will
+ *       continue tracking the table until it reaches the SYNCDONE stream
+ *       position at which point it sets state to READY and stops tracking
+ *
+ *    Example flows look like this:
+ *     - Apply is infront:
+ *	      sync:8   -> set SYNCWAIT
+ *        apply:10 -> set CATCHUP
+ *        sync:10  -> set ready
+ *          exit
+ *        apply:10
+ *          stop tracking
+ *          continue rep
+ *    - Sync infront:
+ *        sync:10
+ *          set SYNCWAIT
+ *        apply:8
+ *          set SYNCDONE
+ *        sync:10
+ *          exit
+ *        apply:10
+ *          set READY
+ *          stop tracking
+ *          continue rep

This definitely needs to be expanded a bit. Where are we tracking how
far replication has progressed on individual tables? Are we creating new
slots for syncing? Is there any parallelism in syncing?

Yes, new slots, tracking is in pg_subscription_rel, parallelism is not
there yet, but the design is ready for expanding it (I currently
artificially limit the number of sync workers to one to limit potential
bugs, but afaik it could just be bumped to more and it should work).

+/*
+ * Exit routine for synchronization worker.
+ */
+static void
+finish_sync_worker(char *slotname)
+{
+	LogicalRepWorker   *worker;
+	RepOriginId			originid;
+	MemoryContext		oldctx = CurrentMemoryContext;
+
+	/*
+	 * Drop the replication slot on remote server.
+	 * We want to continue even in the case that the slot on remote side
+	 * is already gone. This means that we can leave slot on the remote
+	 * side but that can happen for other reasons as well so we can't
+	 * really protect against that.
+	 */
+	PG_TRY();
+	{
+		wrcapi->drop_slot(wrchandle, slotname);
+	}
+	PG_CATCH();
+	{
+		MemoryContext	ectx;
+		ErrorData	   *edata;
+
+		ectx = MemoryContextSwitchTo(oldctx);
+		/* Save error info */
+		edata = CopyErrorData();
+		MemoryContextSwitchTo(ectx);
+		FlushErrorState();
+
+		ereport(WARNING,
+				(errmsg("there was problem dropping the replication slot "
+						"\"%s\" on provider", slotname),
+				 errdetail("The error was: %s", edata->message),
+				 errhint("You may have to drop it manually")));
+		FreeErrorData(edata);

ISTM we really should rather return success/failure here, and not throw
an error inside the libpqwalreceiver stuff. I kind of wonder if we
actually can get rid of this indirection.

Yeah I can do success/failure. Not sure what you mean by indirection.

+		 * to ensure that we are not behind it (it's going to wait at this
+		 * point for the change of state). Once we are infront or at the same
+		 * position as the synchronization proccess we can signal it to
+		 * finish the catchup.
+		 */
+		if (tstate->state == SUBREL_STATE_SYNCWAIT)
+		{
+			if (end_lsn > tstate->lsn)
+			{
+				/*
+				 * Apply is infront, tell sync to catchup. and wait until
+				 * it does.
+				 */
+				tstate->state = SUBREL_STATE_CATCHUP;
+				tstate->lsn = end_lsn;
+				StartTransactionCommand();
+				SetSubscriptionRelState(MyLogicalRepWorker->subid,
+										tstate->relid, tstate->state,
+										tstate->lsn);
+				CommitTransactionCommand();
+
+				/* Signal the worker as it may be waiting for us. */
+				LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
+				worker = logicalrep_worker_find(MyLogicalRepWorker->subid,
+												tstate->relid);
+				if (worker && worker->proc)
+					SetLatch(&worker->proc->procLatch);
+				LWLockRelease(LogicalRepWorkerLock);

Different parts of this file use different lock level to set the
latch. Why?

The latch does not need the lock, not really following what you mean.
But the lock here is for the benefit of logicalrep_worker_find.

+				if (wait_for_sync_status_change(tstate))
+					Assert(tstate->state == SUBREL_STATE_READY);
+			}
+			else
+			{
+				/*
+				 * Apply is either behind in which case sync worker is done
+				 * but apply needs to keep tracking the table until it
+				 * catches up to where sync finished.
+				 * Or apply and sync are at the same position in which case
+				 * table can be switched to standard replication mode
+				 * immediately.
+				 */
+				if (end_lsn < tstate->lsn)
+					tstate->state = SUBREL_STATE_SYNCDONE;
+				else
+					tstate->state = SUBREL_STATE_READY;
+

What I'm failing to understand is how this can be done under
concurrency. You probably thought about this, but it should really be
explained somewhere.

Well, so, if the original state was syncdone (the previous branch) the
apply won't actually do any work until the state changes (and it can
only change to either syncdone or ready at that point) so there is no
real concurrently. If reach this branch then either sync worker already
exited (if it set the state to syncdone) or it's not doing anything and
is waiting for apply to set state to ready in which case there is also
no concurrency.

+		/*
+		 * In case table is supposed to be synchronizing but the
+		 * synchronization worker is not running, start it.
+		 * Limit the number of launched workers here to one (for now).
+		 */

Hm. That seems problematic for online upgrade type cases, we might never
be catch up that way...

You mean the limit to 1? That's just because I didn't get to creating
GUC for configuring this.

+				/*
+				 * We want to do the table data sync in single
+				 * transaction so do not close the transaction opened
+				 * above.
+				 * There will be no BEGIN or COMMIT messages coming via
+				 * logical replication while the copy table command is
+				 * running so start the transaction here.
+				 * Note the memory context for data handling will still
+				 * be done using ensure_transaction called by the insert
+				 * handler.
+				 */
+				StartTransactionCommand();
+
+				/*
+				 * Don't allow parallel access other than SELECT while
+				 * the initial contents are being copied.
+				 */
+				rel = heap_open(tstate.relid, ExclusiveLock);

Why do we want to allow access at all?

I didn't see reason to not allow selects.

@@ -87,6 +92,8 @@ _PG_output_plugin_init(OutputPluginCallbacks *cb)
cb->commit_cb = pgoutput_commit_txn;
cb->filter_by_origin_cb = pgoutput_origin_filter;
cb->shutdown_cb = pgoutput_shutdown;
+	cb->tuple_cb = pgoutput_tuple;
+	cb->list_tables_cb = pgoutput_list_tables;
}

What are these new, and undocumented callbacks actually doing? And why
is this integrated into logical decoding?

In the initial email I was saying that I am not very happy with this
design, that's still true, because they don't belong to decoding.

/*
+ * Handle LIST_TABLES command.
+ */
+static void
+SendTableList(ListTablesCmd *cmd)
+{

Ugh.

I really dislike this kind of command. I think we should instead change
things around, allowing to issue normal SQL via the replication
command. We'll have to error out for running sql for non-database
connected replication connections, but that seems fine.

Note per discussion offline we agree to do this stuff over normal
connection for now.

--
Petr Jelinek 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

#59Petr Jelinek
petr@2ndquadrant.com
In reply to: Andres Freund (#57)
Re: Logical Replication WIP

On 14/09/16 21:53, Andres Freund wrote:

Hi,

On 2016-09-14 21:17:42 +0200, Petr Jelinek wrote:

+/*
+ * Gather Relations based o provided by RangeVar list.
+ * The gathered tables are locked in access share lock mode.
+ */

Why access share? Shouldn't we make this ShareUpdateExclusive or
similar, to prevent schema changes?

Hm, I thought AccessShare would be enough to prevent schema changes that
matter to us (which is basically just drop afaik).

Doesn't e.g. dropping an index matter as well?

Drop of primary key matters I guess.

+					if (strcmp($1, "replicate_insert") == 0)
+						$$ = makeDefElem("replicate_insert",
+										 (Node *)makeInteger(TRUE), @1);
+					else if (strcmp($1, "noreplicate_insert") == 0)
+						$$ = makeDefElem("replicate_insert",
+										 (Node *)makeInteger(FALSE), @1);
+					else if (strcmp($1, "replicate_update") == 0)
+						$$ = makeDefElem("replicate_update",
+										 (Node *)makeInteger(TRUE), @1);
+					else if (strcmp($1, "noreplicate_update") == 0)
+						$$ = makeDefElem("replicate_update",
+										 (Node *)makeInteger(FALSE), @1);
+					else if (strcmp($1, "replicate_delete") == 0)
+						$$ = makeDefElem("replicate_delete",
+										 (Node *)makeInteger(TRUE), @1);
+					else if (strcmp($1, "noreplicate_delete") == 0)
+						$$ = makeDefElem("replicate_delete",
+										 (Node *)makeInteger(FALSE), @1);
+					else
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("unrecognized publication option \"%s\"", $1),
+									 parser_errposition(@1)));
+				}
+		;

I'm kind of inclined to do this checking at execution (or transform)
time instead. That allows extension to add options, and handle them in
utility hooks.

Thant's interesting point, I prefer the parsing to be done in gram.y, but it
might be worth moving it for extensibility. Although there are so far other
barriers for that.

Citus uses the lack of such check for COPY to implement copy over it's
distributed tables for example. So there's some benefit.

Yeah I am not saying that I am fundamentally against it, I am just
saying it won't help all that much probably.

+	check_subscription_permissions();
+
+	rel = heap_open(SubscriptionRelationId, RowExclusiveLock);
+
+	/* Check if name is used */
+	subid = GetSysCacheOid2(SUBSCRIPTIONNAME, MyDatabaseId,
+							CStringGetDatum(stmt->subname));
+	if (OidIsValid(subid))
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("subscription \"%s\" already exists",
+						stmt->subname)));
+	}
+
+	/* Parse and check options. */
+	parse_subscription_options(stmt->options, &enabled_given, &enabled,
+							   &conninfo, &publications);
+
+	/* TODO: improve error messages here. */
+	if (conninfo == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("connection not specified")));

Probably also makes sense to parse the conninfo here to verify it looks
saen. Although that's fairly annoying to do, because the relevant code
is libpq :(

Well the connection is eventually used (in later patches) so maybe that's
not problem.

Well, it's nicer if it's immediately parsed, before doing complex and
expensive stuff, especially if that happens outside of the transaction.

Maybe, it's not too hard to add another function to libpqwalreceiver I
guess.

diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 65230e2..f3d54c8 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c

I think you might be missing outfuncs support.

I thought that we don't do outfuncs for DDL?

I think it's just readfuncs that's skipped.

I see only couple odd DDL commands in outfuncs.c.

+                Length of column name (including the NULL-termination
+                character).
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>
+        String
+</term>
+<listitem>
+<para>
+                Name of the column.
+</para>
+</listitem>
+</varlistentry>

Huh, no type information?

It's not necessary for the text transfer, it will be if we ever add binary
data transfer but that will require protocol version bump anyway.

I'm *hugely* unconvinced of this. For one type information is useful for
error reporting and such as well. For another, it's one thing to add a
new protocol message (for differently encoded tuples), and something
entirely different to change the format of existing messages.

Well it's one if on wrrite and one if on read side in this case, but I
can add it, it's rather simple change. One thing that we need to clarify
is how we actually send type info, I think for builtin types Oid should
be enough, but for all other ones we need qualified name of the type IMHO.

+
+/*
+ * COMMIT callback
+ */
+static void
+pgoutput_commit_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
+					 XLogRecPtr commit_lsn)
+{
+	OutputPluginPrepareWrite(ctx, true);
+	logicalrep_write_commit(ctx->out, txn, commit_lsn);
+	OutputPluginWrite(ctx, true);
+}

Hm, so we don't reset the context for these...

What?

We only use & reset the data-> memory context in the change
callback. I'm not sure that's good.

Well we don't do anything with the data memory context here.

This however I'm not following. Why do we need multiple copies of this?
And why aren't we doing the assignments in _PG_init? Seems better to
just allocate one WalRcvCalllbacks globally and assign all these as
constants. Then the establishment function can just return all these
(as part of a bigger struct).

Meh, If I understand you correctly that will make the access bit more ugly
(multiple layers of structs).

On the other hand, you right now need to access one struct, and pass the
other...

Point taken.

This does rather reinforce my opinion that the _PG_init removal in
libpqwalreceiver isn't useful.

I don't see how it helps, you said we'd still return struct from some
interface so this would be more or less the same?

Or we just set some global vars and use them directly.

I really hate the "global vars filled by external library when loaded"
as design pattern, it's how it was done before but it's ugly, especially
when you share the library between multiple C modules later.

--
Petr Jelinek 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

#60Craig Ringer
craig@2ndquadrant.com
In reply to: Petr Jelinek (#50)
Re: Logical Replication WIP

On 14 September 2016 at 04:56, Petr Jelinek <petr@2ndquadrant.com> wrote:

Not sure what you mean by negotiation. Why would that be needed? You know
server version when you connect and when you know that you also know what
capabilities that version of Postgres has. If you send unrecognized option
you get corresponding error.

Right, because we can rely on the server version = the logical
replication version now.

All good.

--
Craig Ringer 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

#61Steve Singer
steve@ssinger.info
In reply to: Petr Jelinek (#40)
Re: Logical Replication WIP

On 09/08/2016 06:59 PM, Petr Jelinek wrote:

- the CREATE SUBSCRIPTION also tries to check if the specified
connection connects back to same db (although that check is somewhat
imperfect) and if it gets stuck on create slot it should be normally
cancelable (that should solve the issue Steve Singer had)

When I create my subscriber database by doing a physical backup of the
publisher cluster (with cp before I add any data) then I am unable to
connect subscribe.
ie
initdb ../data
cp -r ../data ../data2
./postgres -D ../data
./postgres -D ../data2

This make sense when I look at your code, but it might not be what we want

I had the same issue when I created my subscriber cluster with
pg_basebackup (The timeline on the destination cluster still shows as 1)

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

#62Steve Singer
steve@ssinger.info
In reply to: Petr Jelinek (#40)
Re: Logical Replication WIP

On 09/08/2016 06:59 PM, Petr Jelinek wrote:

Hi,

Updated version, this should address most of the things in Peter's
reviews so far, not all though as some of it needs more discussion.

Another bug report.

I had subscribed a subscriber database to a publication with 1 table
create table a (a serial4 primary key, b text);

* I then dropped column b on the subscriber
* inserted some rows on the publisher
* Noticed the expected error about column b not existing in the
subscriber log
* Added column c on the subscriber, then added column b after column C

I now get the following stack trace

#1 0x00000000007dc8f9 in cstring_to_text (
s=0x16f238af0 <error: Cannot access memory at address 0x16f238af0>)
at varlena.c:152
#2 0x00000000008046a3 in InputFunctionCall (
flinfo=flinfo@entry=0x7fffa02d0250,
str=str@entry=0x16f238af0 <error: Cannot access memory at address
0x16f238af0>, typioparam=typioparam@entry=25, typmod=typmod@entry=-1) at
fmgr.c:1909
#3 0x0000000000804971 in OidInputFunctionCall (functionId=<optimized out>,
str=0x16f238af0 <error: Cannot access memory at address 0x16f238af0>,
typioparam=25, typmod=-1) at fmgr.c:2040
#4 0x00000000006aa485 in SlotStoreCStrings (slot=0x2748670,
values=0x7fffa02d0330) at apply.c:569
#5 0x00000000006ab45c in handle_insert (s=0x274d088) at apply.c:756
#6 0x00000000006abcea in handle_message (s=0x7fffa02d3e20) at apply.c:978
#7 LogicalRepApplyLoop (last_received=117457680) at apply.c:1146
#8 0x00000000006ac37e in ApplyWorkerMain (main_arg=<optimized out>)
at apply.c:1530

In SlotStoreCStrings values only has 2 elements but natts is 4

Changes:
- I moved the publication.c to pg_publication.c, subscription.c to
pg_subscription.c.
- changed \drp and \drs to \dRp and \dRs
- fixed definitions of the catalogs (BKI_ROWTYPE_OID)
- changed some GetPublication calls to get_publication_name
- fixed getObjectIdentityParts for OCLASS_PUBLICATION_REL
- fixed get_object_address_publication_rel
- fixed the dependencies between pkeys and publications, for this I
actually had to add new interface to depenency.c that allows dropping
single dependency
- fixed the 'for all tables' and 'for tables all in schema' publications
- changed the alter publication from FOR to SET
- added more test cases for the publication DDL
- fixed compilation of subscription patch alone and docs
- changed subpublications to name[]
- added check for publication list duplicates
- made the subscriptions behave more like they are inside the database
instead of shared catalog (even though the catalog is still shared)
- added options for for CREATE SUBSCRIPTION to optionally not create
slot and not do the initial data sync - that should solve the
complaint about CREATE SUBSCRIPTION always connecting
- the CREATE SUBSCRIPTION also tries to check if the specified
connection connects back to same db (although that check is somewhat
imperfect) and if it gets stuck on create slot it should be normally
cancelable (that should solve the issue Steve Singer had)
- fixed the tests to work in any timezone
- added DDL regress tests for subscription
- added proper detection of missing schemas and tables on subscriber
- rebased on top of 19acee8 as the DefElem changes broke the patch

The table sync is still far from ready.

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

#63Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Steve Singer (#61)
Re: Logical Replication WIP

On 9/18/16 4:17 PM, Steve Singer wrote:

When I create my subscriber database by doing a physical backup of the
publisher cluster (with cp before I add any data) then I am unable to
connect subscribe.
ie
initdb ../data
cp -r ../data ../data2
./postgres -D ../data
./postgres -D ../data2

This make sense when I look at your code, but it might not be what we want

I think if we want to prevent the creation of subscriptions that point
to self, then we need to create a magic token when the postmaster starts
and check for that when we connect. So more of a running-instance
identifier instead of a instance-on-disk identifier.

The other option is that we just allow it and make it more robust.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#64Steve Singer
steve@ssinger.info
In reply to: Peter Eisentraut (#63)
Re: Logical Replication WIP

On Tue, 20 Sep 2016, Peter Eisentraut wrote:

On 9/18/16 4:17 PM, Steve Singer wrote:

I think if we want to prevent the creation of subscriptions that point
to self, then we need to create a magic token when the postmaster starts
and check for that when we connect. So more of a running-instance
identifier instead of a instance-on-disk identifier.

The other option is that we just allow it and make it more robust.

I think we should go with the second option for now. I feel that the effort
is better spent making sure that initial syncs that have don't subscribe
(for whatever reasons) can be aborted instead of trying to build a concept of
node identity before we really need it.

Steve

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#65Petr Jelinek
petr@2ndquadrant.com
In reply to: Steve Singer (#64)
Re: Logical Replication WIP

On 21/09/16 05:35, Steve Singer wrote:

On Tue, 20 Sep 2016, Peter Eisentraut wrote:

On 9/18/16 4:17 PM, Steve Singer wrote:

I think if we want to prevent the creation of subscriptions that point
to self, then we need to create a magic token when the postmaster starts
and check for that when we connect. So more of a running-instance
identifier instead of a instance-on-disk identifier.

The other option is that we just allow it and make it more robust.

I think we should go with the second option for now. I feel that the
effort is better spent making sure that initial syncs that have don't
subscribe (for whatever reasons) can be aborted instead of trying to
build a concept of node identity before we really need it.

Well connecting to yourself will always hang though because the slot
creation needs snapshot and it will wait forever for the current query
to finish. So it will never really work. The hanging query is now
abortable though.

Question is if doing the logical snapshot is really required since we
don't really use the snapshot for anything here.

--
Petr Jelinek 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

#66Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Petr Jelinek (#40)
Re: Logical Replication WIP

Some partial notes on 0005-Add-logical-replication-workers.patch:

Documentation still says that TRUNCATE is supported.

In catalogs.sgml for pg_subscription column subpublications I'd add a
note that those are publications that live on the remote server.
Otherwise one might think by mistake that it references pg_publication.

The changes in reference.sgml should go into an earlier patch.

Document that table and column names are matched by name. (This seems
obvious, but it's not explained anywhere, AFAICT.)

Document to what extent other relation types are supported (e.g.,
materialized views as source, view or foreign table or temp table as
target). Suggest an updatable view as target if user wants to have
different table names or write into a different table structure.

subscriptioncmds.c: In CreateSubscription(), the
CommandCounterIncrement() call is apparently not needed.

subscriptioncmds.c: Duplicative code for libpqwalreceiver loading and
init, should be refactored.

subscriptioncmds.c: Perhaps combine logicalrep_worker_find() and
logicalrep_worker_stop() into one call that also encapsulates the
required locking.

001_rep_changes.pl: The TAP protocol does not allow direct printing to
stdout. (It needs to be prefixed with # or with spaces or something; I
forget.) In this case, the print calls can just be removed, because the
following is() calls in each case will print the failing value anyway.

In get_subscription_list(), the memory context pointers don't appear to
do anything useful, because everything ends up being CurrentMemoryContext.

pg_stat_get_subscription(NULL) for "all" seems a bit of a weird interface.

pglogical_apply_main not used, should be removed.

In logicalreprel_open(), the error message "cache lookup failed for
remote relation %u" could be clarified. This message could probably
happen if the protocol did not send a Relation message first. (The term
"cache" is perhaps inappropriate for LogicalRepRelMap, because it
implies that the value can be gotten from elsewhere if it's not in the
cache. In this case it's really session state that cannot be recovered
easily.)

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#67Petr Jelinek
petr@2ndquadrant.com
In reply to: Peter Eisentraut (#66)
Re: Logical Replication WIP

On 21/09/16 15:04, Peter Eisentraut wrote:

Some partial notes on 0005-Add-logical-replication-workers.patch:

Document to what extent other relation types are supported (e.g.,
materialized views as source, view or foreign table or temp table as
target). Suggest an updatable view as target if user wants to have
different table names or write into a different table structure.

I don't think that's good suggestion, for one it won't work for UPDATEs
as we have completely different path for finding the tuple to update
which only works on real data, not on view. I am thinking of even just
allowing table to table replication in v1 tbh, but yes it should be
documented what target relation types can be.

subscriptioncmds.c: Perhaps combine logicalrep_worker_find() and
logicalrep_worker_stop() into one call that also encapsulates the
required locking.

I was actually thinking of moving the wait loop that waits for worker to
finish there as well.

In get_subscription_list(), the memory context pointers don't appear to
do anything useful, because everything ends up being CurrentMemoryContext.

That's kind of the point of the memory context pointers there though as
we start transaction inside that function.

pg_stat_get_subscription(NULL) for "all" seems a bit of a weird interface.

I modeled that after pg_stat_get_activity() which seems to be similar
type of interface.

pglogical_apply_main not used, should be removed.

Hah.

In logicalreprel_open(), the error message "cache lookup failed for
remote relation %u" could be clarified. This message could probably
happen if the protocol did not send a Relation message first. (The term
"cache" is perhaps inappropriate for LogicalRepRelMap, because it
implies that the value can be gotten from elsewhere if it's not in the
cache. In this case it's really session state that cannot be recovered
easily.)

Yeah I have different code and error for that now.

--
Petr Jelinek 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

#68Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Petr Jelinek (#67)
Re: Logical Replication WIP

On 9/23/16 9:28 PM, Petr Jelinek wrote:

Document to what extent other relation types are supported (e.g.,

materialized views as source, view or foreign table or temp table as
target). Suggest an updatable view as target if user wants to have
different table names or write into a different table structure.

I don't think that's good suggestion, for one it won't work for UPDATEs
as we have completely different path for finding the tuple to update
which only works on real data, not on view. I am thinking of even just
allowing table to table replication in v1 tbh, but yes it should be
documented what target relation types can be.

I'll generalize this then to: Determine which relation types should be
supported at either end, document that, and then make sure it works that
way. A restrictive implementation is OK for the first version, as long
as it keeps options open.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#69Michael Paquier
michael.paquier@gmail.com
In reply to: Peter Eisentraut (#68)
Re: Logical Replication WIP

On Wed, Sep 28, 2016 at 10:12 PM, Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:

On 9/23/16 9:28 PM, Petr Jelinek wrote:

Document to what extent other relation types are supported (e.g.,

materialized views as source, view or foreign table or temp table as
target). Suggest an updatable view as target if user wants to have
different table names or write into a different table structure.

I don't think that's good suggestion, for one it won't work for UPDATEs
as we have completely different path for finding the tuple to update
which only works on real data, not on view. I am thinking of even just
allowing table to table replication in v1 tbh, but yes it should be
documented what target relation types can be.

I'll generalize this then to: Determine which relation types should be
supported at either end, document that, and then make sure it works that
way. A restrictive implementation is OK for the first version, as long
as it keeps options open.

The newest patch is 3-week old, so marking this entry as returned with feedback.
--
Michael

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

#70Petr Jelinek
petr@2ndquadrant.com
In reply to: Michael Paquier (#69)
7 attachment(s)
Re: Logical Replication WIP

Hi,

attached is updated version of the patch.

There are quite a few improvements and restructuring, I fixed all the
bugs and basically everything that came up from the reviews and was
agreed on. There are still couple of things missing, ie column type
definition in protocol and some things related to existing data copy.

The biggest changes are:

I added one more prerequisite patch (the first one) which adds ephemeral
slots (or well implements UI on top of the code that was mostly already
there). The ephemeral slots are different in that they go away either on
error or when session is closed. This means the initial data sync does
not have to worry about cleaning up the slots after itself. I think this
will be useful in other places as well (for example basebackup). I
originally wanted to call them temporary slots in the UI but since the
behavior is bit different from temp tables I decided to go with what the
underlying code calls them in UI as well.

I also split out the libpqwalreceiver rewrite to separate patch which
does just the re-architecture and does not really add new functionality.
And I did the re-architecture bit differently based on the review.

There is now new executor module in execReplication.c, no new nodes but
several utility commands. I moved there the tuple lookup functions from
apply and also wrote new interfaces for doing inserts/updates/deletes to
a table including index updates and constraints checks and trigger
execution but without the need for the whole nodeModifyTable handling.

What I also did when rewriting this is implementation of the tuple
lookup also using sequential scan so that we can support replica
identity full properly. This greatly simplified the dependency handling
between pkeys and publications (by removing it completely ;) ). Also
when there is replica identity full and the table has primary key, the
code will use the primary key even though it's not replica identity
index to lookup the row so that users who want to combine the logical
replication with some kind of other system that requires replica
identity full (ie auditing) they still get usable experience.

The way copy is done was heavily reworked. For one it uses the ephemeral
slots mentioned above. But more importantly there are now new custom
commands anymore. Instead the walsender accepts some SQL, currently
allowed are BEGIN, ROLLBACK, SELECT and COPY. The way that is
implemented is probably not perfect and it could use look from somebody
who knows bison better. How it works is that if the command sent to
walsender starts with one of the above mentioned keywords the walsender
parser passes the whole query back and it's passed then to
exec_simple_query. The main reason why we need BEGIN is so that the COPY
can use the snapshot exported by the slot creation so that there is
synchronization point when there are concurrent writes. This probably
needs more discussion.

I also tried to keep the naming more consistent so cleaned up all
mentions of "provider" and changed them to "publisher" and also
publications don't mention that they "replicate", they just "publish"
now (that has effect on DDL syntax as well).

Some things that were discussed in the reviews that I didn't implement
knowingly include:

Removal of the Oid in the pg_publication_rel, that's mainly because it
would need significant changes to pg_dump which assumes everything
that's dumped has Oid and it's not something that seems worth it as part
of this patch.

Also didn't do the outfuncs, it's unclear to me what are the rules there
as the only DDL statement there is CreateStmt atm.

There are still few TODOs:

Type info for columns. My current best idea is to write typeOid and
typemod in the relation message and add another message (type message)
that describes the type which will skip the built-in types (as we can't
really remap those without breaking a lot of software so they seem safe
to skip). I plan to do this soonish barring objections.

Removal of use of replication origin in the table sync worker.

Parallelization of the initial copy. And ability to resync (do new copy)
of a table. These two mainly wait for agreement over how the current way
of doing copy should work.

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

Attachments:

0001-Add-user-interface-for-EPHEMERAL-replication-slots.patch.gzapplication/gzip; name=0001-Add-user-interface-for-EPHEMERAL-replication-slots.patch.gzDownload
0002-Make-libpqwalreceiver-reentrant.patch.gzapplication/gzip; name=0002-Make-libpqwalreceiver-reentrant.patch.gzDownload
0003-Add-PUBLICATION-catalogs-and-DDL.patch.gzapplication/gzip; name=0003-Add-PUBLICATION-catalogs-and-DDL.patch.gzDownload
�"�
X0003-Add-PUBLICATION-catalogs-and-DDL.patch�=�s���?��w�IjY�,����c+���')�]'�h(�XS�JRv|/��ow�	R������Z"�]�v~�v`O��j���ag��������XS�>8�os��L�������=6�K���z��c��zc�-�y��y�_��x��-_�����U�G��gV�_�q��M����F�5�/�-�[|����w>�^��'���Y����Nl�]�sq~z2<��d+�\2�����������6��I-������m�x��?����?��Ok��N��?��Mf��x0Z����;��Co���7�$� �g��Aj��i����Y�2����/F&-�<���e�����
��X�w�
G�`��=3���p	�W'��:��|,�x�`��N�)B4�.g�'	l��UU�i��(�#��:�,�W!��#�_,����������-��Q8��I�0
����3;��
�42�?��*����r<MIHC��#��m)������:��5�?V�k�B�Fhi!j��ZT���J���d�����"�u��,���n�;l�Y�9�q����M��
Vck���P��Fj�KR�����2��4�����Q�sc��z��<�(��(��F�����Q�����Z�� �
�(��>���aMg��@ �F��%�A�!����8��Q)L#Y�=�n���@)r��v���+��|�<��~��o�aK�`}g�O���~Ze0����0��L�w9����]��<T������<�m���@����c��O���1�;�n���u�(��f1�p1�F���0��d�z0�[*��x��]�K��VCM�_E�v�L�4Xa�E@iw���B��=b'7��I��C���Q���v5�i08�4K����t��CF�#��-o��n���g�zF�����
pbs�t��|���A��!I��[�M��`2����	�t��j=`a�R���<P�����.�t�����,��mg:e{{3'bV�db4.y�M���v��q�Z���
�n��s�����P���k���lW�-��_��#���w��,���]y9op|�����h^�H�{K��Jc��(���$��?��{TS�|�����M��AOp�2E�P��n|�X`���q�6�N�5���g
���K���0�6d"@��#�����w<��Bi����/y�����]4�
4��F�Qi��Mk��0�hZ����3��|�����\��%��BjG4��q/���/��-ga��e���	e-�w��L`�9������&��.$�f����X�m[!�
Rd�BL�8���Bc���Zx�.<@2��(t�W;-)%�=��}\2�.i���<������e����.������}���\)���]��w�X6�F1G8Dk�V���W��;�I�����E0�YE�%[����9��:�����T���T��<���l
�>�����?V��X������M[��_� $�����(.���&��j��ZE>�O�V�@���4�y1���������3+bw��Md��t�����Tx"]���;���C�/y =��$��d&$2�v"��5��T��������>;���iV�my?�]��6�@a�����q�LS��MR�n��R�G�(i�Ei����R���tE�����;�K*
W��,&��@��(���|�/��w�����<�/r��8���s\��r����J�']�I�V�n�Z��^��A���#������0'#��h�������y��dNS�C��iJ*�(+�����k��J��nw�v���4���SRQ��0TU����"
+�J���{���B3��l����`�e��<%8F�	@�~p_�fG����eT���P��hl��`�{�vDy�_��(s3�[k�@6�����k�V����.�Y��TFe0S��w���5Y:�gE`��U!�`2����U��Gw�O�b���u,���J�a3ZN���,hz���YB���B�)Z��4�7kZ��J���5]g���n4h<7�'�6��3�Jh�������9����gf0},�`E:&8m(g��4�-�����KP�(�v��>�u(]������"c0�y���@]C�a�.~�U�jN���O���5o��c?h��J��/;�~����������B��������2z����G9��
�.P��v{'�^_y3�r��t_s�N���x�B��w��iLn��,x$�1	Y"e0���4n}�[-�;G��"��p�	��Q{���5�l"�(���&����(I"�2�s�`���L=��-W����X��&>u<����i���v-i	�=:��y_��A�~�ZN��"�������;���w�E�Pis�>��:��>���1�x�Z��df�y3���9$��B7�<�����\�]�����#Yk��.��[�����,��
��lCY�P�[��S�����
O�\���Rj>2)��b�����\\��K68����d&�{
�=
U���{RJ��$�����SI	fm�'�G�4�����d
�U9�j�^��:�W�V�X�Y�J����*x�39�<�%5��ze>^j	Y��\�@Tb��	�Ng��Df��0Qp?I�x~�0<�������c�o9[���W�5�"��������pI�*���
�N����)>�$*_6��p4�I������h.*y��bD���Z|U�QG�ko�����r�2�����4%��/|���n��� v.T'\��
���������JM[V��#\���e��/����$��xDK�wQ�b�<f�����E��h
!�L�|+2S��y��$ ���
F&�F�9�:�
�B������g3Z�]6�8p�%�Bt�~F!Dv��o9���g��_�,k���(�z��������f�i�BFein�#QJaG�l���2Fl����HR�������:��%t���5hU���:�Q���'k �X��&<��J�Q-��:�c��9�ZhT��a���fPz�����gi��j��C2'p��P��b?pf�G������>)��1	'`��0�����m<��Ww*���kgG���$~�V���?_����XQ
��_F��+*L����c�g�njq#���da�+�\��O������wad�[�\����Y_��fW����z�h�����D4��8�
m��(�:6�hq�2���(q�@2}FT/x�J���Q�'U��D�s�f�72,�B�c��Sh5�W2TI�����#�������QM �j�����h��<qC?Cf���S���{�v7\�k�"S>�l�pT����b���_	Q�����:g�HC��U�M*�����inV�,�H)u����Rg���:
����i��V�4����;M���zg���'U9!+Kz�g���MDD�������ND$���|�"��q��3�7�����j�k�������Z��|���^������rn��[�A@O�x���N�'Z4��� �)rO�t~i��{��3�_e;+�[t!�!7��XL�j��"�)^��-����a�GU��
��B�E������u�} k@aR��V��b��M��o�+(�}!�2	G���;�9%�`�3�e��VW�_qd���W�A����;+�	�����wT�L����\�����8�Z���v��z<��E��+��?����C
A6���?M�Hop������p�����$�(oU�H�`�5��~�u�@G.����}�<����VHL��*$��_���J7���
���R�d�z2�<� w�G%�8z�>���r�W��[�����5t�B�(��s��D��-y���d����W�����;'���I?�km��_H���D�e5��������(G��G�c��[��0��7�e�X�<�a�����~-r��tI�=���	��8;�_��b
�>x^�g?�f1G*�H]��*�p���	���x��:L%X���s��]�d�������{Q�I��J5�V�9�h�3�������he��aiQ&
q}x��D�j����*�L�B+�r�xnNK�KX����p!�#y�������$~B:���R9%x�����y����#k���������_(:'\�����z�R~<0E�Q7&��� �6������r"cy�P��������zrmA,��+���#�f���_U&/-$������D��2���?C��|d�&%�����ck����@�m������������s��-���������������<cM\�+S�������t<���o�.`��$�����������5g�/�8Mk�h���`�����a�i6���*OX�����hK�J�~���{|��^b����%k�k��'���A��0�5�����y�S�v��fZ��TGQ��F���]�c��s�����1{���Y�29�)3�X���2��������G��ev$�\_�]c|���nI.�5�a���.e���
d�B9G����� �y�j��|j�c��dNzK:�����|/�`M��)N�R<��q�v���\���x�O���U����d6�'����\nZ�0s�T8���rb�LF�Yw]��
f�Y�~�5f���r����:����HU�Jw��&�t��V��v��U�Tx�/P[�yj-:M�E�O���{�_�M�����%)���m�!n���W���d�R8�z�����B�V���'��.dIdn{��T!3����m�$��Bh~�K���U�5�2�b�f
Y�>V�2&I3��������6&��n��V�v�0s�A)a��M��*]����7����h�j\���}6��>����v���\�@������o��o����O&x����S�}����$=3������+��T��/������w�����\��0
�o�H���k3.�7T>~��K����D3:����Y��=�m�#��G��	>���^(-B�V(�^��v��J�o��[��5��!r�7�8{�o�bK�������^��D/��]$�
Y�V������j0�����������30�%8J�|�p[w0���y�t�l4�L��;3o$����d�vw���#�>���rag�*�~��3��2O�s�4�+�Ej��T�,��e}j���;��x�6�M40g������g�OY�g�@m��Zv��G�<8���x����;��B�l�Kg����-�������C�;-b5>���w�w�OUel�H(������K��
D�Gn��Mj2;$�6��v����U$����g���� "�B��%Px���xs��E2r�����!o��b��1)�j����gW�-��8=��>���7�^,��`�s���U��]�^����{��
�X����>���"��.��a����z}%���p}s�_�����E��~��4�T`T03����t
c�I�4���������;j��K����C�~�Ui�P��&�R&EN�>���(��o�Dl�Z��t��b����s��h�w,>���/��2��~wkk���)[1W/��������Mq���DI	��PM�!g:�D�/�/$7Q��#�Z���V����S>��GB��]�i�yBP�&A�V0y�,S���>��2��U���U��5��H����(���E���i%c�}�l��N�:m�gw},��)���("�GD���x����7"��BP���!2�d��Tt�'6fna�@�:���Hf��2�
�?�����l4���Gw�@S��?+���h�f��y"z=GS���u~��uy��G�b�T1������wk����\��l���{
� �p���}��FG�M��(�8����P`�����S��c�t(�
���i�K�w$��$��H��*0"�C���7bE@���I�_b�)����^���N�^9T���mp�#)A9�1��
�3�����+8��W��V�QY���t���d����7�0<�2>}`��`�����+����U�irl���z��y�6��&YZ2���(������t��F���RJHGz4�n�f��fGL{�����S/�a�`����.G�^������s-��?>9=�����
�:�[}�%e$8I9��&#����Q��Yj�[�K8�E���F����C���'���q�`��N�K!%������?�#K�[�=���,�z[�%V���R2�I.�����h�{^U]U]
��'��Qft��N�W�G��.�{����\s���s�
�j�����*[,Gl��X��>��/���*��%������{�.����@<�N�xZZ������|��'b���\_}e�|}���
z�4z�^E%���*5��V���'I��o&���j1^_��@
�P���yX�ggG$�
�a���VH���U10A��~��hU��:;V:�n<�
N�]�3�Q[����d74O�X��{��#��3`��7_�������~��z��,nJh�=y6�N�������HOj��wX��P-���v���X�8#�����O6�����b;���V�j��;�U!^��K�
�=��������T��}�gj�l>��q����x���*m"�j/������
l����G�B���n5��f%i�\(��Z��`#z���+����]�4�8�w����z�o�SSc�\E6,J�����������
��_TgXU�4[���������'���d:��V���r�4@L�6��Z3$*�;������S2����|�W���%V��Uh�4�Y:(�I�d����E�g�wt���$�y"y8y�����:��1������\�y�Ro���q��w%����K66�k��_`���l��q3�a��rcC�eT�:
��h'3���g)J�~<���q�������y�8�2����$���W�~\�"�qW�|-O��Y��g@l�
}�-����o��7�j���sz(2X�"�n��U��K��*�-6(�m��� ���/O�o�/����O;��P������E�����+��q��x
BG���yq�j}f���/c9���V�OzwMnf�%:��_��<����;����iG���:1�!�gx:T���X��]�����i��j�_����Wyk��8��x�(�I\H�'�C��^D.2)�wo:/��B�~��5+��76���.�v��r���W��U��=�R����~XA������pz��w
p�����; �N&�[���%�:M���i��M���U(z���-���1��0������y�J����56
�����9�����p�]7Ce�X��P]���N�Tz'��p#8m����x/^k$%�6�`�"R1[��
[��`������?����Z�&�W[}�a�
�(�K��_�Taz�-��p�Q���7��2!n���G�����J�f�"u���45���vK	u��`�sE����'D�_�� <e�N�"
��������C���8�����;H�f����bnc�[X��si����c�A�CH����7</��/Gw��^lvmg���&%���_��l�%iY�����o��l��*�wV��j�/�xu���%�#6SB�TU�*�x&��%�X���b����E�'P�v��N��fcqZ.+�������,��j.��(��g/�Jp���g�3��}���J�^�;;d������CM���N�	����� ��0r�h:km�<������v��Am$��pm=��p�D��`O��38���>GBx�/���y4��G��K����A������b8��a���������$W����7����
���7�k��0��8���5��r�Q`�,��6$�1����/x7��+���t�����)�q!��G7���*�5��gq�2��6_N���/�!D�����|�Dq2��\���l��0IBw��Mo>�X�Z�K���������$
�5G�h��k����Z)��U��&�}��2W�M����#Q�
A�P�K�	9^%R�������Q&���'WO�����$��!CT":��`e�l��@^Wu����S$0d�v����\�<2�A�_���j9�RV��4}���|�DFL�Ix���O��S�6/���5O��=���Z	����>'�@d�tpK*1*2BaF�5��/�]����l��.�8�����Ig���^M�	�H���f>��P<�OZ����',���Y���qB@��I
��q#�����Dk�
�E�E�����g]��F��_��~0�w�T��u���cam)N�@1�����HM�R�F%��O�� ��|<���E�}���E��7�e�
X�l��#�L�5����4�*�a��:���l��j�G��f�6s��l�!]d	J���4�_��E��)n�_��a3d�F7,��&����>��HZ�l�;��U��������`��*��?��Z*>�����$]�O��
%x�e�3I]`���
phr� 7����}�Zs���{����eX�����Y��
���]�hJeGT%��(9������y��p�f�G��	�b'kk��/)�^S����MP2�G;+,R��":w7Z���}=�vw�F��Z�
;8�[�14�)�=��/�����H
t/�
B@T���\�.�X�V/4h|7�y���ZJ��x��='M�?��k��B���KTw��:J��Pn���������Ul��T�5XN�HW���qC�����?�VDj�&���L�Y��E9�*)H��P�pR��D9��t9z���/��5�BD���?�u{�����m1���X1F����Kk`r���.�z��������&
i\�fb��(\�a7���Ic�U�!���,�k�R��4�"�<�q`��L�S�(M���lZo,0iQ������q�����C�O28��lMJOU�O��N�
8\��w��W�e����?E�e50s���>}�������+�+�K������<�:L���(#�M�^	x��-�S=�4���S�#d�+���H��c�L ����*j�x�Fe2#}�m��1
� n�
s�*�"H��)��oWqmZ� �-8����F����oA�`R�H�����4�5��u����^��#I�P!�����7�>����6
��?f-*�J�C�P1�wT�d<�
��/n���,�2��cQ���!7�������+�'�5Z��K�����uq$�����QJIK���dd�L�^*�b���G��Ytm-�o�{z~��+����,3��O?��\�c(�-shNF��S���%�XJ}u�@���,������S���j�4��BE���E��6	U���<,e�tI�y8U~]:Q��!�[��*�c���mW��j�w5e��I�����f������P�����UN6��n��3�
Uhi��1`�b��E�'.����X|&o9,�}�=��w��*�V:���0�����$
���r"��=��������Z�3}�!U�B<8�!�If�����[|t{0���Z>Z�<|�A)��P��
��-'E��y��G��3�4h�'S;������+�������9���tbA��C^�q`[?9P��z�[��_���������U��`Mc� ����fG�����i�%6�mB%������Vw2�k���&��i]�R�r�n)���h�^8�b��H���Mf
ClhCi7'������l�@��4Y�d���G9!���e�r�[���2%��c1FR����d�Z���6��|�Qk��3�����Y���d��F0D�K��1^�T4g�?%���V.��t��������9��TC�-nK��uI|H�*�`��D�+���s��85�aA��[qk��#�e,��8�WN(7�Wq�+j!��������6�*�k"�+)���vP�@���l�c�r�u�	9�<1\J\�Yr}|L}�y
\H����1%!y��r%m����>L�2���1���'�0a�g}q�g�?���A����HC��1G�)nZ�(}��>p	�>
\��?
&d=y��v������
���ee[x��;C6}���r�����$�0�\�����eU}w�-h�D���K1y�������T���j�����=n�u�����I���q<A���9�!�g�zA��|�gq���=3w]w����.K[��H�����(2>;��cx��������l>�y�D��}����>�G�B�i��>�����t�4���c��JI9����^M<��Uh	���Z�v��`?���uo�����,#�����h^�6�.��#
@�uG�`� qzUL���B6�Y��g����u����$#z��Z�b�.����jH��Z�K��%\j����1����r��#�4�U���zZ�����_A���f����9�!;d<����dl����c7��%�9[
�k���v��\��w�!8� u�h@uW���j�I�n��4i�@���1�2��XX��Z-W���5YF���^>P@Wx�n��Q��u�PU�wZjl��T�w��}ZI�A��T�� 
�;��A�W��4\��b��\Qqe%OV�����$������"���Y���Ki��'��}�&�(J6�z�C��/N��0�����IW��+�+1�a%��&��r�,���=��*B!�4x��a?�H�vp��
�UW��"�;i�-�y�����	�I��;�3`��p��EQ�E)�����$���l��8�����pF����:w]�/��6�:��	�����"A���tQaU�C/7��w_7�
~��U����jW��jC�%�8�;���hy��1�v����92*�&0~_Z�&p'\�4������h�E���rP�z��;�~���J��	����%�}^���<����q�C����?'���)"yz?���x������b�c��AO�K��S�/h{�`�l�.�cq+�MM���p4�o*"oHlj�����������w��I��������[x�&A���p,o� ��	%W~9.���h���:�/~�eN�
b>yR���rQ���U�~��P��8x�����������*���eZ��
����;��|�`�����nC��-���8,��~V��hJ�|��H����t`���[�������:_^R�C����y��{�%Gr��m�3#/�U��:k��]�#��m��q �-OT��<fp�O'�u�8T(����f�����y�^�Th(~��q���=��?n|s����dE�xE���V�Mt�����z{7f����}�f�<������.t5��~s��EA�upr�\�����{����ro��n�������t����#������c�sz��s��TV��p�[�?�3N7`���]���z������	�����|����s<��mak���`���L�i�P1���k��`��!�����1Q��c�����TX��TK��R��=z�&S[dg+-d�XI��4S��O�:�v8�_i����c�z�4�]C����� ����@�M�z�n~�<k�f�Y%u�q'^�z �,����!�Hk���e���o�Z�B�!�������x������-x.Vb�����%�
_���6�P�k��]�2L�H/��}��y���0Gfs������i<�h)��!o�*E�{�������F�d����7�]�%�����4����l��5���N�`;F�u�����J:1yZ��/^8i��b������i[t:�/��]r�j��Q�?E��/����R��cH�����������'�x.���v�\+���������p�����Ju��H��K��?~��C�Y$
���H�K�<z�"9���H��._$�� �1��y��"*��y�y�j���n��0&���}4��T�������/��Q��%�������d���uSIZ.��������@�k�vQ�Em�vd���,��{%��e���De����r?�HC�~�0�a-�mg��+qO'���o�V"���������}1�G:C����y��O��N��+}4Ye�q#y>.��|�G=��{�co
��s{kX���x��,�e���y��	u����Z^�����F��X�9��K��R+4'tdIs�2�S|]�����+:�����6P��/�[����b��4x7JG���1�5��������D��SX�9�������ae?VN���fnV�����9R.Ac2�p�w�<1^��'I����L�eU1ee%%����S����oU��e�r	�U��,Q�U)�i��Fl��d1c����$o��3"z��	'��~��CC��7��gvK��r0dB��g"���!��znZgI��bH��R��WiR�3�����
c�Q���p�X�&����+�5Gj����c��4�j���x0�w5,�P���h�g��-S?�p���W���S��O�)���{9��Qq@7�$����-|����C3���U_�+d@�o0�oJim�/�8ALg+��f�KJ��P:&4����g��H��W1&����>[9�<��H��HK
k�t3��s�)CO�$�l���]�?Z:N����q�(��S4v�
.��L�����q�6s��J[A����ux/�"+��f�l�����I�M��q����l,b:3�S�69A��oA�'�����q5��!ku��J�qe�&��d����YY.�z����b] @����#����(L��NKe�����/zp�jS|u��mL�x��p�����cf�������:�����.��3U�������Z����lSt3�N:[5
'l�^��bH��SG��;9��.wb��T�vnF4�E�U�Y9�d�.~�%y�6K
�?h��tp�����^��Y������RD
���E�t�.�Z{�A� �(��)��g
xC}+�`����h��/bz'����F�`�oa&�����������+b��)�'�Z4	"MP�%�Y�D��t���J�R��@n������cE��4{�v���8�Y��N��"��Iy��!F��F\�8��r�S���p-jf\u�����tN����m����p�S3�WNq����<�6,f�w�<P5�bc�_�79�C�����?���)�����:!>t��,W�0#�\�����"���+�[�AA�+��/\`��$V<���)G<���o���"�@��3��E�4��xG�F5����\������������w<M
y��uDU�f����IdI���C.2}�7���$ZST��8�cJb�<�"��D����l|�"�F}J��q���a�BV.�Z^����f�K��v��V���hM�%�rD�4�f��-��R������6�m��O$�/������Z�%,������O��:"n)�P�Y^��H�V+���8FMg�.��N���%0ne9��7y���%4zZ�&��x�]j(~��G=
Dv�{8G���l�1@����� ��������<B����:�A�����$}O����+K��N����\T�UN�����U�[Y:������E
:(
�M[�2 �����:D�@��)F����*	��P�������Bn���
���X�p]��
�&%���5fn�2rZ1d��}�&���
��5��Q�D��&�����b{H�*��o.���2�@q�5�	��8��~�bW=���W6}T�e{���5�_�\����_�la=���8}��8��f�����^v��)D*���x�O�����������d����l�
�6M%�}\� �����������_$M��b�`�&��E}�Q��J,���L��~�5����':K�&�\8�r
-5�0��OP*���j���u�OeV�3���iG�r��������8�m�z���a��[���k����6��v�R��F�@k��$�yX��p���V��r�
�?�wl
<�F�e�����x���� �������j���]�&`ey9'�o�QrO�5�����Q��8�A�Ot���W2���;W���}%�JT-�H�6�)86y|7hsa���� �������oRH~G^O+����>�D�*b�q����k�39i�5��%t��K��O�R����x�$�+�4���X�6Ls���H�
�����x`����sB���Pm��ip�@����W�w�k4Z�Bs��u��%#?dU��9��.�n�em$�`C�\��]�-
):E��,�J�����;-�f{�$BM%��ZZ�+U�fDHD��y�fVT��;�E�����^���7��N����\�$r�]�m������>z�##��m�.��t�\��
iSh{���A.�_�A4�qPC����VV:� �f
�]��~v�h�un�I���x��M����U�F6�n���6����P[�O�	<c^�[�*�2��K�t���H���i�`!G}�|�����|�,|+�>����ck��h�tP�4HXC���������*�r�a�p-X�7�����}���t��+��C��`'�v:����<�RH��;�O��;�����B������^�R�U�����bj�o!O��e�RW�w�V���6n_��dCHP��P����9��q?D��D���#�j��x<����o�����+���2@q��B���DY�.�|�n^�v�4g'V
cz{�����k!�;���=������+��Kg�\�LY9�Kk�����g�|��U�2C�+�{<��?D�D&0P*N�#���W�)��O��N�!��9<�-Wkx:�+Bd�9��:������I��=@���qj�B��9���t�v���,Mo�����e/��6����u�xg�/<�]��#�7���������a�������E�p��D�
Q0�*�`�F���U��T���sO���y�E�����,�������f���r=��[���vcv��&��B����Y��L�l.��qh�-*��Z�B�[�h�E.��>��
�^��P�,��P��,����>���aM�)
����;��{V5�!����$����I��W��7��2��mf�O��Jd��$���;��z'�twwg�?�T��j��������~OX��-~���X%�Q�nG��oG����,q���L*2�>�TiY��������h@���4�L�,R������[��������
A��5q��U�\+��}��d�'�
���5E���]��Ky��)��!PP��F�
T:� �T��x�����v��f��A)x�WnH��}�5�������1^Q`1���H�In�04�S���c\E.O���������,�K�=�B��n0�W�w/�[���a� ����������H��x>����^�7*��T�[�iAs�9h�r3����0��[a|�BJRp(���#���.��m����Z��,���G�G�����2���@�]Q����w��@x�u!{���B!h��pv�8�K�����f��?)-�e��BH����'��Y����/:G��Z��n7N�|kv���m� uU��Z�y^o���|k���n����6G�b��i�~�Z����+��7���|���?�Bd���V����S��*����i�}z�M�`�Q
A��9l��j��C4�>v;�#t���q�U�<r��z��R�H��7��h|oL�����Yu��2N�~|'HObw�I����LM�	��Z#�����-��_�]�/�;BI���A�Qo�4)/@��b'{[T��q�~���V����.�4O.�O}�=6��l@�w��{���t8a>��9�=����yT��w
x���9Q�OO��S�J@����`��.��4h}w���V@@9��|����W��+�bmL�����q�v�I�?���\�O�m��Wt��#��1R�r%$�0���P����c�l�xO=�+��������P��\)h�%��L�P������~t����������������I;���]�����I�BY{L2�,<�������g��V�S�n���W����SI�����E]f��m`����������*�Ns2�n������['d�WHs�_�f���F����b?�R����F���Q�S�����Z�Z�X6B ]����,�B��(�
'HB�C�Q�di]��J���F���d��]|V��}����Wzd�_������!�z�X_f
�;�2S�G���K�gD=�X3OC����+��mcT{9�+�OU#+7��c�����uV�j8!o�@��gX��6��.I>���H���$�m$K���7�����[+���""B(�%�������hw�;��������)Hf�������|��DH"�a� ~����D�����^��k>�������1r�����P�M�*w�T%�qW=.��P,����������f'3.x�0�
|��3���.��S�k������O�s!P��������?�<���}G;h�����z���li�}�C�:�����B��]4�
c�tG�f�y��~�V��\�{����v��To�O�/tf�x�����{Mn��+���V?���T�w���W�N����S�L(��i'g���
��+X��K��%���
|����J�N��%��X&��U�
�qR��F��.�6����bHfI�����x����6��Q�y��'%:4~�H'����5�\z����A�2���[��a.-��)����R6-��q�^�h��3���r�I�qw�����~�z��{	p��5�_D�S���T��/�2�I��P��rd�~�n�A�d��q�u5��Rw�tx�_&��3S�`���4��4�-f�r��T����t������|�t�E���"G�Ud1�9�d�Lq�|��oA0m��#�w��`]vd��d�A/s@E�tXe����A�0����YDZ��~���:�_aA�,��y�3��7�v�~��'�jp�k�P�IK�e�s
����������;=���>������s���������N��q�u�D6G�J<
����p�vc�y�3w�$?�<�]6A����;��\�5�of&NX���
7us)F�t�lA[Y�d��!u]|������6����^M�4R��,�9��X��G���:�\�_uON�>f�<�<x����S��/���/���2�_f�[1O�2��l@��������)�a���+�����������<�VP���j���l����
�/"8� ��Q�dY��9���m�f���2o*�m��>��wS'Q:K�{�����/���q2��~6����G�w�j�q*K����l�s�/������^V���G��|�|m�v�kAGb
��3SGO]�EQ�`�`Od��@����F����9���R#�R��D��$� T�����	�z���O<����L;�%���J�����l.�����~eJ��S��9sa�&�	�����=�����o�D���fd�E����Gw�����V
je�N���7�z��D�����)l�0�B�����O�xd������{�>����;���S�)P���1{�[=�����R�N����AA�y.����B�#���0L�xkm��g
�H�MI����Tk`<=��W��v�,L�[^�6;���-Y��]Y�N���2X�i�W�Z����4F��Y�R�P+�n�X�����q��ex��u���>}�n}�?36��*g��U�ff���Tw�O�,�l)�/�w����F��p���(�#Q2�����Z��x�~���x`��`�5ArE�-0�/ij	i2L���C�rDG��v���CkH~�@��m���0���8�Q��I*l�.���Vk���!�r��a�����D��"��5���Y���K�I|��1�B����Q�m:;�=%^	���xT�9�jL����q(�?�fx�������sv.���B�������N���w��r�F3��w����s��tH���y���w?�����K9��v������vvq����2���Dx>ZT���r#:I��g_��a�nJ�n`#	K�6�����������U���}a�������b�,3[[��K�Y;@\���aH����,�h`�_}������k7������c��v>j|��K����9���%���/��_"�k�,�����,�����\m}��TI�L������xr4�#6��M)|lD����X��z����`�E��'�*����d�}2���S�D3��8,���W�|4�c�<��~Dh�'�A�L�8�1;u8�x�h�=��'�$�B�����
�`�S`it��\���t��������Q��6�'S��9��-���	��`Z�2����y��%��$�/����~'�3k��C���`� �-�������C���y �AX���q/�!	7�,��a1���6��=\�@�������72����>n~�}!�ro�������B�J���Vh��A[�[ts���=<�G7k����\c�������"=uJ�c*��vm�Z���g��`}����o�N����B�v2�Q�I�gk>%�Gr'J����:u�h��(1�������G�������F��gR&T���:R����9?��;�t�y&�3��x��6���f����4��8qRs��o��"�
���bB�r�b����O��
[����
+2�Q)���T6�����k���Y��*�V����Z����W��0��>���m\N2��Q���[��H��+��j]�p�-������	�)�x
��p�N��N����>Z�U�
�.����C`�9n�����f�j�H
�a�Pc��J�S���|J���|���@��4�E�P�!�1'�JbyX[�������Q ��r��`��[Z�6��d����>\�I�=����.��_{
+�b�3A#����������G����P��T�:
Fp������LY���0��2�{?W��t/�ep����|������m'z8�QS{��
9��#����w^l�����/j&k�N��'@d���S�='.(����|��bj&m6����f�,����w!�+SqO�
�VjUr.iR��JMJ��%M*�]�I���M~ �ja
x������sNIH?>��@��F|��RNB?���1&?��c.aPe�y>���4����#��@��}�xA�X�=Z��H/�PHy��<q��y+������,�l����d�8;9S�-3��Yi@�hY��v�&����)�T�n����L-o��������4�W���w���-O]�P�,�>��qC1�z�b���6��qC1�����u���rVx"��
GDZ���*f���T=��)�S����P9�4���{��\ua��������S�s=[�O#�B��D��*&��*V���;f�4p59!�'��W'�`XzJ�U4?����LY��YNZ�d�L�����c��v�b������T-��'}3 I� �#�wT�h%��G��y���HS��vc����=�
���I���k��[����rJ�#m`H\#a�C��z=L}�������ecl�.p1;�
���8/�>T��kul�
��Y�t�"���# _Qu��P@.��q��5H�Wm����SzN�Y���_��Ce���Z /[��u���!��`���gv{����Q����7���rd�����(�/�=���n�9��L��
�F4�l���
�sbz.a
iuY}�����."G��W��6�!��3#��3��gF�����~^"�"j;�W���E2������R�������:����(_1��b������XQu�@.�>^*�����`lI3����pE������V�5��	�k��L$g����%��O�)�2��p��f����"��������������	������Gq6�J~Z��d3S���i�8����S�c�s�:���	���z�I���('����/�$^1&q�L��
�_Wd0V�j�>�(�`[�i�t&��c�N=	`�ar��Oo`b1��)D�R�"�~8'U]�S���������pV���^�[6�|$���
��x�%k�4�����2�]2��6���'�d�����RM��,����v�z�f,��@c?_�"�F��Z�'�����%(�_#;A�R���y)'qQ.�{�m�W������]t8|���CLg��"�����[�]��C[���VCk|���U�yz�h��^��Q�>C�3�������W���h6��x�����
������ 7TY���XO&,F*�P���Kp_PF'�����Fp�	�8~�+� �.���po�?<��Tat8��f�p���V",b(�}�ID$<@�V�\��U�����v�
��_+����kz7����e���\�	��������<bV��)P����)s��� ���Jj�`�b]���# �/���{���b���j��������	���XC�M���4H�)����k�M����i/c���7��t�����O�[p�(����
�w.G�LU��g�t�r��KW��M�O�x����g�j�	��Gc���I*�r@7��%9A�h(��8b��������h�f:� ���d ��![�b��b0zP�
61�&!al!��y�����Q��P������'T^��B�� �IG��5S%�\�����a�U�hQ�2i����4ak���MR^]����b�`����H
"���.)���=\�w�8�-2=����p�w�V*a����+�~��B�
�"*��T�2S��z��LFq2����2@Um�,+���LM�kv9�|H�m��CM������;�',l��C1���4Xv.�W��+6���##U+�TF�
@�b�v��8>k��q�Q�OF����]f�X
l5����
u��eZ��I�� @K�/�YhC�#e�a*�$��X�3W�D�[��A�������*�\:���~�`8���i)�]4(�K����c ���,���[[�����c�	v��8�n���*��>p���b\�k��-r���8.
}�o��}��$R�-K��J.]|�j����|Ty��Y${3��{IW(��������u�<��@�����3%�;P` ��p��ly��2�n�:zVfU�M�������f~����s
�\E;��G?6X���u4�����l�=��N$�������PR<�J�Zz�mH����D~��$\�K�bia#�~0/E2z�`$P"�0��%06R~J��d%���e%��.eP��#����R%��[�_w:����o�`�����j���w��E��/���#`D����LV�gri�z�*Y7�N�F��s0�s��H=v{��h��9��q^��� ke�i�.���)��3P���������q���x�pp��}�U���U��~�^�UvS���"QG�����`�o��e�K�2ZK��
�c;��u)����/Z�]��>��|�~&Q�$�����������>�MxQ��|��-(�>��`���p�����i�<��b������X��h	e����Fm��}6&j���1#����s�_�}:��VR�����0��Q.����p��Z�}��/*�����������s����Z��T'�,�S����h���. p^��<������!�f����P���4��{|�S�k��(�h�a�2���'�f�`�k����8k��
������,nq�Cq23;��\����^[���Y�9hE�S��>����A�h�\�80�&�r��1P�� ���CFT�_��M<�9*B��j�3��,�mo��nTJ����i������8����)J9����#�j][w ���nngd�6��`����(��A�VY���s<m8�1V������,��`�d����r���g�;)�|�(����Q���������������r��2�"f�3���@c2�j���o���!��)E�Tw�������g������:�F)�Q�YC����7���/o&�Q>�8.�#�t�9�n�R!��%�	�p�����d����@�g{B.S1������R�aD ����$��|;x>>��2m?Y2@����1��]��=-w�����"��"��f�H�5b0������&�����b��^�����~�m���"�Ul;5�����f��QL���9T{G\9Cn�B3�EE��eQ�a�E,�L8�s�;��ii������6i8iq�k��'4�t��M�-��p���0^nU��<.��0z��Q�f^,���������L:�����IS��YH�`Hw>������FC�6�lW&����x�,~���fd{��,�N����>��QH��z�� *�<��O��
�hr3��c�{i��5%���Z��������>z@��|�����'����M��'����>��0��Q%�G'^���d�-��F&IX�h���"j6�����������a`���N3QAW!�s�l���:���;Hv^����sO<?cSl:@38�m�Y��gU�������&w����,�<�*������$>��~@�b����ljL�g��)�������A���K4

oMhP����:��*�o]Q�m7���p:mBJ�P������%m*|�>jkzS��H�j���!�Qp�!,i$cy>����5�_U#������g KZ���v=-h�������2I�"�U$����`U
(��>�]2s�x5�S�G0AmF�]�����3h(y�-����u�,�*�
_E�H?����S��
��*�>b3(�0y�Y�*T@��j�k���q!Wa���)����]���d���os$��,�` ���P��)���j�����U���$���'�2�S�Q?Y�'4��lm,/S�4T++�,�{��6T����p�����ef�.VwT�XD����D.�{��n��P����k[����A�m��Z�p�|���yK�)[��N���-���V�:[���.J���<��e��/�������&�b\���?C����,b}�b���9n7�j,2Y�u�����?��������R9�{��`�J=����W��TV;I����:
o��|�.|����J��s
�8K,8M�9#@?Z?t6
~��_]5�?���p����;��F�T����r�:�29���\h���`xu
��&Z���>�U����w3�rO�8uA�Q���c�ju�������0���"�-hZ��j�������l7pU�]��;�4R���t!@>:��[����pg7�U*{��h�������wJ �V)`0~��q�7�S!���,���]�b���:j�o���.'Jf���n^�v/Z]�����e��RH���z�Ras���/��N�
��n�r�SZ�x���]No����|��������5�w��^c�v�����"����5����'�e��K)�K����]a��4`�0��R��XZ�R�Z�b��%1|2�����m4�����t}���(��	$����Q�c;`��D�2G��3/��3�#�+I!\����>A�NZk��u�N	�8c�4C�br��q��sUZ/�k��1q�Dw�� �]V=s<	���f�
�b"�A���$�'X4�cv���[nq���p��g'�c9��0MC�x��#D�z��F+V��7d�.e(-y��h��n�)���'J���2�Z��70��U8K�jI��`�b��K��P�����V��f��������jH�$��S)�`��B�������[�erZ�wX���X�2�C�JIx��*��@; j�zQ�W[�����n��U�{����dDka1v!���������3��X���Y���6�q��������t3�)ZCU�r4�e����������n���U��0+g�
�OB�S��� ������s�:]^Q��%1q�R�E����
���C�U��;�q���$�X�D���8e5�6�;i���^4;�>i��D	4���a�F�j��H+�){[[�r�,�Q���ZD<f�W����/�z3�GC��������k%���J��hQ:m����h�&����`���h��92�Da���!�Y�	�QW�(���T��G�IP��2r:P��	s�KT��N�����@m?dXw����*�Rj24��
��c�:PoB�a 3Q00
��G����*�������
4�6����1&��0u��(�����	��y�����t�����+iigk�6 ������q[/n��de��w�z@�����o��1>_H)�@���=�y<KmA8�o0�2q���Ukt$��xu'(Q`4[�`���<��\���P�Z�8����f�� �g���a����V�+�v��������e�����~8���2�������I9�"�*t����>���./���UKD�c8�-j�	
9�U��V������u���]�-f��3�?�������vi5A���u���y:�����xa6�����Fx>�.�9���L���5����X� A���?G/�.f�z�epn!l���P�P�U�sg��X�t�L�&��k�nz8iHG��}$��J��L�����L��*��;�,G�a��T��au�`��p��^��`�D<[�-`w���0��������P�[��������^0�~9D�@Z��>����>!C�R#����mU��:�y�����|K���O�p�O�x>]��N��T��a^OFd�3�x
�@�r&�POx��W�/ZW@�xI(NY4����,�cOb|�Q��
Z(%x�T���6���������A
���2�z��~�Y����.O-A�k�+����:��'B��`����~�
��L�/�J���o�deG�Y������
H��Rz����qLy�W�����R��Z�������!��XSq?X#�U����d��k��PL�+`�i�v����f������8����K� ���:S��R�k?0U�E���d�N������0���/���K;�501�@�<����k�H0vU6<[\UG��������F��E�q�&}�k�1�tO��2��s�	��5if@a����E���B��E��@�`�s� �
�pS���%������!e'�d���U �Y����`uiA
yk��e������d�Z[�YZ�����d ���h�@0)�0�Q]�N]�)�Fy�%26����������)���������kh��������PA�ey���Q���n����F)J����� ��?/���C �W���S��u
��w������i7/����:F�w�XvZ�X�Y��'��C����l4�N��8s��qH"W�����nCX�^I8Qv�"�����/��8��Jl2]��[���$?.<�n����K�E|�I(����h��/0����[0����F]�.a�Wf�I����86�q���#S�W��c/���m4R��GrI�X��)W���x��S���K��LZ��c2���`4���`���p*��8�k�6-������O#W1������[L�8D���^��i��\��mU��Am9e+���k���F���2��&�,���'��P��F��2d�x�?����28=>?�����j55NVf`��$�,�*����s�yCl�=�����N�_7�f}�������'Il�����9���T����"��d5������q!�%��r���N���A�p���T�{���'��v����)V���*�����
o�kW]^m2i��.�C��+eb�����M���.~���2�V@[p
�_?����W�F���FZ������4f�e��!�~�_����.[}���-�
q������"S����_�O�����l��W-
�����7��7e���;�u�)�d
�M�������`e1���Ax��+�<A�i��:��d8��'z��=V�er��m'�"H�����R������a��)P��(�[y�e�>�
�L��}n��z����)�R~�'��1�����i��T��c�)Z]d����0$���;���>��������E���|��1���pB��$��q���yh5�f}����~r�����-l��o�^R�G��5����q��!U��X�G�������jP�Ke�}���� �K�w	�c#��o�#��p��V�����n�r���5zB���cY���-����r������k�OJ����:p�������i�S�/��N��m�D�x�����P��j��'����jFF��j�nb��i�~���i�1L�&\�������u�j��7p��������G��7�'���P6����R������hP����U*�h��W]f�<d�o�p�P��`
N�f�����h�<q�g��kj�kz���{Em�1��I�G�x�F���\���IDY_N��6��o�5��q�������+�����Z�`����$���05�kM���D�g4��a(�6*n;�A���x�hP����9Q���&����q��f	�����	������0�G�	n����q��������%m�pKp�x��9-�E�
��o��4�����
���Q/����`h�
'��.>�Z��Q�?�0���~og��k`�QE8�B�k�F��� ���>IgI�v(���jw�����aB�|mm�:
�F�x��l~8�\��^&-)������e���Vp�L��P��gi#�/��P�_�=b�SN��B��t�d������^C��m��}_D��� �(B� ���A��s�c�&���(J��t�j�����Y��<m�4.�����6��*�O!|�'��Kj�������U��2�^5/�&���v.��
�*���NY1��~�Rb��&�a���eW���pwX�+�������-���Mc��������*�����/������"�~�qGWb�e�Mz�Yw�t��G�mhJ�)��pdY!�f���@"��7	��$fJ�+���\�
��,���L�8�������"3�p�P4�V\Cg�M�/+l|�����#�I�v;p56Jq���,�;����Ai���		���s�ST�b�;nu�lE�@�����P��^�����Q����-�/ku������d��l7�O<�V����E�V[m�`��h�@�����'@a���Zq�v�~5*���n���#�;F��l�����%�t�Z�]v��>0'��z��U`_��F��#)����n4�����?q!����J<LtW���wI���vA��n�!!�u��g�N1��`NUR#R�?�7�����1Z�"Z&���`��iZ��y�EyR1�<��4�g@��q.E�����\���{���Ln"�	dC/�l����-���=�o��5��X�G�����;E�D�2P�r��]8.=3��n����+�4I����>G� �l��Ir�;�oO����)�Xy�j���+��uk�S�#��<�LZo|_f�:$9�a3��[��K�-���fJ�H��e�7"���>��$CA�����/�0�@��J��1#B��\�&�9�cT���� b��K�����	���aM�I�g������~�bC4�]�+������y����,+�����"�Z#�/�hkr �������o�:��6�/O���yA�cF$���j�><�j�k��*h����4Sv���[�Or3��m�hhF+?�T��Ci��|&
Q��G�zd��1U���Q��0p���D��������V�����<Q��y�O[��I���������x��.��)M�	��d����	��eZ�f��#��Xu����p�?�1���b��Hh�������(%��k�T)Gu�;�����h]5��Hy��~8�+K���gW����K��t)}����5�U*��;��C���v�]�e%�bs[,6������1b�)����g��J�>��4l_P~�[G��R��S��%�ppCY=�L9�D��h���`�g%���[@sJkW�V�F�e0��v�Z�E|��
8��@*��;�,M�TjC>iY �z'0��[�RK������S�V��Q0���&M��������,�����^�����Z/q@vh�d�>��Z*�'bQ65\bY��W���bAa�l�m���x:�]y���08,�`��Y��&h�����
��
����Q�?��Tj���~��n���!�\�
5F
�q �!���������t���nC= �z�#����9o�'h,$U��N}���~AV^�k�V�I�x�)��`��tV(���v��{����������f
�<_�b�S����!��A�W�����I�$�����N���'hv6���x�1|#�>W�`���Z�p��$��^�9��P��a�����F�<�Y�
�}���Zl2�`��W8�[`�n�E�4�j|X=u*,�T�ltW���e�[���p�`�P�t����d����f&�nh���&�X��l/�_��1F���#�+$�7��������,?�
�����K\�}������G�V�W��Lw@o��*�
���r�7��R)-X�G��,���B=�U�+;O���D�'���
0004-Add-SUBSCRIPTION-catalog-and-DDL.patch.gzapplication/gzip; name=0004-Add-SUBSCRIPTION-catalog-and-DDL.patch.gzDownload
0005-Define-logical-replication-protocol-and-output-plugi.patch.gzapplication/gzip; name=0005-Define-logical-replication-protocol-and-output-plugi.patch.gzDownload
�"�
X0005-Define-logical-replication-protocol-and-output-plugi.patch�=iw�����_1�kb��&Y�lG��,�.Y���$=mC5 @2[���{�����L�I_tl�f��}��o�`���<�G��~`u���g�����;����>��]����C����?l�����0/��#��\�`G�?���o�����W�S+�/��7�>�.����S�����E�9����M2�'���oW������������.���k[�x������(�;���;,H�0�Y�%c���[�V�9���O��n��o����>y�v���zg}�<^�P���.S������N����(��N�l���Q���PO��G}>�p,�f�(<�c+���O����$$�����U�HZ&)��������'9~�='��1$E0{b�c�4��.P���G�[4v��s�����v��e����v�O��W�Z}K�_���s�/��Z]3����;�Vk����,�����u�w�G���#�n?��?|��@�]�����������A��~���3x��pf�)��,��a��vgy�#�+�/����:|��+��sD�������O[��^�w�Q��u���M�����]�JA9�����Z�� WW0�:\��;��'K�����'��-�\k�������I������<��W7���������wr��]^u�+���;��g��b`�����=K`0��`����x����N���[���d��$�Fvt?
��x R�Vrc���!�a�N���>#�Nfb0%TP.{Ku�E�[���tB3O����=G���J���%H�����N�����[+rQ��+b�=������z��h��H�
n��[t�&G���4��6����IE0�7q�?�yF��A�+R@"	������1��^9T�:W';jy�����~�B���h����<�.R@�Pz�P#�6uW�Pl�I}\��u\�������c1E'�d	��cE�mQ�&tk��l�gC�~ID-M�;��s�|��3S�R8�E�-�K�
����/�w��BS�P�rRw�C'W��2� kRs# ������z�<��#"��1�#P��2��4�-����:��s,�t�y�Ka������!c�t�	��S�B1%�R�?N0�"QU�X�+���Y6XD�������� '!�~ �����p`��	�*�82�[�\�oJ���1��#iC�����-�������Hq�A�p���kko���*��@��j��+�f�#�f��@���
v��`�l���MMf�~���I	�����.��*���i4Q�$�����3�sL���#���>@��81������w������ly��2��v=�@
��"*b�9C/$���1��.�u?���a��Q�L\�O��;�o���i���F���*�M��UZ�����
��@q2�}:d���]F�Xy=�'�H��k[2��`q�����K
��='4�E[�s��v��BB_I��UJ^E(�Hq<c�"�������&�"
t���<��g�a�?�	�|�^`4���Cb��P�x\�6Kf2
��;RU �,p���	)�!�LO�k����)����X��0�:����u��*��V�<�K��k�)��c���&2�]��?����1}�U�����ZY�m%��.����A��`nw��?��W�����&tB*m
\��������qb���T"`P�e��,�*7��g�ZB$S.2�!V��VK�
��"��Q�U�[`���8/���(�cste^xJk��:���u���&Y:>�t�x��e���{X��v�Oe��1&7����>���xe�=�G�4H��f��lYP�;�sHw(C��("�D
�6��(���kJ"���Fc(2�b1�n��T��n��Y}�G�����S��>E,g4$PV(��)�w�Fk5L}'U���X���&���fB4�0����	�!�C105D���!�r��F�KU�X��'f��+2
��W}7������A�3��!?�m<z�h{	hV��������	���3�H��H��h�=?~��&�}��>����B���(��S�:t�F�a�D���%70n'��Ge`�w��Q:`Z(��U b�Y��x�7���n���7J���5��	���HD=����=��#%�Pfv�Q3�s����3��$j_%���0��1��02]y=��]n���B5��U�dez1@!
%�-��l�gk�y.'��F�	#-�������yc���?���c�M�l�Z4] �9�6cC��A��
	�[]�&�z��Z���}���m��R]�=��^o�N�BA�es�^���s_^E�UV�O�x���h�XY���&Tx_���Y���@�1d��S%��DC��8�,���4��P4��A`GN���zx9m�O�U�%h�%���/�|���0����_Jm �g��H��������.��|�Yc��N�l��*g�&+������n9W�v�����>(�H�r��7`�Y�@���O��?T\�m6���V��������L���L6��A�k�)�_|.�3��O ��y�\���;��M:�>*��&�i���K�u�k�z_�u�I���P.�=����&��f��*+|��(�a�et+Y�/ ������-���U���>��������
�Q��+R�T'��Y���������6���g�(,ui0J{��/'���
.���	�.��(��~6�je���'q�5�]�H����
�@�X
Z�rP}@���K}_V����n�s���Y��D��9�Qe.��$<�,T'G�M6Lbxx���A:w6��L��#�P��t��t���8������EjsIwR5W���DpUy������_�7������	|V���g&TT'~����`q�sb�r��f�By�y���TN����gb���k�f;|��=������x������*zd��j�ic�������a����V;p�,'W�7�����%�CP��Z�y|���r-���Zv�����u?������� ��7%�tR���5+�lr�_$VS���^�����]k"�(D��I/����@;So�)�=`��|����-�{���Y5w�5w|�����>?<=��������=�o�u�Zs��+���qO������������j�����v�
�Zyn������X:A������/A�A�����*�b�S����*�:���}�n���3�|�����e�]�9�~w��C��W��?7� ��R���s"k��`hy�U"�^����7��oo������s�c?���lC�����P��CR�@��>��;����@�� �"�G�Q����[5�[!!��;�\�H[+�e�d���h��w�~{�a=�,��>����r��v}�\������9���2u~���[QW�8�*�f4���0|�m}�����p���/����Zm�'�&m�O;��:�Y����7��n�������r/�XF���$L��J��:���j�U��`l�#����hU�
%!�=��(����x�i
j\|�����L�"����w��Bk:���	H���;,Ys����Xm*��o}�9�Y7�����z����zS����
-��
c�A�2�����3�Km�Gn'quD��^R ������,�!}���J�����L�]��+��[���fy�XH�����"��K�O��(�N��>T���W�|!�tm��Y�uEOF�U8�*�x��):��u������w����Y�]#���n�a�q��]���I4d
��G����v8���,AsA4!���g��Z�� ��#D�r�8�~��3E�<LGz�[�UB6+�{�9I~j�%\Bs��1��!F��a�=.�V	��%J-���9����v�AN4�A&<Cj	����_i@d�&]��~v��pTM��Wg�A	���y35����&n�2�t�1���p���A�+&A�9lq}J$^�uZs�x�_V�8h�Q���h�����`���{����2�<a,|:0Z�	��,�N���y��C�kh-�^���8����&�F���5w�l�4\���68Z��zE��N6�>
�d����X�V��jG=�h6?X^��a�m��Q%`J!0Jcd��z�H��'+CyZ�y*3��K�J���`�H
��q���R^*5��F��r���Z@�#�[�[���xT��h���:�=�*y��4����e�r����
$c����bQ�"��1c>���&�+����u��.�����8�-9�E|cj��R���*��n��>{���kM��������������t�"|P�M1������������*�[+�O~S�u+,p4h�G�G<$������.l��p��x���n$�20���>�A�����]���������/,a�������^��E-)�T,V �'�F���@���S1���D�nN�����Z����\s�
�G:�`�8��������1c��-k��������0�������)����\�{���GE%b��h��d�PG`H��8@�������4��Z�h�juH���7����{�q�V���u{��m�?O]��kP�-��c����5
i��(��������"O�I[Q�T����R�,�a���_������	�\����0}��D��c���*	�d�)k�r����j��[��%�����32�	����)�lRes��ase�����P\/l�����dZev����e7E��pI���a��}�c��W��RI��l���.��@�M}��0v���vV���t�����^Zn/jJ-W�u���du7O3zZ3�R�)94m�����m����X,��go������O������v�P����wqz�S&9�UT���+ht�ZT�F���c��:��mq+�x����|S��J���%Pg�Y*��(��B�b�	5x�z�\\47������^E�(k��I���#Y�H�A�Y��L�����-�2!oAn�y�T���CAG���#�f��r�X3��6�1�f_9��P�����HA���W���r8xuz�?����
��'�#<�pLk&��x_�
�?<�B�������{4r�%�+gV�o�z�U��d3/���]?�Y��R��	4�Z���Oic������V�6L�b�'�T�M�������B�Q����<rG3m��-���#����xpY|����v��il�oJWkW�Z���p���~�r/%r�>1�m��6�cK�����ss�K�J�Y��-��0�����y��S�g�g��Sr���qJ��?����i!�*�f����{F����6��B�*Q��5�(���$��m�^)D�xn��<��_�Ux�U�P�}W�C���x��)�^	�,^�5d��0�����uqj����v�<�U�y�_�C(N������������#4��"�'�}��
��\5��:����tTO��uWp�78�Q�d�t�������]=���?e��/�� ���{�KQ��e�RPAWOA�{�P��~-p�vC�����Qn��Zql�|�� ��Zc��P��/B�z.k�������x��s&���AS5i��k�\3�M54��E16V �z�R�;�T��y ���>_6
D��h��x{���5�p�Y26UT��6��6�Hm��	H�!�(U��V����o�����X�I���Y��BU����
A� �t�Q+�Ok5�so9p�X�A�~���1)n������D��`���N�x�/Ihs�O�p����e2��q�D�=H����z���M�l/��8�+����8)��d<aS����0tb���|����X���=����x�q����b������v�<�����T��%�4�fw@VJ(�X�f��"Ti���M��B�����:��h�^�Mfi��6�����4Q�E��V��� ��*���pC���O��I	�Y�x��uK�������s�A|��b[}�A����_�t
3�@�B���������~�����?��8>\^��n�o(�T��(\��0��s	Z�KJ��������	n��m��zuv�;m�K��Sz�z����:�o����AO���=iSI���_���X�_3c7��!@��r-����P���h,$ZU�k��}W^�Y�������h���<^�|�����&w�S�B��S���#~arQsQ�G7�
z�l�\	�`�M�Zj������w{zH��R�@�1F<V�x��*�` `�N���������gg�������{E���k~4M�-8��,������������%�"���Sv���]�p��������i�z���bg%�R�&Y����&�K����1��y}UE%����-�?���1��#!�Sa�a-�K-����#�o9���L�\FSh
�	/1�*`�4�"��yS�)��t]	����1f�!�,3~OGQ��/�9�Fv;��7�9����}�RM��4d��������6~-�0|Mi���nxK�|r�����z�y��!7:H�A�q���5(f�����<~,l�|��r�V�MZ�y���J������P�[d�|����d��N8�5X)0��$��Q���wJ(63�D�?�a�������F"�g&����^kxU�1�58���8�J�1D^��G4���������.;�%��	8��]�,�X�or�)���g��M�i�+���h�
*C�Sz�uFK�����HP��ml��� �6��Y��w�>�#�[��*m���+KO���{�-���%Q��@Jg.��h6�+sPj	'K%e@����s��n���8�_aQ���`��Po�K�pv+����$;�J��	��K��zA�E���o!v���
��&	5M@@�"P���q����nG��B����F�������o\[�%fE��a�����i���i*���0r��uR����Mv�������$T�6������j�����F�Q� 9;x�����x|m/P`�P���Q��K�N�%����!B������4S�EQ�Z5�,g�}��we�	�d�M���J8���������:���n�����S��k�a���4��L�W�Di�0��EM���%T�v��?B�d�r*��[*EI�Lq{��B�n����xv�t�,<J����6�[k�f�����t��C��z��t���J��Z1�a���h��by���g���J����Gr"�_<����1�����c��R� �.��tR�����}{��������pl7�|���D�����d:�Q��t��IF`*����/�/��u��������^���8/f�
[|8&�80l���H��Ap\�|�����M���g,0k�.M[��t��h���~;|�����o��'�S�N�����9��J��+�o��-E�p�����;����7;P�U!��x�_�����Qg,�������]��H�
��W&�����
�7�'��b"�����(�B��t���
p��
����WD�����'�
��!��2_����}���D��?:�;?������#�~�S�#(��
xi}F�>�6YkJOPg�H	R�p��k����<v]N�^���w1+��p~�����1N$xmv��C(��8'|�0��M�Uy+�+��c�����;�����u�����]���|�c�f��xe�y��XS��]���Dh��D$v���:��^s�&�%�_k���	���m�!���dt��������s������?��&�T2���9��M�#OU��7���IvI�f�3��LZf��kE!|9&nb�X���I��bq��&T"�RJ�p3��u��C�%��k+�M�3��F*Y����g#x�$�:�?;_h��h7����:�P���<�R���[� ����Qkn�dcs53��"�@5j?��}����h	N����A�`j
Ec�%�k��3c��XZ��(��?��^r��~�"n=\U�[��^�C�c�J�A��s�[��p]��!9�����\�wgtzn�r��=�x#�
@����-�-����M�����Z�C��ip���������NB��:�o�EV\�<�z)O�t�����-�y�5�39d��t�0�d���_�=��(����;���\S�I>"���
�$'��Dg��*����3I�P�^bH~yM�p��l��!�;��xgBk�0T� k;o�%�e��J���r�T��F���~N�n�X7�}�����c:=�����?��bv�����c.��V�n�H�C��8�[b��<�u��M�f$sL���wO�:�������>�jY4��>4W��]������������6�HN��
w��d��s�|�Nc��Z���j��Zn-��h��O�g ��:g}���Y����y��^��z�J��������!�G,�7�m���[���G�o+Hz��4G�������B�����}K�*zQ�bj��F�!?��*�,%1jFsEE����c���RHYo����UU-G
�6��7�|6K6���o�B��H�p 0��>����b��1L�4`�}��(�1���>�7|��o ��R�g��S����r,k�h@#�\��
��F����WSJ1��\�����U�\���6aM�Q��Z��Y�+�O���'��NO�S������Y�����W�7l��'���vip�V�g�a��(�e'�3g��
�`M����Z���N�{����������q���>?����4o.�����2�Lf9����������S�t��B\jR�]'Y���)��d��6��E8&rOt�T�����C�����f�����Q��A�^skvWK�6�"�JEVF�{s�4
f��M����=q�����T���iP���m��;?���Oz������Y���	�%��C������(K��
z�%�,��P}�&��tnS���zM�~�����.�����������j��Mqa|(���5������sR��
�Js���8�L�����6��E:����'�?y~��f����-�:�$K��%�����;���=��B
�7dA��� ��z����B���iEb������J6��R�2�+�0/�s���<&�2ZB�N�Q�h8.�q:�,�)�t4i
K� �d	�����9����|7~h7�H2�f���%,���3_�3f���L�E�/l�����D�6H/t��#��E�hB�37��m�NLa-��lK��
��Hc(����	�.��}����*,)���3�D���I��l���|v�����O&��q]�`���0����4�lv�N7���q�]��q[�O�U5l�!��Te+�x����!�<4����h�6��I��Z'����:k`��A���.��d��O��o	�t�CH�{��1R{G�T~f�[�LR	"�J"�Xe$	E��#�4�-U��#�<B�h�&$���jr}L������������g/�����J:
[q�U�|�t@�diI7t��:'g{p����}���k����������'M��6�'g�����X
`��+���55��<5�:�F�1�:���@������iszp��+���B�MFnoI��9�S��c���oY~� ;��X���7kz��A���d����|�����V~
�O��� M�����E�����Z4�&D�E�B`��A��V�w���c���mj6.�~NFMY�$��2��zv8���zSg��xcSN�B�~�����F���[��y��L=��n!�V[���2P�(��"`�-� u���.I~���ra�'�"[��k�6���i(k@�:;������h<2l��������� �����sk��]�����S��0F+�������c��pNH��Ln�;J���G&e&���/�^��X�+&J7������r����+<��:j"iu�I��('/�B�<��t<G�
,?��XNH�O,�{�7���j;��i�*��Y�znE�?pI0TM�p8�|>��4#4�ph(�����y��(����p�),����1��O�%V�����*��P����
���
��n�2=n�u2�_@k�L�;w�G:s�i��e��n���7�KT��f;C�E��#��1iy���9C2�V��&�=��05���t�a�1�6c�z�8L9�)q;�*��P
�q�L>�$�p��7���K����w ,ff�j�������Rj�
9M�_��v����,���l����0�^t������k��9�7.b��_.���"'�-2����3�&��	x�p@mg1����n��?o���	U�'t&�n��Y�A���$����b�'zf	��v��n��1�'���c��V�R+'��`�jDwz#��� JSAEy�s}mv�2\�I����'<��&�m����oO$��$��������Lz<]��R���(sk�M�~���*��,=#�����A�y�;�6�H}Q�{����y�����tiVM��V����F
CS�����i�	��v�O������?��O��]��k������cRY*�����q���"��~\�#�O\L_��5�4�h��4��1([s���i]#S*�H�:C�
q]X��Q�X���v�5M����IF�r�����3~�J�+���g�
�;��]���W��
`ul��a���I&BE��)���/��(
.���1�j�F�3#f�?Q.�S�LN��j��=��Z��x��!'�8�|�V{�Q����h�q`���X;-�G�n�Za��>n{���$X��NJGh]�+T�Y*-W������XD��f8�z�~Yo���E�!^���59��Q}�=�DP��/��������8���e������������Z����'�*�cdh��e�5q��
-Ai9_��F}Dx�7��M+�H�:�/���������-�Z-,E@X9,~c���k��h�}�P]�����sG�e��tj-B��pv����/�w�'�X���r���$����u�,��y���`6�S�f���^�,f�4c���0���n`��HV�5��G�6h���O)f
�m��t,����e�5��N�_�#��P�]��vX��}�z�m�W(�\%�����-jw��'��>�D������r-�q(�}�n�`�HR��vt:a
Ka�L�!� �`+ir!��\�}��������_��dN�
Y���t#��w���U2A�f�����H<�ja��E��;���a����a�j����?�mU�Uo���}�HD����n2�!�������/�]o��wd��$��|fl!�+����1�J�A~3�dS��!8v;�������.�q#��L��O8K	g�W���
��a��<C>�)O? �b0�d��r6����%;W9<�sp�*���
�O�%��f��y�]�I����a~�/X���%��#�%
q�Z�����z�����>j��������_������	�<����kB����_L1�T�;���]P���(�������<^*�������u2O�fH3,d��O=�k�E�����?�B�
�]e�*�Mq��T��~�oVC*U8=A/�^\
���2d��a.���1��
���+������-a�p6��C��kS��^��YE�B;��eK:����q���4���;Mv���k�R������y���I2�;C�yp/H�l�z����QPK�C+�C�.|��b�:����VI�����2"�\��z4\�X����Ra
_R)�c��T�����&R��+
H������������VQ��N��t�����kn����h���16B&H����5��M��fwGy�����J���8�D�\b�,")�������p�	}������S�T�(�YS����:Q6��4r	=8�p�_]��h4l1[ze�-���XbCx(��V�b=.�o�o��F��v���9���LR:����=����fq�����J������1_4�{,��`���f5�L/Z�}�=�.����/�t�o,�	�)R��P ��,_�u���Ke����d�}Gj3�CI�E0�S�.��n������+(E���-Z�|Q�+����..dD=��I�/0Yz�������h�\UHr�Q��pO_��^.%,:C8�����>8�D6�{���|Oi�t_�tA�}a7\+7���Z������#�#����{�"N<�S�Md,[pL�07���������p��5�0rX8Z48@��7_n�����u>Aa�
0006-Add-logical-replication-workers.patch.gzapplication/gzip; name=0006-Add-logical-replication-workers.patch.gzDownload
0007-Logical-replication-support-for-initial-data-copy.patch.gzapplication/gzip; name=0007-Logical-replication-support-for-initial-data-copy.patch.gzDownload
#71Steve Singer
steve@ssinger.info
In reply to: Petr Jelinek (#70)
Re: Logical Replication WIP

On 10/24/2016 09:22 AM, Petr Jelinek wrote:

Hi,

attached is updated version of the patch.

There are quite a few improvements and restructuring, I fixed all the
bugs and basically everything that came up from the reviews and was
agreed on. There are still couple of things missing, ie column type
definition in protocol and some things related to existing data copy.

Here are a few things I've noticed so far.

+<programlisting>
+CREATE SUBSCRIPTION mysub WITH CONNECTION <quote>dbname=foo host=bar 
user=repuser</quote> PUBLICATION mypub;
+</programlisting>
+  </para>
+  <para>
The documentation above doesn't match the syntax, CONNECTION needs to be 
in single quotes not double quotes
I think you want
+<programlisting>
+CREATE SUBSCRIPTION mysub WITH CONNECTION 'dbname=foo host=bar 
user=repuser' PUBLICATION mypub;
+</programlisting>
+  </para>
+  <para>

I am not sure if this is a known issue covered by your comments about
data copy but I am still having issues with error reporting on a failed
subscription.

I created a subscription, dropped the subscription and created a second
one. The second subscription isn't active but shows no errors.

P: create publication mypub for table public.a;
S: create subscription mysub with connection 'dbname=test host=localhost
port=5440' publication mypub;
P: insert into a(b) values ('t');
S: select * FROM a;
a | b
---+---
1 | t
(1 row)

Everything is good
Then I do
S: drop subscription mysub;

S: create subscription mysub2 with connection 'dbname=test
host=localhost port=5440' publication mypub;
P: insert into a(b) values ('f');
S: select * FROM a;
a | b
---+---
1 | t

The data doesn't replicate

select * FROM pg_stat_subscription;
subid | subname | pid | relid | received_lsn | last_msg_send_time |
last_msg_receipt_time | latest_end_lsn | latest_end_time
-------+---------+-----+-------+--------------+--------------------+-----------------------+----------------+-----------------
16398 | mysub2 | | | | |
| |
(1 row)

The only thing in my log is

2016-10-30 15:27:27.038 EDT [6028] NOTICE: dropped replication slot
"mysub" on publisher
2016-10-30 15:27:36.072 EDT [6028] NOTICE: created replication slot
"mysub2" on publisher
2016-10-30 15:27:36.082 EDT [6028] NOTICE: synchronized table states

I'd expect an error in the log or something.
However, if I delete everything from the table on the subscriber then
the subscription proceeds

I think there are still problems with signal handling in the initial sync

If I try to drop mysub2 (while the subscription is stuck instead of
deleting the data) the drop hangs
If I then try to kill the postmaster for the subscriber nothing happens,
have to send it a -9 to go away.

However once I do that and then restart the postmaster for the
subscriber I start to see the duplicate key errors in the log

2016-10-30 16:00:54.635 EDT [7018] ERROR: duplicate key value violates
unique constraint "a_pkey"
2016-10-30 16:00:54.635 EDT [7018] DETAIL: Key (a)=(1) already exists.
2016-10-30 16:00:54.635 EDT [7018] CONTEXT: COPY a, line 1
2016-10-30 16:00:54.637 EDT [7007] LOG: worker process: logical
replication worker 16400 sync 16387 (PID 7018) exited with exit code 1

I'm not sure why I didn't get those until I restarted the postmaster but
it seems to happen whenever I drop a subscription then create a new one.
Creating the second subscription from the same psql session as I
create/drop the first seems important in reproducing this

I am also having issues dropping a second subscription from the same
psql session

(table a is empty on both nodes to avoid duplicate key errors)
S: create subscription sub1 with connection 'host=localhost dbname=test
port=5440' publication mypub;
S: create subscription sub2 with connection 'host=localhost dbname=test
port=5440' publication mypub;
S: drop subscription sub1;
S: drop subscription sub2;

At this point the drop subscription hangs.

The biggest changes are:

I added one more prerequisite patch (the first one) which adds ephemeral
slots (or well implements UI on top of the code that was mostly already
there). The ephemeral slots are different in that they go away either on
error or when session is closed. This means the initial data sync does
not have to worry about cleaning up the slots after itself. I think this
will be useful in other places as well (for example basebackup). I
originally wanted to call them temporary slots in the UI but since the
behavior is bit different from temp tables I decided to go with what the
underlying code calls them in UI as well.

I also split out the libpqwalreceiver rewrite to separate patch which
does just the re-architecture and does not really add new functionality.
And I did the re-architecture bit differently based on the review.

There is now new executor module in execReplication.c, no new nodes but
several utility commands. I moved there the tuple lookup functions from
apply and also wrote new interfaces for doing inserts/updates/deletes to
a table including index updates and constraints checks and trigger
execution but without the need for the whole nodeModifyTable handling.

What I also did when rewriting this is implementation of the tuple
lookup also using sequential scan so that we can support replica
identity full properly. This greatly simplified the dependency handling
between pkeys and publications (by removing it completely ;) ). Also
when there is replica identity full and the table has primary key, the
code will use the primary key even though it's not replica identity
index to lookup the row so that users who want to combine the logical
replication with some kind of other system that requires replica
identity full (ie auditing) they still get usable experience.

The way copy is done was heavily reworked. For one it uses the ephemeral
slots mentioned above. But more importantly there are now new custom
commands anymore. Instead the walsender accepts some SQL, currently
allowed are BEGIN, ROLLBACK, SELECT and COPY. The way that is
implemented is probably not perfect and it could use look from somebody
who knows bison better. How it works is that if the command sent to
walsender starts with one of the above mentioned keywords the walsender
parser passes the whole query back and it's passed then to
exec_simple_query. The main reason why we need BEGIN is so that the COPY
can use the snapshot exported by the slot creation so that there is
synchronization point when there are concurrent writes. This probably
needs more discussion.

I also tried to keep the naming more consistent so cleaned up all
mentions of "provider" and changed them to "publisher" and also
publications don't mention that they "replicate", they just "publish"
now (that has effect on DDL syntax as well).

Some things that were discussed in the reviews that I didn't implement
knowingly include:

Removal of the Oid in the pg_publication_rel, that's mainly because it
would need significant changes to pg_dump which assumes everything
that's dumped has Oid and it's not something that seems worth it as part
of this patch.

Also didn't do the outfuncs, it's unclear to me what are the rules there
as the only DDL statement there is CreateStmt atm.

There are still few TODOs:

Type info for columns. My current best idea is to write typeOid and
typemod in the relation message and add another message (type message)
that describes the type which will skip the built-in types (as we can't
really remap those without breaking a lot of software so they seem safe
to skip). I plan to do this soonish barring objections.

Removal of use of replication origin in the table sync worker.

Parallelization of the initial copy. And ability to resync (do new copy)
of a table. These two mainly wait for agreement over how the current way
of doing copy should work.

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

#72Petr Jelinek
petr@2ndquadrant.com
In reply to: Steve Singer (#71)
1 attachment(s)
Re: Logical Replication WIP

On 31/10/16 00:52, Steve Singer wrote:

On 10/24/2016 09:22 AM, Petr Jelinek wrote:

Hi,

attached is updated version of the patch.

There are quite a few improvements and restructuring, I fixed all the
bugs and basically everything that came up from the reviews and was
agreed on. There are still couple of things missing, ie column type
definition in protocol and some things related to existing data copy.

Here are a few things I've noticed so far.

+<programlisting>
+CREATE SUBSCRIPTION mysub WITH CONNECTION <quote>dbname=foo host=bar
user=repuser</quote> PUBLICATION mypub;
+</programlisting>
+  </para>
+  <para>
The documentation above doesn't match the syntax, CONNECTION needs to be
in single quotes not double quotes
I think you want
+<programlisting>
+CREATE SUBSCRIPTION mysub WITH CONNECTION 'dbname=foo host=bar
user=repuser' PUBLICATION mypub;
+</programlisting>
+  </para>
+  <para>

Yes.

I am not sure if this is a known issue covered by your comments about
data copy but I am still having issues with error reporting on a failed
subscription.

I created a subscription, dropped the subscription and created a second
one. The second subscription isn't active but shows no errors.

There are some fundamental issues with initial sync that need to be
discussed on list but this one is not known. I'll try to convert this to
test case (seems like useful one) and fix it, thanks for the report.

In meantime I realized I broke the last patch in the series during
rebase so attached is the fixed version. It also contains the type info
in the protocol.

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

Attachments:

logical-rep-v7.tgzapplication/x-compressed-tar; name=logical-rep-v7.tgzDownload
��X�<�w�F����[oSC�����k��������GGH��ZHTv�I���wf�F'M�m����y���{��@��l��M����W���y3�`�����W/����������e���:u�v���c��v�&�z�=[�n3��W��Q��Zmz���m�����W���g�V~�{_-��pM�/�P����]���y���c=��6uf�������Mc���N�����c��V����(�2�9�+x�f[��C���������|���2Su�a��V��A�w�@�������_�������Kh���R -�HK�"-�����ry2�|��
�i����-~�m���=,\?���Z,m�`8��a�s���y�"��n�-�������X�.�����.j��
��3t�D������n�p	����a���Q���e��5��h&3\�r��^�7lf���j���%���{�v�]8A�C-�*���f���&\����<�5�|��^�{��c4��^8���$�f.=7p
�^3;5�F����n�0��C9������[h���Clqf"L��"���kO_4��z��W�sd7N�
�q����<Z�a1�L#I\��G��i}�G\�%�X<����m��p^G��Q�_����d���
�h�S�� ;�qM�s�����g������H)Z�\w����-�x�{��WjU:M0��DC���� `�L����u:���#�&��\6����k+}���>0�l�����A�5�6�:5��D����k�Q�����Po�+�,�_�������&������p<>y9<y5~n�6�C������_���,������!��Y����R�R�1p5��0����%j:T�{}���-k��@��_��R����e���xr?M�y���Q��7�:!�}=Q��d��y	�o*�_�����j��P%��[H7�����W:��q5Oj�T�T5�Nw��=�K�d�{��X�A��uH�M�eKQI���xx><����X�.�0�|
h��44
hZ�P5�U�Mp���
����>+��;������~�i�r������k�,����b�C<�-[�,x��C$������mXDHG���u��me�,"��dW^*�p����u�1��������%�yp�<�
1����-���RR8>����eY����mr���:�t�9����Gp�e6�z<�,%��w��w0yA�Cbb�,+�J���27����Lt��#,?�D��1�~q��L����:K�L[���<������C���z��4-]5Y������B):I[�N�C	G���Q#��
$�O#�y�C��9��i�l�����~����w��T��(q<����pq7�����f������ku�p/����[�Zm�|<�>:$"	�6�+��� 58��>s�p����%;��-�K��K)��n-��p5�����g����k3��
{����^���^H���2�Rs�r�[|�z���������&�~���@g
QRg
��"��CV��#gVC�������V�w7�B��������||ALD7*�oR5cfy��K�H1sFu&�c��9�����m!0��m��JxA\�u��E��L��bt������!�d
��|�0u�,6Q�!m�<6�rh��|'���s�^[
�i��K�1��8�A�w��yf&{�+a�(���{
���[3��� �a(c�2��d�}�\sik��L����U�k�^�NU�(2M��X!� � pA����UY*�J�n�nwvL�q����M\����4��,��n�d4<��������xrvy���/'1�F�W/�9�����y���m���m���T�)�N��0�L��W�s�<��B���xQ��k��nK���}���#���
��H�*�>�����Jf�b��������B�	���
�c��\8��;���p�84�����/R��[19:��05��)v$�� �c����CG�v+}�1��)�]��1���F�9�L��M����5[��n���o��M�}���a�%�YD��4��;(s�ou��� 4[��n#;:���!�\�C��n0��=��}��P�w��|�c����~��G��p��P{+z~�q_$��A��>r����%��N$	"�R��4#�X
���i6��7��B&`�^W����Sm~��B��B^SC/��h��!������O|�1����r��ZT�J�G�a
��b�I���o3��{���sXH
O����@�;Py(Muf9�?gf�w�U���Zw��,7GtuA�(�)8Mtg���8��K��R��!I�f�7���s��[N��#�VN`Q�L��,jE��FBd{i���f��U�Z��Q��J�6�����(�G�T4��|����X���VG�cE���N:�lH�����Q��JK�=U�l�x��l�d�;�%c7�8
�d�`�Y�p1p9�2�'C����	���4%�{v���	�BMk�M Sw8�p��|3r3hC%kV�(����In1jK�� ��*��N��.��<����������CDp2]��a<�a����9���a9d�C�s��b`��Jh�z��!>o��Z�C��M�e8T�s���P-��"�v�<�A�H�tv��KF��]�g�����NDYS���|��t�5���/S����}�B��X�1���L�%���l����h��~W����E���(I������<�����I����Y��P��.��W�Lp-�}L8�r-j������/����R�_H�Nq�"��tE��Th�-k���6�w-��[���5����QI��{E
�L�^�^���:��;*�������f2N��#�#=�vG���/�)��'�W�
�����jE��	^b���J%���Yf���\�{O�hG�z��BB��t��}�M=;^LP����[,_,B��)nB��Uz/%Q�,#F'����o�9wY�^��9�����
Z:�Jb����4�+5�����s�F}��.{�j���g��3�������n��7����0�*�D~�JK�?c�������3��G�E~��V�fU"�����-�@��
��l!|�D�����
��/��������&�Dqp���C��3���ZJ
��#������Fsk[�P�+`�zb����
H!���R��}\���>h��at���m7�v����D��>�w���<a	�'�c��sN�(�d��|�T�RzT��4�8���&.����Q3P�e'aW��=;�2��v���5\�F1 �4U��.k4��������[b��� S��b����o��.�P�X�f{3�1�Y�!�v����S-�_�F����,;��O.�]a��F,S=q�:������C���g
���B��U���]R������b�j�u��
%=`N5Y@70U/e��S��s�xs�"��(r��G�t����������b��`rIu�F��������[����w"��%�������N���G����y>/[���h+v�geYb�mg��NE��_���k%kyRq�����c�qt'��s4���U����J�8�b4���E��o��oV�W��oY�J�E�p,a*�����xry:�.�����D���hg��h8�������CtUN�����������fx�����\�|�S�iG������;J8PH�^��	6T�U�J)���HJh%g3�]��i���r=b����5���Z���}��	�u�����"�mM5`������#���c�)����[�B�;������v�c��Zc<����j���[�AmiQ�������m�������1�=c�OB�B�1�����,t�� B`�VFuv��t���5nB�F)����4B�������hx�vZ�bqq�w����]��8��4{��i���<��H�Vn]�D��(Co
=���^�A��T]�4���H�kQ��^�����A�#������B3	$������]��Z[(����h|6�`������\���U�p�N����K��&�Y�",u�GT�t^L���g� ����=��9��Fl��_~����Q��f�9��Z��`00�s�9��p���&���rK���\���V�����1�:��	����c^'��]�V���)KE���"9�������1!���S9�S=X-J�[�^1�g��x-��A�2Y-mv�|���&>�V����v��3�/��W��9re�����O��E�@��/���q��v5�a����N���� ,P���A�|��0�$�$�K�
�� Z����N!�`��H��(��g���(=�K\��g�'��&5�3}�!�o�HUP@��!���l���p��h�s�"�����!C2
1'E�������X�:?�s�d�a���O6Q1"�je�<�*)K���p��w�5�w�>4�������s2�_P�h�.�3a���w0[������i�"����T��f���E1�p31��`I������%	�~��*D&��]��d��]�m
�5U[S�]�L��"�#I;{:���6���,��J�!�i�J���� U}�R�y���C��>q���%��R���t������j�Tx�x���+�K�Z�8���`z����_X��H�������{J���jF7���.�fNj���8�h����\�������g�p�u�_3��!M�Xvn9��iQ5�2�1���Q�w ��]��K*�8��p�E~�F��"aR�6d��f�������?�`��
%�2eB������2�c����^0������P��~i�
�eP�H���F��c���"-���F���Er�!��]L���X��i�L��	Ow|]~�H�l���e4�N��3'�2�R��:t�+C�n��]��&��>�0	"&���!K��|������li;]w���Z����������ej��d��n�C%G6��Cf*�s�p=�J?x�er�
�22A�REN���H��-�w
����=�����(|�[���4 O��_��%'�����Z�������{��d�����f��W�Cu����Vi\���u�ba���%�0��[����v\�w9;O~�{���"�kY��o��;�����i����?*�b���G��
�G���F��X��-G�1����9����r�h��������T���7������� j04�z����%����%�L����f��9�����Q��^9���a)�P~t"?�j�r�
���S���yXk���c��"Ze�tty������)8b7`��p���cX���+�(���{�4����_y�����x��=��D5nrf���2S��J���z9V����K�*��V��J����=��1�]����Kr�u�%��F?<$B��SpHn��+�Z��F{ ��Fh���W����wk��_��Z}f�[f���4����ws��}\�^�k�1CoC-|���i=������Sd@�~������N�m��rp�Q�[pw��+�z���ThA������OF���U��5���/�x�o+�5L�����{���@�������}�n�!)�!Rp+P�MU;�����Vov��0��(���U���Z,�R\���	8������*qo�:4�������m(H��~���p���L�9y�l������}m����/����-��u��! ;��@$�l���~#iZ��������[�����	���s��X�L_������b8��V%E�����������@�3iEp���{����.�+���-&X�	�(����n��;C�#p���F#�~��S���!R���#�$��3�K�+bY���LpWeqXJ�
��\C9!~]�8�6�1��&����;/�+|��4/��|4AU+�:�a�,MK�\Gt������������w�q���,�q6�o�Us�|;�v��p��M����R���c���3TY.psk���7y] �8n!���.iV�U�������m�4Uo`q�^��a��#�����~_L���
K_&2�2{��e�j�o�_`����s����\N�|s��l�^�{���a-�*�Z�^kT��lY�/[��?�����GJ:�b~�v�NMP�|���g��w���R��]�7���,����3(h���knP������<� �ut���n��<V�Md��)D���D7��L=!��j�$�"��5�{VX�1�eZ^4m�S�>i�V(�h�taX��^��l?x�GGd�}�����0���x�������!p7�-�&����*��������&p����U I����{�Q��F��[��6�������;����Z�������^�Wo4������T��s��Z����S8�������T��T$}�1�+���t��;�:�v��Q�������Q(�&���DH����������F:�y}g;�����4_�`8����P[����{���R��j�f��i���b{�UZ ���Y�m���V�?��L{��W�qN��"q������Q� 	��U�CBe,��o�q3'�j7{�RY����{��������}s�]�{~vr���j��4�08�Jc����������9;z{���9x}|H)�kk]x�����z��.����)��$�K��0����Z��5V�"9�wm��)����fp�{��m�k����m�1TP�}��"Cj�jw�(�%�j�XOy+-~M8�F�FBG�/E�
b�I��B�������c�y�4�E�V��<Rz�XIz���o��'+��D�r���h�����w��Pa������'�IJ���,�7]�R2v"G ��v��Qn}�
����,Kn����@���1��+ry@���y�'VO�fz"U��A����^��plR�i-��7�W��@������1����^1h��=f:WY6�����>�yw��xS��	���w3��� �� Z�r��la���{?�?o���L��&s�����M����=R���v�VX��+������
�/Kf(9�������^��BE�l�����oU�;(�o+��N[)���K�����o��jyX������@�G�4����m	�g��w����Zl��r�m�K�wh���pv��3�A�|N���|si���0���Q�;�c�>cu������H����g6�q^����.�8e�����wK�]����v$8%<	��l�� "c�"�D���)�8S��yry�PA��p<V��w}d�,��[*[��-�j��@�G����7)�Z%���B9�v!����w^4/O]��N��=Y8�p���Z����)���Z
d/2|�U�FW����*���\N����KD������;��*�2��.�0y��a�#{!�Iqv���Z�>��2�M�-���Os,;3�Il�l{�Q|��Z\iH�n|&�g��h�)6���2>?��{����[��{@T�{����U�}x���.��C0����o\��'���s`J2��eL�A��xL�S�!Z�`��tc��W�h���0��f��Wn���R���	c�!
���	]�QY����7��er�����o��i�+�K�?hG��
C:JP#��u��:�,���������������x��{��{�

z5�"�{L�
.h����wF����Lgk������q��6B�L�/�H.N����C|������Y�E<<;=m�����.�7r!��������#��*Z�7hR0h]=5� �IR�p��b5���SzPEkdQ�rzR���
�����L)�D�$��)2�n�Xt���j��x�X������������>P:�G001��i�%�O�W���/�^o��������v���F�By	Q$�6c�a�
���+6 Ehw�����>u�����S&����$w��d�bgt��/1� ����m��)��l�*s��@���M��}����mC�2��,����k�Z3c6(M�T���Tf��%PI
�Kf��V|���Y6k�:HK�:�b�-1 :?����& �L]�M#������CL�L'+�9�9��)����K*���������B��)zr�<��Z8%��]k)
���[�7p@Tz�����[������� <�0D�B<n�%�l~����1��$���TE�^��t��~�v~�\��������Om%D���aR?���cz������`�J�6���0�Q�g� ���7��r��%�����H��I��3�������r#<%/�\5�wU�)�j��'ao�?h���6��h\Z?4��m��K��3�������J��M��0F�,�r���2��YYMt+�2LO�ek��9D�.�2/B�F�"W	���S�`1-y+P�6��e
C}j�g�;����"T��a�K���7������d���`����0�������������K�����aVoH����x�l�[lP�����n���V�r�j�����p�^8�J�
?�(����i#�ER�$�Q<��,rT����lo@�B01P?1�a��b�R4������r��v�Y���q����l4���P����;�_�����y����]x�
Xw�'g�z�c}��W!s�n����(���)������Y�����Uz��$~�����U�y�h.�o�������d����9?C	�?���o���Y7G�%���W�O��3�������iK}�X�|C�M��C���dA�_�|��Q9yic�\�����N)�$l��g�&�E�3��)~�w�o���<F�1*��.�U�gj��]����U����;�����~�������98=2Q�&:	n�u��{�^��SR�
��Q|a�0=R9X(��������f}���_\�n��I�(���U���l�v���h�:�R��r#��Y�"���'�u�t1�x/���d#6Qa�� f����YM�0�Z�n�\}�Tv�s����x��mE��������Ep0s%�\
/-��`�������9NV��RX�/��x��|�VK������/�F��b��6G�����:,�������$D�;�jM(
���G��9��'r��~ci9���6�6����io�S���,Le��S�F8�w�;{�d��H�N�h�O����~0�
������$\X
����Zi�c��r�C����Q�g�s�Tk2�������wR/=O��BZq�Q��<�Y���j���==�O1��dQ��BV3Z�����'Z����3+U^8%�kx���o0,��|�����h6rLhR���@g_��2*T����r�-�[�G�H��������xEJ=���lcJ�$�Cm��u�$O6��q��|�09��,�9�	8~�|���"�2�0��Y9}x�E�kF���q�����s�I�`��(m������Rulcw�BkQ�m�#_u��.��5��%G�lx?w����^��<9�{�	l��6w�]������`dl#���?G�bb`�x4���]v�(��=������W\�@����-� ����6$�KQA=>�1&�-s���#��J����7��)���������b��8C�7�b���]�'�^�:�"���E�s�6-��o^�1���X���/�A,�)�yg��~�����������>�����.R=�{x�Q2���p�xT�4��K8$��?&�sT�|&f�Ue��'��������������2�{upq� ���u���h��@�K���!�'�<
f3���nF��^����jC�k
�6t��/(L"6��c1�����z�K�B8��
<6��5b�$���4�������y��e�Dolc����G�����������7����K������_t8@k�A�]��H4��)��8�FD��ux<�.\���5�AQ��^ �k-0M7�
���;�=
����� L�l��Qb�hQ�De����y����E+������[���{�ZK�
��^��c�+j����R��gU�,D0����q����i���os��_n�w�� ����M2�hc.|E����+��A��>���,)���eC/�r�)���X�V����f���y��G��X^��h�P�qv�C�ox�]�W����ru�,�	�����13@a�5Q�C!X+��Mo�������Zf.��w��v�9�����S4���R����	� �F8Kh{�!>�j�������[_�u�CC����3��x�1P�l�QL����kz����3�W���0�H�:��N��@4�i-���G�|>��})���m.�P������Oe�8a	o�2<�m���~��{N��C��#�sG������d�����-Ch�O!�e������K<�����n���#���S0��f���������8�Urmk��9:�_�\ie���&U���Vr�UtE�Ch�Z��mY�u!) �����g�X).�/G��k���Q��vvW���r��lT�Jp�S�������:?�F���I&�j}US1.�������LG����L��9/Y����oB�M��������S���ew9��{
��^rP��h���b�0 |u�����c�>��8d6����~�>|���_�+�S!����J�9��~n]�����q�sk�7�N����[+G��4��l�n�"UW{��	�rg�^�n�Dt�:.�"e���Qy��n�q�w�\����C#E�� �����o0�H������3����������E%(u�n�/��H�MV;�MYx<����?Awb���t�e�5]��^���<���.�T�ekzc��k�M����������=sw�����~_bo�V�%��05n�(����||�����`�l��M?D�}����P��!����D!�L��-��S]R'����Y�W������E����/��e$��\T*i�!F��>.�?2M� �i�8��Q\��
]�~�
���x�*@k���ZQ|K�G��K�����r����$CX�a1�o�����><��#�{������ig:���mr�����yH�^����dX�"k�������O_���=Nl�@��:�
5�q)o���Y��2�L�
�$���g,�n�����W>�M1���<�g�����,_@J����wk#(��r�I)����� ���s�Q��L���������������e�j[�<��L�y/�����Z���O}�^����!$I���!l�E�r�b,�v)_�)�0��
b�\4N������B�A�
������VnzO��S��6KN�2��j\,hC�cX����"�~�>R3O�S>!-�vn�
��]'�v��pk�@�u��P���5<�Y(��he�k(����Ea�Y���E	D�����j4 �V�Kr����J�c{�2�l��W4M�c�A4�HN����]������}��y�	���G�� �K���D����\�<k	i��,�&�OP�;�8
�(Jn#�s����Y:�f�)�����
A/�n��U�����?����������0��Vh�8F�x����*u����3�!��C6;CE=z=��f.��� +n�� �����������B2��SJ�������%�X�Y.I.��Ar���&;�l��L���`B��%A�[����U#a��(��.��Xs�0od�,���[Z��%Zg�.�6S�^h<G���7�`4�����C�,���`�7%
0�\?�`3�zEo�K�b0��
�=d�b�f��60�^�N),��umP����@\�+zi��)+��FV�x��/Wm!��Ny��s��o:�D!����C�����]z��SF�LB����g��a������v�.�PQ�����sk����L�I����q�r�%�s|�t�������u��M�J�l=kQ���h�������~���������-'!p��5�F>�;�,V��}��<����
��zP$_�5�xXz������F��.�_���9?k_(O��3���r������1l�;[I��t������������:��i[���"�.
����%�3"rf2U�g���e�xn�kx�J���g*��5���S(e����J�z���"%�v���c�G�����C���`u}7���V�$K��/%5���?���U����eE�*�g��d�S�������X/i%M��r������M����-��n,kE�s�K�<*������9q
��P�U./�����J�;Pj�T0'�	�u���tc�����;>2���X�K
�Hu���
�]��)���P>�l� �c��3�e��{`�������K�)��Zj|�J�������~YZJ9���w�h]=g��7�����-��2�Z5UW?=������%���:WG���d{�L�W��gS��>�W����b�LG�	:����|F��Q���V36���X��-[���Q_r��t]T-gm�>/���4M/QE?��\�c�V��J�>,���.*Y�nV���
��4���9e��o�6��z�y��H�hCI����I����j���:���~��m��]�L[GG'���0�_cw�Qm���j��j�?��~�?��Wm���^��7��j�������~����v{���������5��{Ok��;�9	��TYX�4���:`�dD}�O����'�L���o0�]��/Us�i��{����
�����������2��	����	��u(9)
=�)o��_J(�Ty�����-���$�Xe���p��05��
���.�p$��l����� � g����h���QD��0��j��Ue�W�'�}Sw{{�����!`>����A?���W3g[�8D�zB�R�X�.���V:JkNk�����ojgw;g����sy4�/x��sk��vf��
��p*�lM�2��U
^c_-�T�T��8�=�����4�Bl�-����&��[�SS5�2�q��~���{�1}�*cf�����X���M�Wj�&�������3�x�����<u��W	��nSy���������[���q4�g�����4R�����������
l2��^�pWu����~4�W��v�"Nm<x�����w�:�L�y���K��l]�����n�CC�R������!�^e�����s�=m�vQ��^%u�����z ���.�E]eki�g]W������*]�I�$��/S\������#!S�?�An�v�����DCO�;�Fn&1���u�:�,�)t�_���DG����J�����9�����_T)�n���8���R����S������CiJO��S���f�'�������t��F�QO'n�s�6+�)w�\���`�V7��\�����V]��Z^1�S�������"����sILn����j�=h�J^ �
^j����^�Z�T�{��n��HAu�)(�����-��[��w��'���K�C�O��)?0����� m�W� ���5��'�X���5P���]��O�W��`K������j�.L���V<UUf���u ���f�8z��@���rIZQ��t
g�*�P+Nb��/3tzZ8`�}����&2�!U���(N����jC�P�k�Z�!���H{�������w5*@����h>W�$.�P{N����E���#�\n2���������3n�	V�����p15��r��i�@�\�n���`B�W�-��:����I���E�)�p�X�(���0�,�Z��h����y���8��q���������p{���h�%� �2
4l�A�`�����-�X�\���3I�j4�_�(����E<���p<������`���;;{F�Z����z�y��d��",��������\v�S��'�6D|�Y����fy<$�R��a��'F��Q�����yx���/Ka�;&�{�'RDa��/fLZ�<f�W��vZ�M��P���
�Md2m��E����6{����������'�;IE����Z'�/=war�:��0��f��l���0_�{�a���$��)��=�*�A���[�K^(�r��4�b��^m��1�.-~6����
�6D�~����,��B��.�C8������3�"����E\��8��������������-�T9DS�����'��U���EqoI����6���A��������J�	�V%[��#�b���]&	*�1���\�[�r���(%���yD���_0����t�gtTF�CD%�Y=M	Ihs�W����t�!=��{��2Wyn�)��7S�]����jf�mG���[{���-
�-r�u�W�u���;]��pq�w�������X������M������\�Y�T��u���B�k-4�qV:U���RoQP�����Y�T��Z�j��k��{]m�����������EU?N�C���k�;5w/������9[9U�h����������Z��Z���W�>vk�k��|�y���� ��A��*�U*��1�����d~x2Y�o?���r���N����r[[6Wm`�<���;���
�����[[�<G��:������E�mY���g���:�*v��B
�6f���bp$����3,f<M��H��dq�r�����n�z�Q�����N�AY`�j�X�9�US��J����`���J��|�J��.f�(_�
&�Mh^<��fC(�[����$]�X��I4�G1?|�������t�C�gt��X??h�9����5�V���G������w�*GS=f�z�R�����W3�_~��wi������C������f���+���������f
�b�.���E�a�G��}����H]|}�ZV�X��o���w�=���u|�:����,k�i�gt�3�N����d8& ����J �%��I�Ki4�!���������
*�W�>�Q��S�����-���Os��s���y}�+	kIM���:���c��9�X�~����x�����`gp��fj:?��E�i1�f�m� g`
1��=���P�"�I
R|CLTVm���i����`L�aX������W���|���#j����6�V�����t�t!�D-��){��
uXH��c^:&����G7�l,������eO"�+�c���S	�����5�BHj��#��D������{�E�b����os�F�`|����)! ���3fCu�%�s������i�Q�;������o4	=�H��5��:�b5w�):}���T�R����9nP7E^h��/�����K���My���}��b&U1���u��;Q`E�������,
����
�:�P!�`v,sB�5����Y} ��<������Usy$[�hO�����V��2Y���6�\.��=�Z��1�����^b�����wc������a�v$��z���e�S��B����P����+��w��XM1��A��=�������]F���e��4#9��>z����k���_i���~���6A�s�(����)J}��E��>^Vwq�����L�|:�Q�\�c�]���Kd"b���LA�s��e������/Y���+�p�U�����:��[O.�������]���h������'�j�&q�{Xm��`G�a�#Dyb��v���h���N�9���I��H�euQc�_�/3(����2=g��G�g����sg����m������Ru�sf��3����:=Gm��������w��U��k�O����W�)A��Q�����&|����x���>����/%��2�I�w�;=�]������WW�6��}������j����/��Z��&1?Hoj�����mN�2Z$Yj����y"_����/}j�U��B��m!Va����3�bS+[�0G�H6��� �\RC�����:���:zZ���D{���6c��(6���	Si>���3/�����Y
�h��j��"fp�������6qTY���AB��6�7��}�U����#U����.����u�.�g�N��I�qvq����r����[+���"h�����H]����F*�De��*W}x��������H�`�������2X !�k�`1��>rK�J�~�����I��Y�[�JW��{/���.HzV�� wT��F�m�����+w�x�4����I�7�"���*�����!��b���3i�R?�&���rg`�{3i_�������@\}���Z���+�����fq��~��%F��0� �j��4K#���y�u��}|�<���JU�����������L�@��P)��F�<�V�0�����.i�)q1��h2j�:|*X�^����:GaD�]���g����+\�W��YD�"H��g����lV�G�\-
�����'�8��t&s��N�8�
����bm�tez0��B�^�<2"�Qpd�\a-K�g�>m!��9�=�p2�^���f�����Y4��~ytt����a�9�����:=�B�����u�.2!�O��z��x��$��5�{���������\v"w|u�LI�W�R�4w�Q�Qj�BE����<k$�gS��C�yt���-�����j�������X
��f�~�_�.�q��XI7�Y(Fm�~�������-������yu�b+�c!�����8�b����&�Pr����q����%�3������u���p����n�(��#��BC����9X��3?8��rE	���A�[�_�T/@��3�s�����v���x=��S@���j3���T���63-v"u��9��B�u��R%��W)�[��/��L����R���dx�5?
��7��U�#���|��w@_(:A��bZ#�������-��
e�R'�v�m��W�r�C�9�l�j�cI��rF���.�0��� VE�,i�M�S��P�tH�t��a��O,v9sD*\�q����lr���h�Qs'��CL5��`�&�y��V���F��;Z%CJf�<��;�vv�Zo����pX�Tv��fsg�������.A���)�'���<���?��`�R�
;>��|��������{��f'KI�Z�����z\����8c���e��b��5��!�y�t� Y�nZ�z�>�^����Z�+�*f
K��Z?��2�GsC��
_���n�F�V�fu7�i4��$�hn��M�������Q/th����Q��H]��>hh�C�DL:����{���������0�}�J/���%2��������X�vZ���I<���\�|f�S�G��B��hr����_�`v�h��=��l%�E�M\�b3�7�u�_=r{��?���8�����g�\(��Qs5C��u"�����42�%�Y2�9�����c�4z��������Q�c�z�C��<�f(�=V������s����t��n�}����0B9�����{��fN�\����0��ew��������Wvw��k�e3|x.U�b��������1��s�By_Y���I��%��s�c���Txs-��s�5+p��������)K�<�	�]D����{�@h;��n����8����������dm�1���^�$\�����������?����c�B�@y}���@R����Y�/���:��I�ZSkV%�����VM�g/��[�8@�r!�}��[��0;��^������
��|&����+��x6��������w@>~������������'�����A��m���u�A�%�oa���:��w����E����V�k1����*�����e�]�������~}h4�:v5�n�u������TrFv�>8��:k�����	a��w�	����rm�=��$����=#p�i�3�9s�p\���}�M�:��7����z���J�����=v�����{�dZzVO)g����������Y�&B����:�^�q�6n �����5��(���D����/�0����o���V`�(��< N�b�
w/~2�RU(�0���%M�B��� �o���O�O�t�"���*%g�`�Q�v+�am>�����v���tI:w�D�)>�\z�-;/�}O���*�^�yN���!5�����GxP f�0����L�O�?>=�^�p��'���&���94=��{=FT�/�?;>J?:=x��gz`6�%�3�?�|���'8]����O�j�A��m�����r8��T�r����)4����:#�z�������s�.��#���*�����^oC��4�tp~o�X�[V�����`b��)���#�v�V-�[�s�6����G>����%��B�2�`����Ui�DjU<%s������{c�-/���{<�V�/E
�}����NM���zy���A����������(�(��<�:���.^D��/%��;���b�FR�^c�����}�������C@�����n�����~�y�������-��7��o�����_;LNF,�	'�T�������?ef��~�e��9��>����Bo��}dZI���K�
�p��#I���h6�R�y��Q�C��w�z��*K,[lh��X���_�z�i��������v_�����zE��:�uT"f��w?�����r}�5�����q���i��	{}��"��j���e�����~t9�+h`<U_
�4*��������N���<#oX������8�8����	�G�3��rE��q�iv���������E���:����0<5Ra�����9�S��\�p����n:k�,����g
�������Gs�~}T����o���J�n�New�V����t.��qF�s�?�T����b�l�"R>��B�(�w��U!^��K�
�=5�1��S����,�����<��5W�>Ml����d���,��Q�������?sB��p��N8/I+�\���D 8#�1��_Q���>tI�8�
�����.�ajjT�P���s������D����>�uFCU���l�
�&``>�S����d�5�L���~L����qiL�5:�p�66L���K�Q�^-�|��H[b��[�_�sG��/i��;;xY41�7|��d_M�����,������,l�dE�2�����t5������q�1"~����F~-���H&��!n������b�t���Y8�fs���g)���� ���gG���S�N�O[G]��ft
J�QW��|���������kyjf��m=Ab*���<NY��o0�
r5.v�9=���H97�d,��/�
��E������^��u����Cb@��;��P��go�N; ��^<M�=<�d���Hv~|��i���o���������7����pr9�*�vVy�j<�z���k2}r���'�NvH�^�����/U.gF�9?hw��t_\����\�2��"V��4��x�0�K\H�'�C��^H.2)�wc;/��B6�y���x��]���� ��fy�UE[��K�O�i5�����	��R�5���f^��@�;a0�_iBW+y�i� �P��^��%��&a�������lD����1
�A8��M]�Z$�D�\'��QXg�v��aTz�[���uR������)�uJ���h��n��-��������KC��W�f!,�"s��MZ���Pr
������9����j�/8�O���r���K�*L�����2���&-�L��fa���vd���Y�������45������X���1.-*�<#8!a�jA��)Qu�UaPh\s�nZ��Z�mh���s�A�7k�l��@����na������J��\�����L7w]7�/��^�n7�^lVm����1%�����y�%iY�C������l�
�*�OA��j��E4�IR�R��#5�B�T��*�x&�j������_H�������`��e":�m��%�rY+|����v��?�Q�����F	�{�<��t���L���`���3]���t{�,@X���?���^�����<T�t���F���h���a4���.�����P�*H|�rm}=�z�X�`cO��S����>GBxe.�S]�h��{�������������b���a�)��E
��I�<�O.C��J��*��r�Y��{�;����_���?�G������X���lZ^�n��W�������ky�k�B6��n&��*�%�/���I,�m����� �C����e��dF�A8c��)���>	f� 
m~�[�0G�z`������r���#T4{���N��VC*����`��}���!�&EYE�3Q�	A������	9^%�����{FrD�Y��<�f�t��Y�P8�a��!*S�26Y
e�0���5�W�)f0d�v��c�F�<2�A�_<m-�$r����4}����P�2�|�����i�(Z�O�;89>���.�~�2J�n%�9	"�3�i�������"T$�Tr����&S��t��^t�0OL<���:����n�n����}dg2����'g�_���Q7iP>�n�1�9���d-��_L?a8��?8��\m�I�u��'k{�I�;*p>�����4SN��"�Gjb��7:�7}�71����&E,���^�!,��b�\*L\�(�F���j
��q�T "�4%NQ�C|�9��I�U��E����t�%$E."���Z��R��!.�_��a3d��4,��&����>��PZ>o�;�����a����&UE����x��0���\tud|�Q��k4���z���N��XK�)t8��A��rO2�m���\���'���f3[#��t�;va��t����J8Q2�5rkkp�.����`>�=�;,����!��� zM��E���dl�vRX�4�Et�n�d�Q�zz��&�C
�
;x&�dch
R�{.�_`���-���i�\�:�QZ,L�RC�\�B���wS��-��k�d���'��s�D��(�v��i`i�X3w������T\���]�Zk6��:���MQS�
�B�+�zi������!r�����	�1�T���������e��
�	�'�hK��Hmh��C}L����]�*DT,jK���u{��?[�-�xm1���X1F����K0�9@RU��<7>�~l�@���l&3M8�F���0i��Z�"G���v����[�G:C�<�q`��D���(m��dlZ�n��h�T��`�����H���C��lpdFy6)=��?�[:j��`s���=x{q�l�f���h��f0�����RPR�`Ez%t�{���9gc&�^X����@������lZ��Z)���c�+���H�����`�!�U4������dN�J�c:@��7���� i7������k��io����E�FdC�F��J�J*1���4'��3@��v������K����C�����I�)�7��Y� �1kQ�T�����~JEK�c�P]��6R\��]6y,
~�"��W�b<0!�@<c����F8x�a��6�.��`<� n�CR�����
����l&Q/f������,������
=�������,������\[�"P�[�p<�Ke�Y���R���Z%w�3����8�W������R(�[�1`�x���MBU/�����I���7�T�u)t��yC��.��c���m�V��j�w5e��I�����Yf������P�q���Uv6�Ln��=n
U�4h���bC�4�.�7��hq0�:������N��K�N�K��[iO~��h��&s�4"��,�����;���/��_������5�f��Y�		O�e�����x;��`bg�j�����36B@9w�$��`���J�����N�����
�I���`���X{��\#�����G��Z0��}�
h���Ka5���"l*~s�6�|}%F�ie���4v��]��Jzv��	�
��Xb��6V���c0tG ��6��q3�C�=���`�X���!Vx�2Z���bl�H���M&
ClhCk7
�S�L�����~$X����d���G�!��+��e��]��eZ$�������&f%{�\-�����NF�	W�<`�i���`�a�����!��%��X/Y*������c�(��a<�
��������_��9��-S�<7%�!���������sj,HK���sj0�J�?|��,�
�3ze�r�y���
�\\]������W�_�HK��n������@�	�������K�����R��(x���}�K����B�rq|@R�#��d$�����+i�g����a������A���?��	�=��k�C8�a��*��DJ�@�������	��_���.��C�v<��pB��s.Z}Z��+l�>��ea�gW�l��Zd��6��_zdYf�+�2+Ri�����Z;��G��D�_l�������$/���:f�,*t�N:k5\�����a4A�v�����_�(�!��y���l��]@����?���R������F�������?~<�'2s<_���&!k�D�>�;a�G�HR��
<��_u�;�W����!�J��Hy^G�=�h<��W9K�_����+E��(���{�6�/���Y`������Ei#�"�8��^��pL�
�W�TJ(*$c���=y2z���X�:��J2����Rkw��.<�C�7�2�X:mX�@P[U-��j��-��>c"�����N�r����������?�WbD����2xGN�f-��K�����������5?������4^��C�����.J����P����s��o���;�'M�+PjqrL���{,��DC��U�bm
G���O��@4��^�[������tU���[�!U��(�W���9v��b:\}��Je���m��4\��|��LQqe%OV�����$�����=E����3}�������7�^0�����:�p����#u��vq=���y�~%B�!���dq]V�b8���GIE(����>�'miu�V�g
^uM�(�|��q�������=�p����:�*	W^�'�AG�����ieO�k6J�8�����pF���}�&��^V�`�0iF�����	Z�b���i�YT�������j��[V�G�G�Q��!���K<qVw�������P��wg����c��x�}i����H#�����m����(O�����5v���Je7��%��g�d�p���./ao�Q\S��H����7����I0��E$�o&8/���Rq�<��~)�9rJ�m�g*�K�X�	kSA���8M����)�M-�4���!��o\�z��0`�t��(/`�����5	�)g�����x�((����q�oQ�BFk�V��H�������7�x�I	+�c�pQ���]�f��P��H}�
��_���n���/��L+���`���b��A���7���vsO7T����+��2U9����FS�S\F�>�e#��~���q��[��K�|h�{u�����%GrL�m�3+/�S�}v�Z&P?zK��B��=y������V�<vp�O'Nu�8T()uA�*������y�S-�����g�y��>n}�-e�
D�{���2�����=�Xo��YlB`?M���O=>xcM��.[�>j������EA�Mp������+y��7z��nu��\~7��������/'g|��o�d,�����s�����������qf�6���94WO_p^x�1���>����?�z)O=a[���u�y�I�C&��4Q�X�s����`��!�����1Q��ca���Rau��P-�:�0��{�M����VZH�����!h��v��M�^���J|��2�,[�K���[(p<(m��=�����c�z�>�����:��*���k�J4�d�v�>1EZK}9F��������7�s�4eM��[�_�/-�c�:��.��,)���/h����r_����6��ebFz9����G�NK�a���1{��
�����!�dc���S��~���L��7�&[�pO���JVQ� cv���_�o��F����:y��1���[%�#T��P�9�����z9���:�������UiwP_T�r�j��Q��E�N/���R����+�G�!`7���m3XL�\2�u�������������`�@`��'�:W��Wv	�l������ t�' 1�/����x ��Hf�.U,�fL�xJ^c��V�����|�������gGg�]n�>���T�������_�����K�/�7�K]-��q���1�t\>�q������>��^������k��������������sj�Q�~�����"��)�0������\��=�=�~[�������&���!���3���I��������Z�F��G�U ����q�4�|�C/��~���pw���/�~����l�<O8�.��[W������������qIs\j���Y������U��/������}%��g���\���Q<J<5Y�q-& �O���v��2���Y������� ��������%p6��K���-R��S�����$��X\����������m�����K���[5���F.A�
���#��U
gB��U
V�+6��a��1Y���B�7���?=��
'���������F>�+b�����zd���a��iJ���m��<	�H�-��'V���4i��O������9
��K��4�;�����X3G���v�X�&�����u4
o��,�UN�d�3{��[>����>��|<�n����	���������@nr��<@'��a4F;��Dz�V}a^���S��|�����>�0�i0��?�GO(E�m�19���s���d�}5c�xkz���c��<�� I�����H7��#������$���l������@KB�I�3=c�����q���<cot9{v����<��N��|�w}:���\���r�l����:j�j���t��6������g�� [�W��Q��<��u5��!iu��J�ua�&5�d����Y[.�z@����\ @����#�����(L���N�Km�����/+38K�)�:��5�j<Pg8���p�13v�g�^C$�Si(�K��DU$��~d.��Qz�ce�)�W'���F*l�^�ubH���D��;9��.w���t�nnF4�E�U>�2"I�]�fK�D/��~2p��uF�v���M���oP	���n3���6��]��"�D��A�Q
xSV�G�
xC}%�`����h��/bz'@�>^�5���0���
�K>g�����zZ���YR�Ou�X<i�f���5�6�DuOq�P�X�#<r�F�F�O�<�G���.8��z�l��$��������yRVmk�Q+��9��^�~z��s�YW�ky�f����v�a��5���;�����Q\��h���
�Y��hT��X���7����a����B�?���-�����:!�v��,W�0#i��]�+ ���+�K�`�,VD{_��A�X=��]S�x|}�O���k���b��
�<>�jF�%��$��%�E���4/A�x2���:d��UM��"�1+V����W��#d�o���I�MQ]�@����y�E}���I���f+��}J��p���e�BV.)������a�K��N�G���/����r���,�O���(�>l��/n�������e���f�Vy	*-���s���[�0v��8;�5�b�>�"�tF����4��P��R����a�#i|^�A���h"l��������y4s1H���q��,�=�E�h�W�j4$���y6#59�P~�x4��FPlr�Y\�>�'�f�U���������}9������x�����V+i��Y���OX� q�����-���+80N=Z��F�L��f�����PK����^YY�h/dV���_����7}�%C��I��]����]�QFNCQj�whr}���QHY;�-@i����1�<6����Z=�k�Bz��R ?�Z�	��8����Gi��w^^��^m�]��0�_����7��<��{��Z�>�\��7bd�u'�w"��SA��iB*�@����BO����������d�`zY�t���I~�X�`fb�S������/���21|�k���E}��0��X�?:�8�:kJ
EVH#>3YZ�q����-�@��Q'�Rn��T5�������Uf�;�|���q��/o��9�l�)!u�z���W*;��^o����k#�o�+E�@��
�N�T��j>&1;he_j�+u{���c���i�����������A�[����V��\m����,������"��Pd��?��eT8�����*��J��q�:�3���_��E�$o���&�m.lr<:�

�{n�Z���	����+B���������UE�3��������L�Z'���uI]a���7G])����r	��<����
,~�8��!����cp
�~gh�<����Pm��ip�@����?t_kg�`M���p��%#;dU��9��.�>kl$�PC�\��]S����"�D����^Cr���r�5�#BO%��FZ�+U�fEHD�%��8����w������i���j��}{�9�hu;0��zWn[e<s������}jd%w�
>�YAH'���VH�B�k
�
6ri�"R���A
�;ZY�`T�5v)�]���������J*M�R��x��M�����l�F6/���6�
���P[�_�x4��pW�Ue8���t���H���	~�Q/�'7*^���/��%�G��O�>�;5�@����Amr#��c��O�d�9���`�_��{:���[,���]Kly$�J27�w����_e�����S�����S��u��������v�_��k����n�i�m!{Vg��I]����?
\�.�a��"��P#�"SO�cT3�z��9	?b7��T�Q����M���V������1�SB$��Q��;�h+�������O_w_�N���������B�w��-7zxpr���^	/
:���/`
�|/��*����l��4�<�,��"�������CJ�jB�	pd��
9&��������"���;�Zw'v�B�,5������E7�SMh��au�[��p���i��y8����7K����}�k�����-t[�Lg����|�P����p�����~P��+��v��o��h��Dw
Q0����?5��T��v�9�&���b�%���qP��Il����b��]X��������v���p�\�y��i}U�,VF6e�rkB�F{E�T������y@S�Y��c��{���Y~�P����I����m�������PJj����[PU�iA{��"�{N��3]���l��+�E�5;�\�*�Y.g�u�&E��wBOwv�w���J�1���j{�����%��{��|�����U�����p�!�n~�P<�G��q�I�B�@��.�~ �x>�8	���0�����<�"�DY^�h��]���M0�qq�C�n��G\���*m��p�>d�c�+'�����r�C�v'Q�n�b%P;�z��v�q�o]��'� �l�]`vv��
����(���
�Q��r
-ewC@ob�� �+
,F���5�-��~
\6y�P��D�S�P\:�p4�ck
����K����H����wXU����M����h<^L�����f��
I%�T�v4��A����Cnf��3�����f)����$'���;3��+/�ngh��r�l:���Mjx�{tJ}��(#$F� ��zE\���M��4��/EB��/���u��K�����������������Y�H�������������YG*�[��#*��;�Z���_�t���Z���A��[k����L"h�}
�m�p�+V<{�>x�Z���W��M���g-������"���khH��3�7�����D���qg�O?=���Q8��(M ��5L^�#�!�{�;�t��^�8���o��<?�R�H��W��h|cM�K���9u��N�}|;�Lb��G�NsW����D����b�\�����W`D�K����Ams
�i��QG��0�S������z�~����������:~
x�}x�;��������Z��g�����T"�Rto����>��T^)�f��������v�5^%�H��H0�r�R���?E���"���}�n��?�+��G��&�AA~�8���������Ga(����^��.�|��k����\II-L�!:��k�z�����1^��u��o��"������[�Z����S��|Q?&S�X�l�u�Y�A��-i�D�v\���h�m��>�'m
m�1IL�pC�B��
�������ZKv%�m�9����J�<��n��L��]dp����M:kq~UD���<�g%�FN>��B�p���q.|L����m��L�V�km#������iI��Z+���7���H�n.��e#^�~��`2��$t?4��$�+�R�=����c4�t���t����D�-������"�N�7��5a���k3��`4��*���pV�3@A�<�Qu	�WJ�w�Q5�eP����f�U:��c}���:8��2�p6B^�@����mA���W<)�@��F�s^y#�����Ep"��.""��]�:��]M�v�!���`�Y�~���v�d�1�O{�9�e����Hd;��o��nYM4{�cU��.�y��V��Rs���9�.�9�rnbW����+1�;�q>���`�2w�W�}��l;�p��T@;���L8A����x?g��f���/x������o���?<������~���V��8�VV����-�{�d�Sk����Ap�|]aL��(��������Ki.��h��{������:8�������0���kr��\�w��p�Z��5v����+�T���N��P�y�v�9���W���9�r_K"��S����5���9�r�Kb'�L��jlP��4�����]�mB�]�9���gah�v���'�2���V��k������E:a��l���W�������3�(!�%��6����m������LK�~�U����oMQu�������{qgg�\�G���q�����`���w�h��x��a�o�$�f��{9�}���A�d��q�u=���w��y�_&g��S��`v��������g�2�E)'��'��0]G��.�92��v|�������j�
&cf��p�}���`�d;GJo��`]Vd��d�A/3HE��_c����^y?����YF:����M������`��%�k�~x�j�����\��%�d��m�����������������w9}f��s�l>arGxpx��t�oZ�����^����� �0A�v����yI�����a����~������=Qc�ff���$�m/wQ7�R��4����|�&D]c�1f1�Qk�.i��u��P����Sc�A7�c���u���E����.��4rk0-kC����[l�?Px��~�@�,������d:���
����y3!m��V{�a��������9�dy���t�HW��o�gK��W0���_�;y�%�f�������6+UW�ySsnU�v�l0��(��x>�n�O������*��$o��b:�>n��ul<��,�V�
�����-��
����J�%>B���fk[%��L&��VOL=ui~Ei�%����m �;6p��H��hr<F	t��O��D��$� t�����Q�=t��'��CLf��f���I����l�oeP#~eJ��c��9qav&�	��������SNy��:Q{����Qt<���?��\��*�V�ti�x���Q:�(o��j4�EFNH�|����	�l�:m�����,�����;�}�e��`<f�t�=t��\j���M0W+(�v#�d2)
�*�}��Z��o�M�x�r�$���������������t�'a���%hs����%��x<DW����#��a�G������1:���x�<�
���@Z���H�O���iO������7��3a�?���]�`�L,1S/�]�+�)�-F�E������h~Lu��w(%g7����A�~O���������j�&D.��)�����M����v���������=�ph
�O������/�������.
g�h�C�m;��h+�Q@��t8�G�.��-�B1���sM�h�����Rx�+fF,�0o<(c�c���PO�W?/(^�;85&�G��8	��oS3�f���X�{79;�����p����0���T���vmU��j��{�
:��^>�M�dn��7�a��~�������_���W�MV4���A !�S����Dx>*���
��$~����������[��
@��{Tg������r��k�/_�����������d������/�gmi�mF�!e�CC����d��������k�N����_v��;�����w�o��=���f��7K�_�"�k�,���rA�Y����\/����$^�M��W4�,�Mp�
�SJYq<��&������n�2h��0hzh���E	}�Iv�%�]�`=�e8��`���0�#�J���X0�������$t���2f����Ga��d��]h�\���9�P
L�!�n��9��.���H���b1���&�dK#��%���eK���!�
��*3x@��gI\�Lby!�����;���AP����v��^1%��(��'j�?/|�Y����G���$\d�6������P���$V5��5]+NW������HW����-����l��[H^I3��
������47K���{��&�|�+���#��e�:��H�S%�1���Q�������z;��X3x�m������C���nC�:*����������]�����a���&ft���(1�������G�%t
���S%g�I�X	*�H%��~��)G��I�z�����Dn�&������&��4N( N��\G��;�k��H�/����%{�>��jjM"^Kd5�]�D<F�dJ��s��yu�n�][��NZ�jZ��jE>4�_
�a�I}z%����df��$�*�j��Q@#�����U�"��X��hi���~M���K�%�-����t/�������*�������c,;���s�_]��a0��a,���=��S
?�������f�O_�
�l�pB�$���W��RV��+]H�X�������U�$;�������I��)�-�Ma���SX���	A�e���0���=:$=��U�FP��L�`�VW���DY���8zWD�V�?�����?/_a��xPI��=�G��=�����!�k�7+/6���x���IZ8 ��#8d���������8;�
SS;i����G4cm��P��1\��{���R���pI�'WjRR.iRc�JM�.5��':WJO<g�!�v���$G?>��@H�F|��RNB?u��cL���c.qPg�y<��H4�,��R|�4�g��2�����UX���rO��s�p��Y+�U���$��R�k��%�<�N��Fn�|���4��[�,
~~;M'����)�t��ts9-��Z����M����j_0Y���5O�<u=C����P8\���b����Ym(��nC�������,ae���D8%��hI?���CafO�!N���b8��~`[
�U�E������E |��9\n<�=����?�$9�A}UJ�SU�*I�w��i 49!�'��W'�����h�h:~|���DYrg�,#-�T&����C��0rF;�0s	���fj`����Y�G
I���:Fs �?=��k.{D����v:v)_����'����h�k���5����yH�rJ�#m`H\#q�K��z3L���Cu���ycl���Na?bv���qVt��>�c����t�s��y����T������B9GB�1�� u�_��Kdg[x����,%>�w����xh�l��<\��������-�h��}�x��wpz�2>�X�t�v��y���D:��gJ����G6��
�"G���I20������������xFL�$,�!�.�/�W��E���B��n�t�`��	�?���L,�����D�E���_�+�������yMJ��t���������8�4�%E���
����
��6�*�n5�e�����B�+w
�bK����+�W����n�Xsk� ��!��Dr\�,X�	�4�B/�a	.���~Y5�w(�A��<}���7�>a��[~�(�fZ���(�l&jQ�:
'���������_�t����l��$�L��e����j��bf^1f�bp9�|]��X]�����l�m�F�!�9��JB�};�@�T��Mw1�����8N!�bA�#�S������E���,K8�mg�W�I�U�e�G"Z�H���:�d��'
�c����k��{�
%�����Y���Vi�&��,����v�(���T0���~�>l �[�F������x��H��}����JE(#cD����E�7�
�W�5w`��.:��T���AjN�� �Z�\��C��u�������������S��oO����}�W|s�`�r�O�f3Q<��}�^t0����^�sL���Pg��b=����C%�����4��NX	/�	F������6W�A�*����Y��w*�A����� \-�A����rW|��BXJ������YWy�n�����X~��fVOt��,?���>��|�>��������q
��B���9}Yk��G�+-�s0Z�.�������f���@���SC`p8	�yf�t�$�i�!��L[��4��SG'���6o�~S�%L��������������<J���#s�)=�g����_+���ujH8��/}B��m���x����g�z������1f�Q�lh�r 7��%9a�hh��8b��������y8���
��h6�����E��1�+��M��ID[�M��d�~�-��_Gh^(���	������$�1�����S	$�a�?m���Z��L�|��:MX�tc�&)��WW�ju@,=������B"t��K��op
W���8���L����`����m�JT�as���M��U:�|
8f�8�A/����Q4�o�H���SH0������[�9>�H�-�:M���Zc;��',4��C����i��\����+6���3+U+`W��
@�b���d��r�I\p�g�^������(�j���9z���J�G���BK��IhC�#e�a+�$��X��3W�D�[����������*�\:�����`8���i)��7(�J���Rf��p8�tb��Z�w5�6{vp�����������T����7�bZ�i�O-2��w�8.
}�o����j)��%0����D��ZO��������f1���8�%^�H�O�[*�>��a�yn����=�r���P�p�QwX�����c�6��g���
��b��gO�?��������O]E������bm�/���x�>4c_s�we�N$P����PR<�J,��6$�E�_"��]��%��b���L?��b6�P0(a��);��f(I	?=kDI	n�KT����>���T��J������q�����l,���8cZ��v�9���?���j&iX�#
�d��'�6n���tS�co1��=�?[����P��������oZo��"�Pe7a�l=����vp�����D�o����B�����p�K0�o4��9��[5���/�K��n��rM$����2��������#�TFk����}l���.e�>����������#�IT)I~�c�^W?�>����
/J��������qw�Q�5�6���IU�>QX��%����P�Y���i�6*�G�G�v��3R��������o�yx���WX7�����v�����=`��Ac��/@*�������������gKxg��TG�<�c�����h���.p�'c�BBF������J�Al�^����������!�,F�a�,z�����]@-s�X��5NZ�.����*�!K�8��8����m.Ig��^[���Y�9Eh��Y���P�g�6�$����1�#�@���^�Q�zq��<��]K������m[x�7M�R��	�NkGu0���9����U��no����������jNk�
F���HG�(V�{m�����!�9C�r��WN�@��f�5{����{5e��Rg��)���(����(��VHb@�6�:r{��S��S�T���G���hL��F
��
��1�r6�(���N�4hOI�}B\F�����}��5��xS�������l>�/���Et���4C��QjB��=���0��������s������T�-� ���w����zs��W��O��C�l�O�Po���a�?6�����q��Yi/"((2��n���QC6�!M��Ha�l�{�Z,P�k��{�/�-�21Z������F�~]��<�{�S!�v�3�j���+'��Hh�#STD|^����)��e����3�IMK��d�������xK�)(pDC��s�'���9����J��V�<���7c��WVL���`��N�4���c���/�{4%�������P�t������M����b<>'�"���o��9����r�
['�R����0 �� }��*�z��t}���r~�,���?lkJv��2S/2�yE7=�|����HY_+Ozc!��D����si���0��I%.�~xE�[l��6�����L���+�f#o�!��G�K��	v��/�i6�"��%ri�&���fS���;H�_����sO<?bSl� 38���l}�g5������C���W�`9�b	��	�������$>��~@�|����ljM��<�����w�a��be.\�UPxk"���?�������0�6-������_�N��-E�������%-*|�:kz[���2�D;CD��q�CX�H��|F#�-�4�i#��F<��p��g KZ���v<-G���
�IeOE��H���B�CU)����}*
�d�"0�j(L���`���L��uCE�g��������z�R=J��Ji�WQ4�����()��p]��R���r�"
�����BT:V-��|M�w\(��c
�
���w��B��I��d��Eo3G(�Y:X�@�!*C�v�2�b�[QY���#�/�=����*)���}�A��Mucy�Z��ZYA�8��@J��UWK
���%k��P.^�"�� �4����C�(��
���\
�6j�W��Nu�R	�A�^�_E7�1iN�%��j���6��V��(Ia���b�_rQ�H�cc�L�M�������;�,5����4��X�gRwe	�;�G��a���V��"��*O�w,�������Je�?�
���5�z���7t������%�0����8�K�	����2��U��S ��Xb�n�.��Y�ip�cg���������>}�W�x�qJ��\��R-_#D��
u�er$�����/���6�e��L5�}�Ok�Y�����3rO�8uj�Q���c�nu3�����X��2��
��T�~y��6��Mq�
��<�H#��
}m���Y<w�
��T����W�4�{a��(��T}��J ���(`0~�fq���S!���\Qv�.y�H@k5�����O[GZf�/�9>}�==��=�'-��/���*(�W*6��u����i����Q�tJk
�����m�L��o���x�b�������8��b������"����[*d��	O����.��.�$����
_����x��)vciYK�Z����b�d,l�23�8h�����t}��r�(��	&��M�Q�c;`���|b�d�d^~����Y�Bzzy0A�N����m�v	�8c�4C�br����i���^Z7'����.��h8�pVY���$�O�;P�i��TL"x�E��9v�������z�w�{NEB��r(;���9��KeF��!{������X������%��m�V��:�a�������2B�_�����Fc)X+����0��:��-�	�<=�>5l�UC�2�j���=)U����\G����<z�<�_�*�[��AG�F���e���)%��������hX��"r^U�{�f�R����Z�c^�Hrhc�;�����H(9cQ����t:��?.Z�t�[cC��n�0E0�GCS9���Yp�����f����Y��<�r��1�0�$�=�I�`��}p�����N�W���|Il� �]����/���IY�~�J�s`�OB��&���*�)�8�1|���Q@��}xz�	6�Q�%J�`X�n�6�E#:Z9HY5��ZY!Kjp���Y��3���j�E]oN�h��"
��N�hJ�V�
th���`�POxR�
���h40���z�6��#CMV��l��X�����E�q����Om�x���+�����]uH�s�Q�3$�q�S:��}�a]SkX�U��
�dh�����$t���p���`H��(XK0���	U��Sq_�hm����cL��a���H��o�O�p�����*��k�!���������.n���m=��|���!�Fm�,|���=��(�c|��R��>X�M{"�2x���,�������VI�i�������*Q`4[�P��=}�9��B�Z]�p�
�74���jF����}�����7���r�^��?��YT����q��]�Zfu��� z�q<)+._�BM<��c�����A����	����82&���j�Th��I������@sN�������M�^�!h�x�$��~�B�6�@��f��&�������O����s�fA����(�m����l��U�C���@�8���P�!���>�<�-��&]�J�	�u�oz9�4d�E��>�l%Zc�w* v����B����Bp���J%������D�����'���8� ���������aq}�����P�[���	�����^0N~�0���|�xKF��
��������Wk�2�N�}���D�&�u�����5���Y��.ii���%���0�NFd�3�x
�pj�LF���J���O�.��b�P��p�{?BwY>����R���P(�����@a�mq��kj�v�)��k���^�	f-NPR:��X\A��/�����T��n*C��7$r,�y��*J��S�7E��dq�T����$��Rf����e�8	�<m���8v�����������!��X�q?X#�U2-�=H�!��'D��<W������S������$��z�r.5�dr P�3��/���C@S^t1�u�D
�p��	_
�,����z��C]&�G_A{nm	����g�UM��*�W��~�C�g�8�K_�������d@,=�Q���X�fF���,Z��4��!�F��	���A�
�h��5�K���3yCJv�)��q�sT��$E
�A�������K�2���5��d�Z[�^Z�����	d���d�P0))7���I�Z�)�Fy�%26�����a��'<S��T�V��_��m[����7�3�MP����	�{q��V���z�4���[
�]����8��}5<�=��������^:���q��z�8�����]8,��,$,�,�@�g��C���@6v'�\����8$QZt[�s�tCX��$�(7R��Fxo���/�����EW���~ux�;��������'�\���������	�`jj�v��=���
&��:)`���f}eF�T����c�wc����f>S�� c����HQ�>�K�Z9L�.O����UX8���A���$���I�����M9��S����N�����G�2/g>�\��r��F���t��K{�����-��J'[������O��n�s���b�Z�_&��Y4�8�*�I�&T�o������/tt$��68=|s��O����8I�	�a������.^>���[bC�Ig~
u��cj���]7� ���,�cb}s<���T�^GB��Ij�"���c!`�	�R[�y'�zo�����U*��o���_;��������P��5�8�����v�eh�I�Ly�.��A���<�4OR���|Y��
hN��^w���L�E��&���������,�zAc��\a����Zs�Q[}���%p
q������<S����_�O���{v�j\�Ql��������GI�k���~�}J�&Ihi�|��d{���dT��>����'�5��E�0�G���Ymh��v������R����C���O
�M�Z���������e�g^V�S�@�4�S���q���Z�os-��~[��Z�*��FN��=����E&p�h;�Cr��P������D;<�	��O�����	<����b��D���g����0���
�������	a#[[,��o��P�{\�5����q��?�S�{�C`�E�gdFV�&�X,��[���_"�K�86�~��#"n���������a8��T������-�~���Ky��4~��;��o���Y�����L���>�������)����V����u�o<H����H��j�����NnQ3�*�V�w	��^����l:u,�����+�G��hb���b��7p�����W*,�:8������|4F�:���v�����v�R�����f3���Y�����Mv@�=�)�-�1fg�'��9?��*�Vk����4����������r�z��Go4����};)����,����{|�nmp��S��Z#��8����c0�A�}x����05�kM��1��h�T)�8P�mT���I32��A�s� �K�M��9�5��c�5b)�C�~��d���ml�i>BNp3#m=>GH�h��A�P��
�t
[��p-�gP5z��9����T�
G�P�!��j��vu��-��fq�`w0�c���no����4P�At�0�X�:��"o1
uB�����dn;����_5��@�h�aB�lmc��sV���h����pf����DZ�-�wE%���K_�@p�L��P��giC�RB��[�P�����z,���&.�������i6k���������
�a�U�RA�97�+�RO�&>�"�����p������2};?9k�>>j�^�N��P���9��<�"�'����9�
r��Zj�sqpA�u/�O�!���v�[�U��vY1>	�Rb�'6�a���eW���`gXo�jW��R����L��*~�q��>�����~�_h��/������"�~�qGWb�%�Mz�YOu�nJ��mCSO����#K
�4#�)���$�
|��)
!���,��x@������7]��y���.\�+�F����0LA�	����7~D�M������Q�[m^$��]o\S�zs�H���1'0Eeq/�sg#]]7[��R��<�2e�f���G9mtit"�p���Z+��P��$���&���i��4S9j����3Fc��\��9�O��M�������u���QA��v���TY��1�es�t��nM��u���L�
s��88>y��~���q$�s���f!AZ	��3a�F�=!q8�i]m�?/H���nA��n�!!�M��G�N1��PN]�R�?��	�C��sc�\C�LT����������������b7���4�����8���7����L������a&�!
�dC/:��l���w���o��5��T�G�����E�D�R��r��C0.=���{7~��)p�����u�����7���$������o�����cvU{����|
��:��`&���~(�{9�a��`����t#�O��2��axl/;��q+2�}�[��2�$$��?�3���FH5��5�2"d|��d2�s0FE�
�o	!����r���1�qX0��)=I�L�sMuS0�������[y�t���{of��X�f^��n�q�f9|�D[�
������Uv����vQ}�x�]�/��w�����	�p��I�y[*�
WBZ�L9=0<i�%�Gnr�z�w�&ghrV~�Q�{�fN>��R'�}����w����.Usg��S���A���OT�/����hu��������}6������9�����m9���+qJ�hB<A8����e��a�����Sx�:�Cs�Cs�����X���@F1�,Oh�=�S�#K��+J&�/�"�t���Zt�Z����������� ����x~Vp�K1����:��2w^����^���n�4��+�e�,�KJ��fC,6��6ET�1��t	���7�X�rJ�/(?��#eZ���S�P��]X]RV)SV�
z�����w�b~DbAi�J���P/����mxk���7�������`i���V�NK�t����������Z����g�J�R�]N=+x;�`"����8}��z�R)����u4Z\��@�%���T����B~"������� �m<}J�������VL���f
�8�N��atX^����j+L���?&��~)Hj������Y�����n��n����L�
u&
��'�!�������9��t���nC? �z�c��kj�e����T!�;�=��Yy����@���8S��#��PH�+4�k��} �����������gR��]
]�[�'�4��Tqx]�o;�b���
������/x=A���<�CG��4B�\w�����hM�Mr���~��D����
k�^x|E���rW���Mn�a�qp������]�H�����T��E���VwL�����J�&��{���|����XV&���F&+���������hc�{��H�����k���h������W�_�6;{�|7|�z��oq�%n������Z������eU�9�;��onwR)����:�4;���ne�������;�?��u0lu�~�I��q=��qg+����N*S���9}T��m����T��j�Qk6���SkT�;��n���-W�{�g����(����!�s�-{�o��
Y�Z5��Z��@��n�Y�^=�{As���_�U��{���N8U�]����UV�6�T������p<����s���ge�_>8�����������Xa�U�{Z�?����*���� '��������d��,U6�*�R
�X��I���OB\|y=�1��
�*����]��f��,>����&�����u1�����1�V~0p���9oQQ��nS����s���j-o��m]\O�������[��r�m_�p�y�
W�@�RF���d`bn�	���������,��~~����V�_}-_]7xI�:���S�N���������
�6A_�
0�t���s�����Sr���MWvr�df�_j]��4��q�������?�=v:�G��!0 ����q|������6Vn�'"��(]��w*����(V*�'�Qo��GF�!��<HC�VgH����}�[�7k5��s�����~�7������|�7�l#~��+=o�z�<�Z�I!Pv�d:����O�bZ�*�[/��
f�+��n-�.���A�>�I�sO����������AL�Gq�:�������o����Z��Uvz�J�:W��������rs_�Kek-���d]��]jO����5S�0��~2)����W7}]e���^!����-��������=���@����r�~��n\�����h�5�xU�0�/?�����Q$Uw���%w}�Qope�q\>8����Og�N���b�'a�^F��3�1~s������lm<P���7O+��U�V���uW�/���g�r�.%'�5WC�������{iY g�
^����^mPkT*���m������|KYP�R��Q�<�������'���KO�?��X�<��/����^�?��/t�����
����9���L-��'���O�-���S�A�@�IvSq��L`S����8��@�����W��w{J*����WH �NDyj��.s�P#�&�4�����-�,��p�6�s��FE���xN��o�.\�a����z��M�%]��rU��A��F��Vu7H�����P�:V����I�H<��N���xw��*<"@j0o'��{��7�d`�e\^y������,rd�A��A�P�O��xb���S(�b���3�`��f�k�h�^��=�����n�>b��h���Pz��-����`����.!��
94-�s^���(M't�:���w=>=�^4�)��i8
����h�a�r�Rqi��W,g��G	�V��t5�/���F��<|���z!l
��,7b
6V��S<@��a�V�rCG9>���z<��R���7!\yz��w���ZLF?/�����G����:��E�t#K���'Hn���N�>R:�rv���8H�/i�z��A5�}����`��[L�`�4�C�+�����8q���~���K��g��8����<A5��
���ye���qDL�	2�<��z�eRF�{�v�x&�p����m��f�����p��I��wy��b�a�P�cP�AS���$��-�Pg�y��u��:��M����x���f�����
���p��T������T��Z����T����p�V Pe��U�B��NqY���d��1�1�����h1��d=��
F3�jDA���uzq|��"�@��d�U���E��Zm��Q �S	r*2~�U�s��[�������6OWM+<=3��2�����u���sE�:�p��G��+'1�������ypVZ�EkvrVZj�+���Z�l]M2um4�L��;]�p����h��9�fU<��+v����^q_�����>����5G�����pX���T����z���5m����_m��l�T����"��tk����fv����V��:���K�NW]v���5��_������Q�7�8�+wd��l��������-lZ�f��������E��s�H�� �m�
�m�C~Fy�f��GK�Dl�h��(�2�#�u8g������T�:�|��������O����Q�G��%BdK���:WwL�4j��Q&��1"�3}0���3@�6����l��K�q`���(Wc��CI:���7�h�b~�\�|��Is���z�F�ML~�/���oZPt�%�V���G���[V92�v�c��w�a��j������Ks)�)����������!M�������+T�[���,��������QuI����=��u�f�����q�[�;Yr�%4�0�������)N����z�������4�WEZ�q���Z��F^�|� �a�r��o��GZ���Kl\}�L]f�7�s�]#5��l��EN@���l�m���D�|�e�v�V�b*Gm`�#�7F�K��Xk��s��@}�J`C�M�#���8h11�
�p{|�$5�%��7���'�{�����w{S.i4�g�`K��M��e��?k��(X�����^!Vo��yC��{7F�wq�^�1q��
�?�]��I��24���>��u�����p�}da"w�&����]Uc�`����eLs:�� ,.WB�%[������c�V9'�����(�f�G<M��l���23�h:MM��9�rS��y\���~	P�J�������uf�*�w�&�����
@�/�`���k8~��
NL3��C??t}mE��)X�>�S6�\�]���*��md��[
N���a�EA���#k��<�1:%���@�����n�����ny\w���&95�x�xEB;�l_��&�����9R�������(p��*�k��-����_�?
\1i�l�����Ys��V���a����YM:����w�E�qnw��}��]d�a}W	����JD��WF����O�){��L����0���$�����������"�a�0>���']�!�%^�7uzf�.�y��O<����W�,���b6�d����E�����
6td�������r��&�sro�'����:eF2)�E��O.�����;2�xCKd�G��He���	C(XZ,0_�j+j"���H�����}r���p�^�0��?��cR.���a�u)�����2+-L4Nyd/�x��p��>1r��q�i�K��]5��) �[(#�O��g���+�<�/� )�/{��
O�T�ee�I
~|(����BK�9Y�0���$��� ~o�1Z�l��Dy�Q�z�`Ki9�=Z���>�o��(mcL�1�J,��\~���L�������&����e�S:/����&�c�I���ReV+��2Z���d����G����e�E�@O���NP�����S�,��I}����\�/>^����[J_3gP��/���9gTL����x<�LU�D�I=��9��������:�i(�e��$n���D�k�z`$dXN
1���/�������@4~Q��Wj��J��SU�����v���n���
���zj
$�������z5��F�s�l��xKV�VP:\�i�ZZL��qb��):0�&��#������
�����,����t���T���L<j�?�U��{��^����1�[�X�)s���Ce��x���[��1��E��i�O�s����q����'�*�s5�����G��~%�x�U2B+v=�8z��W�UY��$��4��e7������,�Ko��/�
9LjEI[��8�b)W������>��>�^��+$1�3�*RE�F&!�a��j��(Q���*���h���$=��x~4����p~��:�Q���c=��}Av���PB��y�E��Np�)M���?�@q����F��q�>��c�������X�?�,�^�v�g]B/���s��y8#������mW*�a������8-��_Nr�`�����
.#%J���2�<����Y{��%�*y�\��!��&Js{�}���N>i���^�����2���efK��2������|d~K0���[d��N���k��J��=a}.9M�!�S�B�����zU�l�^<��?3���$2���������w�G��\qp~�u��=�B�e�/;��`����]��H��93�y���8����p���o�@��6��o�93�����'���o���v�IC����46]ed0�r�/����'�c�$�}bQ���S�3kf\�93��������Ss���.~��18�)KV�v���6��
-�����m�����QL��~B`��-����h*-�g���r����S�?��N:%���~BnQ��'��	T�����{��l�g�#wx�W��/�������8�e�����3���m
2q�{f=p�������iV9{h�G���sM���g��3��c{��5
�5����J�����w��Y�rHr��a ���������`�0	S)+�_���`Z�,&04��!"e�����?�_��/?������j�,=���
��o�*�]r�p���o�	�y�y������c��{Nk��<�q1L)%���;�IE���d ��,��|���D_����o1g��o�(�������q#�Y
���x�v����:�D//������{?J~�d��!��x�(B�m�;��n����Y��u���B�N�;�k	��)���/�}���Y0�m<��V+�"��<��,]J���U����gst�s�*�w]�'.��>���1��b�By_Y���~4�T��-1}M8�h�Sq�B��L��\�d6T%������`
�n�R,%Ii��6����zAT{�J/���������|
������*�_�Jwxb���zA	^���e^��/�@>�%���_&Y-�5���������x�]�<�����C����'����t��c�V����T/^(.�	�'8�v�������hI��^�N���[v+]��l�3��mk���(��g��gk;
��Xk��������D>fW�h�;��:%�fC=~FCUr;;X���0u<(�.���j�����d���R]�9u�/���1�EU2p=��MTc�U3cZe�L��kvF'�5���`V\b�i�n#~��|�sW���c����`�KI�^�O��"Z���������v����r�4�Oc�b��;5$��o�uN�<��9��
J��/Nh��8����[k��
e�y��:�+�3�z#��4�a��;�a~�9'�#�B��5D�����N�{~vr|�#�y'��U�������S�����m�N�����F:�9�q����Hj8��h�v^���P�O������Yt��o�k�DGp�$ $�p������3s5�1m��6],�����xP��[/�(�3*�I�=(Z����_��d��V7�V7��U3������c�Q��A���v��g�e8g�"fAk��'-x\B �����G^n#6<m�x�78�����3�^�O�R���k;���^��h����r
�n'���K��Ctc{�?t�����D���rm/KH	��3��705��*"���C���|>;]��P�n���~@�Oe��W��b�~����������3������u7��-, ���3�}����y��IHms��GsoO�^�`��*�������jm�6����z�4���4���q������3���y�T�I"�3���b�������vv�����2Y��%^c�,x� (��QV�����2&�*�]��k�b���_��O3�[0�7��o����-(2!�_��V]�q7��Lw1�<=t�P���j��2�I2����s�>xj:jXm�n���<�,�,�x_�T0RgwE���vdo�X����SC.��f^WY����6���_��,B�m�V�}�.���d�����_�@����jHZ������������3���6-|1K�h��yE�%�[m����uI����A&�@�F�R����^RU�u�\��LD&����gN�����3�luk����:k�9K�Y���b��4�`~?�>*�C}����?���F��4�% '�"�k}����j4e}��p�(%s�B�C9�S�hbh���������,�*"^����=�����r��!I��+���d`zd�f��b��k$t��<�A.��76d'�B���c�`���M��r�����a�RN����V�!~e��ML/�q�6_L)�+>Eg��Seun�C�YS+�M�el�����(�/�����T'��q�2
%�rcCZt����q��A������l���b�G�HBk|��!����0*=�-�)���)j�U��!�)�B�Z�R���i�hC�n���^�$�tds3c !,��$s��&-�ff��9��Y�Z��a����@C����^�x�����0_�p��
����o�P�C�8��[��K�p7��{'�"Z�������de{�`0IK9�����P������A�1��X�B����d6gv�8�vJ
���^$���$�~	��z�l�����=�YX�,o�HF-,��&����T��������N+�,����
�^���z�}"�
��x�x���l���_�B��OQm�c�d*F�����������;e�x��8�cu~�a�f��5P7���0&�+9���� #-�+,���K�
�NU�LULG)��su���|��e�!UI^]�U�8fsk�1��s8Rc�_��<3
������I���]�S���.�WV9
	��y4{������g�M����h6gLP2%9�_�IR�7��h<M�s�yT����rx}9��_s����q�e��v��N�&��=�e+P,�)v�Q�c. �.Q�b��"t�<�-�*����h�e���d6�
������l�a�0����g���������u6���X���`x��H��|]�O���4�
�~�x,��4�J�l�����f'����Ll%~�������P�`<�����h������y�q�K�0�P�q,[:�y/���4�n�O�����F�)O�@X"����^��WY�J��3_�$?�T�����CF,�A�f���X�hfd��/`�R���O�!k��I�5_rR���M��,�����(
�L�������f�s��9#|��H���!���:���F(��O6����a����F�F�X��TdR�Wx`/>��z���Bl�C(i1���g]�����s����"�p�6����)�TmjK]�>`��l��2�D4O@���Z�	Z��D���f�G�A��0���u���-&�
��s�-��B�����Z�|;C�z��,���A��?����h$d��'�����^�\��77G�Ln�X��aN	���	������>TIG)�5^�������gG����������Q��pF�������������������(�f`��qHV�s�o�6���"������c����/����-�H1M�����0K6o�.��
��
l����?�#x&���F���b0N����?��x�
wl�d�����E���l����%J_�V��C��bT�ZU����=
d1J#�`��z��<|����8�1��%�UV����Ut�QC?��m�@��Fh��������a~�)�_�N��O�^m;T*�����
���g���3E��������Ju�����~�.��"V��n��;�{K��("YT�U��������Tu6�Sz���C��U���j��
��t��\�p�Q��
U:�$r:��z�s�^�JsjN���n[$����"-���V���EK�sa�����b��{�A�R	�����+X�L�U���;3�����9��5e��Tpy9/���������`|�/r&�o&h����#��
��-�C8���!���hB���m��
]z��&?���F'�z�[��v#���hVb�QT�`,o����,�oTa����7
l�q�&y`l���dc�X�,%,c�a��-�c��tA����D�C�_�[�����������[>.{���3�!�?����m8�������	6�J\�_a;����{���������Je���V�+l�tC{,]T�^��D:E��MA�����J���9�����d�����}�:=l����k��g���������N�G��j��l�B��$�y��}�mM��p2<�O%N�z��x�*N�����d�_�������5����g��������g�	#�{�����Y6o�O�������5w�s9x�4�(m*��-�,(��p,�Bm����Ecy�����l��n���j�o
���v��_����3_��f8�=v4]�	�9�����w|���������ga����r|�k�D���=����`<������m�uxM_�����gl
��w�A&u	���5*m��H9>��}D��;������^��:���):���e)CS��E������q��Xq����N]��R��z,��.����C�4����~`bHg��9D��6�}��
���0�Qc2�'�8Vu�v�\�n�����AU.���j�y�����f-���0�"6��'��9�^	�Y��"��*��e��\�����<?���ZF?|�����,�CUQ��s���%*���>X��.S]�H>lp��\�]�}=������]*�����[�"���,,&��(AUm��Y)
�5{�`N������a0���e�'�9B�d$�����j<U}t������y�����B�7Yk�������}���t�}&��qe�X�"�Hp$2�`v��D�z���
|���(9J���\����"�rDn�d��RP��n	�6��^�����8X�2���M��� �$ct �!�=�h����w�"�2u�A�x`����."���r������U�z�%t0�7�������>��!�Y�����hB79����g�G�?�cS"���g_
Q���#H�Dq���s�����N8�����!�r:��!a��~Qt���?899;��.�G�WoO.�o��;���%�0�
���K�0x`f��;G���ET�����u�c�A<~���pr9�*�U�^�3)���U�L:�����
���`\������SA|����5��`�#���������t�L�G`�@X�1*����2���	��/B��;��Q`���v~Eu���cS��T�{dy���&�3��_s�V�iKz��a�prcl��$�T������\Jnd69�a�.�
e;���pf.�aiS!	��Ht3o[e?�<���*eu���%b�W��v�8|����&�CzN7����E���?��y��@ygY�������}:�y�O�<�fhn�yh�.�e��iYG ��]�'�Ix|�Fi3�����898)DW�^9��|z|���ut����3)#����
D����W�e��m�����/x���D����S m2�y����)�Z�+e���2�CBMaWR�='
u�6V��	�/NHLj��0�+�'�	'��@���j��z�R#<Lk�(�Q<L�:@�	��##"��la�������`�����S5���0�8�
]��.�s$K�4	FV��Xi�|���:��hv\G�LXf�<C�5�3�����V���N�`��	G=b�x����NV}`A4���|�-J����C�[,�6�����l�#mb�X��D/N�6^�&[�����D��eEF���r��6�!�p^b*]V�������BD���'�����"�}��jx�g��m"C�.i����<��?%R�!,D�8���(����jF���iM�����K��}�c���Ic�zGP�*�9�b������4���A�
1�K�X�g}�0�FYi�d3�}La�������t6d���845�[V����k����q���-����!31bE�	d�fs:���3co]�RJ���t��
�
�J;Ih��,c�����&���R�R�Av	��9�1Pxw��Q;��`����/Hd��{*�Gw�e�0&�i6�������:o���U/����w�v/�O����,�h���A�����>&K9��413�����%��D�����9l����J2��B��[6��*���d�3��c[�v����w���L�i��1+|<$�N�������a�
�1��'(~F�������0%Z`	)��e��Gs�G��]�`4�����A_��s��6b�����������Q~� +�)�?��}��9�D� ,��%�^���A�(�Vz7hy<��6�����t����8���~������k�v����d`,@�(��?��k������!]�e��>=�8>le6�F��*�o'��w[r�I?��v(C�� �7c��>�l����b�����M[Y��A�B"HC�q4�2��U@��X2-�h��#1����)�j�S��|��	S�Y%�Z<oT��� ���q�|H����\b=B9',�����G�����)
"��_+G3r��^�<��^d5E����_Dc����q
��Q��8l�����#�L����8�d����"G�M�������d��;�?��CV�������������C"��$`&td�������:g��yD��2��Q������_e�9�s��3Hu���#+���dV�K��������=]�3p%��h�2���"� �';���sY�f��+�[2��
1����z9�2+��������'���5��FE�
e�sH<����i|]��tM���%�/���`BX<����,dJ�t������T�B_c^������r*i�D��#��;N��p�y�����\��V.��R7��;��-��j��������V�	X�t���m���z�B�����qgH��(n_)���|u']jz�D��
��.x��������[4hC��j<L3H���4Qa8��+���Q�/���9�h*�9����d������H��*T���I����"����
.���/�P�D��H����$����M�� ����C�����?Z�D�Y;�jZ�l�3x��v��s���99��f�l��q+]�m��D��F���(�`�����,-�2]4��OgQ��)���g���${�[	@�<t�n)�uL+���������^�&������7���ZRrZgN��n4g�>��t!����Y������YqvY�������z�*�(Y�u���y�kv�%�i����f�i��4��OA����G���I��e���^����`�^���8�f[�z�e�p�*%X��:��w�����
���8&�W�1�}�`)\�$��>{�(������Y=��N�������1��yEd0@�|���_TV�����n��
��}u�:92�S�������w�M��Z����m�L���z��sJx��-�����yy���Z"e����K�J!�,O(���P��*���j�Q��)6t����J�	�K���o[/``�`�w5�
e~����Ib5����M_�^\���G�l�@��_�3/��g�K(h�!��H�]@$��W���RiVkAo� ����<"�����m��O��&QQ*tk2��b�8�m�@���<�n�����ux�����*�\`!�M�����Y�),��ME�+�9�2k�<�	��W0f
@�3�Y�w��������k{B�h(�2����m�U>C���#XE(-���b X��e$�#���v.��[����Yp]�I���N�7���j�j��k��S*�v�H���>5)lH=�5����v�_�P���(��	�t���.��v:�`<Pa1���"��0@�:%�b��4yq4��:��c
^�7��:��mt����G\�������>�%��u^�@��P����\�	�I��hF�X��RH=���Er�qQ������sw9s����[�$Qe7�%�I
"�~��H���L2�.Fa��8T6yL����N�iI�b���I����+KS�Q�X5����_�]b Ta�8�U2���B���K�0�a�1Q�x1�P�E/tVc�?o6�v�LU���p�y�iC�,Y���q��p�Q_++�;C�n��yBI
��y�N�RE+�`S�!�����'3���/����X4JX�jV�mU_�Id���
b��������<��c�Fy_m6{�?���6B����������PI��>;9Q��A���8<;=����������I�JK%�������'��#!)�)4r����z�-\t���W��
�	������:�o�O����sl�����������9��/go���'8������������������E�Mk������!tzqt|������g���X�}�y��9~���9l��7oZX@u~����l���|�Em�m��m������M�9��T�x,�K>h2IT/�	���rD_`��/p�M�[:��?0r,�0X�	��a���dt.RI�7��Go�v��������<fj~����+���},��~�=@�wF�}�e������	H�K�(^f�J�2�M������}Vc�8�9�,���ApJ'�����)�y3JF���%�p��'K��H:Y�2���_�_��Vp�g(R��5Bd4Zy���Zyu�<�:~��M������+�w��{�� ���������"*LX�����5���&H=��e� �����C;�r�V�d�������o�;e�b�q�	�H���d_[���������
U����4�Tnb�`����k&����%>w7&������)0��{��������
�C��[��+�$��Z+���7n57����~S?���8\��������?�&�Bb�LA;=��Y,z<��������FY���a&$	�<���������o]�������eZ���p.|#��t��f7��X2�BC�(�������{C�oK�����C���,!�`}�1d�2g��;���3KR|y��;*���ClV`{f����*#h-�������{"���ho
��a4>����Ob<�g��jz���vK��nU=*?R�4j��F�h�����9�S�
�}����'(��qlA�f4ol~� ���a�0�W�T��x�r&�:E��d�?j��X~��S�����q��hXg�p>��^��%J�;p�_�N�E��o[��b8��$T������qH2��o�D�����}��!�(��t��7��@K������ �?�~|n���h��OT��$A�4�2����uE�k����]3k���-J����~�r(������6^�<������\=�k�~�T*�����/�r����S�m��'��x~?���HT*e���_�&|��=�E�Jv���u>����Fq
,5�abJ���[�3Nu���f��%%����o[		�X����a�<�{}}����-!�����+nV`�t�������Z��T��W�Rfbc�S7�[��RI�)�x:]���|<2Ba)7�1f�y�	����A�94:�F��H�f7d_�3bez�F����9�qR4	�<��y|PL.�w�.iW[%�01��5JJ��t:����<��#�����`�K�`����k���T�I�f�nM~l�=�s�������kJ�D�t^%��{v���:�-!��KF���,�����e���k|$����;�T��{�
��kNyf���[;_:M��������!����	
FO@$��:�������	�'�����6�p^����.g;r��=������K�?!�y���QPM��w���n���	N<@QU1�R��}f��w�{�1��d+���Q|����C0%TL���qr�Z��Q��$�Ecs6���q�.���k�H�����V�=���E�[�;�������oD��[[������+�F�k��y���em�j�Z�����#u��������J���9F.�K8�)��3�����),_w����O���"�����.���k�?�Z���x���tB�C�A@����;�%��7�>�q���(q���ZK�s�&U&#
Z���^
������d��� wD���t��I���)}��e�q��GuE���y���+��g�-�O�']�z��+)�4B�,g ���K�k����@x�7�y�K:��3cL\K��x����V�kH�t��N!q��a�������f+�Dys��@������1M�.bJ*T�6�>����F���0�.-��}���������~�/;�'p�Q"s&��4�C'd�'l�������m������V���h[���.!'��Um�����*��q�}Lf�pC�!�����I�<��/�;5;��dF�
D\m6w���mg��d�m��)��Pb��������D�d!��$R��EN;\U���OtV%@�u�����*����Nr���\��^��T�yA��[���~���p�B/��Zg���7^�&9������xh>������</M�r�bkfB�8���|h���a�����!��f���b�[���)!�^�g'�^�8f%�f(p�"q����t/����rr��;���I���9~��q�"&>�+r�����N�'���'H�a4��1)�t1VJ�<I�����%sVZn*�N��D�}i����LsunL_�[�� �I��d�����([*)Q!�Q4���
P��������r�
��!#�=��x^(�����m�U���������B����d��T����e%����f�J�Q��vv+��j-�iTWS)�Fy�U�@�G����[x�&M��L�]���x�s4��"��� ����_j�s��?t��)�0ba>�����G��S�6�-+�PH�0e[8W���<Y����yJfJa.G��8U�3�����:���
�U��!u��: � nu�E7s<����p0���e4�@1[+�`�v�[�'�UO	*O�����R#���#�U���4��v��b�Q�G=@�J>G9�~��&��<��Q�x�9Ip�q�U"�c������>2�a����,yN���U*���Z�1�,C�5gv�z�D�:'����Ym�W�"/������4�K�M�:�)H%o�O/��]��>�.]D�H�y`#J�X�'6�G�'�}d��u_
m�k��{{a���n/����g	j�����++�v���|��v�&��p'����v+��}2�1���S�����qs��b�@\��e�>d�=�a&g8nU�_��dR�I������8�I�^���� �O;�d���r����Y��p��
t���ie��@#��/�d�!��[��I,����Wp�\����'(�'7!��Jf(wnN���N�V��p�y�f�����a�/vA�5
�@v���������7�ef
��h������S
3I�n!��q�������k����n=_���z��o&
8���.������������5�E-�6�����d��Ob��S�'�l�n[�Y��N#Gv��x�;�>��x������=6��]C�)��y����\N�6��+j-!�@���5!�=�-tL�"L��t
��m�~c��D�'�R��q��70���7����;�����!��p���oM��T�^��0fH��_& �5�����������~:��	?����� km��v�F��NN3Y� � �4������7y�e������flx�_ ����������[b7Z�2>��lty�i�s
H�x����Zw��Y����Q���V��-�0����d�� �X��1����������ux�n�mW?M�n����V}�%���T�����&Km�r7�R$/>Y�$��b!KN��TO��O������o���i��,���l,��	�����f�u$U��x<�G ��+<�
R7�"�+"{W����=�1q6���uj �v@��$��K�}$w�J����4V��!�h��-P���	{��D	S�]��wuI��������}��s���]��>��['��m����/�n���!�\��57�a�M�>.mfg�f�� /K���9��s")�ur��v
���^�l����V3���U��	b"w���	����$<�-81��@~�C������.���[��s���^���V�"n������U��'����"n�_s�3����+�T��DR9��_�T�Q�4-=] ���Yu?�_��6j�o56�r�B�C�[C+��W.,��$�]����}����HaA.�`
O=dE��n���c���>A�0�ld�+���f-�f����A��W�����$��� ~��t�����t0�_����a��v�Z���|F�r]T�����&�b�N)���h��d�)����p�%�g�I	l��+��^������9�s{�c�s������gb����g�������>�P.��+�Xoe��,A}�R�����f�x����E�K�Z#�H� �[8�4�R�������)}��J�������3��(�y|}D(���v���bT1�����f/��,����7����B9�}���L�= b��c�^��Y����N�?G��_��F�&�������c��������fD#Q�kN�N�Q��b����[��G��0�RJ��T�U���E������y����FT A�{;�J��m��|���5�c����l��@#Y�W���E���S=�E�9R�|��D����h�}��'�������������AS/y����t�A-!P��J�	?����i��^��Tvz;�Z���������N�m>}�k�,��K}=�zq�������}=�����*����%`8E��%���F��W����B3DU
���47�Y���9Z�T[�;n)�����4r���\k���]V�3K�8U��^�lh��k�E�V�o2���!~o�-�[z[��8��\.��-�!bw������14�m�-b���@	Z���#�z���[���F�-����h�����&U����l����}k
����TFPK^^���-����3�*�g]��D[������L�~���J��^����z�+�3��Ct�(������tUW�!�D5�������o�H��6�[5BgnY�e���3�Q�eo�U8��l���,����kkj]�}�FFa�\���*�[��[[�����)��:=�`�LP� D�X�U��6{&;(��w�J��q���p^��!"�2�F7���v7J����&YV�7X�@>&�bu|�i�/�� �,��D�=�}�X����IWe4=�nfR�0]Np��������f]cni����������h���tg��	g��A�y�Jx������lh���N/����E7���~�~Mf�%=�����,�A��������7�!���%M���H�X����Q=��
�_o�d�	
����b�D�6~s-����!E��K?K����a�#+���TE�������,���hT'��4Nl�R�v����s��U�z�(�vu#1C�	nY6��Q������-4�����RVJw���^�f��h�#$��`:�`b��.�;��^�2|��=����&�	��7�U�L�����c����}��#tZ��>K9�����nq{N^Z��#��iJ�l������=&`4�K�Ya���/@��a����FQ�Ha�Qj��58/�%ypL(�VlY�"��{	4Ej@�����5��H9�d�����	��r��ue����\��bV�li�pB�P6�^����=��I��t!yc�iJ�eM7S�8>y���V+���~kUp��d���V�$��\�����cmc����y���(5��L�67
*H�?��U�^T�?2�y�������0��A%Y`��<��zt�2� ������IZ8 6��A�a�=':o�|x=���J���ac�Wx�kF'�E|�!�<�Z��|h�������oiKI�+OK.��l�������!��~���HK���S��*���(/�s���H��!�g4�?�sK��4"���W���W�A�Xo�������)�������|,�M��6X�X����g��$<��6|I��������43^[/h���V�v��~�Sl��7�#S�aF���N_/F��\�������;xxr7���������"~����_s�e�[��2e*����f�7AO���`]��=U�E?�X��9�R�[�g�;�����<bK�,ff�R��g`�e.��{x��F����ALG�o�l�W)�x�;�h{������#�fy#Z��������#	Z�a�����U+K�����g�{��B,[��aV��Egy���j���
����k_��H��<����}���s�=:�8�N�����JU�����{de2�gvLf���1Q��E[��@��c1���t��#�2��G�t��](���
�hD�x���mW�C�R�Cd�4o���}�$�c���Kk��A�|z�(�`��?�P��y�S,�f}i<{���]�p3�v�����������O�����xXLPKD����$�D&lBs�M^j��Nx_G���(�-N�#��p&@��@��>��u24����/x�6���K����xN�R��|�Y��p��p��tj��dZbB�<3����L��.� ���������]�i���#A�?�V*au����,��������.}��=��=�C��3��+�����9�����}�������`���b!��yX��n��O���������h���N0���>< Mq����*W�d����m0����� ��qX�����2��6�o��Q��]����x��3p��D$H$�`��[��	���G���R?��m���<���L�����P�����i	�?�9�Q�x:�z�,����"SJ�`�I�X��H4UB��mh�u��;j�����9���h����H�RGQsZ$1����S��R�1$)uaa��Za�Q��f��"u9a�����Q���Go��w�A��B��q4�YH�OAP���A��������$v�DJ"�K�������A/!��Q�74hw	@m4�WF��1�ol��������;�����k�f�{$u,Z5���X�v�2�P�Ca3v�<+�sK���v�.�r}9�1������M��	������oC;���nlO�J�G���B����o4���2�0�59.=m�d|{iL2#V;�wv(��e��R�*o<>���R���Q�p��@��������^�6���J��������4����"�����V�0��D�����}�m�>��iG$s�����w�T���5c��y7��&��
�@������U$V�����h.#`��`��r��d���`jSle�o.swK�eF2��8�-���ZLY2-����Z�!L�
U�E�p��
]�o�w��*�Mc�; 3YO?zf
�q=�����!~���T�P��W����B�y.���	�F��-�M�B�����?���E�s^h�c��h�W+���Y��5s�[5E����B�^�q
�r��Le�7���a���S�$��?���o��oO�~z��i��_K�����rL@�A��X�72���$ ��wr��*qI[w�a.�%�`2}�;|�4�#��=�$�R�**�����2��#������������j��Ia��d��a���4WV�,���t�6E��G�OB��jdPg�H��y`�����/��uU�B7�������z����+����z����������[����;Dv�sw��_��w��	,W�A6K�P�4h���bSn��8	�v�G~��l�.�_�(�Jf�i0�`1��}�M���^�AK,�d���^�Q�+`3s,��%s���\]���(ne*W�����n����xq=��MM�E�?>%�C��cTD�"	g��H��]
�VZG��2*�f��Q�	�����)��J-Tru@`;���b��>n��\]=�)W �o���$��6"�����X���A���
I,G:�&�*+e���q.���I����T��(���O�$�mK��������uid�_��=��3��3�����4^#+	��d>���^-��\f��,��1���m9�2�Y6]�W��"�� Ep� �3���1��L[��!��vw����69wU�}��w�~�>j���?��37z)�|��%AJaD������3��JQ�GD�*}���u�o�����-���U�+�lXe
��
�A83a����R��������0'��+�A��6qa{<2qyH�������+R+
����P�VAZH���cJn�D�����!���J��+\����#��zm�R�{��]_PIO�"^A�"�{�m��!~��$�"�8�gt�l��m��2���b+����Q.?�q���QUZ_����
t>�rj�T���{�p�Ri4w��N������}�8����c����t1A38�`��c������Ptf,;������`J$������V��S82b!!��>���e2]�A\' ����S����^f���h������w3���B9*0������������;v���>���Dn��*M�&�������!���yn�+X��En;����|t����*p�=��V������F�R���f���{�A�n��:�@����y��0������l$�A���,DB2��BP��8��rV��D1���[W_s�������L����U�u�G:���
�]�[oM&��[t�,Q�Ml��W����Ojp��'e%0�Z1����8�������A�e���j�\�X��n���c>$�X���4D������m!�kG�+�O��n���������Y���p�J�;�!��JE_
noomq�-t��u!�C�.���(#����z�
���I�|�s�^I��V(��O�����.��o�g�oo�]��T�6�]%��fD���0�l�6/K+�s�d�����;���9]��t�����N���_ ���������J��v���3Fv�h^Bi{2��a'f�9�i����tc�C����3L���1o��dG���)�����o��E��������N�nh��h��#r
�����"��+��,]����9���ko��������:{�m�8�{�$-���"��k��s��H�0�EEt0�j�[�*��'O;�VWX��������IL�v��h�����,qvx\k6����#��:CQ�q�_l9'jJNa���(���}�^R��PS�s"r{����61���	t���L���]VZ&L������V���n���'�'���
��_P�{�W�;�*������z��i��V����S)
\�A�(we�_�n�1GZ���nq��wr[�P��)�a���Q�q0�1Ht�	�#0�����|���`,!��7<7���N����G���;��?H-7�5���B�e�3���C�*5�L���������4c�8��1*"�O8l��Ll��T���OZ�����p��5��p���zs�m�
f����r��{5��������?��v�n=	i�%�����������Vy�ey-m�9�x�F����j8���J���[%�j����A�$5��h|;������YU��O�7�%�/�}��$l��X����A_a�������h6�����=��X���0"���l�������A!��
������A���n��WY��`����\k^����>O���NH��u:�J	�F7�g�7*����=m#��a�����KWq�p����&�������?4�H�l���.��_�|^o�5�J���$��uR;�8����K�tD�����#����@&j$W��J��S�S'�W����pw�Y���`�7)g/E�%,R��p��<��P�~�J��r
�Z?�.���<���$"�ViJr>�9��XQ����TC��u���B�1��c,X)(!���Lh�D%��/�SW�K�W��5������������C��<,&u����Bj
�dV��z�����b!@�C)����2C�"��2E����2���4\Z��_V�Fz�WVc�������#�5��{~��=2;����Ie2�6�G�/I��S(�&�`Az��`�p�a��N�"��-z�=�k���q*���}�( ��@�������R^oT�O}�u�XbB������.���7XW���(F��5�������^U�ah��q�f�)���A�:EF����n�H��"0L��qpu�8�s�4�z�yB1������dhRh*��/L�;~W%�����Px��7^�n7��{�H�n1��u*�>�_]8K���h��r~W���O�>�,=���=�U~�rj8��\�����.m�Z��/�F��jkL�����"k�Y&#�M^>hv;��49�i����%��c�o����zxr���#1WM�S��{'�n�
���V*A�i�e������j�{2��hp���S��TgY-����<I���B,T����w�T���]��6���u^�h���^W�gG����?2�}%��-�	#��,�v���S�T��;������D�Zx��wc_|7�s5����Z?N�s���^�?E��<�#�rF^��e�5]\����s�{�#f���-�:�Y�����t {�~8�K�D�A��9n���!����pB�L�q#!��������Lb$��<� <��I��4��|!�B�Fb����02i�Y��=���h�.�g?sP)2I����
I�b���o
S;4�V������^
�{����n��L�O��d	������d[�C���gG���E����������:�����~�h�L��l�4�keE
�7�N[M?������9��z���������^i`������"mc�hH5Nj��H1���q�$�!�9����iDVg���s�,-�d��!����.Wm^��[���h\Q��g������r��$j�K����%2��d|���6ImR�=};�n�g��~>9���"Q+��T��8�[���K�$���$���-8���O;$!��4q�/K���SL��N��'a2�u�7[�Qk�qu=}��N7���]ZK��m�����D��A���8$kY��`9G2
�c���������=����GiI� x���P@�����
�~��<�sZI��-h����x���0�":���y�/�HiK)9�9m���61b4��:BZu�B������K/6�Ap$�)��W��=~��VG����I���u>�SL��v��lsw��\d����}�v��X��Z-<����e���B��"��p�J��O��+h������;"�����/Yp0I�5������a���-�:�B��d��I��0�'��"BA�}��o5A�e�Q�t��~�&��o���W��O�g�zpP��f@s�|7Y�en���@�f$���D!�C�)�#	�i���dY�����3���TX�>��1YP ����#�4O��..3��5M�LB�����$T��S���&��[�^=��W�`]���kn"�o�6
���b�Tr� ��UN����+���y����`��W����je�8� x��2a�Cc�9j��xM����w,�W��9J��5��m���G��!_ad���Mv�{��]�{������4?�y��	���������������R���kP����[�!N����0�e������s	C�h�Ug��`&������S�����~�r��_����eB�nZ
t�rj���
?�3������.�/K��eK�W9!�
���f�iI������~w��.�����A�n��U������i�����!��~f�8�O�r�x���air�f/�}�;v�{�>oW�[;A�>>�@�r:E(<������4��7oc����<
'���a/L��v��j��*V�g��vIfD�����AO��L>�����������z�������B@h���Wx�~SP��8F�[�0O��a���"�i�u�9@��j���P�,��]�+���
�u^*JE�����������g:[�~�������M���)��6�6�)��Z����\�'
/d��������5�\�+��s��?����"6��4Fm$�7,�g����;7B���h�(M�������m<0
��Z7�Y��w��+����l�����N?B�j�aZ]s��&����]��qb��;w�
?h�Jll���4��L����+�tE7�B��f$A(�8Y:~�X�tu��2�(6��������{i���2�d���#+���������J��J"�7��~�?'@ZC|�Y����9
���'�}V�x����,���j2�YE���/��|����|\��.������Xp��e=�/_1[3��<T�~\�.�M2�9�@�B(���b�K6&m{�}]���'@f�4�|%x��7�i��E!,�)��7��������tj
���~�:c�
wg�0d���B��l�s�Ec��n�tk;�������5��y�x�u��2Tx�&34�`w��(F���1� X�5�j��h��zJ0�3>7�����s����_�:�o���C\�vx|�F�	Y�Aymf�6�]��vN�i�I&�(`jZO�z<��� ����$���/E)���0V;�����ISWsp��=nd-WV������~u������{���=��|0���<��;sV��i�7�+�����]ht�������-@����Aewy�?���.����m��a�!*���YzwF��`����&���*���S~�>���w��z���~��=���Q~o�z����w���Cm}��������������������f2�����I���t��'����^�`�i��t�����`��	��p��p�=���6��26�L�����o�S��^��K>+�p�j�8�g�L�jW�7�#h��=�;x��T�oAs����>��9������_#if.e���v�H�I����BtG:���AS�����p�Y������
�:	>�����yUI%�����gP%3��J�j}c~-�M�����]4����W7w0�ln�Z����{z�3n)��L�������n���z��k��Oi�O�w��������������1��W�&�
�����dFs�E�NuS�_m�����}����>�]���/5;��mo��J�i����~�Ka���.(@.B�m����* <�X�����������&$�G0	��X�b3�T}��/����/W��q~�����_|u,���o���C�����\&�� �`0�(�D�TV�������"��e�%�a��z��
_M��A$��/����U��vq�r<|^l�WX�����R/pA'D��� �80�AV^�I
2�/6�f���B?�NK�Tt�r���#s�

�l��49pN�A4�>Rn%li|}/�Lj(�.�sqa�`����	
���
g�����.����ot��8M8�v�3�W�����$��g��2�3�^8�8m��Kl�E^`0*[��#i��J+u�`�������d���:���.M�+��P2�+��
��fb��O�	�C�����!.�}|��Ug;M����7��!l*�����V}������g�C�F1G��N��i)��;��s�������G����
j)��0�_y��EA��&������3wc]V'R�p+�Qf �+9!�6�8�sAc����!T>������7"��0T>M� ����l�0�	op�p��>M*s��.p4����m��������L?%;�f?������4��x�:Xa:d�t�h+�QT���T�F�����T�:m�pm`t���7#��J(X|2 Y�G_3
	���G�CFh'��VTi0�F�����&��VN���������QX��Z�h�^����@�lD��)J+:�Ap�`"�S�t�x�8�=�|b&�=�L������^�wh��-\�����9��[�����H�i.����4��>���!,��H��=�0@�
��������ad�Hqt�.�T�#����N�P@�Q'#5B��������� ��L��wa�A�����4Z���V>;��~x:����G�'u6�����.�8���"	��e���3p.M��5@�"� �����eE�s��&�qg4����Ss�+��&&�tz���l����ej�00�O���v~x���>�Uwt3d���c@���1$�0��~��-`�OAU�z�U���an������pMa����5��DvX�	&�h���ZC�x�F�1I�z�,��	�X�\�^�W��u���������BwU�**��^�.�R4�5"�M0vs��&�����K�&��*`6'FZ�8�73;��:0Z8����(_jR7�3�h�S�z�8�Q���X���PFC���jL��4�@�) ^�;z�q�����������*K��	�J�O���.��o�)�F��=�������,���*e�+w�3Y��Z�C�K���K�0��c:1I��KB	`�"W������
��!���(?�DpNz����vy�}�o���GC��S�-�T0�g|g+Aya���
��9gI�i����?�
2<m��!2p��a<t����	����N3D=��Iy���"����P������^������4���Zz�S�6���-���a��yj�f��������1����q��D51�q�T���)�O��WH�A��HS��g��R��tcPw��Eg!�#��D 
�#�N������U��Vt�;�w��O6���VDy��p�����|�mv�����1���}��0�?H�!�{�����LK^�f��=�&&��>J�v�����G������y�
#�#n��H�q���d!�0
�V���3��<��ba�9�a0yF���OG��HH��jiu6����tk��������TKx�j�^<�>�$�.o�0�������3A���xz��T$���g�2�U ��5�����H-J4�gc���5)��P�
(B�6�G8B���v��[u���@��[8k�>I�L�����2{s�b��0��-���gw'!�X�7��*6k�=�w�BlEY�E%�#��Z�5QU�)����i������`�[��&�O��X��������+lk�G=U`='���:���:{b�d[>!��������=�g��^�h����$���h��o����\kVW"pP���M���!_�}�m��a�����m�*���-6":Ix��O�����J$���H�us$���?(�������p0���F�����^5�������9��7��Wk���������5%�`�U_
���x�D�U:k����I'�`NJ���./C�)� V�E��[�b]�`����}�),��a��;�����������
Q�����d!��`��)<�������
F��62���?z�K
�d��9�/y�'���q��u���G�8��R������G��A����^Vd
1>r���B�UpXyD���8>����7�$�M��N��NB_S����$N5Vq��=�X��x�����E�[9=,m�jM�L*4lx/��4�L��d���<?4�&����k&4jY������O�K�t�Ag����v}8�������B�@`�PK��;��o�A��e��KbQ�?����-GN���`�����9����X�6g:�0����Sm�]�r�USAg��t���s���l�+�������A:���`#��G��Jv���b@6��>j\4�K
(���w���g��r2���-r3��za�Bq�p���)X,���^�����=�[��J/�~>�N�p��,|*�)�(%�.�^C�?K���EN��h��-Y��2��2���	����9�tn�^�����<��v����q���q]�!:IUF�v�~�u�����F���1�&�p����7��D4��hy�kgo�a�R���~`�>�d���g4�>�
�t���k������w��Or�8-���	#1p��`y���lr�Kw�}i���	WS���~����������>KBo��k�25/
i�A!���v�Db��n_"�h'���so^�D�b�}��.�`�W?���9�dW�����������J5�����vz�+�����>��+9��QE�k.��}�#Hz�:n���������l:�l��(�����o���W��]��\�'� �&��|�-c%��O�
r�m$� �'��/�6w��A�!3_RX����-����c[)�����+�xT�������~�j4��h�hQ��Qw8��u�i�L��k�f�K��60H)0"e���^��8
�p��y�=lp��Y;�Y�sz���1F�����plzZ+-�����U���O�l�0���S���4)9����!�A��-y�6R)������>�A��-;~Hj�+�)l
���2����]��8 !���[�!#�$�Av��F����-��h��o|=����7	�T<�O5Nkl�'���Fk:j9��NJ4�5��*�(�g�a�p�`!CZ�	������RM'��������B����w��E�G��)+��v��T�[%���0�
�l��"eg�v����)�����W��r��D	F�c�/��[�*��^�Pc�xJ��/�8x�Hm^��J�uKz�LN��|���k�`0q�-iF���)�����N�3�RnX��t��@R���
[)�-=�,�Y�B����=^)���������c��	�X���	���g=`�O9�D�9^��TpA��:������ �����L6���"������P�=�AEQ.�]�H��������2L?�/���3�5���?�=�4�Fis0jE�
�g g�Q`oP~��O�$�JtN(�+G�������W�~��yy������y��S�:r��&��x�#�Gj�/��)H�g�^��E%���(�	��3}@b��U8�W<}>�����x��`�&\e�7�*Q�h[��!�di��`�P?no�$��[ �(WB��0��(�1S���<����0?N*FA$����3��>a���{/����c�.�)X�'w{R��%1]��f�Y,�T���Uo'�����{�T��q�Q�m0��vV���9�i����;����G���{����Tf

|5���9�Mf���P�������.�+��J�lg�E{�+/��X��;�Kj��]4�yT��,��\���d�9~�����:��K�x�s���,�vwc>������i2E�/c1��	����>
1�{7)l
,��u��-��E�x�TW�a����d*�v�n�6��"�l���%O(u�ax��^f44f
��	��b�
�x5����u��#2)����oj��/�������A�jai��u�G����a��XAtb�h6��%O���m��`UN�L8�j8S8�w<�Q.PeZ3�RY���
mM�������F�
�z�T	���#�M�Z���4�2H:a�\�O�=C.X�B��}F��c�)��j&����Y���^��]�;���l�
J`��Cy���������&g�b����G�.��Q�.�]����M
)b��;�mx���O�U-9�7�����,V����S�Cx��8�&�	:��4�lLR�E�[z�&=��u�2�M&��r�J��������I��������|��X=z��g��V�A��c�_fR��$�0z�TI"��o�3��<������lFy��Sl^;�������t�)�&K6�5����f����Y��:�������Sl{�sJ6�����&�G��T`k��ot�����a������EN��I��9��g����F�\�W(��2�������!K}�CNL���>T	�)��u�oe��C�m���j�RRcj�o������Y����e����[���d&	�C�!���B_�|I���T;R91�I2jG�B6�\%�#c�)�
"[�	z���7���lM����)�����EL��g���O��
FZ�������7r�)����Q��.�jvAl0��s	M��w��m��cA�������H?Q1G	��	j����9��t��`��.�H"�ci����R&9�(��'�4�h39u���L�����66�vA�Lkx��0��.?@�^�o�[���H��Z:�0(mK���Q~�I�~N�����
'�Q�_�fL:�����|�%,q�8�!SN�FV	N���d�jN������x�V��"}"�
���F������0���#�ul���+��]Uo�����w��%{*��xsoE��F�8��1o��O�&��"i$)%���$#��e��MD���*�����&����Z�C�X�z7��sH7v��5\��V�&�{��V�N��Q<Ua�u0����<�q����cy�E�[�������9\B����
��D��/'�'*R���]�~�&DKKQ����a��E�o��KK�~�1*����|��5)Y
���Q
�D�ae�x���)��%�_�_�[w'#�%��3t���36P2Z_�2|r�"e�m�o:���t��y8�����w�d��~X��a?�\����i��[����YP��RU��K�h!2	���+�0e�����
��y������0p�CBK���L|�7���o�����;���6{��0\D���C4V���P�$�L�s�\��~����F�"�bY�����x�E�X�;
\Q��7�(Q�X��]^��j�����i��uvz�h�P�MC)�L�b����{����u��z
PU��y�15�C������\�JXTx0s���iHMR��E������a�`t���|#�!���	�\\�.\+-�0�;��d�\��Z�x����"��HT���0\2�pI�`{
�'�I�a��V�+�LS�Sd�6;*�W��7�D|I3tzDx������L-����R�+���G�0�w����n�Qw/�e>����2Ys��K�)T,��P����?y�$KB�M�������%��uJ���z����I�E���DS4��b}��
@��)��B@�d���[=���������5��x��0�,���UVo%6�XU[���v�����x>A���f��@��T�S�?���T�]N���I	��Y|��:8g�J%T���5�7oo)Y ��U��f
�@0�O���L��.�K�f�?�s�}	;�Q�%� �>v�d1��q��^�1�p�K��� ���Z��������%B�QvTqC��vAr7�����B$m
��I�fc�$g�A	���F������q��@g3*xK�
2�t�������qyR;�=�P��HCx��+KV��	�[���5�C"�.�8'\��PN�C�g�P�&�r����U�R{���5���3*�f����+���F���KHvf�����������<����$�i�m�#EZ3G������<��t��G�q���S���ov�Nm(��(<v=Wp��s�~�i�&�:?���Y�&���l��oN��dU�!)%Q����IJ
��J=�[��[��P�f�����0v�[�,r�5s#7�vdP
!��c����������+�Gs��MU����uv?�(�h���PfNe��h��"�� ���a;7P�jHi	�
'&��l	�:�)��7d����~'�`�~#@�o��FP�����9)�?�
���W%��_�:��X�E|1e��o<������&�Nk'��y���s���-v��~#��5�i������q��kJ[�����A��Hy�g��]���^��'L)�qy����,U����OS�<W�lv�:���&{nL:��/[����q������w�%E_
����,Z%Uv������p�}������;�O
�O���}��,2 A�7Ti$��Z���w��F��9�o7
T�T6���w	W��)\���(��RA�������:8��q\?�7���j�f��r��R����������6���eo���q~�3�����AH3J~@�X�����gn9�~64-���]���}���AQ,�p�A�)���K`�{0v��w�Y���8���O�[���(���C��|�9�G�Pc�~�\���G���x�09����?�R�����������S\���+!W��"!����#jK�2y��������D��:9;zw\o��@� F"�4
���� -(�Sk1��0bA�oU��	�� R��i3���i���d�IGB��C-�i
8��~�� ��>�$���h<#�(��`���]���Z������=��_�m�S^��'x��s��U��un���^����<�������Og �Xf �o	���"���9���Z$�zp�<�H�������{����<�}OA�	U���t�%�LU��

?��7=���*��^�5]��A��c�Ic/��]O1���i�(�8�},i���	D��R��Ygj�y;�P������it��vR�s(_M�~�ou�{rF��qjh�h/9��q�8�![�������}� ��e��Zs��;PW��.-RTs�b(���+��������d���HMB��hl�������L����iXZ��H��?���r���"�b��T�7���=��~D����������(�X�Y��e7g��n�
9P�no��� �p��z.����E�����%J�CS�ip�������V�VB��Gu}�%'"��^�� ��I� o��&x&P�,�����#���k��=��TJG����d{�+
��D��r�vS�M�*
T��#i�Q����
�k@������*CJtH�uDe7^����*t/k�5�MY"��OV�R�,�jhT��D&�/S;��a�&CS���tg0���N8�ie���\�S(�.hW�Wc{�0^��b��#�'+��j�@2���������j�|zY��E�V�
�W� }=X���f�;�H�,���*Xp����	��"_��|Yx���tY=����m.��k����y�����E~��gLK�r���Yg/�A�B%������6p�?!����L�`�v`p��m����V���E����C��`t&����=x��*�RI+�\LM��lE�eo�f���jf���������	�'M!e�9�GTU�:rP
kS��������b�u������V8�=�@l�_�,�:���x���R��#y�q7�/_�y������,�|����5n)��/�:u��{���K�I�_�)0��cz�cq����%��T����`N�P��Z)�YJ��D�����a�~�]`['��f����k�6.)�
�i�	���]/��c^Z��Am�i4!�d���A�4|���]���l��{�8m�n��.~n�C�:�qbu��"�"�+
��M��'��[KJ8GJ�>�,��&.�8�cN�B����[�n��Y�c"����6���6m2$�$�`���;>�:L��0h����]�G���\�-��JIVF�;#��Q�6c��������%����d�AS��M�v����:=�l5����]\��4!���V��c�������fCY��L(�?��@�G7�����d����Z��&`���As]][����a���
����<�hU��qwN�2R���l55�b'��}�|��',��i�M�R�'{�[��P^�\RG\�d�a������p���km��f�oQ������
�?!9�z����
���d��S�
������1RYY�����@�f��0���"u�7G���q���	�����DC�.��#���I7�~tn�����,��]�	��!g;Wjo��x	���+�2�=�/,�O?��L�m��:@��	R����&���R����!��c?�gjC:��z��yB�xb�pZ����P��q��|hRJ���z2��}
�j�3���-K��A������2�)
��&��v0��+20.��W
���a<�2��,%����1$����:���Zf,���$^�B;o�;��H�9w�h^��0����I`�T�h!$I������c��3�v���5I!��[���K�����0	
���g�F�^Z�I�O����34��%�K�Zl�[g���l�����@�i3Y��[K;���A��0"��[�E������������k�o��������M��\0T�+G4����X
`�*
��hct�����
��Y��OS���!"�M{|��S�P��M��B�,&�7�~�S���G�-����Gv���&-������!X��f�I�C��vf�	���N���v���
���ske�(�U��23�r=A'��=y���>�0I)���qEG���;Ih$��9�u1��9h�S����
�r��^	^Y2����N>E���v��]L��$#���!����:���(2c�A��H��x�x��������S�ah���n��%�kz��H�H��]��+:V��+2RN�w����K��q��������S��%d�����p(��(A&3��
�z�.R�M���<�����
�H\�b���L���'���u�G����V�j�u�`"���g8d:�����6���J
s<i�s6�]�}^B�)�����Q�1�|T�I�(A���	&�[�R���>��
��bf�U�-w�n4'��9MAr2�Q<E����[5���� VJ��kL�#O�!���Q������,��&��#��u���C
htNn�������E��Pu4���O'���b�f&,I��3yvO�C�/i�\U)�S��}�kVle��%f&������r"������.Y�����I{��K�Y����os^����u��sL�A-��s��?�ay>D:��L`��+����3Bx6&���,m�#������]��N���q8�08�?�u�8L�.(�
�����0��X;��\|�u������w��
�P[��V� a��Y#��A�el_��\v`�|�q4��-��Wa��AG�sJ��B����zM	�����"V��~���R�����F��nn�z�?�xJ7HU��u�����&G�3�����3K���5�?�<^�L��#
��Z�f�Z9�����G#:PO�	Q��������������?���T�Q������_[��N)BL��]�j@�p�Ts�^���Em"�,���{�m�]�V���A�y�+�6�D}V��������������u�F%~��LMPh�04�����[��ys��o�X�/'�
������D���+�����7)�EBW�k��}M�o����;�������i������`�Ab�Z>��.��L�����+���"�?�g��F}�n���"�[Lo@<�i���*=�D`E������yc�^��MC�Ns�.���+p���`uj���+n�X��������?��D1�0�6����Pg������1c����eq��~�4�)8g�C��A��4'����������"��D�	������rZ"��gM��&���e���5!�D�T�f���a����|����yv�9@��qyQa�Sv��o�U:hc�KZ���k-F?b�AT���������%W�z����'�:�,�.����/�N���h�.��Gs����H���zi�ICJoh	��S��4:�#��>9|O���������O2G6y����"h����2 ����S��p�C������
��I���q�(Y�Lr� EaN8����h	���T�&���h�<�f�e=�V"�������Q{��U��MsU�(fQ3C�s��`B'��q����z��%V4eo
��c��2t�Z<im�;C�oM��c��$T�qx���O��H=���h������q���k�A"���?��<j3����8xC�V�3��,��$���;"L
�x2RAAr?�V��'�]��r��Ko��/�h6����d�Zf�F�p���4���#��4#�t����UTsdUe��w����`���_�m�Uo�Ge�3���h��.����b��nV�h��������	���&��K��45�w������&���U��n���_[�����&Z�#��.��M8s	�%�����
[0a��y�|��S^!�b0�d��z�����%;V�<��;�#^��J
��K�S��B��7�'����,�O��S�������K���
�l�om�r�;,��������";$z��
Y�m����":,�o��7Db1�k����0�"/������d��v(�D�dZw�q���p4�����c\��76�Z�@n��H����+�V�	��=W��[���3�!	����jO.�d��b���0��~TH�,��bP������������
x�dJ�?��ZU�&���5_���������9�����P���$����,����D��V�$����!gL8$�=�=�Q������x���!�FK�o���N��/Y���G��d���\c���4�L$�hra�k���4L��\�Itn��E���n�3kH�����R�QeY*������8?3q66�[t"Y��� �0B.H�c3�%.n#�����Q^8I�$a�l����1�$�*;p'�tVr�� �R�����	����XD��������	IY���n�nV���$�3;����I�)��
���8��^:t}�s�3	�����$���%��5��:���_Zz����K���
��G�3�
FRI�l��	>qZ��.�M��!5�=k��<�m���Kf7��;|���Px5��eo�=��'�!&2��"�1
j�j+���Y�Vk��������t������7E�H���#s���4��T��?97��X�g��w���H�������X������Y��* +��� q ���y��
���H_|B���m�I� ��<�,��7kA����?3qjBoT�Fu�T&�rx�U�����
`N"�9'�vVv4:	��1�{�e"���g�B���?W p�B��[Ov:����N�<����jQ�+�4>�<���[���o�}���g,�k����oL 6��"�'�l�
�0E��[w�D�@	Nr\��m�V��AcX�GW��n���}������Ll- ��v���������5��P�o���
!�1����W���s�U}l����g�`o������������*����l��������������f���P�?���t�����_���R<�V���������N�s�{������{���;����{v�no� ��p��p�<����nU���g�<���>����@����2���ak���{���������p��jm_���o-7g���L�r^�<�^�o��K3CE�.�9����n�����������}�?O�z�6��h�@��ZOV�
;�M��t��1�1��s����_���hMG����?�S��&4�}�>	{�l��N4E�w�Swj������N���k7��.��-p�N>���:vq|O�A�c���KN+WwS�A���x�sg������A�r�)��`�l��|p3�d��/'A4��2��������h*���
��'���2��p�}��1��;��;��Wx�?�������tz�/�J�������n�����.��5��O�;��'k�4�W;������;�O
������'���9����������t�o"���Rp���p3w��Q620Tw���+�W��7��~`��OTNm�����M}���;V��K�[wGqg�j�����������Qd��IU���uF��deo�����}��+�v�������������d����\'��~ZT���,�=)7��"K�!���k���v�R��,�����_�2��n��ISd��t�
+�A~eg[R�I��b��10e!l�Y6^���v*�8�Y����0j�q{�Gc�+_�kG'��U��+O7���@_$��[���,T~��Ggp{;O��uw��:���,�����X�SX_-���"=�X]���6h1�2��b���g������^��9�[}!�t���8��n����.�iw�d5-^���*d��;V��i�W��~����7��=��:�T*�~;O�V����E�ZP5�{���2�����j���g�
����V�!������b�y�t2:���/o@�������%�����ALf*w(����Dr:�*��9��R��7���,]�So�U���)s�d���@����������A�b���"�N����*���bw�����(���xxf6�43!��	���`u����� S�W/p����t����/6�U�d���d������v3<�q�b����X7�%�F�r�,�`�p�!Gf,�*]���kp�������dt#?��
������v�K!G�O�w�@���w;������A��D������_J��^|'�1.wt���������Z��T{��S�$m�ap���O�5��v�]�����h���u�8��/�0�0u5	�p�8VW3�i����F�+�f�C�*XQ��KB[������p+�T�����S���j�*�����'{����x��v/�������%��^��`R�O0���i�z=W�����4��;[��m~�O�{����B��aC�����
A�c$`�P�C��f�<26��
CK��lI;@�������`����V���[,[�N��{�W������<���W�����h�5���M�w�`)<}�)��w�@�������8���b�A8����y�%���R?������"XV�m`�����o\��f�N�[�Kp��V��xO��l�`�����M�x�����8}����BQ���FRA��2p�������
��lo��s-&��j����l��x�����x0q����!�7��A�g�[8�2a�W�7r��]��C���G}�I�����bpU��&�>�@��9*P/�}����/6_a�2+��pXwP�����y����Ugc-���kR�To.�Uq��
'�r��n�i{N�>�1�����������B���>D#r��4	7�Kn����N�N@N;^�	aj�P�D���$����uh�P����x����T[���ev5�nY�����`J>H�ev��b��1t�#����3�X��p��F�� ��7���x~[T�����Xd���xwM�.��asdb����~�5�����fJ�%����5�,���5���_���������8;���)O8���\�����%[�������r��%n�Z<K6M��A�Q}����9��.i���Lw0�HZ}�'
"�;�����@��
�@���r��9�Ht���D�8|
�������<�ug�t���9�B��X�|�����������h�����A�t����s)�m�}T����`<����6���o�����XH�a����F(��`���[X0�+^�#����4��#�BC���;Z�H�:�|��=���RzU���]�$YuzE���#������d45D�aa(�5����)vrjX�!��eNM���<��N�/�S�%^����X�hVK�?&�nP�^h�h�On�&��p�t~�X����d��3�vHY��q���;	i��s�*��A�����Q�Dbs�!�]r'Q=�P��7��3�|<�g�j��+~��Uf���n��%������b�X(��A����@.�		��B�d���!"�en���'p��/�K���(���,�G%�]f6)T��.��:�j�^����W���ob}\�Ch�����I

�V�������-�����0���4������"�B&����1)(i��FAT���9l�����K�z)P��'��(��
Ju����xA�c�m&?t��M���H����6�OT����T���W�N�O����S'O�m>��bC�U�#y38U;>V�����&����.af�E��E����rY��E���y�5�����I�hM=�����d��u���"e���|1���b���cS�����u��9�����;a� �N�S?y��W�\!bRS�.Yo�A��+Q�����\�H(�
-��?C���0I�����yj0u��9����p'^�t!���������H��Vw6uS���k�N����p�q�EzN/���PL���7�P��QJ�fD`�"p<��!E=����Cg�9�X���t�@����,u���"`0 W��E��.3�-8+��^�$D�%GGL||���.���{�3�|�.Y��-I�Q���B���K���2@"+��+��d�a*��nJqZ���$���\���<&��]�-�e�7u���>Fh�����4nG��H�n�9�sV���^"b�NHZ����OZ��^tf�����d��X�-�*���P$G'&j"F�����<��b�fRs��C�����A�RQ����@��#�.�#���4�/-8lNq��8�&H������`cq '�T`_�w`��x6��'�t3N�XC�r��z��<<'2!�k�����j���5Ky*f�s�C�x��j��#!U r�G�`r�f���&�4�NyX2{�$���'������
3	7��:3��Y�*MomqU�)�s�s��!*�I|FAb���)3�a���#�I:���4��"n8�M�V�nD<�hu�w2E�6C.>����L���|,>����(I�)�,�=��_��8����A8�z��Q�:�����	79kj��>{��2w�X����E*G��c�h�����!���GV��$��9�����;	�������E�\���6Q�����];����BW���Jc��(�{����i�
�,Vd�$�T�E8$���i �B��Y*�����C�Pbt��&�,@�A�V����6R*k��0t��� gS�sA���h~:�	O���g2��a:�O��%�X��^�"e�I��P��yrlXR��$@�;&��/�MH�������"$h'�tte�=������*����$e����SZ��{e�w4�8���J!_a"�$�����&��u(��uq����pB��t�1�;g�o��m�M4�i��"�}�L�i�*����3�I>�@|�
#�����>v�'_�K�l���#Tyf�v�Tyv�{�$ZpK:?`Tw2p&	�AQ}��q��w���5iU�&y�a�e`�f�������A��'(�{���g��ut�b�����MS����L�,-����^d��(�����������e����c���!A9FV��d�������:40��13���=�L
���c����/�:S��=b[s^���E.�EF�[��y�+����{5���jH-�(�s���b���w�0��I���� �1.Ws�#.	A����F�sc�c��1.S�0���H:"T!��[oG��>���x81�b
}���V��c�b�r���Dq-�{^�
�.�(��vl���d�O^�����COK,������4�745F�<��x9�w�� ����9��%���������~�w�Jj�����kw�G�+H���d�����;OVY�0sltH�7/5[�
i���������c���3uLZ2��acQZ����Ei��;�=��`��z���Mf�����tJ��k6������Ax�X��^H�HZ0tn�]�oLM���|��PlsJ� ��Aw��]�eR}
-��(w�I7+��n	���) �B�����D���C�)��u��!E�Y,�	�}Fk���&���s��:���=�=�.^+���+����:VGtF��#�A�=����������\�����y@��"��7�n��
�2<�k��7������8x�k��[���*{J}�0�c�K�������|�\E(�Q�~�v������N���������;��!�j�.L�
)��X'�q�%i2OD��E���;�5�Kp�9Gr��O�*5�I�5cO�����v-�B�5z�v�lqW�����g��������������qp�%nx�B.�4�`�y����/+�B�n������,<��Ji���{8�W<�>�Ym�u�X�q����F99v�V�|�r]�Q���IG�Z�q�
eH�{�>*���8�nd��0�����|d6qIDV���D7�/�kR
�F8R�U�3K����S#��Ith�}o����=��y����k��
q�HA�{�h1�rM;V�(kN�������p�*���'>���4�x��1�*Cv^�M�~���~xZ
������^�U���p8:�\�3	�����,*UHaF�������N�9����I�#S����]z{���.�`R9x��/�fZ�!|��smI�z]D3��C�����/�8����
������2�����j!��`�-�����M���N$ht��t���-A0e�t6�����u|�w�O����b���[��<+X���1�:��p����mH)Ey�r7E��!�LH�u���1�$9�������&��"��� ���Ot�����-�AX�~H�Lh	a�����:`���<���w�Z^�����x��_�d	P9��WT-9\�uL���h�A���Nf��@�J�m��Y��3�X�N0I�lV'=C���z�y��D�Q��U�A!o�s����']��O�7D�901s�F��\�����zcv4��X��1���WfTY`O��_�D��$s:�L	lr�	��J���0:2�O��=:�<�s,�UOO�e�K���|������u�{��{����)����
�?����Z��mN��5��GY<�	�d"]��{�b@��`���{���\��<B�/�IX����r�j�����!=�a�v��	������R�:�&��[�O$H��I=�������H�T����s��:I����m;��������B�`����J'���:.5gm9�?���� YH�`�t�9@�Ce�0�"���'F�
��-�(�������L���K��J~S��o�B���bE�t���a:�f3�uxQ�]���[
n1����6e��2��-�H���wV����
���u���q.��R���������!=}�m#Iz���e;���_
h�'w��#�m��X�&�)��"�y+��Vn�R�8`�k�7@#wM�	���e���#g�r�������1:�CH�@��M��f������������`��>yV*O;��j{� E�����"�`g�)������@�}��t�v�����z������_d/�&�V����N�����g�%xE��_��ak����W6X�r"��~���0�`�~k�����$�.������-:1k�tK�[}���hf��e���jv�i�]�Bo��u���)�f��D,Q;��3d���$Fqb8h���eJjb�4����i��4\� *�Owi�{[�2����S��	�&��-�K��c�W�&K�I���_����"��Mtd#e��b~��vy��p��A;�c�4����u�7�!��(����^L���3��/Wv���ut���}��{9�k��:
��e�'��� 9�Rj4>�����E��Kjk�y7 �YA�3�����[����aO��0eCn�S'	��j<F���%�K���8��z� ��@��7&J�3b@��B�$�;f*�XL�~���3 Z�q"-40��o�f}Ok_��B��<���D_�f���'���p1t���Cqp���$�v[d����L�zO���.��{M�P���cU�$���EI����7�r�b�.�����n���&����LD	2���>P����IW�lz�����Q[Y\�at�/��D����@rupy)>Mt/����V�p.���[���]�g���R[d���ck��6��y�������C��v+b?���:�r���������"�<���<�T*�au�i�Z ���3�����O?$��#+#QP�G�(�0Cb�?o��s���u�����I�����(�F{����';����Jeg�S=�*WQc�+*�������s����T�	Y�J�r^����/�+���r��J��M�xP�o����r�PfINW��0@�w
��eD.�b�)� w�@��qb����=��^4��@��e������'��j�s���o��m�����U*[����v�r�[+��ty
|�K�Ow��m�<%�s�m%���4@�3�<{�8n��Y-m������]x�s=�K4�@����p,�+�Z���'����0?S�������,
������J����:���X4�����*b�nU���$b�Z��1������B���/1H�D��s�8c���Q����
J0�TP���K�D��;�A�s�c�rN]'Ws	Sf�.����~�<k}���D+Kp�cR���U&���)f���:���Zd�u,��KT�a�b���K�2�gik���C�x���l�	�n��O�$F�y>"v�?�*�������U*{����~>:�6��������.~lo9���F���L���g8Q�&��<��U��*�����2+� ��h���K�2�^_�}��6��(�d<�`�U _��)�����S��|w%}�0-$�_�������cq���@rg������\��>k�z�t��d��_�������$��$J-����G�u�z�5|��^����hI}����W*������N�������J=!�$E��~`����{�[?U�
b�SNw�m�P4��L��7	yoBK�����d�����r�@�����o0�85����9�<��3�<�p��6���M���R��P�XS�I+�hE���[+�Rp���$��o��,������sgw��.�C�D]��R�Qs:������!v�V�?��eA���?A�1��x��@g����e6��g����?�q��f8-
n���+{�<:�v�[f�,g���Pq�������d_����h������-����v�}��i����������>�D�4�(��J��A�"�]���Z���G0�:d�U�y�50�mY�o�:<~�l�X�D�������C�z7�~�d�!�ac������h�q��Kpu����j��%�����| �LOB�Q�j���G�g��-I%���O�@�T���j?���?��
�{.�Og�l����j���v���{������ti��.�wq��`��N����D�	`���4Y�vt����F4;{��M!�\D�}vP~Q�o����
���������7s��l���\,��O ���u����S�	������)e�XwJ8v�R0��;�wHy������w&���UCg���qu����^G�A0��2��l�S�{�I1�>$:Og���m���|lt2��`�-�1r{�I�`�R�����O�.��N8��"����)� b��}DMt<���_g���.��o�+��*T�^�@� rn�^��C4�F����>w-�����o&�A��\&ul�Q(�-oP���� RC��4QWt�@�|U���.o��������1Q:v����P�����kj����-�Y���5HK��4��m����8�����G�y�`4Kr��>=�s���j�F�!W������U7^M�-��Tk��/��BK�l�A
���8
8���HI
���S
WD9l{u5c��VW�-�(2����V��8���p[S�g��5���&:Yp�M�� �k����SI�7���@�V��`��/(�`H Y/�/.�.��di�N&����������I�V��u~��_�5���z�y	�X���d�p�C����������o+x$��N�\PRB��\�V�"]--y��_�&����5l����H|��!�����x����:6�T�Ik��u1����b���/�8�7�,5�cs�
]b�Uf69���+2l�XF���D�������X�W��I�f������D�$����:��|��l���	� �v��B��\(�N{���u���Eg�n�4:����l�m<�?8��������O������h+=N����J��e0���8��:�o'M��YU��O�7����e7��[���W���
(�y���"Fx��$g�
#��?�K�C�5����Q�����\\i�A�`�����\R/l+b[|I���f�$������ �:#�����}��FK��Lh�n�G�� �-3��a����*���1;��%\ y�$,�M?��%q��A8�/�/)�q�s��7�����NV78!io��l"K�Cx�Y0�R�w�vX0�1|{,E��x�����JR��Q�jp*���Ji
N7x��?�#�m���x����a��3g|�Q5�*�������'�����sIl�Pl��������(sN)�8E�K� �G�(0�'N�"{JX����^H�[��Z���o?��������e�+��$���A��5^wW���bp�B�`:�zx7��XZ�b<��"&<v���~�s`�?�`!��hL��ss��|
��P�Az��"��&�����������8dEB-�l���"����%��*�o���V�@��t:)5N��?]��Ge�)��'��
g������t�e�#M��Wx��9�D�{�BnmU��` �#�����Zu�kD
���Av����M��c�u(����P�X�W��z���`��{v����b
=]~}Pe	q�w�B��s�Q/D}ZP`���<�;��9W�1=fL�V�-�G����k���G6^���/\�W�Wx��gn���F��y��Uo�R/dI`
�3�;.��d��(��_������p����7*���" W��RTO�R^����e�
W�=.�mb��p����te���D%���2.�+����eg��bZ��[��
9�9"��u�~�JK2�(�� #%��F���`���u��L�R�

�?�Vt��fJ��#a��&�Qf�f���.��z#�"��!A����4�?3��_���K�7^M�qK����
UuR��6�����H��4)����bT�>��;g���C��.�3��Z�&�UB�/���L���1x����7�a�-�����Y��Z��F�����8P��k��RO��U:sLL�]��K��@�����:�8&�F'�3n$}B�nMT���������-U�q��{��#}�J���t�s����-����9�Q�H%`M�	z�t��p���t(�Nj?�~���$�K3�����TU��.r���1cht�>��>�P��3���K�~��;��yF�,��K~[7��Yw7�R�<��r�d^�Gx{��=�&�[c ����K���!F��'|���ai�h��U��V���,�����F��u�( ���1�/<��[�
���	��x�M���_N(Dr/���)y������s%��Q�pB�h�~C�����;}�}���TGE>���P�v4����om^�5i���j*�`����[Jz�y%��=l�G.�� ���0���3%��OI�Ab���<(�R�����`��������' �3J�XuH<Z�A*�^r�����������P��t5���i��������O4%�q_��U,Z�r���R =S�H���F�M���<����^�__6��.��.6U�(v#�7��@Xl����J��H#��-��}$u+�@c��,��Qt,C0`��I�y�j�<B����>������9���C�n��(�P���@N%�=!��f�;Nz����ge�aDX^h�f�C�1�-��Wlr���CF>�|�E,A	��83�&�c�I��&��0+y��
��x�m���e�b0��~���jPuZ�}��A[����[��r��u�j�/����#=\�M�q����s�W7���e�3m�t�T!������d*��V�	c���=�����H'��#�����I�����������Gl�n7>S�p�5��=�F�#����9N���A�T�Z`}�����[�`��<$l��bd��{����fG2��`!a,;)��1K.R�Qh�i��q~A�!�W�����g��B��c�"@P���#��@���L&o�p�VVqk�G���V�2&���8�s���K�VF�.2k�!S�DkQW1E�(1�D��K��Q��iyJ���r|���q`N���n���QJM��m��.V��G���Y0��l���0~��dEM>�M�qB���0*���:�x�BTl;���G/u�e�XI��X
Yf�#m�NPs�W���4D�M�K��Wd�R��~�"����	EI�0@��s
B���r4�$������*]�K�#����s�>�=��I`.Y��*��dl^Y�Q��[(S6?��p5��*���VICO��Q��������	�DZ+4�2��)Hb�	5:E�l�(�7��o�hJ���V�����|c#���o��n)�������E��=Ed|�U�oB�]��H������/��&�6�������Y��������&$a������Nj�;m�9b$l���$��{	�Df�G ��"�:���[ ��@�o������?�@Ju�FMz,*�6�x8��
b�N�Q��f#�J<Q�?�����r���!-K�T67��C�}�/�;,?v������1r���Kv����p]d��Ne�1n
4�R�Iy!�B
�^(
�Y�Z��6��U���%t�@�\�6�~�T����0n�+c����V�B���&6^M��.��s{>`J��%_"���|��
�r%-])g���8=j�'mBA�<Ka6>9jq�wG�_svQWg����]����)=-,C��	q�)�|��d��t[�?��#�|�^_������{�����r�d��V�h�����V4m��BsL�IB~��7C;��)^�f�>����������M5{e�K2���2	�\�y;���_��sYP��~%n�nr
�]rHa�!����,i d1|.h���2Jd)=�ND���)����Ng/-+���4^|FA���xi�9�{��0s�+��%��
�����g�DQb(=*y��kA�W_��>��*������{s�	�_mANX�(v�S���MB<
��s�xfpo��b�g���k���'��D�D������N���_���o��^��O���"����>EA��"�-�Mhp$����(i��KBNI
�}��$��D����C�,a[���Zo��"�E�rl	J/}g���b�R��?�'nM�g\����T��{I��Yk���DvrR��)8��L�:��hTt�-r���<��/��s~�k�c�
�9�pg/~�M���CH�e{%O�(s�E�r&���L��{�(��`1����������?�Xv�}|�����nj��������]|�SC�?���p[w�^��;����x=����^�x�W
K`��s|�����0P�\�Zt_����+�|����,#��Oz��{{��Jw�W������J��6��G���7����S�cf��'b�)k��3����& �����@N��A8a���t�\��X�V�@a��a�=��=����J����kQH�%�}�s	����(���&���1��(RQ����N��k(�m��GA������������,:`���j���~����
�^�!���;�B@��r�)f�1�!�]�X� �&����cb�&�v���-�+�~��U��\�U���T)�@���W�a�s�����W���������k8�$P[����e���+��H�5�w����&FD4������j��&lR�����A�$n�,�7^��P �J"5|'��[y������"�&S�����+��\E�uv��OA�_����\F: ��9���w��������E+�_U.Bc�RPX]B�k��t����R���ow�����i����RMl�����'��(��1�yGJh�P��#5�����A7��k��V�G8�Z�k���p�^�Nj��g2�p����>��Gm����T��e�A3x��������M���f%N�K6V����x�����y-UJ7�H:fK.z)-c���B���t��~L���K2 r�������R�E��A�{�t���no/�En;���������n�#uV�����Gqv�����P�����sN�^w�#{�f�tgWSY���R��M���S�l]pR����yS���=�����'�CN����I�)#��T���en
���0�t��CP��x��^���%��-7P�60����?�������n*h�b��;^]�Gc�JJJ��m��#�ah�
���P(E	�Z�	�PWf��d�4IP��s�V�6��P;����u���0
#4;Q$SR����e?���v�i����^��A�~�k��}~�(Z�^��Z�����(b�*L:[h���o��0Iv�-�����!���@ 1�17Z����R�HM���GkX�����Jl�9Z�Z@?7�2�avP�eOgr��F�P~��*�O��h���g��G��-jc|�]?���7r_K�Tg�>�qs�����;�541m�4���Su���iU����%��vy�c�n�w�7��l��mq0�����	%'	��T��w]=6���w[���]^�[�y}���Q�D�M�SP�H���Ay�����%j����������P�C���J��G�������1�6�w��)
vH)_��L�����`<��j�T���(������j�yY��l����+&��������L����e�hO5��.����?m��'u�8�7N��AW�KV���w�W1�^e����G��)���K!�*���e�����9S����?:V)�\��u�N�)�����-Gs6��=Y�q�h�W������%�������M�
�8�+��\�����[A�NP@�����	�6/"oQQ�<z�����v��$w����G��9lF��W�����������q�mS={�6�Y��X��o0����������c��o.}�s�Ut�0����)@/RX?H���h������C�}e��������r�*=}�A_�G���)@I������i<�B��/�P������dwgn���~�h������"\��}q�B7~bQ F?j��Vu�n��X�wvYoJ�KN<)��)���H��n�>Jazv:#5;)�M%�����������!E��8,q�</��v����Vx�����4c���b|�����AG�Ij^Q�):���Q�^��(:���{
�R�$mO�������8��2�iuq	����Q��j���%���Db~�"��F�%7��������8���J�u:�(7Bss����jJ�WQ��
l��E��fl�����%JZ��Dr���q��3#D�~x��"B'AV���4Y�<d}n<��D5�1(��H�-�����8�u��tb*�m��
(�w	H���E
���6��	�J��-M�C�V1���w�yzE9��*,c�7�u�9�M�bs����DA����2	�psS>��3�B�������6��%��I��
'��%���n6?(.Nou����%���#t�}������T!.�0���:�w����~�����������d����>����K��}Tp%�3�!/����l&�"n���~<l
������::n4/�~�q�j�6.K�{�cG��d�6��m�&W<�>�X+�I8���}|�h��	]n�b���������+*�`O�_^O��H?t3AY�-�&��"'��#[TL>���d��9��r��%���Y�%]�&�c��a�S���(8�{�w����j^8�:�o�B��V����B�����;	Q���`��S�i<�G���X+t����^������AJ#w��nn�E�z"�Ln3���C����R���=Uj��c:���V��yq���e�@�h�+��1_�J^����-r��"�X����h]��@!1�K�_�-8���'f��:�>Yn�ef!5�jC��b�nk�������?���z2�m��%
]��&��GCJ��R�]fm���.)h���3\P�#8'���i�Q��D�Vhs��5�o�M�Q��l�ukS��-����cz�[�AL��;E�=�)^���?l\�..��������b^k+�U������L������9��������0�jib\M���v:�^�zO��,z$eU��$�����i{b��;�J59�+����%&�7s02�
��f�t���]�:q�N�O�8�
z�x�=�M
�~ImS�^����"��1�(w=�(�	�1����L��!c,�[�,�b5#r��$��m���~%�Pwt3d���6��fcG���/����G-�[\���*��!����~����;vK1�B���oc�D�XV�5`�������pT��p�:�P�
>E����0Z;�MvM��4M>����
>J�������(s��U���G���'��s,��P1��V>)sn�1�����U5F4�$�[�C������8d�
Vu�5�����c��!�8����~�X�akz�p<�{Z"�(��Q�����1g2V�X@���W/�#v�����@����	5��,�������7^n�	�$6��fq�����t�<��6�������mI���6e��1t�8�����&���P�z�tl���#\���g���&]_����es
=#�]�y.���I����������Kw�h�YVz/�SN2��m�^'��y��d�'�HP�� ����I_������sJG�,�8���,8��KK�B\6�u	�M�q�mM&PQ6]8 y�S60	���X�$7��$s��ox�n���������
^����PLB�;d�lZRx6�d�|��	��;�	��\��D-J"��H��^��9��k�r�
���"U9�����5/��m���EaW[�D���)��oKC��"��n�M�I��
7	4S���d��j�>��
9�^M�4;8��	��p c
h���1+�T��	xI�t(����|sM�[�<B/B����C2`l�p:�k��������,���.��h3!&nz:=���;�
��d@����}K�V�K���@!����i��	hg��1��,��$f���pv!��43(A��GPaG�G>����7�FUo'��bzd�d��d�7L�V�#��:���6��o���t�1N#�M�J&P�D�?�K�<
7�p�������Af]�T@\	�Z�"��Di��SM_
�=���j�;E�p0�2�UG&E�������5���D��)�s���
�*$��mW��I	��-�6����.��#��=_�:�#d�Qk����5-�f\���,k��l�/�QF������ey~�Y���1�%N?��~�c��.fI����,��J� oJ�X�����|4�X��������V+�n��%N��"R������|4�K�26q!�D����T�_5��=���	�;���td��b��|v]���j4o����Z��=�V�;���8X6C��STm��@j��h�A�%r�(�pt��6d��l�c�`����OF��G����z
����G9i�@d�!�R��>%��))(�/qDf��;����P �g4��4U��?����t���z]�85��Yt������d�y�&��x��"dKO�[]��^'�H�t%��aRL[����}��h����x�#�>�V�p�V|���z?�{�0��c��\���A	��d�V��I�Z�NO�o�������I���6�gGge�4�P�X��',��x���;���5��p���~�y������*\�q"�.eq�[��[�w�5�R�5�;4�O�#�R�/N<�B�i���������-\"[����=�q��3h���J�`\���p�.��z �+��C�I�'#I��`��Y�-�:1J�Q1�B�l�Vr���M���t7��"����g
\p���SQ����pP|�
�:%#�K�4a�q&��2���X�����A7-�f.���	8����c�WO�&��%�������|90������X�d�U�������F���R�(��I�"�����,A�l*/���g��0�.��&$��F�as|4I�Q:���m4����$`*)N�k3�%XQ��:OK��B���]P�'v��9u69���;%PA�9���I����0K����{�*o��f�y���oI���82PKB�-��H��y���*����30,
�N���m�5C����B�f!�`+��H�����������L%�F�9��T?y�@X+�F��1��Nj��9*�S�5��8�l{��X����3�EL�m
���J[%�H�������������s�8��iB�0�lk(���+�N#������U^����M5�t�J�0x�������e���>�#�K�A �$��4���ID�,	DW��(��$ {Z"���`��Mb���Es�N�Am]�1@�lN���y�f�y3	�Y�]Dbw�����p4C�����!��'�[.�;���O��o_7�hG��N��,I��N�L�Iq- M��m�`����d�A&c��s������'y����8�~�l��z�G7�<���[7ssMj��h�Y�dT��]y��)Fr�6�����������yr���u+&J�6g�=��(.��M�d\�.���WN��J���2�u_`X��%��Z�\��g���_4Nj?�?���v���c�������+a=���������7G���X�G	��|T�S"���%�o��N:t2*%tn�G^�C�����<�F���`�H8�<�\&N7kR�=(W'K�\�7��/K0x���}����h���k�����r�����A�����1�7��:������J�l�h�,In�������<T$������?k���B�E�����{���vK:�Z�,I�t��]I��a���j�3��jvD��sGQ��%X2P����@��G�����|�+��T�z�^���V�#R��8�LNVC���7�����"�p��aNo��-�.l3�AB�2ZR��J��XR��	�����3��(��l�6s�8���w����l�d�R3����A5?�i�q@��l~��T�����������:��h���0C���G�r;�;GBB��b�Z��6�i�%/�$����-hK��h�A9��%��IJ�5��l.��!1����[L�q
��_���+����F�!V<�
��"��	8P�-A���
��(�
���2G����c������������ 
�,������L�<)��2���������~�����g�M�%��e��,�����|����y|��9u�3NQ�]�ta���0�I! �zD5���`E|`������L���Y5���Y�0�f�����2���eK7����s��g�_��|�F&��Ni��*��o��Q��\��9�R�q��<~���x�:��%����on�C���6��-�n��_[��g���[��_[�]�j��>�kk�.���2r�_��
��������k��KfM��r���u�,][��%��kn5~�0_�D�#���JY��}�J#o�+��vYf�bU�����v1�`�6��^ ATxX	��T@��nuE�eEY7����y�h���f��_Ea���T����m��G+����W��V�m�����"�jPpI������y~���;�yT���p(apb:S
M������O�p�yX�s�75Fr������w�:�J(�zfz�h�$N�< �x����4�0��f�.�z	Mh���1%\�IS1����@�h3H����|0K~x�5�4/r��^u�f
�����#�!�*,Xp/��Q�,~��=�I��J/[[�����q��d�4�c%	0n@-M��@]j
�.N���!
'%�`�B��i����8���C;���`��u@oL1���r��x����2�����q�f��������&������1#�����8�f,�G7��x4en@O�F���9CO�
&K�����m�1B�)uwr�_��l�RN
���mf.���h�+X��t<)��@^�(�����R���k�M��]m21C��qM���$c���a���]��v�	��{j3b���u��}�`��Eq���oVz���U���w��?�3!F$�����b]��Q/�O@���X���h���59�y��r�E���H�m��@�NF3�*[���9"��4Y�%�fz���)C6:v���Li4����������9�[A��[�3|/��t+�;���������K/����t
 �����e�{��2��j�Jd���p�P���~��F��
�)�����=�d����H���V��gbG!K��yq��[����X�MR�v�$}b�r4�0�W��m���j��g���e��������urC��������;����,a��E]%��.$�fD�!y`w���~�J�]���f��e�����f8]��2��c��q�j��P�$db��I����P�#uw{���(�D�
86�q�-��eAR�%�'��m;12��{��2��k�Cz��5p�`��t�"}��;N{JNPr�|���#�������Zo�.`9`��/����v��I��GXf:���"U�������!��
�L�Fg�5�C�4���+r^'al34�����`ut
?}���lxu��������Z_	�2E|�����[KwYs���&6�)���z���FKz4>�SrT������Q�v�iQpQ�<�E2�r��JCm/����r��%�����1wlL���n���s��	>�����Q)���s���Pf����$�w�S�����m��RI�^��{����/��r`���F������lcz��K�yk�������*^_�c����k&�R�7���<ZO��fg�"�.�X����z��\j��F�
��oR 	�V����f(�EW�&,����������'^M���wi!�A�����R'J����i-�P
�l����v���
1�ls������?���z&i��0>`�f_�lDf���0���!� �La�����Ksj
�7���)�(�$�b�����A�x���l�)��] ����O2��������4��X]h:#s���dM�8w�&�'a�mZ7b�ed���r�p��U�4�#	�l��J���	��X���M��)���"'��
U��������ca[��Q��q]}�������[������#���%;?k^�������^���k�+X���:.�vY���p��UmcQaprR�"O��= ���e
�!��n�R�L4KB�Dp���M��LQ6��o�s:��@l��H��X��c	��X��� H���	D�L�NZ�)^�cvr{	�Bq`�{�`Vt+������H���P<�����4e<9�������lR��~���9��^������d��_��R*&�8��WY����8@L
B�����.�P����B?C��H����:�rB#N�4�#�N���;)�l��WL��h�UP�JG�$V�?���K��Ep����ICo�����@�T�^Z���^��������+m1������@���c�%&!gw��x�r4;;��0�����A��q�����N�3C�c����\J�\��Hn������=t�?�<)�S����/�|LMJf{e�zNG+�Y�RP�@�k����Z��g"�[�&V�Xy�R�����Z���p����5
d��]V+����t�[��]����G��)��?x�N�O&�������)��T����S�J�0� UC��
Zf{����=(n��s3���X��d&�������N]z�A���{�xT�U��OSU�-C.�O�������ix���?7���3(
N��X�0��
4ah�(���C�(d��,fI�w��}���O)K[%�HVK���B��@�(�0��}	|���p����|6H)�m`�M��Xv�2��$<��W�[`�"�}�j��D�i;�;?1cZ���x�H��������M�;]�g�:tr�N�aw�u�X�=�g����<K/�������W��=k
���4�������[�E�s`!����$����@���P<���b�mT�H�Vvz��:*��x�X}�8�\�;��ttqv���^7/�h��X6^�X��M�(R���y���CZ�L�
��1��U�RQG�&���k��JR��3C������O;��K�E���khOG#7]���
���(�9[q'�`�9+"���t��tM�	>���Fy���4�Q��������9=3�u�6?��3Q��N���&Mg����-��\�b����yn�2\EG���9o":�������8�Z�V��1?1�N5�,���/����/e��V����~57rH>V�����:���'A4���Q@�&��p��IT�d�$�Xg��LitK�R�H�aaxgd<��[lF%RK��-:$�3�h�� �!�K��0l��������'���Y���%�HkZ�N�K����k��*Ur a���!��s�^E��HfE���%��/�t�Pb
�0A�5���M�yi�
��1hR#q����b��!�M��`��Fh$�K���,b�BR�<��<s3�m��.b���('�t�,��a,d��<z��S������B�������&?��W�������a�]�~��_��h���f�<;��Q��)�7R)�c� a���J2���� N<��0��u��Li���M�]@y��������5go�:xI����MG�&�iA�3�t��D�t��j�	2q��k�UpZ|cS#�i9�i���K��v�C��H/���p<5��b�`.i��2T���w52�%���(����<,���������Aw<r�1C���f������:G����Y��H�AH��������c�4F��ij��cI��;�e�����J���J���d���K5i$�w���b��;���O!��q{���5��(X�:)��r�Z:�*��"�v.+�C��ey�	^_sB��keM�i�.�+��IS8-���\Y��q��5�f��"&��|�Y������N�4����2sc����KX!�E�n��9V����d�U� {vf7�����S���c��� �����r��g�2cu�e �&�+�G��`�>iF<�Ap��@����'�M7��*`�V���h���C8������;������Z2�����`q��)��A����!/!�{Z���;3�g4C��jc�.�`��0Ib.3�gS�#�0��l�"9>��9M9\^������W"�+Fz��x�O��q#�t���Fg��j���X[On���x`��Y�����tR�`���,��A8q��J-��l������h�NF����X��6L�8��"���@�%������������&����L�6fY'��
�Gc)2�bSs�bJP"W�G���n�����+�l7�Igq6�=�)���5���j���2*.P���������e����*���^��������6���ML�����~�������VyK�W��O������mo�%��Q<����qf�L�<��]D6c�r8�N����YU�[��
��_v�}����#*4�k�[`��6�9�.x�8$/X�gb��]��R����f�>'*�����P���/j�WlV_��=�y2�L���	���n��tg�7�p��a�����O����6��y�������`�s�t:��I<g��T# n�lb��`����aP��7�n�nJqv�O�������w��*G���V��^[[�����s�%`!?���qn)�5�[zg���ER�Q�f��s�U��;�V�q��`2	n��@n��7��&H���9o��J_��KX�~��0y��6�eW���w�0�4�Q98>���zw�T�p�$D��]�64O��=�D��Xw8���g&>vs���(Y�u9��nk���L�Zc���$
�����m�����O����V����s��~��~����d��:�x����T��gh8GC:�I�M��F��wq]���k���+v=r\����������
f���C8{�J<$�n���g�p������!�D��M�����g���D8C�������E�c��S�J�B�&��9�G4o��P���$v����B�h�����q��1�hg����c��st�%���=��f'bD+�n�R��%���i?�d�|�l�f�bj@��j
x<�[�mHH�5#tbfBk�&Ut�^����Lt�hB������:�2��d��b	SI�:�;0.���KQ�B���+�\��z��|�[��0�.��
{=��
�s�����������tU��A�8e>�rE*n
wbJ����P6����.�[�U���9�P�����������D�N;�N���q�vf���+�F�*���`����8�cH;�OJ��\)��*��^���������+SX|��c�le9MD���)�j�a��Ql����&7��K[!��&�	��SC����6�G������e�����eI��K�R�Eg�1��yk�[s7o�&��2��e��I��p&T��)��(!�������Y����
�6zXma&I��0�\{�	�H)Sz?���^E�'��j" ���/��\���`�u��������#6�Dk2�l����W�w+�KE7������29e����FR-�#[@��~yjV���	��D\6\��(��X2F��g~�z�����>1������2���ts�y��������j�n�-G���0+c�/� ��0������	2d:#�I@��U1�k�]���R�Znc����7CMV��cR�����P���r�?�����?�x*����c�fzj�9���N/��odP����o�Ki>���9�$g$�c�8N����n����0�n����|y���&R*\�av����U��I�v"����5��)��^xQaV�=����:�_GCB_�����>dI{%�&	���k�����-�!1FSM�t�}=c�Q��K�����1���e�����}����$�n��7�D�#��h�H�CtK��`g���#�O7
����qc���P�*�� �t
0�uJ��>F;�I3|����G�����z����9�j{�A��_���(J�����P�5>f2j�6&��S���I�|�AB�/���k@Ac�$Im�W��j�)��zZB���XT�Q����g���a\��b��=R���L{}{���{�	]�Qn	��z�:7����LN,;����O�5�h��'o��B����74r���������_�kVr"����W���.����Q'D*TS��c���|��:�����Xz�Y
AkM�
*��I������2k����0yv���tT�LH��,��ot	(����V��=���*��2���n����"s�+����t�����)�f��- �$T���Xn|��{��{���m"G��(�����O�����[
�<b2T�Xq����.�ol,���{��{,�k��I��f��v���mR{qf
JK�klpu���
*A��.(����������i�}wA���	�����#8����
0jBf~�y<�-�������_��0�+F����i$��@�^�����I�����6Y��6b$���D1�@�"�h�E?����1��v�Y6-��b�AcE�����U7P��
�^?����=}�?���j���f�}��+�0k�.k�kMZ��:)��;Mr81b|}�0������oa;Q�7�]��T	SZ��.#.����[��=\�.��5���s�J^2�D?��(�#��"��J
��ax��KR��#��.�M',���?NG7,mq��h�	�F�#&���@4u�����I��F�
o�����94�������(u�g���h'!Qo�0�����KB��)zX�L�Y�.�<����6�3��HK��U� �S�||]G���fv���n�y���Sld�,�����c��������	�����%J/4�OG]���;.`��(_N2O��cz�"�5�1�%�k���1�0;�����������9,�#�y�3��>��.)Y��l���'��2�������l�.?6�?H������#2/���/;\C��;ei�������s�>��r�E��h�a��������Yr�"I��t���wd2�aCm����rYbG���w A�xc�(�Hp��b�Q� �g~�������m���p`��#O���^�<��=�k:JA(�������U2�hYH�{�~�@�����=��a�'Gd�u��F�������@i���.$#[�b�q%�(WY)�
i]@���NL;�U[�Ee��3�(�l���.���|���(�.&�O
���~����9�<blD�3��r5*)kd�Gw����M4,W~�w�� �p��~��/F��9c���3bQv6��HD�D����G��S�&�KggE�g<��lh���-r���1�}��������)����4���+�	OE���E�W3�;	!gp<�����J�.:���0����N��������{i	�&���c����'��d�)��E;?��$�YF�D	?��1&Xl���Nj?��a5���}t�-�J/�j0���9��O���R5h����<��pa��O(��y1� OGev�`���C�!Z��c�����Q��>��`E���rf�c}9Lt��i/W����R2�T�[�.i��D�G���*�@dSF.�#3��t�b��,�����|�6�Z#T2
���J�c��I��]��64�#�*���L�9���FF��V���v�S76�n#G_��D������hy������Q����<�TbY	R���R�VI�T�8vHi�
b�tTK�3�{O��1�ja�i,��x�Q�S����AU��d\cA�5MvNi��9U�7_����%P[i	301C��_s63���$����5(0tu#\�������L���!2��$��"��]���D��=dl)�V2�^R����+�����q�t��T=�LO�9s#<Xk�o��.�`�7;d�xPbR�����������B�j�rJ����$��5���t�%0l�43���$��m4����dL�cr�����a�Oy����
K����)��x�n�!FV
�w��@���dp�������M���SQ&����H���bY�V2���V�����;�$g���i14Y<Oy��nd
������1.�t�6���&�m����4f�(&e� �?Y�'�����+�;e�j�6�5u���e ��ER�#��L�
�}T�K7��g���]/�eK6���p<�i�
"�����q8T�I��q;�k��K��Xh���m�2��^��y����G��D�#c����en_��85C�
B�K�����g���:�E���Y=S�'[��vZO��?��;=���a�����ZZ��v���G�V�z�+	��,�+Kq�&�������<�t3��U����8(�Q6FU)��M��Z^_���X�?uC #!�9�>n�����3�:<;n.� �����Z�H���F
�c���C�Z������Ea�����</8v)���$�R����u�F�Bo�����]������!�c0���6~������ylr����{�w��g���)�M`-��	(�	���%~A�b��,����w��&�����ja�
�J�
��wu�h�;??C���)
��
���5:[����E�C�^G�sL�`�T���&#�x��!7a�F��m��8i��'�|>��m>�"=����
��2`E�M3K+2H������'�����,�">���]�u"���f=q�!u�uJ)����vY=��(���� l��p��:�
I�m����4��	e��G:����EF$����O��-�'��E�\��k]z�[	t���b�B��Eja�L�����X�`�"4��� ����:3[�yO^��V6r{���.�o��+����|�O(�X|n�I�F`*pm��&��D�,������Pc��BA���T����/�a�:���#m��w�	���S�M��d
+��5��/S���:G��I���v�B���u�����Kco� ��5B#������iVN���k��OV��"lr��U����j��#�f���M=��|�I�G��[��t������K.��w)^��. �;�����k&c��6bat�:�+c�l��_]A��w�N��q�49<����}���4�-��������z.�W�xv�ND�D���}�.��i|!xOtr]�{�����m+9��51��������6�1�9���)7S��HYe����==����\?3.�\$��"�)�����X�����������l��	N��
$�
^�����]��CM��|��P��B������'��{��r�r�C��~��PNo�����������������6����du����I� ����LE��t$T��=D�=�v�71�B J'��wF���Q��u���C��3��u�����������W�[Nx��`l��{!���e�5�k�J�tQ����W67
�>�k-s�(���%_ZJE��)W��X��q�{#��%�(���z���(.*I��(����G7S�?����(���3i:��i�;j�d�4�y^'���l��5Q��WF�5��:�D0k�[�J��]Y%{w�����2��`sI�3>�!�M��X�9+����q�_���0Y[Uf|����J2����F��5�L��Kz��N�I��e���lnZ*�K.,u�"L'Q�����Cm����Q���{��\e' t/�A���;����K�	�E3�G�n{��ls�+��{��N����"��-,��#��+��O���K�Cx��I8��V.�d�AS��c���4y�m�:!�DN^�k���+eU�~RV4-n��@��~�>�������O���#u���M����Nw����Ea�f��� ��Y"&�9����@�����{�'%O��C�if�q}*���s�� �s���:���;��;p�&��8KH�F=J1{O��[�PE��Y�7w��	�9v�c�����p�a�p��0�0�����������L��@q������H�}HV4�����{���~�gPn}������>�jA��A��(�;[�o����^O`yW��/�������������m���v�IUw�f� ;�qpD~!��rp�5���y^Y�(=6���[?�9r��+��\���E�p;�(1�@���3C0]�H���l*��T�[2�k.9	��q��S_���&���s�<E��e2�b��Nd����Ob�zj��~�d��t�_��_���K�p����eY��"�s�<Z�_��
�_'ln��m!�5��l��:a�3���hB�E���qp��)U��(ABj��U�ZB
�����HiFg������S�@��fvH�D�K��5��\�5fb���=���\��g�f�'���B����
F����G�s	m1A�g����2�D��D=$�Rc@�S�$�bKV��F�����%��$��6�$&�t�L�o��f��d]A����k���K�^���=9Zy�GV9-O6�	��Q�G����B�V��N���s-�@H�L*u��'!��`j�2��"kT�[���6�o�����t�.�cK-W�!�
D�N����U��h�^_}�����c��5gF��x���cE�d���vY;��0�4����%�XF+l��D�2��;��Q��X����D�p�O<�`���a<]
����pK��)�]�Z�:DO�1��$���'-�e^����������d��qx��D��A`���.0�����-�I `Ps�f�z��c���{{��4�;	�R�A���,m��8"
��w����������RcG5�����l�@Q'�����!�:���t<^��h&X���(aw�>�N5@��VQ��(���Q��W�>�}Rm�����
�t[zDe�D��{��t?t��xqv�V�J���X[<��
0\���V[S������s��m�j @��w���X��	��VG��x�hXN����g�a,��"�����\��1�y6�`N�e�����zL��.�4i�����&,d�_�h�9.aE��
�����K��TVK�)N���r���"�|�l���'(�,ys
����Lg����sd��3K���{�o2�W��I����$�58�f*�Bfv��D���'��fH��7�����Z���d�&���
�%K��?|�s�	��yP������J�����=M`�w���y��L�R/���<�U52�R��z�w�N'IN�3��I��7�F���T���2��F�����W�^��b��ZV9L����Y��RmjT���OF@mei�,_���G���	��$����H�#�i�k�\D���lc�� �$8f4"���nH��nfZ�(���g�YL`��{c��,���`�Y�ME^(h��f�	1v>!m��W����CR=t\����S�vyy�z��<������������GO_��r���i �-/cJ�O�{���=�h��.������jY�d��f�s){��.��	�H��k�f���*Rn�PTo�hv��E`^���qX��~c�$���j;OxH���D��=�6�v��������;,��p��(�EF�=r�Ps����#�87�y��W�l�V�L�~��d�O���h#�
M�]��C����%���I��'S�Y
��,��	����%��-b��_�������J������{~��^V��0��c!C����� R��NbaAWp�f/�>�_99�p{-���wy4f���J���a;�n��n7<���7���
M����P��O�zd���I�Uie`����G�V�p��$�fw�:��D���7D\���c��&��2�I���;�a����;?D���/g�z��,{;�(q�uL�o�"����#'�`C�9ilL(�S&���C��b�Po�=�tLD_{R�������b�������k$��)C��3W%f�����p`�V8�3������s�;��.$�ZqP�\GW����?�+%O4JJ����8�\��0����Z�rt@���h��U�������bYq����02�;n��Zu���1��aD��(&�.�����V��(�#�2|�)�?!�MO�u��t���!rC���<���L���6���N���sO�9�1�qI�^������4C�M��s�\8gm�{�j����$6�������]Lb�|=�@�
&�t'������8��WN�E�lU����J�����NwG{�lll,����,���T���N��S���.��Z�@Qj��~�8���?(@Y1��+]���[����_�?�Sh<������V�
h�my���6W��8��`ZB|����S%e�@��[�:n�m�Y,�sy���q�s��s��~��[���i��E+<�M��S�f�cc0�xC\��4]at�6	K���c�HL����t3������\7��x;���v���v���������A%V{6@#Vc����������^A����&��fHC��R]�L@eb
CU��
'��8n����;�c�T��0�3������Z��e�����'_#E/1
9�����mh�� �(q����X��<U���^�|�]���-��_E�V�w��,��)����#�f9����Q�X���l����Jew{�����S��&��)�������Z�����
��#��-;�$�zv�Dz�����r<��K�K�c-�A�Tw*��b�~J��e��6�6C�o�1*H	9}������#�dMb�Jv��;�3
������	�/\�b"�e^�����`����EK��qv��o�L�'���#��=x���^��P� ������zF�RS��A��kBJy������:��{1?�1��ILp5������n+�O�"�UT�N������n��Ww�;��d'��|�Y��$��G��Igc4}`���t9�i����eu��|~
:��	�2�������9���I���v���`_�v'������_��$�
�gY��$���d-v��[�v��Q�i&�
9i[m�����O�X�������V�,�%��/S�[=�������c�;mg�����&H��p;9���2xMRP�Cx+%�sJ�?�i��R@����6G�����3��%�:T�L=�>e0���3D�V�����/��������NI��d`S�R��	�e�J6�m���2,�!�Z�"����;�T�Y�N�*������}�|>������P�ip!6��]�i6��\�l��
�VS�H�W�\�e�i�V��-g��3�l�
�y��i�Y�X{���F�����=/���/�--Juoc>�a���heH�)���5#;iw��lYa�k?�\"������]�=�!��f����&�����I��54�x�h�)S1��-$Pt+�9gt@-�LF�Nj0V�^;��5�<��R�����d�F&4�F�2�/]��p��D��2d;QZ��OwV����|�I��h$�k������j��Y_����_����.��6X�;�����h��@n<Y�����K���,���H�}�v�����5�B�(l�����?
��r���n8���d��!��	�i�Z�����B�wa��Oz��#@�j�C>S��UlL���'O7��Op�)9�T�u;!��u�6���H��������T�L7��A�����u�Yy��=`Mi��5��
�n�q�D�����&��Nqpr"<�9d��v-��{y�?��Y�%���ITos���6/����s��9�t����-@�7��8K�l��#��{��#���P�}�����������\��R�)����m|��I�wYJ���'������V����k8�dt1Q��.�_uQ��9M�s&~.�.��5
�
�V��50��J"F�����U��u�l���!���v��gY�)��2�l����tP����zq��A:�,���{�����,�C�����
t\�r�����O[�P�����"^n[���3V�����M������#�������co}�qA������WDF��l�n�X��a���9H6e��k�fSQ�2�)J�A\E��1��
��VA��3JK���Ng��"b����l
�ntS�r���{Q��{�W���pc�K������N�����s<����o�����(21������F6���:��M'^vine�9���8{s`��������knfA���Z����vW���L_���<��,����*�KY���{��-_�������T-�2��/�'�S�=���&�DY��_��Xo����7����Z$����q�T::qW������e�L:����|*����#jhs-�����x��D_�Ek$o�5���1��+����~����j�U����:/�)�i��3��O�:P���Z}%�p#f�������� E;[=m	\&�F��k��Hvr�i�%���[M��/�B�6���IW#��=��Uw��I�.�C���*�yY��d��O�{k����G7�F���<%g�UYqRP��Ar+'�v�����w����	�z�	����}�5����sow�[�����C��$��j)Kv�\#���0��mu���������;�Xb���M��+�a��}T,S�E����.��(�RU�
��4C��)����O:�%�Dk��$�\��L������4���x��^u���S{��-Q�����V��m�����������z�hZ-'�O�[We�\�����{}���M.%2/�4��=�R�28u��c�[�hh�Y����M�������Dqg�j��������z��V�Z���������v���v����{Zd���e��o�������M��VL��B)�����a"�$P�>�`�OqUrc�������3��������\�f�z Q��J�U){Qk|��'cJ��6o��2�\^[R;	������&�P������g2q�����Vr�|����t�1��fg��N��������G���������c�l����Fv����O��X,8 ��C-C��c8��W�o��$��B���9K%���C�ft�g������z�Uk�|z�������&��
�x�������r|���r���6���MB�1�7��_���E����U��)�uft�B��3?�_�V(������][���+W���6�~���d��m��m���~Qg���E
0l@+-q�ksX��0���P���2� ���]%\�/�OD��@�Z.�T^^��m35K
�Je
���1�����I��!����)����5�����~y�}�\�CeS�+_�,*�r�����=v��~�R���W���'{~�h�/�6S�b3�DA�j���R���K�����*)��"������1����9��;(]�Q�8����[VG���Ei��#�%����d ��m�a�*�B.-�$iY���d
K���q9����jB���w�������Ny{�����U����ZJK�	}�b�{_�����?z����[����U'�d�������Ko(� �����#*8���@"�-,��8?�5�E�Y\	g����GD���l�Z���c����w�E��&������V��n�������g������2�����/.��B�(�+��+�]���V�m+g[mom��
�T����a��Sw?��n�lj�N/wC0��ZP����9U���H�7��V��m�,:����g���.a�Q���#����t�IG��VP�m-t�$Z*<Ze����SE/<�%E��u[�r�h�0��b���
:U��<����wsMe&|�;�C�Pn��d�
�,}Y����G�@fG��a8���h")J��f&5��+tsH�TbT��;F=�����D���M�s���qr�x?�.h��������e��E8 ����[����UF����?4$�	DH�.���+/eF�K����'��I�����KLbaL$�|��4�4X�+����l�r�?�v�O��/)��(!_����]��w��O�*��4�g�bG���:*�!C���>���3)��'h��Z$�O��7:L�S6�N�O���Y�I�H�\�b�n�z�3%s�d)�m�\������>��g&Y�������c���2+jU�#2cY���:����3�.^�:�,x�Gu(���Pj��K�M�Y�{�#�RPN9,�'���6���3��!�������mF(m�V����z�'���; �l�~�W�d�?������{�A$�#���AW����gb-�G�pDa�'0H�����S�����E�{UH}�W8������P���Z��v����|:c*���y��`wM���.�q���g���5.[����������P��i�	<7���z��������y��|���q)oa��_���0��!�8��;�3��L����g�A}Q��hZ�t65aa����s��/W�����$����By/w���^{/7��b=�i/��"�%�t�����b�">�%�4�YL���	WM%���G�������T�)�
q�	�jN��=��4���;:s%W������������$g#2"�m0C�o��mr�*�l���[S�r�m�&��:j��B
'�H
 ],��Qq)k��8x��Y6[P���d��`��t�gd��'d}�3����d�5r����� ����F�b���8�A�g�p�P9���vLI�hS.�� ���1{�R��=������d�g��E��W��9/���,�~F80��=�S���|/�Z�O�u������'�9,L?�Q���[�W>(���s?�l���t$�����.~�&}���za�UIj�x!
t�=}�_�����8���`r���T�������y�)��Fb�E
�>����F�\m�.����e���"�Z�e[�Vx�;�+��p��^wg�R�����mh%�[*�~Y�!�#�"�p�	�Xi���3	���{$1����z�UU2F��J��u8#��X�"�g�����5��J��Dx:��j�"
�(FKN�$�Q�`�{��YYH"�i�9-���gx������X��w�d-4���Q�k���
B=�||�T�!��]B�����G�������Kb��Hg�^�mS?��Y��7^%c}q�(�V��ju���R��f��YFI�F����}E���L��r�q�����m����-�ch�h���M�-��]FT�Z�J��@Z1x]�TE�r0�����Y�N�PM���bd��UL��eb��ax���[���a��c���YCI�6][��	�s��p�������0
?}Z;v���W�� �Jmq��"3�^�D�L��i2��`2	n+��A2$Nfh,������_�b��Q���5�<��D����R�H��M�E��_n5�7���D5�����B�I��d4����!��D20H� ���\�\OW�,���K�\.����
�[ 6�[�I\���6�����Z������Q{��������m��n��'�g�������4�E��h��5���Nn�g�3������s%�!�F�5�������h��t�GN���������#J\$�)M�6��&���S9"��dq���*�3:���	�Vl�����.}��0����Gt�%�7����fZZJ��By���Q!�<6�7r��W���J��t7�n���u������O��a?v\�Gv������E(��g�g��^D'R��S����'1'����
�����_��K�	7�V����
�<9*Z�w���8!���>]�\e+f�szm,*���,�����I����	�@Q���S"���x�K��kOgI?�{LcC/��j�(�q*6�qq�����`�3���n�b��s����wM.�y(���3����?Yh�q����'��Rb���[�T�v��'�`�
�(����Q�(j�;�R#�8��(#Z
����S�'������_+�evra5�(N���@����3?Onwn�{���g�Gm���Y�V�=%%"�{=�����Ai��Z�g����3�`
���k��rx��1�QNm4�������)Hm��@p;���]mW�;A�����>�������A�e$�����>9|���=A����`�Ni�����p<�
���s�ZA��>JR���o���:xnk*�M����]�m\���)]���0�jb\_�U�0�	X�t�������w�m�d��":cA�W(e�d�Y��*W�����,�rq���`U
fc�$D�7�Oc
��9���4�m/RJ0s�`wo����
����j^;	�,(I��'U2��O�Sm����Y?�^�����t	W�
�3��jM���J�1dI���wz�����3�
�������g(�^��U�0���c������L��7X�%vK��MnEy�Y,)�O�>(+<�*7)�h�_�IM�:����,����6������v>�P����f��?�B���h|�/����1?s �P��eV����R�*t��dymNq9f�)�G����T���������;�|�y���g=�
���!i�
c�J����R��Y�4��^�������q���������JL@%���NU�3����!O�[�&��[�����R�w�z���zO��
���!����M�\�mV�%��J�=��B
�'����N���������Ro�a8	$�VG�=|s:��X7��N��y�h�y���:{��[w��i���L�5�B���R���H�Mn�����hr��E;�+:�%"��aD�oJ��1W�h2���Q���z����Z�q����0�M���5�0���8kw#�1���h��<p].U�l���Z���%��*���Q�����6c��,�'��!��*�����?���)�?�l��5��	�X��E��E�vtR��Z~�����Ow��+sw6q����;�J3X��\�����/�����r�eM ��1t�%H��B�g�<���g��!�P�JZLxrzvY�a�lB�i8��Cq�q��v��8�,�ApXD��
�0�Vu�
,�V��g�1f'��J�+��%;����)���4opw��jF$������FN7������|g?��
/��[�����]xfW.������:��G<u���.�cPB����6�7�1"���()�.��q�v~={v��6C��+�������,(��4������[����e_��p�-X_��7���L��xEI ��%��Xf�]�xig�q�U&��I���r���6>�^�q��m���Ajv������w�[�)��U#+�@�.�������W`0'��ta
����E���V�}��
���BbU������m
��M�jVn�������1^���L�	��,=��5�
����T2O%D|��m��f���5��*��������>kz��5fs �A �X�~����5�o���������V�\��lc|��k���:�_U;:����Z�3���������X'l~�������^7/�f�9$P�������3�&�y���8kR�����_�������N�k}�/w�<�N0�6n�>E�b�]q�n}� ,�.@"��z�
���<�C��I�YR3DX�I>_�Z��$d������zr���xa��(T+h�U����F�5Hx���\��x��'�A0�����J����V����DqI/��~+m���cbaW
74�0�(�Y�"
[.%~�������~q���g�z��[{[w=g��)�{3_l^�[�R�[wl���n#;�Kh���V�v�H��������7t����[�A�E�+��>���7Y�do�s����U�m��'����mG�����>o������#s�baQ.�� �q�)
�L n�J���dA!�clH��buO%���)&��	(b��	����{��x�'�~r�F4[�#D	�utG���Z����"������`,d���8�+LaB��*LzG������o}RA��;��I2���q�{�g0���	G�vO�����_	�E�����B0��f_��g��	O���0�<�����YX���`YtrC�����?�1����C4�t�I,:������M�����{8�����i8s���aA�f�~�d�Nc�H]��������
Rw�#��|��r����P2����� ��iI��j����a���[�.����~�Q;�t�:��<�8]������H������*
p�l�ew9�g���vQ�F*�����*uv���d���X����i�-��2K�!���?��YC��+�x
<V�"W�m.�v�����M��0.���_~���E�R�q��_~�h������X�9>��_]�v�c���������!Zl��x)P?}w�J� t��������O�BH���\@�:�,f���],$�6^�1,J����e����F������JjXwn�Pc��
r��Z7l�N���j��a������pw����iA�L,H���O���S�[mQ�S�Iw��
�E3��y�,�5�'��BT��>B-�{��f�(��x���C��uBJ���
��T~�ooQ��l^��������zyB
�����vQ��S�h�up{4�f��E�x����1����^����h���;E�<>�T������$�_,>���
�m|�Qa����{�yj���y��[�V�O^<�Z���`aV����M���*������������~���!>)��y��w��}��w���y���t*l�Ow��xgVs�8R`������,�����J�tV�Q��%*X��KU��Xh���m���A;�*�&�	�a�&���R�:K7���������-u�q??��������
��4�_y��i{�k��j{���j{w�����,R���C��^eO�p���>�&20"���gn+����D��E����U���@2�AP�D0J������b$����g]�y�����YH���!��(��a�p��������je�K�j��o��
�a{�B�������+���78��Uc|c����V3�S8�hR�H���j�������gI��W\r�n�m����
���yZ\���[W����G�_�uj*`���R�R�n��u�^p��?vY ,8)>f���{b��X��7����	���-0��Sh��n�.]=���k��9g�,Q+�By�������wa-��"�["I���JT��/H� ��q7��e1yL��B�q�	���$�������r!���3|�Dt��B���t`&G��z�@�Y�������3���p<-���������A�)��?:]UI��/S:AZg�\U����??O �c�H�2�Jn
��@�v� bX%����d�-����S*p��$�2^;`1�������:5w��"(�,�K�dc�?��_]f���m!��v�#D�_���}������G9C�==�����H����\��	���������zF�a(�]uXo^>��8\�����!���}�J�Lx�W�:~�n�a�+�z�N���`�c�����C2���m{���m�A���C�)�:��}���~�xv�{�Zy������0O~t��h(3�P�.3�	��/_�TW���������F�w��T�w����-�[f�6�[Qu=��!��a�]�
��G11��_1����8������BB�b\���y�K�x}��K��C����B:�8�_��?� Q��5��(rr����4�@Y�l������`����%���f��1z�����5��*Z�D��������
���<�~�������������r��|�}���������m�����=�5����,��gR�>U���~���v�r?��]����jeT~���6�2~��_���B)�g���c��z�5vkt{F�f�oP{Wjw�6�������e�$[|�=�_����yc5t���z�s�Pn#|;�.�ty= @@�6�
�t�	~���6|	q�� �0j���6pc�&��Nyg[j�-��b��ry�H�|�	Ie|���!7��=��!~��{���h�+�zZ������~�
P?�o+WwW�P�#3d��}W���G2�=xDP`��s���������I��VX1���~d��P�����,��l1x�b�����4�L0x�L��`��g�*������
�d�"��P{���5h�o+�z�7��&��
�r�$��:�*�]���m�Jk��n��X�=�5V����z��0]�[�+e�0��]Df�y��n�m�j^���6����-7��6U�CS;��vM;wi`��/�^f�����3�F3{8Y����� ����n�CR��j3��A9��o��;��A!��F��l�5�������T�����*��l'QU80[o�JWB�t���fzCZ ���T��qW*�9��*;�a����my����]���sc7w|t%F����H�C��=op;���fn/wp�T����F��:���+Nq���X;g��sA$A�Q���=2c`��ZV�W#m��I�����T�pj������jUIOm�m�4X
V+4�������8����������6�l��������zv��dG�8�L~�����^QK��$�}�����!	��;w7$���u��ys�8�yw'��Y���EKo�R]46-�M�g�s���\\K�(���7�6�$0[{_���c|OlQ��z��X�b��w��!+ks��;�+{^���B���x����[s��2�"����k���/O������Af@�TP�Y�����j�ZW
��?�
�z|��Z����z����/"���hiY�Z:�g<���l�`���!�9��z7������8�aU�����I2�?`
���5J�Rk�
�����H��^��/�I&���$V5r0��fb�I��D26(
K>�.�L��=L�J#1�f�T\a��j���3[�fdh����������h���?'�ET���������%�:�g����I���4��FA����g3Ni��=F�f@v�H�����|�~�A�Z�QB�&�L���?��Q�_#qk:�hn�����K�"))vh [�V+�H�7����M���+u[;��a�xq��]�-I���.�i����p�������q���J��p��6d9��)O�"b��l}y�^�P��������r4�~j�Lu&$T���y�
�t�!Z��}f)k,��f:6b_��C��n2�E>��'�;���V;^p���d�e;�5��lt��c��6K�Z�"���lXi��S�!��@����3�GLB�d#^��
+az*�OO��'��Yz�%N}����nn�����wF��h�O�����'��vB��/�v�W7�y�-�����������3;z4K�%k�%o����'������O	O���vj��rcg�?�����8��}I��o�����9&B�
j_����B�����9RQ���j5�o��Y��V*�Z����G�^�4[��Z���Z�rx�������Ya�+��c�&��,�,���������D��+��a�U�*��Qu�hU�����7����Ak8l�G-�A������T�;��3u�/���8���7�b�K�������`�g�f��z���#G�R���f�Y���V*;�%I�~�:�9�V������J���D�#��`���
���F�;��<P?��?T�1/���&3�b�����}M��*��.����q"B����:��z�u����g�Q
��~k4���|o�=�h�iucQ,���[YQE�I���K���wa������g��7��o��T�����l{0�nCP���<z�_�.�I�>s��W����a�0NI��M�r�|0��_�0;<e9\$�]��j�Gn���*
h�%�2�w_m5�������{P���j�_�<�
]�Ov�V�0�ciu��7�doQe�{��n���!�����G
���Ov{���j9_-�g4�c����t����KH���7������1f�����I���v��x	��@�O���veN|�
�� d�F$Q����R�tDP�:=�� ���k�FaW�:��9�VV]@���l�Q56���6j���
�R�b�g�f�kF��~����1�t�"������+htM��n#1�����b��Z��#����:���0��C�n�hm��yJ����2�z������CH5��|&p��`����S���$��[�u)��&s�TY�_n����N�k�}���_W9�PW��;�6����kSW�������r�%����35Z�k�
������	�5�(Vu(���H����&����Uea=�/�HzL;��C����[��7v�:�������k�Q������J����_W��u�+�a�[�f�?���_��o�����n�]�����.fDW{��K�������� �`���������|�BN��b�g���!�jHy���DzV����uF�b�m��a��tX?��t���7���~����|���21!�e��$~�]���]����4G���+��Z�H���
(/k/�0�zo1��z��� Tvm�y����=���t�n����1B����{��e*w������E��5��{t��Y
{Si��
+������,����}5���&�������L9j�XX���}D�)�
9c,��=�k����r���V��(����W=n�{m|��!i��� ��*&7��@������	��X��`|�|�������^}_�/�� ��Eu�t����Qj���9�J��7��3},�-�!dG�?�����4-�Eq2//��0"��w���*�B����,��a9K�������bN���YA�L�t����q;�����5�#Zo��S{*9G��u���`{�MC�s���(�~���hZ�E
��5��V����N:Q=�
�^���[�Am�1X�Z��X[H����^�k"-N�7Dv�S*�����W'�'o�7��������������2���M����'������g��������r������..���H[c�R�UP��������	���v��g�0>p�#�`|z)wf�Fwf�vT���V��L3,�	�7��>�L���}�����K3�o�������6�R���[x������3�8��� I0lN��
��%����C1��Q�
U�a��5���3�v��Sd`fu9�c���s������?�V������5{i�)K[���?|�U\�D��L!r�k9���q�vw������{��6e��,�p�h D����e���u��{�]���>���M��~���r6�A��`Q8H#�K���4�}�����@z�B�K6����
?X����l,2D*B����2����_\�z �������3�������8�o@�y��@�h�OeBf�-~�S�O6�(	���_j�?���ry�5j��p3`H�l��D��o
��l����`��_p�>�f�@�{eA�����$� �1�@�w�������N?�R?��W������U
R���ZRDU!��E���x�y"�/f������."Q}
D�'_y�{W�l����n�Q�-}�w�Esg0���:@��;S�@��%��< ���x`�n�%u	�������%��&���p*8boF�.'���i^������t�K���{�rx4���}�Vm�dK�0�,��^��n�O3v���d9��^0�)���������Pq:��������t�f�������W>�x��Z�m]��
�����?���`�<���������.�����v1��I��C��q$'u&�o�/H��D@q��x�U-�w�Z@E����x�z�G���	>���P{�]$p����!�-`]u�1�����y�e<�S$T�T"
@;E��$� �U_$VBq���E?��n������za�
;��{�"�C����$oPP��c��eh�P���?��s9L��|�-�a/�~!�fo���r�Xm(���,��gp���9��v`�����g���8A��)����i��5���S<��{1'��o1���Oq�Q�R���u����U$�R��Nfg��_����-B��CO�I�/��%U)�0��?��B�������f�!a�[���b���l��T�U"���w��,�4��B_|�\J6��{	���W:�x#�a�
/9\d��&��K����`���/f���&h��
�S�HTZ5���&cM�l��(�)s�n9*>���)�hP��[�5�3�y��V��_��e���	���{a/`���U��<n��r����<��C��G�j$���m�ZJJ/o�� !�YX�L�[��g��A��0 -���&��hRBw�#h��}��N,�^�>�WWT������������]tJ�8�`*|K1�e�(����Gc{���m��NM����!	���<���~���{��Lf@F��������� R�/~���������y��������:����/������t�{��\�C�Hc��4
+x���S}s�N��<����3��k�O���&���(���FT�D����yp���7(W|w�����u��S�DG0�!�VC5	BL���}$�@������)�y1f�]{Hf�1Y�mK��(h�h�=�Y�5�'QU,��DP�%�6�R�H�-�,�,�F#q�M�6��R)8��>�[���|8�3�������2���X0�0<<�<C<k��\��z����>��3WPp��w� ��^�@�y��0F���Us����M���7��,���-D�a�������I#8	����zd������k&�d��AgL'�[��������Q����D6U���)����31�uPtP�>�6*DY�Fe���?��d��yd%<5��`�#R�V�J�/���7����Z�B���N��8A��@�QCbB�J�����r�i��4d�A���
-/���~�������\�%��Jt��^V��D���k�$�P�&&W�?���t�
0�"�^��]yp-.n�/V����/_�.;g����5��A��i��x��5}���Rg��^6��&��~��I��	5�^����E�u�/���e�+!7|�-����8�B�T6��niI��p.T���ej^�iD��f�n�� ]�/&��f�p�����f��<�W7�s
e�sE���B"��������~o,4�w�%�pU���P��~�"#�pY�k8���J�L���.{p=�����N��WTq����;����{+��oT�_.�G�j�2X����)�+A�pdU�Y�y?���H�_9���.w�?`���}�9o��X�m�����G�|sP_��R�UT��������5h�8f��j��`6.����h��z�M�z�^����_V�����W'���*��WQ�VSV���~7KDz"��I�\a#����^S6z�������9
��{�r	�����7�!���OHdL�C
�m��g��������`[��N���sq�/��pu�Fh3�9TOuOqW�%�����_�`8�Z������[�9�A/v�/����s?�z�������O���D���r|�#B�J���K)���P�!�N�Iat�����qv
�Zs�V����:������i�@>bE7Dz��]N}@�)���!a��FFL`�=-@ �qO����� �m��Yn�
}RSF��P_�	l�)�{����hhT�!����.vM�X�LG3�������7��t�@/�M����D{���]n�V/����YmO��#X����K����dN��7�&@��Vvv�W��C�T��C��g�
�����������#�z5���<�c����<�G"C@�+<�FXJ��P3��#�?�R����UI�����	�.oF�������Q���I$/eOi��p.���$����/�g�"������wU|��O^0�	������/d�/3��L������@\�i�'���{�d�9��+�D������N�B�]�'gOL3}���jPA{����[��wq�d'�+fY���k"��?�RZ�����F�$/�#��em�aS�M�7�7C���0(,Sx��oT*Z��yh$�g�n������6�OS�z����oT	��b�>&lZ4]�V�����C�7���I���
���k���]_E|C�;E��/2E�p��4��1�!�+��nn
�1�62`�wyfO���W���V�J%oU%gGWD���h�v�y��'I3:��h��
���fJr���0������b�D��EP�I�'�I����2!�.�Fh�C�xm���.%�����T@*�K�p�-���4��m�cj�!h�kl�����ofx]�g�z�B!��AN�)����<YR8����x�����c�9���R�Z����Y�����y�u�c4c{���}�>P�m�V?�{���8��>��J���������c2���"������-��h�w����2�s�H�K�L���
U<�yh�$�T���l��y�N[L��
���GV%��]���=�?���������4�-�����`�2�
��%��~F(�^�xjkt���S��{��s��YM�\m���=� m�.����1�dxd�fi���8eeI%�=3�j?�g��;����g#�j�T%���@;l�M����?f��wm�A��C����K�����5�-��T�^X-��i��mU�����{T���i]�a���D��W��7�j�p�`lRv������
����f9��yl\��f9T�iZ�h�|�UD����c�@[��O>���B���������#���h�p����1�]��n>zB@9Hjq����!� Z��QROb��I����D�I������l������I���[�D���9�}�y
"1����z��b�qXjT��l���`�g�G�y�� ���q8Cx_�����D?��7�7�*$� �_���-��h��u������P�P���w�?�V��2W?�ju���1�K���yh_	�xV9)��%�&1�&��E� �������Vf���(<�D�������������T
��}o�A������J�e���0���[|����^A�G��48���6`�g��FW�E��I����N�H��Q'������2e�(�[t����sI+��/�K����s��U��2��6���_�ynH�8�v��'�!����B�Z�[Ra0�����!�J]�� R V�#3������K�%����'���7%�$�.��o*�nR�)g�rjC�P������L}�9�-T���[���h5�&^+N�@l:�#����������Z��������{�'D��a[��j�or��A�8j���Kj$eI���=*J�]�{8�x�,�$���r*GM<+(��p=q��W��ad��4U��E��f����f7��%L7�QlT�8��l�+A���aZL`���{�^��;�
=:�z��p�|�.��G�}��(�u����KeF���{�O��u;?�#��.���������f��X�b�)�s�cL�d)lmd-��@l
dH���7S+�R?��S2�H}f�=�kq����F�
�:��2�dn�~�;����[��q����5��-	sk'e_�{��C�!��,-u�� �F=t=~&H'����
��tf��'�����
-���Q{�O���
�AQr*��y�
X��j�����n�PR��j�U� Ry�����������mz�%����L���x���|���M�7���[��pX.�~�Um�q�Mk#�RJ1�qL�������R�B��A�=|h���su���H���s�k����A��w��b��'���vi��MUdZm�_�����s[���
>�1Ay�&�wE��F�^=����M{C�D���Y�nLY��k:���X2hc�����y�� ~+V��G�����������r�Pk��2a���'���-�|���Z3����$��$!��@�xl�$.���\g����\g�}���R^F��"rG��M�������A$�4^�X�`D�
�Xo9�)���Ti��,����3��ZA{�u�������y���]�b+�		��)��,�n��U<<bgg����
*�U��j3�_rt�,m��h0%m�e<�eM��=��Gty�*PC�[�A�*n�[�[�_^�����8��N���GK����:e�@�a&C#pgV�(m�<�4n�2�+����=���g�f����W�,���c&�"r���������YKVL����R��A2Y�~jG��#�|F�I6�!I(Z���������j��
b���>8}z���O}��T�!*���~�0�K�)��1M>�Uw��hG�n���:�b��`���9AmBa��s@R�3�8����L��>p	�����M������8�,��t�=44[��*��i`mH�e%����k+�(~��(#�@kD��~��]w�6$�l���B�(�+�9c>���R��`Bq��b�6���?+�w44�^�:"���^@[��%�3��M9$ `/v���|�x���K�����H��-��[l��z���s�
��|�1���L8�ogw@-JJX��]��8�Wh�6����b��h�x4���Q���F����b�
CJS�iK��%�z7�(jK����|M.��pK.���@7("$Q��'��H�&�P T�m$:����oQ��0��E������KB�<|!n�Gv����\~�2��[*�K�J�9����A����N\���%!��]�7(���/1����i6�}<C��I0GV�sH-w8��N��]S�yY "i���)��7�Z$5\����jr~�*����81G�r]$�h��dE����~	�g{�aym9�[�S�q:-�'0xj����I�
��+�����\��e9����L�/���-(�{w�	 ��wc��vFUsTOR���rR����Q�JY�RGI�����n	�Y��q>�Oh�<=d^\��N���E�,����ZS�����-�(�[o���(�����
�#>���?�p�&�2)��}����-���r�e����}oA���:���g��W�>�V�*��c�
+[]�����S
�M���"��o	9�%�?|������������N�l"��mKoUS�yI��2}=��e������_���op�$n����X4�tuv:b�'9��h����y�$H,�m�TP4b�&�)X��j��������y�������z��}�
1���>��d
��P,!<JZ/�%����������>�_�y�.�5��
^Z���4�T��r��~��[b��f��BF�y������
"��XI�qK�F�zeL���e�)b��\���6��43�\]<��M�Rp�tX�PL3~��1�(~��*�=���s15/z�b��a��?��#����G����W9c3�(��{?jZ��c�&��d$M����x�B��UC�T�K)�z�$������x[���8V&Y
���	(f��d>
ghZQ��,��@�������n2�z�N��<(�������)�*-��
8_�.y�~}��z	��[���A��K�Z�K��6t����RG�ey�M��^��I�k= ��=�z�_s�D�uq)+����S�P����ze����J��������g�����K"��0"U����$�y/����d����iC��
nE���]�	���`��$)m�����p~��e:�������Z��	���/�qJ�:P���F��h����O��7�&>��;��
~1�q"u��K��)��;�2��{n��u�������������R�$�z��R�y�8D"�����.�a,J�@&�OJ�2����h�=*���(�-"��2��J����6��A���h[V<j�J�cGb)��L�d-��'� f ������6�Z1�5��p���~#5���$H/h��e��
s��$k��E0�7vX6h���d��E�j���f��2��W��|�G��j��iU�c����<�l��
y-[����Y�����	�)��q�X�?Ncm+�]��b;����:��~:�y��/��N�#�bC�T\�#���=��a����L���W��W������Y���u��]���v
�W�Y�.WV�7��W��'�?���j�����������}}r���=�"�7�YV}�5R:'��:;����������5������a�|8��5�{�6��t'^��`q,n��V8�U�l �Z�v<�{�50Z���N��^�����g/�~�/y�!��7d�{����s�������x��i����@3�F�O�������82O�r����{�LEFg?����<�3���Zv��_Z�|| �����RJ
a���F�\>�7^��00�mmE�4�����c��	n�p�oo����u��-�xt\��e���K�������C$������(w#R&�&�#���=g�(Zm����U6g��<��P��c�/����C����t������
YMvN��'o�g����9��N2��2p�c��GH�%�Di�I$Sr����-~���l�6Fj/�u���V<������d�\v�&��fJ���VOyj��[��x��s'xF��a����Tn��[0��(�#v���>Rv)�������EEE�s�S�C�	{_��L����"}�ExQ���q�.J[�R��E���7%������,$*���3|'����b��	�eO�z�T��m%�%����N�O�v���j.|�;b#-%��2������[�x���Qn�(��������f�La	�3S�����d��)�"��*s`��D02C`�<lP��	���'P�1����
��D�z����j��a�sRC�ICp[v�_�'�IO=�������y�H�!2�Ne���Zz��9b:���Q2�����]^���'X�j��~c����a��g��_:�Z����}4���*�/�Q��w����j����E�����b�����w�0��s�l��G��e���t��f�Y��!��0	T>��U�Wb1'1�z���oL(����L��B,�Y������>�����y�N=;o�t�9A�YU�JfU���n��d�H�AK�-':�R]�����I|_���#��mjHp�9�
��V~�v���|\|6���v�RQ#:��$��D�(���@����P���l-WS����v��Mhc�s>S,r������:I�u�"����C�	���1w��"��W�:8����� ���)�q#?xao6R��'��6�s����S����&�I2���.���P�L�7�����
>xh,�e%���GU*�fS�����/���w\9"/��V���9[�bH��6%Da�)�>������o���p�Lz�zE�����D�g�y,T	����9�e�I�����@|8����������#��Gx����[9A6��J���^�3����8p������'$3�;t����k��[��KM���!�F:��W)�������"�=��&�\�!�m���j���!�c-���]������w��{L(@z��[�3b�L",��J����j��*�=+#��g�]8���@g��N�V�?���_[���["�,/��V8:�����{V���ot���%�����!l�W��u
�X���[g���-�<	�3���"`9���!�t�
L?o*�8]f�`�ep/�
�s��+X�-��f�i��2%���
����9)C�9��������g��=�j	�Y.��#����t��T�Z	A�VK
�1�ls���h
������)E�z�L�QT�c����7%�+�t����Bhp3���U�%m�r�<��I�/(��j��j��4`��~���?����D��Ho^Be�N��pa!�)� �������t?��ptM�Q����]�i��L���d�������"�lo)�)�6QL�N�[T��Fe	1������9"���M���sd�P���X�x�s��������8��f�Dd���dO�V�3�����ud&#F���vK��(i#���z���|\t?Z������g�l{�q�o���KO��n�{�dh�9x�f��8�'���/?��q���'��2E,,��C�M�o���_�@�^�q��F�~����[�V:6��u.���`��(a|����i���*�$T�\�q��	R�0C�]L��a$K���1�6}�����b�R��J��"$��NSi�\��0l����=��Dczb'�<p�����{� ��u��x(Y�<��������.)K���k��$}��d�����n��b)�Q1A(=�>*nA��DfLFr�R���8+f��<���� 1n��b1n�����4"�VNA�w��};��4]�~�iI]n����q:����8Q�8z���#=c�vt>hRC]��F��JoZ7��w�9sB6��-���������6�,��^��f�Q��m�D��	��Iv,v���o��nZ��pXIP�M&��3���J�o��"���O�w$�r'�.�a	�Fc�����%�^�^/����xT�]���v�	Vb��k�Z88�K�=d�_��Q]�k�W���`�'w�?��7�Z�B�-�=���T�v��NGo����y;��������<�*���Dh{���v��-fc�C�7q2)�@��Q�K�l�f+���0���_���+IKQ�������j���K��3�N|��f��sO&���Bf�M�%�C�����'�������R���c�zGAY���<Q�[���b��zN��o���kBD�/xD���mcX�[s�H�8�u-z���Q���6��v�!0�}|0�s�C	�����T����5���~�1g�,�,���������
0� R���v�K�1cJh�W�;!���.��O�Y��>Sp�_����,�x(�x'�2c���o	����%%����=V8=���9Q���d�#��ohW��E�:��>��%������&,�����b��=�#�����Qp�}1�^�BK��(���H$.�#�eo���#Q����
��'������DI���I�����	��9�(8n|9���0�C:���X��� �E�Bk�2C���s6oh��!�"�r���`���J{M�2Lvk4m�������k	r���F����p��32����)%��u3L��z���������vwK�
�}�b�%��r���.~��tBF��$��Y7h���C��w����\��K�&2`s�������
���|�(gom���?f��T���������Y) �zl�Ib�,����d���)<q������H��� '��[��@GX����i��}��p&t i�@A��"�)��Y�&��	u`D���ok�Ci?��5z�"��������o�&�<�D���^ ��-�����A���!��3�UD�n��]b�~���+��n�.-��A*�:R�8hJ&�r��';��6WY���l���������dq���n��:R�P)S�9e�}b����"��)r=����c�UH	��W$2�H�f�jSU�1���[���/g��oS2y}kx7������[����\���\eT�����T'e9m���b��s���;M9AxS���l�g���L��\�j��[�Bjn�`���G��cs������)y��\�3����o�R���}��������n�0
��������]D�`o�.�T�RP�e-�z���%(������x@��;&e����c&��CI#���(��_!�������0���!&����U/�������������<s���N��Y.~����6<����`���#�M��/d����be��������eQ�9J��m�\c&���S��"����6L.��6����6�!�I�k��a������s�H���1������o.���Z/�y���O[4���S�x0u(�Gw����M����O��6����l5 ��;��C�n*��L@U3m^���jf�I��3������7���J�����6,e��;����#��\�]h���$!���G%��3��AI�<?4�G�c�Ov+m�.+��j�Z���r��U?��T��������Du������g���?u�`��{��c��UxC6��ZJ2W���9&�������@%�w���L�	}���_F�!���$�N����l&l��x=����kc��je�M����jB�^���Z�"v��f�=V{������G9�FX�����iegD�����Z���"�fg�W�L��b�`��cMw9>�N�y�a�$n`�e(��FJ3r��nk�.F�=���AX��b�3O��v�1��3�s���G���A8��
n�$��:�HkX?����7��Vs��	��l��(��<�
69�?���d#1xP�h��O��zC�4g�U����B��_�~����M�	���c��|��$y6�_�R�H���1S3�K	v��*���MX�b/0Kgj�����j���u�;���'vO�V/`�_*����9/Q��m�UZ���7�G�W���A��������]���Q�Y��H��_�/����1F�!�UR��Xg�>;�.4UhF�J^�vc"�"LK����YLJ�j�����Q7�'�b�����U����2~�'���-����
~=�{J����~�����SKF�!]f���!<��Hg5[-���r)<(�7}1�X�1�g�M���"�;����ho�
?y�:H����3[�M����D��z���~�dw��)A��"g7 �P
�!fi4����(���?���?�����S���1�s1�Q�`V��>y���.��m:V����&?9����
����,���Ao��H��Cj)���R6������+iW1;����p��5��L��O������j�J�%r8e��B�Q��N��8Z��{S��s0�D8�}@�l+�����+
T����K�8����^'�������
#4�OnN^�3�T��^�b#{8���u��z���N��}q���h\���x�N�N�O ��?�pg��t4��B�����9�Sjs��OE�y��q��+39�:G��\�a����n?E�?qm�>{��`�3��{�{t�z�\��h��i3X��o�����Hi��`d�~u�����]��O&�nu�T�7�GF��]�>��;�5�����R&���M��x�p���)�����^���/�����"S0�H������P!p�����=�o��)�<�"
.��^�)�dP�����z
ZG��G�v���OX�2
��:���=�Y�C�MN��#���4��D�����{[�AX�J�_qc( Jofs��c��$�@��<b���\"�Z����b��a4.}�$����Y��c0<%6�����3�����Xd��<�P�����`�z��u�����9*��������	���0�i�`u����2
�?���E��5&
��.c�yX���-'v���t�<z��1������ND�/H�2UG��2dj��������;E�r*>d�H!#��i$�Vo��3����p�O�mK��=<��>���9��S����a�bF�q�#�-��rb���>*O?��uxj>F;�����:�}L|tC	�	YsK��,�Ue���t����B>�?��n&�O�;�N�3,C�l���K��P��H��;h�KQFQv��,���O�<�
��O�������[����WJ�;Q:<j�,0����!�ew�w\��%�����i�4c��Ep�a�����U��}��*���P�Ru��?�SN�7��jn����y-QleY"����M�+��'����(m�;Q��w��d�Cf �sC)���"����
�6��1����m�G#(����H!�����EJ
���XC�G�^���WjINgf����G#`�:D��-�e*��������c��gv�9{e8��w�L7��U����]h�<�%$�w�n�M3J��@;h�@9�P�iG�#��
D\����3>^rM����;T}&���Eq����]��o�p!M��p�����0,�lp'Y
��3�V4#��d�}�&l]�9����2u>���LN����X04�;������5�bAP��K�M���$6uG���h����&@K`��i�2B$9MN�xf��K�3�RL��������p������tn8��,���rI�Gr���6)��#*��E0�$�c�%I�j����	B����z�'�]����2BY�:���=ey�r?�>Hg�3ISg��>dG[oi�Q�%��
���h��A�+��3�,���2�@G�)����x/���p���(�#�����A �������wW4tnwW(��u�q����E����9D�N0���:�(>
������]&:��$M�R�P<_�h��=�:w;�v�N����A�n�>�#�
�Vv�C����9��Z]�qTyk�L���P.��� �;�9�X��(��zc�@�`i��Z��&1&�����dv8>c�I��C)���Y�8�����d";3��X[���3(Y�uG^��L����7���M5�L�Z9���5����<6���K<h&TQc��W��yI��]������
�@�2�G�z1o��`��4\�����a�
5�0�G5>�Dn<K`�#=���t��������Ek�'�6�Y��(,��<�Q�L�����n��Li������H;#���t;3m��V��[�^���9=�_X@��]^c��x�������Q���ed�5q��}�h!���u�����������(6c�_8�Zv
�{���<H4��Jy��*�r��( ����v �q�� qo'%!�SYG�=�b
��h���0�)PO�!J�r0��QZ���/����q�w(�K��h ���7��L��_���m������B������(*}L�c���F�m&�i�\���l,��nm�����\#��$_Iv��$����J%��bKO����~�����0��m�?�2�2���+!������t����$&����H"���'1��5oW��s
o��f�����]o@
�<P)��0�^�����Y?�$N��'7���!> ��KX�"$Qa-�9��%%��i�����<E-����>K�#�Z�FX�j%L� �������=��5�[�(^�t��W�8�9��f:���>�/�hE"�xN�4���e3������� H��	��V(���oZ��zHn������(���.�	K��-GjF
$b)|=�wH��$�k>�s���&��$�0+gCKr!1��������v��u;on��oMD�������Zbf��cPi������'�e�3��0��=�q�`11�~���Y�'_GPa��;��)����..�_��&���Z_�Pzw�*��v
�AH�R9*���j4�J�?��#{�C��o�����}�N�\]vo��ta{g���o��UT���r,������d���#2��oO.�����w��t��X��c��TD��r���h�`>O�����Q��������K�C@F*x���~%0a���D���p��fyH��;���,d;'�G���i��������}���N��7+]�k9��S2��(7T1g���t��;��2.����kw�{��3?��~�F����D�n��[}�V�O����u��h�C��
g(g�g�6sp��(p�}��!���U��$��)�������
�(ZIa���j���kH��eN�	�� IG/|M�I�>��%�Gu��t������8�{CJ���l�pd�FpE����m�c�T�J'#�D,�#�?!h��"(�d���R��^������6Rr@��@o�/V����^��o�{)�pF����$"5_4�aW������)�xr�Q$j,�'	�����}x|��/b���wP��P���+�ZI�|R�>r�������*��h�b�@�Y��VX(�b�X�O�
XIZ����E�a�"�N��n^��1HP������St%��
��-R
���Z��,����ed`�������EA�i�to�����9=�^LJ���9�9��O�������rS���^g�
�����[7�<Au^��
&u�����Bs��(V���K���T<w8���%H���N��.�b�-���0{���y���C���P|��B�:9���(ZI=����������m�/SC!�l"��D�ES&�D���&Z��X�U�[�r�ij��8n������Y����@v���������N8pjd
'������XZ'K�����I���|(�Q=&�q*_��;)o�(k3��=F�X�"���X��DM>&�Y�dvd����M����g�>l�/��G�o��1
fEY]��V|Q����?�/��a�-��@����O�>�VJ�
1j�Ii
'y:���>�P�$?$X�OH;��g�D�e�/�d���R�8illw@����:�O�&���N9F�)����'��r��1P5���3�2�������b��rQ�������pH��&l��D{To{���Sn��L�M�D`P�d"Q����9%Dx��_.��{��g�%�"���Qb��Bj��n�pw���^��4]S'O�f����K��,{�3��B<n%�
�hC9
�F"(*�K4��������1
i�0srl�������l��	>���,�Q���[�L2�!����>�������6	�%|�X��L�eL�X�j�K���~�8m=B���-�8U�.�6��?����)�q�H���Y�#3��w��)�"�qe�{�@��r&f�l[��B���
���M�������3cv�<����;#2��)��A�cF�g���.����-��`;�R<���6-��C��P�rHh��^�P���dD�c���w�mb��9I~@�<+M��i�(�^��6
`>�����\i��U�)�q�������,��\<��/�i�����8�v������9�X���d��IO�?���Ib�
8E��h�9�M[�v��/�n�i�_d���]fe`G�E}��H@�?���?���Fd���9$��4��g�����-x�����n���@:+4�(�R9��T�g.-3��9d���G���Q�� 3�64�;S�����j��8�oSZ�}f��n�b:`7��
[���}�dm��G�t���HJF��B0q��<"`��)��p:�����M�����
����#�a�+�t�j}��R}dk5�f�=�f�)$��+�8
Y}K����	��G(�n�J3�a@�,�.a��1���O���g����}�\qfF��9���<]'d�:�=�^�K{-��Z���R"Y����
f��D�N/�~��H"��4N�mZ���!h�J{hY�y�q��x��jc$*��4����i<�Zh��9'q)*[H��!"���5���,�$����b_��j<����E��X��J�(b8����]���op#�9��`����p1��Q�
@�Q*oSRG����� �*|��k6j.�I@fB�T�8Dr���sF�
�M��]�1y�
����\�F���^\l
|�x����/��	��n��A$s��/�
%�$D	�d�QzZj�T�p(b���{����)�5V��D@J)��"%#T{������bL��|�>����[D>6�S��R4g#�bFo!^��������HlK��8�`�b���}Odw�A�B���b��j?�:X-��BK��t���zi� �3��C�&�d0���%xbz����������#�R_(�I����F�g�u����g���T�����*3��mW����8\x������Y�{y�]���9�b�&_`ay6:PC�:2����s��V7�U�j�kex�)'��XG�_^�Q$~8��}�]���u�s�~wu����Oj��>��`K�w�W��7��r>�
�g����[�2�93���cox	��&!��#��r3����v:p�!����j�OT^(���wE���#�q(���!�i���7�v��$�<�u�6:�H��-�������v�d�kx���	[c�
��Q�15������`�
�YV�[
����X���� ����N�(9�F@,�0��^�}��5n�]��;W-�T�8g��]�F�t(��]��R CHV�Lfs��H����q�HS&�%K]�5��,�/�,zK���Jqc�����������HIs���/V�C��B�������H[C�B�m��Qql�0��o�~*�zV!�S9@Lc�cy�W����$�	.Q�����V�Y������<4��	\{����]p�^y[/Gqw/f���1Y%�PVK�H������d��`1c+���f+d���DD���mk����F�fr���s]��'$�C���P�"��$�Z�a����<����d�6�R�%�6/!�M��%��)4:��m�;��EfJz�6X�������_t���������W�4�81��$NW�-�JF�e1h�����BK�.��l�L�gd����w2������f����b��E����fk��<��6���;Xe5=$J�����b��pe��`��Y.q���4�u�s�=���@[���E�"e6�����xkf��):
/$���@��������1]hp�Y'���z��d�h/�0T���pzs=�����QT���V������xT���
������'	-��T!���R��Z
U���4,��e5�.w	U��<�(B1*��S`��
u�c0YMp��5�hMX(�L��v�.7Y���<��9�"0��%����H(q�R�%w6�5�]��Y�=*>J|�w�D�����	�Qcs����,���&��`��F����W4���a��}��nP���PN�D�����_���3��^�]>��N���������]��.R��l�0���%}
)�^�3�����K����R�=^�������6�h_l�n�������J�sE2u��!�#��KVlGA��������O~\{�n9>#kR�/eD��c�>q��n��}z�Hh
�
�Eh��G�x .K�~��U����/�f��X�0���4�t�mnT�����l{�F�-��s�B$re�m�AE�j�W�e�I]�+M�����x7b�-_����o:"C]^���:��s������=7��u�(�%U���GxNKLRc�ZxMl�$�Pk�F�n�`�����'��]�B�c~to�cC,l/mh���"���'����.�oF�������<?ur�������X��K��O\#�Ma�J��*��h��cU}k��e��lpX�Kl����l`��@l�\	q4�ds$2��`z���������/�:��'�_�����5��E7�l�N
��i��g���w7����Q���hE�1��q\C���Kn�	*��~����N��o�_���k +Z6G�dqX�o`v~L�|�4��M)-�d[G��J�Z.�������.�MZ{�����pR�������X��-�rm��D���7���g�����'o:�[������7)����UJ��6���\>�������v���]���S���wG����jC���JI&���c�a�/0����|OG�&(Z�HJ����N�j��>����d�"���eS<�
B�t������rH3��1�t,f@�q�!b������T����q��������r����u�/o����n���v������}�m�m���3T�Z_�f�qc}���yt�{y}��5�hQ����������h�����F-NTz"�+�
gL��^��Pu�|
�t��{���O8u���������=���S{���6�Ga�3x �a���d�����{��n�ek�9o\�F��$s���{F��Me�Z���]��N��hr�M2!�:�t��j�_b>^�,�-ny<]L�s#t�?�_���7��%����Zx����G��^�����G�[.DT���#��5�I���4�>�����c�u-<�_��1����<�P�B�J���E�k�u��Z>�
I���%':������(D�������"z�����$���#�|��^H$|�B=}�U����ph�z�8���D9�q��n��m����JWo�=LA����F����5�5���\YQS[�ZQa<c��x��w��'�B�F�F��"���[J�R	�b���3x�}o���+x�G���G�=�X	~h��i��g��>;w���9���-�:�yZzJov���?���H9g��6�V��a��{)�w�s.�8�z"�
��7'?��X!*g��E��>3C�L�����?@���"��}O�md4�:w(�G����UJ�N��k����s��U�;v;���]���N���O����"���Je�O5J�FvN��GF_;�Z�1�.�i���
H0i�Cx�[g�a�,�|��0�T���	zV6����,q<.��2_�m:������W��+_ed���.}1��S(Jl���d� l�����ej�W,�%�%e���d@�������Zb�E"����R����~]����@:aw��vUo�
���h�<���4Y��6%�YV�X��J�_���53��;����Uh��=)({�'7g��xw��;�$r.A�s�KK�z*�j�uD6�z��o��8�!��^���VA��+��MQlq�ay�j�����	)I���5�Oa\�2��M;{`�^H���d{G��2�z�N�������7@�1�Sj�����3u�~��M��-�b9L��D��8��I��
Ds��N�F��=������U�e	'����b�re ��OC����������-\
`8a���>,
NV�����F`�o����r4�9���:���w��'������M�������M��������z�9�zX����C��l:�������������f]Yv�|^_���������X'R���J7o��
���L�YMK��X�f�<���?_�9�D�V�F������~�.�|���>p�20\p�aw*������d@kk�k�/I��6@3����������3���2
j}��]�� c�f)��Mq�N),"�f�@��}�QtUeZ+��w'���Y������O�$?�(R�sr�������#��al���+���d>�Z~q�N�SG���<��v_�t�=dw�]Q�P$�`i���y�Q+�IKy3�[��s;���=7N��h9YB�|��hp�!6/@�����F���B<A{�/?�����0�u.�zW���E�R$V�eb����U����F�"�G-������Q���m[@c���E��m�{sy��
�&��d���h��x���H�R8PQvh]�,[Q��������lB�
��,=��%�LP��|��(
����-�+}#�0��[t��c'��P��6����[�7�����e��?g@�^���k���65�j��������$��s�����7�����W����������'��x	\�F�I�J	cJ���-�2
����$�	�?��oA����>�=���IB�l��7�J��h�!��1"�D��sJ������h�Jbv�6��M��D�f]]��o���e��@L��d��z�@����&�����`:_-{C�h��*3���I�e8%Y�k/��9����&Wi���fH_��X6��dbm��am2j�'��
���#��Lo�<�|-�3��=�\����a-�$H��DV�JuU�?5J�$1i��Woz��]��;�~�e<O��7��/�j���<��O0�+*��Pi<qO������n���@�.f+�O�y<m-E����5�v�)�o������}��.e��z0�f{������~�!q��E���3�[���$�ss%uBf^]83�^L�q���ii��]JJc;	Gon�Y�h_p.�[�g(*��)1�����T��p/$��?�[�~
��y�j���7�L'
"$��@��4P����"J��^��Y�2�)��dE���7�N��Q�.L(��H���(�4L�{��UwX?,�����/'�:BkI��,u:������&���~����?��p�]��U���s����}�(��X=�d��o��H���R�h�t	�L/+�����V-v1��'W�C�Z���5���pod<{*���?�E|$E�^���e���$(��W��&N���A2������@��?��)U����b ������)��C����5������5���|��������2����Z�j�D�n�Iv�;��_PWa��\��c���R�p4�yXR��X���x	L}c����RW�m5Lr�s&�J&�`��:��I\O���sKM������U0��V��e� ��m�`�q88c�0�?��d�j��Q�g��~(�����~L�92t����]����9mw�����
s��Y�o�kFh��6�|UG�������}��2dtVaC�{���c~�_�(zX"z9 k^�D�%�������r
Q�U?j�U^�s���l�Qr��:��"��������!�W�g�����
^�
�wUo��{k����^���=q��L5�!�zb��j�"s�&I��j��4i��N3����e�t�GZL�tuIy4�7�!:k�#�}vHNuDH,<�����t6�U�5i��^������
;�:)��	�B�sx�v�Z�dG��~H��S������+\t���vm�����e�@��X �.�%`A{����YW�i�Z4|����]1l�`�F���u(����a����vcD����h�n���V/���:@-0��o�z��_��>A�Z�w�3+��G��@�={I�;�p{����~=�7��{�����d'�#*(��%t������J���lJ����8f��f�[���O��n7u?
U���j_��nj�lj�E��*��uk�����
R6�	\���������;��dk���z-��)�<����n�v[��V��d3D����f�K�����W������u������������cI1A6��,�3��0K��6DB#������k����@�}:���'Sz���R��b��Bi���M}M����)��9E
�`��9�
�8��g&�[v�puz�x�B��j� �8E����R�U=>n!�\�H�Q��f<������l6G��z����Z����%oU��{7E4"	ON�5�S3
<3�/��(�M1����-�����k��:M������S��"����$$�`>W�j
��,��a�X�\�A���4AK���y�����pL�Z�m,������zP*�e�C��h���B�m���.�'��c
Go��S����o���c-�W�uz���o~�j�,����}~N2�K&��SA�0]�#x�e
GH�$�p����bv���L���)M�?5 �q��+5����e%��b�������l1��`_*��l��
����g���; �y�1A�������	�P45�[����&d.W��0B.W����C���-O@�X����h�X(�=O�W���o��l�-���`8���0x����)�%�����y��!7C�����}�����f����@������#0�5���G��Mh�_l#�6ppR�n�|��������\�����S�PY���N�l�t�����JW����!r
����`�����n#{n��i�%�no����b?�-f�X_�����?��F0��ve��e��b�v����B���;������r��z�z��QV��3V�D-5��OC����g���Ro�>q�&��1 iT�dp����$��eQ������
$�pAm���fl�������9n����Y���,���lja����P{~�7]B$^*V�$	�>�Z)o��
�	A^��d.IH����HQ=7�[Zv�E=-6$A�C���}���Zw�������^3�G����j�YK2��	��*Kc�@����@f����lKi�9*�(e(��,Y�'z���9����5�~�m��q�����0L�F,X����d������b�v����ch�{����J�t��E����D:����X�~n���H
=�6`1���w�9���z���A���M�(��,�v�����E�K�Cbb�]|��n�7�-��M�|�#�D�R�S�E��/m!Y�?D$�/~�U�/�������[uy�m�Zuo��~�i)Q��n9�����V�Z�x���yt4�{���n!kY�2�u�*5T~�+(>�2�`VT�"G�����M M��2/%;��(��[r\����������[<%����eY���V�����`�?d/���\M��c3��"�9��'�������n����>�:�>9?��5���oco�'���8��~{y���~���ay`5S����� ��������6n/zD��j�su*�,�!{OlOO	�����2�[���J�������V���Jo%�u������
8��VSN����9��a��g[�-!���a?��,L�>]G{EQ�B��D�y�<d�$&|����aA_8���HT^���D����w;r�S�4�o�~��	2�r�X��lO^�F#����*`n`1B��pH��a�o�
���3Tw�`��>�%"qz%�S�K�����>�B�dh��R-�i}/��d������1^������D�3�X��F��@�@2��:gGG�����]���>
$��rCuC��_#�]����S����J�(�9]E���K*uE����/��0x��`���#���2���B��tG��Q��ucE�s��o}oN���Vo�����&s�s+aO�6� \
S4g/����lA����+����B�8P�C�^�M=��vE�C�3���,|��^���y������J��Z��;��Uk����Y��%��7��~��9/�u���a�B�dZ�d^M	��~��8�X�xG��,�7Y��V��g���fJ���1|��s��vi��V���"��Y��N9��k�
��>��~�Q������[����~�6����3P'1+e�
���xp���"fG�B�����j"�����@$�]8O�{�o��f��V����j�V��l%s��F�����x��2��y�#���)NuR���kP�
�}�F�]�R�
.�
�q��b���n?�`vQ�S�z�>F��NT�5LP\G��O��f
T����X������Ixm�
_EN?�����cW�3z�
ps�ifH
������xx_���<G�y
�q�uJV�=��c�xI��/�
���P�4��_01��}���+��J�:}\K"��S��i�����{����'���e}�+rHY�����%���� ��lrT���l��<gN;�`�%�Uj�bk�#��L������������������V�����u���q�e�89T.FW�
��6���U��'���~Jvp�Dhi�^)\1t����Z�T�[�"��z�G�"�J�z[>�S
�����,vF��9FGB�;�y���W\��k�*�)��)y��^77��JR�^ys��*��^t����m�}=,��6P�������z��U.����h��9�����)IH����n�E��dR	�L���(� !o����1�V��%�
	w������1r�)Va�����?�b��v
�/�
���&���2��'���3��Qm���[�����Q.���~�x�%�$Z�E��$�=_)O�$�r���{[�'H����4q�������t�}?�'o�%e,V=GSuP�r�LE�Ad������I�K"\������7JX��4��8"��Z�����91��YAw�3���&�U2���\��(�f�)Kdx��H�n�2l���+�FVI4�J�~���R��c���k���N������_�SW����Y�<d��sl36Q���I����������N���j�-�b_�������n�9��W�O5�[8�v#�M�]���eI��;Pd(P�ar�d��T������-q�p�kN9.����4���������Y���P�k�ZZd����=Q��5R��y�
�o��N������-i��j��AnQo]8�.M'�����Sb�����iZ�
P��H��������J�k���z�xp�
����*BAV�R?��;h������xn]����n����t.H�n[�]v���.N���X%�-�<}{rE�"v�zp�:��&��'oR"�w��[@��e�O>����Fe0���j�j3���������RpS9<�G`�n]xG�g�+�(�����������n�|K�����:�\w���_`na��ayWHaD/ T�w���X���$`m���)!3�d�?{�(<(q�\>P" TS`�/�;V�v0?�%�To���cHNw��(���	�}���������]r�0d�9��m�{�O�~���MM?}�_9��o��my*�o3�n���C��;��S��?��c5X�`t���s5�x�(E	������X+T,��H�\�}H���n�  e+���9�/��7����8�.���^;:(�aY-������q�5et	�oSJ����M���~�v��k������t.���R�N�������_���|a�w�w�RG�Ve�*N�Bk��*|*a2��%7V�/bV��W��<Mq�L�s�"���{d�s�@�___�Mx��v���y�����e�"���R(?{F9��e^�N��r&�R&��'�bP�������aFJ��l�;�C������Gw�$x��7����;�����M�B��'����=��H QX���>^X�R�P\����h��l��6��DN)�Y%�,.�#����w�C��S��X?�^������-�$$%�(Y���e�S
���.�q�6�B�������BV��V�	���m��xN�D����}�����X@	���'����p|k'
����uu���IK������6Q��)>C	�Sh���>S�'��=,%T���vnQ�/v�7�L ���]v�05����#}�G�:�+��0�-)3�S�N[������Y�����������Yf��/���U;�������o"����:,2r~H��7�����Q� 
���	�6��j�_.�5��E��m)�S��W�9����<��������?a7�z���!���J�	���I47��Q��w���Y;j7R��[6����A@�3�b&gDk�(�/w�b
H.��:��G���}�$�������6T�x��`���U+,W#������bGM��_�S/z��H������?5o�����t������r����K:B�����K�9�bhT&�E���b8�y	��\��m���_��X>?�WK�J�{5d���S�
���{�(�UF��CA���&m�8i�3N������8������(����[�~X��:q��3�TY}G�
���(���F���vQ�D,8���Sl�q'y��|�4M��N����SY�%���72��L�;E�{�9%�b�F�pYaV+�#���������)��R�����`VL����U@����%���{��d8c�<����NQ�(wU�
�"��!H	��4^R����`8��t6��r
tO�����1����S6��FY����D�,6D:Af�NJ�;/D�������+>��u.��k��Ks�3�t�Rx�Y�L�������������%�=��}�������D��[���2�pX�0	'��l��wW�i��?m9#�61�T�����*�YV{�b3����,�&	����U��/���v�kG���Z?���V���2��hs��O�C9����h25Ry��g�+�;�_`��;8
�[Ct��D���zm����.�m���G`!�"���OG�S����~K���C��|QTL{Z���_.������A���M��UT#ZJ^�#��K����.��`��|���$YqzO�n$+�������I����Ix��8#a��?���~���!h� �vj��rcg�?�������?�������?�������_ov��	
#73Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Petr Jelinek (#70)
Re: Logical Replication WIP

On 10/24/16 9:22 AM, Petr Jelinek wrote:

I added one more prerequisite patch (the first one) which adds ephemeral
slots (or well implements UI on top of the code that was mostly already
there). The ephemeral slots are different in that they go away either on
error or when session is closed. This means the initial data sync does
not have to worry about cleaning up the slots after itself. I think this
will be useful in other places as well (for example basebackup). I
originally wanted to call them temporary slots in the UI but since the
behavior is bit different from temp tables I decided to go with what the
underlying code calls them in UI as well.

I think it makes sense to expose this.

Some of the comments need some polishing.

Eventually, we might want to convert the option list in
CREATE_REPLICATION_SLOT into a list instead of adding more and more
keywords (see also VACUUM), but not necessarily now.

I find the way Acquire and Release are handled now quite confusing.
Because Release of an ephemeral slot means to delete it, you have
changed most code to never release them until the end of the session.
So there is a lot of ugly and confusing code that needs to know this
difference. I think we need to use some different verbs for different
purposes here. Acquire and release should keep their meaning of "I'm
using this", and the calls in proc.c and postgres.c should be something
like ReplicationSlotCleanup().

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#74Petr Jelinek
petr@2ndquadrant.com
In reply to: Peter Eisentraut (#73)
Re: Logical Replication WIP

On 02/11/16 17:22, Peter Eisentraut wrote:

On 10/24/16 9:22 AM, Petr Jelinek wrote:

I added one more prerequisite patch (the first one) which adds ephemeral
slots (or well implements UI on top of the code that was mostly already
there). The ephemeral slots are different in that they go away either on
error or when session is closed. This means the initial data sync does
not have to worry about cleaning up the slots after itself. I think this
will be useful in other places as well (for example basebackup). I
originally wanted to call them temporary slots in the UI but since the
behavior is bit different from temp tables I decided to go with what the
underlying code calls them in UI as well.

I think it makes sense to expose this.

Some of the comments need some polishing.

Eventually, we might want to convert the option list in
CREATE_REPLICATION_SLOT into a list instead of adding more and more
keywords (see also VACUUM), but not necessarily now.

I find the way Acquire and Release are handled now quite confusing.
Because Release of an ephemeral slot means to delete it, you have
changed most code to never release them until the end of the session.
So there is a lot of ugly and confusing code that needs to know this
difference. I think we need to use some different verbs for different
purposes here. Acquire and release should keep their meaning of "I'm
using this", and the calls in proc.c and postgres.c should be something
like ReplicationSlotCleanup().

Release does not really change behavior, it has always dropped ephemeral
slot.

So if I understand correctly what you are proposing is to change
behavior of Release to not remove ephemeral slot, add function that
removes the ephemeral slots of current session and add tracking of
ephemeral slots created in current session? That seems like quite more
complicated than what the patch does with little gain.

What about just releasing the ephemeral slot if the different one is
being acquired instead of the current error?

--
Petr Jelinek 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

#75Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Petr Jelinek (#74)
Re: Logical Replication WIP

On 11/3/16 9:31 AM, Petr Jelinek wrote:

Release does not really change behavior, it has always dropped ephemeral
slot.

Well, currently ephemeral is just a temporary state while a slot is
being created. It's not really something that can exist independently.
You might as well call it RS_NOTREADY. Therefore, dropping the slot
when you de-acquire (release) it makes sense.

But what you want is a slot that exists across acquire/release but it
dropped at the end of the session. And what is implicit is that the
slot is only usable by one session, so you don't really need to ever
"release" it for use by other sessions. And so half the Release calls
have been changed to Release-if-persistent, but it's not explained why
in each case. It all seems to work OK, but there are a lot of hidden
assumptions in each case that make it hard to follow.

So if I understand correctly what you are proposing is to change
behavior of Release to not remove ephemeral slot, add function that
removes the ephemeral slots of current session and add tracking of
ephemeral slots created in current session? That seems like quite more
complicated than what the patch does with little gain.

What about just releasing the ephemeral slot if the different one is
being acquired instead of the current error?

Maybe that would help reducing some of the mystery about when you have
to call Release and when ReleasePersistent (better called
ReleaseIfPersistent).

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#76Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Petr Jelinek (#70)
Re: Logical Replication WIP

On 10/24/16 9:22 AM, Petr Jelinek wrote:

I also split out the libpqwalreceiver rewrite to separate patch which
does just the re-architecture and does not really add new functionality.
And I did the re-architecture bit differently based on the review.

That looks good to me, and it appears to address the previous discussions.

I wouldn't change walrcv_xxx to walrcvconn_xxx. If we're going to have
macros to hide the internals, we might as well keep the names the same.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#77Andres Freund
andres@anarazel.de
In reply to: Petr Jelinek (#72)
Re: Logical Replication WIP

/*
* Replication slot on-disk data structure.
@@ -225,10 +226,25 @@ ReplicationSlotCreate(const char *name, bool db_specific,
ReplicationSlot *slot = NULL;
int i;

-	Assert(MyReplicationSlot == NULL);
+	/* Only aka ephemeral slots can survive across commands. */

What does this comment mean?

+	Assert(!MyReplicationSlot ||
+		   MyReplicationSlot->data.persistency == RS_EPHEMERAL);
+	if (MyReplicationSlot)
+	{
+		/* Already acquired? Nothis to do. */

typo.

+		if (namestrcmp(&MyReplicationSlot->data.name, name) == 0)
+			return;
+
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot create replication slot %s, another slot %s is "
+						"already active in this session",
+						name, NameStr(MyReplicationSlot->data.name))));
+	}
+

Why do we now create slots that are already created? That seems like an
odd API change.

/*
* If some other backend ran this code concurrently with us, we'd likely
* both allocate the same slot, and that would be bad. We'd also be at
@@ -331,10 +347,25 @@ ReplicationSlotAcquire(const char *name)
int i;
int active_pid = 0;

-	Assert(MyReplicationSlot == NULL);
+	/* Only aka ephemeral slots can survive across commands. */
+	Assert(!MyReplicationSlot ||
+		   MyReplicationSlot->data.persistency == RS_EPHEMERAL);

ReplicationSlotValidateName(name, ERROR);

+	if (MyReplicationSlot)
+	{
+		/* Already acquired? Nothis to do. */
+		if (namestrcmp(&MyReplicationSlot->data.name, name) == 0)
+			return;
+
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot acquire replication slot %s, another slot %s is "
+						"already active in this session",
+						name, NameStr(MyReplicationSlot->data.name))));
+	}
+
 	/* Search for the named slot and mark it active if we find it. */
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 	for (i = 0; i < max_replication_slots; i++)
@@ -406,12 +437,26 @@ ReplicationSlotRelease(void)
 }

Uh? We shouldn't ever have to acquire ephemeral

 /*
+ * Same as above but only if currently acquired slot is peristent one.
+ */

s/peristent/persistent/

+void
+ReplicationSlotReleasePersistent(void)
+{
+	Assert(MyReplicationSlot);
+
+	if (MyReplicationSlot->data.persistency == RS_PERSISTENT)
+		ReplicationSlotRelease();
+}

Ick.

Hm. I think I have to agree a bit with Peter here. Overloading
MyReplicationSlot this way seems ugly, and I think there's a bunch of
bugs around it too.

Sounds what we really want is a) two different lifetimes for ephemeral
slots, session and "command" b) have a number of slots that are released
either after a failed transaction / command or at session end. The
easiest way for that appears to have a list of slots to be checked at
end-of-xact and backend shutdown.

Regards,

Andres

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

#78Andres Freund
andres@anarazel.de
In reply to: Petr Jelinek (#72)
Re: Logical Replication WIP

Hi,

/* Prototypes for interface functions */
-static void libpqrcv_connect(char *conninfo);
-static char *libpqrcv_get_conninfo(void);
-static void libpqrcv_identify_system(TimeLineID *primary_tli);
-static void libpqrcv_readtimelinehistoryfile(TimeLineID tli, char **filename, char **content, int *len);
-static bool libpqrcv_startstreaming(TimeLineID tli, XLogRecPtr startpoint,
-						char *slotname);
-static void libpqrcv_endstreaming(TimeLineID *next_tli);
-static int	libpqrcv_receive(char **buffer, pgsocket *wait_fd);
-static void libpqrcv_send(const char *buffer, int nbytes);
-static void libpqrcv_disconnect(void);
+static WalReceiverConn *libpqrcv_connect(char *conninfo,
+										 bool logical, const char *appname);
+static char *libpqrcv_get_conninfo(WalReceiverConn *conn);
+static char *libpqrcv_identify_system(WalReceiverConn *conn,
+									  TimeLineID *primary_tli);
+static void libpqrcv_readtimelinehistoryfile(WalReceiverConn *conn,
+								 TimeLineID tli, char **filename,
+								 char **content, int *len);
+static bool libpqrcv_startstreaming(WalReceiverConn *conn,
+							 TimeLineID tli, XLogRecPtr startpoint,
+							 const char *slotname);
+static void libpqrcv_endstreaming(WalReceiverConn *conn,
+								  TimeLineID *next_tli);
+static int	libpqrcv_receive(WalReceiverConn *conn, char **buffer,
+							 pgsocket *wait_fd);
+static void libpqrcv_send(WalReceiverConn *conn, const char *buffer,
+						  int nbytes);
+static void libpqrcv_disconnect(WalReceiverConn *conn);

That looks good.

 /* Prototypes for private functions */
-static bool libpq_select(int timeout_ms);
+static bool libpq_select(PGconn *streamConn,
+						 int timeout_ms);

If we're starting to use this more widely, we really should just a latch
instead of the plain select(). In fact, I think it's more or less a bug
that we don't (select is only interruptible by signals on a subset of
our platforms). That shouldn't bother this patch, but...

This looks pretty close to committable, Peter do you want to do that, or
should I?

Andres

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

#79Andres Freund
andres@anarazel.de
In reply to: Petr Jelinek (#72)
Re: Logical Replication WIP

Hi,

+ <sect1 id="catalog-pg-publication-rel">
+  <title><structname>pg_publication_rel</structname></title>
+
+  <indexterm zone="catalog-pg-publication-rel">
+   <primary>pg_publication_rel</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_publication_rel</structname> catalog contains
+   mapping between tables and publications in the database. This is many to
+   many mapping.
+  </para>

I wonder if we shouldn't abstract this a bit away from relations to
allow other objects to be exported to. Could structure it a bit more
like pg_depend.

+ALTER PUBLICATION <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replaceable class="PARAMETER">option</replaceable> [ ... ] ]
+
+<phrase>where <replaceable class="PARAMETER">option</replaceable> can be:</phrase>
+
+      PuBLISH_INSERT | NOPuBLISH_INSERT
+    | PuBLISH_UPDATE | NOPuBLISH_UPDATE
+    | PuBLISH_DELETE | NOPuBLISH_DELETE

That's odd casing.

+   <varlistentry>
+    <term><literal>PuBLISH_INSERT</literal></term>
+    <term><literal>NOPuBLISH_INSERT</literal></term>
+    <term><literal>PuBLISH_UPDATE</literal></term>
+    <term><literal>NOPuBLISH_UPDATE</literal></term>
+    <term><literal>PuBLISH_DELETE</literal></term>
+    <term><literal>NOPuBLISH_DELETE</literal></term>

More odd casing.

+   <varlistentry>
+    <term><literal>FOR TABLE</literal></term>
+    <listitem>
+     <para>
+      Specifies optional list of tables to add to the publication.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>FOR TABLE ALL IN SCHEMA</literal></term>
+    <listitem>
+     <para>
+      Specifies optional schema for which all logged tables will be added to
+      publication.
+     </para>
+    </listitem>
+   </varlistentry>

"FOR TABLE ALL IN SCHEMA" sounds weird.

+  <para>
+   This operation does not reserve any resources on the server. It only
+   defines grouping and filtering logic for future subscribers.
+  </para>

That's strictly speaking not true, maybe rephrase a bit?

+/*
+ * Check if relation can be in given publication and throws appropriate
+ * error if not.
+ */
+static void
+check_publication_add_relation(Relation targetrel)
+{
+	/* Must be table */
+	if (RelationGetForm(targetrel)->relkind != RELKIND_RELATION)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("only tables can be added to publication"),
+				 errdetail("%s is not a table",
+						   RelationGetRelationName(targetrel))));
+
+	/* Can't be system table */
+	if (IsCatalogRelation(targetrel))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("only user tables can be added to publication"),
+				 errdetail("%s is a system table",
+						   RelationGetRelationName(targetrel))));
+
+	/* UNLOGGED and TEMP relations cannot be part of publication. */
+	if (!RelationNeedsWAL(targetrel))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("UNLOGGED and TEMP relations cannot be replicated")));
+}

This probably means we need a check in the ALTER TABLE ... SET UNLOGGED
path.

+/*
+ * Returns if relation represented by oid and Form_pg_class entry
+ * is publishable.
+ *
+ * Does same checks as the above, but does not need relation to be opened
+ * and also does not throw errors.
+ */
+static bool
+is_publishable_class(Oid relid, Form_pg_class reltuple)
+{
+	return reltuple->relkind == RELKIND_RELATION &&
+		!IsCatalogClass(relid, reltuple) &&
+		reltuple->relpersistence == RELPERSISTENCE_PERMANENT &&
+		/* XXX needed to exclude information_schema tables */
+		relid >= FirstNormalObjectId;
+}

Shouldn't that be IsCatalogRelation() instead?

+CREATE VIEW pg_publication_tables AS
+    SELECT
+        P.pubname AS pubname,
+        N.nspname AS schemaname,
+        C.relname AS tablename
+    FROM pg_publication P, pg_class C
+         JOIN pg_namespace N ON (N.oid = C.relnamespace)
+    WHERE C.relkind = 'r'
+      AND C.oid IN (SELECT relid FROM pg_get_publication_tables(P.pubname));

That's going to be quite inefficient if you filter by table... Might be
better to do that via the underlying table.

+/*
+ * Create new publication.
+ * TODO ACL check
+ */

Hm?

+ObjectAddress
+CreatePublication(CreatePublicationStmt *stmt)
+{
+	check_replication_permissions();
+
+/*
+ * Drop publication by OID
+ */
+void
+DropPublicationById(Oid pubid)
+
+/*
+ * Remove relation from publication by mapping OID.
+ */
+void
+RemovePublicationRelById(Oid proid)
+{

Permission checks?

+}

Hm. Neither of these does dependency checking, wonder if that can be
argued to be problematic.

+/*
+ * Gather Relations based o provided by RangeVar list.
+ * The gathered tables are locked in ShareUpdateExclusiveLock mode.
+ */

s/o/on/. Not sure if gather is the best name.

+static List *
+GatherTableList(List *tables)

+/*
+ * Close all relations in the list.
+ */
+static void
+CloseTables(List *rels)

Shouldn't that be CloseTableList() based on the preceding function's naming?

+
+/*
+ * Add listed tables to the publication.
+ */
+static void
+PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
+					 AlterPublicationStmt *stmt)
+{
+	ListCell	   *lc;
+
+	Assert(!stmt || !stmt->for_all_tables);
+
+	foreach(lc, rels)
+	{
+		Relation	rel = (Relation) lfirst(lc);
+		ObjectAddress	obj;
+
+		obj = publication_add_relation(pubid, rel, if_not_exists);
+		if (stmt)
+			EventTriggerCollectSimpleCommand(obj, InvalidObjectAddress,
+											 (Node *) stmt);
+	}
+}
+
+/*
+ * Remove listed tables to the publication.
+ */

s/to/from/

+static void
+PublicationDropTables(Oid pubid, List *rels, bool missing_ok)
+{
+	ObjectAddress	obj;
+	ListCell	   *lc;
+	Oid				prid;
+
+	foreach(lc, rels)
+	{
+		Relation	rel = (Relation) lfirst(lc);
+		Oid			relid = RelationGetRelid(rel);
+
+		prid = GetSysCacheOid2(PUBLICATIONRELMAP, ObjectIdGetDatum(relid),
+							   ObjectIdGetDatum(pubid));
+		if (!OidIsValid(prid))
+		{
+			if (missing_ok)
+				continue;
+
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("relation \"%s\" is not part of the publication",
+							RelationGetRelationName(rel))));
+		}
+
+		ObjectAddressSet(obj, PublicationRelRelationId, prid);
+		performDeletion(&obj, DROP_CASCADE, 0);
+	}
+}
/*
+ * Check if command can be executed with current replica identity.
+ */
+static void
+CheckCmdReplicaIdentity(Relation rel, CmdType cmd)
+{
+	PublicationActions *pubactions;
+
+	/* We only need to do checks for UPDATE and DELETE. */
+	if (cmd != CMD_UPDATE && cmd != CMD_DELETE)
+		return;
+
+	/* If relation has replica identity we are always good. */
+	if (rel->rd_rel->relreplident == REPLICA_IDENTITY_FULL ||
+		OidIsValid(RelationGetReplicaIndex(rel)))
+		return;
+
+	/*
+	 * This is either UPDATE OR DELETE and there is no replica identity.
+	 *
+	 * Check if the table publishes UPDATES or DELETES.
+	 */
+	pubactions = GetRelationPublicationActions(rel);
+	if (pubactions->pubupdate || pubactions->pubdelete)

I think that leads to spurious errors. Consider a DELETE with a
publication that replicates updates but not deletes.

+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot update table \"%s\" because it does not have replica identity and publishes updates",
+						RelationGetRelationName(rel)),
+				 errhint("To enable updating the table, provide set REPLICA IDENTITY using ALTER TABLE.")));
+}

"provide set"

+publication_opt_item:
+			IDENT
+				{
+					/*
+					 * We handle identifiers that aren't parser keywords with
+					 * the following special-case codes, to avoid bloating the
+					 * size of the main parser.
+					 */
+					if (strcmp($1, "publish_insert") == 0)
+						$$ = makeDefElem("publish_insert",
+										 (Node *)makeInteger(TRUE), @1);
+					else if (strcmp($1, "nopublish_insert") == 0)
+						$$ = makeDefElem("publish_insert",
+										 (Node *)makeInteger(FALSE), @1);
+					else if (strcmp($1, "publish_update") == 0)
+						$$ = makeDefElem("publish_update",
+										 (Node *)makeInteger(TRUE), @1);
+					else if (strcmp($1, "nopublish_update") == 0)
+						$$ = makeDefElem("publish_update",
+										 (Node *)makeInteger(FALSE), @1);
+					else if (strcmp($1, "publish_delete") == 0)
+						$$ = makeDefElem("publish_delete",
+										 (Node *)makeInteger(TRUE), @1);
+					else if (strcmp($1, "nopublish_delete") == 0)
+						$$ = makeDefElem("publish_delete",
+										 (Node *)makeInteger(FALSE), @1);
+					else
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("unrecognized publication option \"%s\"", $1),
+									 parser_errposition(@1)));
+				}
+		;

I still would very much like to move this outside of gram.y and just use
IDENTs here. Like how COPY options are handled.

+/*
+ * Get publication actions for list of publication oids.
+ */
+struct PublicationActions *
+GetRelationPublicationActions(Relation relation)

API description and function name/parameters don't quite match.

+CATALOG(pg_publication,6104)
+{
+	NameData	pubname;			/* name of the publication */
+
+	/*
+	 * indicates that this is special publication which should encompass
+	 * all tables in the database (except for the unlogged and temp ones)
+	 */
+	bool		puballtables;
+
+	/* true if inserts are published */
+	bool		pubinsert;
+
+	/* true if updates are published */
+	bool		pubupdate;
+
+	/* true if deletes are published */
+	bool		pubdelete;
+
+} FormData_pg_publication;

Shouldn't this have an owner? I also wonder if we want an easier to
extend form of pubinsert/update/delete (say to add pubddl, pubtruncate,
pub ... without changing the schema).

+/* ----------------
+ *		pg_publication_rel definition.  cpp turns this into
+ *		typedef struct FormData_pg_publication_rel
+ *
+ * ----------------
+ */
+#define PublicationRelRelationId				6106
+
+CATALOG(pg_publication_rel,6106)
+{
+	Oid		prpubid;				/* Oid of the publication */
+	Oid		prrelid;				/* Oid of the relation */
+} FormData_pg_publication_rel;

To me it seems like a good idea to have objclassid/objsubid here.

Regards,

Andres

--
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@anarazel.de
In reply to: Petr Jelinek (#72)
Re: Logical Replication WIP

Hi,

(btw, I vote against tarballing patches)

+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry><structfield>oid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry></entry>
+      <entry>Row identifier (hidden attribute; must be explicitly selected)</entry>
+     </row>
+
+     <row>
+      <entry><structfield>subpublications</structfield></entry>
+      <entry><type>name[]</type></entry>
+      <entry></entry>
+      <entry>Array of subscribed publication names. These reference the
+       publications on the publisher server.
+      </entry>

Why is this names and not oids? So you can see it across databases?

I think this again should have an owner.

 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 68d7e46..523008d 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -112,6 +112,7 @@ static event_trigger_support_data event_trigger_support[] = {
 	{"SCHEMA", true},
 	{"SEQUENCE", true},
 	{"SERVER", true},
+	{"SUBSCRIPTION", true},

Hm, is that ok? Subscriptions are shared, so ...?

+		/*
+		 * If requested, create the replication slot on remote side for our
+		 * newly created subscription.
+		 *
+		 * Note, we can't cleanup slot in case of failure as reason for
+		 * failure might be already existing slot of the same name and we
+		 * don't want to drop somebody else's slot by mistake.
+		 */
+		if (create_slot)
+		{
+			XLogRecPtr			lsn;
+
+			/*
+			 * Create the replication slot on remote side for our newly created
+			 * subscription.
+			 *
+			 * Note, we can't cleanup slot in case of failure as reason for
+			 * failure might be already existing slot of the same name and we
+			 * don't want to drop somebody else's slot by mistake.
+			 */

We should really be able to recognize that based on the error code...

+/*
+ * Drop subscription by OID
+ */
+void
+DropSubscriptionById(Oid subid)
+{
+	/*
+	 * We must ignore errors here as that would make it impossible to drop
+	 * subscription when publisher is down.
+	 */

I'm not convinced. Leaving a slot around without a "record" of it on
the creating side isn't nice either. Maybe a FORCE flag or something?

+subscription_create_opt_item:
+			subscription_opt_item
+			| INITIALLY IDENT
+				{
+					if (strcmp($2, "enabled") == 0)
+						$$ = makeDefElem("enabled",
+										 (Node *)makeInteger(TRUE), @1);
+					else if (strcmp($2, "disabled") == 0)
+						$$ = makeDefElem("enabled",
+										 (Node *)makeInteger(FALSE), @1);
+					else
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("unrecognized subscription option \"%s\"", $1),
+									 parser_errposition(@2)));
+				}
+			| IDENT
+				{
+					if (strcmp($1, "create_slot") == 0)
+						$$ = makeDefElem("create_slot",
+										 (Node *)makeInteger(TRUE), @1);
+					else if (strcmp($1, "nocreate_slot") == 0)
+						$$ = makeDefElem("create_slot",
+										 (Node *)makeInteger(FALSE), @1);
+				}
+		;

Hm, the IDENT case ignores $1 if it's not create_slot/nocreate_slot and
thus leaves $$ uninitialized?

I again really would like to have the error checking elsewhere.

- Andres

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

#81Steve Singer
steve@ssinger.info
In reply to: Petr Jelinek (#72)
Re: Logical Replication WIP

On 10/31/2016 06:38 AM, Petr Jelinek wrote:

There are some fundamental issues with initial sync that need to be
discussed on list but this one is not known. I'll try to convert this
to test case (seems like useful one) and fix it, thanks for the
report. In meantime I realized I broke the last patch in the series
during rebase so attached is the fixed version. It also contains the
type info in the protocol.

I don't know if this is covered by the known initial_sync problems or not

If I have a 'all tables' publication and then create a new table the
data doesn't seem to replicate to the new table.

P: create table a(a serial4 primary key, b text);
S: create table a(a serial4 primary key, b text);
P: create publication mypub for all tables;
S: create subscription mysub connection 'host=localhost dbname=test
port=5441' publication mypub;
P: create table b(a serial4 primary key, b text);
P: insert into b(b) values ('foo2');
P: insert into a(b) values ('foo3');

Then I check my subscriber

select * FROM a;
a | b
---+------
1 | foo
2 | foo3
(2 rows)

test=# select * FROM b;
a | b
---+---
(0 rows)

However, if the table isn't on the subscriber I do get an error:

ie

P: create table c(a serial4 primary key, b text);
P: insert into c(b) values('foo');

2016-11-05 11:49:31.456 EDT [14938] FATAL: the logical replication
target public.c not found
2016-11-05 11:49:31.457 EDT [13703] LOG: worker process: logical
replication worker 16457 (PID 14938) exited with exit code 1

but if then add the table
S: create table c(a serial4 primary key, b text);
2016-11-05 11:51:08.583 EDT [15014] LOG: logical replication apply for
subscription mysub started

but the data doesn't replicate to table c either.

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

#82Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Petr Jelinek (#72)
Re: Logical Replication WIP

Review of v7 0003-Add-PUBLICATION-catalogs-and-DDL.patch:

This appears to address previous reviews and is looking pretty solid. I
have some comments that are easily addressed:

[still from previous review] The code for OCLASS_PUBLICATION_REL in
getObjectIdentityParts() does not fill in objname and objargs, as it is
supposed to.

catalog.sgml: pg_publication_rel column names must be updated after renaming

alter_publication.sgml and elsewhere: typos PuBLISH_INSERT etc.

create_publication.sgml: FOR TABLE ALL IN SCHEMA does not exist anymore

create_publication.sgml: talks about not-yet-existing SUBSCRIPTION role

DropPublicationById maybe name RemovePublicationById for consistency

system_views.sql: C.relkind = 'r' unnecessary

CheckCmdReplicaIdentity: error message says "cannot update", should
distinguish between update and delete

relcache.c: pubactions->pubinsert |= pubform->pubinsert; etc. should be ||=

RelationData.rd_pubactions could be a bitmap, simplifying some memcpy
and context management. But RelationData appears to favor rich data
structures, so maybe that is fine.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#83Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Andres Freund (#79)
Re: Logical Replication WIP

On 11/4/16 9:00 AM, Andres Freund wrote:

+  <para>
+   The <structname>pg_publication_rel</structname> catalog contains
+   mapping between tables and publications in the database. This is many to
+   many mapping.
+  </para>

I wonder if we shouldn't abstract this a bit away from relations to
allow other objects to be exported to. Could structure it a bit more
like pg_depend.

I think we can add/change that when we have use for it.

+   <varlistentry>
+    <term><literal>FOR TABLE ALL IN SCHEMA</literal></term>
+    <listitem>
+     <para>
+      Specifies optional schema for which all logged tables will be added to
+      publication.
+     </para>
+    </listitem>
+   </varlistentry>

"FOR TABLE ALL IN SCHEMA" sounds weird.

That clause no longer exists anyway.

+  <para>
+   This operation does not reserve any resources on the server. It only
+   defines grouping and filtering logic for future subscribers.
+  </para>

That's strictly speaking not true, maybe rephrase a bit?

Maybe the point is that it does not initiate any contact with remote nodes.

+/*
+ * Create new publication.
+ * TODO ACL check
+ */

Hm?

The first patch is going to be just superuser and replication role. I'm
working on a patch set for later that adds proper ACLs, owners, and all
that. So I'd suggest to ignore these details for now, unless of course
you find permission checks *missing*.

+/*
+ * Drop publication by OID
+ */
+void
+DropPublicationById(Oid pubid)
+
+/*
+ * Remove relation from publication by mapping OID.
+ */
+void
+RemovePublicationRelById(Oid proid)
+{

Permission checks?

+}

Hm. Neither of these does dependency checking, wonder if that can be
argued to be problematic.

The dependency checking is done before it gets to these functions, no?

/*
+ * Check if command can be executed with current replica identity.
+ */
+static void
+CheckCmdReplicaIdentity(Relation rel, CmdType cmd)
+{
+	PublicationActions *pubactions;
+
+	/* We only need to do checks for UPDATE and DELETE. */
+	if (cmd != CMD_UPDATE && cmd != CMD_DELETE)
+		return;
+
+	/* If relation has replica identity we are always good. */
+	if (rel->rd_rel->relreplident == REPLICA_IDENTITY_FULL ||
+		OidIsValid(RelationGetReplicaIndex(rel)))
+		return;
+
+	/*
+	 * This is either UPDATE OR DELETE and there is no replica identity.
+	 *
+	 * Check if the table publishes UPDATES or DELETES.
+	 */
+	pubactions = GetRelationPublicationActions(rel);
+	if (pubactions->pubupdate || pubactions->pubdelete)

I think that leads to spurious errors. Consider a DELETE with a
publication that replicates updates but not deletes.

Yeah, it needs to check the pubactions against the specific command.

+} FormData_pg_publication;

Shouldn't this have an owner?

Yes, see above.

I also wonder if we want an easier to
extend form of pubinsert/update/delete (say to add pubddl, pubtruncate,
pub ... without changing the schema).

Maybe, but how? (without using weird array constructs that are a pain
to parse in psql and pg_dump, for example)

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#84Petr Jelinek
petr@2ndquadrant.com
In reply to: Andres Freund (#78)
Re: Logical Replication WIP

On 04/11/16 13:15, Andres Freund wrote:

/* Prototypes for private functions */
-static bool libpq_select(int timeout_ms);
+static bool libpq_select(PGconn *streamConn,
+						 int timeout_ms);

If we're starting to use this more widely, we really should just a latch
instead of the plain select(). In fact, I think it's more or less a bug
that we don't (select is only interruptible by signals on a subset of
our platforms). That shouldn't bother this patch, but...

Agree that this is problem, especially for the subscription creation
later. We should be doing WaitLatchOrSocket, but the question is which
latch. We can't use MyProc one as that's not the latch that WalReceiver
uses so I guess we would have to send latch as parameter to any caller
of this which is not very pretty from api perspective but I don't have
better idea here.

--
Petr Jelinek 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

#85Petr Jelinek
petr@2ndquadrant.com
In reply to: Andres Freund (#77)
1 attachment(s)
Re: Logical Replication WIP

On 04/11/16 13:07, Andres Freund wrote:

Hm. I think I have to agree a bit with Peter here. Overloading
MyReplicationSlot this way seems ugly, and I think there's a bunch of
bugs around it too.

Sounds what we really want is a) two different lifetimes for ephemeral
slots, session and "command" b) have a number of slots that are released
either after a failed transaction / command or at session end. The
easiest way for that appears to have a list of slots to be checked at
end-of-xact and backend shutdown.

Ok so how about attached? It adds temp slots as new type of persistence.
It does not really touch the behavior of any of the existing API or
persistence settings.

The temp slots are just cleaned up on backend exit or error, other than
that they are not special. I don't use any specific backend local list
to track them, instead they have active_pid always set and just cleanup
everything that has that set at the end of the session. This has nice
property that it forbids other backends for acquiring them.

It does not do any locking while searching for the slots to cleanup (see
ReplicationSlotCleanup), mainly because it complicates the interaction
with ReplicationSlotDropPtr and it seems to me that locking there is not
really needed there as other backends will never change active_pid to
our backend pid and then the ReplicationSlotDropPtr does exclusive lock
when resetting it.

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

Attachments:

0001-Add-support-for-TEMPORARY-replication-slots.patchtext/x-diff; name=0001-Add-support-for-TEMPORARY-replication-slots.patchDownload
From 1cf0aca7f1405f31229ab679c9451b51a8cc18de Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Wed, 28 Sep 2016 23:36:58 +0200
Subject: [PATCH 1/7] Add support for TEMPORARY replication slots

This patch allows creating temporary replication slots that are removed
automatically at the end of the session or on error.
---
 contrib/test_decoding/Makefile          |  2 +-
 contrib/test_decoding/expected/ddl.out  |  4 +-
 contrib/test_decoding/expected/slot.out | 35 ++++++++++++++++
 contrib/test_decoding/sql/slot.sql      | 13 ++++++
 doc/src/sgml/func.sgml                  | 16 ++++++--
 doc/src/sgml/protocol.sgml              | 13 +++++-
 src/backend/catalog/system_views.sql    | 11 +++++
 src/backend/replication/repl_gram.y     | 22 ++++++----
 src/backend/replication/repl_scanner.l  |  1 +
 src/backend/replication/slot.c          | 72 ++++++++++++++++++++++++++-------
 src/backend/replication/slotfuncs.c     | 24 +++++++----
 src/backend/replication/walsender.c     | 28 ++++++++-----
 src/backend/storage/lmgr/proc.c         |  3 ++
 src/backend/tcop/postgres.c             |  3 ++
 src/include/catalog/pg_proc.h           |  6 +--
 src/include/nodes/replnodes.h           |  1 +
 src/include/replication/slot.h          |  4 +-
 src/test/regress/expected/rules.out     |  3 +-
 18 files changed, 209 insertions(+), 52 deletions(-)
 create mode 100644 contrib/test_decoding/expected/slot.out
 create mode 100644 contrib/test_decoding/sql/slot.sql

diff --git a/contrib/test_decoding/Makefile b/contrib/test_decoding/Makefile
index a6641f5..d2bc8b8 100644
--- a/contrib/test_decoding/Makefile
+++ b/contrib/test_decoding/Makefile
@@ -39,7 +39,7 @@ submake-test_decoding:
 
 REGRESSCHECKS=ddl xact rewrite toast permissions decoding_in_xact \
 	decoding_into_rel binary prepared replorigin time messages \
-	spill
+	spill slot
 
 regresscheck: | submake-regress submake-test_decoding temp-install
 	$(MKDIR_P) regression_output
diff --git a/contrib/test_decoding/expected/ddl.out b/contrib/test_decoding/expected/ddl.out
index 7fbeafd..84ab7d3 100644
--- a/contrib/test_decoding/expected/ddl.out
+++ b/contrib/test_decoding/expected/ddl.out
@@ -702,7 +702,7 @@ SELECT pg_drop_replication_slot('regression_slot');
 
 /* check that the slot is gone */
 SELECT * FROM pg_replication_slots;
- slot_name | plugin | slot_type | datoid | database | active | active_pid | xmin | catalog_xmin | restart_lsn | confirmed_flush_lsn 
------------+--------+-----------+--------+----------+--------+------------+------+--------------+-------------+---------------------
+ slot_name | plugin | slot_type | datoid | database | persistent | active | active_pid | xmin | catalog_xmin | restart_lsn | confirmed_flush_lsn 
+-----------+--------+-----------+--------+----------+------------+--------+------------+------+--------------+-------------+---------------------
 (0 rows)
 
diff --git a/contrib/test_decoding/expected/slot.out b/contrib/test_decoding/expected/slot.out
new file mode 100644
index 0000000..28b2f89
--- /dev/null
+++ b/contrib/test_decoding/expected/slot.out
@@ -0,0 +1,35 @@
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slotp', 'test_decoding');
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slote', 'test_decoding', true);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT pg_drop_replication_slot('regression_slotp');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slotp', 'test_decoding', false);
+ ?column? 
+----------
+ init
+(1 row)
+
+-- reconnect to clean temp slots
+\c
+SELECT pg_drop_replication_slot('regression_slotp');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+-- should fail because the slot was dropped automatically
+SELECT pg_drop_replication_slot('regression_slote');
+ERROR:  replication slot "regression_slote" does not exist
diff --git a/contrib/test_decoding/sql/slot.sql b/contrib/test_decoding/sql/slot.sql
new file mode 100644
index 0000000..839a440
--- /dev/null
+++ b/contrib/test_decoding/sql/slot.sql
@@ -0,0 +1,13 @@
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slotp', 'test_decoding');
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slote', 'test_decoding', true);
+
+SELECT pg_drop_replication_slot('regression_slotp');
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slotp', 'test_decoding', false);
+
+-- reconnect to clean temp slots
+\c
+
+SELECT pg_drop_replication_slot('regression_slotp');
+
+-- should fail because the slot was dropped automatically
+SELECT pg_drop_replication_slot('regression_slote');
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 2e64cc4..0f37ddc 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -18464,7 +18464,7 @@ postgres=# SELECT * FROM pg_xlogfile_name_offset(pg_stop_backup());
         <indexterm>
          <primary>pg_create_physical_replication_slot</primary>
         </indexterm>
-        <literal><function>pg_create_physical_replication_slot(<parameter>slot_name</parameter> <type>name</type> <optional>, <parameter>immediately_reserve</> <type>boolean</> </optional>)</function></literal>
+        <literal><function>pg_create_physical_replication_slot(<parameter>slot_name</parameter> <type>name</type> <optional>, <parameter>immediately_reserve</> <type>boolean</>, <parameter>temporary</> <type>boolean</></optional>)</function></literal>
        </entry>
        <entry>
         (<parameter>slot_name</parameter> <type>name</type>, <parameter>xlog_position</parameter> <type>pg_lsn</type>)
@@ -18477,7 +18477,11 @@ postgres=# SELECT * FROM pg_xlogfile_name_offset(pg_stop_backup());
         the <acronym>LSN</> is reserved on first connection from a streaming
         replication client. Streaming changes from a physical slot is only
         possible with the streaming-replication protocol &mdash;
-        see <xref linkend="protocol-replication">. This function corresponds
+        see <xref linkend="protocol-replication">. The optional third
+        parameter, <parameter>temporary</>, when set to true, specifies that
+        the slot should not be permanently stored to disk and is only meant
+        for use by current session. Note that temporary slots are also
+        released upon any error. This function corresponds
         to the replication protocol command <literal>CREATE_REPLICATION_SLOT
         ... PHYSICAL</literal>.
        </entry>
@@ -18504,7 +18508,7 @@ postgres=# SELECT * FROM pg_xlogfile_name_offset(pg_stop_backup());
         <indexterm>
          <primary>pg_create_logical_replication_slot</primary>
         </indexterm>
-        <literal><function>pg_create_logical_replication_slot(<parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type>)</function></literal>
+        <literal><function>pg_create_logical_replication_slot(<parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</> <type>boolean</></optional>)</function></literal>
        </entry>
        <entry>
         (<parameter>slot_name</parameter> <type>name</type>, <parameter>xlog_position</parameter> <type>pg_lsn</type>)
@@ -18512,7 +18516,11 @@ postgres=# SELECT * FROM pg_xlogfile_name_offset(pg_stop_backup());
        <entry>
         Creates a new logical (decoding) replication slot named
         <parameter>slot_name</parameter> using the output plugin
-        <parameter>plugin</parameter>.  A call to this function has the same
+        <parameter>plugin</parameter>. The optional third
+        parameter, <parameter>temporary</>, when set to true, specifies that
+        the slot should not be permanently stored to disk and is only meant
+        for use by current session. Note that temporary slots are also
+        released upon any error. A call to this function has the same
         effect as the replication protocol command
         <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
        </entry>
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 50cf527..8d9f628 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -1434,7 +1434,7 @@ The commands accepted in walsender mode are:
   </varlistentry>
 
   <varlistentry>
-   <term><literal>CREATE_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</> { <literal>PHYSICAL</> [ <literal>RESERVE_WAL</> ] | <literal>LOGICAL</> <replaceable class="parameter">output_plugin</> }
+   <term><literal>CREATE_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</> [ <literal>TEMPORARY</> ] { <literal>PHYSICAL</> [ <literal>RESERVE_WAL</> ] | <literal>LOGICAL</> <replaceable class="parameter">output_plugin</> }
      <indexterm><primary>CREATE_REPLICATION_SLOT</primary></indexterm>
     </term>
     <listitem>
@@ -1465,6 +1465,17 @@ The commands accepted in walsender mode are:
       </varlistentry>
 
       <varlistentry>
+       <term><literal>TEMPORARY</></term>
+       <listitem>
+        <para>
+         Specify that this replication slot is a temporary one. Temporary
+         slots are not saved to disk and are automatically dropped on error,
+         or when the session has finished.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
        <term><literal>RESERVE_WAL</></term>
        <listitem>
         <para>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ada2142..03e51e0 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -703,6 +703,7 @@ CREATE VIEW pg_replication_slots AS
             L.slot_type,
             L.datoid,
             D.datname AS database,
+            L.persistent,
             L.active,
             L.active_pid,
             L.xmin,
@@ -968,12 +969,22 @@ AS 'pg_logical_slot_peek_binary_changes';
 
 CREATE OR REPLACE FUNCTION pg_create_physical_replication_slot(
     IN slot_name name, IN immediately_reserve boolean DEFAULT false,
+	IN temporary boolean DEFAULT false,
     OUT slot_name name, OUT xlog_position pg_lsn)
 RETURNS RECORD
 LANGUAGE INTERNAL
 STRICT VOLATILE
 AS 'pg_create_physical_replication_slot';
 
+CREATE OR REPLACE FUNCTION pg_create_logical_replication_slot(
+    IN slot_name name, IN plugin name,
+	IN temporary boolean DEFAULT false,
+    OUT slot_name text, OUT xlog_position pg_lsn)
+RETURNS RECORD
+LANGUAGE INTERNAL
+STRICT VOLATILE
+AS 'pg_create_logical_replication_slot';
+
 CREATE OR REPLACE FUNCTION
   make_interval(years int4 DEFAULT 0, months int4 DEFAULT 0, weeks int4 DEFAULT 0,
                 days int4 DEFAULT 0, hours int4 DEFAULT 0, mins int4 DEFAULT 0,
diff --git a/src/backend/replication/repl_gram.y b/src/backend/replication/repl_gram.y
index fd0fa6d..e75516c 100644
--- a/src/backend/replication/repl_gram.y
+++ b/src/backend/replication/repl_gram.y
@@ -77,6 +77,7 @@ Node *replication_parse_result;
 %token K_LOGICAL
 %token K_SLOT
 %token K_RESERVE_WAL
+%token K_TEMPORARY
 
 %type <node>	command
 %type <node>	base_backup start_replication start_logical_replication
@@ -89,7 +90,7 @@ Node *replication_parse_result;
 %type <defelt>	plugin_opt_elem
 %type <node>	plugin_opt_arg
 %type <str>		opt_slot
-%type <boolval>	opt_reserve_wal
+%type <boolval>	opt_reserve_wal opt_temporary
 
 %%
 
@@ -183,24 +184,26 @@ base_backup_opt:
 			;
 
 create_replication_slot:
-			/* CREATE_REPLICATION_SLOT slot PHYSICAL RESERVE_WAL */
-			K_CREATE_REPLICATION_SLOT IDENT K_PHYSICAL opt_reserve_wal
+			/* CREATE_REPLICATION_SLOT slot TEMPORARY PHYSICAL RESERVE_WAL */
+			K_CREATE_REPLICATION_SLOT IDENT opt_temporary K_PHYSICAL opt_reserve_wal
 				{
 					CreateReplicationSlotCmd *cmd;
 					cmd = makeNode(CreateReplicationSlotCmd);
 					cmd->kind = REPLICATION_KIND_PHYSICAL;
 					cmd->slotname = $2;
-					cmd->reserve_wal = $4;
+					cmd->temporary = $3;
+					cmd->reserve_wal = $5;
 					$$ = (Node *) cmd;
 				}
-			/* CREATE_REPLICATION_SLOT slot LOGICAL plugin */
-			| K_CREATE_REPLICATION_SLOT IDENT K_LOGICAL IDENT
+			/* CREATE_REPLICATION_SLOT slot TEMPORARY LOGICAL plugin */
+			| K_CREATE_REPLICATION_SLOT IDENT opt_temporary K_LOGICAL IDENT
 				{
 					CreateReplicationSlotCmd *cmd;
 					cmd = makeNode(CreateReplicationSlotCmd);
 					cmd->kind = REPLICATION_KIND_LOGICAL;
 					cmd->slotname = $2;
-					cmd->plugin = $4;
+					cmd->temporary = $3;
+					cmd->plugin = $5;
 					$$ = (Node *) cmd;
 				}
 			;
@@ -276,6 +279,11 @@ opt_reserve_wal:
 			| /* EMPTY */					{ $$ = false; }
 			;
 
+opt_temporary:
+			K_TEMPORARY						{ $$ = true; }
+			| /* EMPTY */					{ $$ = false; }
+			;
+
 opt_slot:
 			K_SLOT IDENT
 				{ $$ = $2; }
diff --git a/src/backend/replication/repl_scanner.l b/src/backend/replication/repl_scanner.l
index f83ec53..9f50ce6 100644
--- a/src/backend/replication/repl_scanner.l
+++ b/src/backend/replication/repl_scanner.l
@@ -98,6 +98,7 @@ PHYSICAL			{ return K_PHYSICAL; }
 RESERVE_WAL			{ return K_RESERVE_WAL; }
 LOGICAL				{ return K_LOGICAL; }
 SLOT				{ return K_SLOT; }
+TEMPORARY			{ return K_TEMPORARY; }
 
 ","				{ return ','; }
 ";"				{ return ';'; }
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 0b2575e..9226539 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -47,6 +47,7 @@
 #include "storage/fd.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
+#include "utils/builtins.h"
 
 /*
  * Replication slot on-disk data structure.
@@ -98,7 +99,9 @@ int			max_replication_slots = 0;	/* the maximum number of replication
 										 * slots */
 
 static LWLockTranche ReplSlotIOLWLockTranche;
+
 static void ReplicationSlotDropAcquired(void);
+static void ReplicationSlotDropPtr(ReplicationSlot *slot);
 
 /* internal persistency functions */
 static void RestoreSlotFromDisk(const char *name);
@@ -329,7 +332,7 @@ ReplicationSlotAcquire(const char *name)
 {
 	ReplicationSlot *slot = NULL;
 	int			i;
-	int			active_pid = 0;
+	int			active_pid = 0; /* Keep compiler quiet */
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -346,7 +349,7 @@ ReplicationSlotAcquire(const char *name)
 			SpinLockAcquire(&s->mutex);
 			active_pid = s->active_pid;
 			if (active_pid == 0)
-				s->active_pid = MyProcPid;
+				active_pid = s->active_pid = MyProcPid;
 			SpinLockRelease(&s->mutex);
 			slot = s;
 			break;
@@ -359,7 +362,7 @@ ReplicationSlotAcquire(const char *name)
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
 				 errmsg("replication slot \"%s\" does not exist", name)));
-	if (active_pid != 0)
+	if (active_pid != MyProcPid)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_IN_USE),
 				 errmsg("replication slot \"%s\" is active for PID %d",
@@ -389,9 +392,12 @@ ReplicationSlotRelease(void)
 		 */
 		ReplicationSlotDropAcquired();
 	}
-	else
+	else if (slot->data.persistency == RS_PERSISTENT)
 	{
-		/* Mark slot inactive.  We're not freeing it, just disconnecting. */
+		/*
+		 * Mark persistent slot inactive.  We're not freeing it, just
+		 * disconnecting.
+		 */
 		SpinLockAcquire(&slot->mutex);
 		slot->active_pid = 0;
 		SpinLockRelease(&slot->mutex);
@@ -406,11 +412,39 @@ ReplicationSlotRelease(void)
 }
 
 /*
+ * Cleanup all temporary slots created in current session.
+ */
+void
+ReplicationSlotCleanup()
+{
+	int			i;
+
+	Assert(MyReplicationSlot == NULL);
+
+	/*
+	 * No need for locking as we are only interested in slots active in
+	 * current process and those are not touched by other processes.
+	 */
+	for (i = 0; i < max_replication_slots; i++)
+	{
+		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
+
+		if (s->active_pid == MyProcPid)
+		{
+			Assert(s->in_use && s->data.persistency == RS_TEMPORARY);
+
+			ReplicationSlotDropPtr(s);
+		}
+	}
+}
+
+/*
  * Permanently drop replication slot identified by the passed in name.
  */
 void
 ReplicationSlotDrop(const char *name)
 {
+
 	Assert(MyReplicationSlot == NULL);
 
 	ReplicationSlotAcquire(name);
@@ -419,21 +453,31 @@ ReplicationSlotDrop(const char *name)
 }
 
 /*
- * Permanently drop the currently acquired replication slot which will be
- * released by the point this function returns.
+ * Permanently drop the currently acquired replication slot.
  */
 static void
 ReplicationSlotDropAcquired(void)
 {
-	char		path[MAXPGPATH];
-	char		tmppath[MAXPGPATH];
 	ReplicationSlot *slot = MyReplicationSlot;
 
-	Assert(MyReplicationSlot != NULL);
+	Assert(MyReplicationSlot);
 
 	/* slot isn't acquired anymore */
 	MyReplicationSlot = NULL;
 
+	ReplicationSlotDropPtr(slot);
+}
+
+/*
+ * Permanently drop the replication slot which will be released by the point
+ * this function returns.
+ */
+static void
+ReplicationSlotDropPtr(ReplicationSlot *slot)
+{
+	char		path[MAXPGPATH];
+	char		tmppath[MAXPGPATH];
+
 	/*
 	 * If some other backend ran this code concurrently with us, we might try
 	 * to delete a slot with a certain name while someone else was trying to
@@ -448,9 +492,9 @@ ReplicationSlotDropAcquired(void)
 	/*
 	 * Rename the slot directory on disk, so that we'll no longer recognize
 	 * this as a valid slot.  Note that if this fails, we've got to mark the
-	 * slot inactive before bailing out.  If we're dropping an ephemeral slot,
-	 * we better never fail hard as the caller won't expect the slot to
-	 * survive and this might get called during error handling.
+	 * slot inactive before bailing out.  If we're dropping an ephemeral or
+	 * a temporary slot, we better never fail hard as the caller won't expect
+	 * the slot to survive and this might get called during error handling.
 	 */
 	if (rename(path, tmppath) == 0)
 	{
@@ -469,7 +513,7 @@ ReplicationSlotDropAcquired(void)
 	}
 	else
 	{
-		bool		fail_softly = slot->data.persistency == RS_EPHEMERAL;
+		bool		fail_softly = slot->data.persistency != RS_PERSISTENT;
 
 		SpinLockAcquire(&slot->mutex);
 		slot->active_pid = 0;
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index f908761..912bfb7 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -41,6 +41,7 @@ pg_create_physical_replication_slot(PG_FUNCTION_ARGS)
 {
 	Name		name = PG_GETARG_NAME(0);
 	bool		immediately_reserve = PG_GETARG_BOOL(1);
+	bool		temporary = PG_GETARG_BOOL(2);
 	Datum		values[2];
 	bool		nulls[2];
 	TupleDesc	tupdesc;
@@ -57,7 +58,8 @@ pg_create_physical_replication_slot(PG_FUNCTION_ARGS)
 	CheckSlotRequirements();
 
 	/* acquire replication slot, this will check for conflicting names */
-	ReplicationSlotCreate(NameStr(*name), false, RS_PERSISTENT);
+	ReplicationSlotCreate(NameStr(*name), false,
+						  temporary ? RS_TEMPORARY : RS_PERSISTENT);
 
 	values[0] = NameGetDatum(&MyReplicationSlot->data.name);
 	nulls[0] = false;
@@ -96,6 +98,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 {
 	Name		name = PG_GETARG_NAME(0);
 	Name		plugin = PG_GETARG_NAME(1);
+	bool		temporary = PG_GETARG_BOOL(2);
 
 	LogicalDecodingContext *ctx = NULL;
 
@@ -116,11 +119,14 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 
 	/*
 	 * Acquire a logical decoding slot, this will check for conflicting names.
-	 * Initially create it as ephemeral - that allows us to nicely handle
-	 * errors during initialization because it'll get dropped if this
+	 * Initially create persisent slot as ephemeral - that allows us to nicely
+	 * handle errors during initialization because it'll get dropped if this
 	 * transaction fails. We'll make it persistent at the end.
+	 * Temporary slots can be created as temporary from beginning as they get
+	 * dropped on error as well.
 	 */
-	ReplicationSlotCreate(NameStr(*name), true, RS_EPHEMERAL);
+	ReplicationSlotCreate(NameStr(*name), true,
+						  temporary ? RS_TEMPORARY : RS_EPHEMERAL);
 
 	/*
 	 * Create logical decoding context, to build the initial snapshot.
@@ -143,8 +149,9 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 	tuple = heap_form_tuple(tupdesc, values, nulls);
 	result = HeapTupleGetDatum(tuple);
 
-	/* ok, slot is now fully created, mark it as persistent */
-	ReplicationSlotPersist();
+	/* ok, slot is now fully created, mark it as persistent if needed */
+	if (!temporary)
+		ReplicationSlotPersist();
 	ReplicationSlotRelease();
 
 	PG_RETURN_DATUM(result);
@@ -174,7 +181,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 10
+#define PG_GET_REPLICATION_SLOTS_COLS 11
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	TupleDesc	tupdesc;
 	Tuplestorestate *tupstore;
@@ -219,6 +226,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 		Datum		values[PG_GET_REPLICATION_SLOTS_COLS];
 		bool		nulls[PG_GET_REPLICATION_SLOTS_COLS];
 
+		ReplicationSlotPersistency	persistency;
 		TransactionId xmin;
 		TransactionId catalog_xmin;
 		XLogRecPtr	restart_lsn;
@@ -246,6 +254,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 			namecpy(&plugin, &slot->data.plugin);
 
 			active_pid = slot->active_pid;
+			persistency = slot->data.persistency;
 		}
 		SpinLockRelease(&slot->mutex);
 
@@ -269,6 +278,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 		else
 			values[i++] = database;
 
+		values[i++] = BoolGetDatum(persistency == RS_PERSISTENT);
 		values[i++] = BoolGetDatum(active_pid != 0);
 
 		if (active_pid != 0)
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index bc5e508..c9b9db1 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -264,6 +264,8 @@ WalSndErrorCleanup(void)
 	if (MyReplicationSlot != NULL)
 		ReplicationSlotRelease();
 
+	ReplicationSlotCleanup();
+
 	replication_active = false;
 	if (walsender_ready_to_stop)
 		proc_exit(0);
@@ -794,18 +796,22 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 
 	if (cmd->kind == REPLICATION_KIND_PHYSICAL)
 	{
-		ReplicationSlotCreate(cmd->slotname, false, RS_PERSISTENT);
+		ReplicationSlotCreate(cmd->slotname, false,
+							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT);
 	}
 	else
 	{
 		CheckLogicalDecodingRequirements();
 
 		/*
-		 * Initially create the slot as ephemeral - that allows us to nicely
-		 * handle errors during initialization because it'll get dropped if
-		 * this transaction fails. We'll make it persistent at the end.
+		 * Initially create persisent slot as ephemeral - that allows us to
+		 * nicely handle errors during initialization because it'll get
+		 * dropped if this transaction fails. We'll make it persistent at the
+		 * end. Temporary slots can be created as temporary from beginning as
+		 * they get dropped on error as well.
 		 */
-		ReplicationSlotCreate(cmd->slotname, true, RS_EPHEMERAL);
+		ReplicationSlotCreate(cmd->slotname, true,
+							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL);
 	}
 
 	initStringInfo(&output_message);
@@ -839,15 +845,18 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		/* don't need the decoding context anymore */
 		FreeDecodingContext(ctx);
 
-		ReplicationSlotPersist();
+		if (!cmd->temporary)
+			ReplicationSlotPersist();
 	}
 	else if (cmd->kind == REPLICATION_KIND_PHYSICAL && cmd->reserve_wal)
 	{
 		ReplicationSlotReserveWal();
 
-		/* Write this slot to disk */
 		ReplicationSlotMarkDirty();
-		ReplicationSlotSave();
+
+		/* Write this slot to disk if it's permanent one. */
+		if (!cmd->temporary)
+			ReplicationSlotSave();
 	}
 
 	snprintf(xpos, sizeof(xpos), "%X/%X",
@@ -931,9 +940,6 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 
 	pq_endmessage(&buf);
 
-	/*
-	 * release active status again, START_REPLICATION will reacquire it
-	 */
 	ReplicationSlotRelease();
 }
 
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index b201631..c9eef79 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -806,6 +806,9 @@ ProcKill(int code, Datum arg)
 	if (MyReplicationSlot != NULL)
 		ReplicationSlotRelease();
 
+	/* Also cleanup all the temporary slots. */
+	ReplicationSlotCleanup();
+
 	/*
 	 * Detach from any lock group of which we are a member.  If the leader
 	 * exist before all other group members, it's PGPROC will remain allocated
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 599874e..d453ec4 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -3884,6 +3884,9 @@ PostgresMain(int argc, char *argv[],
 		if (MyReplicationSlot != NULL)
 			ReplicationSlotRelease();
 
+		/* We also want to cleanup temporary slots on error. */
+		ReplicationSlotCleanup();
+
 		/*
 		 * Now return to normal top-level context and clear ErrorContext for
 		 * next time.
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 17ec71d..e2a6585 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -5174,13 +5174,13 @@ DATA(insert OID = 5016 (  spg_box_quad_leaf_consistent	PGNSP PGUID 12 1 0 0 0 f
 DESCR("SP-GiST support for quad tree over box");
 
 /* replication slots */
-DATA(insert OID = 3779 (  pg_create_physical_replication_slot PGNSP PGUID 12 1 0 0 0 f f f f t f v u 2 0 2249 "19 16" "{19,16,19,3220}" "{i,i,o,o}" "{slot_name,immediately_reserve,slot_name,xlog_position}" _null_ _null_ pg_create_physical_replication_slot _null_ _null_ _null_ ));
+DATA(insert OID = 3779 (  pg_create_physical_replication_slot PGNSP PGUID 12 1 0 0 0 f f f f t f v u 3 0 2249 "19 16 16" "{19,16,16,19,3220}" "{i,i,i,o,o}" "{slot_name,immediately_reserve,temporary,slot_name,xlog_position}" _null_ _null_ pg_create_physical_replication_slot _null_ _null_ _null_ ));
 DESCR("create a physical replication slot");
 DATA(insert OID = 3780 (  pg_drop_replication_slot PGNSP PGUID 12 1 0 0 0 f f f f t f v u 1 0 2278 "19" _null_ _null_ _null_ _null_ _null_ pg_drop_replication_slot _null_ _null_ _null_ ));
 DESCR("drop a replication slot");
-DATA(insert OID = 3781 (  pg_get_replication_slots	PGNSP PGUID 12 1 10 0 0 f f f f f t s s 0 0 2249 "" "{19,19,25,26,16,23,28,28,3220,3220}" "{o,o,o,o,o,o,o,o,o,o}" "{slot_name,plugin,slot_type,datoid,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn}" _null_ _null_ pg_get_replication_slots _null_ _null_ _null_ ));
+DATA(insert OID = 3781 (  pg_get_replication_slots	PGNSP PGUID 12 1 10 0 0 f f f f f t s s 0 0 2249 "" "{19,19,25,26,16,16,23,28,28,3220,3220}" "{o,o,o,o,o,o,o,o,o,o,o}" "{slot_name,plugin,slot_type,datoid,persistent,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn}" _null_ _null_ pg_get_replication_slots _null_ _null_ _null_ ));
 DESCR("information about replication slots currently in use");
-DATA(insert OID = 3786 (  pg_create_logical_replication_slot PGNSP PGUID 12 1 0 0 0 f f f f t f v u 2 0 2249 "19 19" "{19,19,25,3220}" "{i,i,o,o}" "{slot_name,plugin,slot_name,xlog_position}" _null_ _null_ pg_create_logical_replication_slot _null_ _null_ _null_ ));
+DATA(insert OID = 3786 (  pg_create_logical_replication_slot PGNSP PGUID 12 1 0 0 0 f f f f t f v u 3 0 2249 "19 19 16" "{19,19,16,25,3220}" "{i,i,i,o,o}" "{slot_name,plugin,temporary,slot_name,xlog_position}" _null_ _null_ pg_create_logical_replication_slot _null_ _null_ _null_ ));
 DESCR("set up a logical replication slot");
 DATA(insert OID = 3782 (  pg_logical_slot_get_changes PGNSP PGUID 12 1000 1000 25 0 f f f f f t v u 4 0 2249 "19 3220 23 1009" "{19,3220,23,1009,3220,28,25}" "{i,i,i,v,o,o,o}" "{slot_name,upto_lsn,upto_nchanges,options,location,xid,data}" _null_ _null_ pg_logical_slot_get_changes _null_ _null_ _null_ ));
 DESCR("get changes from replication slot");
diff --git a/src/include/nodes/replnodes.h b/src/include/nodes/replnodes.h
index d2f1edb..024b965 100644
--- a/src/include/nodes/replnodes.h
+++ b/src/include/nodes/replnodes.h
@@ -55,6 +55,7 @@ typedef struct CreateReplicationSlotCmd
 	char	   *slotname;
 	ReplicationKind kind;
 	char	   *plugin;
+	bool		temporary;
 	bool		reserve_wal;
 } CreateReplicationSlotCmd;
 
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index e00562d..b653e5c 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -28,7 +28,8 @@
 typedef enum ReplicationSlotPersistency
 {
 	RS_PERSISTENT,
-	RS_EPHEMERAL
+	RS_EPHEMERAL,
+	RS_TEMPORARY
 } ReplicationSlotPersistency;
 
 /*
@@ -165,6 +166,7 @@ extern void ReplicationSlotDrop(const char *name);
 
 extern void ReplicationSlotAcquire(const char *name);
 extern void ReplicationSlotRelease(void);
+extern void ReplicationSlotCleanup(void);
 extern void ReplicationSlotSave(void);
 extern void ReplicationSlotMarkDirty(void);
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 00700f2..cff6d9b 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1423,13 +1423,14 @@ pg_replication_slots| SELECT l.slot_name,
     l.slot_type,
     l.datoid,
     d.datname AS database,
+    l.persistent,
     l.active,
     l.active_pid,
     l.xmin,
     l.catalog_xmin,
     l.restart_lsn,
     l.confirmed_flush_lsn
-   FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn)
+   FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, persistent, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,
-- 
2.7.4

#86Petr Jelinek
petr@2ndquadrant.com
In reply to: Andres Freund (#79)
Re: Logical Replication WIP

On 04/11/16 14:00, Andres Freund wrote:

Hi,

+ <sect1 id="catalog-pg-publication-rel">
+  <title><structname>pg_publication_rel</structname></title>
+
+  <indexterm zone="catalog-pg-publication-rel">
+   <primary>pg_publication_rel</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_publication_rel</structname> catalog contains
+   mapping between tables and publications in the database. This is many to
+   many mapping.
+  </para>

I wonder if we shouldn't abstract this a bit away from relations to
allow other objects to be exported to. Could structure it a bit more
like pg_depend.

Honestly, let's not overdesign this. Change like that can be made in the
future if we need it and I am quite unconvinced we do given that
anything we might want to replicate will be relation. I understand that
it might be useful to know what's on downstream in terms of objects at
some point for some future functionality, but I am don't have idea how
that functionality will look like so it's premature to guess what
catalog structure it will need.

+ALTER PUBLICATION <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replaceable class="PARAMETER">option</replaceable> [ ... ] ]
+
+<phrase>where <replaceable class="PARAMETER">option</replaceable> can be:</phrase>
+
+      PuBLISH_INSERT | NOPuBLISH_INSERT
+    | PuBLISH_UPDATE | NOPuBLISH_UPDATE
+    | PuBLISH_DELETE | NOPuBLISH_DELETE

That's odd casing.

+   <varlistentry>
+    <term><literal>PuBLISH_INSERT</literal></term>
+    <term><literal>NOPuBLISH_INSERT</literal></term>
+    <term><literal>PuBLISH_UPDATE</literal></term>
+    <term><literal>NOPuBLISH_UPDATE</literal></term>
+    <term><literal>PuBLISH_DELETE</literal></term>
+    <term><literal>NOPuBLISH_DELETE</literal></term>

Ah typo in my sed script, fun.

More odd casing.

+   <varlistentry>
+    <term><literal>FOR TABLE</literal></term>
+    <listitem>
+     <para>
+      Specifies optional list of tables to add to the publication.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>FOR TABLE ALL IN SCHEMA</literal></term>
+    <listitem>
+     <para>
+      Specifies optional schema for which all logged tables will be added to
+      publication.
+     </para>
+    </listitem>
+   </varlistentry>

"FOR TABLE ALL IN SCHEMA" sounds weird.

I actually removed support for this at some point, forgot to remove
docs. I might add this feature again in the future but I reckon we can
live without it in v1.

+  <para>
+   This operation does not reserve any resources on the server. It only
+   defines grouping and filtering logic for future subscribers.
+  </para>

That's strictly speaking not true, maybe rephrase a bit?

Sure, this basically is supposed to mean that it does not really start
replication or keep wal or anything like that as opposed what for
example slots do.

+/*
+ * Check if relation can be in given publication and throws appropriate
+ * error if not.
+ */
+static void
+check_publication_add_relation(Relation targetrel)
+{
+	/* Must be table */
+	if (RelationGetForm(targetrel)->relkind != RELKIND_RELATION)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("only tables can be added to publication"),
+				 errdetail("%s is not a table",
+						   RelationGetRelationName(targetrel))));
+
+	/* Can't be system table */
+	if (IsCatalogRelation(targetrel))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("only user tables can be added to publication"),
+				 errdetail("%s is a system table",
+						   RelationGetRelationName(targetrel))));
+
+	/* UNLOGGED and TEMP relations cannot be part of publication. */
+	if (!RelationNeedsWAL(targetrel))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("UNLOGGED and TEMP relations cannot be replicated")));
+}

This probably means we need a check in the ALTER TABLE ... SET UNLOGGED
path.

Good point.

+/*
+ * Returns if relation represented by oid and Form_pg_class entry
+ * is publishable.
+ *
+ * Does same checks as the above, but does not need relation to be opened
+ * and also does not throw errors.
+ */
+static bool
+is_publishable_class(Oid relid, Form_pg_class reltuple)
+{
+	return reltuple->relkind == RELKIND_RELATION &&
+		!IsCatalogClass(relid, reltuple) &&
+		reltuple->relpersistence == RELPERSISTENCE_PERMANENT &&
+		/* XXX needed to exclude information_schema tables */
+		relid >= FirstNormalObjectId;
+}

Shouldn't that be IsCatalogRelation() instead?

Well IsCatalogRelation just calls IsCatalogClass and we call
IsCatalogClass here as well. The problem with IsCatalogClass is that it
does not consider tables in information_schema that were created as part
of initdb to be system catalogs because it first does negative check on
pg_catalog and toast schemas and only then considers
FirstNormalObjectId. I was actually wondering if that might be a bug in
IsCatalogClass.

+/*
+ * Create new publication.
+ * TODO ACL check
+ */

That was meant for future enhancements, but I think I'll don't do
detailed ACLs in v1 so I'll remove that TODO.

+
+/*
+ * Drop publication by OID
+ */
+void
+DropPublicationById(Oid pubid)
+
+/*
+ * Remove relation from publication by mapping OID.
+ */
+void
+RemovePublicationRelById(Oid proid)
+{

Permission checks?

+}

Hm. Neither of these does dependency checking, wonder if that can be
argued to be problematic.

As PeterE said, that's done by caller, none of the Drop...ById does
dependency checks.

+publication_opt_item:
+			IDENT
+				{
+					/*
+					 * We handle identifiers that aren't parser keywords with
+					 * the following special-case codes, to avoid bloating the
+					 * size of the main parser.
+					 */
+					if (strcmp($1, "publish_insert") == 0)
+						$$ = makeDefElem("publish_insert",
+										 (Node *)makeInteger(TRUE), @1);
+					else if (strcmp($1, "nopublish_insert") == 0)
+						$$ = makeDefElem("publish_insert",
+										 (Node *)makeInteger(FALSE), @1);
+					else if (strcmp($1, "publish_update") == 0)
+						$$ = makeDefElem("publish_update",
+										 (Node *)makeInteger(TRUE), @1);
+					else if (strcmp($1, "nopublish_update") == 0)
+						$$ = makeDefElem("publish_update",
+										 (Node *)makeInteger(FALSE), @1);
+					else if (strcmp($1, "publish_delete") == 0)
+						$$ = makeDefElem("publish_delete",
+										 (Node *)makeInteger(TRUE), @1);
+					else if (strcmp($1, "nopublish_delete") == 0)
+						$$ = makeDefElem("publish_delete",
+										 (Node *)makeInteger(FALSE), @1);
+					else
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("unrecognized publication option \"%s\"", $1),
+									 parser_errposition(@1)));
+				}
+		;

I still would very much like to move this outside of gram.y and just use
IDENTs here. Like how COPY options are handled.

Well, I looked into it and it means some loss of info in the error
messages - mainly the error position in the query because utility
statements don't get ParseState (unlike COPY). It might be worth the
flexibility though.

+CATALOG(pg_publication,6104)
+{
+	NameData	pubname;			/* name of the publication */
+
+	/*
+	 * indicates that this is special publication which should encompass
+	 * all tables in the database (except for the unlogged and temp ones)
+	 */
+	bool		puballtables;
+
+	/* true if inserts are published */
+	bool		pubinsert;
+
+	/* true if updates are published */
+	bool		pubupdate;
+
+	/* true if deletes are published */
+	bool		pubdelete;
+
+} FormData_pg_publication;

Shouldn't this have an owner?

Probably, I wanted to do that as follow-up patch originally, but looks
like it should be in initial version.

I also wonder if we want an easier to
extend form of pubinsert/update/delete (say to add pubddl, pubtruncate,
pub ... without changing the schema).

So like, text array that's then parsed everywhere (I am not doing
bitmask/int definitely)?

+/* ----------------
+ *		pg_publication_rel definition.  cpp turns this into
+ *		typedef struct FormData_pg_publication_rel
+ *
+ * ----------------
+ */
+#define PublicationRelRelationId				6106
+
+CATALOG(pg_publication_rel,6106)
+{
+	Oid		prpubid;				/* Oid of the publication */
+	Oid		prrelid;				/* Oid of the relation */
+} FormData_pg_publication_rel;

To me it seems like a good idea to have objclassid/objsubid here.

You said that in the beginning, but again I am not quite convinced of
that yet. i guess if PeterE will move the sequence patches all the way
and we might lose the notion that sequences are relation (not sure if
that's where he is ultimately going though), that might make sense,
otherwise, don't really think this we need that.

--
Petr Jelinek 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

#87Petr Jelinek
petr@2ndquadrant.com
In reply to: Andres Freund (#80)
Re: Logical Replication WIP

On 04/11/16 14:24, Andres Freund wrote:

Hi,

(btw, I vote against tarballing patches)

Well, I vote against CF app not handling correctly emails with multiple
attachments :)

+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry><structfield>oid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry></entry>
+      <entry>Row identifier (hidden attribute; must be explicitly selected)</entry>
+     </row>
+
+     <row>
+      <entry><structfield>subpublications</structfield></entry>
+      <entry><type>name[]</type></entry>
+      <entry></entry>
+      <entry>Array of subscribed publication names. These reference the
+       publications on the publisher server.
+      </entry>

Why is this names and not oids? So you can see it across databases?

Because they only exist on remote server.

include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 68d7e46..523008d 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -112,6 +112,7 @@ static event_trigger_support_data event_trigger_support[] = {
{"SCHEMA", true},
{"SEQUENCE", true},
{"SERVER", true},
+	{"SUBSCRIPTION", true},

Hm, is that ok? Subscriptions are shared, so ...?

Good point, forgot event triggers don't handle shared objects.

+		/*
+		 * If requested, create the replication slot on remote side for our
+		 * newly created subscription.
+		 *
+		 * Note, we can't cleanup slot in case of failure as reason for
+		 * failure might be already existing slot of the same name and we
+		 * don't want to drop somebody else's slot by mistake.
+		 */
+		if (create_slot)
+		{
+			XLogRecPtr			lsn;
+
+			/*
+			 * Create the replication slot on remote side for our newly created
+			 * subscription.
+			 *
+			 * Note, we can't cleanup slot in case of failure as reason for
+			 * failure might be already existing slot of the same name and we
+			 * don't want to drop somebody else's slot by mistake.
+			 */

We should really be able to recognize that based on the error code...

We could, provided that the slot is active, but that would leave nasty
race condition where if you do drop and the other subscription of same
name is not running (restarting, temporarily disabled, etc) we'll remove
the slot for it. Maybe we should not care about that and say slot is
representing the subscription and if you name slot same for two
different subscriptions then that's your problem though.

+/*
+ * Drop subscription by OID
+ */
+void
+DropSubscriptionById(Oid subid)
+{
+	/*
+	 * We must ignore errors here as that would make it impossible to drop
+	 * subscription when publisher is down.
+	 */

I'm not convinced. Leaving a slot around without a "record" of it on
the creating side isn't nice either. Maybe a FORCE flag or something?

I would like to have this as option yes, not sure if FORCE is best
naming, but I have trouble coming up with good name. We have CREATE_SLOT
and NOCREATE_SLOT for CREATE SUBSCRIPTION, so maybe we could have
DROP_SLOT (default) and NODROP_SLOT for DROP SUBSCRIPTION.

--
Petr Jelinek 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

#88Petr Jelinek
petr@2ndquadrant.com
In reply to: Peter Eisentraut (#82)
Re: Logical Replication WIP

On 08/11/16 19:51, Peter Eisentraut wrote:

Review of v7 0003-Add-PUBLICATION-catalogs-and-DDL.patch:

This appears to address previous reviews and is looking pretty solid. I
have some comments that are easily addressed:

[still from previous review] The code for OCLASS_PUBLICATION_REL in
getObjectIdentityParts() does not fill in objname and objargs, as it is
supposed to.

catalog.sgml: pg_publication_rel column names must be updated after renaming

alter_publication.sgml and elsewhere: typos PuBLISH_INSERT etc.

create_publication.sgml: FOR TABLE ALL IN SCHEMA does not exist anymore

create_publication.sgml: talks about not-yet-existing SUBSCRIPTION role

DropPublicationById maybe name RemovePublicationById for consistency

system_views.sql: C.relkind = 'r' unnecessary

CheckCmdReplicaIdentity: error message says "cannot update", should
distinguish between update and delete

relcache.c: pubactions->pubinsert |= pubform->pubinsert; etc. should be ||=

RelationData.rd_pubactions could be a bitmap, simplifying some memcpy
and context management. But RelationData appears to favor rich data
structures, so maybe that is fine.

Thanks for these, some of it is result of various rebases that I did
(the sync patch makes rebasing bit complicated as it touches everything)
and it's easy for me to overlook it at this point.

--
Petr Jelinek 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

#89Andres Freund
andres@anarazel.de
In reply to: Petr Jelinek (#86)
Re: Logical Replication WIP

Hi,

On 2016-11-11 12:04:27 +0100, Petr Jelinek wrote:

On 04/11/16 14:00, Andres Freund wrote:

Hi,

+ <sect1 id="catalog-pg-publication-rel">
+  <title><structname>pg_publication_rel</structname></title>
+
+  <indexterm zone="catalog-pg-publication-rel">
+   <primary>pg_publication_rel</primary>
+  </indexterm>
+
+  <para>
+   The <structname>pg_publication_rel</structname> catalog contains
+   mapping between tables and publications in the database. This is many to
+   many mapping.
+  </para>

I wonder if we shouldn't abstract this a bit away from relations to
allow other objects to be exported to. Could structure it a bit more
like pg_depend.

Honestly, let's not overdesign this. Change like that can be made in the
future if we need it and I am quite unconvinced we do given that
anything we might want to replicate will be relation. I understand that
it might be useful to know what's on downstream in terms of objects at
some point for some future functionality, but I am don't have idea how
that functionality will look like so it's premature to guess what
catalog structure it will need.

I slightly prefer to make it more generic right now, but I don't think
that's a blocker.

I still would very much like to move this outside of gram.y and just use
IDENTs here. Like how COPY options are handled.

Well, I looked into it and it means some loss of info in the error
messages - mainly the error position in the query because utility
statements don't get ParseState (unlike COPY). It might be worth the
flexibility though.

Pretty sure that that's the case.

I also wonder if we want an easier to
extend form of pubinsert/update/delete (say to add pubddl, pubtruncate,
pub ... without changing the schema).

So like, text array that's then parsed everywhere (I am not doing
bitmask/int definitely)?

Yes, that sounds good to me. Then convert it to individual booleans or a
bitmask when loading the publications into the in-memory form (which you
already do).

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

#90Andres Freund
andres@anarazel.de
In reply to: Petr Jelinek (#84)
Re: Logical Replication WIP

On 2016-11-10 23:31:27 +0100, Petr Jelinek wrote:

On 04/11/16 13:15, Andres Freund wrote:

/* Prototypes for private functions */
-static bool libpq_select(int timeout_ms);
+static bool libpq_select(PGconn *streamConn,
+						 int timeout_ms);

If we're starting to use this more widely, we really should just a latch
instead of the plain select(). In fact, I think it's more or less a bug
that we don't (select is only interruptible by signals on a subset of
our platforms). That shouldn't bother this patch, but...

Agree that this is problem, especially for the subscription creation
later. We should be doing WaitLatchOrSocket, but the question is which
latch. We can't use MyProc one as that's not the latch that WalReceiver
uses so I guess we would have to send latch as parameter to any caller
of this which is not very pretty from api perspective but I don't have
better idea here.

I think we should simply make walsender use the standard proc
latch. Afaics that should be fairly trivial?

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

#91Petr Jelinek
petr@2ndquadrant.com
In reply to: Andres Freund (#90)
Re: Logical Replication WIP

On 12/11/16 20:19, Andres Freund wrote:

On 2016-11-10 23:31:27 +0100, Petr Jelinek wrote:

On 04/11/16 13:15, Andres Freund wrote:

/* Prototypes for private functions */
-static bool libpq_select(int timeout_ms);
+static bool libpq_select(PGconn *streamConn,
+						 int timeout_ms);

If we're starting to use this more widely, we really should just a latch
instead of the plain select(). In fact, I think it's more or less a bug
that we don't (select is only interruptible by signals on a subset of
our platforms). That shouldn't bother this patch, but...

Agree that this is problem, especially for the subscription creation
later. We should be doing WaitLatchOrSocket, but the question is which
latch. We can't use MyProc one as that's not the latch that WalReceiver
uses so I guess we would have to send latch as parameter to any caller
of this which is not very pretty from api perspective but I don't have
better idea here.

I think we should simply make walsender use the standard proc
latch. Afaics that should be fairly trivial?

Walreceiver you mean. Yeah that should be simple, looking at the code I
am not quite sure why it uses separate latch in the first place.

--
Petr Jelinek 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

#92Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Andres Freund (#89)
Re: Logical Replication WIP

On 11/12/16 2:18 PM, Andres Freund wrote:

I also wonder if we want an easier to

extend form of pubinsert/update/delete (say to add pubddl, pubtruncate,
pub ... without changing the schema).

So like, text array that's then parsed everywhere (I am not doing
bitmask/int definitely)?

Yes, that sounds good to me. Then convert it to individual booleans or a
bitmask when loading the publications into the in-memory form (which you
already do).

I'm not sure why that would be better. Adding catalog columns in future
versions is not a problem. We're not planning on adding hundreds of
publication attributes. Denormalizing catalog columns creates all kinds
of inconveniences, in the backend code, in frontend code, for users.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#93Andres Freund
andres@anarazel.de
In reply to: Peter Eisentraut (#92)
Re: Logical Replication WIP

On 2016-11-13 00:40:12 -0500, Peter Eisentraut wrote:

On 11/12/16 2:18 PM, Andres Freund wrote:

I also wonder if we want an easier to

extend form of pubinsert/update/delete (say to add pubddl, pubtruncate,
pub ... without changing the schema).

So like, text array that's then parsed everywhere (I am not doing
bitmask/int definitely)?

Yes, that sounds good to me. Then convert it to individual booleans or a
bitmask when loading the publications into the in-memory form (which you
already do).

I'm not sure why that would be better. Adding catalog columns in future
versions is not a problem.

It can be extended from what core provides, for extended versions of
replication solutions, for one. I presume publications/subscriptions
aren't only going to be used by built-in code.

Andres

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

#94Steve Singer
steve@ssinger.info
In reply to: Petr Jelinek (#72)
1 attachment(s)
Re: Logical Replication WIP

On 10/31/2016 06:38 AM, Petr Jelinek wrote:

On 31/10/16 00:52, Steve Singer wrote:
There are some fundamental issues with initial sync that need to be
discussed on list but this one is not known. I'll try to convert this
to test case (seems like useful one) and fix it, thanks for the
report. In meantime I realized I broke the last patch in the series
during rebase so attached is the fixed version. It also contains the
type info in the protocol.

Attached are some proposed documentation updates (to be applied ontop of
your 20161031 patch set)

Also

<sect1 id="logical-replication-publication">
<title>Publication</title>

+  <para>
+    The tables are matched using fully qualified table name. Renaming of
+    tables or schemas is not supported.
+  </para>

Is renaming of tables any less supported than other DDL operations
For example

alter table nokey2 rename to nokey3
select * FROM pg_publication_tables ;
pubname | schemaname | tablename
---------+------------+-----------
tpub | public | nokey3
(1 row)

If I then kill the postmaster on my subscriber and restart it, I get

2016-11-13 16:17:11.341 EST [29488] FATAL: the logical replication
target public.nokey3 not found
2016-11-13 16:17:11.342 EST [29272] LOG: worker process: logical
replication worker 41076 (PID 29488) exited with exit code 1
2016-11-13 16:17:16.350 EST [29496] LOG: logical replication apply for
subscription nokeysub started
2016-11-13 16:17:16.358 EST [29498] LOG: logical replication sync for
subscription nokeysub, table nokey2 started
2016-11-13 16:17:16.515 EST [29498] ERROR: table public.nokey2 not
found on publisher
2016-11-13 16:17:16.517 EST [29272] LOG: worker process: logical
replication worker 41076 sync 24688 (PID 29498) exited with exit code 1

but if I then rename the table on the subscriber everything seems to work.

(I suspect the need to kill+restart is a bug, I've seen other instances
where a hard restart of the subscriber following changes to is required)

I am also having issues adding a table to a publication ,it doesn't seem
work

P: create publication tpub for table a;
S: create subscription mysub connection 'host=localhost dbname=test
port=5440' publication tpub;
P: insert into a(b) values ('1');
P: alter publication tpub add table b;
P: insert into b(b) values ('1');
P: insert into a(b) values ('2');

select * FROM pg_publication_tables ;
pubname | schemaname | tablename
---------+------------+-----------
tpub | public | a
tpub | public | b

but

S: select * FROM b;
a | b
---+---
(0 rows)
S: select * FROM a;
a | b
---+---
5 | 1
6 | 2
(2 rows)

Attachments:

docupdates.difftext/x-diff; name=docupdates.diffDownload
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
new file mode 100644
index 63af1a5..89723c3
*** a/doc/src/sgml/logical-replication.sgml
--- b/doc/src/sgml/logical-replication.sgml
***************
*** 95,110 ****
      how the table is accessed. Each table can be added to multiple
      publications if needed.  Publications may currently only contain
      tables. Objects must be added explicitly, except when a publication
!     is created for <literal>ALL TABLES</>. There is no default name for
!     a publication which specifies all tables.
    </para>
    <para>
      Publications can choose to limit the changes they produce to show
      any combination of <command>INSERT</>, <command>UPDATE</> and
      <command>DELETE</> in a similar way to the way triggers are fired by
!     particular event types. Only tables with a <literal>REPLICA IDENTITY</>
!     index can be added to a publication which replicates <command>UPDATE</>
!     and <command>DELETE</> operation.
    </para>
    <para>
      The definition of a publication object will be included within
--- 95,110 ----
      how the table is accessed. Each table can be added to multiple
      publications if needed.  Publications may currently only contain
      tables. Objects must be added explicitly, except when a publication
!     is created for <literal>ALL TABLES</>.
    </para>
    <para>
      Publications can choose to limit the changes they produce to show
      any combination of <command>INSERT</>, <command>UPDATE</> and
      <command>DELETE</> in a similar way to the way triggers are fired by
!     particular event types. If a table with without a <literal>REPLICA IDENTITY</>
!     index is added to a publication which replicates <command>UPDATE</> or
!     <command>DELETE</> operations then subsequent <command>UPDATE</>
!     or <command>DELETE</> operations will fail on the publisher.
    </para>
    <para>
      The definition of a publication object will be included within
***************
*** 195,214 ****
    </para>
    <para>
      A conflict will produce an error and will stop the replication; it
!     must be resolved manually by the user.
    </para>
    <para>
!     The resolution can be done either by changing data on the subscriber
!     so that it does not conflict with incoming change or by skipping the
!     transaction that conflicts with the existing data. The transaction
!     can be skipped by calling the
!     <link linkend="pg-replication-origin-advance">
!     <function>pg_replication_origin_advance()</function></link> function
!     with a <literal>node_name</> corresponding to the subscription name. The
!     current position of origins can be seen in the
!     <link linkend="view-pg-replication-origin-status">
!     <structname>pg_replication_origin_status</structname></link> system view.
!   </para>
  </sect1>
  <sect1 id="logical-replication-architecture">
    <title>Architecture</title>
--- 195,206 ----
    </para>
    <para>
      A conflict will produce an error and will stop the replication; it
!     must be resolved manually by the user. Details about the conflict can
!     be found in the subscribers PostgreSQL log.
    </para>
    <para>
!     The resolution can only be accomplished by manualy changing the
!     subscriber so that it does not conflict with incoming change.
  </sect1>
  <sect1 id="logical-replication-architecture">
    <title>Architecture</title>
***************
*** 229,237 ****
      loads the standard logical decoding plugin (pgoutput). The plugin
      transforms the changes read from WAL to the logical replication protocol
      (see <xref linkend="protocol-logical-replication">) and filters the data
!     according to publication specification. The data are then continuously
      transferred using the streaming replication protocol to the Apply worker
!     which maps them to the local tables and applies the individual changes as
      they are received in exact transactional order.
    </para>
    <para>
--- 221,229 ----
      loads the standard logical decoding plugin (pgoutput). The plugin
      transforms the changes read from WAL to the logical replication protocol
      (see <xref linkend="protocol-logical-replication">) and filters the data
!     according to publication specification. The data is then continuously
      transferred using the streaming replication protocol to the Apply worker
!     which maps the data to local tables and applies the individual changes as
      they are received in exact transactional order.
    </para>
    <para>
#95Petr Jelinek
petr@2ndquadrant.com
In reply to: Andres Freund (#93)
Re: Logical Replication WIP

On 13/11/16 10:21, Andres Freund wrote:

On 2016-11-13 00:40:12 -0500, Peter Eisentraut wrote:

On 11/12/16 2:18 PM, Andres Freund wrote:

I also wonder if we want an easier to

extend form of pubinsert/update/delete (say to add pubddl, pubtruncate,
pub ... without changing the schema).

So like, text array that's then parsed everywhere (I am not doing
bitmask/int definitely)?

Yes, that sounds good to me. Then convert it to individual booleans or a
bitmask when loading the publications into the in-memory form (which you
already do).

I'm not sure why that would be better. Adding catalog columns in future
versions is not a problem.

It can be extended from what core provides, for extended versions of
replication solutions, for one. I presume publications/subscriptions
aren't only going to be used by built-in code.

I understand the desire here (especially as an author of such out of the
core tools), but I am not sure if this is a good place where to start
having pluggable catalogs given that we have no generic idea for those.
Currently, plugins writing arbitrary data to catalogs will cause things
to break when those plugins get uninstalled (and we don't have good
mechanism for cleaning that up when that happens). And that won't change
if we convert this into array. Besides, shouldn't the code then anyway
check that we only have expected data in that array otherwise we might
miss corruption?

So if the main reason for turning this into array is extendability for
other providers then I am -1 on the idea. IMHO this is for completely
different path that adds user catalogs with proper syscache-like
interface and everything but has nothing to do with publications.

--
Petr Jelinek 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 (#93)
Re: Logical Replication WIP

On Sun, Nov 13, 2016 at 4:21 AM, Andres Freund <andres@anarazel.de> wrote:

It can be extended from what core provides, for extended versions of
replication solutions, for one. I presume publications/subscriptions
aren't only going to be used by built-in code.

Hmm, I would not have presumed that.

--
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

#97Petr Jelinek
petr@2ndquadrant.com
In reply to: Andres Freund (#89)
7 attachment(s)
Re: Logical Replication WIP

Hi,

attached is v8. No tarballing this time ;)

About the patches:

0001:
This is the reworked approach to temporary slots that I sent earlier.

0002:
I ripped out the libpq_select completely and did what Andres suggested,
ie, WaitLatchOrSocket, that needed changes for WalReceiver to use
procLatch but that was trivial. Otherwise it's same.

0003:
Changes:
- Moved the parser of options into C
- Removed the dead references to "FOR TABLE ALL IN SCHEMA"
- Rephrased some things and fixed several typos
- Added needed check into ALTER TABLE ... SET UNLOGGED
- Fixed the UPDATE/DELETE check in CheckCmdReplicaIdentity
- Added owner
- Fixed permission checks

I didn't do any of the text array instead of bools and
objclassid/objsubid as the reasoning for former is wrong IMHO and the
latter is quite premature and I am still not convinced it will be ever
needed.

I also didn't do couple of things reported by PeterE:

relcache.c: pubactions->pubinsert |= pubform->pubinsert; etc. should be ||=

This one does not seem to be true, there is no ||= and |= works fine for
booleans.

And

The code for OCLASS_PUBLICATION_REL in
getObjectIdentityParts() does not fill in objname and objargs, as it is
supposed to.

From what I see it already does that.

0004:
Changes:
- Added separate DropSubscriptionStmt statement for DROP. This was
prompted by Andres' comment about event triggers. The event triggers
actually work fine as all the SQL is only supposed to touch
subscriptions in current database even though it's shared catalog (it's
only shared because we need the catalog pin but that's implementation
detail), but the DROP would break if name matched subscription in
another database if handled by DropStmt.
- Added SLOT_DROP/NOSLOT_DROP options to DROP SUBSCRIPTION, the new
DropSubscriptionStmt helps here as well
- Added owner
- Moved the option parsing into C

0005/0006 - Mainly just included the doc patch from Steve Singer and did
some additional doc fixes.

The 0007 is something that's more a question for discussion if we want
that. It adds new GUC that sets synchronous commit for apply workers and
defaults to off. This gives quite noticeable performance boost while
still working correctly even is provider uses sync replication. This is
based on experience (and default behaviour) of BDR and pglogical, but I
am not quite sure if core postgres should have that as well (I think it
definitely should have the option, question is more about default setting).

And that's it for now. After some discussion with PeterE I decided to
skip the initial sync patch as it has quite high impact on development
of rest of the patch (because it touches everything, I spend all my time
rebasing it instead of actually fixing things) and can be done as
follow-up patch. I also believe that it will also be polished much
faster once I can fully concentrate on it when this part is done.

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

Attachments:

0001-Add-support-for-TEMPORARY-replication-slots-v8.patch.gzapplication/gzip; name=0001-Add-support-for-TEMPORARY-replication-slots-v8.patch.gzDownload
0002-Refactor-libpqwalreceiver-v8.patch.gzapplication/gzip; name=0002-Refactor-libpqwalreceiver-v8.patch.gzDownload
0003-Add-PUBLICATION-catalogs-and-DDL-v8.patch.gzapplication/gzip; name=0003-Add-PUBLICATION-catalogs-and-DDL-v8.patch.gzDownload
���1X0003-Add-PUBLICATION-catalogs-and-DDL-v8.patch�=kW�����W���������!!�0��0K��=��=�Yj����G�a����[U�-���l����$�������������YG�f�h�lv{��q���i�y�3m�{�I{����'���w���|�Z=�l�����l��������!��{��?���/��	�7�g��������������q�|��ZG/[�����|����n�/��������������������������Vly�,b�����������m�v#
��l�5T�:~c�?�X�]�?9��O��M]��?��f��y8^�&�����coG��!��@?!���	���S����J�����>|0��1
�������	S����?r�Qh��>rd�������,����v��c29|	_��
Gt\F]@�f9N��(��R��A�3��v���R�e���DQ��;�������d�"��bZ�pa�E�* ~��x��l���$������X��u��A�V�����3�R��4�������>��\_���4���G���a���(�BtpP� �]Y�	
�Z��F<l�BkQ`�?t|H,�Qq�U�zn�P�p��Q�3l���9�����0uI�P�f�N7�����j�$y��<�/G�#�����&E9���'J`�ha\�2P�h6�c�����s<�I����U��a@����p3����!��B�B�s�����
00!�apK��Bq`�qp}�[9�dp��~�e0.������4�a�����������X�y�j�Y���pc�k�j�fE(e�B����\Q��W
HX��D���0drL������x��Q\�J�#L��r�+'`��0�0�(���0�!���y��up K����z�z��I�9Go
WP��Quw������G������ ���8���*��5�i08}��/<�e�zK�,���?�������>��E;{�5�c@��w��JD��L����!�f�%A�f�����I���4*]g��(��z�R�T
��|�������;�����3�Q��M*^n��`��o���u�������`X�{�C5�7o�~��[��? ?}�f[���F��n3�����/V��'��~d�(�������xqz����}k�Ou��42�N�	~���2P�!�����2������O��^�(PG1hM`,X[X�%�	��m8��_�tzZI������A�5A�NX���QF��zu�:h�b�:�Z2T�ml�\k�j�����
�����n���5I4\;�I�{��'�yuG�d�.�_='�������0�s�!�������!���E����Ax0r�&V�����`&�J[�4vx����$�0X-�B/z��#����rN+��5 -�#P��WUw�J\P��������O�;	��*r%��.����uo��bNp
�����Y�X�{P|��B'!���|cVC���+�XE1�p�	dby`-��v�`��#5�IBS�	�<c+��u�k�������cG�������?������
�sa�[^���V1��`s�c���S�	bs�78L�L�|��!�=�O� ���	�0��i�����J.���L� Oa���l��Y���bv��������U(�����2O�(�.�����KP���'A����L;���N��D����c����l��
�N��;���U�K�(L�$|!�/Y����&����7
�a������PN��L�\�hsH'c{�I�=/��yZ�����w0��wA�-�������1`q�/���'{�f�y���A�g�������t~�]A���
��z^��E.��'�g�R�
��X��J����K'�)u���UT��K��5
d����v{�^?�����WQy*�0T����%
P�Z���s�6����1�*B2�;
V��e}��%G�
��A�P�f'�_�.G�dT��!w���l�������e�`IJBd ���o��itJ�K`o�]�
���w
p/����\���� �.���!�+[U��J����/,3XD� ���F��U�"�7�X��K��������Y4����-����
:Y%h�F�t�,i	���3�Z��d��+������j=��q
$'��Z)�B
��\@�;3�>�u�2�6�spU��L����%�U�Q��J��:�.�u�������h>�'5���H7��
u��j��i8�����
Gc�U�Yk���p���7(�����M���x>��+\�[- 2���{���`���9xh�v5�2{�(~.�4�I�:-�U�B����T��� %�\&!K�Tz��l�.�����w�H�(|7�q������ @YY3������k���L��$b(��w	V
m���+�n�
�A�O�J������]�w`j,�Z�Q��]KZ�?XFn$�����@�_z������y���lp�r���SQa��9e����v�KEo�}���u�����-�!����s�P����|HH_�	LI�/�8�a,�M�����H4��4���lC�(�P�1�
�#��9�{vq�Fg�]���R\>6`l��<����������s�iH:5�\����O2�Ir-u�J<z�e��/�Y�k��(~Cl ��)�HK��gRja
���b�<]c�X��6.B *���D��&�d�hFYT��R<~3�w�6%Kr��#7d�����
��q���H���E� �YE<�����b$�DBR��PTP��
8T�
u���{7����f1"��2[Q��CQS�t��i~��#y�F����
!����P�^j�D��a���.�|V�=P����d��A
	�'�t��"Z-�aBp���co8���
�L�`Q�G^bRd����p���s��}!di��gJ'��M�y��L�%�&A�\}�*���@�g�7l��t���f������B�
��I%��?K�I#7�u�W���T�����wn�A�~��==Jw����j��#.�p$�q�3��;0Hl����t�����"�:{��Tf�I
p������O���wi��-!��2JV=��<�c~m�e�������kDL&�O=��F��������qM{?Os��A��.�Es�{4z��w�O�����h/�����}��SO���aQ���Gq9z7$l��c,S�Q���)m�������nB�?J���rC��LrV,L�G���T��!a��Go��C0=g^���\T!�Sp������j�QJ����D���`�S	�wB�axi*��nSv�PP)���Q���t��f����2�<'XQ�)������u�o���7&����	�WU�)!�X���
D��I�X�)R`L{+e�����7���y�8 ���g�D(����!�����e�YS#Bt�T��a���D_���Y�	�!�������Y����z|A	��������4�������t�*C���PB�1EX��0�bb�G6�����_�� ��"������s�Z�/�E����h���n(+&������r��X�vC�/?���=mp���cT,�ET�����9uL��0�8���i[)>��}��v��^=�1����h�.<	�
A����8	s�$���&1����R���D�t��:^��+?�����|���-E��
$?�?E vu�9���\��G�lyb
�:�+
0��5)s��{S��pq&��X�0�%�.B�1D"`Q1	��!�Q������0���.�����~\#&��B}D�X3���KK��4Dy�8e���A~i�:�b�������*D��Y�<,qk�X�����k�'�N�z�����C�����N�b��wj#s����w�	�"�_Wn�"V�\�|�6M;��������\�J��R��b���c��������iP�B)��Xj�w#.�2���AL� ��X$��-l��d�[�>9�J9$��7�o���"[��i2�Q�M��tVK/Ck�
&����2�
V1��)���A
Cq�����d+\��K��?Uau�1���O	�����s3��zb�a�?,N/.�N�KFVBU�pd*�r��I��*��7�����1���0Uo\��xw�;����:����[�V���,.,��j��
>&�|�\�D�P��x�%*��E��\i����k�L��z3����clR���M~0��*m�&S�>�V����O)H������A	��g���,��"���m$�X���=J69|n9:O�6p�1���������;�J;�Q�r�e�H,4�#�z(*.����,��Ct��p��b��D�������A��|	�^��9��9��k��g���M�7�5���i5'����}<iu��sh:�w�7��+�&�XC?������'g���Y�*�'7�X��`2�����������5���������>�j��5�a���AG����e�BC���_���Y�z��r�n]�H&��G���VH%"������m�T"��,�#6��S\��b�r)V���������N@R�K����-UmP�X_��������U�L��ys]f�
j�y���9j����*}&�^i�W-gj��4G��L��z�p��v;�� o��tuU�.����5�7����/����q~�	���!�lC<��}_���9�W�P5�d��L�NC�2��h�d�DN(jM3d�g
���d���Z<��*���e�k���e�e�RG�2_I�V��=�|-��e�^�i��Q��<����D �����_��W�sj�������a��jstm���Z�"M��"a:�0����h<`?'ST^	n�x#x������ ���l�W;�<yEb
���8�\?V
�;�
-���4P|r�9!��<+�qA�x�����)�@j�hFW��':./>::99�''��h�������E��"q`E,���j�Hf��y{oK�
9k�������|{���^���J�����~q�D/}�]$�
*Y��f��tZ��f8�~�����pp�������|��dhh;n��0��YIo��A���?ks��81K�3��X�$��T.T�����n<���H|����&\�����E���T�4��p�95��*��l��&�����R�w�W0T��o����40v��v{��Z����m�70	�r�4!O������mT��*����!�9���f;���(
����w����05�etS�"(y@I��v�S<���\^�b��mc{/����?G�*���/��`G���E��3�����a��1�V�[6,,�����|ET���P�D��;������:��
y��8�i=�U��l�-�{�������L�	���i�t�����;rh�X��N���2�������B���x�?�j�dkk���8��\��?]_��^^�/��<���]��A�E4�yQ8f���X��W\����K����L�q�����4����������O�#E�~r'��0��"�$��w��s+�~L�`J��������~����N�����`���"�����Y�����([]�El���_|L!��r/���������[�p\Sk��<����VA�E�Y�m����w�����X����z��!)�Z3�G�!�(�f�&->Sw�4����/JZ(�_������g~�5�4f�����C���������6�#yA|�R��U�n(��$I�(�5�q����-������(����F��l��S�{�;U���?��IWD��a���A2�"��2���0���W�P7.]:�-4�7�Wg��������k�`��9�>���������x4�����@@�Bk�����Oa�3�E	z���I���3C��+A�H�O
�Q6�]���Q��_�p�����n��:�V���.��rb����������1fS��E`�����{�{�tvD���@z&���ny�G�\��'��a�Q�$�"�)���b�����*�<�9��'<}I#��u"�
X@�Uu�����q�E!������uW�`7!(��� ��4��r����E9���Y�i�(�,7
�k�?�62���~0�����)�JzC�����z���trh7���0���!�[�O<$�xX�e�������]�������=�#Y��Y���,��[��HX�D
�d����`��Cf��N���v]�:=�����*����{��������MU��<g��U3������#8*��%�� �?B�������7���~���&>F\`��tA,���N	3��J��f��G���&>�3���kcyk�8w�q����U���\��E
?�aG�Sao�x�>�i�����9����
�3�����tz�����t����2�C����$�C�@�J�B��9�=4��Rj@���!r�����1y6	T��3����>]���.b��S\�x]H tpL�2����AHte6���!�������� 6o��[�?�*�}n�r;U�1�����g��$��g����_N���ri�s��9��VU���'p�R�P1v�s@����}����C�����e���|�����[�l��qr�X��}�>�C�,�84�8���}H8���(1�5���@��=�%�K:���G@��u�
^��G
��;�]r_a�Dj�t�H���V��S����9���q��������������n�V��,�/=,F��]zC#_��q�����X�$��a8c�`����k����+���������*k~�H�C��6��$�7���F#9��/F�4*�lS>���a]^���<��`oY'S1�h�@�{2���Dj��(�����+*h��9;�n�]��%;m��b��
c{/�vlUvl������6��e��������5����Yg���Y�>�>��1�fAJ���.)Xk��z�J��E����#$*4��lT�>���b�:�5�UU�+,�=�$�Qa�W��XW����'[�j����Z<\Mv������Pgx&�9����v��,n��F�2C�������6�'D
T���
�,yG8#�����Q���H]�x`��i�4A/���E��x�6�u�RBvf�~�i >-�\{����K���|?O�x
!�|N�S��)+%�^�6�}0����rNV@��,��x-,u�Y�g����^L�
�>Py&�@e���d	t"�'�?����+"�e\4���d.�$n���_V�����J���k��It,o��A�E�eX��
%�
��WP�;(5����"����Y�O��)�����W\r=9x���Os�]���
J?7��V�H>7��U��K�D��m�9�o���!P��+_���\_� ���u�"Q������eW�����+�����>�BG_ �U�������8^�?�r��+�
���4��,n�����|�6���;��}����w���cy�\��������T��]����2�i��W��^3S��VJX�F|	^5
���F��8p�7�#���������s�L����`"wjG8�;�������6L�u�O�a
��o����w�8��{�E�q�K��B^����t=��lH���c����&&u�b�II�s�P"���\�[�q\~:X�����5)`���m�8x�My��r�*'"��z.�!dl����E�g>.F�yF
Vi��^m�C�-8�H.;����C�d�������+���A�/d�!��Z�f�E����A�b�n����8�q���?������%�OOZ�)W��A�q�x��sp$�����i� :���C%��@H1���-�}Z�T����C: �t�����]��=��>_��U���V���?�Wa�Xy_Z���C���\J���\��;3nQ���e���:�i�%�@���M��*�<o���-�C��
��c��<�c,)�h�InS=rE*|����ve%���c�1��	��{���[m-W����hP������������hBX}�X��C����x�(���$�8�/�J��-O��}2��]��fP�_`x��M�A8
N�wbc���;8��>�=Zy�.���i4��G}������AY����Y0^����;pWGw�Y�>�/o"�1�F\t��9%�,��{�?�|��_��p��G��t.������
�}��l�5^�b���/��pr�]�m��-f���~���E��Ly�������K {�Y<1L4_&Q����(W��r!��e�$�;�T2XN l�;T(z ���p|w�����������i�J�L���FO����J.s���(��:f���2�g�RRxZ8) ����:�$~���I<O��EI6�cP":����<�"5�9>�y}����
�"/���|\pPWb7�������[�x-�}��|�	���'���m�[��5�[�}A�/~_73&��a�1�x�TL-K*1A!F�4�!����(��U�����?	��k�{�Et��WK��%%(������I3Z/�	�s}y�>;k�"�nR�|j�HC�~a�5=�Q�D�(��q�Ygp���������A�{jmp�@|�
��TlN�@2����� &��%�f�S'�����Q��p��*�`���
�<F��$6l�tN�X7@"C4%u�	�4M�j�FE�&�6q������C$�/"�SM-��R���SX����C�����,�s��7�a�����n��k^�4���E�R0`�T�����w�*���D��T.�?R�(��%x�e��{]B���
thQp17����}��s��8�z�LI���=����q_,q/�R^�^D��d��\�$�e��x.�G��	�B'�0������K���d�����R��":s7ZV��}=]���x�rV���H:jM(`H��������7GRqN���q(O��R�kVT�V/4q|7�Y��JJ�����='��p���4�����K�w��<r����S�O���G����9@U����B]����
)I���~��@��M�����`-��/*�v%H����	���pKT�� �����1���gK
�V�/��Ot����������a�r���a���QU]����A������4`b)���C"iE��cwQ��WjS�(�����B�(�k�H�(���c��r�)����ia����E��24�kB��������'��Q�g�����&���USl������^��-�>���	Y��~�O�	%���!�I��.Q���U����Te0dD�iP3!�������l��X)���c�#+��B���c��1�KY-Z�x�Fe�@}�m���f� 7>��zUe�p3Jk��U\��7@{s6<./�Qy�6��$�[����=RQ���N�g-��m�����^�V���PG���v�SLo*m3�Aj���EESIzhn�>/m4�C���`8\��]&y,
5��A���x9���B<#���F(��u1p�#iL�=�F=$%-oz0�[Q1�9�}l2��x)3�]!}�C��R)?�F���>�"8���Yj��O���\�c������M����Y
Nc��U�	V��E"�{s�.N��~�u���+�qk4�?!o�P5�n��'��K�G�E8�.^���
����=�R�m�*�_-�����nh��(0����Y���J?v����-�����j��	� Z1t���zg����=Z,���A����}���i�wk����1h7�����gX��z+>�>�E��}�F���>W;�+!��]�x"�h�e���37������k�������'l��a�I��!Ro9)���+���i�yf����b�d�?��c�'����,�+~D1l������X�����@k��OV��+��P���FG����KQ��v�q�
��,���!�j�e<-��?���J������D�d�
j��� FO�z<�:��tKq
�8FX�
�c�1|�I�0@�R��b;�g
�����g����:M�)��NF�C,m���e��]7�eR$�����jm���k��`�|�Q����\a���,HcX��u3�C��a�u3F!IEsv6�u,~��r	��d����m,�1~���"X��z�j�C�Wi:%%�����X���iAsZ� �������q��+#���+��������L���K�x��5Q�4)%���**c���LpLT�K��������(��@�������!���q�|@�O��c�Z���_I�>s����	��el,�vY�I7L^]\������lT�P�(�`��h����
���/��C�����u<�W��'�������/?����C]^�����M_������1����e�a�u��I�yV}w�mh�����K6������r�Y��'��;�g��<����W����F��E��d�,��I����{����6!����������g'�t*?>jI8],���o�����c��
� �p1����-_�M����iu��!��Aq����|�x:�W>!D�*Y���U��I��/��S�q�u4O#l1�}�u���7u��	F����MG�`XQ�d�����Y�~�����b���#z�!��u�����rH�v)rs�3���j���X����b]�1m�4�4���z��T��Zb��'���El����u�'���l,����97��E9�U��%����1x���]����6J]5;�P�f���f�����[B%��WL�B�w��PY���-BeA�#��u.���H���D7��hz��l����P�yT��$z�V�_�9��n5���������Z�k����� o@������N�������p��Z��1��"\���.���d����$�D�f��BX�N���7M�����A��a�����c=[���@br���SJ\U%�ot�QU����Y:��d���8����5�3fM�
������b�D�	�����������|Y%Q�����������(�*�y����
����j���
	Z��-����qA��������-#���
p�s�;��a�z�G���f xP6S���T F9Rs�������
�/CV	����������B���1�A?F�������s@�S�&�	��T�[�c�`!6���~�Qp����s;II
08�����������,r�������a���p�%s!E�	��Qu)B���h���*�.|�e��Fb�_\��z�0�|�|��x?�m0Z��KL�@_����sK���p��@i{�2�-9�$i><�H�qtE�k �h��U)$E?��x�2���(]�a1���H#�m�-�F��	��n��1��Q��q�:��.�����<324Z�!��se��5>2*uO�n^4�3���MQ�j�������I�?��	���
�[e����=3���� ��yCI��g�d7����W<#��4#��!KX�;8��z����N�rK��;��K��RWzi�s)��\c��(w�f*�����S��������1����;��4�8����pJ�c��c��tL������D��
�����nG��@�iI��H���~R��s�<�`e^��������2��
]�����2h�G����a#�Q�*���
}Mc4"���#�[V<>*�W��n�]��f��wE����G;R���G�c�e�I��������0���7�L�Q]_P)��n`P��b�3,�A�'T!����T>��,.���fs��
���
�;%w����k!���=��)3����D]��lJ��+X��W�ulBj�����"K�^L��9��@�.�����DU�hj�A�!@��X���%��aT��ll�o�6*K�/l��r��
������p�k�������������n�'A���Q(�~�
�;X�E9���L��Y�3�(��$�������&i�{L�_>q��P0K������C,<�����!���iz,d"�Y0K&)����s�xfI���L���i
d��+�X�@^��``����1��O���q����=qs
�3On��dk�S�L�+��2
j����AW��Z���wrdKMR�-5�6r�<h��1�v�d���x��;�L����D�@�g�H������5�P��ea��B2���g`�+p�:�����
��S��G����P~�u���~��df��t]�����^��F�����n���F��K�1�N�_�����F�O�/^����|�&H�n�A���o��NZ�������u�<����8�q�= ���
�o��F��DP��-��&)�ZI���4�����x^[�z�5���W�o��]�C�DV������W:0��\�pL�o��l�j��`2[k�W�������
����AV��M]�"���O������������(�>^k����^�
�G*��i�%r����8�&�Q�5�1?Q�������A���}Mv��<V��)|K���[H���&�D{j��:�$^�b+�y��ic�m��$����� ����n�
^�X�N�,�VZ��K�����#e�I�@�&�~QjM��b���i�:��``0�J/~U>V�R�:�6S�|��"�#D���Z����{R%Pj��U���<��%��������w���`��E��m%���g�d5�BnM��4��S�!�y��Uxdi�^��k?8cu5;k�5s���c�@���U�1��Y[t���i��>����d|�W'`%X���{D!��t���g����M3I��S��8��t��}|��c\���3<���4~O��&��{'��8OV���%�V<��-�y~A�TAjk�~��_b����c|`?�1f�M����hwm�����{����J���bu5��S�9e�e"Y��M�>��Q���p�*|$7K���]`�����^�|;
�E��`	V��|�y:<��\���2�
2��������y���7NO7Wq����'�S���i�}�v`�4g{���f��t�=#3�B�x:B��o����	�l���V@�7�u:$��<�2\@
��T�,tZ���@
��p�������j
G�/8���:t3�/�u�X�&��������
�D>R���V���P�nH�Qu/_��Z/��u�Vy��������M���N,����N�
���N'VF��*z��m��O�bqE��
�:��@���b�Dgt(T�1*F����W��g�	�������F�����	l��[��O���9)����	���6��~���a_�v��s6;I�r�{dL~EC��B�sE|M5h�x�!���N�9��h�{`VrdG��O�L�����^qb�� �`��RB��]�����}��`/�U��S�*�I���?��W���~�oL �/�Gude���b��qK������C��\�{������q�a�����c�������a
T���;l)��h[�m��)��.�f,��fq-E�I���[���^�Pd��2�1������.��
�'G���?���)��|��@]����@:��+����d�b���WW�K/p�
�$]��u��k��c,8$|�,x�w�:����#�_R%�;�����|�������g��$F$��|����,��DGM c��06���O2
K'9�����L~s���0_L14��H�)��``�=YL�_��h�y��l!���;�5w0�wJ�)K9�%{J�>\���p�%s1���-
������E0Hd��O8~:)������*�?C�lW�?��� �C�jT|��J��q��9�`�Ze�������p���,�"s������T���$)����E!�"������E1h���v2i����%xAA#�� �ND�p�~q��������~��o��J ���FB.������n�VK�P)����'$��(�1�����
{�"��h#�T��O��������U^	�p/dVQ�o����{��!����
�-�\��ma�G�3���o/������h�fHR���7��#��$�R;��K$�j����y���F���E��f���U
<��=E%s�X����{��<��G��R�}^��$Uw���%����t({
��#�QPL%�yO[�3h_�r�G���t��t���N$��8��NO	�V�	�3�8�Y��y�E��s����q��y~{ ���-�p�(������ex�y"Y�O����4����f�	?6��*����']���/a���������laU�Q�������-�����u�>M)'�#L�z
2l����Q�'��I��Y�[ B�b�t�"�%���~����A�U�{{�aX;Z#��	d�_�Y����(r|�a�F�J,6�]�Ha:�R�{q
�2�dO�@�@���J�a-v8���W���f��1����R�]_/R�<6� �c��N��q�.�j//W�w�n~D�8����nhaHeW-;�|0d(�S��B0���F?���,�i��Qq6��N�'VG-����3�9���I(�B�X0�\������7����)��E������J����G�A�����XtOp�1P��4G��B<�bas��r��/X��i���v������Z���G�������g�Z��g�1q����>K)`@�P����u�<��2�PN��x����(,1�����S@����.�� ���4���]F�=T���ZED���
� A2��9���l�����!Dg���?>�p�n�� O�F��R{��MT�p1�T2Sa���}�9��R��!1w"\Q��U}���)\E���
��4L3DW�\�\���������p<��T�-#��m�f�!x�-��������oN	����[CK�~�>}
��������\��H��"@4����e�L�]^4F����nwxn8o� tn���X��dl6�P���'��
����yN^o�������Z�-{���C���W���iv��^���^����S�!r��y��a���o�2�����`��)��������^D3���8�J�F�Q��(��8�5;d�Se@m�bL6�Fv�U<��<�lZH��c�8|�G@x�KY���]�r��������)j�QV4��N��p��C���($����Y4���y�����NL�K��'\/s�*�<1��!Xw	n1]oA�[�o9����Y!�����#M"���@���#M�������eU����aj���^�Y3^=yA���7R1�w�C�C8��Wl��v�~Yi,�9��3�x~Oag\�(S.#_�F�G��j�0�m���QB�-��A��vx�l|� ���:��������`����9`9�+g�{���y����>������_NK�9��<q�@�P���|�o��Aq:�����V��T��?���@��L����7z�8ot����s���>O�_��3�+�j���+x��W�_=k�J����y�����$��
�q����P�Y)��O�w��a
v����a�V��	]�j����I��H���wv����.��w�#��m)p����������|�}�����,`c
��_��������<��N�H�Y�ix=
���a��?���w�h�"��[�00t������s��T+����'�l<�Y&^2�)������#(v�U�+�
$Yo_\5:�U��*��L�E>����
yM�lN��	qh^B������\��7i��E.��>��
����`�\��`�>Ol���Fh����ar�C�(���~@U
d��%�
>����s���j>�f!V���5�����C.�$�]��!vV����am8��V�����a�OO��YRj�#����a�p��6`�:h 2y}7��w�)��''�(��bL�>b��
�E-M\�����"p'
�tr3SO�J.Q���	n�0��-9.�����tsk>�J�;l�U���}���I4��?�K)�z���#
�(�GEQA�Q	���
�=Qe�s��������������#���*�,�� ��/�����@�O��t9���� R������n�U�(R�j��h/k����%����{���UM��$��2���:���~}�sM�X~pV;?o��K����^�qN� ��o�� ���}�s\�7N��y��P�k���r�N��y�5��V��	��5���S@��.���(�o�N�"����t����Wh�>�4.@�����qw�/��u���`�w�U�x�������VWnk�p0�������n�?A�"7�,��`]��E�����8K��=k����{�1��������Mx7��/�%P���6&0}�������z���j>��5z
�p����J��S���+������;F���*�6�����f���i���y�{�������,�k{��|�:x	}x�[�����X����3n�pF��O��(�c���r��$Z(�HA��u����N�� �P�:H	���4@����'���.��U�x�E~)�;R�����:�I���:���$���C��`�F����"��� ���$�gk���L9��6�1�y���c�B����6����F.
*�U+m��t�(cT�G��b�����;
�f
U d8N��/�^��)!}�O���j�mEa;���_Z>����Z�{"y\\�~���$f#
t����*o	��`������z7��r4�D�j
���-��()�$nf�������$�����q�[��~�������~d���OE-�<�#��k������t&_8�#Nc-�Q&��F�J���1�]����������/�w���fT�Oya�S������5v���}a��/��(&�`|{����
�OfQ���'B�:�i$/���%]���]���!_C�_4q��F���d$E	-h9��7i#��,|��N��L}o6N�uR�a<	�/��������Vto{�nW��;{��*���<��r*�-J��_�pMg:���r��d���bN#�~�#�\^
��.�����[�V��
J�E&�`��/0������EE���8�7d����WF��L�����*�hxcV�d��v�
P�*�l7l��w����]&?��7e�Z�Qu�^�`�M/4���9+dq3��!f���J���J�A�����i���	/�0nz>�PT!ug(�,��ZDK��hq��eS�`"]9�R+��-��g�3W�E����2��2����2�E9'H��'�=�m�i���AT������,�e�1l9!g���t�(A
a����x��:��0n����<�-6�0�T�IG5�6=�V����R�i����������4�Ex#�k�~Q�������<�������<���
����&������Ff��kG�?��k������#L�''�n���}�>����"OC�����	�|aX���7w�$����������*�������"��f&M(&	6��E�Z�R�B����pEG�&��H������]p���<qg�$����9$��O�4����Y�w������|y�<x��`H�����&p��rH�z���dV4q@C�2�H��f�J��E����Z=���<Z�,��S���d�����W��8������+��Q~�RB�~��6�k3
����s�&l�;��I��(]$���iY���k�X����X�GQ:��0�K�o3�E�P\�p�H�����EA���+(�|]�
�Q9��6j@*P�m��5<m��0��&I�,��
R�gN��(���Z�q�g��_p�_Dwqr�x�n{1���
�.�p��k9�Is}idLia����G�.��~�m���j;�R�!B�<5�J�W��jnP��� G_�9�.s��l}3t�*���������+AN+i��oPnG`&���
��N�b����>����qG���A@�����E9����	�:t7�Sr�z�C7�{�����C��+�go&���|�=F\��8A��4������j�8���_�90�{����S_Qw����S_W�*�ck.Oog�{Hu.��O�g��W*�g�<��j1�7
iLFjFr����i�sbOT��mZ�;3����9�$x���#�����|/���}���]8��9$�!2���N�N@�w�~�G�?0"����D.����P��a_87���"�^wb���(�GJi�����L����_�.��)3p{���W���8DG��>\��!eyy�3��)%������F!s#�"bC�P%�1�Q��.SO"!~�����FV�Y�^P{x	���2�#���X�oW��W������{�qt�[���Z�`=����o7*Z|�a>���hwR���0��i��Y�?��/����QQ�$g���*|�=��
�12�e�'�v�����=�>�b�������FL�%��:���n@�ED���t�#���S��r].�E"fk���q<�C��D��
\��"��G��=�N�<;:Q�s����G���q��E87�W/�B���Ktt��J�~�EP����2���>o�����kT��k����=K''3�b�����F�O���p08:<�VG��(
�=4������b����>HCu-�V"�����^�|,�X��\�/��Bx���9���.�!]F���I��c��r��}���p!�HP
H��@7I��M_����#���b0����dJ#��!�=�!i����I+^�e��	��~k�Jn��J��O/�2�������N��s����j���(���v�s��C�x@
1I_M�Ajc,�Y�A5��(5��������l�����#���C��nzJ}�Ro[��f�t�.b0��`p�W��E�K�IP�dTj
����^s6����u#���:5������~�~�f��X��#�	^}��u��@|�_�~�TD9;��047*<�;VO
 |��������FF�5�����{T���=��M�����FX)$T��rJ`����G��(������1�y��|�����:?d��l����(�\���]����Tb^�tB�e���	FB����Df%G�/�f��,1���Q���;D3�V+�6(�`0���2k�?$Sl���/R*�`��A��jY���Z0�i��g|��^1���/0�"��r5]� �*�c���95	�$5)XO��)F��s	��x
A���<�4�������h��)��og���,fj.�4��Y*�jw �
�w�u�����>X�1!���������>
�����p��r�P_,�,������QAv���DV���T��N�����&�����2�2+�������r�{*�����L�(����h��{t8@�oP������D��+�dkK+�������x�sU�-�N��������5��u�-.b�_}{�����k"��P���r��!4�k�Tr�Q����	����d������Y�M�
 p�,q����.(:9l��M���_����@J�^$y*)q-��
v@J��h,��rv�6)�p�7:����W��%[��.�0A���"�	Q�3���PK��t)�x�a���`��uQ���)�eR��RMw~s����c�db�`���F�G�h"�%���a��ct�I���+hF��e�����39YFV��7������;A�$>���u��L,���Ed�?�����0�/���]�����j�AO����f7�f�P�1�l��~�����6T�����mOS�@��q+�U	��=`(�@�h��q1��`(@�G�h\�����?�����(f/m��c�}8�3TT�b�,A
���Z(���4r�e�W	�M���_��=��$�Y�0�Y�=�o�m�4R4����
�a�i]������z��y�Uq��H�Y��O���j����3z�O��y]~�gH�W��,6WO`�|!���r2'?�;�N#��7�a|)i+�0����^��,��x��8���==���#8�i<�o!K��0!��0����Qh�~�/j�j�O�����>�ni_6/{f�f�����jj�ZI����e�����E[���J�p��"GQc��A���r�T�#1����P��b������Z�����u�)��|#p���g6���)�Z@���x+c�
^d�j,C��rc�v����{'��
�Q�
��V=��s(c���#�������X�����E���&���
	��D0�:���X���>~��@-�������������\����d����G/)�6�+���9�ZpaU��J������(q�|�T��3�!c>�
e]a�{�K(���X�d�D�A��Ex��LJn�t.zY����B���x��C�#W��gnE��M�'g��"|���pJ�=������,�v��q����SU�;����N<!8�a9�E�����?�q��2M�j���R�X@�uM��;66VH5r��m�Cd4j/��c�>	����}9�I�0���Bk��Ne�#�sI@�Y��3�����~�����%������ ��/N�5E>)�M&�.�7���s��}d����
� ]-���U�B/l������aNB��h���}}y������"�2������ P���r�@���C���:[	���W�r6
!!PHN"2w�p�R%���v-�����^�uv���������>��s�@��Vi����>b,4����3�l�M_Q�a���2��\U���:��{�)�/�0�4�k}�#����8��kY���������;����A�����+�m��:����a�x�/�L<@������
X���:�7���Zz�V��(���]~��;yyv-����1�:�cJ�#�����Z�x2��R;�8d�xy����u���_��6��-'�sE``8��IY A�K5��8��))
�����<on�<��o���'c)Pj[�DA=�
��6OP��q����
��y}I���:�s�kL	��rVL��d�li���������)YN��t!�$�N�a�������F�!&��l��!�~]D�T�A�p8���d��d��_�y�ae/��?�%���)>@S��
U1N�h��C�MH��*��wdz��I%��a�?i�hU2 J�3el�p=�XX������Z�
D��;A�K��� LWg}T�}
k��)�O�d��P�+Hk������N�:�v����YJ+Y	O��5:,�rx��@S��d'��=�����Wx��dx�:awv�z���Uv�*�	�^�v�����!�ag��Q��`���A^y�-���FTvR0@�8���E������R�G�8J��d9���`!�A�]�MA|�����Z��i��(��/��:^��!y�
�0w8�
y��t�2/����dEK
�dk��lD�����!r�y�R��i��V��<*�J:�x��]�K�m>��`��L��E�`Y<���F��5����E�E�������Y���/BJ����g���}_�j4Si4$��$�#[nU	>�������]���gPU��`��V����I��8�eJP�?����&�uI^����6�	��������{3�y��&i�������z��-��S�����&U��O��
�W'e4����H��������5'E.�`�6X��T���B�'�w+F"j�UOn
�U���j(��V:#�5l�����]H�&�������2���^�����8�z����i�F�>����F�j%q/�;����5��,�����Y�u0�����-�[�q���mLq����{�u����
oCq��d	>�����"0�gc�Z�g�gy����2��-�X���Ga}<T���hg�h;�3��:��]H����V���
�\�3�7=�Ud�5���.1l��v�=�c.��o.��_��=��|�y���8+������������9M4T����6V�R$���y��U����e��	de33H�6 �������Z)i�-���5;z�����;���>]Dwk�Ka�%�,N�����#���ju;m��+��h��*��[�U��;�����E�8��d�Z8F��n�(d������l�4|P)(Bdl��i�����j��������b^=�Di0���@�Y�c�aV��]���y�M/��v�2�������EmhB��({9���+�3H������N�v�T�_����&�:�K�M���b�W*����x��M��)BE��Y�=E,�5-����^K+uZ;D��h�&��z`|R	��
s���P��7������(E&�}��gi���-��
A��)���E�rdoL��Q��A�)��o�Q�z9g��Dev��!�/��Y�!�G�:�<��;�����(��Rnb0���������7����Sz?�u���>G�-��'�x�6J�V
B���)�B��K�0YL��ih��a
8���ws���gl?O�Nk9�����@��|!Du��@�2gK���U����#u����E�������-�E�L�O@9_�r1�w�?�����}�m�
�m���%�/�M�bZF���D[�9 ���36=�@
���K~Fq�
���99��jO���&wI���t���7�����iRR�L�]����=�S^�9�����*���
���*�5����x^��Dk��RLQ�_������9e{n�S�0���v�����Y}$j�{��g	��mPB���U��]Twl��,�,CY^8z���|P�G���[�	hF��!L���r:�B�E�K���d�m���0��{[rUEcn��jG��a
���>��L����V��L��9�����"�
QH4�y�wE�|�X �obY�/O.N>J���8e��>O
�������G?f�������/M�c������{
���4.������=�pW4�����4�t!v�9������c�n9���R
���2�l���qg���������=<�����6>498Q�L�e����Zv�u���kT9k="��;��M�\�%~��;���J �2��s�FE����`��\���n8����X�n����t�=HIE�J������Q������x�Iy��J�����fy>�b�2@2` ���A��M�xRa��!�y (%��Z����:K�,.V9���t�PU+>5�]S;��Z���N���$|y�*z�	2�<��Q���u�Le���$����7�R��!�u���
��X��
�X�j�:�\nlb"���*<����$

W�9���d���l�Ao3G(�r:�W�!�C5v'WRZ��J1��2�a�{�?��ME������4\4h����,�S�Z���`���=I�l��X����^[b�r�bqG�A4N�OP��gk��os���T����mV��v8������g��R����Wv�-��mf����j����	P�#������31��cC����M� �����-B{�����=���j�2z�y�����==���G����Z=���p�r;��q	���R��]�-����:
o��r������k���8s��\b7��DhY����cw�����F���\�D�0��#�Nu�4���2�V> H�*��+��*s���0\������7Qq/�(�>:�5����u3�tz���Ze	B�Z�V.�����	0��e���h�;����a,ra�k��z����H>~��^�c��Y<�K�{{����ju���������S��h�h��A��w~:t
�/H����7���(��m@"�S)�����[�g��v��>P(a81�6A�������F���8�[�4���"�i3-%i~�������]��P|���_�*���6��e;�
��J�XI��5S��R����HP�r��7}��P����A��:�E�*����/�������w��m�����p��/�*u<�PY���m&��L�2$�UPs�{����]�H���9�L��{,l��g���}�k&�	\kC<	0q��]d��}���70�]3J��t�c
�Ze�3���;<ou{��J�
��(I����v�[�1�����.T���	��	oVo���b�6r��
i��
�,�=X�
��	���������p���u_�GA~B�Xw���������	�Cz�d�+"(Q��;�q;�+����0�T��b���i!��Zp�&���)�(d�H�o��o��V,5:m4���j�rw0�2�E����/eV��roT,��*|nm
v��{��vm<��{L�W����j��Z#���@�n��+��bj�'��n���{�y�����������h�1�������[�������{ ���c�5�z��1�2x7T<�Q�`��i\v�6�P���W�9)@j1W<���_W���{�`gN��h��I�D���(I��h<bs���������-A��>m����5�����M���<a)�K%����U`M��OC�jx�<U9X�pa7X`"l���E�~<�;��d�o��T�
�	�����OFj������~��@#Mx4[r4�he�@� <�3V����G�M`��2ry �u��&�)�{��;������@��]87������1
"L���x�:����� �FjC������2�j`�x���p��� ~�p
	� 8Q
��u����u���C�T�����%�9<:���j��v�3:�
!���:hL�S�D�Iz�s�7 �_�I�0>Z����}�<Km�8�o e62��Vf�pI�R���nP��A`"��>l�{uv���^�f�zP�����o�|V����o��J}��So�|�gq����xN�p�Lj)�A!���tV	���*����/?�Gm������)B����j�!c���Z��&l���D|l�7�z6��!�5���`����)��}��	�����
�� ��'f���6��O�.�����L���7;;��C1A���p	��}�&�.�K��D�83!g�����������H�����D��
���E��q,��h���k10�=�E�P�w��	3��W�����;C�C��}�^���X����lvW����������P�_��&����0���L�`C����'��>A���p3����o��+�El���������l;���2�%�r^i�"�����a^�&h����'���3������|Y����M	�A�f�����S0����j�F��VT�����2�6c��������DM\tg�r=�B��tu��JWg�����dJ|I�x��(�E8��:H�}CB�%���W
��|����d����	R��%��IL-_�dx9!
i�n�k��0v1������(f�7`?a%B�$1d]!]�D>�Q-r��Y������L��
����b<���;��S�q/�&��nb1���T�xM������H ��u���U�~N`��P�
��.�DWoh��]�&�����4��V��[�4vS�W[�T�����}���F~�y�t��}Y���8��y<a�����A�)�����at�Ld
amPV��<T�"����A�~+@A����b������_�B�M��Ac������(*�
+�N)�����v
�B�J��5	�J�����n����F���D]W��M�L��y^���J���S�
�����q�[A��R�N���f�?_s��v��� <�63�&.�����k_LK�X����� %���@�;��s��������H��V-�i�������S�j���
:o��/����z��,Me��D��a��S6�g������(h��ez��SE��gR�(;����x�V�u��w-�����������~��|>wd��{�,V���c��C#�1�@�s������g�����4�W��\Z ������1y|�q|��������C/���og$+W����V�/������vT��Ba�Dq��Ep����%V���3�x�q�)��e*�'�L��\���� �<�p��>�8��KC+:����-��Z'�p�pgpP+>���1����g;�����'�"����'zM��P�?���?�������4d8zrq�5OO�z�!]g&�0}�F_��/�{B��
�!����u*�u�~6�����}�������^��o������8wt�$�q���\Ih�x��Mi����������Z�jQ�Uf��ny�#.l�c���5��{�������|��Lu�����[��'[���`T�[�
F�ksLP�8���N�)���fW����0��"�wj�
���j���Z=���	�<��+@�-�U	Mdvw0�~��@�V�"���'�~���i���[=z��h��`<���;jw�)��h`ti���~��Y��=��`e�S����=��q�{��x6��,������yl[�����9lM!W�o����w��Y1L(O����X��q\��(��A3����;�V�s�47��]��4`M
���|�-Yl��R������E��G�,/f�"y4`&�;)>�o�>���JX�QM����a���`����5���qz����9W,��o�^b��G\��)��5�����=\7�f��G	Z��=�J�����V5�%��	o�Gf�/�����Z��	�����`gP�����O����Y*��@�DG|��`��i��}�sZ��N������u�<kv+��e��mv�k����O���0	�3������u�q�������^������������������������[g|��������Z��p���S�\,[.&��e8�����c�
h�x0���w4�f1�,���-�� sl7�lS���8�'�}L&)x�f�������������f���h2���m��[�n�������5(]4���-X'rq��_5�������|g8q�w���C�d��/7���	��H(Z��6�E�;M��1�j��iD��s �~kN]�br�3��5�r�[�z�(*��6L�������W7��]s2Ms[mD���(�1��������;��q�Fd�n�F�z�x���cK���w��Wp��i��p�z~1=�z�5B�`��n�I���.D�^�4�0
��y�����-�+�x����8k�����_I��g����EB��/ASN��D�g#������d�����4���kh�
:Zj��rDh���kH���K�r`V�T�	�(CV�g�x�4,����v�D^��R�i4	��'���M������E���Z�7�]$��`C0�k��:[p1���(<����(�?������
"�P:�"�����B�nD&��������!�`�k!V�]�d_��V��C���U��^�3f}��2��]���*y��\���q��bg���`T��t�Y���e��E�J�)�*���>]�!�X:��������S+��3�0�����1�c�1�����p�'Q"vQ�y�n�V���y[0&�S���.MP2�6��x*��}r��B���M��Z�n����������|B����I���?���
�_F��1��K� �`��k)��{���v�R~��L��=R���W�4���IG��T��W@����}����W���`E�w������4�{_�U�$?����<���F��pt��&q����Z��6&o�Y��_}2�����	��^���N�he-p�4	����P�@���������0J|��O��n
`����n����g��G�����X�p����pt��	�\0��_�]?w���v�y�4���vx��(�%*���d���w�N��e�i-(�T%��J&w��Sp�� �M��[�p�V~��q�[%�	|1�a�1Ys@�[P[M��������D��ll������������i��g����+Zf�k�N�"G%�lv:���@��BLO�n>Q.IH���B��m�3��6=m���W�m����x�9���6��L�F����0����x<�b�V��N��������}pR����S`�������Z=�O���aPs���@��@#}��h�B�����MZ�WMX�����%�uNd%��R������S}����x�������%{W<c�z����������y��-�D��VY7xb�o��=|$�]8-?3��?����g�^�#5O���p2#���9c��H��J�{���N�la�yzl�o�+B�
k�sq�l�5�$A���C����4��cw��M�ES�|��%E��:��l/:�p#���	�Si�d�&�$*�c��`���3"��*�T�s8���{_����'v�xwe�h�i��{���6+�t�b�;��~7���;��'�`+3<�=!S�&�����md1���7D���6tQv�7���Q�X~�.��E���nqS�Y<���5oK�����0w`p{k�������N_y������VU��	bZ�rz����w.������]l8�#���)���="3V�3�B�f���f�j��}2F��*��9Wu��`->@�|�Z,�,�!3���H�V����F�T��1+���������?92����F�8O����H����o�����5S���sN���iS_�{��� ����0��6��aO�/�*P���n������v�\U-�(v\;��W������� {^���U�$��������.��Xc���
���wy�0�gd�Ry����r�mS��e�rp�Y��N�"�����Y����5\.n�Yb*����-�/�%&b���R�CF���V���Xd���D�%���6�N,��f�\,Ui6�!�,�iV������%_�HBo0��vBx}��4�e��!��������(�os�3���^���L�
0l�?V%�]����mU�����'n�6F8�u�w� z�0ns���^�������p�`�1�]\���4��_��/>y���qx7�������O!����(%$�P|u�S�	X�q����3��
�TP�Z���0�FS�9>ZB��5*�������������h���L��g+b��uP9BG��U@��)z�Ax��KO���3�]�,�h:�X|CE<��@`���dHJ����`���@��i0�n�w�x���0��>�"��
�38C��!��[����)*P���y�4X���,���*~���Sm-{�����A�g����0U:���,���U��J�m�$�^��F�|�����`��g���S�+�>��@���1nF�n�Oq��9n���:��|s����V��e5�9Z�?�n��n��Z1%��{*�>=�"I���z�������O*p[�	
0004-Add-SUBSCRIPTION-catalog-and-DDL-v8.patch.gzapplication/gzip; name=0004-Add-SUBSCRIPTION-catalog-and-DDL-v8.patch.gzDownload
0005-Define-logical-replication-protocol-and-output-plugi-v8.patch.gzapplication/gzip; name=0005-Define-logical-replication-protocol-and-output-plugi-v8.patch.gzDownload
���1X0005-Define-logical-replication-protocol-and-output-plugi-v8.patch�=kw�6���_���6r����NR'���d�:��V�8�]��$n(�%H;�����$����7���$�D�y�`���(�����s�{l��8O��}�������_YO{�{��W�~zh����g�<d����}FX�����0��%�#�-�\��gG�?g��o������+����7�#�m�C���������S��p��d�On�����/�c�;���>��&�my,��bP
� ��c��� ��$f��L\�^o�Zu�vGD�w2�:�u����Gv���[�Sg���0�xBc�LuY�{�;���(;=b{�������Q�����\8�|3�$�Bpk��������HHH�o{������L5������~����{O	�'I��Z��;M���1�<��������=.��v���8()���@����u��Q����;�������Q������������1�:K�l��e�����������'O�cP<��R�u��������z��^��~������3;���kf�6c��v�[��pI~XVguv����_���#��\�����x�2�������b7���3���c����'���a�
�QG���a���,�����������(v<]�o��=e�l9�BX���^p�DlEH�hN��<7��������N/���/�����~�}�vg��;B,�[#p�g	�f��^}�v���9�x���LR�������G�5D��Jn��Y�#�7����7��`����\���
���RZ��#�_K'4�DN+�s����%L�[�$
@�q��n *���39.;��"���"��3���8��g@�f��d��j�E�lr����A#�h�`
J�'Qczs'���gt�t�"$�0P���Hs(��C��S p}���������($�}|����AaM�����"e�q����H5"nsPw�n����o\���	�P(x?StpRH�@��;V��jB����|6���$@t����N>��'p^?3U��!�#]to�]*WXm�U~��U=��"�������8���N��qY�������m�����(����b��'����	n����������`������<~��H�	�K7�2K?e�<�Sb.���I�!�UU��)�9���e�E4��\|(��O�	r���I	�.���m)����p���#��E��dx�����9�6���0Y�2�����Z���)� �(a����0�o���5�Z��?��h�?`@@�l��tHa:�`G����	��H��d���K����!�g��[��Z �&
@�dR�P��Bc&s���a�qd����H�����"��P�bB�����s�m�^�M �sCf������PDE9Gc��D�B��#�������k��x�W�'o���}��4	���#j��f>|��E��Gi�o4����$d@�t��=�����>zNz�Z���d �!�&��5�W�N)8F���|�me�����
;	}%uW)y��#�������?��O��4�uw��H�����<t�@0'���{��D���\0�9JB��q��,��(��X}T����9T�[k.��G|�2<Q�9�kl�D�vd�cE#����{�k.��eV��6����\2��\�Ny�H����5���H�B�&�������_�!���B�]����l+}vQF�5�2��s��d�9 �4���L5��RiS������]���S+�:@0SA����(.ukT��$=�k	����L��X��Z-m+�9Y��Z��{�fnIN��B����B>�@W��t.�WgUsa�n`.�I��O:� ^|�"F������D�q���y '�~cr����ho0�L}���h�I�����-K&�����;���H*����!5��x���s �����8�f�?��;�Y7i������]S������O�9�	����dJ&����Z
S�KU���F,��d��/�?��2�6�c�v�PL
k0D:7��Q�R�,�G�s��gd����n����[MW_A~��x����
������%W��	���3�H��M�J��U�������h�k� ���s����g�N�W��a6:�$n,/���q;��<*��3�����B��/C���������v���>���*�_�6��G���"�(��k�\@���}���V���%������}~�0C���?��������~��b�F�f��_�KV��0E������|�!�g�`��4��0c��8����;;k!`�@g�u�'��m1V]�U+7����Q�2G<b��� ����$vC�8�:�s�x}���T����N��k�QaB�ys�~��������"�YV���x��d4C�Y��!�M,��}n+K�
T��B�1d��SSjUDC��8�,�UMju����A`GN���fx5m����To	�d��
_�e)L�!n���R�1����U>�)���m�B� ���5������r��X���~Sy�V�sEl_�gV�^@i���*������D�g��d���J�qE����F�g$K�2��2Y9;J�x��	R�?�T�g�c�@�Cu������,�W�t�T4kC��~yW�.�T����7�������\B�/3|�]3L�?�p
UV��>Q$��1��V�
\_@������-���U���>��������
�Q��+R�T'��Y���|���h�=p��+
+]Z���?��)�c�@�K}%fB��z���T?�r�2����L��.�l��K~��`��,�-C9����}����+	kUed��].�@�?O7��)�GeT���3	g���#�&%1<��u� ����S��{d�+��luQ�N2����q��?��Hm. �N�j%i8JW3/<UQ���C��8��>�7�O�
2��������O({-	'9'v�`!�8n'(���g.�ue�N��8S����X��&��������%5�����Ww�����p����UC���������v�2pYN�<o���	J>����s�������k�
���c�.����AEV��g��{�@�-����jG_�Y�y���"�*�o�;U����6DfY����^�?U�2v�^'3�{��c�Ro�[ {���Y5w�5w |�����>=<?�������]�{�?��� ���Vy���<�=���^�����s��:��s����*�k��ew�z��c�@X
�*���i��I�z
,Dc�z���������n�x���V�������W��Z�2���y����G��W���6� ��R��vs"kO�`dy�U"�^�����g/�\�g�Y�����~G������W��\������;�}�w�U����"��/��<D��Qo��o��d;��}
pyZ< ma�4�9�����5�v�������H_y�Z��\>�7H����J�����~|p����������x�����AU�6��)M����o�{���m���}��?�jK�8�4i���8� �i��.^���i�v{Cj9�
��p���7Q��i9z�N~��Zm]�([�H���?Z�vBI�hOw0
d/f.�h�����g!:S�
��}����[�5[�_�$QuW����?@FU�6�t��>����������������T��5fA#K,��x����}�����R���ID����:��27+K}�C��6��;<9&:3t����������Y�!R�����Yy����Z�'x�|�og|F*_F��|�b�����<���'#��,v�{<��yM������r���GE�ie���k���uXv\gxA�7Dc
����q���4�N@���+@P�c)�&2���o`�4�����p��[NG�o2u�����H�cK�J��aee�������FYb�4���)bDLF��"n��\_����
�����+o'��DSd�3����-���@vh��8��N_������8(!��<o�j��,�xp
����H�nB�=��������q��(�x��i�!�
~E88����@3�D�CE;L>��s�����Z�C�'��OGFk:����Q���5<O�uh|	�%�kx�
�n�l�Ys��A�vI�5�1^l���8|�WT�t�!`��0M���@�Uj%?�vt����`���5<����7��4Fx���G�0���+�X���X���t��/-*-#C�)#*0J@�-ZJy��T{�a��u��j��Do5oU�o�Ae3��N�������N�t��W�����W���
���%�yDA��t6��U�4����/��v�J�mC�?�v�����L�Y���E�KA���T�t�������^kR�YyZ[+
����H�I��*���W�?��?��+
��b���A��d���Z���G�v�}�~�C�XD�Xwv�`��
�[/�����dHu#y�a�����h�r?�-��8���|�iz��
VQ|(q*�����]��J�2`��q"kd�JTMx<I�@���x�����������/A{��&�����+���=3��������q�1$��$�"�����B�r(�E*
���o��w�Y��zJ�&?w��R7)��}"S< �+m����Z��Z.R$d
O����/w�r]#���S�����%d����{
j��t,1x��e-�E�F�SX�v�Q7i<
�*�!�VsSJ��2L�����8�1P�����f������|��K|�#�;%A�,�� e�2���W�Z��j��dvW�;�
Y!+���S�&U6'�6W6�m^
����1@*}�M�YfG���YvS��
�T�+V�����9�zop�a�-���	Z���$��W=
c'	ig����A���O�����~����r�Rc"��[9�[�}��)����4m�����m��Y��ON_�|w6`?��R�w���3��}��'�?f2�Vp�
�?�6���������1|t$~W��J�k��������e�L�JeT�<�H��]������;5WD�W���-�H�����Tt���j`����m�q(���d5��K��8nT�7r�f!��!�q�5���@����fJ����5DJ����TRZ�.O^N�b�ro����a�������mb��
�������>���8jn��UL6'V�Wl�z�U��d��r��������q�7�����SzX�y�������Rm����
���y���V��3��x��G�x�-��o�8�{����.���V�5�}�M�d�J'��Sn��iF������s�����x@�D��-��RvDl-'�\at�@)S#+�U�"1h����K���urzvz�%w�n������J�
���j6��� t�8�q���Y�Y���Q���W����k���.�y��/��k��
�����:���/��������q
���"��*"�N�h���Z��J��XBq2�tsOuV����H��qH=��=y\�#�o�U��B��\�6*��Q]~]��
�hkp��4��������vu]5g�p'd_JCA���J�����~����.8�{�P��~-p�vC�����Qn��Zql�|�� ��Zc��P��/B��r�x�^��4"^�������q!h�&m#t-�,(�TC3L�QTc_�G�G�!U�#�s��d���
�Mq����d�T��>��
��*�V�s���@�p�c`p�|��o�-
)��j���^A;T{��|\��~*���O�i&�wx?g������\����������?8��D�d����M#�_R���{\Sk`�(G$�1�� x����p%O�H ����k����7�������A#�Jwt�r�e���[�SK�[/�w�f��E�J�(���.�C�X4�e��*���{�2SASy�<d=h�4?n��i��eI`w�Y��b�j�U�	���sA���GNg���]/���6���(KM�}VUn`�>���2�i���;��OD��k5:<!~~k}�&/����~��j5W�6��
Ah��\��k������7������s�+jd���R��#r� Y��r�4�����/��N����h�G�\?���R������h�i�&�e�Gt�>�c<a�� �L��72�	P�������h���-����oC��l/�e��w{��������_�(��%���:����$�V�V�g7�XCrdqM�ghY9����6����g�*S3\�Fw����j>V���u��YFX�]SD�m������~��I�T�M\g��B#�((��{cw_�E����PQ:�S��l�V�
s�E\/(?���J�S'������<_1���0��T��%N����XVg;qk>}��U��g,���hQ���h�z��y��\����g��G��7e�AW*�����9��^�-��lu��b=�T��e�1U�]_wK�[���Un����fH�\(u��+����44�AC�����/EA;�u��=�������sE���}~4M�-X��-�h��T��@��������=\�������]Qp��������i�z��f{%�
v~CM��P��c��	n�z��zYz���!h@��
�����0W�-��>+H�w9g��,�\�QH�������hZ���M�)�����h[�a����6f���Gz7���R_�[A��v�c�.s�]�����l*�!�7����j�{+c�����tS�����[�B�W�����R�>��`�4�"���	��j����Z���atuA�9���5�Sv���I	^�1�Ti��~�#t���9_5no*� ���~
F
�@�����E4|j�!����
"����
1����le���'���k&����^k$Z�)�54���8�I�V���#j�adVb��
���:����� ��]���X�oj�W)���{�\�f�F�#���l�����*����q�~��ehT�46��T�����E��w�>�-���[��p�	ZC���G�{��V�PMq�(B��A��h5�/�!���@��6��e57Y��o�����p��Q/1q�xv+����,;3J�����C��|EV���Fp��4����*/������aGF�
B6o�����C]8���{�5;_C�X����S�	"�Mx/
c}����'���+[�c����-�zHNI4�x�YQR�c�aGR����z����,��]U�fK���]u������!����r��9��@<��*h���Lo�Xn�6-U�����Y��b[�W-=Me�TV�����L�(=H�rO�s����%�����s���L]V���1��pyS�q8T�"�/�F3�f��)��,N��'�"|P9��j�M�	�����R����eD��}��:@8��>�t�������q�����DmYO+P���~5M���^S�F��tX2��w#���%��r7��[�=�.�����
w\3U�lM'�>��e�r��|�x��E�gB:��=�:����h���=��N���k-��RH%([
����L����[r�0�g,�o� ������m�D2$A]�U��hA����w9�)Yse�.�|���P�w��:�4��Z�4~����
�zt����8����s��0�����/>��+�8�w��N#�.�	�P��aL���x���Af�X�kS:!�(��g���__=s�����������������$cB.L;�����xc�����Zs�������'F�?<M?O8m92����~=@�"���x_�fW����p��|P��B����4NM�cK�]���PX"(++5qX������g`���&/y����a]�/Q��O�/�;��6��H����CZ�����M>U9�'x��L	&�p��#�I����lYI�j��~������R�m�L@�f�\���u�7w���"���J��l��C��No�)�eU��zu��Z�x�ui�����^Ys^�A�(CBU����,�����x�����s�i���kmx-8�}rr�E��x����r�����z0��g��p���T�,�b�GN�������DG���IvM��g0{;�3i�E���$����|\i��{�3�d>gO ����pq���LP�n:��`1��S�L���2�&����|so����zN�v���/5�n8�m3H�����~{�ir��d�	{�k��-�z:u�M����@7w?��}���	�xh	O����a�`jMcS��6�_���K<�ak�&��j~W�z�N������xU�2n%m�;O�{�)<�uD���[{�pA~�������7����[��lhpv^�~p4��I!��~#�u!�i�R��a\(b$�.�r�?�����������"��_�� ��E��%oP�xG&}K�;j\ �\~+��)"Q)�E��o���o	�����r�z���.�Ri0$��:L�:��
�-�K���Z�"F�I�����X�S��eGY�y�-x�������ET����!�{�������t�!C�sfrzx3���?8�" �F���9�'��N�H�Wc}q�������N7=�M����v���N{�7}*��'@P�&{���w�C~2�l��`r��i���	wO�����)�������M���&���X�Ni���`��:}�������@y3�L^��z�N������O@C~�\"o�&��:h;����e�o�����
�
Gv�dt,l\��[��T1���S��5��H����Wqf��	X3
���������O(!e�%.gtU�;(��.
v�<�`�l&3��/�"��L�q 4{F�k����c��1t�O<���Q���x����Wn�����}�yZ�b=�>�	5n)���8u��{E��C���=��P`B�cy����s���4��p�M�)TP�6*|���F������A�����������vV;:=��)�
�i�	���]���@�1
{�����4�h!L���
������e�~|�����t�����u��?��%qE_��d��NO|�3�J*8GI�9�l��r�d_�B�L�|i\�&lc���C]!]_�Z@Fu�"CJ�	�w�?G����Q
$��DA�^kkv7�K�6["�JEUF������^��L3����'��o�H+�#YED` �^v����N�����^���]�:�ZZ�O3��1��������h�F=$\���dv�.,�d	!��d��
��-���i�\��^[��0�M���<F(�N6��������RV�
6K����8C�`G�{��������S{��Z�8�6�@y�����Q������p��r_�a���\T����l��@#�$�u����V��P�=�HL`|O����yU+!Q�[��}�v �3*u��h��(�$��q#2q\m�|���)�t4���4��dqdW-�	��9�/������v�N
}0C�v!��^������W=0�z-{��a
��y�L�m��:�����[T�A(�?sA)�F����>���3�#;�e>4�;PA�P�n�W8,���k���~�Hr�5	q�V�_/f���AR���h�lK���j�M����F�c�S6����t���OoH���`)\��������2�,��`-ch���0�S���is����Zy5��]K��\��B	`��x��5���!��7��:<9�+s�a��u�I*I��7V���6L�1b
.��*���F��n4f��Cg5Ey�����I�W�����Z�L�p�c�������L^Z:c�G�4��9�y���C���/_��^�O_���������;M���0��O�1����`�**�a���0���6�x65�a���f��n������6_�_���d�����(���/�4g�
L-�.pTg<��t�fM�6�����,�r��p�X���Oa�Iq���u���h�Yr9�V����'��IV�
�8�6v{�1��&�CG�	�+mW�%�~��0�AF�#@�C�-^kb6�:�
�W���`x�$+������8x���S�������������D������$��Pu$
�v/�`A�.�i>��N��Y}]}�jS���,���FF.x���]�bS�����w5L���	YJ)����/�}#���;e^�,]��KH��3'a*�d|�,�u(Z��n���57�2��\����Z5s�������I��f��ag����m�1��a"Pr�2l2�d�w��7���F��5w��\�y���Kh�hl���[����V9!����&r�,F��dG��RQ7��J��vmx��J�p2��:T�t���d<���b���j!i��I��h��yJ��j?;yx���a�u��s�u��]�v��*����j��^$����P�T��
XY���E��U��=/��>���������V*�#�Yuh�=��<���:��@�{�������w�IRL��wl�p���P�6���A�Ie�6��tw��M~@�����ER����NS�8S�+��qNZ?�,����*'��v�'����c�.v����Dg�"��t�
����a]7c�V���x������w���Q�Y�:X�(a���#�B���/�D-;�k>K�l<�������0V\��;����rrh]�*Q�^������B��T�#������"������C�	h��������r:B"�b���A����r�M���3�z����`�������0�[n�}(�[�f(�tk����@F�g@�CW���z���f�	�p�7�O>���������M��/I-�_�S�zH����]=�R�����e�B��=����_Yz��������
�C��>�1'����g������s��l�K����R%�(2R����b�)����9�(c��'���\t�O�������+������crR���R7"7~L�/�H��k����y��{N���W�d����gX��>����F�E�6S�����~z���JD�i��-�wzZ$�W�/ C(�g�j��W|���,�w�e������;������+n�L����Lq���7�(DI��hR��e��hx��	�mD�,F����O�>��z�\�oBM��7}�T{'����d&�I0 �
��c��E=g
T���c������#q.U���t�wX=g������n^�k	yv\]T�� �U�����Jm�|Is���t���G�0��ct).���|��^�U|.���/�>��?���'`�P5����I��\��V�#M~*������ohJ����4:�#���9�T��nZyn�n>�����M~E]oJ�x��$�+g��oL�;(]\a�~B�z\4�%J�]�4w���&�N���`@��?Ca0d[j��@��M�$Y'��@����JDT�RM�[>�
f�;��a�pzx55�i6N3VH����,h��1�d�_Cw�~dj���
!}�"xN����b�m��h�m��V�!b��Y��(�B���4���3|���M�x�(����wG[���q �{
�Go��k#���&��k���Z�W0���SS5_,�Tz�����4��B7D���}����L�/M���)������w0<;A�z��v����<�jc�8E��;���a����a�jp������mU�U/�N�}�H/D��f7�� Yf�Z�k�(Bs�9�wt��5*���|el��+����1�N��~���WU��!:v;�������.��#����_p�
���m�
-�`2��cs/����<}�BV��������D'+�l_e����l�<{�*�>)�,���Z���&��V+����`�?@z��o%�Ov�����;$�GO�?��;�h��vx�����Ph��b��Z��� j���a9\M1�Xz%��5� a��/���4� �(�H��bZG�y2O`kH3C,�e'��=.l?�1�����%����^U�����_����
�Q��	j��p�R(���-C�i}�P��.��%["�3�Q][��X��t����L��Z��e�(������(����vN4�<�l�QBw����M���$�s�v�iPd(
v���I�'6�\��V���\�*)��b;��b��}F������^{BK��~3�Xn�)%����>M a2��N��Q�M�z1�ICP��,	�O�C��C�{�4,X���Ya�@��}X
.��t� \�B�z��!R$DD���|�W�"��*-r��e�������7@�xW���O���3�	��H��P����W������z�(���&L.4�r�AX�������I���	�9,�O~����4K���z��o%��n4@R%�E����y�O,�x_���6���o��5����'Ti�+6�x����Wv�S��=>�����BN��\�|��~��;%YE��l����0�kl��c�V[���~������"��Q
hS5��%w�9R[R}��Hq(.����G�����
$lg���lE+�w:��=�@����m��'�����_���r�y�E/��uGn��F��I�;vc���Zs��;Q��RW
e��z����J����UXq��
��:v���"\�^ h��q�e�[h�
S��e�?}��������u�	�R����`#�/ 0~K�������_If�c���"*�f���& ���"��F�a��x����x������{+6���U,���wt��O��C���������,i������>���{��(N���
0006-Add-logical-replication-workers-v8.patch.gzapplication/gzip; name=0006-Add-logical-replication-workers-v8.patch.gzDownload
���1X0006-Add-logical-replication-workers-v8.patch��{[G�8�7|�{� �j���d�n��:y�<zF�f-i�dLb��n}���ws~�FH3}��������q<Pa�Iw��O�������^�����n��O��;��ngo�n��x���H�����S������/��f���p2V���a�N};�� �����omN^,���zv�����w����]���v��������v&O�OW������_�A����u�	�j���e��n���p�,.���-�n�YO������%�����*��?��j�v/��\����j5]y:��W4�7�]�<��y�4���v�*�K�0���$G���Q`��B��(N&��p�e�>{��q+�M�E������;��i;���QNu���]c�v�y��A�&��d�`��!�Lj�b���Y���dZ�����kz�X���������c����?U�KW?��),��i�.D�+7#Knm�rD��)s��=UV����N�����`�jj��,��6^o_�n,���Vf�N��u2	rW�h����k���U�V[�G����Av��=TM?����nf�����5o�4�U;��w���[���������A7�t`4{���^���~%���=3��&
a*;a#�p1���Q���������]�?��E����w�`Tl�a������I�O��p��%�w����7�DIg�z�)���u4�����P��uk4�;�������G/or*��+{������-]Y�k�h�����sP�`��_{���:����t���e�]�����qs��hTg�����t�h�,�Z�J����E-��'a2�q��U^sj��q�B8����F���������%�a#`�B �>l�x:)��Tv1z�vM��a<��G����� ��e��e����Q��nq��:p�-@�V�&^��G}�r}����������gj�l�;�����I&��.{{w���a������\U{OT7���`myQ1���Oa6v������9{1�j�	����4|Pu{
V���=��&m����M���v�=*�v�*���Q!o/����L�����Skk0[��H�������~P������F���mwv{�2JK�^�}[������v�;;U8������/�E�����q�����P}�L���y��KX���u����U'N@^H�����WN�D����&�x*��$�-����R��1,
m���*�N��?�����7���b���[zQ������t�_�v7�<AP����A�b���
?D���\���^b!v�:��a/�����3�����A�N���	���- ��6N�w/�������/����o��U�d����8�SqO��
h�,�b��)	az�p;!��y�3���(<�a��k���x�>Wi�>,�]�������������v�K!G������^��������,!G~�r� r�Q�>�=���qvu|�#n�^t���������Z��T{��Sz
�_���-m�fk��p?�<�EWM���q�q4K�������8���D]O#8\����he����H[%+j��ih������n�S��=	v�����4U�V����?����]���C��{��l�4���zuy~��@~�!�ps��^/	'$��x�B�i:�,/?����m~�O�{�����Ujt���x]G����>�d(	�!�o3pG����9�������O�9��;
/[�b�=_��axK,�������Z�������w����i?wM{�%���,U���6�*��|sA4_��c� �0�����7���{"���e�WT�OZ����+JT���&���`^�i�����J

�H��17%|`U!��U���h�7��&w�2M�p�A_��]x�,g����������q�L�$V���${
qun�h#��>�LT�P*�vQy���I���[��n2����8�r4��0�v<���d�d��h:�1����]&��:�n�����}��v����y�{�H|+]/�k@��O�g�U'$����3J@�C�4�W����@}�������������eV���������`T��m�:������"b�ZrM������*�6g��_Q1�M4m��I�G@��U�a=4;���'�:.	��Ni�tk�%���p�d'I'���\k��F}�S�0�����c����g0�d b���QrOp�j�_	��b��dp�\]0������RQ��F�`.	�'���������`#�YI�k�h3���F0�0��%�#��5M��bL#���=S!p�&�%�z���(A��]����5�,���5�����6,9�l;x���f'�8�1t�h���&3k�`�5���8�����O��E�p�v�a�F�������n��a�\!�FC#��NA����������m(�E
�>�������Gj���*��{AH�a���Sg����9��L|l;5�����~��#t0�O��B,"�-��
�-��!|�x�nB0���Q��/6�K��o���0*��P���������s@~�������1Gc9�&Mi�����t�xx�BK����	���*J�U�k���wi�8d���&x�;���[8�dk`gM�vX�h'z<�:�ZAhu��CX��,���T7����8��/�����p�>�D�hVKAo������&��st���������
c:$�5������t�������d��q���[`�4���N�=��4��$��Ch���T^�3T.��h������^����
���p���|�A�D���z�i	a�a�3�>�,�
��Y*��	����i0J��'p����K��B�e:
��g�?
i�ef�B�����qqr|xpu|~��f8�9q���j�ux���:B����LRh��U���}�<G[��
��,H1��4vD���|!2�:7��IAI;�6
����-`.\���K�zqV*��������aqanP�#��M|���Ei��q���0���T��h
�mxL��!p_��?@���������UD�LA�3���	^��N8� �}L��"�G�fp���D]�<i4�R�W�z�&����G�h����a��qw�����d�;�y�����b���������qT��7GW
_���'
~'����1�B���f�C�ze�@n�:S��'F��c�q��E&�?P��oHu|�j8Jb1��$���<I����';�x���6t���7D�L�r�p�E*�[��?��P/�?]!��(�{����u�;��������0"����p��9Q����X���F=�����2��RW#��j��>|����8�L��y2�Y��Vz�D �����Kw�{�S����
�����e�&gEc�/�KT�m8:b�X�wo��>�<�0�]�����|Il�)�DhA�;
U���-z� $�2��}I��	�4�s����u�:^�d?\v��?������GT��f��@����M#��p��m���`��j:r5O�z�����p2D�P�4�7�����N����Z��0���@��	��mg�vg���!��d&�<�\��������
���a�@�M"<�Pd���-�i9!E������h\E@�Z��X	�X�u~c�0Lm8L�c��y�N7� }��	�m�A9�G���s"bg���Q@���K��:T������v�:������GB�@��`|����&�c8{��@|���&Q�&w���8F����a����zg+�A���-�
��xn+�Q��PB��Z���F!���5i��A8�S��4��"n����V�nD<Fh��''��Q�h�\~��=��9����L���o�o5��d9��P��5�)���ZS`�>+�m
���
F8�'�$v����}���x6G!����,R9�&��2T�+��Ct��X��LRz���A<~'��8�F��S��ba|�G��D%
��.�"�<v�i�l�>�3���Q�`��1x��"��+pYQ/�s%���{�h�3B%��R:������*wg���a��X���'��z��;1�F�����HCr:
D�,��4��H$����,����O~�����Qy���~Z�E�������!�O"�wO��HOO����w'i�d:������.H���1X�v�13�qF�G�l(+2�U:v�5�G1J������\?a>���e^8��/�#�:$qsD�\�9+
Pf�U5����s����(h��������)a�F}����ib9sd����� ���k���3�`jf(c�G���v�7X�l5uN@�M�vM�^��7�������ZP��L�Erh0S#�������Y��t����!�v����4����-��	�����L����R;��aP�5a�i�	����KP�'5�����eO�u�6����T��]7%�Ja������I���8�v%�!Dy_��oG����.�e%2�F��C2y�,w4,�?�Pk�@@��ib`���@��{���2H��)w0���:��=�}����9���#���;�M�B����z�v��]+<���QH�E7�����|�;{����=0r#�
����^���/��p����f�������L%�N5W#)�d]�']�;���A����aK��O
R����SLP�S�t�NL��b�.x�f��0L��������A�Or�*t��6l��g���fU��54��d�G��j�B@����~��+�SA�<�0���+�.��k5�L�E$@��(�*���+�'��l����]!�i�����x����i���J���c����+���B�i�
��X�����U��QM�%��6l!z�
6��w(m�Y����9\����o�;X���c9��z���E*��5@U� �R"�
N/��,�q:�����f&���f�nT�9�����C�v���5�UZK
-����)M7+�n	����) �E�����$(BR��]4$�%o]jvH��z�����3������]�D���.h��p�Z)�z�R)e�������Q
��=���7��tDo�����ry@��"��7�n���2<��X!���������8x�k��[�!b�\�hIR�xl�[���|�\G(��I�&&v�����dO�(����X��qwqV����lH��:5�����a'@������,�C����pM�R�i���=�3*Q�th��S^+8.��Ksn�^�%3�`�W��Bw�N����c�0��{�
^��Y7Y�"�� ��LR.^�u��1Q?����bew|K�`���������~VH#��o�86��x�����[��@����(������X���0�>q����N��<�>���8E�VB��q���8 }d���[�����G �hk�u�F�F
hMJ�!;��Q�g���b%Y�Yvj��C"�M~�E��������t\�tM_�!�!(}�6
 �]nC�n%b�("z01vt��n�A
��������F��v����(�=*�G��:�
���gW���8/G����!���I������@�Q�R
k������(;-z�L��R%=�L9��b�v����v8w�i�H��k���j������u�1$��uE�\8:�:UT~��i�^a�un��;��
�F����(���U�X����	�NsD}����	]������jd�c%�t:z����v|Mz�O���*c+��^��=�e���O�Q8
�rs�\J��R����W�� S�����X�A������V#���Oj���^�St���'?��-6gn��am�!�S���
07�kW2��d�F�T���.�G���S'K���~�����Y'��H����i�kG���4Z�����?ulB���L��`l�@�"��F��t��h���������c�k��������������q#�
���C��r���Q�zcT��,S^�Q��=u��I��	��L����Z`�+�Rt���h?"��5�9��k�f��g�&�k\�o55��}�jwo~/��6%�t�@��C����W���N�1�u��0E��������I1 t��`@�=��|��y�}��w�$r����r�j�_��!=�a�x��	������Uu�M��7�_H�Y�~>�W�c�Aace�2�[.Y�/�I�4�6m�!n�.|;�7��9u��Y;�����p��$o��u�o�`��AU$n�`����W�}�p��O�N�=(*lJ�� �:�������r.f����|%��a��Q���o�"E
�;��0C3���:�l\5�����M�A]����mQ�����~��g����?�V���������J�=��N���5���nI��^+��v0�!?�>�N���G�����5�MKSV��=G$M����dqw���&��k*N�v�.k��9���s��te �5�
�B��Nmr=`��"*�.1�b(�C��K_Kp��n��d?���;��z{�$F����	�"/akc������@�����E�O>�66�w�������E�������m��Y7����B����K�:pN*:Q�����p�^��o�&����f���+M���U":�	n���t�����rR�p<���\Q�.[�8��������
�S��?�X�v~/��MOEl��>��e���D%i6OX7k��4\�0.��[�����zuo�VB���nN�h�"/
z�>0�]h���D��w���[,��X�D�VRv�+��XkW��
���>���\q	[7{�1�������v���������>�	��������`s\^���r�����e��)3m{Q���+A�%����������75>���`gH�z��4�=SX���,|�A�f��!^�?F~�9����F��9�>P}���'�#�/+tBr�c_���������i���u������D[�����a<�ArM��P#XL��H��h}��?�������'NH�k���$Q6�"c>������������~���y�1Zf����$�eS���9���FW���s�z��@F
P~�I&����\MI(|�����N6���N����<���0���W!p��99A>C"���:E�|��le���n
�e����	}f��M!�E�
>=�6+�p*�I�k8�|>$�Kh�2���B��+���b�O��.y)��~}{k��W�m����n�D����H?~�#w)�#��������Da�u������
����r�u\B"?#Q�hR���!���jqq����j�'�����N	���^Yq�6���}��st'�"��N�>P@s]����*���L�t�	���!n
l�
&��~��te�|�`�y�A��r-��br���'&:4�gX����D��m������'�5jes��o�� ���'��'�Z��no�����\����+[�B�nR���j}��/O	��`��������&�Jc�q�t�y��q�7Q~�1�aMSn�0��������!������vK�
�l����#�G$d3�O�f'j�.�)�������Zmc����$��hv+�=��Q�#�l����EC�%J}�>�5��)H������A4L=��?�W�����^��r%%�}*)�ZT��%p����m�*������� pey���8�./���I��x��.Hn��VV�|1��}yq�����h���~�
����`�H��
Uc���5l����Y�X�Cr��^.�$��;�������h=+F�NQ���\�{a���	Dm�����bt,l(���Eq������gs�!f�<n�5��[���-����1Owu��*�*�b��Jj��7�����R���/�����B��i���������1U�Pt!�{������of���+W�t��bZ�c,.��H����Q������+����g8�*�t�W�����`pp	 +	��b��m�Q`�5�d�c{I����%��7����F�����y��[��%m��}Ii�Rt��ir����s�������r��n�D�I�dJ��q8(z+�cvf(*�Pc��P��=��W��T�{����E��d}�	C�F&�	��%���J3��qHN)�)����T��e��,���u2��Z�o8����n�4~I���Cwa���BK�Z�T�����D�$��L��M�Z�~���q��������M`�'q�zpj���[���H�&#����h7�Iep���^�����;��*�g9T� 9����<&�nMv�W`6�h��������
M�.9�d\Nd:�+�0m���(�(�z
���U�oh��u�w�ar�!���X^�#
|[U'o[�O�4��� �P���&[�-���Zt�i��i��0��K�&K�H[���wEC�z�����IZ-�4;�Hc������Z��t �m����
�Le�*h�b*}�\����?�����	������B�w�a������;�[�z1����%��2|CGY�����*�7a�A�/S;�Z����>�����m���AT���0���5
�x��?�qb���N�������59B����w��@!(����4�S�5�v�5y5Q*7(���p�Y�`~wLo0�<������x:�������\_{�����d�L�}>.(����f���S���:@?jn66:�X0���76�{��Zmw��>��#�6Jp�)EX���U����/����Tx���L���h��_�W�V5Q�8�N�~B�\_�XQ�hr�@��|�ZbW~9�LB����HJ���P4[\�&]A�c)[�^�q�JV���������k���H���m��g�kP�����~j{�n�v��������hD���BAWH�O�ZR��#�<�
0�9uL��=�s���j�VG�!��8�:�B���n���}��5����}��b�^�^��G�y�=N�����u8�pE�����s���:6����Y�_
l�b�}��q��B3����/�-�i����t��"�1���T��M�y�����j���020G����,�W����U|��P	���{|xx~�hE���d���uq	�?�7����t�,1�
Hq���x����^�k����V��!��r�)���,IW���W�[��ut���t�7��Z��d�!V������!���e�s��	������N�������?����8��D<�� ��=
[T�]�^h������(��t����`?�&�B�M���M!l�Z|M���}�('l���i,��@�����Q�|��80�8A`f�<"]v����
��Hi�[�}���M�4g7N&%������W���?��V������
A�	��0�&J�S�7����9L�1�}U���nL�+�eU���]����V��J���TGh����R�^�hiD��Z��"��F��aB����-���Q��\��\�\i�����/K����-v�V�!�r��~�I:y��Q�8z�uN�n�=�R������3���NZ6ma��8��iOt9/��W��N�i����%��:�� �����@;�9F�(Hio�B(������W�@6Qz�>��D(@���_{���=�"�q��w��c%��V(a58���R�����~�����<wI�I��g��h��Q7��*�[�s�rK-����;�Ip�P�#R�����/��(�vN(�)��[� I�NN'\�=%\};�$���	R���������$%.�o~�pY���+��ti�D��
/j�W�bp�B�`2�{=���A,,D	���tEJ?�9H �H��j%��g���<_�:0T�E���7�I���w�C`��G�\{0K��9H0!w)ON���f��-���;t��QP�`2W���?\6N���*=R�QN�w0�Z�k~��MWa�4���a�<A���eEc��\����xc�����
Q���0���6��S
3�������b^Y�w������;#�����n)����WWUWPN0+����z� ����wM���).��G��1m�Z	�����;��|J`Y{��F?q�_�^�	���-X�s���E5
-{���x{#Kk8���wE���u@i>a���A��
)�U/��t��2 ��MR�V�R^��j�e�
��=.�mj��p����te���\.���2.�+����Ug��`f��;���8�8���y�~�Z�2�(�� #%���;��X1pp��:����}E����%�� ���)��Hi�����Yh%����BoadQ�c��#$��X���g.��K��a	����$i���}���B�\� BZ�6�z�����&������]B�Z�g|w�����k��K}R�}�����P�||���e�n�~�8�
fl(6&i�'���8*@��.�B�R�H���J=U��V��1I�w�����	
'^�?�8����L����Ri>�5Q}�c����w|T��5����8!.�\P�S����m������5�i��\*k
(L���|�S'���C�uz�C����M��4,��N�OU����"g�;����t���
��t�������w���=���{��`����1��LZj��!�N��K�o��G�xrg,Tp�t\`)�Q;�H������#,m-5���V��;���2�� s��h��d�=%&���Uqk �������)�g@�t����B^�"	����
HP����)��!0W������jEcv�Z^Fm1Uh�CO������	�m7@������%]BW
���)f�a*����7��W�[���f}����>����I�@=U�=���$��![��CqA-_z��	�Qr#��{|B:w�4�e���Y�o�%���?�\�m��C���uL�����E#��02hJl�9�Y���4��+�@z���P�!�� �#yDi��.��j�i]j�]b�"PQ�F$�?��a�Q/Sy$+�g"�����������
���������l���e�e�Jw����D�_E%�\S�v�WFQ���r*�	qV�s�qr�^Ln��O�}�!`y���EP�%��pDwI^�A�EC<�����%��Q
f�M�00���M
�bV�4�{2t����0�w�r���`?���Su<��6�8M�%���O�_�d��W��V�qy|pr����������e�!�F�n����g0����Bj/�&$�#��T�Y-F�4�{L�5[�N$�J^�����O����?�K��B�n|����k�{"������9N�����T�Z`�}������Y0�
\6�H"a	�RH
	~�7�r�&�H������\�����(	8�5��8�����`B���S�\�F��l 	(R��q.���$���������U�Z��2!��t�(8�oqfw'�e�����d"�������T�r"c���l���(U���<�h�DP9
>PY�80'�KZ�TZ�(��C�6�@Z���o�#����X��,
�g��6�hb?Yj��&����$�A�a
���:X{�BTb;����0�L�.��3����n_&�s'��o���4D�M�K��d�2��~�"���9�	���0@K
����4�4����R+]�K�#�&��3�>�=x���\�j
*UU�����5<���wP�j~���r.|�U�������BO����]��3���Vh�fb�SN���Sjt
��~QDuD�_���F{K��o��y�b���o��q)�����g�e��Ed|�Y�oJ�]��H����*A�4M8m<S1�0��2�:�j���(LT6%	��6Xd�-wR��i��#�`���$I�=H�$2�=�������O��O��O��O��O��!�R���G������C<�Ea1P*��Q�k%������Qk�C�����Qp&;����l�;��c�h�>f~o� ���@9�G���N	
�E���tpQv#�Bs�4���c%�X��r��7�u���h�a;\����\B����V������\{&-~e,sZx��_(�������q��%�#zf�L�x�"���dSg���� ,W������=����gG-�K�P���(�m�O�Z�g�w�����eC]��U�
Nw��cN�����?z$����"�
���!l��s����Bxy���P���5�2�{��M��sZ�����^�����1�&	�1���$J&xQ�A��cF]t�=>I_�J�Z�W�^���l�I(����!m�J�������+q{v�S\��c:��?�TfA!��sA��`�WQ"���t& ��N(�0�t:{iYi7�����3
2���������n���^�
-1�V�A^4�.=c�
C�Q�c`�]����E�A8�L!Xd����p������)���Nw��L[�q��A�=b�'���[���,�{-�����������y��i����~2���=H����)����S�t�)�����GR�������*�$��d������'��8��<��d	��G��z����*r�cKPz�+�$��W�����8���=�
�W����Z��Z��$���yJN9#��N�#�:��9��;�/q���_��|����C�1���eDD�e�!�����'�K���<D9�F�c.�*��i�{Q0
�V��u����[����n-;�!����r�^�����C��W?��z�>���!��q��f��{�[������W��n���^����������������f@_��z�a��_�_���3!�e����z2��(��]o_K<��snJ������]�uw7z�������d}���Q��:V�F�����j��J�s�����e�M�1H^Eo�I:hY*�Nx��X��V�;���7��(�;�������pQ�Y���x����c��|&aP�@Fb�����\S&��B{����0�m\rE�� tt[��=P�L������r���w�cY���yt�O���C����`p� �h�L
��f�D���x|'9�0,vj����70t����Fu���\��.{P�����_^�b$-�v_^��[�	%�F���t`�m8&w2oW�`����y�����51����G��o^���HU4i���"��td-�oKU�����B[�P�I�*Z]�O-���4Y�El��]���g*r���
���ynM�������5�����F7� ��-[�Vx+�����B_;{;O�;�Zmgo��������6J��)E��6�(b���r����F6�N
AY�@g��%p�������V��p$�����^��{�:=8>{� ����k��Ko��[T|��^zF%�Z�0��w�:9xsv��iv5��D�s��RQ�?��8�qV�D���*����;uJAZ5���|9B�e.�
�C�6�)��h���T�AN)���������������|X��S�InI:����	�d�jQ���(�/���h�u��q,9�E�����qdo���12#%a�$���a���u������������=G=��Z��'�QGb��I��0#���T���Un
��$1�z��rP�����^��&�x��<P�60�w�J�����c)D�T���`6S�+�	��{�8����"���G���
p����P1:)����Z��s�k�
����m���vp��:$�����aF#w��fd��=k����F�[k�zX���(���m?����������J�����x�*�;�[h)�����0��p'�a4$8\����a<������J]�t������}���9��-*���c����3�-#f�%'0�0NEcY+�p:@��� ��������tQm7�����VsTt�k��L��:n�Y���s'����-���6r���RS���`��*G�~R�n��U��E<�i���]����{(O0T�����U�������.�����.�c���j.�l����*]������\�fm�����ul��93��)�C�����"Y��c�jZ�mc�n�o54CJ�Rzc8Dh���!�t(gl��%���<��j��o4��Zj^\^�D[�W!K&b���1D��L����e�iO5O������?���uu|�89>kp�]�.Ye
���\���z���
����Kxr�������lde������Y-��S.��
X�J�f}Ij��9�^���.c�8c4��ltM����b�����4����J\�o@\�����[C�$N�A�,��9��mQ����&����p�V{�[awoN�#��6#]��Q�@f���:��xur����>Wk��S��X��o0>������&����o�?��
k:Q|�8�v)�m?&�o-^\�~����N�������sV��+$�Z<
��NJ&%�4xw?L���r�~���bn����?���'0f}k{�kD�T�/����,�y�����o�/��|*�`c\��M�{�IV%�;E�s�&��3�G)L�Ng_g4��.����p�N������[���%q��.S`B�J���W�fL2��o[�~�H�( s�v��5u<A��D1��k������������8=/���'����.���L���[~ZIZPx	��0��gj����X0�=�<��t�C��~�('*-��h����yK�C������q6\�S#�n3�/���
��%[��Dr���QTG5'�~xX��D'�AV���Y�(guf���_5�1��i�����2
�<k��=�J6	��6N�����L:�{���o�
�����b����UL��`�^QN����/���������r�����

XF�$��I4�Fv]���gv��
�[�d('p:�P��/Z3�����	���{a���}����������4=T~����g�z�x��
�����W����-���4�����l��%3����NG]�����+��yy��o�d3�q�7a�U�d�����sutr��"�������U�y��]j�hm��������5O�&E2��+l�V��*�r�p{J������0a}��0���"�3�WS:)���QuK�i����������=��y�t�h���r]����N�R������x�y�����hH[e5/�]�7x
�}~��x��)�+Y�E�������
�����#���[�����W	g]~k;}�O@J#���nn�E�z"���r��C����
���=U9N�dB]Y�������T��a�|�H��|W�r���`:��E����{���|��Bb������sP9�e��5t�2���$��k�������4���=���u���k�I4��%����+���>��2���~qU�-�%of���GpN�Qc�1��"p+���~����{��(�]��������`�rj=
=�-q�������������������1a��C&!�Y]��h��e�����e������-?��v����t�0�zel�z��v:�^�zO�NCz$UU��;�]��Aj�=�J==�����%����s�5����i���]��?�xq|�N��8�::y�T�M
�~�lS��d�@E{T�c����`��=�r0y������JmQ��K����v��8J��
����xM��v�
�n�_MG��c�;^�����Z2��dOWU�9FKK�:��r����b.����%�-1a�8��k���52��'����!x1�XM
��2|�$5���a�v�����ai0p8��9|�b����SB��~���UU��G���'_(r]t���P1���>�y�1����e5B4���[�C��0�a)6��@��.�f���1ht8=7����/+>lM/���LE��Xp�3J��xsn�&���4��z�\���jH�D����Pc[��Z����/{��v��Tbs��C��]��N7����SJ�X�pl����}�nS�YCG���p��n���
��GNG�?���5��zF�Hm��UmXiX6��3,�����B&"�t��-j���t����U��2�?��&�f�u���w.��x�Ei"K��t�m�^9�<��O��q8�b���C���t/�e�Y�P�����xee����8;��	KOr?�L27I����F���-!~J
���E����t�C��&��g��F��m��=�?�	��\��D-J"��H��^r��5����
r����$U9�����5/��u���EaW[�D���)��oKC��"�A����4�H���)��R�qo�CDE�h/�Q�}�u��CT8�1������5O����$�	:_���!�\S��4����=~:���7�L��d/tx<I%���������*�L�	��	�NO�<�yn�)��?rv����u�7�Ek�;o�����XbZ�{L�=Mn,�Yez"�]��G(
�J����'���&<�����V��I:�� ��(]G�
�e����-��e�����[4~)�~����m��	K�p�O��/����N�$��=b8���@��=,QlKP���H��s������|PQ� |��]�����r����a.0�lw�v��{��a�Y�4V���2@<)��E��16���Tw$_��2�u�l��b�W�z����K:����:���=��3�A��O��Q&���E9|�X�����8�s�����H��":�t��"��okk��Gc

O��Y)n�&��K _�\c,"����xq6/��L�����%�t��3Ot�[C�#�o���SL�JG_#�9����a�[2���y��U	�V��Q,�!��)�6@K 5Li��m� �)�T�H���&��a0�{��'#����?�Dg���Q�����4c 2��H���?O��wJ

��8"�C��Fb�T(�s�o8M�)��:1�_:��B@�l�>>3��yt�B��E�b�y�&��x��"dKO�[]��^%�H�t%��a�Q[���G}��h%���x�#�>�V�q,\|���z?�{�0��c�\���A	��d�V��I�Z�o����^Ws�-������+�|���W��|p�r8bM��DR�@��:�2��@_K�H��Er���������t\��l���MLpnAKoly����������>]��S)�8�J����Q�-���������K�a�v�������T14,�k������ADx�I��2��d$�1�?����p�F�<*�_@h�
�Rp��i��Y�6����XQ����h�;�Z��	�B����S2�p�K���s�n�L�������u�^�
�iY6sy]��Ml'%4D��z�69<�p*�M�?,�����9����&C���:��C��C�U�8F@7�N�e����.eJ�S9xA$��� �[�.���uh�oMl�6�G��en��8 �FC���f����6��P�BE�.�S�$:��9N�=p���^�����_T�s�Q���=m-��he���$�����k���I��������#�$��|�D���<����:���0��\~�c�f��X�Q�B�,�l�y��^X��4[����D����#H���
��2iD\�X�d�x��R;e�fSy�C��G�q�K�M�1/1s������p����U��TK��A�8����9�8M����&�k���r���z�4�<��;n�Q�5����T�M���8��X� � �_���V�=B��*r�W,�g+3�_��?���BtE���!����%~?���!���s\4���(��e���t����nB�W�0e���E�vWQ�
�/�)la��R%}�xa���skI�:���H���V;R�c8Upw�$�O8�si'�h��4
����{�K��u��h,����C4����?[sT
<�8��������<�c,��n������x�	�dT��]E��)Fr�:�����
�����yr���uK&�L��g�=��(.��M��\�.���WN��J�B��r��P`X��%��Z�\��G��]\�\�hk|���Q����lhjd�b�P����,P�pK�3WR|�^���b	%l�g�QiN��3,�Y�;�(��x�(�Ey-��V��-['�I#�D�h3�8�����E�:Y������|�X�q���g�����.���+<,,G���\0����6f��v]�`���S������%I#xk���g��$�L����{�g��vS��)�"<�z��c��4���nO'/��%�c���+�s;�zqT�jNF]��hqy&#�(J�����j�>X��i������BT�OrE|�5T���t����bD�US'�Ik�Q����f3���\����!��������m��"�C�B�r�sKvb�^)�!K
�?f���hw�a��O���S7�����)> �~����k`���V=�g��5(���OV��5���#�B�]z�Y\G���w����hJSn�q�HH���,V���f^!M�������y�mi�m;(�8� ��"I�����U���E�1$�?9��In����_w�6��;�����vTD=1�	*�%F�\��,�SA�q��T��_Ws�`Lc����E� ��D��,�@�m|k������L�,)��2�����_���~�d���Yx���el��/���7��>�'�>�o��S�:�l@����V+��g0�i! �zD5��4le|`������\���j�?Y�3�f�����2���eK7JF��sS�g�_��|�F&��Ni�+�*�����QsP
U�b������{�����������m.\~6|s+����Y^m1pk���}x^mQ��������5��p�n�#��f��j+#���_��M�-���[�G�6�����(���_Wl����NZR<��V���a���:&��v���L�{Wy]c����0�����6�K8��1��	���J0ou��x�+�/+��A2Q'��T;d6ML�*
��eb��p@,�B��8ZIr%��J��ls2��H��yW���cH��$�;x;�H?��!c�D[�}PSoo���]���L54nX2�[<�1����i�����:�-�-�dF(������m�8-_����������ph��E��%4�5�����S$M`X��Z�K�R�� uN���,���V����+Tz���5`�����8l��`��0�Gq������'�\+�lullEW�{k��V��pR�Up$�D��4=�u���8�sC�4W��U

V�)�:S�ho��hvTb�-���1Q��������L4���PLw���6C5�]��vD�����Hl�>�X���XdN��Q7�?�f�F���}h=�b�\�75r>��@7L��7q=�Lc�fS���"�`��h�$�,@���](�	���!V�l�xR�����QDI+�&w�tY�j�q�Zgb��� k��1H���=���)��4��T*��&e��!�6����z�!��^9q���|���LS�"��FgB�HD�5�������������z�����kr��/�!�X��%��
�U����fU��	9sDki�K4��2s
NS�lt��1�'~��h�1��o�����9�;A��;�3|+��t+�;���������K/����t
 �����e�{��2��k�J����p�PS���~���(w�q� x���s�7�)7�R�"�U����Q�R�g^���'��1�&)\�U�>�r�<��KnK�������f��u�xu����u
r�����Q
cV�Q���,e	� �C(���(I�(t!�41������
J0���|sOZ���W��S�V���d���t��?N��Z����W�L��8�8?�!����nO{6��(���G���X�$Xz�����y�A2��k�CZ���q�`�ot�"}���MwJNOr�|��}#����w���[��/a)X�o.�����v��H��G�����|��A������!���L F��5�C�4���KrV��k�3���`ut
?u�J�lx
���I����_	�2A�{�s�����Z�Yo���&1N(���M{���FIz4>�SrR������Q�v�)QpQ�>�E2�rI�HCiS/����r�������1wlH���n���s���=���F����m����G��Uf����4�w�S���+l��R���^��{��w�/��r`���F�/��]"���e�.�q'4o-��2����q[���x�~��r��X������G�	���_$����u5S�����(���M
"A�j4�X�����zc�bM�>����^?9qjr=��Gw�t����(m�$������@����=�7j����c�N0�h�8��-6����
��Fg���fI4`,����|b"��F
.��0����y}�L�����G��'&��1�%������O%����O3}�yG]�|;s����sB0�|i���s�n�`~��s#�\FF��,+� �7�������F��f1
N0*1���xRb�|s>n�Xytz�\��T��+'+�RoO�il]6�^�4�G|xrpu�]8_�1Us�����b�����&f/>j\}�����oll�T�S;�l��u8���6���(89)I���F����Q�����Y�p)*&�%�A"8yj�&���&v�w�w2M� lhW,�b����Y�c�`�#QG����#S&�&��/�1�:����8 n��!z���5�(,�]� �����(MY�NN!=s'��.����"�hA-�����d
X��������j�<���u@Vsx0
��R��u|��zS(
��3s����~��ho���8�ge����S�5�FJ#u�5r*`����"	�0��sY�R#g�e�3v�������N s*{/-�LO���������+m1������@�{�_�%�!gu�x�r;;��0�����A�~q�����.�3?�a����\��\��Hm����f�=t?�<)�Q������|LMJfySe�zFG+�Y�RR�@�k�u��Z��g �Y�&V�Xy�R�����Z���p�����d��YUK����t�Z��]���{��)��?x�N�O&�������)��T����3�J�0� SC��
Z��L=N�p��^���y����N2�������P&�&���I���=C<��"g���(�"�� ��}���a f`��4�|��_�uE��9�'�e�Q����0�R�Jt�!��nY��,�o�>Y������!��h�%�Ek�@ E�td�m���6R�8b��wH~��60���l,���P������.}q��Y6�M"������]��1+��U<I$���kb�\����.��3Y�9=H'���y�(��d���`��
����8)@e*|����&DOZp��$t�����y��.��X�/��B��Nw}Tu��;���!��F��geg���#�
H�/M�;�����S�FG�����e������RK����o=�)�E�uQ�h��Q�C��1�����\��V����&����_{!�[�����V�Qu�7���%"
g��5�'q��$ u�A�!=gK���,��3"b�_���O�4��s�
mx�/�T�5���;���2�&��x
1E�hs<�v�h�:���l]��z��s��g�s���*:�-]�y�)=�k�����b��:m����&HO��1d9�}�=qCn�@�,����������;pS��G5�)�
������<
���/�[6�����l��C%��:o�0��[1�2� C�;'Q��bS�(�>R?o��(�0�2�5sL���(�D���Hn�hvW'�~�pL
���Z�r����I�xi\�l�{@�*$��}3DrN���I�Hn��"W�U�mJL��%�����Y>/��a8�wMj$��>�sU�64��	T#����Bi��y�UH��eYRd��c���p��AB~6������$7L�,�)G/�>g��D���[H���������+���}}�rC�\�>l�i6.������V��l�qgGu]�F{-�F;�(R�d�,�v/�W	��#���^'<���E����@���[�X��k�^�q���C�T
Kvv�J)0�5%�O��P�	�������oay���'�������?��9>r�\�@Vb2`3�S�i����Fyywus8<`�0�qs�;uL��FH4���f�� I�wSc��t��VI��_�4��.��)B��&���^�A��|��z������97�_�-%�yi�����B�Ts�Te:��Q-dE��� �4��^N��s��%-�ex��;n
��������y������H.�`�v��7�T��?,�9T�v�90�Z7$���"<�������)A:�n����go������x�	��vPPJ��\�\R�3R���������U�#iV�j�4#�� 8"H �<�%��o�r�A���b����mV�}��]8u�[;-�U��u08���� ���IqP���!��c�����S���[�1Eu�B�fX$1���	eJ���$�Q����0'K4�pr	����R�E�t�	���c2���Sz�Y�����QX{����[m�c=�#w�q*���K�`�����TR^e��S�Y�A�j�$��'j����5%��R��5C(�$2H}IE~'�3'����v���������^,�D�����hHD�B|�on_A�@�C�������z=��v��Z��������_��X�Z�ZG��Qx}d�9g}ccw{{�#'n��Zm��lnm-�����n�~�\,��������]�mT7�j�������w�����?v����O��/�d���������i��8���0������YV���5��������mh���#rz
,��F�&w�W���*#��L����FS����l������0<hz��E
e��m����@�� b���	���m��F�v�z8
����v��"���%v�S���/RR�Y�_���"zn�`C]�6��l��i$� ��c>�`����a���7�n��;I~��d�����w��)G���Vmd^[;������9����Z�������-�s�t���%BO���GYp�G�N��G\�*�������t��:~��~����dS��j�_����4������4���}��K��hp�!���A��%��4zw>W�p��B��]�/4���=�D���v8���g&fts
���(��+��sQ�5��q&F���L�I��J�A��3���I����'�������������%f���7:�(��Z�R1G_����tT���<l�������;7��QW�����8Zd��y�?��L	x���p:��wx H �	�
�0��u����C<�f��<'2�Q���[���?G��\����E9��$�B�B�:�9�:4����P����n���j�Op4+�����8L��J0D����ls���T���f'b�'���R���X�i;�)�|�l�&srO��*
x<�S��DH�4t�bBk�&Ut�^��P��s�-�Al������:�*��d��	�RK�7�:0n|��KQ�<�����,��qz����[�@0�.�W
{=��
�8�$��������tU��A�8a>�r�(n
[Jy�Fh;_5������S�S���y�P�O��������D�N;�L����=���'�e#o�j�D+
��L�1�����n��bJU��^����(����+S�|����l�9D���	�j�a��Pl��^���`����Y�wB��t��%��M���:�=�q���|sxU���2�F|1�g��q�����7�q�@9?N�����O!>���S�QBX��)������
�6Z�[�
a&��0�\{���wH){x?z�����z@��������D�a��$j���5�e���
%��K6���-������R��5�<3x�7��FN�-���T����l�_F��U���G�s�8WF��9��#�������ge^he@��g��O�E�w%cb�$�"�1[{������(&<����h��;z�����s(%��+uAn�H���q@�s�J��xW�t�P�����<l��MP�����~��#�(��p��b���k�Ld@8\vLdL�Cmbf�B�!$7&���`�Q��q%��28<'���$��
�N����-?�f��6�a/��~�DJ��5�n�3��w8�a]�N�!U����6s���/&�*���QZg���A���4���,i���$a�[u������w���(*�j:fN��s��V�4�����~��@.�G��V����f�Aw�E�n&*�z�E1#�v�7���y��/.G^��g�����b���P�:�� �t
0�uJ
��G;tI3|m���G\����x�o�j9N\s{nA������{(J��������+�CS��)X���c��u����!������P����+R����YJ�����0Z"�TlDe-m�g9��#n�(�08y��;8�^���:D��`B�i�o�(����Mh �#��O~=C���b�M��z�I�k/����a����!��/�{y�����%+9�l�s��ww��>#�zj����	_�=�X$B%6_if<��lQ�]iVC�Z�u�J�z��.��o����i��-L���i:�5����\���2�F�uo>5�	<�����Q)d��\���+r(]mw1��sh��an 	���,�����^b��8��:�#wKT���V����7��:�)�x:�����l1��]@���X��t�\��X���9�y����U�����l����������'P��=���������ux~�����K�xh��"�����8�#.�����d�0�g��]�m���x*^�:@[��O1�-4K#	G"�����rN�t��|f�(��/�%�!#�X��4�q��9F�.��P5����� ����i��LC+���H���������`���������i��upx�h6�/��zyp�}���utpu���I�|� ��3�I�!F/�&�uv0\�l'�~�V����*ez���e�-�4��m�����e���=b|�P�KF���}ezD�T�5SIA�o4�ti�[y�1�����$~�]��,�ei�sOG�Hh5�1Io���6��f$A�+��g��~��X"p�HS.H���W���
��&aBo�0�(���K�����=�v&s,he�`BrJc|�D�%l���)��H����CS
��O����,���6�Hui�������%�����	T��O�%J/4�OG]�S�.`��(_N2O�aUz�"�5�1��k�x�E�0;�����&���99*�#��x3��>��&-Y��l����'��2�������|��8n��I�����h2/C���/;\C��;ea�������c�>��r�E/�h������m����Yq�"I��t��g�d2�aCm����rYbG���� A�xc</�Hq�_c�Q<�g~�������mf���p`��#O�p�^�<p�=�K:JA(�!�����Y2�hYH�{�~x@������=��a�'Gd�5��9������Y���@i����&$#[�d�q)��$WY��
i]@���NL;�U[�Ee�*2��fl����.���|��V(�,&FO���~~��_9�<`l��s��
3�*)kd�G������M4,W~�w���_�{��sB��l��p��1���)��(;�(l$"| �o��#\����R���s���a�24����9r���6x����XO`�+V����4��o+�	�Du��E�W3�����7�b(�Q�	%i�k�b�NOM'��m��������z_A������Zr��������{*�l��&Q�Okd�	[<p���N`5���}t�-�J/�j0�����O�]�oR5h����<��pa��O(��y1� OG+vcD���C�!ZH�#��'��Q���=��`EOgr��c}9|r��i�P��Q�R2�T�[�.i��DwG���*�@d�(��#s��t�b��������|�6�Z!T2
���R�#��I��](�6d�#�����L�5���FF��V���v�7~�3�����R�3��K��u��GZdGIc�j�82�V%���K�n$;Ry��!e�*�U�-���:�k�#`��S;������g.;u�������<(`k�����s�0o9�$��C�I��<2:�Rf`b��)�f�^��|S�%��nD��^��y��)��5Df�����R$��i^��hq�=��-%�J��Kj�4�8�C��`95����j$��5gnDkm��B"����s���a�,�I�B��7U���a��[H[MZNi��������
��{�S�����G�9����;Y��IuB����0��)�2��sBa	+RoD�o��2������7H�2��f9���2��\\z*���R���=�W,/�Hns6����x<}G���,Z4;"-��Jf)O���\��#_C��1���B�F����������l%���d�'��$3s�\j�~��W��F�����d��hC�wD9�k�!�}�
u��T�L������l������+�S$���^��;�*8IL0NG4`�3�a��	
"�����%�����_4O]�(U�16���Mf������S3��$��$�������Y����j��SU�A:h��l|���7g��A{6D���UK�z_O9=����\��`"t%���Uqe�"�r��2V7S����n�����q�R'������*�_�.>\���;q���nd$6�����p��qEZ��'��=�y���`�U
9n"��Q�X�#�����D���H��0�p�ZI���{xe��~%�bY�:�
#R��~���c��X�x����!�c0�V&*~������yl|����{�w��g���)�M`-F�	(�	���%~A�b���,����7���<�*o����*�+�W�C�\6�N�����-�3�(�O�(�X��l����zy�9��mBBr��x�:����SM�����u
�3����0�ms����V�m@��K�g�,-� ��'2~?�t�R��� ����&f�H�D�y��z�C�t��f�����z$�IQ1M3�A�T�@��� 7���Y�����6'���`�b�Y�P&��>LZ��L����C�u�Qo%<�
j��Y�S*��E�*�c�'�G��qj�,�HW��	&�'xSgf;������Ooa����G����}�_�}>���9��+���
\>��/,�.K~*%��8�����PPf�F�7U����{X�����H�]nD~1���f��e��
�s��������F���pj����0Plq`�Ej|�}���[}�^���zjn��Y�,��
�B����R�'���K6������r��j��=�f���M=��|�I`D����tL�����+.��w)^��. '[�����k&c��'�8�6u�S��������{(�����Yzx�)����	�i�>ro[���['%[n����������K����o��\�����wJ��Sp�Vz�c'�hj���]����mXc�s<��n�����>2�Jo'�v{�X�|�r����r�P��l��$���b�SdFf
��R�(^�8Ig~(��7v�g��z�wY�5W���C��s����v�������	����U�C9����r~(Z �&�����@�&O��MKS�;��^�g�3�EV��P�O�����<Y�lG�t(��/Q:F��!?v�#�}��1�s��F�j�_�n9�MN��
{k����wW/��[IW����^�����Qp@�\k��e��-��B&
�3O����~'6�����/�8a��K,��F�pQIe��La�s�k���Ey��6�I��1����Q��3�q���:���d��A��� a�M�:��K#�6���z����UU�w�0�:?:�R�0�:�r�^����Rn�*��yvN��eeF��
z�$c�Y�n��!_����?���*�T��
]���t��B���Rw(��d�������@������f�8��'0W�	����&}�N��(-�p�`��������\�J��^������k��e�����Jn��e;�R{��q�M���+2�s��T���03M�t�N�$����Z���RU�7�TM����-�8i�����������U!�H�e�&Q�g��{�JG��0^�dJz�T��,��}E�d
����A�-�����{���$7��>u��[qP���Q�q�g�����H�%$yq���>�"a��5TDQ�e����p�p�G���DoCd�"b�<\{1LF&�x�%|8���X�5��nxv�����&��B�[xO\�O�/�*,���r�y��y-"h��=HZ9%pg��m�������1,��v���:��2 #��S���My6��.9�
�����G�"��/$~YN����6�+K���^�}��5G��{�v����(n'%b
�M�9�R�5������*��%7����ph�,:�k�jj�
0�a.�P4M]$s,&��D������V��F�wH.L�13�����m�4'�1i�QVY)"?�����}��01t��������P3����f:��N�E2�������v�L��gD	2���"�j`x��tTDJs:��t�lN�B24��C�$�^����&�03?�.��u��R�<4�>\�BMT�0RnD�"�]Hh�	�,��-�8��8jO) �!����$�C�X���0��^��/d&M����$-)�{U�}3��/c�"��
"ud�]��%�`��W�������8��iy�VO����%RdV���$�;Q��,��[8!y: 0���O���I����c��qP�nC�������N���<�-�8\UD��*��?=?jPTa����z]|�9���������������Q�9��f�������LCK#j\"�e��L)\;�3�%���!j�oNT	���#
6n&�S���q9�
'��2���P�D]�C��iK��,}�B��P���IX�I<
�H���M�H��Y��9�3�F3	j����S/�b,{�Uao/�b��}'�Rf9h8r��m|G���.[����1�`�!��Xj��f~���Z��(��7Q8:$T��:��������:�Sv�oC�T4�n�^���K�qx=��'�F��������#z�(�
 B��{��-�K�C�Y����g����$����U�C�w��?����80�LG��|:h�U�(�N'&����Mh��:-��D�r2��p .�?i$�����%%�b�g��@�p��!s��r�5�cr�1A;�l��	�k�r;X0�i�������3\(POk[2+�SY-g8������4�!��o��f�,��!��kN�2��6����f�,1���q��(^^�'��*S�����d,
��$���aQ;#�8��!�������K�{h)��A�cK��,������1~$�?�=��&w:�+�k'{O�4�}�����]2�*��S����Ul"����J�h�>��*�z��o
65�~���FM7�$��
���^�z���6kY�0��o
f]AK��Q)s�2?�����Y�2���D���	��8��m��Bf�����
Z."QhQ�1�x�S�3��{i�X7$�U7��m��y�3�,&�]��H��c@�DXs0�����"/��f3���;����w[�@��z���i������e������E�m0�~l}���x��%1+��X����2�������?������Kg��$�Z� Y$���\��f��G�n�,�)�Z��W0���[7��7�]�i���89><��~e�$���j;OxH���D��=�6�v��������,%�p����EF�=r�P����#�87�E��W�j�V�L�~�X2T����(�&��B�a�9'���D�+i	
�n�m��~V�qo�qJsn&q��d�q��i����co�26&�&;����|����=,����a��w"�Tbi�����@��������WN�"�^s.b�]��������7ov�����s��
O>�M/s�BSc:E�/T��S��genR
UZ9X��:�Qf�`�/ru�E�{_�xJ"���".����]�PW9�$������0�iv�������3	��p��@��8��:���3]T���I5x,��46&����)V��!�f1�U��=�,&��=)����HmF	{cvM�Y�5����!�����@p��8�*K����n�CB�����hej�8��n��r���J����+<�"N+�?}JB�S-9:��T�E4����e�����H1��8�U���M���[����f@yoL/���6JH�K:h<l���p���?>�����&g��:u^:������\D��]B�BPc����Ng�O������8���d�l�aI�Y���'H���.��6��U5VSCx���Bs��w�.&�X�b
�FS�Ls��`�sN^^�h�+'�"{���F�[���;�M�M���6o�3�J���O�U�����*�E�L-t)e(��R�������A���)<^�����?�n����)�B�q4�w��.A��W@c�m����6�_h%���/���*�(��p����q+nCM���k�$`��[��W�S�:�MN+��(Z�!����A`v�ao���+����&ai:�5��I8���q:����_�Wy������n{���f��������t���=�+�0�
Q����4f'� y�.�t�@13�!���<��`&
`2�
e�����^�SCu��5��;�c�������N���#�Xu�<��#�;']#�D�0
5������5)�������?o<V3O�S>��5�2o��!�\}x54�h
�f��mH����T����E��- t�������j����N{�WL�r����B���O��j>���M��b7�F��SXvI������s�cW��(�w�k����BWZ.�@�o�!T�[������"��������8 ��^�_���P�u7��+�Ab/��)T�v��P�&�>wa�����E�K�`v�y�O��-��
�y[�v0�_�pv{��v{�K,O�:�����{������&5�
���&�$��|w��0�Id0������
�Mb����z�U�t?X�|����BvzO��w;�����V}6��m����e���>whQ9�l��L�����F���XT�����3��P!������]��8����M���
�Eo�������uaoH��]�i��_H�b���Y���j[������kQ�Lv

������}�ic��#�F����Y1�JZu_�t�(l��FK���pw�����	M�B��vr���e�b�����NJ�����*���x.:��m�Q�%�4oKu�����W�g0L������/�_�0�S_sn!���|����2fe�S �+�n����)IeX" �
��I���
f?�6-������U�/�����]�����������Yp!6����4�K!p�bb�&G�����+�E.���4�M+e��3�9���������4��z	���P/�f�I������e��������
�aS�V<�2$�Tp]�
������^��0���G.�v�j^��?�'���#h��_2I�dBM�u$���V<S4����Yd�6������%:��n�1���e�g}`"���I�.d}���Mp�����kCtk�;U���N�V$��-U�4�h�fr�Mx4��5���5��^ww��5>��W��t����5���%O.F�@��d:�;NV9�E7�e'�JceA-�JA��=���;��Ph�
����~_����)�����S@M\�Fil����8�{7�������gP�S� ����ES�8	����b�N0%'���F'��������	3�����f�������O(V�(�������)����#}'��x�(�o)4���g�Q�����h��)�]�k�^�%o|m�#yG��\o��s��t}%���8��0xP�M���B$[,�5������H�,<�_E#���x'���g1���T�B��3�*��[a{��}���r�I���r��Q�o����X]L�^;H�@]T�yn��I�I��m�@y���n
�k%����$7!f(nU�{n]6T�-a�aj����y�y��p�4[�7�%�}Fm��^d{|��&�Qx���S����e�����`.���B�ma�����v��7~|:Gk��W��c������m:Baa��M�<�t�H����*?����{�M\��
����+"#c�5Q7�-H�0FL�$+2������(s��%����L������TZ��z�)%�@_����bp��7F� �B���o�|BN�M{/
�U����x|5n�r58������Q�~�kN&���^J��bcPJn
�ld�{��Cy�t�e������q��9��7�\�������	�Y��f�x����^kn'pe�?������#��b�����������{������Q>	��a�M��4����{R9���yl�LT�{��1������p���-P
��E"
��wI��G	p�|����Pf�t��������M�.q|D
���cq����vCT��9���t� v��������8K
��^DQ������r���E{N��1P���\���>N��#p��#�!H��VO[B�	��1��Za �e%=���DD<9��3p�����MNofk��Cy�t��;����p������+'��w^V�<����Zc���V����`t�=���oYA���\��������6�������s��z�f��w�~��y%�������g>����Y��&�Z�����\�;�a�s[�a����a����N"�
�|�����gXbg��wQ�:������L�B�
1���_�g���~�I����$e�����+�����"������F75O���o��hj���!�UT�4�J6�-4���z���Z}\M�����y��L����w83�o���)�D�e��8�UJS���r�{�>M?O7���	7�a�3��Q�Y��v
.��k���{�=���d��fg�;���i����)BhD[rg��R��-�/��%������m��VL_�B)������a"�$P�>io�OqNr��$7�mk���#���a��[�l�H3g=�(��t%�����5>�t�	%Q_���S��,���U�z�i��q'T	�)lO��3�7���j~�%�W>��t�:������mW����F�^��=P��e�1v���2�c=a���'���!%��~�P�d�<����z�7�U�H������V��js����F����a��:h�xv�������&��'x���q<�,�������zd��u�@�vI����'�|4O�����W1������=u�'��k�*�+������5�'\!��@��R���wo.�3���������5.����8��AK�_7���&=�sp���a�,�0mln.���'���B�Z.�T^\��;��f������c���#��
����zt������z�l�������w�s]�u��`tM�����[�^N�'�'[��N�����{���^��=�������_� GW6��K��jkc_U�r����b����Y���
��o*4\T���oQ5����%Z��$yJ���\��1�����
�����
k���7,}fg��K���1=����������[�������F����:PZ��'�
�m�|�:q5��������-�1�N����']�u��P��Arm��GTp4�O�DLZX
q~�kd�8!6��0�fhi�?EM�?dK<�"�=8Q�-�u�]�]mzQ�=�.j�,��.-�n��������XQ�RZ�*�bz+���t�E�+�:�
�ml����]Y�
0�����~���-�M���L���~��XK����`N�����&7���2���y'��]�,�9B�%,;J�rr���<���p�������9����J��TY:b�t������GIJ������cD�]Ty��e|�n�-(����]_Q�)�����9��j6��B9K_�y���X�����$x��%)�cx@��$~�n��J��0\G�c�;���@t���$;�I�a�!���#�r�6��J�
�Y�y�s�������u%�^U��z���d�4�����[t����t)y�]���uzpxy��%&�0��K�#
��(�� ��B�?�`���K�sJ���v7�v���n���$�q1-k#��y��;q�����p��w"p�!0��K
������I�����
c�T������B@)��iVtV;�'W�I�[������"Y�c{l�|t��I��8������s>8�������Z�c��X������7c���������QJkB����
wFX��n�����CK��8s�����@g��lHe#1c!thp��J�GgU%�x����4��#��k\�C?���4��qDq��� �����������3��P��G
8H��LJb���q�������*�>�kg����Bg���6f��m�vw����t�T��
)���.ZF������"g8��
������g��
��Q���"||V�_�
T��q��������y��~����J��Z���oaH��P�����e..N~��'�6�&
�_s�� ,�36�uN����QK�����+��V�W�i�&���'7�e�k��$�����<�]C�V�g���&�i�"s�Lf��d��x_)+�_������f�Z��+aLs^����3W�3��,����zE��lrf���<��m�
N��a�8��	c/M��u���Xj�0Ic�==a3�\������<���PA��e����HF����yF��hB�w:����8O&:#7x����:P)l�	)��ty&����Q:(N��4�6��~
r<���.s�?0a��NA��=xV6�E},��������s�����^��=�/K������D\����� �{��������_��0��A������g�H���#����@x{~�=05����0���3nJ2[��h�s������	�e�|m������M?�<F(�N1s06�.ZR�m�.��f7��&���_^^���� r��^��i�gP���rZ������j;������DqK�t�/K�;d
�y� VY� ?�0��@GD	�Z�7\V'��a<�c�"�E��`s�����

t8w�Y��#����"����>T:�5�/'����CQ�������g�!�����L���t�L�_���������
E����= ��U�k����"���^8�;��tM�?V�u����������
��h���@�;s��Ou,������`<?�Q��������)j���2xH(�~.�g����a*������&^�j�`T�l�Xe#��Z5!�`4��Myk��s�_Z0(]�<e�r0��K9�rx���]�e�C���U~����4�7�p��}/�1���6�|=�1�/3�&��
l ���������#sG�59Ia9�1�����K��rU��m��L�i �5d��:��`B�O�����8��q�I�8��%�v��x}qy~��/��	Kk��"�S)f����6������{"�jo��jt9o��I$�j�Y�9��B{��q<�~c��S,	�_�u�.G��{}������.W��ss�����{��iW�.�M~!5>e�����~��j�3�%�{W#��:��#�C8���i7U
C>�`�-�������Y�������3r.l@a	�5�������u<AK;�#�e����d�Nz��.���T*RCQ���p����d�8���/�S
�|g6g]�\t��nY��j��#����
�	����fV����Ri�m�Q]�>6�7r�ou��z�V����[;��[7{L�o�P��~��g���d���, ^x������}��<��{"%*zL�=4NI8�6GLp����#��T]BQ���*<��T���Q�:��Nm�C�����z�*[1���kc>PY7���`�cM�D�NL�
j
���F�D�]�~���%U�1�
]x���������[�iF0����M��k+����f/0H?��r5�C1N(��-��s�\��#�=ym�Lv����Zmg{7x�������]��p�5���.5��C)�rB��(�)9�r����]Al��Zf'�V���T*��i��:���v���y��g��6.��ui5��R�����y>zl8��{;@����7~D
K��]~M��o�sF������������$�v��l����F���C;����'�������N
���Z��hQ�[�.����E���E��^�-j�����_�Nt��k��`�k�7/��/��/�(v����$�w�*W�P_\�w��$S����Wi���`�M&

��$Y��5`_��G�:�k�r�b;h����0��ZP>�����I�c�w�c=�:������$��zN(qZ�����H����]���:�x�]�����n��d7'�xq)��-#a)^���
���Cg�S��(��w��������X�����Ao!���}��)z���K�t4���T��Z�q���W�����S�<��cF���>-�Tar�.
PeG��s�cP2����b����^�f!!s���fa�����enA���X�
��`>6�V[?�(����Y
m�����������~�V������"*U�N
?KJ%{�I�	�/��6����Q5'��+����~�����7rN���r~z�����E�;�~uy~�YabRTA���������*�`fA�F��/	)ME�4n��R,�������R���}PU����R^�������lun0�{U&�A"���
l|Q+�����|�����H��CT����nP�	_��$�gD�J�Y�
9��.��FwmM�!��cZ��rB&�����=a���Qd�y��k��"�9V���i�g3WR'�WW����
�/m�"����g����	�����Tj����LU*0�D�����n���%b�Y!�(�~W#M�l����wV��v��p�������
ck�g�K)�d��oo*�K;pM(H��:��@��ae�q�	��'�Q���s�l/y�an���E��Ng����G=G���b�af�J�S�p�`������{�}YC�h9M���|3���M(�!�����(�1���[\m�puy�:>k^�H{o�Q{�oCrWMs�)X���������_�]�J�?��xsF�ez�L�������"d���5z����6�}1���l�6
��_��%��pk�����qgC��.+���t�5������+�sw����!��?�4��&1�"�c�Y�4�j4z���x�8���a9$�,0�b������S�{hs�N�p�������L��  r-��D�m��A@�SCXU`9�%�h:a�j��T���_�q,����2��M��w���D"�>���������F������P^���n�O�{��=�5�+w�v��z$4 s}J��U�R	�Y��X���6#F$��%��T<�����OO��fh?����_���66�(.����N�~k��1~r������cd2S��^P��t��?���W#^Z��i������9o�����~�������7@�:�~���b4��|3���=����`�:>Fu��!�����:xy��kO��p�\�0����c���+�S��>X���]!��`,����6�C��\�:7��k���H�h0�C�&����}����������*��"�Y	�?DD@���W5�XuI�{nF��5�x��{�)n��0��i�����;����;U��^�K�gUe����}��s��t������R-�I�]�cYh\�`��v?�z�O��\�7/����f�<<<?;R���`MZ%�\��q�|���|E?/��i��	:!%7�xC�5x��`
\�t�"m�f���
��c�����e�-��;��W�g'�����l&�l)o�G1�����'��������0�Q������P}w{�����$����Q�����	�`P�s�`��(�,3,,������4^U��Z�X���� ���i�a�Y��V
�C�\:���y�=�Q��J�]��Yxj�l�w3AS��f�����z�67�����#�Av�W��s�,}�����yc�_f�<�Y���o�/�/�k�������&o��l|��q���M8I��?��h�����5���;^>r�����
��������:��k�@v/A?��$�2V���T�a^#���" ����;��������!'�DCE@:B�@�Q�p�i
���>�5�M��[�����Onp.����rs����������o}RA��;��G2�1��/<�s8���4�l�� ��s�����]@m�o��o,$R�2f�����*������������S�N� ��� �x��w�	&��R����h4���>��w�G���{|�B���T8���?�����c��L���T<��}����4	��G���4H����(���
����\�G��� �iG��sj�7��a�n��K+���i��S+�t�:3m��>�.XQM�� ��H}2^���B]1i]N����&�����*}�OU�B����v��w!���k�p�8k"��YV)rl;���liV����$����-Ja������csT%��m���_��rY��`�j��_r��������V�N���.U�����������C��mM�2�q��TU@�����]���d�;��g�xd�=G�@�F���k�J� �*tUu�����5c!������$���a@�����
��������zX8o�<x��������s�!SR4�y��)8���V����a�=�z��k�L5r�oso�A�A0��f��P��^d�YB:�a��+��l�P�9z�zk��G��6��������W��������zyB
��K���v^���R�h�5p;������r<���������\���K��g�����r��<9�j��6`��	��^+~	�
$7����������� �������1,}������=��,+�W�4���+u`9��������S�T6�!>���y�����}������y��wt^l�Ow��|�Vs�8R`������,��,U�R?:K��^�5,S����;,��_�������	Si>iX�	ej���N�M:?����a���lJ]i����t�4~)����`,M�W�?t�����f���mg�����7G���T<��t�S��.�r;����H`����*�a�5sQ8pQ���l�E����8��a��3�2���;����t���YG�s�uu��}���lH!'�$���7��uy��R�m�`��-���&��9l.[H��-z�3�Z�`�7�����la�o��W��r�tJ�!RMf�T������?�s����4c��.=O���yZ^���[�<-������-Og�����/���	5�p�k�d)Z����p/8���,��3eS+<1��y,�<�����w�������V�X���W�����3@��
m���JK��H����
��-���n�|d%�����e�������<f�w.�8:�T�a�Fi�mzyE��E����>�"��v�F�\:0�#��������J�b�����Fy�i8��Le����b� �vg���$w�)�$�3R��$�t���g�1U$Y�G%7����Q�J1��cA_r�������)U<8]J�N�?����?�fH{D�]���BZ�^�'���?�G}����	>6�#D���c�>B�	>��#D���c���T���#�r�Q���St�^��m���������a�y�@���M~�QECJ�p�x�����/(u�T���4�\�a��5����,�!G}�dh��-��Fu�����gm����g���g�3��]�����?moTw\�y����FK���Z}��O@�}�b���n���~�
���x��-K��-|��i���Y��fT���$��8��%�0�3��p6�;-������m\�*$4j���
�3�������\��]�x<���Q�R��Q�����UD�SX�o���9��>g�$�Q���9�=��(1o�7{��������
����V�������lu��l@���q���p\T�>-�n~��T��u���[��n�}�n������0�����G9�?���y�"�h��F=h��P����j��_�VE%����:n����-��_�.���|.:��R��n�n�����
joK���F�S{���L�d������A56#o����_\��m�o!�E�.�(��Y��>�/]|��/!��;F
[:�n����n-c�@����el�R����c�o8!���=�f�!��� >�o�p�V�Mr	~AOK 4�e�z�����%��%;T���>2C_�U�v��La'���������~������l�%3���#[l��E���ma1~d�����Y?��	h��`�R�
�p��k��TX2?��Y�%��TEY��~���5h��%�=�Y�A�	t?c[��~BsgZ%`"7��8��m�iM�[���R������2XkO��kucs��f#��h��2�x����W-j���D�V���������hjK���i`om�>
lWw���<��v>~D�hf'�2y8b���2u����o�/�r:Tmb�0,����-�v��8(����p��
���������l�ztY���m��
f�-��Jh�������CoHDZ���>�J<U]%a'v�
���G�]�����	97v3�GWb�M��?�������v��v
�S���n���3k}����xQ���s6�?dAT9����� sF��e�}�m�1�L]Q���kN�����U�*i����I��`�FC�m���������9�ysm?�y%
���h�
8W������E�,��;�K�Rd
���wZX|{��~^�)�'����E
=�b������kuH�*Z�������;�,��8�?�3B�nv��3_O���#����/>��P����(i�p3���������j!��7���?W�z|�,9�-������Q|�@���^���?o<����l@��LI�J\�dk;�v�Ob��[qA��I���O��l��S�-W�A����0�zP�lW�H	&���"V7z0�d�(����?�f�S�VC�]��r=w�8���Tvr��+��T�uT��J4�C�n'W�V��F�/��9(WQU?T��������j��=P�&*3l$O�f��8�3i��2��8�=G%���Q�u�n-��Q�����L�Z�QF�&�����?
����G����cg��-]�?�q�ZRb�0�@��m�
<#������7��W�V:���1 !�����������y��1�q�_c'j�y4v���;/�������YYOcl����X�*+/����j�h,���1f�
���5s�
UY�6��A���1D�\��"�&k�������k�x�n�YF�(G��}54�������S�3_s3�FgO<�����*0��2�����C����)�a��[!�@������H�����c���j�����VY��;���
�����Q��Z�S���j���Z�O��_!�{�����b�l<W|��T=�]���E�9E�9e�/,t�����(���UBT������^m{q��-�k���
0007-Add-separate-synchronous-commit-control-for-logical--v8.patch.gzapplication/gzip; name=0007-Add-separate-synchronous-commit-control-for-logical--v8.patch.gzDownload
#98Petr Jelinek
petr@2ndquadrant.com
In reply to: Steve Singer (#94)
Re: Logical Replication WIP

On 13/11/16 23:02, Steve Singer wrote:

On 10/31/2016 06:38 AM, Petr Jelinek wrote:

On 31/10/16 00:52, Steve Singer wrote:
There are some fundamental issues with initial sync that need to be
discussed on list but this one is not known. I'll try to convert this
to test case (seems like useful one) and fix it, thanks for the
report. In meantime I realized I broke the last patch in the series
during rebase so attached is the fixed version. It also contains the
type info in the protocol.

Attached are some proposed documentation updates (to be applied ontop of
your 20161031 patch set)

Merged into v8, thanks!

There is one exception though:

*** 195,214 ****
</para>
<para>
A conflict will produce an error and will stop the replication; it
! must be resolved manually by the user.
</para>
<para>
! The resolution can be done either by changing data on the subscriber
! so that it does not conflict with incoming change or by skipping the
! transaction that conflicts with the existing data. The transaction
! can be skipped by calling the
! <link linkend="pg-replication-origin-advance">
! <function>pg_replication_origin_advance()</function></link> function
! with a <literal>node_name</> corresponding to the subscription name. The
! current position of origins can be seen in the
! <link linkend="view-pg-replication-origin-status">
! <structname>pg_replication_origin_status</structname></link> system view.
! </para>
</sect1>
<sect1 id="logical-replication-architecture">

I don't see why this needs to be removed? Maybe it could be improved but
certainly not removed?

Also

<sect1 id="logical-replication-publication">
<title>Publication</title>

+  <para>
+    The tables are matched using fully qualified table name. Renaming of
+    tables or schemas is not supported.
+  </para>

Is renaming of tables any less supported than other DDL operations
For example

I changed that text as it means something completely different.

alter table nokey2 rename to nokey3
select * FROM pg_publication_tables ;
pubname | schemaname | tablename
---------+------------+-----------
tpub | public | nokey3
(1 row)

If I then kill the postmaster on my subscriber and restart it, I get

2016-11-13 16:17:11.341 EST [29488] FATAL: the logical replication
target public.nokey3 not found
2016-11-13 16:17:11.342 EST [29272] LOG: worker process: logical
replication worker 41076 (PID 29488) exited with exit code 1
2016-11-13 16:17:16.350 EST [29496] LOG: logical replication apply for
subscription nokeysub started
2016-11-13 16:17:16.358 EST [29498] LOG: logical replication sync for
subscription nokeysub, table nokey2 started
2016-11-13 16:17:16.515 EST [29498] ERROR: table public.nokey2 not
found on publisher
2016-11-13 16:17:16.517 EST [29272] LOG: worker process: logical
replication worker 41076 sync 24688 (PID 29498) exited with exit code 1

but if I then rename the table on the subscriber everything seems to work.

(I suspect the need to kill+restart is a bug, I've seen other instances
where a hard restart of the subscriber following changes to is required)

This is another initial sync patch bug.

--
Petr Jelinek 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

#99Steve Singer
steve@ssinger.info
In reply to: Petr Jelinek (#98)
Re: Logical Replication WIP

On Sun, 20 Nov 2016, Petr Jelinek wrote:

On 13/11/16 23:02, Steve Singer wrote:

There is one exception though:

*** 195,214 ****
</para>
<para>
A conflict will produce an error and will stop the replication; it
! must be resolved manually by the user.
</para>
<para>
! The resolution can be done either by changing data on the subscriber
! so that it does not conflict with incoming change or by skipping the
! transaction that conflicts with the existing data. The transaction
! can be skipped by calling the
! <link linkend="pg-replication-origin-advance">
! <function>pg_replication_origin_advance()</function></link> function
! with a <literal>node_name</> corresponding to the subscription name. The
! current position of origins can be seen in the
! <link linkend="view-pg-replication-origin-status">
! <structname>pg_replication_origin_status</structname></link> system view.
! </para>
</sect1>
<sect1 id="logical-replication-architecture">

I don't see why this needs to be removed? Maybe it could be improved but
certainly not removed?

Sorry, I was confused. I noticed that the function was missing in the patch
and thought it was documentation for a function that you had removed from
recent versions of the patch versus referencing a function that is already
committed.

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

#100Erik Rijkers
er@xs4all.nl
In reply to: Petr Jelinek (#98)
Re: Logical Replication WIP

On 2016-11-20 19:06, Petr Jelinek wrote:

0004-Add-SUBSCRIPTION-catalog-and-DDL-v8.patch.gz

This patch contains 2 tabs which break the html build when using 'make
oldhtml':

$ ( cd
/var/data1/pg_stuff/pg_sandbox/pgsql.logical_replication/doc/src/sgml;
time make oldhtml )
make check-tabs
make[1]: Entering directory
`/var/data1/pg_stuff/pg_sandbox/pgsql.logical_replication/doc/src/sgml'
./ref/create_subscription.sgml: WITH (DISABLED);
Tabs appear in SGML/XML files
make[1]: *** [check-tabs] Error 1
make[1]: Leaving directory
`/var/data1/pg_stuff/pg_sandbox/pgsql.logical_replication/doc/src/sgml'
make: *** [oldhtml-stamp] Error 2

Very minor change, but it fixes that build.

Thanks,

Erik Rijkers

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

#101Erik Rijkers
er@xs4all.nl
In reply to: Erik Rijkers (#100)
1 attachment(s)
Re: Logical Replication WIP

and the attachment...

Show quoted text

On 2016-11-22 14:55, Erik Rijkers wrote:

On 2016-11-20 19:06, Petr Jelinek wrote:

0004-Add-SUBSCRIPTION-catalog-and-DDL-v8.patch.gz

This patch contains 2 tabs which break the html build when using 'make
oldhtml':

$ ( cd
/var/data1/pg_stuff/pg_sandbox/pgsql.logical_replication/doc/src/sgml;
time make oldhtml )
make check-tabs
make[1]: Entering directory
`/var/data1/pg_stuff/pg_sandbox/pgsql.logical_replication/doc/src/sgml'
./ref/create_subscription.sgml: WITH (DISABLED);
Tabs appear in SGML/XML files
make[1]: *** [check-tabs] Error 1
make[1]: Leaving directory
`/var/data1/pg_stuff/pg_sandbox/pgsql.logical_replication/doc/src/sgml'
make: *** [oldhtml-stamp] Error 2

Very minor change, but it fixes that build.

Thanks,

Erik Rijkers

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

Attachments:

create_subscription.sgml.difftext/x-diff; name=create_subscription.sgml.diffDownload
--- doc/src/sgml/ref/create_subscription.sgml.orig	2016-11-22 14:21:05.000000000 +0100
+++ doc/src/sgml/ref/create_subscription.sgml	2016-11-22 14:21:27.000000000 +0100
@@ -151,7 +151,7 @@
 CREATE SUBSCRIPTION mysub
          CONNECTION 'host=192.168.1.50 port=5432 user=foo dbname=foodb password=foopass'
         PUBLICATION insert_only
-		       WITH (DISABLED);
+               WITH (DISABLED);
 </programlisting>
   </para>
 
#102Erik Rijkers
er@xs4all.nl
In reply to: Petr Jelinek (#97)
Re: Logical Replication WIP

On 2016-11-20 19:02, Petr Jelinek wrote:

0001-Add-support-for-TE...cation-slots-v8.patch.gz (~8 KB)
0002-Refactor-libpqwalreceiver-v8.patch.gz (~9 KB)
0003-Add-PUBLICATION-catalogs-and-DDL-v8.patch.gz (~30 KB)
0004-Add-SUBSCRIPTION-catalog-and-DDL-v8.patch.gz (~27 KB)
0005-Define-logical-rep...output-plugi-v8.patch.gz (~13 KB)
0006-Add-logical-replication-workers-v8.patch.gz (~43 KB)
0007-Add-separate-synch...for-logical--v8.patch.gz (~2 KB)

Apply, make, make check, install OK.

A crash of the subscriber can be forced by running vacuum <published
table> on the publisher.

- publisher
create table if not exists testt( id integer primary key, c text );
create publication pub1 for table testt;

- subscriber
create table if not exists testt( id integer primary key, c text );
create subscription sub1 connection 'dbname=testdb port=6444'
publication pub1 with (disabled);
alter subscription sub1 enable;

- publisher
vacuum testt;

now data change on the published table, (perhaps also a select on the
subscriber-side data) leads to:

- subscriber log:
TRAP: FailedAssertion("!(pointer != ((void *)0))", File: "mcxt.c", Line:
1001)
2016-11-22 18:13:13.983 CET 10177 LOG: worker process: ??)? (PID 10334)
was terminated by signal 6: Aborted
2016-11-22 18:13:13.983 CET 10177 LOG: terminating any other active
server processes
2016-11-22 18:13:13.983 CET 10338 WARNING: terminating connection
because of crash of another server process
2016-11-22 18:13:13.983 CET 10338 DETAIL: The postmaster has commanded
this server process to roll back the current transaction and exit,
because another server process exited abnormally and possibly corrupted
shared memory.
[...]

Erik Rijkers

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

#103Petr Jelinek
petr@2ndquadrant.com
In reply to: Erik Rijkers (#102)
Re: Logical Replication WIP

On 22/11/16 18:42, Erik Rijkers wrote:

On 2016-11-20 19:02, Petr Jelinek wrote:

0001-Add-support-for-TE...cation-slots-v8.patch.gz (~8 KB)
0002-Refactor-libpqwalreceiver-v8.patch.gz (~9 KB)
0003-Add-PUBLICATION-catalogs-and-DDL-v8.patch.gz (~30 KB)
0004-Add-SUBSCRIPTION-catalog-and-DDL-v8.patch.gz (~27 KB)
0005-Define-logical-rep...output-plugi-v8.patch.gz (~13 KB)
0006-Add-logical-replication-workers-v8.patch.gz (~43 KB)
0007-Add-separate-synch...for-logical--v8.patch.gz (~2 KB)

Apply, make, make check, install OK.

A crash of the subscriber can be forced by running vacuum <published
table> on the publisher.

- publisher
create table if not exists testt( id integer primary key, c text );
create publication pub1 for table testt;

- subscriber
create table if not exists testt( id integer primary key, c text );
create subscription sub1 connection 'dbname=testdb port=6444'
publication pub1 with (disabled);
alter subscription sub1 enable;

- publisher
vacuum testt;

now data change on the published table, (perhaps also a select on the
subscriber-side data) leads to:

- subscriber log:
TRAP: FailedAssertion("!(pointer != ((void *)0))", File: "mcxt.c", Line:
1001)
2016-11-22 18:13:13.983 CET 10177 LOG: worker process: ??)? (PID 10334)
was terminated by signal 6: Aborted
2016-11-22 18:13:13.983 CET 10177 LOG: terminating any other active
server processes
2016-11-22 18:13:13.983 CET 10338 WARNING: terminating connection
because of crash of another server process
2016-11-22 18:13:13.983 CET 10338 DETAIL: The postmaster has commanded
this server process to roll back the current transaction and exit,
because another server process exited abnormally and possibly corrupted
shared memory.
[...]

Hi, thanks for report.

I very much doubt this is problem of vacuum as it does not send anything
to subscriber. Is there anything else you did on those servers?

--
Petr Jelinek 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

#104Erik Rijkers
er@xs4all.nl
In reply to: Petr Jelinek (#103)
Re: Logical Replication WIP

On 2016-11-27 19:57, Petr Jelinek wrote:

On 22/11/16 18:42, Erik Rijkers wrote:

On 2016-11-20 19:02, Petr Jelinek wrote:

0001-Add-support-for-TE...cation-slots-v8.patch.gz (~8 KB)
0002-Refactor-libpqwalreceiver-v8.patch.gz (~9 KB)
0003-Add-PUBLICATION-catalogs-and-DDL-v8.patch.gz (~30 KB)
0004-Add-SUBSCRIPTION-catalog-and-DDL-v8.patch.gz (~27 KB)
0005-Define-logical-rep...output-plugi-v8.patch.gz (~13 KB)
0006-Add-logical-replication-workers-v8.patch.gz (~43 KB)
0007-Add-separate-synch...for-logical--v8.patch.gz (~2 KB)

Apply, make, make check, install OK.

A crash of the subscriber can be forced by running vacuum <published
table> on the publisher.

- publisher
create table if not exists testt( id integer primary key, c text );
create publication pub1 for table testt;

- subscriber
create table if not exists testt( id integer primary key, c text );
create subscription sub1 connection 'dbname=testdb port=6444'
publication pub1 with (disabled);
alter subscription sub1 enable;

- publisher
vacuum testt;

now data change on the published table, (perhaps also a select on the
subscriber-side data) leads to:

- subscriber log:
TRAP: FailedAssertion("!(pointer != ((void *)0))", File: "mcxt.c",
Line:
1001)

I very much doubt this is problem of vacuum as it does not send
anything
to subscriber. Is there anything else you did on those servers?

It is not the vacuum that triggers the crash but the data change (insert
or delete, on the publisher) /after/ that vacuum.

Just now, I compiled 2 instances from master and such a crash (after
vacuum + delete) seems reliable here.

(If you can't duplicate such a crash let me know; then I'll dig out more
precise set-up detail)

(by the way, the logical replication between the two instances works
well otherwise)

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

#105Petr Jelinek
petr@2ndquadrant.com
In reply to: Erik Rijkers (#104)
Re: Logical Replication WIP

On 27/11/16 23:42, Erik Rijkers wrote:

On 2016-11-27 19:57, Petr Jelinek wrote:

On 22/11/16 18:42, Erik Rijkers wrote:

On 2016-11-20 19:02, Petr Jelinek wrote:

0001-Add-support-for-TE...cation-slots-v8.patch.gz (~8 KB)
0002-Refactor-libpqwalreceiver-v8.patch.gz (~9 KB)
0003-Add-PUBLICATION-catalogs-and-DDL-v8.patch.gz (~30 KB)
0004-Add-SUBSCRIPTION-catalog-and-DDL-v8.patch.gz (~27 KB)
0005-Define-logical-rep...output-plugi-v8.patch.gz (~13 KB)
0006-Add-logical-replication-workers-v8.patch.gz (~43 KB)
0007-Add-separate-synch...for-logical--v8.patch.gz (~2 KB)

Apply, make, make check, install OK.

A crash of the subscriber can be forced by running vacuum <published
table> on the publisher.

- publisher
create table if not exists testt( id integer primary key, c text );
create publication pub1 for table testt;

- subscriber
create table if not exists testt( id integer primary key, c text );
create subscription sub1 connection 'dbname=testdb port=6444'
publication pub1 with (disabled);
alter subscription sub1 enable;

- publisher
vacuum testt;

now data change on the published table, (perhaps also a select on the
subscriber-side data) leads to:

- subscriber log:
TRAP: FailedAssertion("!(pointer != ((void *)0))", File: "mcxt.c", Line:
1001)

I very much doubt this is problem of vacuum as it does not send anything
to subscriber. Is there anything else you did on those servers?

It is not the vacuum that triggers the crash but the data change (insert
or delete, on the publisher) /after/ that vacuum.

Just now, I compiled 2 instances from master and such a crash (after
vacuum + delete) seems reliable here.

(If you can't duplicate such a crash let me know; then I'll dig out more
precise set-up detail)

I found the reason. It's not just vacuum (which was what confused me)
it's when the publishing side sends the info about relation again (which
happens when there was cache invalidation on the relation and then new
data were written) and I did free one pointer that I never set. I'll
send fixed patch tomorrow.
Thanks!

--
Petr Jelinek 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

#106Petr Jelinek
petr@2ndquadrant.com
In reply to: Petr Jelinek (#105)
7 attachment(s)
Re: Logical Replication WIP

On 27/11/16 23:54, Petr Jelinek wrote:

On 27/11/16 23:42, Erik Rijkers wrote:

On 2016-11-27 19:57, Petr Jelinek wrote:

On 22/11/16 18:42, Erik Rijkers wrote:

A crash of the subscriber can be forced by running vacuum <published
table> on the publisher.

- publisher
create table if not exists testt( id integer primary key, c text );
create publication pub1 for table testt;

- subscriber
create table if not exists testt( id integer primary key, c text );
create subscription sub1 connection 'dbname=testdb port=6444'
publication pub1 with (disabled);
alter subscription sub1 enable;

- publisher
vacuum testt;

now data change on the published table, (perhaps also a select on the
subscriber-side data) leads to:

- subscriber log:
TRAP: FailedAssertion("!(pointer != ((void *)0))", File: "mcxt.c", Line:
1001)

I very much doubt this is problem of vacuum as it does not send anything
to subscriber. Is there anything else you did on those servers?

It is not the vacuum that triggers the crash but the data change (insert
or delete, on the publisher) /after/ that vacuum.

Just now, I compiled 2 instances from master and such a crash (after
vacuum + delete) seems reliable here.

(If you can't duplicate such a crash let me know; then I'll dig out more
precise set-up detail)

I found the reason. It's not just vacuum (which was what confused me)
it's when the publishing side sends the info about relation again (which
happens when there was cache invalidation on the relation and then new
data were written) and I did free one pointer that I never set. I'll
send fixed patch tomorrow.
Thanks!

Okay, so here it is, I also included your doc fix, added test for
REPLICA IDENTITY FULL (which also tests this issue as side effect) and
fixed one relcache leak.

I also rebased it against current master as there was some conflict in
the bgworker.c.

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

Attachments:

0001-Add-support-for-TEMPORARY-replication-slots-v9.patch.gzapplication/gzip; name=0001-Add-support-for-TEMPORARY-replication-slots-v9.patch.gzDownload
0002-Refactor-libpqwalreceiver-v9.patch.gzapplication/gzip; name=0002-Refactor-libpqwalreceiver-v9.patch.gzDownload
�M�<X0002-Refactor-libpqwalreceiver-v9.patch�<kw�6���_��'�lQ�^~�qn][I��C������%6����M����$@������$�M�y���0���iwx������kZ��������~��tx���:V���E���`�=�n?����nw���s6�q���=�����������g���������=�
�����`xg�uz�w�w{��t����wn���o�����������cZq2��,>��^�-����Z�8��g��3�F�.4n3��������0�g� �Y��`� �/0ms��+{)�D-hC�L����)��&�8������8.��`��i3�EsH��2@IM<3c�taFj�������gA�[�&x;7?�@��������qh�q��qf�L�,":qe�=��h��>����4p�)^L8R���T��,�%@x���`a!_x�C���fl�b&�L����o�;3"h��'������8�*�	�O�`f�5�e���WA#�Z�f�YeQhmOL����>7�S��b��mY���W�JRe�Vk;���:��������f�G�Ea�v)6�eo����;K��$��]���]���6OV?+Z}�d�:�������]F����+�����C��!����Mx���l�x���Vm�qX�9�6����1�����3{���^�e�����	�����>*���Ta=k���������
`}0x$^��<���-�+��Fn�����mVY�b�`���F��/��������zp28�ep=��7����s��p>��9gG������P�Ml�q��'!7?V����~��I�9����2����`6�5���L��P3�<Mh��K(��
�_!����j����X�������t��~��Z����N�]�IO���eO�����9��N������j����b�t4�����,E�_�Q�����2�����F��lmc�G�8���r���
Oq���7+�`�^�=Y�^n&7N�\L�^D�a8������2����ol/��v
V�e�~���[�l�z|qu��|0�8~}v[m��]������q�<�6���-v�C�t+�}�r!hb�]���/�s��=���6e0|��l";7�'���|w~�'��b�����?0�������t(���F�|��HS`bQ�BNb<D���>��	�$��|+1�D:HY�%��0��DD�v$FB�!�l��[���������$X2��/��}�2by��xa�a�A|���3A��R�:OK���Q���Xr�.&��\�	P4�Rx�	T�7��]��:������>r�����S�%%;�=�t<�)�1�IC�t�aZ��
I��CO�>*c����}e&v6S�1H���+�zLAs�������6+�G�yA�����d�[>�7�@_Q�@6#�%�����L�E��)�CS����	
��?��yT:��*D�9�q�BMS�o��2o��Q��2�����C-�X���[��[���<��$�B�r�E\�����$O����y�|�"m�a+T����y�(�R�X�5�d@�M��0�[k�ck���BJ�-�����,f�f��r��Xm�!�-��tc��/��b������{2����b��Z�-���H�iN�E�6��U'��a�1��W1�$�Fo��TK�^��P{Wb?����+U�rxi:��d2���j�m����n
�$������������O&AWsiD���|g�=�)	-�w"�2�[
�^����@��w"Ya_�2�&�W��*ap1����)U~��G�*��/��!y��o9D�������`�|���G��'!q����c��J
����B���<��J�0eP��
 
��P����2D�Q�3��2���G*(�#�9*�y9L�UG�x��b��M�$�D��s�Y�����V���_�f���u�9X��X�^��T�e@kd=U�������O�>�m�c�������
��J�����U�q��{�SE&A�o����P�ea����Kh(��������j�N�kt���t��^{Z���h�M���hb�������3J�����6��e�����2�9P#I�Slp��O�E��������$�\C�%��S��	�C�*�t�xu�@B��B��B$4�G���cP�f������p�?���V��3�A0|+cO�(���H�
�lu�8�1\$9T�C��h�����D?}ruy98�]]��~K��:G�@Wg��m4�N�C�N��J�A5�<�B��NB!
hO:r������D=$#�Di��E���(�`3CHf3V������O71W#������]��>,��.��\ERf��uA��L��J���<��kV��l�i���=?g��ZZ�C.�Ax���rU�6��V��T9�(N�B��!������M�9������������"��
&�2�vKf�Z9�D����F��t1^��P�Z�����`c�|��x;��-�S�(p�� �"l�-r��p�#��Q���g�h�vEC3B�+s�7`Br-)��J���&�,�Z���Y����JK������.~�/�
�d��ObGU�����L]bs�����`��5�px""��P�$�cP��j%�s�u�~����jOk��6E�:&�)iO�iS
���-�P���|�[��QOX^�X$+�Ef�l3#f2��k����30���3!�O��j�*������rt�������hpQ#-,��+!T)��t��I7���/�]���7����9|��,E�����n������,$n��J��o���n�h�l����u#<���g����V�ot�������f�k����6�M���NRF&8$��)<|f�0������ ����������q	��	��2���'Xz,/aK��Y9�)y�%Gn�
��y���^.����0�q�
�l�/&jR��C?��\x�bC��&���)<c�p�%F>���S����d^�=���]�v��WW��#���1n������f��G����q�`:^T�6��OS�T�9���js�T���>`������Mx|����5J�[��8U�e�����g Kb�_����7���lL�&�e�Jr���Z�����Xm>���b��ICc��\����/���S���D�'����\/l���������������x���� m�������++d��}�%�R���e���h3�����d}�4�2Z���K����5.iw�$lR�N�����K��NT�FG^�������1V%���j��Y{��������W6:���]��e
����%���n�[H��^�d���^�� � *<���9�di���K��D>H^
�������Qf����V�h	�*e����pd]����t�����zq|������=��%��������i���g��P~v����k����)	-{|������)�����Y{]����A������#��oQ������C�N�n�'�@�+�?��D�56N�wK������X�'��~��uU����������L�f�0R0������Zy�)!�>��f�4���T|���A���s=�T��c;�'W��=9*�h���If2)�
Z��������6%5> ��u�9�_CQ��x�C�D����lN?���<D����z�������&4ciMx<�C�~��}���h*�i���*@Q��q��Z%�����1���n���:#��F�&N)����W���c8	�s�������<����"����1e�a|�n�6�W�s\����Y��~h���� ���)�!X!�)h�n�%�C�k�h�D��G12m:�v!���Rb����mU������I�]���� 4��
A�+o���[�igCrN8��.�}�S�_�-��y%���b�����pzz#�$�y�1YZvk�s�J$���{�GK��n���U�Y��������O]�"��La�~*�$x�jVK��������Q��WU<!��O�hK����<�>�����x��w�Z^��Rk�Xx��-���0��L_���J�����a�T(DT)`�����L����{k���s��`0��!�/c��������N}�����s�;z6&J���J��~FW:��C��&6z�9�yD���M�t�^��S�n_�nfi7�
l>.������QW�����kzP�0�}������	^��C����>����P���p,�_��n������o	%����\����#Z���8����M�~v����",L��r"�
v�qE	��:F������H���.]	-96�@��57�O��*�=�Z�jk���=B�:����*���u�����PO��&�ReJU��L��@I�9��W�����p
�m� �>��-�:�3��PF��o�����G*T�Y4PX��OJ�1���DB�P�9�0�.I9���bL��v�_�klc��:x38�]onJ2��An�P�W�lzu�s����k�s��R���UJ���T�tL���ttr{7�H�	.#Y�23���Y��2�0e7LYm$�'���J��z/ehB��>V��NO���<%��Hu��d��3�#�a~_�����Lryav�Z�KoE(�]�)������������3#������6������	�r�!�	W�n�i����������1�A��zI�_E%�����\\�[���N�;�8_���slN`����Q�W�Eja�Y
G����/�$�o.0!�-�d�/�8�@G�_���7���@�7g�G���!����]P�O�0��+J����l���Z��C�H0>P!�2��I� !��cSj���p	���'iKp(��BX���R$E�||}���e
�SOC�S�������b��B�N��h
T@����_������|�{B�Hf�!����������������3>��@@��7W'?Ft�������v�_���QGX'5�v��G.Q������F��!����N�w)���']�@ ��r�fp�3���!�@y7���z ����d,�Ht�9?���E�#���D�G�%�%&���|#���7:mpQ���Z.
��4;D�x�����R}���_�S����n�^���h��3����pa��g209��|��GM[3�kr�f����N���n���:;����� �e�a���>�ZP?�a��=I����[ZE)v��d�y��v4V��T���
�����?!}T�E{o$�"�F���M�,Dk�V�4�mWfC���q��Q<d����(��&�D!�Z���f�0���^���%��Z.�Q�b�C)��<v�MtI�f��Y�)���2�oia�b�-�|��`�Pb�<5!��]��K����H�B���3K�ex,;��n��-E�����#���+��V����NtE��"h�$p�v�����6��w<Iw�h�����a�����.(#}{l���R��x��7:����~�Qn4*��m.Q	�]�`��S��O�_�D�_�!���Ghu������@����Pj&��H F~�L�:�+���a�'���@.�Z�k��sO�O�a��L;}��L�arHz��Ax�4C��-��G6��l %��w�����Ci�oK*�^��d��-�j9����<�
[������:����RD��/4�/���y������W��f4�5~[~G�����4�
J��2n�)Y�����s�}H/���%���#�=Ld���������%(�v
��F)mAT*� '�z�m�o�e:�V�����H����H�}k�_�uk�[��-������������`m���l}�~ L�r�Z�����W��J��4�����H���e��:�*}u������v{[�F+������(�}ZZ�[�}[%w��*�{�;�;zy<����pp=>�p���]Ys7~�~��2eRZ��������-V�*Qq��*5�����[��A�`f{�/�98�F������8cg��lnJ*�|�!�`Y�������V�TK��k��+1�|�ZA�B<���4��4-��i������6��Z
�����&@��}����h}��T���_��a���J!~_F������Zn�9�!���Hl��0���/|�lw-�K���t��]5������vyI�#2��i<N85�O���6�.+�n�|�Z��� �g]�uL���o�<FA	���V��W��U���q��0�I�d�2�Nw����>I8<�y��3h8����p�Q<S�Jf�������^�0��6h��08 �"�p�����ne�1nxyf�^������c��i�|�Ws�7��f����\�^ud)K���)���e����y���V�l��n���s��������/�4���G��y/V�${������Q�]��]��������#d.P�����/����F��m�U�H��v}�"B2
QR��q�����]���x�lS���_�j�(������x�������?����#�@��
N��\�����M�~�^����Kl0b�RZ(w)WN���\/���j�������!O���G��tD���n5H���/L���8=���W����5�b3~L�5�c�T>�w��������Z��f�;���+�G/BOr �
��^���N����j�([M���I>��$8�I]{����:�U��L'�f��uqA���@�c�Q�!.���"��@5���r��;iG-�q�<�1'��rZ::oQ�?�Up00-��S���/�����z�S���)�����L�!�)�I�<1�L��������f�iX�}�'6���f�1�]"A7��U��*�K���Od��?�'����|>����+�$���m\XBx���j{a������u�����F9� %�hRK�{Z����r#���Y�Fgd���9��M��W�T�tQ"�jB
�4����#7O��j+W��TwJ��6�E������t���>�����7tZ����l�P�[��9#;F��-�1h,e-���P���2����)�.E5���{SK;�]���5t��Y��D�p<����b��k2��Gq4�Jv��.��*N��� =���
=�p���}$.Lk�6
tQ� -.��B;8-�\I��gM��+�6����5��*vM���!�sIw���R���� "���A�[[x{���kq8��CRq�)w���0��=7e���V�]��wG��1S��p�����-Y�5�����8��� ����������(^?��^�EB7g
6�^�a�J�����/d������{��������1v���v$O�ot:�j�y
l7�r����b�����T��5m�����J�n�H�}b�N��46�`�����iA,�|��C��,fKH���X'��lc g��G@�^��������m�`G�;���S�6��f ��Jg3�+YN
���7����
<�s���{1�L9���Q��1`�h��~������B��Aj��$��|0GX�	�M�i�p�%����.�1�'����S`�+eB�O�wp>X�	����m@�C�9-�eN�q��_���L�Tp�Ng�LP
���(�kI�U�v*}^�H�w�E���G�4��nt�6i�]���,�T�\�/W��L����g�2��V��b�V�f^1@�X.��k���'!�G�?���7��?�
���{+�+��(�|�nf�Akq�kq���2R�,>%�U�����e������Wr��Nh��U!�0fwtk�����''���3l������I�
�=bXC��z��2�i��K��h&���B��V��'b�WKe���x{wwq�NW(��r�x�|������%�'-R�[�>��%]J]���!� �Eq�-��3  �P�b��CA�/H*(wJ���O���O#J�a�q�2H��q�P���'�����������?3����������j������V���ORa�@��i��au�|;��h��z����.���~�jp{wq#n������.y�xGK���2J�@�gg��A��4�a���=+�k�:�Nx*�������k�o�^<Y����S�B������@��`	�Ei��*Sh��4o(6N���iB��_�jJ�K����m��7*���@H�w`����A�������S4BOG�Q���<��C�R�Bk������&g*��H���	��-H�I����������?EX�����-�#L~*���
����;&��'�-�7��AB	�_��.�>�_]���n�G��o�);�6U��Ft+��wV�ctk����g�K�����e�q���������0 c2�-��8�L�9Kr�9���)��s��)�,��U!�����J�����N��\�a�5FJ�Xjg���^dW/+��QP�����ua�2�M��
I�\��1sJR�>i�ycI�������7�x=rB��7���R��f|-�e��s����'��v�I��H���18ef��d+8���y/�a����YITp��7�2�LF�����d�o�e���^�/�a����Y����3������[�����$���R�S� g���^Trl�P�{�'��4��md�.�O�<��1I�Aj�s�0K��%�����%	����f��*���b�k.���S�'I�y�~F��e9��
`rcFva��4��6�.�3���l��s@i�'M���q�@��Uk�)X�����*��1�=[�Fb������fUs�[�CRg�<��MU��r�'e�[�j_s9���tQ~.yi>^���c��~��F~Y�&U�#�U���D��C �s����I��yp�7��5c�
0003-Add-PUBLICATION-catalogs-and-DDL-v9.patch.gzapplication/gzip; name=0003-Add-PUBLICATION-catalogs-and-DDL-v9.patch.gzDownload
0004-Add-SUBSCRIPTION-catalog-and-DDL-v9.patch.gzapplication/gzip; name=0004-Add-SUBSCRIPTION-catalog-and-DDL-v9.patch.gzDownload
0005-Define-logical-replication-protocol-and-output-plugi-v9.patch.gzapplication/gzip; name=0005-Define-logical-replication-protocol-and-output-plugi-v9.patch.gzDownload
�M�<X0005-Define-logical-replication-protocol-and-output-plugi-v9.patch�=iw�F���_������K�����"K���,i%:�����`��4 ������n�q���q2=�"���������������ng��<;8x68�=::����p`����{{�����w��ny�vX�����N��[}`^�k��;�:����N��/�U���^U����`�VTg{������������#��p��x�OnG/�/�'��������3>�����m�,��"P
B?�m�e�7d~q�7;^��h4�l��-����m��M��
?����}������;�`��"�<<�v�����a����(��N{l���Q��5���"�����o&��3�B/�����ABBr<�����I�Dsn���-��<�p�sDx2$E0{byc>��N���O�{���6<cC�r���]ev�AI(-��{{�
a���?�w������{��)����3�Fc�D�j-�����U����px4��6�����O���v�c1�o�e��n�S�������7����S��Y����A;���
`8%?�����*;n	�J�����1}d������x�0�������#'r����n#��������a�
�qK���`��
-�:����v��:P�h��#�O{��r������\����
����^�N�C�}u�;���o��/��'����qK���i���^��#��������S
�^	���|;ne�\\��q.H&)JBMdGK�#���"�m)7�����������;��?BH�d&VS@��Y�
��������y"���M\W��-A� ��wA7�Dv����Y���uI�~�^��3 ��S�E2��5��e69F�U����y0��N�0�1���Q�S�w
:�q����d��9���r���:��@���^<��S�>>Ek����&xF�At�2��B�K$�j!�9��
�b�I��xw����1�P(x?Qt&�,���Z!�[��	�20�����_c�EK�;y�?������d	��;��R��rk,�������!�%���P���#�Xg�5��ZCyy�d�Z���!�\��(��yB�Zp�������nM��h:q]�����;^F���1^:��Y�)�f��q)�'�N!���B�M��Ae-,���@��C��=
L��t��NJ�v��0�o����K\�-��7!���F����!���I������O���`���� M��E�����a|mfo��\5���D3�g�j~�C
�a;R^0C6�AD��:�@�O�$�����G83��*���|i4��$�����S�sL���B���>@�581�����D�����}��ly��2��v]�@
���"*b�9C/$���1��.�u/�`^���C������8{w�^����N`�C0�:L�a���	[�JKp�6��AC�0Ph@�Y��GbW�3V�G!��O/+B����5$�D\���*5�)��������9�\�Ra����N�J%�"x�(��i��f�~�q�@�:C��9<����a�=l��{���h"�q�%�v�r��,��(�j�H}T��*�9T�{k&��|�2<Q��k��D�v��c�������k��eV��6����\2����NY�H����5���H�B����x����_�!���\�����l+}vPF�5�2��s��d� �4���L5��Ri���%�����+�:@0SA����(*uoT9�$=�k	O�HM��X�EZ-m+�9Y��Z��{�fnIN��B����\>6GW���d.�Seesa�n`.�I��O:�!^=|�"F���{G8Q��?������G���?#� �kL�KS�1?Z&AR�6��f����x�e�E�A$��H��D��es<pMq�9��hEf_�L������;�Y7i�������R�����'O��	����dJ&��%�Z
S�IT��#���h�������J�fF�1�0a�;|(&���	"��`��z�J�����9e�32
��W}7g]�s����+��!?��=y�d{	h������)���X���r$�f&H���2\�^���&�=������R���(��S�:t��N�2��;��)n`��!/������Ct���P:�k_D������;�u��v���7J������Q�s$�e�}��s��y,3;����������aa�/�fH���c��y=��]m����l���t���b�B��wT����k�y!F
Kc�	3FZ���������	tZY7~���c�mR�r-�.�E��!s�K?��	�[=[�#'p��k�3^0��;��=�J�����f�v�&�_6��&�MR���+�e��4�fXNF3��u0�����������@��
�C��z15�VE4��"��L���V��0�	M��p�4.+��������:P.A�����XC�y_����zvI(�Q�1��Y�\�������.��|�Zc��N�l��*g��mj��0��n%9W�vUMqj���&�H�r��w`�i�@���O��+�t7W�^��kTy��d/�*� ����$��am� ����e�<z1�=Tg��{=~�"|�I'�CE���D>��uu�b�NumZ��p��	���%��2���5�����WPe%���E<���n�����v��"�%w7;ju\�s������00��!2J�zE�����=���_����m�|N~vEa�K��Q�#��x9�|hp���L�vQ������g���o�y��Q��U���x��?;�E��Eh �������T�%a���,����H�g�F5e@����*s�}&���Bu�qdZg�8��w�N�'���f*9r��c���.j�I�?��2���g6��$�IY�$	G��j���*Jb�x��'���:�	XA����PQ���e���,��,�����~����)-����!gb���k���;|����$���x������[=R�n5����j��a��|�(�a��\.����
p�*|0A��{z���:���o��^CJ-;��������d��}�YyD�i:�v�E�_6��.�����zP�8�kMd���8�%�S�(cg�m<��,=�/�V���l�U3G_3����x������=h6{Gm�}�=��� �f�Vy��S?�=���_�uon�K��*��s����*�k��eg�z��c�@X
wJ���i��q��
,Dc���������Vn�x�;<���~�9��C~�)�p%`)Wj���������Us�����t�t����5��?�\���?q������������K��������o��}���w(�!�CRM_�N?��;��gE��S�C���Cb<D��^mT�g�d���|pyZ�'ma�0�9�����5�v���{����H_y�\��\>�7H����J�����|��yH�����������AUi���)u����o�;���M���}��?W*�8�4i����f �I�j�6^���n�v{Kj9�w�`�����I9z�N~��JeU�(�H���_Z��I�hN�0
�/��h������g!Z����}����[�5��_�$ayW����9�?BFU�6����>������������2���T1�5fAK,��x����}�5���6�#���["p
/)���_enV�����mp�wxrL����������-ck^��C����B	��<��K�O���(�N��>���[�|!�dm��Y�vEOF��;�*�x4�)�5=�N����~�V�'U�]#G���|g���:��2�>������|�����pr��Y�jA�!���g����� ��#��qt�:Sg�nx���t1���lVZ{�9I~b�\As�ih eC��h�({�����I�Z���s�p��r�����Lx��z�������u�6'�����Q59^O�%�v���T
�%n�2���J�M�G�u!�?v�lr}J�n�uZs�x�_�4���7���Q�T�����g��u��6p�����	c������N(�dQsbn��j_Ck��
���G���,��CV���m�p
w���h�;��0�	i�:{R��*���T;���`D
���rk.
�m���S
�Q#�������@���X��iE���f:t����������% ��-��T*�=b�0�?d�Ea��TG������������d+su
{�U�d�n:�����U����[H������,� �E:�a�2|��[M�WbK;R��6������+qf[r&@�����"�%�I�C��#�A���m�R�5���<����gFe���sx%��b�k����.������T1�ZQ��x2��J��Ta��A;�>r>�!Q,�z�;;x�A���Wt���
O2���<���p�kf4�����s�v>s"_b�����U
���.:{)c���R�X� 2�H��UcM�X�/��9=^��wg���'@�����=���������
ht������e�`n�8���t�|Zfs�|�]
9��"��T���w������Dl5�M���;�L���O��HhK�+����s���	���q{*�����\W��X��T<�~;c	)x���^�Zl:��iY-G������y�M����z�����d�5$��,Nf��/ �������)0_����VAP�6(HX���3���Vk��B+���M���BV�
fg���I���7����J��Bq�8b��Fw�y��Q>2{����%Q�R��$d��E���\yZ`K)�F�y���(�I7�U��x������������7r�r?|^S*�I�1������������fJAN�@��a_�6�@�����g�oN�_���O��T�]�;��xh�����O�����������F��yu�e��oy�!��bm)/5�Mpj�Yi���u&i%2*�Jd$��P�7���uFs���)"tK�FQ���$~J��dF*:��@0
`Z��g�p�8����L��B�
7���K3q�������
wt�!K�j�fJ����5DJ����TRZ���Nz�1o�7xq��0o������c��s��������>���8jn��TL�'V�W�l�z�U�����b�����,�q�7����Sz��y�������Rm�����)�y���V_�3��x����h�-����8��������V���}�u�d�R'��n�iF������K���O�x@�D�����RvDl%#�Lat	�@)#��U�<1hs�Y�����uv~q��%w��������J�
����j:�z� ��8�v���^�Y���Q��������+�����y����+����J��*���/�?������q
���<���"o�/h���Z��J�[.B�������*]W����7B��z(�A{���Gl_S��e��9�m������:�]�����F�i�k���-��5���j����N������+��.E���JN]p*������Z�.��p���Q��n;�"�����R�Adg��$Y;��+OJt�j�z��aiD�\�9%/!�B�DM�2F��WYP���f�#� ��
��R��C*uGt�<��\�n����=D#}�2�&�}N)�%��������Ai<�4����(��k�����T[R�uU�������N!P�������{����L��~�n�����8�M=�5lq�=��p�AJ=�i-Ev;�LI���pM�����h�s}�:J���<�*�pd@G��.��f������n{7�O{�4*=�y��yk�nuO-qn�����y�]3(I-�G��b��b������L9M��M��=u���X�������%�=�g���]��V�'X*'�x�[]9��"b|4rl�P[���B8�455�Y������
{�$�e�
���>�&�T����������h�)\�Co�T���6��r�#���W*���q����."��-���z`���C=������d<������Kr��8��O��>�/�fx���b�)AhgG���F�4��N�~QH��S<�������MAyC�����|X��<�
����"m���w��v��Q���������6n%�L�
D���,��l��u�%�����"�8�9��#�k�d8C������
�f8t�=��U������h4������}i���&A�`�f��_��<C���_itR1�j��$���6

�����~Q��;?T����"-��z�t���w��������$;&�W��.)<���g�St*�*���N���G��sU����z�1ZT��/��F�v��:�������Q��MYt����,�/6E�b�Wt�1[]��X>��q�vL�s�����V����E�37)�R*J�����J�"�<

�i���g��KQ�`]7uOh/��zu�\Qoi�q��MSe��c�7*5s>1�1�m��ro��x�}�zqW�9�����c�"G��^	����P�%Th��~�����^���s����ua*���Uu����
R�]����'�5�8@-�2�/��A��� �A�l��6w|>�Vb��ob����@GD/�������T��VP#������B4�|{�4��iH�
�+>��������x��5�����������U��&o���# � M���1g�x����|��f6`X]]�~���2i�G��]�blR��{L/U���_��0Ev�W���J6Hi�������2�,'�3~
�q���3��Hr��wC�%�0[��x��1|���)�����e�j
���?�o�9�U����s��X�D�����j#nj0�i�$�)V��Z�U��`@���;W�Y���H<*!�~�1�����hhvr�aj<�M?gd:a��Fu��?����m���V�!\w��Pe)�����5TS�$��/t�7�%Z
��zm��%P4�
�3�GY��M�g��'paz��v�KL�8��J�2�z����R2q����?>_��U����$0
��w���a���8�z��Q�����[�>j��P*���x���P*V)<v�Tp�Hs����A��X����i������V�X��dkKf��S
=yV���x���+��������b6�eW������gcW]3'r�dGDj�( ��s`��3O/�
�n81��+��MKU��d4rV?��V�UKOS�5��~���'� J���������o�t	���$�\�m"S�Uer�&FL =\����A�o�������Y?rJp-������T����x�rz�x��Tu2/e����e�����N���O:���A�����G� %'��<Q[��
��=�_M��������p5����A��Hy�Gv	i���
���n��qz����LU1[���%lYF�6;�:��p����)�_��A�N8?�!��7lO7�-)B�ZCc��R	����x<���'���0��0����/��-��t[���I�EWuU*)ZPE������]FcJ�\��;��>���{�8
��8�����;���~�����_�<:�v���rF�c���%�J)���������j�3T�wSog2��r�3�����NH5J���������W��r��rjj2?������=�_7�����=x��.�X��}G���\�f��p"��I�Q��O���N[�����_��Hx4&�������r<�';_g�y���_�S����Rx'G{-��J�JM�*/`��-c��K��?~iX�K�������N��
�)��<=�@@�P���g6�#>A�OU��	�� S�	5����h����"�BV�!���s�@�r��a�����r�8����<���qF]���]/s�H�����9�:�P����x
Y�:�^�%��*�c]�&y�k�W��|+��P��t�&�tD�+a��3~ ��o�C�o�Z�^�C��\nF�8�%�s��� �=��L1f��14�0U%�����-��x��2�Qz�u�]�a����L�x�d�(�:G�=6�W�8��^�L.������(%\��07����'X��n���T:�f;���	��r:��[}�=������/��K
�m��4�ha���^j��|.d�^���j����@]�;�(9�����d�f�yB!�G���!?p�#��B��TG8���W,����i�Z��(�����r���~�2n=^U��[I���!��{
a���"���1�D�_v�(;��h��xu�{�V{$�������_�B{R���_��~]�eZ�?4EX�I������� �?����Fod���c���<H�d���n��������I��������
 z�HTJcQ��[���[�r���\����=�KE�TI�����$�A{�!�R7������Q�C�|#*�����Tab�Q�v�l^���d7/�dU�AF�|s���^n�0u8{;c�P�������u��������<r��	x(�:���X_��-9*�<�q��M�f,sDi��g��~���^�M�J��	���67@�]�����<��%�� Bd6��b��Sv:'�s�z�����nmS�m���m�7���_��G�������E���D�%{�*P��3����^���������� �����4}��������w��(z� p�Bg���!���/��2,U�r+���j�p+R.���U�Y*b����E����}w��JHo����]U-�
�6�K��?O>�!��}����?Sk�����g=�X�mv]��?�r��(�c�g��|bo�v�@���X�2���tB�[�9<�C<N]��^Q���f1~��%�P�X�kD .�\��D7�e<\m�d
���
��|�Q��}||v���t�k����{���U��N�zu��p�bZ���j�&�s%�xL��e�)h@1�0ZH�h*�C�����w~���_�::����/~��C];��A�t�EE�W�6����_�����
�Q�q%q�A��'Y�����1_��	��X�c!�PWH����Q���P��}������lz?w�	&(Q�"������Mf�R����RQ���.�(���,�����������<�J�H�A����v����?=������g����V��q|��?�nDh.0��Q����5����.YB��1����.����4�����6 Lk�82�@�����2�r=.�����������5u3�=�Q����n�(h�T�^����#�
=P@.�#.��D���xa�%�<���{��)~(� [�-����n]p��#���E<"�e�-��S;*��z^�JHT�V!t�����Jb,7!�'	ys��LW�8_ rtJ1M��7%Mp"Y�U�nBzt������,����AC�P�]H.��m���g��U�^����7o�<o� �N0�z���Uj
��\P���=1���O�g�L��NG.C���T�'��[�K���$��A'��wMB�������-�k�T;|!%�R���lS�og�������n0�&�r0���1.�X
���a<�2��L%�2G�������ed���'���V^�C�j`��C�4�b��FX�-�ou
�t1DH�d�Mc�O���`�E<s�s�J��$��U�$�
�p�������r��'����l��YMQ��q�7�_�Upc��:�V�<��cA?��d�/����X���1�8"Fb^�E���6����/������W��#��3k��N�n,'���a����(�����fh�F3+���0�M�f���/ED��v3��'��W:�W|�$Y�`�,3�{#��,����SK��������4��Y���
C��e�$���\�:\.���S�}R�Fi:e]�.-Zd�\���a�0��	ff����$���y���I���AB�J�mI�_��La�����~������N����j�*�*�J(�%xe):B+�s�&:���j��(%���#�#	�5TI����(X����u�����6gV_W��T�/#kn����k8x������~���]
����bB�R�i~m��l�q����N�W?K��n��m���BX��!_2B��u�� ��'�i�
��>-���*�V�\+��>-8s�����}���|�x[m��#c�����0Y��>�
0��pn����1�s^����h����V�)��UN��((C���&�QA�E+�Qk�T�
��g�]��-��.��s��9��`9�(������ZHZ�z��1Z��k��G�RE��Z��F���e�r�x��u�t���&�J�����(�If�/�(#�1�G�/fw�b�f1,E��3y�O6El"�f��,r����lVe��4����� �^�>)�"7�].A���'��7��"����y�G�uR��M�F-��0�B�P��$"}��,p��;����3Fx���$�q��������	���9������s�3�Y���t���B2��i�zX�������,.��F�w~m@���qbTdV��V"J*x�H����m���0Q�N����,��>�6#�*���10s��ZW�J���t������-2��H���&���x�p@eg�b���j��/���������t�3-��q�3������@7zf��v��A��1�'���c����J+����5b���!���y���6��nB'\�
������.f���?wS<�KR����#���)�&wW���5t+zY��&a�&`����_�����'?n+���g�j��������i������?>��R�$q�T	�����|>��w�q!<oN1��������!�����w�f��Jzm�������b���������+z���A���b:���89�,%&����9�e�4����DQ����2���������QrZ�}�����IF�Ua������$�_����p���j�.���+t���:5D`����$�"w<S\��G��8
Q�F�1��,G�3��"f~Q-���&�&h�S���&�)B��P�p�M�7����#&��x����X9m�G�Y�"��X9�%'��H�Knf(]�V��*-w�����ZBD�We8t��x;/@��F�!_�px=k1��"��X]��j���G��{�q�������r�����	:T�cs�r77Wv�U��H�����zj��!�Z��r��=�����w?>��V�����z'sl�_Q���&�-��	"��� �S%�JW������Mu�v�#�-k�I���=$���k���P����3P�o���I�I�*Pm�9��T��Og���N�a�1�^M
�@�������,'�a@u��#A�������!{CH�����0�c��Xk[�%�~[*��p�'u�"���C��?��"�����As�,�9�>o�����V�$jG�������u��Hc��1�I.�Zg�����t(��T��)�$`�m� M���
|%g_���x.��F�,�xJ��mF�#����N�n�1��/a32O���.N������l��j!��v`9��h[Uj�����m_3�~��My H���V�Z�+���a���q
����&_[)��<5�uL�����-��U��n�������v�����H��C��������w��B��#��1����#>�)O���a0�b��z6����%�WY<p>�&���
�O�%��f��z�C�I����q~�/X��$��C	���xau�z������O��5�q�?���A;Z%d��wF����:|H����jXWSL!��^��}
/HX���2��9
(�)�;A ������d������o�	.�e���ms"'�o�����+�W��>���W~��flCn�rj�Z�2���1<w�P�C_h��K.e������vT���!�p6��Cv�k'S������qE�B9����t�1
���$���.�@2�k����~tS06/	����vJ��A`�a��M9������<�J�?��B����h��c�}�x����������0��gJI��0�OH���9�4nT��_�^�o����K���P������+
�`�}V�,`pV���%]8�����r������d���)�J�\kz�w*�?)��
P.�U���Sd���cB:*�s6�6���r7w/���4
,��	�M�c+�.��5z�0�m�e����}3�E2��%��>�[	ac�
�T��zQE���}�f���0�� ��
���bMx�5��U��
0�+��h��]�mf�����l'4���t6�=_���.�NI�A�$[��2�+��(����n�_reG%�;���-o��T
#l�Gt���T_k(�_��(1��q��e|A	�� ?[�����r/2F~��p�@a�	&`�k�Wu���x^@��m���������hR����������N��G����UCY����������jqUV���B����]|F;��~�H��k�CkY�Z�����jY�O���p�t-k�i�������1�H��_�R�{�5���W�������������	Ht������b�9*^h��+(�` 8����k/f�)��a�z�So��v��5���,,-K�0��'�Ow�����7 ���
0006-Add-logical-replication-workers-v9.patch.gzapplication/gzip; name=0006-Add-logical-replication-workers-v9.patch.gzDownload
0007-Add-separate-synchronous-commit-control-for-logical--v9.patch.gzapplication/gzip; name=0007-Add-separate-synchronous-commit-control-for-logical--v9.patch.gzDownload
#107Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Petr Jelinek (#106)
2 attachment(s)
Re: Logical Replication WIP

I have taken the libpqwalreceiver refactoring patch and split it into
two: one for the latch change, one for the API change. I have done some
mild editing.

These two patches are now ready to commit in my mind.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

0001-Use-latch-instead-of-select-in-walreceiver.patchtext/x-patch; name=0001-Use-latch-instead-of-select-in-walreceiver.patchDownload
From dc95826ff5018be1f001bead888b16ea3effe099 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Wed, 30 Nov 2016 12:00:00 -0500
Subject: [PATCH 1/2] Use latch instead of select() in walreceiver

Replace use of poll()/select() by WaitLatchOrSocket(), which is more
portable and flexible.

Also change walreceiver to use its procLatch instead of a custom latch.

From: Petr Jelinek <petr@2ndquadrant.com>
---
 src/backend/postmaster/pgstat.c                    |   3 +
 .../libpqwalreceiver/libpqwalreceiver.c            | 101 +++++----------------
 src/backend/replication/walreceiver.c              |  18 ++--
 src/backend/replication/walreceiverfuncs.c         |   6 +-
 src/include/pgstat.h                               |   1 +
 src/include/replication/walreceiver.h              |   3 +-
 6 files changed, 43 insertions(+), 89 deletions(-)

diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index a392197..c7584cb 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -3338,6 +3338,9 @@ pgstat_get_wait_client(WaitEventClient w)
 		case WAIT_EVENT_WAL_RECEIVER_WAIT_START:
 			event_name = "WalReceiverWaitStart";
 			break;
+		case WAIT_EVENT_LIBPQWALRECEIVER_READ:
+			event_name = "LibPQWalReceiverRead";
+			break;
 		case WAIT_EVENT_WAL_SENDER_WAIT_WAL:
 			event_name = "WalSenderWaitForWAL";
 			break;
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index f1c843e..6c01e7b 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -23,19 +23,11 @@
 #include "pqexpbuffer.h"
 #include "access/xlog.h"
 #include "miscadmin.h"
+#include "pgstat.h"
 #include "replication/walreceiver.h"
+#include "storage/proc.h"
 #include "utils/builtins.h"
 
-#ifdef HAVE_POLL_H
-#include <poll.h>
-#endif
-#ifdef HAVE_SYS_POLL_H
-#include <sys/poll.h>
-#endif
-#ifdef HAVE_SYS_SELECT_H
-#include <sys/select.h>
-#endif
-
 PG_MODULE_MAGIC;
 
 void		_PG_init(void);
@@ -59,7 +51,6 @@ static void libpqrcv_send(const char *buffer, int nbytes);
 static void libpqrcv_disconnect(void);
 
 /* Prototypes for private functions */
-static bool libpq_select(int timeout_ms);
 static PGresult *libpqrcv_PQexec(const char *query);
 
 /*
@@ -367,67 +358,6 @@ libpqrcv_readtimelinehistoryfile(TimeLineID tli,
 }
 
 /*
- * Wait until we can read WAL stream, or timeout.
- *
- * Returns true if data has become available for reading, false if timed out
- * or interrupted by signal.
- *
- * This is based on pqSocketCheck.
- */
-static bool
-libpq_select(int timeout_ms)
-{
-	int			ret;
-
-	Assert(streamConn != NULL);
-	if (PQsocket(streamConn) < 0)
-		ereport(ERROR,
-				(errcode_for_socket_access(),
-				 errmsg("invalid socket: %s", PQerrorMessage(streamConn))));
-
-	/* We use poll(2) if available, otherwise select(2) */
-	{
-#ifdef HAVE_POLL
-		struct pollfd input_fd;
-
-		input_fd.fd = PQsocket(streamConn);
-		input_fd.events = POLLIN | POLLERR;
-		input_fd.revents = 0;
-
-		ret = poll(&input_fd, 1, timeout_ms);
-#else							/* !HAVE_POLL */
-
-		fd_set		input_mask;
-		struct timeval timeout;
-		struct timeval *ptr_timeout;
-
-		FD_ZERO(&input_mask);
-		FD_SET(PQsocket(streamConn), &input_mask);
-
-		if (timeout_ms < 0)
-			ptr_timeout = NULL;
-		else
-		{
-			timeout.tv_sec = timeout_ms / 1000;
-			timeout.tv_usec = (timeout_ms % 1000) * 1000;
-			ptr_timeout = &timeout;
-		}
-
-		ret = select(PQsocket(streamConn) + 1, &input_mask,
-					 NULL, NULL, ptr_timeout);
-#endif   /* HAVE_POLL */
-	}
-
-	if (ret == 0 || (ret < 0 && errno == EINTR))
-		return false;
-	if (ret < 0)
-		ereport(ERROR,
-				(errcode_for_socket_access(),
-				 errmsg("select() failed: %m")));
-	return true;
-}
-
-/*
  * Send a query and wait for the results by using the asynchronous libpq
  * functions and the backend version of select().
  *
@@ -470,14 +400,31 @@ libpqrcv_PQexec(const char *query)
 		 */
 		while (PQisBusy(streamConn))
 		{
+			int			rc;
+
 			/*
 			 * We don't need to break down the sleep into smaller increments,
-			 * and check for interrupts after each nap, since we can just
-			 * elog(FATAL) within SIGTERM signal handler if the signal arrives
-			 * in the middle of establishment of replication connection.
+			 * since we'll get interrupted by signals and can either handle
+			 * interrupts here or elog(FATAL) within SIGTERM signal handler if
+			 * the signal arrives in the middle of establishment of
+			 * replication connection.
 			 */
-			if (!libpq_select(-1))
-				continue;		/* interrupted */
+			ResetLatch(&MyProc->procLatch);
+			rc = WaitLatchOrSocket(&MyProc->procLatch,
+								   WL_POSTMASTER_DEATH | WL_SOCKET_READABLE |
+								   WL_LATCH_SET,
+								   PQsocket(streamConn),
+								   0,
+								   WAIT_EVENT_LIBPQWALRECEIVER_READ);
+			if (rc & WL_POSTMASTER_DEATH)
+				exit(1);
+
+			/* interrupted */
+			if (rc & WL_LATCH_SET)
+			{
+				CHECK_FOR_INTERRUPTS();
+				continue;
+			}
 			if (PQconsumeInput(streamConn) == 0)
 				return NULL;	/* trouble */
 		}
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 2bb3dce..8bfb041 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -261,7 +261,7 @@ WalReceiverMain(void)
 	/* Arrange to clean up at walreceiver exit */
 	on_shmem_exit(WalRcvDie, 0);
 
-	OwnLatch(&walrcv->latch);
+	walrcv->latch = &MyProc->procLatch;
 
 	/* Properly accept or ignore signals the postmaster might send us */
 	pqsignal(SIGHUP, WalRcvSigHupHandler);		/* set flag to read config
@@ -483,7 +483,7 @@ WalReceiverMain(void)
 				 * avoiding some system calls.
 				 */
 				Assert(wait_fd != PGINVALID_SOCKET);
-				rc = WaitLatchOrSocket(&walrcv->latch,
+				rc = WaitLatchOrSocket(walrcv->latch,
 								   WL_POSTMASTER_DEATH | WL_SOCKET_READABLE |
 									   WL_TIMEOUT | WL_LATCH_SET,
 									   wait_fd,
@@ -491,7 +491,7 @@ WalReceiverMain(void)
 									   WAIT_EVENT_WAL_RECEIVER_MAIN);
 				if (rc & WL_LATCH_SET)
 				{
-					ResetLatch(&walrcv->latch);
+					ResetLatch(walrcv->latch);
 					if (walrcv->force_reply)
 					{
 						/*
@@ -652,7 +652,7 @@ WalRcvWaitForStartPosition(XLogRecPtr *startpoint, TimeLineID *startpointTLI)
 	WakeupRecovery();
 	for (;;)
 	{
-		ResetLatch(&walrcv->latch);
+		ResetLatch(walrcv->latch);
 
 		/*
 		 * Emergency bailout if postmaster has died.  This is to avoid the
@@ -687,7 +687,7 @@ WalRcvWaitForStartPosition(XLogRecPtr *startpoint, TimeLineID *startpointTLI)
 		}
 		SpinLockRelease(&walrcv->mutex);
 
-		WaitLatch(&walrcv->latch, WL_LATCH_SET | WL_POSTMASTER_DEATH, 0,
+		WaitLatch(walrcv->latch, WL_LATCH_SET | WL_POSTMASTER_DEATH, 0,
 				  WAIT_EVENT_WAL_RECEIVER_WAIT_START);
 	}
 
@@ -763,7 +763,7 @@ WalRcvDie(int code, Datum arg)
 	/* Ensure that all WAL records received are flushed to disk */
 	XLogWalRcvFlush(true);
 
-	DisownLatch(&walrcv->latch);
+	walrcv->latch = NULL;
 
 	SpinLockAcquire(&walrcv->mutex);
 	Assert(walrcv->walRcvState == WALRCV_STREAMING ||
@@ -812,7 +812,8 @@ WalRcvShutdownHandler(SIGNAL_ARGS)
 
 	got_SIGTERM = true;
 
-	SetLatch(&WalRcv->latch);
+	if (WalRcv->latch)
+		SetLatch(WalRcv->latch);
 
 	/* Don't joggle the elbow of proc_exit */
 	if (!proc_exit_inprogress && WalRcvImmediateInterruptOK)
@@ -1297,7 +1298,8 @@ void
 WalRcvForceReply(void)
 {
 	WalRcv->force_reply = true;
-	SetLatch(&WalRcv->latch);
+	if (WalRcv->latch)
+		SetLatch(WalRcv->latch);
 }
 
 /*
diff --git a/src/backend/replication/walreceiverfuncs.c b/src/backend/replication/walreceiverfuncs.c
index 5f6e423..01111a4 100644
--- a/src/backend/replication/walreceiverfuncs.c
+++ b/src/backend/replication/walreceiverfuncs.c
@@ -64,7 +64,7 @@ WalRcvShmemInit(void)
 		MemSet(WalRcv, 0, WalRcvShmemSize());
 		WalRcv->walRcvState = WALRCV_STOPPED;
 		SpinLockInit(&WalRcv->mutex);
-		InitSharedLatch(&WalRcv->latch);
+		WalRcv->latch = NULL;
 	}
 }
 
@@ -279,8 +279,8 @@ RequestXLogStreaming(TimeLineID tli, XLogRecPtr recptr, const char *conninfo,
 
 	if (launch)
 		SendPostmasterSignal(PMSIGNAL_START_WALRECEIVER);
-	else
-		SetLatch(&walrcv->latch);
+	else if (walrcv->latch)
+		SetLatch(walrcv->latch);
 }
 
 /*
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 0b85b7a..152ff06 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -763,6 +763,7 @@ typedef enum
 	WAIT_EVENT_CLIENT_WRITE,
 	WAIT_EVENT_SSL_OPEN_SERVER,
 	WAIT_EVENT_WAL_RECEIVER_WAIT_START,
+	WAIT_EVENT_LIBPQWALRECEIVER_READ,
 	WAIT_EVENT_WAL_SENDER_WAIT_WAL,
 	WAIT_EVENT_WAL_SENDER_WRITE_DATA
 } WaitEventClient;
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index cd787c9..afbb8d8 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -127,8 +127,9 @@ typedef struct
 	 * where to start streaming (after setting receiveStart and
 	 * receiveStartTLI), and also to tell it to send apply feedback to the
 	 * primary whenever specially marked commit records are applied.
+	 * This is normally mapped to procLatch when walreceiver is running.
 	 */
-	Latch		latch;
+	Latch	   *latch;
 } WalRcvData;
 
 extern WalRcvData *WalRcv;
-- 
2.10.2

0002-Refactor-libpqwalreceiver.patchtext/x-patch; name=0002-Refactor-libpqwalreceiver.patchDownload
From 32546a13cc136905a378f59ca0b09c25e2668e8f Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Wed, 30 Nov 2016 12:00:00 -0500
Subject: [PATCH 2/2] Refactor libpqwalreceiver

The whole walreceiver API is now wrapped into a struct, like most of our
other loadable module APIs.  The libpq connection is no longer a global
variable in libpqwalreceiver.  Instead, it is encapsulated into a struct
that is passed around the functions.  This allows multiple walreceivers
to run at the same time.

Add some rudimentary support for logical replication connections to
libpqwalreceiver.

These changes are mostly cosmetic and are going to be useful for the
future logical replication patches.

From: Petr Jelinek <petr@2ndquadrant.com>
---
 .../libpqwalreceiver/libpqwalreceiver.c            | 266 ++++++++++++---------
 src/backend/replication/walreceiver.c              |  59 +++--
 src/include/replication/walreceiver.h              |  83 +++++--
 3 files changed, 235 insertions(+), 173 deletions(-)

diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 6c01e7b..c3b0bf5 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -32,59 +32,72 @@ PG_MODULE_MAGIC;
 
 void		_PG_init(void);
 
-/* Current connection to the primary, if any */
-static PGconn *streamConn = NULL;
-
-/* Buffer for currently read records */
-static char *recvBuf = NULL;
+struct WalReceiverConn
+{
+	/* Current connection to the primary, if any */
+	PGconn *streamConn;
+	/* Used to remember if the connection is logical or physical */
+	bool	logical;
+	/* Buffer for currently read records */
+	char   *recvBuf;
+};
 
 /* Prototypes for interface functions */
-static void libpqrcv_connect(char *conninfo);
-static char *libpqrcv_get_conninfo(void);
-static void libpqrcv_identify_system(TimeLineID *primary_tli);
-static void libpqrcv_readtimelinehistoryfile(TimeLineID tli, char **filename, char **content, int *len);
-static bool libpqrcv_startstreaming(TimeLineID tli, XLogRecPtr startpoint,
-						char *slotname);
-static void libpqrcv_endstreaming(TimeLineID *next_tli);
-static int	libpqrcv_receive(char **buffer, pgsocket *wait_fd);
-static void libpqrcv_send(const char *buffer, int nbytes);
-static void libpqrcv_disconnect(void);
+static WalReceiverConn *libpqrcv_connect(const char *conninfo,
+										 bool logical, const char *appname);
+static char *libpqrcv_get_conninfo(WalReceiverConn *conn);
+static char *libpqrcv_identify_system(WalReceiverConn *conn,
+									  TimeLineID *primary_tli);
+static void libpqrcv_readtimelinehistoryfile(WalReceiverConn *conn,
+								 TimeLineID tli, char **filename,
+								 char **content, int *len);
+static bool libpqrcv_startstreaming(WalReceiverConn *conn,
+						TimeLineID tli, XLogRecPtr startpoint,
+						const char *slotname);
+static void libpqrcv_endstreaming(WalReceiverConn *conn,
+								  TimeLineID *next_tli);
+static int	libpqrcv_receive(WalReceiverConn *conn, char **buffer,
+							 pgsocket *wait_fd);
+static void libpqrcv_send(WalReceiverConn *conn, const char *buffer,
+						  int nbytes);
+static void libpqrcv_disconnect(WalReceiverConn *conn);
+
+static WalReceiverFunctionsType PQWalReceiverFunctions = {
+	libpqrcv_connect,
+	libpqrcv_get_conninfo,
+	libpqrcv_identify_system,
+	libpqrcv_readtimelinehistoryfile,
+	libpqrcv_startstreaming,
+	libpqrcv_endstreaming,
+	libpqrcv_receive,
+	libpqrcv_send,
+	libpqrcv_disconnect
+};
 
 /* Prototypes for private functions */
-static PGresult *libpqrcv_PQexec(const char *query);
+static PGresult *libpqrcv_PQexec(PGconn *streamConn, const char *query);
 
 /*
- * Module load callback
+ * Module initialization function
  */
 void
 _PG_init(void)
 {
-	/* Tell walreceiver how to reach us */
-	if (walrcv_connect != NULL || walrcv_identify_system != NULL ||
-		walrcv_readtimelinehistoryfile != NULL ||
-		walrcv_startstreaming != NULL || walrcv_endstreaming != NULL ||
-		walrcv_receive != NULL || walrcv_send != NULL ||
-		walrcv_disconnect != NULL)
+	if (WalReceiverFunctions != NULL)
 		elog(ERROR, "libpqwalreceiver already loaded");
-	walrcv_connect = libpqrcv_connect;
-	walrcv_get_conninfo = libpqrcv_get_conninfo;
-	walrcv_identify_system = libpqrcv_identify_system;
-	walrcv_readtimelinehistoryfile = libpqrcv_readtimelinehistoryfile;
-	walrcv_startstreaming = libpqrcv_startstreaming;
-	walrcv_endstreaming = libpqrcv_endstreaming;
-	walrcv_receive = libpqrcv_receive;
-	walrcv_send = libpqrcv_send;
-	walrcv_disconnect = libpqrcv_disconnect;
+	WalReceiverFunctions = &PQWalReceiverFunctions;
 }
 
 /*
  * Establish the connection to the primary server for XLOG streaming
  */
-static void
-libpqrcv_connect(char *conninfo)
+static WalReceiverConn *
+libpqrcv_connect(const char *conninfo, bool logical, const char *appname)
 {
+	WalReceiverConn *conn;
 	const char *keys[5];
 	const char *vals[5];
+	int			i = 0;
 
 	/*
 	 * We use the expand_dbname parameter to process the connection string (or
@@ -93,22 +106,29 @@ libpqrcv_connect(char *conninfo)
 	 * database name is ignored by the server in replication mode, but specify
 	 * "replication" for .pgpass lookup.
 	 */
-	keys[0] = "dbname";
-	vals[0] = conninfo;
-	keys[1] = "replication";
-	vals[1] = "true";
-	keys[2] = "dbname";
-	vals[2] = "replication";
-	keys[3] = "fallback_application_name";
-	vals[3] = "walreceiver";
-	keys[4] = NULL;
-	vals[4] = NULL;
-
-	streamConn = PQconnectdbParams(keys, vals, /* expand_dbname = */ true);
-	if (PQstatus(streamConn) != CONNECTION_OK)
+	keys[i] = "dbname";
+	vals[i] = conninfo;
+	keys[++i] = "replication";
+	vals[i] = logical ? "database" : "true";
+	if (!logical)
+	{
+		keys[++i] = "dbname";
+		vals[i] = "replication";
+	}
+	keys[++i] = "fallback_application_name";
+	vals[i] = appname;
+	keys[++i] = NULL;
+	vals[i] = NULL;
+
+	conn = palloc0(sizeof(WalReceiverConn));
+	conn->streamConn = PQconnectdbParams(keys, vals, /* expand_dbname = */ true);
+	if (PQstatus(conn->streamConn) != CONNECTION_OK)
 		ereport(ERROR,
 				(errmsg("could not connect to the primary server: %s",
-						PQerrorMessage(streamConn))));
+						PQerrorMessage(conn->streamConn))));
+	conn->logical = logical;
+
+	return conn;
 }
 
 /*
@@ -116,17 +136,17 @@ libpqrcv_connect(char *conninfo)
  * are obfuscated.
  */
 static char *
-libpqrcv_get_conninfo(void)
+libpqrcv_get_conninfo(WalReceiverConn *conn)
 {
 	PQconninfoOption *conn_opts;
 	PQconninfoOption *conn_opt;
 	PQExpBufferData buf;
 	char	   *retval;
 
-	Assert(streamConn != NULL);
+	Assert(conn->streamConn != NULL);
 
 	initPQExpBuffer(&buf);
-	conn_opts = PQconninfo(streamConn);
+	conn_opts = PQconninfo(conn->streamConn);
 
 	if (conn_opts == NULL)
 		ereport(ERROR,
@@ -164,25 +184,24 @@ libpqrcv_get_conninfo(void)
  * Check that primary's system identifier matches ours, and fetch the current
  * timeline ID of the primary.
  */
-static void
-libpqrcv_identify_system(TimeLineID *primary_tli)
+static char *
+libpqrcv_identify_system(WalReceiverConn *conn, TimeLineID *primary_tli)
 {
 	PGresult   *res;
 	char	   *primary_sysid;
-	char		standby_sysid[32];
 
 	/*
 	 * Get the system identifier and timeline ID as a DataRow message from the
 	 * primary server.
 	 */
-	res = libpqrcv_PQexec("IDENTIFY_SYSTEM");
+	res = libpqrcv_PQexec(conn->streamConn, "IDENTIFY_SYSTEM");
 	if (PQresultStatus(res) != PGRES_TUPLES_OK)
 	{
 		PQclear(res);
 		ereport(ERROR,
 				(errmsg("could not receive database system identifier and timeline ID from "
 						"the primary server: %s",
-						PQerrorMessage(streamConn))));
+						PQerrorMessage(conn->streamConn))));
 	}
 	if (PQnfields(res) < 3 || PQntuples(res) != 1)
 	{
@@ -195,24 +214,11 @@ libpqrcv_identify_system(TimeLineID *primary_tli)
 				 errdetail("Could not identify system: got %d rows and %d fields, expected %d rows and %d or more fields.",
 						   ntuples, nfields, 3, 1)));
 	}
-	primary_sysid = PQgetvalue(res, 0, 0);
+	primary_sysid = pstrdup(PQgetvalue(res, 0, 0));
 	*primary_tli = pg_atoi(PQgetvalue(res, 0, 1), 4, 0);
-
-	/*
-	 * Confirm that the system identifier of the primary is the same as ours.
-	 */
-	snprintf(standby_sysid, sizeof(standby_sysid), UINT64_FORMAT,
-			 GetSystemIdentifier());
-	if (strcmp(primary_sysid, standby_sysid) != 0)
-	{
-		primary_sysid = pstrdup(primary_sysid);
-		PQclear(res);
-		ereport(ERROR,
-				(errmsg("database system identifier differs between the primary and standby"),
-				 errdetail("The primary's identifier is %s, the standby's identifier is %s.",
-						   primary_sysid, standby_sysid)));
-	}
 	PQclear(res);
+
+	return primary_sysid;
 }
 
 /*
@@ -226,21 +232,30 @@ libpqrcv_identify_system(TimeLineID *primary_tli)
  * throws an ERROR.
  */
 static bool
-libpqrcv_startstreaming(TimeLineID tli, XLogRecPtr startpoint, char *slotname)
+libpqrcv_startstreaming(WalReceiverConn *conn,
+						TimeLineID tli, XLogRecPtr startpoint,
+						const char *slotname)
 {
-	char		cmd[256];
+	StringInfoData cmd;
 	PGresult   *res;
 
+	Assert(!conn->logical);
+
+	initStringInfo(&cmd);
+
 	/* Start streaming from the point requested by startup process */
 	if (slotname != NULL)
-		snprintf(cmd, sizeof(cmd),
-				 "START_REPLICATION SLOT \"%s\" %X/%X TIMELINE %u", slotname,
-				 (uint32) (startpoint >> 32), (uint32) startpoint, tli);
+		appendStringInfo(&cmd,
+						 "START_REPLICATION SLOT \"%s\" %X/%X TIMELINE %u",
+						 slotname,
+						 (uint32) (startpoint >> 32), (uint32) startpoint,
+						 tli);
 	else
-		snprintf(cmd, sizeof(cmd),
-				 "START_REPLICATION %X/%X TIMELINE %u",
-				 (uint32) (startpoint >> 32), (uint32) startpoint, tli);
-	res = libpqrcv_PQexec(cmd);
+		appendStringInfo(&cmd, "START_REPLICATION %X/%X TIMELINE %u",
+						 (uint32) (startpoint >> 32), (uint32) startpoint,
+						 tli);
+	res = libpqrcv_PQexec(conn->streamConn, cmd.data);
+	pfree(cmd.data);
 
 	if (PQresultStatus(res) == PGRES_COMMAND_OK)
 	{
@@ -252,7 +267,7 @@ libpqrcv_startstreaming(TimeLineID tli, XLogRecPtr startpoint, char *slotname)
 		PQclear(res);
 		ereport(ERROR,
 				(errmsg("could not start WAL streaming: %s",
-						PQerrorMessage(streamConn))));
+						PQerrorMessage(conn->streamConn))));
 	}
 	PQclear(res);
 	return true;
@@ -263,14 +278,17 @@ libpqrcv_startstreaming(TimeLineID tli, XLogRecPtr startpoint, char *slotname)
  * reported by the server, or 0 if it did not report it.
  */
 static void
-libpqrcv_endstreaming(TimeLineID *next_tli)
+libpqrcv_endstreaming(WalReceiverConn *conn, TimeLineID *next_tli)
 {
 	PGresult   *res;
 
-	if (PQputCopyEnd(streamConn, NULL) <= 0 || PQflush(streamConn))
+	if (PQputCopyEnd(conn->streamConn, NULL) <= 0 ||
+		PQflush(conn->streamConn))
 		ereport(ERROR,
 			(errmsg("could not send end-of-streaming message to primary: %s",
-					PQerrorMessage(streamConn))));
+					PQerrorMessage(conn->streamConn))));
+
+	*next_tli = 0;
 
 	/*
 	 * After COPY is finished, we should receive a result set indicating the
@@ -282,7 +300,7 @@ libpqrcv_endstreaming(TimeLineID *next_tli)
 	 * called after receiving CopyDone from the backend - the walreceiver
 	 * never terminates replication on its own initiative.
 	 */
-	res = PQgetResult(streamConn);
+	res = PQgetResult(conn->streamConn);
 	if (PQresultStatus(res) == PGRES_TUPLES_OK)
 	{
 		/*
@@ -296,47 +314,58 @@ libpqrcv_endstreaming(TimeLineID *next_tli)
 		PQclear(res);
 
 		/* the result set should be followed by CommandComplete */
-		res = PQgetResult(streamConn);
+		res = PQgetResult(conn->streamConn);
+	}
+	else if (PQresultStatus(res) == PGRES_COPY_OUT)
+	{
+		PQclear(res);
+
+		/* End the copy */
+		PQendcopy(conn->streamConn);
+
+		/* CommandComplete should follow */
+		res = PQgetResult(conn->streamConn);
 	}
-	else
-		*next_tli = 0;
 
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
 		ereport(ERROR,
 				(errmsg("error reading result of streaming command: %s",
-						PQerrorMessage(streamConn))));
+						PQerrorMessage(conn->streamConn))));
 	PQclear(res);
 
 	/* Verify that there are no more results */
-	res = PQgetResult(streamConn);
+	res = PQgetResult(conn->streamConn);
 	if (res != NULL)
 		ereport(ERROR,
 				(errmsg("unexpected result after CommandComplete: %s",
-						PQerrorMessage(streamConn))));
+						PQerrorMessage(conn->streamConn))));
 }
 
 /*
  * Fetch the timeline history file for 'tli' from primary.
  */
 static void
-libpqrcv_readtimelinehistoryfile(TimeLineID tli,
-								 char **filename, char **content, int *len)
+libpqrcv_readtimelinehistoryfile(WalReceiverConn *conn,
+								 TimeLineID tli, char **filename,
+								 char **content, int *len)
 {
 	PGresult   *res;
 	char		cmd[64];
 
+	Assert(!conn->logical);
+
 	/*
 	 * Request the primary to send over the history file for given timeline.
 	 */
 	snprintf(cmd, sizeof(cmd), "TIMELINE_HISTORY %u", tli);
-	res = libpqrcv_PQexec(cmd);
+	res = libpqrcv_PQexec(conn->streamConn, cmd);
 	if (PQresultStatus(res) != PGRES_TUPLES_OK)
 	{
 		PQclear(res);
 		ereport(ERROR,
 				(errmsg("could not receive timeline history file from "
 						"the primary server: %s",
-						PQerrorMessage(streamConn))));
+						PQerrorMessage(conn->streamConn))));
 	}
 	if (PQnfields(res) != 2 || PQntuples(res) != 1)
 	{
@@ -374,7 +403,7 @@ libpqrcv_readtimelinehistoryfile(TimeLineID tli,
  * Queries are always executed on the connection in streamConn.
  */
 static PGresult *
-libpqrcv_PQexec(const char *query)
+libpqrcv_PQexec(PGconn *streamConn, const char *query)
 {
 	PGresult   *result = NULL;
 	PGresult   *lastResult = NULL;
@@ -455,10 +484,12 @@ libpqrcv_PQexec(const char *query)
  * Disconnect connection to primary, if any.
  */
 static void
-libpqrcv_disconnect(void)
+libpqrcv_disconnect(WalReceiverConn *conn)
 {
-	PQfinish(streamConn);
-	streamConn = NULL;
+	PQfinish(conn->streamConn);
+	if (conn->recvBuf != NULL)
+		PQfreemem(conn->recvBuf);
+	pfree(conn);
 }
 
 /*
@@ -478,30 +509,31 @@ libpqrcv_disconnect(void)
  * ereports on error.
  */
 static int
-libpqrcv_receive(char **buffer, pgsocket *wait_fd)
+libpqrcv_receive(WalReceiverConn *conn, char **buffer,
+				 pgsocket *wait_fd)
 {
 	int			rawlen;
 
-	if (recvBuf != NULL)
-		PQfreemem(recvBuf);
-	recvBuf = NULL;
+	if (conn->recvBuf != NULL)
+		PQfreemem(conn->recvBuf);
+	conn->recvBuf = NULL;
 
 	/* Try to receive a CopyData message */
-	rawlen = PQgetCopyData(streamConn, &recvBuf, 1);
+	rawlen = PQgetCopyData(conn->streamConn, &conn->recvBuf, 1);
 	if (rawlen == 0)
 	{
 		/* Try consuming some data. */
-		if (PQconsumeInput(streamConn) == 0)
+		if (PQconsumeInput(conn->streamConn) == 0)
 			ereport(ERROR,
 					(errmsg("could not receive data from WAL stream: %s",
-							PQerrorMessage(streamConn))));
+							PQerrorMessage(conn->streamConn))));
 
 		/* Now that we've consumed some input, try again */
-		rawlen = PQgetCopyData(streamConn, &recvBuf, 1);
+		rawlen = PQgetCopyData(conn->streamConn, &conn->recvBuf, 1);
 		if (rawlen == 0)
 		{
 			/* Tell caller to try again when our socket is ready. */
-			*wait_fd = PQsocket(streamConn);
+			*wait_fd = PQsocket(conn->streamConn);
 			return 0;
 		}
 	}
@@ -509,7 +541,7 @@ libpqrcv_receive(char **buffer, pgsocket *wait_fd)
 	{
 		PGresult   *res;
 
-		res = PQgetResult(streamConn);
+		res = PQgetResult(conn->streamConn);
 		if (PQresultStatus(res) == PGRES_COMMAND_OK ||
 			PQresultStatus(res) == PGRES_COPY_IN)
 		{
@@ -521,16 +553,16 @@ libpqrcv_receive(char **buffer, pgsocket *wait_fd)
 			PQclear(res);
 			ereport(ERROR,
 					(errmsg("could not receive data from WAL stream: %s",
-							PQerrorMessage(streamConn))));
+							PQerrorMessage(conn->streamConn))));
 		}
 	}
 	if (rawlen < -1)
 		ereport(ERROR,
 				(errmsg("could not receive data from WAL stream: %s",
-						PQerrorMessage(streamConn))));
+						PQerrorMessage(conn->streamConn))));
 
 	/* Return received messages to caller */
-	*buffer = recvBuf;
+	*buffer = conn->recvBuf;
 	return rawlen;
 }
 
@@ -540,11 +572,11 @@ libpqrcv_receive(char **buffer, pgsocket *wait_fd)
  * ereports on error.
  */
 static void
-libpqrcv_send(const char *buffer, int nbytes)
+libpqrcv_send(WalReceiverConn *conn, const char *buffer, int nbytes)
 {
-	if (PQputCopyData(streamConn, buffer, nbytes) <= 0 ||
-		PQflush(streamConn))
+	if (PQputCopyData(conn->streamConn, buffer, nbytes) <= 0 ||
+		PQflush(conn->streamConn))
 		ereport(ERROR,
 				(errmsg("could not send data to WAL stream: %s",
-						PQerrorMessage(streamConn))));
+						PQerrorMessage(conn->streamConn))));
 }
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 8bfb041..cc3cf7d 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -74,16 +74,9 @@ int			wal_receiver_status_interval;
 int			wal_receiver_timeout;
 bool		hot_standby_feedback;
 
-/* libpqreceiver hooks to these when loaded */
-walrcv_connect_type walrcv_connect = NULL;
-walrcv_get_conninfo_type walrcv_get_conninfo = NULL;
-walrcv_identify_system_type walrcv_identify_system = NULL;
-walrcv_startstreaming_type walrcv_startstreaming = NULL;
-walrcv_endstreaming_type walrcv_endstreaming = NULL;
-walrcv_readtimelinehistoryfile_type walrcv_readtimelinehistoryfile = NULL;
-walrcv_receive_type walrcv_receive = NULL;
-walrcv_send_type walrcv_send = NULL;
-walrcv_disconnect_type walrcv_disconnect = NULL;
+/* libpqwalreceiver connection */
+static WalReceiverConn *wrconn = NULL;
+WalReceiverFunctionsType *WalReceiverFunctions = NULL;
 
 #define NAPTIME_PER_CYCLE 100	/* max sleep time between cycles (100ms) */
 
@@ -286,14 +279,7 @@ WalReceiverMain(void)
 
 	/* Load the libpq-specific functions */
 	load_file("libpqwalreceiver", false);
-	if (walrcv_connect == NULL ||
-		walrcv_get_conninfo == NULL ||
-		walrcv_startstreaming == NULL ||
-		walrcv_endstreaming == NULL ||
-		walrcv_identify_system == NULL ||
-		walrcv_readtimelinehistoryfile == NULL ||
-		walrcv_receive == NULL || walrcv_send == NULL ||
-		walrcv_disconnect == NULL)
+	if (WalReceiverFunctions == NULL)
 		elog(ERROR, "libpqwalreceiver didn't initialize correctly");
 
 	/*
@@ -307,14 +293,14 @@ WalReceiverMain(void)
 
 	/* Establish the connection to the primary for XLOG streaming */
 	EnableWalRcvImmediateExit();
-	walrcv_connect(conninfo);
+	wrconn = walrcv_connect(conninfo, false, "walreceiver");
 	DisableWalRcvImmediateExit();
 
 	/*
 	 * Save user-visible connection string.  This clobbers the original
 	 * conninfo, for security.
 	 */
-	tmp_conninfo = walrcv_get_conninfo();
+	tmp_conninfo = walrcv_get_conninfo(wrconn);
 	SpinLockAcquire(&walrcv->mutex);
 	memset(walrcv->conninfo, 0, MAXCONNINFO);
 	if (tmp_conninfo)
@@ -328,12 +314,25 @@ WalReceiverMain(void)
 	first_stream = true;
 	for (;;)
 	{
+		char	   *primary_sysid;
+		char		standby_sysid[32];
+
 		/*
 		 * Check that we're connected to a valid server using the
 		 * IDENTIFY_SYSTEM replication command,
 		 */
 		EnableWalRcvImmediateExit();
-		walrcv_identify_system(&primaryTLI);
+		primary_sysid = walrcv_identify_system(wrconn, &primaryTLI);
+
+		snprintf(standby_sysid, sizeof(standby_sysid), UINT64_FORMAT,
+				 GetSystemIdentifier());
+		if (strcmp(primary_sysid, standby_sysid) != 0)
+		{
+			ereport(ERROR,
+					(errmsg("database system identifier differs between the primary and standby"),
+					 errdetail("The primary's identifier is %s, the standby's identifier is %s.",
+							   primary_sysid, standby_sysid)));
+		}
 		DisableWalRcvImmediateExit();
 
 		/*
@@ -370,7 +369,7 @@ WalReceiverMain(void)
 		 * on the new timeline.
 		 */
 		ThisTimeLineID = startpointTLI;
-		if (walrcv_startstreaming(startpointTLI, startpoint,
+		if (walrcv_startstreaming(wrconn, startpointTLI, startpoint,
 								  slotname[0] != '\0' ? slotname : NULL))
 		{
 			if (first_stream)
@@ -422,7 +421,7 @@ WalReceiverMain(void)
 				}
 
 				/* See if we can read data immediately */
-				len = walrcv_receive(&buf, &wait_fd);
+				len = walrcv_receive(wrconn, &buf, &wait_fd);
 				if (len != 0)
 				{
 					/*
@@ -453,7 +452,7 @@ WalReceiverMain(void)
 							endofwal = true;
 							break;
 						}
-						len = walrcv_receive(&buf, &wait_fd);
+						len = walrcv_receive(wrconn, &buf, &wait_fd);
 					}
 
 					/* Let the master know that we received some data. */
@@ -570,7 +569,7 @@ WalReceiverMain(void)
 			 * our side, too.
 			 */
 			EnableWalRcvImmediateExit();
-			walrcv_endstreaming(&primaryTLI);
+			walrcv_endstreaming(wrconn, &primaryTLI);
 			DisableWalRcvImmediateExit();
 
 			/*
@@ -726,7 +725,7 @@ WalRcvFetchTimeLineHistoryFiles(TimeLineID first, TimeLineID last)
 							tli)));
 
 			EnableWalRcvImmediateExit();
-			walrcv_readtimelinehistoryfile(tli, &fname, &content, &len);
+			walrcv_readtimelinehistoryfile(wrconn, tli, &fname, &content, &len);
 			DisableWalRcvImmediateExit();
 
 			/*
@@ -778,8 +777,8 @@ WalRcvDie(int code, Datum arg)
 	SpinLockRelease(&walrcv->mutex);
 
 	/* Terminate the connection gracefully. */
-	if (walrcv_disconnect != NULL)
-		walrcv_disconnect();
+	if (wrconn != NULL)
+		walrcv_disconnect(wrconn);
 
 	/* Wake up the startup process to notice promptly that we're gone */
 	WakeupRecovery();
@@ -1150,7 +1149,7 @@ XLogWalRcvSendReply(bool force, bool requestReply)
 		 (uint32) (applyPtr >> 32), (uint32) applyPtr,
 		 requestReply ? " (reply requested)" : "");
 
-	walrcv_send(reply_message.data, reply_message.len);
+	walrcv_send(wrconn, reply_message.data, reply_message.len);
 }
 
 /*
@@ -1228,7 +1227,7 @@ XLogWalRcvSendHSFeedback(bool immed)
 	pq_sendint64(&reply_message, GetCurrentIntegerTimestamp());
 	pq_sendint(&reply_message, xmin, 4);
 	pq_sendint(&reply_message, nextEpoch, 4);
-	walrcv_send(reply_message.data, reply_message.len);
+	walrcv_send(wrconn, reply_message.data, reply_message.len);
 	if (TransactionIdIsValid(xmin))
 		master_has_standby_xmin = true;
 	else
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index afbb8d8..edb14b5 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -134,33 +134,64 @@ typedef struct
 
 extern WalRcvData *WalRcv;
 
-/* libpqwalreceiver hooks */
-typedef void (*walrcv_connect_type) (char *conninfo);
-extern PGDLLIMPORT walrcv_connect_type walrcv_connect;
-
-typedef char *(*walrcv_get_conninfo_type) (void);
-extern PGDLLIMPORT walrcv_get_conninfo_type walrcv_get_conninfo;
-
-typedef void (*walrcv_identify_system_type) (TimeLineID *primary_tli);
-extern PGDLLIMPORT walrcv_identify_system_type walrcv_identify_system;
-
-typedef void (*walrcv_readtimelinehistoryfile_type) (TimeLineID tli, char **filename, char **content, int *size);
-extern PGDLLIMPORT walrcv_readtimelinehistoryfile_type walrcv_readtimelinehistoryfile;
-
-typedef bool (*walrcv_startstreaming_type) (TimeLineID tli, XLogRecPtr startpoint, char *slotname);
-extern PGDLLIMPORT walrcv_startstreaming_type walrcv_startstreaming;
+struct WalReceiverConn;
+typedef struct WalReceiverConn WalReceiverConn;
 
-typedef void (*walrcv_endstreaming_type) (TimeLineID *next_tli);
-extern PGDLLIMPORT walrcv_endstreaming_type walrcv_endstreaming;
-
-typedef int (*walrcv_receive_type) (char **buffer, pgsocket *wait_fd);
-extern PGDLLIMPORT walrcv_receive_type walrcv_receive;
-
-typedef void (*walrcv_send_type) (const char *buffer, int nbytes);
-extern PGDLLIMPORT walrcv_send_type walrcv_send;
-
-typedef void (*walrcv_disconnect_type) (void);
-extern PGDLLIMPORT walrcv_disconnect_type walrcv_disconnect;
+/* libpqwalreceiver hooks */
+typedef WalReceiverConn *(*walrcv_connect_fn) (const char *conninfo, bool logical,
+											   const char *appname);
+typedef char *(*walrcv_get_conninfo_fn) (WalReceiverConn *conn);
+typedef char *(*walrcv_identify_system_fn) (WalReceiverConn *conn,
+											TimeLineID *primary_tli);
+typedef void (*walrcv_readtimelinehistoryfile_fn) (WalReceiverConn *conn,
+												   TimeLineID tli,
+												   char **filename,
+												   char **content, int *size);
+typedef bool (*walrcv_startstreaming_fn) (WalReceiverConn *conn,
+										  TimeLineID tli,
+										  XLogRecPtr startpoint,
+										  const char *slotname);
+typedef void (*walrcv_endstreaming_fn) (WalReceiverConn *conn,
+										TimeLineID *next_tli);
+typedef int (*walrcv_receive_fn) (WalReceiverConn *conn, char **buffer,
+								  pgsocket *wait_fd);
+typedef void (*walrcv_send_fn) (WalReceiverConn *conn, const char *buffer,
+								int nbytes);
+typedef void (*walrcv_disconnect_fn) (WalReceiverConn *conn);
+
+typedef struct WalReceiverFunctionsType
+{
+	walrcv_connect_fn					connect;
+	walrcv_get_conninfo_fn				get_conninfo;
+	walrcv_identify_system_fn			identify_system;
+	walrcv_readtimelinehistoryfile_fn	readtimelinehistoryfile;
+	walrcv_startstreaming_fn			startstreaming;
+	walrcv_endstreaming_fn				endstreaming;
+	walrcv_receive_fn					receive;
+	walrcv_send_fn						send;
+	walrcv_disconnect_fn				disconnect;
+} WalReceiverFunctionsType;
+
+extern PGDLLIMPORT WalReceiverFunctionsType *WalReceiverFunctions;
+
+#define walrcv_connect(conninfo, logical, appname) \
+	WalReceiverFunctions->connect(conninfo, logical, appname)
+#define walrcv_get_conninfo(conn) \
+	WalReceiverFunctions->get_conninfo(conn)
+#define walrcv_identify_system(conn, primary_tli) \
+	WalReceiverFunctions->identify_system(conn, primary_tli)
+#define walrcv_readtimelinehistoryfile(conn, tli, filename, content, size) \
+	WalReceiverFunctions->readtimelinehistoryfile(conn, tli, filename, content, size)
+#define walrcv_startstreaming(conn, tli, startpoint, slotname) \
+	WalReceiverFunctions->startstreaming(conn, tli, startpoint, slotname)
+#define walrcv_endstreaming(conn, next_tli) \
+	WalReceiverFunctions->endstreaming(conn, next_tli)
+#define walrcv_receive(conn, buffer, wait_fd) \
+	WalReceiverFunctions->receive(conn, buffer, wait_fd)
+#define walrcv_send(conn, buffer, nbytes) \
+	WalReceiverFunctions->send(conn, buffer, nbytes)
+#define walrcv_disconnect(conn) \
+	WalReceiverFunctions->disconnect(conn)
 
 /* prototypes for functions in walreceiver.c */
 extern void WalReceiverMain(void) pg_attribute_noreturn();
-- 
2.10.2

#108Petr Jelinek
petr@2ndquadrant.com
In reply to: Peter Eisentraut (#107)
Re: Logical Replication WIP

On 30/11/16 22:37, Peter Eisentraut wrote:

I have taken the libpqwalreceiver refactoring patch and split it into
two: one for the latch change, one for the API change. I have done some
mild editing.

These two patches are now ready to commit in my mind.

Hi, looks good to me, do you plan to commit this soon or would you
rather me to resubmit the patches rebased on top of this (and including
this) first?

--
Petr Jelinek 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

#109Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Petr Jelinek (#108)
Re: Logical Replication WIP

On 11/30/16 8:06 PM, Petr Jelinek wrote:

On 30/11/16 22:37, Peter Eisentraut wrote:

I have taken the libpqwalreceiver refactoring patch and split it into
two: one for the latch change, one for the API change. I have done some
mild editing.

These two patches are now ready to commit in my mind.

Hi, looks good to me, do you plan to commit this soon or would you
rather me to resubmit the patches rebased on top of this (and including
this) first?

committed those two

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#110Thomas Munro
thomas.munro@enterprisedb.com
In reply to: Peter Eisentraut (#109)
1 attachment(s)
Re: Logical Replication WIP

On Fri, Dec 2, 2016 at 2:32 PM, Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:

On 11/30/16 8:06 PM, Petr Jelinek wrote:

On 30/11/16 22:37, Peter Eisentraut wrote:

I have taken the libpqwalreceiver refactoring patch and split it into
two: one for the latch change, one for the API change. I have done some
mild editing.

These two patches are now ready to commit in my mind.

Hi, looks good to me, do you plan to commit this soon or would you
rather me to resubmit the patches rebased on top of this (and including
this) first?

committed those two

Commit 597a87ccc9a6fa8af7f3cf280b1e24e41807d555 left some comments
behind that referred to the select() that it removed. Maybe rewrite
like in the attached?

I wonder if it would be worth creating and reusing a WaitEventSet here.

--
Thomas Munro
http://www.enterprisedb.com

Attachments:

comments.patchapplication/octet-stream; name=comments.patchDownload
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index c3b0bf5..a94a6b5 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -388,15 +388,12 @@ libpqrcv_readtimelinehistoryfile(WalReceiverConn *conn,
 
 /*
  * Send a query and wait for the results by using the asynchronous libpq
- * functions and the backend version of select().
+ * functions and socket readiness events.
  *
  * We must not use the regular blocking libpq functions like PQexec()
  * since they are uninterruptible by signals on some platforms, such as
  * Windows.
  *
- * We must also not use vanilla select() here since it cannot handle the
- * signal emulation layer on Windows.
- *
  * The function is modeled on PQexec() in libpq, but only implements
  * those parts that are in use in the walreceiver.
  *
#111Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Thomas Munro (#110)
Re: Logical Replication WIP

On 02/12/16 02:55, Thomas Munro wrote:

On Fri, Dec 2, 2016 at 2:32 PM, Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:

On 11/30/16 8:06 PM, Petr Jelinek wrote:

On 30/11/16 22:37, Peter Eisentraut wrote:

I have taken the libpqwalreceiver refactoring patch and split it into
two: one for the latch change, one for the API change. I have done some
mild editing.

These two patches are now ready to commit in my mind.

Hi, looks good to me, do you plan to commit this soon or would you
rather me to resubmit the patches rebased on top of this (and including
this) first?

committed those two

Commit 597a87ccc9a6fa8af7f3cf280b1e24e41807d555 left some comments
behind that referred to the select() that it removed. Maybe rewrite
like in the attached?

Agreed.

I wonder if it would be worth creating and reusing a WaitEventSet here.

I don't think it's worth the extra code given that this is rarely called
interface.

--
Petr Jelinek 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

#112Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Petr Jelinek (#111)
Re: Logical Replication WIP

Petr Jelinek wrote:

On 02/12/16 02:55, Thomas Munro wrote:

Commit 597a87ccc9a6fa8af7f3cf280b1e24e41807d555 left some comments
behind that referred to the select() that it removed. Maybe rewrite
like in the attached?

Agreed.

Thanks, pushed.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#113Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Petr Jelinek (#97)
Re: Logical Replication WIP

On 11/20/16 1:02 PM, Petr Jelinek wrote:

0001:
This is the reworked approach to temporary slots that I sent earlier.

Andres, you had expressed an interest in this. Will you be able to
review it soon?

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#114Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Peter Eisentraut (#109)
6 attachment(s)
Re: Logical Replication WIP

Hi,

this is rebased version after one of the patches was committed and there
were some renaming.

I also did some small fixes around pg_dump and changes syntax slightly
to what PeterE suggested in the beginning of the thread since I like it
more as it looks more like English (PUBLISH_INSERT => PUBLISH INSERT,
SLOT_NAME => SLOT NAME, etc).

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

Attachments:

0001-Add-support-for-TEMPORARY-replication-slots-v10.patch.gzapplication/gzip; name=0001-Add-support-for-TEMPORARY-replication-slots-v10.patch.gzDownload
0002-Add-PUBLICATION-catalogs-and-DDL-v10.patch.gzapplication/gzip; name=0002-Add-PUBLICATION-catalogs-and-DDL-v10.patch.gzDownload
0003-Add-SUBSCRIPTION-catalog-and-DDL-v10.patch.gzapplication/gzip; name=0003-Add-SUBSCRIPTION-catalog-and-DDL-v10.patch.gzDownload
�kAX0003-Add-SUBSCRIPTION-catalog-and-DDL-v10.patch�=�S�F�?�_1��]?�v�ZlB�;��W�-�,�mee���.����{F�H��e7�������~MOw�c������Z�����������w�����������k�u;��{�l���g��K�������@����(d?q���v��u8�x���}o�Y����]��c?-=�o�Xk�e����e;M��9X��v����9��������q������7���+f[��Sf�;;�����j��	������k�&���X��G�jv����C>iX�7q=^��#�����Gb9v�."7��X���um��C��B�}I���J��n����Y��C���/�o^
>T
��1����6�TZ"t�[��;��o��V,�������u���/�X�-���K;v���FxE�6�c9N��(�lw���M������y�Vo��6fB D���;���@�-�5$�] =��a���B�^Qf1��>�x{��c~��h��t��
�����������G����2�����u�0Y��As	-�&`�������]^X��acZ����W	�"��@�^ox�x��������`�?�K�����z�!_x.���Q
-}D���$�XF��F����8G8��e�8P������`����x\?��d@�V��c���H�rQ�U��M`GVh�L�&�V	,�D����,k��tKE�Sq�G"�r�k\b��>� 3�`�H����s3�1���0.�3��XV`�
q��=�^��R�V6��5`{�����d�GX�����Ms���L�yXw�i	�j<�a�3����K%�@k�H��e�6S�j��&�Y6�"1����{��HU��%�]�-���UN�����������e�(j��K�	��j�.t,a�S��-hI�&��A���t7�P��_@`����|�lF���P����X�4s��^j�h���	��Y�#5Q��8��a>(���Csc��(�a�������;���>�
�����]�i1��g���M&���)��l�&@��e?�A�3#�Z!�j����v�+�\���7����N&�V���Y����&�tfY������<8�4%��W����Q���kVk��va��V��~�)
�����,�J�v�����C��?0�
<z�P���.���"
�v�[s~�S�aC{w�@\�G�����zLs�LG%rp�����dgM`.�!��l^ ?}UI|��H�{7��>�_�9�����!C����^��A=���]�9��6���m��v�!}fn�6P��y�����0�*��9����+�b����[�S���!m$X�n��K�g��������,������� @b+-�.)R�������D�d�w)v���0����
��,;� *c�,�0+���`.<������S_l��t<�q�2?�`�q�������H�a��������L�`�<q�������g�m�\�'1|X����cyG78��FW"?��������\%���=�8p�X6�A	1�p"[���f%�������lk�:��YQ��2���L��9
��& >�ip{GZ�"��3�"l^�?($u��3���#�a���������d�k�'��	���������	{�{�BW�$�Y���H21��3��8?=�R=���UL��fI�0�����N�&�K�9�
'��>UH�G?����$���C�����$����1����=1ZO�}2�0���?	�t3���>���I��
�&�( ,��[���<
/�9��� ���43�	��(j4��N<���N������ t���y��X�c��������GC�e�c�q�
bL�1`(?5�����DC�C�g",�C���0�Y���3*���%=:g%���j`�o�J�R����U���*�������_�7���d�U�vQR�b#�k������>d4)��)�
�y)�qFt�`�2e�%������Mv�������_�
'{����:�k0<�^��
�#*P�9@��@ie�0Y����t'�;�<h�c�Y&"f��]	���k$����=�������hH[A���gU���d4���J4� u��!s+�jfR���d'�+;Y�l�(��|��4�Q8�D%l�qlA��6U,Z����K�����#��%'������[B�N!i�6�ev�R��$;<��%�N���i��*Z���an�2~���v�x������{�	����������e����p�]�_z3��#4��n�u��~�z��6w���s����.����C���Z��C�H0����N.�����,��*l���5���*��l��T�
6$f�#Y�J(���	W��-�.������H�����118F�����O�������taM��)U�F�s�.�JqO	�Im
.��"�X���*nwqe$���l�������+j��,�+����+|.��
b���8zqsr{����8�9������b�#�Z$k��vq�	{���{2��,�����
�O�h[>d�/��$&�.���
�e%#d_=�N����O	��8����p������//NO��	J���������/��	���@b��?�9i�Y��
U�i�?S��������Z�3�����TT7TRd�=.7
xoE���Xp�WL����������N�Yo��J+��eL�@cp'�8s<�Y����PD��������4ik#6i��n�p R^�D��frm�WI�!���"]��(@�d��8OM�B_�=6r��`W�+XOK?:��f������q~hV�W����
����$�������R��'Ye�\��?$����cpz{~2<��u�&al�����	b�eJ�h�8��kZY���YLF���H��
�BV_B~�K�r\!��@�#+��;l���bLzy�>?�*�!��|�
?��5?��3��d?\� �X���*��c�f��Z.��w����M{?M�1�t�U��yW����/w��p-4-����'pO�'�z�a��p�
�+�f�����:�*m�0>�.eN����>0�	�i��'����[�qN\�Ls�����9�/b�{�|=�����E�����^�>��w��|���5�HUd�U���>T����#`i:)�0��A�i��*;/=c��K7�����Y�c;�����=�	z�S����xJ�n����������Fd_U�^F�1K�'Q��sf�Ve_z��8���f��?JSK�'{�wH�q����������7�P1�������q��*���^��a�qp�[��������H[_���I��&��g;b�0��H����t�Yv����]Q�*��(m����@��`�N���"y��)���h���J<�!,��H�3Y�E3E�\W�w�.H| �Z,��B�����J>O���T�{�z�0/����[Iq��bUA�*XF���|���L'].������jJ+\�S�d�9!�-J�Y��6����33��J!�����-�k�z#�!����g,��9������(�[,Hj��p��qpn�v(�|?�08��������rr�c����:�5��,pj�����Ls�5�h3���X	e��-��U��Kqj�H*~��jE�Zz�����nPQf��$=�S���:sa�%�4����TR�����l�r�XD{�d��XK�Yw.��(
z_02��qL)!�=c__1IK�\O0�����y�58	@���!
>1�����������.%wa�����1�JhC?!�!��`d�d�b�����3�u�Z�z��_o��M������k����I0��2�Gg�`�`9�?�b��+�F,��O��=��VhA/6f"�)aZ����h�s3������5���u-d����Uy�:5���
��//V��c(���5]����N����+-��������b	����n�o�+*�dS�)"�g�(P}U�D3}�jb��a[�����U�%��x����b0����?CQ��*�%E�������@��z�Aq��4jJd`R!-���)��dc�jR�s0������8��,�����l�6<�[L2�.%3�`�E��t�~��b�	�X��x�b�u�������6��}/J��%�{�4d�O�OO�LI�5�����i�����<���V/�=3E�U�[���v��'-�����-��\�h���*���������[����w�>���Pkf����nC����4hT�37���8�+q<r���VS7�d�c����g�����
c����5-d>E����{�2_]�6FW���"q������
K�\�iR��@�R��?�M��������_eKe��iDGkz�.�����&��I�����F��Kl�������/��x����������
�1���/^���T���G���{��v�4��Y��K�e��y(3��\m1����,���+#����=�t���H]E����)��Q�a'[��t1��N�%s�hWii}6��X��0���{��,��5r��4PKGj�P5�����������CX�#�����V������u;��^���;{��@�7;���H�D����o�����'�l[�V_���)B�����:`Vx@'�^I���j�k���5_ee���W*��'3muz2G��t"�t����hD��_i��y��~Z;����<���4�'��U�+ogW��Mp��k�td��v��5�J�+�H!��@���8]����{C����WZl���!���xE =&J��B:`�$��)�$�����O�q���R���r.?�>$Cn$�7�
�\^�,�d�p"Q�f���SZB�B��L��
E�x��"�&^�4���>��AG�%d�����do���"��C8��^���<G�-d<*��n��I�<�C�;=���:x���'���\�?��F��hp{����Spc���9�Rjh;n��0���J�>&A����N��H��A~�f��<v���yG��L27
�wB~�����\�3��Ba�'U<KUL���3�x�"f9�
���-�W�"I��fL��U��6�b��H�g7���r��^�m�����5y���}$��G������NWy�Sv��+2���(m1����P��.��1�����vt��
�����8zl��w�$����k������D��n�}W�j�|�t�z�s�;���ct����FP��oecb���?����m�kl��}D�;9���&"v���<�p	y�F��az��6���y��F�;6��(�5M����]!�����}Y���aF���T����F��%��x!���z�3�xa70�x�mkK������<:���|K���K���?�.�[��lr�1�d�x�u~{{}��O66�x��������������7W�g#��O���!��s1�z�+"G3T	��e�FM�/vYB���S�8��z�h���6��|8��|:���%�yI�������T3�N�uf	�7G���"%|H�d���O�d`��dy�3N{�RcC0����^_�3D����-�=E��o#6����^���Lf����P�W�q�a���e�3c�'��7�����q%�c�8�,�9���V��?���)�r�����q�v��^�Lb�R�������S�gv��L�y����3w��A��},��}`^���	VK���Q��\�/DoeG��������m����b�4:_��l|�8�!Y�m-��6
R�)F����yDk�^h���yAhn����'W� �����"`Hm���4��F����e;;YF�X�x��K�HU4���;�����j�X�U}���q�H��T��z��(�zZKhZGiI��:S3PJyekM0k��FZ�]
�F�AF��h,���t�;�!B��i����5�������D9X���:���������W;��r�iFqU��w���D��qf>�/k�{HG��I�\����g|��m��ey�@�s�&lF98}2uQQ��w�!����R���B�&y}z���k{Z7�0�����i�mL���i�%6I�j��i�N_�
ll&�M9$�t���u����6�������6�K�������v�{�������*��S�*}�����y��������s�~�4�<Rs�R\�N�Um�_7[�T��K�	$�W�y���&\�&?Ct���R��Sj�J[U�U�X_`	���1�C���	��_����������>d ��� $Cn��i������w�?
��^�|T<]O�p�)C�$�Qi3V�q`t��><>�+i�@����}q��IH,p�'�6U],f�%��1u�Hx��"��7%�Wf��d�s�rn���LC�}J�N���SW�=SO�WT����z8�=(������H��R�R�e������{L���m[�Y�po��g�R`�y��gPA��sC����y�T&}����C1�h��2������xve��+s<��� Q��7@���$f~��9�(�f�d��Z��ySk�HeW��j0��Z�m�<�ma�)��i���nI���L���`:���h�Z����H:2��F�w^r��,��{���X<,��gU{D�,�x7�������$��9g��W?���*��v���1���T�7��=�-lW��2����m��r2���d��p�Q�B};�E�M;:M�������xI�?�)�����x��S��Y<��*_:%�D�D^%������U���I83����aE�b��(�tJ':���=NW�z��L�3�_3��I��&
�&���$���
�1����T��E�S>g�$��si�l.��j��K�\�P��7��&���������Hf���*���vZ��bj����PR��Uc/$�I����\����P3D��]*I��E�].��KE���T�������l������L696�B��@����,��|�{����;�Re��Y��\��M���=tj��Z���l���r�tG�Z��Y��M����0�=�-��*��	j��|����j�6�<��w��G�� �{4m{���������������oNV�
�f��*�{aoa����]�����$��j����R/���um�����/G+.�$�������+�E�)d��m���B��U���6v�/��"0������J�Hjs�ag��a�NI`�����D���j������
�[^X;��5�C-%����������?�Q������w�t���l��N*����k�Vl���2Y|=���?|H���t���r�EES�,�����v�r�U:�md��G�`���p LQqX����~��y��S�[�3�Qq��\,W0Z$(��30w0%d���S>��1��M�!QI�n��mF�������s�OA���Fj������zF�|W��)��j�I�������r����v&��E��1���v�Y���	J������-��rNo9/F��u������d�nfps������3���zrg�7�d�[��=�����4��"����9�SCl}\���EI�u���)�vo�4+;�=���%.���;jp����b�p������������D�
���i��l��9�a�}����y�J���a:���3�X,Mws>���mft�1K�
�j�	��RW�
����i0G����h����N�<�q����0iY��/�:����A��E�������\��j�u`��v��rA��e�_(���� %��%5�m�%����W�h����J�1���#����E�:>�����J��3��x��L�����GF.�dX��O&���v6>����,R��jH��;���Xy���g���B�0��hv�h#�Io�>�1���~����=��P���B��t%-f�����k2]�8���y������(s+�$����}u3��q@���D�D<}`����:LP=�nDvp}$�����,�EA�0ZN2h���C������Z�|k"~��A��o�a�;�?�D�p$d��'���6 ��K�����s��(OF|��aN1���	��c	u�������q�'��}���������?��`��a��3��_�~S����H�d��G6S\������|9��na\�x��D=���d�A�������F���'D���j�i��~�l�.�� d��%
�p�4�'[�N���$���^��F��O�-d+��)7m1*��z2�B����I��H�Uqy���'���c����W^!.��C��:�m���6��_{]�q������g�����^�/+�aU���Ri��1>(���*J/��J>H�$>�~����M���sUm��t��S����^\(����V�RNR����:�)��p�����4U5D�\��t�V��fq�Q��U�g9�F��%���0h�$Z����E�:�+�I��;-Q�k��z��gZ%�e�O���pP.��a�+WVLv
�g�Se��5=����(���
�v'����7@h�o�>��I0��'���'h����Dd-���P�p.�NCD!�����ik�H�����gM~"B����^��\��M[-HF4%�A�X"r��%g
�1���-�*l����G�����_%�]�be)ai��3~����]MJ,_�UJ�>�%R��q��{���=`������7��=3:�D���qW���tW���~�"�^��$��o����yZ�����y��i���P��������v2Vl'S��4������zH�\8>���'7���y8��!�v!G^�P���>����*����Q��9xIu���X�����n"�g1 �O�Y�'�z�3^�Bj����hb��	z+�vB��H:(���14saw��n�������p<F>�jq�4�`v�G8��so�_���]*����E���������
�i#z'�-Q�	nL�v�7��N����i8�|��R��)x;�/	�5�!2�,�r�����T'�
�
�����h����9������y��!/��N�
���JbS����>�Q�K2#�P�^���a�vq����(flOO����xP��E��b''���+���\�(0&S\�]r���2:�l����.��V��FJ\�V����8�%S[?�Zq���n����5�Ij_U	01�:C+���y��<��D��`:<�6e?���3���T�h]_�u&�d��zM	G7�����h�����/��3�2��2���h��Y����Ov�lXc������dRR�+#�-,��]-�`m��y�I�^�)f s�Kn�dX�U�Q�?�h�z4<:>��
=-�������G�lNF�%�]�O~G����=�Ox4���c��6�9�x�8�2
YWrZd������#����`LX8��y
���w7������w��kQ�$/V*�����Ef"�A�5O�<�\bie{�q.Q*�����:��U=�ey6�M(�������M_M��zY��e�\��&$f�Ap��]�2��)�(	�(�,��������V~�E��z'S�kw�y�aI��<�uI�o�q�ui@m-L�&f��&5����V]n
�L�����y��~�Zow�fK���*�x/b�*������;\�q>8���Cp���K���89"����x�w��j
����K�����1�7VD*P�p�w-s�5��q��l�e�7�Qy������Cbi|�p0�*ra�I�_{7��ZW_tt&�U`+
��AL��z���U�6��fH�WH@��Q������V�5�T����f<��-$X����Y�sH�Y���=�hy�&&��/�R��J�H��b6�H��9)������P�����$��O_��L� I��i�37�?����{����c�j���
%HjnaF�+mQxu�i4�����P�$C	�UZ���I���k�9��$��KI/��S�'
� ��^���d��a���w���j~���7�����#L��$���hB�pY�Z�������M�8@����B�1����z:8{���Cg�������8���������c���m�x���y��u�������N���h�����T�Q�lP���3��U����9�o-���(�����:b�J!a8�]�"�K��uj�����'��4��3`���s������
���
���?6X��M2y��i�y!���=0���",rb,]t!!ZL��d���X�'e+*;�9��Q�b��~h����$/2���co���J�����%�����%����.:Z1�R��^�c���Nj�������X��]�E�hVA��kB|QGI]�6z�w���K'�w|���TO[�����ktw?�CJ'�2����\� �m��<����&�o�+�%

�����1��}ur������)�_��3:���b4	��[�����z|pH���_u�
��x�["
��0���3����mlBs��4�����Y
��Z����75����{P�f6� Y��mu�m+N�����r�U��|N�V���	�K`|Q�}�y�O�B���������r}���:",��>7b��ImzM�7B�����������` Q��1IKt��R������x�}UF�e��*���X�&�+T��c��(n1�W���oF��z}���8:gvg��[���=�Y����Q.��NS��,����<!-�V�Y�Q0�����Q�Y�a4@�q9�������>��P���i�m������1���ne�3ktL����esx=�X
h������-1�����-8N;�����y��1a������Sx�I!"����r������,�v2��WE<|S�*�>�V���f`8��#����0�g0	E:�G��6��k#�]�/�st�����58�PZ����c$�8G=~
Hc$��x5{u���]7�2nZIm�'�0�N\�]Z�9}�`���=����^��`�6�S����i���
���Rc!�����?�Z�S|���7@��s���������$?5]�>���M�����8��,s�D=�71������]�.�q�:����=~�a�8G��yw�?0�7�v!|$�2���J�i��0��������E��6���N���;9�9j.������O�V.���,�Sr|�����}��K�n�A��|;@��n(��m�	q�m���d�<�ME��\�D�*�9�A��3�}c����yu2���rj,�1�[�]��R��b�c0A��`~��-���t��M����KH94w���?[�>���Ym4���(}A��%��T���Z) t�Q�v�#
EI���JF3��8C2 3Qfe��Ewa/B8p�=�3��=��,����Z}�Y|���"���j1���D���[���(v����p,H~,?��������m��9[�.~�nn4;��Zj3��������\�w{������k���u0�$;Zw���;�s�Z-����J%����0��i�-�|
Fs}0���@Y��-������z�\B��\��&��#hb3:}��������y��b����s,7�g�����cy�58��1��{C�fHB��8>�T��A6|p)	���HH���G�d�FEF������7>�yT��\R�1]L]�����7Z.�0��,��4&�7+�T�q�2����=4H
E��R���U�.����~�Y�6�5j������������kl�O,��~���COc���{S���4:�]V��~��4�H�����M=��Y�H7��pm)�Ed��l�1��x��x}/`S�t�]7�����57��#H4��s����6K�=~�����4a���E�L�[6�H�Mlv���|�g��+��K*���Shv��E�y��E_������_��)3����D��U0�!�r��/���10�%�:�b��03��!=�i�V8�Q0�����d��X�cr�lm�����x�����p��}}��+'�y�7f���/�`��w���-�V|?�Nax�<�l��P��[�$B�I�V,��k��,V�|�����v�[���l�����4����p��c��^���<)��N\���:Z0�+���sKF�V8����J�G��n��.0�d��q! 7D�<� O=j#="R]����}
}�c��zo���G�o��Y�����3�e������r,
���pdM?�pX@���Hk�fz� ��lNc�� )Nc�'����
%���,M�sPh��z��w�����a��������:w4�'�PBh�Xo2��"���' �$Q$�D�����.�X���Lx����Y��|�&��2���4���� ��y�B16�^�a�@���'��n�Z��Nl/��1���i!'��� ��jW���}���1��7;V�o�����i��l	c�����bk���,�������	I�����ean��R#2F���rL��������4��o4���]��G�H���j�5}����J#m�L������'���r��6L�G�{6LL�9��u���_9�xG���pPd�HCH9��p��"G���C
D��2$>��Q�-H|����.;�o�I�|�1j��r�v��=j��-�f����f�UD:��C��E�����Y���n���7�t_�k��	�Ko�#���^8�	x��1�s�5�����/C�/����s�w%<����s_6t��\F������Y����W����zQmIn|�����(Y�Q���~���%�,,���8vzo9� �_�
��fJstpt�/Q�hJ�N�-���D�u}����
f)�f0o��N�OL�<te�{>��m������@����w_�[���>Mg^�b��k�#�:��r�������BaP,�NJ+�z@d�{N!��S���Y��Y]*���/�K v!UJ��}��X�4�Kj��g���j���y
$�-.���J�Rpx������SFV�{0>�r(>xKl�V|���:`<E����+{As���R��q�*+�F��cc���E��B�/�2F��������9��{�����r���U�@��	�bK�����������!�M:D��<D�]|��f��t���~J������P�ONV8Vtk�O
7�v?[J�Ff�x�%���r�b��F��v8&���h�U�O�G�*�>���/��=#����>�&���r2�q>"�,��N���.�5�E[��g~k'q���[)y��V?Nq���'����g������x�5'�t'Q]�N�JP6�H����<d��"Z�y�8����-W$�����3�t��b��"�S(���R��y��h��Tz���^h��$���"��''��0�I�_�O~�' S���#D^��07�R����������?Q�wpmI�j��1��4@�k���T^������/f�om�����D�-�bL�����?k+J�a���N|��if7����X�����-�����{�G8dQ����fE�q���coV��Yo�4�	�~K5���JO2J�nLz4A��h��jN��[���)(q�6��q����S�����jW�z���#|���]������Y�q���/��z��rQ;�����|i#�Z�^���7�[T��>��q�?��mJD�6��|��o������o���u��|^!����e���:��^A9�����/�kn�Sm�W/����:�S��.kX�����4�9�7���5�7[�7X�U�X���
?�_1w�`~���a���]?�l�
�}�E��C��������� lo]f*s��/�'T>�4��u����J�?�/���/d����5RF��@��|S�G��y���Q��0��6�$*4~b�5bw�����'���*s����fZY-��m�,�I��U��O�8����i�@����e@���E��9^t�t������&�����x ~V�)�����N
�Y:F�'gd����#�zz����E ���Z�q�H����ZL������1���=9`t��=����3�$(�{�?���]=;�s6&�:����;o�o���\�1������Rf���7j�]�|��\���:����G���r��|&}@������M��\r�R��-���Y��53d�XY�y���N��_,��I%�4tk�����g�Dp+��_�;v���(~����/�M�R��v�������BA*f�F��������v*��<���2+��Y}��e.��7���|�Y���5|�~
�!�1������"��xf�����[[z��j=�����h��~N�c]K���v��G�_�=������O������P��Ex�r�y>�Z9�������E{3t�q�'���� �WU�j?���
G��O;��ZO�h2\��3�O��<r��r� fZVA�or���aY{�W
w<���w�GYC=�j�eq�Hi�:����<��
�����)���v<�IsHEt�I��Tse��=gW��~/i�I���=�cE8�����x4��Fs���Cr�dU���.�:��>���g$T��� %��k�_^�/��$T���8Y��N����&.��@����.��%���~�f}7&��|%A���C�J�HKNREb���H'���A�(=8*�����/�Y?���'���Pj��d!Q�C���?���JH�*<��<F���q��P8,A�c�C����
��N�=�e@O�F�
T:���Kdm�����A=A� ���k�V.�o�g/E�x�/����|����[��J,5QW�h�@{N6(�ip��X��"���:C��:�&�l�u?&K�4_����g.W��a���������6@�}M��Ju%�R�3Le��k���!��u��p��ml)�T����.��w�����Ry����M��Q^�o�����{R\���2�X=�����<Wq�86�����A1�_8>�e|��~�B��V7�/��-p4��i5��G@����_�2�0GLtk�$�`dOz�63�/�
Jbc�i."}K�S�NV���g�*����3D	~Mo����������3����h��9y\����%����{����vq{O;�
��el�vR�����8����<��]N��j:i�p��[^�t�r�r<UOPV�����h��"�����v*����R{���!}4�M�:��e�@)&Et��|�|������%a�1<L�C��G"@�x�NAf3�Md���s��p>�c��=Zq~=������z��X������X���oP���������Ke���C6�L:`�7^jvb|ihg��^r�S,
�C��3�v��ta��Be�y�Nf��/�&��)Lvw�/����-��+���x:�N�?cIh�������8Br�+���#��GZm��������C��\[+��j��Kr�_\jWs��ib��!+���K�Md��~��GU�c��S!��-�����I�B&O6&����
vn�����V����d���Xk���~���#6u�Q'X��O�'�qY���h���y;YP1}�r��t���/���aUu�K�������o/`�%�67@S��7\��s�@(����{%�qV�/K��1���q�+����"��q��\����R�?�����^����r����j��
G��	_����Q�_;f�e�F�q�
�H�
)�.���]	w���M���q���91S{[������8%Mw^�*�6�2J99�(�������3�2�6����� #LE�y�0��^� �X=�;��-��I����~0�����8"Cj��1'�t~q�
M������������A��@"�f�������E ��E���e�{�S	;*�L�Rd�b>��R~�m��Y��*m������}�e��������Q��>p����l�PC�P���������Z^��0�B^#XfY�mf\p�"�r��Ukw;�W�������^q�%Y^����q�"���r3l=�+	�}-\�9����h<�����~��e@sd����x����*�� L�[h�i3Z)p%�l���	�?1����Ik9�2��	�)��6������P�5�\j(bB������W��7�L�������?k^^V�+���0�������5&<��7|�D��~�?<
�������T/�javZl]<=��� q��..��D���������ch�x�D�1�k�w7��S��9*V�2X�T,��D3�0'�	x�����9B�*08E�P��qO�c����JFJ�AF��xa
E���i���Sg���XH��N��U��x�}��.����>�9XT�������Z�_V5q�F|2<��X�5��u��!3 3LF�#�W,���r������28n��Av&����Z.����H;�
^i��[�oX?�ff���L���'R)r��������.��%|����u�c�]<�����|���������#��4�$�#�O��d1�97�F�T��\����.*�W;��K��H��5��E+��h�_������n�v�Q>
+��P8
�0�&���i��( ��b��w<�w`lz�F���h(��HDh[P��>ib�I���gZ���ro��F�8/W3
*���>l�H�8�*���&J�UQn7���X�wUu�[����������/���E����g�=�>�H��Lb���$[�K��Zw�r�q%-�
lN��B��G?�mG�#��8'��z{T���+�,_�6�m��R��ex���G�$2��Q�3���1I�G�?{����t
�a�!/��B��>U�a40tF������c������'l�Ehe���b-�8QE�`S�nf��]K��;�n`l�V����Y���< �����v%�����m�6W����P@S$����>���%sHj3J��y������[@�
-{���\VL�����mLG���jh����r�Ip>�|��d�Y���"��Z:���8D���v�Fq~�E��@y8��QL�_���`�
�G�\�!�B��Q�G6+��.}�p7�1}I��B��������r��?�f
2��Y���$-���rJ-H�<������P��
�X.���v���W$T��2v��LHl��Z+�����Lb.���%����
���]�g���ut�M)K��
������Fu�����w�][�NM@��>�|P[���F�g:�=l<�V���h ��l��l���X2"�$Z��H?@�����x�f�z�i�Vdg�c�.��a
	���J.���*�6�OF����^�)��"�\�����w0����d�������k�]����>�*��CXr����X�3��C%��P��wC�i�'�M�w��������
X��q}��s��r�.�q�w[����!R���
��%�'7!Sz�_���`�6����z]$���	���?'�BZwK'�B��J������U��	R<>:]uN�>��0�.{�]qb -�9��b�����Z���>7N�tz��=�TI��JX����P��o�$����d�T��������$�S����!1@q����"��&:BhU���	o����m��A}C�����ck�k%�k�b��W���3���������=JaB:f�`�e�eE�Lo��W�`y7}��GX�f#{�d���a
a��<(����*[���M��
q3����M��C��\u��E��a�����Z��bF�r]�r��md�Q�h4��t;��}�M��d�d��Y@2��M[���r����9��n���9���m����}g`��������t�^�W�-|"�\N��)�r�{��a�?
��Q�|�{.����d�q�E6)�)�p�i�C�c�Y��`y�@!�h�.�k���@��2>R���Q�Va��~>Gsx��}��j������]����d�1���v��%�'P:��!P�! ��*�Q���BI��	�`g���}^��q��c�z�,�+�����U��H�=���`s����_��S{�9k���'���ph�_m�H�)y~LU���jN�p��+��
M�?��n�LJ��O���X|K�0�yI���l�^��78=-�'����������vQ������Ys�z���)��T��h�@�8������(Y/M5��0�2vWtv����1X>n�����_r?���lD�3k�9O&�����^�����#�(�[;s�%�N�C>��#ig��^���<y�����MR�k���dN�S�tV�����S/!�0��`����l�7�~]��iy���������b ���r�I?��z]��Bt������,@yj�������Q���W�C,��a��=�R �PfyGV:Oqu<�-�����O�E�������`\���-X���j��>(��9v9�[��*�����Q��o�Ns�����j	o�G���hC���V���kM���"������vi^y^�6�W����{��{�r�_��u �9�$�6"�����s���g9�J�)�+|#L����~��v�u�o?����Gk��r��zH���\.�~���E�(��1��U�k�� ���8g�����|�r��2*ev�U.o�2�\8��-�������q{�\]#u�8j�������y7��UzeAk��"m��sY)����i�MO��b�t���U�t�h���-��V�����d��N�*�9��������G?����s��+#���57�lhi�S�U5v�A�w(4N|D����K�Q[J��*���i����,V��sUo�k��y}b`�&�4O�������1|	<�w+Y�T��n&�����I\��]D�E��6�	������7V��2�~'��%�6}�0��q
b{8�
&�O�R��ex4�_F�k@����	R(KA����rE&�Y�>�������Y���x�J�C�yRJ����3��7�h��4�/��>:$5����8����W1�x��+�,�����i���)r�=,�2��I�&n)L&��Oq��v��u���$DN�ud��Z�v�R������E��k:L��t�OY��me2�� �O��������yV'��q2in�U'R��e"��k%����,y��,��y�������%x���P,v�_��;��3$T�y�������
!k��z��C&#���:�MI�?���1Rc��mqY</�y��w��2���x����b��Q�����
:���*qN�_K?c���D����n5/�@KGsL���S���Z���6'�����F�������b'+�6�s;;�1J�����A/���^�������G��3���KD.i��sO����&�rn�����|q�D/~Uk;p����$R���qP����>��u��\���\ �1&�����k�d[g������U vq�,$9��,=OA�� ���������@�[^�Z����
:�������]BS��l�xG&,2��Zu8I���,����jd�i����5#��V�Z��I[�;u�tT�^�JCW��B�hK�~���F��Y4bO����e�B�X3<!����<#:�-��z��������/�5�`��6	c�!ZVG��n-�)��Z��g��}��X�k!��,=��5O�]��d�����q8��g�v�:��U[�z��EG��:��J���K�����>���o!i�=��
f��sKJC���K�D����6��!>O�I��r)�����p�{6+j%&�9�O�EF��`�y|�r,0>������S|�-��[���y*�����o�/�%3����+"N/�L���yu|���x�f��pE���
�J�f��	}���e+�����@.���z����WJ�9i�^����n�n����52������9��[+n��<��b�I"Do����zf!`dhl��6(�2���������R�������^��b���.����X{���nt�@�l{�����~�EI��s��
������S�O��~�������c^����>���bv�<G'!���u�o��Bcm������w^5��@7����^)��6Y�K�K��J&����*MOt��������"�>�do;vy%R��U����W;U]��!���S
��'y��<�B[v:�?�����O`�!o?/>Q��1\Nof��kH���9��?Gu���i���v�G����Jl�C���?n9�r�`�\<��y6fp�6<j����W��s>I����c���m��D���i���v�{L�[5Z_�W���y���'W/?7�,���F���|��9��_@�����q\)�#Q0����L�y�;6��Gp�O��=�-��&�������;�����I����.-(tV��������C�U���`����aUO�%��do��1�����T	�	��������sd��q��q����W(�ay��>�
}6���z{��������vp��Uk��^V;?�k����S��S�������v*1���������u��i�F�Z����L����!����~�+R�����ji��t�����N}i�J��2J�[8�����g!�"��i��$;�_\�]���q�`��Y��T�r	I��n�������sj�/-ge�B�#���5�~���@r��7�Q���L�r�'��@�eF�I��@(�e�����������;>EE%cf��AEY�,Bz�8&�����f����+�&�Y(I<o�,,*�t!>S��K�_��B���Q�(����{~}y�}�h��Ru��l�J�L
�����
��>8��}~��A�$\�Y���r>m���b2r5E�\�F���a^�
�H�9>�Gn!&{�w����
P`�|���������L V��S��5cW>��pYZ������..������A�H��>b6��	*y���g<����/(������M3+��\�\};P����r'��p����#�#4�������	�����
~<���M��gue-�����Vmk)]KI��;�v�(�g�?���}K���t\(���c�W�,�{/.B;��B���Zq�"��t�x=B����(��G�E�� ��S��DD
�T�P�Q����9B��;��8]��{+	��G�x�����a_�!������H��>�jU�l��/W�\�I9����\�3�E�%)sV\h��%3%�20O�:�L��dS��M�?�Fo�@f�+<������3�����T�����7��f�	���3�mW��52L7�lB����9�Dp����v���?'C_���(�
��q��;� |n��s3��pP���p����;�4/�OH>��#o-W�����{����|�H8��7�g�g/�W4�$�
~-��o?���$x���<9{���$����at6��t�}�;|�4X ����$Q�*J�WV��f<�g�B��9�Xj��I;X�f�����������.��Q���+692�^�����H-��3|���q�3�/�����nZo
,�����x�R��/�+��>'[���Mh�R��S�D�''6��L���+���;d�O�����9�PsW)���	����DBi���M�i��P�8��p�1��S�c����������7��k#y]63C��QR�iN���9�@qSy�t.L����(��G���d�6��� ��s�@@-�?x����G�����C����K^:s�(M�z9
��9JA���y9E�����&	��x�F�������+��?%������G��M��86�e�O���/�L����U��b���UB�=��bV�~D/�������������g�;-����$�����e�6�W��5���U����;��u���}kr�������j��(EY��@0�^��}���j.�����u���|m�@�b�S.���Bkpu�M���'�|pp���bb�{o�����q�����J�m�!�<���U����v�u^k�W���K�C7��������Gw���o�3yFK8
�$"��0}��zJ���h1F�us��h���v������ �Y����DS�DZ\|��U����-�T�M�X������<��
��d�rP2,(�i$�j�I��{&�U=�8�P �
��LvA���A�T<-a�X99��I��$r�oU%�~�*|���X	��������9��p������b�vu=?������k(/{U[%Fl��S�
�T�g�?�
�J�P>N0����������q������q0��&o��	*I���-�����N�4�"���i^�����>�I������Z�����
�p���(��'��AA���X�7al���l0�6Z��|�~����b���rN��h��jk���o����7�Qn�z2A�7�����9���Sct=�E&D�����A�Q�d/whk����s�qs����R�88(��rxRY%�H��l�D	2=<&�"~��Mo�������
����-;��������*��D�I�C���a�}������?^�g5}���z^M��O��8q�s�n�T��u]=&�j�W�]��
y�����B���i6�:o��7�F��J���_�S����'�B���K�#�
��v�I�����1DYM~I����W��H�L0���\��Der�B�����v��:{�������7���`�jU'�;$ :kb�N���l](����s��4�)c���E��Eb��&�2���;����eY�5B������b��S��m�J���QO���}4�K"<,x��������~��xgi���Z|{�vu��Z��YO�����y�)�|����p���(�fBhs�M4"�P���)*��\�=����n�j��k ��:�D�z��������f���p�V����v�60�2�K[UD������B��r�<��J 1�\Y������Tv]8����G�3�0�)Q����nk��h�;:g%%�E]�����Y��2
�6��|w�y�K{Q�
����B�Um������������H���V�&�/�5�q���G'�B���*%��� �'�*��K����(���5@A���n��������si��6";Z���b������9X>��]0� �3!R����2�"��$�3��Z���1	��h����e0/y����:j���u/��dw1r{c|y�W�"��[�F��[�����]���5-B#H�:j�C���4cx8��)��G�����T��h4�� u5D�����ZJ_]�/�q���Z{�m������S��`0:�q�7�[BL���V��A��P��QL�bo�[�-v�?z�?���5�?�����F[�������&��*��vb���I>wM�g������wT���x�tP<�����,l�x����^0V��$����z3���5�`��R8Y�u/�'#4/H��P��(0�zT����p�����������'�����{?"������I�����������G(]D\)��h{�N�|y�}����sQ8��a#�.f�H��E'�������:�#����������0��|�����m��sIJ��������l�
�m�������k�0&�c�V]Q�%��
+�d����������Y�R����u�uD�y��;)������K�{h+�5[�0�fG�����@P������5�M��F���J�X�w$E����=Q_�+�'��vux�%E��>��j�]��.z-����D%����-]���U��]�E��c�_Z�$pC����O�S�O�
�]��V���x[��b�r�i^j�`�y}��
8��r-%��K2��J�K�2����Ee�n���u�����:Z_X�$�@����fO�G�n�P���h�2�3�j�(l$���l��0�S(�+dg-�a���p���� �4	q���C6��o�d�S���m\E�rb�����*_�����U���(��mQo\E����
D<7�#����'U�10�S�@�M�	O���J���eM�����9x�/).������4G����������IP>,V<j��<m���I|�>�K�y�(��������{��8��2$Z���*v���Kd�F�WYVT>�U(������kJ^�1�,����6��/R���<�[��M���4���<{.Su��r��;)�M��0�Lj���M�iF����Mx��X��>�N��e��}rm0�E��-��t������r��d�^]]:@�x��
I����YK
��$��|�����I��Vx�b������(I8�~=�1�������*1�<���"�IW�p���2�%��R�����tUw_Zq��.��k6(������=��i����I1�����x��
�������f�A����4%Omp��'jK���!�VJb5q�o���=+��yg��|��w�w�o��u�����6��k5�h����C��W��9va/��:n��Q98�
������
��D�,9���\a#�r%S��
�9a��������� ���dO�B�d����Z�z���;M�ox�o_<:D��G�%�+��
Z��AX%�q�����K�p���00��������l����|����Q�DO�k��w�
,I�W�y�LF�T��SNp��l%������K��h:�����DbM�e3����u'��-/=��~\�=���N�z��1��y��^Q�a�X��1$��\o8P��A*|Z��
!f����T�
.^�$�o2~��RM"�k�.�J���?��|�B&r�I[�R����:�{�l��������v����JX���P��]�u��F%yt����.����6��;O�����8� ���e����+���n�u�=8���������
Z�)��?����������7�����.�5.�H��Y�b�b���F�
��QKix���0�tY��var�����]��;����\D�����4[�7��<�S�V#�nXg*U��j�Sdm1�h�����KUh��f������8:����e
���D- (T�]?��-s�[W���>��8&I2�iqAe�/���A���R^���
)M���T>%���Y�p"��qK��y����Y�G���xuE������vs�X�����A�Nv�k}f���N*{X�kk.Q�Vv�`:5��������#�X_w��Bqgo�����Yv��n�!p���,@
7���8��S�@\duFw��hb���lt���Q[�\tQ�7�|��	��ai�Y��oGH����we�-GJ49V��3�|�<�������
�W<F������[���W�G����bG��������������V���j�\f������6�#38W���LG�j<��Ux7��%vKM?_D7������7T���$��x�do?�%�dR��CB2wS*��U��rBw�9�Q�4E��Sn*��,�`�x���I��!;���&q�D�U+{#�)!��������'�|����H T��
e���ug�v��e�7m�K�UN���]
��r��=�q�]�,^w�)�j�YV�8�"f��������ozy�lu2��]_���]����G�����;>x��o$~��N�f��������]pv���I�5c�5���b\���������1�������F
ak��U��_����L!9�	(a�|<J���|�������(�tf���i4���7t�(�)k��%��c�u����0J�.�q���*+<�z�f�M���K�������[xs`�e�` s����[1���X��h$k�R����A��Z9�>�I�xR(�JGao�#�]7}��s���@��G&_��Y,�tF^=���Po�wk?������pa��eX#<��s��h;
���=�����Uf��Ko��~Y����lq.�����.��)�v{�xL��7GE*KC���=C[�h�n&bZ�%� :�X�b��Ka`���9�����$8�6������i����xdy�%��	x!8]�
�B	0�kls������|>�3k��}�0k�Q�������P8-��aq�&�]7k�q.��9,�J���x�L��?���&J��^-Z�@���B��hw-'���W�T�������%������Zb��B{���%�3����k��H������Q>���~K-c�l���ri���Q'dpP<������*B~m�(����3 m����D�|�d�9�e�
���	�Ph�?��j��~;��[�m���Y?O&�z�3&������	\To<����_���A�����)y�}>&d�v4F|4lVX�LO���>`���p�?L#~Cp�4lX���lw���"��e��-�������9ZY���N@s��b���{	�'Eb3O��m��o���kS���	k��(���F�����LK�
���5���\�w��j��l����)�!�dF]4��P����n�Z@W���j���H��u�m�U�o���	��K�z�S���]�+�(��qA�
�!�Z�f���h����<[|����Lm��~�wQo�T����ulO����bp}��zh��Th~��YU6c��
�����G���{���nX����r�y�4D�3�7�
����&��u!/�����n[��ok����'�9?�P�L<6������CS�hAn/���dm�k�0E@�������������Z�'�����l���K��%�fi��:�����,{i�GO�^F��.�g0��$�_jh���`�3������\x��;8���N���R�R�Y+�����B�$<�����G��#`������)N�l����O��������g'99������U�b&�D?��L���W58�WO�>X�?��	xLo���
�n�7���i��������e��I��-5��'�Ce�SCY��z�8����N~nK`�=<v��p�G�����P8��%���5`2x�tAr�sw�=�eP���-Q1�����9\�z*���0\���R���h1�Ct H��(�n����&�g�������q�D/���z�m�a-gd��A�EU�/m\�d��Zl����B�xf��Bk@o�.���
�6�N�q�C���2X�_UfK��	�45�fm��%�V	�������w\�amv���b��W���������=����z���u�0,��s��h�O������l������l�#�Q1X�OJ�����'��3�
0004-Define-logical-replication-protocol-and-output-plugi-v10.patch.gzapplication/gzip; name=0004-Define-logical-replication-protocol-and-output-plugi-v10.patch.gzDownload
0005-Add-logical-replication-workers-v10.patch.gzapplication/gzip; name=0005-Add-logical-replication-workers-v10.patch.gzDownload
0006-Add-separate-synchronous-commit-control-for-logical--v10.patch.gzapplication/gzip; name=0006-Add-separate-synchronous-commit-control-for-logical--v10.patch.gzDownload
#115Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Petr Jelinek (#114)
6 attachment(s)
Re: Logical Replication WIP

On 02/12/16 19:35, Petr Jelinek wrote:

Hi,

this is rebased version after one of the patches was committed and there
were some renaming.

I also did some small fixes around pg_dump and changes syntax slightly
to what PeterE suggested in the beginning of the thread since I like it
more as it looks more like English (PUBLISH_INSERT => PUBLISH INSERT,
SLOT_NAME => SLOT NAME, etc).

Ah sorry, wrong attachment.

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

Attachments:

0001-Add-support-for-TEMPORARY-replication-slots-v11.patch.gzapplication/gzip; name=0001-Add-support-for-TEMPORARY-replication-slots-v11.patch.gzDownload
0002-Add-PUBLICATION-catalogs-and-DDL-v11.patch.gzapplication/gzip; name=0002-Add-PUBLICATION-catalogs-and-DDL-v11.patch.gzDownload
0003-Add-SUBSCRIPTION-catalog-and-DDL-v11.patch.gzapplication/gzip; name=0003-Add-SUBSCRIPTION-catalog-and-DDL-v11.patch.gzDownload
0004-Define-logical-replication-protocol-and-output-plugi-v11.patch.gzapplication/gzip; name=0004-Define-logical-replication-protocol-and-output-plugi-v11.patch.gzDownload
���AX0004-Define-logical-replication-protocol-and-output-plugi-v11.patch�=iw�F���_������K�,���[�=LdI+�9^f�$� ����M��VUw���L�����	tW�]����D��=�����a��W�c����3���c����?xv4��q����|v�C��������������]�8b�r���v�s8��F�n�<~Y?�b���Xq��o����O��w���.���$�r;~�~�z58�;�����1�e^0qm�c=���Rq`�|�I&1�d���z���3'�;"�����������~~eG_��%?u���Y#��!�1��T��e���1��(�r���k��E��m�z���������� ��[�������<;Z�������8��I�Tsn���-��<�q����~������O��d�^��������]x��q���[gv�AI(-���
a��%���w����W�{����u��Y�5qcfu�X�h����;�#{�<������O�cP<��R�u��������z��^��~������3;���kf�6c��v�;��pI~X^guv����_���c��\�����x�2�������c7���s��nb����'���aW
�qG���a���,�����������(v<]����=e�l9�BX���^p�DlEH�hN��=7������������'������~�}�v����;F,�[#p�g	�f��^}�v���9�|���LR�������G�5D��Jn��Y�#�7�������`����\���
���RZ��#�_K'4�DN+�s����%L�[�$
@�q��n *���s9.;��"���"��3���8��g@�f��d��j�E�lr����A#�h�`
J��$Qczs'���gt�t�"$�0P���Hs(��C��S p}��������'($�}|����AaM�����"e�q����H5"nsPw�n����o]���	�P(x?StpRH�@��;V��jB����|6���$@t����N>��'p^?3U��!�#]to�]*WXm�U~��S=��"�������8���N��qY�������m�����(�����b��'����	n����������`��W���<~��H�	�K7�2K?e�<�Sb.���I�!�UU��)�9���e�E4��\|(��O�	r���I	�.���m)�7��p���#��E��dx�����9�6���0Y�2�����Z���)� �(a����0�o���
�Z��?��h�?b@@�l��tHa:�`G����	��H��d���K����!�g��[��Z �&
@�dR�P��Bc&s���a�qd����H�����"��P�bB�����s�]�^�M �sCf������PDE9Gc��D�A��#�������k��x�W������}��4	���#j��f>|��E��Gi�o4����$d@�t��=�����>zNz�Z���d �!�&��5�W�N)8F���|�me�����
;	}%uW)y��#�������?��O��4�uw��H�����<t�H0'���{��D���\0�9JB��q��,��(��X}T����9T�;k.��G|�2<Q�9�kl�D�vd�cE#����{�k.��eV��6����\2��\�Ny�H����5���H�B�&�������_�!���B�]����l+}vQF�5�2��s��d�9 �4���L5��RiS������]���S+�:@0SA����(.ugT��$=�k	����L��X��Z-m+�9Y��Z��{�fnIN��B����B>�@W��t.�WgUsa�n`.�I��O:�"^|�"F�'��p���wx�<�u?�1�F�A�7��W�>�cq�L��lm�I��%�qx�e�E�A$��H��F���s<pMI�9��hEf_�L���>��;�Y7i�������P������O�9�	����dJ&����Z
S�KU��WF,��d��/�?��2�6�c�v�PL
k0D:7��Q�R�,�G�s��gd����n����[MW_C~��x����
������%W��	���3�H��M�J��U�����`C4�5}����N�Q�3b��+t�0��U	���P����@^��A��kG�h�i�t�W��!gEC�av��u��Vw��n���zR�#D�O��z�S��{.�GJ����d�ff����og��I�>?J�!�i��b�adz��0Fv�M#������%+��
Q�"�Ref{x>��sY0RX�M�1�r���/����0N����������n�����t���(��
�#^1OgH����j��W�]����O�����V�gxe������0!���M?Mm�Z���_�,��ga<�r2�!�����&��>���U*Nm!�2����)�*�!�xk���&
�:T�Mh�� 0�#�qYy������M��r	��^2���/��&�7�KB����a����*����7w!�����Tvrds
V9[U��RK?��<t���"��j�3+� �4�Gr�������r"������Pq����"�Z�^�
�3�%{�T�����A�k�)�_|*�3�1H ��:kn���;��m:�*��!�i���K�u�k�z_�u�I���P.�=����&���f��*+|��(�Q�et+Y�/ ���E@��������qe�N��n���Z���(��)Z�����zv1�~b�
�8����.-Fi��o���1\����3!�E=~S�?��M�Z����I\&F
f�E6��%�u�D0P�E����T���?�JA�����2���> ���I���#�2��e����3�I��Y�����:i���f����=2�O���A'����8_���P�6�t�U��4%�������M��!~{�����'`kzfBEu��'�������G��{����3����P���F��)�K�k�Qp���iK��������������oU��|��l���������a����V;p�,'W�������%�BP��������~���Rj�1n�NW���"�G�����p ��Hc�I��/�����U~�X�����������]"�,D��I�����@;So��=`��|����-��v����;��;>�}U�[���vG����Y������:���Gt�U`�5�pO������������j�����v�
�Zyn������X:A������/A�a�����*�b�s����*�;G��=l�����?��y����k5G�����_�����F�C:T���nNd���,�*C��"�kpru�������k��������7���u��[�����T;�{��Oc����YQ��T�����Q��(����������l��O.O��-���2���\R����;p�=����+V�P������q�]�TW���O��0��2u~���[SW�8�*�f4��71|�M}�����p���/����Zm�'�&m�O;��:�Y����7�4��noIm!��^�p�6
�0�/gBo��/|T���c�����K��N(	��F�����M3P��<�,Dg
3����/{�[p+�f������ ��
��������G��J����������uY�_���[W���*��,hd�%���qT�/t���Xj�?r;���#B���Y'�E�fe��p�;�Wz�'�Dg���<^����2v5�;�"@��t�0+/�[��@��O�������C���{���BLw�V���YW�dd�W���r�G[��#��Qw:\�]
�7���0���#@9vmv�����"���hL�!S��?�^�f��	���z�q,��@�~ ��
����:���q�)���M��]�0��rl	V	�<��,�x5r���(K,���>��c eC��i�({\����K�Z���s;w��r��h��Lx��z�������M�6'�����Q59�@�%�v���U
�%n�2���I�M�G�� �� �6��>%/�:�9D���g�8h�Q���h���������y�W8V�xH�����(�hM'_�8J81���	��/��d{
O�b����M��X�!k��5�.i��;��mp������N6�}"��5��J��'��.z0�l����G�~�����F��B`���/~3��5&�z{%+CyZ�<�����E�edh0e�CF	���BK)/��j�X#L��nqT- ������j�m<�lf4��]��}�<�i�����rp9���������w1�(~�����
�f�V�����T���
`h����Z����	�"��6�Hu)h���*��n��>{���kMj6+Okk�A��S�?�^E����b�����}�C�/C<U��V4�1���V�<VX�h�����xH�����.l��p�%�z���n$�21���>�A�����]���������/�`�������^��E-)�T,V �'�F���@���31���D�nA����Z����\s�
�G:�`�8��������1c\�q����C��N�/B�l�/�k �2Y���*{r��]p������i��qG��!u��I��2�����z~��\��"EB��t����r'1-�5?�:?���YB��(������A���GZ�R�Q4od:e��mGu���p���i57�Y*�T
��:��5���l&��`�
�����<B�ST��
R�.��\{%������Jfwy��g������9�lRes��ase�����P�,������d�ev����e7E��pI���a	�}�c��W��RI��l���.�|A�M}��0v���vV���t�����^Z��/jJ-7)5&�>����E�����|��L)�+A�������������������������T���;���o�����������������F��Euo���oy]��U�������&8���4lAY�:��RU�%2CA�����:����F��q��G�?�Ar2#��fa�0-��s[�@�x�%Y
��R!�������Y�8�c�m\n��;:&��}���R{5duM�R<�:?�������W���������d�w�9�c:}b������+������:�9�Z�5r���������j��)Y����;��>/t\��4�F������}�:n�l?h�T{<����`*}�l���g���7�A���;�k��9���/�d�������U��FMc�~S:Y�������h���7�n �B"����x@�D��-��RvDl-'�\at�@)S#+�U�"1h����K���uzv~v�%w�n������J�
���j6�z� t�8�q���Y�Y���Q���W����k���.�y����k��
�����:���/��������q
���"��."���i���Z��J��XBq2�tsOuV����H��qH=��=y\�#�o�U��B��\�6*��Q]~]��
�hkp��4��������vu]5g�p'd_JCA���J�����~����.8�{�P��~-p�vC�����Qn��Zql�|�� ��Zc��P��/B��r�x�^��4"^�������q!h�&m#t-�,(�TC3L�QTc_�G�G�!U�#�s��d���
�Mq����d�T��>��
��*�V�s���@�p�c`p�5|��o�-
)��j���^A;T{��|\��~*���OWi&�wx�`7�����\�����������?8��D�d����M#�_R���{\Sk`�(G$�1�� �����p%O�H ����k�����g������A#�J�t�r�e���[�SK�[/�w�f��E�J�(���.�C�X4�e��*���{�2SASy�<d�=h�4?n��i��eI`��Y��b�j�U�	���sA���GNg���]/���6���(KM�}VUn`�>���2�i���;��OD��k5:<!~~g}�&�������7�j��m�K�������k5�so9xYcM���W��<0Zq���e���A����Ji�%9����'W8Q�7����s�DnGJ�������M����_�-����9��d2e3P���?$@a V�"���	7��H�F���
=����k�������3�+�"��$_�Y'��S�D�����T���kH�,�)��-+����7��������V�bj�K���nt0�#K�-�|��K�6	������0������=��J����V���&�7�F�QP@�������������tT�i�f��� ���^P~�����N�%�1y�b�uIa��?��?K��S�W���v��|>�������(X�;�����}���0*�����8m��N��o����T�e)}�)r���[�����o�z������c���������'}%.����I���R�P�>&��Wr��ihN��`_?�?_��v���{B{����zK3���h�*[�\[��P��������io#�{{�$���������0�Q�=�P�9���J������/�@������V������C��0��'�S�`,��[��}V�2�r��?Y�����h	��}1�
*�>��!�dS��������3m�<8"z���n<E�"�(����8�p],������3��THCzo8]�����V����;�����=��%�`��/�b�59x��}�izE��9��#�����4������sn?�Ik>����c���cz��>8g��G���)�s�j��T�AJ+�?����d9����h���C���D����bh/q���>��O��c�LL�����H�(SVkh���q~��1�Rm�G������'��]uTqS�A�M�$YO����Z�R"
���J�"�G�Q	��3��5�UFC����S����il�93� �	K4����Q��}n[�{��*����*K1����������&Q�~��(�.�j(_�Ch3p-��Ym��9>�jn��8���=��+�f��^b����V��Q�+Xvf�����������2����$�i�m�#U^3�������2�l���Q����pP9���kv��R�J�����D��&�^42��6V�OSO&��W�B��''[[2+����h������������^��5t��=>�Y(-��.����=���9�[%;"RCD����s���xzQU�t����\��xmZ�"o%��������Zz�������W�=�Qz����>���};�K�'�Rm���*�61b����~'�p�~#D�_��fP���S�kY��OdE��rD�����+��[O���y)�8���-��Ut�6pR��}����:%��?�)9�7�����V���	�j��O��������<�dD�F��?�KHs�6n8?�t{0]@������f����N�}(a�2Z������\��l��tH��z�
t��!
�|�a{���hI��Z0���JP�������>�����a|�X�~A��o�?����>dH�,���RI��*���0�r0S����]��`w������u�i����i���������',p����q���=`��3�{_|,�WJq.���(��F�]P������z;�������f��,tB�Q������z�����SS����
'`�-�9��I��\�.v��3E�p����+�;����73P�)]O��~x�~�p�rd�?=�z�8�E��1���������<��2�8���C���"h��4�����89�k��DPV*Vj��`UyC���h�M^�&��K��x_�x���^w�'m�7H�H?�)���<�>�y�	�|�rNO�L�L���5FD��9���0��������������6l������3�
6o�z��E����~����������S�������.I�V���6��]�������XQ�8���_�#4Y�#�]	��X��q�|�����$�Zp0���r�0J��,�;�,x���`�1;�����I��*Y�����nIO�#�����S������`�vg���$sEI�9b���.������gr�|��@4�G)��,������tH
8�bt����6��e�M�}������;�)�����|��_j��ph�f�8�!G���R���s� ����W[n�t�����E��n�~%�4{�
��<8������ ����:�m��b���xL��:MF��������V�s�q���Re�J�Hw�1��Sx���)����&��"��G��E3n��3�K��#�������(�h�z��B,��"F��B,�
���)�2�P�Hz]��^p����'�7z#+Dk���A�'�,5tK ��F-�,�L��Fw��@�7��V�SD�R�����������%����\*���`H�Eu�^u&�
�[ )���n��E���Q���&��
�����f[��]�'�y	&���
2*��C�r�������C�r�����f���p�E@�����s�O�@)���>����4n�Q1H����nz6�`�#J+>>8;�������o�T�eO���M�����
���d��-��"��!0\����9!h�S�k5���wk�Jo�Mlk�����>>:���/���u.���e'2,��U��f��������G��������D��M��+t�vv��7��~�@���:����$X��<x���a�b�[)�Wk�[�r�E����R�f�/�������'H�PB�xK\���j9vP��\��y���Lf�{_NE8��Z�@h��`��?�1�:n�c���x�����G�c=����{���x��,�z�9|�j�R�1��q�B������6��{|-���
��\#q����%�i.��j�0&S��lmT�,����'�����n���_�'G������*vtz��S�����W�49�+��c�,�MA�i4��B2�FS!>Wg����^������i���}�[����2�K,�(��"���,'���Rgl5�Tp���s(��K
�<�2�P��=������M���2���B�������E���l��f����H0A��!����n2�jm�D ����tw9G��c�fGGuO�%�$��V�G����@����{����Y���<??��u� ���f��c����w#Bs����zH��;t���6]Xt�BV����t�[X,���������aZ�8��y"�P��l�q%��q9=�W��&l�f���q�������77w�EA��:�*�?�qm���r�q�%��w��-��������}O���C���o�F�Hv��#�&�,��.{n��>����P����VB���
����@gT�c��	Q<I���Fd�������S��hR�)i�����Zv��s�_V�g�����`���Br��n;�%>c��z`��Z�����y���x��u�����R�PH��R����)��}�=�gjGv:r�|hDw��<�����pX�[��@%��:���k���<�^�nq]�����(����]�`��;������lv�Q7��������qY/�R�}5�
�1�1Ue*Yx��8Z��F�M5`���-#��.<����j:T��:�����4�Ro�x�kP��!Br'#n#uxrLW��,�����T��=&n�:%	m��c�\�-U��#�<A�h�&d���j��L����������1�
�������%�}���t�
��i�1r�:.:g����_�|	��n���
Q�Y[]w�vc9a���c(�7D��fUT4�@c5�aX��m��lj4�5��x)"�����'=9m�����%���g�Q��_di���Z�
\6�&��x<�������=l�m/'YT?�2��r� �������62H�)��wi�"��r���i%N03��4$q�m���c@�M����
�W��hK"��`
��$G����[���l4u��V;U��TIVB1-�+Kq��Z���S5���%_W}G)� @���I���Hl�^D��l'\��|���9�������}YXs���\�^���&����{7��j�����RN�kK_f�F��-v���Y�t[��n�gN�:T�(��2X��P�I�<�Okn8e�i���T`�j�Z9M�i������N��������jc4�D���e�d�����o�����sk������*����F+��.p�
�RO���rB~EA�M�6Y�
j-Z��Z���nX_�8s����o9�v�d�u���,��xFq��o�B����L��2^�=>��*�w�:~v0����-����+����h���P7	T:Mw�FA�H2�D�����8�|1��#6��a)z^0��|�)b�5�ue��0T>�G`���({�/�yx��u~��
�I9���r	��4�<i������/�4�m���;�����m�7j���q�����%����e�����q�\W�1����~ Y�#��UNFG�nOp%L�y�<]������:E��-�6�tM3���n�f�$gq��7���k:���+�"�*|u�Q�P�G*=�m�_��Zvb�|�f�x6��-��Wa��@gw���S�����U�~������l��,Gj�p5��E���*;{���UC��{��t�DT�_���iA����0�	g��V��3+�^���z��a>	�(��P4���PZ98��d��3(���'��9����������p:��o��|�
?v1S������_�Z��!����Nq5!��z��F��[����6	{6�U�����}?�q[��8C}PcN�GE��N{�7=�����~�z%���J
Pd�(4�����S��ys�Q���N�?
������{5+�W�k�/���]�nDn���_\��{����i������`�(1����l�)|(��q�,v%��m�]�ye���m�.���"�[ ���H2B�
;^@�P����&������X4��P�v�_/^�w4���!#5W�&������?�o�1P��6�����g9�X���1���jY�>4	4A���}51�H������o����N��1�L��`@8���i�<z���1P��i/9�5�G�\�p3C�
��z�Vi�c�W��@�"�����(�A����yB�6��������X���a0��:�R\Uc���?�����\���_V}.���/�O���j��������3��?F�6�.T�S
���
�����it�G��s�������p�|��;�c������ 4�lYHV����*	vP���������h�K���i�hY�Mr�N� �0�&�]#~&��`�����
}��'H�N�U�j[�9������0�|:�Fw������jj��l�f��@�e9Y��c6���� ���
�B�>E�>��!���Z�-���R���C�8��HQ��j��i\�g�4�
��d��Q�y�E���b'Q�@8��<���+�F�7�1LrI�:�%�H�`F�C1��j�XN� � �h+ir%��n��+9���7�s��_4�dA�S2tm3r%9��`xv�v����~	��y:��vq�vUwf]e��W��X�����E��R�^�n���^����n:�A��d���:\Q��s�����kT>5���J�W��1�c��N}�l1X/���uCt�v�����{�]�GB�����,�=���Z4�d�9<��^�1My�����K����NV,��������6y��U�}R.YN5����zMJ��V��C}�r�� Y�J���.����wH���*�w�������_
���*!+��3:����@:�$�W�r��b
��J4�kxA�:
_��,8�i@ANQ��9����>&�d0����f�X~�Np1.{\�~hc�9aKP7$\���,8%�yW����/0cr��S�B����P���[����B#���]r)K4�D�g���������4�^;�
�/
�V�+��Q�5�C�Q��'1��hvy��^����U�����yI0���$����P&�c�@Ol�����l���UR���(v�?�E��;��������&��f8���>SJ:���}�@�d6����q�z/����b|������X�����>6�\iX�����f����>\�-��A6�������C�H..���O%�H� EN9UZ�Z����S�I)�o�r��:���"��g�Q���� �Q�����{aO��Q`��M�\h"���X)w������Ym.s&X�����/�i�X/q���1�J3�h��J&��*R$�;7���X���(��m���k�[�AO��xWl��^�
F�����h3{|�e;�1����������t�wJ���%���^aP��Fy���8w��+;*!�!xEly���jaK�8�s����ZC���P\@G������/�H������V�tx�{x��1�c��
{O0�_���_�����^m����.�^E�:w��&��U����w�<BU7���b���%]�f�U��:����'�u��3�	�E��+�@"��_�Z�j��r��-T�Z�������kY�N��������(F�_@`������i�����x�w�ET7��U?L@�#EE�/���<�Q�B��]�@���Vl\{1�XL!����[�z���{e�7�`aiY�`���=�}��o�����<��
0005-Add-logical-replication-workers-v11.patch.gzapplication/gzip; name=0005-Add-logical-replication-workers-v11.patch.gzDownload
0006-Add-separate-synchronous-commit-control-for-logical--v11.patch.gzapplication/gzip; name=0006-Add-separate-synchronous-commit-control-for-logical--v11.patch.gzDownload
#116Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Petr Jelinek (#106)
1 attachment(s)
Re: Logical Replication WIP

I massaged the temporary replication slot patch a bit. I changed the
column name in pg_stat_replication_slots from "persistent" to
"temporary" and flipped the logical sense, so that it is consistent with
the creation commands. I also adjusted some comments and removed some
changes in ReplicationSlotCreate() that didn't seem to do anything
useful (might have been from a previous patch).

The attached patch looks good to me.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

0001-Add-support-for-temporary-replication-slots.patchtext/x-patch; name=0001-Add-support-for-temporary-replication-slots.patchDownload
From e4a814f7abb86e78daa63bbbd37cb00f2b7d9180 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Sat, 3 Dec 2016 12:00:00 -0500
Subject: [PATCH] Add support for temporary replication slots

This allows creating temporary replication slots that are removed
automatically at the end of the session or on error.
---
 contrib/test_decoding/Makefile          |  2 +-
 contrib/test_decoding/expected/ddl.out  |  4 +--
 contrib/test_decoding/expected/slot.out | 35 ++++++++++++++++++
 contrib/test_decoding/sql/slot.sql      | 13 +++++++
 doc/src/sgml/func.sgml                  | 16 ++++++---
 doc/src/sgml/protocol.sgml              | 13 ++++++-
 src/backend/catalog/system_views.sql    | 11 ++++++
 src/backend/replication/repl_gram.y     | 22 ++++++++----
 src/backend/replication/repl_scanner.l  |  1 +
 src/backend/replication/slot.c          | 63 +++++++++++++++++++++++++++------
 src/backend/replication/slotfuncs.c     | 24 +++++++++----
 src/backend/replication/walsender.c     | 28 +++++++++------
 src/backend/storage/lmgr/proc.c         |  3 ++
 src/backend/tcop/postgres.c             |  3 ++
 src/include/catalog/pg_proc.h           |  6 ++--
 src/include/nodes/replnodes.h           |  1 +
 src/include/replication/slot.h          |  4 ++-
 src/test/regress/expected/rules.out     |  3 +-
 18 files changed, 204 insertions(+), 48 deletions(-)
 create mode 100644 contrib/test_decoding/expected/slot.out
 create mode 100644 contrib/test_decoding/sql/slot.sql

diff --git a/contrib/test_decoding/Makefile b/contrib/test_decoding/Makefile
index a6641f5..d2bc8b8 100644
--- a/contrib/test_decoding/Makefile
+++ b/contrib/test_decoding/Makefile
@@ -39,7 +39,7 @@ submake-test_decoding:
 
 REGRESSCHECKS=ddl xact rewrite toast permissions decoding_in_xact \
 	decoding_into_rel binary prepared replorigin time messages \
-	spill
+	spill slot
 
 regresscheck: | submake-regress submake-test_decoding temp-install
 	$(MKDIR_P) regression_output
diff --git a/contrib/test_decoding/expected/ddl.out b/contrib/test_decoding/expected/ddl.out
index a9ba615..c104c48 100644
--- a/contrib/test_decoding/expected/ddl.out
+++ b/contrib/test_decoding/expected/ddl.out
@@ -702,7 +702,7 @@ SELECT pg_drop_replication_slot('regression_slot');
 
 /* check that the slot is gone */
 SELECT * FROM pg_replication_slots;
- slot_name | plugin | slot_type | datoid | database | active | active_pid | xmin | catalog_xmin | restart_lsn | confirmed_flush_lsn 
------------+--------+-----------+--------+----------+--------+------------+------+--------------+-------------+---------------------
+ slot_name | plugin | slot_type | datoid | database | temporary | active | active_pid | xmin | catalog_xmin | restart_lsn | confirmed_flush_lsn 
+-----------+--------+-----------+--------+----------+-----------+--------+------------+------+--------------+-------------+---------------------
 (0 rows)
 
diff --git a/contrib/test_decoding/expected/slot.out b/contrib/test_decoding/expected/slot.out
new file mode 100644
index 0000000..1a8c8df
--- /dev/null
+++ b/contrib/test_decoding/expected/slot.out
@@ -0,0 +1,35 @@
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot_p', 'test_decoding');
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot_t', 'test_decoding', true);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT pg_drop_replication_slot('regression_slot_p');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot_p', 'test_decoding', false);
+ ?column? 
+----------
+ init
+(1 row)
+
+-- reconnect to clean temp slots
+\c
+SELECT pg_drop_replication_slot('regression_slot_p');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+-- should fail because the temporary slot was dropped automatically
+SELECT pg_drop_replication_slot('regression_slot_t');
+ERROR:  replication slot "regression_slot_t" does not exist
diff --git a/contrib/test_decoding/sql/slot.sql b/contrib/test_decoding/sql/slot.sql
new file mode 100644
index 0000000..8728db5
--- /dev/null
+++ b/contrib/test_decoding/sql/slot.sql
@@ -0,0 +1,13 @@
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot_p', 'test_decoding');
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot_t', 'test_decoding', true);
+
+SELECT pg_drop_replication_slot('regression_slot_p');
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot_p', 'test_decoding', false);
+
+-- reconnect to clean temp slots
+\c
+
+SELECT pg_drop_replication_slot('regression_slot_p');
+
+-- should fail because the temporary slot was dropped automatically
+SELECT pg_drop_replication_slot('regression_slot_t');
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index eca98df..0f9c9bf 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -18465,7 +18465,7 @@ <title>Replication <acronym>SQL</acronym> Functions</title>
         <indexterm>
          <primary>pg_create_physical_replication_slot</primary>
         </indexterm>
-        <literal><function>pg_create_physical_replication_slot(<parameter>slot_name</parameter> <type>name</type> <optional>, <parameter>immediately_reserve</> <type>boolean</> </optional>)</function></literal>
+        <literal><function>pg_create_physical_replication_slot(<parameter>slot_name</parameter> <type>name</type> <optional>, <parameter>immediately_reserve</> <type>boolean</>, <parameter>temporary</> <type>boolean</></optional>)</function></literal>
        </entry>
        <entry>
         (<parameter>slot_name</parameter> <type>name</type>, <parameter>xlog_position</parameter> <type>pg_lsn</type>)
@@ -18478,7 +18478,11 @@ <title>Replication <acronym>SQL</acronym> Functions</title>
         the <acronym>LSN</> is reserved on first connection from a streaming
         replication client. Streaming changes from a physical slot is only
         possible with the streaming-replication protocol &mdash;
-        see <xref linkend="protocol-replication">. This function corresponds
+        see <xref linkend="protocol-replication">. The optional third
+        parameter, <parameter>temporary</>, when set to true, specifies that
+        the slot should not be permanently stored to disk and is only meant
+        for use by current session. Temporary slots are also
+        released upon any error. This function corresponds
         to the replication protocol command <literal>CREATE_REPLICATION_SLOT
         ... PHYSICAL</literal>.
        </entry>
@@ -18505,7 +18509,7 @@ <title>Replication <acronym>SQL</acronym> Functions</title>
         <indexterm>
          <primary>pg_create_logical_replication_slot</primary>
         </indexterm>
-        <literal><function>pg_create_logical_replication_slot(<parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type>)</function></literal>
+        <literal><function>pg_create_logical_replication_slot(<parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</> <type>boolean</></optional>)</function></literal>
        </entry>
        <entry>
         (<parameter>slot_name</parameter> <type>name</type>, <parameter>xlog_position</parameter> <type>pg_lsn</type>)
@@ -18513,7 +18517,11 @@ <title>Replication <acronym>SQL</acronym> Functions</title>
        <entry>
         Creates a new logical (decoding) replication slot named
         <parameter>slot_name</parameter> using the output plugin
-        <parameter>plugin</parameter>.  A call to this function has the same
+        <parameter>plugin</parameter>. The optional third
+        parameter, <parameter>temporary</>, when set to true, specifies that
+        the slot should not be permanently stored to disk and is only meant
+        for use by current session. Temporary slots are also
+        released upon any error. A call to this function has the same
         effect as the replication protocol command
         <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
        </entry>
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 50cf527..9ba147c 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -1434,7 +1434,7 @@ <title>Streaming Replication Protocol</title>
   </varlistentry>
 
   <varlistentry>
-   <term><literal>CREATE_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</> { <literal>PHYSICAL</> [ <literal>RESERVE_WAL</> ] | <literal>LOGICAL</> <replaceable class="parameter">output_plugin</> }
+   <term><literal>CREATE_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</> [ <literal>TEMPORARY</> ] { <literal>PHYSICAL</> [ <literal>RESERVE_WAL</> ] | <literal>LOGICAL</> <replaceable class="parameter">output_plugin</> }
      <indexterm><primary>CREATE_REPLICATION_SLOT</primary></indexterm>
     </term>
     <listitem>
@@ -1465,6 +1465,17 @@ <title>Streaming Replication Protocol</title>
       </varlistentry>
 
       <varlistentry>
+       <term><literal>TEMPORARY</></term>
+       <listitem>
+        <para>
+         Specify that this replication slot is a temporary one. Temporary
+         slots are not saved to disk and are automatically dropped on error
+         or when the session has finished.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
        <term><literal>RESERVE_WAL</></term>
        <listitem>
         <para>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e011af1..d57f1c9 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -720,6 +720,7 @@ CREATE VIEW pg_replication_slots AS
             L.slot_type,
             L.datoid,
             D.datname AS database,
+            L.temporary,
             L.active,
             L.active_pid,
             L.xmin,
@@ -985,12 +986,22 @@ CREATE OR REPLACE FUNCTION pg_logical_slot_peek_binary_changes(
 
 CREATE OR REPLACE FUNCTION pg_create_physical_replication_slot(
     IN slot_name name, IN immediately_reserve boolean DEFAULT false,
+    IN temporary boolean DEFAULT false,
     OUT slot_name name, OUT xlog_position pg_lsn)
 RETURNS RECORD
 LANGUAGE INTERNAL
 STRICT VOLATILE
 AS 'pg_create_physical_replication_slot';
 
+CREATE OR REPLACE FUNCTION pg_create_logical_replication_slot(
+    IN slot_name name, IN plugin name,
+    IN temporary boolean DEFAULT false,
+    OUT slot_name text, OUT xlog_position pg_lsn)
+RETURNS RECORD
+LANGUAGE INTERNAL
+STRICT VOLATILE
+AS 'pg_create_logical_replication_slot';
+
 CREATE OR REPLACE FUNCTION
   make_interval(years int4 DEFAULT 0, months int4 DEFAULT 0, weeks int4 DEFAULT 0,
                 days int4 DEFAULT 0, hours int4 DEFAULT 0, mins int4 DEFAULT 0,
diff --git a/src/backend/replication/repl_gram.y b/src/backend/replication/repl_gram.y
index fd0fa6d..e75516c 100644
--- a/src/backend/replication/repl_gram.y
+++ b/src/backend/replication/repl_gram.y
@@ -77,6 +77,7 @@ Node *replication_parse_result;
 %token K_LOGICAL
 %token K_SLOT
 %token K_RESERVE_WAL
+%token K_TEMPORARY
 
 %type <node>	command
 %type <node>	base_backup start_replication start_logical_replication
@@ -89,7 +90,7 @@ Node *replication_parse_result;
 %type <defelt>	plugin_opt_elem
 %type <node>	plugin_opt_arg
 %type <str>		opt_slot
-%type <boolval>	opt_reserve_wal
+%type <boolval>	opt_reserve_wal opt_temporary
 
 %%
 
@@ -183,24 +184,26 @@ base_backup_opt:
 			;
 
 create_replication_slot:
-			/* CREATE_REPLICATION_SLOT slot PHYSICAL RESERVE_WAL */
-			K_CREATE_REPLICATION_SLOT IDENT K_PHYSICAL opt_reserve_wal
+			/* CREATE_REPLICATION_SLOT slot TEMPORARY PHYSICAL RESERVE_WAL */
+			K_CREATE_REPLICATION_SLOT IDENT opt_temporary K_PHYSICAL opt_reserve_wal
 				{
 					CreateReplicationSlotCmd *cmd;
 					cmd = makeNode(CreateReplicationSlotCmd);
 					cmd->kind = REPLICATION_KIND_PHYSICAL;
 					cmd->slotname = $2;
-					cmd->reserve_wal = $4;
+					cmd->temporary = $3;
+					cmd->reserve_wal = $5;
 					$$ = (Node *) cmd;
 				}
-			/* CREATE_REPLICATION_SLOT slot LOGICAL plugin */
-			| K_CREATE_REPLICATION_SLOT IDENT K_LOGICAL IDENT
+			/* CREATE_REPLICATION_SLOT slot TEMPORARY LOGICAL plugin */
+			| K_CREATE_REPLICATION_SLOT IDENT opt_temporary K_LOGICAL IDENT
 				{
 					CreateReplicationSlotCmd *cmd;
 					cmd = makeNode(CreateReplicationSlotCmd);
 					cmd->kind = REPLICATION_KIND_LOGICAL;
 					cmd->slotname = $2;
-					cmd->plugin = $4;
+					cmd->temporary = $3;
+					cmd->plugin = $5;
 					$$ = (Node *) cmd;
 				}
 			;
@@ -276,6 +279,11 @@ opt_reserve_wal:
 			| /* EMPTY */					{ $$ = false; }
 			;
 
+opt_temporary:
+			K_TEMPORARY						{ $$ = true; }
+			| /* EMPTY */					{ $$ = false; }
+			;
+
 opt_slot:
 			K_SLOT IDENT
 				{ $$ = $2; }
diff --git a/src/backend/replication/repl_scanner.l b/src/backend/replication/repl_scanner.l
index f83ec53..9f50ce6 100644
--- a/src/backend/replication/repl_scanner.l
+++ b/src/backend/replication/repl_scanner.l
@@ -98,6 +98,7 @@ PHYSICAL			{ return K_PHYSICAL; }
 RESERVE_WAL			{ return K_RESERVE_WAL; }
 LOGICAL				{ return K_LOGICAL; }
 SLOT				{ return K_SLOT; }
+TEMPORARY			{ return K_TEMPORARY; }
 
 ","				{ return ','; }
 ";"				{ return ';'; }
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 0b2575e..40a799c 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -47,6 +47,7 @@
 #include "storage/fd.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
+#include "utils/builtins.h"
 
 /*
  * Replication slot on-disk data structure.
@@ -98,7 +99,9 @@ int			max_replication_slots = 0;	/* the maximum number of replication
 										 * slots */
 
 static LWLockTranche ReplSlotIOLWLockTranche;
+
 static void ReplicationSlotDropAcquired(void);
+static void ReplicationSlotDropPtr(ReplicationSlot *slot);
 
 /* internal persistency functions */
 static void RestoreSlotFromDisk(const char *name);
@@ -389,9 +392,12 @@ ReplicationSlotRelease(void)
 		 */
 		ReplicationSlotDropAcquired();
 	}
-	else
+	else if (slot->data.persistency == RS_PERSISTENT)
 	{
-		/* Mark slot inactive.  We're not freeing it, just disconnecting. */
+		/*
+		 * Mark persistent slot inactive.  We're not freeing it, just
+		 * disconnecting.
+		 */
 		SpinLockAcquire(&slot->mutex);
 		slot->active_pid = 0;
 		SpinLockRelease(&slot->mutex);
@@ -406,6 +412,33 @@ ReplicationSlotRelease(void)
 }
 
 /*
+ * Cleanup all temporary slots created in current session.
+ */
+void
+ReplicationSlotCleanup()
+{
+	int			i;
+
+	Assert(MyReplicationSlot == NULL);
+
+	/*
+	 * No need for locking as we are only interested in slots active in
+	 * current process and those are not touched by other processes.
+	 */
+	for (i = 0; i < max_replication_slots; i++)
+	{
+		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
+
+		if (s->active_pid == MyProcPid)
+		{
+			Assert(s->in_use && s->data.persistency == RS_TEMPORARY);
+
+			ReplicationSlotDropPtr(s);
+		}
+	}
+}
+
+/*
  * Permanently drop replication slot identified by the passed in name.
  */
 void
@@ -419,14 +452,11 @@ ReplicationSlotDrop(const char *name)
 }
 
 /*
- * Permanently drop the currently acquired replication slot which will be
- * released by the point this function returns.
+ * Permanently drop the currently acquired replication slot.
  */
 static void
 ReplicationSlotDropAcquired(void)
 {
-	char		path[MAXPGPATH];
-	char		tmppath[MAXPGPATH];
 	ReplicationSlot *slot = MyReplicationSlot;
 
 	Assert(MyReplicationSlot != NULL);
@@ -434,6 +464,19 @@ ReplicationSlotDropAcquired(void)
 	/* slot isn't acquired anymore */
 	MyReplicationSlot = NULL;
 
+	ReplicationSlotDropPtr(slot);
+}
+
+/*
+ * Permanently drop the replication slot which will be released by the point
+ * this function returns.
+ */
+static void
+ReplicationSlotDropPtr(ReplicationSlot *slot)
+{
+	char		path[MAXPGPATH];
+	char		tmppath[MAXPGPATH];
+
 	/*
 	 * If some other backend ran this code concurrently with us, we might try
 	 * to delete a slot with a certain name while someone else was trying to
@@ -448,9 +491,9 @@ ReplicationSlotDropAcquired(void)
 	/*
 	 * Rename the slot directory on disk, so that we'll no longer recognize
 	 * this as a valid slot.  Note that if this fails, we've got to mark the
-	 * slot inactive before bailing out.  If we're dropping an ephemeral slot,
-	 * we better never fail hard as the caller won't expect the slot to
-	 * survive and this might get called during error handling.
+	 * slot inactive before bailing out.  If we're dropping an ephemeral or
+	 * a temporary slot, we better never fail hard as the caller won't expect
+	 * the slot to survive and this might get called during error handling.
 	 */
 	if (rename(path, tmppath) == 0)
 	{
@@ -469,7 +512,7 @@ ReplicationSlotDropAcquired(void)
 	}
 	else
 	{
-		bool		fail_softly = slot->data.persistency == RS_EPHEMERAL;
+		bool		fail_softly = slot->data.persistency != RS_PERSISTENT;
 
 		SpinLockAcquire(&slot->mutex);
 		slot->active_pid = 0;
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index f908761..1f1c56c 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -41,6 +41,7 @@ pg_create_physical_replication_slot(PG_FUNCTION_ARGS)
 {
 	Name		name = PG_GETARG_NAME(0);
 	bool		immediately_reserve = PG_GETARG_BOOL(1);
+	bool		temporary = PG_GETARG_BOOL(2);
 	Datum		values[2];
 	bool		nulls[2];
 	TupleDesc	tupdesc;
@@ -57,7 +58,8 @@ pg_create_physical_replication_slot(PG_FUNCTION_ARGS)
 	CheckSlotRequirements();
 
 	/* acquire replication slot, this will check for conflicting names */
-	ReplicationSlotCreate(NameStr(*name), false, RS_PERSISTENT);
+	ReplicationSlotCreate(NameStr(*name), false,
+						  temporary ? RS_TEMPORARY : RS_PERSISTENT);
 
 	values[0] = NameGetDatum(&MyReplicationSlot->data.name);
 	nulls[0] = false;
@@ -96,6 +98,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 {
 	Name		name = PG_GETARG_NAME(0);
 	Name		plugin = PG_GETARG_NAME(1);
+	bool		temporary = PG_GETARG_BOOL(2);
 
 	LogicalDecodingContext *ctx = NULL;
 
@@ -116,11 +119,14 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 
 	/*
 	 * Acquire a logical decoding slot, this will check for conflicting names.
-	 * Initially create it as ephemeral - that allows us to nicely handle
-	 * errors during initialization because it'll get dropped if this
+	 * Initially create persisent slot as ephemeral - that allows us to nicely
+	 * handle errors during initialization because it'll get dropped if this
 	 * transaction fails. We'll make it persistent at the end.
+	 * Temporary slots can be created as temporary from beginning as they get
+	 * dropped on error as well.
 	 */
-	ReplicationSlotCreate(NameStr(*name), true, RS_EPHEMERAL);
+	ReplicationSlotCreate(NameStr(*name), true,
+						  temporary ? RS_TEMPORARY : RS_EPHEMERAL);
 
 	/*
 	 * Create logical decoding context, to build the initial snapshot.
@@ -143,8 +149,9 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 	tuple = heap_form_tuple(tupdesc, values, nulls);
 	result = HeapTupleGetDatum(tuple);
 
-	/* ok, slot is now fully created, mark it as persistent */
-	ReplicationSlotPersist();
+	/* ok, slot is now fully created, mark it as persistent if needed */
+	if (!temporary)
+		ReplicationSlotPersist();
 	ReplicationSlotRelease();
 
 	PG_RETURN_DATUM(result);
@@ -174,7 +181,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 10
+#define PG_GET_REPLICATION_SLOTS_COLS 11
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	TupleDesc	tupdesc;
 	Tuplestorestate *tupstore;
@@ -219,6 +226,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 		Datum		values[PG_GET_REPLICATION_SLOTS_COLS];
 		bool		nulls[PG_GET_REPLICATION_SLOTS_COLS];
 
+		ReplicationSlotPersistency	persistency;
 		TransactionId xmin;
 		TransactionId catalog_xmin;
 		XLogRecPtr	restart_lsn;
@@ -246,6 +254,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 			namecpy(&plugin, &slot->data.plugin);
 
 			active_pid = slot->active_pid;
+			persistency = slot->data.persistency;
 		}
 		SpinLockRelease(&slot->mutex);
 
@@ -269,6 +278,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 		else
 			values[i++] = database;
 
+		values[i++] = BoolGetDatum(persistency == RS_TEMPORARY);
 		values[i++] = BoolGetDatum(active_pid != 0);
 
 		if (active_pid != 0)
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index aa42d59..b14d821 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -266,6 +266,8 @@ WalSndErrorCleanup(void)
 	if (MyReplicationSlot != NULL)
 		ReplicationSlotRelease();
 
+	ReplicationSlotCleanup();
+
 	replication_active = false;
 	if (walsender_ready_to_stop)
 		proc_exit(0);
@@ -796,18 +798,22 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 
 	if (cmd->kind == REPLICATION_KIND_PHYSICAL)
 	{
-		ReplicationSlotCreate(cmd->slotname, false, RS_PERSISTENT);
+		ReplicationSlotCreate(cmd->slotname, false,
+							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT);
 	}
 	else
 	{
 		CheckLogicalDecodingRequirements();
 
 		/*
-		 * Initially create the slot as ephemeral - that allows us to nicely
-		 * handle errors during initialization because it'll get dropped if
-		 * this transaction fails. We'll make it persistent at the end.
+		 * Initially create persisent slot as ephemeral - that allows us to
+		 * nicely handle errors during initialization because it'll get
+		 * dropped if this transaction fails. We'll make it persistent at the
+		 * end. Temporary slots can be created as temporary from beginning as
+		 * they get dropped on error as well.
 		 */
-		ReplicationSlotCreate(cmd->slotname, true, RS_EPHEMERAL);
+		ReplicationSlotCreate(cmd->slotname, true,
+							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL);
 	}
 
 	initStringInfo(&output_message);
@@ -841,15 +847,18 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		/* don't need the decoding context anymore */
 		FreeDecodingContext(ctx);
 
-		ReplicationSlotPersist();
+		if (!cmd->temporary)
+			ReplicationSlotPersist();
 	}
 	else if (cmd->kind == REPLICATION_KIND_PHYSICAL && cmd->reserve_wal)
 	{
 		ReplicationSlotReserveWal();
 
-		/* Write this slot to disk */
 		ReplicationSlotMarkDirty();
-		ReplicationSlotSave();
+
+		/* Write this slot to disk if it's permanent one. */
+		if (!cmd->temporary)
+			ReplicationSlotSave();
 	}
 
 	snprintf(xpos, sizeof(xpos), "%X/%X",
@@ -933,9 +942,6 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 
 	pq_endmessage(&buf);
 
-	/*
-	 * release active status again, START_REPLICATION will reacquire it
-	 */
 	ReplicationSlotRelease();
 }
 
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 83e9ca1..276261b 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -810,6 +810,9 @@ ProcKill(int code, Datum arg)
 	if (MyReplicationSlot != NULL)
 		ReplicationSlotRelease();
 
+	/* Also cleanup all the temporary slots. */
+	ReplicationSlotCleanup();
+
 	/*
 	 * Detach from any lock group of which we are a member.  If the leader
 	 * exist before all other group members, it's PGPROC will remain allocated
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index cc84754..b179231 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -3878,6 +3878,9 @@ PostgresMain(int argc, char *argv[],
 		if (MyReplicationSlot != NULL)
 			ReplicationSlotRelease();
 
+		/* We also want to cleanup temporary slots on error. */
+		ReplicationSlotCleanup();
+
 		/*
 		 * Now return to normal top-level context and clear ErrorContext for
 		 * next time.
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 047a1ce..e65bf9d 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -5176,13 +5176,13 @@ DATA(insert OID = 5016 (  spg_box_quad_leaf_consistent	PGNSP PGUID 12 1 0 0 0 f
 DESCR("SP-GiST support for quad tree over box");
 
 /* replication slots */
-DATA(insert OID = 3779 (  pg_create_physical_replication_slot PGNSP PGUID 12 1 0 0 0 f f f f t f v u 2 0 2249 "19 16" "{19,16,19,3220}" "{i,i,o,o}" "{slot_name,immediately_reserve,slot_name,xlog_position}" _null_ _null_ pg_create_physical_replication_slot _null_ _null_ _null_ ));
+DATA(insert OID = 3779 (  pg_create_physical_replication_slot PGNSP PGUID 12 1 0 0 0 f f f f t f v u 3 0 2249 "19 16 16" "{19,16,16,19,3220}" "{i,i,i,o,o}" "{slot_name,immediately_reserve,temporary,slot_name,xlog_position}" _null_ _null_ pg_create_physical_replication_slot _null_ _null_ _null_ ));
 DESCR("create a physical replication slot");
 DATA(insert OID = 3780 (  pg_drop_replication_slot PGNSP PGUID 12 1 0 0 0 f f f f t f v u 1 0 2278 "19" _null_ _null_ _null_ _null_ _null_ pg_drop_replication_slot _null_ _null_ _null_ ));
 DESCR("drop a replication slot");
-DATA(insert OID = 3781 (  pg_get_replication_slots	PGNSP PGUID 12 1 10 0 0 f f f f f t s s 0 0 2249 "" "{19,19,25,26,16,23,28,28,3220,3220}" "{o,o,o,o,o,o,o,o,o,o}" "{slot_name,plugin,slot_type,datoid,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn}" _null_ _null_ pg_get_replication_slots _null_ _null_ _null_ ));
+DATA(insert OID = 3781 (  pg_get_replication_slots	PGNSP PGUID 12 1 10 0 0 f f f f f t s s 0 0 2249 "" "{19,19,25,26,16,16,23,28,28,3220,3220}" "{o,o,o,o,o,o,o,o,o,o,o}" "{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn}" _null_ _null_ pg_get_replication_slots _null_ _null_ _null_ ));
 DESCR("information about replication slots currently in use");
-DATA(insert OID = 3786 (  pg_create_logical_replication_slot PGNSP PGUID 12 1 0 0 0 f f f f t f v u 2 0 2249 "19 19" "{19,19,25,3220}" "{i,i,o,o}" "{slot_name,plugin,slot_name,xlog_position}" _null_ _null_ pg_create_logical_replication_slot _null_ _null_ _null_ ));
+DATA(insert OID = 3786 (  pg_create_logical_replication_slot PGNSP PGUID 12 1 0 0 0 f f f f t f v u 3 0 2249 "19 19 16" "{19,19,16,25,3220}" "{i,i,i,o,o}" "{slot_name,plugin,temporary,slot_name,xlog_position}" _null_ _null_ pg_create_logical_replication_slot _null_ _null_ _null_ ));
 DESCR("set up a logical replication slot");
 DATA(insert OID = 3782 (  pg_logical_slot_get_changes PGNSP PGUID 12 1000 1000 25 0 f f f f f t v u 4 0 2249 "19 3220 23 1009" "{19,3220,23,1009,3220,28,25}" "{i,i,i,v,o,o,o}" "{slot_name,upto_lsn,upto_nchanges,options,location,xid,data}" _null_ _null_ pg_logical_slot_get_changes _null_ _null_ _null_ ));
 DESCR("get changes from replication slot");
diff --git a/src/include/nodes/replnodes.h b/src/include/nodes/replnodes.h
index d2f1edb..024b965 100644
--- a/src/include/nodes/replnodes.h
+++ b/src/include/nodes/replnodes.h
@@ -55,6 +55,7 @@ typedef struct CreateReplicationSlotCmd
 	char	   *slotname;
 	ReplicationKind kind;
 	char	   *plugin;
+	bool		temporary;
 	bool		reserve_wal;
 } CreateReplicationSlotCmd;
 
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index e00562d..b653e5c 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -28,7 +28,8 @@
 typedef enum ReplicationSlotPersistency
 {
 	RS_PERSISTENT,
-	RS_EPHEMERAL
+	RS_EPHEMERAL,
+	RS_TEMPORARY
 } ReplicationSlotPersistency;
 
 /*
@@ -165,6 +166,7 @@ extern void ReplicationSlotDrop(const char *name);
 
 extern void ReplicationSlotAcquire(const char *name);
 extern void ReplicationSlotRelease(void);
+extern void ReplicationSlotCleanup(void);
 extern void ReplicationSlotSave(void);
 extern void ReplicationSlotMarkDirty(void);
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 031e8c2..d680ea3 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1423,13 +1423,14 @@ pg_replication_slots| SELECT l.slot_name,
     l.slot_type,
     l.datoid,
     d.datname AS database,
+    l.temporary,
     l.active,
     l.active_pid,
     l.xmin,
     l.catalog_xmin,
     l.restart_lsn,
     l.confirmed_flush_lsn
-   FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn)
+   FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,
-- 
2.10.2

#117Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Peter Eisentraut (#116)
Re: Logical Replication WIP

On Sun, Dec 4, 2016 at 12:06 PM, Peter Eisentraut <
peter.eisentraut@2ndquadrant.com> wrote:

I massaged the temporary replication slot patch a bit. I changed the
column name in pg_stat_replication_slots from "persistent" to
"temporary" and flipped the logical sense, so that it is consistent with
the creation commands. I also adjusted some comments and removed some
changes in ReplicationSlotCreate() that didn't seem to do anything
useful (might have been from a previous patch).

The attached patch looks good to me.

Moved to next CF with "needs review" status.

Regards,
Hari Babu
Fujitsu Australia

#118Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Peter Eisentraut (#116)
Re: Logical Replication WIP

On 04/12/16 02:06, Peter Eisentraut wrote:

I massaged the temporary replication slot patch a bit. I changed the
column name in pg_stat_replication_slots from "persistent" to
"temporary" and flipped the logical sense, so that it is consistent with
the creation commands. I also adjusted some comments and removed some
changes in ReplicationSlotCreate() that didn't seem to do anything
useful (might have been from a previous patch).

The attached patch looks good to me.

I think that the removal of changes to ReplicationSlotAcquire() that you
did will result in making it impossible to reacquire temporary slot once
you switched to different one in the session as the if (active_pid != 0)
will always be true for temp slot.

--
Petr Jelinek 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

#119Andres Freund
andres@anarazel.de
In reply to: Peter Eisentraut (#113)
Re: Logical Replication WIP

On 2016-12-02 12:37:49 -0500, Peter Eisentraut wrote:

On 11/20/16 1:02 PM, Petr Jelinek wrote:

0001:
This is the reworked approach to temporary slots that I sent earlier.

Andres, you had expressed an interest in this. Will you be able to
review it soon?

Yep. Needed to get that WIP stuff about expression evaluation and JITing
out of the door first though.

Regards,

Andres

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

#120Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Petr Jelinek (#118)
Re: Logical Replication WIP

On 12/5/16 6:24 PM, Petr Jelinek wrote:

I think that the removal of changes to ReplicationSlotAcquire() that you
did will result in making it impossible to reacquire temporary slot once
you switched to different one in the session as the if (active_pid != 0)
will always be true for temp slot.

I see. I suppose it's difficult to get a test case for this.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#121Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Peter Eisentraut (#120)
1 attachment(s)
Re: Logical Replication WIP

On 12/6/16 11:58 AM, Peter Eisentraut wrote:

On 12/5/16 6:24 PM, Petr Jelinek wrote:

I think that the removal of changes to ReplicationSlotAcquire() that you
did will result in making it impossible to reacquire temporary slot once
you switched to different one in the session as the if (active_pid != 0)
will always be true for temp slot.

I see. I suppose it's difficult to get a test case for this.

I created a test case, saw the error of my ways, and added your code
back in. Patch attached.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

0001-Add-support-for-temporary-replication-slots.patchtext/x-patch; name=0001-Add-support-for-temporary-replication-slots.patchDownload
From b6c389b5ccd17a4c8a68d429048c2e7db34bfd51 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Thu, 8 Dec 2016 12:00:00 -0500
Subject: [PATCH] Add support for temporary replication slots

This allows creating temporary replication slots that are removed
automatically at the end of the session or on error.
---
 contrib/test_decoding/Makefile          |  2 +-
 contrib/test_decoding/expected/ddl.out  |  4 +-
 contrib/test_decoding/expected/slot.out | 58 +++++++++++++++++++++++++++
 contrib/test_decoding/sql/slot.sql      | 20 ++++++++++
 doc/src/sgml/func.sgml                  | 16 ++++++--
 doc/src/sgml/protocol.sgml              | 13 ++++++-
 src/backend/catalog/system_views.sql    | 11 ++++++
 src/backend/replication/repl_gram.y     | 22 +++++++----
 src/backend/replication/repl_scanner.l  |  1 +
 src/backend/replication/slot.c          | 69 ++++++++++++++++++++++++++-------
 src/backend/replication/slotfuncs.c     | 24 ++++++++----
 src/backend/replication/walsender.c     | 28 +++++++------
 src/backend/storage/lmgr/proc.c         |  3 ++
 src/backend/tcop/postgres.c             |  3 ++
 src/include/catalog/pg_proc.h           |  6 +--
 src/include/nodes/replnodes.h           |  1 +
 src/include/replication/slot.h          |  4 +-
 src/test/regress/expected/rules.out     |  3 +-
 18 files changed, 237 insertions(+), 51 deletions(-)
 create mode 100644 contrib/test_decoding/expected/slot.out
 create mode 100644 contrib/test_decoding/sql/slot.sql

diff --git a/contrib/test_decoding/Makefile b/contrib/test_decoding/Makefile
index a6641f5040..d2bc8b8350 100644
--- a/contrib/test_decoding/Makefile
+++ b/contrib/test_decoding/Makefile
@@ -39,7 +39,7 @@ submake-test_decoding:
 
 REGRESSCHECKS=ddl xact rewrite toast permissions decoding_in_xact \
 	decoding_into_rel binary prepared replorigin time messages \
-	spill
+	spill slot
 
 regresscheck: | submake-regress submake-test_decoding temp-install
 	$(MKDIR_P) regression_output
diff --git a/contrib/test_decoding/expected/ddl.out b/contrib/test_decoding/expected/ddl.out
index a9ba615b5b..c104c4802d 100644
--- a/contrib/test_decoding/expected/ddl.out
+++ b/contrib/test_decoding/expected/ddl.out
@@ -702,7 +702,7 @@ SELECT pg_drop_replication_slot('regression_slot');
 
 /* check that the slot is gone */
 SELECT * FROM pg_replication_slots;
- slot_name | plugin | slot_type | datoid | database | active | active_pid | xmin | catalog_xmin | restart_lsn | confirmed_flush_lsn 
------------+--------+-----------+--------+----------+--------+------------+------+--------------+-------------+---------------------
+ slot_name | plugin | slot_type | datoid | database | temporary | active | active_pid | xmin | catalog_xmin | restart_lsn | confirmed_flush_lsn 
+-----------+--------+-----------+--------+----------+-----------+--------+------------+------+--------------+-------------+---------------------
 (0 rows)
 
diff --git a/contrib/test_decoding/expected/slot.out b/contrib/test_decoding/expected/slot.out
new file mode 100644
index 0000000000..5e6b70ba38
--- /dev/null
+++ b/contrib/test_decoding/expected/slot.out
@@ -0,0 +1,58 @@
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot_p', 'test_decoding');
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot_t', 'test_decoding', true);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT pg_drop_replication_slot('regression_slot_p');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot_p', 'test_decoding', false);
+ ?column? 
+----------
+ init
+(1 row)
+
+-- reconnect to clean temp slots
+\c
+SELECT pg_drop_replication_slot('regression_slot_p');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
+-- should fail because the temporary slot was dropped automatically
+SELECT pg_drop_replication_slot('regression_slot_t');
+ERROR:  replication slot "regression_slot_t" does not exist
+-- test switching between slots in a session
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot1', 'test_decoding', true);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot2', 'test_decoding', true);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT * FROM pg_logical_slot_get_changes('regression_slot1', NULL, NULL);
+ location | xid | data 
+----------+-----+------
+(0 rows)
+
+SELECT * FROM pg_logical_slot_get_changes('regression_slot2', NULL, NULL);
+ location | xid | data 
+----------+-----+------
+(0 rows)
+
diff --git a/contrib/test_decoding/sql/slot.sql b/contrib/test_decoding/sql/slot.sql
new file mode 100644
index 0000000000..3b0aecd6a8
--- /dev/null
+++ b/contrib/test_decoding/sql/slot.sql
@@ -0,0 +1,20 @@
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot_p', 'test_decoding');
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot_t', 'test_decoding', true);
+
+SELECT pg_drop_replication_slot('regression_slot_p');
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot_p', 'test_decoding', false);
+
+-- reconnect to clean temp slots
+\c
+
+SELECT pg_drop_replication_slot('regression_slot_p');
+
+-- should fail because the temporary slot was dropped automatically
+SELECT pg_drop_replication_slot('regression_slot_t');
+
+
+-- test switching between slots in a session
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot1', 'test_decoding', true);
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot2', 'test_decoding', true);
+SELECT * FROM pg_logical_slot_get_changes('regression_slot1', NULL, NULL);
+SELECT * FROM pg_logical_slot_get_changes('regression_slot2', NULL, NULL);
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index eca98dfd34..0f9c9bf129 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -18465,7 +18465,7 @@ <title>Replication <acronym>SQL</acronym> Functions</title>
         <indexterm>
          <primary>pg_create_physical_replication_slot</primary>
         </indexterm>
-        <literal><function>pg_create_physical_replication_slot(<parameter>slot_name</parameter> <type>name</type> <optional>, <parameter>immediately_reserve</> <type>boolean</> </optional>)</function></literal>
+        <literal><function>pg_create_physical_replication_slot(<parameter>slot_name</parameter> <type>name</type> <optional>, <parameter>immediately_reserve</> <type>boolean</>, <parameter>temporary</> <type>boolean</></optional>)</function></literal>
        </entry>
        <entry>
         (<parameter>slot_name</parameter> <type>name</type>, <parameter>xlog_position</parameter> <type>pg_lsn</type>)
@@ -18478,7 +18478,11 @@ <title>Replication <acronym>SQL</acronym> Functions</title>
         the <acronym>LSN</> is reserved on first connection from a streaming
         replication client. Streaming changes from a physical slot is only
         possible with the streaming-replication protocol &mdash;
-        see <xref linkend="protocol-replication">. This function corresponds
+        see <xref linkend="protocol-replication">. The optional third
+        parameter, <parameter>temporary</>, when set to true, specifies that
+        the slot should not be permanently stored to disk and is only meant
+        for use by current session. Temporary slots are also
+        released upon any error. This function corresponds
         to the replication protocol command <literal>CREATE_REPLICATION_SLOT
         ... PHYSICAL</literal>.
        </entry>
@@ -18505,7 +18509,7 @@ <title>Replication <acronym>SQL</acronym> Functions</title>
         <indexterm>
          <primary>pg_create_logical_replication_slot</primary>
         </indexterm>
-        <literal><function>pg_create_logical_replication_slot(<parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type>)</function></literal>
+        <literal><function>pg_create_logical_replication_slot(<parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</> <type>boolean</></optional>)</function></literal>
        </entry>
        <entry>
         (<parameter>slot_name</parameter> <type>name</type>, <parameter>xlog_position</parameter> <type>pg_lsn</type>)
@@ -18513,7 +18517,11 @@ <title>Replication <acronym>SQL</acronym> Functions</title>
        <entry>
         Creates a new logical (decoding) replication slot named
         <parameter>slot_name</parameter> using the output plugin
-        <parameter>plugin</parameter>.  A call to this function has the same
+        <parameter>plugin</parameter>. The optional third
+        parameter, <parameter>temporary</>, when set to true, specifies that
+        the slot should not be permanently stored to disk and is only meant
+        for use by current session. Temporary slots are also
+        released upon any error. A call to this function has the same
         effect as the replication protocol command
         <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
        </entry>
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 50cf527427..9ba147cae5 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -1434,7 +1434,7 @@ <title>Streaming Replication Protocol</title>
   </varlistentry>
 
   <varlistentry>
-   <term><literal>CREATE_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</> { <literal>PHYSICAL</> [ <literal>RESERVE_WAL</> ] | <literal>LOGICAL</> <replaceable class="parameter">output_plugin</> }
+   <term><literal>CREATE_REPLICATION_SLOT</literal> <replaceable class="parameter">slot_name</> [ <literal>TEMPORARY</> ] { <literal>PHYSICAL</> [ <literal>RESERVE_WAL</> ] | <literal>LOGICAL</> <replaceable class="parameter">output_plugin</> }
      <indexterm><primary>CREATE_REPLICATION_SLOT</primary></indexterm>
     </term>
     <listitem>
@@ -1465,6 +1465,17 @@ <title>Streaming Replication Protocol</title>
       </varlistentry>
 
       <varlistentry>
+       <term><literal>TEMPORARY</></term>
+       <listitem>
+        <para>
+         Specify that this replication slot is a temporary one. Temporary
+         slots are not saved to disk and are automatically dropped on error
+         or when the session has finished.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
        <term><literal>RESERVE_WAL</></term>
        <listitem>
         <para>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index e011af122c..d57f1c9234 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -720,6 +720,7 @@ CREATE VIEW pg_replication_slots AS
             L.slot_type,
             L.datoid,
             D.datname AS database,
+            L.temporary,
             L.active,
             L.active_pid,
             L.xmin,
@@ -985,12 +986,22 @@ CREATE OR REPLACE FUNCTION pg_logical_slot_peek_binary_changes(
 
 CREATE OR REPLACE FUNCTION pg_create_physical_replication_slot(
     IN slot_name name, IN immediately_reserve boolean DEFAULT false,
+    IN temporary boolean DEFAULT false,
     OUT slot_name name, OUT xlog_position pg_lsn)
 RETURNS RECORD
 LANGUAGE INTERNAL
 STRICT VOLATILE
 AS 'pg_create_physical_replication_slot';
 
+CREATE OR REPLACE FUNCTION pg_create_logical_replication_slot(
+    IN slot_name name, IN plugin name,
+    IN temporary boolean DEFAULT false,
+    OUT slot_name text, OUT xlog_position pg_lsn)
+RETURNS RECORD
+LANGUAGE INTERNAL
+STRICT VOLATILE
+AS 'pg_create_logical_replication_slot';
+
 CREATE OR REPLACE FUNCTION
   make_interval(years int4 DEFAULT 0, months int4 DEFAULT 0, weeks int4 DEFAULT 0,
                 days int4 DEFAULT 0, hours int4 DEFAULT 0, mins int4 DEFAULT 0,
diff --git a/src/backend/replication/repl_gram.y b/src/backend/replication/repl_gram.y
index fd0fa6dde0..e75516c8d2 100644
--- a/src/backend/replication/repl_gram.y
+++ b/src/backend/replication/repl_gram.y
@@ -77,6 +77,7 @@ Node *replication_parse_result;
 %token K_LOGICAL
 %token K_SLOT
 %token K_RESERVE_WAL
+%token K_TEMPORARY
 
 %type <node>	command
 %type <node>	base_backup start_replication start_logical_replication
@@ -89,7 +90,7 @@ Node *replication_parse_result;
 %type <defelt>	plugin_opt_elem
 %type <node>	plugin_opt_arg
 %type <str>		opt_slot
-%type <boolval>	opt_reserve_wal
+%type <boolval>	opt_reserve_wal opt_temporary
 
 %%
 
@@ -183,24 +184,26 @@ base_backup_opt:
 			;
 
 create_replication_slot:
-			/* CREATE_REPLICATION_SLOT slot PHYSICAL RESERVE_WAL */
-			K_CREATE_REPLICATION_SLOT IDENT K_PHYSICAL opt_reserve_wal
+			/* CREATE_REPLICATION_SLOT slot TEMPORARY PHYSICAL RESERVE_WAL */
+			K_CREATE_REPLICATION_SLOT IDENT opt_temporary K_PHYSICAL opt_reserve_wal
 				{
 					CreateReplicationSlotCmd *cmd;
 					cmd = makeNode(CreateReplicationSlotCmd);
 					cmd->kind = REPLICATION_KIND_PHYSICAL;
 					cmd->slotname = $2;
-					cmd->reserve_wal = $4;
+					cmd->temporary = $3;
+					cmd->reserve_wal = $5;
 					$$ = (Node *) cmd;
 				}
-			/* CREATE_REPLICATION_SLOT slot LOGICAL plugin */
-			| K_CREATE_REPLICATION_SLOT IDENT K_LOGICAL IDENT
+			/* CREATE_REPLICATION_SLOT slot TEMPORARY LOGICAL plugin */
+			| K_CREATE_REPLICATION_SLOT IDENT opt_temporary K_LOGICAL IDENT
 				{
 					CreateReplicationSlotCmd *cmd;
 					cmd = makeNode(CreateReplicationSlotCmd);
 					cmd->kind = REPLICATION_KIND_LOGICAL;
 					cmd->slotname = $2;
-					cmd->plugin = $4;
+					cmd->temporary = $3;
+					cmd->plugin = $5;
 					$$ = (Node *) cmd;
 				}
 			;
@@ -276,6 +279,11 @@ opt_reserve_wal:
 			| /* EMPTY */					{ $$ = false; }
 			;
 
+opt_temporary:
+			K_TEMPORARY						{ $$ = true; }
+			| /* EMPTY */					{ $$ = false; }
+			;
+
 opt_slot:
 			K_SLOT IDENT
 				{ $$ = $2; }
diff --git a/src/backend/replication/repl_scanner.l b/src/backend/replication/repl_scanner.l
index f83ec538b6..9f50ce64a5 100644
--- a/src/backend/replication/repl_scanner.l
+++ b/src/backend/replication/repl_scanner.l
@@ -98,6 +98,7 @@ PHYSICAL			{ return K_PHYSICAL; }
 RESERVE_WAL			{ return K_RESERVE_WAL; }
 LOGICAL				{ return K_LOGICAL; }
 SLOT				{ return K_SLOT; }
+TEMPORARY			{ return K_TEMPORARY; }
 
 ","				{ return ','; }
 ";"				{ return ';'; }
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 0b2575ee9d..d8ed005e7e 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -47,6 +47,7 @@
 #include "storage/fd.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
+#include "utils/builtins.h"
 
 /*
  * Replication slot on-disk data structure.
@@ -98,7 +99,9 @@ int			max_replication_slots = 0;	/* the maximum number of replication
 										 * slots */
 
 static LWLockTranche ReplSlotIOLWLockTranche;
+
 static void ReplicationSlotDropAcquired(void);
+static void ReplicationSlotDropPtr(ReplicationSlot *slot);
 
 /* internal persistency functions */
 static void RestoreSlotFromDisk(const char *name);
@@ -329,7 +332,7 @@ ReplicationSlotAcquire(const char *name)
 {
 	ReplicationSlot *slot = NULL;
 	int			i;
-	int			active_pid = 0;
+	int			active_pid = 0; /* Keep compiler quiet */
 
 	Assert(MyReplicationSlot == NULL);
 
@@ -346,7 +349,7 @@ ReplicationSlotAcquire(const char *name)
 			SpinLockAcquire(&s->mutex);
 			active_pid = s->active_pid;
 			if (active_pid == 0)
-				s->active_pid = MyProcPid;
+				active_pid = s->active_pid = MyProcPid;
 			SpinLockRelease(&s->mutex);
 			slot = s;
 			break;
@@ -359,7 +362,7 @@ ReplicationSlotAcquire(const char *name)
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
 				 errmsg("replication slot \"%s\" does not exist", name)));
-	if (active_pid != 0)
+	if (active_pid != MyProcPid)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_IN_USE),
 				 errmsg("replication slot \"%s\" is active for PID %d",
@@ -389,9 +392,12 @@ ReplicationSlotRelease(void)
 		 */
 		ReplicationSlotDropAcquired();
 	}
-	else
+	else if (slot->data.persistency == RS_PERSISTENT)
 	{
-		/* Mark slot inactive.  We're not freeing it, just disconnecting. */
+		/*
+		 * Mark persistent slot inactive.  We're not freeing it, just
+		 * disconnecting.
+		 */
 		SpinLockAcquire(&slot->mutex);
 		slot->active_pid = 0;
 		SpinLockRelease(&slot->mutex);
@@ -406,6 +412,33 @@ ReplicationSlotRelease(void)
 }
 
 /*
+ * Cleanup all temporary slots created in current session.
+ */
+void
+ReplicationSlotCleanup()
+{
+	int			i;
+
+	Assert(MyReplicationSlot == NULL);
+
+	/*
+	 * No need for locking as we are only interested in slots active in
+	 * current process and those are not touched by other processes.
+	 */
+	for (i = 0; i < max_replication_slots; i++)
+	{
+		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
+
+		if (s->active_pid == MyProcPid)
+		{
+			Assert(s->in_use && s->data.persistency == RS_TEMPORARY);
+
+			ReplicationSlotDropPtr(s);
+		}
+	}
+}
+
+/*
  * Permanently drop replication slot identified by the passed in name.
  */
 void
@@ -419,14 +452,11 @@ ReplicationSlotDrop(const char *name)
 }
 
 /*
- * Permanently drop the currently acquired replication slot which will be
- * released by the point this function returns.
+ * Permanently drop the currently acquired replication slot.
  */
 static void
 ReplicationSlotDropAcquired(void)
 {
-	char		path[MAXPGPATH];
-	char		tmppath[MAXPGPATH];
 	ReplicationSlot *slot = MyReplicationSlot;
 
 	Assert(MyReplicationSlot != NULL);
@@ -434,6 +464,19 @@ ReplicationSlotDropAcquired(void)
 	/* slot isn't acquired anymore */
 	MyReplicationSlot = NULL;
 
+	ReplicationSlotDropPtr(slot);
+}
+
+/*
+ * Permanently drop the replication slot which will be released by the point
+ * this function returns.
+ */
+static void
+ReplicationSlotDropPtr(ReplicationSlot *slot)
+{
+	char		path[MAXPGPATH];
+	char		tmppath[MAXPGPATH];
+
 	/*
 	 * If some other backend ran this code concurrently with us, we might try
 	 * to delete a slot with a certain name while someone else was trying to
@@ -448,9 +491,9 @@ ReplicationSlotDropAcquired(void)
 	/*
 	 * Rename the slot directory on disk, so that we'll no longer recognize
 	 * this as a valid slot.  Note that if this fails, we've got to mark the
-	 * slot inactive before bailing out.  If we're dropping an ephemeral slot,
-	 * we better never fail hard as the caller won't expect the slot to
-	 * survive and this might get called during error handling.
+	 * slot inactive before bailing out.  If we're dropping an ephemeral or
+	 * a temporary slot, we better never fail hard as the caller won't expect
+	 * the slot to survive and this might get called during error handling.
 	 */
 	if (rename(path, tmppath) == 0)
 	{
@@ -469,7 +512,7 @@ ReplicationSlotDropAcquired(void)
 	}
 	else
 	{
-		bool		fail_softly = slot->data.persistency == RS_EPHEMERAL;
+		bool		fail_softly = slot->data.persistency != RS_PERSISTENT;
 
 		SpinLockAcquire(&slot->mutex);
 		slot->active_pid = 0;
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index f9087619d2..1f1c56cc21 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -41,6 +41,7 @@ pg_create_physical_replication_slot(PG_FUNCTION_ARGS)
 {
 	Name		name = PG_GETARG_NAME(0);
 	bool		immediately_reserve = PG_GETARG_BOOL(1);
+	bool		temporary = PG_GETARG_BOOL(2);
 	Datum		values[2];
 	bool		nulls[2];
 	TupleDesc	tupdesc;
@@ -57,7 +58,8 @@ pg_create_physical_replication_slot(PG_FUNCTION_ARGS)
 	CheckSlotRequirements();
 
 	/* acquire replication slot, this will check for conflicting names */
-	ReplicationSlotCreate(NameStr(*name), false, RS_PERSISTENT);
+	ReplicationSlotCreate(NameStr(*name), false,
+						  temporary ? RS_TEMPORARY : RS_PERSISTENT);
 
 	values[0] = NameGetDatum(&MyReplicationSlot->data.name);
 	nulls[0] = false;
@@ -96,6 +98,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 {
 	Name		name = PG_GETARG_NAME(0);
 	Name		plugin = PG_GETARG_NAME(1);
+	bool		temporary = PG_GETARG_BOOL(2);
 
 	LogicalDecodingContext *ctx = NULL;
 
@@ -116,11 +119,14 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 
 	/*
 	 * Acquire a logical decoding slot, this will check for conflicting names.
-	 * Initially create it as ephemeral - that allows us to nicely handle
-	 * errors during initialization because it'll get dropped if this
+	 * Initially create persisent slot as ephemeral - that allows us to nicely
+	 * handle errors during initialization because it'll get dropped if this
 	 * transaction fails. We'll make it persistent at the end.
+	 * Temporary slots can be created as temporary from beginning as they get
+	 * dropped on error as well.
 	 */
-	ReplicationSlotCreate(NameStr(*name), true, RS_EPHEMERAL);
+	ReplicationSlotCreate(NameStr(*name), true,
+						  temporary ? RS_TEMPORARY : RS_EPHEMERAL);
 
 	/*
 	 * Create logical decoding context, to build the initial snapshot.
@@ -143,8 +149,9 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
 	tuple = heap_form_tuple(tupdesc, values, nulls);
 	result = HeapTupleGetDatum(tuple);
 
-	/* ok, slot is now fully created, mark it as persistent */
-	ReplicationSlotPersist();
+	/* ok, slot is now fully created, mark it as persistent if needed */
+	if (!temporary)
+		ReplicationSlotPersist();
 	ReplicationSlotRelease();
 
 	PG_RETURN_DATUM(result);
@@ -174,7 +181,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 10
+#define PG_GET_REPLICATION_SLOTS_COLS 11
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	TupleDesc	tupdesc;
 	Tuplestorestate *tupstore;
@@ -219,6 +226,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 		Datum		values[PG_GET_REPLICATION_SLOTS_COLS];
 		bool		nulls[PG_GET_REPLICATION_SLOTS_COLS];
 
+		ReplicationSlotPersistency	persistency;
 		TransactionId xmin;
 		TransactionId catalog_xmin;
 		XLogRecPtr	restart_lsn;
@@ -246,6 +254,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 			namecpy(&plugin, &slot->data.plugin);
 
 			active_pid = slot->active_pid;
+			persistency = slot->data.persistency;
 		}
 		SpinLockRelease(&slot->mutex);
 
@@ -269,6 +278,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 		else
 			values[i++] = database;
 
+		values[i++] = BoolGetDatum(persistency == RS_TEMPORARY);
 		values[i++] = BoolGetDatum(active_pid != 0);
 
 		if (active_pid != 0)
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index aa42d59610..b14d82153a 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -266,6 +266,8 @@ WalSndErrorCleanup(void)
 	if (MyReplicationSlot != NULL)
 		ReplicationSlotRelease();
 
+	ReplicationSlotCleanup();
+
 	replication_active = false;
 	if (walsender_ready_to_stop)
 		proc_exit(0);
@@ -796,18 +798,22 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 
 	if (cmd->kind == REPLICATION_KIND_PHYSICAL)
 	{
-		ReplicationSlotCreate(cmd->slotname, false, RS_PERSISTENT);
+		ReplicationSlotCreate(cmd->slotname, false,
+							  cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT);
 	}
 	else
 	{
 		CheckLogicalDecodingRequirements();
 
 		/*
-		 * Initially create the slot as ephemeral - that allows us to nicely
-		 * handle errors during initialization because it'll get dropped if
-		 * this transaction fails. We'll make it persistent at the end.
+		 * Initially create persisent slot as ephemeral - that allows us to
+		 * nicely handle errors during initialization because it'll get
+		 * dropped if this transaction fails. We'll make it persistent at the
+		 * end. Temporary slots can be created as temporary from beginning as
+		 * they get dropped on error as well.
 		 */
-		ReplicationSlotCreate(cmd->slotname, true, RS_EPHEMERAL);
+		ReplicationSlotCreate(cmd->slotname, true,
+							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL);
 	}
 
 	initStringInfo(&output_message);
@@ -841,15 +847,18 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 		/* don't need the decoding context anymore */
 		FreeDecodingContext(ctx);
 
-		ReplicationSlotPersist();
+		if (!cmd->temporary)
+			ReplicationSlotPersist();
 	}
 	else if (cmd->kind == REPLICATION_KIND_PHYSICAL && cmd->reserve_wal)
 	{
 		ReplicationSlotReserveWal();
 
-		/* Write this slot to disk */
 		ReplicationSlotMarkDirty();
-		ReplicationSlotSave();
+
+		/* Write this slot to disk if it's permanent one. */
+		if (!cmd->temporary)
+			ReplicationSlotSave();
 	}
 
 	snprintf(xpos, sizeof(xpos), "%X/%X",
@@ -933,9 +942,6 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 
 	pq_endmessage(&buf);
 
-	/*
-	 * release active status again, START_REPLICATION will reacquire it
-	 */
 	ReplicationSlotRelease();
 }
 
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 83e9ca15d1..276261bd7b 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -810,6 +810,9 @@ ProcKill(int code, Datum arg)
 	if (MyReplicationSlot != NULL)
 		ReplicationSlotRelease();
 
+	/* Also cleanup all the temporary slots. */
+	ReplicationSlotCleanup();
+
 	/*
 	 * Detach from any lock group of which we are a member.  If the leader
 	 * exist before all other group members, it's PGPROC will remain allocated
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index cc847548a9..b17923106a 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -3878,6 +3878,9 @@ PostgresMain(int argc, char *argv[],
 		if (MyReplicationSlot != NULL)
 			ReplicationSlotRelease();
 
+		/* We also want to cleanup temporary slots on error. */
+		ReplicationSlotCleanup();
+
 		/*
 		 * Now return to normal top-level context and clear ErrorContext for
 		 * next time.
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 047a1ce71c..e65bf9d1c1 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -5176,13 +5176,13 @@ DATA(insert OID = 5016 (  spg_box_quad_leaf_consistent	PGNSP PGUID 12 1 0 0 0 f
 DESCR("SP-GiST support for quad tree over box");
 
 /* replication slots */
-DATA(insert OID = 3779 (  pg_create_physical_replication_slot PGNSP PGUID 12 1 0 0 0 f f f f t f v u 2 0 2249 "19 16" "{19,16,19,3220}" "{i,i,o,o}" "{slot_name,immediately_reserve,slot_name,xlog_position}" _null_ _null_ pg_create_physical_replication_slot _null_ _null_ _null_ ));
+DATA(insert OID = 3779 (  pg_create_physical_replication_slot PGNSP PGUID 12 1 0 0 0 f f f f t f v u 3 0 2249 "19 16 16" "{19,16,16,19,3220}" "{i,i,i,o,o}" "{slot_name,immediately_reserve,temporary,slot_name,xlog_position}" _null_ _null_ pg_create_physical_replication_slot _null_ _null_ _null_ ));
 DESCR("create a physical replication slot");
 DATA(insert OID = 3780 (  pg_drop_replication_slot PGNSP PGUID 12 1 0 0 0 f f f f t f v u 1 0 2278 "19" _null_ _null_ _null_ _null_ _null_ pg_drop_replication_slot _null_ _null_ _null_ ));
 DESCR("drop a replication slot");
-DATA(insert OID = 3781 (  pg_get_replication_slots	PGNSP PGUID 12 1 10 0 0 f f f f f t s s 0 0 2249 "" "{19,19,25,26,16,23,28,28,3220,3220}" "{o,o,o,o,o,o,o,o,o,o}" "{slot_name,plugin,slot_type,datoid,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn}" _null_ _null_ pg_get_replication_slots _null_ _null_ _null_ ));
+DATA(insert OID = 3781 (  pg_get_replication_slots	PGNSP PGUID 12 1 10 0 0 f f f f f t s s 0 0 2249 "" "{19,19,25,26,16,16,23,28,28,3220,3220}" "{o,o,o,o,o,o,o,o,o,o,o}" "{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn}" _null_ _null_ pg_get_replication_slots _null_ _null_ _null_ ));
 DESCR("information about replication slots currently in use");
-DATA(insert OID = 3786 (  pg_create_logical_replication_slot PGNSP PGUID 12 1 0 0 0 f f f f t f v u 2 0 2249 "19 19" "{19,19,25,3220}" "{i,i,o,o}" "{slot_name,plugin,slot_name,xlog_position}" _null_ _null_ pg_create_logical_replication_slot _null_ _null_ _null_ ));
+DATA(insert OID = 3786 (  pg_create_logical_replication_slot PGNSP PGUID 12 1 0 0 0 f f f f t f v u 3 0 2249 "19 19 16" "{19,19,16,25,3220}" "{i,i,i,o,o}" "{slot_name,plugin,temporary,slot_name,xlog_position}" _null_ _null_ pg_create_logical_replication_slot _null_ _null_ _null_ ));
 DESCR("set up a logical replication slot");
 DATA(insert OID = 3782 (  pg_logical_slot_get_changes PGNSP PGUID 12 1000 1000 25 0 f f f f f t v u 4 0 2249 "19 3220 23 1009" "{19,3220,23,1009,3220,28,25}" "{i,i,i,v,o,o,o}" "{slot_name,upto_lsn,upto_nchanges,options,location,xid,data}" _null_ _null_ pg_logical_slot_get_changes _null_ _null_ _null_ ));
 DESCR("get changes from replication slot");
diff --git a/src/include/nodes/replnodes.h b/src/include/nodes/replnodes.h
index d2f1edbf0d..024b965a24 100644
--- a/src/include/nodes/replnodes.h
+++ b/src/include/nodes/replnodes.h
@@ -55,6 +55,7 @@ typedef struct CreateReplicationSlotCmd
 	char	   *slotname;
 	ReplicationKind kind;
 	char	   *plugin;
+	bool		temporary;
 	bool		reserve_wal;
 } CreateReplicationSlotCmd;
 
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index e00562d274..b653e5c196 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -28,7 +28,8 @@
 typedef enum ReplicationSlotPersistency
 {
 	RS_PERSISTENT,
-	RS_EPHEMERAL
+	RS_EPHEMERAL,
+	RS_TEMPORARY
 } ReplicationSlotPersistency;
 
 /*
@@ -165,6 +166,7 @@ extern void ReplicationSlotDrop(const char *name);
 
 extern void ReplicationSlotAcquire(const char *name);
 extern void ReplicationSlotRelease(void);
+extern void ReplicationSlotCleanup(void);
 extern void ReplicationSlotSave(void);
 extern void ReplicationSlotMarkDirty(void);
 
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 031e8c2ef5..d680ea3aa5 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1423,13 +1423,14 @@ pg_replication_slots| SELECT l.slot_name,
     l.slot_type,
     l.datoid,
     d.datname AS database,
+    l.temporary,
     l.active,
     l.active_pid,
     l.xmin,
     l.catalog_xmin,
     l.restart_lsn,
     l.confirmed_flush_lsn
-   FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn)
+   FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,
-- 
2.11.0

#122Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Peter Eisentraut (#121)
Re: Logical Replication WIP

On 08/12/16 20:16, Peter Eisentraut wrote:

On 12/6/16 11:58 AM, Peter Eisentraut wrote:

On 12/5/16 6:24 PM, Petr Jelinek wrote:

I think that the removal of changes to ReplicationSlotAcquire() that you
did will result in making it impossible to reacquire temporary slot once
you switched to different one in the session as the if (active_pid != 0)
will always be true for temp slot.

I see. I suppose it's difficult to get a test case for this.

I created a test case, saw the error of my ways, and added your code
back in. Patch attached.

Hi,

I am happy with this version, thanks for moving it forward.

--
Petr Jelinek 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

#123Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Petr Jelinek (#115)
2 attachment(s)
Re: Logical Replication WIP

Here is a "fixup" patch for
0002-Add-PUBLICATION-catalogs-and-DDL-v11.patch.gz with some minor fixes.

Two issues that should be addressed:

1. I think ALTER PUBLICATION does not need to require CREATE privilege
on the database. That should be easy to change.

2. By requiring only SELECT privilege to include a table in a
publication, someone could include a table without replica identity into
a publication and thus prevent updates to the table.

A while ago I had been working on a patch to create a new PUBLICATION
privilege for this purpose. I have attached the in-progress patch here.
We could either finish that up and include it, or commit your patch
initially with requiring superuser and then refine the permissions later.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

0001-fixup-Add-PUBLICATION-catalogs-and-DDL.patchtext/x-patch; name=0001-fixup-Add-PUBLICATION-catalogs-and-DDL.patchDownload
From 38aba08eb00ffc0b1f0d52a38864f825435a1694 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Thu, 8 Dec 2016 12:00:00 -0500
Subject: [PATCH] fixup! Add PUBLICATION catalogs and DDL

---
 doc/src/sgml/catalogs.sgml                | 35 ++++++++++-------------
 doc/src/sgml/ref/alter_publication.sgml   | 41 +++++++++------------------
 doc/src/sgml/ref/create_publication.sgml  | 46 +++++++++++++++++--------------
 doc/src/sgml/ref/drop_publication.sgml    |  6 ++--
 doc/src/sgml/ref/psql-ref.sgml            |  6 ++--
 src/backend/catalog/Makefile              |  2 +-
 src/backend/catalog/dependency.c          |  4 +--
 src/backend/catalog/objectaddress.c       | 25 +++++++----------
 src/backend/catalog/pg_publication.c      | 27 ++++++++++--------
 src/backend/commands/publicationcmds.c    | 12 ++++----
 src/backend/commands/tablecmds.c          |  9 +++---
 src/backend/parser/gram.y                 |  2 +-
 src/backend/utils/cache/relcache.c        |  2 +-
 src/backend/utils/cache/syscache.c        |  2 +-
 src/bin/pg_dump/pg_dump.c                 |  3 --
 src/bin/psql/tab-complete.c               |  8 +++---
 src/test/regress/expected/publication.out | 22 +++++++--------
 17 files changed, 115 insertions(+), 137 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index aacd4bcb23..5213aa4f5e 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -5149,12 +5149,11 @@ <title><structname>pg_publication</structname></title>
   </indexterm>
 
   <para>
-   The <structname>pg_publication</structname> catalog contains
-   all publications created in the database.
+   The catalog <structname>pg_publication</structname> contains all
+   publications created in the database.
   </para>
 
   <table>
-
    <title><structname>pg_publication</structname> Columns</title>
 
    <tgroup cols="4">
@@ -5179,7 +5178,7 @@ <title><structname>pg_publication</structname> Columns</title>
       <entry><structfield>pubname</structfield></entry>
       <entry><type>Name</type></entry>
       <entry></entry>
-      <entry>A unique, database-wide identifier for the publication.</entry>
+      <entry>Name of the publication</entry>
      </row>
 
      <row>
@@ -5202,26 +5201,25 @@ <title><structname>pg_publication</structname> Columns</title>
       <entry><structfield>pubinsert</structfield></entry>
       <entry><type>bool</type></entry>
       <entry></entry>
-      <entry>If true, INSERT operations are replicated for tables in the
-       publication.</entry>
+      <entry>If true, <command>INSERT</command> operations are replicated for
+       tables in the publication.</entry>
      </row>
 
      <row>
       <entry><structfield>pubupdate</structfield></entry>
       <entry><type>bool</type></entry>
       <entry></entry>
-      <entry>If true, UPDATE operations are replicated for tables in the
-       publication.</entry>
+      <entry>If true, <command>UPDATE</command> operations are replicated for
+       tables in the publication.</entry>
      </row>
 
      <row>
       <entry><structfield>pubdelete</structfield></entry>
       <entry><type>bool</type></entry>
       <entry></entry>
-      <entry>If true, DELETE operations are replicated for tables in the
-       publication.</entry>
+      <entry>If true, <command>DELETE</command> operations are replicated for
+       tables in the publication.</entry>
      </row>
-
     </tbody>
    </tgroup>
   </table>
@@ -5235,13 +5233,12 @@ <title><structname>pg_publication_rel</structname></title>
   </indexterm>
 
   <para>
-   The <structname>pg_publication_rel</structname> catalog contains
-   mapping between tables and publications in the database. This is many to
-   many mapping.
+   The catalog <structname>pg_publication_rel</structname> contains the
+   mapping between relations and publications in the database.  This is a
+   many-to-many mapping.
   </para>
 
   <table>
-
    <title><structname>pg_publication_rel</structname> Columns</title>
 
    <tgroup cols="4">
@@ -5255,21 +5252,19 @@ <title><structname>pg_publication_rel</structname> Columns</title>
     </thead>
 
     <tbody>
-
      <row>
       <entry><structfield>prpubid</structfield></entry>
       <entry><type>oid</type></entry>
       <entry><literal><link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.oid</literal></entry>
-      <entry>Publication reference.</entry>
+      <entry>Reference to publication</entry>
      </row>
 
      <row>
       <entry><structfield>prrelid</structfield></entry>
-      <entry><type>bool</type></entry>
+      <entry><type>oid</type></entry>
       <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
-      <entry>Relation reference.</entry>
+      <entry>Reference to relation</entry>
      </row>
-
     </tbody>
    </tgroup>
   </table>
diff --git a/doc/src/sgml/ref/alter_publication.sgml b/doc/src/sgml/ref/alter_publication.sgml
index c17666c97f..eb8cb07126 100644
--- a/doc/src/sgml/ref/alter_publication.sgml
+++ b/doc/src/sgml/ref/alter_publication.sgml
@@ -40,21 +40,20 @@ <title>Description</title>
 
   <para>
    The first variant of this command listed in the synopsis can change
-   all of the publication attributes specified in
-   <xref linkend="sql-createpublication">.
-   Attributes not mentioned in the command retain their previous settings.
-   Database superusers can change any of these settings for any role.
+   all of the publication properties specified in
+   <xref linkend="sql-createpublication">.  Properties not mentioned in the
+   command retain their previous settings.  Database superusers can change any
+   of these settings for any role.
   </para>
 
   <para>
-   The other variants of this command deal with table membership in the
-   publication. The <literal>SET TABLE</literal> clause will replace
-   the current list of tables in the publication with the specified one.
-   Similarly the <literal>ADD TABLE</literal> and
-   <literal>DROP TABLE</literal> will add and remove one or more table from
-   publication.
+   The other variants of this command deal with the table membership of the
+   publication.  The <literal>SET TABLE</literal> clause will replace the
+   list of tables in the publication with the specified one.
+   The <literal>ADD TABLE</literal> and
+   <literal>DROP TABLE</literal> will add and remove one or more tables from
+   the publication.
   </para>
-
  </refsect1>
 
  <refsect1>
@@ -65,8 +64,7 @@ <title>Parameters</title>
     <term><replaceable class="parameter">name</replaceable></term>
     <listitem>
      <para>
-      The name of an existing publication whose attributes are to be
-      altered.
+      The name of an existing publication whose definition is to be altered.
      </para>
     </listitem>
    </varlistentry>
@@ -80,9 +78,8 @@ <title>Parameters</title>
     <term><literal>NOPUBLISH DELETE</literal></term>
     <listitem>
      <para>
-      These clauses alter attributes originally set by
-      <xref linkend="SQL-CREATEPUBLICATION">. For more information, see the
-      <command>CREATE PUBLICATION</command> reference page.
+      These clauses alter properties originally set by
+      <xref linkend="SQL-CREATEPUBLICATION">.  See there for more information.
      </para>
     </listitem>
    </varlistentry>
@@ -95,16 +92,6 @@ <title>Parameters</title>
      </para>
     </listitem>
    </varlistentry>
-
-   <varlistentry>
-    <term><replaceable class="parameter">schema_name</replaceable></term>
-    <listitem>
-     <para>
-      Name of a schema.
-     </para>
-    </listitem>
-   </varlistentry>
-
   </variablelist>
  </refsect1>
 
@@ -124,7 +111,6 @@ <title>Examples</title>
 ALTER PUBLICATION mypublication ADD TABLE users, departments;
 </programlisting>
   </para>
-
  </refsect1>
 
  <refsect1>
@@ -144,5 +130,4 @@ <title>See Also</title>
    <member><xref linkend="sql-droppublication"></member>
   </simplelist>
  </refsect1>
-
 </refentry>
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index a6bd3602fa..87becb4c0a 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -16,7 +16,7 @@
 
  <refnamediv>
   <refname>CREATE PUBLICATION</refname>
-  <refpurpose>define new publication</refpurpose>
+  <refpurpose>define a new publication</refpurpose>
  </refnamediv>
 
  <refsynopsisdiv>
@@ -39,14 +39,14 @@ <title>Description</title>
 
   <para>
    <command>CREATE PUBLICATION</command> adds a new publication
-   into the current database. The publication name must be distinct from
+   into the current database.  The publication name must be distinct from
    the name of any existing publication in the current database.
   </para>
 
   <para>
-   A publication is essentially a group of tables intended for managing
-   logical replication.
-   </para>
+   A publication is essentially a group of tables whose data changes are
+   intended to be replicated through logical replication.
+  </para>
  </refsect1>
 
  <refsect1>
@@ -66,7 +66,7 @@ <title>Parameters</title>
     <term><literal>FOR TABLE</literal></term>
     <listitem>
      <para>
-      Specifies optional list of tables to add to the publication.
+      Specifies a list of tables to add to the publication.
      </para>
     </listitem>
    </varlistentry>
@@ -75,8 +75,8 @@ <title>Parameters</title>
     <term><literal>FOR ALL TABLES</literal></term>
     <listitem>
      <para>
-      Marks the publication as one that replicates changes for all
-      tables in the database, including the ones created in the future.
+      Marks the publication as one that replicates changes for all tables in
+      the database, including tables created in the future.
      </para>
     </listitem>
    </varlistentry>
@@ -124,8 +124,14 @@ <title>Parameters</title>
   <title>Notes</title>
 
   <para>
-   This operation itself does start replication. It only defines grouping
-   and filtering logic for future subscribers.
+   If neither <literal>FOR TABLE</literal> nor <literal>FOR ALL
+   TABLES</literal> is specified, then the publication starts out with an
+   empty set of tables.  That is useful if tables are to be added later.
+  </para>
+
+  <para>
+   The creation of a publication does not start replication.  It only defines
+   a grouping and filtering logic for future subscribers.
   </para>
 
   <para>
@@ -135,23 +141,23 @@ <title>Notes</title>
   </para>
 
   <para>
-   To add table to a publication, the invoking user must have
+   To add a table to a publication, the invoking user must have
    <command>SELECT</command> privilege on given table.  The
    <command>FOR ALL TABLES</command> clause requires superuser.
   </para>
 
   <para>
-   The tables added to a publication which publishes <command>UPDATE</command>
+   The tables added to a publication that publishes <command>UPDATE</command>
    and/or <command>DELETE</command> operations must have
-   <literal>REPLICA IDENTITY</> defined, otherwise these operations will not
-   be allowed on them.
+   <literal>REPLICA IDENTITY</> defined.  Otherwise those operations will be
+   disallowed on those tables.
   </para>
 
   <para>
-   For the <command>INSERT ... ON CONFLICT</> command, the publication will
-   publish resulting tuple operation. So depending of the outcome, it may
-   be published as either <command>INSERT</command> or
-   <command>UPDATE</command> or it may not be published at all.
+   For an <command>INSERT ... ON CONFLICT</> command, the publication will
+   publish the operation that actually results from the command.  So depending
+   of the outcome, it may be published as either <command>INSERT</command> or
+   <command>UPDATE</command>, or it may not be published at all.
   </para>
 
   <para>
@@ -171,12 +177,11 @@ <title>Examples</title>
   </para>
 
   <para>
-   Create an insert only publication:
+   Create an insert-only publication:
 <programlisting>
 CREATE PUBLICATION insert_only WITH (NOPUBLISH UPDATE, NOPUBLISH DELETE);
 </programlisting>
   </para>
-
  </refsect1>
 
  <refsect1>
@@ -196,5 +201,4 @@ <title>See Also</title>
    <member><xref linkend="sql-droppublication"></member>
   </simplelist>
  </refsect1>
-
 </refentry>
diff --git a/doc/src/sgml/ref/drop_publication.sgml b/doc/src/sgml/ref/drop_publication.sgml
index 6c0e3ba959..d05d522041 100644
--- a/doc/src/sgml/ref/drop_publication.sgml
+++ b/doc/src/sgml/ref/drop_publication.sgml
@@ -16,7 +16,7 @@
 
  <refnamediv>
   <refname>DROP PUBLICATION</refname>
-  <refpurpose>remove an existing publication</refpurpose>
+  <refpurpose>remove a publication</refpurpose>
  </refnamediv>
 
  <refsynopsisdiv>
@@ -29,7 +29,8 @@
   <title>Description</title>
 
   <para>
-   <command>DROP PUBLICATION</command> removes publications from the database.
+   <command>DROP PUBLICATION</command> removes an existing publication from
+   the database.
   </para>
 
   <para>
@@ -82,5 +83,4 @@ <title>See Also</title>
    <member><xref linkend="sql-alterpublication"></member>
   </simplelist>
  </refsect1>
-
 </refentry>
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 678b6f7fe5..93d024b4c7 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1604,11 +1604,11 @@ <title>Meta-Commands</title>
         <term><literal>\dRp[+] [ <link linkend="APP-PSQL-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
         <listitem>
         <para>
-        List replication publications.
+        Lists replication publications.
         If <replaceable class="parameter">pattern</replaceable> is
-        specified, only publications whose names match the pattern are
+        specified, only those publications whose names match the pattern are
         listed.
-        If <literal>+</literal> is appended to the command name, tables
+        If <literal>+</literal> is appended to the command name, the tables
         associated with each publication are shown as well.
         </para>
         </listitem>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 5866097dd7..73b514c04c 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -15,7 +15,7 @@ OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
        pg_constraint.o pg_conversion.o \
        pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
        pg_operator.o pg_proc.o pg_publication.o pg_range.o \
-	   pg_db_role_setting.o pg_shdepend.o pg_type.o storage.o toasting.o
+       pg_db_role_setting.o pg_shdepend.o pg_type.o storage.o toasting.o
 
 BKIFILES = postgres.bki postgres.description postgres.shdescription
 
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index d72c948342..4e5b68f8b9 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -166,8 +166,8 @@ static const Oid object_classes[] = {
 	ExtensionRelationId,		/* OCLASS_EXTENSION */
 	EventTriggerRelationId,		/* OCLASS_EVENT_TRIGGER */
 	PolicyRelationId,			/* OCLASS_POLICY */
-	PublicationRelationId,		/* OCLASS_PUBCLICATION */
-	PublicationRelRelationId,	/* OCLASS_PUBCLICATION_REL */
+	PublicationRelationId,		/* OCLASS_PUBLICATION */
+	PublicationRelRelationId,	/* OCLASS_PUBLICATION_REL */
 	TransformRelationId			/* OCLASS_TRANSFORM */
 };
 
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 8fb5c0a683..59249804bd 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1781,9 +1781,8 @@ get_object_address_usermapping(List *objname, List *objargs, bool missing_ok)
 }
 
 /*
- * Find the ObjectAddress for a publication relation.
- * The objname parameter is relation name while objargs contains publication
- * name.
+ * Find the ObjectAddress for a publication relation.  The objname parameter
+ * is the relation name; objargs contains the publication name.
  */
 static ObjectAddress
 get_object_address_publication_rel(List *objname, List *objargs,
@@ -2267,6 +2266,11 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 									format_type_be(targettypeid))));
 			}
 			break;
+		case OBJECT_PUBLICATION:
+			if (!pg_publication_ownercheck(address.objectId, roleid))
+				aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PUBLICATION,
+							   NameListToString(objname));
+			break;
 		case OBJECT_TRANSFORM:
 			{
 				TypeName   *typename = (TypeName *) linitial(objname);
@@ -2315,7 +2319,6 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 		case OBJECT_TSPARSER:
 		case OBJECT_TSTEMPLATE:
 		case OBJECT_ACCESS_METHOD:
-		case OBJECT_PUBLICATION:
 			/* We treat these object types as being owned by superusers */
 			if (!superuser_arg(roleid))
 				ereport(ERROR,
@@ -3278,16 +3281,8 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_PUBLICATION:
 			{
-				HeapTuple	tup;
-
-				tup = SearchSysCache1(PUBLICATIONOID,
-									  ObjectIdGetDatum(object->objectId));
-				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for publication %u",
-						 object->objectId);
-				appendStringInfo(&buffer, _("publicaton %s"),
-				   NameStr(((Form_pg_publication) GETSTRUCT(tup))->pubname));
-				ReleaseSysCache(tup);
+				appendStringInfo(&buffer, _("publication %s"),
+								 get_publication_name(object->objectId));
 				break;
 			}
 
@@ -4802,7 +4797,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid);
 
-				appendStringInfo(&buffer, _("%s in publication %s"),
+				appendStringInfo(&buffer, _("publication table %s in publication %s"),
 								 get_rel_name(prform->prrelid), pubname);
 
 				if (objname)
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index d77db2043f..e3560b7f94 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -1,9 +1,9 @@
 /*-------------------------------------------------------------------------
  *
  * publication.c
- *		publication C api manipulation
+ *		publication C API manipulation
  *
- * Copyright (c) 2015, PostgreSQL Global Development Group
+ * Copyright (c) 2016, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
  *		publication.c
@@ -60,23 +60,25 @@ check_publication_add_relation(Relation targetrel)
 	if (RelationGetForm(targetrel)->relkind != RELKIND_RELATION)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				 errmsg("only tables can be added to publication"),
-				 errdetail("%s is not a table",
-						   RelationGetRelationName(targetrel))));
+				 errmsg("\"%s\" is not a table",
+						RelationGetRelationName(targetrel)),
+				 errdetail("Only tables can be added to publications.")));
 
 	/* Can't be system table */
 	if (IsCatalogRelation(targetrel))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				 errmsg("only user tables can be added to publication"),
-				 errdetail("%s is a system table",
-						   RelationGetRelationName(targetrel))));
+				 errmsg("\"%s\" is a system table",
+						RelationGetRelationName(targetrel)),
+				 errdetail("System tables cannot be added to publications.")));
 
 	/* UNLOGGED and TEMP relations cannot be part of publication. */
 	if (!RelationNeedsWAL(targetrel))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				 errmsg("UNLOGGED and TEMP relations cannot be replicated")));
+				 errmsg("table \"%s\" cannot be replicated",
+						RelationGetRelationName(targetrel)),
+				 errdetail("Temporary and unlogged relations cannot be replicated.")));
 }
 
 /*
@@ -125,8 +127,8 @@ publication_add_relation(Oid pubid, Relation targetrel,
 			return InvalidObjectAddress;
 
 		ereport(ERROR,
-				(errcode(ERRCODE_UNDEFINED_OBJECT),
-				 errmsg("relation %s is already member of publication %s",
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("relation \"%s\" is already member of publication \"%s\"",
 						RelationGetRelationName(targetrel), pub->name)));
 	}
 
@@ -148,8 +150,9 @@ publication_add_relation(Oid pubid, Relation targetrel,
 	CatalogUpdateIndexes(rel, tup);
 	heap_freetuple(tup);
 
-	/* Add dependency on the publication */
 	ObjectAddressSet(myself, PublicationRelRelationId, prrelid);
+
+	/* Add dependency on the publication */
 	ObjectAddressSet(referenced, PublicationRelationId, pubid);
 	recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
 
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index ebdef06665..bf16b653b1 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -3,7 +3,7 @@
  * publicationcmds.c
  *		publication manipulation
  *
- * Copyright (c) 2015, PostgreSQL Global Development Group
+ * Copyright (c) 2016, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
  *		publicationcmds.c
@@ -240,8 +240,6 @@ CreatePublication(CreatePublicationStmt *stmt)
 
 	heap_close(rel, RowExclusiveLock);
 
-	CommandCounterIncrement();
-
 	return myself;
 }
 
@@ -320,10 +318,10 @@ AlterPublicationTables(AlterPublicationStmt *stmt, Relation rel,
 	/* Check that user is allowed to manipulate the publication tables. */
 	if (pubform->puballtables)
 		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				 errmsg("tables cannot be added to or dropped from FOR ALL TABLES publications"),
-				 errdetail("publication %s is defined as FOR ALL TABLES",
-						   NameStr(pubform->pubname))));
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("publication \"%s\" is defined as FOR ALL TABLES",
+						NameStr(pubform->pubname)),
+				 errdetail("Tables cannot be added to or dropped from FOR ALL TABLES publications.")));
 
 	Assert(list_length(stmt->tables) > 0);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index c9dbe1857f..a64e2ca2de 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -11341,11 +11341,12 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	 * UNLOGGED as UNLOGGED tables can't be published.
 	 */
 	if (!toLogged &&
-		list_length(GetRelationPublications(RelationGetRelid(rel))))
+		list_length(GetRelationPublications(RelationGetRelid(rel))) > 0)
 		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-				 errmsg("could not change table \"%s\" to unlogged because it is published",
-						RelationGetRelationName(rel))));
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("cannot change table \"%s\" to unlogged because it is part of a publication",
+						RelationGetRelationName(rel)),
+				 errdetail("Unlogged relations cannot be replicated.")));
 
 	/*
 	 * Check existing foreign key constraints to preserve the invariant that
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0e2aef1fb0..a7f220791a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -384,7 +384,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <list>	group_by_list
 %type <node>	group_by_item empty_grouping_set rollup_clause cube_clause
 %type <node>	grouping_sets_clause
-%type <node>    opt_publication_for_tables publication_for_tables
+%type <node>	opt_publication_for_tables publication_for_tables
 
 %type <list>	opt_fdw_options fdw_options
 %type <defelt>	fdw_option
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 96b6c8b09a..07fdca16cd 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -4698,7 +4698,7 @@ GetRelationPublicationActions(Relation relation)
 		relation->rd_pubactions = NULL;
 	}
 
-	/* Now save copy of the bitmap in the relcache entry. */
+	/* Now save copy of the actions in the relcache entry. */
 	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
 	relation->rd_pubactions = palloc(sizeof(PublicationActions));
 	memcpy(relation->rd_pubactions, pubactions, sizeof(PublicationActions));
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index c7a4127402..395d379be6 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -49,9 +49,9 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_proc.h"
-#include "catalog/pg_range.h"
 #include "catalog/pg_publication.h"
 #include "catalog/pg_publication_rel.h"
+#include "catalog/pg_range.h"
 #include "catalog/pg_rewrite.h"
 #include "catalog/pg_seclabel.h"
 #include "catalog/pg_shdepend.h"
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4bcde1bede..d173f59a74 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -3293,9 +3293,6 @@ getPublications(Archive *fout)
 
 	query = createPQExpBuffer();
 
-	if (g_verbose)
-		write_msg(NULL, "reading publications\n");
-
 	resetPQExpBuffer(query);
 
 	/* Get the publications. */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index d7216a06b8..d6b333a72d 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -960,7 +960,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"OWNED", NULL, NULL, THING_NO_CREATE},		/* for DROP OWNED BY ... */
 	{"PARSER", Query_for_list_of_ts_parsers, NULL, THING_NO_SHOW},
 	{"POLICY", NULL, NULL},
-	{"bPUBLICATION", NULL, NULL},
+	{"PUBLICATION", NULL, NULL},
 	{"ROLE", Query_for_list_of_roles},
 	{"RULE", "SELECT pg_catalog.quote_ident(rulename) FROM pg_catalog.pg_rules WHERE substring(pg_catalog.quote_ident(rulename),1,%d)='%s'"},
 	{"SCHEMA", Query_for_list_of_schemas},
@@ -2195,9 +2195,9 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 	/* Complete "CREATE PUBLICATION <name> [...] WITH" */
 	else if (HeadMatches2("CREATE", "PUBLICATION") && TailMatches2("WITH", "("))
-		COMPLETE_WITH_LIST6("REPLICATE_INSERT", "NOREPLICATE_INSERT",
-							"REPLICATE_UPDATE", "NOREPLICATE_UPDATE",
-							"REPLICATE_DELETE", "NOREPLICATE_DELETE");
+		COMPLETE_WITH_LIST2("PUBLISH", "NOPUBLISH");
+	else if (HeadMatches2("CREATE", "PUBLICATION") && TailMatches3("WITH", "(", MatchAny))
+		COMPLETE_WITH_LIST3("INSERT", "UPDATE", "DELETE");
 
 /* CREATE RULE */
 	/* Complete "CREATE RULE <sth>" with "AS ON" */
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index b281649bc8..ebbb8c0880 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -33,16 +33,16 @@ ALTER PUBLICATION testpub_foralltables WITH (publish update);
 CREATE TABLE testpub_tbl2 (id serial primary key, data text);
 -- fail - can't add to for all tables publication
 ALTER PUBLICATION testpub_foralltables ADD TABLE testpub_tbl2;
-ERROR:  tables cannot be added to or dropped from FOR ALL TABLES publications
-DETAIL:  publication testpub_foralltables is defined as FOR ALL TABLES
+ERROR:  publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL:  Tables cannot be added to or dropped from FOR ALL TABLES publications.
 -- fail - can't drop from all tables publication
 ALTER PUBLICATION testpub_foralltables DROP TABLE testpub_tbl2;
-ERROR:  tables cannot be added to or dropped from FOR ALL TABLES publications
-DETAIL:  publication testpub_foralltables is defined as FOR ALL TABLES
+ERROR:  publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL:  Tables cannot be added to or dropped from FOR ALL TABLES publications.
 -- fail - can't add to for all tables publication
 ALTER PUBLICATION testpub_foralltables SET TABLE pub_test.testpub_nopk;
-ERROR:  tables cannot be added to or dropped from FOR ALL TABLES publications
-DETAIL:  publication testpub_foralltables is defined as FOR ALL TABLES
+ERROR:  publication "testpub_foralltables" is defined as FOR ALL TABLES
+DETAIL:  Tables cannot be added to or dropped from FOR ALL TABLES publications.
 SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_foralltables';
        pubname        | puballtables 
 ----------------------+--------------
@@ -64,12 +64,12 @@ DROP TABLE testpub_tbl2;
 DROP PUBLICATION testpub_foralltables;
 -- fail - view
 CREATE PUBLICATION testpub_fortbl FOR TABLE testpub_view;
-ERROR:  only tables can be added to publication
-DETAIL:  testpub_view is not a table
+ERROR:  "testpub_view" is not a table
+DETAIL:  Only tables can be added to publications.
 CREATE PUBLICATION testpub_fortbl FOR TABLE testpub_tbl1, pub_test.testpub_nopk;
 -- fail - already added
 ALTER PUBLICATION testpub_fortbl ADD TABLE testpub_tbl1;
-ERROR:  relation testpub_tbl1 is already member of publication testpub_fortbl
+ERROR:  relation "testpub_tbl1" is already member of publication "testpub_fortbl"
 -- fail - already added
 CREATE PUBLICATION testpub_fortbl FOR TABLE testpub_tbl1;
 ERROR:  publication "testpub_fortbl" already exists
@@ -84,8 +84,8 @@ Tables:
 
 -- fail - view
 ALTER PUBLICATION testpub_default ADD TABLE testpub_view;
-ERROR:  only tables can be added to publication
-DETAIL:  testpub_view is not a table
+ERROR:  "testpub_view" is not a table
+DETAIL:  Only tables can be added to publications.
 ALTER PUBLICATION testpub_default ADD TABLE testpub_tbl1;
 ALTER PUBLICATION testpub_default SET TABLE testpub_tbl1;
 ALTER PUBLICATION testpub_default ADD TABLE pub_test.testpub_nopk;
-- 
2.11.0

0001-Add-PUBLICATION-privilege.patchtext/x-patch; name=0001-Add-PUBLICATION-privilege.patchDownload
From ce569a3f72fd04955e514585157cff207268a81c Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Wed, 5 Oct 2016 12:00:00 -0400
Subject: [PATCH] Add PUBLICATION privilege

---
 doc/src/sgml/ref/grant.sgml              | 15 ++++++++++--
 src/backend/catalog/aclchk.c             |  4 ++++
 src/backend/commands/publicationcmds.c   | 10 ++++++++
 src/backend/utils/adt/acl.c              |  9 +++++++
 src/bin/pg_dump/dumputils.c              |  2 ++
 src/include/nodes/parsenodes.h           |  3 ++-
 src/include/utils/acl.h                  |  5 ++--
 src/test/regress/expected/dependency.out | 22 +++++++++---------
 src/test/regress/expected/privileges.out | 40 ++++++++++++++++----------------
 src/test/regress/sql/dependency.sql      |  2 +-
 10 files changed, 75 insertions(+), 37 deletions(-)

diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml
index d8ca39f869..6b0fbb1ff4 100644
--- a/doc/src/sgml/ref/grant.sgml
+++ b/doc/src/sgml/ref/grant.sgml
@@ -21,7 +21,7 @@
 
  <refsynopsisdiv>
 <synopsis>
-GRANT { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER }
+GRANT { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER | PUBLICATION }
     [, ...] | ALL [ PRIVILEGES ] }
     ON { [ TABLE ] <replaceable class="PARAMETER">table_name</replaceable> [, ...]
          | ALL TABLES IN SCHEMA <replaceable class="PARAMETER">schema_name</replaceable> [, ...] }
@@ -276,6 +276,16 @@ <title>GRANT on Database Objects</title>
     </varlistentry>
 
     <varlistentry>
+     <term>PUBLICATION</term>
+     <listitem>
+      <para>
+       Allows the use of the specified table in a publication.  (See the
+       <xref linkend="sql-createpublication"> statement.)
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
      <term>CREATE</term>
      <listitem>
       <para>
@@ -531,12 +541,13 @@ <title>Notes</title>
             D -- TRUNCATE
             x -- REFERENCES
             t -- TRIGGER
+            p -- PUBLICATION
             X -- EXECUTE
             U -- USAGE
             C -- CREATE
             c -- CONNECT
             T -- TEMPORARY
-      arwdDxt -- ALL PRIVILEGES (for tables, varies for other objects)
+     arwdDxtp -- ALL PRIVILEGES (for tables, varies for other objects)
             * -- grant option for preceding privilege
 
         /yyyy -- role that granted this privilege
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index c12f31a376..3e60c5476b 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3249,6 +3249,8 @@ string_to_privilege(const char *privname)
 		return ACL_CREATE_TEMP;
 	if (strcmp(privname, "connect") == 0)
 		return ACL_CONNECT;
+	if (strcmp(privname, "publication") == 0)
+		return ACL_PUBLICATION;
 	if (strcmp(privname, "rule") == 0)
 		return 0;				/* ignore old RULE privileges */
 	ereport(ERROR,
@@ -3286,6 +3288,8 @@ privilege_to_string(AclMode privilege)
 			return "TEMP";
 		case ACL_CONNECT:
 			return "CONNECT";
+		case ACL_PUBLICATION:
+			return "PUBLICATION";
 		default:
 			elog(ERROR, "unrecognized privilege: %d", (int) privilege);
 	}
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index eb5436f72b..3e00414879 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -568,6 +568,8 @@ RemovePublicationRelById(Oid prid)
 /*
  * Gather all tables optinally filtered by schema name.
  * The gathered tables are locked in access share lock mode.
+ *
+ * TODO check table permissions/wait for code changes
  */
 static List *
 GatherTables(char *nspname)
@@ -651,9 +653,17 @@ GatherTableList(List *tables)
 		Relation	rel;
 		bool		recurse = interpretInhOption(rv->inhOpt);
 		Oid			myrelid;
+		AclResult	aclresult;
 
 		rel = heap_openrv(rv, AccessShareLock);
 		myrelid = RelationGetRelid(rel);
+
+		aclresult = pg_class_aclcheck(myrelid, GetUserId(), ACL_PUBLICATION);
+		if (aclresult != ACLCHECK_OK)
+			if (aclresult != ACLCHECK_OK)
+				aclcheck_error(aclresult, ACL_KIND_CLASS,
+							   RelationGetRelationName(rel));
+
 		/* don't throw error for "foo, foo" */
 		if (list_member_oid(relids, myrelid))
 		{
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index 025a99e55a..5ebd86a061 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -314,6 +314,9 @@ aclparse(const char *s, AclItem *aip)
 			case ACL_CONNECT_CHR:
 				read = ACL_CONNECT;
 				break;
+			case ACL_PUBLICATION_CHR:
+				read = ACL_PUBLICATION;
+				break;
 			case 'R':			/* ignore old RULE privileges */
 				read = 0;
 				break;
@@ -1625,6 +1628,8 @@ convert_priv_string(text *priv_type_text)
 		return ACL_CREATE_TEMP;
 	if (pg_strcasecmp(priv_type, "CONNECT") == 0)
 		return ACL_CONNECT;
+	if (pg_strcasecmp(priv_type, "PUBLICATION") == 0)
+		return ACL_PUBLICATION;
 	if (pg_strcasecmp(priv_type, "RULE") == 0)
 		return 0;				/* ignore old RULE privileges */
 
@@ -1721,6 +1726,8 @@ convert_aclright_to_string(int aclright)
 			return "TEMPORARY";
 		case ACL_CONNECT:
 			return "CONNECT";
+		case ACL_PUBLICATION:
+			return "PUBLICATION";
 		default:
 			elog(ERROR, "unrecognized aclright: %d", aclright);
 			return NULL;
@@ -2032,6 +2039,8 @@ convert_table_priv_string(text *priv_type_text)
 		{"REFERENCES WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_REFERENCES)},
 		{"TRIGGER", ACL_TRIGGER},
 		{"TRIGGER WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_TRIGGER)},
+		{"PUBLICATION", ACL_PUBLICATION},
+		{"PUBLICATION WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_PUBLICATION)},
 		{"RULE", 0},			/* ignore old RULE privileges */
 		{"RULE WITH GRANT OPTION", 0},
 		{NULL, 0}
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index cd1e8c4a68..c668ec7494 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -492,6 +492,8 @@ do { \
 			CONVERT_PRIV('a', "INSERT");
 			if (remoteVersion >= 70200)
 				CONVERT_PRIV('x', "REFERENCES");
+			if (remoteVersion >= 100000)
+				CONVERT_PRIV('p', "PUBLICATION");
 			/* rest are not applicable to columns */
 			if (subname == NULL)
 			{
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index f017ee4721..260c48d364 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -73,7 +73,8 @@ typedef uint32 AclMode;			/* a bitmask of privilege bits */
 #define ACL_CREATE		(1<<9)	/* for namespaces and databases */
 #define ACL_CREATE_TEMP (1<<10) /* for databases */
 #define ACL_CONNECT		(1<<11) /* for databases */
-#define N_ACL_RIGHTS	12		/* 1 plus the last 1<<x */
+#define ACL_PUBLICATION	(1<<12)
+#define N_ACL_RIGHTS	13		/* 1 plus the last 1<<x */
 #define ACL_NO_RIGHTS	0
 /* Currently, SELECT ... FOR [KEY] UPDATE/SHARE requires UPDATE privileges */
 #define ACL_SELECT_FOR_UPDATE	ACL_UPDATE
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index 6b25eb9101..8008cfef71 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -139,15 +139,16 @@ typedef ArrayType Acl;
 #define ACL_CREATE_CHR			'C'
 #define ACL_CREATE_TEMP_CHR		'T'
 #define ACL_CONNECT_CHR			'c'
+#define ACL_PUBLICATION_CHR		'p'
 
 /* string holding all privilege code chars, in order by bitmask position */
-#define ACL_ALL_RIGHTS_STR	"arwdDxtXUCTc"
+#define ACL_ALL_RIGHTS_STR	"arwdDxtXUCTcp"
 
 /*
  * Bitmasks defining "all rights" for each supported object type
  */
 #define ACL_ALL_RIGHTS_COLUMN		(ACL_INSERT|ACL_SELECT|ACL_UPDATE|ACL_REFERENCES)
-#define ACL_ALL_RIGHTS_RELATION		(ACL_INSERT|ACL_SELECT|ACL_UPDATE|ACL_DELETE|ACL_TRUNCATE|ACL_REFERENCES|ACL_TRIGGER)
+#define ACL_ALL_RIGHTS_RELATION		(ACL_INSERT|ACL_SELECT|ACL_UPDATE|ACL_DELETE|ACL_TRUNCATE|ACL_REFERENCES|ACL_TRIGGER|ACL_PUBLICATION)
 #define ACL_ALL_RIGHTS_SEQUENCE		(ACL_USAGE|ACL_SELECT|ACL_UPDATE)
 #define ACL_ALL_RIGHTS_DATABASE		(ACL_CREATE|ACL_CREATE_TEMP|ACL_CONNECT)
 #define ACL_ALL_RIGHTS_FDW			(ACL_USAGE)
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index 8e50f8ffbb..33b11c5732 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -19,7 +19,7 @@ DETAIL:  privileges for table deptest
 REVOKE SELECT ON deptest FROM GROUP regress_dep_group;
 DROP GROUP regress_dep_group;
 -- can't drop the user if we revoke the privileges partially
-REVOKE SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES ON deptest FROM regress_dep_user;
+REVOKE SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, PUBLICATION ON deptest FROM regress_dep_user;
 DROP USER regress_dep_user;
 ERROR:  role "regress_dep_user" cannot be dropped because some objects depend on it
 DETAIL:  privileges for table deptest
@@ -63,21 +63,21 @@ CREATE TABLE deptest (a serial primary key, b text);
 GRANT ALL ON deptest1 TO regress_dep_user2;
 RESET SESSION AUTHORIZATION;
 \z deptest1
-                                               Access privileges
- Schema |   Name   | Type  |                 Access privileges                  | Column privileges | Policies 
---------+----------+-------+----------------------------------------------------+-------------------+----------
- public | deptest1 | table | regress_dep_user0=arwdDxt/regress_dep_user0       +|                   | 
-        |          |       | regress_dep_user1=a*r*w*d*D*x*t*/regress_dep_user0+|                   | 
-        |          |       | regress_dep_user2=arwdDxt/regress_dep_user1        |                   | 
+                                                Access privileges
+ Schema |   Name   | Type  |                  Access privileges                   | Column privileges | Policies 
+--------+----------+-------+------------------------------------------------------+-------------------+----------
+ public | deptest1 | table | regress_dep_user0=arwdDxtp/regress_dep_user0        +|                   | 
+        |          |       | regress_dep_user1=a*r*w*d*D*x*t*p*/regress_dep_user0+|                   | 
+        |          |       | regress_dep_user2=arwdDxtp/regress_dep_user1         |                   | 
 (1 row)
 
 DROP OWNED BY regress_dep_user1;
 -- all grants revoked
 \z deptest1
-                                           Access privileges
- Schema |   Name   | Type  |              Access privileges              | Column privileges | Policies 
---------+----------+-------+---------------------------------------------+-------------------+----------
- public | deptest1 | table | regress_dep_user0=arwdDxt/regress_dep_user0 |                   | 
+                                            Access privileges
+ Schema |   Name   | Type  |              Access privileges               | Column privileges | Policies 
+--------+----------+-------+----------------------------------------------+-------------------+----------
+ public | deptest1 | table | regress_dep_user0=arwdDxtp/regress_dep_user0 |                   | 
 (1 row)
 
 -- table was dropped
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index f66b4432a1..fa47c3d0b3 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -1487,38 +1487,38 @@ set session role regress_user4;
 grant select on dep_priv_test to regress_user5;
 \dp dep_priv_test
                                           Access privileges
- Schema |     Name      | Type  |          Access privileges          | Column privileges | Policies 
---------+---------------+-------+-------------------------------------+-------------------+----------
- public | dep_priv_test | table | regress_user1=arwdDxt/regress_user1+|                   | 
-        |               |       | regress_user2=r*/regress_user1     +|                   | 
-        |               |       | regress_user3=r*/regress_user1     +|                   | 
-        |               |       | regress_user4=r*/regress_user2     +|                   | 
-        |               |       | regress_user4=r*/regress_user3     +|                   | 
-        |               |       | regress_user5=r/regress_user4       |                   | 
+ Schema |     Name      | Type  |          Access privileges           | Column privileges | Policies 
+--------+---------------+-------+--------------------------------------+-------------------+----------
+ public | dep_priv_test | table | regress_user1=arwdDxtp/regress_user1+|                   | 
+        |               |       | regress_user2=r*/regress_user1      +|                   | 
+        |               |       | regress_user3=r*/regress_user1      +|                   | 
+        |               |       | regress_user4=r*/regress_user2      +|                   | 
+        |               |       | regress_user4=r*/regress_user3      +|                   | 
+        |               |       | regress_user5=r/regress_user4        |                   | 
 (1 row)
 
 set session role regress_user2;
 revoke select on dep_priv_test from regress_user4 cascade;
 \dp dep_priv_test
                                           Access privileges
- Schema |     Name      | Type  |          Access privileges          | Column privileges | Policies 
---------+---------------+-------+-------------------------------------+-------------------+----------
- public | dep_priv_test | table | regress_user1=arwdDxt/regress_user1+|                   | 
-        |               |       | regress_user2=r*/regress_user1     +|                   | 
-        |               |       | regress_user3=r*/regress_user1     +|                   | 
-        |               |       | regress_user4=r*/regress_user3     +|                   | 
-        |               |       | regress_user5=r/regress_user4       |                   | 
+ Schema |     Name      | Type  |          Access privileges           | Column privileges | Policies 
+--------+---------------+-------+--------------------------------------+-------------------+----------
+ public | dep_priv_test | table | regress_user1=arwdDxtp/regress_user1+|                   | 
+        |               |       | regress_user2=r*/regress_user1      +|                   | 
+        |               |       | regress_user3=r*/regress_user1      +|                   | 
+        |               |       | regress_user4=r*/regress_user3      +|                   | 
+        |               |       | regress_user5=r/regress_user4        |                   | 
 (1 row)
 
 set session role regress_user3;
 revoke select on dep_priv_test from regress_user4 cascade;
 \dp dep_priv_test
                                           Access privileges
- Schema |     Name      | Type  |          Access privileges          | Column privileges | Policies 
---------+---------------+-------+-------------------------------------+-------------------+----------
- public | dep_priv_test | table | regress_user1=arwdDxt/regress_user1+|                   | 
-        |               |       | regress_user2=r*/regress_user1     +|                   | 
-        |               |       | regress_user3=r*/regress_user1      |                   | 
+ Schema |     Name      | Type  |          Access privileges           | Column privileges | Policies 
+--------+---------------+-------+--------------------------------------+-------------------+----------
+ public | dep_priv_test | table | regress_user1=arwdDxtp/regress_user1+|                   | 
+        |               |       | regress_user2=r*/regress_user1      +|                   | 
+        |               |       | regress_user3=r*/regress_user1       |                   | 
 (1 row)
 
 set session role regress_user1;
diff --git a/src/test/regress/sql/dependency.sql b/src/test/regress/sql/dependency.sql
index f5c45e4666..2bdd51532c 100644
--- a/src/test/regress/sql/dependency.sql
+++ b/src/test/regress/sql/dependency.sql
@@ -21,7 +21,7 @@ CREATE TABLE deptest (f1 serial primary key, f2 text);
 DROP GROUP regress_dep_group;
 
 -- can't drop the user if we revoke the privileges partially
-REVOKE SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES ON deptest FROM regress_dep_user;
+REVOKE SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, PUBLICATION ON deptest FROM regress_dep_user;
 DROP USER regress_dep_user;
 
 -- now we are OK to drop him
-- 
2.11.0

#124Erik Rijkers
er@xs4all.nl
In reply to: Peter Eisentraut (#123)
Re: Logical Replication WIP

On 2016-12-09 17:08, Peter Eisentraut wrote:

Your earlier 0001-Add-support-for-temporary-replication-slots.patch
could be applied instead of the similarly named, original patch by Petr.
(I used 19fcc0058ecc8e5eb756547006bc1b24a93cbb80 to apply this patch-set
to)

(And it was, by the way, pretty stable and running well.)

I'd like to get it running again but now I can't find a way to also
include your newer 0001-fixup-Add-PUBLICATION-catalogs-and-DDL.patch of
today.

How should these patches be applied (and at what level)?

20161208: 0001-Add-support-for-temporary-replication-slots__petere.patch
# petere
20161202: 0002-Add-PUBLICATION-catalogs-and-DDL-v11.patch # PJ
20161209: 0001-fixup-Add-PUBLICATION-catalogs-and-DDL.patch # petere
20161202: 0003-Add-SUBSCRIPTION-catalog-and-DDL-v11.patch # PJ
20161202:
0004-Define-logical-replication-protocol-and-output-plugi-v11.patch #
PJ
20161202: 0005-Add-logical-replication-workers-v11.patch # PJ
20161202:
0006-Add-separate-synchronous-commit-control-for-logical--v11.patch #
PJ

Could (one of) you give me a hint?

Thanks,

Erik Rijkers

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

#125Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Peter Eisentraut (#123)
6 attachment(s)
Re: Logical Replication WIP

On 09/12/16 17:08, Peter Eisentraut wrote:

Here is a "fixup" patch for
0002-Add-PUBLICATION-catalogs-and-DDL-v11.patch.gz with some minor fixes.

Thanks, merged.

Two issues that should be addressed:

1. I think ALTER PUBLICATION does not need to require CREATE privilege
on the database. That should be easy to change.

Right, I removed the check.

2. By requiring only SELECT privilege to include a table in a
publication, someone could include a table without replica identity into
a publication and thus prevent updates to the table.

A while ago I had been working on a patch to create a new PUBLICATION
privilege for this purpose. I have attached the in-progress patch here.
We could either finish that up and include it, or commit your patch
initially with requiring superuser and then refine the permissions later.

Hmm, good catch. I changed the SELECT privilege check to owner check for
now, that seems relatively reasonable.

I agree that we should eventually have special privilege for that
though. But then we also need to invent privileges for PUBLICATIONs
themselves for this to work reasonably as you need to be owner of
PUBLICATION to add tables right now, so having PUBLICATION privilege on
table does not seem to do an awful lot. Also I think if we add table
privilege for this it's probably better named as PUBLISH rather than
PUBLICATION but that's not really important.

Attached new version with your updates and rebased on top of the current
HEAD (the partitioning patch produced quite a few conflicts).

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

Attachments:

0001-Add-support-for-temporary-replication-slots-v12.patch.gzapplication/gzip; name=0001-Add-support-for-temporary-replication-slots-v12.patch.gzDownload
0002-Add-PUBLICATION-catalogs-and-DDL-v12.patch.gzapplication/gzip; name=0002-Add-PUBLICATION-catalogs-and-DDL-v12.patch.gzDownload
0003-Add-SUBSCRIPTION-catalog-and-DDL-v12.patch.gzapplication/gzip; name=0003-Add-SUBSCRIPTION-catalog-and-DDL-v12.patch.gzDownload
�&�KX0003-Add-SUBSCRIPTION-catalog-and-DDL-v12.patch�=mS�F���W���K ��,�`c�pB����sO9.J+�����F��pq��u��H#i�]0v������{�mz�{^�:�fl��;{����{�;q�A�s���~�������N{�����(dC>g�k�����nw�_����'1��~�?��������+����p��I�s�O�m���i|��:{�;���]��|����W�&��������l����<�
�~x|uv9:�8g��8At���c''o����:�"�%b��f�T��o���#��{l~
�1��� ���A���av����b1n��?
�X���>um�wc��C�I��G�Z���*��7��b6�2�oe~.~��Vp��
��<ty��R�#��q?����n�u>pTZ
,���;n�N?4�j����c��
;�����s�
��[��v����y1���.#��A�9q�����6fC �E�g��>����[QkHz�Dz4���K������4��!�xw��c~���:����P�-L���rg^A��n_��O_y\���d�����p����o'��F�n	x��������5���J�N�C����f+������ �.�oC�AQ�nO����|�`? �V%������Xk�����K
��_��@��;�@E@,H��g���w3��C�I���#��q�����9������^;�;�I�`;�4�
j�����[0T�-=��]�Q�T���X����(�����#�VSR��|������O
<����r�5��"{��ZEe��l����<�I
�����~����0��~��>���7l��h�-�`��`'�t
m�"1�U��N��5M�l�E4����{���T��%�]�-s�������I��a�vY�����mBh���KKX���aKZ�����B��7��`(����!��^K8!L6���������
X�4��~f�9h��<������Gj�hX�q8���bP�QA����]Fs�Nx��Y����� �����f�6�8xz��\g29a��q���}��j�pU���=����/��
+a���J��m5p����{�d��?aN�&���\'��������o6w;�v��)����:��z��^�Fw��
��t:���ui��8�;\g�+}�R_ ���?���(��3��1�i��xvx �x�&�3����w-�$�Bo��g�G�LT����_%;+2s9�������Z��=e@��������.���r���x@5���������jj�0�jj��g��i�q��I���^
��������B��+v0���<�z�/i+���A.Q����JkZ�r�0q��
DG"��-����HP ��0�?�K	����Rl�L%a���s�9n	ATj�,�0G��s0s���~qB��/6\Y:sk��,��������?�M4v�P���M-��C ^>�)s��S�x��a������I���U��ta��(t�n�ou%���O�;���:r��&>�����l��
b�D��OU�*x���	�g:������s��E�_��s8@-�L@|����*���E�o�E���=4~PH�H���;�G�=�X aY����WfO��#�;b���m��S�w����I��/�Hr1��3��8?=�R���L��fI�4�����I���+�9����Z�*$��|����(x�q��1�F��a�Pt����F�����Ix�L�}2�0��~8��t3���>���I��
�&�$",���;���4� J8�� �
m���h`5�xeG���BpO�#�D�0'�����<�K�/��x�p��w������G�l���M�O��(�T0&���D���&��2K�G~6f~�B���eu�dF��":7S
���Ue��B��t��
;^�@��;��3�������uj��2
K�Yn�y�N��=��"EY?eXaC=/2��n-bP������T������qz>:��Q�d�N��QZg�
G�o�3US�wDJ�H6P(��
&k�7+�|�A�
��/��0��e"b'��U�H�v@��:�!�c��FE�Q�{�'U����4�i���
M+HScE��������`M3�*B��.B�*[6�)�_�4�HT^;�Vk�[�x��C�w��'�9�&�c;%GV-+4Np�Z�����4�B��m��
�\���I~x��+4�������U6vB�����~�A�M;�
<���^����Y�U�S[�4��}�q�rbjy��.����l{�
���{�^�B=6�[+c���D|�_o����A���F���H0���5���N��mYT)U�p/uk)��*��m��U�J6$f�Y�J)���
W����6
���pp���h�3_���D���,��� ���5mFTfT)1����+�=��5l8_��H�C�.#������0V�����I�:W���0�_���+|.���
b7p�x�������)4}v(s
��!����G��0J	6@�m���m��dl�i�����uB���IL27���sd/)+�F6����������������O�{x:b�?�����0�1�D����;=?����!;9Jl�g6'�4o��Pe���s��\=?�m�U8�����.���*���"����(���({bb�],^y0��^z��:;W��]�*�XW\V1}	�����,�|��>JC�;<�of�w@�����Fl��%�,�@�>8�4������F����CbwSpE��I�2���{:O��B_�=�
�-a��KX�J?&���������qqh��W�7������$�������R��'Ye�\��	������cp|uz4:��M�:�al��O��	b��J�h�8��kV����YLF���H��
�BV_b~�G��|!��@�'��;l���4&��u�H�dL�	�V�����y�����dS�/p��X�����*��c����J.��w�����z?��1�tgT���EW�D�N]���k�q-4+�?��'�pOn �z�a��p����fK�����:�*m�4>�.eN����>0�	�i��'����;�qD�.@f9�AKb�#��8_��a-�@�����l�E�������3>��p�8R�~0�������X�N�A!L�a�k��-��+��X����d���;����j�yeOf�>��%��(���������y��9��W��WQh���I����Y���e����%N���������������|��*�������Fc��������TT	�L�Z;���X��p�T���]�0'������NJ��h<����M�hF83����g���0�wEm���~V��C6(7�a7[+W���2�
�`��20�+��_��4���de��r]Y�z#\��@$"�XP��������|Y���h��*%`^#��[Iq�}1���i,#�~Z�0bf&��)Bt���Kt5��%���u	�������,�UJ
c���L��z~qo������bH32���'�r��q��P���Ic��[���������5�����bNN]�v;�<i�X����Q�)9?p�`���qtK�y���J(�>n��j�]��m*)��������3�i������Iz�S���:sa�%�Tc��h&�Q_RZ_�Y�O,��`2jQ,�%O�[fE�/�e�8����������$��T.�'��r�������
 Y�S�%��
fv�]��0+����.L�"�s��1@	m�'D2$��l�U��Q��9�\�eg��������n���8y���������(b�e������N!���+~�6�l�W$
-��O��=���h�,6�"�)aZ��dh�s�������5c�mt-d��������X�����������k�����*���RZ�x�����+�1��\]Ua��c��'W���f�n��U-���z��>k51�3����^y���b�����f��s6
��e�����n�������e��U���T=��8cu5%�0���F������1V5����9@�lUq�c�d��l��BD�O	�-'�T��b47�	g�D6	^�}{62�`-Aa���P�:�j��[��l�a�������=nZ2����'�	����K}QJ�j���/�;A�����e�L��_��y��]��I���"�'�;s�.Z�@��
''�nw��6��L����t�a��5sm���.]��:d���Q
���J/���������FC������z�s4WSt6��I|��NH����]+���I�|u�[���-��e�t��7���o�%����)�l�l���{{m�l��&����2[���2�\#:Z�������m�=��4���77����
�0�T�z������cp�?�}ty���4|!4
��e��jX����6{o�����8K��}Y�m��'KW[lt�&��/o�3o�r�.Ce3'QW(���7CD
�
�i9l�����.�����.�cG��JK����H�R��m��K,��]hP���Z9R+����F���Me$��J��X�<L����N:��n������o����vG`��;��t#�<RI�|c�|}a>Qg���������M�j�%� 5����:q�B:����S{� �<��"�(O�y�R�W>8�kk��;�X~f�K��;�=���x��A���@�"�x��hg��?�g>G���y�7��������!����M������;��`-i��M�Ax ��=��.��i�^��!MHL�+-#6���i�/^FI�I�#�����/���p��	�&�F~�Sl\h)���x���O~����-p�.�}�Lr��98�$�e3�]�)+��1nk&��A��:�<�$�S�)ij��A8|�[VB�D������VF����@���b����y����)jm.CR���/^V��b��iF���C�[�y���b8���tx
\�������j���>'XM�]��7[����Ak��$����S~�������Z����
T�����L�?%��B~�#p�\�S��B�FA �,{����{&AR��1;$�8k[���:V�m���|Y+R�*u���`�%o���:���v�����c�:����l����]�XR��%mB�DnoW��S����-spN�U��83?��nAC�nY��E]��8T]��c�[�
�l�����i
��{�;]Rs���F�
�	^]G>����������1�����]|$�����;�h�md�������d��Z�[xS��VA$E"����?���?Z@����o�8�����a8�^`(����
u�V��I�����/w�7�o"k��������:^[�y����J�=P������������l�U`8������''N��m(�6q��z�G����o'��
B.;�`
�u7N��.�������c7�8><�89�����������5L����M��A����xV(/'ST	��E�zM��YJ���S�8��x
�����M���h8���x��n��|IW<���ZF��|��:u�t�����(%|H�d���O�d`��dywSN��2cC0��6_]�9e����6���~���#��zN��B2�:�\ �bC�_��x���q\d��t��q�R����v�����q�8N{�l�����x��+�P��6tsN�n�����4D��;����Y63~fW��d���b�x3?���`�������L�6M�Z����b��}!z���@���}��]e�>~,7���%
O�W`�C���6�m�����B�+����F���fFrU���]y���O��(���:�B�Mt���_���E2=��u�������TE��V�7����F���������4�^HI�gx�"A�����U��6^�35e���6���i���TXN�r��Fc��/o��!���H^�Zs�n����M���&��]�l�������lI���5�8�����������8��W5��$�����Y�R�D�?��v�{���/�<��.����>�T_S���w�!����2���_{�����5v~��{�`6��i��6I�jp����\��L0�rH����?�$miom��;s?�\���%iiii�����l�]����u��q�/��ONq������b��V����;�)�����H�Iq
g0�V���������N�z\>(��
��Lo�o�3DVNpH)5�E�v���Q5\e
�,������=��7�}�@��#��;����H�>�'�P\�]�(%D�� ���I+�`0��N���M�%��J[�����FG���T���4m ^����x@G��$$����x��-��%��1u�Dx��"���$�Uf��d�s�rn���LCU~J�M���SW�@SO�WT�k��z8�=(����T�H"R=�)��WPM~Q������������o�5��3I)��<��3��`�������<[*�>J��r���r4��
���C!���`eW�x�iiA� �o�HE����f�sZQL����O�+l���u��M*�vvV�\�;o���l�LI�L'\uK�
'dz0��#���8�G������d#��n�n��K�fa�N�9�'��A��<��� r�`����-�w'Q4�9��4���o��P���]^��������m�Yma�"$�Q�@�Dh����Y��n'����o��X���d�����_W7����$�8`\��8����9���C���S�O�H�U��9�h��X%nm �����t��
s("C�D��)���+T��8]����3���~��z&1/��(���X�w����6��$S].�+c�|r,SML=����b��>����BHr�P��,��jggo�g?t���n��UFi�W����)�b�2����M��9�.;;�[$]!'N����������N��]*�`����FN�)��SA��[�3��;��0Z���i�Io�R�c�R����%���t������d1����g���#vg�-�Ck��"p�O�hJ����NO��l�h\O��!)��G�;��0��Y��������J�,
���wc2�r�{K4#��n�x�z�mD��V�Vn#A�������'��=���i��r�P�PQ�����7�����<���";V?>���Yw�l�����w�>��n����
���6N�����V\VI�]�{^��7�jE�6�$*-���s*�_��7�����9�3�[��.���y�Y�y��S�v6>0O�"��Ag�!l�;g����O*k�:��Vpy$CW�S���FY+�HG����72^;,�^���Z�M��2]{���T�������
C*
z24.M��h�0��-T����E`vm�������y��)j@��E��o��c�W��p��aF <).^���!F�1vF�����y�r��>���E�"QI����]F�������s��A���Fj���6�
S�|W`�)��Z��������r�^��v&��E������v<�Y���	J�����3��rNo9/F��u������d�ogp����t��3��zrg�'�d_��=��D��4��"��'��9
�C���k�N��p����
��iVv�{�=�K\�D�w T8�/�I=�h��Q��q����i�B&�'.����	��es��FY�sUEDQd����t�cg�[F�N�>�?�mft�1K�x����=����;};j��Q?7��7:�|��9k��a7L��D����h��u���f���1�@5�����"�"�}G� ��gY�J�H6�6H�d�yuq�������9Z�����h�g��;��hQA���O�.��%��Q���A�67(���t����o*�;.�%��86��������*�����z��c�%2��b���&��8��>�=(��d��{H������{{���s�n�z!�v:���~�����L�3N���c�"�p�6���;�!�d�j_���c@��S�<�D4OX139��&��O�E�U�����+J���r2(�e�ZDg`L�D�p����[��h������Y�'��#![L?������\��J]>��+Iy���P
�p�I'NOK�B��?W_%��?A����_�����d��Q��{e�������
��Q:�o<2����&�����1G�v������@�iu��g{�b�xN��@6����?Y z&��W#L�&��r�B6�X���&�d+����!�d������E\����lE^=��-F�P��h���^ ����]��_���{{R;=��z~�2�rp�:�����v�	 m����u�t�}?
?������d1���������P����Jq��@
P��=U��4�A?H�$��~j���
���sUk�Vw��S����]\(�f����V�RNR����:�)��p����!+�j��
�VQ��?,Y����E�T�$r:�`�K�{	`��I����m��u4|P$���*Z�6�"Z1�l9�4�J�k����x8�
aq�*�+&;�3��2�����{�c�2*����)�����@h�o4���I0~�'�A�&hl���Dd���|�p.�FD������i���iE���D0��
���|��Z��6���hJ4�X�D��,K��c0�[U�l)>�
��-�'	F��JH����R�R_�f�i1���.�X����H��K��/���RS���<7�w��eo�{ftp��ww��"����5����]��JaI���`;�;���3��-]+��r�Z(�+�~5,m�������'��I������ �<m/����.w�p��]��W3"��syf����EA�~����xJu���X������n"��g1 ���Y�'��3^��l����hb�	�]�vB��H:(���14saw��n�������p<F>�jq�4�`v�G8��so�_�:���T&��}��+3��[a>�F�N�[����n����U�.�'%�p$�XS�^�S�v_k�Cd@Y���)�-WP��+�*�g�oz��f�i�-�3n�k9(�W<����4������$�6��������$3�u�����Go�@����bz��!l�a�x<(�l����V��dQQKb����J��d�+�K^��YF��������e
�
��H���?��������O�V���i�?>�t�fz���5L����*qyl��G[��uL�������ZW��R��
����7�DFA:Y���j���:D����U�����r=�T(x�G����q�������������o�II���<�o���{w���������z�����=�.���a�w<�����pE;���a��O{4����w��>��9M��(fx�?������h�?������+���,L�)�H�(d]�i��io���������r�10a����)T|H����o�Nz���/yD5�H�X�H|d3����h��<���r�����D�(�G���AW���a���Y7M��))��"�6}5!�e����r����,����wl���@�]$D������onJ�Z��A�n��L�?��]�E�%�����%��]����u$�q�0
�������[u�5�2����Z����k����-r�:�`���E�TV�on+�p��Y����
����{.=����7�B4?�1�C�5N�Y4�
'.���:�t\�X�@E�1���5�\��}������4G��;��F�����u��<��!�]'�����k]}�/L�����4XN02�m#�vWAR���!�\!��Fm�h��ZM��Sy�$VwJ��hD�B��`m{��f��! �g�~s�,��{�4a0y<Nt�������bE"!�iE���I���~|���"��!�&��%x���d�YHrLk����=f��k���V�f�h(ARs3Z^i��k����(�=�\/�d$J@���D�Oz��]����$�'XJz!�2>Q���^���d?�a���w���j~���7����p�#L��$���hB�pY�\�������M��F����B�2����z:8{����Cgj������8��������c�")��m�x���y��w���W���N��q�n�w}M��U��A����X�V	�F�t��a���u�\0���9*��A�w��`�/=z��W�/�����������S0����*��o*4��]�`�P��������<�LW���.�����t���h1������S�������,�@"`F�"��6��������l������=��*IF$g���F3K�W��7�Mh�pKy�z�n��2;�M~�C���b�v�e�YH��	I��O%u1�T���Y��m.��^�/��^S=mb����������<)����.�sQ�<��*X,�I���,5i~�\Q.ih(��/��	f����h5���(L���n���	�$l�oQ�=kt.�	�����G�i7���m0n�4����>����n�qJ�{��?��7�^5��q�����qQ]7��9�u�i��u���V7���^�Q��2]�N�I�jE�Q��w�����L��.�h�[�(����G�J�#�BN�s#V-�D���}#���\��O�1��(��{��D-*��|Ki	����fd|�o����2�El�*AO�OA,���-��
�����X������iWpf~��y����M�����t0�����p����.`%�y>>�5�E�Gd�sL�����9�sy�������LK��mC��}����>�F��D��]6N�����\yX���.��������������*���<���L
�6����D���>gi�����*����V��)���5��?f���?�I(�a>�u��f_�9��:|������F���A�������
��%#���8��k@#�������4����A�q�Jj�?��Qt�������������(p'���k�I����� 5O�6�=h80P��P/7���������D���%���0�n���qF%�����������h�����@�Y�Hf�#$�)0��I�|� ���������)�1��sL/D�"�s8 +�w�C�ag�r�S��������C�����,]D�o3L��������^����N]���To�Q������EyJ�����U���>�r���b����p� �F���.v\���6��v�V���Ba.k"[���h�����}yv����:��H95���mI�.��/�uA�1*"�w0?t���zra��&n|�J�%��;�e�_,TE`K	7�P�y������q��s~�:��k;������B%���,��P��%��Y�0u�}���f/����f0��]h�V�s����n�3�^���x>�wc��V|�z�]�j8)���O��/��F��mbdb`���<���������Y=���BM��p��^�������L�����T���>���;��b����|�P�QP#���\@g��������>
]�n�������O�M��E}�\B��\��&��#hb3:}��������y��b����s,7�g8����cy�58��1EO��{c�fHBj��D�T�g�A6|p)�-��HH���'�d�F�DF������7>�yT��\R�1]L]�����7Z.�0��,��4&z�*gif\���+��k
�K}�Q� ������Xp_/��!��
,��,�s�
���1it����?���ir�,���5�z�����n�1��RJ��R���c�;��K��^&������n�O�E�Kn��G�h05�2���m:��{���
z�i��+
��x�4�l8=6������s�R��9W8`	�T�������w�.�.���B!"3��?��aSfl?�	�-,�`�'b��	��7�c`�J8�1�t,���7`fR%Cz��:��p��vl1����~��*�����������;��>_����l����WNz�>o�}_�������[���n4���6y�������w�I��7���X�����Y�1y���+��7P9s����U�4����p��cP�^���<)��N\���:Z0�+���sKF�V8����J���n��.0�d��q! 7D�<�(O=j#="R]����}
}�c��zo���G�o��Y�����3�y��>���r,
���pdM?�pX@�c��h�	=C�ev6�1`]�������VB`��z~r�&�9(��W��;s�L����U]	xH�Kd��;��`(!4U�7�a����Z�(i��a[Lv}(�wY&�X��l�,Jr>W�&��2���4���� 1X�B16�^�a�@���'��n�Z��Nl/��1���i!'��� �������}���1��7;V�o�����i��l	c��w�bk���,�������	I�����ean��R#2F���rL��������4��o4���]���G�H���j�5}����J#m�L������'���r��6L�G�6LL�9������� 
���R�p���6rN��(��Er���7��eH|lE��[��d�e�]<
vb� ��6x�!u��r�v��=j��-�f����f�UD:��C��E�����Y���n��j\���j�/���So�#���+�p�%��kc0�*k��wYA_�L_�����l%�2JxP�-�9�>o��[����)�����/����#��Y����������a\�P�������#����[@�����Tcq+��)�a�T��)�4%|'��dbR"���>�c�N���M��7"K��'&��:�2	�=p�6�����U�}K���/��-Lw��3/n1���5�^�!�V�zv��|W�8w����c��u�9�(�P����g��|u���O� /q��Tq�w�Y'Z`���.���5/�k��*�)�8�����+U�K�E�m�o'�qNYYY������<��	,��ZU��3�����:c�F����iSK9N����,y�����#�I
��2+��#98�+��a�������[ ���YE	��� +��*��!�K<
y����C$���C���g/og�}�!A��<9:�R�t���{���x�SZ�v��p�����tG��5v��\b+�/g(��i~h�cbk��\'V<�`<T����}�!4���Y4�n�������qe��v��eu,�Q,�B7<�;;����J�����q�3?	�-<�&?�����9!�;����t2W����DR,�!{��"����.�Q�0d�^��0_��Ys������mV�y��A���	��Eg�������v6./2Y
-�%	�4��YJ�����-P���w[���������-�{�B��.���0ep��
~U��d�I������1^�*+��=�O��]z����p�����V�Q�wS����l��b�"F��;(�Y�[��K�@��?�N�[�nJ�_�=f�+�2}�l�������w
u��pl��4}�����0!�����8%���i����jNX)�d�����h�B���;�����f���!F���rp��G5��v���u�q�Q��>Z�����Zgo�����=��M����\����8~4_���j�.������6�����M�B����T�
p7������V�q��K��5����_�..��W�k(_�����v���ujW����U��Z��u��u�	��N���7��o�����k��j`�a�r��7�3��`~���c����8���{
4��]��?��{�>�����e��2�k����G��F����*(}M.��~�.�~!sg�@�M�
!5`��z���
}|^`�g���G����2%����n���������������������m�F_�d9�uZ���<L���f5t�R^�@@Yn���T<C���[�$9�=���|8�;xd��Mb:K�������D(zz���E ���Z�q�H�����L������1�[�=)r�M�<N��{g\rPJ���P������������z���Q�iW���)����U��������vm���rv��l��&l3`�=>��At��oTnbp������l�8���J�-�!����<$�������=�TrqC���~��A�B����c��������,�'�/�< hg��,�h|���"`V����D �x�h�/�����B���7�Y���z.�,����5��Z����0�NI����P����c.��hG���C���N^�%	��p���Z��?O��G?��R}����G$���������,��p�C��)��y��6^�.���CN�?�( ]Y����+U��Dm8	�1��z�D������x5���|���1�������}����U
��3��+��	���C=�j5k�����#�%��Owr�OWwwX�W4d�H�j�q�&�!��'�@R�I�Y���]Y���m�r�����a�u���sK���o��5�� �����_^w~�}��	�H�P1��
A�?��[�|�n�G�P��3`d^8�����0P���J��3J�%�#n�����.|���RLB-�J"����� ������H�DY��N���JYzP=X��_,2�~^wMO�����(�.B#�����7)��R�5���zxdmy�
N�j��P8(���B����/_���oc�4F��Lm*A�%`��D��PuiN��3t/r�<�n����q��`���KE�w?�D��zbq���E�%�s2g!�����ZQ�L�	j�p�PG6�lc��1Yb���rE?dsy����6/�P=��_���*�k�KW��(Z���a*c_S5����������ncK��S����F������e��;�0��n�[�����,����p�>�|F��i��	v@OV�����a��<���*D�H��c�~�B��V7�/��-p`��i5��G@�����2�0GLtk�$�`dOz�X�/��Jbc�i.m}K�\o�NV��g-����3D�~Mo���1������t�3����h��99o����%a���{����vq{O;�
��el��vT"!{��>��`))�{`�=��N.\w��G'����O�3*��Sf�X��Xu����0>NH��>�����.�6Q;�a������E��2�3���D��H3��������9������}C_��������h���l�l�S���S���$��b%2��AE
���_c"Z*WP�l�T9�WH����K�N�[������%�Q��p9�)>�j�?J�k�������o~��dw�B��R��)��]��1�sI�I4��v�?�������1��O%g���>����v��-��n�=T����������1J'Ux���Z���&���2>����DV��2���T�0�6`����Q��H/��Q��v�dc�G��=�`���=Y�o���!KJ�@���e���q�'�q;b�Iu����d|�����}��U�����_���[@F��@u���e|����2�j\��%\���!iM�6}�l��V�Qo�����P>�6��5���G��e	�>�pu�lNvE����[Du3�7������A�U�}\0S���p�^���Q-��_@A��C�����"xx�����Ym���d�v�E4�qK:�K4�c�D���:`o
W�8���������@iq�O���;�Db��$�M���[��0�v�T�8�$�l���<{2�T�����p
B��#H��������,X��,9�O3�&�csL�w�����]2��k!g�jz���X&�i�q/XyH^R��PdY�[f������!��tE,��~��g�&����e���V�H��H��Y�X�i����+�
]�����5�j�m��a�}�����)���e����f�w*�*��[�v�ss}������'Y�����a4,G(b�3`Cn�
�q%b�����U��+�,�s,����,�>��6i�6^2g����
�[	S����fG��V
\����*�,G������lj�ZNt��B�cbiJ���j����pTd���9�
��"#l���U~�
>�><n;l������������K�g�~�i��k�����
�}"�~��
����`p|����1�����tR��� q��..��D��������c�5����c ��~���s���e���X�}{���daN;�)��gs��c]`��p��S��8�����
���|��&w!�������
q�'$��!5���"-� ~w�No�����}8��-r���7��!N �m"�l?�j�
���wx�����k�7j���Lf @f�U�xE|qc�����Vep�:��L=I��R&��A{���������~^����'�@�
O�R�%!�%N�/]~AK��1"�-^����3�x�3��K��E�I���w�G��j�IG0�@��b�sn��:�p	�i\u�]��u�)�8@���kDq�V��-���%�t�%7EU�LMJ�J��B���WX�%���g��N��4��X��a�I�M���@� �m�����i�0SJ���J+��Ml1W�S��\�(>�����#i�H��w��\<(1KVE��x�/c��U�����C61���|����,Br7<�q�!Gbd����$�r�^���#���4i�o`���C�o=:���8k;*���9�����Z
���g�5���l����O.���%8:'�	*�r����IR=������k�
yq�<JNJ�������_��e���=�
3>'l�EheW��b-�8QE�`S�nf/�]K��;�nal�V����Z���< ����4:v%�����m�6W���LT���q�D�+�9}d��K���f�HC��'�C�[O��(Z�`)u�$��&�Cs�����o���?=��8�|*����I#O�Et{�t���q�\7����%���������x����(��?�T
(����C�����lV��]��,n6c��b	���5(U��b��P8�
����Y�0i!� �D�zuXM���HT����?V���e����������]���	�
��2kE��^Q�$��-�T*:��,l@W�x���<���p���oJY�Mn�����S�����g�����{���puj�����Qm�����	���H��T[�CLZ��8���r�I+�b�R�h��"�y���z��������[��*������)��	>I��gE.���+yIm�5���^�)��"�\�����w0���d������]k�]����>���-K^6���{��y�D�*��m=����x���X�;�t����+�^�\v������K���V���u�}8�����h����m�q����E6o-��M��-z�^�y�s.�����������A��P8,�U{������UP���BV���	��gF�eg�+N�s ��C�zW��Tk8����Q������|�8up�+a7CY�������'����1.(� d8�&n%��Zn-v
	'�D����/��`�5MK��H`xc��K=�:@�����<�C�v��V�V�y��,�!fv]m�[s';����t�������������	����~�����F�d��=����>l����`��l__ek����x*�^�>�n��x��j���`���V_����3:�������cm#���E��Y�������m:��%K�� s���Il���~������)�Mv������=l�^d���8�=g~����1ze_9����r9-�y���������`X(z������$�������������ag��;�4&��M�^�_8PH=�
����#��S�
�p7��)lP5��(b��/X\��|�Y���5{NM�#�N�a_�O<������8�&9�U���HuOJr�N��;����Z����K���f��H�*,]U^���������a.1X���Kpj����fL:{
�����V�D����T�M Q����}��*�����8�7�������>����S�����������''�B�(�!{^@kv�]��^$��)�5��f����r�O�;�������.�0h��uj�y���������Lw���q;���T���)&�e#*��Y��y2�����a�\(��bx���zkg�������t��#ig���G�y��?�����?�`���*���*���5��+��^
B�%`��
��Y�n�]�&�9��,;�!��;��:�@jo|���~�G��r���2�%��h������=>�"�3�7*��=�*���>��}���R
��,��J�9���g��U��p����>]�qsq=������]m4��X'�6G�.gsK{T�7���3�����i��QQ-��:���nm��QV��q�u�I�_�2���.���������u�Vu����*���>^���Lr�a#�qX1�}<���x��������7���oM*���ogZ�����Zq�[}�F��.���$]���@&��YD�R��+`Y����r9���-�sv��.���.�;�+��BW��Je���u��Y�l�(�<nu����s����x�Q����f���)>��+Z��h30d��J�E&�'%69<)���]8�:V
�Yt��_����Z�/��h�<��%�nv�U��A���4���]�)�v��0�I������Ys�,����;�.Nc'������G$����$P�e��M����&����2a�a0~0W��v��1��@���������;?�/1��n%���@����w_V�3���������\���>��<Vv�a����{\����<����f�1�AlG����I[������;���z
H��"A
e)�RVV^���0����p���9�~�Z�u�;OJ�P�}&��p3��J����9��8XA������<}��W�b��������=E����HDF99)���-��d6��)z��;�N�������2_�.Rjta�����5pM����.�)����L���)q`W���9�c���Y4N&�M��D�V�L$�h�d;*��%o6��r:����Zu����oLt����+^���N���	��l�����4��@@��������H�� �D����`S�F���D3dc���6`[|�z���]>��z;.��z���}���Uk+�j����+������36���N�k���V�
�t`��\]9������o��fs2>��y@it��M�c�PV�d��vngg;F)���;���>����#p���9"q�R�}��%MUx�i]�y���e�-�z\v�/N����"9�jm�p���D��3?0
@���'
�|�k���$�F�d��8��s
��l�,������
�.���$G���)(9DY�~���[\��z��\��{�SA�Z���V�Kh
!�����E&W�'��6�E�"7"v\���1�T��=�f����
`U�?i�}����j���4t�1(�+��D���G�Jh��E#�$~x�\V��!��5��1�=O��7����+������[��\A
�!�k�0��euDJ������<@��~p����+�R��]�4���J&p�8���\z6l�q�#�m�u��z���#mN�EL%�i��%zhC��Xt�������j3���%���J�%�����6��!>O�I��|)�����p�{6+j%&�9�O�EF��`�y|�r�����q�j�)>��������<�{t]���Sg����������&4�k^_�n{#���@��\�kotC������jbB�_���2�H����W �����MD��+%��4r�_��M7g7kCB��m�jf~�X�����M�_���$�7e�BO=3�0246Il�^��E�|Q���Xr��Ysg��H}�	[j�`�wy�yc��Iq^7�q l�=r��t|?��$_�9h������X�)�'Zf?uS�J�|�1/^YC�
�ogm1�p����V������K|���KL�V����;����@7��i|�Z_�,���%�j%�G�M��':��z�wS�\Y�S������P����t�k��.���������U��Ij2�_�������O�~? �Xw�����`m���Y0�����s���$������>K5 �,�,N�H���tx��9�J
������	V�����gcf7k��&*�x%n0����(a�=�<��[J�8��\h���t�U�����ysu^k��������M�Y�P#�Y|>��T]��/��q�t-K�m
�G��&�<�����
��3!z.>[��M�AM�]�Kw<s��������]ZP��TKW�;�7�}V�#`���"�#�U=�h#�����!���S%|�'L�&���n������y:vc�X
*�B�X���9V��I>�(��v\r�B�04�n�_���7��Z��F�m�Sy���xj��Yz���N%�9��@�)�[��.8m�h�bAv@�Z0����>$�S�"���E�7'�~�v�'>�����n�SO-_iXF�}+Gs�����,�WD��?-��dG������p5:.,Q5+���jS.!���mQ�����|J
��rV�6T=��aJX���w�a�_	$�x~��L��$�� �Mp���^f�^��4�
�R�Z��i�/�����j����( �&�Wr����lA��BR2J�T���Q�:�y��h���������������o�\��������������3P���u������q�	�����b���5��ct�]<�T|���A��^���'��J>m���bZr=E�f�����A^�
�H;>*�n!�}�WM�Yw(��U9��G���Q&���S��:cW>��pYd����3�!~�JEV_>@����F�Q/L�Q����=����|A1�����
�Y�_�����B%���;�!6���f�����X~eOP��?Fx�0����m��=�+k��������XK�ZJz��Y�E�<k��l��[.
�j�P�T���5~���aC���D{��$��t�x5B����(�UH�O�6#��S��IDR
�T�.P������B
�;���7]���k
�J�L�t����a_�!������M��R�j}�l�����\�N9���\�3�E�%)sV\h�T&3�%�2pP�:�LV�d{��M�?�F��@f�+<�tj
��z��W�Z�Y�oRmB._E�W���^������x��\6��	�S~"8�}�R��s����c��Q9)
�A�|��f>�j����ts(��%�:�'��Agv�s^�E��|r�G.[�1����y����������}�}�z�
;�&������������Db��'g��d���w�4���8��;��{�o��#�����$JUE1��j����g�B���:��Km�;�`GL��R���y��t7\~Z���3
?p�E�&o�����B�����|!���80
rF��;�O�
����`�#{�x���A�P�K��8X�	���]h��6����ytd3���;z��
,�Cv�Dzo�Q�}��s:5w��b����zM$������xe�>�[�C�>�8���__�N�s�x����9��E�33���%���t����s
$�0��N������({���~4^�O�l9-��?>������G�^�;�j94~�p����3��2����`���,�O~��StT�����������E������R|���_��:���z
��>�����9V`|�� ��Q[M�Z[��a0q��J�{�?vT��=��}4Vb���T3V�q^���q��e����D�4�}E�@Y�M�Uvv]�*e�6����e!x_�$��3�$���Z?3�Q��!|�W��_�����������r��`<_[FA����K���\]q�A���?�\�>�������	��eh�&q������9Od:��!7��l��[��_T������*y$�r�;n���o'�K�L���!��k�L_����9g1Z�i������������wa0g�C�d!�T"��t}���vq.�iS.V�$%��-����"���
#	���e�{��	Dm��.���qN�.�]0�'��W.�
�����|>?=uW1"��x�?@�U�|���X	�����{�S����L�����"�_]�O�,92��:
����
#6��)�r�`��?�
��B�R=��>�j�z�K����Xn��b�w7��{9AUI4��C�������P$r|3�+2����S"i�^�:�z��7�p�DW��z?
?�I���Q-����m��d�2����UI>�����L1��PNT<'w���]�5�u�7o�m#�(7�=� ������d�������1��"K"��d�(!��(@��;���/��p�����z���a7�j0(���x�{�A� ��~���Mo���'��������-z;�����7����D�I�C1��a�=������?������ V�=5���:���-?p�����(�>k� zMX�2��4y�6mIu�����l^u�4�^w��]T���^���RT����q
��X.��>����n52w#�c�2�����7��@��n����99����60�����z�u���5�^5^��j�A�U�<��������;�.��u�l����m�����ILk���I����h2��dv��e���+�~��EO�x�����+gvoG=���8.��������F�8�Zz������
l��q��l�>h<f=U��N���50�H��{<�>�	6��l�	��	7���C����(F�ps���}�U�U��^	�7`&��s�|�0-��4Dw�S�RD�/����Q�1\��"�kox4,��];(�i��HL:Wc�&v�\����P�E���p���yvQk���N4�]�����.ci���,�]�I'�S�;�������MG��n���]������������H���V�&�2�5�q�u���;	������D��Z��}��v_����u@A����j���m\������:��n5���R9��C=s�|�1��`jA�dB����e�EJ`g�)\��3tXc���>4]m��`*���5$�Yu�u�q�^:K��b���x����#D�����#�|�K�k��ekZ�F�Vu�r�HM	i� 5p�S����CYa}��7�������-�ctk�)���qq�#m����l��W��U�*�C����}���)<�+z�����O[(���(�X�����+��@/�G(������g�����bh�<e'��	��J.��Xp
�B���E�����n�r�U:9�����j��Eoga���z���:Go �_��Y����{����b�{y3��yA�og����NF����b����<��_;E�M�
<Y>7�0�������5OLrw�]��N���8B��"�J	UG��t�����K���]��aW������)K�p�p�.����(��v��?4,�����i����/��n�q�CHR�X��e����g��h@�sD������^��1�#����9����L&�=�9z�:Mm&�U)�5��Zg�P����^@��R.~@�I�a9�t����`]�%
�ow�)X�����j����)�����XXB)qk��������'��Qe��D2��R��^��R��+��E�%�s���0���+��~��Z��<��{�L��n�C���Iz��IV!��9���opX�XN ��K�gl=�'qZGq�U��di}I��Vy}IZ�P����,�-�U���^][[����h}a{� ��I��Z�*���O�~��Bm���Q��M��@r
�v����;L@�|2����&!n���~�����
��q�K�����cN������F�����zc�J��EA�� ������yA�����Gsds�����7�w����U��J�)���A����I��w9���%�����Yx��h�����N����P8��G����G�*����H?l#`Q�����4�{��8��2$Z���*v���Kd�F�WYV�>�U(������
kJ^�1�,����6��/R���<�Z��M���4���<{.S��r\��:\�O�Lj���M�iF���Ox��T��>�N��e��}rp0�E�����t������r��d�^]]:@�x��
I�+��YS
��$c�|A������K+��[�U�����(I8�~=�1�������&��<����IW�p���2��%��R����tUw_Z���.��k6(������t�����X,���B������������yn>.�,����<��#�=S[����:c=�����[g(o�
f�y���,�d��(�����z�l��Z����z]]5��m�e�+D����{v������I8��Ox� �f�)��_��=���3��x�� �C�n.�����KX	�8�H�D/�Jvj��cP������N��(��+'H�*���~I�����6�|��}���?�~��-&��4��p��M�_���Z j���m2�.[�� I#�4���h�jp�	.����{�zm!:���f����x�J$�[6���Zw���r���x7�������v��{�y����=�r�
���!����x��J�R����l1�/ ����Tp��&������:��h�\svU��|�_�S2��������a�P(���M[V�4mL� S���Adn<?�y�l���QK{�i�����V�t�u��gn���h`��v��qQk��_�\�u�=8������5��
Z�)��?4���������7��N��.�5.kH��Y�b�b�����5�k��"��l7/a���49�ZQ9�z&g1`����y�f��,��6;�V��F�&�T�������J����Y[+����Am��R��z�~���C�������MD���n���������KH�C
�$���$���������qe��@6�4q�.K�p"���>+N�m0nI<����O���7�������`�nn+��cP<<�����r����3�Ie?�ym�%����L�����B�T6u�S����U(��]���=��3��4�1 P��H��W@W�b�������>�MBT7������b<J`+���.����o]1A�;�"
�+c�����	�>�����N�&��u&�/��pyr�~p���J�b�����V���U��������(F�w!�c�t��lD��e��k)��"�����g�
�������&��ilA�O�x��S���/�[��A�v�����emmkm<d28��[2����!!��)��*�j9����o��Q�"O�)7wNe�
N�eN�F4�j���*�I@\%�g����kJ���	n����q�\Z}\$�����ap���3P;�����6�%�*'[k�.�i�9[���8�.n�;��j���?�d�Nq�pCS�n����7��n�:����/�	q���\|��#T�sR����6�n\'�w3l}�K[gH�.8;����������ggR1�VO�I�Zb���J����I[#���U�*q��G��a���A��0G>%�pA�|���Ou�t:�AP�4Q]��:G���o��:�S�u����0J�.�q���*+<�z�f�M���K�������[xs`�e�` s����[1���X��h$k�R���A��Z9��H/8���
�j�|�z������v.��eQ=2�Z�b��0���E����y��S���}������k��5~������z�f�-�j�{�������_�~��:[����vG}_W5�Q�=`<�%o�e(���ghk����DL���D�+X��w)�@{;��Q�p0���42S�?nR�VO�,��W�#�/�w�\(�w��cnu���4��g�yf�u�of�>��N��4	���q���W7k�q.���Q%�J19|{w�_*�.=�G���<�����I>d�:�����H9��E��"���!��y�����d��7��FB���{���>�3_��r�I���[��?#��~k0cv�lm�_������2X7���y����[2��������0?�
H�E�`���k+�S(�Bi*�lwjR�4���5�&���y2I���7�$�^������v����,�[�Tx~������`�1Hi�c@XA�f�M���r�;,�� <�h�l.��
+�T��{I FRv�f[�Z<�?
i3is4���c��v�������P����:AE��D��������b?Qx#�r��O�3M86l����V��{����������+��-�}F]4_7���u�u������������P����f����,�"��S���S���^�+�e(��aC^�!�[�f���4�Gy���=	���>�~�w�����7^���=!��������7��C���B���������l8$8$T<Bv	�n�{��w��������
�)/��`*�k���B�Vr`�|���������}[�&�<q������d��a_�N�-:��Dr�����+k��D�<�)@������N����"��e�e������8_�o�J���]Ii\��o/�����Hw�(�������K
M���~�_��x��+��a��v����S�T�3k���V�Y�����>��g�	��.�5">m��5������Sbf����x��IN����{du�/�I=��O�-Sv���:��'�}"���<
�7F�bJ7�r����U��A�Y������c������'�C-�SCY��z<�8���NpK`�=<v��p������pP(�I��	��LO�.H^����GQd�Q�%j����Y4�QO�t�<�a���-��x��E	:�M�y�����}�vu6��������_/������l0����������3\����_��,�C�Q\h
��.�%z�B�bu��)8n�HYX�������`���������
����� �7]�r]����,��.z�V�;����;��������'�V�[/3��.���rnYm�i��c�����qqs�m~�=�!��Y�pT8x�����B�
0004-Define-logical-replication-protocol-and-output-plugi-v12.patch.gzapplication/gzip; name=0004-Define-logical-replication-protocol-and-output-plugi-v12.patch.gzDownload
0005-Add-logical-replication-workers-v12.patch.gzapplication/gzip; name=0005-Add-logical-replication-workers-v12.patch.gzDownload
�&�KX0005-Add-logical-replication-workers-v12.patch��{[G�8�7|�{� �f�c�b�N�-���M��I#���Q4�1������mn����m�!��������.���@mv{Awk/����0�����O�����n{w�����nw����uU3��������676�������"������h�S���5��q�w�[���G�$|������o�����~}>��]���-6�����S������wjg}�u���~|u����>|�D0��x�.'��kkk��w��1�w=��C��$5����}��}������0�d2G��ZMW�;�M��l�2�5g�9�A��';j���Z��h���u�(�j!U{'��q8��S����8��n�����S���������t��(������b;�������	�d}2�I0X�t&�N1����,��]2	��Qx��5=}�\��T��`�M��1w����*?IW?��),��i�.D�+7#Knm�rD��)s��'�����m�te������6Ym�����X���������d��X������lz�/�Z������_o��8���{��~�]3\z�����5o�����j�Q�.�w}k{����7��~4���������W3���F	hf�����D!Le'ld��.��z4��Q>20T����+��������n��M>L��3�:��	�N������ww2����(��_O;��=`�������3�n��q�vS\Y�U���MN�'�����/�}�dKW��Z0Z�g�t�T3�v��������,��&���~YeC� �W:n��
���~z�;��M`��P�_iS�q����$L&3����kN�Q8��_Gpw��hP\���T{b�0l,]b��-O'E���.F����It=��y����W����V�l�6J@V�-�<Y�h����k����V��������7[���_�T���w�����L�l�������I8�����rU�m�n������b�K��l���3?�[s>�bf��#�Au�q����,�>i{HmM��U7C�
k�m�{T�[�Uxc��B�f�gu���Q����`�*X/�%/�a7��v������Vw����=J��m/��-o��Wk;�{�U8��������"��oG�8x����M����6����	2?/���R����+������k��S2�v�x�	4����A�W�-���x�u�J����@�S�h���R�Ys�-�����e��:��/L���'&��W�x\I�G���D0X��C�L@��UH��%�{�q�
{�x@���6���M����� 'nml�66����p8�������O�|�N?�]�W�������N�==�6�����v\�$����q8���V|�F��Q��%@�cd�Xs�.�S��|���U���o����� ��.�*&E��]�RH�����~��V������n��"�z)�)�����h����7�����q���k��4l^5N�?��K/��S��� �ni�4[Xu���5��.�j����[���Y����7&�����`�'�z������Fk:�f��C�*YQ�LC[������w���Z-��vo�l%M��U4/q�{Ov�L�/�Pk���E5'��+��^]��* �`�-�����K�I	�$��g��*���,�3z���S�^�fc�o��]{{)G��p-���,Ju:����@��� ��ahN;-n�%���bN{�N���V��m�Wnq�{�r*z��_���������������p��]��^p�7� H����M�J8=�\�W��W#-L�9�����&p�������@�������$����Q�prw�l0���DfB�t��]$�������������{4���E�;U�&S8���/��.�[���m�D��Ds�S���8H&j���]�����:7����Yt&��(t�(�<���$\k���_�	������9�c�t;��pp�Q2Hp4����I��j���t]�������>L��\�D�[9�z�0\��<���b8!y�^]�Q���h�b�5'@��^4N&���X�v�6,���
��p��m���plF��Wg�`-���kR�Lo.�Uq�9�
��r�Zn�i{N�>Z�����xp���8qX�pI�0�uJ
�[�.�E,�+';!H:A���X35�x0����x:,kz�n��X�I��I�T2F�M<����%�j�����9su��~�JEeGg5n,��$�<�������;���gA$��M�AH��ST�����Xd��8�[�4�2�1�X&F�L��	 ����N�z
��v����#���@��z�?���0���m�{��l\�����E�\���9�I�7����'n�Z<M7M�A�[�E�Q���ss��]��r��
A���"8
NDw���r���(���C#8#������hLt��k�!�y�>�N�9����`+2���X��bC;D��Q�����?�F��(�H+8|8��R���!����	!�h:F1L���H/���Nn�p��C�oT�_08�z���F�:��c�����4��#���Q��
-�H�& �rP�S�(eWu�1����i"��U����=�HC&o��
����51D�aa�!�H��D�Ch���ZaE,sZ�h��S���p����x^Soo�a�,�M�Y-����Z<���$��-���6��F7�������rZ���s����np�QO����o�9�d:F���� p&��s���;Ry%�P��7��3�|<�g�z��+~��U����1K���%�%�u ���p����0��{BBd���&Xz"���(1���N�.
����(��%�(�y��M
���������������
���������@�a��M����p�#^S0I���V����>�mV7�
� �8s�����U�X@�D#��x#&%�p�(������m�p�D{K,���Y��s�Z7����A��0S7�-^T�A��a������N P1��) ��1Y���}a���6�OT����T�s0��t~@`F$x�:�h�(:�1���Z������uu�����K1\q���8f��
���/�1��i�J?.��8`:@�:�S�������f��
�Q���\\5|L�;j�4��0f�.�t����!BdDQ��5�!�L�R��y��Q�=h���
�@=w��!����h(���R�Tn��$5;S\���Q8�������3=�E�qP�o���OC���t����L��"pHO���t0*���[w^��|x�[��9��Daw?Lb�o2������KJ	\�tS���
�����+�x42=tb���(f�B����^�.���AN��#{x7"S�������P/Q�BP0����i`���]�������w�
7�/�%�����	�(T�zj�@�	�������%-�F$�����o���x1��p�)��hzwrs0 �%Q���cL���6��#�}d���������<��NHZJ��a��Ba0dN�0��f#.�;�*�k��/u'�C��������������@r�K�F�[�
O*8�&�uQ7��LB	�Y���L���Y_s�2
�q�k�J`%`c��qh��I��0��0��E_�	;����i�'������+��L�I��.�G��/��P)�>��X���$K/�kh�	�it���]V�������%f�eB��D���
;7�x��^����k���Y�P�����*����I��>D?@	��jiG���?���|���DN��;�">����4KZ����5����NF��r�q��`��xl���3k��2@��,�G����CY��$�B�rkM���@���5 >O6��p��YS�&�������>�w�H����n�P������c�GX0I�����������nOq [�Y��5-n�(���x������:H,��'sD��I���mo�L���eE�H���?j�%����PKi�(k<������v�q:`��>p�$s����dL�|��/�6�#
��(��,^� Z"��#������>����RF�1R��i�����#_��>�xC�=}�#==1���� ���e���K����� ���X�����P��������_T���|�($"�"�r���,���y�x$��@���X���1r���,(@�!V� Js�MC��|�1�;gNp
�vZ��e�iS'�"�����i�*���
�@*��R|������q	>��o�a�`-T���Q8�7�5az�{ /�����CcC�jA�0u���L����G_�J�fM��qKr����C�*�@r3(���'�{��+3Q�VkK�`��A!��)��&8 7//M@���x��Cv�=
�A� |_n6SY�v���+���J?�&]����L��}Y����?��L��t��}��	����p�xB��=W����o�.�Y�j �� �����s0�L��������>�4r?�(���6�����o���y>w��D��G!��0_�f6�}���f���p����+,���zmO�����n���90�GF�r�2�;�\Y��0�u�t��(j�}����-=@�<)H1�&�N1A9O��
;1m����������00��oN��]>�Q�����-��Q
�UUF�p����e%?v�1
��?��A���Ny��"�$����Z���2��e6�`|�����\��������w�h��#N�i��s��:*+��"����6�4��
e��6bI^C �fX|T�:F5�����9��+�d�����g��b�p�V���
�`�������ZH�]�O}�p��U��J��78��.���a���.���
���Q!���b�,Z��=��Wi-I4����Cr�4��H��%8G"^����~��
>��I��w�����u��!���"S�w�������&<wy�X�3�x���^��k����J��l����7G5`���8�^�LtS�]��*�B��

R������!�^d��n`c��z�N�z/{�J��}���n-���s=�%I���nI��g`��Ar�`�&����9�f����=���2��bqSp��9�Ym�����0<��<.��^�����D�<F���;�5�K1�'r����D5�I�5cOy����/�A�5z���l�q_����:!g�>������}�*x}~gE�d��$#���B0I�xi����D��������-1�!��S������Y!�4:���������>�ko�Fb�"HD��g�+�wbiD������N:�;�
�p��T(C���[	�����+������n��@&�����)��=)�5)����CFy��V`���d�g��|��$:4���b��_R��"�q��5}��8b���u�4��v�
I�E��5���������n�i5�?���CWrE<R��\\��r���C��x����*�^_�]}�c��h�G��Kcp&�����"�G�J)L�9
�:B������3J��82�������G�>���
�u#�����n�u��k>�Q�����E�s
�x���TQ�%j�5{����	;��b7�E�^4��P�*V-cy�l~�&(8��M���V$ht��$.O�����D����;�z��5�y<�����u��{�J��)��<uXG�4���s)RJ�B�g^�
�L�c�c��V���[� *?��/`"{12L�M���D�����A������N�S4�D���]u��/���=P�����/�2L�,�����:Hg���"1�z�{����?P��h]:s�����	�L�2*�����X�)�f����a��+�B�"�U�	O��;�No�K�&3`b����+H�sm^��7fG���UP��pLyxeF���u�5J$L2'�c2��&�j���Jq�
�"�������2����i"��*�H�xx��qe����*�����u��\S��L��uz����N^-��:
���!�B��g�D�>$����w��1�(�?����E����1���S���)�=�����%^�}V'@��V��V��7Yx_�P!Abd���4_5�������ln�d��\'�P��m��5{������Ch�l��R@g���C�����-�������}T=�����.^d8T�
�	B?�J8qb����)_���7��������u����������F������5���D�l�8�����qp�Po^6ypu���K�Ee��*��-�GH��oXf�d�kX�7/����2.@(����;ux~v�8����m$I�{q��������\@�8;�{�1/h��C��6-MY�j��4�r�����!N�d���8�����~���S��������86zY/L:�����#�����(����K�.}-���{aX������[[%a0�
�NH�x	[������x��'�[���ckcc��l���^TM,Z�?�������uO1�L*���I�D1�������l�
7�ENQ���pa����h�N+��$�J^%�c�����M�n){�/'�
����0�����������
]�����:��L����%Ja��r�����T��N�
1X���h@MT�f��u3�f�M�5�����H[��`<[���eu�pE�y�P�3����B�%�$��o�k\6�b
��&:����_1?�Z���T������@O��K��������(�������C]'�'������Mt���}�����
 X��(tF����]�/���O���h���~]	�.������\�%%��������;{@b�S6�	���"gg�Kj4F
���1�K]�}|�72-��A����[�V?�1�Y����2Vg�t?UOO,���N��&�������i
�kb�[����"�bF��D�����)���&\=yp��@\;�$���s���&E=�&_e^�����f���g��2[�f_%�,���|�y|�5�r���S��m2�h���N2=$��jJ�@��~'&]u���w"�Gm�q}���-���
�����	��@��)����e+s8wtS�-kUFO�3Cdo
�-�m����YI�;P��H�_�I��!I_B����r\9�u�@~v�v�K����[��' �����n�D����H?~�#w)�#�y"?Y���=�F��)#�y��!��������~F�-��$e9C�sp�=i���v��lt���p�5���������Z�����9��oRA'd	H	`��/Zz�l�S�E&�m:��B���E�KE�P�$������s0U���]��v|�&7d��k<�U�3k�$A&s����
�b4��x��=�2
s�,W�ZI	q�m���9�f���O����Vnw�{��������|��m�y��9�4Ev���7��tly�{qpyp��j\.y^��4.����92��(�9��� Vd��%C��y��A[4��8�}���R��d�/�r�D��f'2j�.#X��������j��n=���X4��������k����H�	�Diu�G�&�5��_r_b���;����:�����}X���S��sg%PI��Nt����S���4����,+XyG��E�p0i���Z���Q�
�!F��//��0��M����V�w�"��eP�j�s�-\��an+�t/�?�e���q'<������g���)J������c{�����Q��a.�����,F��zR�K�6�������F�uK�j��E4Y�����C�TeY%\�ueI
Y���:;�P*s9����������Vwa!��u�|������E~O�?��U$��Y<���d����������;�dj�������J�����N�JA ���?8q*{A��@���������u�z�5|��^���=oI}g�������ja/��7Jp����/)MT�.26�3�X���)�����H-!F;�t���	��j��{�pP�V4��+QT���4E��[O��juk�Z"9l]�:L���hd��@�Q^�\d���4�������������oU�Z&U��lY'+[�����-�K��c���x;t�n�nS�	uH,�k�S�>jN����2�f7a0ju� �T�a��.����6�����wt�����o'�#}�V��,p����'��]�{U�l�R��w�L��P�1*��L��-������>�#ov���Agh���K1�W$�����LDS82cJ&Jg5B;&����!�D}e���E�t�2�"����HC�V���V����7��68����(��-�zT�R}s@���M��o�i��d������K�\R���$�p��w��IdBjP-�{:��A�}G�FC�2\4P1��y�����~������c��
��ln�v�{�(�v�;�Zwgk�]/&���D?[�7���a��Q����Bi�Q���2������q;�#�����&�[>D���	�(�^�*����1N������|���&�(���1�(p����}����n��&���#J%V���������
�����[���O<W�zXu���k�|�2����)���������C����p
{a ?^��@���F'�k��Z������>���7���N{�t�(�I�a���Nu{��"j���E?V�1
�wxa�V~�_1�D�^�:�	�s}�bE�E7[H��k	mh"��f�=��
(����B�lq��<t���lESxE�	�_L������kN(��"�2��Y�K�-�A�coC�)��=�1������������I[#
]!q:<=jI�G����+�h��1}��N/G��i8�[���_�����U�^��-���6�T��k������z�j��q�Q�8�'RR���D�Q�^^�2|����+�Cf}~)�����4�������XwDm��H�M�����\A`��!����]o��3g���W������9�
M?d�����<������J8w`������F(2H,-%[�g��K0����y|�h5���e��1Z@���>���Gf���_����8&�E7|	��d��,}
���dI�ZX������=/�3������X���!�����d����*^�'W
�'�--/��>N���F�����l���l�����9�'��i����Q����*vR@�����L	��34���5.R�+mKo�ak��k
~���G�8a��u��'*�����P�:��~�q��	33����'�Wp�<M����{�vn��9�q5avuL�����������e��>]P�N����4Qz�
��	E'-��a���9��:�Gwc�W^�,������&�U7p�kb<���#d���3Z��.��V��3o�������!�����(0�b%n2�9WD���k������5-d���q�������_�N�c)���y����{���|�u)�Lh�����Mp[Xf2��A{�A��~����f����'w�d��y;�%�%%��y��1�R����j�'$m��#�M�^��8�
Pj��R�}�K�	�8�������dD+���H�B�RY��
^w?��o�1���$v�b0L�q�NG%4>���Ml����F)��RQ�C���$�u���a�ArG�r�;'�&�b��n�$q'
�������c�
�v���twP`T��M����7�x�������H��i�jc��5����C18y�\0���^�� ���}��	�]�"���$��$XH����3nnS��@*���xT�����^�;�!0��#r���%��$����''��c3�����E���:��((~0��+�gG�.'�GU�)��'�;N��5�m���0G�_p���q	���������1�@.b�XV��Z��KD
���Nv�r�uG�)���PE�QWa�
�,���L��o]���o�y�@��h������*�+('�����G�`�iA���&�����"]�#z��6z���\�fU���h��0���`O����/N���������]������aJy���%�5M���"��]�:�����O���p��,�WQ:ei	��kq�)8,�	)/�h5��2e�k���65j~8k�f|�2Wkq����]U��������iN0������E�oiZ���L��t�ej�ud��������8�n����WJ��a����nQ��L��c$���DI��,���T�o��0�(B��P�d
x
,As�3h�%
H��z��d��d]��XSu!E�A!�a�h=J\�t{L���ai��.!F��3�;ufXK��H��>)��u���b(�	>>���2A7o?y���6��4��QK���h�MW)Ki$�����*
@}��������b=��������tq���N,�g�J�*�4������1EG��;>���������o.��)Jjf��@�Y^R���GU.��5&���	>���j���:=���}��&Q]�	�DU'���b�Au������qw����c
@M:c���@�g���Q��g����=�g�u�aE��wg&-5��M�H�%y��n��h<�3*8z:.����b�Vx��pX�������Pu+l����R�BN�9@o4_G�����������5��pN�QM���3 n����jL�{����M��$�bP��h��+��UL��	��1��-/����*4���b�R�L�l��������U}A��.���eS���0���R��E�+�-t�a�>�p�k�pY����~��*�|HsC��-���x�����
�P��(��F�T�=>!�;P���C��,��
R���������N������@k�:���i��������O4%�q}���,Z�r���R =S�H����U���<�,�^�__5��.��.1U�(v#�O�������<���3�F_[|[�H�������irs@�����rB6E����2���}l��`
�����^�)Y��+��C���9���8+���8wJ/�(�O�}�!`y���EPU��hGw�^�A�EC<�����%��Q
f�M�00���M��bV�4�{2t����0�w�r���`?���Su<��6��d�%���O�_�d��W��V�qy|pr����������e�!�F�n�����g0� ���Bj/�&$��T�Y-F���{L�5[�N$�K^��	H��O����?�K��B�n|����k�{"��j����9N�����T�Z`�}������Y0�
\6�Hm	�R����7�r�&	�H�����|Ka���Ej8�1�HM�=�`�1���:�P����5W��y,[H
��`3f�t�?IXb�/��gf���L�!�)�]#J@���[�c���h�jEBv�*�b-�*�()����u�"���"J��=-O)"TN�T�0�I���-�4J����M�!����;���0`;�`"K�Y���M2��O�������i=IiPpF�B?���^�������=�1
��F������h����W��&�	������x"
�hS��R��Y����_��"qsBQpp����R�b�w)=!
%�|�v�c�JW�������)���h'$0W�Z�JUUF26�,g
���������p��_h����d����(n�aEWj���"�Z�����2&5���"���G����i4���R+��E�s��������[���C�n���$��r���@����t,�%��<=6Eb3
GN�TL*L��������/"
�MI�,�
f����'v�z3�H8���/IRs$��d�@�#E:Bu.5�S �S �S �S �S �C�T7G-�"��x�
r�1�*%����$������JQ��C���
�9o&m��k �N�b9�C�.3�6J��F�Q2�Q8�.BB�/��*9���8v��*�&e�X	)�'�	�ck�����?dx!&t��1�4�������>�^�I�_��^��W����X{1�Z��2�,e����(\KbL6R����YJ�S6�>�����i���S���Q��v����o�:�l�����_��,�r�����!��G��,��`8�����9�9�+������z��^�,=�w����G8��.Z�cb��%M�<������������N�d�W���9f�E'��������5{%���?����9.�\�������*(�\��f79�W�.9�3ko���Z�@�c�\��
��U��2*�"�Xp��y"������[D�x����eQ��a7��N/���uS+� �l����I���������?_Zi�"� �Y�,���Wh�[�Mh��D��=��g�;�@���8D�&�1��g��J��wL�����~r�
MdO�?�i�\H�4h\�C?���$;�y���a��)�:�al�lB�#���l�GiC_�qJ�8��It���vM�Dy��m�#��j��\�����"�9����H�_1|%������3�`{l�y���������G�8�����32����;�Q}��n�3������?�����7|�1*<���=�QFt@t�_�Bj-�+}��D���C�si������=�F�*��h����Li�V<�Q�v�,Gi�q�^������:�>�(������q��>���46�m��QzU�&��(�zoG�����j�.w��U_��5�eW�p�5�����s���������	�/�(
���}<9��z�Z"����s���a[�n��U���'�����1��d����Q�0
��������L�A�I���i��*)k�>0����&��b�UyA�� C�o�Q�cL���J8��z	�����[��;�p
����_����ZI?���g��edcP,���M�?JEe"�R�&�7�N(K���%7P��Z�A���������	�,����:��y��G���Pp<���X�7
������P,q`"P�P���A8��w��C�b`�&��{q����V�<�W7)-�ex���UI?P+<��E F��za���.0���N2i�yN6��c2'�v����8xq�����~aP�w�x�x��u���TE���
�B�L@G�b*�T��k/`I(4Q
%�C��,����������X�U]��t��~y�"���p��������t�m�Y?�\3��N�ot�b\���ou���9����Be;Ov��;�Zm��nwk�3�5m��XS��mmP0R��1�$b+��l
�
r402��N�sK������W��?�`j�=8i���C��uzp|��AF!�`�����������J��L7`�c��ur����;��j�Y�����4�5~pqq�����*e�U$�w+w����j���q�zJlp��-�I��=�l*� ������d+��������V0���c�[���:��?��A@0�=���$w ��D�x`JN}Qp:���:�|kg���#9�,�@t�`�`|��b�{8)��������@�Q�h�����D6��kp�a��%d.�d�|���Bn�IL�b"��8��������I�bDh���
�����&2��<�X�D7��0��Bo�.���1N$+e�6�����b�w�k(T�f
@��\���4�\��|�B�9e�Efc���	��pz�����p���}{�������F��c����Q/��{������-P|�]L1A�����v�*�;�[h������0��p'�a4#8\����a<�������]�t������}���9�-*����c�����3�-#f�	''0�0NEc�X���g��z��
��f��G��j������*��-���&���m����e���x�p[0A�m�P����
Z�k��|U�������[�{��X����8`zQ���$"�J�f��E��Hg�Y�Mf�?����������Qe�0����%����jg�E�����K���t���9C�((�?�����"���a��_�mc�n�o�4A�R6d8Th���!�tH�j��%]�hB��j��o4��Zj^\^�D��$K&���1,��L����U�iO5O���&��?���uu|�89>kp<]�ni��2��om.cDy�������e�)����x�y�-���g�x .���L��Q�g=�*mS��%�]X��lz-�f��-���l����5Y�*K�=�
�$�4[ubq��Pp�C���n
M�8�������[�E����
G�7����Zm/����99�tc3��t��0�����������z�\��O�c=hG����k��v8J��o�q<�X�E���z����S8i�/������A������1����O��su�3r�*;}�D_�G���)@��z�����i<�Rn�/9W��n����wg��,��	xY����/����>�!(�bY��'77���a9_��2�����FS�^qnV�OQ��\�����Q:��3I�qD�)i6G|�8��bnR4�}��d��Kz�i��pP���C�������a�����j:
�X�$aMO�;�F�
��Z'���G�CJ8�����h��������Qp�%��j�
&~Zf�.?%-(�6x��35���D,����H�l�:��!�a�{����t�Vn�������WL��������7��������l��6��}���}���QT5'��~xX��D'�AV���Y�gufH��U5�1��i�����2
��i� >�J6���6N������9�{���o�%������_����sL���`��QN��
�/������:��r����I�

XF�$��I�3��v]��uhv��
�[�<('�o:�P��/`3�����	��Aya��{��
��������4=T~����S���x��
������W�~�������4���D�l��%�3����NGk����,��yy��o�d3Qq�7a�U�d�����sutr��"��������U�y��]j�h����8����5O�&;2f�+D�L�x)�r�p{J������0a=��0i��"�3�WS�)�v��,��r�EQ��;��-*���{2>��
�r��%�����%]�'���a�S���(8��w������_8�:�o�B��V���3�W����;Q���|��jKH�G��[w�������������F�!��x��0DF#���
���HI�1�{�r�8��d`]Y�������T��a�}�H��|W�r��=a:j�E����z���|��Bb������sP91c��5t2��K&��k��������p��������u)��k�J4�s%����+���u��2���J~qU�(��of���GpN�Qc����"p+���~�,�����(�]��������`�r�<
=�-q������a�����[�����1p��C&!����v�-*-�w[�gf�l��Cj�� *�0M)�^���������������K�IU���G�,v?H���9`���*��Ts�rZ^��.bk����V:`��9����vM>���_Ch5>�.��A��9[�f5��)�Y�6�m28��Tl
����}�cl$�T�D�2f�R+�Q�N.QS"��y�����m�$��Dc���C�k�xsC�j:r��!?-�#���%%;��R4b|Z�����+�cd7�*F��.��n�	
���\�6����`m=m*G����Gbj%���S$��~�������d�tc�a��1�������_������{�
���>�=@�>�K�{�c�����;�^��F��X���1��]/��Y'��

��qK���
Vu�5����0������8T���~���akz�p<��`*VFz��Q��#��s#3'���U����/>UCz��@ r�e���*����VU}�/�����DL
:��jOx���^���Q��j�c�����Cw�
W�J:��~����w���n�_<r:2�Yg��pM�3"Hj���j�K���f��`�.nL@2������nQ;�6��;T4�*���G*�6c/���sa<�@�(J�Y�����/h��y
�9�uBF��E�eB���{!^�����f�9���c(�(�-�����/����L�y���e��I�7�w72��m	�Sj��/���f(���2F6�.<p$7b�l���>Lx��b�jQ��EDr���������W���h��%��i$��HQ�S.{���S�(Bh/
���$���My��<{6�����U���0#Enh�4+M���}r���Fi�7��,Q�@��;�#b�<�B����)�P|5��|MQp�<B/B���#d`��p2��������$�<�#?�.��hc"&n�#:=���w����f@����}�V�K���@!����i(2@?����c"�ircI�*���B�?BifP��7���~��|6��}o����N�Q����%E�:Bo�@-�Gn)8u("(�l�����K��c�Fn��$N�X��K~��0~y��p�!������z]��Lda�Q[�"��D���SM_
�=���j���p0�P�UG&EGA�S�}�Ks�E`�S.�g����*���mW��I���-�9����.��#��=_�9�#d�ok����5��f\���,o�9�l�9/�Q�nfx����J��B�8������$xK�A�3H�0��EG��y�[�7y�[KdtMS�_hx�V�Jq�5y�^�
gc�h��C���y�f���M�G,�����x��Z�Q|���<�brU:�������u���x����=��k~@�J�<����`�OQ�Z�aJ�el3�;���D�~���6E�!����O>��}��'�������,����DJ
�yJ~�SRP����w�o4��B������i�L�������I�v�e����������7��k��4����%���T��%�U���TLS�f�%�.{�g��V"�!����0��m�g^���J����_�
�>>-A�5����Pvz���b�u������u5W�"�><?==�������y�*��G&�#�$S�I$%�p(_�C*CK��K�H��Er��a������t\7�l���MLp�BK�my����������V]��S)�8�J����Q�-���������K�a�v����|
�U1|,��k���!��A�Dx�)��>��s$�1�?���Cl�F�<*g@h�%�Rp��i��7�6����XQ^���h�;�Z��	:����S2�0q�K���s�(�;�������u�^�
�iY6s�]��M�'%v4D��z�69<�p�*�M�H,�����9�����C���)��B��!�U�8F@7�N�e�����.eJ�S9xA$��� �[�.���uh"tMlH6�G���e��9 �FC��=���m
�����]X'�Itv�s��
z���!����{a�*�2���B9�{�f ��Y72W�-���O��	�-�u�GjI��9��$=k?xR�3?t*��ax��,���t����j����q�/�J@<$k�{a��l!�w;IZ�c�d �O�*���qy�b����}N'K��e�Me�=��5v,%�5����}f[�	>��V�6R-�zwi�/�k�|�$0N���0�&��&�
����������F���&z{S�7]��6*�nb9�<f�<~�~���2��1�����q2�,J&D�!&
��l�����0tl��8q�q�\�s�P[�aP0��mnb����^�����g��]��q�I�(��a<E���!ltF��O/�_���a
"�gG��Mp�jw�q�
�N�$��'p.��0������q�`oti����!M����rG��<b�jKb����gm�������Gu������\���1=����4������4�H_'��q�����n7^O.z��Nc�$�i����'�~�%��iC��k�v�����_�o�2Q�
,����8�BK�����h��������m��?za�v���M�LB�$j������nI�v�J�O����ZZ,�����>*�)�y����7�x'�����C�#/����o�vP�e��0i$�hm&��5��(W'K�\�7��/K0�������c����e��}�������s�>�;���������T{�<�1�5�$I��c����l��DR�R��/���n
�7�_��VOsx�9�f�����d���$s,�aRu%yn�Y/��Y�����-.�dDEiV����@
��q�=R�Z�J�I���s���zyl_Z_�H�j�d3�m
5JpW�l&6�����a`:];��w������[qYhPno�=�+�:DcIa����r��N<�bQ���0�u���pXW?��$�/�U�Y`
������������r����S���c�{�V��Ko3���������1W�Qi��4�	)����jYV��+�I�49��;���-
r�m����S$)q�~����Y��>���'��1�
���������y�X�<6����'�CA���q��K4�%�x*H?V���Q��j�i�[���D0���%HJ�/�`�c����0���%|]&_����+�����,�=oR^��-[��q��f�������g��r�^g�1��2��jE��F0-d�@���������,��2\w���}aVM��'��of��v��V&~��l�F������-)����	��S�����,�;�Gd�ZCU��+s�� �%x��1��,u&{�i��
����~emm�W[����~m}�W[��n�7~m�w��-�[�������������7lSndc�����M�/�5�2��{���lm������t�ncD����G�����<����F^E����$�E�"s;����7�FmLu�p�����[����������n�L�I�,���M�����Cs��3:����i/�V�E�;������L0<�-7E��*��F(I���&�y~����81y����p(apb:�
M������O�p�iq�s�75Fr���N��w�:�J(wzfz�h�$N�< �x����4�0��f�.�z	Mh���1���J1����@�h3H���}0?Kq���4/��
�^vcg
�����#�!�*,Xp/��Q�,~��=�I=�J/[[�����q��d�4�c	0nX-M��p]j�.N����"
��`��Ui����8���C;���`��u@oL,1��s���00�6eF'�����P��:��m�m0$R��0Vc>�5��zq��D��n��Q<an@O�#W�M����'�
�����M\��6����:�;��/Xg6Z)	�K��6s�a�}4`��,�B6���y �xQ�
��]%�C���f%��������(��Z�l���|���#��.Ma;���F�=�	J�jd��M��p�^d���WN�7+=_�l�*�T��H������#Q�{�l������� �'�fl���u�E������K9B�"Vyk	$���xU"e���F��~BN#��Z�$�S3��\���!��k��	���,����7^�������� �������i�����geEM���������y�~	�f��2�=�P�V��G%rF[R8o�)��V�@��x���8��u������Cv�)Q��{a�L�(d��3/��~���R�p����*I�X��Gf���%��%������a�q�:j�:xsr�:����k�(��+�(vz����a��!uQx�P��X��VC�������PU���'��������A+h	�p���e����'�G������+B&vJw�����?Rw��=�rA��cq��m�^,s�
,	=A]�k�d��P���5�!-����m��7:v�>�����;%�'9u>{���\z�������W������7WM��c;yn$����#���M��U��� Y������l�F&������I���%9�������w��:�F*�Mbf��k���N��,���Jh�	��c�!T�G��Z��z���rm�g����<cD��G��yH��j���.I�����QN<J�)%
.�����<H.Q��hhl���s�W�!��R[��4��������.�B��`���pl�;#�ds���Q�o����"M�]��5�=�
���E�'�W�����m��h��}2�Pg�&DxW�8>f�Km	�[�v�L&gp�V��:~����\35�����G���zbD>�Iui�2s]��9fgf��:����jR��S�����(�E���k��I�p�F����P��k��<Z�;���n�E9e@tc��x�k��a�Q;�mL��v:�yDC�5P��h�	�<��eP
0:�/76K�3`������i�4jpq���.���+d��7�=>1�d��0y>��0(=}*a�~|����;�H�0����!,V��	�����Qw���	��A�c���Xr���L�\���,~�r3��z����(2avt.B
�����'��|�$���������4�Wv{���D���e�����IC}��'W�����US�17���+.vq��:=hbV�����w^�=X�����I�<���� _����j���*b����9�ndh�Z�(o(Q�u��b�W$r����n��`j�a�}�'�D���v��-�*<K���:z�=E�m��=�crn��N������K8q�C����AW��8�^��"�
��<0
<�����2��3w����i�^�)���"zq�j�6�@tuj�>|��(����^d/��M.5]�'~��7uq}�`p_=3W�9��Gz���f���qV�C�y:U_g�`�4b�Q�^3���VA
+)�P	��8��.5r�YF;c'-�y����2������������ax/�������;8�����Xbr�g��w*����L
S��a<���GM{��j�<��&9�J�������Nn���h]����M�����GED���
4�15)E��M��[���fUJI%a��3k��Z��lf-��Xk`�U�Jy�k��K��s�.�/�A�fU-q��J�q4gA�kw��J;�9���B���;9>���W��S��L>S}F���T0+U�l�L
�+h�3�8i��{\{�gf�!'��;�L*K�S��C�<��"�&�'��(�����>�����\��N��!�� ������~a�Q��P����Fa��[h��KQ'����L��_��n�����d�;�J�nJ�I��0�=`�Q��a�����Hq�X-&�!yh�2���D�r���:a@CFx�k/�����)�f�(6����V6vq2����W�!�8�Kx��	sw��/�du��� ��z�u�D���g���7<�.������E�g��}h
���,����_��[�D�s`����}:� �Q��>�$���L,��]�1_���) �4Q�(Z+W�Ni]�_���������lJ-/������lr��E����G!-R��/
��p=��Z�����(������n�O,���[	G�%~t�O�l�4����������D���citz���m�SD����gD�>���s��i������_��&-j:�� �w��e�5.Z��b�b/��x�-��06u:�?�� ���e'�0������!��St�Z�z�&�S(zz�|=�S���mu���L��DQc�r0��{���iBY&?kC�
}/��w��<��jS��s}��yDC^��l*
	F+�B��JKu��`�q�b*eL@��wN�����F�Q*}�~���Q8a�_���AQ4:����������N����<2���$��aE&������:��2��JUH�[�f�����W�,�X���qE.��*�4���K�{	�w=F�|^�7�6p����HR�}j����gH=�$F ��m�<V+���D?�������L�����<l$��]�1n�Y`#�^t}�Ta���3~���-!�{)���Kp�E����?�x}�z�l\6WU�?��f�������$�6��Z&�v�Q� L�^YJ'�^���EGq&0�Nx~�	��?�i��(��������N��,y�.����0�R`�kJb����b|!t#�)���0e�Ox�v�����qs|�������X�f|����o]�,���<��
px��ad#��w���1��4$h��o���A������Y9�^����-���il)]$cM��DMI����4~��"���)=��9�sn�$[J����7MK��������t4�Z��~�#t(@�i�W��.+�
VSKZ����w�NE33��??>����3+^�\>�����o��xEXs���2s`L��nHX	��vn��9P)��S�tR� �=�7�~���S�9�����������r��g�r#Y�� �5����G��P�>iF<�Ap,�@�y�K��6��:`�V���5\c����L{�k�p�&+�vZ�+�$�]-�!Z�C�}8�
����'��z��p3�~J���s�6���S�q��$1�|:�)��@���6
����fc�&H.���rR��(��c �C�zL&@��t�A�V#��ZWC95
k�����c�-Bq��x�.8N����v��|����I���R{�9K2Q���v��m��������\j��f��D�/�����$��6���2YQ�7���E�BU�:
��T�����+�(q��5��Q������Kk=w����`������Z�Z���=
/���,3��ol�no/r��
�W�=���[[�kkkj��_����������Wk�
�Z�>��W�;��k_������'r�q2b���In0I�4��ZDc�q���d�U�,�����|�V�v_��64{�!v�9
�ydc|�#���C�	���p&���U�)�I�k6�s�N�O_4=Kq�����q��a�uy��c1���o�P�6��t#�d;s}���x
c;�E�nS�
;�)`���)��,�/PpF=�O���.P��s���4tU�1W0HU��0�E��V7D��$��`2}���������#�w]�62����z[����m	X�w-��[X�~������}��D��}���,���Q'��~�#.y���]���M:�h?�Q?�v
gw��)}I��/a��I��U���d�_Q��r�%�`4���Ge� ��X{��;��m8d��Z�
�����v��b���o;�|h�3-�9Q�}���w�����	��31j�tNdH�4HWJJ���O�N?�<i�./~l�6N_6.1�����QG9������9���`<������[kT�xx���m��a^��bG����";N�(�I��`J��
�������A��G(ox��[����I0{��81)���=h����9�5����.���&�hR�:0����Q����d�����t��Vk��Ya�oO�a��XPb!����f�c-,H����f7;��;Q�����,H�2O��8@I���`�6���x*EWQ����Bl'B������Z�6a��k�:��f�/\�
b���.���UI6$+v�H��ZJ����q�#V\������^�d6���L���� ��w1�R�����P�0��$Q���'�%����R
:X�	���+Fqh��P�K5B�����|w~������H���x:���|���� ��p��d���X�����W?�-y�TK'�XQ�g�r��(['�us%J��h��7�7t,�DL]�������yf#��� ���L�DP;�
CT�bSU�*��.m�h���_'J�/��m����9� ����������r�15���<CG�������D�a�
���q��_��}
�Au�����z�*N�����G�U����jW39d����_�����8�F�CbHy�����5���"uD@�U7^@��'�P����-�P'l(��\�Y��oYu����n�9���#�Id4r��m�s��J�G��f��2����%E=
����81"��QF�`.�]wV�<+�B+2/<#}b,��+�%A�����g?LE1����F[���S'|T���C!(�`\�r�E��tF���(��U"�7��:����
�&���a��o���TG���C'9G���C�P��_�d"����c
czj3��!��`dP�;��o�+Y>���9��g$�TW8j��/�o����0�n���{q��X&R*\�av����U��I�v"�������E��^x1aV������:��?�B?�����>dI{%�&	���k�����{G|�O�T�1s"�C������\�|��;Lry�8���r?��6�����u3Q��c,�I�c�A���K�p~q�8��t}�-����ed����09�k���SR�>
��K��k#N<�bw/��s}��U�q���sjf}����CQZ�7�����_�����O���<S$��U�#Y�%���B�%�W�����
��R������q1��b#*ki=�Fq��G����[��������4��!"��N�LD��~unBI��X~�����;oB���#O*^{��n�oe�7����'/Y��dK������]��S�NT�L���Y�"*��J3s��ug���J�����Tz���w�}}�g�O�oa���5-������Y����P����4r�{��1M���\w
�XJ!KE�ZW�^�C�j��)��C�'�s[@I�D�f��x������A��)��[��N��?��i���M����!���g���6�F�r�^������������k��h�P�b�&��������fU�no=��B���%e�^4w�^�����W���\��C��������q��d%5&��1?����m���S�����"{��'m�YI8*��$����s��C�@�3{G�M|/�
���"��Q�;�,��1Zv����1-`d��g�O��e�z�X�>u�@�~�
�������@t|�`O��N�����F��~�������gG������MZ��)��9Mr�1�{y�0��������`;Q�3�]~>W)�S��.#n���o[g�=\�.�5���s�J^2bD��(�#��"��J
��ax��KS��#����������g�-K[�u:�hFB��g�Iz�8����h�5#	r�(X��<�\�3����>��@����6�W`G4	z������6F]�>/v^��a�3�cA�(��S���&"-a�N���N!�u(r��j��~"���g1�����E��Kcf���.EE��O�F�xh,Qz�|Z8����p� �E��p�y��s�����H\#�C������,x�\h�F4�hL�iQa���3����	��7i��}`���>?�/���M�\&�t�q;�M��4�#D���x�qe�������)�o�w��s�����/z	DC�l�n[4fD���IBU���=�'��
jkw,��;Z�<�	*��s�y�F�+������`�8[��7WG�o��h{0f���{`�y�������c�^�Q
B�&������	E�B����
?��'��9���89"���
u�1L��/�=J��k�7!��%��K���&��R�WH��&d�vb�I��"-*�V�iD5c�>��v��(d����B�e1%zZ\���+7����Y�c���$T��1WIY#�<�?�uT5m�a��C��D��b�����%f3����Y��O�UD��Da#��|_�RNt��B.���������Q���������;����
x�_������7~[Mx&��.�����o$�P���p��N(���\s�Xwzj:Yn+�w/������
B��N�>��������|�Sid��86�~Z#cL����K��pp�A������m�WzQU�i�� �~
�B��x{��Y@@+���i����~B��X���y:N�#�����
�B2��=�U�����Q?+z�8��0������TM;������9��z�ruI��$�;��4V)�"�@q���T���6P������#����
��i��V���u�H����B��!cq�5G_g��iU�72�����[�k���c(��c��L�Z�!=�,
-�#�8�";J�W���I�*���_�D#����)kUA����hq�}��^#������
�'=s���[T�hN�5�A[�d��v��S�ax��')�M������234�N63��������5(1tu#B������L���!2��$���"��M���F�S�9dl)�V2�^R+��������q�t��T#��G�9s#"Xk���^�d�S7;d�xLb�R������������B�j�rJ���W$��5���m�%0�k�47���2��m4���dL�r�����a�OY����
KX��x#��x���!F
�7��@����3������[���SQ&���4O���byiGr��yGf���;�$g���i1�W2Ky��n�
������1.���6��&�o����4f�(!�d� �?Y�'���dQ��;U�j�6�5u���g ��EcR�#��\�
�CT����g���]��eK6���p<_i�"������q8T�I_�q:�k��K��Hh���]�.��^���y����G�����1�.�k23��
�E���D%��%�of�����z���vP�������������O��~���J��W-��}=���#{#s=������RV����8��X�L�BZ�Z�*���J
���
������p-����E,�_�!���R�^�y��i��4�� �����|R��qi���j��5�%Z��G�Y�[�J�������+�D�+�����W�
��;�'t�B��.�5����2�P	�����g���c���p�k���;��<��EN�lrk1BL@�L��$.�"��]f�$��9N�qUyS_.�V�]	�����Aw�7�h�1E�}�F��Fg���x�h�`����{��,�h��d�k/�1�6���h���W���S�����/>��m���H�h��m"�XR?kfiI��?����������Y�'�61�EZ'"�`l���P'"�����hW�#AO���	�q��:
w|�`�!��:v4@��9����H��������29��a����d���]�z�K�z+��nP�]�Z��R��,,T��>Y=���SCd�F���L0U<��:3[�yO^��V6z{��?�.�o��+�����&�P���\_����T����M$xa�vY�S)I���04�\���26��7��_���uFw�G����r ����,5�t-#�VH�k�m_fL��6
�u��S��Vw���b��.R���������j���Ss{V��fYu@l8*�W��*?Y6�eX���-D����TK��7C��n�A,�M#j,��f�c�����8^q���K��/t9��L�m�\3�8�A����2�����$L�C����4���3M�~�N�L���{�*���:)�r{5����D�L��]���<}�����X����S:�������;�DS3�-�jw��m����i4�p3}�T��QUz;��������7�{����������@d3E�` y�u��"[02S�>��
@�2�I:�C��a�+=C������gx�������+<T���Ng����PNWNx��'{_%<���\���a��!����2A�n�>�`l�
dn�$Y��4%Y�9��6<SYd%	��d�z����u�v�H���������C�`4<`�c�P<��7}�<�mi$!����������4����^H�}wu�����t%[������+[d�����^y���/,d��?��+�l�wb�8X�������jQ��r	j��$P&�1�g����:��FJ�_��Yk��4��,�5n:���<�HYK6�4Q��F�4��:�D0k�k�*��]U�{w�����*��`sI�3��!�M��X�79+����q��g��0YYVf|����L2����F��5�L��sz��N�I������LgZ*�K.,u�"L�Q��/	�/
�pH��kF�#��}s����9�:�j�w�D
���/�������^��%�Tq��E/�>���_���n�`���?Y��/�w��o'���{Z�"�9L��
S0��M�!��H)y��e��/UU}s��hZ.�m��I�T}����gW��
AG��/6�?fp8�V:����&S
���z@Nd��t��+�'kXn>�o���<=�S}�&�q���x����8������_��P�[>��/��%F�pF�,!��{�~��	�����"�J/�No�p���s�8��'z"�������a22a�3/��������L&�`p������H�}HW4����{���~�gPaV���UN���BNM'�R��g��$��p�/g��c�����1,��s������/��'�}k���I�]r`��)'�?D8�_Hl�|w�����W��8J+
|t��y�'�J� G��IQ��j f���Br���k)��
�TJwRnn�'�,.Z���6����c&3]��hz�H�ZL���L�!3iJ�ZO�0C���_����:��������0q���GYUd����#�N�WG,�C���){\k�[JC�H,Z�G����;}OM�0O��"���!2���%K��3��PR������Q����_\���9}
�P�����h{r�b���`������3�AF���<���p�p$��)uf�7����_�}�_�Q�LB�� ���$G���E�G!�/'��,�<�%�hk��=����)�nL��f[!j��6Y�{1aR��4�E�K�V���V):�D1�����\���d��SU�'��@t��U����z
�����bO�*S�$I��<�`qn�����!�I�O��8��L�����d/���u���;����$Q��)3�C�_E�e�������E�@%��J��_�g�vn2�p�}���>�^���V���P�:�:8��5�4�h��*��F+l����2��;��V�oY������p.x<�`#���<m3����#/�	���J�:DO����u�)t�m�>ii�����D����d"�����D���b���.1��?��k4�@��n]?�9{�I��gl���+j�wR>e���#�m�
�qDz�w�5I*
l3	�����������)���9�)��N����Cb�c�x�
"M����8e�6�n?@���S,�(q�z���>�}R�$����
>�-=�w�2�"4N������t�u��x{y~�Z��
|�x���`����ve������s�Om�� @��w�������	�~Y�����h�N�������a"���������
��Pz:�pS��y�~L�G&�h���4�y�vXc�r>(c���0��w���i�_f�d*�����t`5 r�D����7�O3P�Y���5'c��Y���R3gf�{����d//��Z]�)~J��p��T\��������%O�m���ro��u-:�=���M��%J�����n�(D���3�rG�;m���E*���>�LB��/
�Z�^x�I�yB�*6�\���w�NpIn�S��I
��7��F�N�����U�>Z��^�z���6�Y�@��o
�]A[��Qzsd5Q?�������2���D���q����H�#�i���D���|h�� �8f4"���nH��n�\�(���g�jL`����xc��1��`E��ME>^�j��f��1v>!=F���>3�I��p���������������[�\�������O��kl8V.�g�0��eL���5������Xe�H���A�H�k3��T=�d��v��LS`�L3�`b5)�n(�7o4��"0/sqr|x`������-�u������T{m�����4l,���eK��]�7X<Jt���QU9��������[��F�q���XO�n�J����R�3������Q<M�G�&��sN�)N��Y'H���(������������L�
���13�/�f]T��"Fe�`�]{�%�O� ;}�Y`�2���Lc�D��'1�
��+8G��{����E���\���F�{��]Je����{���r�{�|T-�^�n���t
��_����@=2 �����r��x������8_�'�f�����D���7D\����)����r.k���;�a�[��;?D�
���gR{��<;�(q��uL�og"�����X�Mil�(�S����C��b~�^��YLD_�z�����
��������k$���+C��SW%f����7p`U�8�3��)��s�;��.$�ZqP-�D�7�R�/����%%WxJ��V.�2����Z�rt@s���h��U����_�bYq�������")�oc-;�����^�1��m��v�t�x��V��(�#�2��)5> �M���w��t���!rC���<�������&.��6����s���qQI�^��K���,C�O��\8�m��j����$1���9��]Lj�|=�@�
���tx����^�<�WN<Z�{�����Z���������.kkk��8���/�/[��j}K���m��"��2�CY��K��S1F��l�g��t���x?�n����)�B�q4�w��.A��W�c�m����6�_h%���/���*�(��
����q+nCM���k�$`��[��W�S�:��N+��(Z�!���BA`v�ao���+�?�;ai:�5��I8���q:�i��_�Wy��lW�a���T��e��c��o��
��t�&�dQ+DQ;��������!�[��!
I|Q�Iu3i���(�o�h��2�L�����d��!#�x0?�����w]�M�[���2�8-�&��i�yu��U��I�N���� �=�y����y���)����y��LM�����AFk�;~0S�lCz��)�RN�r�/���l�iWow���V���j�A1]�i"K�r
!Bo�Uw�*|>A�;U�,��B�gRa�q$���K�R���0^�������]�[Z
�}���EP�o�X{P��.� �n3��#�T��WxA~1��CI�� v������#8�`F~`��z@q�������Bn.	�Y�f>>�����+`�m�n���~q���'���_dy���x����C
W�p5'x��U���d:!�a�����;���Ns�A��&���Fl�l�\������������������nl��6k���N'�i�&;��������{U`��s5�,h��tw{cQ�b����j��B�u}Q���:#7f �����JbV\������-���
BFo��I$�����a�M������# ����^���do�V��h������(h&�ikm�����i��4:�d���c����UJ��']l�t��z��m��0���)`n)� ]/GJ�����jA���M�[
��Sy��H�	Q�QN��S���m:A����`�8G�mu8�~!���C�R_sn!��������2fe\$S �+�n����)IeX* �
��ID��
U}w���9m�Z��hmoom"�����?��.�h���)�we�� �^
�C�3k4;Z��v _i,r���lZ)��������L��u�%�f�K`�u��5�O��N��H�.�t��(���W�w6�xQ������	4\��`d��.9�-*���g�@���W����^��G;�8����|�$�PSx%���F����M< *�?�]j�$O!�����q�dFcE����)X����)u�Y�GFmd��F�b�-C������O�*C�S���tS�;�<Z����w^����;�^��~�^ww��5>��W��t����5���%�/�#A��d:�{NV;�e7��)'�JciA-�ZA��=���;�Qh�
����~_���L*������@M\�Vi���h�{7�������gP�U�P�X��qO�8^	����b�N0%'���V'�0������N�����~u3M��eqs��'-l,]���V����v������m<N������3>)N`PN�4��r������u�����7�:���#�6n��
�������{r�I��M����&��f!�-�{����ao�p$[J��~�\|�S������M*!E
�c�M��=i�>KIX�ds��X�dg�Z���5���*��v��/���\�\%�9�<�8������s+L�}K
%SInBLQ�����l��[����P����l����Xh��o�K:(����O���� �c��moQy�d����Woa�\:x����B��gm	�o��t��\��-�.�)�tK�t���&]��y����hk��U~*��Kw� ��������m�D��� ��1�s�,�,/������erS�����2�l$7Y;Ri������.��Ng�!,����,�n?���	9����(Dw=��������������J
'F���9��g;��)ad�+����3�)d��M���e��	�]�[[f����D(��Xs���knf&�g�����e����x!�����e�,���*�x��9+c�
�����{������Q>	��aLN��4����{R9���yl�aT�{��51�oK���5��@(���(��r\&���%��ao�j�B�=��.��2.�������5��Rdx��y�KO�
Q�����$o�U�x��fzC�b{���5�R��Q�j|@�D�v��g�h��S�b����=��+���v�\��8mR������j�jd���V�dG���x9QON����&+$k�����t=�P�d������`8I���2<���Il���U)OF+�}�����$�6��0h/�3r�[�E��8��r�o�������<p������������3y^I�=�������h:tp�=A�	���dw�5BF1W�s���V���76��I�C��o6�^�K���`��.��@��wQ�@y���W(�#�2�w�0���_G~�10�(I�Y3�$��
�d6�H4�D4�@���MM��~v�A
�W�;;���^E%`K��dw�BC)���p�������ZN����������}�3��f���BJd^fi��{Q�4ep��+�����������+���>��z%���i��������`cs�^R��uwv�N8���i����)BhD��;�e�J�}Q~��.����eEn���d��VJ��l4��(=!f��D���A��������Ig����	�p:��" ���@�N��h�RV�� �`�b&��}e�BOU���vT��i�O��	�)lOg�3B���j~�%9b>��t�:������mW'���F�^��=P��erFv���2�d�m�M���=!%��~�P�d�<����z�7�U�H������#V��j3����F����a��:h�xv�������&��'x���q<�,������{d
�u�C��vI�����'�|4O>����W1������=uF*��k�*�+����w�5�'\!��p������wo.�;���������5.����8��AK�\@7���&=�up���a�,�0ml��|��M���r���������6���P/T��]/����i����p����;��D=����f�>^5�����r���4
�k�E�F���r��vvz�[�Zmw������H�`���2DB���F��\T���.������}UQ�M�gc��w g���7P����0N������Q�yxYY��H`I����7��mc@�XJ��K0y[����O��������a��5�g����jiI-���U���nmnnT���[�#��zB�����'�Ws�G�0��8`��������L��~�u_w�
�($�6E�}DG�H������'�F���v#��+�la���S�T1�C��s-�������|�E�����#4�����n����V��[����I��, %��'.��B_hQJW�K�T��s���V��������S���^9�'�~����;��D�^��`:��4�.��
�T����
orsN�.S���wry��r�#�]����/'GJ{o�S�vj������nw�#%�R���*KG���g}��GI��f���cD�]Ty��e|���-(����]_Q�������9��j6	�B9K_�i��6*a�X�����$x��%u�cx@���,~�n��J��0dG�c�;)��@t:���d���a�!���#�r�6��J�
�'Y���s�������u%�^U��z�����4�����[t����t)y�]���uzpxy��%&�1&8�K�#
��(�� ��B�?�`���K�sJh|��n���k�n���b|�k#��y������6�C��R�%���M��R���q���F���r�F���Is!���5+:���+Q���-�hrgN�,���=6w>:��$�h���0���9� �m}qUfE���bf,K�V_����mf��{Q��o��%�5aImzy�;�	,�ZaWd_J
�!�%�@�9�f�kj�3�T6�����:4��h�
�����^�Lo��
{�Qc��5.����~�g��|�8�Xp_h����p��fzr��ZK(�!��$����R@�?4�\�_��S��gt�����X�L}g����Q-�v��US1K\�+�(O@��V��}�VE�3N�
wp|�j��qv�>��gh\>>��/��*~�8�g��f����]K�}{y|%oa��_V��0��C(qr�����2'?r��zD�f�9@I���:�Y�r�(��%Kz
K����s�����w
�q�������5�q�����(��!~+�3`VK��4��j&���w����;�/Qo��MPT3B����0�9�f\Y��+)$�~Mmn���,I69�WdC���6���PUG��������:��M,�u��1�����T>�b����KYc�{� �������V$
#��dc=#�F5!�;�v@L�u�'��<�UFa�6������=��S���H�cBF�	Z?9u��K�9����m� �s<+��"?��y��vvP���sb�Q��������{���l"�K�uXt�>�{�aa�q�����}T�^��h|@����A$�������p �=�����q`_T���7%���E5���MpqI���������uv[R
���U#@E��9�A-)�6xNG��r�@�///[^Eg9�B/����3(��^9M�vv��6k����f��+��[*�~Y� ���)�q�	�XeE������I %$k��pYU��K���)�d����V�G#+(���	d����B���v3.?Q�p�����Jx��E){��GK�2���D0��2��":��3Y;���3g�+����0vV���J������z�X�����i��X�����������+70�+�P")|%�����~��� BM��U~��e�7.�hi��0��J6��g��3_\K
4c�i�ANE�V>�{6hO���yU�&~j��*o4�������^���9�C�H9���&���|g�����H����lo������x�lOw�����l��_��7������_(������d8�39���g'.�����@^��oZY��`�u�~�	�R���
������!$��x��������!���;&���)�(��X7�WW)AKFt���8���@s�����&����L�t
�Bo��0��5LN���n�~'���(���Ut(S�������}���y�4�+t��&���2K����U?j�~56�`�����M1�8��uc�����!S\^��\�
j���}�,G���K��9�: ��"�5�F�����u<A�<�#�e
���d��|�/�����4/R�V�z�p����e�8����/_/S
�|�6g]Lrt��nY"�j��#����
�	����fV����R��M�Q��>6�7�~����]�=��6�K�{���c�}��)�cSn��JQEs���#�P
�.�OS!
��P�mE����V,	���
�&��������K�
7.V����
D9�^��w����2���>]�^e+�{�zm�*���4���I����	A����"��x��cl�O������=����ot=T�9����8�N�2����tme�^���F���@��y(�[�3����b�k�q����'�u$�`�I{�^��l�{����
��C]�|�(r��x���H#�?��y����=�A�W��wA�Jj��\Z
7�S�$����v����S�����s��P��J�����������r�9���Q�A����^|��U1�>z�5]�;���
^Lg�v�cb����g���������j[����N���ew����'tV]���*�^E���/�<jQn~Q���j�����������~��$%��Z������&��(�]#f�(���������W�]�c*�f?r�V����h|�Y�4:I��|_���Q/���Z�!����q�<L������-�p���������}f��q�����w�Vg�."fr�zm4pW,���B����v�����vs���Rx�[�6Z��w���+hB��!�w�?�`��)�f�2Z�O�b�������Z��Q~�0Dd~.=���[S��jIX�%'M_a�RV7N��t��US����R�����2@�]8S���A���N��-�4{������s��p8���������
i�
�K�|l���~Q���1�����)%������-�R���~��J�������D����x��������G�l�4�TRCS�E�Hj�#��9�������c��.��������g���Q�O��j�:/���iA�Q~�$��U�0����K1y�{|'*C�zJ��B�AUa�B��Jy�C��Oj������U�F��7��E��������j~�n�"E�Q���N4�A='|mG���9U(�g1+��n�T�]�5Y�Dke\�i
~�M	=�������y��o�}�9^8�=���X��:���a�\I�4^]��=?>+����G���3g��&�r&��'�RP��/��3U����@g�S�������Af��;���]�t]�u����Y�`�Q��e��R.��k�d+���Yd.����z���U�/���� e�_��p������f�_�F-�����������3,	:;���{��c�e�_P�����+�Oy���������}��e
m���5����0"�7�����[+�@�4|T�lq�����A���y"
�a�4G���\��5���`���#��znU<|1|��TFc�����2��C��	�������`���<���qpt�(�Z~=������'�����;���wY�U��{�,��Q^����������QHB�J�iJA�t����P����������!�g�����_5�r�D��u��� �p���)]`�� ��k�'
mL.�������qF���Y�~���T��M�������lm����`|=%�7J�6FN�76����|g���
���AO��[s��p�.G�� ���T��b8K�������1"���()����I�v~=}z��6C����:u�Wk(���������;�?��e_<�@�-X_T�7���L�^{AIC��%��Xf�_�xi�0r�����I���r���7>�^�q��m�(�-	j�������w�k�)���aK�@������I/>aN0�r��^��)&�Nm,��`)�sw����A�'���T���e�������:5��t$�A�R"vS��.�}W&��5����E��R�������YVt�D�J�����j���������!r��%f/!Y8n���0-�!\�V<�����W���0��T�����>a���n��L����:8:�W�j��K�a��w�e�qU���Q��f=~�5��l^_��������>z��?i��s=��y��f&f0�\���V2�m��h��7�iw�)H-�Q��A
8�%+�K���>�zoR8,�oY�����Z�=�) Qf�f3�gKy=�A�g�t8�<x����l#���B��v+]����d�P��'Y.0�����N~�`P�s�`���X�,3,,A�����4^U��Z�X��������i�av]�
V�[]����y�=�]��J�]��\x��l�w[AS��f�����z�67�����#�Av�W��s�,}�����yc�_f�<�Y���o�/�/�T�������&o��l|��q���M8�J��?��h�����5���;^�r���
3���TRc��?��i*cX��t�:?������SrP�M����'r������,�=[�)�x!�r��g��P*o�@�����e]������a�R���{����`���l�n��M�����0��
��2�����m�S�48��u��k�D�n�]#��$;V��M[}�3�yF�t{0'����Q���E���~r�_�N���	�u G�F�{A�GN����<��[��Dar�s��lW�����J�����e~L��T�<��A;v&s\�	��]��������7�
?��Lv�Tq�u�'��XH��e���;��U��
8��D~>7�_�8��s��qS�;r�[������?O���]4��s�I�;������=���a�p*Td�����p�v�1�aN9`�~*d�$k�H]��������4H�����:�Xw�\a6xs��Q�q/H&E"��9/�6��i�u�-6�E�><��n�3;q=���
���n�8�A,���d�g��c������M�� ~����?o���g8�n�Fq�.���W��&�[6�`}<������f���&�x��E��
�\��h�F�8[�+1Q_h�7?��?����T�?������_8��zur~�u���?NlN�?^x�A~k�wi��7���8B�������h�� �����t< X��#;�9��
H4Zm�QgXTiU1����g��6�Q������$��
4��n����������y���{����?\�����Z������N�y���������1�[<^�f���~#�{k�����4}�Zf�"���Q#^)6��g�������[3W?N��(�����8u����.�^7���5@/�G�y�VGB�%�xr��e�����Cc���WP��/���"4�O����D���J��6`��	����	�
$t!ff�������� ��������1,}�5���=��,+�W�4���+u`9���������CU6�!>���y�����}������y��wt�'l�Ow��|�Vs�8R`������,��,U�R?:K��^�5,S����;,��_�������	Si>iX�	ej���N'�M:?����a�RhmJ]i����t�4~)����`,M�W�?t�����f���mg�����7���4]��t�S��.�r;����H`����*�a�5sQ8pQ���l�E����8��a��3�2���;����t���YG�s�uu��}���lH!'�$���7��uy��R�m�`��-���&��9l.[H��-z�3�Z�`�7�����la�o��W��r�tJ�!RMf�T������?�s����4c��.=O���yZ^���[�<-������-Og�����/���	5�p�k�d)Zxy]�����]sN���������<�o�x�����lu�l��e�[��V������p� K����P�z�%�r��]X�O�����GR�D>�U��S�2g�
�qYN3�;l�B	*�0I���6���`�"�����r�};W�[����ne���h�b�i��v�FwL���4�Fu�2���f�b}���NW]�����w��)W]���������*�����������]%�V��1��/9�q���s���*�.%i����XF���n3�=���^�]!�J/���r�������������"�O��e!������O����
�Vxz
*����B���\��)F��^��m�������B��a�y�@��-N~�QECJ�r�x�����/(u�T���4�\�a��5�S���,�!G}�dh��-��Fu�����gm����g���g�3��]�����?moTw\�y����FK���Z}��O@�}�b���n���~�
���x��-K��-|��i���Y��fT���$��8��%�0�3��p�.�W1���v�m\�*$4j���
�3�������\��]�x<���Q�R��Q�����UD�SX�o���9��>g�$�Q���9�=��(1o�7{��������
����V�������lu��l@���q���p\T�>-�n~��T��u���[��n�}�n������0�����G9�?���y�"�h��F=h��P����j��_�VE%����:n����-��_�.���|.:��R��n�n�����
joK���F�S{���L�d������A56#o����_\��m�o!�E�.�(��Y��>�/]|��/!��;F
[:�n����n-c�@����el�R����c�o8!���=�f�!��� >�o�p�V�Mr	~AOK 4�e�z�����%��%;T���>2C_�U�v��La'���������~������l�%3���#[l��E���ma1~d�����Y?��	h��`�R�
�p��k��TX2?��Y�%��TEY��~���5h��%�=�Y�A�	t?c[��~BsgZ%`"7��8��m�iM�[���R������2Xk{k�����T�3��E�`f�g��V����IMm"l���rS[nS�{4�%[i�4�dm�>
lWw���<��v>~D�hf'�2y8b���2u������/�r:Tmb�0�3����-����8(����p��
��������l�ztY��gn��
f�-��Jh�������CoHDZ���>�J<U]%a'v�
���G�]�����	97v3�GWb�M��?�������v��v
�S���n���3k}�����xQ���s6�?dAT9����� sF��e�}�m�1�L]Q���kN�����U�*i����I��`�FC�m���������9�ysm?�y%
���h�
8W������E�,��;�K�Rd
���wZX|{��~^�)�'����E
=�b������kuH�*Z�������;�,��8�?�3B�nv��3_O���#����/>��P����(i�p3���������j!��7���?W�z|�,9�-������Q|�@���^���?o<����l@��LI���A.���������V\Pl�_�����S������Tw�`P�~��?��T7��-R��#������L?�4
1�d���T��kWa�\�&Ne;3�������/�lUg����B���U���.�����UT������������gT����S���4��LE����w3Ni�Q�=D�f@n�[�{ml!i�>S��~����<3�5x��B~������x����qK��k����;6P�z���H�7�6��M�������@�ku���D��9���<(��r�n}�p�����n�Gx����9��})9aVA�����*� ���������,1is��)GCn�v�\gFCUV��,h�p�s�)Wi�H����ti�c���.�[t��.��g?B�E
��t�9�������������O���� �
LE:C���4s�9���j f
nX{���VH(�l�k��0n��b��X=z�����q���U���l;���ko���T-��Z�#��S"�WH���n���X>[�_�+U�#Av�`�xN�vN���$��+
;"�|�Ukkjq��������dh���
0006-Add-separate-synchronous-commit-control-for-logical--v12.patch.gzapplication/gzip; name=0006-Add-separate-synchronous-commit-control-for-logical--v12.patch.gzDownload
#126Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Erik Rijkers (#124)
Re: Logical Replication WIP

Hi,

On 09/12/16 22:00, Erik Rijkers wrote:

On 2016-12-09 17:08, Peter Eisentraut wrote:

Your earlier 0001-Add-support-for-temporary-replication-slots.patch
could be applied instead of the similarly named, original patch by Petr.
(I used 19fcc0058ecc8e5eb756547006bc1b24a93cbb80 to apply this patch-set
to)

(And it was, by the way, pretty stable and running well.)

Great, thanks for testing.

I'd like to get it running again but now I can't find a way to also
include your newer 0001-fixup-Add-PUBLICATION-catalogs-and-DDL.patch of
today.

How should these patches be applied (and at what level)?

20161208: 0001-Add-support-for-temporary-replication-slots__petere.patch
# petere
20161202: 0002-Add-PUBLICATION-catalogs-and-DDL-v11.patch # PJ
20161209: 0001-fixup-Add-PUBLICATION-catalogs-and-DDL.patch # petere
20161202: 0003-Add-SUBSCRIPTION-catalog-and-DDL-v11.patch # PJ
20161202:
0004-Define-logical-replication-protocol-and-output-plugi-v11.patch # PJ
20161202: 0005-Add-logical-replication-workers-v11.patch # PJ
20161202:
0006-Add-separate-synchronous-commit-control-for-logical--v11.patch # PJ

Could (one of) you give me a hint?

I just sent in a rebased patch that includes all of it.

--
Petr Jelinek 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

#127Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Petr Jelinek (#122)
Re: Logical Replication WIP

On 12/8/16 4:10 PM, Petr Jelinek wrote:

On 08/12/16 20:16, Peter Eisentraut wrote:

On 12/6/16 11:58 AM, Peter Eisentraut wrote:

On 12/5/16 6:24 PM, Petr Jelinek wrote:

I think that the removal of changes to ReplicationSlotAcquire() that you
did will result in making it impossible to reacquire temporary slot once
you switched to different one in the session as the if (active_pid != 0)
will always be true for temp slot.

I see. I suppose it's difficult to get a test case for this.

I created a test case, saw the error of my ways, and added your code
back in. Patch attached.

Hi,

I am happy with this version, thanks for moving it forward.

committed

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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@anarazel.de
In reply to: Peter Eisentraut (#127)
Re: Logical Replication WIP

HJi,

On 2016-12-12 09:18:48 -0500, Peter Eisentraut wrote:

On 12/8/16 4:10 PM, Petr Jelinek wrote:

On 08/12/16 20:16, Peter Eisentraut wrote:

On 12/6/16 11:58 AM, Peter Eisentraut wrote:

On 12/5/16 6:24 PM, Petr Jelinek wrote:

I think that the removal of changes to ReplicationSlotAcquire() that you
did will result in making it impossible to reacquire temporary slot once
you switched to different one in the session as the if (active_pid != 0)
will always be true for temp slot.

I see. I suppose it's difficult to get a test case for this.

I created a test case, saw the error of my ways, and added your code
back in. Patch attached.

Hi,

I am happy with this version, thanks for moving it forward.

committed

Hm.

 /*
+ * Cleanup all temporary slots created in current session.
+ */
+void
+ReplicationSlotCleanup()

I'd rather see a (void) there. The prototype has it, but still.

+
+	/*
+	 * No need for locking as we are only interested in slots active in
+	 * current process and those are not touched by other processes.

I'm a bit suspicious of this claim. Without a memory barrier you could
actually look at outdated versions of active_pid. In practice there's
enough full memory barriers in the slot creation code that it's
guaranteed to not be the same pid from before a wraparound though.

I think that doing iterations of slots without
ReplicationSlotControlLock makes things more fragile, because suddenly
assumptions that previously held aren't true anymore. E.g. factually
/*
* The slot is definitely gone. Lock out concurrent scans of the array
* long enough to kill it. It's OK to clear the active flag here without
* grabbing the mutex because nobody else can be scanning the array here,
* and nobody can be attached to this slot and thus access it without
* scanning the array.
*/
is now simply not true anymore. It's probably not harmfully broken, but
at least you've changed the locking protocol without adapting comments.

 /*
- * Permanently drop the currently acquired replication slot which will be
- * released by the point this function returns.
+ * Permanently drop the currently acquired replication slot.
  */
 static void
 ReplicationSlotDropAcquired(void)

Isn't that actually removing interesting information? Yes, the comment's
been moved to ReplicationSlotDropPtr(), but that routine is an internal
one...

@@ -810,6 +810,9 @@ ProcKill(int code, Datum arg)
if (MyReplicationSlot != NULL)
ReplicationSlotRelease();

+	/* Also cleanup all the temporary slots. */
+	ReplicationSlotCleanup();
+

So we now have exactly this code in several places. Why does a
generically named Cleanup routine not also deal with a currently
acquired slot? Right now it'd be more appropriately named
ReplicationSlotDropTemporary() or such.

@@ -1427,13 +1427,14 @@ pg_replication_slots| SELECT l.slot_name,
     l.slot_type,
     l.datoid,
     d.datname AS database,
+    l.temporary,
     l.active,
     l.active_pid,
     l.xmin,
     l.catalog_xmin,
     l.restart_lsn,
     l.confirmed_flush_lsn
-   FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn)
+   FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,

If we start to expose this, shouldn't we expose the persistency instead
(i.e. persistent/ephemeral/temporary)?

new file   contrib/test_decoding/sql/slot.sql
@@ -0,0 +1,20 @@
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot_p', 'test_decoding');
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot_t', 'test_decoding', true);
+
+SELECT pg_drop_replication_slot('regression_slot_p');
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot_p', 'test_decoding', false);
+
+-- reconnect to clean temp slots
+\c

Can we add multiple slots to clean up here? Can we also add a test for
the cleanup on error for temporary slots? E.g. something like in
ddl.sql (maybe we should actually move some of the relevant tests from
there to here).

It'd also be good to test this with physical slots?

+-- test switching between slots in a session
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot1', 'test_decoding', true);
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot2', 'test_decoding', true);
+SELECT * FROM pg_logical_slot_get_changes('regression_slot1', NULL, NULL);
+SELECT * FROM pg_logical_slot_get_changes('regression_slot2', NULL, NULL);

Can we actually output something? Right now this doesn't test that
much...

- Andres

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

#129Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Andres Freund (#128)
Re: Logical Replication WIP

On 13/12/16 01:33, Andres Freund wrote:

HJi,

On 2016-12-12 09:18:48 -0500, Peter Eisentraut wrote:

On 12/8/16 4:10 PM, Petr Jelinek wrote:

On 08/12/16 20:16, Peter Eisentraut wrote:

On 12/6/16 11:58 AM, Peter Eisentraut wrote:

On 12/5/16 6:24 PM, Petr Jelinek wrote:

I think that the removal of changes to ReplicationSlotAcquire() that you
did will result in making it impossible to reacquire temporary slot once
you switched to different one in the session as the if (active_pid != 0)
will always be true for temp slot.

I see. I suppose it's difficult to get a test case for this.

I created a test case, saw the error of my ways, and added your code
back in. Patch attached.

Hi,

I am happy with this version, thanks for moving it forward.

committed

Hm.

/*
+ * Cleanup all temporary slots created in current session.
+ */
+void
+ReplicationSlotCleanup()

I'd rather see a (void) there. The prototype has it, but still.

+
+	/*
+	 * No need for locking as we are only interested in slots active in
+	 * current process and those are not touched by other processes.

I'm a bit suspicious of this claim. Without a memory barrier you could
actually look at outdated versions of active_pid. In practice there's
enough full memory barriers in the slot creation code that it's
guaranteed to not be the same pid from before a wraparound though.

I think that doing iterations of slots without
ReplicationSlotControlLock makes things more fragile, because suddenly
assumptions that previously held aren't true anymore. E.g. factually
/*
* The slot is definitely gone. Lock out concurrent scans of the array
* long enough to kill it. It's OK to clear the active flag here without
* grabbing the mutex because nobody else can be scanning the array here,
* and nobody can be attached to this slot and thus access it without
* scanning the array.
*/
is now simply not true anymore. It's probably not harmfully broken, but
at least you've changed the locking protocol without adapting comments.

Well it's protected by being called only by ReplicationSlotCleanup() and
ReplicationSlotDropAcquired(). The comment could be improved though, yes.

Holding the ReplicationSlotControlLock in the scan is somewhat
problematic because ReplicationSlotDropPtr tryes to use it as well (and
in exclusive mode), so we'd have to do exclusive lock in
ReplicationSlotCleanup() which I don't really like much.

/*
- * Permanently drop the currently acquired replication slot which will be
- * released by the point this function returns.
+ * Permanently drop the currently acquired replication slot.
*/
static void
ReplicationSlotDropAcquired(void)

Isn't that actually removing interesting information? Yes, the comment's
been moved to ReplicationSlotDropPtr(), but that routine is an internal
one...

ReplicationSlotDropAcquired() is internal one as well.

@@ -810,6 +810,9 @@ ProcKill(int code, Datum arg)
if (MyReplicationSlot != NULL)
ReplicationSlotRelease();

+	/* Also cleanup all the temporary slots. */
+	ReplicationSlotCleanup();
+

So we now have exactly this code in several places. Why does a
generically named Cleanup routine not also deal with a currently
acquired slot? Right now it'd be more appropriately named
ReplicationSlotDropTemporary() or such.

It definitely could release MyReplicationSlot as well.

@@ -1427,13 +1427,14 @@ pg_replication_slots| SELECT l.slot_name,
l.slot_type,
l.datoid,
d.datname AS database,
+    l.temporary,
l.active,
l.active_pid,
l.xmin,
l.catalog_xmin,
l.restart_lsn,
l.confirmed_flush_lsn
-   FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn)
+   FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn)
LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
pg_roles| SELECT pg_authid.rolname,
pg_authid.rolsuper,

If we start to expose this, shouldn't we expose the persistency instead
(i.e. persistent/ephemeral/temporary)?

Not sure how much is that useful given that ephemeral is transient state
only present during slot creation.

--
Petr Jelinek 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

#130Andres Freund
andres@anarazel.de
In reply to: Petr Jelinek (#125)
Re: Logical Replication WIP

On 2016-12-10 08:48:55 +0100, Petr Jelinek wrote:

diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
new file mode 100644
index 0000000..e3560b7
--- /dev/null
+++ b/src/backend/catalog/pg_publication.c
+
+Datum pg_get_publication_tables(PG_FUNCTION_ARGS);

Don't we usually put these in a header?

+/*
+ * Insert new publication / relation mapping.
+ */
+ObjectAddress
+publication_add_relation(Oid pubid, Relation targetrel,
+						 bool if_not_exists)
+{
+	Relation	rel;
+	HeapTuple	tup;
+	Datum		values[Natts_pg_publication_rel];
+	bool		nulls[Natts_pg_publication_rel];
+	Oid			relid = RelationGetRelid(targetrel);
+	Oid			prrelid;
+	Publication *pub = GetPublication(pubid);
+	ObjectAddress	myself,
+					referenced;
+
+	rel = heap_open(PublicationRelRelationId, RowExclusiveLock);
+
+	/* Check for duplicates */

Maybe mention that that check is racy, but a unique index protects
against the race?

+	/* Insert tuple into catalog. */
+	prrelid = simple_heap_insert(rel, tup);
+	CatalogUpdateIndexes(rel, tup);
+	heap_freetuple(tup);
+
+	ObjectAddressSet(myself, PublicationRelRelationId, prrelid);
+
+	/* Add dependency on the publication */
+	ObjectAddressSet(referenced, PublicationRelationId, pubid);
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+	/* Add dependency on the relation */
+	ObjectAddressSet(referenced, RelationRelationId, relid);
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+
+	/* Close the table. */
+	heap_close(rel, RowExclusiveLock);

I'm not quite sure abou the policy, but shouldn't we invoke
InvokeObjectPostCreateHook etc here?

+/*
+ * Gets list of relation oids for a publication.
+ *
+ * This should only be used for normal publications, the FOR ALL TABLES
+ * should use GetAllTablesPublicationRelations().
+ */
+List *
+GetPublicationRelations(Oid pubid)
+{
+	List		   *result;
+	Relation		pubrelsrel;
+	ScanKeyData		scankey;
+	SysScanDesc		scan;
+	HeapTuple		tup;
+
+	/* Find all publications associated with the relation. */
+	pubrelsrel = heap_open(PublicationRelRelationId, AccessShareLock);
+
+	ScanKeyInit(&scankey,
+				Anum_pg_publication_rel_prpubid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(pubid));
+
+	scan = systable_beginscan(pubrelsrel, PublicationRelMapIndexId, true,
+							  NULL, 1, &scankey);
+
+	result = NIL;
+	while (HeapTupleIsValid(tup = systable_getnext(scan)))
+	{
+		Form_pg_publication_rel		pubrel;
+
+		pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
+
+		result = lappend_oid(result, pubrel->prrelid);
+	}
+
+	systable_endscan(scan);
+	heap_close(pubrelsrel, NoLock);

In other parts of this you drop the lock, but not here?

+	heap_close(rel, NoLock);
+
+	return result;
+}

and here.

+/*
+ * Gets list of all relation published by FOR ALL TABLES publication(s).
+ */
+List *
+GetAllTablesPublicationRelations(void)
+{
+	Relation	classRel;
+	ScanKeyData key[1];
+	HeapScanDesc scan;
+	HeapTuple	tuple;
+	List	   *result = NIL;
+
+	classRel = heap_open(RelationRelationId, AccessShareLock);
+	heap_endscan(scan);
+	heap_close(classRel, AccessShareLock);
+
+	return result;
+}

but here.

Btw, why are matviews not publishable?

+/*
+ * Get Publication using name.
+ */
+Publication *
+GetPublicationByName(const char *pubname, bool missing_ok)
+{
+	Oid			oid;
+
+	oid = GetSysCacheOid1(PUBLICATIONNAME, CStringGetDatum(pubname));
+	if (!OidIsValid(oid))
+	{
+		if (missing_ok)
+			return NULL;
+
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("publication \"%s\" does not exist", pubname)));
+	}
+
+	return GetPublication(oid);
+}

That's racy... Also, shouldn't we specify for how to deal with the
returned memory for Publication * returning methods?

diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
new file mode 100644
index 0000000..954b2bd
--- /dev/null
+++ b/src/backend/commands/publicationcmds.c
@@ -0,0 +1,613 @@
+/*
+ * Create new publication.
+ */
+ObjectAddress
+CreatePublication(CreatePublicationStmt *stmt)
+{
+	Relation	rel;
+
+	values[Anum_pg_publication_puballtables - 1] =
+		BoolGetDatum(stmt->for_all_tables);
+	values[Anum_pg_publication_pubinsert - 1] =
+		BoolGetDatum(publish_insert);
+	values[Anum_pg_publication_pubupdate - 1] =
+		BoolGetDatum(publish_update);
+	values[Anum_pg_publication_pubdelete - 1] =
+		BoolGetDatum(publish_delete);

I remain convinced that a different representation would be
better. There'll be more options over time (truncate, DDL at least).

+static void
+AlterPublicationOptions(AlterPublicationStmt *stmt, Relation rel,
+					   HeapTuple tup)
+{
+	bool		publish_insert_given;
+	bool		publish_update_given;
+	bool		publish_delete_given;
+	bool		publish_insert;
+	bool		publish_update;
+	bool		publish_delete;
+	ObjectAddress		obj;
+
+	parse_publication_options(stmt->options,
+							  &publish_insert_given, &publish_insert,
+							  &publish_update_given, &publish_update,
+							  &publish_delete_given, &publish_delete);

You could pass it a struct instead...

+static List *
+OpenTableList(List *tables)
+{
+	List	   *relids = NIL;
+	List	   *rels = NIL;
+	ListCell   *lc;
+
+	/*
+	 * Open, share-lock, and check all the explicitly-specified relations
+	 */
+	foreach(lc, tables)
+	{
+		RangeVar   *rv = lfirst(lc);
+		Relation	rel;
+		bool		recurse = interpretInhOption(rv->inhOpt);
+		Oid			myrelid;
+
+		rel = heap_openrv(rv, ShareUpdateExclusiveLock);
+		myrelid = RelationGetRelid(rel);
+		/* filter out duplicates when user specifies "foo, foo" */
+		if (list_member_oid(relids, myrelid))
+		{
+			heap_close(rel, ShareUpdateExclusiveLock);
+			continue;
+		}

This is a quadratic algorithm - that could bite us... Not sure if we
need to care. If we want to fix it, one approach owuld be to use
RangeVarGetRelid() instead, and then do a qsort/deduplicate before
actually opening the relations.

-def_elem: ColLabel '=' def_arg
+def_elem: def_key '=' def_arg
{
$$ = makeDefElem($1, (Node *) $3, @1);
}
- | ColLabel
+ | def_key
{
$$ = makeDefElem($1, NULL, @1);
}
;

+def_key:
+			ColLabel						{ $$ = $1; }
+			| ColLabel ColLabel				{ $$ = psprintf("%s %s", $1, $2); }
+		;
+

Not quite sure what this is about? Doesn't that change the accepted
syntax in a bunch of places?

@@ -2337,6 +2338,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
bms_free(relation->rd_indexattr);
bms_free(relation->rd_keyattr);
bms_free(relation->rd_idattr);
+ if (relation->rd_pubactions)
+ pfree(relation->rd_pubactions);
if (relation->rd_options)
pfree(relation->rd_options);
if (relation->rd_indextuple)
@@ -4992,6 +4995,67 @@ RelationGetExclusionInfo(Relation indexRelation,
MemoryContextSwitchTo(oldcxt);
}

+/*
+ * Get publication actions for the given relation.
+ */
+struct PublicationActions *
+GetRelationPublicationActions(Relation relation)
+{
+	List	   *puboids;
+	ListCell   *lc;
+	MemoryContext		oldcxt;
+	PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
+
+	if (relation->rd_pubactions)
+		return memcpy(pubactions, relation->rd_pubactions,
+					  sizeof(PublicationActions));
+
+	/* Fetch the publication membership info. */
+	puboids = GetRelationPublications(RelationGetRelid(relation));
+	puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
+
+	foreach(lc, puboids)
+	{
+		Oid			pubid = lfirst_oid(lc);
+		HeapTuple	tup;
+		Form_pg_publication pubform;
+
+		tup = SearchSysCache1(PUBLICATIONOID, ObjectIdGetDatum(pubid));
+
+		if (!HeapTupleIsValid(tup))
+			elog(ERROR, "cache lookup failed for publication %u", pubid);
+
+		pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+		pubactions->pubinsert |= pubform->pubinsert;
+		pubactions->pubupdate |= pubform->pubupdate;
+		pubactions->pubdelete |= pubform->pubdelete;
+
+		ReleaseSysCache(tup);
+
+		/*
+		 * If we know everything is replicated, there is no point to check
+		 * for other publications.
+		 */
+		if (pubactions->pubinsert && pubactions->pubupdate &&
+			pubactions->pubdelete)
+			break;
+	}
+
+	if (relation->rd_pubactions)
+	{
+		pfree(relation->rd_pubactions);
+		relation->rd_pubactions = NULL;
+	}
+
+	/* Now save copy of the actions in the relcache entry. */
+	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	relation->rd_pubactions = palloc(sizeof(PublicationActions));
+	memcpy(relation->rd_pubactions, pubactions, sizeof(PublicationActions));
+	MemoryContextSwitchTo(oldcxt);
+
+	return pubactions;
+}

Hm. Do we actually have enough cache invalidation support to make this
cached version correct? I haven't seen anything in that regard? Seems
to mean that all changes to an ALL TABLES publication need to do a
global relcache invalidation?

- Andres

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

#131Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Andres Freund (#130)
Re: Logical Replication WIP

On 13/12/16 02:41, Andres Freund wrote:

On 2016-12-10 08:48:55 +0100, Petr Jelinek wrote:

diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
new file mode 100644
index 0000000..e3560b7
--- /dev/null
+++ b/src/backend/catalog/pg_publication.c
+
+Datum pg_get_publication_tables(PG_FUNCTION_ARGS);

Don't we usually put these in a header?

We put these to rather random places, I don't mind either way.

+/*
+ * Gets list of relation oids for a publication.
+ *
+ * This should only be used for normal publications, the FOR ALL TABLES
+ * should use GetAllTablesPublicationRelations().
+ */
+List *
+GetPublicationRelations(Oid pubid)
+{
+	List		   *result;
+	Relation		pubrelsrel;
+	ScanKeyData		scankey;
+	SysScanDesc		scan;
+	HeapTuple		tup;
+
+	/* Find all publications associated with the relation. */
+	pubrelsrel = heap_open(PublicationRelRelationId, AccessShareLock);
+
+	ScanKeyInit(&scankey,
+				Anum_pg_publication_rel_prpubid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(pubid));
+
+	scan = systable_beginscan(pubrelsrel, PublicationRelMapIndexId, true,
+							  NULL, 1, &scankey);
+
+	result = NIL;
+	while (HeapTupleIsValid(tup = systable_getnext(scan)))
+	{
+		Form_pg_publication_rel		pubrel;
+
+		pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
+
+		result = lappend_oid(result, pubrel->prrelid);
+	}
+
+	systable_endscan(scan);
+	heap_close(pubrelsrel, NoLock);

In other parts of this you drop the lock, but not here?

+	heap_close(rel, NoLock);
+
+	return result;
+}

and here.

Meh, ignore, that's some pglogical legacy.

Btw, why are matviews not publishable?

Because standard way of updating them is REFRESH MATERIALIZED VIEW which
is decoded as inserts into pg_temp_<oid> table. I think we'll have to
rethink how we do this before we can sanely support them.

+/*
+ * Get Publication using name.
+ */
+Publication *
+GetPublicationByName(const char *pubname, bool missing_ok)
+{
+	Oid			oid;
+
+	oid = GetSysCacheOid1(PUBLICATIONNAME, CStringGetDatum(pubname));
+	if (!OidIsValid(oid))
+	{
+		if (missing_ok)
+			return NULL;
+
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("publication \"%s\" does not exist", pubname)));
+	}
+
+	return GetPublication(oid);
+}

That's racy... Also, shouldn't we specify for how to deal with the
returned memory for Publication * returning methods?

So are most of the other existing functions with similar purpose. The
worst case is that with enough concurrency around same publication name
DDL you'll get cache lookup failure.

I added comment to GetPublication saying that memory is palloced.

diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
new file mode 100644
index 0000000..954b2bd
--- /dev/null
+++ b/src/backend/commands/publicationcmds.c
@@ -0,0 +1,613 @@
+/*
+ * Create new publication.
+ */
+ObjectAddress
+CreatePublication(CreatePublicationStmt *stmt)
+{
+	Relation	rel;
+
+	values[Anum_pg_publication_puballtables - 1] =
+		BoolGetDatum(stmt->for_all_tables);
+	values[Anum_pg_publication_pubinsert - 1] =
+		BoolGetDatum(publish_insert);
+	values[Anum_pg_publication_pubupdate - 1] =
+		BoolGetDatum(publish_update);
+	values[Anum_pg_publication_pubdelete - 1] =
+		BoolGetDatum(publish_delete);

I remain convinced that a different representation would be
better. There'll be more options over time (truncate, DDL at least).

So? It's boolean properties, it's not like we store bitmaps in catalogs
much. I very much expect DDL to be much more complex than boolean btw.

+static void
+AlterPublicationOptions(AlterPublicationStmt *stmt, Relation rel,
+					   HeapTuple tup)
+{
+	bool		publish_insert_given;
+	bool		publish_update_given;
+	bool		publish_delete_given;
+	bool		publish_insert;
+	bool		publish_update;
+	bool		publish_delete;
+	ObjectAddress		obj;
+
+	parse_publication_options(stmt->options,
+							  &publish_insert_given, &publish_insert,
+							  &publish_update_given, &publish_update,
+							  &publish_delete_given, &publish_delete);

You could pass it a struct instead...

Here yes, but in similar code for subscription not, I slightly prefer
consistency between those similar functions.

+static List *
+OpenTableList(List *tables)
+{
+	List	   *relids = NIL;
+	List	   *rels = NIL;
+	ListCell   *lc;
+
+	/*
+	 * Open, share-lock, and check all the explicitly-specified relations
+	 */
+	foreach(lc, tables)
+	{
+		RangeVar   *rv = lfirst(lc);
+		Relation	rel;
+		bool		recurse = interpretInhOption(rv->inhOpt);
+		Oid			myrelid;
+
+		rel = heap_openrv(rv, ShareUpdateExclusiveLock);
+		myrelid = RelationGetRelid(rel);
+		/* filter out duplicates when user specifies "foo, foo" */
+		if (list_member_oid(relids, myrelid))
+		{
+			heap_close(rel, ShareUpdateExclusiveLock);
+			continue;
+		}

This is a quadratic algorithm - that could bite us... Not sure if we
need to care. If we want to fix it, one approach owuld be to use
RangeVarGetRelid() instead, and then do a qsort/deduplicate before
actually opening the relations.

I guess it could get really slow only with big inheritance tree, I'll
look into how much work is the other way of doing things (this is not
exactly hot code path).

-def_elem: ColLabel '=' def_arg
+def_elem: def_key '=' def_arg
{
$$ = makeDefElem($1, (Node *) $3, @1);
}
- | ColLabel
+ | def_key
{
$$ = makeDefElem($1, NULL, @1);
}
;

+def_key:
+			ColLabel						{ $$ = $1; }
+			| ColLabel ColLabel				{ $$ = psprintf("%s %s", $1, $2); }
+		;
+

Not quite sure what this is about? Doesn't that change the accepted
syntax in a bunch of places?

Well all those places have to check the actual values in the C code
later. It will change the error message a bit in some DDL. I made it
this way so that we don't have to introduce same thing as definition
with just this small change.

@@ -2337,6 +2338,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
bms_free(relation->rd_indexattr);
bms_free(relation->rd_keyattr);
bms_free(relation->rd_idattr);
+ if (relation->rd_pubactions)
+ pfree(relation->rd_pubactions);
if (relation->rd_options)
pfree(relation->rd_options);
if (relation->rd_indextuple)
@@ -4992,6 +4995,67 @@ RelationGetExclusionInfo(Relation indexRelation,
MemoryContextSwitchTo(oldcxt);
}

+/*
+ * Get publication actions for the given relation.
+ */
+struct PublicationActions *
+GetRelationPublicationActions(Relation relation)
+{
+	List	   *puboids;
+	ListCell   *lc;
+	MemoryContext		oldcxt;
+	PublicationActions *pubactions = palloc0(sizeof(PublicationActions));
+
+	if (relation->rd_pubactions)
+		return memcpy(pubactions, relation->rd_pubactions,
+					  sizeof(PublicationActions));
+
+	/* Fetch the publication membership info. */
+	puboids = GetRelationPublications(RelationGetRelid(relation));
+	puboids = list_concat_unique_oid(puboids, GetAllTablesPublications());
+
+	foreach(lc, puboids)
+	{
+		Oid			pubid = lfirst_oid(lc);
+		HeapTuple	tup;
+		Form_pg_publication pubform;
+
+		tup = SearchSysCache1(PUBLICATIONOID, ObjectIdGetDatum(pubid));
+
+		if (!HeapTupleIsValid(tup))
+			elog(ERROR, "cache lookup failed for publication %u", pubid);
+
+		pubform = (Form_pg_publication) GETSTRUCT(tup);
+
+		pubactions->pubinsert |= pubform->pubinsert;
+		pubactions->pubupdate |= pubform->pubupdate;
+		pubactions->pubdelete |= pubform->pubdelete;
+
+		ReleaseSysCache(tup);
+
+		/*
+		 * If we know everything is replicated, there is no point to check
+		 * for other publications.
+		 */
+		if (pubactions->pubinsert && pubactions->pubupdate &&
+			pubactions->pubdelete)
+			break;
+	}
+
+	if (relation->rd_pubactions)
+	{
+		pfree(relation->rd_pubactions);
+		relation->rd_pubactions = NULL;
+	}
+
+	/* Now save copy of the actions in the relcache entry. */
+	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	relation->rd_pubactions = palloc(sizeof(PublicationActions));
+	memcpy(relation->rd_pubactions, pubactions, sizeof(PublicationActions));
+	MemoryContextSwitchTo(oldcxt);
+
+	return pubactions;
+}

Hm. Do we actually have enough cache invalidation support to make this
cached version correct? I haven't seen anything in that regard? Seems
to mean that all changes to an ALL TABLES publication need to do a
global relcache invalidation?

Yeah you're right, we definitely don't do enough relcache invalidation
for this.

--
Petr Jelinek 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

#132Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Petr Jelinek (#131)
Re: Logical Replication WIP

On 13/12/16 03:26, Petr Jelinek wrote:

On 13/12/16 02:41, Andres Freund wrote:

On 2016-12-10 08:48:55 +0100, Petr Jelinek wrote:

+static List *
+OpenTableList(List *tables)
+{
+	List	   *relids = NIL;
+	List	   *rels = NIL;
+	ListCell   *lc;
+
+	/*
+	 * Open, share-lock, and check all the explicitly-specified relations
+	 */
+	foreach(lc, tables)
+	{
+		RangeVar   *rv = lfirst(lc);
+		Relation	rel;
+		bool		recurse = interpretInhOption(rv->inhOpt);
+		Oid			myrelid;
+
+		rel = heap_openrv(rv, ShareUpdateExclusiveLock);
+		myrelid = RelationGetRelid(rel);
+		/* filter out duplicates when user specifies "foo, foo" */
+		if (list_member_oid(relids, myrelid))
+		{
+			heap_close(rel, ShareUpdateExclusiveLock);
+			continue;
+		}

This is a quadratic algorithm - that could bite us... Not sure if we
need to care. If we want to fix it, one approach owuld be to use
RangeVarGetRelid() instead, and then do a qsort/deduplicate before
actually opening the relations.

I guess it could get really slow only with big inheritance tree, I'll
look into how much work is the other way of doing things (this is not
exactly hot code path).

Actually looking at it, it only processes user input so I don't think
it's very problematic in terms of performance. You'd have to pass many
thousands of tables in single DDL to notice.

--
Petr Jelinek 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

#133Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Petr Jelinek (#125)
4 attachment(s)
Re: Logical Replication WIP

On 12/10/16 2:48 AM, Petr Jelinek wrote:

Attached new version with your updates and rebased on top of the current
HEAD (the partitioning patch produced quite a few conflicts).

I have attached a few more "fixup" patches, mostly with some editing of
documentation and comments and some compiler warnings.

In 0006 in the protocol documentation I have left a "XXX ???" where I
didn't understand what it was trying to say.

All issues from (my) previous reviews appear to have been addressed.

Comments besides that:

0003-Add-SUBSCRIPTION-catalog-and-DDL-v12.patch

Still wondering about the best workflow with pg_dump, but it seems all
the pieces are there right now, and the interfaces can be tweaked later.

DROP SUBSCRIPTION requires superuser, but should perhaps be owner check
only?

DROP SUBSCRIPTION IF EXISTS crashes if the subscription does not in fact
exist.

Maybe write the grammar so that SLOT does not need to be a new key word.
The changes you made for CREATE PUBLICATION should allow that.

The tests are not added to serial_schedule. Intentional? If so, document?

0004-Define-logical-replication-protocol-and-output-plugi-v12.patch

Not sure why pg_catalog is encoded as a zero-length string. I guess it
saves some space. Maybe that could be explained in a brief code comment?

0005-Add-logical-replication-workers-v12.patch

The way the executor stuff is organized now looks better to me.

The subscriber crashes if max_replication_slots is 0:

TRAP: FailedAssertion("!(max_replication_slots > 0)", File: "origin.c",
Line: 999)

The documentation says that replication slots are required on the
subscriber, but from a user's perspective, it's not clear why that is.

Dropping a table that is part of a live subscription results in log
messages like

WARNING: leaked hash_seq_search scan for hash table 0x7f9d2a807238

I was testing replicating into a temporary table, which failed like this:

FATAL: the logical replication target public.test1 not found
LOG: worker process: (PID 2879) exited with exit code 1
LOG: starting logical replication worker for subscription 16392
LOG: logical replication apply for subscription mysub started

That's okay, but those messages were repeated every few seconds or so
and would create quite some log volume. I wonder if that needs to be
reigned in somewhat.

I think this is getting very close to the point where it's committable.
So if anyone else has major concerns about the whole approach and
perhaps the way the new code in 0005 is organized, now would be the time ...

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

0002-fixup-Add-PUBLICATION-catalogs-and-DDL.patchtext/x-patch; name=0002-fixup-Add-PUBLICATION-catalogs-and-DDL.patchDownload
From df169d3fec6d67c3daf301d9ed9903545358dd7e Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Mon, 12 Dec 2016 12:00:00 -0500
Subject: [PATCH 2/8] fixup! Add PUBLICATION catalogs and DDL

---
 src/backend/commands/publicationcmds.c | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 954b2bd65d..f1d7404ffe 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -389,7 +389,6 @@ AlterPublication(AlterPublicationStmt *stmt)
 {
 	Relation		rel;
 	HeapTuple		tup;
-	AclResult		aclresult;
 
 	rel = heap_open(PublicationRelationId, RowExclusiveLock);
 
@@ -564,12 +563,11 @@ PublicationAddTables(Oid pubid, List *rels, bool if_not_exists,
 	foreach(lc, rels)
 	{
 		Relation	rel = (Relation) lfirst(lc);
-		AclResult	aclresult;
 		ObjectAddress	obj;
 
 		/* Must be owner of the table or superuser. */
 		if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId()))
-			aclcheck_error(aclresult, ACL_KIND_CLASS,
+			aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
 						   RelationGetRelationName(rel));
 
 		obj = publication_add_relation(pubid, rel, if_not_exists);
-- 
2.11.0

0004-fixup-Add-SUBSCRIPTION-catalog-and-DDL.patchtext/x-patch; name=0004-fixup-Add-SUBSCRIPTION-catalog-and-DDL.patchDownload
From 2cab724505487663b53f8af09c8e16a70c52014d Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Fri, 9 Dec 2016 12:00:00 -0500
Subject: [PATCH 4/8] fixup! Add SUBSCRIPTION catalog and DDL

---
 doc/src/sgml/catalogs.sgml                         | 28 ++++++-------
 doc/src/sgml/ref/alter_subscription.sgml           | 25 +++++-------
 doc/src/sgml/ref/create_subscription.sgml          | 47 ++++++++++------------
 doc/src/sgml/ref/drop_subscription.sgml            | 23 ++++++-----
 doc/src/sgml/ref/psql-ref.sgml                     |  6 +--
 src/backend/catalog/pg_subscription.c              |  2 +-
 src/backend/commands/subscriptioncmds.c            |  6 +--
 src/backend/parser/gram.y                          |  2 +-
 .../libpqwalreceiver/libpqwalreceiver.c            |  2 +-
 src/backend/tcop/utility.c                         |  5 +++
 src/bin/pg_dump/pg_dump.c                          |  7 +---
 src/test/regress/parallel_schedule                 |  2 +-
 12 files changed, 75 insertions(+), 80 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 51f062ef70..7b3e95bc9f 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -6216,19 +6216,18 @@ <title><structname>pg_subscription</structname></title>
   </indexterm>
 
   <para>
-   The <structname>pg_subscription</structname> catalog contains
-   all existing logical replication subscriptions.
+   The catalog <structname>pg_subscription</structname> contains all existing
+   logical replication subscriptions.
   </para>
 
   <para>
-   Unlike most system catalogs, <structname>pg_subscription</structname>
-   is shared across all databases of a cluster: there is only one
-   copy of <structname>pg_subscription</structname> per cluster, not
-   one per database.
+   Unlike most system catalogs, <structname>pg_subscription</structname> is
+   shared across all databases of a cluster: There is only one copy
+   of <structname>pg_subscription</structname> per cluster, not one per
+   database.
   </para>
 
   <table>
-
    <title><structname>pg_subscription</structname> Columns</title>
 
    <tgroup cols="4">
@@ -6252,16 +6251,15 @@ <title><structname>pg_subscription</structname> Columns</title>
      <row>
       <entry><structfield>subdbid</structfield></entry>
       <entry><type>oid</type></entry>
-      <entry></entry>
-      <entry>Oid of the database which the subscription resides in.</entry>
+      <entry><literal><link linkend="catalog-pg-database"><structname>pg_database</structname></link>.oid</literal></entry>
+      <entry>OID of the database which the subscription resides in</entry>
      </row>
 
      <row>
       <entry><structfield>subname</structfield></entry>
-      <entry><type>Name</type></entry>
+      <entry><type>name</type></entry>
       <entry></entry>
-      <entry>A unique, database-wide identifier for the replication
-      subscription.</entry>
+      <entry>Name of the subscription</entry>
      </row>
 
      <row>
@@ -6275,16 +6273,14 @@ <title><structname>pg_subscription</structname> Columns</title>
       <entry><structfield>subenabled</structfield></entry>
       <entry><type>bool</type></entry>
       <entry></entry>
-      <entry>If true, the subscription is enabled and should be replicating.
-      </entry>
+      <entry>If true, the subscription is enabled and should be replicating.</entry>
      </row>
 
      <row>
       <entry><structfield>subconninfo</structfield></entry>
       <entry><type>text</type></entry>
       <entry></entry>
-      <entry>Connection string to the upstream database.
-      </entry>
+      <entry>Connection string to the upstream database</entry>
      </row>
 
      <row>
diff --git a/doc/src/sgml/ref/alter_subscription.sgml b/doc/src/sgml/ref/alter_subscription.sgml
index 29e1a24a2f..a7cbdb85fa 100644
--- a/doc/src/sgml/ref/alter_subscription.sgml
+++ b/doc/src/sgml/ref/alter_subscription.sgml
@@ -39,8 +39,8 @@ <title>Description</title>
 
   <para>
    <command>ALTER SUBSCRIPTION</command> can change most of the subscription
-   attributes that can be specified in
-   <xref linkend="sql-createsubscription">.
+   properties that can be specified
+   in <xref linkend="sql-createsubscription">.
   </para>
  </refsect1>
 
@@ -52,26 +52,26 @@ <title>Parameters</title>
     <term><replaceable class="parameter">name</replaceable></term>
     <listitem>
      <para>
-      The name of a subscription whose attributes are to be altered.
+      The name of a subscription whose properties are to be altered.
      </para>
     </listitem>
    </varlistentry>
 
    <varlistentry>
-    <term>CONNECTION '<replaceable class="parameter">conninfo</replaceable>'</term>
-    <term>SET PUBLICATION <replaceable class="parameter">publication_name</replaceable></term>
-    <term>SLOT NAME = <replaceable class="parameter">slot_name</replaceable></term>
+    <term><literal>CONNECTION '<replaceable class="parameter">conninfo</replaceable>'</literal></term>
+    <term><literal>SET PUBLICATION <replaceable class="parameter">publication_name</replaceable></literal></term>
+    <term><literal>SLOT NAME = <replaceable class="parameter">slot_name</replaceable></literal></term>
     <listitem>
      <para>
-      These clauses alter attributes originally set by
-      <xref linkend="SQL-CREATESUBSCRIPTION">. For more information, see the
-      <command>CREATE SUBSCRIPTION</command> reference page.
+      These clauses alter properties originally set by
+      <xref linkend="SQL-CREATESUBSCRIPTION">.  See there for more
+      information.
      </para>
     </listitem>
    </varlistentry>
 
    <varlistentry>
-    <term>ENABLE</term>
+    <term><literal>ENABLE</literal></term>
     <listitem>
      <para>
       Enables the previously disabled subscription, starting the logical
@@ -81,7 +81,7 @@ <title>Parameters</title>
    </varlistentry>
 
    <varlistentry>
-    <term>DISABLE</term>
+    <term><literal>DISABLE</literal></term>
     <listitem>
      <para>
       Disables the running subscription, stopping the logical replication
@@ -89,7 +89,6 @@ <title>Parameters</title>
      </para>
     </listitem>
    </varlistentry>
-
   </variablelist>
  </refsect1>
 
@@ -110,7 +109,6 @@ <title>Examples</title>
 ALTER SUBSCRIPTION mysub DISABLE;
 </programlisting>
   </para>
-
  </refsect1>
 
  <refsect1>
@@ -132,5 +130,4 @@ <title>See Also</title>
    <member><xref linkend="sql-alterpublication"></member>
   </simplelist>
  </refsect1>
-
 </refentry>
diff --git a/doc/src/sgml/ref/create_subscription.sgml b/doc/src/sgml/ref/create_subscription.sgml
index 3ae4cd89b6..a5cd0a16ff 100644
--- a/doc/src/sgml/ref/create_subscription.sgml
+++ b/doc/src/sgml/ref/create_subscription.sgml
@@ -16,7 +16,7 @@
 
  <refnamediv>
   <refname>CREATE SUBSCRIPTION</refname>
-  <refpurpose>define new subscription</refpurpose>
+  <refpurpose>define a new subscription</refpurpose>
  </refnamediv>
 
  <refsynopsisdiv>
@@ -35,23 +35,21 @@
   <title>Description</title>
 
   <para>
-   <command>CREATE SUBSCRIPTION</command> adds a new subscription for
-   a current database. The subscription name must be distinct from
-   the name of any existing subscription in the database cluster.
+   <command>CREATE SUBSCRIPTION</command> adds a new subscription for a
+   current database.  The subscription name must be distinct from the name of
+   any existing subscription in the database.
   </para>
 
   <para>
-   The subscription represents a replication connection to the publisher.
-   As such this command does not only add definition in the local catalogs
-   but also creates a replication slot on the publisher.
+   The subscription represents a replication connection to the publisher.  As
+   such this command does not only add definitions in the local catalogs but
+   also creates a replication slot on the publisher.
   </para>
 
   <para>
-   A logical replication worker will be started to replicate data for the
-   new subscription at the commit of the transaction where this command
-   was run.
+   A logical replication worker will be started to replicate data for the new
+   subscription at the commit of the transaction where this command is run.
   </para>
-
  </refsect1>
 
  <refsect1>
@@ -68,7 +66,7 @@ <title>Parameters</title>
    </varlistentry>
 
    <varlistentry>
-    <term>CONNECTION '<replaceable class="parameter">conninfo</replaceable>'</term>
+    <term><literal>CONNECTION '<replaceable class="parameter">conninfo</replaceable>'</literal></term>
     <listitem>
      <para>
       The connection string to the publisher.
@@ -77,7 +75,7 @@ <title>Parameters</title>
    </varlistentry>
 
    <varlistentry>
-    <term>PUBLICATION <replaceable class="parameter">publication_name</replaceable></term>
+    <term><literal>PUBLICATION <replaceable class="parameter">publication_name</replaceable></literal></term>
     <listitem>
      <para>
       Name(s) of the publications on the publisher to subscribe to.
@@ -86,11 +84,11 @@ <title>Parameters</title>
    </varlistentry>
 
    <varlistentry>
-    <term>ENABLED</term>
-    <term>DISABLED</term>
+    <term><literal>ENABLED</literal></term>
+    <term><literal>DISABLED</literal></term>
     <listitem>
      <para>
-      Specifies if the subscription should be actively replicating or
+      Specifies whether the subscription should be actively replicating or
       if it should be just setup but not started yet.  Note that the
       replication slot as described above is created in either case.
       <literal>ENABLED</literal> is the default.
@@ -99,18 +97,18 @@ <title>Parameters</title>
    </varlistentry>
 
    <varlistentry>
-    <term>CREATE SLOT</term>
-    <term>NOCREATE SLOT</term>
+    <term><literal>CREATE SLOT</literal></term>
+    <term><literal>NOCREATE SLOT</literal></term>
     <listitem>
      <para>
-      Specifies if the command should create the replication slot on the
+      Specifies whether the command should create the replication slot on the
       publisher. <literal>CREATE SLOT</literal> is the default.
      </para>
     </listitem>
    </varlistentry>
 
    <varlistentry>
-    <term>SLOT NAME = <replaceable class="parameter">slot_name</replaceable></term>
+    <term><literal>SLOT NAME = <replaceable class="parameter">slot_name</replaceable></literal></term>
     <listitem>
      <para>
       Name of the replication slot to use. The default behavior is to use
@@ -118,7 +116,6 @@ <title>Parameters</title>
      </para>
     </listitem>
    </varlistentry>
-
   </variablelist>
  </refsect1>
 
@@ -126,7 +123,7 @@ <title>Parameters</title>
   <title>Examples</title>
 
   <para>
-   Create a subscription to a different server which replicates tables in
+   Create a subscription to a remote server that replicates tables in
    the publications <literal>mypubclication</literal> and
    <literal>insert_only</literal> and starts replicating immediately on
    commit:
@@ -138,8 +135,8 @@ <title>Examples</title>
   </para>
 
   <para>
-   Create a subscription to a different server which replicates tables in
-   the <literal>insert_only</literal> publication and does not replicate
+   Create a subscription to a remote server that replicates tables in
+   the <literal>insert_only</literal> publication and does not start replicating
    until enabled at a later time.
 <programlisting>
 CREATE SUBSCRIPTION mysub
@@ -148,7 +145,6 @@ <title>Examples</title>
                WITH (DISABLED);
 </programlisting>
   </para>
-
  </refsect1>
 
  <refsect1>
@@ -170,5 +166,4 @@ <title>See Also</title>
    <member><xref linkend="sql-alterpublication"></member>
   </simplelist>
  </refsect1>
-
 </refentry>
diff --git a/doc/src/sgml/ref/drop_subscription.sgml b/doc/src/sgml/ref/drop_subscription.sgml
index de40a7660f..9f2fb93275 100644
--- a/doc/src/sgml/ref/drop_subscription.sgml
+++ b/doc/src/sgml/ref/drop_subscription.sgml
@@ -16,7 +16,7 @@
 
  <refnamediv>
   <refname>DROP SUBSCRIPTION</refname>
-  <refpurpose>remove an existing subscription</refpurpose>
+  <refpurpose>remove a subscription</refpurpose>
  </refnamediv>
 
  <refsynopsisdiv>
@@ -29,8 +29,8 @@
   <title>Description</title>
 
   <para>
-   <command>DROP SUBSCRIPTION</command> removes subscriptions from the
-   cluster.
+   <command>DROP SUBSCRIPTION</command> removes a subscription from the
+   database cluster.
   </para>
 
   <para>
@@ -38,11 +38,9 @@ <title>Description</title>
   </para>
 
   <para>
-   The replication worker associated with the subscription will not stop
-   until after <command>COMMIT</command> of the transaction which issued
-   this command.
+   The replication worker associated with the subscription will not stop until
+   after the transaction that issued this command has committed.
   </para>
-
  </refsect1>
 
  <refsect1>
@@ -63,9 +61,17 @@ <title>Parameters</title>
     <term><replaceable class="parameter">NODROP SLOT</replaceable></term>
     <listitem>
      <para>
-      Specifies if to drop slot on the publisher. The default is
+      Specifies whether to drop the replication slot on the publisher.  The
+      default is
       <literal>DROP SLOT</literal>.
      </para>
+
+     <para>
+      If the publisher is not reachable when the subscription is to be
+      dropped, then it is useful to specify <literal>NODROP SLOT</literal>.
+      But the replication slot on the publisher will then have to be removed
+      manually.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -101,5 +107,4 @@ <title>See Also</title>
    <member><xref linkend="sql-altersubscription"></member>
   </simplelist>
  </refsect1>
-
 </refentry>
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 48ff9b95dc..640fe12bbf 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1618,12 +1618,12 @@ <title>Meta-Commands</title>
         <term><literal>\dRs[+] [ <link linkend="APP-PSQL-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
         <listitem>
         <para>
-        List replication subscriptions.
+        Lists replication subscriptions.
         If <replaceable class="parameter">pattern</replaceable> is
-        specified, only subscriptions whose names match the pattern are
+        specified, only those subscriptions whose names match the pattern are
         listed.
         If <literal>+</literal> is appended to the command name, additional
-        parameters of the subscriptions are shown.
+        properties of the subscriptions are shown.
         </para>
         </listitem>
       </varlistentry>
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index d5078fca2d..fe94779ef2 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -3,7 +3,7 @@
  * pg_subscription.c
  *		replication subscriptions
  *
- * Copyright (c) 2015, PostgreSQL Global Development Group
+ * Copyright (c) 2016, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
  *		src/backend/catalog/pg_subscription.c
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 1b8d589d1b..5a7312c0c4 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -57,7 +57,7 @@
  * Common option parsing function for CREATE and ALTER SUBSCRIPTION commands.
  *
  * Since not all options can be specified in both commands, this function
- * will report error on options if the target output pointer is NULL to
+ * will report an error on options if the target output pointer is NULL to
  * accomodate that.
  */
 static void
@@ -160,7 +160,7 @@ parse_subscription_options(List *options, char **conninfo,
 }
 
 /*
- * Auxiliary function to return a TEXT array out of a list of String nodes.
+ * Auxiliary function to return a text array out of a list of String nodes.
  */
 static Datum
 publicationListToArray(List *publist)
@@ -309,7 +309,7 @@ CreateSubscription(CreateSubscriptionStmt *stmt)
 		if (server_version / 100 > PG_VERSION_NUM / 100)
 			ereport(WARNING,
 					(errmsg("publisher major version %d is higher than subscriber "
-							"major version %d,  the logical replicatiion might "
+							"major version %d, logical replication might "
 							"not work correctly",
 							server_version/10000, PG_VERSION_NUM/10000)));
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5baf580af9..95f68b6a3e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -9019,7 +9019,7 @@ AlterPublicationStmt:
 
 /*****************************************************************************
  *
- * CREATE SUBSCRIPTION name [ WITH ] options
+ * CREATE SUBSCRIPTION name ...
  *
  *****************************************************************************/
 
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 41a0ac1275..f99158e251 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -104,7 +104,7 @@ _PG_init(void)
 /*
  * Establish the connection to the primary server for XLOG streaming
  *
- * Return NULL on error and fills the err with palloced error message.
+ * Returns NULL on error and fills the err with palloc'ed error message.
  */
 static WalReceiverConn *
 libpqrcv_connect(const char *conninfo, bool logical, const char *appname,
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index ce98420ff0..2273405426 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -216,6 +216,7 @@ check_xact_readonly(Node *parsetree)
 		case T_AlterPublicationStmt:
 		case T_CreateSubscriptionStmt:
 		case T_AlterSubscriptionStmt:
+		case T_DropSubscriptionStmt:
 			PreventCommandIfReadOnly(CreateCommandTag(parsetree));
 			PreventCommandIfParallelMode(CreateCommandTag(parsetree));
 			break;
@@ -3211,6 +3212,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_DropSubscriptionStmt:
+			lev = LOGSTMT_DDL;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index e20e5e423a..4968c27ded 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -919,7 +919,7 @@ help(const char *progname)
 	printf(_("  --no-security-labels         do not dump security label assignments\n"));
 	printf(_("  --no-synchronized-snapshots  do not use synchronized snapshots in parallel jobs\n"));
 	printf(_("  --no-tablespaces             do not dump tablespace assignments\n"));
-	printf(_("  --no-create-subsription-slot do not create replication slot for dumped subscriptions\n"));
+	printf(_("  --no-create-subscription-slot do not create replication slot for dumped subscriptions\n"));
 	printf(_("  --no-unlogged-table-data     do not dump unlogged table data\n"));
 	printf(_("  --quote-all-identifiers      quote all identifiers, even if not key words\n"));
 	printf(_("  --section=SECTION            dump named section (pre-data, data, or post-data)\n"));
@@ -3600,9 +3600,6 @@ getSubscriptions(Archive *fout)
 
 	query = createPQExpBuffer();
 
-	if (g_verbose)
-		write_msg(NULL, "reading subscriptions\n");
-
 	resetPQExpBuffer(query);
 
 	/* Get the subscriptions in current database. */
@@ -3621,7 +3618,7 @@ getSubscriptions(Archive *fout)
 	if (ntups == 0)
 	{
 		/*
-		 * There are no publications defined. Clean up and return.
+		 * There are no subscriptions defined. Clean up and return.
 		 */
 		PQclear(res);
 		return;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 0de902eb7a..e9b2bad6fd 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -97,7 +97,7 @@ test: rules psql_crosstab amutils
 # run by itself so it can run parallel workers
 test: select_parallel
 
-# no relation related tests can be put in thi group
+# no relation related tests can be put in this group
 test: publication subscription
 
 # ----------
-- 
2.11.0

0006-fixup-Define-logical-replication-protocol-and-output.patchtext/x-patch; name=0006-fixup-Define-logical-replication-protocol-and-output.patchDownload
From 00b5c7896ffce14ca62f92670b485ba2e9ccba61 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Mon, 12 Dec 2016 12:00:00 -0500
Subject: [PATCH 6/8] fixup! Define logical replication protocol and output
 plugin

---
 doc/src/sgml/protocol.sgml              | 92 ++++++++++++++-------------------
 src/backend/replication/logical/proto.c | 12 ++---
 src/include/replication/pgoutput.h      |  2 +-
 3 files changed, 43 insertions(+), 63 deletions(-)

diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 4026687220..368d86c126 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -2126,7 +2126,7 @@ <title>Streaming Replication Protocol</title>
  <title>Logical Streaming Replication Protocol</title>
 
  <para>
-  This section describes the logical replication protocol which is the message
+  This section describes the logical replication protocol, which is the message
   flow started by the <literal>START_REPLICATION</literal>
   <literal>SLOT</literal> <replaceable class="parameter">slot_name</>
   <literal>LOGICAL</literal> replication command.
@@ -2134,14 +2134,14 @@ <title>Logical Streaming Replication Protocol</title>
 
  <para>
   The logical streaming replication protocol builds on the primitives of
-  physical streaming replication protocol.
+  the physical streaming replication protocol.
  </para>
 
  <sect2 id="protocol-logical-replication-params">
   <title>Logical Streaming Replication Parameters</title>
 
   <para>
-   The logical replication  <literal>START_REPLICATION</literal> command
+   The logical replication <literal>START_REPLICATION</literal> command
    accepts following parameters:
 
    <variablelist>
@@ -2164,7 +2164,7 @@ <title>Logical Streaming Replication Parameters</title>
      <listitem>
       <para>
        Comma separated list of publication names for which to subscribe
-       (receive changes). The invidividual publication names are treated
+       (receive changes). The individual publication names are treated
        as standard objects names and can be quoted the same as needed.
       </para>
      </listitem>
@@ -2179,7 +2179,7 @@ <title>Logical Replication Protocol Messages</title>
 
   <para>
    The individual protocol messages are discussed in the following
-   sub-sections. Individual messages are describer in
+   subsections. Individual messages are describer in
    <xref linkend="protocol-logicalrep-message-formats"> section.
   </para>
 
@@ -2208,36 +2208,31 @@ <title>Logical Replication Protocol Message Flow</title>
 
   <para>
    The logical replication protocol sends individual transactions one by one.
-   This means that all messages between two Begin and Commit messages all
+   This means that all messages between a pair of Begin and Commit messages
    belong to the same transaction.
   </para>
 
   <para>
    Every sent transaction contains zero or more DML messages (Insert,
-   Update, Delete) and in case of cascaded setup it can also contain Origin
+   Update, Delete). In case of a cascaded setup it can also contain Origin
    messages. The origin message indicated that the transaction originated on
-   different replication node. Since replication node in the scope of logical
+   different replication node. Since a replication node in the scope of logical
    replication protocol can be pretty much anything, the only identifier
-   is the origin name. It's downstream responsibility to handle this as
+   is the origin name. It's downstream's responsibility to handle this as
    needed (if needed). The Origin message is always sent before any DML
    messages in the transaction.
   </para>
 
   <para>
-   Every DML message contains arbitrary relation id, which can be mapped to
-   an id in the Relation messages. The Relation describe the schema of the
+   Every DML message contains an arbitrary relation ID, which can be mapped to
+   an ID in the Relation messages. The Relation messages describe the schema of the
    given relation. The Relation message is sent for a given relation either
-   because it's the first time we send DML message for given relation in
+   because it is the first time we send a DML message for given relation in the
    current session or because the relation definition has changed since the
    last Relation message was sent for it. The protocol assumes that the client
    is capable of caching the metadata for as many relations as needed.
   </para>
-
-  <para>
-  </para>
-
  </sect2>
-
 </sect1>
 
 <sect1 id="protocol-message-types">
@@ -5272,10 +5267,10 @@ <title>Logical Replication Message Formats</title>
 
 <para>
 This section describes the detailed format of each logical replication message.
-These messages are returned either by replication slot SQL interface or by
-WalSender. In case of WalSender they are encapsulated inside the replication
+These messages are returned either by the replication slot SQL interface or are
+sent by a walsender. In case of a walsender they are encapsulated inside the replication
 protocol WAL messages as described in <xref linkend="protocol-replication">
-section and generaly obey same message flow as physical replication.
+and generally obey same message flow as physical replication.
 </para>
 
 <variablelist>
@@ -5420,17 +5415,6 @@ <title>Logical Replication Message Formats</title>
 </varlistentry>
 <varlistentry>
 <term>
-        Int8
-</term>
-<listitem>
-<para>
-                Length of the origin name (including the NULL-termination
-                character).
-</para>
-</listitem>
-</varlistentry>
-<varlistentry>
-<term>
         String
 </term>
 <listitem>
@@ -5444,7 +5428,7 @@ <title>Logical Replication Message Formats</title>
 </para>
 
 <para>
-  Note that there can be mutiple Origin messages inside single transaction.
+  Note that there can be multiple Origin messages inside a single transaction.
 </para>
 
 </listitem>
@@ -5464,7 +5448,7 @@ <title>Logical Replication Message Formats</title>
 </term>
 <listitem>
 <para>
-                Identifies the message as an relation message.
+                Identifies the message as a relation message.
 </para>
 </listitem>
 </varlistentry>
@@ -5474,7 +5458,7 @@ <title>Logical Replication Message Formats</title>
 </term>
 <listitem>
 <para>
-                Id of the relation.
+                ID of the relation.
 </para>
 </listitem>
 </varlistentry>
@@ -5484,7 +5468,7 @@ <title>Logical Replication Message Formats</title>
 </term>
 <listitem>
 <para>
-                Namespace (empty string for pg_catalog).
+                Namespace (empty string for <literal>pg_catalog</literal>).
 </para>
 </listitem>
 </varlistentry>
@@ -5506,7 +5490,7 @@ <title>Logical Replication Message Formats</title>
 <listitem>
 <para>
                 Replica identity setting for the relation (same as
-                relreplident in pg_class).
+                <structfield>relreplident</structfield> in <structname>pg_class</structname>).
 </para>
 </listitem>
 </varlistentry>
@@ -5575,7 +5559,7 @@ <title>Logical Replication Message Formats</title>
 </term>
 <listitem>
 <para>
-                Id of the relation corresponding to the id in the relation
+                ID of the relation corresponding to the ID in the relation
                 message.
 </para>
 </listitem>
@@ -5593,11 +5577,11 @@ <title>Logical Replication Message Formats</title>
 
 <varlistentry>
 <term>
-        TableData
+        TupleData
 </term>
 <listitem>
 <para>
-                TableData message part representing the contents of new tuple.
+                TupleData message part representing the contents of new tuple.
 </para>
 </listitem>
 </varlistentry>
@@ -5631,7 +5615,7 @@ <title>Logical Replication Message Formats</title>
 </term>
 <listitem>
 <para>
-                Id of the relation corresponding to the id in the relation
+                ID of the relation corresponding to the ID in the relation
                 message.
 </para>
 </listitem>
@@ -5643,9 +5627,9 @@ <title>Logical Replication Message Formats</title>
 </term>
 <listitem>
 <para>
-                Identifies the following TupleData sub message as a key.
-                This field is optional and is only present if table in which
-                the update changed the REPLICA IDENTITY index.
+                Identifies the following TupleData submessage as a key.
+                This field is optional and is only present if
+                the update changed the REPLICA IDENTITY index. XXX???
 </para>
 </listitem>
 </varlistentry>
@@ -5656,7 +5640,7 @@ <title>Logical Replication Message Formats</title>
 </term>
 <listitem>
 <para>
-                Identifies the following TupleData message as a old tuple.
+                Identifies the following TupleData submessage as an old tuple.
                 This field is optional and is only present if table in which
                 the update happened has REPLICA IDENTITY set to FULL.
 </para>
@@ -5665,11 +5649,11 @@ <title>Logical Replication Message Formats</title>
 
 <varlistentry>
 <term>
-        TableData
+        TupleData
 </term>
 <listitem>
 <para>
-                TableData message part representing the contents of old tuple
+                TupleData message part representing the contents of the old tuple
                 or primary key. Only present if the previous 'O' or 'K' part
                 is present.
 </para>
@@ -5689,11 +5673,11 @@ <title>Logical Replication Message Formats</title>
 
 <varlistentry>
 <term>
-        TableData
+        TupleData
 </term>
 <listitem>
 <para>
-                TableData message part representing the contents of a new tuple.
+                TupleData message part representing the contents of a new tuple.
 </para>
 </listitem>
 </varlistentry>
@@ -5702,7 +5686,7 @@ <title>Logical Replication Message Formats</title>
 </para>
 
 <para>
-    The Update message may contain either 'K' message part or 'O' message part
+    The Update message may contain either a 'K' message part or an 'O' message part
     or neither of them, but never both of them.
 </para>
 
@@ -5733,7 +5717,7 @@ <title>Logical Replication Message Formats</title>
 </term>
 <listitem>
 <para>
-                Id of the relation corresponding to the id in the relation
+                ID of the relation corresponding to the ID in the relation
                 message.
 </para>
 </listitem>
@@ -5745,7 +5729,7 @@ <title>Logical Replication Message Formats</title>
 </term>
 <listitem>
 <para>
-                Identifies the following TupleData sub message as a key.
+                Identifies the following TupleData submessage as a key.
                 This field is present if the table in which the delete has
                 happened uses an index as REPLICA IDENTITY.
 </para>
@@ -5767,11 +5751,11 @@ <title>Logical Replication Message Formats</title>
 
 <varlistentry>
 <term>
-        TableData
+        TupleData
 </term>
 <listitem>
 <para>
-                TableData message part representing the contents of old tuple
+                TupleData message part representing the contents of the old tuple
                 or primary key, depending on the previous field.
 </para>
 </listitem>
@@ -5780,7 +5764,7 @@ <title>Logical Replication Message Formats</title>
 </para>
 
 <para>
-    The Delete message may contain either 'K' message part or 'O' message,
+    The Delete message may contain either a 'K' message part or an 'O' message part,
     but never both of them.
 </para>
 
diff --git a/src/backend/replication/logical/proto.c b/src/backend/replication/logical/proto.c
index 56790c859e..9a2a168b40 100644
--- a/src/backend/replication/logical/proto.c
+++ b/src/backend/replication/logical/proto.c
@@ -200,7 +200,6 @@ logicalrep_write_origin(StringInfo out, const char *origin,
 	pq_sendstring(out, origin);
 }
 
-
 /*
  * Read ORIGIN from the output stream.
  */
@@ -214,7 +213,6 @@ logicalrep_read_origin(StringInfo in, XLogRecPtr *origin_lsn)
 	return pstrdup(pq_getmsgstring(in));
 }
 
-
 /*
  * Write INSERT to the output stream.
  */
@@ -487,7 +485,7 @@ logicalrep_write_tuple(StringInfo out, Relation rel, HeapTuple tuple)
 	}
 	pq_sendint(out, nliveatts, 2);
 
-	/* try to allocate enough memory from the get go */
+	/* try to allocate enough memory from the get-go */
 	enlargeStringInfo(out, tuple->t_len +
 					  nliveatts * (1 + 4));
 
@@ -499,7 +497,7 @@ logicalrep_write_tuple(StringInfo out, Relation rel, HeapTuple tuple)
 		HeapTuple	typtup;
 		Form_pg_type typclass;
 		Form_pg_attribute att = desc->attrs[i];
-		char   	   *outputstr;
+		char	   *outputstr;
 		int			len;
 
 		/* skip dropped columns */
@@ -584,9 +582,8 @@ logicalrep_read_tuple(StringInfo in, LogicalRepTupleData *tuple)
 	}
 }
 
-
 /*
- * Write relation attributes to the outputstream.
+ * Write relation attributes to the stream.
  */
 static void
 logicalrep_write_attrs(StringInfo out, Relation rel)
@@ -644,9 +641,8 @@ logicalrep_write_attrs(StringInfo out, Relation rel)
 	bms_free(idattrs);
 }
 
-
 /*
- * Read relation attribute names from the outputstream.
+ * Read relation attribute names from the stream.
  */
 static void
 logicalrep_read_attrs(StringInfo in, LogicalRepRelation *rel)
diff --git a/src/include/replication/pgoutput.h b/src/include/replication/pgoutput.h
index 7083cd7e76..c20451d1f2 100644
--- a/src/include/replication/pgoutput.h
+++ b/src/include/replication/pgoutput.h
@@ -16,7 +16,7 @@
 
 typedef struct PGOutputData
 {
-	MemoryContext	context;			/* pricate memory context for transient
+	MemoryContext	context;			/* private memory context for transient
 										 * allocations */
 
 	/* client info */
-- 
2.11.0

0008-fixup-Add-logical-replication-workers.patchtext/x-patch; name=0008-fixup-Add-logical-replication-workers.patchDownload
From cdc02919f9d1b2c2d1c5cdf7eb15e1c9a0b38f09 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Mon, 12 Dec 2016 12:00:00 -0500
Subject: [PATCH 8/8] fixup! Add logical replication workers

---
 doc/src/sgml/logical-replication.sgml      | 136 ++++++++++++++---------------
 doc/src/sgml/monitoring.sgml               |  10 +--
 doc/src/sgml/ref/create_publication.sgml   |   2 +-
 src/backend/catalog/system_views.sql       |  16 ++--
 src/backend/executor/execReplication.c     |  28 +++---
 src/backend/postmaster/postmaster.c        |   6 +-
 src/backend/replication/logical/apply.c    |   4 +-
 src/backend/replication/logical/launcher.c |  13 ++-
 src/include/catalog/pg_subscription.h      |   9 +-
 9 files changed, 109 insertions(+), 115 deletions(-)

diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index bf5966e5ac..6603533ba6 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -6,7 +6,7 @@ <title>Logical Replication</title>
   <para>
     Logical replication is a method of replicating data objects and their
     changes, based upon their replication identity (usually a primary key).
-    We use the term logical in contrast to physical replication which
+    We use the term logical in contrast to physical replication, which
     uses exact block addresses and byte-by-byte replication.
     PostgreSQL supports both mechanisms concurrently, see
     <xref linkend="high-availability">. Logical replication allows
@@ -37,7 +37,7 @@ <title>Logical Replication</title>
     <listitem>
       <para>
         Sending incremental changes in a single database or a subset of
-        a database to Subscribers as they occur.
+        a database to subscribers as they occur.
       </para>
     </listitem>
     <listitem>
@@ -54,7 +54,7 @@ <title>Logical Replication</title>
     </listitem>
     <listitem>
       <para>
-        Replicating between different major versions of PostgreSQL
+        Replicating between different major versions of PostgreSQL.
       </para>
     </listitem>
     <listitem>
@@ -74,7 +74,7 @@ <title>Logical Replication</title>
     PostgreSQL instance and can be used as a publisher for other
     databases by defining its own publications. When the subscriber is
     treated as read-only by application, there will be no conflicts from
-    a single subscription. On the other hand if there are other writes
+    a single subscription. On the other hand, if there are other writes
     done either by application or other subscribers to the same set of
     tables conflicts can arise.
   </para>
@@ -84,14 +84,13 @@ <title>Publication</title>
   <para>
     A <firstterm>publication</> object can be defined on any physical
     replication master. The node where a publication is defined is referred
-    to as <firstterm>publisher</>. Only superusers or members of the
-    <literal>REPLICATION</> role can define a publication. A publication is
+    to as <firstterm>publisher</>. A publication is
     a set of changes generated from a group of tables, and might also be
     described as a <firstterm>change set</> or <firstterm>replication set</>.
     Each publication exists in only one database.
   </para>
   <para>
-    Publications are different from table schema and do not affect
+    Publications are different from schemas and do not affect
     how the table is accessed. Each table can be added to multiple
     publications if needed.  Publications may currently only contain
     tables. Objects must be added explicitly, except when a publication
@@ -99,38 +98,35 @@ <title>Publication</title>
   </para>
   <para>
     Publications can choose to limit the changes they produce to show
-    any combination of <command>INSERT</>, <command>UPDATE</> and
+    any combination of <command>INSERT</>, <command>UPDATE</>, and
     <command>DELETE</> in a similar way to the way triggers are fired by
-    particular event types. If a table with without a
-    <literal>REPLICA IDENTITY</> is added to a publication which replicates
+    particular event types. If a table without a
+    <literal>REPLICA IDENTITY</> is added to a publication that replicates
     <command>UPDATE</> or <command>DELETE</> operations then subsequent
     <command>UPDATE</> or <command>DELETE</> operations will fail on the
     publisher.
    </para>
   <para>
-    The definition of a publication object will be included within
-    pg_dump.
-  </para>
-  <para>
     Every publication can have multiple subscribers.
   </para>
   <para>
-    Publication is created using the <xref linkend="sql-createpublication">
+    A publication is created using the <xref linkend="sql-createpublication">
     command and may be later altered or dropped using corresponding commands.
   </para>
   <para>
     The individual tables can be added and removed dynamically using
     <xref linkend="sql-alterpublication">. Both the <literal>ADD TABLE</>
-    and <literal>DROP TABLE</> operations are transactional so the table
+    and <literal>DROP TABLE</> operations are transactional; so the table
     will start or stop replicating at the correct snapshot once the
     transaction has committed.
   </para>
 </sect1>
+
 <sect1 id="logical-replication-subscription">
   <title>Subscription</title>
   <para>
     A <firstterm>subscription</> is the downstream side of logical
-    replication. The node where subscription is defined is referred to as
+    replication. The node where a subscription is defined is referred to as
     the <firstterm>subscriber</>. Subscription defines the connection to
     another database and set of publications (one or more) to which it
     wants to be subscribed.
@@ -141,7 +137,7 @@ <title>Subscription</title>
     databases by defining its own publications.
   </para>
   <para>
-    A subscriber may have multiple subscriptions if desired. It is
+    A subscriber node may have multiple subscriptions if desired. It is
     possible to define multiple subscriptions between a single
     publisher-subscriber pair, in which case extra care must be taken
     to ensure that the subscribed publication objects don't overlap.
@@ -153,32 +149,33 @@ <title>Subscription</title>
     of pre-existing table data.
   </para>
   <para>
-    Subscriptions are not dumped by pg_dump by default but can be
-    requested using the --subscriptions parameter.
+    Subscriptions are not dumped by <command>pg_dump</command> by default but can be
+    requested using the command-line option <option>--subscriptions</option>.
   </para>
   <para>
     The subscription is added using <xref linkend="sql-createsubscription">
     and can be stopped/resumed at any time using the
-    <xref linkend="sql-altersubscription"> command or removed using
+    <xref linkend="sql-altersubscription"> command and removed using
     <xref linkend="sql-dropsubscription">.
   </para>
   <para>
-    When subscription is dropped and recreated, the synchronization
+    When a subscription is dropped and recreated, the synchronization
     information is lost. This means that the data has to be
     resynchronized afterwards.
   </para>
   <para>
     The schema definitions are not replicated and the published tables
     must exist on the subsriber for replication to work. Only regular
-    tables may be the target of replication (ie, you can't replicate
+    tables may be the target of replication (i.e., you can't replicate
     to a view).
   </para>
   <para>
-    The tables are matched using fully qualified table name. Replication
-    to differently named tables on subscriber is not supported.
+    The tables are matched between the publisher and the subscriber using
+    the fully qualified table name. Replication
+    to differently-named tables on the subscriber is not supported.
   </para>
   <para>
-    Columns of a table are also matched by name. The different order of
+    Columns of a table are also matched by name. A different order of
     columns in the target table is allowed, but the column types have to
     match.
   </para>
@@ -197,11 +194,11 @@ <title>Conflicts</title>
   <para>
     A conflict will produce an error and will stop the replication; it
     must be resolved manually by the user. Details about the conflict can
-    be found in the subscribers PostgreSQL log.
+    be found in the subscriber's server log.
   </para>
   <para>
     The resolution can be done either by changing data on the subscriber
-    so that it does not conflict with incoming change or by skipping the
+    so that it does not conflict with the incoming change or by skipping the
     transaction that conflicts with the existing data. The transaction
     can be skipped by calling the
     <link linkend="pg-replication-origin-advance">
@@ -217,42 +214,42 @@ <title>Architecture</title>
   <para>
     Logical replication starts by copying a snapshot of the data on
     the publisher database. Once that is done, changes on the publisher
-    are sent to the subscriber as they occur in real-time. The subscriber
+    are sent to the subscriber as they occur in real time. The subscriber
     applies data in the order in which commits were made on the
     publisher so that transactional consistency is guaranteed for the
-    publications within any single Subscription.
+    publications within any single subscription.
   </para>
   <para>
     Logical replication is built with an architecture similar to
     physical streaming replication
     (see <xref linkend="streaming-replication">). It is implemented by
-    WalSender and the Apply processes. The WalSender starts logical
+    <quote>walsender</quote> and the <quote>apply</quote> processes. The walsender starts logical
     decoding (described in <xref linkend="logicaldecoding">) of the WAL and
     loads the standard logical decoding plugin (pgoutput). The plugin
     transforms the changes read from WAL to the logical replication protocol
     (see <xref linkend="protocol-logical-replication">) and filters the data
-    according to publication specification. The data is then continuously
-    transferred using the streaming replication protocol to the Apply worker
+    according to the publication specification. The data is then continuously
+    transferred using the streaming replication protocol to the apply worker,
     which maps the data to local tables and applies the individual changes as
     they are received in exact transactional order.
   </para>
   <para>
-    The Apply process on the subscriber database always runs with
-    session_replication_role set to replica, which produces the usual effects
+    The apply process on the subscriber database always runs with
+    <varname>session_replication_role</varname> set to <literal>replica</literal>, which produces the usual effects
     on triggers and constraints.
   </para>
   <sect2 id="logical-replication-snapshot">
-    <title>Initial snapshot</title>
+    <title>Initial Snapshot</title>
     <para>
       The initial data in existing subscribed tables are snapshotted and
-      copied in a parallel instance of a special kind of Apply process.
+      copied in a parallel instance of a special kind of apply process.
       This process will create its own temporary replication slot and
       copy the existing data. Once existing data is copied, the worker
-      enters synchronization mode which ensures that the table is brought
-      up to synchronized state with the main Apply process by streaming
-      any changes which happened during the initial data copy using standard
+      enters synchronization mode, which ensures that the table is brought
+      up to a synchronized state with the main apply process by streaming
+      any changes that happened during the initial data copy using standard
       logical replication. Once the synchronization is done, the control
-      of the replication of the table is given back to the main Apply
+      of the replication of the table is given back to the main apply
       process where the replication continues as normal.
     </para>
   </sect2>
@@ -262,81 +259,82 @@ <title>Monitoring</title>
   <para>
     Because logical replication is based on similar architecture as
     <link linkend="streaming-replication">physical streaming
-    replication</link> the monitoring on publication is very similar to
+    replication</link> the monitoring on a publication node is very similar to
     monitoring of physical replication master (see
     <xref linkend="streaming-replication-monitoring">).
   </para>
   <para>
     The monitoring information about subscription is visible in
     <link linkend="pg-stat-subscription"><literal>pg_stat_subscription</></link>.
-    This view contains one row for every subscription worker. Subscription
+    This view contains one row for every subscription worker. A subscription
     can have zero or more active subscription workers depending on its state.
   </para>
   <para>
-    Normally there is a single Apply process running for the enabled
-    subscription. The disabled subscription of crashed subscription will
+    Normally, there is a single apply process running for an enabled
+    subscription. A disabled subscription or a crashed subscription will
     have zero rows in this view. If the initial data synchronization of
-    any table is in progress there will be additional worker(s) for the
-    table(s) being synchronized.
+    any table is in progress there will be additional workers for the
+    tables being synchronized.
   </para>
 </sect1>
 <sect1 id="logical-replication-security">
   <title>Security</title>
   <para>
-    Replication connection can occur in the same way as physical streaming
+    Logical replication connections occur in the same way as physical streaming
     replication. It requires access to be specifically given using
-    pg_hba.conf. The role used for the replication must have
-    <literal>REPLICATION</literal> privilege <command>GRANT</command>ED.
+    <filename>pg_hba.conf</filename>. The role used for the replication connection must have
+    the <literal>REPLICATION</literal> attribute.
     This gives a role access to both logical and physical replication.
   </para>
   <para>
-    To create a publication the user must have the REPLICATION role, or be
-    a superuser.
+    To create a publication, the user must have the <literal>CREATE</literal>
+    privilege in the database.
   </para>
   <para>
-    To create a subscription the user must be a superuser.
+    To create a subscription, the user must be a superuser.
   </para>
   <para>
     The subscription apply process will run in the local database
     with the privileges of a superuser.
   </para>
   <para>
-    In particular, note that privileges are not re-checked as each change
+    Privileges are only checked once at the start of a replication connection.
+    They are not re-checked as each change
     record is read from the publisher, nor are they re-checked for each change
-    when applied. Security is checked once at startup.
+    when applied.
   </para>
 </sect1>
 <sect1 id="logical-replication-gucs">
   <title>Logical replication related configuration parameters</title>
   <para>
-    The Logical Replication requires several configuration options to be
+    Logical replication requires several configuration options to be
     set.
   </para>
   <para>
-    On the publisher side the <varname>wal_level</> must be set to
+    On the publisher side, <varname>wal_level</> must be set to
     <literal>logical</>, and <varname>max_replication_slots</> has to be set to
-    at least the number of Subscriptions expected to connect with some reserve
-    for table synchronization as well. And <varname>max_wal_senders</>
+    at least the number of subscriptions expected to connect with some reserve
+    for table synchronization. And <varname>max_wal_senders</>
     should be set to at least the same as <varname>max_replication_slots</> plus
     the number of physical replicas that are connected at the same time.
   </para>
   <para>
-    The Subscriber also requires the <varname>max_replication_slots</> to
+    The subscriber also requires the <varname>max_replication_slots</> to
     be set. In this case it should be set to at least the number of
-    Subscriptions that will be added to the Subscriber. The
+    subscriptions that will be added to the subscriber.
     <varname>max_logical_replication_workers</> has to be set to at least
-    the number of Subscriptions again with some reserve for the table
+    the number of subscriptions, again with some reserve for the table
     synchronization. Additionally the <varname>max_worker_processes</> may
     need to be adjusted to accommodate for replication workers, at least
-    (<varname>max_logical_replication_workers</> + <literal>1</>). Please
-    note that some extensions and parallel queries also take worker slots
+    (<varname>max_logical_replication_workers</> + <literal>1</>).
+    Note that some extensions and parallel queries also take worker slots
     from <varname>max_worker_processes</>.
   </para>
 </sect1>
 <sect1 id="logical-replication-quick-setup">
   <title>Quick setup</title>
   <para>
-    First set the configuration options in the postgresql.conf:
+    First set the configuration options in <filename>postgresql.conf</filename>:
 <programlisting>
 wal_level = logical
 max_worker_processes = 10 # one per subscription + one per instance needed on subscriber
@@ -346,7 +344,7 @@ <title>Quick setup</title>
 </programlisting>
   </para>
   <para>
-    The pg_hba.conf needs to be adjusted to allow replication (the
+    <filename>pg_hba.conf</filename> needs to be adjusted to allow replication (the
     values here depend on your actual network configuration and user you
     want to use for connecting):
 <programlisting>
@@ -360,13 +358,13 @@ <title>Quick setup</title>
 </programlisting>
   </para>
   <para>
-    And on the Subscriber database:
+    And on the subscriber database:
 <programlisting>
-CREATE SUBSCRIPTION mysub WITH CONNECTION 'dbname=foo host=bar user=repuser' PUBLICATION mypub;
+CREATE SUBSCRIPTION mysub CONNECTION 'dbname=foo host=bar user=repuser' PUBLICATION mypub;
 </programlisting>
   </para>
   <para>
-    The above will start the replication process which synchronizes the
+    The above will start the replication process, which synchronizes the
     initial table contents of <literal>users</literal> and
     <literal>departments</literal> tables and then starts replicating
     incremental changes to those tables.
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 4d04330776..5c61ac785e 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -310,7 +310,7 @@ <title>Dynamic Statistics Views</title>
 
      <row>
       <entry><structname>pg_stat_subscription</><indexterm><primary>pg_stat_subscription</primary></indexterm></entry>
-      <entry>At least one row per subscription, showing statistics about
+      <entry>At least one row per subscription, showing information about
        the subscription workers.
        See <xref linkend="pg-stat-subscription"> for details.
       </entry>
@@ -1564,8 +1564,8 @@ <title><structname>pg_stat_subscription</structname> View</title>
    <tbody>
     <row>
      <entry><structfield>subid</></entry>
-     <entry><type>Oid</></entry>
-     <entry>Oid of the subscription</entry>
+     <entry><type>oid</></entry>
+     <entry>OID of the subscription</entry>
     </row>
     <row>
      <entry><structfield>subname</></entry>
@@ -1612,8 +1612,8 @@ <title><structname>pg_stat_subscription</structname> View</title>
 
   <para>
    The <structname>pg_stat_subscription</structname> view will contain one
-   row per subscription for main worker (with NULL pid if the worker is
-   not running) and then additional rows for workers handling initial data
+   row per subscription for main worker (with null PID if the worker is
+   not running), and additional rows for workers handling the initial data
    copy of the subscribed tables.
   </para>
 
diff --git a/doc/src/sgml/ref/create_publication.sgml b/doc/src/sgml/ref/create_publication.sgml
index 2a0da598f3..995f2bcf3c 100644
--- a/doc/src/sgml/ref/create_publication.sgml
+++ b/doc/src/sgml/ref/create_publication.sgml
@@ -47,7 +47,7 @@ <title>Description</title>
    A publication is essentially a group of tables whose data changes are
    intended to be replicated through logical replication.  See
    <xref linkend="logical-replication-publication"> for details about how
-   publications fit into logical replication setup.
+   publications fit into the logical replication setup.
    </para>
  </refsect1>
 
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index acfeeee27a..ff1ac75a90 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -719,14 +719,14 @@ CREATE VIEW pg_stat_wal_receiver AS
 
 CREATE VIEW pg_stat_subscription AS
     SELECT
-			su.oid as subid,
-			su.subname,
-			st.pid,
-			st.received_lsn,
-			st.last_msg_send_time,
-			st.last_msg_receipt_time,
-			st.latest_end_lsn,
-			st.latest_end_time
+            su.oid AS subid,
+            su.subname,
+            st.pid,
+            st.received_lsn,
+            st.last_msg_send_time,
+            st.last_msg_receipt_time,
+            st.latest_end_lsn,
+            st.latest_end_time
     FROM pg_subscription su
             LEFT JOIN pg_stat_get_subscription(NULL) st
                       ON (st.subid = su.oid);
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index 44248a49f1..897118f52a 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -72,8 +72,8 @@ build_replindex_scan_key(ScanKey skey, Relation rel, Relation idxrel,
 		Oid			optype = get_opclass_input_type(opclass->values[attoff]);
 
 		/*
-		 * Load the operator info, we need this to get the equality operator
-		 * function for the scankey.
+		 * Load the operator info.  We need this to get the equality operator
+		 * function for the scan key.
 		 */
 		opfamily = get_opclass_family(opclass->values[attoff]);
 
@@ -109,8 +109,8 @@ build_replindex_scan_key(ScanKey skey, Relation rel, Relation idxrel,
 /*
  * Search the relation 'rel' for tuple using the index.
  *
- * If a matching tuple is found lock it with lockmode, fill the slot with its
- * contents and return true, return false is returned otherwise.
+ * If a matching tuple is found, lock it with lockmode, fill the slot with its
+ * contents, and return true.  Return false otherwise.
  */
 bool
 RelationFindReplTupleByIndex(Relation rel, Oid idxoid,
@@ -258,8 +258,8 @@ tuple_equals_slot(TupleDesc	desc, HeapTuple tup, TupleTableSlot *slot)
 /*
  * Search the relation 'rel' for tuple using the sequential scan.
  *
- * If a matching tuple is found lock it with lockmode, fill the slot with its
- * contents and return true, return false is returned otherwise.
+ * If a matching tuple is found, lock it with lockmode, fill the slot with its
+ * contents, and return true.  Return false otherwise.
  *
  * Note that this stops on the first matching tuple.
  *
@@ -357,10 +357,10 @@ RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode,
 }
 
 /*
- * Insert tuple represented in the slot to the relation, update the indexes
- * and execute any constraints and per row triggers.
+ * Insert tuple represented in the slot to the relation, update the indexes,
+ * and execute any constraints and per-row triggers.
  *
- * Caller is repsonsible for opening the indexes.
+ * Caller is responsible for opening the indexes.
  */
 void
 ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
@@ -412,12 +412,11 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
 	}
 }
 
-
 /*
  * Find the searchslot tuple and update it with data in the slot,
- * update the indexes and execute any constraints and per row triggers.
+ * update the indexes, and execute any constraints and per-row triggers.
  *
- * Caller is repsonsible for opening the indexes.
+ * Caller is responsible for opening the indexes.
  */
 void
 ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
@@ -475,12 +474,11 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 	}
 }
 
-
 /*
  * Find the searchslot tuple and delete it, and execute any constraints
- * and per row triggers.
+ * and per-row triggers.
  *
- * Caller is repsonsible for opening the indexes.
+ * Caller is responsible for opening the indexes.
  */
 void
 ExecSimpleRelationDelete(EState *estate, EPQState *epqstate,
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 9069a43e1d..8212db3b9f 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -935,9 +935,9 @@ PostmasterMain(int argc, char *argv[])
 #endif
 
 	/*
-	 * Register the apply launcher. Since it registers background worker,
-	 * it needs to be called before InitializeMaxBackends() and it's probably
-	 * good idea to call it before aby modules had chance to take the
+	 * Register the apply launcher.  Since it registers a background worker,
+	 * it needs to be called before InitializeMaxBackends(), and it's probably
+	 * a good idea to call it before any modules had chance to take the
 	 * background worker slots.
 	 */
 	ApplyLauncherRegister();
diff --git a/src/backend/replication/logical/apply.c b/src/backend/replication/logical/apply.c
index de50f1dde6..3f78b30797 100644
--- a/src/backend/replication/logical/apply.c
+++ b/src/backend/replication/logical/apply.c
@@ -568,7 +568,7 @@ check_relation_updatable(LogicalRepRelMapEntry *rel)
 	ereport(ERROR,
 			(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 			 errmsg("the logical replication target %s has "
-					"neither REPLICA IDENTIY index or PRIMARY "
+					"neither REPLICA IDENTIY index nor PRIMARY "
 					"KEY and published relation does not have "
 					"REPLICA IDENTITY FULL",
 					quote_qualified_identifier(rel->remoterel.nspname,
@@ -1163,7 +1163,7 @@ reread_subscription(void)
 	if (!equal(newsub->publications, MySubscription->publications))
 	{
 		elog(LOG, "logical replication worker for subscription %s will "
-			 "restart because ssubscription's publications were changed",
+			 "restart because subscription's publications were changed",
 			 MySubscription->name);
 
 		walrcv_disconnect(wrconn);
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 7d5a233580..f5b5ebcd06 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -251,9 +251,8 @@ logicalrep_worker_launch(Oid dbid, Oid subid)
 	{
 		ereport(WARNING,
 				(errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
-				 errmsg("logical replication worker registration failed, "
-						"you might want to increase "
-						"max_logical_replication_workers setting")));
+				 errmsg("out of logical replication workers slots"),
+				 errhint("You might need to increase max_logical_replication_workers.")));
 		return;
 	}
 
@@ -293,7 +292,7 @@ logicalrep_worker_launch(Oid dbid, Oid subid)
  * slot.
  *
  * Note it's caller's job to ensure that new workers are not being started
- * during this functiion call. That can be achieven by holding exclusive
+ * during this function call. That can be achieven by holding exclusive
  * lock on LogicalRepLauncherLock.
  */
 void
@@ -567,7 +566,7 @@ ApplyLauncherMain(Datum main_arg)
 									   ALLOCSET_DEFAULT_MAXSIZE);
 		oldctx = MemoryContextSwitchTo(subctx);
 
-		/* Block any concurrent DROPs. */
+		/* Block any concurrent DROP SUBSCRIPTION. */
 		LWLockAcquire(LogicalRepLauncherLock, LW_EXCLUSIVE);
 
 		/* search for subscriptions to start or stop. */
@@ -605,8 +604,8 @@ ApplyLauncherMain(Datum main_arg)
 					   started ? 5000L : 180000L,
 					   WAIT_EVENT_LOGICAL_LAUNCHER_MAIN);
 
-        /* emergency bailout if postmaster has died */
-        if (rc & WL_POSTMASTER_DEATH)
+		/* emergency bailout if postmaster has died */
+		if (rc & WL_POSTMASTER_DEATH)
 			proc_exit(1);
 
 		ResetLatch(&MyProc->procLatch);
diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h
index b337d6dc7c..342066f17f 100644
--- a/src/include/catalog/pg_subscription.h
+++ b/src/include/catalog/pg_subscription.h
@@ -21,12 +21,11 @@
 #define SubscriptionRelationId			6100
 #define SubscriptionRelation_Rowtype_Id	6101
 
-/* ----------------
- * Technicaly, the subscriptions live inside the database so shared catalog
+/*
+ * Technicaly, the subscriptions live inside the database, so a shared catalog
  * seems weird, but the replication launcher process needs to access all of
- * then to be able to start the workers so we have to put them in a shared,
- * nailed catalogs.
- * ----------------
+ * them to be able to start the workers, so we have to put them in a shared,
+ * nailed catalog.
  */
 CATALOG(pg_subscription,6100) BKI_SHARED_RELATION BKI_ROWTYPE_OID(6101) BKI_SCHEMA_MACRO
 {
-- 
2.11.0

#134Andres Freund
andres@anarazel.de
In reply to: Peter Eisentraut (#133)
Re: Logical Replication WIP

Hi,

On 2016-12-13 15:42:17 -0500, Peter Eisentraut wrote:

I think this is getting very close to the point where it's committable.
So if anyone else has major concerns about the whole approach and
perhaps the way the new code in 0005 is organized, now would be the time ...

Uh. The whole cache invalidation thing is completely unresolved, and
that's just the publication patch. I've not looked in detail at later
patches. So no, I don't think so.

I think after the invalidation issue is resolved the publication patch
might be close to being ready. I'm doubtful the later patches are.

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

#135Andres Freund
andres@anarazel.de
In reply to: Petr Jelinek (#132)
Re: Logical Replication WIP

On 2016-12-13 06:55:31 +0100, Petr Jelinek wrote:

This is a quadratic algorithm - that could bite us... Not sure if we
need to care. If we want to fix it, one approach owuld be to use
RangeVarGetRelid() instead, and then do a qsort/deduplicate before
actually opening the relations.

I guess it could get really slow only with big inheritance tree, I'll
look into how much work is the other way of doing things (this is not
exactly hot code path).

Actually looking at it, it only processes user input so I don't think
it's very problematic in terms of performance. You'd have to pass many
thousands of tables in single DDL to notice.

Well, at least we should put a CHECK_FOR_INTERRUPTS there. At the moment
it's IIRC uninterruptible, which isn't good for something directly
triggered by the user. A comment that it's known to be O(n^2), but
considered acceptable, would be good too.

Andres

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

#136Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Andres Freund (#128)
Re: Logical Replication WIP

On 12/12/16 7:33 PM, Andres Freund wrote:

+-- test switching between slots in a session
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot1', 'test_decoding', true);
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot2', 'test_decoding', true);
+SELECT * FROM pg_logical_slot_get_changes('regression_slot1', NULL, NULL);
+SELECT * FROM pg_logical_slot_get_changes('regression_slot2', NULL, NULL);

Can we actually output something? Right now this doesn't test that
much...

This test was added because an earlier version of the patch would crash
on this.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#137Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Peter Eisentraut (#136)
Re: Logical Replication WIP

On 14/12/16 01:26, Peter Eisentraut wrote:

On 12/12/16 7:33 PM, Andres Freund wrote:

+-- test switching between slots in a session
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot1', 'test_decoding', true);
+SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot2', 'test_decoding', true);
+SELECT * FROM pg_logical_slot_get_changes('regression_slot1', NULL, NULL);
+SELECT * FROM pg_logical_slot_get_changes('regression_slot2', NULL, NULL);

Can we actually output something? Right now this doesn't test that
much...

This test was added because an earlier version of the patch would crash
on this.

I did improve the test as part of the tests improvements that were sent
to committers list btw.

--
Petr Jelinek 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

#138Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Andres Freund (#134)
Re: Logical Replication WIP

On 13/12/16 22:05, Andres Freund wrote:

Hi,

On 2016-12-13 15:42:17 -0500, Peter Eisentraut wrote:

I think this is getting very close to the point where it's committable.
So if anyone else has major concerns about the whole approach and
perhaps the way the new code in 0005 is organized, now would be the time ...

Uh. The whole cache invalidation thing is completely unresolved, and
that's just the publication patch. I've not looked in detail at later
patches. So no, I don't think so.

I already have code for that. I'll submit next version once I'll go over
PeterE's review. BTW the relcache thing is not as bad as it seems from
the publication patch because output plugin has to deal with
relcache/publication cache invalidations, it handles most of the updates
correctly. But there was still problem in terms of the write filtering
so the publications still have to reset relcache too.

--
Petr Jelinek 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

#139Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Peter Eisentraut (#133)
Re: Logical Replication WIP

On 13/12/16 21:42, Peter Eisentraut wrote:

On 12/10/16 2:48 AM, Petr Jelinek wrote:

Attached new version with your updates and rebased on top of the current
HEAD (the partitioning patch produced quite a few conflicts).

I have attached a few more "fixup" patches, mostly with some editing of
documentation and comments and some compiler warnings.

In 0006 in the protocol documentation I have left a "XXX ???" where I
didn't understand what it was trying to say.

Okay I'll address that separately, thanks.

All issues from (my) previous reviews appear to have been addressed.

Comments besides that:

0003-Add-SUBSCRIPTION-catalog-and-DDL-v12.patch

Still wondering about the best workflow with pg_dump, but it seems all
the pieces are there right now, and the interfaces can be tweaked later.

Right, either way there needs to be some special handling for
subscriptions, having to request them specifically seems safest option
to me, but I am open to suggestions there.

DROP SUBSCRIPTION requires superuser, but should perhaps be owner check
only?

Hmm not sure that it requires superuser, I actually think it mistakenly
didn't require anything. In any case will make sure it just does owner
check.

DROP SUBSCRIPTION IF EXISTS crashes if the subscription does not in fact
exist.

Right, missing return.

Maybe write the grammar so that SLOT does not need to be a new key word.
The changes you made for CREATE PUBLICATION should allow that.

Hmm how would that look like? The opt_drop_slot would become IDENT
IDENT? Or maybe you want me to add the WITH (definition) kind of thing?

The tests are not added to serial_schedule. Intentional? If so, document?

Not intentional, will fix. Never use it, easy to forget about it.

0004-Define-logical-replication-protocol-and-output-plugi-v12.patch

Not sure why pg_catalog is encoded as a zero-length string. I guess it
saves some space. Maybe that could be explained in a brief code comment?

Yes it's to save space, mainly for built-in types.

0005-Add-logical-replication-workers-v12.patch

The way the executor stuff is organized now looks better to me.

The subscriber crashes if max_replication_slots is 0:

TRAP: FailedAssertion("!(max_replication_slots > 0)", File: "origin.c",
Line: 999)

The documentation says that replication slots are required on the
subscriber, but from a user's perspective, it's not clear why that is.

Yeah honestly I think origins should not depend on
max_replication_slots. They are not really connected (you can have many
of one and none of the other and vice versa). Also max_replication_slots
should IMHO default to max_wal_senders at this point. (In ideal world
all of those 3 would be in DSM instead of SHM and only governed by some
implementation maximum which is probably 2^16 and the GUCs would be removed)

But yes as it is, we should check for that, probably both during CREATE
SUBSCRIPTION and during apply start.

Dropping a table that is part of a live subscription results in log
messages like

WARNING: leaked hash_seq_search scan for hash table 0x7f9d2a807238

I was testing replicating into a temporary table, which failed like this:

FATAL: the logical replication target public.test1 not found
LOG: worker process: (PID 2879) exited with exit code 1
LOG: starting logical replication worker for subscription 16392
LOG: logical replication apply for subscription mysub started

That's okay, but those messages were repeated every few seconds or so
and would create quite some log volume. I wonder if that needs to be
reigned in somewhat.

It retries every 5s or so I think, I am not sure how that could be
improved besides using the wal_retrieve_retry_interval instead of
hardcoded 5s (or maybe better add GUC for apply). Maybe some kind of
backoff algorithm could be added as well.

--
Petr Jelinek 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

#140Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Andres Freund (#128)
1 attachment(s)
Re: Logical Replication WIP

On 13/12/16 01:33, Andres Freund wrote:

On 2016-12-12 09:18:48 -0500, Peter Eisentraut wrote:

On 12/8/16 4:10 PM, Petr Jelinek wrote:

On 08/12/16 20:16, Peter Eisentraut wrote:

On 12/6/16 11:58 AM, Peter Eisentraut wrote:

On 12/5/16 6:24 PM, Petr Jelinek wrote:

I think that the removal of changes to ReplicationSlotAcquire() that you
did will result in making it impossible to reacquire temporary slot once
you switched to different one in the session as the if (active_pid != 0)
will always be true for temp slot.

I see. I suppose it's difficult to get a test case for this.

I created a test case, saw the error of my ways, and added your code
back in. Patch attached.

Hi,

I am happy with this version, thanks for moving it forward.

committed

Hm.

/*
+ * Cleanup all temporary slots created in current session.
+ */
+void
+ReplicationSlotCleanup()

I'd rather see a (void) there. The prototype has it, but still.

+
+	/*
+	 * No need for locking as we are only interested in slots active in
+	 * current process and those are not touched by other processes.

I'm a bit suspicious of this claim. Without a memory barrier you could
actually look at outdated versions of active_pid. In practice there's
enough full memory barriers in the slot creation code that it's
guaranteed to not be the same pid from before a wraparound though.

I think that doing iterations of slots without
ReplicationSlotControlLock makes things more fragile, because suddenly
assumptions that previously held aren't true anymore. E.g. factually
/*
* The slot is definitely gone. Lock out concurrent scans of the array
* long enough to kill it. It's OK to clear the active flag here without
* grabbing the mutex because nobody else can be scanning the array here,
* and nobody can be attached to this slot and thus access it without
* scanning the array.
*/
is now simply not true anymore. It's probably not harmfully broken, but
at least you've changed the locking protocol without adapting comments.

Any thoughts on attached? Yes it does repeated scans which can in theory
be slow but as I explained in the comment, in practice there is not much
need to have many temporary slots active within single session so it
should not be big issue.

I am not quite convinced that all the locking is necessary from the
current logic perspective TBH but it should help prevent mistakes by
whoever changes things in slot.c next.

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

Attachments:

0002-Improve-behavior-of-ReplicationSlotCleanup.patchtext/x-diff; name=0002-Improve-behavior-of-ReplicationSlotCleanup.patchDownload
From 3ddeac5eb01da1642a6c7eb9a290a3ded08045dd Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Thu, 15 Dec 2016 09:43:17 +0100
Subject: [PATCH 2/2] Improve behavior of ReplicationSlotCleanup()

Make sure we have slot locked properly for modification everywhere and
also cleanup MyReplicationSlot so to reduce code duplication.
---
 src/backend/replication/slot.c      | 58 ++++++++++++++++++++++++++++---------
 src/backend/replication/walsender.c |  3 --
 src/backend/storage/lmgr/proc.c     |  6 +---
 src/backend/tcop/postgres.c         |  4 ---
 4 files changed, 45 insertions(+), 26 deletions(-)

diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index d8ed005..ed50e49 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -412,30 +412,60 @@ ReplicationSlotRelease(void)
 }
 
 /*
- * Cleanup all temporary slots created in current session.
+ * Search the replication slot list for temporary slot owned by current
+ * session and return it. Returns NULL if not found.
  */
-void
-ReplicationSlotCleanup()
+static ReplicationSlot *
+FindMyNextTempSlot(void)
 {
-	int			i;
-
-	Assert(MyReplicationSlot == NULL);
+	int					i;
+	ReplicationSlot	   *slot;
 
-	/*
-	 * No need for locking as we are only interested in slots active in
-	 * current process and those are not touched by other processes.
-	 */
+	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 	for (i = 0; i < max_replication_slots; i++)
 	{
-		ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
+		slot = &ReplicationSlotCtl->replication_slots[i];
 
-		if (s->active_pid == MyProcPid)
+		SpinLockAcquire(&slot->mutex);
+		if (slot->active_pid == MyProcPid)
 		{
-			Assert(s->in_use && s->data.persistency == RS_TEMPORARY);
+			Assert(slot->in_use && slot->data.persistency == RS_TEMPORARY);
 
-			ReplicationSlotDropPtr(s);
+			SpinLockRelease(&slot->mutex);
+			LWLockRelease(ReplicationSlotControlLock);
+			return slot;
 		}
+		else
+			SpinLockRelease(&slot->mutex);
 	}
+	LWLockRelease(ReplicationSlotControlLock);
+
+	return NULL;
+}
+
+/*
+ * Cleanup all any slot state we might have. This includes releasing any
+ * active replication slot in current session and dropping all temporary
+ * slots created in current session.
+ */
+void
+ReplicationSlotCleanup(void)
+{
+	ReplicationSlot	   *slot;
+
+	if (MyReplicationSlot != NULL)
+		ReplicationSlotRelease();
+
+	/*
+	 * Find all temp slots and drop them. This does repeated scans of the
+	 * array so it's theoretically quadratic algorithm, but in practice
+	 * single session does not have reason to create many temporary slots
+	 * so the negative performance effects should be minimal.
+	 * If this turns out to be problematic in terms of performance we'll
+	 * need to rethink locking guarantees around the replication slots.
+	 */
+	while ((slot = FindMyNextTempSlot()) != NULL)
+		ReplicationSlotDropPtr(slot);
 }
 
 /*
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index b14d821..c90ca92 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -263,9 +263,6 @@ WalSndErrorCleanup(void)
 		sendFile = -1;
 	}
 
-	if (MyReplicationSlot != NULL)
-		ReplicationSlotRelease();
-
 	ReplicationSlotCleanup();
 
 	replication_active = false;
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index e9555f2..d84a4b4 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -806,11 +806,7 @@ ProcKill(int code, Datum arg)
 	/* Cancel any pending condition variable sleep, too */
 	ConditionVariableCancelSleep();
 
-	/* Make sure active replication slots are released */
-	if (MyReplicationSlot != NULL)
-		ReplicationSlotRelease();
-
-	/* Also cleanup all the temporary slots. */
+	/* Release replication slots held by current session. */
 	ReplicationSlotCleanup();
 
 	/*
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index b179231..7213ffd 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -3875,10 +3875,6 @@ PostgresMain(int argc, char *argv[],
 		 * so releasing here is fine. There's another cleanup in ProcKill()
 		 * ensuring we'll correctly cleanup on FATAL errors as well.
 		 */
-		if (MyReplicationSlot != NULL)
-			ReplicationSlotRelease();
-
-		/* We also want to cleanup temporary slots on error. */
 		ReplicationSlotCleanup();
 
 		/*
-- 
2.7.4

#141Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Peter Eisentraut (#133)
Re: Logical Replication WIP

On 13/12/16 21:42, Peter Eisentraut wrote:

On 12/10/16 2:48 AM, Petr Jelinek wrote:

Attached new version with your updates and rebased on top of the current
HEAD (the partitioning patch produced quite a few conflicts).

I have attached a few more "fixup" patches, mostly with some editing of
documentation and comments and some compiler warnings.

In 0006 in the protocol documentation I have left a "XXX ???" where I
didn't understand what it was trying to say.

Ah so you didn't understand the

+                Identifies the following TupleData submessage as a key.
+                This field is optional and is only present if
+                the update changed the REPLICA IDENTITY index. XXX???

So what happens here is that the update message can contain one or two
out of 3 possible tuple submessages. It always contains 'N' message
which is the new data. Then it can optionally contain 'O' message with
old data if the table has REPLICA IDENTITY FULL (ie, not REPLICA
IDENTITY index like pkey, etc). Or it can include 'K' message that only
contains old data for the columns in the REPLICA IDENTITY index. But if
the REPLICA IDENTITY index didn't change (ie, old and new would be same
for those columns) we simply omit the 'K' message and let the downstream
take the key data from the 'N' message to save space.

--
Petr Jelinek 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

#142Craig Ringer
craig@2ndquadrant.com
In reply to: Petr Jelinek (#40)
Re: Logical Replication WIP

On 15 Dec. 2016 18:19, "Petr Jelinek" <petr.jelinek@2ndquadrant.com> wrote:

On 13/12/16 21:42, Peter Eisentraut wrote:

On 12/10/16 2:48 AM, Petr Jelinek wrote:

Attached new version with your updates and rebased on top of the current
HEAD (the partitioning patch produced quite a few conflicts).

I have attached a few more "fixup" patches, mostly with some editing of
documentation and comments and some compiler warnings.

In 0006 in the protocol documentation I have left a "XXX ???" where I
didn't understand what it was trying to say.

Ah so you didn't understand the

+                Identifies the following TupleData submessage as a key.
+                This field is optional and is only present if
+                the update changed the REPLICA IDENTITY index. XXX???

So what happens here is that the update message can contain one or two
out of 3 possible tuple submessages. It always contains 'N' message
which is the new data. Then it can optionally contain 'O' message with
old data if the table has REPLICA IDENTITY FULL (ie, not REPLICA
IDENTITY index like pkey, etc). Or it can include 'K' message that only
contains old data for the columns in the REPLICA IDENTITY index. But if
the REPLICA IDENTITY index didn't change (ie, old and new would be same
for those columns) we simply omit the 'K' message and let the downstream
take the key data from the 'N' message to save space.

Something we forgot to bake into pglogical that might be worth leaving room
for here: sending the whole old tuple, with some fields marked as key.

So you can use replica identity pkey or whatever and the downstream knows
which are the key fields. But can still transmit the whole old tuple in
case the downstream wants it for conflict resolution/logging/etc.

We don't have the logical decoding and wal output for this yet, nor a way
of requesting old tuple recording table by table. So all i'm suggesting is
leaving room in the protocol.

#143Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Craig Ringer (#142)
Re: Logical Replication WIP

On 15/12/16 13:06, Craig Ringer wrote:

On 15 Dec. 2016 18:19, "Petr Jelinek" <petr.jelinek@2ndquadrant.com
<mailto:petr.jelinek@2ndquadrant.com>> wrote:

On 13/12/16 21:42, Peter Eisentraut wrote:

On 12/10/16 2:48 AM, Petr Jelinek wrote:

Attached new version with your updates and rebased on top of the

current

HEAD (the partitioning patch produced quite a few conflicts).

I have attached a few more "fixup" patches, mostly with some

editing of

documentation and comments and some compiler warnings.

In 0006 in the protocol documentation I have left a "XXX ???" where I
didn't understand what it was trying to say.

Ah so you didn't understand the

+ Identifies the following TupleData submessage as

a key.

+                This field is optional and is only present if
+                the update changed the REPLICA IDENTITY index. XXX???

So what happens here is that the update message can contain one or two
out of 3 possible tuple submessages. It always contains 'N' message
which is the new data. Then it can optionally contain 'O' message with
old data if the table has REPLICA IDENTITY FULL (ie, not REPLICA
IDENTITY index like pkey, etc). Or it can include 'K' message that only
contains old data for the columns in the REPLICA IDENTITY index. But if
the REPLICA IDENTITY index didn't change (ie, old and new would be same
for those columns) we simply omit the 'K' message and let the downstream
take the key data from the 'N' message to save space.

Something we forgot to bake into pglogical that might be worth leaving
room for here: sending the whole old tuple, with some fields marked as key.

So you can use replica identity pkey or whatever and the downstream
knows which are the key fields. But can still transmit the whole old
tuple in case the downstream wants it for conflict resolution/logging/etc.

We don't have the logical decoding and wal output for this yet, nor a
way of requesting old tuple recording table by table. So all i'm
suggesting is leaving room in the protocol.

Not really sure I follow, which columns are keys is not part of the info
in the data message, it's part of relation message, so it's already
possible in the protocol. Also the current implementation is fully
capable of taking advantage of PK on downstream even with REPLICA
IDENTITY FULL.

--
Petr Jelinek 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

#144Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Andres Freund (#134)
5 attachment(s)
Re: Logical Replication WIP

Hi,

attached is version 13 of the patch.

I merged in changes from PeterE. And did following changes:
- fixed the ownership error messages for both provider and subscriber
- added ability to send invalidation message to invalidate whole
relcache and use it in publication code
- added the post creation/alter/drop hooks
- removed parts of docs that refer to initial sync (which does not exist
yet)
- added timeout handling/retry, etc to apply/launcher based on the GUCs
that exist for wal receiver (they could use renaming though)
- improved feedback behavior
- apply worker now uses owner of the subscription as connection user
- more tests
- check for max_replication_slots in launcher
- clarify the update 'K' sub-message description in protocol

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

Attachments:

0001-Add-PUBLICATION-catalogs-and-DDL-v13.patch.gzapplication/gzip; name=0001-Add-PUBLICATION-catalogs-and-DDL-v13.patch.gzDownload
���SX0001-Add-PUBLICATION-catalogs-and-DDL-v13.patch�=kW�H����������1	��g�Y\���s29>���5�%��f����n=Zj���Lv��$�������������-�i�������Ng�;8��]�?�q����iwvtph��S��s���X��5�/�k7���7������~�����x����������<<�:7B����[���a?E��z���j�8���&��E����`�nN�g?�V��=;�,v����g����+f��x������/��j���<����tq�:^���'�����O�����83���O����0!�'�h������	0v�)���9�F�	X���zX��V�>��~�������|���������5���	O���0?p����xk|��K
������1�f��g{%=Y|�@���#:*��#m3,��A����z,a�r5W�n&���~*���Mp�|9���h��NJ4+�-���A	O�]di�o�NB��������P����b}b���v�e�������L���y�GnF���������0�fa��g��[��"��
@4u@���ptP�*�?�~c���=+��5��(��F����[tV�pl�2�o����x�)��,����� ���P���n/��vqX�rE2�\������F~������L�R���KZ�hx~X�4�>
P�h6��������J��X����|zh������%�#�e��a��A����;Z��0���n��PC(,�'�k:��uz���]c�G�����00�����<'��nL��/�V��7���L#XT�6+B����+��.b
���������%�^d��!+��w$���;��bW�>�0�`��.��qFX�M$`b�ja:�0����� `�W	� �s�O� ��V�i��($&"�"=�a�I0io����W2Z��pO��B`��I��(?(�S`p�+�e6D��e�	�;���\�C�N���l�	�����}4��A7j�[LD������v7
�7�-��7���Z�M�����1W)�:�H@a�����R����f���������js;dF�"�V<�"��Z-���z�m��f'&�J�[0���~�j�nk,~@
�����R�
��;�b�%]�������cH�?0�J�j[����_����� �#3t�%?Q�{��<:n &��s}e�6�|=���2�������$�N�(PG0hM`,�X[����m8
`�t�[I���v��KA�1E�NXx��AF��zu���b��[��jma��ZUk��wfW���8�C�o�I����1M"0�K�����;b�+�^�p9��}B�Hp���?����3�sCH+>��(�.l�.�����S#�uA��Y�	sZ9����yN�t�u�d�{�
�s�W�]�#����uR1��i���AmJ
��NP�����U�d�c�br��u_E������u��V�q���c��5}+kV2V�������v�W�CH����d�(��3pN 3�= dA?��S���K�x�B��'2oF��h����s����rl���SaP�(9,���3�u�5A���G�Ex��LB�>M=�y��/`~��av��6����5(����1A^�)o��ec���p����;�`^���,
#_E���e���*��e {rq5��q`{��h�p������9a��f��lL����V ����'d��7����7���$v�._��{�{�2
J��$���$��Fq�&�~Dl�vX��~�8�+��y B$�~0g��'b9Q���Rv��{_�~�x?3D,�������#�6T\�|Ff�Q,��6��~���A��:�L������0(��Oh?Y�����V�����r���jTq��tMY�:<���V��9l�����T��2UlD���z��.��Z�E���Y�����|�(����h�>p����[��/�����Fu�k������F�����l[�x�)	��<�C�1�6��y��.���`F%��*�����{9�L<]�U�����
�
���2��,*��4�`=�Q��m�a�IE,���e)�T�*lN�	p��EE�9�29K�����U�mI�����pQ�V���"�\Q����V���������Zh��\\
	�U�s���`�\V��dLp�T��UI[�3Y�����
(�t��:�U(U����v;��hW�\~Gj�]��n�)~�u>����!����on�T8m?����&(�>n�{�%W�m�m�`�������\�*^����o��j/��5��C����x0�l)������.9��X��B����d4��)Y�P�4	Y"d��R����F����Fz�[����'����%2����%9���%I�P��o�����Gq�U������e`�;����4���j|��	I������
�@�<�/�{� l�r��=#O�j��tx�R������f���\�d;��ULo��>����=�%[-|HO��>
�i������H_q�#*��]]��D�OICQ�Q�[�����4��������9�~9XKq�D�A�����3��%r���7OE�!��sI�:�|�YO��(��R�Q�/3��x���m�
E�bY�`"-�=�Rk@x��\,���������#Xq�g���>�i��6Y�:�Q��,I��^������&BS�s�	�m������E�7C����\�|X�(�~v,X'|b,�L��:9V�(.(��T�z��,
<����;;\�H`X�)��W�����e]�NR�D��4��$��R�TqTXZ�L)Je|�'�J�N&�k�k|'��VD�aYLk��r��s���c�f��$l����x92�R�4�������Y@����1���H�3�cr��Y���L�%�&x@�<������+��@�Gl��U�,��f�.�S���
2V'��n�|���Fn���NuK��Nv�*L��m������(�1>��j������`�Tz�=�]Z�����I�L5���
0`%�C1�4�aB�����y�eT�z�������J3G��$o)6`#�2�h`���'i�gi����0_����`!wQ/0��=<q��AiC`��ed��\�/1R(�)wSx�*�����r�nH��>;�4.$��&�)CYK�f��:t6���2H�4�M���>��yV\�Ms���@�i�<���D<u/Gf`��G����m��#v����N6�� [���b����������u]y�����nP��w�E�p��iw�EYG�
����P�axL�Bc��ji+E
��i�:\�T���@m���$���V&�4h����Px�w����"E��u�)�p������@;b���*�!�~�C�&��u��r�M�'�S��A>u�����f��0}�-jxA)D���"�j�j*�f��4Cf���*#	C�x`�N���v�4���`�I7���G���,��d����2_�v�����$9U9�l������7#Y>@��U@�����/?��0>nTo
�CP�������2�� �l�%[3���������a�~���!����g��|��4�/
I`f�,C7��)u/���\���\����.b�������KTB��@�6�>D �Q�)��FO!��m�H~TO&�G�f����0s16���*g�\/��!�;nR-����XVB��E�(vC8�\�����G
���R�,r��xN��,��H��B��-��r=��S*��B�&�(q�@Fr����:�W���n��8�z��P�4b#x�[���:�k�m����G�����������z���l�z�L/���f����+��$Vj�����i!C��`d��(��`s�,��������-*�9I�*p>�I��� ���>.��#]�4��Y2W�BY��V�&^(�k5p.mbvr���;�`��.��n���K�q����P10����g[�C�yw��%��9W��7����+(#������
�6F�d�}��Z�b �*7
3�([9���_B���F ���x�h����h�1d���=�X8F���+�|U��r���9����A��m%����Wgj��D������������F|��P'�.��\�z`���|%Q-j�o���������:���j���RAh?&���	52����*����kB'�����_��_YM����w�}[%}���4uv��56)�[���������&S����j�z��T�q��Fu����b�J=m�x�~�7U��������!O��0��#�u}�=Yi�T�Wb�����"J��U\,��'�R2j/��)h>�1�����QR�'�`K�������9HK�8��0�|	�^���vn�s����O�����j<��@������a�U�w���oV�@��������lK�IoR��v���k����������:������)��j
W����w,w�����v�.���x������ycCg!�������c��$��h�������l����N�\)�L9�<�H����b	'!d�Fh��V�NV��Gl��3cO-��U���I~���t�l�V�g��RB�5n-������0��2U��'����.3�
c	�������
KIJG��C���7C'm���}��f3��V�w���Yx�Y����`u�X]:�FC��x�����8��}"N��$a~�?�m�'���H���^
��l����R�9;�R�D�1{�QY8��4����@Tn(�v���n/Ch�����<���VH���4����\�$)JX���i�c��m��0�G�z�k4���V"zX�D�o�����py��O#�*~�E=.���p=���
>���_&�	������{�=�k2E��M_`�1����M����'�"�9�Y\����/fP�7}�v���{����h�!A�lw�}���c�s.(7�c�u��b��f�f�_�F��WkJg.'����,:|���P� ��@�%���[���Y:~�B��h
�|���7��[�h5�O?�������M�"�
hP��t�~�Q�������`4�&��v��;�+���G���R��l��{5�>�`�������� �����=sL��t�/A��D�#���'�v�k�Hc���`���(R==�J��,AB8�t�
p#;��{Vx������["���zZ��n����@o"���u��^�
��:2:��^o��f(i�*����P)��LQ�%+��$�-(>{.��^H����2��{{�������oW���+`�4����y���x��K����b��5�����sc��9\�0�|��_��I\{
�lV
���a��{��?^��QC��2	���w��&�h1�-���sm[H���!��������k<�������A��a
;���=��8�����/����)i�xT$a�_�����#H������3h���3�pzO�rTX��5�����N�a{4�0��;�_���3������,��&g�
�{t��;�[;���a�.�xxn��rGm�
���$C��n8��C�E��<?���C�����oz��g�����W��7W��	8���xW6d��}g�p�
Hc��p��l����]1J�T���09Ul����x4�|6���%�R���F�c�f��d�����x3�6���Lv���@P?�wFJo�H
��#���Afy���l�n�F�2���`
��V������%����D��b�X&��7������p���l�x}����yP�vgG����*H������k��k����/�.Y����������,}N�`{kO�4��6h$�[Y_>��@��7�4k�mRw�M|�Q����P��h�\k�t-����"�@��r����z4���-�`D�<��:r��^G��zW��Z����X<x~��<� f�P������&����t4�;�z���������d<�����P@�tLi������0��<�}��� Y(-�d8�T�c�Ia���������[jN.�8���dl������t.����d81�TdC���=n�
��1�7�jH�$�9��_X;�������i\��������Nbda,@�=�}��m=���<�<�X���;�������}�A�<��;���>k��S��N-O_&r.m�z=�����pp��7<�V��P�
@�z�4�n�����`�R������sz3����(JX�`O��o���0�'�E�l�	�8��S��L�A�dyM
m�!��.p!lhP74
m�������,|8y��,���{[����w����g��O�q��������'���KO�e��_�D���b@�����@��w��{�#`k���LT(�? ������.�h�d��? ��BAM�J�GTR�.��=:k�6����
f	yN����g4h�����P�"�����*}�+��2���N�9�=90s�Fuo�S����u�3`�YD��#���w�|m	����U�Qx:�!/��bC�9�=4��4R��t=��>�{@�,��$�|G (�Y��;��.a0TB�Q(^�'9$�n	����`$B�J_tQ	��i9Bi9"�u��-�}���Q����A�����q�g'�2Pc�������Q4P.myb
*%�:h�A��M;#<�)���S����R1;���G�g�	�!�`���Y��l}�l=��;�l��qx��?m^�9?�C�.v84����N0$��%��k�_������%p0�$�[
�%�`�#���;�]r_���)-�������\���E`G�N���Q���,�l�e�qF���68���L#��O�.G�����gn�4]�*���1��C�����~��2�9#mZ#���������\�����~r=�3(`<�>�iT�lC=S��i�]��� y�����O��8�8����pzP�V����{3�zw�����+��|�}{��.��is�jh��0�2n/�����qx����t7�5u����h`
����Q���������$y���� �VzO����k������!�1��HTdN�����B��8��h�6�����M�"�*/�q�d����@�x�yo��?Ml�%��d�D�.PddD�P������U�s���.���$��s��r$�9�'���(�����d���o��o�o�^~��a�z�PP��s�D
�No���'I�mf�������0``>�=K#���M��9;��/���1����4�n]:�p����3�����I�6ZLc*���+��:�;�(|����o�E/��Fh��I�3��t�����X��}A\�i�B/O��H�<ha��������V0��/�&X�-���k&�������O�_@�o�4(���y��u��],O��)��.T�+-��c�3������2��[�B)���V��������-�2j[�;,fJx����WWg���6Z��F��X������6���g�����F;��
�� �������3s��x���{
V����q<����h;G�}U7��������PqA]���O�er�\��{SWp�e����~�����9����]i�I��>����j7�#���4V�w�}��J��7���;g��������h���
�G���e���,�gc�gf
�7���B�"\�(���'�M�i��U�����r}���[.K5�G�a����d� 8�����p�\V�%/��BA�g���w��I�[��B&�X&Ho!~�W����Mg����_�~�-`
��e�
r�3��@���
�9G�2�i���r��~�d������(tu�����{9��l��
'\W�����d�[}�-R�X
�L���Y'�L;`��q�P�?��� F���)F��VA��2���=����l�>Y�g$����$�R���g"�M����/�vo��?���>0�d���u����P�Fr{W�3�����]g��)����cl�g����o����!s.o�A��j�/�dwLz����,j��+i�DB�q'(&UK������`j����7h�id~��fv^����`�I�R���,�\��R���;�8����{�no����rW�r���!��>�j�H��
Ohh�<����s4]��9_Z&�����f��Qm��[�,������GG�;��S�b�^c0#���������<�����3�Ucf����..&��t��@�~wp;�d�3���E���t��� ���`u�bc��k����3�Qh�\d���Mf������f�Rl�R����lSXEd�\kryp����q1Of�RA	�pT }���&�J����Y�����[��b���;�u����b4��&>,�F`�`�px{=KF�p����%c�r�*H���F����V�pPi}�%���n9^��@��|Tg�t�>���6��I2�����m�
�^S���V�9#�� 7�j�gO�S�t�sZ�[:@d(J������[T�}�e�V�����&��g�6N��::F~_53��J���J	�fX�<]����#����9�KT^���z^�����]�T�a��t�� ;�����=�O��3�?
���Wj�����������G�����+;����<��A�3=�8��5N>)y�f�&&���e|����&�	`�5�Q�bUP�`��)�S��@8'��` �4��-��w\�!J81����>�{��n�������Z�n/y���bn�
&�5{	��e�������41
��1K=��nJ#�p�Y��YO����6���[�~���zjH��Y�}���gz�����;�jN�S�^E7�~,=_4[���e��������3`��)������@�;&��+;�d�T�������2z������XI�:s�?���-���w��{T�o�${jA�nuG�����Q*+��������I�^[��w�?�u��4 ���Uq��5#�Td�4��Q���3[�T�/t����,i���t^��K��PAEG(��1t��.�(����[��&�H
L^k�
�h>���
�]!�*��iv����<b^:����i�!�%V��H�g����d��g�l+�bD.`U�[� ��OY.�dh9x�$U<^LF�,��m|�10c��N(2�*a��A����F��M�~�����+-A��1��7M���5}��ll��+�IGWb����C���E�J��N���~�\�>G�Q���nnY���t6��H����-�7L2��%���h�����P��xR�6u9�a�z�P�B B6��&�T�4���f9h�b���t��X�S��Fz�!�3)�Ko
~7
>��ljA�7�6����f��C�q�LFb�i���j:��M��q�������'*�}b�4B�
h�BN�f�2B���6���d�2�������a����|R�B������E��N�:����e��;n����	jP<��-X�4X�rBuB�8����4��(�j ��e�h-�6]�����R���X����:�=(��b��A�o�e�6-�e���
������	�2F;����T*w�
�Cf:�9��]#$�X��N���l4�h�(�7s��c�7
����Bx�0*���2E��P@��I"���M}�9���n#-~������W����������cOC��l������rS'�93U����u��F��Q�n.�7Jo��x�cM.R�m��� �s��SG�z��L��%�����6������~as��x�p�m���0���Z
?����w)�!��]6\�����,�g)�-�p<�K_�;�fYr�K��.�h����`��w�����5��Jy"���s�.�h���YxX2/��Q����r��6;�&���
�_^H��K�����J)tghO��$�u^��,�6��SVFv(�����*;Z��9�����	A�&�y>�l���be*<cT����r�zwK6hhK�%����9?��� � ��wr?��,���!�;���/��_����-���z;�Z�	a����;�x_����������7�C��.�d��Q�j���6Z�XJC�U�	gm�HC��l��X�I���QvhE�p����*�8(b�W�o���Oz���������FK��!�e,����i��;"�����-��H��FOR�������u2d$��B��iUYe�b���]��+���
���l]JH�k�����R��+����/�H����ce���]�i������Q�M�5J:�tz��`@�#e��a���g3�`�����J�����c����)7������8�����l�"�q��UXa[r��!�/�b�*d9k*2Q�5����������3��%���2fq�B������������f8(k��F�0���T<^~��b�m���E^�0w�0ze�|g��]�
�\�����S�K��,��������z�8�La%�C���Q�,�����<?�MG�A���P��+p\�_��k�8�x�#9��p���S1?py	l�0�	�xih-j�Dw�|��@)����e���$$��u����*�	m�	���.�_D�7��'���E�Ok^~�������,�������C��v}����G�eq��)�"���L�Q26��G������GaC�U���9Z�W�I�N��d��'h���\xxk
0O:��
��[t��l~���(m1RX�[	T`�Y|v������q����|1�.�����!^�G}��$�N�\	�Ue�U���yu�j_�U2�i|�����'�A���	�����_�B���%i��/aS��[q����������"}?��	��t���v��3Y�������i���$3z��z�`�����jJ�vY�;Lg�j���X����������8�0X�UN��5���
��|����nCf� ��������O_R��!�,����pu-z�:��5�P���Eg�.�-B�v�����z�����^:(f*!sL��Nq���DA��XU���3k��Zg�3�
�r-,xXM�����)sDKm�����n�O+�/��u����o��{��^�R��Tk�����2���g���/�+�Gu��$a�����(��;O%�b��������f�x�5��������&��P_��0�G?z�:?�c�������vIq�9���xrQQ���VGU��U&�T�Y�W��K�j�`���O�
`pQ������&�?��gN��3���*�#�q�p�[~���jBV��%~;VJ���N��s��+��-�-2`C�x��iA@���O^F�WAe�2Y��������v�_��u���z�fz�l��^)�a�v��d�[
��D:����IO�s����RW�w���W�\����`��zj\J��y{��.�0�f�������z�qw4�o*�pI�i�(��
�O~�b����w*�����Rs@��=<]���I�RCw,%S�"�3�X�'��`��6Z3�"'�N�_YP������bU&�e:H�/W���
e�K�w����y��q��w�Re��2�
(�.X�m)�� x��7��{�����^����~Gx�W�8_Rj�CX��n$�t1�e��y�"����s8������&�����6Ze���'?����<��gV:Q�2�s_�L=��GV�����i�<� Z[���%	k��m~���>�1�(��^A�U&8���g��_�(���s��g2����\�=�s��.���U�A�
�������u?������/Qo�=n�Z/v@.t���sj������	P���T\���^�
��������X����{�
C8�s���64s������8��8
��Z�a�[����P�H�i�����,�-��]=����9���3�@yx���Bz�����e��4��������Q�X.�l�WE�!����i���XL�r�kE�Qkx���[h���R���#��<�MF��c�z)	�����
��Dn��&�C�%�r��qI\)�������,{*�2E��q��B��.�X�v{��
���
�7;�P)]�#c�
2ox�Vs>*�P��m59�`�\�HVQ�p Xb���_V%Zu3k��[�<���{�!j
I�MmK�t2��D���p�zT'�dt�:����E�T��A.!5��	�7Z��.�w��q���Y�d2�����,����d��E��8���"��v�������hRw'�%~P���H���>��pRP��J����C�#(�m�`��?L�LL4�@�&��~(yKJ�$x0d��
���Z���<��R����� ����Z�s����>��b�2O,n1��F���S��M7��<��rjmm��^s���#[j��l���y=����>o��-������>�)J�J9#��$�L&du�CE�V������
;*Zl��L���
�]������bym�c����H��=��6��V���5._5�M-mDd���Ef4�������,�e1�a��c�J�
���%p����[9�R�uK�����z�����K\���=>i�6!4J�8�T�fB���,��|}C���}u��#������>����������oc
�0^��/��"������Z�;��}�q�f��Wr���BU�+1�����8I3�5�6�����yh_����EP��[8���8�����+A�%�%p\nB�bH�,�����nrIw\k����X����/��A�~W~��>�������u�`��4z7JG�������b2��4��7��>�b�H���3V�2)����i����C����6tj���+��H*^�l�DM<���,�.&����'���1��f�/|�$o�15�-}��u�8\
�:B����h}5���|������k��G�>fkWbO�����"�tE6�����A������s��
M�*������
��@��1��B!��zny�i����P[z��^�K-�~���+V�k��\zd.���e�\}��
s������su�������`4���C��r��=�L�>��xV�����������+����Z7���f��$��cz��yX��^��`0�����H�������v�i�{�9l�p��;���m4������,��.���ph���-����������NM��nr,�`�n4Xt�*��pw�	�UKF
�!w�>.�
#*B�7���,7�f���s|�+����t�''�Yo4�ug��]fw����9E`Y���g�xt;��8d��'�a�mD���
�mu���^:IgT�MH��6�L���&`']��+z;Wy�Z�Y8Ms��<lY�m����F�����"�9�zJgh��d#R����8�"�/4Iamn#T�n�6y�����g����\�p������B�� wX}k!@��7���|����`��������dS�P��dNx9��~��k���SL�u.Z�V������e���D��
:��H�3��hx�=�B�fsC�b��Y2��C�S�Z���{Am%9-+(6�7��/�����I���8:�(���@�XB���eJA�G����#;�����v�����1?�pS�����4�i���8���9������B�*W����/Gzr�9!��R�~j&�����4x��y��fp��uN}?$B�z�M��9}���|�ps�S��eQ�3�ONj>�tB��&��� ��Z����F�;��^Fy�U��K���<Q�����
x\g$s�j��YwK�v|n��X�^�	��v���d�|9x!����6�������/���)+�IV�1zO�Y���7�����,�����Na_#�h19
�����^SO�?iM9$��t����_B%G��01�d�gu��������b��Z6�����J�����'�za�:��r�����w�*���u� �t�`���g��{�-D�����+���s�X��l-O+����aV�zK�m!�^�/�v����z%���?��9�\�N�����E��5����~H�:���$;�O���U�&%��Xr����d���
.���@��.��,�,�L�z��,��b�����8���81K>Nx9��/8������T���ba�
8���f�p,��5@l�U!�)��JO�*:f_��@�rZ��(<Mr�E����~������X)29�%"`��!�l����tLi����#�cP������i:���I��u�H%P�J��Q���NK$%%���\_6�hf�%����)��xr��\i������>X����Ib'���$�K�PW�md���w�ey	{/���Qh�^���q�	fZ���w&M�f���`�ui�e����g���-�����=
��
��1����A.����_����Y^�T�:�9N��d�6E�*`R�W���/�<����T��-j6��x:�l3Y�&G��X���H��a	��]"Va���Dd���%��X������z���cj/�����oF������]9��7��#-0�9+��%3�EA3����#x� �H��Q��idz���y�e��YN-��FrB�6���C�zH
�Q�������Jn���>f�J-pO�������Ih���5l>$�z�>[a�X��lY��K0�zsO#�3'�!��.��*����	
���J
��>�|�t�W��}8(��o>������_\��Zw��9����?�ZPg��ng.��l�f�3���{�Y�9��s�3o��
v�@��%����xj���E�?�1v-�9xF��{I���n��y'1}�pT8�Bx=����^'��J���W/���)R�0WQ9�Qk�� Y��/@���|�r/�	E@�swIs��/����{����q<�)���@q���N�B�f��?6�U��`-����P���tYY�V��
�N���C�x�MZE�rCcc���
�r9�::��gG(&�6��Z�eP���>l5�(����M����V	^������+�a��������� ��]�����8�~�R���,6[^�p�&;,Q5�{s801Q������\t"p(Zo/6Z`�?I1dUJw
	wcR�����9�iV�B(���>S��|��e^
�����+�G�*��}�+W�lS9�FQtR�@{qC���9:e�t��p�n����s�%��,���pX������� l��@0�c���c<���,sK�+������yeuj,�6�DQ;��b������{��0��>�
Ur7���_N1���w5d������v�r0������o�P����w��(���&�
D-�$(G����j|�(OC7�)��qD� �e�
�#emms�����v��f�w'�o���RE<�E}-��'����5G"��g������<��e�;I����h�F�����]�Z\�X�z*P +������Gc��]+M��`��To�����WG���or�<i^6-
]�O�:R��/"�!7��%(�E��;���f���I3�����N��5.�ag�Sb\j�-9-.�6u8����WW'' �����1\q�	E�N�u���Z���%����bS7IB��|6��L5���F���n����!�x��F�[+��j}����������"��u�"������h�A������l��M\Z�L�xB�����M���`T�0JR
4N.�-6^R��1���-���C���-��,$=����8uD���>
�J>�\����!�$%���f5��|����;���D�p,�`o���x�0�1�l	�/���@����#�G�oY�t��Ar�}+!H�j5
B����Qi�D���hSvyd��� r���@{~������F�E ?��?z������^0����a�Aa������!���].s�������C]��Q�\1F{�Zm�R��:���|�(�C�-��!����l~��I�`����$A��(z����%Z�I�x�|�:�s��m��R,�%�J�@N
�"[�:���#��� 4�����W���#����*=C2�e��#Tf9�q����FK
��Ct��ta��@.T(�����[���j�J�5j\�����C�IGF�F�<����b-%���G�}������rgko�\�����XM#K���FdJu��e�h��b:���.��,��UJ��C�6uw��C�M�N��Y��uB�P�Al�vl����u:�9�w	��1E]��+����~u�`�R������2��t�G��J��Z#e}��D����}�L����,H&���nYd?!D���C�~��uda��"���p�7��W�%0�b�|H��g1m�)�
�{��}iRT�O�p�\�����"C���XQ^���w��C;"�&u��mR���9��8�T�?{���2����z���9����j>�f{^FX�/���W�����z����y��)zZ�{��Z�W��{[[���m�%�n9Q��*Q������ 2D-�L��������aJ�5����
DUZ���\c������I< n��4]O���K���hD��;�S-9N�s�w<���~����hs-�s!�c����x��\K��;��C�$i$P��=����
���K�3XU��Y���Y��t�_[sbBE�������d �qF��|F��=5�y$���[���\�c����Hlr�|�x�y�Y�F��%e�.n@8�20n����?�T��t�����<�'j���G��#��#�	�_se�2-���?��8�z��;�;��=��e�y|����]����+�Uf�x���U�/z���lG�Hc��S!c�(�i(7+���E����*"Ed}�5)�.�.#���������n���89i�D���lE�Ci�DZ���m8N�����Qtq�8�'��v������4h5�GT�����m���>����i�����k����h�u�:�j�t�+6<�j����>�#�2�^�����&|CH� v�o��������[\]Eh����=��O��i�r�2K�YU��������<��8{,�{��(�~=����y~>�����������"�#��������C�����Z�����V������N�Fb�D�O��8��*������M���=Q-�����@5��GN[�P���V-G����7�������	f����tg��6M�Pl����o��FC��$�4
�
��V/G�_U�LHi	��A��TX�/����mJ����8����t&�5��4��F�����y��@	��
i�"jY�l�9+�����p����x���b��E�5�D$^���V��9�~X��W����k.��|���l�� c���J
hC�'�����iW����F�F�e�~�c���23N����������rz�4l������S ���PkU��������k �8F��SnP�"���������M�	�Y&MUb����; �2�
4N$�D�=�_����FV�+c�� ���0�<��4�����GW#t��!P�����?�o?-w�������Iy�>�%{)��fZ��`G�S�m�i�W��S�3�t�n=Vp��;�1^)�m�Npi�Fhe�+onu�6�%�:�<���I�"z�o�/����WJE��"�~]�����������^gZ����|��<����N/.�I������Y4���5�� �3�� ��nf-�l\@5E�9����xVB nd����X��3m	lc��?����~��H@���?�cU��e-��#�b����Gt&_k�3Gc-��eZ�~�#(I�FD�4w];+n�O�6���m��"��\����k�.����������^����b���7W���1O��k��g �F��8V�#����G��{��q��'�?�?�2�J�5����i~���cf�\t���8{��7G���y?�>�\h��_�������p�R����A����k��W{�l�,;�_����d4ils�IUn��bIx�z��P7VZ��*Wnn�$���Z�*7�������z��~�h�);���Y�79���bkg��yn�8�E�I�6������$�Cr��N��������������r���}���.����D���^t��+��������f�����;;����� N�+���8y_��[�9;>���2.��A%b�o�s u�)i�K%�9C3�Z��&���/�2��1~����nZ�n���sC����O(0�������P�4Z����(���N=I��"�R�������=�<m�}�_�.�� ���uY���4��7�0�T�IU�>?�o�����[�d,�9����q���y���5��:�P�l7N��o������\s2��������(���������S��R�V���;g6_�0�3l6���i�����,��x�q��w�������m���$��g�Ymg�_|��Z�n�����(2Bof���$��/wQ7W�-w���/s,\��j���.���bL�Z�]R��n�^����$�'��8�q�1<N�_�/O/;GG'�@'���>""|�k�)["������5�B���y2��hI��3f�����o�w�����nu��S`L�#���j������UX_��%*8�<:*����)��g��z0��h7�Y�-^(�� �6�M����pR�A8 ��	��)����t�tB��w<`O�]#����#���q�|���Q>$�����MzQ��#���uw'�>!M���CY+ ��bE�6��C��\��Dk���)������>���4��AVK*#��h���N�"mbJ�g��5�J��7�F�E�4�F)\E��/S��ic���/]��,���b�Y(�����Yo��^�B�m����:!.�����#�&������PRg6����H� �a���`�#������W�q"���=����^�v���2��'rE#Y��-��(5--��e�I��(��o/Q��������x��v�`���3�!.b��1F��]��p�O����f��*�L v�������^M����dw�]��:O�h*|A	\!����8�xa�������l���mzkF��nK�����k���	Ki0�z���k� ������P���!�q���lWk�=���	��89*���27�;�[���P:�X.�F�M�a����
�Jy�zw@F�_������E�����G]
{E}���(�,0V�	h��,U���F��7�7U��B�d��lc��S����>��J/ytcx��V�N��6�R(��i�b�H���jL�&6E|����"������J��;�mU��s�)�f��D8��v'�Dm�:'S@���uG�K���*�
��G9U�J3Vc�WHo�TP���r'��^������R��(N���N���v���G(�`� N�HLz��Eu�'�V�IS��6�[R��*h�cSf�8��N���8D�7
676����~�_6X�yp@^
��S���������<]�D�B��4�Mfw��d��������c��E�={�	��n]�'��������I����?���h��G��N��#��+9�tf�����q^����;aA�{<Z�`^��VI�=e���Q�PD�W�o����),G9�Lx��c�M}��I7�&�M)���0�q<���N��t@Fe�d
;���T
�&(lUb�AM��$�tL$7�G&�Z6�pQ���"<��X� ym��I��t�&��~�R��z�"P_<���&��__����:�h���51GA���b��&��HG����DT��I��Ea�A��l�����P^8�����m$2�2��*�Q�`�2_yde�=���sN������"j�6FF^�SA�2L�i��"�Z���T.��kB��^d_��������O���\m+�m����/�"�����w��K_`S�����m�b������q���$,���A��D|.&�M1��^`�P8�P�(V���X�8'v[(�p����cnw<pZL�����%�
�>-���zj+���]���7���������^�R���m�
"�tS��[Y��������C����3�K+!~H�E�B�%"���q��;��-�U�=�1dU�Y�o�����IU�����]���F�Yd�
���s�<�&w���F�PV�_6qQ�6�D���dt����������T�>3�t%��a����d8���9�s��j�dg�?�v�?j~����O�S{~�% �������%��G[���ob�Y���I&��>BI�D�����M=K0G�b�����.&V��@�R|������Je{_B6��I�h��'~���u<g;����p����[����+BH����F,b��u���4H�>R�?$	�[������1Gd��?&�$4�h�����`��~a�U���"w�6��Hh���Y|&���:r�fI�������cH�����;E�@R��&(�4&Q���3�u�LE=�>�v��n}{�Z+F;�:�w��uJ|�^?`��1q��q�K]��E�kagX�GR|��=�!H�f��W�R��8���C��k�����X����mf[4~�H�Y�V�����/ H��������~oB���~����^{>`�����n�[�����Ol���>`m��7�_�_-0�fl���=m����&!����0U������>9p�Q��f*t�G�����2�����YgRP�5s�	E������/��f��4���z����zh��C��^�Nk2_LSC��6���b4`����N�_&
#A�0�2j�/�[��/�������d*!+;:f�M�z�y�<���hX+���h��/}�r	E����H��T�*���Y~0|���]�^��O�*E2��*�u��<�����U�4���)��+T��K��u���\^]��������:��fB�i�;i�Z��/9��o���t;x�����L�V8�[��RB�'o�7}�0ScZ(��2zk��|���a!T��c��
�|��$%��,v���X=��w���&��
��2���������,(��e���B�Pt��e/���)���un��,�=�R�q�?�~�$;�=~�;����0���b���@w��
���k
�����F�?�"�=�#GQ7��-9�3�xP���/<�����ubj��t�0�������D�Y	:���O�$�������<�����z����TX�R��vI�
�W�Rt�^�J��A�+ehC����]�uv|���Ifbs	���UwF���6������!����SLx����`g���Ha��)���.nJ���S����c�\��p��\�O�i"�����e!��?S��W��<t*�p!�C.��G�2�a9kd}��p�tx4H���Z�����	�RT��{��.����3�b�x5��T>o�N��,���w{l!
K��m�������%����
���&b��.����8��=�R��d����X�R+N��R�d���s���.��ll6)���"�����*�tKU	Q��w�B���$^;
����H����e@�P�F����7�rZ��rDPZ�U�pH���j� ��%���ZP��gzJdYTH?��z>���hQ7����������
a4��j����FO����Bs���1��4�����������yb-��\�<~;�Pz?\8s#^Z&sKOYM�����|OV������Z�3��8��t�����4�������N/:����y��?Q�����z��*Z��IG�� ����s����[���*��PO����9��$U�|��KT�cf+1f�C+�k�f:(�-(�u�>����7�V� w0���?_<v�k���E�J71����3�H%QY���r�P�Po�]��/���NN��F���Ui��C�,f4�4�4�����++<D���C �][V���	��F'�3K����������7	*��?�y]�~v�Y��c�@����B�e��W��U-�}0UT�*������(i�|�T)�H f�Z.c��dUa3x��^�W�����k���Z����eRv����s�e�7�P��U����"0��3�I1�����p���{h=��M�Y���q����C�1��k��EHO��a�^W�;�2,2���2�,(��<�H5������#w��H5jV�m����4d�/��c_�=��U����bz=&C�RA$�)�U?��*�z��.R��g�c���ql���4��K�|$O�G
�R��@���-�4��=o��Hn]�/ 7"��W{��b'a_����`���e��Q���`vM�Bc�@h5	�:_�_�5Z?<wA$X�#���,��1�����|N�����ieuw�Z�K��`�U������fe��_-Z�T������VSk~���e����f��S"F�����j�7�
/���h�������"�v�>�n1}u?�@�C�(#�_W��������� �+
(�,6�[y��f�0^����;n
�/n���~��=�+����o�W3��Yb�q#������H��bXJx� f���YWy�j������Q~�tM�&���{Y~���>��|}����/���q�p�Q
�I���5�������9��8���P~����f��\��a ����&08Cn^X��pft��^y1�)E!;r���t���z����~I��9��������0�O�}�~d��#m[���k����i+B��z�H��@"cB��mfPC�h�>���0�@��h�����l�U.@�(�(<&��0t�QxH�����E���x�=��Of!��5����&�����;����X_a�	��L9@Su��b���#����#X���.�u�S	��=;�i�n}�*3�Y��0��'�J��x��e��Pa����J}<T�-E�+�G�D��u�qo��Y:�I����FP*�Yrp���]��dow�wV:KT���D�J�N�*�*�:���g���l��F�;4A�S��[Ha������s���s�#���V�N'��P����������?����%p�����X|G^X	�X9��>4[��������������Q/�8k�9#V�1����~HG�X��*E�"���|�a"jA��)�[�'9w�(�YK'"/�<dZ�t$��D6W���������P�|JK9��I�Uj5�b�Ho�N��N��mm�:����;����E����j�J�`{xP9�u��j�l��'�'	��w��s�p)l6�A�`��ql
�JA=��/�*Cj�A&B�
�H�>�t��1�N���������|����&)IY���#�rx����<Ns�y�x\f��u��+�2{��q��MN��w/���o��y��?��+���F����%�\��5��yp�5:�a'�u���A@�����ni~��Wr�� f���W0[��$���>;�����f�w3�l7�[c~f�QPCO���p~��fdjp��+��Fw�Ym|75��u��E������Xi1qq�x���
�B{O����8����`�A�,�����\��^���h��c��{6���l���n�1�DUG[��M��a��lC8r���]��!\R�3Y�gc�RQ����y<
l�g*Z�#�8�Q��[z�JwpP���)f�z\�[����U���~G�H�N�������c����
?�w��oN:��G�������;s��Z����Xb�����c�'�<},q�~~�Lao���U�3��w����+,��v�4� $���`�����	��E��7��Z
�x�XT���c�=~9��Y~�w�<�]I!�+�:���)QH��_���*�Au��[�S�zmXe�����Dn����-��ObR���h�:8�����8�-��6��@���	�;|G�+�;��z��m�%�$��WO_��������n��1Y�+D��i=��I��e������q�_��P�4�,|CL�W;h�>��[��"�ky�:�������;�����Y�;F�(�U����!�,Y/��:�'�cI���b�^�|Sw*�^*;x^;B��`�*I��zh�R�:�u{�?_�#�7�������NLwa�����|�F�[e=�����1��X9X�+��@��)�)yl�Y��p&Q����{����������J���o������_�r�0���G���b�L��F���U���b�`s���AkJZ���2w�z���S�� S�.p#�C�pf[���/�]7f���9qx����T�>c]zD�f�a�})����O� ��
���p8�A��B�C1�����$��|��|����e���X���5,���X����)@�;��J�x�A�>�U7��xC�m�
b����]�a��k1n����\3Aq����#�76l�;=_��&������X!�v�4�j��+G��%��;�����0d��2��vJ*�"NFEU�iHWq�EaV��3I�ii�����J)�_~�1�6��pEs-�pD3M�s]F�r�94#x��#������%���A��r�dT3H�v���y��-tLQT��������>�`J�!l�'�����}��L���/&��Gs���3�����\��)U5���H�&�_A�������N��8�\�o`g
���6�d��Vf
I�<��FI��R`x(�!�^�����d���#�)��$��Bt
y�1��{�c&(~���a�l��5s�����S�������u����y��l�S�M�IaW.��	�,��1{����/k�����=?fr� 38���l���gU�������6E��9�b����_��"\�����)P=�
�f;�Z/�3���t���A�	����*
�NdP����6L�a����NxC��
oP���$uP76�E�\G�`;��Cu����(G�8��tbX�?��f�2�d������B�7\"�����p;���w����dR��S1@@{����_�A����a���A���5&�~�[��������`�:��_F��TV�m�X������hE���+�^������\�H�����P���Ef�������+�JA��xU����}K��*���Y�6s��*G���E�2UkwJ%�+V������������Z���x*.G���&����u�F���q*r�d�PMWK
� ���ef(/VwT�a�D����D.�����&WK���k[�����J%���j��U��Sl�����j��h?8����R�s��Es���F��>��@{I�.�^X�H-�eo���DO�ve	�;��G��a���f�i$��&O��8�T������Je�?�
�<�&�]`����n�9��v�S����\Q���]�^K���q
�)�P,�`7MLh�,�cp�c{�������e�u�_c ��#�M$L����dJ�B��*G��.���e���x�����������)��:��g���x�i<#�
\�a
tS�n������+�o&0h�D�\���_��<������������|��������Y<wK�����~���[����|��������K#�c/c�;�F�
�u���N;�}#��u������;k)�]��|s|��sv�a3���_��CMPPG�2��m���j7[���:P���w�ag�v(c�,���~s�G�Xg"5�`�c��99�e�E�OU�+��U��'�eGu��8�f�x#tM��i��a��������r���`�Kb�d.l��23�8i"q�-�Ev�\��te�	�=gSq����pa&_����1�@���2v�����u�A�y�}��E��(��)Z��i�$���~p����q�
w����%4�xI�_s�������H�����>����W����6!��F��;.��H�?�;���a(�<Y�Fh��(�o���J�}%ZB�^[�~�m����sG%����7�qx��ME�
���/���v*o�?f��=�J(a�������dV�����L�B�������rHZo�#�"�(�6B���f�I=�LehwH�0U�S(����j�J���
���;1GQa5vhw�*�L�n��t�ez���I���4��l���q�Md�;�U�`�*:n��.���93�N}qx\���;!#��c�A�z��0�2z4��I�`O���8k��>����e>"O(H��b�(z����� ���5�����T�����������_{���q����5�f���hW�����}�RkhF�����j	�����Hb��N�G�=fR��R��>�����3S=ZP��&�.��x'�I���'�/�]���8LRKU����Z�����V�pW�����Y��iS�)'�u���*s��5������jW0)+�����0!��Z`J���	&?��}�i�v�����LK����0$����d����t��<^L��(vLw<�'/J���s������A���cL���L�}uu|r��z|�l�B�t��������;�!h��3����	��!���:����}����u���I�mQ�)����og���qb����$64���%��vT�8Ch� hbr{���}�^A3��/�C��<z��Y��}��G���v���2�����%e���S����I~pL��8��9M6~��������v��QA�
D$r�u�>�*2���V�T4q�ws�k!`h����!��*Bs7Z��4w��]��H����iC��A>`6-����V���$���o����@��z�`@���:�/�I������x����S	�*Ba����Z`���0��|��0���. g$��U:��_[�����
��]��$~���o�A���-��T�������������.�8� Hx��[�����@��O�O�e���a��a:#u�S��;�@(����d�{�0�����������Ok[��2�r�����$zM�(����dz���z�,�Kz�.G����j�W����)��![�:�QW��J�W���/��b�P��x�{;Bw[>#�����F)c*^�R<�;������|i��Rw�=7H��:�Y���0��p����kG�xc���P<@8G��H�������f��U��?�Fx$�M�	7�B�`rW4������F��(�5w6�@�89��x���
�O����9���������2���K������rR[�cC&�OhE�G2@��q�3�=����xH��-f��n�<5�EjS��R�k?�V�QX�k���C����0���X4�%��;�M��Caly��
����1k��l~V�T��q���ZqS+���<DzQ���|����t�L�\����0&��{k��:��,�]����<����y��I`��o���l�z�.�VJ���ayS2���;G���U&��JARF�X]Z�v��ZmiE��k��u!�����d�[[�YZ����=��C������4�i���4�����{����*IpR��A�D�r"Ne����rkm��=y�D�)�c�#e����WwH�{fe�J������N��������eo�)�j80���u�p�w����@�����c=C��[����Q��L���
�5��X(���3I��U�����|-�}������I��r��KN%`\^]�q\�F�u�{���&hiP�r���xw������o����d KP�����:OV�O �'Xw�����?Ah0�_!8��'���. VH���v�qB�#'����/(�((,�nX�hf_����AA���(P_�*�����$�����������+��	?[����X[l�����ra/gq����s��F���t�P�,�j�v�f����+�l��~��W]~��c�k��Ov�e�e�;�%��#���5�j}Mp�2�V>�?��a�V�B����Q�>9E���L�	�a�����.A&����[2G�I{~�x
��qBc�n���7m���;{����A�!\��b��d YHh/oS:e��m��=�+����V�HEn���n9E_��)��5�8�����G��e���RY9 ��VA�z�/�o)G�������L�"���r!4^��2����V�	<�Q��l�+���bi.v���S�
{�������,�"�N%�����%'}���`hW4@���4/;��V������G���'?X�Lpp��
�}J0�4��^9?���,v"�:����N����M+}�>L&���b�W�c���Y_�&�O���h��[P�(�[Lm�:�[���%���Rf|gVV�b���4�3Q1�1��S�V���r+�B���6"���G�M�>���:Fp��T�nc�����PM�+]�����
%��
m��+�a�@5I����!�@�LJ?
���qt����
D�$�^�(M�.�����G����#�;��`� B�gd�F��:�Z*�Z����_"�K�7���="n��z�Bhw����n\����������o���~
"O[�d�dP�����;o���w�m{|�|�l����V��l}�<R�7y-�~<�3�������V��-c�as�v�����u�qn������6^�0���5����*�����g�W�wX��8
�����|4N�u�Y��U���Awo��q{��^u/���Y��K��> ~������X�}
��C�qx������4�_�u�U[e1 ����'	�A�����F�����MS���
�Fk���;��(DN����W����r����/��:����%���zq����a��-������w��w��Aw�8�X�9=1?��~�|��3�
5���;:~�(Z6�M7/ ,�h�V*n���[��8�m�l#R"���(e����0�*���m������Z�mGUN�L���z6��a�x���:�,��K������Q��OJ�2G���$n������&�2�Ww���C��)}��{���<�������b[���[��������R�{�E.v���4�*U:��|Z�5�Q��W���Y�K���P~1M=����Gr��^��G,��F��.�m����R�>����8���b�@A���U�J
�P��+�;Fdt��w��|�.E�!�I�4���h�'�0��l���������-UD�n�����.�����u����sf��`?Ci.Ac�sL��]mFw��x�tyZ�����0��U��3`Rw��qv~	M���P�c���p�p���6�����_aG����B�eo�����������.Pd:(� �
�2�F,�wQc1
�c��G�$K��>���,��k�~��@9D�$X5J�p?c��iMs�.�9�[%�p@���3�Z	@p���
3��gi#�/��U���@=R;��?j	��wi��b��nl�U*��[��4{��0��B	$���A��}��1A�{8�g��Z�����2}�89N��xN��Y���Z#�_�&��@%R)�ON���I~q�h�}�������goH�HN���&
��?F�]A6���_������x������t��z���V��j#�f�;|��
|��?SN��'�H�uE���>>��W�������e�n�Q�W�o`�/k4���a����F�~J���J���)p��	lcv��$b
(���6S=a{*��Y��7j�7����co���/��� E���n�{�~�$|�x^�M�rE�z���C�(�
f����(�{4���o��TP��i=��)�"�����U��_����'^;����];:q��4�z�K
�5;�$^�����P��g����n1�+p�e	����\u[Q��V~��I��[9�ug��jG��Tm����%�C��x�gB#��	�P9#��n������`����8z����9]S�)�-4Xy�n:5+��j���GS��b�2�������@F�����	������#���������hs�����+Y`�����AXX���,03��(��j����(�:�=m�[�#�>��IBLUMM[���H� ���y��U��D��1�t����\#���}�Cg�
OM�p��9T1���0����L�G�4r�~��sx����v��eh:l�({7�
�����S����%������������}�
��]w\zl��3t�������YL*�
D�qw4�<������w"�~���_����)�q�F<f���6����p����)f����C�}��X�+%k[���
N����J����C~���[�{l���M��s�C��]�.[S�s�S6����=q��Y!�T�%����c������j)x|W���f���W	j,I��r��:C���Y~,@V8Bpd={I����
�i!k�pE�?�������%��l�,u�������3���r�*����?fV����F��M��y[+�W"[�M������Nfs��'�:y�A���l�j�E�-�?E_�!:#{��`��c�{� ��X��cV}@����,*B�%�Y����U?�1��m�R?�l5���u��YWb�&���x2��H���,�Fo�Nrk���9�9Z��'#G����Q���*�/�T��������-\�����)���F��q��������&�~��i�;��n������e������1`B�����K/bu-e\�n���J=V�{�����Yv!kj�M�vu��r���M�1]<��z�_��y���i�rF;�l)?��'Ub���)mD��]9���`R��g���G�K�����]�o��,(�niZ�������n[��2�n��I���7d�zI�4��j��4���'�JuV,�������SiV2�`��R/`�~�7�H27�>4d�m��@����/��%�s0����;(i�l<N�1���������r\���yG��������qWmc�\u��	F�#C�����,�}�=�t+���^���Yu�g�[��3
h����
?�������>����1��X��%}��m���G�&d2��g�E�!!�*^�g��1�,04�
�����=�Tv��{����5�v��lE�q@��= Hh�<�0�������{K�M�����	Z6��i<Fi�H[��� ��m�[�%'���b�X�:*c`X�4��7�w�d1��9}���:�6N�b�W��`
������j~�<�/*&��������V�f�?����{�Y3����k";�-�>�-�C[3)�����1}>�c��W���+Z���,�����j {����za�����W�y��z7�W�B�D�h��hYUf���/��
��G��
@�`J��j���(��*{��G��Z�6!!
0002-Add-SUBSCRIPTION-catalog-and-DDL-v13.patch.gzapplication/gzip; name=0002-Add-SUBSCRIPTION-catalog-and-DDL-v13.patch.gzDownload
0003-Define-logical-replication-protocol-and-output-plugi-v13.patch.gzapplication/gzip; name=0003-Define-logical-replication-protocol-and-output-plugi-v13.patch.gzDownload
0004-Add-logical-replication-workers-v13.patch.gzapplication/gzip; name=0004-Add-logical-replication-workers-v13.patch.gzDownload
0005-Add-separate-synchronous-commit-control-for-logical--v13.patch.gzapplication/gzip; name=0005-Add-separate-synchronous-commit-control-for-logical--v13.patch.gzDownload
#145Erik Rijkers
er@xs4all.nl
In reply to: Petr Jelinek (#144)
Re: Logical Replication WIP

On 2016-12-16 13:49, Petr Jelinek wrote:

version 13 of the patch.

0001-Add-PUBLICATION-catalogs-and-DDL-v13.patch.gz (~32 KB)
0002-Add-SUBSCRIPTION-catalog-and-DDL-v13.patch.gz (~28 KB)
0003-Define-logical-rep...utput-plugi-v13.patch.gz (~13 KB)
0004-Add-logical-replication-workers-v13.patch.gz (~44 KB)
0005-Add-separate-synch...or-logical--v13.patch.gz (~2 KB)

Hi,

You wrote on 2016-08-05: :

What's missing:
- sequences, I'd like to have them in 10.0 but I don't have good
way to implement it. PGLogical uses periodical syncing with some
buffer value but that's suboptimal. I would like to decode them
but that has proven to be complicated due to their sometimes
transactional sometimes nontransactional nature, so I probably
won't have time to do it within 10.0 by myself.

I ran into problems with sequences and I wonder if sequences-problems
are still expected, as the above seems to imply.

(short story: I tried to run pgbench across logical replication; and
therefore
added a sequence to pgbench_history to give it a replica identity, and
cannot get it to work reliably ).

thanks,

Eik Rijkers

--
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: Petr Jelinek (#144)
Re: Logical Replication WIP

On 12/16/2016 07:49 AM, Petr Jelinek wrote:

Hi,

attached is version 13 of the patch.

I merged in changes from PeterE. And did following changes:
- fixed the ownership error messages for both provider and subscriber
- added ability to send invalidation message to invalidate whole
relcache and use it in publication code
- added the post creation/alter/drop hooks
- removed parts of docs that refer to initial sync (which does not exist
yet)
- added timeout handling/retry, etc to apply/launcher based on the GUCs
that exist for wal receiver (they could use renaming though)
- improved feedback behavior
- apply worker now uses owner of the subscription as connection user
- more tests
- check for max_replication_slots in launcher
- clarify the update 'K' sub-message description in protocol

A few things I've noticed so far

If I shutdown the publisher I see the following in the log

2016-12-17 11:33:49.548 EST [1891] LOG: worker process: ?)G? (PID 1987)
exited with exit code 1

but then if I shutdown the subscriber postmaster and restart it switches to
2016-12-17 11:43:09.628 EST [2373] LOG: worker process: ???? (PID 2393)
exited with exit code 1

Not sure where the 'G' was coming from (other times I have seen an 'I'
here or other random characters)

I don't think we are cleaning up subscriptions on a drop database

If I do the following

1) Create a subscription in a new database
2) Stop the publisher
3) Drop the database on the subscriber

test=# create subscription mysuba connection 'host=localhost dbname=test
port=5440' publication mypub;
test=# \c b
b=# drop database test;
DROP DATABASE
b=# select * FROM pg_subscription ;
subdbid | subname | subowner | subenabled | subconninfo |
subslotname | subpublications
---------+---------+----------+------------+--------------------------------------+-------------+-----------------
16384 | mysuba | 10 | t | host=localhost dbname=test
port=5440 | mysuba | {mypub}

b=# select datname FROM pg_database where oid=16384;
datname
---------
(0 rows)

Also I don't think I can now drop mysuba
b=# drop subscription mysuba;
ERROR: subscription "mysuba" does not exist

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

#147Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Erik Rijkers (#145)
Re: Logical Replication WIP

On 17/12/16 13:37, Erik Rijkers wrote:

On 2016-12-16 13:49, Petr Jelinek wrote:

version 13 of the patch.

0001-Add-PUBLICATION-catalogs-and-DDL-v13.patch.gz (~32 KB)
0002-Add-SUBSCRIPTION-catalog-and-DDL-v13.patch.gz (~28 KB)
0003-Define-logical-rep...utput-plugi-v13.patch.gz (~13 KB)
0004-Add-logical-replication-workers-v13.patch.gz (~44 KB)
0005-Add-separate-synch...or-logical--v13.patch.gz (~2 KB)

Hi,

You wrote on 2016-08-05: :

What's missing:
- sequences, I'd like to have them in 10.0 but I don't have good
way to implement it. PGLogical uses periodical syncing with some
buffer value but that's suboptimal. I would like to decode them
but that has proven to be complicated due to their sometimes
transactional sometimes nontransactional nature, so I probably
won't have time to do it within 10.0 by myself.

I ran into problems with sequences and I wonder if sequences-problems
are still expected, as the above seems to imply.

(short story: I tried to run pgbench across logical replication; and
therefore
added a sequence to pgbench_history to give it a replica identity, and
cannot get it to work reliably ).

Sequences are not replicated but that should not prevent pgbench_history
itself from being replicated when you add serial to it.

BTW you don't need to add primary key to pgbench_history. Simply ALTER
TABLE pgbench_history REPLICA IDENTITY FULL; should be enough.

--
Petr Jelinek 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

#148Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Steve Singer (#146)
1 attachment(s)
Re: Logical Replication WIP

On 17/12/16 18:34, Steve Singer wrote:

On 12/16/2016 07:49 AM, Petr Jelinek wrote:

Hi,

attached is version 13 of the patch.

I merged in changes from PeterE. And did following changes:
- fixed the ownership error messages for both provider and subscriber
- added ability to send invalidation message to invalidate whole
relcache and use it in publication code
- added the post creation/alter/drop hooks
- removed parts of docs that refer to initial sync (which does not exist
yet)
- added timeout handling/retry, etc to apply/launcher based on the GUCs
that exist for wal receiver (they could use renaming though)
- improved feedback behavior
- apply worker now uses owner of the subscription as connection user
- more tests
- check for max_replication_slots in launcher
- clarify the update 'K' sub-message description in protocol

A few things I've noticed so far

If I shutdown the publisher I see the following in the log

2016-12-17 11:33:49.548 EST [1891] LOG: worker process: ?)G? (PID 1987)
exited with exit code 1

but then if I shutdown the subscriber postmaster and restart it switches to
2016-12-17 11:43:09.628 EST [2373] LOG: worker process: ???? (PID 2393)
exited with exit code 1

Not sure where the 'G' was coming from (other times I have seen an 'I'
here or other random characters)

Uninitialized bgw_name for apply worker. Rather silly bug. Fixed.

I don't think we are cleaning up subscriptions on a drop database

If I do the following

1) Create a subscription in a new database
2) Stop the publisher
3) Drop the database on the subscriber

test=# create subscription mysuba connection 'host=localhost dbname=test
port=5440' publication mypub;
test=# \c b
b=# drop database test;
DROP DATABASE
b=# select * FROM pg_subscription ;
subdbid | subname | subowner | subenabled | subconninfo |
subslotname | subpublications
---------+---------+----------+------------+--------------------------------------+-------------+-----------------

16384 | mysuba | 10 | t | host=localhost dbname=test
port=5440 | mysuba | {mypub}

Good one. I added check that prevents dropping database when there is
subscription defined for it. I think we can't cascade here as
subscription may or may not hold resources (slot) in another
instance/database so preventing the drop is best we can do.

Also I don't think I can now drop mysuba
b=# drop subscription mysuba;
ERROR: subscription "mysuba" does not exist

Yeah subscriptions are per database.

I don't want to make v14 just for these 2 changes as that would make
life harder for anybody code-reviewing the v13 so attached is diff with
above fixes that applies on top of v13.

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

Attachments:

logical-replication-v13-fixes.difftext/x-diff; name=logical-replication-v13-fixes.diffDownload
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index 8be9f39..2c58cc6 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -107,7 +107,41 @@ GetSubscription(Oid subid, bool missing_ok)
 }
 
 /*
- * Free memory allocated by subscription struct. */
+ * Return number of subscriptions defined in given database.
+ * Used by dropdb() to check if database can indeed be dropped.
+ */
+int
+CountDBSubscriptions(Oid dbid)
+{
+	int				nsubs = 0;
+	Relation		rel;
+	ScanKeyData		scankey;
+	SysScanDesc		scan;
+	HeapTuple		tup;
+
+	rel = heap_open(SubscriptionRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&scankey,
+				Anum_pg_subscription_subdbid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(dbid));
+
+	scan = systable_beginscan(rel, InvalidOid, false,
+							  NULL, 1, &scankey);
+
+	while (HeapTupleIsValid(tup = systable_getnext(scan)))
+		nsubs++;
+
+	systable_endscan(scan);
+
+	heap_close(rel, NoLock);
+
+	return nsubs;
+}
+
+/*
+ * Free memory allocated by subscription struct.
+ */
 void
 FreeSubscription(Subscription *sub)
 {
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 0919ad8..45d152c 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -37,6 +37,7 @@
 #include "catalog/pg_authid.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_db_role_setting.h"
+#include "catalog/pg_subscription.h"
 #include "catalog/pg_tablespace.h"
 #include "commands/comment.h"
 #include "commands/dbcommands.h"
@@ -790,6 +791,7 @@ dropdb(const char *dbname, bool missing_ok)
 	int			npreparedxacts;
 	int			nslots,
 				nslots_active;
+	int			nsubscriptions;
 
 	/*
 	 * Look up the target database's OID, and get exclusive lock on it. We
@@ -875,6 +877,21 @@ dropdb(const char *dbname, bool missing_ok)
 				 errdetail_busy_db(notherbackends, npreparedxacts)));
 
 	/*
+	 * Check if there are subscriptions defined in the target database.
+	 *
+	 * We can't drop them automatically because they might be holding
+	 * resources in other databases/instances.
+	 */
+	if ((nsubscriptions = CountDBSubscriptions(db_id)) > 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_IN_USE),
+				 errmsg("database \"%s\" is being used by logical replication subscription",
+						dbname),
+				 errdetail_plural("There is %d subscription.",
+								  "There are %d subscriptions.",
+								  nsubscriptions, nsubscriptions)));
+
+	/*
 	 * Remove the database's tuple from pg_database.
 	 */
 	tup = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(db_id));
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index fe30dda..bd865ef 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -370,6 +370,8 @@ CreateSubscription(CreateSubscriptionStmt *stmt)
 
 	InvokeObjectPostCreateHook(SubscriptionRelationId, subid, 0);
 
+	ApplyLauncherWakeupAtCommit();
+
 	return myself;
 }
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 0f04cb3..783d97e 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -280,6 +280,8 @@ logicalrep_worker_launch(Oid dbid, Oid subid, Oid userid)
 		BGWORKER_BACKEND_DATABASE_CONNECTION;
 	bgw.bgw_start_time = BgWorkerStart_RecoveryFinished;
 	bgw.bgw_main = ApplyWorkerMain;
+	snprintf(bgw.bgw_name, BGW_MAXLEN,
+			 "logical replication worker %u", subid);
 
 	bgw.bgw_restart_time = BGW_NEVER_RESTART;
 	bgw.bgw_notify_pid = MyProcPid;
diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h
index f6a3bac..057b36e 100644
--- a/src/include/catalog/pg_subscription.h
+++ b/src/include/catalog/pg_subscription.h
@@ -77,4 +77,6 @@ extern Subscription *GetSubscription(Oid subid, bool missing_ok);
 extern void FreeSubscription(Subscription *sub);
 extern Oid get_subscription_oid(const char *subname, bool missing_ok);
 
+extern int CountDBSubscriptions(Oid dbid);
+
 #endif   /* PG_SUBSCRIPTION_H */
#149Steve Singer
steve@ssinger.info
In reply to: Petr Jelinek (#148)
4 attachment(s)
Re: Logical Replication WIP

On 12/18/2016 05:28 AM, Petr Jelinek wrote:

On 17/12/16 18:34, Steve Singer wrote:

On 12/16/2016 07:49 AM, Petr Jelinek wrote:
Yeah subscriptions are per database. I don't want to make v14 just
for these 2 changes as that would make life harder for anybody
code-reviewing the v13 so attached is diff with above fixes that
applies on top of v13.

Thanks that fixes those issues.

A few more I've noticed

pg_dumping subscriptions doesn't seem to work

./pg_dump -h localhost --port 5441 --include-subscriptions test
pg_dump: [archiver (db)] query failed: ERROR: missing FROM-clause entry
for table "p"
LINE 1: ...LECT rolname FROM pg_catalog.pg_roles WHERE oid = p.subowner...
^
pg_dump: [archiver (db)] query was: SELECT s.tableoid, s.oid,
s.subname,(SELECT rolname FROM pg_catalog.pg_roles WHERE oid =
p.subowner) AS rolname, s.subenabled, s.subconninfo, s.subslotname,
s.subpublications FROM pg_catalog.pg_subscription s WHERE s.subdbid =
(SELECT oid FROM pg_catalog.pg_database WHERE datname
= current_database())

I have attached a patch that fixes this.

pg_dump is also generating warnings

pg_dump: [archiver] WARNING: don't know how to set owner for object type
SUBSCRIPTION

I know that the plan is to add proper ACL's for publications and
subscriptions later. I don't know if we want to leave the warning in
until then or do something about it.

Also the tab-competion for create subscription doesn't seem to work as
intended.
I've attached a patch that fixes it and patches to add tab completion
for alter publication|subscription

Attachments:

0003-Fix-tab-complete-for-CREATE-SUBSCRIPTION.patchtext/x-diff; name=0003-Fix-tab-complete-for-CREATE-SUBSCRIPTION.patchDownload
From 3ed2ad471766490310b1102d8c9166a597ac11af Mon Sep 17 00:00:00 2001
From: Steve Singer <steve@ssinger.info>
Date: Sun, 18 Dec 2016 12:37:29 -0500
Subject: [PATCH 3/4] Fix tab complete for CREATE SUBSCRIPTION

---
 src/bin/psql/tab-complete.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 8816f6c..6dd47f6 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2298,8 +2298,10 @@ psql_completion(const char *text, int start, int end)
 /* CREATE SUBSCRIPTION */
 	else if (Matches3("CREATE", "SUBSCRIPTION", MatchAny))
 		COMPLETE_WITH_CONST("CONNECTION");
+	else if (Matches5("CREATE", "SUBSCRIPTION", MatchAny, "CONNECTION",MatchAny))
+		COMPLETE_WITH_CONST("PUBLICATION");	
 	/* Complete "CREATE SUBSCRIPTION <name> ...  WITH ( <opt>" */
-	else if (Matches2("CREATE", "SUBSCRIPTION") && TailMatches2("WITH", "("))
+	else if (HeadMatches2("CREATE", "SUBSCRIPTION") && TailMatches2("WITH", "("))
 		COMPLETE_WITH_LIST5("ENABLED", "DISABLED", "CREATE SLOT",
 							"NOCREATE SLOT", "SLOT NAME");
 
-- 
2.1.4

0002-Add-tab-complete-for-ALTER-SUBSCRIPTION.patchtext/x-diff; name=0002-Add-tab-complete-for-ALTER-SUBSCRIPTION.patchDownload
From 36a0d4382f7ffd2b740298a2bafd49471766bdad Mon Sep 17 00:00:00 2001
From: Steve Singer <steve@ssinger.info>
Date: Sun, 18 Dec 2016 12:51:14 -0500
Subject: [PATCH 2/4] Add tab-complete for ALTER SUBSCRIPTION

---
 src/bin/psql/tab-complete.c | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 4cbb848..8816f6c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1410,7 +1410,7 @@ psql_completion(const char *text, int start, int end)
 			"EVENT TRIGGER", "EXTENSION", "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "FUNCTION",
 			"GROUP", "INDEX", "LANGUAGE", "LARGE OBJECT", "MATERIALIZED VIEW", "OPERATOR",
 			"POLICY", "PUBLICATION", "ROLE", "RULE", "SCHEMA", "SERVER", "SEQUENCE",
-			"SYSTEM", "TABLE", "TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE",
+			"SUBSCRIPTION", "SYSTEM", "TABLE", "TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE",
 		"USER", "USER MAPPING FOR", "VIEW", NULL};
 
 		COMPLETE_WITH_LIST(list_ALTER);
@@ -1446,6 +1446,15 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST6("PUBLISH INSERT", "NOPUBLISH INSERT", "PUBLISH UPDATE",
 							"NOPUBLISH UPDATE", "PUBLISH DELETE", "NOPUBLISH DELETE");
 	}
+	/* ALTER SUBSCRIPTION <name> ... */
+	else if (Matches3("ALTER","SUBSCRIPTION",MatchAny))
+	{
+		COMPLETE_WITH_LIST5("WITH", "CONNECTION", "SET PUBLICATION", "ENABLE", "DISABLE");
+	}
+	else if (HeadMatches3("ALTER", "SUBSCRIPTION", MatchAny) && TailMatches2("WITH", "("))
+	{
+		COMPLETE_WITH_CONST("SLOT NAME");
+	}
 	/* ALTER SCHEMA <name> */
 	else if (Matches3("ALTER", "SCHEMA", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER TO", "RENAME TO");
-- 
2.1.4

0001-Add-tab-complete-for-ALTER-PUBLICATION.patchtext/x-diff; name=0001-Add-tab-complete-for-ALTER-PUBLICATION.patchDownload
From d4685b991245270221a33e0cf8e61d00fb0bf67e Mon Sep 17 00:00:00 2001
From: Steve Singer <steve@ssinger.info>
Date: Sun, 18 Dec 2016 12:47:15 -0500
Subject: [PATCH 1/4] Add tab-complete for ALTER PUBLICATION

---
 src/bin/psql/tab-complete.c | 16 +++++++++++++---
 1 file changed, 13 insertions(+), 3 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 6ad05b7..4cbb848 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1409,8 +1409,8 @@ psql_completion(const char *text, int start, int end)
 		{"AGGREGATE", "COLLATION", "CONVERSION", "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN",
 			"EVENT TRIGGER", "EXTENSION", "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "FUNCTION",
 			"GROUP", "INDEX", "LANGUAGE", "LARGE OBJECT", "MATERIALIZED VIEW", "OPERATOR",
-			"POLICY", "ROLE", "RULE", "SCHEMA", "SERVER", "SEQUENCE", "SYSTEM", "TABLE",
-			"TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE",
+			"POLICY", "PUBLICATION", "ROLE", "RULE", "SCHEMA", "SERVER", "SEQUENCE",
+			"SYSTEM", "TABLE", "TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE",
 		"USER", "USER MAPPING FOR", "VIEW", NULL};
 
 		COMPLETE_WITH_LIST(list_ALTER);
@@ -1435,7 +1435,17 @@ psql_completion(const char *text, int start, int end)
 		else
 			COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
 	}
-
+	/* ALTER PUBLICATION <name> ...*/
+	else if (Matches3("ALTER","PUBLICATION",MatchAny))
+	{
+		COMPLETE_WITH_LIST4("WITH","ADD TABLE","SET TABLE","DROP TABLE");
+	}
+	/* ALTER PUBLICATION <name> .. WITH ( ... */
+	else if (HeadMatches3("ALTER", "PUBLICATION",MatchAny) && TailMatches2("WITH", "("))
+	{
+		COMPLETE_WITH_LIST6("PUBLISH INSERT", "NOPUBLISH INSERT", "PUBLISH UPDATE",
+							"NOPUBLISH UPDATE", "PUBLISH DELETE", "NOPUBLISH DELETE");
+	}
 	/* ALTER SCHEMA <name> */
 	else if (Matches3("ALTER", "SCHEMA", MatchAny))
 		COMPLETE_WITH_LIST2("OWNER TO", "RENAME TO");
-- 
2.1.4

0004-Fix-typo-for-column-name-when-dumping-subscriptions-.patchtext/x-diff; name=0004-Fix-typo-for-column-name-when-dumping-subscriptions-.patchDownload
From 106d0af41363b49e25d1756adb09b8ace6100145 Mon Sep 17 00:00:00 2001
From: Steve Singer <steve@ssinger.info>
Date: Sun, 18 Dec 2016 12:57:04 -0500
Subject: [PATCH 4/4] Fix typo for column name when dumping subscriptions with
 pg_dump

---
 src/bin/pg_dump/pg_dump.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4968c27..536366a 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -3605,7 +3605,7 @@ getSubscriptions(Archive *fout)
 	/* Get the subscriptions in current database. */
 	appendPQExpBuffer(query,
 					  "SELECT s.tableoid, s.oid, s.subname,"
-					  "(%s p.subowner) AS rolname, s.subenabled, "
+					  "(%s s.subowner) AS rolname, s.subenabled, "
 					  " s.subconninfo, s.subslotname, s.subpublications "
 					  "FROM pg_catalog.pg_subscription s "
 					  "WHERE s.subdbid = (SELECT oid FROM pg_catalog.pg_database"
-- 
2.1.4

#150Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Steve Singer (#149)
5 attachment(s)
Re: Logical Replication WIP

On 18/12/16 19:02, Steve Singer wrote:

On 12/18/2016 05:28 AM, Petr Jelinek wrote:

On 17/12/16 18:34, Steve Singer wrote:

On 12/16/2016 07:49 AM, Petr Jelinek wrote:
Yeah subscriptions are per database. I don't want to make v14 just
for these 2 changes as that would make life harder for anybody
code-reviewing the v13 so attached is diff with above fixes that
applies on top of v13.

Thanks that fixes those issues.

A few more I've noticed

pg_dumping subscriptions doesn't seem to work

./pg_dump -h localhost --port 5441 --include-subscriptions test
pg_dump: [archiver (db)] query failed: ERROR: missing FROM-clause entry
for table "p"
LINE 1: ...LECT rolname FROM pg_catalog.pg_roles WHERE oid = p.subowner...
^
pg_dump: [archiver (db)] query was: SELECT s.tableoid, s.oid,
s.subname,(SELECT rolname FROM pg_catalog.pg_roles WHERE oid =
p.subowner) AS rolname, s.subenabled, s.subconninfo, s.subslotname,
s.subpublications FROM pg_catalog.pg_subscription s WHERE s.subdbid =
(SELECT oid FROM pg_catalog.pg_database WHERE datname
= current_database())

I have attached a patch that fixes this.

Thanks, merged.

pg_dump is also generating warnings

pg_dump: [archiver] WARNING: don't know how to set owner for object type
SUBSCRIPTION

I know that the plan is to add proper ACL's for publications and
subscriptions later. I don't know if we want to leave the warning in
until then or do something about it.

No, ACLs are separate from owner. This is thinko on my side. I was
thinking we can live without ALTER ... OWNER TO for now, but we actually
need it for pg_dump and for REASSIGN OWNED. So now I added the OWNER TO
for both PUBLICATION and SUBSCRIPTION.

Also the tab-competion for create subscription doesn't seem to work as
intended.
I've attached a patch that fixes it and patches to add tab completion
for alter publication|subscription

Merged as well.

Okay so now is the time for v14 I guess as more changes accumulated (I
also noticed missing doc for max_logical_replication_workers GUC).

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

Attachments:

0001-Add-PUBLICATION-catalogs-and-DDL-v14.patch.gzapplication/gzip; name=0001-Add-PUBLICATION-catalogs-and-DDL-v14.patch.gzDownload
0002-Add-SUBSCRIPTION-catalog-and-DDL-v14.patch.gzapplication/gzip; name=0002-Add-SUBSCRIPTION-catalog-and-DDL-v14.patch.gzDownload
0003-Define-logical-replication-protocol-and-output-plugi-v14.patch.gzapplication/gzip; name=0003-Define-logical-replication-protocol-and-output-plugi-v14.patch.gzDownload
0004-Add-logical-replication-workers-v14.patch.gzapplication/gzip; name=0004-Add-logical-replication-workers-v14.patch.gzDownload
0005-Add-separate-synchronous-commit-control-for-logical--v14.patch.gzapplication/gzip; name=0005-Add-separate-synchronous-commit-control-for-logical--v14.patch.gzDownload
#151Erik Rijkers
er@xs4all.nl
In reply to: Petr Jelinek (#147)
Re: Logical Replication WIP

On 2016-12-18 11:12, Petr Jelinek wrote:

(now using latest: patchset:)

0001-Add-PUBLICATION-catalogs-and-DDL-v14.patch
0002-Add-SUBSCRIPTION-catalog-and-DDL-v14.patch
0003-Define-logical-replication-protocol-and-output-plugi-v14.patch
0004-Add-logical-replication-workers-v14.patch
0005-Add-separate-synchronous-commit-control-for-logical--v14.patch

BTW you don't need to add primary key to pgbench_history. Simply ALTER
TABLE pgbench_history REPLICA IDENTITY FULL; should be enough.

Either should, but neither is.

set-up:
Before creating the publication/subscription:
On master I run pgbench -qis 1, then set replica identity (and/or add
serial column) for pgbench_history, then dump/restore the 4 pgbench
tables from master to replica.
Then enabling publication/subscription. logs looks well. (Other tests
I've devised earlier (on other tables) still work nicely.)

Now when I do a pgbench-run on master, something like:

pgbench -c 1 -T 20 -P 1

I often see this (when running pgbench):

ERROR: publisher does not send replica identity column expected by the
logical replication target public.pgbench_tellers
or, sometimes (less often) the same ERROR for pgbench_accounts appears
(as in the subsciber-log below)

-- publisher log
2016-12-19 07:44:22.738 CET 22690 LOG: logical decoding found
consistent point at 0/14598C78
2016-12-19 07:44:22.738 CET 22690 DETAIL: There are no running
transactions.
2016-12-19 07:44:22.738 CET 22690 LOG: exported logical decoding
snapshot: "000130FA-1" with 0 transaction IDs
2016-12-19 07:44:22.886 CET 22729 LOG: starting logical decoding for
slot "sub1"
2016-12-19 07:44:22.886 CET 22729 DETAIL: streaming transactions
committing after 0/14598CB0, reading WAL from 0/14598C78
2016-12-19 07:44:22.886 CET 22729 LOG: logical decoding found
consistent point at 0/14598C78
2016-12-19 07:44:22.886 CET 22729 DETAIL: There are no running
transactions.
2016-12-19 07:45:25.568 CET 22729 LOG: could not receive data from
client: Connection reset by peer
2016-12-19 07:45:25.568 CET 22729 LOG: unexpected EOF on standby
connection
2016-12-19 07:45:25.580 CET 26696 LOG: starting logical decoding for
slot "sub1"
2016-12-19 07:45:25.580 CET 26696 DETAIL: streaming transactions
committing after 0/1468E0D0, reading WAL from 0/1468DC90
2016-12-19 07:45:25.589 CET 26696 LOG: logical decoding found
consistent point at 0/1468DC90
2016-12-19 07:45:25.589 CET 26696 DETAIL: There are no running
transactions.

-- subscriber log
2016-12-19 07:44:22.878 CET 17027 LOG: starting logical replication
worker for subscription 24581
2016-12-19 07:44:22.883 CET 22726 LOG: logical replication apply for
subscription sub1 started
2016-12-19 07:45:11.069 CET 22726 WARNING: leaked hash_seq_search scan
for hash table 0x2def1a8
2016-12-19 07:45:25.566 CET 22726 ERROR: publisher does not send
replica identity column expected by the logical replication target
public.pgbench_accounts
2016-12-19 07:45:25.568 CET 16984 LOG: worker process: logical
replication worker 24581 (PID 22726) exited with exit code 1
2016-12-19 07:45:25.568 CET 17027 LOG: starting logical replication
worker for subscription 24581
2016-12-19 07:45:25.574 CET 26695 LOG: logical replication apply for
subscription sub1 started
2016-12-19 07:46:10.950 CET 26695 WARNING: leaked hash_seq_search scan
for hash table 0x2def2c8
2016-12-19 07:46:10.950 CET 26695 WARNING: leaked hash_seq_search scan
for hash table 0x2def2c8
2016-12-19 07:46:10.950 CET 26695 WARNING: leaked hash_seq_search scan
for hash table 0x2def2c8

Sometimes replication (caused by a pgbench run) runs for a few seconds
replicating all 4 pgbench tables correctly, but never longer than 10 to
20 seconds.

If you cannot reproduce with the provided info it I will make a more
precise setup-desciption, but it's so solidly failing here that I hope
that won't be necessary.

Erik Rijkers

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

#152Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Erik Rijkers (#151)
1 attachment(s)
Re: Logical Replication WIP

On 19/12/16 08:04, Erik Rijkers wrote:

On 2016-12-18 11:12, Petr Jelinek wrote:

(now using latest: patchset:)

0001-Add-PUBLICATION-catalogs-and-DDL-v14.patch
0002-Add-SUBSCRIPTION-catalog-and-DDL-v14.patch
0003-Define-logical-replication-protocol-and-output-plugi-v14.patch
0004-Add-logical-replication-workers-v14.patch
0005-Add-separate-synchronous-commit-control-for-logical--v14.patch

BTW you don't need to add primary key to pgbench_history. Simply ALTER
TABLE pgbench_history REPLICA IDENTITY FULL; should be enough.

Either should, but neither is.

set-up:
Before creating the publication/subscription:
On master I run pgbench -qis 1, then set replica identity (and/or add
serial column) for pgbench_history, then dump/restore the 4 pgbench
tables from master to replica.
Then enabling publication/subscription. logs looks well. (Other tests
I've devised earlier (on other tables) still work nicely.)

Now when I do a pgbench-run on master, something like:

pgbench -c 1 -T 20 -P 1

I often see this (when running pgbench):

ERROR: publisher does not send replica identity column expected by the
logical replication target public.pgbench_tellers
or, sometimes (less often) the same ERROR for pgbench_accounts appears
(as in the subsciber-log below)

-- publisher log
2016-12-19 07:44:22.738 CET 22690 LOG: logical decoding found
consistent point at 0/14598C78
2016-12-19 07:44:22.738 CET 22690 DETAIL: There are no running
transactions.
2016-12-19 07:44:22.738 CET 22690 LOG: exported logical decoding
snapshot: "000130FA-1" with 0 transaction IDs
2016-12-19 07:44:22.886 CET 22729 LOG: starting logical decoding for
slot "sub1"
2016-12-19 07:44:22.886 CET 22729 DETAIL: streaming transactions
committing after 0/14598CB0, reading WAL from 0/14598C78
2016-12-19 07:44:22.886 CET 22729 LOG: logical decoding found
consistent point at 0/14598C78
2016-12-19 07:44:22.886 CET 22729 DETAIL: There are no running
transactions.
2016-12-19 07:45:25.568 CET 22729 LOG: could not receive data from
client: Connection reset by peer
2016-12-19 07:45:25.568 CET 22729 LOG: unexpected EOF on standby
connection
2016-12-19 07:45:25.580 CET 26696 LOG: starting logical decoding for
slot "sub1"
2016-12-19 07:45:25.580 CET 26696 DETAIL: streaming transactions
committing after 0/1468E0D0, reading WAL from 0/1468DC90
2016-12-19 07:45:25.589 CET 26696 LOG: logical decoding found
consistent point at 0/1468DC90
2016-12-19 07:45:25.589 CET 26696 DETAIL: There are no running
transactions.

-- subscriber log
2016-12-19 07:44:22.878 CET 17027 LOG: starting logical replication
worker for subscription 24581
2016-12-19 07:44:22.883 CET 22726 LOG: logical replication apply for
subscription sub1 started
2016-12-19 07:45:11.069 CET 22726 WARNING: leaked hash_seq_search scan
for hash table 0x2def1a8
2016-12-19 07:45:25.566 CET 22726 ERROR: publisher does not send
replica identity column expected by the logical replication target
public.pgbench_accounts
2016-12-19 07:45:25.568 CET 16984 LOG: worker process: logical
replication worker 24581 (PID 22726) exited with exit code 1
2016-12-19 07:45:25.568 CET 17027 LOG: starting logical replication
worker for subscription 24581
2016-12-19 07:45:25.574 CET 26695 LOG: logical replication apply for
subscription sub1 started
2016-12-19 07:46:10.950 CET 26695 WARNING: leaked hash_seq_search scan
for hash table 0x2def2c8
2016-12-19 07:46:10.950 CET 26695 WARNING: leaked hash_seq_search scan
for hash table 0x2def2c8
2016-12-19 07:46:10.950 CET 26695 WARNING: leaked hash_seq_search scan
for hash table 0x2def2c8

Sometimes replication (caused by a pgbench run) runs for a few seconds
replicating all 4 pgbench tables correctly, but never longer than 10 to
20 seconds.

If you cannot reproduce with the provided info it I will make a more
precise setup-desciption, but it's so solidly failing here that I hope
that won't be necessary.

Hi,

nope can't reproduce that. I can reproduce the leaked hash_seq_search.
The attached fixes that. But no issues with replication itself.

The error basically means that pkey on publisher and subscriber isn't
the same.

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

Attachments:

hash-leak-fix.difftext/x-diff; name=hash-leak-fix.diffDownload
diff --git a/src/backend/replication/logical/relation.c b/src/backend/replication/logical/relation.c
index d795c5b..7ab94f5 100644
--- a/src/backend/replication/logical/relation.c
+++ b/src/backend/replication/logical/relation.c
@@ -59,6 +59,7 @@ logicalrep_relmap_invalidate_cb(Datum arg, Oid reloid)
                        if (entry->reloid == reloid)
                        {
                                entry->reloid = InvalidOid;
+                               hash_seq_term(&status);
                                break;
                        }
                }
#153Steve Singer
steve@ssinger.info
In reply to: Petr Jelinek (#150)
Re: Logical Replication WIP

On 12/18/2016 09:04 PM, Petr Jelinek wrote:

On 18/12/16 19:02, Steve Singer wrote:

pg_dump is also generating warnings

pg_dump: [archiver] WARNING: don't know how to set owner for object type
SUBSCRIPTION

I know that the plan is to add proper ACL's for publications and
subscriptions later. I don't know if we want to leave the warning in
until then or do something about it.

No, ACLs are separate from owner. This is thinko on my side. I was
thinking we can live without ALTER ... OWNER TO for now, but we actually
need it for pg_dump and for REASSIGN OWNED. So now I added the OWNER TO
for both PUBLICATION and SUBSCRIPTION.

When I try to restore my pg_dump with publications I get

./pg_dump -h localhost --port 5440 test |./psql -h localhost --port
5440 test2

ALTER TABLE
CREATE PUBLICATION
ERROR: unexpected command tag "PUBLICATION

This comes from a
ALTER PUBLICATION mypub OWNER TO ssinger;

Does the OWNER TO clause need to be added to AlterPublicationStmt:
instead of AlterOwnerStmt ?
Also we should update the tab-complete for ALTER PUBLICATION to show the
OWNER to options + the \h help in psql and the reference SGML

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

#154Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Steve Singer (#153)
Re: Logical Replication WIP

On 19/12/16 15:39, Steve Singer wrote:

On 12/18/2016 09:04 PM, Petr Jelinek wrote:

On 18/12/16 19:02, Steve Singer wrote:

pg_dump is also generating warnings

pg_dump: [archiver] WARNING: don't know how to set owner for object type
SUBSCRIPTION

I know that the plan is to add proper ACL's for publications and
subscriptions later. I don't know if we want to leave the warning in
until then or do something about it.

No, ACLs are separate from owner. This is thinko on my side. I was
thinking we can live without ALTER ... OWNER TO for now, but we actually
need it for pg_dump and for REASSIGN OWNED. So now I added the OWNER TO
for both PUBLICATION and SUBSCRIPTION.

When I try to restore my pg_dump with publications I get

./pg_dump -h localhost --port 5440 test |./psql -h localhost --port
5440 test2

ALTER TABLE
CREATE PUBLICATION
ERROR: unexpected command tag "PUBLICATION

This comes from a
ALTER PUBLICATION mypub OWNER TO ssinger;

Does the OWNER TO clause need to be added to AlterPublicationStmt:
instead of AlterOwnerStmt ?

Nah that's just bug in what command tag string we return in the
utility.c, I noticed this myself after sending the v14, it's one line fix.

Also we should update the tab-complete for ALTER PUBLICATION to show the
OWNER to options + the \h help in psql and the reference SGML

Yeah.

--
Petr Jelinek 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

#155Erik Rijkers
er@xs4all.nl
In reply to: Erik Rijkers (#151)
Re: Logical Replication WIP

On 2016-12-19 08:04, Erik Rijkers wrote:

On 2016-12-18 11:12, Petr Jelinek wrote:

(now using latest: patchset:)

0001-Add-PUBLICATION-catalogs-and-DDL-v14.patch
0002-Add-SUBSCRIPTION-catalog-and-DDL-v14.patch
0003-Define-logical-replication-protocol-and-output-plugi-v14.patch
0004-Add-logical-replication-workers-v14.patch
0005-Add-separate-synchronous-commit-control-for-logical--v14.patch

Sometimes replication (caused by a pgbench run) runs for a few
seconds replicating all 4 pgbench tables correctly, but never longer
than 10 to 20 seconds.

I've concocted pgbench_derail.sh. It assumes 2 instances running,
initially without the publication and subsciption.

There are two separate installations, on the same machine.

To startup the two instances I use instance.sh:

# ./instances.sh
#!/bin/sh
port1=6972
port2=6973
project1=logical_replication
project2=logical_replication2
pg_stuff_dir=$HOME/pg_stuff
PATH1=$pg_stuff_dir/pg_installations/pgsql.$project1/bin:$PATH
PATH2=$pg_stuff_dir/pg_installations/pgsql.$project2/bin:$PATH
server_dir1=$pg_stuff_dir/pg_installations/pgsql.$project1
server_dir2=$pg_stuff_dir/pg_installations/pgsql.$project2
data_dir1=$server_dir1/data
data_dir2=$server_dir2/data
options1="
-c wal_level=logical
-c max_replication_slots=10
-c max_worker_processes=12
-c max_logical_replication_workers=10
-c max_wal_senders=10
-c logging_collector=on
-c log_directory=$server_dir1
-c log_filename=logfile.${project1} "

options2="
-c wal_level=replica
-c max_replication_slots=10
-c max_worker_processes=12
-c max_logical_replication_workers=10
-c max_wal_senders=10
-c logging_collector=on
-c log_directory=$server_dir2
-c log_filename=logfile.${project2} "
which postgres
export PATH=$PATH1; postgres -D $data_dir1 -p $port1 ${options1} &
export PATH=$PATH2; postgres -D $data_dir2 -p $port2 ${options2} &
# end ./instances.sh

#--- pgbench_derail.sh
#!/bin/sh

# assumes both instances are running

# clear logs
# echo >
$HOME/pg_stuff/pg_installations/pgsql.logical_replication/logfile.logical_replication
# echo >
$HOME/pg_stuff/pg_installations/pgsql.logical_replication2/logfile.logical_replication2

port1=6972
port2=6973

function cb()
{
# display the 4 pgbench tables' accumulated content as md5s
# a,b,t,h stand for: pgbench_accounts, -branches, -tellers, -history
for port in $port1 $port2
do
md5_a=$(echo "select * from pgbench_accounts order by aid"
|psql -qtAXp$port|md5sum|cut -b 1-9)
md5_b=$(echo "select * from pgbench_branches order by bid"
|psql -qtAXp$port|md5sum|cut -b 1-9)
md5_t=$(echo "select * from pgbench_tellers order by tid"
|psql -qtAXp$port|md5sum|cut -b 1-9)
md5_h=$(echo "select * from pgbench_history order by
aid,bid,tid"|psql -qtAXp$port|md5sum|cut -b 1-9)
cnt_a=$(echo "select count(*) from pgbench_accounts"|psql -qtAXp
$port)
cnt_b=$(echo "select count(*) from pgbench_branches"|psql -qtAXp
$port)
cnt_t=$(echo "select count(*) from pgbench_tellers" |psql -qtAXp
$port)
cnt_h=$(echo "select count(*) from pgbench_history" |psql -qtAXp
$port)
printf "$port a,b,t,h: %6d %6d %6d %6d" $cnt_a $cnt_b $cnt_t
$cnt_h
echo -n " $md5_a $md5_b $md5_t $md5_h"
if [[ $port -eq $port1 ]]; then echo " master"
elif [[ $port -eq $port2 ]]; then echo " replica"
else echo " ERROR"
fi
done
}

echo "
drop table if exists pgbench_accounts;
drop table if exists pgbench_branches;
drop table if exists pgbench_tellers;
drop table if exists pgbench_history;" | psql -X -p $port1 \
&& echo "
drop table if exists pgbench_accounts;
drop table if exists pgbench_branches;
drop table if exists pgbench_tellers;
drop table if exists pgbench_history;" | psql -X -p $port2 \
&& pgbench -p $port1 -qis 1 \
&& echo "alter table pgbench_history replica identity full;" | psql
-1p $port1 \
&& pg_dump -F c -p $port1 \
-t pgbench_accounts \
-t pgbench_branches \
-t pgbench_tellers \
-t pgbench_history \
| pg_restore -p $port2 -d testdb

echo "$(cb)"

sleep 2

echo "$(cb)"

echo "create publication pub1 for all tables;" | psql -p $port1 -aqtAX

echo "
create subscription sub1
connection 'port=${port1}'
publication pub1
with (disabled);
alter subscription sub1 enable;
" | psql -p $port2 -aqtAX
#------------------------------------

# repeat a short (10 s) pgbench-un to show that during such
# short runs the logical replication often remains intact.
# Longer pgbench-runs always derail the logrep of one or more
# of these 4 table
#
# bug: pgbench_history no longer replicates
# sometimes also the other 3 table de-synced.

echo "$(cb)"
echo "-- pgbench -c 1 -T 10 -P 5 (short run, first)"
pgbench -c 1 -T 10 -P 5
sleep 2
echo "$(cb)"

echo "-- pgbench -c 1 -T 10 -P 5 (short run, second)"
pgbench -c 1 -T 10 -P 5
sleep 2
echo "$(cb)"

echo "-- pgbench -c 1 -T 120 -P 15 (long run)"
pgbench -c 1 -T 120 -P 15
sleep 2
echo "-- 60 second (1)"
echo "$(cb)"
#--- end pgbench_derail.sh

(Sorry for the messy bash.)

thanks,

Erik Rijkers

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

#156Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Erik Rijkers (#155)
5 attachment(s)
Re: Logical Replication WIP

On 20/12/16 08:10, Erik Rijkers wrote:

On 2016-12-19 08:04, Erik Rijkers wrote:

On 2016-12-18 11:12, Petr Jelinek wrote:

(now using latest: patchset:)

0001-Add-PUBLICATION-catalogs-and-DDL-v14.patch
0002-Add-SUBSCRIPTION-catalog-and-DDL-v14.patch
0003-Define-logical-replication-protocol-and-output-plugi-v14.patch
0004-Add-logical-replication-workers-v14.patch
0005-Add-separate-synchronous-commit-control-for-logical--v14.patch

Sometimes replication (caused by a pgbench run) runs for a few
seconds replicating all 4 pgbench tables correctly, but never longer
than 10 to 20 seconds.

I've concocted pgbench_derail.sh. It assumes 2 instances running,
initially without the publication and subsciption.

There are two separate installations, on the same machine.

Thanks, this was very useful. We had wrong attribute index arithmetics
in the place where we verify that replica identities match well enough.

BTW that script you have for testing has 2 minor flaws in terms of
pgbench_history - the order by is not unique enough (adding mtime or
something helps) and second, the pgbench actually truncates the
pgbench_history unless -n is added to command line.

So attached is v15, which fixes this and the
ERROR: unexpected command tag "PUBLICATION
as reported by Steve Singer (plus tab completion fixes and doc fixes).

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

Attachments:

0001-Add-PUBLICATION-catalogs-and-DDL-v15.patch.gzapplication/gzip; name=0001-Add-PUBLICATION-catalogs-and-DDL-v15.patch.gzDownload
0002-Add-SUBSCRIPTION-catalog-and-DDL-v15.patch.gzapplication/gzip; name=0002-Add-SUBSCRIPTION-catalog-and-DDL-v15.patch.gzDownload
0003-Define-logical-replication-protocol-and-output-plugi-v15.patch.gzapplication/gzip; name=0003-Define-logical-replication-protocol-and-output-plugi-v15.patch.gzDownload
���XX0003-Define-logical-replication-protocol-and-output-plugi-v15.patch�=iw�F���_������S��#�^lI�0�%�$�x�<4I�AA�9������/�Z'����$�]]��W^G���z�����z��{���3|2��w�#��o�����������g�<d�'��}N���n�W
`��KG�{��>��O'���������������l�}����w���?y�}�v��~�����9������?�7����	\�c��<���/�(�Qv�1�wX��a��K��_��Z�:s�#"�w<�:�u���G�d��v���zk��<^�P����������N����(��N{l���Q��5����P�.K��X����z�/��Gv���������8��I�D����g���#Or,�}F�?eH�`�����i���^�������mx��q���]gv�AKh-�{{�Ja��%���w������{����u��X�5vcfu��p����;�{6�z{O�v{���3�Wh�/�]�X���Xk|[����g������	gv0�����m���`w�'��<���"������#�-�����!}e��bK�R<m��:������=~t�\�u�����]N�RA9�����Z�� WW0�:\��;��'�]b��M\{�\�t���������������^zn�#�;��yyu3�:�<����_�v�+���;���^�CD���5b{���`�������x����N������d��D�F~tC
��� R�VF�a�z lx����;uc��=���
�J��pv�*D��!�$VR�@�S
��L\W��f/�F �(�@=�T|����Z���\�����~��3��GS�C�mp�3��c69D�U�� ���0����(�1�H���gt�:]�I���t��9���r�)�:��P��T_���c�?>E�����&xF�At���8B��S���9h�
�b�M�.��u����)>8)K���+����5�[Cf[>r�{ �hU�a'�s�����
H����Nzw�Q*_Xm�U���U=���I�QE�t\a'B�H��I��`����m����a����V��(�����m��L������c`������<~��(�1L7�0K?e�,�Sb.���J�!�VU��)�9���e�94��\|(��O�	����C	�.���m!����p���#��E��dx����9����0��"�����Z����	� �(a����0�����5����?��h�?`4@�iV�t<a:�`G�f��1��H��d���K����!��kS���_	-�6�H2)�+�ya1�9�����8�|aI�$P�������9���P7� ���q��!�^�e '�yC����!�PEEyGc��d�B��!�������6�?<
X��+x���g��>U>M�.t���PXC�������q���
*��8q2@}:����]D�X�=�&�H��k[2��^o������
�q='5F[��Uz������HP�'���W1
�R��4��i�3�@��I�H	]�pG.���3
1���G�9��/=�A���V1C!C�u<.��%5@FB�pG��
��A���	)�!�XSm������� �
������E�=����.'��V�X�k��+�)���i(S��p�s��!�c�U��j �U�A�l�0��WFg[	h8h���� ��]0�;N��,A���Bh��:
!u9C�=����+��N�t:��
N���L��e�����������l�a~�u��B�����e�j�20���-�0pj�NYQH��R������JX��`�����?'8�
�L��2}�������(�{{X��A��
8c0����F)^��@���2����$��,(�`,�:�@w�;�WF!�WB^�O��)%&@R{u�n��j�]���#D����~�*�S�@�����{�f8����t�
E�Y2A���c�wR����W���)����&F06���iVpC@�aj{8E������F%L�Z�&�O��AWe����mV^�s����g_A��k<z�h{	hV��������y�L�35I���I�i�}?>�[�r�>����\g��8��S�zq�I�a�D���%-0v'��Ge`�w��Q`v(}�e bH]��x�E7v��n����7J�7�kR���|�D������s=R"ef�53;��~;3,L���Q�������?�����a��b�F�j����%+�k
Q��oi�fCx^������9fC
K��f4��<�yZ>@�K�����+��Bg�8C�������j�+��R���F��P�}�\�o�.i���5X�X�4�4�g8�U(��lb8@RoA��M#n0���iNs<���X����=j.
���Xs17��P�,e�����A�=��S)����6�P�& �$\�����zqo9?z�j�p���d��5���S��C�������c�s]V$����c?_��C^{�X���#����k���.�����[E������Z�{a�z�U~�=��	\H�����v�9���N���]r���58�<#9W���bH6���@��	R�?�T�g��&���&:s	���X��6�D�����K8���K�z�������������\�z(3|�Y3L�/f��*+���(�a�ep+��S:�3��[rs�����y0wT���+y�uZk���Y>�5��ZK�x���Z;H������M���O��W`��`�v"����A�|f�8&����
���
I5���wggU�L����i�x�5]�|vQd%�,�[7HE�`�4p�B�}-���
��$�4+$'�����
�t���4,R��������%P�JTO���&&1<��E� �����K�{[*I8��LB��#�s����$�1��;���I�R"�*��<DE�����W�����j�^;�^�*��?a�lA�8���O
r��f"Dy�x��_W���45gb��c2�0�����%�{�CYUk�N�	U�D�7����VU���Ge��5p ��(����q����SJ|]��\����O��@J-;�]����T�����Y�y��i:������7�:��r����^�����]k"�(X��I/�i��1{L��)E@`��|��r��������4w�5w�{�����>y2z�����g]��$L5�AL�=���� �n�	�n�~���u�^��YM��U���NA?+�����;��J��"h�S�P��%H#>Hb�S`!.�g��G�+X��T���<���A�=��O���2W��w�����p}��?5�����p@gC7����=������c��Q���//_��|s���`������f��.^}�ru8�ujzp�m�Y�O]"z�Gi`9<&x~����	�
	�vP�����w@��Xi,s ;�%Ek����&�����)���`���z�� �3��+����v���k���2u~���[QW�8�*�f4��W*|�]}�����?p���_��������J(M���v>�t�����h�7��joHm!��^N��zI���u�ku��j��G��(G:������JBD{��Q {1u�\�����.��@m4���e�rn��t^1�D�]vX����U	�����O}p9�Y7�S����z+���z�����
-��
c���2����3�_Km�n'quD��^R ������,�!}�'EK�����L�]��+������fy�XH���j�"��K���(�N���T���W�|!�tl��Y�uEOF�Y8�*7���):��u5������O|�9���c�f��������.�Lo��$2���/�if�����o������ �������S}-�I��"n9e]�����+�#=^�-�*!�����<^��$?5�.����H�#b:S�q�����o�����\y����'��H������i��% �C��c����,�,����#�������	et�4yp
����������V=�_1	�a����K"�b���C���p�����@3�D�cE;��
�����_m�X-�!������5+|��(�����:4�����5<���
�n�l�YsG�A��I�5����j��+��t�!���h�&{�|��*���T;���`D����
�m���S
�Q#�����W�@��� D��+�y*3��K�J���`�H
��qW��R^*5��F��r���Z@�#�[�[���xT��h�����=�*y��4�����������kH�����<� �E:�c�2|���I�WbK;R�j7��.�����8�-9�E|kj��R���*��n��:}�?�O���'����������t��"|P�E1���s��|�C��<��O4�1����V�<VX�h�~��x���=��]<� {���x���n$�"0���>�N��m��
|�\��4�Da	�(>�8��]tvR�.jI	�b�A�8�52�%��<���$_ rusz�`}���\�g@�����=��������ht��b����Pn�8���t�{Zfs�|�]9��"��T����o�7��Y��jJ�&?w��R7)��}*S< �+m����Z��Z8R$d
Og�i:_nI���F�'B���w�9K��Se��b�1�X`6�hM�Zh9���L�,�����j�x.U�C<���� Ke��!�Wgq2c�&9��$��L_��*�w��G �J�*Y�AA��E��k���Z���G��.���r��dvfN-�T��|c�\��.�y)��#��h�q5�g��#�g��+LRe�dWE:FZ_�j����%�T�j4�'f��2[�TS_�(��$l�����ZK����W�r�rS}QKj���(B}~'K�yZ��Z����L)�KA���Q���q��c�.���~����}���Js��t�=���??9�9����u�%�T������\i�����u��mg+9x�9�n�S��J��L)Pg�V*�*Q� 1��v=O�1���h��B�"f��h�\��Oi�,d����,T����wn/�C��� ���^*du��P�Z@��{6����K��`p7�2��Um(�WCV�� R����OIi���]���9��y�������<�pLK'����+��B�����}�9�q�:����-Y�X9�2'Xc����>%�z)w������
�z#M�1�����}�*n�l?h�4/�xb�A��T�<�nY���uo<�p�#w4�L��r�?<b_���]�j������t�v����	��S���7�n �B"��#3��Qio�}��[�	=7)��M�������"1hs�Y�����urzvz�%���������J�
���jVY=P�g�Y;�hX/�����(S����Kr������CYu������?��+��
�����*���o��w�����+��q����<��*NB^���&���j=#�{by�� #�s��lM�8Fz�	�C�����B_�}C�0WM���l��� �S�s�Um�aT(�8��<�n�f�aW����A���Ki(���^�R�:|��T���M��5T?�_����x0j��m�VD�1_�8�����$k�:�E�A�[�_�W3X��q�����q�g�&m#t��UP���f��� ��
��R��C�tGtm<��\�m���D#}E2�&�=N�%��U�����Ai<�4����(��+����6T[R�u����Hv��M!P�����R�{7�\������]s+�'�3q�zz
lq��2_������� z&�F��v���b�?�zZ�E9"�n!��{t��h+y�U�����%]�_�$w��9����zw|����=��m��&�V����|�o��^t��$�Bmh�280D�E[v���/�n�^b*hj!o����M������ ���,	�>>K�T�Z���>�R9q�#H�����41���k������0�f������
,�cV�W&9-3Vp�5��H4`�F'��o���������oz�P��
����!
��_q�wP���{������@>�@��F����.�`.;$7�E0�`g'7�&_�#����������]N���>�O�V���]��8m�d������O���s?H����][s�F�~��TdR�dIIv���)J�m��vD*q�f����5E2hYY��o�����c�=[��)`0�������o��y��5�0[7��$�q<�I��^�H?���8�D�����j#��F����u��YFX�]SD�M�}�����~��E�T�L\k�PB��((��ycv_�E����PQ�b��B��}�(�wA��n4�d2U���'����%�J
���A%�U�����eU���2����Fv/p:����Es���e���p���n���y�;?;nw�V�])��B�br�(�{I����U����S���T9w}���m�@�J,T�+s�2�>�2��S�{������9
�M���|%��,���	m\\--�+�-�0n:����l�Z�5|��krUOR^��������;\�����C^j�����A�P�9|��J��m����/��O[�7���>t�to�;�CP0��[�h"����s���O�N�-��}�'�4�2@@%�R1.&�;y���� �A������t�lFb��oBv��'O��.;���&��T��&P-�e���,�B�*�|�����I�5�G|\u�����kq��k����ja�-����Pku��hD;�@�~�\sc����8A)�x������:���������%[n������R)��~�#��N���/�3�l��
�O���&/��>��M
9�[@X;$9�����w������fV�����U�
�Eju�v8����QF�!5f10k��sp
|_UU��D�`�61V���/U��E@�H�=��@3@#��8TB����m�;�����XDoc.Z<�u7I�b:V��&u�;lwO�����~)s�:A+�����n��
*)ny�:}���b�~�=;6cA�.��������Z�.������7��n���W�)q2��xe��r����(�����������"]��$A��m\"e�
=�������"��k�p�P���:�W9%��k���R�J�����D�"p��au�j8��T�Z)�l�N�l��
�
zHH4�x�i^B�c�]KB/��j�z����,�]T�f���l�L�-�i!"���p��>��<=�,:�fG�/Yf�&
U�����Z��b3-[r����,w���9�Qr���n=������}��B��r���L]T�N�k����yS��8T�"�/�F1�b�����,K�C���8A���
Q�=�A�`�n5U�J��,[� �����=����I���i�s�<j������Pr����h��U���'����;���&������=�EN���A�+�i�����~��h�g�p��S�O����}	[��*'������_d:~C��}����C���z��t�9��"����.�J��l%�����~H��+a^�-Y���������k�/Rm���$A]�U�$�\AU��3�w��(+si�6�|���\����*(4��J(4�4P����
��>�v���.^�j���[�#�y9��2����u�'��=y���j�5a��-������MF�5��e�RM$@�T��r���}��z�����O�n0�/e���]<"��d�E�#�������_C����W���2��@�J����w?��O�V[������C�1-���u.cu���fo����,"��~��0p*���[��b%c���S)AR��"�T�4�?�e��x��������|���wz~|u���6�� E"��%�G�G
��z��="��)�=�3dJ0�}�`l�E�<�g`[�J:Fd0P{�?�C<?�>P�m�:��t�I��(��`���n�z^�^���V�}(���m8W��j��[�����(�L]�&y�+�W��i*�X�P�ku�&KwD)a��2~ ����kU��2	�,��9��"�q2��V9��A��G=�b�3�c����i�o0�#������qa�����8�%�?���J���xQ6u��zl���v����\��M�aB8������0PVdG����t��vr�sh_et�9����yr6���/��K4�km���4��`���^*��~*d��t����]=�y��&{jQr�{��B��^���9�'{~`1�7�hMc]��/^���J<�ak�&#�jnWzj�9N������x5�������,`�����	~',l�����I�U���5�.�����K���lhp��^�~�4�H!��n-�U!�i�R�Pa�+�%�*�r��P����������"��_�� ��y�h��soP�x�F&}C�8* �\~����CNE
t������n����r�z��O4�O)�#��:N�[��
�
�8]W7����$Q����&��
���i�z�)���������ET���}!s�u������f2���:�9=�����pyi�������	c(�:T��X_��
9�<�q��M�f,s�c��G���^���n�m�J5���Ko�k ��a~�OF���Ln!2
k��U2���:���9A�V�wz76��f���m�6���O���q��y	�~�u����Z�a�^�(����K}�^���}��v4���%���2M_���c�����M���^<���Y�d�OFk �������i��G�rj~��)^��)�,1kq��|�t���}���%���`�������ey\����������d����D����1�f�����r��6;�����x'�v��1V3��
>�7\��w G��X����;*{�.e�!m�U0�({|h3����K�o,���}5�D'�d<\m��������|����yrr~�iuU�k��}�i�okY��Y�[�7,��'���v)r���i�5,��)�Q#C�`u�th�":��^\u{'W��g���Y���?T���,N�X�QmEhj�iF:=��J��ER�J:N�d#.�p��8M����`<���j&;�+�p,���
���R��
P�O�M~�����HA�� �����3jm��'����tw1C��m��7Gu�-�%�$&���D�
�@O��j5�W����y�����8����� 4��f��b����7C�n����jH��tD��}270��,��_K��W�
X���Is;��5�aZ�8��9"0P���q%�k����RF��6K����Q�0�����77{�EA�%*���?��9d��@9H�������;��U�p���P�a���\�����l�7@#s$�u����N��P��HH` O�(��yUK�O�[�}�v �3*u����(�$��r#2qlm�b��	�t��_4�YcaW%�	��:�/������f�N��?E�v.��N������W>0�z
{��a
��}�/��~rc����=�� ���o�{bcy����,����\�2
�� O(x��k��6�����i
��3�5	Z�T�������ARm��g�YK���j�I��L�C�1�)��a�M2�������c\�	�.B_
f�8��MU�J^Ex��1��aS����e`���'8��V^�C�r��C�4Wb;�F�L�,Xu�t0DH�b�Mc����8�0�p��*�$�$��I��NI|�f19r#*���F�����c��}g5Ey&��^SI�W�����4��B�|�c���\���L^Z*C�G�4��h9�y����c���^����4�^������VW���XN��'c���Q�0Y%�0�X�fC�S�a��
�0Mi3NJ�x7�f��&��w7�W|�$Y�`�,R�{#��4����SI�������:4��i���5C��e�$�����:X�����S�}�F�I2a]�!�[d�\���f#?��
ff�������y��3���*"���+��H�F��� #���!��/�M���UNQ�<U���OGp�R�;t�V��P�U�h���B�RJpO3K�D+�:��Z�Ad �	��4�C+MN���:F5�a�GV�t#pW��I��z����f�
�q���,������������y����~��{�V%�������
}2
�d����*�@R;/�q�
��>
���*��V�\K��:-8SR����}�:�z���
GCm�n��� ^�%>�
����qn����13^����hx�%���V�(��U���(�B�����aN�E+�Rk?W�
��g�\��-��6v�us�9�f`9i�(������JH�z�F#��xa�<��������<<ue�0�:����:���B;��*�$��r��Z$�����P�T��
Xi6�>������=/��>����\�e����*�#�Yyh�9��<���8��@�'������\w��HRt^�sl_����P�6�����Ie�6��tw�8u~@�r����Q�2��VS�8�lW�6����z ��#�eNFK�n�q%L�y�,�oi�����:A�,���t3���n�f�$kq��7���[�0o��+�"5*|y�QBS�	G*<��m�_��Zvl�|���h:q�,��Wa��@�x�Q����U�|K�FW��E�R+���	�,"?�S��;���f�z��E��QS{�r�U�!N�d*��A�[�F��{���9�������ps�A���6}ie�����F��2"�X"��#?W�f6�u��
��7���L=��v��zIj���s����:�U�������ny/��$��������9����a3"*Xq�z���F#�}t~�m���?O�;�+I�,TBh�"#E�)�O.��b\
��S��0�M�p��i�e���y�\�Y*@��N���x5"'�X�QbG���I��}�l� P^q!�r�I��
�
�{�4�V���2|��U���d������O�]@�V��(8-R�E�NO�$%�*��9D����Z'��_����p	����k�6���+t��z�:�D`����8�"�<S\����+q������5)�YSVgEL���Z�
�=MP��`�AM=R6t�W�&a��>m��q��K2�$�@Nf�r�"��z*jI���t�Nl5��c��7���z ��12�������V����!Ey���`(6g�g��f�GS��3�<���������)�v���#.����
Y�Gr����lv{�]����-�f?[�m���U��[/�z�I�����8���x}������fmG��?���B/J>��?��e�����K���t(��z�$��/�L#�p����*��	�>�1j$f���u�Z�#����\���]z0�j�����2�X �i/T9��(9�98�~c.(z���	b:��:��Q�l�d������db9	gT���@1�c��&�l���@L1��.������K@�-T����i:|����O��i6JR����5�i�e��9nT�Gm���!$ ?���a���#1G7�4nA�J8��V�T�D�����"��+���v��Y������]�$�T������]�ue�
ex���.��j1��5��d ��6���
B��&���_��Z�\��k���&����]�\	�FCq�r�p
3�3��6#�{��	�)��:S���Z�����UN�#�d����}v���~j!"���+�I%�E*������BP����@�\������6���"W�n�Un�'��j�cE�)�c��?�N����`n�(
����Ppvy�v��h�q���V���{��GL��
YS#��o�c%��X2}���'����mF^�I{f9U�WZ���5)�6!���������;0n6� K,/X
�&��y��B�
�X��;�1��\�r?���b����YQ�4_��z�9��,��o�	��Q�0�(�>Eq�@"���*�z���h[C�jb�-[����qas|�	��%���\V�nH<6��YpJd�vt���?��m��XVMPU�;W�bS��f��l��\����LMGUm1���U:I|���h9�F�G�%es�(��c��(����8P0}>�1m.�B�����`�$�3�WmPd(�v��I�'&�^����\�*)���.[x���A�>����x������eD� a$w��N}�o�&4�
}4)�����C*pY�"0����2(�Z�����
������f����j.�)i�]����v�"��0��@>r����1��fUi yu/C�h6���F����>?�K�8f��"9cCA��*^)3����I���O�0��E�1�b�R�_��&
���\�\�.������I7x��1��H]�������*R$�;w���9��(��i���j�Y�^O���pWL�A�
�#,��c��������S�j&��q���Biy���t������kd��C��689��K;*1�>�yI�|���l~K�8�smH���")
����������P�4����b��,�i�fz�:$m���;7
��o��
��0���v����^}@���M��{�?����hR������w�V����G����eC�/�l�����,�8rY���D���]�F;����~�7bx���C+Y�;���6�*Y��������d�[MX���_�<���b�3X�no��5~�ZR�-C�~Py�8
Vu�8	�QA@2T��p�W<����_�q�5�b1��7���S}u���J�f`4{���eH�}�~�������<�7���
0004-Add-logical-replication-workers-v15.patch.gzapplication/gzip; name=0004-Add-logical-replication-workers-v15.patch.gzDownload
0005-Add-separate-synchronous-commit-control-for-logical--v15.patch.gzapplication/gzip; name=0005-Add-separate-synchronous-commit-control-for-logical--v15.patch.gzDownload
#157Erik Rijkers
er@xs4all.nl
In reply to: Petr Jelinek (#156)
1 attachment(s)
Re: Logical Replication WIP

On 2016-12-20 09:43, Petr Jelinek wrote:

Thanks, this was very useful. We had wrong attribute index arithmetics
in the place where we verify that replica identities match well enough.

Well, I spent a lot of time on the whole thing so I am glad it's not
just
something stupid I did :)

BTW that script you have for testing has 2 minor flaws in terms of
pgbench_history - the order by is not unique enough (adding mtime or
something helps)

yes, in another version I did
ALTER TABLE pgbench_history ADD COLUMN hid SERIAL PRIMARY KEY.
I suppose that's the best way (adding mtime doesn't work; apparently
mtime
gets repeated too). (I have now added that alter table-statement
again.)

and second, the pgbench actually truncates the
pgbench_history unless -n is added to command line.

ok, -n added.

So attached is v15, which fixes this and the
ERROR: unexpected command tag "PUBLICATION
as reported by Steve Singer (plus tab completion fixes and doc fixes).

Great. It seems to fix the problem: I just an an unprecidented
5-minute run with correct replication.

The first compile gave the attached diffs in the publication regression
test; subsequent
compiles went OK (2x). If I have time later today I'll try to reproduce
that one FAILED test
but maybe you can see immediately what's wrong there .

thanks,

Erik Rijkers

Attachments:

regression.diffstext/x-diff; name=regression.diffsDownload
*** /home/aardvark/pg_stuff/pg_sandbox/pgsql.logical_replication/src/test/regress/expected/publication.out	2016-12-20 09:53:28.023321098 +0100
--- /home/aardvark/pg_stuff/pg_sandbox/pgsql.logical_replication/src/test/regress/results/publication.out	2016-12-20 09:59:32.380705797 +0100
***************
*** 79,86 ****
  ---------+---------+---------
   t       | t       | t
  Tables:
-     "public.testpub_tbl1"
      "pub_test.testpub_nopk"
  
  -- fail - view
  ALTER PUBLICATION testpub_default ADD TABLE testpub_view;
--- 79,86 ----
  ---------+---------+---------
   t       | t       | t
  Tables:
      "pub_test.testpub_nopk"
+     "public.testpub_tbl1"
  
  -- fail - view
  ALTER PUBLICATION testpub_default ADD TABLE testpub_view;
***************
*** 120,127 ****
  ---------+---------+---------
   t       | t       | t
  Tables:
-     "public.testpub_tbl1"
      "pub_test.testpub_nopk"
  
  ALTER PUBLICATION testpub_default DROP TABLE testpub_tbl1, pub_test.testpub_nopk;
  -- fail - nonexistent
--- 120,127 ----
  ---------+---------+---------
   t       | t       | t
  Tables:
      "pub_test.testpub_nopk"
+     "public.testpub_tbl1"
  
  ALTER PUBLICATION testpub_default DROP TABLE testpub_tbl1, pub_test.testpub_nopk;
  -- fail - nonexistent

======================================================================

#158Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Erik Rijkers (#157)
Re: Logical Replication WIP

On 20/12/16 10:41, Erik Rijkers wrote:

On 2016-12-20 09:43, Petr Jelinek wrote:

Thanks, this was very useful. We had wrong attribute index arithmetics
in the place where we verify that replica identities match well enough.

Well, I spent a lot of time on the whole thing so I am glad it's not just
something stupid I did :)

Yeah sadly it was something stupid I did ;)

BTW that script you have for testing has 2 minor flaws in terms of
pgbench_history - the order by is not unique enough (adding mtime or
something helps)

yes, in another version I did
ALTER TABLE pgbench_history ADD COLUMN hid SERIAL PRIMARY KEY.
I suppose that's the best way (adding mtime doesn't work; apparently mtime
gets repeated too). (I have now added that alter table-statement again.)

and second, the pgbench actually truncates the
pgbench_history unless -n is added to command line.

ok, -n added.

So attached is v15, which fixes this and the
ERROR: unexpected command tag "PUBLICATION
as reported by Steve Singer (plus tab completion fixes and doc fixes).

Great. It seems to fix the problem: I just an an unprecidented
5-minute run with correct replication.

Great, thanks.

The first compile gave the attached diffs in the publication regression
test; subsequent
compiles went OK (2x). If I have time later today I'll try to reproduce
that one FAILED test
but maybe you can see immediately what's wrong there .

Seems like tables are just returned in different order but otherwise
it's ok. I guess a way to make this more stable would be to add order by
in the query psql sends to get the list of tables in the publication.

--
Petr Jelinek 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

#159Erik Rijkers
er@xs4all.nl
In reply to: Petr Jelinek (#158)
Re: Logical Replication WIP

On 2016-12-20 10:48, Petr Jelinek wrote:

Here is another small thing:

$ psql -d testdb -p 6972
psql (10devel_logical_replication_20161220_1008_db80acfc9d50)
Type "help" for help.

testdb=# drop publication if exists xxx;
ERROR: unrecognized object type: 28

testdb=# drop subscription if exists xxx;
WARNING: relcache reference leak: relation "pg_subscription" not closed
DROP SUBSCRIPTION

I don't mind but I suppose eventually other messages need to go there

thanks,

Erik Rijkers

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

#160Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Erik Rijkers (#159)
1 attachment(s)
Re: Logical Replication WIP

On 20/12/16 10:56, Erik Rijkers wrote:

On 2016-12-20 10:48, Petr Jelinek wrote:

Here is another small thing:

$ psql -d testdb -p 6972
psql (10devel_logical_replication_20161220_1008_db80acfc9d50)
Type "help" for help.

testdb=# drop publication if exists xxx;
ERROR: unrecognized object type: 28

testdb=# drop subscription if exists xxx;
WARNING: relcache reference leak: relation "pg_subscription" not closed
DROP SUBSCRIPTION

I don't mind but I suppose eventually other messages need to go there

Yep, attached should fix it.

DDL for completely new db objects surely touches a lot of places.

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

Attachments:

fix-drop-if-exists.difftext/x-diff; name=fix-drop-if-exists.diffDownload
diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c
index 61ff8f2..7080c4b 100644
--- a/src/backend/commands/dropcmds.c
+++ b/src/backend/commands/dropcmds.c
@@ -441,6 +441,10 @@ does_not_exist_skipping(ObjectType objtype, List *objname, List *objargs)
 				}
 			}
 			break;
+		case OBJECT_PUBLICATION:
+			msg = gettext_noop("publication \"%s\" does not exist, skipping");
+			name = NameListToString(objname);
+			break;
 		default:
 			elog(ERROR, "unrecognized object type: %d", (int) objtype);
 			break;
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 25c8c34..bd27aac 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -492,6 +492,8 @@ DropSubscription(DropSubscriptionStmt *stmt)
 
 	if (!HeapTupleIsValid(tup))
 	{
+		heap_close(rel, NoLock);
+
 		if (!stmt->missing_ok)
 			ereport(ERROR,
 					(errcode(ERRCODE_UNDEFINED_OBJECT),
@@ -501,6 +503,7 @@ DropSubscription(DropSubscriptionStmt *stmt)
 			ereport(NOTICE,
 					(errmsg("subscription \"%s\" does not exist, skipping",
 							stmt->subname)));
+
 		return;
 	}
 
#161Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Petr Jelinek (#160)
5 attachment(s)
Re: Logical Replication WIP

Hi,

I rebased this for the changes made to inheritance and merged in the
fixes that I previously sent separately.

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

Attachments:

0001-Add-PUBLICATION-catalogs-and-DDL-v16.patch.gzapplication/gzip; name=0001-Add-PUBLICATION-catalogs-and-DDL-v16.patch.gzDownload
�e9fX0001-Add-PUBLICATION-catalogs-and-DDL-v16.patch�=kW�H��������~b��Npf�%��=�;'��#Km[Y���n���������l�����[RWuuUu��[�	���������[�ss����~��������j��??��s��z.s�u�X�����n���zh^��G��.����_W�����g����������g��c�|��:������k��q<�������tr�����S�b7?}wyqv:���b�����Z���rk��hl1�3[a�+���i�+�|��=�)|�2gn;\�	�0=L������=7��XU_f��3:�O���$Rj�����Y���4��vt��nt<�����L9��g����V���[�GNj`����0s��i����W���}�*�@��*�<�5������R�� ���w3��R�����.�y��7�U��a�W�[���r���`���V+� a��[�AD��������\Y�
�0�J~��h�b������s�}bG��2�k������K���t�4����#/�/o
�-���.1��,Bw��<v���U"��������B�N	�7���E`�������,*Pq�8�;�/	]tV�z��e�K���[�)������<P���� 1���`h�z��>���e�+�d���!	0
|b��?5si�����I�O]O`�=VVC
�RK&
���i�QY;:z:�V���&���
t�\�C��I�����i������=�z �H��t�P`5�H}Gf�-�3�N	�}��;<�����vM'����,��(����v�q),���	<��
���`r�i��td8�	�&'�BW�]VJlg�}�/���K@����
M/3O��]�bG�"����;��=)����G���h0��m:�0WsP	#�^P�&���0#@��0�A��^���)�a����0�1t&���A}g�������U����t!>��4��V��k�)08��@9:
�rV@�Fa?3����L��v�vA�,����G��8�����b"�g��p�b�~��iv�lE���6G��n�����*A���G
����U�n4]6������e����X�3Z5I����9�{~�>l������������kqo�|����5kt{�}0�����[bJ���d�%�t�'/ ��O�����(��m�������'�a�f�+~�J���{t�BL�!���A�l�z�k��$^�#�K.;����Q��>`$���X���2|��M�6D��D:��%X�o���� ���Y',��yX����^v�H���,H"P���Is�������;�k�G~���7�$�\p��&��_����c?�W*\r�P�R�I'�����'�-�!��������O$$��d�c ���yS�"{l�X�A,cg���Pe Y^�}N�j�'y����Nj&� ���	�M��QR�
+�SR��Lv�=�/!w�Y�u�J.�m�X'�m%�7*�9�	 Z���fc��@���
�lgi[p��(��)��K�����8�2���B�S��0u���$���)��q" ������}��;�O*(��m85��#�S����:���^S����q$_�G`N�$������GI��|�a��k�x�@�_������%��&n_6�������E�,��(�Q��\�����r�X�'W��hr��'��A��;0�F=0|p�)�7��c��gG��Tb2��?���N�*vP����|x9���s�.( �QPn�1��&1��7��4��#b�����N���1^��,�"��9��8��$9��JX���
��������F�5�3���!by�&f6 @������32��b������s
f����e:F��Dw��A�t}B��*�����*��.�����T��;%fk����Q�4������q4;��J�Qh*S�FT��Sq�����L.�0\��9�����8���/YK���7�������2��\L~fT��&��g�l��x2|��E���II�������!�-���v��3*]�UA}z��s�t	�P.w;��;T��U��5`q%��\����Y�">�����T�"9��X��+�����W�Y4P�\�������Y'h�F�t\/i	��iE-*�.��e���|�tO*m\)�+���N��E���[e=��L��e�J��L�\����:�U`�|	*���J��X�Re���hw�i�v���w��u���������9k��iY����N�����*��o����n���Qr������n�\<��%���+����^�A��1n��`�<�k�^N����H
�K�
n��N���$m!D�=�JFSJh���DJ��%B
*��@�q�9n�:9:ne	����xp��h�KPbc��X��3!aM��Q�De,�6�J���>z������B~"��E����k��Sc��m$!$�Z�������y�\��A��w��=#O�j��tt�R����������\L~`;�=?�7�n�5�M�������R���%����g.��/@��4�C��D��}bW��-��S�P�n���V���j(
�-��)�{z~�&��]��R\>�`l~�4����?9��������lj���N�d>��'�i��]������@�pk��F�t����Q0����I��5 <X~.��`5-�!�}n�����G :K5�&�PG1�M�%��;x��D�j�5!�m�;�����Qd������c@�<����O��IPZ'�j��R��R����a��7vgGKb�k5b��/;�*�]6�4UOU>K�Q�`H��/�)E�D������LV��f:���t����q	�&w�9�kE�����V�-�>0w�<!nx+�V,�*���!W-�M�h^x[�$�#K
p��=W�8&'���~�Xg�/0�����u^]$o]����?bkw��f	�4�tA�"�n(���:���p#��=�
#\7�Dp�[�v��Ta��l3(��=���@����=�C�x��d
����������6�O�f�����l4�+/�1�yj������/�*�3���=�mW�9Bx7 yK�Y��G3��=��>��������n�K��2|�1f��	GJ��h/#����}��B	O���#d��JM.W�wC�V��1fq!��}0Y�gD�Z7c?�9������A���Io��>\������:o�s�"L�����'��z2C�<2��_m�.�L��D,p�Y���h�K
��f�D���e���U��4u��]��(G3n�z�(�:�U(�\aM��
�cJ�]U��H[)RTOS����S�)*��)(N����.=ae�L�6����wz��\�jR�
\g�"
���P�^^
�c�>�����gM1�mRY��OV���u��t��z!�������~������eA
/(�H6:Z�_�HM��,�^�f����[m$�bC��)�5����%�2��2��}6�h��%3����h	XK�N�����$�*����R����f,�������r���������|K1�R��6S����MU�tkfR���Q�3S6�������?$�p���}���:�F�E!	���g�F[>����L�
�l��:^P�E,~�}�|�J��H�����8�?D t��)R���!)���������f.��&�XV������ E��M���[����ad�G��n'�W~$
|�{��	 A�c���������ka���:����6�����B�*�S�<%�>Q.Do"�G
d$'�X��xE1y:��J�SI��	UH#6����> z�#��vi�f��xDh}�:a����_��ge�v�����=onhv���&�RC��w�POr�#��G�&c�Cgyo�
��oQ���IBU���M�D����p.�Z�I4�����8��&��4�Bi\��si�S�N"���s&vq.v����^Z��k��wv���yE�<�B� �0���%-�T��:n��E�b\A1��g�Wo��	R$[�k���C���R)�i�QL��	��X�
,�4A���+6@��L��@#�!#�2��@$��0:M�]uh�
U��>.�I�h/�"oky�"��~�:S�4$b���0��_���_����8�:�u������������(��hY�EM����o/��IWW�H[@����
�C��	��\Oh��^W��P!��^\c:�_*��_u�"%��j��]M����U���TOSg��Yc�r��>�������nr��A����L%w8lT�/�I(����F�'���1��*�Z���{�3�Iw{�����'+�J�J��`PX�]D��>���Ez�XJF�_?��<F���?�C���l��z~�6x9i�G�f����/��k4�����R��������[��UH���y�����l��|0h�5G�T]mDg[�mz�
}v�x�_e�^}��^�/�)L,�>�Mq�k���{����������
�xP^h����zK#�
��d�+�~ZT2C�]B�0_�QN37���uK�Z:are8r�,�"YV�O�%<�����)�Z�:]���}�@b��=��f�~���K~���l�l�V�g��RB�5n-�������0��2UM�������2�
c)���M���
�H�F��C���W\�'m������s���N����yx�Y����`��X=:�FC��|�����8��"N��$a~S<�o�'���X���^
��n����Q�9;�R�D�1�QY:��4����@Tn(�����^?Gh�����<���VH+��5�����]&$)JX���Y�c���u008�~��><P������&��d�����~�W�+,�I������%7|� ��O|��'�����{��t��W��&���c���=��*i#8~_L�DB���4=��/^���n��FIC�"pMK�vj�!A�lw����c.(7�c�e��a��f�f�_�F��WkFg.���������C1�J�D�
����t���2O�/y�u������v���b�b��V�����]XY>���.���u�����G����z<�~4O���xt���h�v���#<V����t��a?�����w*���q����b�����%�\���W�r���.Bq
�c��|.s�E����D���'H�����!nd'�g��o19
��W�Sz�����HM*�%���ZN���kj�J���$������f���l��n�60S)�j3�6A�98<G;O�d�B,i���nQ	�����Et8;�����������]N�vqu����,`@r5u=���N��y����O0�'�_���}j}�,��?'��1����6���q���+����u��������8;ET����l�t�Mh�
[h9p������gCl�hl��wD�������@G��JS���X������.��8|���^AI$��2	{�F��fCb�,�����A�g�����{�����'�a��� $�So8��9���L����~��g������D�;��1��0���������`�0r��<:7�x�#����
��_�!]�7��!��_}/�v�����~vx��������p���������|
�zx6��
4e��.����_�l���>��,%nW�R2����L�T�.�~8OF?�M����w���A��x&��4BaR�������>��c���������%C2EC0�������r�[g��5�0_��El��T� ��B,��x" D�a!��G7 ���-��1g�Mokm���j��-�D������f�f�����?7��PI�U�������
��K�S��U�E��|/�r��E�Y+��6����?�;�
	�V�W�� ���
D�M�\u��o���Y���"��y��FW���']���)��1�7��|i��x�Fm�4X�z���b%��������q�xc��ce�W8L�1����������>�<���s��v"<m���W��dt���������>ps
V�ga�s�\���UA�PZ��hx)��$���(tMF�W�7�����|6q�+��n��������iI��������B�mo�/���7�R�'��o�A"Fa��6�w��>�g@����Ye���9���U�u��oG��CN@Pb�#�$.��{�����(�N%u�C�������q�D�6^&|0I�
��R�~��������z��
wh�����u��W�UN�fKu.��5L����m�^�%@@A����~�\C�Ir`qTp�������4!>���!�O���@��q�.���fuC��� �_|������W��nw�[����8��j�C�������G�6���G"����E���H������Z�d@�r��;��=��$���x�&h�)�����.��h���%6�F�����0/��
]n�{t�8m�351���!������gs��*j�<eA��~�+��2����&��|&8`�H��F�F��+��g��'�����g�����E�SW�F�y�L���

�g�{h��i�4�z�=dpwk���<��K*�a2)��C�%��,+�� /��i��]x`]E��n��D��iY��,PfKb���e����?��J��j��jy���I-
P����x�+p
�K[�Z�r������fy���|@��F����%�cv��E�gO	��D����Y��l}�l=��;�l��qp��X��|}~H��]�ph8����CH8���(0��8B����K��h��@��
��`�#���;�]r_���)-������Y_���8��r�jO���J��,1o�E�qF���}�H��kD#�O�.F�����fn�4]+�$��1[c�!W�_C^��X�������`T����%;��9�:O�^r=�+�+`<���iT�"��z��O�:�:9y�� �w��3���$��FP�UT)	�>bQ��K=�;N.^P��rv���=�bUGvZ_���*�������7aGm�n��5�ugM�%��
�aY���nP�:~2o��;c�w	�fAJ��.���{��v�J���W��JJTho����!����y4��6�7�������Ew�4w~u-������O�k	p5Y(�-�Pn�������p�C��dq��v<+q/�\��,X 8G�:�������9���������������D�W��bH����4����m�"�I�{��:x'��/?���3�	1{���|F����3��kH1��hTa��O)X)u������(I����0�����^e�G��QG����x|;YX�bzo�����1�i���%X0������[�o�$�q������+R�!� BI���Z~+9�����X].���{&����V�P��@���4(\�<�q i�S�rJ#���Kn��Y����B���o��s����D��W���,,�6�ZE)�^��&1C�t�W�8���:;@��:jE�b�O���R�=>�|��4���WR��H$�8>;z^p>���gXN���`��������M	����Q[�����2��<���U�~�n�K��W]���C�,�l-��#�G7.��xg��N�O����X�^�^V���,�[c���������$�y��y�U����s��YS2�K���b=�L���%�����L�"�FPxb�A��&�%�����I3�C��f�����^������-����q��~����oJ4�%�I�qw>��q��2�P��Fx������������	:�	�[�_���t�eV&S�`V��@?�&r
��.�����kz�P������cY�iI������h�����j���BzU��`x�j����+u���-���F~��?�Bh%������������8_(��xFD� ���S�RQ��]e��;����w��X���3��{�i�L��T��SH��&�R��������r�x�C2�|���~�W({#���������g��1/����p�g����o��-�!s.oA��j��y2�;&�mI�w5S�7U"��r' &�J���/��/�`j����7`�id~��fv^�eax0���T)���j�Sg�m�w��g�W[��~'��;�p8�������� @
��Z0T����'�	4^���1$�N�#������n:����RoM������\��FI7������psG�H���3����74����/�p��h�u��q/�=������q�>1��A��q�M����HB��J��Nrp��p�_��,3�V��&l�ae�+��MA�0k7���q%\���8�UX���FA��7hq�����d�,e����
���2�dQ�2�|6��i?�q�����*?����_*�����p���
��n����n9=p�d�U��R)����[���ZTZGq��(��\����B��C'�i2��S*v��
�h��<u�[����Ib�����V�9�k� 5�����]��������i��R��Tw�}!$�����Dmy����o'��-�#_53��J�I�J��fX�<]����#����9:%s�TZ��q�^]���\*u!�Q:�o}�K����=�O�������m��Z��K��������y��|�<���g���A�s��<D>�S��~�m����V�Ai�h�F,4/�[9�hz�0��%v\�#E.fP��-i�"=u��l!�	�g���8��-�\��9���wB]�=@���@FRps�|�M��e����U�8�f��$��,�cW0|4J�)��#]��4�G�M������o#���/�^PO
i�"K�����>��mJv�<����9]N����{1�|�l�������fG~?m�I�������w�!�h����aa���t�j�!���T��/�+��vuGJ��������Z�en���k������$�S��x�;t����R^�^����r���<�q��Y4��i�'�
������"9���-�R�lQS�����r����e_��y��.�Gn����F�+`u�F��-�J�5GR����&���G�z�?W��+�RE�)��qR��0���N�}�����OR��0!P�i�v(�s<��Sf6$+�B�.����
HA�Q�\�)��ud�����s�������y,��w$3�N���"�������j`X!`����g�:J����FIC�\p��|�^�w��v��n���tx�1H/�yN��[[���`��qb?������B=��K���4�Gg�Yd�1��||�I���D;�,�e��'~B�q%��e1��E���C�O0��x���RbC�~"3�4x�L_D:
�"����j_=� l��%������o2��j����TQ3�����(�&!��4�av5������q����N��&��}b4L�
h�BN��3B���6���d�2�������a����|\z�������y�<;����������i��x�jb�5(`�#'��1,I���:�u��I��4�-�j�e�h�� 6]�����gRH��X��H�Ou�{P����C�\>S�	Y�=Z����7�<��[����<�Ox3`)��V04��Pm�!������e�IEEq�����Q�
������#�Qi�f�b(�!�P�h��D*������0X��c�
��a���K���8 ��9����5}
��������h-J���>����sf�.~Q��`��F�J#�\o��$�Q_���|�2F�A��D���pK�3i�F���hWZZ`v����s0���Q3���Y����U|��WVR�C�\��.�"8�����3� { ]����"y�����Y�1�o�?����	����TN����������a�k��� �`X8g�RJ�����9+��K�g�i4Q^n��fG���,j���i�vi���P)����H�;����3��������J?�w+����-�����z��	B'�y:�l���bi*<cT���o(����4����W����|WR�)��wr3C��,���!�w9>
�hG~y~~����R���v����c������y��B�'_�\]��X~c<���0i;���W#�����Z�R:��O8k[�-��Vf�.���O
��.�M�9��y+�*��x�9_�ab��<�&�E�#.��������9��,�/F\���k����WRo�Xl���b"ak=QM�.����)�d��Hv�>6������������B�W�}	d����j��[��5�b)�W�)�o_��?h79����^�i������A���5�J	tz�b����6��e��f���a++����;���i��Sn0-�)-�q�����vE<�~%�������u�a_b��UHr��=8 �"4+Z3���������/��0��1������t5�\���=�6�AI�6��9-����Z���}h����/����{��+#�;����jV���?d�(_�g1�?��NZ7���Ae+!���Fe�t��N2��t|7����C�{��q9~9����X���d��r��
xt�
6?pi	l�0I����Z� ���.� ��6R�=�$sL;%�������R7��s �!'�?����������	�'�s������yj{��./�=�2h����B�]�n���eY�.u�,I��!��s��M���8�+�k������*S���.����w O��d�S�	��~
.�|k0K:���e�H���e�����E�JH`u�o-$`�9g��A<��O��zF�Ng��@��qLZ@��E�������`��J�*[�:�����V��@JP%sP�����N�x2���+�r\��R��U�z�$M7z7U��5������s|v|YZ3C����=!�[B9=�eE���Jnw���Y����k��I�g��{�@�����������w��7�VS���X3Y���/hk�q�a��3*��++{`S+����s�����R4����_X�����heQ�F�@��+a����x�!�2�.:�(u�l!B���W��Kg'����A1S	�#�ex�Ce�'
Z(�����YK��u�9��@!]�%�����YS5%�h�x���0��a��������T���~��D�<��By��F?_���>4*n��
w]N	��e�����8-y4���1���X�"�Rt�q�~�k�JRn�P1�6bg*�{�p���!��C�8.]�cI�XV���Z��Z�����%{s�pdr�BJA���T8|{:v�J�`�/�da�y;������Q���������=�����,c9�Q�.�T���{^t�\�\�t��7��D�h�r��4�����]�M��x�&m��j~s�U���L�F��'^��O��Bt�z�2O�%f��<�&_�EEqF��������2N'��v�W��"�����1�.�h�nt�����������������*$@�q�p�Z~���j�"�"����Hc�G�c'+����<h� �����N�d.|x����Y����+��n�n�{��n��7�eX��@�L�Ki9j �S�D�H���������s�����_��1�$�*�qbL�Y)�]5.&�����{�����p�P��WU���(���:[�9?��b3�n~����F3�������r�����ac�O�D�H��d"��h��^�Y�P����[�h��:��Cw�W���sj�w�������(����$K0���x���
�����qK���LK�4aD�e�����f����Y�t�n?�W�@h�A>�*NL��<��^~w�����Gi���
���=������i*�]�?�Y���	�)W��5'��~Y�5�='�=���,v��u]mS|���8�7��Z�#I���v�ONx��w��b���P��6�
���W�����{���`;X�pGla\� �m�13��o�o���<��}��;��6��e�f�b3�J'7����`����   w�5�-?�j�$�����V*�{��q����`�_�����S�*��G2�O������"����s)�7.�[�.����|��^5N�O�w)��y���l�NeH��:��
Y�����
��BpR2����l����I��p�	b�O:�[eh���g��^�(����{��g���__�7�s���AD$ BnL�w�&t9�'n�y@��%�����S����K
�yX�ev�x�KlY�C�����K�*����m���*���V������V��?b�"V����|�x��*iggPT�����XF���$�1��E�9y�%.J��K�8�?@!#���FR�")0�C\��$��:�3s�bD o�*X�-i�������kZ�>�:�/9Y�]���7i��n|]/�!�c,1�[-��q��>��]{�u�`A��b2AZ�U�-&��}P%����x:���R��$�,���=�`{BV�u;$1&E3?`l�z���)��?n�D�j��r�X_����x�S��F��*�orf�Q|*�������z��x'��v�R���NJX��� Qs��&�uV�������8�
��rCg�a���]6��`-�O��0�d<��=C;�������I������X�E�L
Y&��(��Az�	$��W���H���>�H8)(�X%{{��������~0d�k�������(���?%�`	@I��Lm0-�L��@��y1)����2/�
������i�*�F��'�5@0��kV{_G^2�\0�p`��:N��<trdKmR�-��6���>�z������>^{w#��1�4�3����}6����4�5�h^wTaG��/��c<O#�{W)��.�Q�yA��>�B���_�&�����V����e��F���
�f�zZh�kO�
��y}M�_��)�{R)����x�#�������8���{E�j_�zu|p��.Z���4�L��"�~�Lba��wY[]�4cIw���	:J^fSrW^�h=N����[&����%}�2�;C�DV-���kW����bL8�h{�F`l\��T��R�/�,�h��������w���7���-������?�p�k�q��#J�����qi2K�8��H��fY"�SKt��������1?��;���H@���ib��K��3Q��H�����th���:� ��gr+�%y�e�nc��$�r�P[���N���/%��BE1����6tj#���+��i�1���x�E�Y�TG�9�M��{��!��u���;����������ga)��|�C���M��0a�WoZQ�&>�\3�>"�[�{J�@�����%��?>���\I�?�u�I���0�A��MY����1��B!��zn���i����P[x��^�K-�����+��k��\xd.���e�\}��s������su������I8���C�,�9�������N4LB{�L'H���X+���K����Z7Rv�S,��#����Y��^��Pr�����H�����F�����X�4�pL��;���6�ohN�)��yrD`�`���������;�A���"�'`�<��,X�v��G#U�p4iIY�!�T�~0�C���V��
��e�7���,5�f�}��s|�+��R1�0���;�M��ptW���������JY�|S1�g<z��)	tk�y�ZZA�����V�K'��	j��^��)1��ka�^���v��n	�
1��4��b��e���wHo��7����+����5���<rVE,_h������D��m�V���%�0U�}�>����s�
�^@���&RS��X�
��<D�L]w������*��/u�k�������f�
� A��Y���l5��:n_6;�K�.���x�H8����'��<�>7�?���&�	��>����|�������b�<[��B6_5O������Z�Dg:����/S
�?l�_�XV�L�`5������L��������i�I���4�i��USqLa1��S�t+,���U�(�s"�%l_zr�9�q6��%�����=���t�c��0F�k`a��P����#�5I6��6q�����r���C��ZL���KLM��?9�������0�6	O�q��j\����V��Q@��3��O�byg)#@C��3��[�������0}>7u��T7�<��@�2
v���B����,��O$6���
��������(+�%1zw�%���o�{q��Y��I{c�����_�b$r<���~���z��Ik"���xe�H{�Tr�.3N�xV'y�/8��+�l�����VB�[�,9{}b���-���������Jydn!E
�1�$7������}�6�o�kI_�9��eW��b��P��������2��y3���O���5�RZU{%s���
h��M�ek�_�I����x�j����?\�#[WA����ba���."-�	i�4�����R��r^f������_g~��K�^.F���	HV�	|�O�^FZ�������?U��X�d�U���$��k
)���<���6�DS#T�G��2���������8�YD(O�W1~���x�,��X�F����V)�`0���47`�ed�����F���p6��H'q�P[����AJ8���=j����j���D�����������/��,#���n�y��o�CV
�Rt��;�����cqy[~��4}[^��s���U�����`H�_ !�N�DL2�4�R�:H�kIV���,*]O���[�Lo��;��X��R���%�*)o��t���D/d?��l��������K�oR0�Rq�K�*>��Y>����8�F�Of��.���$x�q}lk�4����D��Y�{���+i�D�z�����06��S!��D���p�7���~6��E�!��x(S��J�i���Q�3_��kG��A�B�����id$||���y�e����-��BrD�6��������	y$�m���@��)�~?��	*���X����(����*j��k���.������bYnf{�b�]����{�w�9Bxqu����w1��8�cd�����@����+�����]}�S@��~�i|P1d�B��Zp}�U�����;K|Eq������/sA�b��U��A=�����<�����g�����$shBb&%��M?+��L���������%����s�����V�`
u��QXm��:	�����e�|�^�O����s��uZY����L�~%T�����
����" �����{����G��S�C�B�Lw�[�8�T�Q�Q3v���H3���]
C��S(�y�|z�,�S+�q	|��	���������M���1C�r.�m�H:e� �6~�-���s�h4�
����x%}G#d�o&0�/���j��������F�w��	�s��c]b�c��]�����G\�W�D����U��4m��

���'�Y��O'���B>��M�K��?�g�V?�S������t�c�Nu�fu��������X�U�.�]D
���������>�m�������:�&�����I���jYMd���K��~�A��^����x3�Cll�<mE��������$
�$$�0���T)9��5�������]L�k�?�m�pn��b�`���y�"����%�T*��r��4{g����=�����8����{���S��<�}.yU1�����|��W��G��GaTQd�l ��8bVV�T�{q��������%�����Q����:�{�/5������I���$��x@�f�I"����WZ�n��������YoP&k�u�P7Wg'�GG�C06��g�`�	2	l�C.��eR����ay	������-�����c�7��VL�A�=�kt��j�����\���R(�����(F{f,�T0(	�U��@��^jm����!���c���z�	)O���#�o��Zb	u�g4��Yc��a�40�L�"$e
6T������}�4���Y>��Ur7��_N!��w5x�v{��V�]�lm���A?������P-��a+�����obA��@9���$��S(V������g"������Z�`�V�)4iu��Ad��E����xz��=�������Q��Q��j�6�����y2�]���U���|�J~�|1����]X�]��[T���zSV�����2+`�h)w���<zZ���S�c���	�_R���C�*or�<i^6-�a9���v�����zH
2��3L��5&7Q���x�Mp4z�=v�$}k\����ZR~���l
-Aho5���C�/����:9���z,�[8��
[�)rv�t`(��x�v����-�
�I�0��������)�SGM�=�A�L]���c��4m����[������_-���u���E�]��E���92l�A������lb5A"�1�;R&�85�
�3�QBa'�l�\6[d,��-cnm�Q#l�����HW������J��A`W��p9��O&I�\�)�4L�(�� ���:��e0�� �@o�#�y�F�B��[@���<!@�Ko���O��PBv\`A�y�4���
����71+Fu�FQ�q�H�(���M�����z�����y��gJ.-	FY$%$��Gt�{�����;j�y���h��h&�����s�#
-��$��Y�L��x���]���T��~ogo��e{��E�:��I���Md:P�Bq4n��������X� �b"���u��Oz��cKy���4*295x"��X�p���<0�/����u|v�yu�<9��D_��3 ST�mCeV����q�hqa��my]��!*d���YpW�-T�?
�@%��������#��r��)��>B��r5��@����G��q�-�+�]��,���/a(R8�RS:B��R]vh�0'����6���>�J	�p���.�yt"���n���C]�,�s��[ �4�L�o��]@��_����x��zoo�E�(�T6�$!�/��Ny������ju����YC���h���d<^��A2(O�2�~L�@i%�v�;2)��������3�YD]�B.��;��{_�8�+�M�m���p!�*�����Ky��&,2��A��%��$x0]J<X�� �m�)�`�lnn�19��8�TG?����2����u?���9����j>�f{^DX�/���W���P�z�V�<b���t {��vw�R��n��6�?�����-G*��9���N�C�Y���@x�6�f�k�#<��fvJy���y sV���r�R���I�
O�J.Q���!n�hz�Zr�F3�u<�r����*5n���J�B8>�<�p��\I��;���������Q��y��T��XT��u�Tn�w>�WV�������xv��y2�C�fS�L���#�����*G<�I�;�ON����4��L�A?t��w���|�9���)��b6\�|B�@���t�����i:0�J�
�q���G��#%~��Y�=�@7��>��8�z
m:�;��=��e�Y|+���������*�d4�O�*���X�_�#n�1p����
��4�������A��;LX�uM�[���;�����OF�$n��qr�<�K���Ci��K���my������Cqq�8�'�����S��y����C�!�������&|�3Z}��Z������6V�����vU���������������8��}���_��������������uiI�d���-��"��aw�������$I%r�2K�YU��������Y��?p�X����c�����{d����3����n��;�E�G�==��������'��Z����~�\qw{�\G1H"���H%����������C�C�����^[{!����xR~"���!;_��V�T-aol���W��	��O��^����uS��)F������`z�������,��SS'�{TZ�.��u}*,����������T��^�;vM�7I'��x6(�~�R�u`}���Z�^{N�b<�Y�>�c��p4��qv��@g�$��g�q����N?(t��`H��5�K�>���d�T�q��I:���@�<R��6&����Ti'A�
��
j��������L.��]v.D��h���@}$�4l������SIr/����,�������#Ia�@{����E4��k�[;�y��D��k����M�u����e7��s���YeMmb)%�
�
����@�((�
���*�-���{����]�d�^���g�p���L����Tj������A�5]�fa�$Uv��k����k���F.���$0�0������p�����o�/_�(�?����		�����}P����aWH�w����ZJV��Jn��0Q���V����[`����T�F% �+�Dju�6dKb�g'���)�X���o��:�"��^u�?���F���l]5UO��1TW��@�.�;� ������r�.-�Y��z�������:���� ��nf-�l\@5�/��xZ �ev�'��9��7�Oj�.��8<d������_�iQKp��������C�!��������GKrn�����J�T���r];Kn�O�6���so�E��~���s�����������^��AS���CC�}���R�����I����G�@n���������rVa��V��Ho6�u��^2y)[�Z���t�=}���w+���`�V��k���Yu�W�N���d+c�kRx'�Y4Ri|�5�1(�����(��b.H�&����Qa�X������_�`���r��_�R�x����D��UFb���M����"f�kk�s���-�k�q�~�v�C�^�Wo�
��/������-�.��.�'?�����_��~��,����a����En��r����.����4�"�k��w%L�������	hX�2����$�_��m�)^�J�5
0if",^��2�����2�Y�Jyy��a��a��a���.����d�}��@4�Q*F����$jt��P�S��i�s�o
��SZ_���t�h��j�y�WzE����f�
1i�������Ny�h.�0oa�yq`(�aY�?m\~s���&;���{��,K�������4�Y
�g��N_V������[����\���z������yf���;���A����6/_���Q���
�*�*����������m���*��'
`}{�^|{�\�Z���?�,�����	��_���/u����[�����s|}���������n�w+r~�\�H�>
�nC��������e����C����`Z��@��"@m��t_���p�� ����_�>�����k�a�5���^us��W�����z��|-�G�Q������V���;H��n��%8O)G�~*��f���?-��F������ob0s;������L�1�w������\J��q��}�+�����q��?��|�h���4���R��}���8�Bq�	�Q���$gG�X�OL�mvs�c<S�5b�� �1���k��r��N���<�
@Ie����pe��GL"�Cp�XQ�Tz��h��Q
Wa&���0`Z[33��#��F3�2��G2f��>�=l����*�l+���iq�����y�B�kT�bv�18mu�>@]�E���j8D_SK${D]MU�TP�	$���w��X/���	=�:��x�!/�hs�S��h���LO
f���+�q�����k��c�����1x������9
aK��!�Y��������x�l�4KD}`�f�:�X2��r�w���'�&�$��R>�y*��B���7��O /vms<���������BoMH��m�;1��{�/�
����f�b(��q�|:�N�N����5>�����w���o��E|N�^�SS��8N��I����6���@������]�!�S��P�Q�1p�N�Q��v��R��r8v��@�&aU�k�Sb<���&8��� ��[��)�P�T1h)�����9�cX^4e9A�

y�t����@<X�[L_�#�&��T4*������-��hbS���JJ���mn�W*��A�Z+���M17cUD�����YOp�_;� ���;��;��uQAUy�~�g��4��M���_!��SD�:����e��M���s��QjW��l��������, c�h�������m���b]tl���J��������<�
�k��WT���lk���AgR����>����)9����n_���6p�x��Q�q)�<]�D���4�M�w�x&;o��z7�	$���
�tr�c�+�u�;T+7P
��&m������&���{b���7N0.��g��������;AA��?�"A2�^��IB���<E(�r�����]��EN+�n��pu����(�
��'#�I����BP��`:@��^2�
;��P
�e�+`�>yTQ�Ng+X����t|M7E\&}�J&��JQb8��"���xK�Uce�����x��	c�B��z�"P��
x�M�b�>{�{�u
ch��M�`��K�G�o>6Y��:���z�NXL�Iq�(��H)���s��������G����
G]@~[��@j,T�$z84�|'Rp�GQfjU}�
5w#�d����2L�q��"�Z���T.��+L�����/�j��e��M��-Jr���H�mLq������r�_��������1�hJL�]�mo�n���(�0�
	B w@�.!��.P�_Q�X4���*dN%TD,���$�3V��8�LY���?/A<�v��j1����/�i���i,���w�b��Tb�>�����R�����K�)�n�y{�"�}�m��\�~����~�~a%��)��!$X"=o����N��]��M�
�CReA�(�,���U%�QyX������7�,X�I{������|��p��K6'��o��5��l|�2:qdU������U��� ivj�%ob9	����&����^�y�����)`���A�;m\�xM����P����*/��<���+��h�d��uu��WK/�CR5[sI]��8�,��a�����X1�EJ�Q���;���@~���c��EC�(���	t'w��D��Q�!9mH�t [
��T���)a�{":v�LV��sg*��!�$Lo�g������t?�WQh�s�t,���;�i3���WY����- 0p���vN��?5���a�aXG���,	��=[2\x��P�Zp��@br��+�LO�Q�/D:�	��|��hm�AUS�������J%��vw�CF_�:
#f�*9�����/��v$�ZAL��5.�����N��C�Z����2���[<��e����[���k�^y~�n'%0����>x�<m�b��*\<ej�:o5����a��!�m5..������h�X�k���]����������8X|����(��
�(+KFev#�E)��`�tt���6�v���s��{t�R�+e������@1�G��7JIE��#�x��������~r�~A3�1ia���l�,��*���l��"[7I6���[�r�o���-�������L����[���J����~�ps�����x�PT"����E�!7������G��]:�j���$O�@��fp�Z�~3��1��C��~zJcA�m=��2��{0E����D7H������n�A�w�AL?���5��N�E���>���^M
�B2���,��PD�`�&��M����x�����k���[BO#�BJ��WQ_��G���|1gmJt�G���S�T����v�/������2-�g�O��G�d�?��h4���ynt��C��j-�z����l>I
��� ���,���K��������E��-R����~��
�.�%<x��.����5ZvI�O��bRQ�.�����,�j��>O��#�D�-�N5����������N���U���II�����U���]cU�7	V�Wh�2����2b=y���{q�j�;�W'���+n�k*;��z��N7��Z.��7G�{�z[���}��Iq�Bi��|�aR	l����=Ya���PF�\���9�F���PE�:��	��*_��������\b���?��.���j:X������V�2�2�X�2����2��u������O�Y�\���cBd�l@��:�a�S��Y��w��<���W�#�+f�qD���.����Q���k(��Q�=���ruczh`T�CyF�K�����0��z����XY��>�-��+f{p1aVb63o*��l&�v:V��T�TyQ�
����co/�R��R]�=����u��q:�)���@6����m������(Y���*���DW��8�(}���$�S>	pF�|���x|$3F��U�O�.����6�#�(�K<l�sv+}�O&@���/<.>�������ft���P��r���<J����Y�y��Y����A
�������L,L����,�3��R��������>N
�������i����Um[c���a��������LJ����2d,lxvh�������P�Q^�{L�,�d����XW�KN����d���s��%eE�������"���>��*�}KU	R��w�B���H��*v�&e�n���sq����*G�sa���{f\QuZ)�sDP\�e�pH����j����!���"�#���2����t�F����nF
���.����a4��j������3�N?V����a�F��6t�%���'C��X��5S�����?��g@�����X�`��<<���`+��\@
���)���Q�/���3=M���cqxuz�����g��S�a������w(��D!E�����s
�|��f�Yr����B=E���;�T�<�R/Pa�:�����zZ�\X�]�[O�A�n�	�N���n��0�_����������!�Z@���x��j��,s�@u��(�F�v#���{d�P
��=�
3��)��|=�vJ�h:l��y����X��XZ������&��j	��D06:���X2\�?��@-��E�IP9���9����P?����*�������(���`�b���j�����V�\-��@�{��J����B��2��o@�6����yu�k�
9��h�>����I�1:��x��C�W��gc���u�����N��-���3�d�C�����m,'|;8�!�&<��_�t�	�#{��Ge����L���L3
5S��1R�\@�uI��;VW��K�6��}d4W�/��c_�=�U��]g>��J&bac�S �)D�V?���N���.Ru�g���R���jm�3.��%K>���#n)luI��g=�4y�����Hj]�/ ���@0
����6Lu�k�t������0�7Q���������j"u�<�:;l�������-��|�I(ctCy�!;&���vvj��������Q��e2�#�
���J��_-\�T�����VSk~��e�����z�I�qu�CN����+��}�X0PL���3AAV���v��O���-������fs@����w%x�26�[y����Ha��@Kw��_��q���v�?��T���N����q������B�_��D@�"�K)�%�Ye*p������$_�ZE�)?�X��	'��^�f��O`-��y��#��:NJ������������w��v�A��>���T��t����/oVm�%tg�	L������<3�sn�<�g�C��4�~1x���z����~��'c���������/���u�~�m�����b-��W4mI�Y�1W�Hx�B�����jh������

��G�SD�i��{$	�4��1:� ����C���$���S����qj� �|$���$��DJ���h���Y���2a��c���P����4�7��
��u��T_v/:���b?�t�,��J@����Nv���T����F��=?��~���,�m�
+/��U��Ce�����\q)y jGT��;�g�x'M��"A��g�����V]�%�xk�����Y��(<PT%<Ujx���TqL���]C�/��d:���{�<E�;e��
�?t:on������l�U�����ln{���D����?����%P����{s��/��{���(>���-d�6�)<���>�b���t7�8k�9CV�2��5�^~�������U:>����|>_31�d��)�[��I��(�YK�,/�<xZ�t$��D�������������?���|���*��h����
�SH'f���zGq}�Q`�UT����~���Tz{���x�Qq��0��TC��*��n���3)�b�z�n��4���P�F.�Ud�a_1���vUZ����/^���O����R��=��2��@{x��T*n���R�k/���I�u������2���w�
h��Ot����@��L��?��������:��|����S�Qq+��I����!'o�����^o+*O_<��^F�7�<�����za��d�����/�=������A;��>��[Ut�����\e�q�C������y��C����g�o-�='m�y���/����(������w���������k~�w�)�Y���k}3���az���;�M�����Yh�n J~��/^u�1���u&�y����U����oA� u��0�e�{��`��F�������7����������??�{_+U��e�}���[�
����\=��7P$��do�|�:��;�V�1����KF��q���i���E���z�Xd�]K��F�K�HN�����P��
I�(\�4����S�n����'�nM��
� w�)�Z������������^<	���?~���
�]�]���7}]{!�4���0����BR�w=�<������:L�e�j P���>8d|�_#���X�V0[�b���*�$3�����D� �����i��S15,��w��Ln���yhtO���9t[��d�j���&�'�v�*��V[Q���^�R�il�Zxs.���f���{��������R����=����)_��e���1s�R)f�I�JUn�g*�s����4����~�R�������h�����-�Xg�N~��B0�u�N��XH�������NH���1�����>��v���:���k��-,���p�0��}�P�	,p�L�pH��X��x7�;�(�S�[87n�h7l�1wS��������G��Z=�5�<���Vkm��d��-I���T�z������w�,�]�VXu��)S��j{�m�V*��f4�He�
`�UJA��8(�����/���1�V��h8J��O�r�U��h3UI�	���=�sd�����i<��o�-���_�&�U�m���M�5�e?�*����v�����[�4_]��{~|&���~u��-cIO�xA�����O�yK�����Z���q����fL7� 1!lW����(\3U����A�^/��:�%[�c]�Y{�L�}s}Z�u�)�z��pi�����$����u}YtJ����
�oN��7�v�-n'�;���&OR��Q���;MA�i���E�r��kTB����o��U3����N$*�S�iadT���a�2Qs|�=
�����W��oc�SI��f�u<s7j����������3�6�%���&h�s�|[�d�,�7q�fW(�G9R���VW6�7En8`6�K�G��n�R��s�!��9�m���r>�I��L���L����L�PW����vHo�R�K2���-gQ�|�^m$���
�����Y��|�%M��n+��9��h���<��\[T|\gM�����%^�oE~tg�Z13���t�_���f�����8��x8�7��ZQH�*M��������!%u���������:�KNk��
�8(��1t�]M�
�w�I2)����N^�9�8����U��p�q��j��:8;���oS|$�b6<�g	��mPB�~�,��j��.z8�n��M�%�*_8{��s����:9�������S�>�~T�������Jf����3�N���pUMcn(I�Ix�W`"�5�g���j0������3���$=�Kz����B�'�+4h����) �4���n�v?��d��C�)��DP�b������t�=�1?�}���3d�2���9w�{
|�)@���{j�������w�h��������i6�B��%r>a���9f����7�e%
�!3����`��1�d��i?�&��Y�z��pU�����"f�cmv�����B�]w�r�*g��e��(Du;���?��=J���h-����9Q�"��HY��l.`�4dK}q
����Z���{JRQvS!O5\T�����ma��}�.��$e���OaA'����N4[��$�
�-R�bs�%��,�a��{����(~��{(M��q:aqr�'�J��z����i<��A-�T�_�Ac���`U�!���I��~�������2��/���[��\gO~�#�C�|��u[�NVP	Tp�9�2����D�F��OI�������|B���4�8�Gk�������@S�������Z{�+i]Z�*�\L,%���������bO1���h������N����V�9�\ �>T�%��B<rTfm�����e�$�	'�889@A
-skp���W��j��]��T�j������5���z���R����:|��;K�*
�z�JIs�#PJ$��L�'Z�=G�W�N/,����G��X�i����w��f��u�e����z�&��z�n~v�{[�~���t�^�r���q	�u�QXw6oL�Yrs�9��c����2kJ.[����ME�����$?��h��$~�h\^6[g?��]����I���Ay�L�U��Ue��2Z������$X��9�J���:^<������������S��h���$��(����^[�r���������p�i.L��/�c�����R��enh$����I>~��6�_��,������zo��S�T����V?/KN�}�����;�5>v3^��k��`���L�K$�t0d'l���~[��C%��������:g�2.xO��������0:@_��V/�v�%�A�\G��`J�d�����L�Pi�����o)���9�4N^�E���1bIv\��TU��JZao��_����`Z����&��.W�0�9��n�J�z*������^p���Y]BS&&���Vr���O�t�
6F��aq�GR����JZN�=VGG���0��''
����7�V������6V���u���I���������3v�\E�U���P����y�zf:���x3��-Lu{�:��������w�E94�wc��O�{��'��#�c����qy.���*���P+��������gT�}��y
�T�"���^4����\~�bY�p���s��2�����5��Y�R�@0^x�������++c�b;`J�99n_�}�lo�}~�6!S�����rm��PW[��+��HT(M���z�������8d
	�{�%�-�k��H&�`R��%�P�ZvW���5�E�a��
�hV@+X�K�����l�Wu0��I���"���Ag�y�����2��A�L��Z��;%?Tr(�P�1j5�������>���`W����r<����L�JQYC�
l�&���%"S�:��o�����BlwtI��NZ�X��E��{�R�,]qP7���)��6t�
�
q�����[��>W����c{��a���	F&�����@���jJKO���2!RG��������=��d�W�An��p{�s�vv=o��wt����-�����i��a��8\���#��h����F��Ym���b.�0o���5��8��i�1�����jiFQ�[8����J�;�T�Aa'F:(�F��j�F�����V������nw4��IF%wkB� UE'|������tN}������T����O���n�r*Cd�r�>�~�I\�gm��Ou�h[r�hb%���9�(D|�[SA���7�����Jww�W�o-^}�����*Q��mJQ��V��M	�V�suv,iW���_��}'��6�PK0��X;�j�,@�w*?��1�����\����]S}f��y
�����M�r��L0_��%|�m*q�[�r�Q�;WY3��Z���g��=������SNjh��s���/9����G�c��IYI���e�I�]�S�-wO0������L�6�X��\fZ�H������f�\MFh�O'I1���1�=� �F��2QJ���
,�p�1$.���i�'�O�A_^���4��� ��=�2�r�����q�R�w��zo�����OLt���c"g�$�:j�K��v"�o��9ZB�"Q�'��r�&��8E4�#���}!	TmK�D�\�}'.�����+��V5xq�
�3�VL�����buU��V�)K**_�����gI������:`�C��A�!:�t\TE~�
:`��Q�g�����D�PE�AY��P���-�Z��P�V��I�����i�9���
!��4w�j}��]�� ��?e0�2����nF�Mje��Ed���.�#E�6��{b
~1qo��:Qo6|��N.N$j��
C+��c�l$T#��u�,q$r
�HA�q�9��R��@�������w�*�m���a59�U*���n�:@:$Y����Pg <�rU����@�x@��������q9}&��Dh���,5��L
E�{�H��'����EJ�5Q�����Wk;e	,7z��q�n��P���dr�|4M��=m�E+���L5���m)g�@[u��H��R�W���/��E ������fa���S��S�����)G�������D�ir����\�������}���4�o��d��#G�xm��hE��
R���P!Y��} �X	thJ�Sj3�*B�&����x�P#/�+	R��������F��X�})L��
s��h��y(^�b�I�&��b��(��+#�/���7�Q�������3�Va����!Za[��$�zCn�=�[�G�\rhR�Nn!_<u��9?0���aUAKT���X��5�s�1u��M�Z�d�`Lc��5�&T��HeQJodnk��mJv��Mu�d�)�7���!�p"�(�`h�5�g�<��I1`f	���b��4%BM��2�tPp�R$�R��jC���x�d�[�HJ6���H+�Cv��M����h��NKP���*�����e�����SVV�+j]�\X�reekaM������5	���3��-:�����o
W'����Ll�I^hp��E��4R%N$��~y�;������A�E@��L�gC"������tQ�����_��uM��Z
�S��;��C����w��`
���Z�48i��1���3��d��f���������A��}t��Q��L���
�>���b"����8�qxw4���k��~��w�\H����o�%��d\���65��x;�M����'h.) P�����W����($�@���]�7�	<t��`�@�g���6����WD�W��I��������w>��~����	 (��
0
;k����|u$/GPP��4
���
*���0�{����ce���`g�
,g���*��0�Z�2-`FA��X�������s������t��J�,�j���f������lQ�mm/>��������';�����L�YB��O��`��&��c+����0�G�y�p����m����TK��X�a�mr�+.A&���<Z2G�I{v+��T����i��w��� �i+�M���|yw������1-����?+�n�����C_r/c�qf
�^�����l.}��n!q���5��S��hj���^�R�����F�u����
��d�769�/��Gb��C�s�,/�VA�z�/�n1����a�p��&����y>Gm�Ccuq�j6%�y�l���
,�;'��J\������J�V����-��E��Jh��]��W������E`���e%]��j��|o=2���7�]�)Z������y��$�:N/Q�m�W���.��9�p�/��x0��O�j��H[i������H�'��\��l���1��fBA\d57�;��J?��l�y���;��=��(��J�g9��Q|+��wbXEqKVg_�w��`�'Us��P��kf��w��%�Nm��)�����H����/��0+�����~������!x������� ~�E[�W"�$����p$}�'���$�
��������V5�U��	��������y����^����Wv�J%�����-~�,��kP��=4��r�!)Q|������ai�8���-����:k5���PU^{��Lzq_���TY|��2�6�kw-1�Z���6�N�t�l����w8=]�_.3?�\C�V��h��q�����a�v.��g�Q�,�e��V	c� ��q�7�{���n>v�m��e���������>a�sl7zl!����<�'����;�E�!�2������������������uS���
�����w���4�W�K�eS�4�'��;���{o��Mt.���������)D(�RL��M�{�|x:}�I'��0�'�������"�-��s������^s����@-�&J�Y�;�����t���|:Js[,�����(o+ro�Z�>��J���5�G����Z�mGU��f���f��D��Puw�$p�K��M�����i����R����M����Zf�/�.A	>�&0�
~]z�_����s��`AC7AV	/f��G5IY���V�[��6o)�MO
������Zr��+�����%��B(���c���H9}n���#����m��=��i�n:�I�d��GI�
svU|��j9$*)�����_��!�_��&��f�)1��-e8��4<��� SC!�����`���g��5g}�A�FK
]�o��7�Z������v�~
F�T>1�O��1�~S�����$�iI&���)�u�z:�L
�.�# ��/e��
5����Gv
GM�����8�X���;���\�{c'�v�}I����nw�@y���`��*��j�H���Aa��P;&}J�d���}�3R��7Lz�C�	��"W�v���	�3V����� ����g������ 7�DBpL���aKL���5A_P��Zo��z�vH)7�I��"1��![������z�{�����G��A�XUC�0����1��]J����0��]�j���������4�%�i?�Z�Y�"6q�*�J�r�����N���+������}���?���g��1 ���hB�,�U��r���8'��a�T��n|�v���A-��g��l���������%z�|$�O���uxDI-N����D�����x�����l� W����)3����4���F�yr>��J�.$<���|,w0y ��Ldk��0O���iV��������
��������%������M�s/O���CL�o'R~�`�V/�|�����`�{>J�.fz��M�c�
�1�gv?������J[v������}��3�X������Oq�Q)�78�k��`\��MB/,��j�v<E��txM���,s�&y����U�����oDi�$���E7����5��BP.�����j��K�~&��-���3����y����O�� ����Ofx �OGl���v���Y�!���y��p���P����mw_D>�����q|"{"�Ox�'�U�������Z�	����['{�W��@;��HXXZ�?-03t$����CC�M��Fw����is>$��I�5�Obb�jj���>F2�E�����FB�c�A��c2��p|!P��Z��Q�O85��_�@EQ���$�����{hN#��w���W���k$�[2����lD�w����|/8����[B0��|����N�-
�������F�'6�:�~'�y���<�Q���0��c������������{�����Q��V	�xL���	mV��O�Y�
w_��������er��c	n��m��w68�%�X���8����u��L�f,��j?G:Ka�(�9���i�����|���U*�R���.��h��F
�5������5��6�Dg���l�Z0��G���#�
��!$rZ��-\������vr�i�y�#�����#��Ub�]�_,Pe��������WxqS�h���5ok���Jd�)��9Xs��l��0�,��������NQ��D�)�j���E���/�s�`�;�����f��2f��2f�0f�?7cV�`���1�>Z�~��jJ�����$��.����12$�xv����Y�
_V��e-`F�_r�_r���F�,���#��}�'T�^��H����u�-Z��)U�S���F��q���g���MI,P����^��Kn������e������1`=�����/bu-lu�7���Jeo�[��<^���B������]�����-�
���	�����f����aB�R����VU������&&%����d�\�Lp�Q��I������g7���1�{iR��1�|�k�V:�M_��Sr�>7�����h��R��f5��mA��R��J�'��e!-����L21>��X���
����M��/���D��d%aF��)T��9�Ds�/�4D��?�����db�Q���Z)L���<�
Vt�BZ��
�������.���h���Fu�6�+�n���;{��Je'��������Lw�6}���:Q�����~t��g���D�� ��!���'�z+�L��	Z����e�hC(�	���F�����)p�J�{;����W*���n��`
����>[�� �������l�\P�
�?MR��]��Q�#�7Y<���,�G�&�j���R����V���$�U�j^Ge��k��n|�&�i������u����ar�(5$x#Y�k�vF}��4O���
i���*�w��jKY3��G�������B�X�L{�?����L��������M�CZ��>7�2���?�����?i��)��7�q]����[�Oq�w���ws�+�Ot�Vx��Ue��{�tJ���}���$��Z��z�����ze���������/K
0002-Add-SUBSCRIPTION-catalog-and-DDL-v16.patch.gzapplication/gzip; name=0002-Add-SUBSCRIPTION-catalog-and-DDL-v16.patch.gzDownload
�e9fX0002-Add-SUBSCRIPTION-catalog-and-DDL-v16.patch�=�W���?��b/��j��m !�pZzS�a�z������5V#K�V�p���ofv%�����������3;;3;_�Z^���9�oY��������;����C��;�v�Ag�������l�����:����u:��W��)��Q�~������p��"p�B�n�<:�8�"�������e?��w������{��F�m���/��������'?�^{��;�y9>�:��>�8g�Y^p�,�a���76���s�-B��f��U��;V��#��@�F:����y3��5>�����p�������_B���XO��K0P�{�T��C��C��jx'���#|��f_�L�x���;��8����k�E-��},�o�Zi��B��e�����I�'�=Gq����6�������-��wP���]��Wv��%�������j3l@3�r��Q�a�5*�A�b.{7�\�1���ea��R;+�{�����w0;-
;��:0b����,�;��Y'g�����&��Z	$��FGz�������$
����I���r�E�?��~WI��/��.��~��q������r��[^4��ui������Z��+�F����� �V�������;����[�P|P�D��g\�>����+���;%���a�8r=72O�l����Xn�sTxta@b�w&[
p/��Ol?�Oyb�:��`�7�/[�:Xc�)��
����EC�&�UC5�y� o�*�Ngyu��Q���� F<
�Ja��d��JJ�����aZ�m�V���uY�a9�vU�=�^��2�n	6��M���Q
�8�^:d�����&�k�>�q���S1�4.4�=���7�X�Bk^$��*���N`5KF�`^�=(�KZn��bRd2�����C�ra����"�7#�"l��.��W���
�V�R�L�j���$lj������`@w7����%���i�o3,��VG�~+`u�,������� ���D@�Nl���h�A��UZG5��|�S�#3�:�0��=���`{CH]]@X��������`U�As{��D�A�1��u�����2����y�v�(~5duTY	��VB�������ec�qg3�l����5��i��
rl�3���wZ-{o���%X����������;����_�.{�bC*�a;��6XrKw
u9Cxt���{�?@��7������c��PDalG���G��w�m��$�Bo�L��4�tT���Hn�p��0����K�g�j����	v�Fs�g�C0��Z�3PdH[���i��?@9
{��nGJj�P�.J�����u�@~Fn��O�l/�yHZ	�.�����.Cwa��J=$/i;��t��_�������.����+���0�Dq�[�I-I�"�@��s��I����6��>u� dbn��a�BR�h�`��Y�^<E��X�{��83!��k���a�s��AD��!!J�6��X	�����^����+`�	�x	������Rx<��s���y;pH+L������UR,����)��7y����;
��:r�f.����u���F0�����U�5��.t�������R�8����M9L� �{& :A'����"��3}�az.������DsK�:ya��-�9En����'�=3�ws���#]���`�����y�����C�����)_~
��;���V^�))�|�p��|&q�����ix����|����T�f&�A�9hR���>���}���0�"�
��sF������/��[������������O�\�40
*
Cl�cO,�I��YBT�����,8�G���)>���}� ���u��Q�=�������R��B�Z��h����L@^2�zO�mm���pC��
u�`J�YE��g"������D�\��hy1{����Jw����t���8���~�&+-�0d��F����0���{��@()K�kw(�X���-�8a�bL�`�!p5�[��o�����1����9_XJ�l�����'��J.���@�I*�~T������Q��u�X�1�,�3������k$����>V���&�(�=�n�;�*hY��I:�%)�UHZA�+B$V������I���Y+l�('�|��4�cQ
8�D-l"qlI��^E��
U�X�����Y���8��fh�R�R7
EH��	.7�p���&������t
��NXNV���DS���t��1��2�SK���t��v��������JGd�U����e�T�M����]��7/_���>�C����$#���x�yy|u��Zn��Mks�~>���mI@Y�(1�`o.O��G���"����t�zTh(a��1����p\_��r�G>����/��#;ysu*4y3��l<��ky���������]�|=Z+��������q����&rN�..����j~_.<:����#�?���@������ �-��[-� �_��|1M�x�
�(0;��TgV����5>/y	TXU�9��A�i����	�x*���.��lE������B�����G�\Y��ce��dFg��Q%�T��$M����"��6V
���������f�������v�
�2�������S��m�����[a����vv:n����/�7���14.Y��k\.����6�&)�!f��R��43���T�.-���V���R�,��I���-���O�����I)���	W�����/����M�p�
�9G��&{m�7�u�����;�������*E#���M�R�ZA �K.�p~$��e�7B���1�Y���4����sE�����p�|x��%�3����|5`�\����f�H����������C0����)���$�����5;@���J����0��c�������|tB��&U�o	7k�5jf�X�:���nt��������%���6�����d��jV���[U�-"g.�P9G��O�����NK��r<a[B"�o��Es+RZ�96j.����-"�	��j��.U����@�@.�1`p5yP�R�`A���^�<$c��e���I��tL��B����UQ����y���`�u	[`�WP�I*�W�&��a�0�U�M�,�N��A�����[}�IgI����X�W��y�V���_�]���g��i�F�B�&M�rA��h�:b�������l�\� �G[:�9�)��������	2���z#���4�e�N!{�o� �
�r�N� ;LDV�r4W�\L�B�]�N�}���B2EX���/��W��S9r��0��d�,�.��OP}.�G9k��m}��Z��Y�'Y��go�b���h3�@R��Z�K��.���O1��������p�B
�3�J��=�&�B�m�<�����j%
�I����)}a�����
S��IV��d��-1a���D�>�X4r��[�S��P���j��M���p��y2
9Z3�P�_�����~5��Xr�����(�5�W�+?�5#*�`�S��������=*{�����G%��>L�������JFd_U���Bc�C~�	�5�1�*V%�z��
�1��9��*�c�;{�W���B���N�J9���M�T��q}F�O�T�_����z��G4��j��q��k�(���&�&�Or������(�������;������$��M�h�sF��v�iL�K7��H������~�5��Q��s��V�L��r�U�Q�M�H�����-]�
H�#=6�lWA.�`!3�a�I+�H�c$�j��T��cv���*39�r��K����U����%�����U������Y�5�����WI�Y��1��v"������4S��?��p�^�'�Sc�X�V�Z�,�0������`'���,�����y�����B�(^��$+��{�U>".��&���K�(w�5
n�Kiw4��%����0=e.���z�{���Y���4sq�c�4��J<��H�Vd��	:����e��U�����>mY�W\�y0�n]\>�}IE
���:!���_[-KjO�ZEQ�g��6''w��l�)���%�{xn'/4^�$&?�@[#r��]`��Wx2����X�2�QT�J$�����R����^�;�ou[�[a�|�������� `}����-AW!�s����0�����0c�sJv�(��k��@���K�0�~�z��,`��g���K��KG����'���Eo�9�����/W�4�����Ys����Xq@�:���Yo6=�]��Y��^��v���R��M���4�J���4i���4
���r��>c1��m��|n!��?{��^��?���c�nU�g�	�
FvI�ma3�
���*�wT	� ���K����Ojw��x����U�T����A�@�lASmxYY�3�q�.%z�6�������"X�(B��f����EQ�+D�54���;td$�}(_a���m���p���L���IgJ�8	?3��;u�r��|���I���S���Q��F�f���+Ck�P�9q������S�����rk7�f�,���El�gZm��b�2�������%���0i��o�aV��s�����r�STk�N�����d���K$u��/�|��U��#E����
���^����s�V�����V|��P������:1u�Pe(��Uan�g�l}j����Q��y�-SK��e�Y.�=�|��S��UF|����W�#U~�4�]i�X	(�/i����G�o�&��N7	Yo�t������j
����V�RE�2��g�������=L���L�s=K^�CJT*����o�0x��s|y����,!4����A5,�����W��+�������Xq2Y��B�E�lW���#�`��r�5��.5R)��;CGrp
4&�h�����N�u��$���vp������������r3��w�F@a���k�������~;�sT+#�l�2�F(�V��2	�Yw8�[��~�������l�t�a��8��@�P���x�?Q����a���������t������/=����I;���^��}�8���g�Gy��#!}��rmuzr�3���D����������L{�'0;�@�W<�@k���?N@8>	��{�,���]cZ�:�J���3�������kH�m"�����3�x���=O��� ������[K�E�%����@ZL:G!`�O������������G�F��)@.�TK�Zq?^�+��T���������?�!������ ������J���������-�	~�4<��H�?�Aw���9o�(��l��x�6��q��K,F��������#��RF��5}�f7NV�b���dL~���5����������W��:���N����W�K�~��K�����v�t@���	�c�����sb~��������/`��U���<G����@������fx����b��+v�yR��g�do��$�O�������zQ�����S|0	U����O��Q��3��|��� ��F'��<f���VF���:|�N��{�}g��IT��	��������T�%g����;��l1��w_���;�m��Y]�C;#��
N�����lB�|�t��������sH����/?C�����H~+<Y���?�sr|�z�����w�9��
X�!��A���L���!�i��]ln�@�;�`������Wc���%�F_�z���m�����Y#>
:t���������Q�iC���������.�x��u`~�����������t�A������meCL�����6�8<�r�QP�"�DD�7���?�����D���'����'���y��8rt�������dx�pI�zC; �����f�qK�?y�(����=P�`���8��'���[��`�0��g��<:��x�����+�����1����<��"��cxq�������
��m�0�����������������t����z[5d�t~k��~�Q$��q�E��Mo����m9N�V���+������������������fz��Dq�=n	��H5��_���.i���+��0�?6�!@�����}���������r6���0N��<�L�'��l^��v�c����6����Mj�[m����s�9;@K*I�R�T���)Q���'����e�
:���B� �����6Q��B#�h�-��+�R�b���'hy���z���_tP��f*k��$;����r9E*� ��R����O�B�1��!FJ��Z}�9b��\��C��%��>R��)�'�`x?����Z=D��Z=�c��P�u�Z������Gy!r���X��	8�NR�������gM��~h���]��^�n'���$�u���>�6B���ou�4",�H#�g���]8���[���%z��a������\���0u����>���������a�����k��������8��pU�^[S�Zl�jfL�,���v��JF�����
��v���C�pV>���+}{c3�.��x�0�����]��{vmbt\����R)���3XH>�u��Va)l�un7��x���Wq���~�/����^~�)\�R'��r(�[��1+&z���_ARW�\��7�BE�r�$������ud�_���<9���i��mb��6�u�,;�9��YZ�W[&0f���L����y��\Ru��	VIe?��
����)"� 8�/
����2:iC�������XC�y��q ��?�oI�t���G�O���2�}X���h��4UK���~��R��V���z������5�&$��k t�)��~��X�^�����i���>�������-�����j��xs����<��H?�=	o�y;8]��\�������}lL���SOwP��W4��Q��p��2Q�1	����~tT$CX�^�_T�����@���m�/��
�F�v�9v,��.(�w��	8�6�����2>Cn	S�fT��vt��3d�=����/2�� 
���J\������6&�bn&]6�t�N�����6S�8=m�.�����l��E��v��n�%��2;��r����r\���f����t#��^�f��k��a�F��t��~��<��� r�d������bi��9��o��P���SC^��������9}�Z}a��h�Y���Di����y8�n���C��o�WC ��x�����x\W�..NH"��]���L��I8G��9��������K�O�H�UBL��p<,�P��&(sa��W��(�?S>qj��*�#�6�F��,�������E,JZ(T�M�Y�7����>���H��Z=d��z��(�S/v����De���.+s%CA��za@��|��������zW�n�Bq�����(vWD���n��� �eg�q�@��z�4���}�\��Ew�~2.���1G��\��T�).1��T�|��� ��8��~N���|����b��H����Z��aX,A�s>�*���p��������z�
��1�Q��������1����nHJV�&Q�5
���G����r�Gez�������5E�����7l/u<���V�Ua#E���mZ�)�S��.-�n�5�9��0��WK�`V�����[d�;*s>��}--�C�}��);*�}���b�e���>I����V$�4���=�R��^�&W�LR���?�
��O��N���l�%�;������E�Y�8��))l;��h��<������p�k�'���
�C�5����������a��UF�/(y����N�j��V)����z�x�"�����p��N'}\�x�9����������6��2F%�_!-����1�<9�N��{X��K1�OO�w���[
4��k�\N�h��h�����n)b���6��I_��b��v��v�o�P�Ch<HT]�0Pja�a�!�js���Nk�4�|�M�d�_A�n��M\��������u!���������=��\��qWd;���
�1�6
O���||{�P�����+���gq>���D��oAj����:���}��6���{�3�����K��5Fw�9���r:f�Gqy���`���[�S���.��rJ@�m�Zo.@���5��jv}�/y�r��2�_�j�s��6����:
����"�?��v��]�Of\��"Y��lF�a8�S7Ss��=0+gj�\�_�'��4=G.���-����-�[�Q��-��q>�y����;���D���*�h��E��(h(�()���*l&#�W/��/����h���J{Ov���XZ���VY��$ml%�>W�TZ���]xP�DM,��U�!���
|1�acQ���TbM����.�$cg�[���4n,����IG,Y+i�5�K��n�
���Q�`2���h����K�0�8F�a�o�_�?�H �9��)�Z��k�Odl��d����:��[G�#=XD�c�{Q��P����|��-�#��D�����I������������,�M6&���T�;4dM�2)���KNJ���I��e����)�i��rBg}����<n~��Sy�VO�0�kwst1��"s�{�p�t�S���fz������S'�S�*f[Y���;��������(��k�a*|��x�x�{-����6{�������S�V{ka�l���C_?���$A��M�@�b,B�?����	G�	
�����XU'z�|��?+����Y�oM�������B�����n_=���Ex� /��Eh���T��%C�(�.�}=��������n�����=�J[o�IQU�J\`���{J�w@�]���)�m�����jwW��+�`N#��\F�L�8�q^EN�v	X�z_�CJ-�4�9��yX�[8m��bKG��h���f#�i�lN�o����k�E�I	�e m������x'5I�������P�/Gx��%���I�K#K\B�b��7���9<�:��%f��#������/�.+%`�,	Ii���w-��A[�Hqb?�c�%���Ay���YP���������\��g�_�p@���t�������_R��j���W�����<>�`��aSk-t^(:0��L�|p�75Wgkq�=/'�1U�b��$w����<�}����Tj�����O,�N�j������7������	��P�����Ju�?wI���h&���T�Z�b~�5wm����;���
��1Aj��[q}�}wW���'��~]�5S�UB�u����`�oBK�V!�y�{o�����>������iX���RiX�7�:���Ne��e���z[��?����>�=$�e�7���i�����
��j����I�������tj�>�������+5B��r���R��q�S��5j�d;�E@������^}��-o�p�"c�0B����N�*Z�Q�"Z��S ��0N��b�G�a�T
���_[���U��!�4�'�V�'��~p?D���v����
N���?��8~������Q��|��C2�#pal�"���I0��o:O��Uz��&"B��
����z,�Z�7��(���Q,��Q��`"%3 �`�o4a���7�@pC�u�`�M�Wq%��uQjX�	���?BZ�������[.Ro�������I��i��@a������7�=<3�����n<T��7;T�Z��uQ��H������1��O��Da����^��U6�i+���B���S�~�i���B�'7�{8{�?A��wl�d�3ON$�|
���������dHg���l�����f^|���k�kw�����������,��w��p�|�.9m_\���zJ�����`�1~��`xT*�+�U&�^(+��G/���v��%4X.��+����>�F*��{����>��B���Sl��c�Y��	��1��w(F��������H�w��i�3����Av��O��y�?�@���ne�c���&V$��#~�j��#�8^�@�pe�C��z�$�2��>��2~�u-��!4�X]$�<*s��2�����3�(Oq��o��0��� �fa"G�+��S�('j?��>$��]4RP�-����r>����fb�������W�7]p�
�
h�+�{�
lt.����+�Q����\�%�����EmK��x��d]g��X�v��z����Z����RR�1U1N�t�VL�m�^��;��Nd�����d3j�\���r��6�7n��'Y�U�x� �\���\���x
�z�4�����	#�V1q]�x��X�?xS�#�^�/�����p��p?����������x�s���Z�m�)\>�6pn�	�Yb���r[���,�B��b[�8*�&K���m�nd�����1�p��5P2^�>�z{���v�E��~N&����
7-Mx	��������������Gu2;�+�Fx�S��$VY��,D�$j#|D��fX;�/��n���y�sl�����_g�����- 2�.�r�����W���&�T�����h<Lck�J�V�@!��x9�F55h�S�F���L��H<~��;�c=+
��+=�5�
�]��^��MU�[���0a�*��ay�UZ�;9]UO����8�:UAN�!��'�?�oN_6/@+��%�"����U���������I���7;��S��97<�n��th�@�'�y�ev��_x�M�L��r�S�\���\�z��EVlgd?�]}#I�7����n`J�����3�H��}X�y��7;�Z��Q&����)l�������QNz�YX�Y�Z�A��ae,)M��{~����,p�~x|t���mE?��[����,�<=������:�{<[���Q?�0�;�Fp���?����7���fg���0���#-��u?!"�bx:>���z�z��	a!���v|i�����{#����~�@���:SI$�`�����_������n����7:��n�c~^��f�\�9����>�2�"G��](������m���B���b�\�K���K�ZDvf�S �6S�l7��S/��)�j}1fE����R�����bU�S16������)��u:�I�0�4����d��1Zm�74I�Kn[������,�C��2P��5?�R]��������9��k+��,�`U��x2���3���]���!���Ta2(�3p}iE�#%���&kn\��}�����~V����}[#v{������<�A!���w��w���u�E�X0�R�i���3d��F��RAZ��E��"��fm�yK&��&k���R���g<B��mb���,�>����o��e�<N���d8��D�_>+�����+R�0AN������;l,���4��/�����I�l!�=0�}��[��}z�={|�L�W�R�7<)�-�h���
o,��'�`����H�@��P�8�6�1�R4N���p2er�p���y���-D�K���S�/qS!A�������=���K��=��4v�/�/��������$i���v ��
��������X�b�0������
���.�N����#+X�|����B�6]���f�w�|�xu��]��nZ��\S�u��nP���3�"*a�������/w�����A�%�0�Sv��c�^u����'���~Z~����������`�<��������Ne-�gz�e��`0[=�K� �b'��WW����k&&����dB4��,=����E�uB��f�3�<n������]��'����`���F3$�%����)�W�������c����g�$�m��p�k�(�7V@�:]X�w��6��A<�=���)d?��K�����xy�A&��p2��d4n�����J����`�����%�s��5W�KG����������W'��9��7scM���y`Aw�q~�a�l����Z�{�JNNR��v���O����#
1 �)���H5�_L���#���}��E���a��;��Z�������N`:!'"��l��m�,���t�Y,��9���9���{le�
��-�r`y�7�������5=�4�h���`��~q��RC���6�a	j���+2����a�X�Ul��E��PI0�D�'�b��B�&V�Z��V����8�crg�o���/�:i3�"����;L15Y.��yJ[���� /�`H����Or�l��,?�~Wh���	����Za�
-������v�@gV���Fa��8��E�4���������7EE��d���}q�(0c-�rQ���_���1isLI=������U^tjOU�������z]����������"���"T�0�m���t�n�yC�_^��L��0�kpx54��e0��$�$��m
H����x5{u���]�t�rnZi_�'���N$�vA�-8,�"���;E�I�	�l��9c7t�0���������3N��^�:��)�z5���G�wk�8�a��L�~1���.J��u&J+7o}:�a0�pu��Eq�s��,K��=�_���/�O��`���t�����}~�a;H���f[�����\��8����h���RxK�{2
����c�#"�~[`��I���;%�/����y��f�m.�^]�g�����u�s��:�'P��tP�o����WC�����2��%��7���N�*z�E�P�j�'��h��;�}7����$�U� ���#M2f][#��1/jQ�ny)�19Jv�2t���zYa�'�|��Ll�����1e��Y�*�`�R�)9��C^,��m����*�.3���`�$��%gX���w�R��w���y����!�	���>Z-��	m:�=d�}����d������S}+f|o%w�G��E����� �s`������m����
�S��_�%����W�n������P3+ �6>���n��� �~�u�A��d��������V'GL��h�jS�F���r:-�C+Z�#��c}0���@�W�����������V�,0A����J����E��������
�s�s���k�9q���8��5}�	%\���e���uJx��@h�f������k ����@������[�&�Q=S`;�N�k�+Z2.W����V�l�W�Z����(`�������S�|D�,�}}��@�����O��������S���+��17����Yp��p��i�}��I~j�mJ�k��D�"	�7
��I�����	�k�M�#�9C�R��_W��"]S�i6��O�g� '�kCH#%�Rg��y������G�������8=��O����A������_��l����	6�l0zO�\M�}4����������!��f����:����K���J,B}
��}�������S�,�Q�
C���hl*���6�}#�������]rBKa�#y�?|���NI*���������@���YR��v������L����8	j�!����"^���O��x������f��F���=���K]'vy_�,[�����=�&{3��`���d���/a�	R!���W��0��N�Z�a(2�I��e�����=�ff�J�����]��SF��>�Q���w�m���C(���&r��U��N����q�[���:�-&��D�I�]�Q<�F�;E��6P��� ��x���������<4�f�7!a����E��x�d�����w���Z����7A���
v�/�
�'#�	����n_)}!>��[�t�h@��=�?�D^HVBO����F ]K�G����r�C�ib=����� ~#o;Q�C���������6�����C6{�h~�6�L�=n�pu����M��t��m=���?�c$;��B4�l�()�m<Hz���q���hw���.�+�7~�[���I��|o��ww�t�6�����������-������ N��5)$�c�y���_f��<������31r�W,3�`�;*��UOOt�Z�=���-��T�������5a(�]cz����oK���//'��X���{Ec1b$�P:!5i�x����Jr<!��GF�G�s���r������N����lh}f�W�;���X�|��z���`M������7�����sN�|�Q��A*��a.�sf���
H���^�Fi����84�I����/��I�A�#��
���#@>:��Z1��Ly[��LF���)���#�4��^���X'��gyf�{���h�;��^��R�d7�5>����4!�����z��k��`���wOQe7s��	i7�u�km���^G�HC��(+W$c�2�xi|Q9���v����F��}��ql�N��DJ+�nql���cs�#�d�u��pP?�J��� ��y�������CI��k�~�W1�R��c�RD\f��"�o�;Ix<@.vx�a���.��S��R��b�6�-wL{(����`�?Y����/=�8[W������Iqp�-u^��
i��7+o����#�>fG�������^������A���������_}d��B����5o��}A/���FGJ�����U��"��O�����V�\������afV\-���� �9(3�9�T��J_]���WO���p��n��pL�����<��y��w{~&`�m[�s8����nl��]�G�>��}�la��|?�{q�d�9�B������+��W��T��;�T����T
y��S	��Z��(���vLUz�����A)����-�n�k��J����F���y*��-�����T�rp��������8�,����h||�T}�$QJ�����]u�x���1y3V6A_S��)'�q�*��FAX�s���d��9�����v|(
�93Lw�g���G����O;@Vq���'��-�
�&GH>�g!�;Cx�t�d�{�������<�/=���S&GG�p�^��K���z�(��n�������qH!������B���	�9^	��n�	�5���K#k�8	\�1�~Q�����Z���)��_��CF
��$�s�I����2XP�X���(�;�Wj�������q�s?����_Q������������F=L�0��|�k~��`��
8@�(q�D��tbG��J���/�F��sb�@�$6�a���hULs��<�������0(�M>#N���4��r�%�:�Q��R��e"V)P�����n1�-�Gs���l���>��
q�P�h2Y��j���f1���Q���h�G��t%�������E�>;]tf���h�N�v+�wS��A���� eLq��_��R�[��G��<5�^����65�_�#a�'�2���0�=���@U�+4v������;l����=��f���������o��l�J�|���[�=��!�{���kN;Au��Z<V�������A5����O��v���8�9���/.�M��9}	?N�Wg�k��U���~�h�Bm�}n~�A`�N�q���a�8<��AON�3��G�����/��K�n����'{[7��H_
~^#�Z��e���y���z�K��~���u��n���q��j��N�u��e[�o��n�:�������+���4��y�u�����5�|���-���e+��_nZg�/��]��M";f���V?fa��5SP�@��p�}����$�����.0�.�C��B�~`���+����?0r��V��G@!��4.�~k6��o���~��W�
J9T��C�#�l��d��O�)lu`�`�6�g9����9��y��8+j�2|�����hJ5����<S�r�Y��s�{�[2�������q����E��������%T�3z�h�/�l������O���r�)���
<#���x�����H:�8d7Y��9��lK�C�����X��R4�,H.����:G���������Z�P������l�j�0�vBB�rN'��f���P.4�(�z��/EE�*���(_�R������cNM|�;�]�8��NZ��r���r\�u��C9�O��~��PO�O�=<���	�9��������#��������V�
��pY<1�a]2�}�?$�o��M���_k�g�����w;H��'�(|>ZCM�HM�N�d��N�(� ��[������lT?r�)~�-�/	��Z)�v���AD��{� ��"�U} �y��p�yL�W���}�~���_l��P����F�G�y/w��}�[C�	�'�'Af����:$�|!	 ��L��S���`����Jrx|N��������9�G�_��=�+�����k>�P� �6�P���r���?/7��CN�/t�����_U��j���8	�=�9�x��h1\����9��.�Yr�!�[����d$����j k��Z/��-��N� o����&��W��H�����Z��(����(N�����Y�C.��O�����(�-�9��.��~�8��e��		�x��`�0~`A+�Y���������i������g�����
���p2���U������f<
U'|7AV���n\�s3���_�Q"���:'!W�[
+7��	�E���O�~Q~%e������ �HN���N��j;�;�����2����������;�������u��%�����dE]�����,����1@ydkyb���z��TW���'����>B>?S�+e&w���m*��m6���
R��Y�9��`�����+�V.���Q���+e�����a4v�
p�u=�k�����xG� L�(���!�`�������q8Hr��%l�S�IHmS�����������tZ1q���T2�L������C�r�i|.��|��YN�txOos%A����||�z����Qn��S	�V=�U^a�����1��y���X�^����r`c^��j�qnh)����X���D�u�f��#�[����K��i����6��@w��1>i��!FLt�nR]0����G�����1���tg<��
�QLb�(��	�s�H�����a�f4�X����u~TZ������ht�����?��$�t���sv}��n�i� �a���-��IM}T-Y����RV� .�FB-�oH����X��w^r�q�TOPU�u���\)���b���e���2fn�hc0����u�LfP�Y;?_������>v�q^�^F:]&�[LObF8���
���$c�v�1�%�����fg������J��X��-��;�l  =����l�R��M�n�v\��m���/u;59��:IPj�;;2B��"�r��e��5�l�:��A:	��`�{C}�|�L�zPt����u��w0�C�����c5u��v�0���k�>��$j�db��C.\t�HT:���t�����@E~	�KG�;�Ll�!-�2>����D~d�QkmJ+��*�cI�S����j��+���������?��
��;ll����][��]P$5 ��������X{�6w�1G�Q'X�����'vC2�pe;�*��T@�F����&�j��<:��jL�K� ?��K�[���d�n$�$���1f�
�#&pt��W�j���H�Yt
�:�-�"�_y�-�����+�Z��i��p�����n���	~#�e��	(����������V?u�j��Y�m/XDc`�d��D?I�aZ�)�}v%WHq�0���1�S�[����O?��,�9!#\$5mb'�ltxX��t^����?Rd�%e�=a*����u�Bda���X�$KG)9�;�NG�	�����.����s��9�-_@����4�XQ6��#n+���@F��,��e�=��
c��2a�p9A����bc��@@*	���	���m[m��re{�AaH��8�����GY ��� C^I8:��1fXda�����;e���N���}u}?PZ����+N�����HG6�p�$�����BJ��Pf7WOd��h2)����v%"W{lB6d6x��N���/T�C��Uv�7;��2��nT"�Q"r�P��x�X{��&�%
]t�*e�0�>F��]�x9�����
��@���2�e�;Nmx�_y��������i���qu���?Ex6/hV/�q��H!������j����T:��G��)����*�+�uzq��P���2O�=�������Q?�����e�Z���g��I�y;��B�*�W|�s��D�xMA	��,!d�x�������+����m�d�wo<�A�=3�Y;���{L
��R��)��	�w�����o����10��{����A;	�m��#�����+�_$��c����^s�Q[g��>���52bd�Z�,��y��y��H����N�}��k�J�6r2��0��(��@?����s��@�
O�R9!��q�������B)%�S�:��rf��un0���h1k������[�>
��)��.F�&���.!�ZW��~��]�%))���SR���A4{�o]p��pU�PXi�6B?�R���F�A>+M5���T�b'vv�a}��`� p4�<���K�E��g�M8 SL�����\fW���n����u��*���<�H��L��Qf���r�����I��^�)��&�pr��j.��t\���Q�X%~~=�0��+^��I��@j *pP�OL�%Q����K�T;��V���xK�Wa��lN����;*����9����![gs$�%I���|��!P!'+��'8Z����1���;@?�d�$����/.�0 ;2q����Q�\��(���pX
�bc�/v�0Cs,�oM������]"���E��G���]%�]<$,KS�"���m���U���y��Y��8`�������.i����m;���uv�����
��2[���6����_ih?wj�q�M���Z�2fuiX	���	7��&c��B����V��e0�9/�Et{u�_�I��9�7�K����G����X�H>���P�^�f�7� �
����X/�\�,��B��0^��&�KF��W�q��j�rX*�GG�G�BH��0�S���t�;�gL�$`;�	Y��)3#������+�wq8���2G��Xk�����!oEv�_Q.��.�dV)8����0�x������� !`N"���;�0���c��?��?>y,��}x��������EN�Q\�	���N�	6Yj+����� )�`��l��E,9�S=YEd� f[[/��s����V���>�v
s��c|����5Q�������?p��gx}�j.�tG��we�^*`�M�C(.l;X��4'�=�^'zGQ+0�(���B��YC��R�[����t���o��bE���5r���l�{���gg5�=��?�u�7[���G����|��:�5��}L��Z0��Q[��u�G��S4<{��y7^��Q!���.�6K�k��s"�$������:,�J�JeVG������U�L��
�^YuN�>��0����Xqb /���kq�^Q%F��h|������������!���E�8�j�~�M��>���T���p���(���T�_��8t90��������C74/�Z"���Yv��4��}�v?jlS�38�,o�j
-/E���_wV�0Z=t��G���xaBP
�5���a3���S����~�o�@�f#{�d��������T:8�������������T&w����m��C��Bc>�Cc���h�Me�<����*��z������(&�������n<��t�)��R41������p6}�Yj�oQa��b�������1���}�D�����31L4��<I�d`���rm�'2���t�Y�TV���_1$t���V�=����/�]���
��I�x������
��a[GU��T����8��'|���h��R���w
;T�����=���+��w�7����
��)�C�BMa��O<������z@x*&��C����K)�� @�������d-{^$����}�y����������g�/�WQ��d�K�Q.b|3I�d5r��IO#c����	��S�(d��l��	$���UL�H�h��9-�0>��v���S����$	��"s/��U�M���j�t\=U����:@kv�]���cVt,@k�_��{��
����<�-�#����Y�a:�����C�]�n���n2M�=���������)a��� "��!k���
�$c�(s�l��='�+�_���
�S��|�!_���t8���xI���A�.����i����Y��P�;���fG�"��f�sT�U5l5bRm7�V��G��.a\��s��)O��%_�����4z���R7�f���X�K�V"��S���!�v��~{��z�L�QX�`/�^2��g�����48=�p�{�@�������v��z�:�I#��r�2ye�j�[-�`��IMYZ_���%����K��U���V??�c��3�r�0?��
���T
���j}=����O�I����G����z>��qZ��p�����
��(��&��E	=1�����4N����H�,���cx,�
��������C�`�e)���������"]���Y�a���|��}�}�{4t@�Vn`�����L��a��=}�`��C�-��C�k��%��ST����td���y�2��g<�,���u��h�3o���z\�=�R>����)����=�b����������L���=���C�#��U���V�h/Q,�t�"�_�#��T7\Wm-�jxu=<4�,��z�R� ��,�P����l�����1��$akQ?�P�e�Cq�j��k���R6��>�z=���0��#z����]���3�]���$��p�����uY9�����2����`��@�Ds��+�����Y�d��Au��`�B�L�#�����������
m��K������V!w_
�H��w�d���$v�������Z�_@��2��J�t�=S�m�����;�h�0��U���0��N~������6�4;(����������c(�t7
��m(
S;8�i��o��X}L��p�
�����f`Rb��
3�������dTf]��1�����w_��s����������x�v+d���2V~�a���{R�3��:�`�h��>!v7ZY?o��^��i�z��}��k{cc�'X�"�Q��&)*���l�����������w7�.M�����*�c���nfQ��ozc�y}�wbz�;<���O�R���q=���2�����?n)�L���{^������>��L��d[��D=EV�����W�(�unP4����v���-��'
��R����4�&�OPK����j���g����l)�-����.��8qI��*
��
���=��8�������I~L������D��Q���=9�;`T��������Rb�+{1e&N5�q=.�U*���C?�l'�_�\@�ywT�F�BIY���qIb�N&�D���(v���%z6�c])������e��dxCN�Z���m�J��l�?u��\�mZvv����.��Q�����{8������DP�l>bQi/^{�J���RZ���KA2�rQ���2�I��	���(E9�`>,�S�J@SL��$[�����6���[&��4������j[��m~O-�B.]IJtE�=OE)� 
e�����Iu�Z���K����
t���!yo#��v4������%��T������[�$N�1p)fuj��k�F��:��4Q�����g�lX�tmjkXJ(��D��0�n��n�o�F<����\V�I 4H���5,|����D�������El��w`$$��T�B�i�0��	�
Dj�����OP���O��a�A�H{=$������<�v=D���e�qN���5��7����U����$��s���1?��;[������S���I{�z�W����hZ�)� _"��OPkG��1J�q�M:��kI?Fj����%I�cZ���(G��
�!��VI�����e6<O�!%#���r���BZV�B�E����*>qHftM�+"N�M.h���v�����i7�k��
��6^��L#Gb3�$�>_�'C��
�[��YG�"�$�{�D_�R��J����6
�[���j��f�w������1����"��I3!���'42�	#}�������%J��Rg|�ef
�=�qpc��flb�%-���.�0�Tg���c�=��i���DIq���
���%��K�O��q��8����������>���bv�"gW"+��u�o��Fcm�������wQ]�5�n4��!k�(��������D���!�QzSe��.�����ia��l�,��3nQ�R�w�����1ZWt.H�l����U���b;����`�}������~@8&������(�z�.g��`H�-��
cL
��H�:�
��>K5 hR�����q�\f�5z��������)Q�-���g�67k��e5��xf��S��(�=n��{�d$��\h�����$��=o��:kt~��)�����">��z��1M�"E�~t�.���C	�����t�}���$�p�O��K���}������e8���-�E����.���Q������ho���`�����PU_�%�����M�d��U���:a60����v#{�M.��h�r=���J�rm4������^m����G�������`��
�~�i���]6�?�������N��,
	�.����E��F�����x]\�Y4E� oi-����9�>P8
����bOH�d�5�$���>����ae��XQ�@F�}+�1*�(��<�W@��� ���@��������`#����']=���R4��mM��=u��������u��$Y����.��`�R�����Q���J�_J�'|�����9��P8�2mM+�_��Z<P��et���a>e��'���u���P���J�}�A�����4D�6�O'�*��yn������!��(�i�n
��"��l�d�(�y����u�%�nv����b�
�T�5
�G���Ri�|8<�U6b\�J��+�{���Z1�����^r=G����-���~Q�
��2;>jn%�}RVO�Yw���U;�+G���a.���S��:�P>���Ye����3�%+�e6��G�kJ'8$;�~����9�{&�!����;:��m��+��:S_
:�|��IB8}n�� �s4Q���L����(�<;��M��k
e���7��Ns�,Su+%�������)���L���~�2V��R�V����/����*���d�p#�0s���������v"JmP�����E��T�[�����g���f��<ep�E��r���Pg����%�>�
�!�nh@'h��h�Zg��d[Cn`T���2�0��m"��CN#���{�k�C�@�]�*��\kGjT����1w���4C6�����S�O�9�;�$�������������/�]M�����&x�9�H\7e�	�3J~&�>y�#M:��)��t@���K���^=��s��4���B�9���
^�'g�0�&�d:���|
yI���11���Y���W��W��L�:�M��o9�:�����@�����[�@�����C���2�H�x*J��d ��w��Y�@9�#�mI:�LST��l�h>�|f�H�%���`��I;��f��������p�iWT�,��ML"
�����F���u-2���F��y`�����_>�@o
,��-��k�(�J��~��x�bZm=��*�]�_�����_��w�h�,gZ��FVk P�}��9{���]��$�m7x���S�EG�����^FY��/3 ,'�����C����3h��J6���#-13����Q2�i����_:g�qK3y�t.LFcLP�?�&��i��$G���+�����������Y-G&�%�w����Q�"a)�W1�B� }9����J(	lix}.�lE��������+�����!vu��&.��(�0>�B�.�����P�F��a2I��J�{a"4��?��y41b���,3V�q������g#��F���}E�@y�mU~qS�*�U�6����e� x�$'�3v$��ez?5�Q��!0|�W��BS�b�X�E|�rG��B0Y�-c �3t�eLW��n����!z
\	��533�{�>������P;�VT"o39��L��z;�N����fG=�EUN��x�R(o�������v2?���--g>s-a��k���{:�,��	"�W��k���c�5�.������ ,�j��%��=_�I8�]�������;���k�V����_��R��I�B��2v|�������4���� r�+.���r��V�K�axT9:,o"�d�NR����������U����O�rB� s�H"��1'��b�qu�<��������/{�\�Fl��RV�������?
�J�Z�pX��[�<X�v����\��b��W9EKIt��C����S���(��bZT�|���g��~�nt����o����n�E�v�+���	"V�-��m�xv��2��G��"?������0��pN4<�8�t�]�3�u�7��m�(7�9� �M@��p�`������]c��ktspz|L ��}���$\xn?n���q�_�J���� ,��_=�=� U�l!��U��h��[���E��`'�=y���	��������eNWb���A���Q����
��_5�N�� V_{Z�u5m(��8I�1��J[�$/�V���`��`Y��-��n���v�-�����y���CK��+�r	[�G�T2��r(�+�,t���s�)��Q���X���
��u:���0�{��:?'n7�6�nC�@�w�����N����~���n]�5�_.W���M��s������%L��j\���y��U?���F��� �Ml;����y�W��[B]��c��
`�����vp�3AP����H�i��`a�de;lE�h�7�Q�d8mc��n��f����M�@DH��u4�����]�E|4�d��xMn^�C+����������j&I�����,�}���\������0eCS��F��{�y�='����8��v1��T��>$v��Y��8�������R4�f���y%Q��+p��m����^�H�c����R}�����4�&#+l�M���m�7�(��K�I����Ak�Y������7n��G$���dAS�Uj��jM��� ��z7���
��Z=A`a���>��<�KF���;�kf������d��n��o������~{{k�����o��}����E�����'r�&��m�l��qp���v��q���k�>��y+���h2����`��ahh��DQ�y�[���$Fg��\��2=o��[l���V~�m6�T�EjOZ�
K�k�3��<�=�a^�� �\�WU�1����QD��~u��\���+�$r��jl8JL�
[����
��O/77������)�cQ��,
�zV����do�����&wic�i�8T�=t;����c�b���M�=So��4i�0�m��&������Z�/����\=��V��_P��usa���J����N�����[�$�B�h������^�T������'C�f�Z.D�e�_��������E���w�e>��Q��v-=/�������,�����{_��!%{��;����8A�	,��6��P�!��V,���	��j�Ve�JX3��) ���s����^��~+6�h�MC
��{0���VL���Zg8��i�f�m����C��zO0Q.�39���-�ye��T::8��:m9��o��x�����[�\,�,�DE�����<^f;Fo�����C2l�&���U�:��{�i4{��o��0�Q����^�\���v��x��'Q?��3�����F����l
$�C�����Q����oA���@L�y�����bC��GS<�������2��"_Hx�G�����{��&�;g���fJgj���"�F)3r;7E�����3��N���aW������UkWp�p7.��i:*"��������-H�[�����v�����&�n���i��o�����iq�h�0K��1�w"����:n���r_��]�;zv�6m����(c������Pg��V� �^�:
�V�z����+I!:_�K���t��D (����\4�XLy��d"��?���	g-�/E� �����j��Na����DhRg.)�(�)�T�O@��1�'��A3,�Nm�������~5s^��}�� ����O��}z���$��z�%���opX�Y	 �B��Gl=o�������dP���&�{���&�9T���*D�Ey
�U����u����$�@�+�d�X�]����p���h�r�3�n��l�ut��4�a�"Q�����|G�'g�9�!��0��1als�A����L��c�n�0��,3A��mvE���.��W}%�@3�I�v�8��8�=w=g
�Wt��{��8F���I���Y�i� V�6;�d�
��L���g	�h��r�8{���bH�\;����&�^#�J�C�hG��>+3��u������?*���a�x��4�#3g�p��}t��������R�f/�h���p���3��S����q8D��k��CD��H��W�|L�:h�uw=�����"9�L�����t	�-6���k���-��08�GG��h4m��YP+V7[���^�$�����K��#��9�v�G�if�h��)��	��j����YW�������h�����M�"�e�.�Ku�{�=�����!��;]i.E�����M�e&)�aX��\���e��x���0���H8�3&OF����1�ks����8B���)(4�L�`�`�t�!l���~3`����:�k�3=�<�Y�_0�p��fX�S�����A�zT*U�r8�����:�8�r�}qk����'B~���=��{�N9������$���r�����G"EI����y����u�s����j�5o�q�I�x#`������+����j��������_�� �V����wE��W�r_��DM;��F��b��<�X����I'm��6���Y�������.�����������e����L�h$�9� Yy�,O���p���=`9���9���
��p��	��G�F��M���V`=Q��?+���DL�
��+�7[)�����Cj|�CG��	�	�H|����������&�G���^������wP���x��<��0�;g������zR|wn��9h���������m���L��L��o2��21Mb�k�.�J�go��9N�C�J�)�Q��?�J���������k�����c8{iM�������/�����6�haT����E����$��3]a��[{|?�sl��l]4:EEJT4���y~�y�B�k���K��h]�k�:�9���o#��U
[\6..�c�����$�Gn�����z�����}	s����9$#����4_9�M���j��<��>��N�|#TS2j�i����L��4��k�.�����}�n��&�/M�������p����/��M��o!R���P����l�����={�i�!��K3��zZb:*��GG�����!�.�O �`��Y�H"��IG��y��[T�����-��.������!=-<����a��h��\�gg�="8IGQ��������	7��R1S�������z�t'J{�������7N
�K���2���$M��8���"�;�/�������{����S����������uO ��H�T��g
8px7F���/�+�m�����FE�I���!\^\��c�R/+���o���_5
_�L�(��|4x>0�Vo4�G�^��{�H)�l�?,�*�6t,M8W��ZLGcl�u��nx?��%I�@��|�L�0�tO��P+kk�Vn�)S����2�%s�:���M���V��)�5b�-�?C�=�����s�8�����s�t��	VGv�� ��&�2�U�F\SC������v+�G�je�q�B�����=
��D�g�zo����V=�Z[v-�f���5��L
���x����V��Y5����97D���>?��������WO}%eO�c�4ZH�Q}�*}N���������@�����n���~j�);g����t��2����L����j��6�UK��sI��^�h�ad��IQ�����|<��s���"����dA��(�����������a`6*�Ecj��@����S��j��rx���I�`���DF(B���Kv^!G$������m��5a��"�D!D�������w� L(��@�1��6� 496e���T���$~&���Ub����G���~�r�?���q�m�wL���B�e�N�1��Z���qz���uu�#���0�yL������d>i�}�$��[�5��Z��&�7���l���m�x��n��yS]4�����	�1�V��l�(�mO�3�5����v*��=2��s�_��o{�~�Coo��U�p0���423��o����O�"����%�/gwA,��{�����I����'qn�u�o��>*�a�F��}��	���Qy����y��K)hq��-��,ZP����J�������a��\�	&��*�<I=������v�X9�f��PA�+C��^����jK���^�+I�I��F���.<�/#B&0�~�o�kt��dc�`��&��oP><�K������]b@�:[U(�:9�����H��!>�-�KX�z�,Qq�
�����W��~�6�d&�m]�������X���a$��m���H~�7�������z�6���3
��,����v8&��oVY�]jGa�T*�r�6��{Cp	�7l��T���<9�����x��4�|��-�N��/��{7��Jt@��b�o��������9�b?Uy#���`��r][6���o��<L'�?}?K���{�`�3u�>o]A��f����_�M��x�}����������	
���=�'i���8^�8a�c���fE����;�*L����3f���i���������USU��z#���������?5��y�_=5;�7t���l������d�)����1JIpe��c��[6��q�l�n�
���3�
t`M[��Y��
��o�K���n[�lk��.i9��P�|b6���������hAA���Wn�L�����.e����0
�����F/�Vd7���V]v�I��SI�?���d�mt�=��,��f�8=e�9���]��?W��?���������Ov�5����zb]�S�'��U�����B�"|.���
||W�6���mRE���%1+R�x_|��I/�n�y�������z!+�[�����&���cE��_p>�7F�bJ7�r����Ut� ��}y��b�yNuOWm.����qI�����Z=������dN(��p	��G��T���<8K���_��p�k������z����r�����
U9�<��B�W�=�����������8��0�
 �u�w��x�B}��{���\@��?E���o��rN�P_���������1\K���_(�-MC����t>��CJ���-��g��QUa�x�_
L�4J��M���ZC@?�`5i��� �����v�qxT�W<J+���E��������	Y,����M"��R�V\������rZb����h�P%X]���'��>x�����=�~4���������B����B��/�����n������=�H��~����[���Yq��'RRR���K�EF�R-���<�_������
0003-Define-logical-replication-protocol-and-output-plugi-v16.patch.gzapplication/gzip; name=0003-Define-logical-replication-protocol-and-output-plugi-v16.patch.gzDownload
0004-Add-logical-replication-workers-v16.patch.gzapplication/gzip; name=0004-Add-logical-replication-workers-v16.patch.gzDownload
0005-Add-separate-synchronous-commit-control-for-logical--v16.patch.gzapplication/gzip; name=0005-Add-separate-synchronous-commit-control-for-logical--v16.patch.gzDownload
#162Erik Rijkers
er@xs4all.nl
In reply to: Petr Jelinek (#161)
Re: Logical Replication WIP

On 2016-12-30 11:53, Petr Jelinek wrote:

I rebased this for the changes made to inheritance and merged in the

0002-Add-SUBSCRIPTION-catalog-and-DDL-v16.patch.gz (~31 KB)

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

#163Erik Rijkers
er@xs4all.nl
In reply to: Petr Jelinek (#161)
1 attachment(s)
Re: Logical Replication WIP

On 2016-12-30 11:53, Petr Jelinek wrote:

I rebased this for the changes made to inheritance and merged in the

0002-Add-SUBSCRIPTION-catalog-and-DDL-v16.patch.gz (~31 KB)

couple of orthography errors in messages

Attachments:

subscriptioncmds.c.difftext/x-diff; name=subscriptioncmds.c.diffDownload
--- ./src/backend/commands/subscriptioncmds.c.orig	2016-12-30 16:44:31.957298438 +0100
+++ ./src/backend/commands/subscriptioncmds.c	2016-12-30 16:47:00.848418044 +0100
@@ -585,7 +585,7 @@
 
 	if (!walrcv_command(wrconn, cmd.data, &err))
 		ereport(ERROR,
-				(errmsg("count not drop the replication slot \"%s\" on publisher",
+				(errmsg("could not drop the replication slot \"%s\" on publisher",
 						slotname),
 				 errdetail("The error was: %s", err)));
 	else
@@ -623,7 +623,7 @@
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 		  errmsg("permission denied to change owner of subscription \"%s\"",
 				 NameStr(form->subname)),
-			 errhint("The owner of an subscription must be a superuser.")));
+			 errhint("The owner of a subscription must be a superuser.")));
 
 	form->subowner = newOwnerId;
 	simple_heap_update(rel, &tup->t_self, tup);
#164Steve Singer
steve@ssinger.info
In reply to: Petr Jelinek (#161)
Re: Logical Replication WIP

On 12/30/2016 05:53 AM, Petr Jelinek wrote:

Hi,

I rebased this for the changes made to inheritance and merged in the
fixes that I previously sent separately.

I'm not sure if the following is expected or not

I have 1 publisher and 1 subscriber.
I then do pg_dump on my subscriber
./pg_dump -h localhost --port 5441 --include-subscriptions
--no-create-subscription-slot test|./psql --port 5441 test_b

I now can't do a drop database test_b , which is expected

but I can't drop the subscription either

test_b=# drop subscription mysub;
ERROR: could not drop replication origin with OID 1, in use by PID 24996

alter subscription mysub disable;
ALTER SUBSCRIPTION
drop subscription mysub;
ERROR: could not drop replication origin with OID 1, in use by PID 24996

drop subscription mysub nodrop slot;

doesn't work either. If I first drop the working/active subscription on
the original 'test' database it works but I can't seem to drop the
subscription record on test_b

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

#165Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Steve Singer (#164)
Re: Logical Replication WIP

On 02/01/17 05:23, Steve Singer wrote:

On 12/30/2016 05:53 AM, Petr Jelinek wrote:

Hi,

I rebased this for the changes made to inheritance and merged in the
fixes that I previously sent separately.

I'm not sure if the following is expected or not

I have 1 publisher and 1 subscriber.
I then do pg_dump on my subscriber
./pg_dump -h localhost --port 5441 --include-subscriptions
--no-create-subscription-slot test|./psql --port 5441 test_b

I now can't do a drop database test_b , which is expected

but I can't drop the subscription either

test_b=# drop subscription mysub;
ERROR: could not drop replication origin with OID 1, in use by PID 24996

alter subscription mysub disable;
ALTER SUBSCRIPTION
drop subscription mysub;
ERROR: could not drop replication origin with OID 1, in use by PID 24996

drop subscription mysub nodrop slot;

doesn't work either. If I first drop the working/active subscription on
the original 'test' database it works but I can't seem to drop the
subscription record on test_b

I guess this is because replication origins are pg instance global and
we use subscription name for origin name internally. Maybe we need to
prefix/suffix it with db oid or something like that, but that's
problematic a bit as well as they both have same length limit. I guess
we could use subscription OID as replication origin name which is
somewhat less user friendly in terms of debugging but would be unique.

--
Petr Jelinek 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

#166Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Petr Jelinek (#144)
Re: Logical Replication WIP

In 0001-Add-PUBLICATION-catalogs-and-DDL-v16.patch.gz,

+static bool
+is_publishable_class(Oid relid, Form_pg_class reltuple)
+{
+       return reltuple->relkind == RELKIND_RELATION &&
+               !IsCatalogClass(relid, reltuple) &&
+               reltuple->relpersistence == RELPERSISTENCE_PERMANENT &&
+               /* XXX needed to exclude information_schema tables */
+               relid >= FirstNormalObjectId;
+}

I don't think the XXX part is necessary, because IsCatalogClass()
already checks for the same thing. (The whole thing is a bit bogus
anyway, because you can drop and recreate the information schema at run
time without restriction.)

+#define MAX_RELCACHE_INVAL_MSGS 100
+       List    *relids = GetPublicationRelations(HeapTupleGetOid(tup));
+
+       /*
+        * We don't want to send too many individual messages, at some point
+        * it's cheaper to just reset whole relcache.
+        *
+        * XXX: the MAX_RELCACHE_INVAL_MSGS was picked arbitrarily, maybe
+        * there is better limit.
+        */
+       if (list_length(relids) < MAX_RELCACHE_INVAL_MSGS)

Do we have more data on this? There are people running with 100000
tables, and changing a publication with a 1000 tables would blow all
that away?

Maybe at least it should be set relative to INITRELCACHESIZE (400) to
tie things together a bit?

Update the documentation of SharedInvalCatalogMsg in sinval.h for the
"all relations" case. (Maybe look around the whole file to make sure
comments are still valid.)

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#167Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Peter Eisentraut (#166)
5 attachment(s)
Re: Logical Replication WIP

On 1/3/17 2:39 PM, Peter Eisentraut wrote:

In 0001-Add-PUBLICATION-catalogs-and-DDL-v16.patch.gz,

Attached are a couple of small fixes for this. Feel free to ignore the
removal of the header files if they are needed by later patches.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

0001-fixup-Add-PUBLICATION-catalogs-and-DDL.patchtext/x-patch; name=0001-fixup-Add-PUBLICATION-catalogs-and-DDL.patchDownload
From d1a509cee9e1c2f6c9c4503f19e55520d8b6617f Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Tue, 3 Jan 2017 12:00:00 -0500
Subject: [PATCH 1/5] fixup! Add PUBLICATION catalogs and DDL

Add documentation for pg_publication_tables view.
---
 doc/src/sgml/catalogs.sgml | 63 +++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 62 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 70533405e2..ae33c56db7 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -5375,7 +5375,8 @@ <title><structname>pg_publication_rel</structname></title>
   <para>
    The catalog <structname>pg_publication_rel</structname> contains the
    mapping between relations and publications in the database.  This is a
-   many-to-many mapping.
+   many-to-many mapping.  See also <xref linkend="view-pg-publication-tables">
+   for a more user-friendly view of this information.
   </para>
 
   <table>
@@ -7729,6 +7730,11 @@ <title>System Views</title>
      </row>
 
      <row>
+      <entry><link linkend="view-pg-publication-tables"><structname>pg_publication_tables</structname></link></entry>
+      <entry>publications and their associated tables</entry>
+     </row>
+
+     <row>
       <entry><link linkend="view-pg-replication-origin-status"><structname>pg_replication_origin_status</structname></link></entry>
       <entry>information about replication origins, including replication progress</entry>
      </row>
@@ -9010,6 +9016,61 @@ <title><structname>pg_prepared_xacts</> Columns</title>
 
  </sect1>
 
+ <sect1 id="view-pg-publication-tables">
+  <title><structname>pg_publication_tables</structname></title>
+
+  <indexterm zone="view-pg-publication-tables">
+   <primary>pg_publication_tables</primary>
+  </indexterm>
+
+  <para>
+   The view <structname>pg_publication_tables</structname> provides
+   information about the mapping between publications and the tables they
+   contain.  Unlike the underlying
+   catalog <structname>pg_publication_rel</structname>, this view expands
+   publications defined as <literal>FOR ALL TABLES</literal>, so for such
+   publications there will be a row for each eligible table.
+  </para>
+
+  <table>
+   <title><structname>pg_publication_tables</structname> Columns</title>
+
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry><structfield>pubname</structfield></entry>
+      <entry><type>name</type></entry>
+      <entry><literal><link linkend="catalog-pg-publication"><structname>pg_publication</structname></link>.pubname</literal></entry>
+      <entry>Name of publication</entry>
+     </row>
+
+     <row>
+      <entry><structfield>schemaname</structfield></entry>
+      <entry><type>name</type></entry>
+      <entry><literal><link linkend="catalog-pg-namespace"><structname>pg_namespace</structname></link>.nspname</literal></entry>
+      <entry>Name of schema containing table</entry>
+     </row>
+
+     <row>
+      <entry><structfield>tablename</structfield></entry>
+      <entry><type>name</type></entry>
+      <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.relname</literal></entry>
+      <entry>Name of table</entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
   <sect1 id="view-pg-replication-origin-status">
   <title><structname>pg_replication_origin_status</structname></title>
 
-- 
2.11.0

0002-fixup-Add-PUBLICATION-catalogs-and-DDL.patchtext/x-patch; name=0002-fixup-Add-PUBLICATION-catalogs-and-DDL.patchDownload
From 173215232e14ae209373fd69c936fa71ae3b1758 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Tue, 3 Jan 2017 12:00:00 -0500
Subject: [PATCH 2/5] fixup! Add PUBLICATION catalogs and DDL

Add missing clauses to documentation of DROP PUBLICATION.
---
 doc/src/sgml/ref/drop_publication.sgml | 23 ++++++++++++++++++++++-
 1 file changed, 22 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/drop_publication.sgml b/doc/src/sgml/ref/drop_publication.sgml
index d05d522041..1a1be579ad 100644
--- a/doc/src/sgml/ref/drop_publication.sgml
+++ b/doc/src/sgml/ref/drop_publication.sgml
@@ -21,7 +21,7 @@
 
  <refsynopsisdiv>
 <synopsis>
-DROP PUBLICATION <replaceable class="PARAMETER">name</replaceable> [, ...]
+DROP PUBLICATION [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> [, ...] [ CASCADE | RESTRICT ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -43,6 +43,16 @@ <title>Parameters</title>
 
   <variablelist>
    <varlistentry>
+    <term><literal>IF EXISTS</literal></term>
+    <listitem>
+     <para>
+      Do not throw an error if the extension does not exist. A notice is issued
+      in this case.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><replaceable class="parameter">name</replaceable></term>
     <listitem>
      <para>
@@ -51,6 +61,17 @@ <title>Parameters</title>
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>CASCADE</literal></term>
+    <term><literal>RESTRICT</literal></term>
+
+    <listitem>
+     <para>
+      These key words do not have any effect, since there are no dependencies
+      on publications.
+     </para>
+    </listitem>
+   </varlistentry>
   </variablelist>
  </refsect1>
 
-- 
2.11.0

0003-fixup-Add-PUBLICATION-catalogs-and-DDL.patchtext/x-patch; name=0003-fixup-Add-PUBLICATION-catalogs-and-DDL.patchDownload
From 1755433aa69d20b9bcd9520ac65e7ee4d42ff124 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Tue, 3 Jan 2017 12:00:00 -0500
Subject: [PATCH 3/5] fixup! Add PUBLICATION catalogs and DDL

Small tweak of tab completion.
---
 src/bin/psql/tab-complete.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 9a404dc8ae..8b75ac9f4c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2463,7 +2463,7 @@ psql_completion(const char *text, int start, int end)
 /* DROP */
 	/* Complete DROP object with CASCADE / RESTRICT */
 	else if (Matches3("DROP",
-					  "COLLATION|CONVERSION|DOMAIN|EXTENSION|LANGUAGE|SCHEMA|SEQUENCE|SERVER|TABLE|TYPE|VIEW",
+					  "COLLATION|CONVERSION|DOMAIN|EXTENSION|LANGUAGE|PUBLICATION|SCHEMA|SEQUENCE|SERVER|TABLE|TYPE|VIEW",
 					  MatchAny) ||
 			 Matches4("DROP", "ACCESS", "METHOD", MatchAny) ||
 			 (Matches4("DROP", "AGGREGATE|FUNCTION", MatchAny, MatchAny) &&
-- 
2.11.0

0004-fixup-Add-PUBLICATION-catalogs-and-DDL.patchtext/x-patch; name=0004-fixup-Add-PUBLICATION-catalogs-and-DDL.patchDownload
From da31845bbddc24c9b1885efddeb295f8a93f2158 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Tue, 3 Jan 2017 12:00:00 -0500
Subject: [PATCH 4/5] fixup! Add PUBLICATION catalogs and DDL

Fix typo
---
 src/backend/catalog/pg_publication.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 6ee06c4f13..e628ea8815 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -1,12 +1,12 @@
 /*-------------------------------------------------------------------------
  *
- * publication.c
+ * pg_publication.c
  *		publication C API manipulation
  *
  * Copyright (c) 2016, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
- *		publication.c
+ *		pg_publication.c
  *
  *-------------------------------------------------------------------------
  */
-- 
2.11.0

0005-fixup-Add-PUBLICATION-catalogs-and-DDL.patchtext/x-patch; name=0005-fixup-Add-PUBLICATION-catalogs-and-DDL.patchDownload
From bee09731d4dedfb98b56c1e1511092f9b2b4f8ab Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Tue, 3 Jan 2017 12:00:00 -0500
Subject: [PATCH 5/5] fixup! Add PUBLICATION catalogs and DDL

Remove unused header files.
---
 src/backend/catalog/pg_publication.c   | 6 ------
 src/backend/commands/publicationcmds.c | 8 --------
 src/include/commands/replicationcmds.h | 1 -
 3 files changed, 15 deletions(-)

diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index e628ea8815..89e15d8fa1 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -33,12 +33,6 @@
 #include "catalog/pg_publication.h"
 #include "catalog/pg_publication_rel.h"
 
-#include "executor/spi.h"
-
-#include "nodes/makefuncs.h"
-
-#include "replication/reorderbuffer.h"
-
 #include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/catcache.h"
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 167836d5c6..60de9eb61a 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -37,14 +37,6 @@
 #include "commands/event_trigger.h"
 #include "commands/replicationcmds.h"
 
-#include "executor/spi.h"
-
-#include "nodes/makefuncs.h"
-
-#include "parser/parse_clause.h"
-
-#include "replication/reorderbuffer.h"
-
 #include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/catcache.h"
diff --git a/src/include/commands/replicationcmds.h b/src/include/commands/replicationcmds.h
index aaea845895..f608231722 100644
--- a/src/include/commands/replicationcmds.h
+++ b/src/include/commands/replicationcmds.h
@@ -15,7 +15,6 @@
 #ifndef REPLICATIONCMDS_H
 #define REPLICATIONCMDS_H
 
-#include "catalog/objectaddress.h"
 #include "nodes/parsenodes.h"
 
 extern ObjectAddress CreatePublication(CreatePublicationStmt *stmt);
-- 
2.11.0

#168Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Peter Eisentraut (#166)
Re: Logical Replication WIP

On 03/01/17 20:39, Peter Eisentraut wrote:

In 0001-Add-PUBLICATION-catalogs-and-DDL-v16.patch.gz,

+static bool
+is_publishable_class(Oid relid, Form_pg_class reltuple)
+{
+       return reltuple->relkind == RELKIND_RELATION &&
+               !IsCatalogClass(relid, reltuple) &&
+               reltuple->relpersistence == RELPERSISTENCE_PERMANENT &&
+               /* XXX needed to exclude information_schema tables */
+               relid >= FirstNormalObjectId;
+}

I don't think the XXX part is necessary, because IsCatalogClass()
already checks for the same thing. (The whole thing is a bit bogus
anyway, because you can drop and recreate the information schema at run
time without restriction.)

I got this remark about IsCatalogClass() from Andres offline as well,
but it's not true, it only checks for FirstNormalObjectId for objects in
pg_catalog and toast schemas, not anywhere else.

+#define MAX_RELCACHE_INVAL_MSGS 100
+       List    *relids = GetPublicationRelations(HeapTupleGetOid(tup));
+
+       /*
+        * We don't want to send too many individual messages, at some point
+        * it's cheaper to just reset whole relcache.
+        *
+        * XXX: the MAX_RELCACHE_INVAL_MSGS was picked arbitrarily, maybe
+        * there is better limit.
+        */
+       if (list_length(relids) < MAX_RELCACHE_INVAL_MSGS)

Do we have more data on this? There are people running with 100000
tables, and changing a publication with a 1000 tables would blow all
that away?

Maybe at least it should be set relative to INITRELCACHESIZE (400) to
tie things together a bit?

I am actually thinking this should correspond to MAXNUMMESSAGES (4096)
as that's the limit on buffer size. I didn't find it the first time
around when I was looking for good number.

--
Petr Jelinek 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

#169Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Peter Eisentraut (#167)
1 attachment(s)
Re: Logical Replication WIP

On 03/01/17 22:51, Peter Eisentraut wrote:

On 1/3/17 2:39 PM, Peter Eisentraut wrote:

In 0001-Add-PUBLICATION-catalogs-and-DDL-v16.patch.gz,

Attached are a couple of small fixes for this. Feel free to ignore the
removal of the header files if they are needed by later patches.

Thanks, merged, no they are not needed by other patches.

I also hopefully resolved the concerns you had about the relcache
invalidation and expanded comment in is_publishable_class to make the
intention there bit clearer.

Only attached the changed patch, the rest should still apply fine on top
of it.

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

Attachments:

0001-Add-PUBLICATION-catalogs-and-DDL-v17.patch.gzapplication/gzip; name=0001-Add-PUBLICATION-catalogs-and-DDL-v17.patch.gzDownload
�J�lX0001-Add-PUBLICATION-catalogs-and-DDL-v17.patch�=ks�H���_����r"�e���(�xlg��N��<���I�(�%qL�>��.��@7M6)�v&��u�D|4�h������r�kNy��1����i�0��O����v�S�:�����F�g�������n���zh^�+�g��.�eC���gy�[��ryt�ujD��;�vXw��;�����U��j����5�'�s3z�>^�������O�����/?^����_~`��7��Z���bk��ln1�3�a��N;)��;V�������+�|�6gj;\���cz��c?�86�n{n
������2��"��:2����5b��V��8M���������5���)G���0o�k%�o�7n9rR�g���0s~�2�@������>��K[tXE�G�fXV��0���Y�v�?S�n�����Tb��*�\��'y�_Eq�#������'Sn��X@	+��0�e$@��:h�K`P���`����w���Q`�f������>W�v��+3�F	���8�D8��n����q�t����R[{�S]��!T�/��k�E���#6uK@��<h�c�Z��hvWX����#���eI���r����,������N�{�%^����E�D����>����3�l;�/|���j�$tl�40��������}W��^�&u5�(�Y
=������rzAT������������|G:@.����$�D��b7I`@w���p=pd��t�P`5�H����;Z�g0�t�&��wx��S�J��5�����f^�(eF����;+�Ka`��3��Pl�&78�S��t'�C�$P�j����p_������X�	�F�Vhz�y���+�a���w�0*��hC�Q7f�+,YHz9mf	��jU-5H��k��I$������R���	�����D<��}'
}�����[^����Y�����m�*�W.�Wc ��%P�y
�8���P��s���<m��]������J���bf�
w��^�p��.� ����;h�-��4_l1��3�8��N��[7�X�"�XXXhA�q�WV:v���l���[
\i�*A��.�Ak�rk���S�l�����XxR�r�F(�;���w������L����kqoA����-k���;`���go�n�.5l���Kn��!o D�GC�vo�J���D��������a�f�~�Jw��������rP!�z����a9�@e��C[��S�
��
Z�	�E�aa���Y�l�FhK���Z�U���^"�	�u�������$�j��kw��'Tk������Z
6�kf���|�����$Q\pmH��������������
W��<'��gR	�g���')1�&����b����HHu6��!�E���#�-A��Y�	g���X�N<'^���:@2���������<�s�:��Xi�����T��N[aE�S�D���.��r'���#Wrijs�:�l+��xPA�;�(MWU�*�������
�l{n[p��(�P+��k����M8��	dbY�\�^<���[j����n���������*���x�.�TP�
=�pj�G ��E�u&�E����_bK�
����I7�����I��|�a�����p���X:md�� /�T4q;�0���p������
p�/
fqGq ����c�'"�?�sC����]����y>h����^4��}��	G�>���� �ML�����������=~#v��]������n�!�^P�n�>]c�N8�k�u���]Va�'�yb�D|����t���E���$8���X���
����!Q��f�5�7��F���
=6��i6���U�MBA$�0�2 �}��6��
�`�R�VX}<�-s��>ifp���/]��<"�_��Iy[��k�P�?�Y�c�a�M��i���'4���X���Ve���d/e��v0�?8���t���+�Y�s�:������B�����g�4�&(��I�K]5����n3��(.�+Wr,J�E���q2�L`
�w��x���N�"�s���w�(p67����d��/�3h����8����
������4c���
3�����<���k�@���Z�b�`Y�V$Um�Y���(�;���TY��9}
��'>"\.	��X����u�[N�b�?p��L����A"���1w_�{Y|j����������5;��`7�?^��2����A�%��yP
~L9��Js��3,f64[4��n�N:��d�zrW����+���b�NR]O����(����_�$�
�<}�����
��x%Z��4O��7��-���vq#v�1Gv�
@��Z�����k�(��X=CZ^�;YQ@�������;������2
�li����i���s�^��\��pq�(�����8�],H�F=���^�lm��_�>��������K��x�	*�cts��=�<����@9�!�b�"��W�^y �tu�
����Q��xK���
�j���X:�wU`q
X\	6�F&�PH��./{O*b����X.��
[�r
\%fQ@�s�J�2/�"d��EE�p��%\\��x���W�u�K���<��q�NA^)-���Z\z��o���������U�����\���0�P{�
��/A�5PZ��+��P�lW�/��!�@������V����a�#�Z->����?m������h����vv:��;�m��[�\���XA���������e��z��h6����t)��r�������&�MJa1�[�J�6�����)�p)FJ<�pJ�p4����������`��n|;41<>Bn4����bc��X��3!aM��Q�Dte,�.�J���>z������B~$�&�|>����L!2U��HBHV��%\����x8Ln��D��p����H��������3(�\���2G���7?��U��������Z���^������v�S$�|��B��
H`J����OL���/����H����JA��XP��)�#��)�{|z*r�`�{k06zrFg7�9���WOE�!Y�}I�:I��Q�(i%M����v������dr����l�BR3)����D��`v�����s�>7q��Q�:��6Y�:�QnA/���^������&����q��~g{1���B����iH��'�m���O��IP1��.��T��R�v	K�O-n8����Y����1s�����-�:
�S���p�/h��JmJQ%^a�lzFQ&+�s+mUZu��sUC��X�'Y��"��bBX��c�K3���i�-[q�_a���|]7���0
o����!cd�����C�u��O�L�%�&xB�<������+%���3�vg�l�0��]P����0:2�V�Y�n�|��a���U�N�K��O��*Lq0[J�6�i(u`���z������`�T�,�9��&�4���LtxO������+O���i����\U�{H�8�	�>h�H6��q��#��eQ�>i�IV�I�&��0�W��Q8�;{�W�c�4��pD��.������-v���S(����4��[d3g^��5	[,�m��B[w�dA�E(+I\������&���R��3Mx��?�EH�yV���b�a[ �0�
8�`G<vB�@fh������7�m�H&e�/p�����(�
S
���f�D���E���U�Ph�U;��IP&����KPTU��P�5�*IQh,vU�B#m%I!P=M�B���JST��S�x���KO��(����j=C1:}d�"�:D\c��BD1<~a�"�)��&��gE2���Y��;K�|��:�t���B�e]%�+�e3�>�2Z���"�|c��jFj(�EK}�!��Rm����!d<1F'�`b�Hx�H0����~'���,����Gs�2��vO���	Ir�r|�h/5MoF2}�J]�:�`1�/;�_�A��W������%��)A|R}
S���Q�](I}����-$�����7	�-�>�a��<�F�E"	���g�Z������D�����U������0b'�s�52!�@�6Bm"�b���@�u��)R�k�[�dy`n���$f���&�XV7�1�JKw	Aq����r\��i�02��#�a7� �~$|��HN$�=�f�#'���	_]$��`�D�#�9ZW�|
��8T�' ��M�Qb������V#t ��'��NS%��$�K�)QH#6��w��"z�#	�vn�e��x�k=l�Zr��v�Ld=+�X�}9e�8����&KW6��:
��
�i"C���g�v+��@�7����! ��}�JEyN�
\tmR$r.(��v�pIKW*M�q�t�U�!WG�0�T��J�Zm�K���w�^�]a���O�j��\a����|o��W���U��
�!p��{���Ee����;��+�C�r���v��;�I�����`K��4�(�h	�FS�	�G��S�1qV\�{A`V���[K4�phBb��]�@U��wprN"G{�V�{[�����/NT/
��+f�������������)�	�K.�.g�6�V���$2�e��55������V'�]�#m]�BR2����b]9��$��*���B��	�8� tb��>Y=��UR������~y%����������O{['��5����zC}5������d��!�|\��V"�$'��2UO+%�$�_��]����i�ri��.+,��>��w�������}z�*/�<'����SL0_��n��'`_{�V�R����B���rQ��~@�S�4^`�U<�	t�v��@xC���gfj_2�����S1����{bj�o$�tL�B+bz�7��{�<
����f�<u���[��	�����$=SSz��-_�{/�B�2���R�O�NAy!��]3Y}#�U��lgGFy�F��m	�)tg��j���M�����a�I�����c�g�D����������aw�`��j�v�`��+v<�(*C�m��w�p����.)<����a�N�(t+���� �e����?6�(l�=��j^���E]�a[o�e��M�M��S������b������(M�:�se8rYQrD�E9�@���l�0"S�F$�tR���*��	3j>���t9�C��vr�Ed�rG�P��NWPf�0�9�3*�\f<�)��h�+G����GQ�n>����e$e���������'��6�VZ��aZ�)X��i�����[�<��,�K���=:��G�&�)?��0����!�o����$��;��q��r��bs�ka�:tQ,VF�f�k�����YIdi[�R4Gf��Uy��#Nv{������s/p�g�^!���3W,GV��2w���(a�7�&�������0@�{����������� ��_�����{��I��9�K��s��"-��s�<�k���W�-����T��_�����*���\�O����1Ohr���������
����()��A��))�KE��/����{a��s�`�e�A�M�m53l��,Q�>hBWt����&�uw,7������
���g��pyF(�R{^Sm>�Q��-���t�7���������7��j�������%��XE�Xo���������rt�W���P�xt}���6h�v����NL�^�u_=�a?�L���3y�PkN�����|�<��r�x,�~B�aGc��=x��1��m8��K������V� !��uC�A8����?b(�I����%�7n��Tps��L��/����~�NoB����!�����Z��a�g�
V��A��J�����
���/�*<�6\�����a��������_��'���8���sf� ����o?O�*z�1��	7&?���3|�b��/Y�e|�������/�k�K32WC���Z[c��?(i��k�������t�v�v�Mh�
[hVy������g�o�7hl��������KF�k�v@���*��}z�%�<?}�n)����j��@�*���_`�O0���>��_��A�g���s������9N���8%$�Smt�+q���	�����*~�,�Q~?�,j���$�8~?m�O�kB�������:����������l����J��6�<�o���m�-����h��������y�������q\��?��Tmbd!�EW���	�<�-���I����"Q#��`G�����6�3���l�M�*�����9s��3�
B|x|y��^_�4^5/']8���-��:@@��f�����X����Ee&��o)@��,^I�����N�v�u}����1a���V8�8��j\&@�
b&��hH��@��dx��[�)@�cB:m_� 6�����.���B���������G��yA�]�M F�Qq��!�a���e�(��h;��v:�;����"4tpX�����+��^���:���Q���H#��Cb��(�������!�Un7sj��5���m>�������p�k�F.����61s�u��x���O���,�F&�����z5)��@2��q�g���|L�n8p���Y�+�L��h�X��?���H��O��la�#��W8
-���7�F_�����������'m^���n��<=m���kN}���%P���[�\N����mb��6��g<������3�N�~�~u�:��tf��"�Vw��t��t"��
pv��%��a���s571P7�E�l�o������:��	�+�������o|<o��N�0H^��I��KzEX��0[�s�vn`���o�2���U�I��b�8&�����X�[fBi��	���`�
y/~2�RM(�0�C�]�B�����o������t>j����%�a�W9,�J�a��?��>�������t$������H�{���������{eT�X��Y��r��tS�~d	l�>Y���D�"=�bQ���=A>��2�(���T����J����S�.��
z�'f�Y�Ry����|v��+�e���\�l�B�����Sa������	�9 �>�	�w���#xc��x|����"|��}_[���H���Xzz:3���bC�Y���~)��L=��!1��r���K���w�B
�����d��C%q��u{B�C����F"��$��M�����d�	�7x��;^�*�����T�!�L/�[=;����*�~z.��58�
�K[�Y�J���.�f�x���|��_������:f'�[t�z���0
6���v��]��"S�p�����q����y}y���]�ph8�9p�>$�����s!A�
�O�7sA��rIG���~�Q����}dzI��y��WX��)]>����f{#5�/=;*w���N�� \g�e���3���EB�Za���p5ro��7<sK��*X�t�L�����������x����m����u��E��(���b2���d��p���B}1���"��~������>;{I� �w��<��� G����t�8���.��X�Gp�������������E��(;�����0�5Raj�e�^:�Sv����f[[��v��Y�OYQ����Q������s�f�S8�z�o��J��pX-���t.��qpU��F���=�9A��?��2�2V��8V�4�X���;N��LT}-���l?��I�&6���j�P�[(22��(aW����*��9��N�p^�^���yQ�@pI�	vb&
�����9�����l���M��������Yt���
��rNU���6�Hx��d��I��������y��0���F��b���p�k���]0.�)V`@����qx���wjEo�b���P�o^t���i8w�Q��-�y�
�����}`R��&*���x�!WHY�������,l��!.��~e�'sW�q�0�Z����V~+������/����{&����6�P��@���(���E��M��],O��S�]��WZ�8t�E��0�.���*yn_3-%�rn��m	k?�K@�M,�I����X�����W��W�����[��D��Y������6������t�q��}x
BG���yq�b���oz�������7����pr3�-�vV�JU���+��L����6�{�����Br��/N]��K�m���H����h�3�_t�)'���p,L/r/������[c�B�Z���1
������t/.;���/�h�.�dWZ�y'j�g�����ff�,��%�'���E�Q.�����M�i�j�j��r�����-����q��������J2�e4'���eo1"��u��6��L3��N�;���M�[��B&��&Ho>~�<w���lLg������~�����]e�
r�3��@���
�9G�2�i{Ky�]�c2��b��j����B~U��`����N������N����6��[��c��V0����&�:�d�;��K%���(b4������(�*��U���p@���hW��f?#9x��NOR(U2U��T 2�FF�_��qe��z�c�2cHf��]7�/���H���}��A�����Vs�!���W�e��p��[��z(�Z2��v���~^D��;����Y��w5���4�"!L���*����/��/�dj����7h�����q���z.��K���*������o��Yi����3HW[+����~�`x�8�5��aw��8<��X�#UZf\xH������_5�]0M�G��q4���nn����RUL��������I�v��%�S��e�s�W�N���L�����16160~h�`�~0�,!0�C0�M2���x~��N����mN	���V���L�2��50�vf:��'����2��W`W�%e����'�+�����YZE$�\��yt���9��y0�iHqIo1�G��$��e���pxw3�F��i�%c����WH?w��i�X9�9b�e�O*���x�N8�;�1�E���RG&�$����o��l���6,nWk����)xX����3u.�|�CzKE��}!i��5��5k�JCC��$����{����Y��k��t��u#c��5��I�xx	���JL�k�����yI>������s��7�,��q�w>=�S���4��pD�������p	��|*h�V_�u��|�/�.OO'������A|�#��Jy�X0S
�A�}����WP@�L1	
�����w0�`vO0YL;n����dP
�-i��;v�	�<��%�Q4N\�"�o}�'Q�tV����1]�#����A/zUo1O4�����D��,
:����q�4�h6D���b7�����x��Ff��}x��'L�D���&���,iR_~����M�X�1K5��)z��}?����v��i\7����~l�4�,xF�:T���%~��7*l����C�]0�$��En^g!:��1�6)�t�Wi��0J0��[,A�Y���x� ���-W�c���.���>���`���J��8��gS����B���3WrCRw���x��f��D��+�����t��F�2(IeT$�ol���/��<���?aUdce�������j�o��Ta�wW��>-A���������PC��.hW�1t�3]O���[��"u,�?��1\"�`�#��,�����>�V����L(������OcE��(,&�?��-b�d�g�Gc9`����)��_���-������������]�$7�����>�\�N�0�w
�U��v���]�������%���8�������qz�\�jC��Z'��5���4�<3W���	`�K���W+�{��JJ����k`V���v�
��	"�;��f&�����*ch���P��xR�6uQ
�hM=z��!>�q�9�vK���OlS��/���I���������x^t	6{O��������o����1z���LS3����8�+#�V[�qt=��&;�0vk�Lga���ib��8B��X�h��yLK���z��XJ��*!��������nm681�\N
_��~i�t��j\��C�~��\5M��V�QO����#L�xL!�u�#A�5)!Q'4������#+����8HYf!)d��I-�!�����i��0%���I c�Z�1 �g�7a��'���P��]����t���o��h�L6�1���������dN:h������W��R6��������������5���Bx�0*��&S���U2m����h{�n�-���H�G?�|mbh��g����a��)��g�?f�8���C{�~�9��1^2U�����`��F�D#�?����	}����fB,��p�
�OI��3���=<��f{+n;�_�� ^,�u����� {c#��s�������..|����x}7ea)�-��Y�����l������@+��|����1��{�<i|+��D����}���z�
������d��:��e��_��(��U����m�V�����������I�����Y<m������P���+�uv6�Ln��=n&
M�4i���AfC��kS����]0{�zwK{6�oK����9?���� �%��f+���|O>����0>�jG~syyf��7 ����;h�n$�I�������y����^�R[�\c<��@l0g�hY���5j�b!��W�O8k[�-@��Vf�.���O��.P�M`8���V%U�Aqs�~���?����!E��
��_�[��a��L=}���7����V��',[��������}����u2[dq�����U�e�b1�h������
d����":�cG[Z��d_�Oz�e)��G�&�XY����+A�Q:�$��9jvI�F�R�NO1�H}�l�;�Q6��?�9 le�r���x��?��ZiH��+��VL=�Z���l#%�q���UXa[p��%�/�b�*d9k*� �Q�
����������3����Jl���9��/S�<75�a�/�pP����aN��xR-�aN�F�I���/�"��{��+#�;����������F8��I9�g�N���n�����B��������=��Q��'��#� ��(}�.��'���x���G4r��jy�C��K�^�������jh-j�Dw�M�A{�?��

���D�YJ����.���<Z{����R�]��U8!��w^���6��'��^}�+��p��Y~�>��8m�zdY��k�2kRi�����%�$��h�����(iz��������b�?���8�`8����_�oM1�Q��u;t@����_;���B�������FH�����g��x��=��:�/�C�E����r0��v�G�I�3���+��l��6��W�V�sT!9(���hv�_<��UN��K5�J���,����a5l
 `�-30�w��Na+"~7�T��t����v���d.0r,�%���k��� 3���z�b�.����zJ�vY��Lg�j���X�l���������0��[�UN��
������\eR$.��
���8��4���r�|M�t���$G����
?����x�4^c@f\�\tvQ���"�j7:�W5�Kg'����^1S�c�et�ce�'Z$����
�Yf���^=�H�k	`��jzry��M�#Z���y_be��k�a�v�+�R-��
�{����,5���Q�jr��qVik,�p?5��}��A_v�Z�����1�L(W��`��CeO�w�[�l7�����J��	;c���Q���
�����[�R��j�5��=�����.h<�+J@"a�q
1G|�:�������:��]�rs�=l��(|����9��&[�V	�A[�����3���LUVz��J��=�IA�]��=e%��X���a1����`6��-UocT�V����4Hi��Tn�9u��U��\x(B�T�Y�	���;<I>tU��Q����bR|Q��S]��*U��tP����o�?��*������q���������C]`���E�(3nq��_7Z
(�*���o�;3z;Yr���
�A�����a
��iAh�	�~�ri�MV�����s�{���j�T:���eYe2=xP6S��a���D���2��H�/�7n����P��~�	N�.��p��r�`��AO�KYj0;~A���3��<�9Vw�_���v:F����HIi����������w�w*��� �Rs@�4=�l�I)����L�
f�<F�UgY�������C+R&8~����JN-����$���"1	���>�"EYU#�6�/(�����:9$���@i����*���z���*��
������;X���� �?�Gq��P������(�_�>�t���w���S=���-;���E)��Da;�W���D2T�O������t��M�++�����F���4�b�*N(���V�H6x�����r�.0T,w#$5tN����~��a��w��VK��U�v�.��F
��%f�������=40����gUH�����Lu�Q��F��ZY�WD�nW��{���G���+8QA�u�;���%l��h	���r�Qt��NC��r�R��)�s��_dRM��y	�~�s�R�a�m�(��U��~�<��-�����m|���[s�l�NG?��GV�����y=y��<9�|Jr=����u�ds���0"M:�[eh���g��_uY8��
���<dd7��F����C�$+ ��H�w�&|9��a���H���O=��[/����46�u�q�(
����Msh��6Sq���`�{P9X� +������}�8�����7���=��a~3�t��pIl�Bx���J��9��Y�^���w}Q�?"��j#c���������F�]?m�)�+
�@�q���5
Xi�q�7����O�j�|���
����4��
������
��~1��h?��h�-�����lo��e���jb��e KD���)��
��
81
fq��=L�~JX~����r����N|��t���Rn1���+��y��>��eWM�MJ=��v��Z��s���`��[���U���NX ��`Y�(K
�����2=���X]%�1a�+\_%�(�q��2����.1Wh0��8�C��3�bl����!��AS��d����@�w��7ej�_gs}��t�JcQ[/�%N�7�q�����E��J4���M���s����Y8XL���I���\�����2x��p���a0a ��A_�e@�D�`��'�IC�w�
(���_���@�l�GC�o0=21�\%������:X<P2'��!��7��@&j�DsHy��*����6�M���<+F%'f
W��m3Ol��7����Q'.�l��2�	����b�n�,�������� [j�m^�y}������q��30X���`F��S&j�1N�YG����J��O��hmy���]��pN.J�}���c��]���<�1N���W�����m�TX{S��fR�����F�Pd�k�E�����P`)��Y��b4�0S:s����nh������ ����[xP�����W��&���j5�k�5N��_M�$�]2!�j�,��ls���5�c ���t-1���[�+�83c
n�s��U��D�����h0,�&q_RSt-���Z�iKX�}�^���
W(Y�h�����6Q<X�\����v;W��;���
��K���~�m������(�~����d	�[�X�"K�t�c����\��Z�;�'Vt�����G��}HCL�v�w@�$��u���b�v��x4��=��9l�	�w�;������XnI�z�:hV=�7���8m��������h���
���%zu����FK�6V�&)���,������4'o�7!�/Y�/|Eo�1=��9�<�uL�[\
�:B/o��h3�B����V��Y������}�����r%T��e���l�o�ku�nD�����3@�]0�%��MQ
��@��31��F�tM��v����z����TK�{�.���!5���g��fN��G���:]��5�[Xc���x�y�z�N��\���.���]sH�:'z�G��=�g�����>��|b|	��!��Sx�~�Y��|�Mv�E\5ol/_\/�U4���>��U2�!��l���[-	����>D���s��Ks4����h@<��j2�`�u�RX�`����F#�{��PD�>�
���C<C����7 W�t�>~�-���KNK��N���
��G:�nB���Tr������(�m"CTe��l�����p��2�y��a*�/
&��!������:Q�
h��lK��W�E��2.�7I��l�C�:�.z������������0���g���|�� w��$��g��@��a4+��4;�v�����)��t"S�9�
���2J��0Ud��{�j��^7��N�����n
5
K&�O\�D�����J���6���}���5av���Fk�FY���n�_}�N�g��n��dk���P���i�_,���uy��2��������K��adr��h<Xy�LMD��0��q��WC�)[���h�9�u��W��]����}Q��Y������S3���t�����cA(��k��N���ic�$����
�#cU@m��'����nN7Q�5RY����+'�R&1�j�&��� ��X��8!��R�'��2�:VI�]N��^��Y*P���uF2w�{):�n)�]��9!�Aw�������Q4�����o0���z>l@��[�#O�a>��I<�1zwN�������U�������0�N���D^�b$rt*��KN���f���6	��{O����qiG���9&�8��Y���#|������f9H9~�u�/�	��fo,,�~�����(��6z����'�EfC!�~��&?����������&����9}�Z�\YpK�C[,
5���Rf[o�w���1<�^K�i�O�d#�������i�lM��{�q:P�<}�Yv�o.L��`�EV=�2��C�(��hpQ%M.ni������e�jG��:�c�=X+n�r�x��������L8��
+0|���>6R������l��6��c��h b���^CAn[xS�]0�<%��oWP c9
�}�'�r>�2��@��u��%�V�h��Y,#GX�A��?8�`8��v�X>:�8�3�Q��0���w�i����VR�':��P2H���pno�DR��H������-��R��;_�&�T�����L�,�H�,� �w`�\_u�&�Uj���fo�+�x����������8n
�^3���I&���Z�6�Q����4MeZ
�7����7��I��?�)�RH��`��pY��U���}c38�l4�Xd����m�tg�R��Q�q�O,�� �P����������09���SP��%����
�	CQ�&"�}x�\�ji�E�f��}���L���|jU@���h<H��
�lF�y<C����D�qa�fqA��Q��]�cG��A�J�����id$|z���y��ta��[��'���m-������Y�����`�����������@�J������3�*{��|H�t��C2�"lf{��x�s�7O��y�1���
��nt��8u�q ;�;�kD�n��:�A*���>�~��4��*��Pzq-�~���q���;���b��y������h����`�������y�R���������	�,q����X[�,������g�G:~�xL~���W�$�������1�
�d�K�O�T����e�4�y�;��[����{,������"�6��
������_��(N<���G`:���#l��I<g&	��1������4���8��8�B�g�g����:��	"���oS(�h�rC���2��!D9���N���E�	�������x��=����'
����l�>O�11��
��P5���&�&�������-���$e;�����&M���]�&��|���b�����;���ai ~�N}�
N
'�E�NN'��
|n	��A7���4g�~g�+�!$���*�/v���������W���X�M�.�]D�����M#��/�|s��x�G��a���V';�%��e
����)�V��GsX${M����������>�����fg `�</�����X��Q���Q]���N;���`�\+�o��s{/��A�~�a�7OQe7sB����J��=@������";&x��O���G'F��e����P�����K��!�<����<&��Xd�U�����I�,,z6
fn.��D����W���Ri�+����Q��N�D���q����*�J�/��V�\�j�~�J2+�x@���Yr�`Kb�)�v���������YoT&�uq7�g����463���`�)1	bb"�
�e��S_~��f��eK�JN[l2�6�?��NL���bX	�F
�d������tHj�%��-K����%��%y:��x��2��6KmLQ$�1f�St�����������-[:b(���,�K�+d=�	*W�5�)$L����P��pG����5�C��0����
Ur7/0���<���s�������u�Z�Z>(�v��������#�s}�(**�|���2HEMS+��y��F�&Y7�/��U��7	f;o;{����\�������n
��������pvOY1������P��q@�^�6��^�e2�=���u���bFJ~�|5n�r�W#c��w�WnJ���
u�������Q�'IBgD��%��$dYl��%����:AG~|���Y����a(��?>?�J�/�T�Cn����S�2�1�
����n������M
�q��{FjI�D7h�-Qho5�{��Q;?t_]�����E��y��1\q�	E�N�m���.P���%�!0a�F���P�6��������6����vr�$k��6�Q#�m�Z�j}�^��/+e��?����9����pdK"��T�W���I��
'4�H�x�������(��X?�4Zl,��������F�&��g�xd���1 �>��#:������-�r���L���

bh�
�qx`���M�9�p���`o�#I�@!I���3�s4"����6Z�<��S�"/�����o$�D�Z����
���	M��*@�������	"W���
�Nj�"SrUo�$$��';|�{�}=�w�X�H�wB}�GK�&���=�9��� PL���8+�d�u��ZyXVJ�^X�V������e��u�)��0C�5b�X�Js47�D���3Dn\,Q����d�A�9���l��R,�%�J�@N
�"w��x������/�~B�j^�v_5g'&�K]z�d�������������-)����A�[^@��D��
p�v��<`�U�WC�S���YW��y��H9�h�|@h</������~�(�x��KA����b����b��,5����)U���F	Mp�LL����,�1U
��C��Mw��c�xX��w�t�����`�������A���t�}s,���
�������]A�wP=�@h.�V*�����7�.���S���U�D���V!�M�h��q4�n3/���gAQd?!D���C�~������z��_����XF]�B.��;�Cz_�8�+�M�m�<�'�PJ-������^.���r�(�?b��,�W��\#~�P����I
�p��j{�BNi*%5��������kA�O"�K�����t5{�=�"��Q��+�RV�9������}��9e������(�{��j>Au�gi�[Nd��2&�������#j����m��(|��t
�����jc'�y0�TQ��v�!�&R�4�0�I#�O�J.U�����`v�[0v�s�w<���~���w�h{#�u�a�_`��g<���������K����L=��Q��Qb^�%�DW��Y%Ue����������T�������h �q�`>�QenO�p?��/V9!*�	�Z?%U�<�h3��p�}�[�F�k���]��t>e`�q��)"�����Go�y:8�����&aW��G��"���,�!�������(���`>�n��X�����<�S��t~��gh�z�Y4/�z��^h�/��42��TA��Px����qf������������&�
�6e��{{��"�
81�gg�3_���"%J�Le
|k��<���/['���~L��m�������4h5�'T�5��z���_�����[�y^o��dg�]��1���[���u�#_���i�~�����W�}=������|���GG������4�@>��������<����~��qt7�b@N�fi2�345)jE0B8�>N�=�=������{����<��R�.��W��h|o���������;K"��Y0�1/��wT.VP��;�+VI�A����G��;�#6~U���{�{�R�Se��� ���i��r����}��JQ���[�����_lG�3L���~����'�I�Pl����������I
�|��VT�_E�LHk�^�A��TX�/���mK���8����v&���RoOg��|X��"������4G�,����5�t���}8�c��h<��q��PiM$��(_����~X��W����k.��|�w�l��� �t��x>,��64y���=J��_�/�N��4��
R-k�����%3�4���^�v��:~�N��H}��y�������9��N��Z���=��x�<��cx�;�	
XD���V��sp��7�=����R.m:�b&R�.��h_��������f��W Vd9�:��h#0P�&;_�$���/�]��~bv��e�z����q/K���d����x�Mt�Z����0]��l�����_{_?O���ym���F�1���?�)m�����ym"����?���["^��t	��E�k�0�)��!����K)�\���S}!��=��a�����"*O�lt�~�vO�V��-�=4��4��& ��[�}���
!��j��I?�_�N���{��C�q���|�������_u~�I���g[���L��Z��/��)28�������eMu�M�&��[�����qe���������ONd|7��x�WZ��w>�)�j[��1������r�H`��%9Ws�\�}%�?*�OHX����������/��7I���~�'8��x]�������~��^��![���#K2~��p�����.K�"��G�@��+R�oQu�xq)}o�/N��F�$_�>�G���{���W�n���+��A�p�r{���~��=�<�nOU ��]2:����&	���<�$������Qd3/I���&���w��!���L����h���r�����r�uv�D��.��@�UAb��������bf�[k��F��'5�4�t�v�'���W+����-!
^�(��x�7�-x�K|�S�w���k��"��*@��p|�0����Mm��}�����+V��W�a_3������Fm��X��5�a��Cqs2"W&tpN���}��LZ2/�}m��}���Z��\o����������I�pr�OzF�����`8�q�VEF0���i|��P��A��i���j��=�T��}KR��]�q��MY�
UM�[T�A*���*��*G���Q��0�����C��C%V��z��f�{��<�����3J�f�����u6�W�Y�/���U����u�%����g3���U��V�����-�#O�<����v�{����<�%����W��Z;Z���v;�����iT��=k�{��{G����o.��f��f6eXM��ru{�;��d�:��`MH�t!�7Y��Uvi��*e�i���Yts�}��8��og����y�{rr�1��t�`0��C��!�v�Fz��Z
c���: &�5�3C�(~N1�S��{���y��^�zX-�*��Q�r������2j�Z���/*X_��'Z8�$r����<�����^�02��`Vd�
�5��Uw�
��P�t�W
��
�c����;������A�9`��#������	��L{���=a��cG�OMz���F�$-��.&q*W��'&��L0;���I���?G?&s�]�6�	S�.X���g}������VKJ#��h��+[�=�d"9A���
�J��7�F����F�_E\��/S#��V2C�9��l��/����`[����^e�X)#���t����<�qI�]����fs�3�0���kcd����
���M-��������"�"H�3q�x���G0�gX�� o4�EUmI9��/�x��I��9g�o�TFQ|�-t�I���qs����`���3�!.baici�����>=lN�����qTdXA2O�%&����*K|�`n�S��.�S�g�}4�e��eB�����6&������)�������9���D�������`V~�F�����#���1�noo�!E|o�z@|o�v �9!{WM���x��f�b|+���b	�j.�*����L�qC������{ ����+�&��h��A��
(�*�5F�)5Y`��$:��zI���I�ja�l|#�S�]J�l)������L1'�a��J/zt���b�b��6
(�P�X��~�j;z�c��2>&���7�����^X��kK�;s�Y��X�p�1�^��r����<����q�|]��*���T���fp��W�o�QIFe��N2O�F�>Y�w�I�g���������Q�V��A�����b����g3�4�T���_�Mx���9U0n�IY��Jv�mm��!2=�T
��<����������J��#��;:���.�G�K8��R'�2f��]4�?�&s���n4��v"�D�����o'�u��xTk?P���'������M�x�?�^���9L�S��8������
��%'�w���N�h�������$����c�,C�7��O�IaQ��J�����CX�L�2�0��hJA��t�����H+u�Id?��&�����@H�0KR�b��Q��:��hq&���26�q������2��|�I5V\z�����gg�6�;/I���+
��KO}�+����N���T}�6[�r�$$�A�����I&���kRO��Ay09�!������M�<W:t�By�D��������0�+�����D-�����GF��T����!*L��/R��nCd��0�.�����)�����I�
J�!0��)����VX�������%�j[0o�/�x�������/����������13y�|���{�����(��*,���E��@|�� �C�b1�Y`�P8_�(V��X����le�~vP<�v��j1	��V����������4�����x5o�T�~�_�
j=TS���`=���f9ooUd��M7����X���������D
 -�'	��@F�{d���_.��-����UY,��!��#��&U�RV�����j�+�"k���<�P�w���Z���D��u�\#e����#+��/�g���Cf�1H�S��/y�IN�879g~��Z�����������G���~���y0��W]B�	]�U\Q�x�Uo�6~m��,����K��C�5[����+�z�`�&����)E��� x�"Mw���n�T:�A���~�kl�D_1��,}�����s��9(�����A��p�r��g_
@���$t1�;�l��%����b"I��1���
9�!��6�$��@#�`2	�I�Y�aa�WQ��l�- 2p7]`C{�������g���8�#��h�����-�?�$CX�Zx��@�r��+�M��I�_�t����b�
xmg^�Ud��GG�R8��{]�Wu�GLoUv����_q���ZaP���4.��o�L�Y`�Z�[���I������d~M���Y�5a/�_��i
2`�����MJ \���L�W��F��B��;u�}�~u�h-�O��v��t
��}IG�m2��j^�J=K�8%����[��de`T���]St1
������{�P�����u��K���[���&F�!?�>� f�y�p�����D������+�9�I+[�X�o#g�Xd\QI_Uf�e��I�w�����|��@Dn�����
�e��k{G����T�
�����XZ�n])�C������/R������^�-�nv-��� |�'�z�Crg5��8�*N7����8�{�n�Oy,��mg[&r��"�f�[�"�K�f�t7K��w"A�~��y�5&�O�U��������7�eh����$"<��	|�`#� ������:uJ�-�����0��4U�\���z�b����OJ�S	�H�2���Yg�|�+b�#N����~�,`?�E��#�
��s9����n�z�(��F����z2_L���AX`���h~����F:�8��<���8|��:�����Z"��E����������Y����%
�"|�-d���~���K�9rK��J��T�*���Y~0|���T�^�.������RZ�����b!�X�#��U��?�)*���T�:x�N[�v�s}u����������u������J�p�:���m�^��m89'����V�)�Ie|���W����L�i������As��:����E�:�����*$�"�JR�+�b{*J���Y���j"�io}.�*���[�����b��\�)�+T��7�;���b?�(g�rY�:�U���jp����O����������r�2����u�-�#�����BF-B~�����x6��c�X�M�C��*����L��D��c�;��JC��� ���b�	fa3��b�o�f�l��c��k���Wu��z�.%���.5��������|��S5��!'ut���3"����_/�e�RxX3���C��M,J�C�-!�i�	O<��9�D;)>R#|J�j�O�/����6I���X&�y(�b��V�lM�&2[�?^��,d���4�%�6���2\������Q��vX�:�/������i|������3�0>~
��v�r3�����.2�b�8=t��|3o�NrN.l�{l!
+��m�V���;��6d\������3+�����J���0��J&c5�L��r^s28�#'c5^��[/E/	ec���7��tt�������X�K��z��������Q�U�A����e��EU���0��g��������r�����
�a�~w]�6�AaK1
|��^Er�8�S"����'��'��u3j�Owe]���F3,�Q�;ln@�l���/50G?k���D@�
�a�m�P�#��{�5���7�	%�I'4`n$��7����KyxdL��V:���;��S�_A��|_����4��W_�����.�����������myg��Cy�%
�(�<%���Sx�&?d��%G���=\���QT�Cg�:��^�*[�1�����(Vk��:(�-(�}������NA�W��~�x��W�8aU�6X�nY7��g������X����7�Y<�Q�a��cD��on�8_�a��3��M"��;V�;�Vx�(5{�>@����$��w&�Otgb)p�c���j$m/�M���<���=���.������
�����A/$/)�6LV0_1���j!�SE=�F�,Z~�x����GH�B���b��2��o@�6�����xu�k��I�gd�>n��I�1:��y��C�#W��gc���&��O�t'����?J��^��a��O���m����S���Y�/B:x��@�����aQ�/�/���B�,��$R
,���&�����K��k�6��Cd2W�XK�����(x�`v�]Lof��b,l�s�d2�(��Gj��.����tG=�Y����p��\9��t})��d�I���:�d�Y�'
E�f�z�"�[��-%��L�=�8@Z��Lk�L��������7S���������ju����8��~x��H��-F��}�Y("ct���1=���T��+���x$���`�M��L����C�^-Z���������Sk|��D��<=m�^8E b���0p��[�W|{c���B�0
�x���Lq�����[��S
���E��7�z��bH�z�Y������[�Fx+O0��L&h-���%��b�Q�U�A�T�V��/����r��[	���B�/E}A"<�
a)�]��U�g]�Y��
���:�M����YM<�����HV.������"�Jd7\�i�Y�)\0q��� ���Zj����/�[@��'g����b�l�@,��SC`p:	�yi�=����K{��<�$��!�C�[z�T	Sdm?DF���l�r��el�4�/0�����|H��K��L���ikB��z�H��5@"c,�������V������~C�A@z���a�`6�{� ������0��4��f��<�*�eN�D��G��@H2nM�4�|M����������{�M��g�!>�S���(�������^|��b?�t�-��JH����Nw���T����F��=?3��������ZZy}5�V��y�'���� y($jW�]R���;�g�x7�f�e6�RA�����R�p��w���w��@���T���R�S�q0���j~5E����K���R�w���\�����H���UR�GE����������^z*VYz���������>��R��r������=�qx�����b��t/Lq�i��X�C�{���!]gS^��<Q_��|��Jb�A��)�[�'Y��(�Y�'"/�<dF�t$��D�������$��&�qq?}J+9��I�Uj5v���l�Ng)��?/��]��M����"�b��rT��K��aw/�9�-��O-2����L�?�����b�-���6OY������j���X���Jqnt�������T}�5{�a�l���_�
���*��-F��G2H�T��:��*�����\��[�
�Po=-2�}|�����$�����)v�����#,�����5���sO�{�t�?�A�J�tV�8�������}`���U�g/��Q/����n����E%N�V%�VW���W���w�v��}|�����?^t��&�.��wv��~j�Uw)<[uw�%i��U��f��}�OB����~w�>xe�����}�O�>3%�+�{�oG�X������~�����3m���w����N(f1����t���=��
���b;�Q�vd��I��lh���al������>}}��X.*k���>(�����
�g[���^��{kR�~\<����?�"Q�E�pG��5��(�!�ZL����~4^�M������W�V8"}V��'"���*c.Y4#Ip�m�N�(��J��"fa���Z��x���cL<]�*�6���0�2<���Cy�<�^�:z���^$�����&b0�����n���[/A� S�_C��^I�����WS�;v�_F���V
���������k��P|��
fkXl�W�:�����g��Kf5�m!�F�\�4��0SIjX8?�Y3Jj���r�a�=�1,|Oj��m������^��(=y��kT�����FN�+�����Y�o���'�~2��������l��n�G�R�C�mc".�TZ��e�U�c�������<�*l��:�s����t�R;��JA0�
�9�S�iJi�r�3�p��]�S�;V����?&��$��o�&��o����'����\��/�����~�����?����S	i������6z��;c�{��m���M4�n���)�7]���##�G����e��m���By�x*�$��c�`3~Q��,���x��e�+�;��TR"H�+G��T���^��w�j��*���BJPh����L���n+O�y0����gS9���P��*�m�cqO��l;2h�K�[q��+�/DL�����|��M�5�e��i���M6�
j�7���nq�x�Q�{��P^��tu������
T��(U�k�?U�-���#���u��7?�Jb��Q�vC|��b�5S	�������>n�y��5�������D?m���m:�Z_k7\^;B��`�*���zh]_T�����_l�u�����-�]Ac�����������XEo��~���`|����"^9���
u-��M�A���r����$*�S�yQdT���Q�3QK|�C��{xpX��Q�S���G���a�����2g�d�!�-H��%�6����d�sF|[�d�-�7aLfW$��8R��JVW6��n8`1�AK�'��n�R��s�	�-�9�m���r9��@�&�D\�bA�X&P�+JK�qe��7�9�%�������C�l�6��h�a�?��?�o<��.���&�SQ*Q@c��1�������:k��N�/��T|�8��;3��YP\�e����-;��I�'A?&�yz��K��C�9�p[�]1�qDI]2
���Vz����}�imb",�"1�U�����������IM����Wz�3�/n��*��5�uI��i�����������m���w���Y��y���k�+*&��Ar��N���k�c	��W��=�� ;���t��Vs�9h��'���d1_��Dz���hN.;p�!�L���p2R���[N��$�LW"D5R�������8���oag
���.z���%�Vd
I����M��R`�i�!��%�~DG�6�'�S������dy����{�c�+~<��ag��;����S�cO����SS7V����G��	�v���iZu-�f�.��\"�&l�xi�9���x�'�e#�n03���S�`��12�s���f�;|V���0\ezhspP$L�c�-��A_(m���\��Y�x����j��^��<�����w��Lhi.��UQ�w"�"Xd6
��4fK���)n������iIE�M�=�hQ���8!�����]��3D4��'=��$,�'tb��L'�n0o�����-�x&����O�a���Q��c=@i2)M�����c��=�PUN�/�� n���z����A���5&�~1XU�P@�fO�u���?��&SY���Et��V	�Y��'����&A�F�����X'+�)���M���;����|��\)�Jd���)�7��d�@���"�Jg^VL�bR��W�j�i�dti�Ui�bb)��Q�+,�a�V��R�9z1�49(o��SItgk���H.��>t�5���x�����e*/��t$�D����5|����m�n_{������>������5����J�5�\-��m�`�-q�"U*��+%�+ER"{�f�p<�<�%J��vzi)0����?�O�X��k����q�:i��[�o������x��������0����|��l�����pX�
�u�����k����`��p��Y3v���s�27�M� \����I�xU�t������|�x�������N����	���Bmx����v���	��z��a3���G�
b�uQ���O?�0�pF��m]�9
�b�{����5�1:y��*9�J�N4���~��}�s�H��,����	���t���0��k#���s�T����|T����k�\�#O{��j ��S�0�8�x)NoPC��s�.��R�I�f�a�����'Z��;������.|��Ix�R�Qj�\�����W�V���~H#���]J�
��s��g�o�������3N�����c��%E,�����J�T�(�v���]J+]�I��-��*��9�x��)AaUO�J����W�n�\$��o������x�-c�m��L��`D��6G1�}$f�h9g���������^��i��������B/�o�m�&.�U��]��q�h����z�B\,7�sU��*7��i\����N��:���v����.����yq��+~�M�=������^�f�������/a�z����*	�i�I�B����B���n\s��N���8E�!���s%+�L ���J�6�&������e��:'�5���L���`��L`I���}760�"Z�v���{�lw
��d��%>��5�T�[�-V>	u���;�F�.�Ba:�V��������'��!kH�g��_#���v�Z�dr�&Ua\+p�����:T\��o-b{�M��@�Z��u�D>�/m3�i��-��/k�k�uF�wP9/�Ij;�h,
������p������lT�@����	Rl��H�N�v�a�+0�{9
�A��im&k%���0����A��x����*����b��I���Tv�&��V"x������f�������w;��nn�3o��N��z`�s3o|�;�W�X�odrY}3�g�	���Ci��
4|SfD�n�P��:r���I�}�aK�D�;=wkgW���zG�/���r�u���k��ka`B:�`W�^������T��x~8@�m6�mS�����k����9�Ww�k�O9�P�F���:=�Q�p����q�����V�
'6��y������z��u���=�k�
[�^3R���k��=3�	��#������2��XX��`2"Y	�GO%��_V����&*u����<��4+x�if�}�wZD�>*�R�d)~.|�����fTY���|�G{��R�_�d����$B��j����*,D�Q��Z9FE<@�����nw�RJ�Yr��O����E��E�Ww�*/���O�0�rt2�y��V-�g]�T�)EO}��'�i�/�@��M�db�%���7�a����+o��
���@X��R��k��������_{��*��L�{bl�����&l�.�V��mT<��mP�(��XT��r��P�T~��c&9=���g��W�}f��E��4�9�5M���McR����~���m+I����B�*w��D���+�������"wN�fN9��S���3����SY5m:@��L��
N��;%�]�����m��)���~�����5��:����25�p��w���$���I��gH�(u0����3��@|h(F�
1]��;
���yv����n�o:��������a��W��^�w�_M����OR�|}jU��M�|�B
�M�>L������%3S����y�E7�0&KDR'�&�Z}���1�-GA������{
�*UE�f�74���[5�g������T��V��@E�E���|�?�>�����`|�#?8EHwO����Wh�EK���X=k��|� �AD�Z����G�e�{��J����&��NbAg���s�?l�����W��=��^��Op	���mH��3���x��J>2���~�D���	��U�j��Qa�1��A>z��V.N!$4*����>6<���h�t#��:�Y��J:2��M�r����I�������L�w
o��]45���A�+�����AyHtX����WPg$<�b�] 9�@s�w����f��T���B��y��J�` +��*���<���3r*��T��h�Z��)24l���g���K�	���hz��|:��=�U+��m=����Lj���-@��(0/��u���[J�NzoF��OA��u0|�(f��+Q���FN�����sT
g�uG�s����;���Qh�G�	�JW�����b2%���65����B�����7�!�����_�p%����B���h2��Q*�+)zv�������	qH�S���p�����i
��X,���~�6tc��u�LK|(��p�0#m�z��.�uvd2��V��!�#�����s�m8�����MQ.�n�<M�J��/�u��ki1�U�A~�0��	m���
m��B4�A%���dZ�-���6!���lN���	��6e���MMHm�:��H/�b06J5��	��xa(�y��3������,�H;&�@f�@~*nW�������9/��y&���Y����>@�����aySJ��E0�;s�%�L��*��"==-.B�����S66�++����������&��������n��������]�W���L��i&D�4/B�t�r��<Q%�'Hj`8�� �N�� �)b�s�A��=�m
���	�]��`���tu�o����`�m���F1J�����!��aO��;�d8��}-<F���iQ�����m�~�d����\�������}t��j��L�����?��X�����;��%�����L?WLw��\H�@�w��S����S����k��'Dh�) p�����_>
�G)$�@���W�'�	2t��`�@�nB��.�����AhH �!8�gK�)]!@�-���1����GN@Aa?_P�QPX���X�����#$y9���O�����UX9���I<��%�?+K�=;�W`9~�V�������i3�:~���,.W&����l����nZ���R��
����[q=��~��Z��>��������g;���2����y�	�O�5�j}Cp7�V>�?������������}rx��cc(?'�k�*��>�<��<�y��������B�1|����7m�w�[{�o���X��gJ�b���O���8,8����S*nf�����yKV�v�����-%<����)"��	%	*�^9��|�X�����[gI�[��	�������$P��<Q�.�OG[U 
�����\��Q�S]'�A\���"Y%��r���� ��T��hOy�h����V�����]A{|�� ���Zoooo��R[�E��Jd��W�KU������Th�B�]m_����7�~�%�
�7�a�)Y�$��������_�A�Z���|��!�N[H+�iG���f13�
��&2��_��);*~3�r�7�iJ�������V4���������u��'-��BT�y�*�0���V:�I8l��;E�j :%�ou`���U5��G����ST7��m�*\_!'*�AO8f/P|�2�v}L���vT��$Oc��6�-i�����CG���<Ol��-��o��S��G\�
y%V9��6�G��x1�:H������Q��/�oU3��D~���8��3���w��Z�*B{ttp�wP+�v���au��\�}���kp:�C2�+Kr)��������Ia3���,Z~QEu}�j�M�����$�2�� 9cj�5=�eh5l���0�[�����6�N�,�h������]_v���7X�U�8]�~�z�~����e�<�7�9��
�
�t��Y����K���6����Y��,�e�p�ZZKx���x_	�#��(�'��d�b��2���m�iD@�Rf��c��
?�!~���|C����gKklhh��'H��������rw3SK������X�^�Ha��p�c+M)Q2��o��l}��Wq���'�4��$������s���Y�gfDYC%�A��yB�����pc#�Ja5��jN8�w���b�L�I1�5r^�K�&�������n����m�HcxfZ�����+�����}��j2+��1������7�V����z�q����~�>?mu�'���������������V�Q����`�T��Ww����7��8�7�R������>J�%q�����fKT?>���yq���.{6���/�L��V�4Y�~>i��4;���{����y��]�B�����Q�4�Y���\�����tL�H���3�|a�8bl��w��3�������v#-4��6ge���A�q}80Mw���A�9��_��Ts�����V������]���7����l��X���S&�qP���G�R)��������T���y�s���?F��4t���3zI9�����Zi�U0���
���M��qr���X��w�HG�)}S
���=�o�����}_���Y�D{'
�a��J����_��ZL���Y����B�u�P�Da�aP����n7���� �_LS���K�*:}�I=b�Ko46�-�����l4�aW�Q�
K�kz��r������W4@{���c��A�Mx��3�E)%q �o���>���PC��X9�b��Ff<@�Q��"T�������Po�uJ��N������f�;�%hg�zwNB<�fd^���Q���Jn�,��=]D�������M�7�P�c�;���G)Uwa��m�AC�o)�*�je��W�`P;��_�b�t�d��*�Np��u�R��Q�����d�����u2�vu�o��0�#z������_�q?c����k�����U�l���;�$�X+NXs�#
����Y�R��.~tk�HCH��/K�0$)��s�����J���W��-q��t��!V��C
��
� c�������
b�vQ�qv	�<}�:�N�y<��C��������S����'��+��������h7��_<�v����N�y������� O���/ �j=�q�\`�V��.^wz��a���z#��{�t���z��%~�=�/�J�uxDL��.O��}}�hat@|'�n�QP���kX��c����^0��'��r���lI�Q�O�����x�H��h���'le��4�M���M�d�t�����X��]�� u���O]����W��I�^�&�w�^���0]������}>J�Ws3�a�M�c��
����������$��{��K����3�]����];:l�����f�K��Mv~�^��z��*�����a6�f��x%x��[��������UF2mE�f��j����"3�H���r��_��]�H������y����Vj��jy��s��~&u�c���v�f�Y���V���B9�����M��Y�7PA��f��7��'���7����j0`�)�^hM��u��u�7|)�c�����������0��%�o��4T��Kn�
����d�3y��^�$!�������c$s�Xd��<S�
Rj"�l;�����\������6�3����&���������]j�>��Ir��U����k�-{F�a����{)���|_r
/��vn	/�h �E�::��@4DF�-~�Om�uG�n����8�I����0F� QR��'������u��{�'�U<#69��F�Mg�S8k6������A���C�#_������J�w68���%�T���8����u��
�f�m����%
�0Z����
�qhO��|���U*�����1�a���+H5��=�+����:{�+=�]�i�R�����ds�,? k!8��}�XT�N
�i!k�pM�?��7����%�l��n&�)�[�d&�E��Uv�??���d�W7M�7MF��Z9}���n���������dN�a?����79h���W���(���S��59$}gd��d���c�A���'H�~l�a�1�<"cV�/c����
����f�*��}2F�G���9[����������k�j�hBI8�?p�U�t�g���'9;�X<���������#������y_�	����)Re�jrE���_�����R����P|q�i7�X�����`����-�=E����A=��G�����������5����Qz���^�R���Z�����&����=|Vv)|7F��h���%������a�y�����W�T��B�MaKMveuC��N��P���1>aT:k�����l^����h�B�2�%i��R�4���me����S
�,r���@�%���7k�-��V�}�*5	��z�T���N�Y!I�(g�~+�#��D�*�����!km�{:(�$}�?�I�����QO4'
J�@d����YR�M�(��?h2G�|o�sy\��	�����]t�6V�u�wL0�$��������2���}b�v������Vm�L��U2���C���A���i!�c��VA�E%�?fQ��6V�	��Irl��3�b
��~-_�:��1%�,0`��d
�����J{���`o�b
����>[�r�������P�6��&��P���;2jz���4j��p<Tq�H[��� ���M��Z�%T����b4VKuTD�nX�X�����(Z�|�q�d��u�m���"��:p����`�����a�8�`��bR7k�����Sm-k����V�?X���7�e�tX�
�1�i��9l���I��<��M������������h�/���WZ3|N���
�c\�>�����V�s\A��'>�����
�3]�-�F��2s�=�;%���>J��$���Z��z�@�I�tP�}������Z]
#170Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Petr Jelinek (#144)
4 attachment(s)
Re: Logical Replication WIP

Some small patches for 0002-Add-SUBSCRIPTION-catalog-and-DDL-v16.patch.gz:

- Add a get_subscription_name() function

- Remove call for ApplyLauncherWakeupAtCommit() (rebasing error?)

- Remove some unused include files (same as before)

- Rename pg_dump --no-create-subscription-slot to
--no-create-subscription-slots (plural), add documentation.

In CreateSubscription(), I don't think we should connect to the remote
if no slot creation is requested. Arguably, the point of that option is
to not make network connections. (That is what my documentation patch
above claims, in any case.)

I don't know why we need to check the PostgreSQL version number of the
remote. We should rely on the protocol version number, and we should
just make it work. When PG 11 comes around, subscribing from PG 10 to a
publisher on PG 11 should just work without any warnings, IMO.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

0001-fixup-Add-SUBSCRIPTION-catalog-and-DDL.patchtext/x-patch; name=0001-fixup-Add-SUBSCRIPTION-catalog-and-DDL.patchDownload
From 5fd71a1eb99761cca3cd53990b8302fb58e5a01e Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Wed, 4 Jan 2017 12:00:00 -0500
Subject: [PATCH 1/4] fixup! Add SUBSCRIPTION catalog and DDL

Add get_subscription_name()
---
 src/backend/catalog/objectaddress.c   | 18 +++++-------------
 src/backend/catalog/pg_subscription.c | 23 +++++++++++++++++++++++
 src/include/catalog/pg_subscription.h |  1 +
 3 files changed, 29 insertions(+), 13 deletions(-)

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 936adfc42d..b77fbf341d 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -3341,16 +3341,8 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_SUBSCRIPTION:
 			{
-				HeapTuple	tup;
-
-				tup = SearchSysCache1(SUBSCRIPTIONOID,
-									  ObjectIdGetDatum(object->objectId));
-				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for subscription %u",
-						 object->objectId);
 				appendStringInfo(&buffer, _("subscription %s"),
-				   NameStr(((Form_pg_subscription) GETSTRUCT(tup))->subname));
-				ReleaseSysCache(tup);
+								 get_subscription_name(object->objectId));
 				break;
 			}
 
@@ -4865,13 +4857,13 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 		case OCLASS_SUBSCRIPTION:
 			{
-				Subscription *sub;
+				char	   *subname;
 
-				sub = GetSubscription(object->objectId, false);
+				subname = get_subscription_name(object->objectId);
 				appendStringInfoString(&buffer,
-									   quote_identifier(sub->name));
+									   quote_identifier(subname));
 				if (objname)
-					*objname = list_make1(pstrdup(sub->name));
+					*objname = list_make1(subname);
 				break;
 			}
 
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index 2c58cc653e..c15e9b5d74 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -173,6 +173,29 @@ get_subscription_oid(const char *subname, bool missing_ok)
 }
 
 /*
+ * get_subscription_oid - given a subscription OID, look up the name
+ */
+char *
+get_subscription_name(Oid subid)
+{
+	HeapTuple		tup;
+	char		   *subname;
+	Form_pg_subscription subform;
+
+	tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+	if (!HeapTupleIsValid(tup))
+		elog(ERROR, "cache lookup failed for subscription %u", subid);
+
+	subform = (Form_pg_subscription) GETSTRUCT(tup);
+	subname = pstrdup(NameStr(subform->subname));
+
+	ReleaseSysCache(tup);
+
+	return subname;
+}
+
+/*
  * Convert text array to list of strings.
  *
  * Note: the resulting list of strings is pallocated here.
diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h
index 855ed360fb..645541c9b4 100644
--- a/src/include/catalog/pg_subscription.h
+++ b/src/include/catalog/pg_subscription.h
@@ -70,6 +70,7 @@ typedef struct Subscription
 extern Subscription *GetSubscription(Oid subid, bool missing_ok);
 extern void FreeSubscription(Subscription *sub);
 extern Oid get_subscription_oid(const char *subname, bool missing_ok);
+extern char *get_subscription_name(Oid subid);
 
 extern int CountDBSubscriptions(Oid dbid);
 
-- 
2.11.0

0002-fixup-Add-SUBSCRIPTION-catalog-and-DDL.patchtext/x-patch; name=0002-fixup-Add-SUBSCRIPTION-catalog-and-DDL.patchDownload
From f5f5d6ae0fc627f0fbb3ca7497339c4cd77ab8b7 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Wed, 4 Jan 2017 12:00:00 -0500
Subject: [PATCH 2/4] fixup! Add SUBSCRIPTION catalog and DDL

Remove ApplyLauncherWakeupAtCommit() call for now.
---
 src/backend/commands/subscriptioncmds.c | 2 --
 1 file changed, 2 deletions(-)

diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index a6679879db..9dbd6d4c59 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -359,8 +359,6 @@ CreateSubscription(CreateSubscriptionStmt *stmt)
 
 	InvokeObjectPostCreateHook(SubscriptionRelationId, subid, 0);
 
-	ApplyLauncherWakeupAtCommit();
-
 	return myself;
 }
 
-- 
2.11.0

0003-fixup-Add-SUBSCRIPTION-catalog-and-DDL.patchtext/x-patch; name=0003-fixup-Add-SUBSCRIPTION-catalog-and-DDL.patchDownload
From 4eae87c94c9cf31596c1238b83db9f00e7ea3f8b Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Wed, 4 Jan 2017 12:00:00 -0500
Subject: [PATCH 3/4] fixup! Add SUBSCRIPTION catalog and DDL

Remove unused includes
---
 src/backend/catalog/pg_subscription.c   | 14 --------------
 src/backend/commands/subscriptioncmds.c | 16 ----------------
 2 files changed, 30 deletions(-)

diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index c15e9b5d74..eae50632c6 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -13,34 +13,20 @@
 
 #include "postgres.h"
 
-#include "funcapi.h"
 #include "miscadmin.h"
 
 #include "access/genam.h"
-#include "access/hash.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
-#include "access/xact.h"
 
-#include "catalog/indexing.h"
-#include "catalog/namespace.h"
-#include "catalog/objectaddress.h"
 #include "catalog/pg_type.h"
 #include "catalog/pg_subscription.h"
 
-#include "executor/spi.h"
-
 #include "nodes/makefuncs.h"
 
-#include "replication/reorderbuffer.h"
-
 #include "utils/array.h"
 #include "utils/builtins.h"
-#include "utils/catcache.h"
 #include "utils/fmgroids.h"
-#include "utils/inval.h"
-#include "utils/lsyscache.h"
-#include "utils/rel.h"
 #include "utils/syscache.h"
 
 
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 9dbd6d4c59..22bc2e2040 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -13,17 +13,12 @@
 
 #include "postgres.h"
 
-#include "funcapi.h"
 #include "miscadmin.h"
 
-#include "access/genam.h"
-#include "access/hash.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
-#include "access/xact.h"
 
 #include "catalog/indexing.h"
-#include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
 #include "catalog/objectaddress.h"
 #include "catalog/pg_type.h"
@@ -33,24 +28,13 @@
 #include "commands/event_trigger.h"
 #include "commands/replicationcmds.h"
 
-#include "executor/spi.h"
-
-#include "nodes/makefuncs.h"
-
 #include "replication/origin.h"
-#include "replication/reorderbuffer.h"
 #include "replication/walreceiver.h"
 
 #include "storage/lmgr.h"
 
-#include "utils/array.h"
 #include "utils/builtins.h"
-#include "utils/catcache.h"
-#include "utils/fmgroids.h"
-#include "utils/inval.h"
-#include "utils/lsyscache.h"
 #include "utils/memutils.h"
-#include "utils/rel.h"
 #include "utils/syscache.h"
 
 /*
-- 
2.11.0

0004-fixup-Add-SUBSCRIPTION-catalog-and-DDL.patchtext/x-patch; name=0004-fixup-Add-SUBSCRIPTION-catalog-and-DDL.patchDownload
From e4d177d06cb0d0fa407ba48b93d74282b6e2fd12 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Wed, 4 Jan 2017 12:00:00 -0500
Subject: [PATCH 4/4] fixup! Add SUBSCRIPTION catalog and DDL

Rename pg_dump --no-create-subscription-slot to
--no-create-subscription-slots, add documentation.
---
 doc/src/sgml/ref/pg_dump.sgml | 12 ++++++++++++
 src/bin/pg_dump/pg_backup.h   |  2 +-
 src/bin/pg_dump/pg_dump.c     |  7 ++++---
 3 files changed, 17 insertions(+), 4 deletions(-)

diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index 0107167b42..a1e03c481d 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -799,6 +799,18 @@ <title>Options</title>
      </varlistentry>
 
      <varlistentry>
+      <term><option>--no-create-subscription-slots</option></term>
+      <listitem>
+       <para>
+        When dumping logical replication subscriptions,
+        generate <command>CREATE SUBSCRIPTION</command> commands that do not
+        create the remote replication slot.  That way, the dump can be
+        restored without requiring network access to the remote servers.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
       <term><option>--no-security-labels</option></term>
       <listitem>
        <para>
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index db58367d68..6480fb8e74 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -154,7 +154,7 @@ typedef struct _dumpOptions
 	int			use_setsessauth;
 	int			enable_row_security;
 	int			include_subscriptions;
-	int			no_create_subscription_slot;
+	int			no_create_subscription_slots;
 
 	/* default, if no "inclusion" switches appear, is to dump everything */
 	bool		include_everything;
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index e96f2652cc..a3ed0fb657 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -352,7 +352,7 @@ main(int argc, char **argv)
 		{"snapshot", required_argument, NULL, 6},
 		{"strict-names", no_argument, &strict_names, 1},
 		{"use-set-session-authorization", no_argument, &dopt.use_setsessauth, 1},
-		{"no-create-subscription-slot", no_argument, &dopt.no_create_subscription_slot, 1},
+		{"no-create-subscription-slots", no_argument, &dopt.no_create_subscription_slots, 1},
 		{"no-security-labels", no_argument, &dopt.no_security_labels, 1},
 		{"no-synchronized-snapshots", no_argument, &dopt.no_synchronized_snapshots, 1},
 		{"no-unlogged-table-data", no_argument, &dopt.no_unlogged_table_data, 1},
@@ -937,10 +937,11 @@ help(const char *progname)
 	printf(_("  --if-exists                  use IF EXISTS when dropping objects\n"));
 	printf(_("  --include-subscriptions      dump logical replication subscriptions\n"));
 	printf(_("  --inserts                    dump data as INSERT commands, rather than COPY\n"));
+	printf(_("  --no-create-subscription-slots\n"
+			 "                               do not create replication slots for subscriptions\n"));
 	printf(_("  --no-security-labels         do not dump security label assignments\n"));
 	printf(_("  --no-synchronized-snapshots  do not use synchronized snapshots in parallel jobs\n"));
 	printf(_("  --no-tablespaces             do not dump tablespace assignments\n"));
-	printf(_("  --no-create-subscription-slot do not create replication slot for dumped subscriptions\n"));
 	printf(_("  --no-unlogged-table-data     do not dump unlogged table data\n"));
 	printf(_("  --quote-all-identifiers      quote all identifiers, even if not key words\n"));
 	printf(_("  --section=SECTION            dump named section (pre-data, data, or post-data)\n"));
@@ -3739,7 +3740,7 @@ dumpSubscription(Archive *fout, SubscriptionInfo *subinfo)
 	appendPQExpBufferStr(query, ", SLOT NAME = ");
 	appendStringLiteralAH(query, subinfo->subslotname, fout);
 
-	if (dopt->no_create_subscription_slot)
+	if (dopt->no_create_subscription_slots)
 		appendPQExpBufferStr(query, ", NOCREATE SLOT");
 
 	appendPQExpBufferStr(query, ");\n");
-- 
2.11.0

#171Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Petr Jelinek (#144)
1 attachment(s)
Re: Logical Replication WIP

0003-Define-logical-replication-protocol-and-output-plugi-v16.patch.gz
looks good now, documentation is clear now.

Another fixup patch to remove excessive includes. ;-)

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

0001-fixup-Define-logical-replication-protocol-and-output.patchtext/x-patch; name=0001-fixup-Define-logical-replication-protocol-and-output.patchDownload
From 85537d88ec1f79403d9db28b0bdfede28e60e975 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Wed, 4 Jan 2017 12:00:00 -0500
Subject: [PATCH] fixup! Define logical replication protocol and output plugin

Remove unused includes
---
 src/backend/replication/logical/proto.c     | 32 +----------------------------
 src/backend/replication/pgoutput/pgoutput.c |  8 +-------
 2 files changed, 2 insertions(+), 38 deletions(-)

diff --git a/src/backend/replication/logical/proto.c b/src/backend/replication/logical/proto.c
index 9a2a168b40..6f61357ea3 100644
--- a/src/backend/replication/logical/proto.c
+++ b/src/backend/replication/logical/proto.c
@@ -1,7 +1,7 @@
 /*-------------------------------------------------------------------------
  *
  * proto.c
- * 		logical replication protocol functions
+ *		logical replication protocol functions
  *
  * Copyright (c) 2015, PostgreSQL Global Development Group
  *
@@ -12,44 +12,14 @@
  */
 #include "postgres.h"
 
-#include "miscadmin.h"
-
-#include "access/htup_details.h"
-#include "access/heapam.h"
-
 #include "access/sysattr.h"
-#include "access/tuptoaster.h"
-#include "access/xact.h"
-
-#include "catalog/catversion.h"
-#include "catalog/index.h"
-
-#include "catalog/namespace.h"
-#include "catalog/pg_class.h"
-#include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_type.h"
-
-#include "commands/dbcommands.h"
-
-#include "executor/spi.h"
-
 #include "libpq/pqformat.h"
-
-#include "mb/pg_wchar.h"
-
-#include "nodes/makefuncs.h"
-
 #include "replication/logicalproto.h"
-#include "replication/reorderbuffer.h"
-
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
-#include "utils/memutils.h"
-#include "utils/rel.h"
 #include "utils/syscache.h"
-#include "utils/timestamp.h"
-#include "utils/typcache.h"
 
 /*
  * Protocol message flags.
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 5fc48ac312..04dde5d494 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -1,7 +1,7 @@
 /*-------------------------------------------------------------------------
  *
  * pgoutput.c
- *		  Logical Replication output plugin
+ *		Logical Replication output plugin
  *
  * Copyright (c) 2012-2015, PostgreSQL Global Development Group
  *
@@ -12,13 +12,8 @@
  */
 #include "postgres.h"
 
-#include "access/xact.h"
-
-#include "catalog/catalog.h"
 #include "catalog/pg_publication.h"
 
-#include "mb/pg_wchar.h"
-
 #include "replication/logical.h"
 #include "replication/logicalproto.h"
 #include "replication/origin.h"
@@ -27,7 +22,6 @@
 #include "utils/builtins.h"
 #include "utils/inval.h"
 #include "utils/int8.h"
-#include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/syscache.h"
 
-- 
2.11.0

#172Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Petr Jelinek (#144)
1 attachment(s)
Re: Logical Replication WIP

Comments on 0004-Add-logical-replication-workers-v16.patch.gz:

I didn't find any major problems. At times while I was testing strange
things it was not clear why "nothing is happening". I'll do some more
checking in that direction.

Fixup patch attached that enhances some error messages, fixes some
typos, and other minor changes. See also comments below.

---

The way check_max_logical_replication_workers() is implemented creates
potential ordering dependencies in postgresql.conf. For example,

max_logical_replication_workers = 100
max_worker_processes = 200

fails, but if you change the order, it works. The existing
check_max_worker_processes() has the same problem, but I suspect because
it only checks against MAX_BACKENDS, nobody has ever seriously hit that
limit.

I suggest just removing the check. If you set
max_logical_replication_workers higher than max_worker_processes and you
hit the lower limit, then whatever is controlling max_worker_processes
should complain with its own error message.

---

The default for max_logical_replication_workers is 4, which seems very
little. Maybe it should be more like 10 or 20. The "Quick setup"
section recommends changing it to 10. We should at least be
consistent there: If you set a default value that is not 0, then it
should enough that we don't need to change it again in the Quick
setup. (Maybe the default max_worker_processes should also be
raised?)

+max_logical_replication_workers = 10 # one per subscription + one per
instance needed on subscriber

I think this is incorrect (copied from max_worker_processes?). The
launcher does not count as one of the workers here.

On a related note, should the minimum not be 0 instead of 1?

---

About the changes to libpqrcv_startstreaming(). The timeline is not
really an option in the syntax. Just passing in a string that is
pasted in the final command creates too much coupling, I think. I
would keep the old timeline (TimeLineID tli) argument, and make the
options const char * [], and let startstreaming() assemble the final
string, including commas and parentheses. It's still not a perfect
abstraction, because you need to do the quoting yourself, but much
better. (Alternatively, get rid of the startstreaming call and just
have callers use libpqrcv_PQexec directly.)

---

Some of the header files are named inconsistently with their .c files.
I think src/include/replication/logicalworker.h should be split into
logicalapply.h and logicallauncher.h. Not sure about
worker_internal.h. Maybe rename apply.c to worker.c?

(I'm also not fond of throwing publicationcmds.h and
subscriptioncmds.h together into replicationcmds.h. Maybe that could
be changed, too.)

---

Various FATAL errors in logical/relation.c when the target relation is
not in the right state. Could those not be ERRORs? The behavior is
the same at the moment because background workers terminate on
uncaught exceptions, but that should eventually be improved.

A FATAL error will lead to a

LOG: unexpected EOF on standby connection

on the publisher, because the process just dies without protocol
shutdown. (And then it reconnects and tries again. So we might as
well not die and just retry again.)

---

In LogicalRepRelMapEntry, rename rel to localrel, so it's clearer in
the code using this struct. (Maybe reloid -> localreloid)

---

Partitioned tables are not supported in either publications or as
replication targets. This is expected but should be fixed before the
final release.

---

In apply.c:

The comment in apply_handle_relation() makes a point that the schema
validation is done later, but does not tell why. The answer is
probably because it doesn't matter and it's more convenient, but it
should be explained in the comment.

See XXX comment in logicalrep_worker_stop().

The get_flush_position() return value is not intuitive from the
function name. Maybe make that another pointer argument for clarity.

reread_subscription() complains if the subscription name was changed.
I don't know why that is a problem.

---

In launcher.c:

pg_stat_get_subscription should hold LogicalRepWorkerLock around the
whole loop, so that it doesn't get inconsistent results when workers
change during the loop.

---

In relation.c:

Inconsistent use of uint32 vs LogicalRepRelId. Pick one. :)

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

0001-fixup-Add-logical-replication-workers.patchtext/x-patch; name=0001-fixup-Add-logical-replication-workers.patchDownload
From 30e7b9d70b5c6488dd74eb648c62372911096544 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Thu, 5 Jan 2017 12:00:00 -0500
Subject: [PATCH] fixup! Add logical replication workers

---
 doc/src/sgml/catalogs.sgml                 |  6 ++
 doc/src/sgml/ref/create_subscription.sgml  |  2 +-
 src/backend/executor/execReplication.c     |  6 +-
 src/backend/replication/logical/apply.c    | 88 ++++++++++++++++--------------
 src/backend/replication/logical/launcher.c | 34 ++++++------
 src/backend/replication/logical/relation.c | 42 ++++++--------
 src/include/replication/worker_internal.h  |  2 +-
 src/test/subscription/Makefile             |  4 +-
 8 files changed, 95 insertions(+), 89 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2aba25537b..85bea7e12a 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -6316,6 +6316,12 @@ <title><structname>pg_subscription</structname></title>
    database.
   </para>
 
+  <para>
+   Access to this catalog is restricted from normal users.  Normal users can
+   use the view <xref linkend="pg-stat-subscription"> to get some information
+   about subscriptions.
+  </para>
+
   <table>
    <title><structname>pg_subscription</structname> Columns</title>
 
diff --git a/doc/src/sgml/ref/create_subscription.sgml b/doc/src/sgml/ref/create_subscription.sgml
index 5301abc4ef..40d08b3440 100644
--- a/doc/src/sgml/ref/create_subscription.sgml
+++ b/doc/src/sgml/ref/create_subscription.sgml
@@ -4,7 +4,7 @@
 -->
 
 <refentry id="SQL-CREATESUBSCRIPTION">
- <indexterm zone="sql-altersubscription">
+ <indexterm zone="sql-createsubscription">
   <primary>CREATE SUBSCRIPTION</primary>
  </indexterm>
 
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index 897118f52a..259eb32a18 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -68,7 +68,6 @@ build_replindex_scan_key(ScanKey skey, Relation rel, Relation idxrel,
 		RegProcedure regop;
 		int			pkattno = attoff + 1;
 		int			mainattno = indkey->values[attoff];
-		Oid			atttype = attnumTypeId(rel, mainattno);
 		Oid			optype = get_opclass_input_type(opclass->values[attoff]);
 
 		/*
@@ -82,9 +81,8 @@ build_replindex_scan_key(ScanKey skey, Relation rel, Relation idxrel,
 									   BTEqualStrategyNumber);
 
 		if (!OidIsValid(operator))
-			elog(ERROR,
-				 "could not lookup equality operator for type %u, optype %u in opfamily %u",
-				 atttype, optype, opfamily);
+			elog(ERROR, "could not find member %d(%u,%u) of opfamily %u",
+				 BTEqualStrategyNumber, optype, optype, opfamily);
 
 		regop = get_opcode(operator);
 
diff --git a/src/backend/replication/logical/apply.c b/src/backend/replication/logical/apply.c
index 94859db320..905b4c9b36 100644
--- a/src/backend/replication/logical/apply.c
+++ b/src/backend/replication/logical/apply.c
@@ -254,10 +254,9 @@ slot_store_error_callback(void *arg)
 
 	remotetypoid = errarg->rel->atttyps[errarg->attnum];
 	localtypoid = logicalrep_typmap_getid(remotetypoid);
-	errcontext("processing remote data for replication target %s column %s, "
+	errcontext("processing remote data for replication target relation \"%s.%s\" column \"%s\", "
 			   "remote type %s, local type %s",
-			   quote_qualified_identifier(errarg->rel->nspname,
-										  errarg->rel->relname),
+			   errarg->rel->nspname, errarg->rel->relname,
 			   errarg->rel->attnames[errarg->attnum],
 			   format_type_be(remotetypoid),
 			   format_type_be(localtypoid));
@@ -311,7 +310,7 @@ slot_store_cstrings(TupleTableSlot *slot, LogicalRepRelMapEntry *rel,
 		else
 		{
 			/*
-			 * We assign NULL dropped attributes, NULL values and missing
+			 * We assign NULL to dropped attributes, NULL values, and missing
 			 * values (missing values should be later filled using
 			 * slot_fill_defaults).
 			 */
@@ -329,7 +328,7 @@ slot_store_cstrings(TupleTableSlot *slot, LogicalRepRelMapEntry *rel,
 /*
  * Modify slot with user data provided as C strigs.
  * This is somewhat similar to heap_modify_tuple but also calls the type
- * input fuction on the user data as the input is the rext representation
+ * input fuction on the user data as the input is the text representation
  * of the types.
  */
 static void
@@ -445,14 +444,16 @@ apply_handle_origin(StringInfo s)
 	 * any actual writes.
 	 */
 	if (!in_remote_transaction || IsTransactionState())
-		elog(ERROR, "ORIGIN message sent out of order");
+		ereport(ERROR,
+				(errcode(ERRCODE_PROTOCOL_VIOLATION),
+				 errmsg("ORIGIN message sent out of order")));
 }
 
 /*
  * Handle RELATION message.
  *
  * Note we don't do validation against local schema here. The validation is
- * posponed until first change for given relation comes.
+ * postponed until first change for given relation comes. XXX WHY?
  */
 static void
 apply_handle_relation(StringInfo s)
@@ -479,7 +480,7 @@ apply_handle_type(StringInfo s)
 }
 
 /*
- * Get replicat identity index or if it is not defined a primary key.
+ * Get replica identity index or if it is not defined a primary key.
  *
  * If neither is defined, returns InvalidOid
  */
@@ -562,19 +563,17 @@ check_relation_updatable(LogicalRepRelMapEntry *rel)
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				 errmsg("publisher does not send replica identity column "
-						"expected by the logical replication target %s",
-						quote_qualified_identifier(rel->remoterel.nspname,
-												   rel->remoterel.relname))));
+						"expected by the logical replication target relation \"%s.%s\"",
+						rel->remoterel.nspname, rel->remoterel.relname)));
 	}
 
 	ereport(ERROR,
 			(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-			 errmsg("the logical replication target %s has "
+			 errmsg("logical replication target relation \"%s.%s\" has "
 					"neither REPLICA IDENTIY index nor PRIMARY "
 					"KEY and published relation does not have "
 					"REPLICA IDENTITY FULL",
-					quote_qualified_identifier(rel->remoterel.nspname,
-											   rel->remoterel.relname))));
+					rel->remoterel.nspname, rel->remoterel.relname)));
 }
 
 /*
@@ -665,12 +664,12 @@ apply_handle_update(StringInfo s)
 		/*
 		 * The tuple to be updated could not be found.
 		 *
-		 * TODO what to do here, change the loglevel to LOG perhaps?
+		 * TODO what to do here, change the log level to LOG perhaps?
 		 */
-		ereport(DEBUG1,
-				(errmsg("logical replication could not find row for update "
-						"in replication target %s",
-						RelationGetRelationName(rel->rel))));
+		elog(DEBUG1,
+			 "logical replication did not find row for update "
+			 "in replication target relation \"%s\"",
+			 RelationGetRelationName(rel->rel));
 	}
 
 	/* Cleanup. */
@@ -816,7 +815,9 @@ apply_dispatch(StringInfo s)
 			apply_handle_origin(s);
 			break;
 		default:
-			elog(ERROR, "unknown action of type %c", action);
+			ereport(ERROR,
+					(errcode(ERRCODE_PROTOCOL_VIOLATION),
+					 errmsg("invalid logical replication message type %c", action)));
 	}
 }
 
@@ -952,7 +953,8 @@ ApplyLoop(void)
 				}
 				else if (len < 0)
 				{
-					elog(NOTICE, "data stream from publisher has ended");
+					ereport(LOG,
+							(errmsg("data stream from publisher has ended")));
 					endofstream = true;
 					break;
 				}
@@ -1218,9 +1220,10 @@ reread_subscription(void)
 	 */
 	if (strcmp(newsub->conninfo, MySubscription->conninfo) != 0)
 	{
-		elog(LOG, "logical replication worker for subscription %s will "
-			 "restart because the connection info was changed",
-			 MySubscription->name);
+		ereport(LOG,
+				(errmsg("logical replication worker for subscription \"%s\" will "
+						"restart because the connection information was changed",
+						MySubscription->name)));
 
 		walrcv_disconnect(wrconn);
 		proc_exit(0);
@@ -1232,9 +1235,10 @@ reread_subscription(void)
 	 */
 	if (!equal(newsub->publications, MySubscription->publications))
 	{
-		elog(LOG, "logical replication worker for subscription %s will "
-			 "restart because subscription's publications were changed",
-			 MySubscription->name);
+		ereport(LOG,
+				(errmsg("logical replication worker for subscription \"%s\" will "
+						"restart because subscription's publications were changed",
+						MySubscription->name)));
 
 		walrcv_disconnect(wrconn);
 		proc_exit(0);
@@ -1247,9 +1251,10 @@ reread_subscription(void)
 	 */
 	if (!newsub)
 	{
-		elog(LOG, "logical replication worker for subscription %s has "
-			 "stopped because the subscription was removed",
-			 MySubscription->name);
+		ereport(LOG,
+				(errmsg("logical replication worker for subscription \"%s\" will "
+						"stop because the subscription was removed",
+						MySubscription->name)));
 
 		walrcv_disconnect(wrconn);
 		proc_exit(0);
@@ -1262,9 +1267,10 @@ reread_subscription(void)
 	 */
 	if (!newsub->enabled)
 	{
-		elog(LOG, "logical replication worker for subscription %s has "
-			 "stopped because the subscription was disabled",
-			 MySubscription->name);
+		ereport(LOG,
+				(errmsg("logical replication worker for subscription \"%s\" will "
+						"stop because the subscription was disabled",
+						MySubscription->name)));
 
 		walrcv_disconnect(wrconn);
 		proc_exit(0);
@@ -1354,9 +1360,10 @@ ApplyWorkerMain(Datum main_arg)
 
 	if (!MySubscription->enabled)
 	{
-		elog(LOG, "logical replication worker for subscription %s will not "
-			 "start because the subscription was disabled during startup",
-			 MySubscription->name);
+		ereport(LOG,
+				(errmsg("logical replication worker for subscription \"%s\" will not "
+						"start because the subscription was disabled during startup",
+						MySubscription->name)));
 
 		proc_exit(0);
 	}
@@ -1366,8 +1373,9 @@ ApplyWorkerMain(Datum main_arg)
 								  subscription_change_cb,
 								  (Datum) 0);
 
-	elog(LOG, "logical replication apply for subscription %s started",
-		 MySubscription->name);
+	ereport(LOG,
+			(errmsg("logical replication apply for subscription \"%s\" has started",
+					MySubscription->name)));
 
 	/* Setup replication origin tracking. */
 	originid = replorigin_by_name(MySubscription->slotname, true);
@@ -1380,7 +1388,7 @@ ApplyWorkerMain(Datum main_arg)
 	CommitTransactionCommand();
 
 	/* Connect to the origin and start the replication. */
-	elog(DEBUG1, "connecting to publisher using connection string %s",
+	elog(DEBUG1, "connecting to publisher using connection string \"%s\"",
 		 MySubscription->conninfo);
 	wrconn = walrcv_connect(MySubscription->conninfo, true,
 								MySubscription->name, &err);
@@ -1409,6 +1417,6 @@ ApplyWorkerMain(Datum main_arg)
 
 	walrcv_disconnect(wrconn);
 
-	/* We should only get here if we received sigTERM */
+	/* We should only get here if we received SIGTERM */
 	proc_exit(0);
 }
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 783d97e274..f977effbbc 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -127,9 +127,9 @@ get_subscription_list(void)
 		sub->dbid = subform->subdbid;
 		sub->owner = subform->subowner;
 		sub->enabled = subform->subenabled;
+		sub->name = pstrdup(NameStr(subform->subname));
 
-		/* We don't fill fields we are not intereste in. */
-		sub->name = NULL;
+		/* We don't fill fields we are not interested in. */
 		sub->conninfo = NULL;
 		sub->slotname = NULL;
 		sub->publications = NIL;
@@ -224,7 +224,7 @@ logicalrep_worker_find(Oid subid)
  * Start new apply background worker.
  */
 void
-logicalrep_worker_launch(Oid dbid, Oid subid, Oid userid)
+logicalrep_worker_launch(Oid dbid, Oid subid, const char *subname, Oid userid)
 {
 	BackgroundWorker	bgw;
 	BackgroundWorkerHandle *bgw_handle;
@@ -232,8 +232,8 @@ logicalrep_worker_launch(Oid dbid, Oid subid, Oid userid)
 	LogicalRepWorker   *worker = NULL;
 
 	ereport(LOG,
-			(errmsg("starting logical replication worker for subscription %u",
-					subid)));
+			(errmsg("starting logical replication worker for subscription \"%s\"",
+					subname)));
 
 	/* Report this after the initial starting message for consistency. */
 	if (max_replication_slots == 0)
@@ -281,7 +281,7 @@ logicalrep_worker_launch(Oid dbid, Oid subid, Oid userid)
 	bgw.bgw_start_time = BgWorkerStart_RecoveryFinished;
 	bgw.bgw_main = ApplyWorkerMain;
 	snprintf(bgw.bgw_name, BGW_MAXLEN,
-			 "logical replication worker %u", subid);
+			 "logical replication worker for subscription %u", subid);
 
 	bgw.bgw_restart_time = BGW_NEVER_RESTART;
 	bgw.bgw_notify_pid = MyProcPid;
@@ -291,8 +291,8 @@ logicalrep_worker_launch(Oid dbid, Oid subid, Oid userid)
 	{
 		ereport(WARNING,
 				(errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
-				 errmsg("out of logical replication workers slots"),
-				 errhint("You might need to increase max_logical_replication_workers.")));
+				 errmsg("out of background workers slots"),
+				 errhint("You might need to increase max_worker_processes.")));
 		return;
 	}
 
@@ -304,15 +304,16 @@ logicalrep_worker_launch(Oid dbid, Oid subid, Oid userid)
  * Stop the logical replication worker and wait until it detaches from the
  * slot.
  *
- * Note it's caller's job to ensure that new workers are not being started
- * during this function call. That can be achieven by holding exclusive
- * lock on LogicalRepLauncherLock.
+ * The caller must hold LogicalRepLauncherLock to ensure that new workers are
+ * not being started during this function call.
  */
 void
 logicalrep_worker_stop(Oid subid)
 {
 	LogicalRepWorker *worker;
 
+	Assert(LWLockHeldByMe(LogicalRepLauncherLock));
+
 	LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
 
 	worker = logicalrep_worker_find(subid);
@@ -364,7 +365,7 @@ logicalrep_worker_stop(Oid subid)
 		int	rc;
 
 		LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
-		if (!logicalrep_worker_find(subid))
+		if (!logicalrep_worker_find(subid))  // XXX why not just wait for worker->proc == NULL?
 		{
 			LWLockRelease(LogicalRepWorkerLock);
 			break;
@@ -606,7 +607,7 @@ ApplyLauncherMain(Datum main_arg)
 
 				if (sub->enabled && w == NULL)
 				{
-					logicalrep_worker_launch(sub->dbid, sub->oid, sub->owner);
+					logicalrep_worker_launch(sub->dbid, sub->oid, sub->name, sub->owner);
 					last_start_time = now;
 					wait_time = wal_retrieve_retry_interval;
 					/* Limit to one worker per mainloop cycle. */
@@ -662,7 +663,7 @@ pg_stat_get_subscription(PG_FUNCTION_ARGS)
 {
 #define PG_STAT_GET_SUBSCRIPTION_COLS	7
 	Oid			subid = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);
-	int			wid;
+	int			i;
 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
 	TupleDesc	tupdesc;
 	Tuplestorestate *tupstore;
@@ -694,7 +695,7 @@ pg_stat_get_subscription(PG_FUNCTION_ARGS)
 
 	MemoryContextSwitchTo(oldcontext);
 
-	for (wid = 0; wid <= max_logical_replication_workers; wid++)
+	for (i = 0; i <= max_logical_replication_workers; i++)
 	{
 		/* for each row */
 		Datum		values[PG_STAT_GET_SUBSCRIPTION_COLS];
@@ -703,7 +704,7 @@ pg_stat_get_subscription(PG_FUNCTION_ARGS)
 		LogicalRepWorker	worker;
 
 		LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
-		memcpy(&worker, &LogicalRepCtx->workers[wid],
+		memcpy(&worker, &LogicalRepCtx->workers[i],
 			   sizeof(LogicalRepWorker));
 		if (!worker.proc || !IsBackendPid(worker.proc->pid))
 		{
@@ -724,7 +725,6 @@ pg_stat_get_subscription(PG_FUNCTION_ARGS)
 		MemSet(values, 0, sizeof(values));
 		MemSet(nulls, 0, sizeof(nulls));
 
-		/* Values available to all callers */
 		values[0] = ObjectIdGetDatum(worker.subid);
 		values[1] = Int32GetDatum(worker_pid);
 		if (XLogRecPtrIsInvalid(worker.last_lsn))
diff --git a/src/backend/replication/logical/relation.c b/src/backend/replication/logical/relation.c
index b01cdbf744..80e639ae67 100644
--- a/src/backend/replication/logical/relation.c
+++ b/src/backend/replication/logical/relation.c
@@ -240,7 +240,7 @@ logicalrep_rel_open(uint32 remoteid, LOCKMODE lockmode)
 						HASH_FIND, &found);
 
 	if (!found)
-		elog(FATAL, "cache lookup failed for remote relation %u",
+		elog(ERROR, "no relation map entry for remote relation ID %u",
 			 remoteid);
 
 	/* Need to update the local cache? */
@@ -260,25 +260,23 @@ logicalrep_rel_open(uint32 remoteid, LOCKMODE lockmode)
 											  remoterel->relname, -1),
 								 lockmode, true);
 		if (!OidIsValid(relid))
-			ereport(FATAL,
+			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-					 errmsg("the logical replication target %s not found",
-							quote_qualified_identifier(remoterel->nspname,
-													   remoterel->relname))));
+					 errmsg("logical replication target relation \"%s.%s\" does not exist",
+							remoterel->nspname, remoterel->relname)));
 		entry->rel = heap_open(relid, NoLock);
 
 		/* We currently only support writing to regular tables. */
 		if (entry->rel->rd_rel->relkind != RELKIND_RELATION)
-			ereport(FATAL,
+			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("the logical replication target %s is not a table",
-							quote_qualified_identifier(remoterel->nspname,
-													   remoterel->relname))));
+					 errmsg("logical replication target relation \"%s.%s\" is not a table",
+							remoterel->nspname, remoterel->relname)));
 
 		/*
-		 * Build the mapping of local attribute numbers to remote attrinute
+		 * Build the mapping of local attribute numbers to remote attribute
 		 * numbers and validate that we don't miss any replicated columns
-		 * as that would result in pontentially unwanted data loss.
+		 * as that would result in potentially unwanted data loss.
 		 */
 		desc = RelationGetDescr(entry->rel);
 		oldctx = MemoryContextSwitchTo(LogicalRepRelMapContext);
@@ -293,17 +291,15 @@ logicalrep_rel_open(uint32 remoteid, LOCKMODE lockmode)
 			entry->attrmap[i] = attnum;
 			if (attnum >= 0)
 				found++;
-
 		}
 
 		/* TODO, detail message with names of missing columns */
 		if (found < remoterel->natts)
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-					 errmsg("the logical replication target %s is missing "
+					 errmsg("logical replication target relation \"%s.%s\" is missing "
 							"some replicated columns",
-							quote_qualified_identifier(remoterel->nspname,
-													   remoterel->relname))));
+							remoterel->nspname, remoterel->relname)));
 
 		/*
 		 * Check that replica identity matches. We allow for stricter replica
@@ -340,10 +336,9 @@ logicalrep_rel_open(uint32 remoteid, LOCKMODE lockmode)
 			if (!AttrNumberIsForUserDefinedAttr(attnum))
 				ereport(ERROR,
 						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-						 errmsg("the logical replication target %s uses "
+						 errmsg("logical replication target relation \"%s.%s\" uses "
 								"system columns in REPLICA IDENTITY index",
-							quote_qualified_identifier(remoterel->nspname,
-													   remoterel->relname))));
+								remoterel->nspname, remoterel->relname)));
 
 			attnum = AttrNumberGetAttrOffset(attnum);
 
@@ -450,7 +445,7 @@ logicalrep_typmap_getid(Oid remoteid)
 	if (remoteid < FirstNormalObjectId)
 	{
 		if (!get_typisdefined(remoteid))
-			ereport(FATAL,
+			ereport(ERROR,
 					(errmsg("builtin type %u not found", remoteid),
 					 errhint("This can be caused by having publisher with "
 							 "higher major version than subscriber")));
@@ -465,7 +460,7 @@ logicalrep_typmap_getid(Oid remoteid)
 						HASH_FIND, &found);
 
 	if (!found)
-		elog(FATAL, "cache lookup failed for remote type %u",
+		elog(ERROR, "no type map entry for remote type %u",
 			 remoteid);
 
 	/* Found and mapped, return the oid. */
@@ -482,11 +477,10 @@ logicalrep_typmap_getid(Oid remoteid)
 		entry->typoid = InvalidOid;
 
 	if (!OidIsValid(entry->typoid))
-		ereport(FATAL,
+		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-				 errmsg("type %s required for logical replication not found",
-						quote_qualified_identifier(entry->nspname,
-												   entry->typname))));
+				 errmsg("data type \"%s.%s\" required for logical replication does not exist",
+						entry->nspname, entry->typname)));
 
 	return entry->typoid;
 }
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index 4eb5158264..fd843021a2 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -53,7 +53,7 @@ extern bool	got_SIGTERM;
 extern void logicalrep_worker_attach(int slot);
 extern LogicalRepWorker *logicalrep_worker_find(Oid subid);
 extern int logicalrep_worker_count(Oid subid);
-extern void logicalrep_worker_launch(Oid dbid, Oid subid, Oid userid);
+extern void logicalrep_worker_launch(Oid dbid, Oid subid, const char *subname, Oid userid);
 extern void logicalrep_worker_stop(Oid subid);
 extern void logicalrep_worker_wakeup(Oid subid);
 
diff --git a/src/test/subscription/Makefile b/src/test/subscription/Makefile
index cac5c9020d..bb9795453a 100644
--- a/src/test/subscription/Makefile
+++ b/src/test/subscription/Makefile
@@ -9,12 +9,12 @@
 #
 #-------------------------------------------------------------------------
 
-EXTRA_INSTALL = contrib/hstore
-
 subdir = src/test/subscription
 top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
+EXTRA_INSTALL = contrib/hstore
+
 check:
 	$(prove_check)
 
-- 
2.11.0

#173Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Petr Jelinek (#144)
Re: Logical Replication WIP

0005-Add-separate-synchronous-commit-control-for-logical--v16.patch.gz

This looks a little bit hackish. I'm not sure how this would behave
properly when either synchronous_commit or
logical_replication_synchronous_commit is changed at run time with a reload.

I'm thinking maybe this and perhaps some other WAL receiver settings
should be properties of a subscription, like ALTER SUBSCRIPTION ...
SET/RESET.

Actually, maybe I'm a bit confused what this is supposed to achieve.
synchronous_commit has both a local and a remote meaning. What behavior
are the various combinations of physical and logical replication
supposed to accomplish?

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#174Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Petr Jelinek (#165)
Re: Logical Replication WIP

On 1/2/17 8:32 AM, Petr Jelinek wrote:

On 02/01/17 05:23, Steve Singer wrote:

but I can't drop the subscription either

test_b=# drop subscription mysub;
ERROR: could not drop replication origin with OID 1, in use by PID 24996

alter subscription mysub disable;
ALTER SUBSCRIPTION
drop subscription mysub;
ERROR: could not drop replication origin with OID 1, in use by PID 24996

drop subscription mysub nodrop slot;

doesn't work either. If I first drop the working/active subscription on
the original 'test' database it works but I can't seem to drop the
subscription record on test_b

I can't reproduce this exactly, but I notice that CREATE SUBSCRIPTION
NOCREATE SLOT does not create a replication origin, but DROP
SUBSCRIPTION NODROP SLOT does attempt to drop the origin. If the origin
is not in use, it will just go away, but if it is in use, it might lead
to the situation described above, where the second subscription cannot
be removed.

I guess this is because replication origins are pg instance global and
we use subscription name for origin name internally. Maybe we need to
prefix/suffix it with db oid or something like that, but that's
problematic a bit as well as they both have same length limit. I guess
we could use subscription OID as replication origin name which is
somewhat less user friendly in terms of debugging but would be unique.

I think the most robust way would be to associate origins to
subscriptions using the object dependency mechanism, and just pick an
internal name like we do for automatically created indexes or sequences,
for example.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#175Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Petr Jelinek (#168)
Re: Logical Replication WIP

On 1/3/17 5:23 PM, Petr Jelinek wrote:

I got this remark about IsCatalogClass() from Andres offline as well,
but it's not true, it only checks for FirstNormalObjectId for objects in
pg_catalog and toast schemas, not anywhere else.

I see your statement is correct, but I'm not sure the overall behavior
is sensible. Either we consider the information_schema tables to be
catalog tables, and then IsCatalogClass() should be changed, or we
consider then non-catalog tables, and then we should let them be in
publications. I don't think having a third category of
sometimes-catalog tables is desirable.

Currently, they clearly behave like non-catalog tables, since you can
just drop and recreate them freely, so I would choose the second option.
It might be worth changing that, but it doesn't have to be the job of
this patch set.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#176Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Peter Eisentraut (#174)
Re: Logical Replication WIP

On 10/01/17 14:52, Peter Eisentraut wrote:

On 1/2/17 8:32 AM, Petr Jelinek wrote:

On 02/01/17 05:23, Steve Singer wrote:

but I can't drop the subscription either

test_b=# drop subscription mysub;
ERROR: could not drop replication origin with OID 1, in use by PID 24996

alter subscription mysub disable;
ALTER SUBSCRIPTION
drop subscription mysub;
ERROR: could not drop replication origin with OID 1, in use by PID 24996

drop subscription mysub nodrop slot;

doesn't work either. If I first drop the working/active subscription on
the original 'test' database it works but I can't seem to drop the
subscription record on test_b

I can't reproduce this exactly, but I notice that CREATE SUBSCRIPTION
NOCREATE SLOT does not create a replication origin, but DROP
SUBSCRIPTION NODROP SLOT does attempt to drop the origin. If the origin
is not in use, it will just go away, but if it is in use, it might lead
to the situation described above, where the second subscription cannot
be removed.

This is thinko in it's own regard, origin needs to be created regardless
of the slot.

I guess this is because replication origins are pg instance global and
we use subscription name for origin name internally. Maybe we need to
prefix/suffix it with db oid or something like that, but that's
problematic a bit as well as they both have same length limit. I guess
we could use subscription OID as replication origin name which is
somewhat less user friendly in terms of debugging but would be unique.

I think the most robust way would be to associate origins to
subscriptions using the object dependency mechanism, and just pick an
internal name like we do for automatically created indexes or sequences,
for example.

That will not help, issue is that we consider names for origins to be
unique across cluster while subscription names are per database so if
there is origin per subscription (which there has to be) it will always
clash if we just use the name. I already have locally changed this to
pg_<subscription_oid> naming scheme and it works fine.

--
Petr Jelinek 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

#177Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Peter Eisentraut (#175)
Re: Logical Replication WIP

On 10/01/17 15:06, Peter Eisentraut wrote:

On 1/3/17 5:23 PM, Petr Jelinek wrote:

I got this remark about IsCatalogClass() from Andres offline as well,
but it's not true, it only checks for FirstNormalObjectId for objects in
pg_catalog and toast schemas, not anywhere else.

I see your statement is correct, but I'm not sure the overall behavior
is sensible. Either we consider the information_schema tables to be
catalog tables, and then IsCatalogClass() should be changed, or we
consider then non-catalog tables, and then we should let them be in
publications. I don't think having a third category of
sometimes-catalog tables is desirable.

Currently, they clearly behave like non-catalog tables, since you can
just drop and recreate them freely, so I would choose the second option.
It might be worth changing that, but it doesn't have to be the job of
this patch set.

Okay, looking into my notes, I originally did this because we did not
allow adding tables without pkeys to publications which effectively
prohibited FOR ALL TABLES publication from working because of
information_schema without this. Since this is no longer the case I
think it's safe to skip the FirstNormalObjectId check.

--
Petr Jelinek 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

#178Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Petr Jelinek (#176)
Re: Logical Replication WIP

On 1/11/17 3:11 AM, Petr Jelinek wrote:

That will not help, issue is that we consider names for origins to be
unique across cluster while subscription names are per database so if
there is origin per subscription (which there has to be) it will always
clash if we just use the name. I already have locally changed this to
pg_<subscription_oid> naming scheme and it works fine.

How will that make it unique across the cluster?

Should we include the system ID from pg_control?

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#179Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Petr Jelinek (#177)
Re: Logical Replication WIP

On 1/11/17 3:29 AM, Petr Jelinek wrote:

Okay, looking into my notes, I originally did this because we did not
allow adding tables without pkeys to publications which effectively
prohibited FOR ALL TABLES publication from working because of
information_schema without this. Since this is no longer the case I
think it's safe to skip the FirstNormalObjectId check.

Wouldn't that mean that FOR ALL TABLES replicates the tables from
information_schema?

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#180Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Peter Eisentraut (#179)
Re: Logical Replication WIP

On 11/01/17 18:32, Peter Eisentraut wrote:

On 1/11/17 3:29 AM, Petr Jelinek wrote:

Okay, looking into my notes, I originally did this because we did not
allow adding tables without pkeys to publications which effectively
prohibited FOR ALL TABLES publication from working because of
information_schema without this. Since this is no longer the case I
think it's safe to skip the FirstNormalObjectId check.

Wouldn't that mean that FOR ALL TABLES replicates the tables from
information_schema?

Yes, as they are not catalog tables, I thought that was your point.

--
Petr Jelinek 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

#181Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Peter Eisentraut (#178)
Re: Logical Replication WIP

On 11/01/17 18:27, Peter Eisentraut wrote:

On 1/11/17 3:11 AM, Petr Jelinek wrote:

That will not help, issue is that we consider names for origins to be
unique across cluster while subscription names are per database so if
there is origin per subscription (which there has to be) it will always
clash if we just use the name. I already have locally changed this to
pg_<subscription_oid> naming scheme and it works fine.

How will that make it unique across the cluster?

Should we include the system ID from pg_control?

pg_subscription is shared catalog so oids are unique.

--
Petr Jelinek 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

#182Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Petr Jelinek (#181)
Re: Logical Replication WIP

On 1/11/17 3:35 PM, Petr Jelinek wrote:

On 11/01/17 18:27, Peter Eisentraut wrote:

On 1/11/17 3:11 AM, Petr Jelinek wrote:

That will not help, issue is that we consider names for origins to be
unique across cluster while subscription names are per database so if
there is origin per subscription (which there has to be) it will always
clash if we just use the name. I already have locally changed this to
pg_<subscription_oid> naming scheme and it works fine.

How will that make it unique across the cluster?

Should we include the system ID from pg_control?

pg_subscription is shared catalog so oids are unique.

Oh, I see what you mean by cluster now. It's a confusing term.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#183Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Petr Jelinek (#180)
Re: Logical Replication WIP

On 1/11/17 3:35 PM, Petr Jelinek wrote:

On 11/01/17 18:32, Peter Eisentraut wrote:

On 1/11/17 3:29 AM, Petr Jelinek wrote:

Okay, looking into my notes, I originally did this because we did not
allow adding tables without pkeys to publications which effectively
prohibited FOR ALL TABLES publication from working because of
information_schema without this. Since this is no longer the case I
think it's safe to skip the FirstNormalObjectId check.

Wouldn't that mean that FOR ALL TABLES replicates the tables from
information_schema?

Yes, as they are not catalog tables, I thought that was your point.

But we shouldn't do that. So we need to exclude information_schema from
"all tables" somehow. Just probably not by OID, since that is not fixed.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#184Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Peter Eisentraut (#183)
Re: Logical Replication WIP

On 11/01/17 22:30, Peter Eisentraut wrote:

On 1/11/17 3:35 PM, Petr Jelinek wrote:

On 11/01/17 18:32, Peter Eisentraut wrote:

On 1/11/17 3:29 AM, Petr Jelinek wrote:

Okay, looking into my notes, I originally did this because we did not
allow adding tables without pkeys to publications which effectively
prohibited FOR ALL TABLES publication from working because of
information_schema without this. Since this is no longer the case I
think it's safe to skip the FirstNormalObjectId check.

Wouldn't that mean that FOR ALL TABLES replicates the tables from
information_schema?

Yes, as they are not catalog tables, I thought that was your point.

But we shouldn't do that. So we need to exclude information_schema from
"all tables" somehow. Just probably not by OID, since that is not fixed.

I am not quite sure I agree with this. Either it's system object and we
don't replicate it (which I would have considered to be anything with
Oid < FirstNormalObjectId) or it's user made and then it should be
replicated. Filtering by schema name is IMHO way too fragile (what stops
user creating additional tables there for example).

--
Petr Jelinek 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

#185Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Peter Eisentraut (#173)
Re: Logical Replication WIP

On 06/01/17 21:26, Peter Eisentraut wrote:

0005-Add-separate-synchronous-commit-control-for-logical--v16.patch.gz

This looks a little bit hackish. I'm not sure how this would behave
properly when either synchronous_commit or
logical_replication_synchronous_commit is changed at run time with a reload.

Yes, I said in the initial email that this is meant for discussion and
not as final implementation. And certainly it's not required for initial
commit. Perhaps I should have started separate thread for this part.

I'm thinking maybe this and perhaps some other WAL receiver settings
should be properties of a subscription, like ALTER SUBSCRIPTION ...
SET/RESET.

True, but we still need the GUC defaults.

Actually, maybe I'm a bit confused what this is supposed to achieve.
synchronous_commit has both a local and a remote meaning. What behavior
are the various combinations of physical and logical replication
supposed to accomplish?

It's meant to decouple the synchronous commit setting for logical
replication workers from the one set for normal clients. Now that we
have owners for subscription and subscription runs as that owner, maybe
we could do that via ALTER USER. However I think the apply should by
default run with sync commit turned off as the performance benefits are
important there given that there is one worker that has to replicate in
serialized manner and the success of replication is not confirmed by
responding to COMMIT but by reporting LSNs of various replication stages.

Perhaps the logical_replication_synchronous_commit should only be
boolean that would translate to 'off' and 'local' for the real
synchronous_commit.

--
Petr Jelinek 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

#186Euler Taveira
euler@timbira.com.br
In reply to: Petr Jelinek (#184)
Re: Logical Replication WIP

On 15-01-2017 15:13, Petr Jelinek wrote:

I am not quite sure I agree with this. Either it's system object and we
don't replicate it (which I would have considered to be anything with
Oid < FirstNormalObjectId) or it's user made and then it should be
replicated. Filtering by schema name is IMHO way too fragile (what stops
user creating additional tables there for example).

What happens if you replicate information_schema tables? AFAICS, those
tables are already in the subscriber database. And will it generate
error or warning? (I'm not sure how this functionality deals with
schemas.) Also, why do I want to replicate a information schema table?
Their contents are static and, by default, it is already in each database.

Information schema isn't a catalog but I think it is good to exclude it
from FOR ALL TABLES clause because the use case is almost zero. Of
course, it should be documented. Also, if someone wants to replicate an
information schema table, it could do it with ALTER PUBLICATION.

--
Euler Taveira Timbira - http://www.timbira.com.br/
PostgreSQL: Consultoria, Desenvolvimento, Suporte 24x7 e Treinamento

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

#187Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Euler Taveira (#186)
Re: Logical Replication WIP

On 15/01/17 20:20, Euler Taveira wrote:

On 15-01-2017 15:13, Petr Jelinek wrote:

I am not quite sure I agree with this. Either it's system object and we
don't replicate it (which I would have considered to be anything with
Oid < FirstNormalObjectId) or it's user made and then it should be
replicated. Filtering by schema name is IMHO way too fragile (what stops
user creating additional tables there for example).

What happens if you replicate information_schema tables? AFAICS, those
tables are already in the subscriber database. And will it generate
error or warning? (I'm not sure how this functionality deals with
schemas.) Also, why do I want to replicate a information schema table?
Their contents are static and, by default, it is already in each database.

Information schema isn't a catalog but I think it is good to exclude it
from FOR ALL TABLES clause because the use case is almost zero. Of
course, it should be documented. Also, if someone wants to replicate an
information schema table, it could do it with ALTER PUBLICATION.

Well the preinstalled information_schema is excluded by the
FirstNormalObjectId filter as it's created by initdb. If user drops and
recreates it that means it was created as user object.

My opinion is that FOR ALL TABLES should replicate all user tables (ie,
anything that has Oid >= FirstNormalObjectId), if those are added to
information_schema that's up to user. We also replicate user created
tables in pg_catalog even if it's system catalog so I don't see why
information_schema should be filtered on schema level.

--
Petr Jelinek 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

#188Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Peter Eisentraut (#170)
5 attachment(s)
Re: Logical Replication WIP

Hi,

finally got to this (multiple emails squashed into one).

On 04/01/17 18:46, Peter Eisentraut wrote:

Some small patches for 0002-Add-SUBSCRIPTION-catalog-and-DDL-v16.patch.gz:

Merged thanks.

In CreateSubscription(), I don't think we should connect to the remote
if no slot creation is requested. Arguably, the point of that option is
to not make network connections. (That is what my documentation patch
above claims, in any case.)

Agreed and done.

I don't know why we need to check the PostgreSQL version number of the
remote. We should rely on the protocol version number, and we should
just make it work. When PG 11 comes around, subscribing from PG 10 to a
publisher on PG 11 should just work without any warnings, IMO.

Also agreed and removed.

003-Define-logical-replication-protocol-and-output-plugi-v16.patch.gz
looks good now, documentation is clear now.

Another fixup patch to remove excessive includes.

Thanks merged.

Comments on 0004-Add-logical-replication-workers-v16.patch.gz:

I didn't find any major problems. At times while I was testing strange
things it was not clear why "nothing is happening". I'll do some more
checking in that direction.

Fixup patch attached that enhances some error messages, fixes some
typos, and other minor changes. See also comments below.

Merged.

The way check_max_logical_replication_workers() is implemented creates
potential ordering dependencies in postgresql.conf. For example,

max_logical_replication_workers = 100
max_worker_processes = 200

fails, but if you change the order, it works. The existing
check_max_worker_processes() has the same problem, but I suspect because
it only checks against MAX_BACKENDS, nobody has ever seriously hit that
limit.

I suggest just removing the check. If you set
max_logical_replication_workers higher than max_worker_processes and you
hit the lower limit, then whatever is controlling max_worker_processes
should complain with its own error message.

Good point, removed.

The default for max_logical_replication_workers is 4, which seems very
little. Maybe it should be more like 10 or 20. The "Quick setup"
section recommends changing it to 10. We should at least be
consistent there: If you set a default value that is not 0, then it
should enough that we don't need to change it again in the Quick
setup. (Maybe the default max_worker_processes should also be
raised?)

Well, it's 4 because max_worker_processes is 8, I think default
max_worker_processes should be higher than
max_logical_replication_workers so that's why I picked 4. If we are okay
wit bumping the max_worker_processes a bit, I am all for increasing
max_logical_replication_workers as well.

The quick setup mentions 10 mainly for consistency with slots and wal
senders (those IMHO should also not be 0 by default at this point...).

+max_logical_replication_workers = 10 # one per subscription + one per
instance needed on subscriber

I think this is incorrect (copied from max_worker_processes?). The
launcher does not count as one of the workers here.

On a related note, should the minimum not be 0 instead of 1?

Eh, yes.

About the changes to libpqrcv_startstreaming(). The timeline is not
really an option in the syntax. Just passing in a string that is
pasted in the final command creates too much coupling, I think. I
would keep the old timeline (TimeLineID tli) argument, and make the
options const char * [], and let startstreaming() assemble the final
string, including commas and parentheses. It's still not a perfect
abstraction, because you need to do the quoting yourself, but much
better. (Alternatively, get rid of the startstreaming call and just
have callers use libpqrcv_PQexec directly.)

I did this somewhat differently, with struct that defines options and
has different union members for physical and logical replication. What
do you think of that?

Some of the header files are named inconsistently with their .c files.
I think src/include/replication/logicalworker.h should be split into
logicalapply.h and logicallauncher.h.

Okay.

Not sure about
worker_internal.h. Maybe rename apply.c to worker.c?

Hmm I did that, seems reasonably okay. Original patch in fact had both
worker.c and apply.c and I eventually moved the worker.c functions to
either apply.c or launcher.c.

(I'm also not fond of throwing publicationcmds.h and
subscriptioncmds.h together into replicationcmds.h. Maybe that could
be changed, too)

Okay.

Various FATAL errors in logical/relation.c when the target relation is
not in the right state. Could those not be ERRORs? The behavior is
the same at the moment because background workers terminate on
uncaught exceptions, but that should eventually be improved.

Seems like you changed this in your patch. I don't have any objections.

In LogicalRepRelMapEntry, rename rel to localrel, so it's clearer in
the code using this struct. (Maybe reloid -> localreloid)

Okay.

Partitioned tables are not supported in either publications or as
replication targets. This is expected but should be fixed before the
final release.

Yes, that will need some discussion about corner case behaviour. For
example, have partitioned table 'foo' which is in publication, then you
have table 'bar' which is not in publication, you attach it to the
partitioned table 'foo', should it automatically be added to
publication? Then you detach it, should it then be removed from publication?
What if 'bar' was in publication before it was attached/detached to/from
'foo'? What if 'foo' wasn't in publication but 'bar' was? Should we
allow ONLY syntax for partitioned table when they are being added and
removed?

Sadly current partitioning section of the docs doesn't provide any
guidance in terms of precedents for other actions here as it still
speaks about using inheritance and check constraints directly instead of
the new feature.

My proposal would be to let partitions to be added/removed to/from
publications normally (as they are now) and have them also check if
parent table is published in case they aren't (ie, if partitioned table
is in some publications, all partitions are implicitly as well without
adding them to the pg_publication_rel catalog, but they also keep their
own membership in publications as well individually there). That would
mean we don't allow ONLY syntax for partitioned tables. One scenario
where I am on the fence is what should happen here if we do ALTER
PUBLICATION ... DROP TABLE partitioned_table in case that
partitioned_table also contains partition which was explicitly added to
the publication, should it keep its own membership or should it be
removed? Maybe we could allow the ONLY clause only for DROP but not for ADD?

In apply.c:

The comment in apply_handle_relation() makes a point that the schema
validation is done later, but does not tell why. The answer is
probably because it doesn't matter and it's more convenient, but it
should be explained in the comment.

Yes I noticed, I tried to explain.

See XXX comment in logicalrep_worker_stop().

Yes that was a good point.

The get_flush_position() return value is not intuitive from the
function name. Maybe make that another pointer argument for clarity.

Okay.

reread_subscription() complains if the subscription name was changed.
I don't know why that is a problem.

Because we don't have ALTER SUBSCRIPTION RENAME currently. Maybe should
be Assert?

In launcher.c:

pg_stat_get_subscription should hold LogicalRepWorkerLock around the
whole loop, so that it doesn't get inconsistent results when workers
change during the loop.

Done.

In relation.c:

Inconsistent use of uint32 vs LogicalRepRelId. Pick one.

Done.

Attached is new version with your changes merged and above suggestions
applied. It still does not support partitioned tables and does the
filtering using FirstNormalObjectId.

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

Attachments:

0001-Add-PUBLICATION-catalogs-and-DDL-v18.patch.gzapplication/gzip; name=0001-Add-PUBLICATION-catalogs-and-DDL-v18.patch.gzDownload
��{X0001-Add-PUBLICATION-catalogs-and-DDL-v18.patch�=ks�H���_����r"�e���8�xlg�����<���I�(�%qL�>��.��@7M6)�v&��u�D|4�h����3:{�^g���Iw�N��n����������w�=��s������:�����t�[��kv�����������}�Y^�N��\n���������c�����v_��Y���F��wnF��������X����Y�����������f��x����NN�������<���l���2-�c��/H<k��h��m�q���5�_����D<�����v�sS�/�����8�F�d��������Uv�Y���4�B��n_�:p��Zn���������������I
��5�z �t��m�,�Y��&��pT.@l��*�<�6���0o��R�� ���w3�����k4V����<����*��e��������89�r+���HX!��-� "u��@C\��h.�PC�~%��n4�{6��W_O��������~��5�H�E���%�Quv�4����#/�����������z�:9�]3,r��>��������[�� �A{�����@���(���v��-KB�����f���m��pJ����x�
�C98}�
���?���]�\V��I���i���!���/��c#0��]A/�Y�T ���v|�(f5�\K&
���q�QY;�z:�v��;N��h�����-��$��5{��@���.��C��p#;
0s�hy��tK0����6����N�+%3l�tb����y���E>����.��Eh�,�B��:���4Oa���Xl��@���]V�������X�	�F�Vhz�y���+a���w�0*��hC�Q7f�+,YHz9mf	��jU-5H��k��I$���U�c�kPI�Lj`ka"F�~����>xR���C��#�����s=`��M Ye���B���j����0O����;�J[q�R�����P����5��]i��X��s��a����c�*�`�v���p�����[L��B$��3���
)���+��Z�u������]%�*��@@a�VWZ�J����z��������5�3;bF�&�������_�:{�A�e�^���+�0����z�����n��������R���?�b�-�5�
�������[�����}.Q7}�/�����0
b3r�?T�{���:h#&��u��B6�5����g�P��Fr��@h���ut��@ic��rX���}pF�i[������`����Hc�f�0�h���#�E����'����f��jma��ZU�����5��-?�����k�(.�v@��������������?yN(�)�����9ORblM������!�1��(�.l�C���'F�[�Y�`��!��{N�pC�u�dx��9���}�#x<��uX��.i�����T��N[aE��D���.��r'���#Wrijs�:�l+��xPA�vQ����U�����k*	������Q�V�7l��p����d!�,8N�x������%	��L!���7%�U���
��]<��z���T#�@N%�*�Lj�jM�+���|�9!�n����9��4 ��4���
����5(�t���1A^��h�vda��.���{��_��4��@E���2OD*��#{xv1:��9H�C���Ab80�z=�|�S�	n&����gG��T61O��_�N�nN�+vP����qrz~�g�C\�FO�!�@zA����t�u|:�P���i|��vY�U�����}���� D��s�s���@L�*^`��C*����D�.�����/q�6�����O�Q
���l
"9�A��/�S��9
l���b��������h�[��I3�����~�jw�a�Z�O��B,�X��*���2#Kl��O���6<��f�Z&�*��.� {)���U����^g��}L^��R�����4���z�
60=���4A��M��\�����v�adDq�]��cQr,J>.��aek�w������\gu�g8��W��`g�g#pS�o�K�����f8�y����O����/�.��q3�5�l���?���V
\��f��+�*�"�j#�����G�������F���k��=��rI�����/�c�r*���d������������r���S��ng�,���/����9�9���t��.�,al����j�c���`��T����`1������uct���'�����:��u]����u��z:O'G����B$�o�������Wn�o�+�����y���H��o��M�@�����9�sn�������%\�GI���������r�t0��X�Vo�k�O�kfJ�(4���B4c:�	���>z9�r���-(�8DF3�;���w� �"x�,;�{�����rzqsv�Fk.)����0l������\,;�|R#"���|o,l����Sz\{������*�O��F0��-V+�����K`U��U��5`q%�H-�0B	 �/����T�"a���\R)d� ��J���"�l��%d^�E�:A�2���zIK��N+j�R�u�,�n��s�;|Ri�����RZh=O���]�R�$����}Y��1�)]�W'a,��dX/_��k����Wj?V�T��_��C��v�������
��PG��Z|24'�}��k��S3�h�A��t@Awp���w(�fs��.����#�G�u�+��x�z��l�Su�;J�� /�5��oN�s�|(6)���o�*-/�H^B��.����P)Y�H�)Y�e��R��w�������vv��[�����!r���
w3�F���		kZ�����$�+c�w)Vrm��������C�4Q��1��Ef
��<�@B��%-����������Kay�����4o�_]}8�����+s��~v��^��	�9��;��j�O���?h�=E��g.��A��4M����)��..�G������OT
�G��b�M)(a�O������XK��X�A�����3:����9���z*z ����Kj�I�����8GI�(iB5m7�����6�(� �s��Ld���I��5 <�$���{>�^�#���K�����y=����Q�rzI���"��^�4�t��6�����;���nE����DNC�0�8A�o.� |�-XL��iEw��/(��T�z��KX�xjq�a�v4�R�����������l	�i0��|��~A�D�PjS�*�
+g�3�2Y�[i��������%��<��@�QfX�Zxw�`�\����MoA���
�eg�������Qx[�$�#���=��8�A^�����@]�9`�'!T�����H��2Q��?ciw��f�i~�u�<J
�#nu�U��F��k>hZ������a)���7S�)f�A����5m����T�R�!��5��J���5g`~�d�&�T�����)4X�x��	1u0MzVq����J}��6a����dS7H>Rl�Z�����v��dugnr�C���s��'|�>&NSG4J���(/=�b�z�=���a
OC�E6s��]���2���/��uL�Q�������y�lB��+e�.:��7��3\��1�g%y�,�<hD�����GN��mT �1q������������� Z�W�b������R���uUz��
M��jg�:	�������z	���r
< �&CQ��!)
����Qh��$)���R�p}Wi�
�y
1}u�	3e��]�g(F��WT��k�Y�(��/L]d�>%U�$Cr��H� �u�!�0~g���^���Ny�Q�������v��l�A�G]F��B$�o,_�H
��(c�3dTX����P1���!����L��O�ft�����x�%#��r�hXfs�����1!INU������a��H�P�Y,F�eg��7H��j�V}0�����!�/h�O��a���5J�%�/�u��d��=B�z!�&����g��7�1�G���H$����]k���0���a�0�{���u�F�D�@�F&��H�F�MRl�7��N=�@��bm"�b��L ��\xQMb�l
l��eu�s���t�ln�-�����V##�I�8vC|�G"������@HA���av:r��8����Er`|VND�8����pU��y�C|�\��D%�8KONli�0B��|2��0UR�J����4b#�|w�-��<�pk��]6���G��m\Kn�����g���/����G��4�d���&1SCG�XA=Md���l�n������55��P�(�IBU���M�D����p.i�J�I4�����8���&��4�Bi\��}i�S�N"���+�����XM���+���������>�j��A8n�wOSZ���su�xO�~E��"b��//�i7H�,���T���	��J�M��b��@N`4���P`yd8�g�%�d`f
����D��v $���5
T�*�q'�$r�j���<L�\�rq�zhH�\��a��\�������8�:�u�����������}b�DF������f]��������v�M�kRHJ}�<~B�+��d�W��5T�1���N���'�g�JJ�6��/��/�D��R����5yv�io����Fw���\o��&����_����?$��K�J���$4[��i���$�������<m_.
y�e����Gv����z6��OX�%�����ht|t�	�����5��k���\�SSS(�V.*Qr���b*��L8���G0�����8�b�p<��Lm�K��~X~"&�1}OL
���#��)YhELo���69�c/���!y��Lc���]|�uK_7!#��z���gjJo����Kv�VR&SPJ�)�)(/D��k&�o����C����(O����-a9����_�Y����_�?�1��r��cV|��,��?T?�q����{�������Z�]>v���*�
�P-D[�:��~w;�K
O��&o�Eo��&
���/4&HcY~����
�
l����W�(��BQw��dY�h�i�-�Ta����d�;�"�z<��1J�>�N�\�\V�\�sQ73��/�-����:��?�
�@b�����5|?�E��P����a��Q)�����Y>�j������=��D{
l.Z����a,�Q�����o�aIY��g����!���r�����ah��t
�ao�����!�7�t�o�N����Ij��=�o4/�f���i�
	�����|A�h�������F��@���Q����F�F�1�{VY�����mdU(������ Ghy��������WH+����������&$)JX�
�I�k�����C�A����jX�@�am���!���������4������97|�!��_<'�c��p-?x����.*?Me��e*��_������x=3��f gqkz�8�KAI/�0
������h����TT���B\�.��&�;�f\P&����V3��I����&tE�z�KkB[w�r��x�~��]1x&��W�g�,��5��������(ZAx#?������{�|��������XY� {�U����f�]��
�n�.G7��h�G��X�l�v�`7��p�������E[����3h���=������k���gl�s	*g����'�v4�)�P��G�.�����D����9Qjn�	��K�]7������j�#���4
OJ_��~��I7g����.��xzH�W��&$y��2�����jM^u�Fo��L���TZU���B;O�d��x�ddp�S�^�k��i�0^����~���vvq������@p5v=��~�,U��c��nL~���g����_����������/�k�K32WC���Z[c��?(i���������t�{;�]dZ����U���-$m����
Z���'�k������������C���l��d�.�N^�[�m�3{�%-���$4��671��"���gP���g��F�����4y�Sa>.N	��T��J��@s3��-����=x���g��v0I#��O-�c��Pw;'9h44���3��<:1�x�-��������M:��plk�p�z�A�� �>����F-��m�gq|x|������8���WL�&F�\t��</���S�(i�4_>,5b	v����{n3;�;����������3g�9s.'����I�U��q����q�����d/lf)aj��EJ�YTfr[��Tx�B�����l[���iwZ��=�F�{h��0�C
 ��e�� f����t	��J�������;&���E	�a3�M�\���a�+P�:�-��zD�������b��/�r �Q�^V������+i����O��*BC���aX��|P���W�P��|:�T���i�{H��8U:�sb6$���fN
}���S���'��R� [�nyc��w����X�&f.�Nro��I���������4:�T�&��H�@4�lv!�����
nYZ�0+y�����U���)5���-���U�
GaC��%�v��������z��>9x�v���k�]����������by��o5����SzK���>s��M�V�F�V��g�����q��i�/��.[�T��l�tU����Q���_@�N$�ZAN������1��\{����&�C�������}sP�z;_Gr0q�z=����pr�����^��"&��u;�u{I����fku���
L_���m�Y�5@����5I��_��C���`�L(��<!9���!��OQ�iE�y���\�����@��a�1��G�?�R��0��*���^�4����{���a����0]���=:���"�<�V���#qw����+��� +�R�1�n���,�m�'�0Q�����W��Z,��p��'��6@F����*^QI��z��E��A���l0K[*ob����>��.x�����K��T��^wTr*�����8�3�3�g0��.�Bxol2���2��A������k�VI]�K�COg&��Wl�=��CS�� e�����;$��R.Y�sI8P��@PH��]��,p]�`�� .�T�nO(sH���w���H�t�D6�	��A��BqY��:b���u�KQ�������=����q�g'�6P��O�%5����ri�3kP	\r�E"�,o���O�+���v�Z���}��V���C@�F����n��k�^d�n6[\?>n��7:�/O����'3������@uP`.$H���)�f.�7\.����!�/?*�V����L/i�1���
K�0��g��a��lo����gG�N���I���,�l�U�qF���#�H�T+L#W����F�����gn�4]+�n�����!7�_CY?�X�������pT�������\Lfa?����40^�/p@$�����yZ�gg/I��n���{:�h�@�{:���|��E1k"����wT�rv�����QeGv�^���F*L����K�rj����?�lkk����:K�)+�Y��zw;����u�t��L�w
�P�"�-��^����������!V1�
��H���6'(��g�AFS�j�z�J�����t�����
��|����s5����Z<\MJt��EFF��%�����/]�=gY�����K1W>/*.�=�N�Du�W�8'��|3�
�	[x�]0{5��0BA[�I��t���	�������;	=�}������a8O�"f��h2]�9���.x
S���1�
�����7�Q�N����ZLcJ����z<
��:
����3��A��3{��L���D��qo5�
)K�p��������7D�e�����d��4n��f_+|���o�����b����Rz�t�Db�t����j�(����6���������i�r� ��JK��.������`�@%��k��$R�
��-a��~	����2I������^���������z���(5xy~|y���y�y�.=����A���]5/N_,9���M���������wzwNn�����_���~����UT\��Fw���#}�\Hn{���+8x����rIV~����w����4�������E�euz7�}k\Z+���;F�y��q����e�{��E
�����CK"=�D����y������E������P���!������I3���A���A�zX.�v����R3�p���q�U@I����d��-�C���.y��i��){�b��ir�]\�����/���.y���1X����r��}����A�{��h�u�a:��X�9
`o)��Lf�_,Z�|W7\�������q��w�Ix������]���Fy�8},��
�����Z'�L�c��qi�$�@�f�?S��^�����h?��>��g$���I
�J��V�
D���H�k_:���VO���C`������%�B���}��88�w���j0����
c�,���k�_%[kC�\����C����hvG���Q8+���f����Z$��w�bR�����������L��R�M9��7n2�T�E��`	V�ReV���m�:+mR��y��jk�q�;��o����8��q���v��`�J��i�<V����&�����4>�������\�[��i��;�t���:	��n�b�u�q�L{�c�������	�����#�&��O-���b����%�{w�I�>��oP�I5��� ��)r��
vRw�	]���&��LG#����tX���
�j��l�����}�_�>9K����k�B~#�n���s1�?f� 
).�-F��h��$B�����nf�h�o9B5��dl4��R�
��N#R?m+g8G����S�I�q�/�	"~G>��h:C�Q��D��D����-���m�P������a�R�:k��?�����QcHo����/$-w����f�_)`hH��Dp�7yo^|W?k�tM�.��nd,����5�/�X�T��|�y�6/�gX�����xt�������>����y��yx�Y�hB|={�.�3�OM��+��U��������i��P��8�2�ow��S)����f�a8����}V �

(�)&��?���f��	&�	`�
�9x�������%-�s�5�@��`�4����kS�����$���j�]���>�Kt$#1z���<�Eo���-��&Z����x�EAg����8����X�"]�F4��@�O����>�o�����h����4}�%M��/q}�`�)��0�c��t9E�St�����U��n�;���F���/�����b��(P�
������o��f@���WRtH�F@��������,D�`<���&��N�*M��C	��u��%�2K���$sr���zl<���P��2�s����_�W����2��l
y��[(C�y�JNcH�9�`O3\���H�tE<v�����b��_%��J����
�a��E0����'���ll�L��"{�1��R��
aBP��"L���3��#H�7�w�q8j�����5���c���B�w+z�@������gs�?��A�,t������Z��
��)���vw�i�(�����g�El����LX#��c�"����<�Q�+2z�a���1��v�x1����K�����:�����i������.X�A��^�������q��:�4�4No��Rm�`c�4\�=�����F�Gb�
�3��{I6��j�z9PI)���u
�j������3�CyG���l�$�p^��]Te���0�O*��.�!m��G5�#�g0�:'�n�U��m
r���:}1�X�c�qs����.��f���������w3���v�4Fo���ij�!��4�|e$�j+9�����d��n
��,y��8M��G��k��3/�i��\�yK�}2C%�2=\2�Y?���'f��I�K=�/��NW���!~���;���iv��9���4�x�	�)��v$h�&�"�"���vP�u�qd���@)�,$E��CT8��%=d6��B~5���P?6	d�AQk 6"��L�&l��d��[JX���X��@�q�m4m��f8�R�p�V8����I�Z4����*Z�F�����~3W8v�z��fBr�C[�F��d����J��-1mo���a����vi������Ml����b�]?l�;9����'�S~h��>�w2�K�����by����c���b�����b<0�O�\�L��v���A��#�}�a&���C���v�T��loE�m�`�K������n�����dol��x.z~cB��e���=����,,e��� ���w������Z�th����`�7��2�^u/�'�o�R��s�a����9q_/��s��L�Q��T��Q��E������2����w�x;S�]@���;	��3����?����?v�������M_�����	A�&mx>�l���bm*�=cT�foX/�ni��m������6�g���y�����le�5����������W��o./����D{�q����0�5���;o��0���]jK���o����
�l- �2S�F�U,���j�	gm�H���l��X�I���*�	�bX�����*�8(�c���o�X��z�3�h=W����z���1������q\�����J��~;���a:������������;��Nf�,�qW�c�Y?�*���X,&-16x�2V��l�#[��SD'�a�hK�X����)Co�l#%P����+��Wt%h8J'���1G�.��(S*��)f0���mz�9��4c��3����B>��x�'�T+
�r#s%��
��[K���m�$3��W��
+l\��Q,Y�,gM�4�@�a4c�u��P}�� �@^���0��e����&>��%� �?�i� O�?�i1��8���e^$2w�0ze�|g�����"�8#����1)�����i����u!rP�bT�c6��Q9Y��P4J9��}~���������8��w/���FN`8\-�{��c	������2���A
��A
��n��5h�����R�AS����:K��U�%Z�G�o]��_��k��
'����V������S���ueY���!���������C�,�|w�SfM*-P�8\�d���M<�2Xc�!M�27����r�ZL��p�G'���=b��w���)��<��c�n�nH�a���k�{uU(3R{X��	T�����8����g�~"R���p�h�P.�x���(5IzF�|r%@W��W�f����jw�A�*$�yx����G�A���	��|��_�B���Eq��� �� ����`f�n���)l%C��F��!�[B����y��������F�e�d�0��bm�dF;_SO]l��1��XO��.���ayAm5�+>�
��Z����vfz�;��	�����Zc�S��L�����!tG������Z���)���W��h�/����}�:��k������.J]5Z�P�F�����{�����^�+fj!sL��Nq���DC��X]���3k��Z���r-,xXMO./�)sD��� �K���:bM?����r�T*��������^���[�(j59
����8��5�V���r��P��/��	�D�i���n&��B_��q���'�;�-~�u��a�S�����1t�����w��tS�O����L)cY5�j�j[F~�b4�% �0�8��#��	����t�b���._�9���v��K����MW�-R�� �-��A�z�d:��%�����c�+{�+����f�{�J0�������b6��w{�l6
g[����0��w�i�����s��������P�����vI)&8wx�|���9�z[�q����4����wU��q	���A�	�<M7T�UQ��������'��������Pf*�����n�P�U�y���5vf�<v��������
�M%`��)��,�^����d
���%�I����j��~�t���dz��l�9���'ye�;"~�2)^�o���9	����,2����]��8!�z������`&v���A3�g�Iy�s���*���t�&�M�-��"�N�V7?���%_��9�TLm#Ab���8iz����*R(Rc)����(y�>�"��~��)�3��H�8��UB~G*9����z������$����$�eU�������:�e��^Z���XF`Z�#2,K�Xg���w���yoo�pw��,�g]�	���8����(���AEx��/a��:����y��������E����Q����+��r"*�'�E�r��s��������n[�Z�n�p�o�y'���i�q
�?����Lf�z*���:���nm�{��?����k���%���E;b����#j�3�n�f���Tx���*�W�XTz�:���tr��p���,��+�J��Q�=�r�#UM���� �:��
��6�u�S�U9�(:eN��ch�A)���9
��/2�&~��Q���l)
���6K@�*{U?o���r@���6>Kp����e6E��]�#�R��u���<�x��Y>%��M���:q�9�}B�&��24����3S���,��C���g2���^������!C�aG$�;n��Og�0IE^�K���������BY�:�8Mo_c�����M���V��j%�����c�>8���|�2gb���������3��o&r���.�M�Oo��X�T�#�1���s��/J�G[Pmd��X���zq}~�h����6�w����h2��?�Fk"�?���������i[�����S�b������!7~�\�X��/&]���-��>�������,z�WMl��d�h�R:��#�awS'��,]��)�O	���\���X��oX�n�WT�-fc�}p��b6/Z�W�����I�g�>�y_�Yb._��9{+��*���	$1�!��e������\������$3&�t���d�:.�Q����_�%�

f��w��|[����C�1�[5hj[\�����������B��LM��ln��}��Ui,j�����F<�Z�����Z�f�������{N�35�� �C�=	}�����P�{���<&�@?�a�H��?L�D8i(�X%{{����"(�m�h���
��B&&�+�d��=�R�J�$x4d����D-�hi"/0TeQ�p��"���1�g�����L����m������CqC�?��%�M8W�>!w���T�����;���R�dK����9�����y�?n�w�7~�HQ�w�D-7�I:�hB�=TIq�	��-o����C���E	�����u���[��G0&�Iw�J�����M�
ko�Z��O���7�v�H�t�������
,��5^�ffJg��R��

p�]���V6u����~��y���UW��w���i��������K&�Q-x��;�mn����b~��%F1;zk���g`f��-cp�;�
}�h0��
�E�$�Kj�����2P�6mI��o�+1@��
%�M������&���Z�[�n���r���a�~�����-�:���������~���,��z�#k@Zd��N]`���p�+��Zkt'����0�Sb�h���c���.�h��Q�n1{V����Q������b2��4��w�=9|�m"��C�X�����v����8��:�-�]��S��D��_��Hc������$�_��E��tu����m�&d��%�����M�!�'U6g�'��	z�KaTG����Vmb��C[�z���7K"�����������S��A8��2�tM6������7����}��

�.���������X�����]M�P��~n;{�i��=�P[y�%��N�F����kZ���\3'��#s�\�.s���-�1��q��<^=W�K{��Q~
F���9$�j�=�#���3��k�ho��I>1��a����)��	���uc�I�&;�"��7��/���*�U�	
W����*���F�������II�~�S���z���9�R�\4 ��w5���:P),^0Wq�iX��=�~("CW��Q
y�!����hl��+\:y����s�%���K\��U��{�#�Q7!���Y*���]�o�[�6	�!�2W_��J�xG8�r��U��
��
��|�^}�T�(y���y�������"ff�����\6���!I�y��a���sJ��PFK��G���H}��A�;�u����u �E�0��@E�
M;��q�zc�f�:������X_%�r�*�y��j5Z�o���f��mwPU���%���'.X�T�w�������A�g�t����O��0;��d��D��lVg����R'�W���y�~r��L�Jd(������/�����J`NdGH���z����02�re4�<x�&"e�	w�8�������b�g4��:�V���\���L�������sB�TH�K���Ps{:�j�t�1� `��iH��P��1W�r�f���*�����J_�g7��(���^zz����b�)���B5P�tyGC��u�p])�sWy�$�.�
�D/V�,����:#������M��.�������;��d�B�
�(�r�`��7@IQ��6���������0���$�M���
'�D�oaU�*�fuVovV�X�pZe"�h19:��%'V|M3��I��oE�=��������#Tr��d����Or����N��e�$�?�:��mz�7�D�Z��duH�V=o���������Z?�g��y������M�CP}q��>`-S������-���]�a)�-�7�������������'R2�����hPf���_�&��=�8
(}����,;�7&�V��"�X�������M4���&��|ua^����?�#yz������x9B<N`�XN�k}&�H�����z��sE����i6���
�Q��Xn4���Ng�� �-�)�.�Q��	���+(��������N9�r�Er J	�:@�v+X���,����#����u�X0��N�H,a���(��3���w�i����VR�':��P2H���pno�DR��H������-��R��;_�&�T�����L�,�H�,� �w`�\_u�&�Uj���fo�+�x����������8n
�^3���I&���Z�6�Q����4MeZ
�7����7��I��?�)�RH��`��pY��U���}c38�l4�Xd����m�tg�R��Q�q�O,�� �P����������09���SP��%����
�	CQ�&"�}x�\�ji�E�f��}���L���|jU@���h<H��
�lF�y<C����D�qa�fqA��Q��]�cG��A�J�����id$|z���y��ta��[��'���m-������Y�����`�����������@�J������3�*{��|H�t��C2�"lf{��x�s�7O��y�1���
��nt��8u�q ;�;�kD�n��:�A*���>�~��4��*��Pzq-�~���q���;���b��y������h����`�������y�R���������	�,q����X[�,������g�G:~�xL~���W�$�������1�
�d�K�O�T����e�4�y�;��[����{,������"�6��
������_��(N<���G`:���#l��I<g&	��1������4���8��8�B�g�g����:��	"���oS(�h�rC���2��!D9���N���E�	�������x��=����'
����l�>O�11��
��P5���&�&�������-���$e;�����&M���]�&��|���b�����;���ai ~�N}�
N
'�E�NN'��
|n	��A7���4g�~g�+�!$���*�/v���������W���X�M�.�]D�����M#��/�|s��x�G��a���V';�%��e
����)�V��GsX${M����������>�����fg `�</�����X��Q���Q]���N;���`�\+�o��s{/��A�~�a�7OQe7sB����J��=@������";&x��O���G'F��e����P�����K��!�<����<&��Xd�U�����I�,,z6
fn.��D�
�k�`�\*T���re��yv'K"q��(TZ������K����;W������
)�D�yt��8���d���-��D��yn�Du����m�G�������i������1{JL�A��g�b�����_"��ay�������������7��VB�Q�=�k��~�)t��Zr	�r�R��c�w�GI�g�E�r�Nt`qa��*Yjc�"��1��c�V$����go��C!�Fd�X�]!�LP���1N!a�0�L������;���~�9P*�!�/��P���y��/�����c������������A�T:���*K^����\_-��J1w��RQ����g�r���I�
F����CU��MB�Y�������$0(������n
��������pvOY1������P��q@�^�6��^�e2�=���u���bFJ~�|5n�r�W#c��w�WnJ���
u�������Q�'IBgD��%��$dYl��%����:AG~|���Y����a(��?>?�J�/�T�Cn����S�2�1�
����n������M
�q��{FjI�D7h�-Qho5�{��Q;?t_]�����E��y��1\q�	E�N�m���.P���%�!0a�F���P�6��������6����vr�$k��6�Q#�m�Z�j}�^��/+e��?����9����pdK"��T�W���I��
'4�H�x�������(��X?�4Zl,��������F�&��g�xd���1 �>��#:������-�r���L���

bh�
�qx`���M�9�p���`o�#I�@!I���3�s4"����6Z�<��S�"/�����o$�D��K)%��U!M&��aU�6e�G	}7D��W8���E����0BHH�Ov���/�z6���������N��MVY5{.s4|�A����)qV���ctP)�z�Rio���;X.>�C�-��!�������FLB�\i��&�(��z����%
q���7��>'^��m�[*����Q���!�C�����Q������@hZ�����f�����|�K/�Lq�6���Yq����Y�%��[�4�x�����9_��������l���j�y*Y�?���>=�>�)'
��#����01��O����)��^��Z�Tq_�P�p���\~z#2��ty�(�	�����6@���>�J��p�����yL�S�n���}]������[ �6~���o���]A����x��+h�T�w�w��+���E�v*Q��Jm���g�B��*�:��h2�,f^2�)V����~B�Pid�:��$)K��4Y�<�3����p�\�)v����.qW2�2�y0!N���ZV�7�\��4�`QB�0�X*��C�F����]n�Z����j5!�4�����g�T�B������'��%{N��h������WV��(k��s)+��of�]�>E��2������~�4�{��|z����R����!e��;g!C�"�����Q�������')��N �`����%�-�(g(!��E�`�\�,'#�l��^�`�8�4/�xr����+�����F��C<��$��x�|���w'Q�zc%P��z���	����K$��� ��w��������R���{�o��L�}���|F��=5�yD����X���\�k��L��HL��|�a�Mxo����)�2wq��8��q�u/��������-|���8�&�f<��]���H����(G����>��8�z��`��{��Shg�F��N�w��}����Uf�x���U�/z���lG��`��S!c@�i(7+���E���w:������*����w)7���g#D�p`���g
��-E:���2���n�qJ��_�N��Y����/���7aO�mi�j7N�|k����]���?��V�������|�l)cW]�.���NG�b���V�������zz�o�/;
���>�V����
�����[\]Mh����=���Gw�(�De�&��0CS��V#�s�����c��c�r����#s<8�/������Up7�[/�=2��9m��~�}|Ln�K��W�-Cqw�rX���$��"���w"Fl����'���D�����K�A�
���S���
���O���SM��������8��g��U=��{	f7O��r��N1��_%/&
���4����S�����>�>������
�����_tr;�7��>����;p�S!��L@�5��4��F�����E���	��-i�"jY�{�+�����p����x���� ���H"�?P�N�>������!���\.�������SA&�"��|6XLmh�Dy�t?�_>�b
�A��� ��F?���\PY2�N�����j7������y�����a;)�k�i���4|�uY^����W�S �8���S���E4��k�[;��x��L��j��!�����b�L�e7���0)Y����R�
���U�RmJv�d�k�Q����K��O���L�Z/���c8�e�4�������N\K�^�k����Z Uv��k������6�M:^(=�_���#�g@�#���-�~�_�7;�M`��t����l~K����.a���x��]#E�-�Z�z)E��+Y��a�������5��v��<D��������������%���������a�q�O�/SZ��^M�?�'��+�i]7tO�c�>������_�w�a����0i��!��ck{��	@�Mk7�e:c9�A������q��)�z����q+��?�>����q�[wU�����������O�Z���G6�Hm�[>���|M[�	�y�$�fn�;���$�GE�		���Zs3|��1�%9�&�(�v��Oy��~������v���}q���>d���dHC�/��2I���ZY4e�!��G�@��+R�oQu�xq)}o�/N��F�$_�>�G���z���7�n�h�+�G�J�R*�������rS����T�:!u��n�7I����X�������p�/��� ��xI�]_7q�6���a�'�����@3lm�[-��m����$w1�Z�
�=�|��E3���X���^X5�w<��������>I���Zy+��j	Q�jF	���9l��\��p�����m^K���WZ�����y^�\.�h3��k��a�X9B�^�q|���G�
��=G[�gS���E���	��\��m�9]
�5
2i�DD���e�yW��e��j�R���n[�n���3&�]�	?��
��n��D��Z��P^���~ BE_����a*��yS��3�2�-��GfM��Q_6e�7Ta4�oQa���v�������-�Lb�*��"���;�5��d��
��&��P�h5�g��k�(����1�*x9������
�.i�?��?��=T��H3^��U��3[�G��y����v���y}y"K�G%&�+(����6v�����v�}o�����}{�V�������bew�7��D��{3�2�&n�������z�mW|�&�]����,��*��hR���NCM����3 �c?��!~;�<mw�;����������iU������&���j�
��	�0�����E�s
����g�3O
��
����j�T��[[�����2j�Z��{�J�?��-	�hB98�95F�~�*��3�p;��l�"s
<m�]|C71�,��U�1��$���;`����q�)b�k��G���r]��a�8���t�hG����O�SG�^t\���I���I�
�G�2�yI�#��{�R�$�������\;��
a����b��Y;s&*��������=�����R�$�HN�=tgC��-�M�}1:F�Q�W&���H������o��&�����b�)���Gx��W����Vx�^�3��:.)�����q�lu�fvz!:m�L�BV���`L5b`��E�qW32U@dAR�y&��B������`���F���-G�;���23)�a:������J(�o���<�s|3nN�[�^`x�z�4�E,,m��rWv>�����0��:Y"���+H��|k�w��{UY�3s�}}t)��<����-+���/��(��1���]�N��F��f���� �$�����
��F��K5R68�E>	~��y��p{{K)�n��s{�@.�s"�:���w�q�M���V�!-����\�U��9�"�����O�w{�@F�_�U>�M�������PU�k��Sj����IpT7V���Fo�@��H��FR��A��"%�R�1'��M�BN��B��^���������<mPX1������$�v�|=&��e|LRIX�����!����Zu=V��f97cU$�A7�{YOp�_;���o���;�u���p�~�gxRi�����_!��SD%u��;�<�mw�<du���'a<�E��gv�K8LG!ZSa�Gb���MR�K����hRI��7�����T�xM'e�+*Q�i��U���`2)(o����������+9��H�������
\�$4
�p�%��N�/d����hvM��y��h���D����*���N(.t�=q��n��YO:q��-�s;��q�����s���qJig�-?�}5�M���}��	��������o��Y�":
nx�������i�D�[96^���\�e�a����b�;�������V����~�M�aw1��JQ��������u:#X��LpMie�k�)�2�72	76�%���1rj����Y56����
m@w^����W�����V U?���/����&k0�h�&IH����"���D�#������`r�C��)���y�t�x?��"9����-Go#QFW��_�9-�Zx�=��� ����AT�Z]_�B�����`*]�	?��SDS�����rC`�S��]�8�,7���EK���"`�&�;^L�:Kig�-�_(/��/}�L!Gcf�v+����=��qPx�P���l] >��]����#���,�U(����Y+L�g�v}Rq���
W?;(s;�i����`+�K�E�CZw�zzk���}���O*	o?�����)k��^�oou����*�����������d���g�WVB�\"���KD �g�d��_.��-v���UY+��!��#f�&U�RV�����j�+�"k���<�P�w�	�Z���D��u�\#e����#+��/�g���Cf�!H�S��/y��N�879g~��Z�����������G���~���y0��W]B�	]IU\Q�x�Uo�6~m��,����K��C�5[����+�z�`�&����)���x�"Mw���n�T:�A���j�5�H���D{�>����n�9[����ic��gC8l9��3�� a�{�v�L6���rgj�H!�$���gF�������u�l��s�L,����$�,�������L����.���H���O��3y�m��sp4KB|x���Cr!,y-�St �1��O��v���$�/E:���q1���3/��	2��z���R)���|Z�U��[���ru����u�L�0&�lD�����_��,0A��-�E�����V��aQ	2�&�V�������������������&�.��S����V�yz�N�����U��j���'�F��v��}�����6�\]5/N����%w��y����PL�20*�[�.�)�Fg�{}V�=@�n��k������W��-
X�r#��An����G����BHn�R���}�����-W������t,2����*����l�$��;�P��l�UP "�jo�g���2cP�+����n���`�.���[W��P������H3`�~3�z�{������3��>����Y���/�t�8�L"w�n|���i�	?��0R���il���{H���Eo�n��/��
��,���1H�j�����#<�V�k��_�jr\����V����0��&�M����p�3������)M��
�:F�����TEs5�>�]d��9kS�c<)�O%�"���Szhg������Y:�8��~��)���h���P70���w�'�A�a��-�~(���|1��a�Af��y��+����e������s�����`��k��<m{bg�&Zv �g�����4���]>���6���/b.!��-Uo+�S��xKgg�����S�:x��<�kR��Ki��f����\cu��nV�Wh�<����2R=8b���:m5�����|\�E���BW�N��	��W+�r���(w;��E{T������J��[-�&���V�^}��
3=��2Z�j��q�7�XB1�L;�S������+I��(���(%V�f����b���������,�joe.�*�y+s��,�P!T��t�X�������E�e��8V!L�D����G?��EN|�O.3~���0bsPJ���@0��z�������F�?�"������cY7Iu��tgTsP�2��AT�qX�S+��G�p+z���\L$����������I����5���+W^���������+����V�r��������8����] �������[ |�X��K�a��V�g��6�(����$�)&<�pF�|�����Ha��)���?��h�����$��c�\��p�]�[��A4E��l�x�����K?���W��<t�p!�C.��G�2�a1�@���_:k:���	�N�kn�����)l��=��(�:���������)_����:m�9��ilk���4��^��Z5/@z�p_��qi��KOS�D�`�K�+*��x�T�*����3��y��p������x�bn��$T���*��.��1�~8�3T���b�.!
��nZ�{��bGmRT����W��NU9�������3�����J��#��*�K�}��uET��y0�-�(��z�)�LO�,�z���X��h�?Z���!>Y��u��?C���F����	���f�����d�a�F��6L�%��9`C}�XK�5�����&�?&�����T�����.���1MF[���b���Na~q��}��70�4�^}�N����Z����R
:
����q����(l�h�x���rN�y���k���j�p��"GQ�s�I�|z�W�0l��Vb�RZ�\X�]�[O��h���K�N���n�8�_������_���U-(�`U�e��^l�9D��V|�rc�v����{d�PF
��=��2��)��|=�qJ�h:l�7y���X��X[�!����!��j����`�=�	�����U����W�����7�*���h��\�����*��2����(�0Y�|������tL����h���J�+!U
���iW���YW�������i�y2(������<�Y[&e��l\Lg\�}���\5�������[>9���[~�(	gz�����?�N4��E00N�vx�cLM:zd����	�=���w/X�EI�8�L3�
5���H5������#wln.�j�:�)�`_�i�\^`-!���{��U��}w1�����)�)���l���*N���.���g������Q�z(��R$Id�"n)nu ���O�<�^��E$�.��[J��'��{dq��6Lw����~�W��G��o������a�� 4�~sy}qRo����`�[��-��� PD����cvL1������yP��`0�&ZL�	Ej�ic�!K�-Wl��K�AC��5�Ci��j��6Z/�"1��g8��-�+���d�r�O��Q<��}�8�j�B�-b��An������j=ya1�`=�,I@p_��-R#��'�n&
��	��q����[1�(�����V*���r����z�q�-��������*���w�bV��u�g��+��_��7�';f5�D����#Y��\�'�C�< �)��p�'A�p����Z�|<��j��sP���<n3`�Po��X+�0K����NBn^Zn�pft��^{(�)�*iH�����7�_�Y����d,��b���<M������<2�����b-S��i�����:�r
��K�b��63h��h�>x���Pq��b4�\�*����$�� �cr�!C7:��d��I:)
�
���8��`��h6��[�(�8_���~������0c�	��L9�GCw���b�������#X���.���S	��=;�i�n}�*3����8��g�7��vz���CK+�������2o�D�V$�D���K���~��,���l��FP*hC�Ao���+��{am��Dw��@���T���"��|�A/��W�Q4����N��~Q ���|�����[�9����[%u:qT��^j8<�X�����b���i�\� ��+���K+�+'0��a+�{��3����Y��Hb1
�L��8�4sF�
g�!j�=|A�����)�Uh��/
}t�Xl%1����������`b������i2#g:Tf"��Ld{�D�������>���|���*�;d�l���
���N���������c�NW���*H����a�W��L'~j��F6�e"����so)@=E�y���W�$W��*�~`�W�s��]�Ln������s
�g���ZU�=�U��o1�=>�AJ����]U!��N����:mH��x�i�q��� ��p�'�l���L���g��a������!mM�xB�;$���2�[������u�|2����������;�e<z����~�������A�?���+`��+�B��A;��>�	xw���/��F�A��?��;�m?
���������������3h���'����G�;X�����P���'{�����L�������?�}o�itc�Z�x�����6t�Q��	���u'�����Yw:^��z�q��G���d;2T��A�64|��0�?n���@O���^d,�5GDL��v�[������o����5)?.����Q����Y������b�[-&��`_?/�&]�w�^��V+�>�~���vx�1�,��$8��6z��R�V��p��K�N-AT��{�1&�.z�W|���fj����<�s�y�|�Y/�[��mN1v�v�v�i���� u���/�!�
m�$�t�C������
��/��Zf�e���CB���5�_(��E�x�5,6���e�d��\����%3����[#E.eKj��$5,���%5��bW9�0���'5r���X���N��M���l�5��{ZoE#
������p����7���A?f��}�l�s6���#l���n��1��s*-��2�*�1K�Rif�i�J6�s���rq
D}�[��J� �������4�4u9�G����\����)����w���	k���7B���g�������_.��r������SI���q�����������4�U����w�s���1���s�6
�x�&s7�h���.mf�����VOx��������X�<]<K���1U����w�_�}<�����gq*)�����CD��po����O��`�U�A�%(4|��K��IH��'�<�c�x���g��(�L�6������{��Q�4������|���"��tUb[�}��&�����_��Q��&�v����K�8k�����l^(�m~�:���VbI��tA���5������~c��jy�:����T%1���(	a�!�?��H1����T^����zq7�<���]�b��Sl��6���6�J���.�!T}0xEs\=��/�na���/6��:������������t~O��v���i��7�x?��S0�@�b�`���Z��������\Nf���Rg��)���(2*���(|���%��!��=<8,V��(�)PF�������N��F
��Q����$A��L���
ZS2�9#�-c���?��0&�+��
O)Fq%�+�_�7��������q�g�	}��������6ZrA���A�gr".S� w,(��%���]����
~U���Y�!_�W�h}���0�k���7hr�v[a��(�(�����
DCvm��q�5I\���xu*�u����k�,(.�2������������<��t���t����k�-����8��.��]��+�{�pn����61�U���H�S�qW�Ya���$����r��+���7YY�_������	�4���XG�a��T�6�����X��,����
��5��Q� 9��'���������+g�N��QS��a�9�4e���Gi����I"=s�|4'�8��`��IH8�jh�-'IK^�+��gI�I��`Nn����3���d=�K��z+2�$O�Wd�&�G�)0�����n�q?��d����)��Dq�b������|�=�1�?|���3f�J�������)�����j�����D��#���@;U~�4��M�IaW.�K6Y�4��r�u������G7�����){���������g��>�X�L�2=�98(&�1��L� �/����W�p��y<T��BT�cY/�w�{�R�a�;�^&�4N��(�;�A,2���J��iq
�����J
�i��������j�����h��ma��}�.��"EE�����N��:1lQ��L7��Hw�A���K<Y���'�����(Q����4��&�T�E�1���x�*'��}j�f�Y=�jA����z��L����R�
 C���:�u���VO���S�
�"��t���,V����H~� _��J��G��T�\�j���_�������F���NI��V%2�������hi�q ���l�E�3/+��X1)��+S���T2����4v1��h������J+VJ)��O����V��$���UgN$HI�����R<�rT��2���I:	M�~|vL�>Z���6W��=Hw���
f
����Gk�d�GS���R�w�6~���8K�*
�z�����")�=o3}8�h���^_;���F����'b	�I���Fl��s�4����7�e�\��p<����S�|����RipT>����y�OJ8�{����ycg��5�L^L0^J8����lM�9���
����	
���
.lo��~��w:���O|��@>v<NRs�mP^'Sj���hUQ�6�HVmq�z��kt=�W���o����1�����g���x�i8#_p�����F���n�������<On��L%K'��T���>��}$YZ�v�������Z:��ok�������[*�~pP>�T�fU��Z�K!��������G��5?2^����P���\��e�2@��dX�n������������i�����8c��z�� �F�����U��n����u�n�RzF��<��d B�����/��4G���L��8��.:���uIK��"����5U2
��]�yGu��Jf�7i�w����b�1^�b�@PX�S�R�b�����2����2��8i:�v��e>%�1)�0��M�QLdI@��|Z��=6�����)�0��gg�����F�-��K��z��I�u�j~�<k�6��������M�\U������v�7o����7����t�����+,h^�4��_�COlq��9�W�Y?k� &z���K�����(�J�ae��+�����o��\�C��8�o:Nu��j��\�JC?�t~�����I�h��9qg�y���	`M�|6S)}0/>X�v#��
���V�]4���5���/��m���n
3���v���OB]c�����P������;v���d�	%q�����H����V(���IU�
�j����'�[F���^a�#���V�f���O�K��o�`h�'-����Z�G�Q�T�K`��N0K�j2����7�/�C%�b [�%q"Fmf�[�%R���z�
|�^N@CgP�bZ��ZI/*0���flP~=^"6����}��X-b�����I����#�<���Y�������)��N�����[!��������n�N���>���\V��_��x��7�PZ{�
����@�[?<��\��{�u_�G��?��N����������~��.s�\j�?f��=�Z����,���U��x���-��9��P`��z[�s)D�Fr�Z�e)�F�����Z�S�1���g�NOu�'��q�}\?i����h��
�j����0#�^4l����{���{������{F����x���{���� ���?��|���?���HVB��S�>���=>n��J�7:�/O�4�
�v�~�������T(Y����|`��U��*"������n�T�WkG��wK;I����8>^���UX����)��r��x����Y������\� �$��uE'�������U^N}���a���dL�t+�ZR�����R���t`O���_��������/J8��
o��4�iE1�W��'d����T��R��z����ou���V%NU���J����� h5��M��]��
����x����Q�/������jQ����$��L6rzB7'��Zm����TW��izsj���E7�����#��RWI�V�����	�U�\-d����W*e3?���E�����r2�S���]g.r�m���j>�t��-�����]wJ���Q�3%�rS:8���i�SkZ�u��m�ej��/�V�5IN�	����RP(�`<�/g~!���P�fb���8��v&��u����	�:$�t�s)�)T;����T
�{��A5Jz��?I���U)�.}�](61�H0A���2�K��L�����y����,I�T�Xk���.���=X<M\h�N/�W��54�TU_\�C��\�U3zV����M��ku�T^�L�|�gQ��������h��?��S�t�����
|�]�������v��QA�D��E��,P|$\f�W<@�T��h�l�$t6|�L�9������*�}�Y�#h������O������?�|�l[������#��
��O����@���Z��@�f��������!�le��BBC��P�O���c�3�jL�FJ7"L��3m����#��+�\k��zKI�]��$|���Z�E�P���RiX�;��D���}�_p�qA�S.�����4�}����Iof\IE��@-�?�����	���������hz?#��BKU���w���~�"C�Fo{�N��T��=��w����h1]��nQ�����������vN�a��������k_W������ �4���f����T�Z���b���J�m�T	*Kk;G�p�Qw�=���+�sL�u��&�q4���tu���-� S��PjSCJp��+d�?�xS"��)�k���%WR�?�*����&���"����g���,_0���T�0�����]��P���b���'N`C�0fQ�XWA����2�G�3������R[gw@&�OhE��2@�1l�=G���xH����b���������]�����X����C����������,D�T��p/�N��Bnk�m������P�nS6?_�������9��r)c�T8�������Gx<cq�?����c��d&���v�Ili*�
����g���
�(��o�$����7�d]s�3wZ��d?�R�,����"T���H;ec����A��������+k2�ml�����F��	�n��z_�e~��:����fB�O�"�Kw/���U�|�����	b���B�"f<g4�������N�@��K��mIW�����k��V�`�n�����X�[��i�cN�S ����s`��I�����?�A�F��Nzk<����|��@������Y�X�3(��eJ 
��h.Qb��8�zZ��P�s�tG����idmq�/9�q�����8��am�6��yB���W]KH����z��B�d	
{��� C��
�	D�&���iZ`�����"��C�q�Dx������;S@�?NHp������-����K�:B��#(��ty��]��sYX����X������c��|�3�G`k5^|k��q�V0���,X��reB�y��F�n��&���[���q�l�����V���a��d��:�����\���dw:��'�"<IU����9��!�����Hp�������"�������������e��9��%Od���w�i��o:�P}�����M[�]�������+�������-g����&��7������)x{!f���]�hOz��^HY�p���T�V9�IG���p/�^�m�%1n9g&8��M��1$���������t:��Q�h��'�����R��:)�"n�*�(�k��M����:�Fx��F[����
�ve/\�
��g�����R?,Vw=K����S��y�v9�~���`f*4x!��6�/��^���g?X��������0��,n�����y��d�/� R�=D��Zu���T:m!�t�}M������6����DF~�:�����h���P�)�GL{�:�[��������f|g�Y�~��|4�Qi�1����V�[�<'��1�!@��������e��RT��s.��bLQ��=�p}����=���@���|��1���k�!P
F�<�I�z������?
��~r�<��_�pg��zNY�q�6��X������%I���� )�c8#c82GE�K���U�8��%���|c���o�!�fhm�T������A�T�=���%�s��Y*��������2�i�/���l�6?����UT�������[OR-�~8�3��:Q�Z�V��z�nc��i�~�o�����"�6^�19��_��eg��p��[��������7�'���X��Yp���� ��L�r���X*��A�h	/�� �h�*���������WB��b����`�Y��g�L��z����u��j�u��Dq�_%�%�� <'�����$�	�2sp��9x�������������;V�$R�|7��JSJ����[�+[�<�U�E��I0�o#�}{�����1&e���Q��C�E}�eh����h2?��H�RXG
G�x��N���'3�� �e�_z������	{ B+5 ��[2 �u[;������yb����$��E_r���J�i�&��%%��
��)����j�4/�����O[����Fa��d�.��q�<�g7�U�e�Z�T��Ru������A����i��}tD,�Qj-�����P�/0X���Y�/������v���(���~��f��Xx�������I����IU���kG�6��������������������w��c�F����x���/�G�������q�@������n��f�����9=1?���.��:=h0gZ��k��j�\��@���o�X\���k����&�{4`�����v���t�d7����hX*�^X��]���J��4tN�����Hv����yF/)g[��sY+M�
��WSA��]�i}<N.�������a7�o��a���'��=y<8��+�w!k�h]�0V�a����
������[���L.\+�YW��I���/H�v�.��
	B��4�������8��#���Fs`#0��[:��Fs�fqu��`������-}Z
�^xE���;_t����|>cY��P��F�����
5TP����� ��nd���\Q�
T~�Rc~����NI���s���}'���`Z�n�I�����+��8
xZ�R���������x;�}� @\\v�i��Jwlw��QS�(��.`�����#h��m!��7�je��W�`P;���D2��`��U(��.b��D�-8�P;dG�d�V1M?���d�������a�G�	���U#�T�~��W?� +9s���I�wzI��V���DG������]���, ����$�%�	^��aHR���n��_��J�A��W[�\�� C�*�!���A��}�1A�����V��8y�vuv	�f�xN��8@��/R���i�O��W���Y~q+��n|�x�N�?��N���'S�?�
A�,��_@
���o�J������u��{�+�+����d;����+^�A�����X�<k(���1A�:�<m^���U���Y�1�H��F	@��;�a����O�z������F�yJv�%�F=L?	��b;��AL"1���J���%n��v45j�7�����co��c��/t}������>it����^I�_�&Q{����U�z�?��t�������(�^��<��7��Z*X�O�zn�S�*���Z�u�/�z�]���vI?�7w���pV���
.�sp<7��IzE*��+��v8�����.��#�Y��yp�o���nK���oTaE��U/���~0�b�D�P"��*���~It7v�#*g���y���?X�a0��q�������u��kO�M��gC6Z��������.6�[gu�@i ��e��<�����;`����U��x�5�+3�A������,0�.�u���B�w������a�Pq�.��6L�F{�����Ux�{
��������:#����Ab�u�L�+H��P�m�� ��c��GpUrwW�����83��hB�s�c���`t���L�'�i���Wm�������A�-�jc�	�|���}�)����%����Mpo�����}�
��m0.<���
�q���/�,&��"�8M��T`DI=���{��{��U�I���V����(/m6��O����k��z����E�|��^�Z�*�����#�|R�*��sx��:��s+���X�i�~Nt�4`�hQ�K�F<7���=)��1�BV��KH���x�}�c� �8����X�on��u� �Xw!�MJq��:C��%��X��q���f8��bQa;5 �����5��<.�l'����1�����Hoe��p�WT�%��(�1��^�4a4�45ok���Jd+�)�'������99�����I�O���}�s�_�w�����OQ�������=��}��r�1
6S� Y���������Y����Z�+����1�|4c��m�R?�lMJ����������M�	1$�d���V��Y��^V����c��_r�_r���7F�,�C�#��}�'TV^��H����u�[4R�VJ�C?C��e�y�bA��~��_rs���M�s���K/=�C��OJ���"�����G�a0@���^������U�IMNCQa_)���R�n�PN�r��K�����M���V�QK�	�>��t��������R�J�"���G�c|��t�x�Q�{���9��-����e�J�B}��ipk����t��H#X���!��Kj�)o�$[d��8��Uj$b)��?���e�J�B��Q�(�VG~���UtS1��CC��8�"tPPI�P��9��hN������t��2��Q�E�d���������%6��{u;���m� ����8�`�I�Q]����u�`��?@����aX^w�g�[��3
h�W��
?��O�~?����1HZ��\��E}���X�'h�&M��E�,�) B��|M��l<��j����kT�58���T*�������5�v��lE�IF��="�Gh�Bq�,��0�b@��
������#O��q4���P�|#m>��`�7!�jq�P����X-�Q��a
b�o���h1�9�io�m����qr�����kx�AH������T����I���J��fO������Ze�`=k&� �!�a�7d���%���ezlk&m��V6I�;�6�����������_i��9��7��q]�����[��q�w���ws��+��t���-���Q������w�(���<�j��14J�'��Ai����!�e�Y]
0002-Add-SUBSCRIPTION-catalog-and-DDL-v18.patch.gzapplication/gzip; name=0002-Add-SUBSCRIPTION-catalog-and-DDL-v18.patch.gzDownload
��{X0002-Add-SUBSCRIPTION-catalog-and-DDL-v18.patch�=�s���?�^��uj���������W����r��M^FC����"U���k���� A��I�����Hb���~���������l������7��-o�����������]�c�1_��.�v������6�4��%Ob����g����W����p��I�s���Yo�~H���Xo�y���;d[]��1N�?s7y��^]����;v�yl�����������9s��	���;9y���j�6����7�����6^����d���c>�8A0����w����	�'�t�@��$;j��T������}"���x���G
�����ec���W�����K�����=������_y�������.o�w&/��:�{zZS:?:�9����ev����o��������g0v�W����p	#���+����5��b.D`���`�����m=g�]k��[��ww�z�`�"������`~�R�8����-`1�
I+�����4;�m������k
������!oP��^-,��a2Ib���B�}VhXSP��L��l4hQ�����`]������_��3������N����
����;7��h�����{;�&����v'���_�� �.�oC�F��>�����|�}@�X
�[���I?���n���w�����X���������u���'����J�;6X�D2�e{�k�7��8�;�q�l������1�uYA�-j��fy}��I�����V<��Za�N=�%%��-v��0-�6L�����=VdX��]ud�y�(��W�M�i��x�@9��
�� ������/t\���W?��w�u�a��s^����Y�A^$��:������l�+
7�]1'ra�z7!�~�eXe����H��H�[g��������lB4�Z�+KX���a+B�����CP�7{��`����.!�^G8!8�	`q���4��[kjf	v4���
��L�}z�%,S��e)�Nh�h
�YN9�D�*#�Q�Q.����`�A���@����g�l��<V�n��m0��1H�9f�#��n��p]���=���������*kaWZ�����v=_��h�?��V��O��i�]Ln�+a�����
G^���"��7`F6�������m������W�6��8�������t��. ���?|����S����i��xzx �8u��Y���x:����������0��3�(��l�K9�51�����
���F��=�@��������.���TR�����b���('�n��RR�D���PR[���3�I����O����%3H+����(�+�b��_80�J�!�dXu7�9$��9�������(L�����L$������)J��	�=��0Y��������!s'�s�8�R��E3�h�x�<�9��(��p�mB
��w�c�s��QB��&!�}[�O
��a���� ]��4����Q��Q����,p{���0i�v�������e��+]A,�	����[
������N#���\�����0�=�y����F"[���f5c����w:�������9I�X��l
�����=��|��m"��7}�a>��4�k��k��lu�z���;�8;�	b�����w�t��z�'�. l�x���k�P6���l��+N��At�?��j���U����W��a��~�l�FQ� I��q���zNE�AKnb����!s��M���
9��Y��M "x�`��/0g�����7,�h�����`�a��q� J>p�>x\�t-�/@���2�6;
D�R�=������r��
`��<����Q��������(��{����)��A
L�1��? �2��DCpD�{0��1��n#�<4��vT��SF��j�w3���}�&%*���F�K��
TV:��9���v��u�g{��������Va�3�c�w!��PR�u�P����3�[Di����,��c�j��7���N�������&3v�|�(�������?���\C��
�z	�t�P�G�/)��P>��[%P�^�A�K�2O��L�j���v@��&�1�c
��FA�������*hY�)HZ��Kk5�V�����%�Ua
5���j�U�4�]�l�lTZ	�Fh����8�VK�[�x���"��G�*s�z�vF�,��H��
3�W+o	i�2�]�W��%�FYc���,B�H:�f�� �l������K�L��r;�]TV�`k�����.8��]oo0�[����j=���,����������W��������h�FV�G��8B�|zytu��)�|�R6��!����{�)e�w�q�={syrt}
7�/���
ON_���[X!|*/~:����a��&*�+����������l|:C�����Ga���	�>����*Xi�,�n���w�C����K"�������A���E�����N!����H�f)8����m��>JeE��4�VGy�g�W����fi<��E���J�3���r��	�H�<�*���D�(�4���}'L��
<�Z�b���~b�"L���&����%����)W^�@&�X����9*�AT��J���D3/�(K�b����p{-�]�IZkF�k��m��g��)���� ���;��=�x����
{]������pc7��z�~����Z��e$ ����~�K��R?���V�E�p�%����A��so3��+(�+������E9�L\����	����6e2��*�  ���(����A'���p��s�i��Nx�:7���-�#k�����R4�-���+��5�3�p���H�C�d/��������Z�Vg�hR��5�>�������<����O��|=`�\���{����-��C��u��`t���sP#�I�����kv��%U����Ga�c��C������1���_?n4��k4�.�6s�����9:�GBvr6��:�L�S�8S�����|����VXD�]��r��Y������NK��r<a[B"��v��%s'QZ�;6j.��W�7_D@,���
n\
���%%�:�\Bc0��jJ����������+��y@��IK���I��tL��B���M��(*Te��	gws0���0}�+(���l��(�x�)�l�pu�e��<�;L>|m�5�,)�S�+�*��2����0�+����z���6�~��U��dX.H8-P'lz�U�������SH�����cN�h
�y,�8���|���?��H��0��2j��=��~�
����bgN�m&'Nh���}.���s����`�!��L�o���C���1�D�\r$NC2�e.D�e���5�O�u�`M�����:X-[����<�5�7c�kz_��I$����{Z'��(��s�'�_�	�F){�������@�c�
�z���6Qd�*���Rj�������h������������\����<�<�HL���Bk��FyKt
��\M}���%^�3OF!�k�B�������@P����K��b��������u����bD���u�����No��GmOf�c�X��E�����?��!q=R������}�Qh-~��F���F=f��B�����VA�f�G�[mv��`o�
�\����	|S�D?�L�X�g�k�����yp��eN��<a�4
�B��q�ol<�l�������z��!��qI��:'��6n�v���
��Izu�&���(����|���-�m�����)m�4k��r+5��%��U��^+�Q�M�D������-]�
H�#=��lWA.�`!3�a���d$�1�E5R�i*F�1�Y�����I��W���|�*�
s�����`�{����Q�u�,�������$�,�������{��A�L�����(9��W��~�aL���@@������b�������EqV��10/y���Q��K4�de���	X��(���kl���QG0�6���itKo�H�����>�jg�����KV�MN$�xz�A�z�,��)Rm��x������R�5:��6��2��*y6��Nh����+.�<�;�>.����gvH���~�/��%��R��(h��Zn���;UD����T�n���=�w�C�@��(��W!�������C%��{)(���li������C�����o�F{�^{�e�(N^w}�{�rE������7eK�U�<���_��M�H��������#
n��g!�&��R!�i��A�&X}8�:�����1�ot-���W��gNu�93���7-%���n6�
h�8�.�Nes��M��U6k�1���nS]��C���Iq��fUI6��&�v|����U���g-f���-{��-dV�go��w���g��1{�*����]#������i��E���U��+3�R>���u����XQ�* ��*���2h�/h�
/+}�5n!"��D/��Z�U��1VDKE���Lo�1�\��B�XC3�]�CGF������>���GI���m�t�D����#�����.���y���@uf�����[�[	<���S��!Bq�� <��+N��h����C��@���4�z��������i����.!5�{��}��q�Y�����k���}OP��;�����u2�O�4���\
�Q;V���������sdOw�|���N�w�����
j"�BzUwH'��Go�/����Z��Vv����gFZ.��Z�����R0����Q������'��>*[e����:|e;R��*���*��J@u|�y����|���������}r����D���`�|~B��<�j�J!m��7<w��u���@�y��[��Me��W"'
�w��v�(j_@�G�+�O<43���>-��<��9����+g�"��l�����	Z�3��tp�5+�o34fe�v�|o�����Nw�{�U�������������E��>���M_~u���^3]j�Ke�����x����	FG���K,0�7���x�*HU
���{Hk�%j&`e��&�~��
[:[��j#��dY�f��L���_��r�?��a]��R��-���+����U\jr�t�����N���m��A{8����K,/���<�
J�6uk�n����������Z9IU���a0��:[���$g��h��nv��]o�lLx�=0[�I���My	
�+����u$D���$��*g�Z��+�:��r�jx!�T����u���CN^n�3�%Q�U�)�5�)�R�g9�3{z����T���"��9���F;�������q���El�y�i�c��F;��p*�����#'Hll"��nSw��c�RokG�8�>bs�,��H��xd[$-&��1�6�w6=!�w�j%���	<��v`�Vj�~�Z�0]�o~���y8�
��;#o���u 0���������;�����n�L'���D�$�#5���0�o|9� �!ol=YM�Hh��%+~����wg�OQjK������_xyU6��}��32y��}��3���\^���~u:�@���1v������`��_�{�����������m����R�&T�������^~7��D����*HM�A��~B�o���H�bHu57�
7
)e�3�����3	������2����zQ'����S�1�U�[���*�"��?����Ho���~V�n�c���ww��^��u�;�{k�A������Q�3Tv�?��0}]������-f����[�t��[��EV��0�)�o�S���@k���Ys���67��K�+0]vI|�8f����*���'q����N��_O�qv~�������M&a-7�����>H4����O�m@��1L���tVc3�IH|[|�
" �Z��P�U����i�������n{�GF��
a��������I�C��hq}�����
��o�JG���S^�;�f~8���]��<� ��g�nl�T�L����p�{����2�O���h����p���p9:��P(��tb7�[���I���l�g@����o�T�B�O��<I!{�CYe�;��1�7������i��
# {���''N�.6�������o�����8��mr�1��oe�����
��m�8v#���������������OO&|��_{o����,�,����$Ew�/����$�qr�y9k�lk��O}uO�L��l'��>��n$MwWwWWWWW�GO*zPh���xC[���x_�c3���-yzt{<OA+��Px	}�X�=���������j��:�0������#��e��;?�#iN����<���@���Q�z���
�����������AT�
�����{� �q�7�>�?W�"���h��
���>��~UD��Jm<C���n��������I�\�A���|������1R��y���2��%�U���?�H�O|��"���t��j�e�j����v��8�@���-��6�B��O�o��~/]���q����+�w�
��]��K�v��d�n,�uH���->�6BO�7ou�$"�H#��Xw�t��l�B�����%�?���zu����6���<*Z*�v�������-U_S��
>����8��8�I
�m��O-�[5=�mMW��fr�#��m f�%���,FQ%����wc�����L&��3����Z�W�|�����U+�rpx|L��- �<��FrX���<6k�n2����'��2x���O����^v�x*\�� ��r$1�-��1+&����AR�~���[o��J �Iv�.�������OL`71���F���n71[9
��K����,)�{;:8k���L������t��:�������������[��U��&'��1:iC����5�o�����)�@��=�oH�����G�O
���42�}�������4QK���R�'�����r���BN6_H�����D�������P����<����x�O;�D(��g�������.�\���k4�����~�~�iD��Ix�����"�(���}/�
}��cs�N]��<�*8���#�e���e��cb�8"�Q���D��6~9!�b�������wg�T_��n�|�Lr
�X�\P�o�Ocpfm^-/�?��4��Pt=��q�v�q�g�<2Y��^d"-(A�����(?7�?��mD��L�l��Q����E��M��yv��	\���:��b��I�B'�tG�
'dz2��Su����f������F������)�.���� ����>y��s 2���}t�S1�^�� h��7�a���vC��\1��t����F_��(�e07Q�x���2�������B�7�Wc ��t��S��x\��//OI"��]4p�L��Y�D��9������w��%Q'R(�*!&KO82��n
�pa����E�b��)�Z�������#.Z�j���,�cK��Up��W��y���>��!�-�,u�z�b_�D��&�Z�P)e��T)mVfK���	���P��2\�g�g�Zg�
�;���-95�1G��e�c?�Q���p������Fs��I
(y���qs1{������bNF9L8�1zp������Rs��W�����-�t5�g��Sy�88����I�R��~�ed��o1��� �8�y~d��
��=������d�D<H�L���p���8��i�[�x{	�Z��S{0{���[�Z��`W���>Sbp�����4�q}��� �t����	�����b]6�a�T6�~:Z�ZK �:_h*5������I:�������PY�,z��?���D����'��7a��7@�1�c����U0��z�WM������J����mH�o��n�������@�E���\LO�'���\n�����k�
%��m���M���I�c��O��I��[���Ye<����N�����Ged������I\3-N����)�T���F*��!"�0fG���<�������
)`�gM]{q�����v�o��_�X����y������i����<��p4#����:P�R-E��x��3���>l�Y���(}��y��p����l>0���t}VC:S�>I}�*V&gf�V<�$�<>a���G=���A����]/��z��vH����/�-�r"�1|EUg���nx.�����W�y��J�d��\���?���wpX���w�),u��9f{)�:��f���s<����-��_��[�U�^�oQ�N�)B;����z1+:Kw�0I���[P�@��s<�'��(��z}?�1j�.����t���\8�\�]=��K�O���c����u�
��%c�{b\�M4���e�r���� u���V�<q��O��yZ�7������&^YD���.��P��du�����?��"d���Smy��'���n�$�bk�;����=,�Z���7��0�	�,�o,��t��tg��*Y�(�s��������y6G���pb��L��1+8FB�a�o�_�?N���8��.�� �����A~-��c��{��������
+d����@�S1YM�t 5P���-���?�4�����S&=�R3B��_�O4=�7}��Dv0�3�����T��?rR���MJ�,�����(L�L���:�������g��o^�US�L��^G���m.�������\0_�T5���������w��iD�}����N�0����X�m'0�8J�?'��`��k1������|u��dqz�����"�s�T*W%K�]
�z��������
��C�E��������5�(����X����
��>`���e8z+x�������T�R����Ty�o����_��*��-���J��dl%�%����s<�[�#���<y����9�gQ`�,� )y���.0���]pPfW��jt[��{�K��*eh�"��4"��e���,��uh�Th��%���2���������"�AZ�?M*�{���"���
����Bv�	�,�#����M���w���;�e���`QHmy��^b���	o_�c"B�Z����9	�N,�Oq	
Z���2|�������K��&�3�e%�\C]�J�dYb�R� ~�o*���z�����6{*�.K(
o��j!�^�]��G��k��<9�L�+H�x�����{���KB��,����U��]�15��(�fd|*e�j����q,i6���)`��4�5u��J�������?I6��[��[��Y�X��"��#��`��E����l�����b�&*#[X�����m]�u%d��+R
��>:5�u�P���
��j0N|�n�g�/���pAw�����[?��r�+�����~Wx*}�����6��?�m�����G�4S���M����>�^,J}����p�AWx��;O,'@?O��2��
@h�7j�"r8����r��,C����Tc��?��8PG�����l�Y�*���U��	��C�����8@2\G^���E����Z^������y
^v;Wr mj������'�s���&`�����q=��q�d�k�X
�25j��*v���%O�=i����,\������i�V2��{N��9K���Y���rY�#��x2����������S���C�s��(V�-g��������.�[`4�o�1���?{�94Gs�V�C���'3�gp���%`'�b�O���
Dz�+��U�?!D���V~��WH�������pA�\q�
)Z�?������7y%	����=�����]�Wqd
��aC~W^����*�#UUT�T���Gkr��_f�������e4o�RlW�a�*z�fw��"k���7P*����I�@�o��NL��'�D�l�J�~x|���q<���L
@�^RU�u��B��z��|��5l#�#>.����������z�{@�?A(���������� <�%
6S��{{��#>���p�����H2�M��m��h�kv\Tb���|���F/.��8U�I��u./��r�v���{��#�z\�Mj���q��\����;�z�\O[���"����^�Ms<��+�F"���n������b
j��q�6��s��{�u��D"���5���VF}��o���'|�����E$��t���;��q]a(�8���_S��J�Fg2��� ����Q�Zy�$�N��y��:z�u-�%�3!4�m$�P.s)�ho��Dd:
��?u��LP���~��d�=��3�(5�0��9<���]8Sn�R�D�z9���Dw=Gd���{U��{�@:s*c�����}+W���!�e�����VJ�������0@�w-Z�Mqw5o�u6{�u]��K`��}Z��+;�ry�49���c�b��ic����x�`��r�s3�;���P��P��n�y��|�����Y��hH�oq�0s)�QG���dwC ���<���<&�����j���o�`SCm�e0�]���I.�����2��_�1K&ovn����o[u
�R87��"6��u=��I.s����a����iF,=	���5:��6{?2^���K��`����{2^�CQ�s����c���%��,��P2
��3nZ�0�TQ�c�-������q@uR;��HWR��8\\��D���S4Z���zX{�_���8���2(��^�>oO�]0ru�u�{��z,�����\�{o�&��t	���������BX���;����b	�22Y��!e����,����.d�F���oP���b_x3�n��l����>����j�^�FK���������?U�_��
r�)�8��H�����;{��j��{�v�����[�g�����n������V�>��q|��4	0)U������d�;~���p2}�K������W
y}$%�������(H,|�#I�4_�u�{0��u�U������>������������h9L�c�l����&�)���Q�N�\6��uP�(���+�����5��J��z�1�l����a�����0�DG�l"hg������.����up9���/`����]�g��R�wR�"'��U�G*�j�&�������8M���:3��U����
���&\8�)@I�p�Uf���}pO�lc{��PW��?�_��5~s�W(y-�s������$�Z@o�t���,�;�37�a�d3�V(q�*�5���,r��z������7\����p"�[�UH��"C����yM<8���?H<w��T���]����R�s�'o��,K-�<3����m�bU���P��;�	D��l[�m�7�mH��|mV��xf����'U�LH�b���,��_���t��Eh��a�Pr�a�#������@T��s`�\VM4��1�+�F%U���I�T` d�_���?�">����E��7�]���=z�E�����Y����-�[��}q`�$?2���z>��h�Y�jE�M�+n����Npq����L0O<EM��Se�$�;u�~4"��
$�����Y�}H�Y���=�hy�&�����x�/D�_>+�(���+�0FN������:l��3�4�/�����N�m �>0�}f�[��yzo<{\�3���������50��d����0�M��C,��Be|���oh(C9����Hz�(!R��d��D�`
d�&�$'�[�0[�y����A����f��2Pw�pw��^�,����^0��Jgc��b�$�[��T�+�*�r�chz�`I���`�X�V�����%\����WKV00D����Qm�:g�Vp�z�|}�\��{��im���n���v��k�0z`f��{����]?,����yu��rF"������g���V�z��1A����dg��|C��[���E���u����k�D]O��Hu/�A`�?�K� �`'��WU����k���_���3�# �����E3���������ey�g��J���[�WL���h�d��]@���#ak�=���b�3�O�R��<��
-b�v�e��H�K@���oQf�fp�'��T�B��i/C\O;���q��!
f�N����������~��W�(i4�K�?w�%-���=o	~���.�X�\����E#�`g~��	����u�������i�7�,]G/<"&;p��/_���
�����}���_�j����r���|��5��?����a�@������_�����N�I���R����8(~�z�_��+A�1������)1D��(Vo�~���B�^m����5X�d��]���=	 ��� _,8r�bbF��bl�N���J��K�mW��]O-�&��QG�~�6|6-41)�.5�h������/CLZ��tH"�b�����?.~��zF�3�?����.
_���$uN�����&a��
8d��E����Z��������%�\J|��dKlQ�*0O,y%O���$&����F��G������z�^���!��I��
�h�.��0�z)2�,B���)�;}Cg���5t��u��
[Sj��:
J�>��I:F�K�<7��{���6�6}Y�.����#%��(.:]��D�u���0zx��<E�L�z��)5�XF�����f2�zy@�q:]�UXg����Q����\��d0_W�B,���CE�� @_����y����:�2�	0�e��D�N����N�]������=H�qR4��*:/�o���f���,"J$!v��k���.���_"�����.��)U�M#�=_i�����\_o��x�ZO%�`������:R�����("�}WR��qV�.K:F�f�Oq<45��N�}�2��,QjuE��>5&C��;x/�usNid�Jq���cf*������B� /U�����%����a�������l���o	�F��x�o3e������"}��tD�
1@�[��R�����O�mw�Z����)9�e_��4P7��;���6��AzaMa�e�f$��9��)Y��f'�?�3<������i��W�p��i�<S��I~nX\��i��TyW�������������ty�����EH�c�|L��8��)v�����4�z��s���1��R���xR�f`{���y�	��aZ�/���!�?Zla_7� �aj�h���v.����[����9�bq�����������~ob�K���+�����q�XS%���B����,�l}��\
5���^����m���DF��������@bE�9O��.$?|���II*�O��s���jd�*�e��
*�����f����q�f{5���%]}N���V��rcC���@'�#f���>M.sj��Wq�1�#n�	v���b����/n���n%i}��(|��a�����w�~����-�^�Kr���	��!�y�S
$�tg�|����>��v�,D�Kw���!��WB{X�����m���b�
v�i*y���$����Ex5��� z[?E��`�����bnn7z���?�?=��v���R�V��"i~���c�}���M?UpX�����f8����B����SB��[v_|{��IQx�y�S]~�~X��~CU<|�@�
N�����.��aO��=�uC����T�Go��J'��8��}�T-�����!�������#:}
)n��~����j�_��Z��\G��=�*~�����o~������!�J`y�t[7���w�������������q�Zo��������#��v��j�����n�����r�'_{�y�G�:����D���e���o5�����O��?v&Z����K������������
{�1{[�w�6���� w!*�P�����I#G�?�NF��������E�t������M:#H��#�)������`X[����T���������"�+	�7,���
��m����1P�b�`I2
�7��iCD�.�`����>��1c7 _�x�jx� ���rl��$�N��O�m��d��)&�|t���bD���������|�$H]j,�'*�4(�y��K�e��K��!nMF�M�s�3��N������B�
%����(��J��8�Y'�fu��C����"��}D���J��i����Tyh"y�K@n����d�:q��&���h���i��DJ���s���}>!~)��{�<@#@5�J�H�K��������Q�����>
!b"]�QVO(��<�b���I�U�`���]E�3���������)F`��t�e���H��������1���*2�~��!�2��e��:7?
@�l__^�[��V
�t��HI��H�\h<�9��S���4��	�2lZ{�Rs�rq�Q��:g��1�iSw?*��]�2qw;c��
�i�*>�^�������Zw���:5���>��C��?���K�D,��.P��{���'���^��*uJ^
]���WO���p�����pt��w>��<��9�N�n&����38�/��;���{���v���s_���>,�N�b���s���zfu�Apt�^mr��7{��s��D��+u��E?k't�Q�G�|~�9
���1�O:�|���_S%�9�\�4��<���8��F�������b�a�o'�pN���h||�T}���</���W6��qT�:c�f��}C�S���zYE07������Wy����37V�rp�P�3Cw�{~do}�<��$��O?Ar�T�m��lOC�t��0�I#4�!������_~H�u�L���LF���r�?j�I���n�>5�r:0�)y4�0�_r@R���h��+���}/��XC�-�4�v���u�8�����`���u���(��z2Rp>%��_�N�LVW��:��]������R��n�<�\��K�q��Y��\�l�����7���D���<`<�y����'>�f(�c��K�(PU���R��)�A���QkD�	9'M�Lb�7�=���o��Z�����0F����g��Q_��X.�g�SW4��X��z�L�*��3����-U�i.��@�MW���o����B��e8���_o�z1���Q���h1G��H��������E�6=]tj���d�^��{���&)��q�A(�k��A�[�t@����5��������k�������;�������|��-��mt>��a���%>j�cr���AKzLSX�ow��b�Jq|���[�=��[�t������o���F�t������j�M����N���q|]���\^z�V�{�
~�u��7���n���r�:��vW��C`�n�y����q b���aXF��A��#B���?��^�H7����'�;��U�/~� �����U���y���z�K�
��������~���y��n��n�����U[�o�^�}��������k���6/�y�}�����
�|���-���U+x��z�s�/���qM";f����>}������eNi���q��G�&�����
��Co�/0��O����FHm�!�F��;FX��P����KGR��Z�>��}qd?��k�e���"���%	88Y`��}
E]��=����,���>��2��a�E
U���W>�RM�A���
��D��LD0����O�-|X��xd�0�J�H����1�c^���c�(��\�2��[w=��q�� ]�38F�������'��R��l�I�F"��lAqv6���AVyd��M�F��a���JO��_"�����F��|���.�����������9��)m�.�K�/����=U�8b��])Y�Z�3'&^tN������C�rrpx��rr���I/2.������PO�O�E8����%�Q���{p����n1��������9%\Ot~F��2_��	�m���� 6o�k��l���n|7��@��I|"����
�TDj*Z����3:����nP�_+{f�Q�����u�$�$D�k���<�#�.1� ����a��G��7�
����~������_�k��Yr�:Yc��e�}�����3��6P�A���$�s����q*��@�N�@�V��5U�X�{%������'�o
9� ����nT������Fq�n�iP1��	
ro�e��/A+����l�l^��C��&�Q@�g���/���p;��I����c���w��V�Xk�a������%�b��Q�Y�����T
d��^�%����T�YSmdM5�!�;S"Yk|�SV����2��8En�V�V����?��Z����������v�(����#4t�e�������!k������g����O���v"2*��� ���_��?}��*������ �zx�������:�����rxx"�����w�x�6xx.�J?A�E���u����7�D"9�*Z;�'�����S#hp���Am�Xo��b�����@�����j���EG��.�y�����K}H�<��<15�*�J�\���a�0����������@���6��N����K�F�����-#�Y����x7����k�V����SQ�b���a]��aTf/�5$��2\���BG&����� 6P�,������=#%���A�Sh?fK�H��*��7^mu���v�oI/$������5}45d|���\����i
�e��v:�+��=Dp[w77��y��}p	��}�=[,����a��M-e�*Y�`m�g�`���VG�E�w@g�n�4�@^�#�������r`c^��j�yjh)����X�4��@���!�0�Ic���a��L�e�����B`��{�O��x�P����D����a��s��`�L�@l,X�iv�m���*��WB���0=2v�x�n�5r��v���7f��q1*"E����"�w��F8��]_2��{�*HlX�,c�pr�#/���c�t?sR�d}�����	���!���7�R[�y����R=AU�����r��
{1�����.�'d�����`�%�A�h���<fEt��x��������K����.Ti�0�7`zq�����W8_�� s�� ���-��rN�'���a[�[O�_
���1�[�9w>�@@z�~���l�R��M_�R;)����������\!*YHb��Q��������|/Co�d#*��)�����V{0VW�g�U��.u7�����B8%��u���w{x�&�bt�n��"XvmW����DmWLl�q���n��J�����x1PQ���/Ap)��#��M3��P�G������=j�Mi�h�
�\��E'���7��i��G�Rlh����V�s�62�s������R3BQ.�xV��E��O9���:�Z�0_�������8(������)��>����@-_�G����s$��.�����R��i0�	F"	��u��q����	c��b�Z��1�,AV�r]81wE����Lu;�7S�5H�� �v�.9�������� �F\Kg
P�9:I��Y5T��V?w����7�����
��2n��w�~�"��W����C��>Fu�H�*�$&J[�?��_�|��X�p���������Q��y�Q::�&�H����Y|v0d��Q�{`Lo��"++������@��#eKAzg��D,#7��s���\L�c�C�d�
��0�J	�d�q3��$�)�_8������T|D�(��8����ez����y�H@���L8@H���l�hk���A�!]�f���

`�edm��>�y��x�r��a����c�w*�*7�Vo�}s	?PZ����9'YR�a���#G8bmKi��S!%bT ���u���,��
��`�^������
��
N6�g5�U�@m�F�����4�����h�	�?�<�0�Iw=W9���+�$��r����h=��H��& �RC9�Rl��������3������d���j^��������c��j�������]J�QcX?F��pxtt0�l��~�:;����4���DLH
����su��;�����a$-�;D� ��E�_������`��b�,>��>At�����a��J�������^�4b ���{2S��7�� ���F,��yi�zB
��R�[�)��	�wwZ���o���)0�����D��E;�H�k��#�����+�����������~���s���
�����k��.������-C�VE;�n��P��\�R�h�%kK�<�4� 'I�����K��o����'z)���������~�������m��cy�X���:yuP���J��e�Y�����G0�C�Qg�CwZ��;��p	y���7h����P.�
a m���;�8$�F��9�u�%7�U�Ba������r\.����lV�h����
��FW<�o��O�<z��=�vQ�9/��fh��]����%^��r��"�r��X!B������|����]�X����H��gb�A�"`����Fd����-�^��p�Cqq��������H�7����1N��.���3�^�{��-����dS�j�f��*6��k.��tX�O��~��t��,C�X����7�H	�"�J���M�o�o���J�K11�-<�w$xfNg��L����3��J+�f/l��t��T[v����2�*d�\p���<T�A�Z����B�fq�������5&>H�L�9��)�>��Q=�j��������Tx�b��1orv�����p�O�*���b����W�-�m��-��]��������c�&��-tI�������-Vm���5��R�4x�-�bn[N���_)h/{j������*nd���QaV����y�M�M���(��R!1�Z�v$����y	.��K��3�:����h��_�,�/?	�(����@���	���E�E������C��i������e�-[�b��F+���~I�^�J:��b�Z.��'��0'�P��(�Q�T�t��7�"G�E#)�t>)����ll�]!�3E���������%/�N,{eT��$�
���A���g�y�����
�p
&|�/k�1�����7&���]H����B��C�@�p��t������"��;�?���#��Kmd����
��mz1�%#3e�'��,1��bg������@7Z���]����a.[?���"���rD4�0�m�{������;� U}%����+[rR��f�Cqa���n��d-����2���)fFY��YC���k��X�{��������X�
8W��\]�D�������__
��������������!r���*t����M����l���L}0���\������>�z����]?,�����*�jn�d��o>'�JrNT*����������F����s��H�d�����6��:&��g��BrN���?��W��*j'�[un��|�������!���E�8���E��v>���T���p���&���D�c��X��92������yCJn*^z�F�����������Qc�����a\}shUchY���C�����)���oq������RX��&��t���6�[z:G�����&
��7��H���?����r���^;�O�������b:���?�p�
V|�YZh.Gwh��l���0���|�Z�AE��U��xv�dT����r<�M����!=�&Y�~V�����/1�@��-�K�aD�N�;7�>�>���Id���]X�4����(CF��k?�Q��;�����zX�'�r���`2<�_a��{��$�V(� � �[8���HI�2���R��������)��0}*>���qw����z��U��=���?���`�(�S]���|�?E�J}f����y���G��wO�R�B��[���#�Z��H�KN�T�f`��56.���f�v�a��x�kL�"|1I�49������U�x�,\E���L�_��]��/
�^D���h���5�-�|)�i)%��(.u�,.��F�N^��&�"��u���TO&�a}�]a��=����G��������/���MN�O��2\��%�����i�a>���S��A�]�v���v�M�M�c�"����*���� ��G�Jj�{������2���7��>,^���7\OJ^?��h��lR��6���x�������m��e��j_\{���{�m��������{�>5��a��j� S����]�����]U^XaK�	����y��o�e�,��������D�f'����#�m�3��*R�K��l���9%.�?��:���O�#��EP2������u�_�/^w��r���L�F���wVK/�oSS���yz}	-����A�h;���Q����X"����\&�o�����#�^62?i�����4���Z��U��k����()�I4��m��[p�d���l�������b`
B\$`��-��9��z
n��'�YQ	XfC����%�@��9����e�M�z+1��5�OHHi^�{��@�Vn�m���wL��b��}u�`��#����A�)r����)y���td���y����g��,�U��d������N\�=�	N������u����1�lFcD�@jmf���>�/���PC�<���<��5:�H�����������%���P�C_��j����q!E�1r-P�j[��>
��r�/����aB6���Z�}�g���$`c�X�n��)��7�z	 ���2��}��XF���n�\�g��-���������gU�q�A�����F�
g��il�4SU�2dS��Z�D��398�d�F���[��K<��+��*e�f�'��xR��
�.�-,*�n����������)�9���P�@������~�U��#�z������k�J�������P��kf�F��Qb]�����1$#ZLu�[J�T���{R��,sV���0�����-;�����%����2���Q�	���r��s��m&��B�.D�!{�������9A\'c�pq��*^\e:G3����	_v���B�=��7�`�|KD��Q]��?Kn��/�����)������M;
$��q���������������V.������k�g��0������E�NA>5L�M��,Y�P��rQ�-�����O�p���$�Y��2�N|V>,������Z)g��j��b����$~�]����U �s��}���!*��g�G�\�"�o/N�n6��%�4q&�����d�����fWB�Ge�J%�]~�W���z���(V����y�P�@����$�T�?c#��\���n�
]�����fmU~���m1�1�N-���~���e6���B]�8d���������K$az��C����*��:�K�M�j��#�)���kOTI��s���*H�R.
z�B�BF�}8��Wb&(����e�����3�#����s����jq��w9
r2�9v��UE��A��Q����KV�UQh�QQJ�B�n�*�b\]�Z;��R��"hwUj4�H�����<����=y���?SV�I�l���
S/�)p)fu��X�D#���R��U��K���N:�w�6�=.��@{�_���7�p7��i��,7<&�<01�&�x�����	x�Nd�����H]DF�xFL� H�4�6	c*�`�@��n-�t5(x��t`$��7C���.H6��l7C4)ZV�g���^
S��z�}��^��/����['�x���<
������17����+���0G�\F��=�Y���?�H�<�o��%�$�s|���S��q0�5[�4>&�1q�z�r4�u�X ��h�\|����)�h��0[��YHKU��0����N-���CS���Sd�	��5�)8@7C{o����~	{�Y�W�#�����-I�/����
��]���`�l�"�$�{�D_�R��p�������[:��j��z��������6/���+#�I2!2PF42�	#�}�������&J��g|��f�>�qpS�[��R�KZ���[�q���t�:�f��g��H����o:�GCd5�\�h��T]q\��]�S?���>���bf��["���1���.����.1;�S
o�x���(C�j6{�B�PQ�=��2����Z��c���J3Ub�=�w�B_�fW����&��7�^�����uCBn�~Ou��R�����,�����S��
�D6����������������'����?g��nC�4SH���}5��S��3=���q�I&��,`��q��v%���������I�3Xpi��<��N_��{��D�]4h�L��"�����>ov��*U�g��<Q��
�N�uR��j5N*H��@�����z>��>p�bZI��'�L��$�����P����2�Zd�"w��K���hEj�y�����o���
X�����2-�8%�Le]#}(��J���	��������[�(d�-r��(4&�����\>:��+���u��[,R2��q�6�`�0Bp�����\5�?�[oJ���N�/
�
L/
���%�j������x_�\�E8G� �sq�������>P�
�9EY�*&v�\uBa�I7~��;LonV���w�d���p��0,|�X6�r�I$	0�����
?b�����V4�z	M���T5������)������DkX�f�lX������5
|X<YI��A���V	P�6��_��BZ��i�l�TKu�xt�nt�� ,�l�wr��7�rE�����I�T���6�>���h�F���4���bgv-=���TZ�B�k��^L�d����G��/���n�w���A.WyfURA�����V.~��V�`P����T%v�@vT+��=[�a�Kn������
�����fv|��v%�}R�H�����S;�*G�7��Q&���U��;�P>���Yg���gT[b:V�DW(���LG�AB�J��t���������)_|���8�B���j��#�W���m��s��h
����^>G��A\hL2�=����P���he���5��nk��5U+OF�����{ k��b�}����`b���Q����h"@������#+���L��I2�/���S���(5�"9��r�U�mCj
�V�qK�6|s��%�B�*>�a�c�s<]�aS��T*��r��M��a_�!m����i ���a��a�����	n���$�����b����*6�<+�L@jT�����,lfq���h�l&M��O�����3�Y�I�gtI�9�C}._J���3�m6��-2|t�n�0���v�L0�u�\�����U ���'�Ac\.�*�C������	�g����Z���~r�"��P&�M
�� ���S%�]�^_w�ys��U���_w������������/����k���>E�L���$`;i#��4���r���{Kn]�+�#9v��$�J5E=yn�X�����Z��'��N�����Tv0���-O��g��
\��Q�)2h6!|d���"E:��E���&��������jC(`�-�(��I�^��?�����i�h���F)�B����GG��j-�eX*D`8�r�02[���n��Td����n��[t��Kv:���
>,�2�j|��`=��N�6b�&/����(�0��o�����6F�>��Xy�����[^�K�ua����w?C��(����;�K����g�!z�0"��	t��ZOt�K\���-��JEU��"T��)d�z�1?�P����Bl����������+����AwU8��:R��(�
|!H:���R���w{e>K�J^��S�}��aJ������a��W�z�3+���1��`����5Ye�vpT��-e��U����{�;�q���|p��OO������L�F���0�)��l�W��0���=�M&��z�hA�}�U/e�c<��p�AM��.��ff���C��ii��g���������2U��v���v������'�rj���c��{C�8�=�������)oi������kC����7��d5]�i������w�=����w�?�F��da�T#�-�>������\���\l�IFLO	Z��z
|I:K9�3�	Ic�d ����E}5�.�h�uA�.S\P�'����q����a�^�DRO'�RPv\:�er��Ds	��%���s�����B��!�h�"C\�,O�*Y*����^��t��
��I���UGC����Z���fi�v�K	{�r8�C�7�Fw�)<�z����uq�2n�|��)Q��������?�1^K���������0�#C ����w��}��t��9����o���#���8�>���Y���f���s��9E�_���F�����_�c7}g��\{�d����v��������0��*+"6�g��;��(@������Y�r�~�RE�G�Z�����d2q�@�h���d� ��HGH\��,M/�������=y���w��n��U��	�+1z�� v�d��`"�b���[�g-u{O-���6k�	�����X%�gb�D�	y=35��)���kC9���n�nK^�U��bp��)�GL���b��-���\.k�	|^9��Z����M9���(i�C
,�R~f�:�������m_\w[?�[�=��;���]al����3F+���v^�dw������������E��^�$N1�}�������i�{�*�;�W���*��&���v;<��k���-���H�����DO�ZO3^�#���MK��$E}�0U�;���?����7�Q��8�`B�~k�v���v�_ "���*B��a]��7$��'M��F����J\<q����7���
PT+N;�c�Veq��vg��o��?��)���o��U��- 1�R����I�y�-%�������p�9k���3������nl_I�����j+x=�W ����������>���t&M���
�YS�1��jN5
��%�$�Ez�������9������E����jdAS�Uj��jM��^�p�y����C���DX������<���Qd-�^����
{3'�>A�[�j��9�=w&������~b���7\���M`���j��7������m6�88V���V�8k��J����=���h6���`
��q�i��D��Br�6���I���	��=2ez��#��|	[����j�{���=i@�(,>f�eN	��|�\�>k�D}���R�������r�rpT?9R������jl8JL%
=�(9������]6{=eR�
x���������q=#�eFe�7t�,����1�Z7�<�����u������l�c���*�GZ&�����qY��QPO���z���;�W���^P��usT]�����[��nk���
���$�B�h����K^�R��0��c
�O��x�/��L���j�"��%|��Q1:Q	���J����Am[z^��5K��Y�5P������EJ�C{4:"����	XT�m5Q�C�o�R,k���	��7P#�0u%����,P�9U�E�@m��Q4�����=�a(u#������s�i����f[��RgP}��L���L��*{Kv��X���aeD[8������[�^p���_��&	,�_��~�Lw���;�)'L�&d�^����W��p��Eg��a9��[y���W99i�W*����.������,�3�C����.��z���np��N�(_�����"��3 &�������E1�a���9b7�A1xoG�+L:�c�vJ1��k������l�-���<�W�U��f�f���W�����=�����!���ikWp�p7/;I:*!�=��wm#�?[���n�
^���y!IM�W��n���;�1>��(�* `��	cr�DnE��U�j{md�N
E�;zv�6����(e������@���V� �^�:
�V�z���[�+N*�\�K���t��D (���^��YLy�/�d"��?���g�Z�<
�Ah���U�	��B����P'�\S�QS
����b�c�fN5>O4�fX@�����+�PS�jf�P���aA�K����E��4��IV%��8�fnX�-�a�d%�?��U�����8���Jy��fesM��NusM"s�Z�\U�v�b�o���kg�����$�@�+�d�X3]���@s���h�2�3�n����uT��$�a�"��g�v6��p��r8jCL�b8 �!b���:�4�����0�:f�Y�R�S;��E ���2������0���NJ�{�a�g!����) ^��w,����E�A�6�s�de��
�-:����+X*3��y=s�$l}��.�����|u��~#��c];�d�2�a-�������}Z�N�+��`x89������R�\`S2v��8D�0�a��N�(U��������Y�w�h8o�=����Q��c����kD4.���Y���A����9Y������V)��g������Kh���Bs��VW���8����+SN?����.M_J���L��9��b�B�DMF��z#}=�O�"m�l��E��U�S������g^�\e��
s�F�)$
�*�*�sIm��D����Y�}#����-8�t��zP��H��u��UM��?����d��+����������d��i�a�^�\V�K&����V��$=pJ6'�CG�w�4K��)z�B~�������
?�v��5|���������2���r�����E#%IA�A���E����f��o��w�9o����9r��;��=��,�(88������'c�Y�dU�����
�3�a�PT�s�e�<(�����A�����F�~���u�������"�*G���!�
���$�K�C\����Ho���CF+��)��7�(��������I
y��*�C��zM��M�|7���*�dp��l�y�����Q$n(��$��l�
}.A�X���u'�����c^,� Et'�7ZNWJ����{����r���c���Y�1�)Z����}@���%��G#�����&��Z$ �N����VJ�.�wM+e�JL������������h�����Ra��'�FP=)�O���F=��.�>��5��p2��d���w���t���]����T�:oJ���n�L��U��'v���t�/�A��}���<��������-�q��?��'X2��]��]�h��Q�
|��6F��[��j^^"���:��3�3y���7���z
�5����9�����}W���X���s������[u�
��-���t�[��R�P�n�����,��MV{��-�
�ct���5�}i
�^_l^����g��~��j�]�h�Z�����enC_��=&��.�2��\����d����A��#1eCJ3���$?�N0�F�~�����&x'�V��f���W�w�oY�P����a��;L���`2��l�i���)8�EI�!���g6��vS�R����������R�.���e�U.
�h����p��9���������b#�?�.��It��=��[��	le���E9i�d{ ����m�a�=��8��"�z@�P���0��8��h0�rY<������#jS�4J�~��7�G���G�W��Lh[���LO5�����q~/)���XO���Ne����	�c.~�����:�'���8^��
�����`b��g�hn��Z[�TH[O�%����-����4�XH�nJ%�5j�\����m]!R��e����LOIg����O0:2K�i7I���7���*�#n�b���T��	�*���n����nM�,������~�j����c��o�
Y[�\�������^x�j�l��Q3�N9��pC�)
���s�M�n:�~f}�����+�M���UG���I�p@����=�X3�u�����������C�v�n�M�-c�@���j���n�Z���-0�4�����F
a����q�����Br?PB�x��(2����<5P����A%�pJmU	q��5Wj��K0�M����]��
8��QWd����n���1��$!J���9x�����	$[�*g�B�ca[��l�g��^Mg�s4K�?��Q=l�4�����������6}�4K�%�@�����o�f���3o�]�k_���q ���>x�%$[��-��cl�������NG�b\{K������f��p����^���&�U�9a<�����
���"��$=C_�pLo��; �:��	n�n�G8t��	�� ���Bf���m��&�	P�PL��b8pB��p�G�	�|����M���}9�2[l�}� k�Q���a�0U��r0nW���X��Y��K)�q��[�����P/��#�������{<�/T���kS�9`���r��&��*�`����<xT[ZU���^K^�"H6�
aT�E}a��I�c�����
:~F2�1#h0cu�b��?���Q��hL���%6d��Q�����������3��f�k,�U��'���P��������{��'�~����M���:�'#~��0�~�e����#�-��+��[4Zx|XPl���?����1%��~���v������r���Tj�[����eV����������������h�[�-�`�>`��h5����*q��A�������_�b�����D��L���Q0:9�4���+���� +/4�V��(.�r����������E�Z������]�{�
^�u�U����?�:���4���c.J�NU�r��	3����0DH�n������+��<c�+|�����=�}R�l_���7NG������������|�������633���c��-�g�7�MQJ�+��C����0��t�u�o������W�c��j�@��m`�|�^�����JN�]|�b��-��U��;��c����0p�<\Q�f���\�a'�4��K}](�2 T����g���<�w�.{���~�$X �fX��6*T��F
���������=a��0�{�����1�&��H9��I�PY����#L��0�B�Z��~��*d/���>�3W���`�ZR���-N7l�*���/�^������NrqT�/�G��~�J���|to����/Zp��/����?���2�����n�7���iyth!��su��b�9RUO�.����qI��������t���}6f�m���!c�*��^?���r98V���g��>]������x"Xl���\��*GZ�\���O�_���k�7|���(�M0X 3}�w��8�B}������l@��?G�o�����zI�P_��������3\C�R�/��������cD*�b��(�G��]8R��l���0�C��&��%���fM}m ����O�*���VMju ����F�����A�jt���Z�d����'&��?,J�Zq���[��i�j��>�.���G�`uv���V������v�%�G�������W����+�������/vk�S���Y�8-�~�eDI�n���cd���KIqu-l/!|��I�|T>|���k>��
0003-Define-logical-replication-protocol-and-output-plugi-v18.patch.gzapplication/gzip; name=0003-Define-logical-replication-protocol-and-output-plugi-v18.patch.gzDownload
0004-Add-logical-replication-workers-v18.patch.gzapplication/gzip; name=0004-Add-logical-replication-workers-v18.patch.gzDownload
0005-Add-separate-synchronous-commit-control-for-logical--v18.patch.gzapplication/gzip; name=0005-Add-separate-synchronous-commit-control-for-logical--v18.patch.gzDownload
#189Erik Rijkers
er@xs4all.nl
In reply to: Petr Jelinek (#188)
Re: Logical Replication WIP

On 2017-01-15 23:20, Petr Jelinek wrote:

0001-Add-PUBLICATION-catalogs-and-DDL-v18.patch
0002-Add-SUBSCRIPTION-catalog-and-DDL-v18.patch
0003-Define-logical-replication-protocol-and-output-plugi-v18.patch
0004-Add-logical-replication-workers-v18.patch
0005-Add-separate-synchronous-commit-control-for-logical--v18.patch

patches apply OK (to master), but I get this compile error:

execReplication.c: In function ‘ExecSimpleRelationInsert’:
execReplication.c:392:41: warning: passing argument 3 of
‘ExecConstraints’ from incompatible pointer type
[-Wincompatible-pointer-types]
ExecConstraints(resultRelInfo, slot, estate);
^~~~~~
In file included from execReplication.c:21:0:
../../../src/include/executor/executor.h:197:13: note: expected
‘TupleTableSlot * {aka struct TupleTableSlot *}’ but argument is of type
‘EState * {aka struct EState *}’
extern void ExecConstraints(ResultRelInfo *resultRelInfo,
^~~~~~~~~~~~~~~
execReplication.c:392:4: error: too few arguments to function
‘ExecConstraints’
ExecConstraints(resultRelInfo, slot, estate);
^~~~~~~~~~~~~~~
In file included from execReplication.c:21:0:
../../../src/include/executor/executor.h:197:13: note: declared here
extern void ExecConstraints(ResultRelInfo *resultRelInfo,
^~~~~~~~~~~~~~~
execReplication.c: In function ‘ExecSimpleRelationUpdate’:
execReplication.c:451:41: warning: passing argument 3 of
‘ExecConstraints’ from incompatible pointer type
[-Wincompatible-pointer-types]
ExecConstraints(resultRelInfo, slot, estate);
^~~~~~
In file included from execReplication.c:21:0:
../../../src/include/executor/executor.h:197:13: note: expected
‘TupleTableSlot * {aka struct TupleTableSlot *}’ but argument is of type
‘EState * {aka struct EState *}’
extern void ExecConstraints(ResultRelInfo *resultRelInfo,
^~~~~~~~~~~~~~~
execReplication.c:451:4: error: too few arguments to function
‘ExecConstraints’
ExecConstraints(resultRelInfo, slot, estate);
^~~~~~~~~~~~~~~
In file included from execReplication.c:21:0:
../../../src/include/executor/executor.h:197:13: note: declared here
extern void ExecConstraints(ResultRelInfo *resultRelInfo,
^~~~~~~~~~~~~~~
make[3]: *** [execReplication.o] Error 1
make[2]: *** [executor-recursive] Error 2
make[1]: *** [install-backend-recurse] Error 2
make: *** [install-src-recurse] Error 2

Erik Rijkers

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

#190Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Erik Rijkers (#189)
1 attachment(s)
Re: Logical Replication WIP

On 15/01/17 23:57, Erik Rijkers wrote:

On 2017-01-15 23:20, Petr Jelinek wrote:

0001-Add-PUBLICATION-catalogs-and-DDL-v18.patch
0002-Add-SUBSCRIPTION-catalog-and-DDL-v18.patch
0003-Define-logical-replication-protocol-and-output-plugi-v18.patch
0004-Add-logical-replication-workers-v18.patch
0005-Add-separate-synchronous-commit-control-for-logical--v18.patch

patches apply OK (to master), but I get this compile error:

Ah missed that during final rebase, sorry. Here is fixed 0004 patch.

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

Attachments:

0004-Add-logical-replication-workers-v18fixed.patch.gzapplication/gzip; name=0004-Add-logical-replication-workers-v18fixed.patch.gzDownload
�F|X0004-Add-logical-replication-workers-v18fixed.patch��yW�0���)N476�Z�x�+K��F�y�<��xh����`Y��������K&��x&��Z�N��:����C�8��{�y������������0������;���zk����N��j�cU�W��������f}�%4�����_� �o����^���|�F����q0
�So�^U����P����=��	�-�f��������WG�����_�a���u�
j��e��n���p�,/���/�^��H&���p�%�����*�������L�x�����r���ZKU�GD2�����<u��+���� �;@�ijo�n�5��j`��i<�F�����w2c���z.���O��Fw����N����^v�N�d�I��h�S�jPm��	�o�Qo#�v�$��N�Q7��i�[t�� �Fr�L�a�]�~M��0m'S9�Q/�p���������3X����m�HW����Mm�rD��)s��}UV��A�n����6#���b�0�M6:���������S{|�L����R\�|�6��eY�j��A��z&a7��A�����d���L�[�����Y�� �q�u��w�3��kf<��qv
�����U�����C���f������c�7�i,0����(5�9�7�q���1��d;�J��`x
q;��oG�h���4]9C;f�h���C�������nf�\{%���Y�`�2k3�h��z�![��6�|�vS\��;��Gmor*��+{����M��OW��^0Z�g�}t��3�������:�2���l�f��T6{&��Nf�r*�n�I���X���Y��z)���v4b9
�q�e���Y�^�8f*��(j��<
����0���S{N������6��o��'!VK�����@�f �l�[{����6j��4����y����\
B��������F	��{��� �m���`t
s���������������_�T��>p�����L��������FI8�����*J;�B~�����ST ����{;;����5���V-?�?��=�?��>�+/DV?��%��R[��{�����e[����G�������Y�n���^����u��
6J��N���h�����^ok�V{��	�}=�K�^�}_����Z��:���IH������Z��tL���M]��J����t2�N�m{����>�n8�T7MANJ���:4��L�= >j
����N�����EK*�2�����G~�$����A�U�h����J�Zw��<���7d��:��	L{��;&��?�\L)��
�G�4]��t8n;	Ck~1T��O�\(��A+��)��0\��@����NuaV����h��.�Ww������AMcX�(1����3L'2��j�Ni�fp��d���PmD��/��8��5�^G>y����s��:��$zp��v����Ys�G��h
�F��x0���\���|��8xP�O��tr��)�������@U��t�_�K�?>�L�;���:cg�(�����p��YBY���1�Zz��	h5A~���p���n�Y[s�;������I|+?����J����L�Z�J(��~����Z������/��N�<:��F�����q��'4��~ROq�A7DLT�A�$����������������O7�r�U%�oo���`�+��P%��o!���<��*����F�h����1x������2o�.X�E,�`����S$�	Q�I< T��7���P:(#�)��h���Y�����	0]��`4������Y<�ML�S��p�.�Z�l#t��\y�������/|��;��`�NM�l��3km�J������Jn	T���p0(����c|B���z�]��������p��N8x�e�R���m���OaR��8s�9�0��	�\5M�&�v
ey:�D�)��=�Q3=nd��Qy?��4���"�R-��~�
c��38gC�h���5F:��0����N<�Q�x<���0�):����I<�~��������I�����?���v^{�������m�[�HP;\C����C�
:
f�)�~d6v
�?�H�uj���)z����K��.<S�z��uN�V�N$�J�S�RN��������������V���W�9��$_l����ay�M���y��l��?���j�9�`��4�s@HF��[�>��r��?]���k�hqr�i8Vh��~`���]>'u=�@Z\_wG�om�f�E��U����3
m�BV��n���J�o���V�T�[E�W��x�d����gS��I��J=R//�O���`�m�����������4�Q�2WVW�X��.��~j~���L�o�c���'�u4Zzp0w��������!�04��s;mi�!���K��������X��+��t�������V�����%����6F�A����K�Y�����M7l�5�����h��i�&@har%E&����C�Q�Y.�'�y��@��z���3�F�$\���"��Bv���NU���S�1sw��y�����������w����z�����&4R%"�9i���A2Er|s���_U�7Q����F��`�:x#��^oB�
�s7
�;w����!��{�'�����fM��0��F��8��X��tpW5����t]����8�h�)���V�<�������9�xZ-�WfG�l����E'@�i?�$S�{��#�ef�����n����`T�$IA	+��0'�"f�brM����L�*�6g��>W#�+�&A�����f�WB���q���C��a.4�n]�����l� �=��b-gw��xT�cd��.XxEc�/+M�z't�0n-������;j9A6��Ny�S����A�)L�����I�%i�B�"[�tq�0~�qB\�����BV(��y+��J'�A������t
�����*�pv	+U&��p�������`-B ��z��Y����q�y�����C�6�-���jF��!���P PL�! !�S��V�!���S��m(�E
�����p�c�a���e4��
��5����S6z�f�t���a�`���%|�����A���m�I4v@��ED��E�T���y�;�6��O�M	��	ppa���Fz����pz�����@���B����sp}�!�����9�h�I�@;��I<'��
�K
�u^98��a0e�u�1���i*��I����oIM'AV�C�F����$�[D�d��05�`� �zT�!��fNO;w,��%��U�5��&� ���Tn�$�Ao=�M��<vS�����b:��5�����,�������+�������"�w~ql�V��p��=�G�4<��O:�K�H��8cE���m��xsts���}�^�����|�1�J�5��b��s�=��L����x	��u�|���}:�
���o��.�3M���Q8	�UQ���v"������ �
�%������#H1��4vD����|��Nn�o�t�G��p���-8�/\���3�`��	�K� ?�/W��oY`!UJQD+�^�G��e�Aa>D�'n�c`���9���!;j���\u�hO$���%�i��������)���Gn(�w�H|�,�J���''����I��Wa����7q��� FS��%`<�{�.3'7����<H
B���X,?o���W0��}������A����_7N�R3NC��&D����W�� ":
R��3��C�`���{^e�\�30���qq�<:T�cV]�(�
�&���Ijf��O�������6#G�������+����H=xC�B8�;o~�x^�C�!�eh�&PKg	��7�����:��r�d�F���2P��/Rd8�'�xlz�����q�L�6��'78|�>�\j��s>���Q0$��;�2W����1�����
f�����{�g__�_�������+'=a�N�7EX<�d��BM��5
d�#���KF��6�������^)j���C��������sj�<���m����,1��h%�>���Zi{%�9�sk�����j��M�.��lwJ��h��(d�jq*1e�VV���@G%��c!����� ���q6no�&��6�R g��H8r��������A'��pU )&(?��5�%�e�h�s�'T4�"4y�PGG=�0�:	
?@���#G�l"W������h�/������2P��!;���7���( N�c�@&*EJU�z�C�d��j��Z`$���f�1��_���S��\���1;3�����k�CRo���������u�B%O7���;�>=���c/K��q�h��J
qMi�������J�x���>��O2<b�F� ��y��s����������X�A���Z�u��@J<������M��`�
&�9%���X��U����q1�A��D��EX0@��n$����-�����F��������}r����D%������g�3��Z��LKw���x��.�K���9����l����_��B
mF�W�5�����Ht��u,�!�B��Gh9����\�B���1��H�5S��q:r%�����+��1(�`�I�a@dF�������g�>���"�i�d�%�t��1LWFO�l9k@R���Z����0���R�O+�f�����dI�60��� f�9��tA������6��wQ��"r'�x [O���Lk������'_���8���sy��P&@�#�U�0JsGKC���t�1�;g�osZ��eB�$<�FHI�� � U6��!�l����Ij��i<�������k�*�k�8�������p����E�V��ld����?L���t��������9_'I�f����qK����LP���f0P�_1u@��^WB�f�^�����iX,t8�	��������3f�������a10�6����$j�Go�+�@��h#g��W�wa�?W���8�����.�eMr�����������
����z>��}fL'S6�9P���A�;�����;�tof�)0��>t^����z�����N.\��n�\�9c��z�����{�D��Zxo-���e4��b�_`��liJn��l>x��D�4�%������{�M|��L�nN��K��T`���E!*t�a��85w`�f��6�e*9f���Z,�*<:�r�Q�>�uO��-P[�s'�$��4������FHO���1���X���,��0l��oO��xbW���0���1��3���2��sh<���(��C�Q��m4���/�SA�<�����+^]"��'���G$��m*��4����x�������}#Zd�h�F�x�������
���e��{v
��B�iC��X�������J�6%���$�*}�[����(�<���+���YZ���6�����V���
�����; &� tL�Q!�6��������f�a�!]K	p��2c�s�O<fll��95����a7@��<�GF����B�<*�W4u6�,���d���p�Sb�2�1�{0<��H�R�b?� Q�k���r�<W�vF�uCL�H�E�q��*�����<$[�`����LLM���	o�&���[:C��dsm�X�+����������}������L�����!�^��g#���aY�����z���2f���&���>����[�d������hF�%;_�H��O���4��
���a
��Bby���~��Gl*M�I�8�E���Se���|Ww�v�����5��n��|l!=���
&��'������������*�E������8LD���
��i���7�F���X;�{d�jc;�G�
R!����M����a���cp6=��������7_��T&���#&���$��pa+�YQ����t�c����������h�w]�{!�fL�&@��E�!�d�*j����u�#��n��7�!*�.�C�g]_����)���A�������i�R��h,�3��8��=�x8G=3=��5 �u�V���9�E���G|o8�e��&l��x

BrU��_�6�_��=X_�
Nc��9��Xb���x6��N����i�@xIR�L*�Q����\-�m������6>	��#7�@�k�X5|y�p���H���t`��G�\�h-�1����ZL�8������9�Q����(�;GCU����.t�|L�
z�=K�P����I_����aV�3�5����[C[wm
���^#�[���d0
=1Q�#��{j]	k�$���������X��l�Q��sE����KT��:��:K�P=a�q��u�fd��q���������c��������7����[�Ya��,aYeX[��a���������.m��C���n�!�6�h��0�B�5�������wv�=�����
Xp�w10� ��?
��)$BH�E��XQ��5�e����WsQ�0.��+�M
����ocS��ao�~0�B�$���Y��q1��.^��L��A]������������u����
���\��)V�����e�B������Y��<�u���X!|�u�	���@�av^�:i�:�����K6F9e
�3��g\[@�N�).����Z�����)}���wwv�����f����x]����M��.��>�;���������������>��R���ZX�^�n{�
$�M|�W���7�'�Z�9(�(O�b��]���&���Pm����j�H+i��J����%�T�.��������D�������7v��a��3��"�(����3G�P+�D2t���Z'���5k���6�bm}ww�����t��q����M�d�NQ��&�M��h������e�-�`,���K)K8����-��&Q��� �W��H^���[�
�V���g+��Zp3��9�
x
v�@���q��,=���_�q�2��������F=�����A�����c}����-�
v���*�lHS�3�E�PN�����Fs�&c��Rrl��KKr�~�@�-T{��������	���IF�36������S�'9����$�B�)����l�i�kb�)��<A��i0�"MM����@s14���CIp����H��t����?{R��x�U�u�m�����b��6��WGI6��X_s_c�������n���&����L��v���P ���Iwr$=���|�V�G|�K	���0
��J�n����4�����R��6p��.$�uW>��VJ�|���1��i��XN���tJ^��rP��>���j;a}��W/�Q��9�_�X�=
�������c(���^���!������	B�A�j�KHx�'$��M�@R���pQ���N����j���N�\e����8�r����:�bR9��y1=��q�yf"g|Q7�����|���
�����1]�p�WdYF=��u�7���M�E
���
>����3��+���������e`������G)����D�{j.����L�(�)+��v{�A��fo�qg{1���V������*N��9�p������n���-plD7(���*�V0���,t����-��bD�B��
���������mg�z:���zqxyx��j\�x^������Z���������<GJ�
F�-5��gk�?Z�j����T�{���������G��
"�A^�l�8�F#B� 8�;�f5h��a�t���nO�j���b�S����y~����2&����^�V�����{�8��[�m;�m�-Db��'��$�Z�dIAo�j7+�s�V�{��ZK�����/
�b��z�����2����������=&SG��,�W��QouY-N�?����ArsE�a���W���-I�����Wk��;k�1��@�B��mf���Fm#T6W�Z:b_��0�g�nx~;B�L��>9yR�����{���iMw}��o�j���n��]���
eQ��(��~}�
?�\������!t�i���(=�u��������R1����j������R�K�/��B_$"�^�(�Y������"�X5�V�T�SJ�������j6J�����b��]��e
l:���|�xy����y�-�[��������?8Q+\!
H�0�}��� t|�5��k1��wMI����%5M����k�����0,�;%m�l���D��@I�p���)Zx��NQ�����@��<l�'J&%�+�e�����������c".�$�������t8U���J�&���d��$T�e|�x�&0������6~���	����>*��K������iex���~�p)����*S9��w����@���}���V�*<Y�xL��;���EO�����4(��0H��]r���* ��7��I�v�*�Fc����A	]0^��eh3�GP��
���K�W1�������N��?��n5�� XR�? w�v���Q�?��
 ��Cc��f���I�5�v+�"Z�@��^.)p�xl�>
���[�<�K�/jP-%#�'F�~��WI�[��V��c����8�����!���^����Aa5
�Z6n�����z���tk�����f�^L��-d�V�)�8��H�z��G�"���H�UjGW�]�N0@�;�_-�|8�j1}vI~�B�^���&N������-|��9$L�_~!�_��$���6���,P�u�v$y5SzC(���pbH��:��^c�Gx�}R�;��	I����A������k�|�"����)���'�:����C����h{d(?^��@���F'�r��ZK�����?����w�z���Q��N)��M8'0�(}�C�l���� U�����U=�U��6S�z���GD��G����H#a�����r�a����XN8�5g@IH'u
)������'����M��JP/������/�/9���U��x�a(y��e����sm�������r����H����]�o������{�@9��fiiNg�����W:�!�����nu�
ra�����/T]>���3Pm���=S�-�����/��T>�(��^3�g�
�^�S
WD9l{u5g��VWGX|�����F;����(�[_h��2�25��Z"-��M��nqU�]C �4~�Jv�i6O��z�^-��:���4����J������O��*�d��}����m���s�A�j7��� j��u���j�[W���T�P �TV`7���#�������V��&�<7P9�d���t
���dE�ZZ�����m?/�3������X���!�����dB��(��'W
�������N���F����6�#_����8'�E?6�}�I`��{��2����T�`��S����!��AxM���F�QM,l�Z|E!f�������P�pB�zF��	x��K���8��8D`f<"]v����
.�J$�?��zn*��qr���nS.��G�_��M�������K
A�
A���,Qz�
��)E,��o���9�:��w�
\���������f}��^h�"�S��{<F�K{9����k4]�����"�O)����'�(0�b)�2�WD���k�������(��/D���8D_z�5��/IM��1�G]��� �_R��-M6��K����[��������L'a�z�
�Y_T
9/�W��N�Y�f��%��=��0�����T>�9FI)HioS�f&������w�@6Qz�!�c�c��� �]$
�}�J�)�8�����!fj
�bP�S	O&U*��|������2���/����p��w:����F�bb�hn
�5
,���*z��&������{�&;��pJi})��V�$q7
�������cL
�v�1�twP`T��M����7�x���5>���t}�B��#^T�.������r�t��xM��XZ�<��1"&<���~�s�@��`!��xL��ps[�����/��GE�9Mj�9����="���k��&����''��c3����VE#��:�Z*(~8�N*���������qU�GJ8j�	�1u�����u�U�#M��zx��8�D�{�B����1�@.b�`V��Z��D
���Vv�)0��z���D���*�����%���;#�����n)������U�WPN0+O�����0���wM���6�����1m�Z	�����;���K`Y��H?s�_�^�`J�u�@�d<c���<J� `E�
��6>�����Q�~\�T_,�"�������:���
��V��Ws������Uj��p���ue���s���W���\����j�tZS��p}���C��G<����R#�]�p�'4��HT��Ar��W�vV�vF!���Q������L����zd�X�(���&uM#�-4���!K!$U ]��� l�?��*/i@���������-�i�{]�D�����%�A�=&-���4mT���Z�6��;�Z��Mz*�(��f�o?��G���F�������������Owt�)�em����
Pa4����UE��"	�?Pz�� 3��{z���#:�����O
�^S�"�����o%�
��]M_�8������4�a����nk�>���s�r��-P|���9�QK%`)s	h�b��pf��t��Ol����E4�f�%QQI����)P]���c��-�G�|��I'�=��h��iu>����X��g�vl8��.�����-I�$M���={M�w��GOg-]�Q'�`������?R��e��bG�[a3�tV���q��y�q�2j��|GG8��x"n
�.����8��/\uG5���D�I�B{�
(O����)���F�����JjEv[]E]/]�k�AO����x�	�m7@���@�����b�1���US��`0q��R��E�*�-t�a�>�p�k���hU��i�~��S�=x�f$,��V��Cqi_z�P	�Qr#�}�xLB:w�4�U���y�o�%��<�z�>�G����,�����I#��22h�k#�9�Y���4��+�@����P�&��� xL�g���j�n_j�[b�"PQ�F$�oN��0��U�<�����F_�|e�@�������YrsHQ����rB6-���e.���~l��`
�����].�X7��UE��w�I�'�y������c��ev��8��
�,��!���A���|����8a>F>���E�@	�x���~���g?�$qp�����<
�^����x���'9e�b0���;��'x����(0]����r����JF9~��l�������#-Z��a���e�!�F/n��
���g0�i��Bj/�&$�C��T<x�p8�]�c�8���t"i����LT�~*���1�����-���gJ��Z�9bO���c�V�81�@�5���Edg�s~�y�u�
3�R��B��F�+0�\B"����\��b��[@�H�F9l1�^��r�������`J1�:3�;�>��l 	x���y/f���3	u.�������Z2� ��(�t	���L��8�������#	���""�������T�r"c��kh���(U���<��wD>9
�SY�80'�KZ3TZ���&#��@:���o�#����X��,
�g��6��Q?YX�B8����$���a�xJ�?G�)1�E�{�c�vN�X�H�i�X�X���&�k7�������4D�M�K��d�2�~�����9�	���0@;����a4�4������]�K�#�&���+�=�LHN�X����@l^Y�Q��;(S5?�h5��*�������]�I���I�T�F�"��������!5���q"���(R2b���hJc��V������Q1'_����s��������e"�'J����d��|�d	-QZ�)�)���V8�k�o�bRa���au���}	��kJf�Wm��0_��6<i���#=�y_}���$���K��c`:��#K�����������������?�Jus����9�cU�`=p^��M��5�W5��Z-
���dP�p39���-��c�	����RGh>�H�����G)=�����
�D��kt�PJ��A��4��P+�������&�*���W�;�a���A�Mt�U�&��g��\&m~e,a�x�_�2�kb��$j���<�����P-9���X'��$�R���-rd�{�C���
��O���������9�e}���eC]��Q�
f�@a����{?x t���
����!l��#����Bxq���P���5�R�{!�M�sZ������h����fvD����}o�v%S��� 
L�Q�L�'��J	 k�J�O��M6�q\��2���_�����`qnK�]�{�C5���b$���`��UKy�`�����"UF?%a��������I�K{�MD�Q����,a��@l�"L��\mjI7�
�]�v�{�TJ*��^����F/���e
�R;�8|�V����V�pA4N��(v�S�5��OBds�������:,��,�P|-��)�����9�,G�"�#�q��d�+?�$	�����G	�G��G	c�����%�
��v�����K|I�)�B�_��'G��h~�i�P)K�<�����,��������%����F����+�v�|�8A�t��`k-
�%��g���>��u5%'�������F��N�E����g��^�y�0wm����Q�S�2���gDD��e�"�������K���"$9�B��`.�*��'R(W�/
����.��?g�����e|��������������~�k����v?������s�f��{�9��%����������q�]���.�z���v������9^�f@_��y�����6�����gB���9�-t!FM������;i���2�#�W�m��7k�n�����)����J��8�����[������4F���|�Tk&TIY����j��^2,x��oh����7Se����!F8X�z��������x�.+1K�>�L�;�(��1�H<���A�X��2�(�	����
g��y[rE{�1 v����3	P�L��������
yd��,��W<���_���a�E��,�m���~"��b��;�Pt2t3�'w�F��b����Fp��1������-
�z^G��i@U��#��2C\q����Z��b�&�4���fC��l����y����E����f��U^��@��67^�~U��#U�$n"�B��1�!����T���sX�EtCI�o$sD�huaG�)BrM�u��eW4����[:����b����W���u@f=�r-Y�;<����#�qy�V���]��J��J�K� ����f�_��������[ym�RYS��monS�;��`o|�#eF6�N�:!�P'��%p�E����U��O8��oO�o.��l�6��c�QhA�5X��7��
��hg�<�v-�
���;i��>;��4��iV���dc%�(E�^\��4�e�J�f	d����8e���sh�
t���`2�0pj!���S9�t��N������1����`1,r�)�$�$]uB&��������a�_&�5'������"����^���s?���;����
D�
F&��*���������f���b�>[���f
3Lb����0HM��W�)(��{Dx��A/�7�i��/pRYe=`���@#;���qcI����DS�*�qo���7A�r�@���rIb+�s�RmZ��;�5,*FG��Vt�@���u��jr�
����}���vp�;,*eQ=��:�_�8����{�����^����C���w��x_����~�o�������:�l����d�|��N������e���+�r��� �%z���a ��}�M���S�I�]m�0(� �7�p�A��\�(�����X8���J'P�y���H��G:y�[T^��8�|�V�O�L���!=a`�.���J�G8���T6��Mif�yD����|��V�v�}��!�K�G�g��K�I�m��B�Z�a�N��e;�DM��!���UZ{����w����8I�x���W$��F�pc�B&z<���(c�y}�^.^It�Gf��@�i��U�F�`:��;+B�(q9�8��4E)��������~��tbZd8��(��U-=��uJ�^�l�~c
�>������qb����]���-����������d�x�'����^�&�9x�zI��5v
Y��Vp�f����C(����r��Q����g��x|�N�9l���je�D.�0������s���K���w����}���������|Y��hI�>"�i�����1^{�Eww�C��l.��caX���1�?�����#-�w(��/���>|P���� ����H+�����U�-d����%���1gc�.���f���P��T�f�J�����-�H�k�:9����o���Gu�<m�4��EW���'�v{k�������GU�2St'���;�Z$=������������xR������U�1���H/|i�"��H��h	���d��)�Sv���q2��:�f������'�O�r$��
��Hf�M��ws�ds�ii�X�"j���<���P�����.��x�*b�^_�2�����=�[m�Eu��7��@Q%{w�����p�����g2�r�r��*����������@�)�zIG8J�z�Zqh���x�`aeLW
������W��.��	X�*#V�O��w��c��Q�}\�4�^��������#�!o�����#b��W�1��nu{����2��)MS.:�L87����\#�W�e�NB8��v�E�;Qx�5z|&�����#�BU�:����XS�	��>�Y�Iw+�PmA�6M3��n�R1�}��������4��
��Q8�$�FDi�F\�H�c�_�2�k����2T��<5@J��Ch�#�;��'��5��{9��Cf�t�����)������~X}�:�ZO�Y��|�&���0��c\�|b�9�c��]�����h��f�5)���[����A'���eK���I���1l�	��]\�<9|�R�=S����@y���7���%G/�rr}��o}
���5���0�,���0���	���]ai��xy-�������!��L�� ������� ��L�&hr��~�ffT�?�^(�wgwkg����{NWN�������[9�q�k7
wIvL�`H� k�������o�/��bA����p8�j��:�L�K�Q�����gIqc��Y�mF�����m00�4f���F�v�81g�v����4'�E�E����QJi�i��r5�9���>�(*�
�s�iKb�9�����1�@/S�x����It=2�K�`�s�%`!��ec�J������d����� 9�/��Z^�u���y����4�)y_��n<��?x�d�.�P`��QU^%m�����
�x��Y6�;�g5��d�c���N8�
���u���M�(p�����(T�����O��g�(��/�����N����lu��r[���,X�����e��i����3��ZFU9�D�D��G��-z�2�H�q8y%�!��G��tiI�h~[��4�����Z��?�<i��|q�h^^��>m��h\b���y���r��G�C*�]�d2����}4�[�.E�h����Q��E�������%]��v����qy�0���w	��C��z�[�_�����f��x�~����B^����`bo��������H�5����R�c�@������e<�60�s���(��Vcuc8.|2'��X[^KO����$�F6��
`C$L��H�8D���C�,��)�^����@�}?5���W�u�R���LM���R���	���gS��Jhm���J��S_�'���(2I����`���.:d�W$= �#u�^��9�����I����:3��C�"�U�������{�/�+P���p�(�R��������R
:X���ER����eax��P@5���6���������"�E��(4�"{4���Z��v�8m��AY-�F�C;�)g�[5�O��G��-��Z�;%�Q���L(�rQ
�CZ�Z@����a�������7JGxr����`J$b�XOF����	w0< �N�{i\��._]I�Qh��5nN�@�=d_�=��lp�Cw��I��8�~&z�U�f�-]��<�����C�E~O��x����0,���fK��c�i��A�Eb�
��oC��H����E�j�S������]7Jo^�@��Y�.9����$&���K�A��*�"gE���oE����������9��S��S?:�?�/��iD2��%��-�e��<���
plGo6��-?���[�Z�,C�(��Rr2b�`K��0u����]M~zan��/����&
aV�UH�V��o��W��������:��P���/O���{������YA��u���'��������v6�#m����R��w��s��x���#=3.����G�K<��I��E���<�E��Nq�A9I������d9ek�g���.���
�Cyq}��[$DHt&k�>�u��<yb�����]^:��q�C���e�yL������
����CVzX��yHU�`��P���q�����2�# !�u�I�J���}M��1M�|�����[M��	Q�Q��{��][�������4����|��k��1Z�kw<��8��z}�L��(�/aQ�Hb�*5��f^2��������&�
K�d������Nh�p����z�V�����8��]HM00&"�	��������Y�7h�}u�o\����������uuz�B�������N�:
�U���p~���x}��*��y
2�p��$V�u��0	�Qgo4p}
6x���"E��&[1��h�Df(@+<$S#��r���P�,%X^��x����I8s�M�=�g�E���l�������0C���kk��K�[�WA
��bG����Y�H"�T�� �N%y��Tc�\��\���
Q|K�wD���w/o\��L�2��<��T�;��w[��Rx��P��z6��x��sv�Y�Ak������������{���i������'������}��n3��l�D��Dd���Sv#�xh���1��3��������^L��T.��
�
���{��[#b�I���=�������l�z}��O��H�~<j4��r����P�y8R�S�+4��x�#8H��J���n�~$R�n�6dY��l
'Exk�x��v����KG���
B��u��O'&�/��fI�y�tCB��oP,��J���X`�_j� �oP���P�b�@��h��!�H�opL��
z�q����Y����.<�a����Ph%���S<�l
���K�y�7�6�����$4� �eU,�
xg�@�4"L�Q�;�|���ZD��������S�i)##�����?z�8� }��I~_�������!=��k��,���i��}xt�h�Ek0�^��8;n^�8l�z�5H���i��E���^;BN�2��e��K
���������p��/�>h4����+�(�'0.L-v�8cQ+�!����n�cw��px���qv���9�CU>*(�0��E��U;�\+��`�0���t����~�AN�����3l�'�BaD�o�3C��.'x�����!s�T�+he��x<�*�io|e�<��:3��Y�k���M<�9�0��w))%��0��=-���#U���h9�7��b�<�����<
�2�x�D����F�����/�De���������PG�c��a���n��5_�f�a#3M���* ��� ~e��%��l,��&~4���u���z�7�eXD�-}��xZ�g��_�3Y��(��l8:0�a��0��!�;�ji8'��#��H�|�4"��v�3������xA�d�������)K�o�%w��GVd�A�������c|j53�g�m�d���[��>�	6l����P._���r��^t������EiD�����r��0�<;���W��o��l[.���(�{2����Y�	���"�.��|�\�,9RT�?�����4�\��y��������Q:|r�+�F�pW4�o{&���;�b�q%qB3��"A~5�Q4!�|���I]��Ud�RU�oo��.��]�;�,��t�N9����L��v2_9�*��:gq&��a��"�����V_w�%9B����M*Pl]�J�1c��*1��u+\[g��$���H��IJxO2���t\�w�(�U'�������@�@�g^M�x0������S�-z�h<����5"��1����h�J��[�Ba�]/^��1�o2��8���.��*����,�V~������73S���Bqm�G����m��#�����fYtl%������:=���V���A��������<���s��m��EJ(�I��Z!F��X'�2a�X9�lu��f�Z+�F��s����M��u���~����BU�f�
��W��r�5��%1oIck���ut��wdR�.\�b�O�M��F����Z�*��iE�R�RH�������V���8����3����)��ZM��
[���I�U8d��,�q��y��)�����\x��r�u,��������w��+1�������d��&k�B\����JgH��64��m������p���b�k��-_.i��5�A�2q���+��[N_L��oA.j�#�#-e�'��0�lf��I/�7�kPb����-�5��w3�\�r�(&�����[��4Z !$#Ac!��3���4�"�1S���������j}�{��	F�E��`+��
��W��S�Q����w��z�A��E�$6�BWET_gJVt|2��zo�1��~t��A*+L}l��������i���;'S���WG����%��U�?Z ��Z6�~�:�n(v�mC��s2>e��HT�V������n!mk9�40m��#�W���tV
	k]��7+���������P���yH��J{0�q�p�� �,t���x
�W�--�����A�L}����%�s�\n)/
�ZD���9�5M?�
�$B�L7�tSO��j��@��G��,�B�r�~K�T�d[�MK0�n�x��~���P�Xx������h_'p��C��h7����4�h��n��5���K�N��!��;�9��h��x�-_:�B9� ���uxrr~����y�j����r����B���r�rd�+4|fh4�I�������z��ut��@���He����*
����\���1��7p���F��L�;�����G�0J(��,��3��31�H8�&�}���Q����$"�b�����70����4��R�>����V9�u�kg��e�Q��F��B���;���"�X���z�9�O��+!��%l1+
��@g��������^.:���]g���|�6� N�I�y�����y�r���b�@n0�!IEdw��JG��4��P���k83���]��1���l�>�����]vL$��W���������z�����c�u'��{/V�-	\B=�`\e��a�kR�v�����u1�v[�x��x
3�������`(�0�-��]w8
�S�����T�=�HV:����
�9�������8�.�-�YJ��\��xQ�~�{���OZK�0�����#A�C#��j����4W��kh	J����<+�5B8+��(y��I"=���U��F�$��t&1N9&]��q�0~Hb�G��~��s�����m����H}Oq� �E��a�8!�AR��"y��<D2K>�(�a39��U�M}uq[���?^_6�������],2vS�K�a}f��vms��z��bO����|S����"%��x����8��$�m[//O�Nv�/>��m�#��>
��^U��Dl��B�j���N�A.��qE�Ds�(���p��l�g�%�0��;A�����
��$QT=W1L��O
����N��5%���&^;�`Gd��A���	�#��C_L�'�����i���W��v!���.=���� ����J9�*�!2$���>Y����SCd�F���L0-��:3[�yO����g237�(�!�6������}F��w���X�i����66�������$:��D�9���n�}5+�1l�B��Lk�H�-� �0,uw|Wy�9�b�s�_j����a�K����t�M0�-����q����u?��i��3cy�m�Y�T�RP�v������'���K�B����q��u��L?�����f�`<�z�67*������t{+U�=v��l�f"��n���A2@2�l����1���Sz{�:K�4e�u_��]Xa}�&�m��Fo
Vn�����'a�]~�;�}�,��i|!x����|�t��m�g�org�W������5�:��x6�f*�~W5�����K�������6�!,���[J�(�OU�K�f�h"C2����������1	$��V�j�����@����0,�����^��3�"G�[$����I��{����Z������{����Z,�.M������j
>�2�!�&>���hxQ��-1D����j�
���i
���[�(
�	��Y�U���
E���GV�-����N������e�US�T�_^O�R]�����Ku����R�7��T���K7L*�
N�I]_2����I]�z��I�@T��[�h^z��e����s�#�#x>��:�7rqyz��`��5e��9LQ4���,��BV�_�%)���6����a����F�`+%������"���u����E���������%���^�R��@�����1o<���9<����#w�������� ���1�����X��d����Q����^4^5��1�D��P�:u#�f���0�[99l����n?�����O
��t�K�y�u��:�-,�O�r~PTZ �&�E�
�VD�t���PIX�����J�3��%�t��������?�~�Z��!�q���scq��/�fY����e��7�%/G���X�^Mc,�[X�x��`|dtD��Y�}u��E�G�J�tY����W�0��n���Xn^���T��2g���E=��z�3���
	�q���*)��V��-���I�y��J���F�p�q��y9�>2�P��i���B-�d�MB��0~���5h%�3�����7���[x;��
�n��G��r��m�*�]U�{w$�����*�,`C;��� 8aS����"���L����U�0y���(�F_�cu�K@�W�8��-��^t s�������)�%d��;���}�G}����L�uy30�$#���13#��K.����"�J�<Y+*�L>yO��e� ���N�����c[@�V}�e������s�pxE�9�6'���W*c:�v3-�Q(�.����.��0gWs?Y�_jo��"{b�g
�:���a*�pd��)�-��Y@h�ep��E�A���[�����P��h�4N������EK�8:?�j�xUP�F�2�<� o��TKi�U�!z���+����|s�v�>`���r�!'X�����v���6��g�����9��78���o�%��i������ylCq.�z�U(L2A�>�f������/�����G�>s���=+�����i�?%c�k4������5��N�sV��H��t0�tEs�����O������2|����UN�y�RNM'
h�����&����f��c���l�K�j��z�5���e��I�o��m�s���(����D������#��/$xo������+K�#F�@����M��$7��{�v�������"'��+�/1%n�	F��l)�����TJ���;_
Zr,��q�r��d��L�7�M�2�E�����d������=������������d����<1�qc4�
�G_z�UE��>�(~u�-;48e��I�3��:���X���]���`�e<!M`��q'�l3���Qd��[Eh������t6(��P��>�d�l^a�J8�ayd��9���j���v�=�F>�L);�`J��M����,�����h< �r�q��P��Q��$)$��y��qD�I�Af��U7Vs����-�`�^����|)�(^�c��v�����$���)�
�zo�������k�J�����{>{1,O��)�������b%�L+H���� p��9AqB}ub���?	�z���QK��P� Zs����s'��d��z,ffq(�AJ0T��+W8=?nPK��/���:�~���F�������c���e��8��r�j=��Q��E<�
@�m�6�m
Sa;�3�E',��(���;��C�,�����<���������OYzMa��A=�z@���5���s���������B�/;��#I�N�A�	�N�I������`t�3��BsA������3��+�={���W]QQ��*]j;�i���p��$��5k��h�k��we3
&h�h��~k�g��}�m�h�r`��0+R�4)4��:�]�7r��u�#�1�Vh�!A�&��lp0�1���Y �c
��$�p��!��{m=���@�Z4N~�=��O�\�����<?{��u��b��|>q)<���������8$�(�
;hzB`&�d�I3�B�h�u>�j�.m����J�`6%�H�H��!�]t�q���F����C"�>�oQ{l����+������3��,�%- ��bB��,u���X�6h�{6��8X��\SUF^d6����L�e�<
���p83do�#3H���b������Q<7�Ojm��X$�����&8�;,p*jGH�g�2������tbZQ����J��]�%�3d�\Z����M�t���D^��
���SR^2�*��6��1���$��7����m����X���&e�t�:���j6��c0�����J�

Sm3���l������S���(��ii�V�%��`F;��#Ac����I_QXY,+5��hMo�P� @r��`��+�w�DK#��!��*�7M��������1��d����%�/��!�=�=C�p^���f�}.�i>�l�\w����4�L�q�����}xuu�~��:=�h���O�?1�@�}���,�e!a��,{	�y���e������V��)K��J#�P��G��`�Mp\d���i�%��w��L�
����\�4�-�_Z��e�)�����B�����D������������%���Q�����z���'���0���f��;Z�/�����e<y
�}L��=|#'���Q1�j�����;[B��.�R�+>�����(�0�/�5�}i�=�-����y���X|q%�D�c���
�x�j�R�1C>*�*�p���C�N�x���V�V����k��
1NB	��.�(;@�a��X����]+�����H��gms��7�%�]���L�
�a�3��k��-��$Je���E�����I�H�z��i!#��f.���T��Nba��^p��5���_9�&p�-����4f�b��G�^��a;W���~���Z4��%M�	����~�zdA��x+�6�`�����G��Pp��M[��}��i��K2o��$�%Wt�F]���	���w����'����`h�#���������J���.8�Z�����]Zx�NZ�����87*�P�BY�nz.��O�<���hOS?�z�C�Q�A"z��<}'�jb���vf/�V�����Z8g%�M�q��oW�;����v���Z���o(�����N'9��AS�]����%!���=�B�E4��o-���X��_U�$�
JP�U�-��s��0�t�m�U�P���9T��R����>X�����������Q��g���')��>5Dnh5����u�����"K�����Y���S/b��7����+�q��x�e����r�����Bn,*|���B+`���%Ew�%J�m5G`p0����������.���������A����LG��S}g{��8;��ru2(��������"~ONB���� ���z���'LXb��a(���.�18��N�B9�7�e��b@K3&H�xc(�����^��������-J�����/�13F�����|n��t�<8t�Ld�a���%��n�D8�$w#@�
�$�8(����f���l�L}��t����7 GE>Kh68�������=��M��1Ayg��-��0zg`'�����4:�������*�b"O��tz���tFI7�
�������Cw0������t
���wP����~�
��m�3�' �__��L��}��M��������G7�S��zwE&���h=7�����M�Y��a�wp"�h�y����G��a�[8���(g��`��k�hO\��4:p8���UN��-a�f�q�f/�m3��q~	��%\#�d��r�0����0�oI9uYR c��*������^�~Y�Y�!��:�B�#�7`j��t�e4��W�pD%�(QHf8x����]N{�����������,=�����w��,M�����S��:Bb&�5%%C�e��T���NoCL�!M���'�r
 ��i����Yrs����z�rw�� �Q�C�E�v���9�+��������[�	��$��� y��O��+���G���g^<5�� �6&��s8�.rdYz$�bl�fl>����W
R���WrIQ��z1���������p��`@y�T&����w��*�=If���-���Xn����M���a�#g'o����0�*��O��|�I��i5� E%�#~��� �,"I$)��$�x:^L�w�V�d:��6&�,���y99I�R��d��-6��A�%���T�8��5�T�4�����lap��������~�o�ya�W�:�+�*���k�(`�3�#��*�'�D{����[pPZm�$�0��K��'�����`03�<�mO�m
��.<��'���b!��e�M�#�](�Y\��O�H�w,��en�8���+����
3�U��� ��V�� �@�h��h�������@�6���b��bwa
=�-Q^Pd!�P��5��1
���2]|�.��j���������]�k�U��4���J>1�X&�	���������+������r'���������U����j�ut:j�Ns���C���f_��Z9�����RO��/��(����^���M&��<]��S`���4�b�v�[�M�Umj��@\�=l-������uR�0��3;v��Zbr�=������F���d��ZV��^h]+�{��c4d1�e�^�&
Y���}���'�����
���J��!��G�A[���^�kz�`�zL)	�-Z3'��o��g���m�� ��o��6�_BM��s��y��?2&��g�-H�)xk��@ceJ�In��������+[���9\���S���
2��FS���^��W_�U�1�#��I��\��"�(���1��%>�,�V��n�����
Vu�5���V9��1��|����*����=�����x�� ����X���(�Mb�T)_��}\C �R7UCz��@ r9����.2�5e������l�����H.�5@����%m(g�d������C�AX
D�����	��n���]��GX��?���k"p��\+i��g�1�G�-n�����?�oK�U�8���E�Cm�Kw����7tU=��D	�88�X�9==�� ���c�yYJ���#0��q*��PNW�K��

)4Kdx����M������%�L$wd�bq@�!�����X��&���-BM�A<m�l�O�%�O�����������o��]����^�1�]��k(~�e{�����6����Z��M8f���u��s����7W{�>�nQ�U�Ke
>�4kd)*~���(*�6���Ev	J@�t�Ml��b�T�HZ����7C���I<<���	�j��~�q<��
b�	�St�������gd�49���'��Hb�[?�%��m7�T3,�� �p�����x��w�]�CIBP�����b����[��b���45Yc�!�[�k�0��8GP�@�-xb��������L��\���#��XUX8��L2�gJ����i�R:����p����h�bsn����x�.g���E0��&Y7������u��D��E��^:T��@�c6������>�b���!xH�,����>����h4�q�C�$F��Psi�p��e�N��m�"�S���a.�H�r�d�f��B3��RV��b�qM;����:���=��3�a��O�|��%P�o���c/ ��K�2������t+"MV���NLR�f���x����U�����J�}74z!���G��!i��s�P�F�#����h2��z�h����t
G���-��F�'r~]'�+���8uN.2KR�����
Pg��M�m���0Fp��F�fl� �"�����mZMX�F�
�O?����'��G��?�yH3B������z'�����c2�@T�h$�O��?���'�2E�]������
$��xk��q2�js:r�����H2��/�e�`�~�c?�7���-iT��q��	�O�A���Z�s���'����������XTW��~P�-��lE�����]��b������g�������i���9��?ot�X]��0���x��$�����(_�#*C��KVJ���r��Z&��\q^�j0�������X���M�>8���7�<��k���kpwhx��G��k��}9�4�O�8s~�L��/�3\.g��?�� �w��E���g�Z�`@q�bRn�����E�$*�>Q$K���U�����S>�����?���%m��� ;q�(�����H������
�!8L��^�OJ�LE	�to�a@A	jdnkKRK���KG��1F�Q3`t�6b&>��i-!��KF����!��f(`�N�2�{h�����w���$���`V��=a��#8i���3������R�&6�� %�K�
��+�)Wt9��^, �_�G�rU���a�)�	x�%2Jx��1�M�������������T>��D��n��S�p��*�	7��
�}'��x~Q��h������P��.���`)���y��=`SZ���|r�C�����@��{o���=mA� ���2\��J^3<���tK:H�G~5�Z�����-2wkx�IH~�#~��0�$]��s���
�R�
����%�gd+�@��.��Y�+�A�~�PW&���c�n��0Hn�������g6�:?1_*�{��2i�;m�SbBdi��1�`�<��c+HVl#�y����o�����;�iK�q��6's�n����=
?��<]�B��n�T�m2��](��Bn�P�$��5��Q0Nn�0�!��W
�1'���V�c�#��-��c��!:
��ZFC�!M����&���&(������������"e��;�mnj�:�[��A�g��/����'<�Q<C����
�sU�g	G�����8�60	�b�&>;��I IYBp"��Q��ni�o���y����!=pB���`&����<brL0:�C�G��$yZ�A|���jb)�Y4��������Qy|�s���)�b-�P��m�<�d9�[�e�q�^1A�%@����X7��Y���3��:��-Y�������a5
��E���$@v�$����C�'��i�Y�N�r�)���	v�Q�4?�9<���cM� ��zy�F�s�#�`?�;J�?:�����8���?��p��y��w������2����urX/�������e�D|Q^M�y5;n�!_�"���9s���*�cl ���\!������p�`�g�i�X�K�@���<�� LzX��-���'4�����X�HC���CYc���PW�E�e�]��?�
7B6�t�����������0W�����_g�W%��NC����r7+e�>@*�S��Z(����^�ZHJ�A�4���AcT
�"����k���S�'����P�T����x�������.�{\�\=�H(�G�;:�GN��/��<���0��u�Q�l�D�W�I5!h�������\{M�@10sn}�TEU���]qj)���\B6?Y�*��D�vi�r��f�dE���9�k>�5!ww������fI�����:rd@����U�D�5(g��"3�H&Bs�Xt����@
0�89����N��s/c7^�~UF<��E<"�	�,���lv%�e��������g�;s�~�t�4F=�-�S4"77N_Fn�p�_F����i�b.�?OH��2��31l�W�����8��8|����jj��{9�{����>��[�[�#_����rL
1=@c=�OL�Y6���9l�1S�26��5�0��U.S(���b]^�����lSW'p�z�|��['w����_��\v�0t'9f��i��^��)�I�g3t�P/G���k�+�e�t��W�1sPU�b�T�V��~� 	���#�����<�N�|D6�s�8�kB[���!�n��6��y^�ow�x���V���!����q^��k�H��\��b���v�F�m���6�|&���x�-��� �p�6�>��,������^I����.�)�K��R��d���y�v��K�9P�1�gT��Q��� S~/8$�d���F��H����O�� ����Y�����E&Ej���\&����.d����P���r�T�%S�P%�g����I�A�L��D�!7Q*�'�&o��zs�$�N�,37nX2�[<���I�F�b;dx
t�����Ij���3�3F�?�u!��o������ty'�FHzm��1�e{��v��g��8Og7�9� �v8�5��8������8^�#���rQi���n<�a��������FH��m�,!�0���g�RK�<0�V����yo�8��5������#��
��O/��z�~nH"����W�JA��4�Zwf���C;���hn��BoL�7���r�����f�2�*��bIg��j������maf?�a��h
(��J��WYNK�����m�sqn�@�#�1������PHa����$�0Q"�]���S��%�jH+%��0�][��](�s���!V�l�6�U'kR�:�����G%�p8��U�#�:�K�M��L%�TD��C��c���}�G�����T*q^'�%�OD�����J���,����j
�p6B�i��#&C@'���Lp����d�R��y���@KO�����m�Z<0nU����r�E�����VF:��b����Mvo�YK���~z5��\�
�F� q��������o��2L�A��Hpzg5��(�OZA��|}RV����z�{i
���k�!���Y�b�.*�����D�hK
�
5����q��A�s7�9��nC���b��^�a&�QZu/��	����W&��y�l*��!n��T�cI�Z���f�Z���������Q�q�>n�<|}r�>)���sJaT������,a������b�R�����W�'~�e�y���x�n5_]5.O
ZAK 8���>.��#�<<i�[�G?4����t�u~B�3�lI����m��%Q���}��y3�������p_H�H����{~M� ,	3��V�D�pxU�������"���7~�B���OG$�u�<�r�}�8�����P����U�xz�N����R�M���3<JWy�������W���F*+C���,���@��^I���%{�5����Fa�O�9j��������t_4Y*���A�KHa��L�}���
��
	�����hqT�I�Z}2b��y�8��=~(�':�?��$����nF�� q:D��"&=�}(�aV���D^���z)�����o��D�7��cH�����42�/���}1{�u8����U��9����JO����"}n��)kJ�a6da��d��N�e]��&��R::�������o��G��ov��~3o-���a��U���&����������%?T5�����gV��j�)9Y������I��S��gC���\]�b�S�	�G=��O�%?ij�#�w�i9�I������3
��#�if�ls��a�Y��'��0��l������-��1���GC�go}A�U������s��}.F:N�����A���xi�Y���,�)4)��`��QW����������mt��S�;���<��6��cN����+�%�p� LL����3S@�9�
N�m�g`kV������
�)������]H����<���	�9���i�<8�Cl�9����ev�-}s"|l��qx|����>�����#�,{�>��c����W\���uuz�V
���������b�D~����v�����6pa���������O��7��6���:u�Tm�z�+�@�8�^dH��]� o�hFW��I��d���������&h.�	��8���������LA�mR'H���Q�ui�c�'m��������f��z���W&�j�I��p��F~�	%��D(@~t�$��h�tbYw���|�b�l���Nc]�S$����y���@"	p�|ID�����a*);2n�4�jW�z�!C����E"���P&�")�C6L!���1=5����"H��<����
���z����Q�{�,�S�b0KN��$M�g[��O����/[�h�?����P~x+*�ry�L�
S��fdC ��8I��:��^U�*7������cR�y�ZB�H�]m���#	�dD���@��.y`C[>�x
K������s����������������t��0�9g�n;[L?Z�[LGD�i���.���=�$)u�;P(�����w�2������I�\6��s6s��X�2�Fc�����"�	���}fVJ�V�]C����d�\)2�-�V������
j[v�j�v�@����}�w���dHSQ|D����h������6Jb&�nK�,�%e���7��*��xR���D������P��T<F�J�[���F��W�Ax��<^�������z6����ZK�z��W�G��������y}H��%7�1�e��w�.�av�L�����XW���A�q
*��}����{o���:�����>�6���K[������!x/�z9���A	��D0(L���Pb���^B��H�C��q�*��1C;���xr4�z�TeL�=Kw�#J���o�������(�������
�^=.eh0&��$�����kC�iv����?v���W�!#5���
��CjR��^'UF��'$(�%6_��T2�����`n-�!����[V+`
,��U����p`U*pi�W���q�h�����hf���mo����;�����(S�?x�����������j{k�������7��O�7[+S���T
��25�����X���6:.�����<�#�j�L*{��XK/}j�<�Z�����h��Z��&Z���^�>*�PS^gG���p��r�I���;������J=��0�Z�M�A�U��F,��Pd$�}������w�
%��G�\�#��@�(�(d�>��p$�G�3�������&Ws�0���p��H��c��+��UswH={=WjD.BfF���~<jkJ��L�DZ[G8�eA
[H�����D�zk��D��
�'��=mzv���G*scc��^�oBL�jV��!����6z�Y�
p�����A��?~YH��N��
�������/+$�>�o�1P�����D��@�\�7�
x|y~�Z�_��.�h��ZK^�?fa�i<��J��+-����]��������V���f������<�@�,��uW��f�@����5�Q`�+0�c7�����1�s�WK/�(wNt���B�����M�PB�����o�@�v�����tD���v3/<�s\�����T
��G����R|�����i�]�e_����nZEN�*`����N���y�+��jm�l������@���dC��eT�����_,K�l
-�������O6bH������X?^�� ���.�m��I��</��T�����4{S�I	�I�dKK��������i������qFY`�����\��0�������[��mO��Y���qZ/w������P�N��3rw���J�|�Gu���A>�N�a'�q[8T��@��BZ!F��,8�G��i*�qE���*�4��+�����	�/�2�u0,r�I���NO��T\�J�Q�#��;/b���+)�����(�7��a��{2����lK���DH[���k^��
�����k+H%_\3)������m���[Z�xu�~�j\�WU�?Z�V��B*3��8��Ag��z�2�����B� ���A$�ItH+�	��6 .���
�o1m��g69d~���X��w4X��/��&-�������RI�{?�fl[e����;��X���������	GS��1�=��[���]�{�m�##��;�z�b%�i��CU��,�����1���$�A�oU��C�����.�ia���9�Bz"+��V�E2��h�������|�r2sx�C��vZ�G�"<@�����"����i�����<<U��e��p�N��!�X��?��J�UZ����z��G�HW�8o{A�Y ��If#������-<����j�dv�`^�8(	P���e��XV���8�G�@����og+Y]�a�<����t��X��76��(��	���o����c����p'�dA)��3��h)�L��Q��;mESqO�y�R��7��c�r;�^�I�'2�Z�Z2���F��$�Ht�m��p�,`ZN-xU�u�CrV)M�Yv���j�(�2������L�;
@����>X�A�9����`�����O��6E�p�6�r��
t8�N8k����)��x�`f�AN�@5��WVl����:n
��(������oO�H��;���5|@�����cO ���m��'��r"����@�r�S�
�-�������q�!��M!��>{}Z���J�Ir��j.-��-gadm1�1CJ��`QS�.���g>�O{�9^`���_��w�)@^��P��r&;f6�1��"f������{Q�������~#�t7��9h�A�
���uUg�r�K�������k��p'���]���������=.���-����Z���W��j�>�
��+eH
��H�q��@O�����WKb��������������k�U��(� ��nl �6/g+���&��:�S���?�[?����h=t�0A_�h����
�y�!0P�U���
/P�,(�RRI/��N��f�Z�*]�M�^�w����fCd]�1���B(g�Q�it����I0�p����NNW0�F�"C���_.��?�2����?oR�����|��n������O��7X�[y��$U��V��-�nLbKhT��?�X�p���F������EZ���_:c���6���>�\H�I��p#w��(C���<mn�w���V����^}��<�4��K9���W�����_V�F����l,�7:��a��m�nV��N(�Da	����2.�l�������t��vIpi)��e,��m�K}g��X�o�
{,��z�p�ob���.D���I�����fD� lA�6A����+E��
c���n����V��������.�d�+�VT��z��`����EK�����[�u[� ��]F���/�<��@�0ix��������\��
P��W2dF����f}�E�?��'4��J�A7�Od��������N����d ���^os��x�V��v���|���X1	�-����������..jfY0R���eu�9/1���M�����:�t4=<�N��y���^��.�X������������U4�v���%3�v��f�T�A��
�%s����nlu:8�����^��dW�� m����[�����A�����^z>�bl�k;��0����[��*a\!]x��3���m��0)*�9�K��
���ZA���9M�}���ON�YG�����s��%�Bmo���O���q��� ���V�P_n)��4O���e�z��e+Vdy���q��EJR�w�7���Tu �����H���V���vv��Z�����W��"���d��Bl�=�i>��B�H<��]�#��fJ;��1�;|ef.��o%���re!����#��i��X{����>�g��S=v�e]�t��(���vv7�yQ8Pg�r	��
��z�`�b�x�6Ca�]�8�64��\�G�H�X'��.��s	�'�r�(�kh9X�D��R�s|h���X�1�[��Z����Nf��.D��p<C���<��R'���6B?kJ�����u���{�,�F/dCtk�V��*C�S�)��z�N3�V0�i&�^���T�p���kX?���i|���p��v�o�.=��{�xH��A�8-�(A<%!	3N-��A��?���[Z3n�[���o�����=���5=:h��ESTO`�T��@�:ZM�B�wa�[Lz����Q�3a�+�����f����S��=�`:�����G�1�u�6�v�D�ww�[i�~.��K�?�ha����kJ���y����6�:����e2������6�(�G)�]�k+�X���x(y� ['�E����,�nk��m<�=���{������I\�d���fxt��=����b=R4B2��W��7:s+8�/�����|�`{��}���r����r���f�q�E4A;�.��������@]T���B@�}�NB�V�'IC�pn��[��pA�$~f*1Yq�
�s���l	CSC��W���`1+�D��18��>���Sl�(
��_��gx������'�|;;DT��wa���--i�;��p������&��}xuu�~��:=�h�����ZsE���$5*��6�MG(,l�0����#���{t�r�i�+�������rZ�'#������:w�T��Ln�l���~O;�r�cV��k�A����nw�ag�����A�A|[�r��.��p�K������qc�l�T���
'F>I���;!_`	#�]S���	7�l6���~���MFe����R[e����D���k�]��`�,��s>����/�57�0�a����,�g���Uy�3]�Y�Ux�>�%��xk3_���=�'�==L��4�y����TNa�rK��
f�q��w�������k�o�
P�-�w�j:���'J�+�������2{�]��e\>���u��15$��Y.���������*������$o�U��<a���`��V��k��T�����x?������51jN��	P�.�5��+���	��\��]��R��������"C���@L�9�P�:�V3p�����MNofk��Cy�u[�����Ij~��!��N���8/�R�����h6T��%jZN�[a���1vu������UY�9FP�@<�����}�E:�g�k<p������������3y^I�=?~�5x�|F�����	RMh��%�s�2
���w�������pw�`x����N"�
-����e�3,���J�e���_S�E��Y��_��^�f����g��w���$��#J�u�9Ij��/R�EE��%��"=�^j"����R��Z�����*y�h�������C�:|���4�> �;�
�y��L����w8s�o��)�D�e��8�UJS���r�{�6�-t�������w�>��z%���Y���������p�����j��`����wU�4PtI�!4�����2{%��(�Lr�����"����2}m;%�Q��d����}���s�Z'%~��t2�N�YC�-u��6Lj�4G��Q��V�J�1)Q_��2'���
�t�D���Q��e�u�����j�~:;������u��]��	d�=B+����}4vbD*2K<{����dR3�u���9C�B;U�YU��?�_��8;��If[�5�UR����������Sn���k����E��_5�^��|��l0��O�p6��� ���������z����g
Dc`�x��L5�G=6`���1�F��n�F6P�{�����fgv�v�����1�)n�n��2������I�����%�3��yp�*JAM����)��M�@����5��o)4|Q��>�oY7ZG�����;/�
;WI��%�{�����e�����y���/;.gX��DM������ZYQ+�omW�v��[[�U��?���;����7(����������1��*k�h��'�A�����=zC�����:�GTp<�O1�^[B��?�5%J&��d�l�f�A[A4UL>�6p�E~sxb���;mvQ���Eu�]��Y��=Z��*��Y?9i���dK������Z�����
W`m�p�*�V[�[{�r���t�1�Sw?m�n�lf�N/�C0�5MP���9��3�����S������\^w��\�q���(���������S�#����������j��hI��#�����f�yA�7�Q�\�)-�aD��=Ty��e|��#�PJ�Q�#w�Wa�f�L�%Oy
'j�G���u�t���[]��ye>�^�CT�G�^���R�6D��@����l��y����C�N�.2�xejF�"����r����SFX���A4���k}u��B�_UR�QE���?4���/�m���]���<�m�/���<��qz�>=<�<�{`����12k{��9�����;�B�%����A���1����������;[����9��,��"������Z��-�7�1A�"ISJ�v������l:	������0W�V��Fs��Q���F�%E	��X�r��G:�;���M�������8�
����D!�$��]��YQ�z�/a�X�����1a#����2U�T�4��tQ4��s*��Eb�0�to�7))���%H�����_S�3��!���������0%shH�O/^�7b�q�Lc���'�|����� 8��D�;�i<��=�\S��
\a�������h�7���m��9l��:�l�J�'���X��ng{���N��om�9Zd*f��y�e�L��.�p��q�B���g��Li��B��qI�R��K����5��,~�)��]O�}s�����V�/��[�������el��e����4���J��'l���/W�F'������Ib>��������V���p��2o��wH�sV��'\����Z��� ���/����e���pf4���������
�����'ZKL����]������P"�^O��?N��A����w� S��W�x66�-'��EA�����w�`�A����0�9�IB���U����tP�I~RR2/���6:�9��m�����%��o��x~�\�P�*����m��3�jg��v���?/@	����z�����j������n1}/ij!����GG�	 
a����Ch��"d�sgs�I��yU����z`0�������mq��/"0�v��
����vp{T���1�IU;F�/f�<���3�h�~T�����4������b�0��h�W&8�,��RZQl��������N��)G���sd�l�#�����hy��0(:?�9��D�7�#�$��sD��9���j�u���,XH`=N�1�b�{����9-
,��@����LN�9������B�O������5��KKTJ���x�����%38[�Gg, 9�*��N'l����Gx�
h����@a_��eIt^:-1>�Ic�\�&g���%Jd�7x��h��.��:��%w����tU��)JpV���C��SNo�13�G0�6��]e��,�w��v�Qz���s^�fOi�He2�{~����q�����=�zw'�1�������m��u��P�O�8�;��N�^��9�v�������+��B���v����I����9S�_]1�.&�#�o:;���'����r��"��.�?]�����q��[(�,�����on���ww6sb��o�����e�,�c� �B�ZA��!@�k���+M������'D��W�Y�NXl�/�=�	�������:k�SV�h��5��/x���vzhb�����`�q�gh� gV:�=����bv��������>g�����DJg�t���a��L�;�0$Z��44s��(y_��Ij)���u���q�$�'�0�b8\��$������C�+�&���91�H�(���ov���T��
���u4���K4#�
���#��<�i�Jt3<A������{zD������a=�]P�B3�/1���p?���H�n����*�������N����bX��\d�&���/�2y�������� :�z4BC#�����YQ6)i[/H��j��W?@��-8;�R��ON����W.���w@�8��F�KJ?_^���������TQ{�[���5a�R�#�S��7��h|y=5�T�9�	M�:�z�����e��k����>�{]=�L.�KY������vFP3RN^��q;[�}�C���0���������<����a����#���Y��<�+i�����?g�;������<�j\����y�+W�Sh���"��j ����0����B��@� �����$���,)������OK�xuqy~��h����kl4"

k]��2��5^'�)�����D�����h�<i����l ��#r���6.�g%���	�[����Aqa	��H�cK�l����_���e��d��6��������$sI)�8�KN�6�KJK���D�Q:cw�����g.I|�8��1n������)1�D_3�������I�������3r�5�"�|�:���N�7�X���m	;]���!O�2)H�S�Im���2�����l�n<Ms���/9��SQ�n�g]gfL��M�R�8f���`n�n��1�������^�je�:Y�x`�TM��i��6���=��z\�������z���=����@@�n� ��v�%��w��7�$j������!�n#}�����?u��:����"�-��KP7p
V��"x �;W]�8�/9�_c�����w�l�|�m���@e����mq]�h�"xD&�e�gA4rC�dW.�\��h��`.��46t���G��4���iFp���vO��g+���=�/�z�Pl�x(������
lj���c��>y���{��{�{��^go�-�(���9qRh�	���#�8Gt����t.��
�A��?�wQKj��\Z
7�S�$��w5���p��;7��'��	[�	�M0��� �r}5Z��y��eq������^���	/�J�h�k�N�h4�3T�e�zkl`�=;�gz�tv6���Zm���U��1I�k���?��	�����S�&�:����������6j�eMb�T�����n�vM�Z��^�8n^�0�f8A����(������Y���]��:�V���������C���4���mo_H<@���k�W�b;h��!,o`�V0x8G�[Q���i��	�>#������X�`#�}�Vg�!2mr���x��X�{���������Z�����C��\\�m!��e�4�1R�5��;��B���ej�sNr�qX$
!�p��	��p{=x66�2��i���=W���mdR�������e��j���G�p$���5Ix�?��u3���o������0���?�!b�C}�PU�P����S�b�#�sg��a�#��FD�6�f�,4��v�r�������rE��}
�T��{�
E��\����D�%�j}�Z��B51��?���i�}\�.���T��I���j�v(����t,�jcs��VHm�q��0'�U���JF�M�[�����r�y'J��t�Ul�m2R^�f+!����f�<�����EnA���Y�
��`>i���QoL�������H)!S�w;���������n���vR���$k�����O�?�'���j5NGW*����2�%5x�q��iQ�-���J
04Y���wz�����s�����~�j�����J*��d�1��NBJSTU�T�U%~ �M�Da�i;��������������v��������0�����
+��m+l;�q����*o�G�d�{7����v�i���*����rr�T�*�D���F�1�i��<�����#�y��Q�}���:�=���X��:�}�`�\I�4^^��:o�z�
�P�=D�C8�ML�LH�OD�&�R_U�g�R�%/�!OI	�&�K�"����R�w5�����{O��
% ������O�\�m��%�2�3��b����`g��R���7��b��^��Q>��2�8��lL��69��H��^�d�����9�?����,=IB�Q&�oQ��03s%f:o8R����~p�w�����v����d1�y=���M���Q0�`��(�1���C�2%�� �'��`�v��i�xuy�n���@�����{u6n�2*���l�?*�	j����
%(d��
����_8Y�Xk2T�����l�����i�k��B["<��w��%�q�r�6Di�5��~�,�d�)o���.�=�������=���zi	�Bc����v����sF������L�	O�������a�xC�88�6m4N�K��A4����b��5�TP ��<,����& 6�6�_K�?	1����__gj}����`r=#���A��FN767����uJ�����
/F�w��;��{`knW.�>~,G�� ��E��{R��
.&j�bs�qL#�����T<�:����;�S���Bx�o�TS�+v�%�
/�
�����}����bm|Qyh�<D.3Uz�9
��R!>�1�2>N9��J';8�Mzt����}x������{���=�7�4�h��Z3������'����Q9�r����_�4���##�.\���:m�k�
��]���J>X�
�Us�LW?��>l�?d��{����u���|5��H\�K����]�,�>��|�Z[�/��b �=Lq��e���	�[�,�~USU���gfT�� �_`�)
��
V�4L�iW�M���4��W�Q,���X�*��`���O��y]���ce�f��Tvz~g�X�W5�%��C���������E���yaF�Z����3 ��a��u:��h�������\k��d6�B��F[+�yO��@l����R���Xa]"[��c`C�!���z�����<��Z��O�33D0�I>Y�[�q>+�f�i��;=�U�1�x�4s�����A~`��>PQz�`��C0���\��J�-r�2,,A�����4^U��Z�\��������k�a�t2%^\�]����y�=bW��J5���
I.<�v7�����)@y3_l^�[=W[��l���1� ;�+h��ZV>w�H������/3o��y��[����*,����Oo�6������]g�8����%������`Kom~X������)w���0���I%5~�s����2���A���Y*m��^�m/���S>��"����n���}R��2d�C_e��
���zhO������!)�LS�H���>8����1�fj;���`�B��2O�m4�)�,�df�N1�Xb�8�6�#b��&��:a�@hc/��:����M	64	9Xc���xI�3��I�{0���9GT
����m������$�c��&��%��h���[�yRk�7������r��)/l���x��������Q��y<�'m���|!��#'>�'Fe;*�o�~&0����S�"�������H���=��U$��M�"�D~>������[���������w�	&�6&Xs���h<������f0�:n��	�q�~N��_Xi�:1_#���_E��O�����Ise���n�*Fp��o����a�\�z ���m�D_ZL�wQ�w;cM��N�e���<����.���6?��9��y��9�����Z9�<��=�W� h�I�JRP����p��{�������y;�x�S�9#M�lI���	�� uGO��h`��r����������h�\tO��_��~��+�^��b�����a�>V�N�������'��3'J9�%��F��1�YL�*�x���;l"�3�+��o�u������a�zh������j�x�8k!�e�-f�8)���'�431��6M����m
�������+���MJ��RG�����Z�(5��Z����6�`�?�B?���������j��������:D�����g�OU�a��a�t�O�0�Z
	�>��L�s��<��^��E��D��AK�E%`�V���zV�h���u[���Mj,�0�AA�y����k�\P�]=,��z��n��c�u�9-���)��"��\tjz�-Jy�0�S����l�9�7����0z/�_�J��G�ev/2�,!�0���b�>p���NH�=w���t������-�SW���wyx��!��^S4��~��]m�154Zb'���7l���&���{�[M��AfT����B�tZ���H� QR�N�����i�,��P�Zx�������M�D��}�K1���O�W�pGU���r����&�(�|�96fU�p��+u`9�?[U���!'��l�C|RUu�pnSI�p�P�-�p��4��,�:��������q��\UU��3X�e%X��������z
K��LM������~I��63�v&LU�M�H��M(S���t]n��Q�nX�Eu�q??����������4�_y��i{�k��i{���i{g��w���R3�C��nmW�p���>�&r0=��q�0���(���qQ���j�AQH�0H��G��Y��q�B�d��y����9�z�\�>�Yo>�����h�
��y��<S��6	p�d�x�f����V-$��6���l�Z���z���Y�0�7n��������C���$�3�������~����w���_dp�y�������.4����iy}fn�y:��.�G|!\�M0����{^K%K�F����H~��@XpR|��M����7��|�����~/��V���V��]����l�0�:�����D�hd�WZR/G�����X��n�|$uK�#+Qu�^>� -��w������1�����)����4J�o��+
�,���\�1���5�m�����v����-V��oht�4�KL��h�Pg*�o+����7��t�%���_�x��r�yK�����";��$+�����:4�T	"�U�~� �K�p�r���a9���K�p����XF���n3�=���^�]!�J/���r����O}�����l�G��?��m�Q�g��c!j�v�Q��
�@OA%;?=R(���<<�(6�k���67���c�u�h]=������`T��R��=\%�&��J?�7�0�W��N���8��@�~��r��hA��U���mmV���m��m����;�xv��xv>c<;��_�<���fu��'?:�h��9X�����D�g�W�+�FY�����i?�W���R�����[�<n���oF�M<IB,0��Q�[�
����N:�����R��������BB�U\�����z�����%Q�!����S!�_7.���T�*��[E9����I�X�,��6J�eY�\���CM��VIq�����EkX�`�|l��~���M�V�J�t�z�?��Eu��������K�_�?��
O�����w��|����.�X\�}���)	Q��*�F?h��F;U����v�|J�**?���qd�l�/u��u���py�1|J=��5�=�~��7��#�{RUxN����2}�->��/l�t����:[~y��!@(w�]�la�� �@�f_�����I��8d��5l
�t�1lHs����-5�UwV��J��_�
�M����2>�o�������r������jX�g4��=�|���/3@���~��V<������Gf����
�J��d
�����8/�n<��G�Z��\���b&�/����~ouk�X��b��x�����	h��`�J�
�r��k��TX1?��Y���TEY�����Ck���V���_�
�M����I0�gtj�U&r�N�<�v�����:-����
j� ����x�a��6�V��a�1B���,�����[~��6��-�m��[nj�m�~���e+��������NuO����#h����f�p�.��#v�;@!S|��<���.g#�!f�pj�{��iJ��B�����k�������0������1��NU��[�����mm��Y�����6��M|��
x��J�n<��4�%���4���yrn���������)~HQ��
n;3��������/9P3��Ww��x�)�Q��0k�l�.��4�rj����A��`]�j�I�4gR���j�����)2;��ZU�����-�+�j��T�\�Y{��'s^�0��AN��J�9�k	��p�.v��e���Y��w�����W�s�6�&���"
���SrOmQ
�z��(i1O������U�8+���+w^Y�i%pz�g�F���mw��
`aeG����_|�W���CeQ�3�f6Q����K�M�BXl�������4Yq�[)=z����/����hmY�^=��x��9��`����9���,\������i��n����$�_
��`�W`;���^�
��]�������V��MJ0y$\������'[F!��l�I4c�j�b�*��������d���3_Y�������U�Z�u;�j���c5!��a������^�|u�T���h�2�F�Ti�9��:W�&j(���S�sTb��Q3
 ������6
���_��_K?�h�d��<�G!?��c4n��8v�h����7�%%�C������3��M�-�z�m�y�n�3�Z���?��}N���,Jj����2��5v��[Dc'�����B`x|_JN�W��4��<��0��������z����BL�cv���;�]3���P���4��Ct�U:,Rn�v(]��X+�v����g��r��O�wQCA;]p���l�=u;�57�lt��S��" ��S��,s �\~�>d�����^���
4�Z,(���[��?=T�Na��@�-��*Szw�P�����7��T��R-��T�)�OH���n���X>_�_�+U/"Av��"�������:�L��Wvt0���j}]-o��k;���?a���:�
#191Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Petr Jelinek (#187)
Re: Logical Replication WIP

On 1/15/17 2:28 PM, Petr Jelinek wrote:

Well the preinstalled information_schema is excluded by the
FirstNormalObjectId filter as it's created by initdb. If user drops and
recreates it that means it was created as user object.

My opinion is that FOR ALL TABLES should replicate all user tables (ie,
anything that has Oid >= FirstNormalObjectId), if those are added to
information_schema that's up to user. We also replicate user created
tables in pg_catalog even if it's system catalog so I don't see why
information_schema should be filtered on schema level.

Fair enough.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#192Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Petr Jelinek (#188)
Re: Logical Replication WIP

On 1/15/17 5:20 PM, Petr Jelinek wrote:

Well, it's 4 because max_worker_processes is 8, I think default
max_worker_processes should be higher than
max_logical_replication_workers so that's why I picked 4. If we are okay
wit bumping the max_worker_processes a bit, I am all for increasing
max_logical_replication_workers as well.

The quick setup mentions 10 mainly for consistency with slots and wal
senders (those IMHO should also not be 0 by default at this point...).

Those defaults have now been changed, so the "Quick setup" section could
potentially be simplified a bit.

I did this somewhat differently, with struct that defines options and
has different union members for physical and logical replication. What
do you think of that?

Looks good.

Not sure about
worker_internal.h. Maybe rename apply.c to worker.c?

Hmm I did that, seems reasonably okay. Original patch in fact had both
worker.c and apply.c and I eventually moved the worker.c functions to
either apply.c or launcher.c.

I'm not too worried about this.

Yes, that will need some discussion about corner case behaviour. For
example, have partitioned table 'foo' which is in publication, then you
have table 'bar' which is not in publication, you attach it to the
partitioned table 'foo', should it automatically be added to
publication? Then you detach it, should it then be removed from publication?
What if 'bar' was in publication before it was attached/detached to/from
'foo'? What if 'foo' wasn't in publication but 'bar' was? Should we
allow ONLY syntax for partitioned table when they are being added and
removed?

Let's think about that in a separate thread.

reread_subscription() complains if the subscription name was changed.
I don't know why that is a problem.

Because we don't have ALTER SUBSCRIPTION RENAME currently. Maybe should
be Assert?

Is there anything stopping anyone from implementing it?

I'm happy with these patches now.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#193Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Petr Jelinek (#185)
Re: Logical Replication WIP

On 1/15/17 1:48 PM, Petr Jelinek wrote:

It's meant to decouple the synchronous commit setting for logical
replication workers from the one set for normal clients. Now that we
have owners for subscription and subscription runs as that owner, maybe
we could do that via ALTER USER.

I was thinking about that as well.

However I think the apply should by
default run with sync commit turned off as the performance benefits are
important there given that there is one worker that has to replicate in
serialized manner and the success of replication is not confirmed by
responding to COMMIT but by reporting LSNs of various replication stages.

Hmm, I don't think we should ship with an "unsafe" default. Do we have
any measurements of the performance impact?

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#194Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Peter Eisentraut (#192)
Re: Logical Replication WIP

On 17/01/17 17:09, Peter Eisentraut wrote:

Yes, that will need some discussion about corner case behaviour. For
example, have partitioned table 'foo' which is in publication, then you
have table 'bar' which is not in publication, you attach it to the
partitioned table 'foo', should it automatically be added to
publication? Then you detach it, should it then be removed from publication?
What if 'bar' was in publication before it was attached/detached to/from
'foo'? What if 'foo' wasn't in publication but 'bar' was? Should we
allow ONLY syntax for partitioned table when they are being added and
removed?

Let's think about that in a separate thread.

Agreed.

reread_subscription() complains if the subscription name was changed.
I don't know why that is a problem.

Because we don't have ALTER SUBSCRIPTION RENAME currently. Maybe should
be Assert?

Is there anything stopping anyone from implementing it?

No, just didn't seem priority for the functionality right now.

--
Petr Jelinek 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

#195Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Peter Eisentraut (#193)
Re: Logical Replication WIP

On 17/01/17 17:11, Peter Eisentraut wrote:

On 1/15/17 1:48 PM, Petr Jelinek wrote:

It's meant to decouple the synchronous commit setting for logical
replication workers from the one set for normal clients. Now that we
have owners for subscription and subscription runs as that owner, maybe
we could do that via ALTER USER.

I was thinking about that as well.

However I think the apply should by
default run with sync commit turned off as the performance benefits are
important there given that there is one worker that has to replicate in
serialized manner and the success of replication is not confirmed by
responding to COMMIT but by reporting LSNs of various replication stages.

Hmm, I don't think we should ship with an "unsafe" default. Do we have
any measurements of the performance impact?

I will have to do some for the patch specifically, I only have ones for
pglogical/bdr where it's quite significant.

The default is not unsafe really, we still report correct flush position
to the publisher. The synchronous replication on publisher will still
work even if synchronous standby is subscription which itself has sync
commit off (that's why the complicated
send_feedback()/get_flush_position()) but will have higher latency as
flushes don't happen immediately. Cascading should be fine as well even
around crashes as logical decoding only picks up flushed WAL.

It could be however argued there may be some consistency issues around
the crash as other transactions could have already seen data that
disappeared after postgres recovery and then reappeared again when the
replication caught up again. That might indeed be a show stopper for the
default off.

--
Petr Jelinek 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

#196Robert Haas
robertmhaas@gmail.com
In reply to: Petr Jelinek (#194)
Re: Logical Replication WIP

On Tue, Jan 17, 2017 at 11:15 AM, Petr Jelinek
<petr.jelinek@2ndquadrant.com> wrote:

Is there anything stopping anyone from implementing it?

No, just didn't seem priority for the functionality right now.

Why is it OK for this to not support rename like everything else does?
It shouldn't be more than a few hours of work to fix that, and I
think leaving stuff like that out just because it's a lower priority
is fairly short-sighted.

--
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

#197Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Robert Haas (#196)
1 attachment(s)
Re: Logical Replication WIP

On 17/01/17 22:43, Robert Haas wrote:

On Tue, Jan 17, 2017 at 11:15 AM, Petr Jelinek
<petr.jelinek@2ndquadrant.com> wrote:

Is there anything stopping anyone from implementing it?

No, just didn't seem priority for the functionality right now.

Why is it OK for this to not support rename like everything else does?
It shouldn't be more than a few hours of work to fix that, and I
think leaving stuff like that out just because it's a lower priority
is fairly short-sighted.

Sigh, I wanted to leave it for next CF, but since you insist. Here is a
patch that adds rename.

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

Attachments:

0006-Add-RENAME-support-for-PUBLICATIONs-and-SUBSCRIPTION.patchtext/plain; charset=UTF-8; name=0006-Add-RENAME-support-for-PUBLICATIONs-and-SUBSCRIPTION.patchDownload
From 3ea2e0027bdeab5d6877ac7c18c28e12c7406b76 Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Thu, 19 Jan 2017 00:59:01 +0100
Subject: [PATCH 6/6] Add RENAME support for PUBLICATIONs and SUBSCRIPTIONs

---
 src/backend/commands/alter.c               |  6 ++++
 src/backend/commands/publicationcmds.c     | 50 ++++++++++++++++++++++++++++++
 src/backend/commands/subscriptioncmds.c    | 50 ++++++++++++++++++++++++++++++
 src/backend/parser/gram.y                  | 18 +++++++++++
 src/backend/replication/logical/worker.c   | 16 +++++++++-
 src/include/commands/publicationcmds.h     |  2 ++
 src/include/commands/subscriptioncmds.h    |  2 ++
 src/test/regress/expected/publication.out  | 10 +++++-
 src/test/regress/expected/subscription.out | 10 +++++-
 src/test/regress/sql/publication.sql       |  6 +++-
 src/test/regress/sql/subscription.sql      |  6 +++-
 src/test/subscription/t/001_rep_changes.pl | 11 ++++++-
 12 files changed, 181 insertions(+), 6 deletions(-)

diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 768fcc8..1a4154c 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -351,6 +351,12 @@ ExecRenameStmt(RenameStmt *stmt)
 		case OBJECT_TYPE:
 			return RenameType(stmt);
 
+		case OBJECT_PUBLICATION:
+			return RenamePublication(stmt);
+
+		case OBJECT_SUBSCRIPTION:
+			return RenameSubscription(stmt);
+
 		case OBJECT_AGGREGATE:
 		case OBJECT_COLLATION:
 		case OBJECT_CONVERSION:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 21e523d..8bc4e02 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -752,3 +752,53 @@ AlterPublicationOwner_oid(Oid subid, Oid newOwnerId)
 
 	heap_close(rel, RowExclusiveLock);
 }
+
+/*
+ * Rename the publication.
+ */
+ObjectAddress
+RenamePublication(RenameStmt *stmt)
+{
+	Oid			pubid;
+	HeapTuple	tup;
+	Relation	rel;
+	ObjectAddress address;
+
+	rel = heap_open(PublicationRelationId, RowExclusiveLock);
+
+	tup = SearchSysCacheCopy1(PUBLICATIONNAME,
+							  CStringGetDatum(stmt->subname));
+
+	if (!HeapTupleIsValid(tup))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("publication \"%s\" does not exist", stmt->subname)));
+
+	/* make sure the new name doesn't exist */
+	if (OidIsValid(get_publication_oid(stmt->newname, true)))
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_SCHEMA),
+				 errmsg("publication \"%s\" already exists", stmt->newname)));
+
+	pubid = HeapTupleGetOid(tup);
+
+	/* Must be owner. */
+	if (!pg_publication_ownercheck(pubid, GetUserId()))
+		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PUBLICATION,
+					   stmt->subname);
+
+	/* rename */
+	namestrcpy(&(((Form_pg_publication) GETSTRUCT(tup))->pubname),
+			   stmt->newname);
+	simple_heap_update(rel, &tup->t_self, tup);
+	CatalogUpdateIndexes(rel, tup);
+
+	InvokeObjectPostAlterHook(PublicationRelationId, pubid, 0);
+
+	ObjectAddressSet(address, PublicationRelationId, pubid);
+
+	heap_close(rel, NoLock);
+	heap_freetuple(tup);
+
+	return address;
+}
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 1448ee3..56b254e 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -641,3 +641,53 @@ AlterSubscriptionOwner_oid(Oid subid, Oid newOwnerId)
 
 	heap_close(rel, RowExclusiveLock);
 }
+
+/*
+ * Rename the subscription.
+ */
+ObjectAddress
+RenameSubscription(RenameStmt *stmt)
+{
+	Oid			subid;
+	HeapTuple	tup;
+	Relation	rel;
+	ObjectAddress address;
+
+	rel = heap_open(SubscriptionRelationId, RowExclusiveLock);
+
+	tup = SearchSysCacheCopy2(SUBSCRIPTIONNAME, MyDatabaseId,
+							  CStringGetDatum(stmt->subname));
+
+	if (!HeapTupleIsValid(tup))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("subscription \"%s\" does not exist", stmt->subname)));
+
+	/* make sure the new name doesn't exist */
+	if (OidIsValid(get_subscription_oid(stmt->newname, true)))
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_SCHEMA),
+				 errmsg("subscription \"%s\" already exists", stmt->newname)));
+
+	subid = HeapTupleGetOid(tup);
+
+	/* Must be owner. */
+	if (!pg_subscription_ownercheck(subid, GetUserId()))
+		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_SUBSCRIPTION,
+					   stmt->subname);
+
+	/* rename */
+	namestrcpy(&(((Form_pg_subscription) GETSTRUCT(tup))->subname),
+			   stmt->newname);
+	simple_heap_update(rel, &tup->t_self, tup);
+	CatalogUpdateIndexes(rel, tup);
+
+	InvokeObjectPostAlterHook(SubscriptionRelationId, subid, 0);
+
+	ObjectAddressSet(address, SubscriptionRelationId, subid);
+
+	heap_close(rel, NoLock);
+	heap_freetuple(tup);
+
+	return address;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a8e35fe..712dfdd 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -8475,6 +8475,24 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			| ALTER PUBLICATION name RENAME TO name
+				{
+					RenameStmt *n = makeNode(RenameStmt);
+					n->renameType = OBJECT_PUBLICATION;
+					n->subname = $3;
+					n->newname = $6;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+			| ALTER SUBSCRIPTION name RENAME TO name
+				{
+					RenameStmt *n = makeNode(RenameStmt);
+					n->renameType = OBJECT_SUBSCRIPTION;
+					n->subname = $3;
+					n->newname = $6;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 		;
 
 opt_column: COLUMN									{ $$ = COLUMN; }
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index a9a1277..f0b9724 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -1252,6 +1252,21 @@ reread_subscription(void)
 	}
 
 	/*
+	 * Exit if subscription name was changed (it's used for
+	 * fallback_application_name). The launcher will start new worker.
+	 */
+	if (strcmp(newsub->name, MySubscription->name) != 0)
+	{
+		ereport(LOG,
+				(errmsg("logical replication worker for subscription \"%s\" will "
+						"restart because subscription was renamed",
+						MySubscription->name)));
+
+		walrcv_disconnect(wrconn);
+		proc_exit(0);
+	}
+
+	/*
 	 * Exit if the subscription was removed.
 	 * This normally should not happen as the worker gets killed
 	 * during DROP SUBSCRIPTION.
@@ -1285,7 +1300,6 @@ reread_subscription(void)
 
 	/* Check for other changes that should never happen too. */
 	if (newsub->dbid != MySubscription->dbid ||
-		strcmp(newsub->name, MySubscription->name) != 0 ||
 		strcmp(newsub->slotname, MySubscription->slotname) != 0)
 	{
 		elog(ERROR, "subscription %u changed unexpectedly",
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index 2307cea..4999735 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -25,4 +25,6 @@ extern void RemovePublicationRelById(Oid proid);
 extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
 extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
 
+extern ObjectAddress RenamePublication(RenameStmt *stmt);
+
 #endif   /* PUBLICATIONCMDS_H */
diff --git a/src/include/commands/subscriptioncmds.h b/src/include/commands/subscriptioncmds.h
index 1d8e2aa..0333d89 100644
--- a/src/include/commands/subscriptioncmds.h
+++ b/src/include/commands/subscriptioncmds.h
@@ -24,4 +24,6 @@ extern void DropSubscription(DropSubscriptionStmt *stmt);
 extern ObjectAddress AlterSubscriptionOwner(const char *name, Oid newOwnerId);
 extern void AlterSubscriptionOwner_oid(Oid subid, Oid newOwnerId);
 
+extern ObjectAddress RenameSubscription(RenameStmt *stmt);
+
 #endif   /* SUBSCRIPTIONCMDS_H */
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 47b04f1..e7ccc0c 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -148,7 +148,15 @@ DROP TABLE testpub_tbl1;
  t       | t       | t
 (1 row)
 
-DROP PUBLICATION testpub_default;
+ALTER PUBLICATION testpub_default RENAME TO testpub_foo;
+\dRp testpub_foo
+                         List of publications
+    Name     |          Owner           | Inserts | Updates | Deletes 
+-------------+--------------------------+---------+---------+---------
+ testpub_foo | regress_publication_user | t       | t       | t
+(1 row)
+
+DROP PUBLICATION testpub_foo;
 DROP PUBLICATION testpib_ins_trunct;
 DROP SCHEMA pub_test CASCADE;
 NOTICE:  drop cascades to table pub_test.testpub_nopk
diff --git a/src/test/regress/expected/subscription.out b/src/test/regress/expected/subscription.out
index 2ccec98..cb1ab4e 100644
--- a/src/test/regress/expected/subscription.out
+++ b/src/test/regress/expected/subscription.out
@@ -61,6 +61,14 @@ ALTER SUBSCRIPTION testsub DISABLE;
 (1 row)
 
 COMMIT;
-DROP SUBSCRIPTION testsub NODROP SLOT;
+ALTER SUBSCRIPTION testsub RENAME TO testsub_foo;
+\dRs
+                         List of subscriptions
+    Name     |           Owner           | Enabled |    Publication     
+-------------+---------------------------+---------+--------------------
+ testsub_foo | regress_subscription_user | f       | {testpub,testpub1}
+(1 row)
+
+DROP SUBSCRIPTION testsub_foo NODROP SLOT;
 RESET SESSION AUTHORIZATION;
 DROP ROLE regress_subscription_user;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 89a3167..7e15f05 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -73,7 +73,11 @@ DROP TABLE testpub_tbl1;
 
 \dRp+ testpub_default
 
-DROP PUBLICATION testpub_default;
+ALTER PUBLICATION testpub_default RENAME TO testpub_foo;
+
+\dRp testpub_foo
+
+DROP PUBLICATION testpub_foo;
 DROP PUBLICATION testpib_ins_trunct;
 
 DROP SCHEMA pub_test CASCADE;
diff --git a/src/test/regress/sql/subscription.sql b/src/test/regress/sql/subscription.sql
index 68c17d5..fce6069 100644
--- a/src/test/regress/sql/subscription.sql
+++ b/src/test/regress/sql/subscription.sql
@@ -38,7 +38,11 @@ ALTER SUBSCRIPTION testsub DISABLE;
 
 COMMIT;
 
-DROP SUBSCRIPTION testsub NODROP SLOT;
+ALTER SUBSCRIPTION testsub RENAME TO testsub_foo;
+
+\dRs
+
+DROP SUBSCRIPTION testsub_foo NODROP SLOT;
 
 RESET SESSION AUTHORIZATION;
 DROP ROLE regress_subscription_user;
diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl
index b51740b..a9c4b01 100644
--- a/src/test/subscription/t/001_rep_changes.pl
+++ b/src/test/subscription/t/001_rep_changes.pl
@@ -169,8 +169,17 @@ $result =
   $node_subscriber->safe_psql('postgres', "SELECT count(*), min(a), max(a) FROM tab_full");
 is($result, qq(11|0|100), 'check replicated insert after alter publication');
 
+# check restart on rename
+$oldpid = $node_publisher->safe_psql('postgres',
+	"SELECT pid FROM pg_stat_replication WHERE application_name = '$appname';");
+$node_subscriber->safe_psql('postgres',
+	"ALTER SUBSCRIPTION tap_sub RENAME TO tap_sub_renamed");
+$node_publisher->poll_query_until('postgres',
+	"SELECT pid != $oldpid FROM pg_stat_replication WHERE application_name = '$appname';")
+  or die "Timed out while waiting for apply to restart";
+
 # check all the cleanup
-$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub");
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_renamed");
 
 $result =
   $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM pg_subscription");
-- 
2.7.4

#198Erik Rijkers
er@xs4all.nl
In reply to: Petr Jelinek (#197)
Re: Logical Replication WIP - FailedAssertion, File: "array_typanalyze.c", Line: 340

On 2017-01-19 01:02, Petr Jelinek wrote:

This causes the replica to crash:

#--------------
#!/bin/bash

# 2 instances on 6972 (master) and 6973 (replica)
# initially without publication or subscription

# clean logs
#echo >
/var/data1/pg_stuff/pg_installations/pgsql.logical_replication/logfile.logical_replication
#echo >
/var/data1/pg_stuff/pg_installations/pgsql.logical_replication2/logfile.logical_replication2

SLEEP=1
bail=0
pub_count=$( echo "select count(*) from pg_publication" | psql -qtAXp
6972 )
if [[ $pub_count -ne 0 ]]
then
echo "pub_count -ne 0 - deleting pub1 & bailing out"
echo "drop publication if exists pub1" | psql -Xp 6972
bail=1
fi
sub_count=$( echo "select count(*) from pg_subscription" | psql -qtAXp
6973 )
if [[ $sub_count -ne 0 ]]
then
echo "sub_count -ne 0 - deleting sub1 & bailing out"
echo "drop subscription if exists sub1" | psql -Xp 6973
bail=1
fi

if [[ $bail -eq 1 ]]
then
exit -1
fi

echo "drop table if exists testt;" | psql -qXap 6972
echo "drop table if exists testt;" | psql -qXap 6973

echo "-- on master (port 6972):
create table testt(id serial primary key, n integer, c text);
create publication pub1 for all tables; " | psql -qXap 6972

echo "-- on replica (port 6973):
create table testt(id serial primary key, n integer, c text);
create subscription sub1 connection 'port=6972' publication pub1 with
(disabled);
alter subscription sub1 enable; " | psql -qXap 6973

sleep $SLEEP

echo "table testt /*limit 3*/; select current_setting('port'), count(*)
from testt;" | psql -qXp 6972
echo "table testt /*limit 3*/; select current_setting('port'), count(*)
from testt;" | psql -qXp 6973

echo "-- now crash:
analyze pg_subscription" | psql -qXp 6973
#--------------

-- log of the replica:

2017-01-19 17:54:09.163 CET 224200 LOG: starting logical replication
worker for subscription "sub1"
2017-01-19 17:54:09.166 CET 21166 LOG: logical replication apply for
subscription sub1 started
2017-01-19 17:54:09.169 CET 21166 LOG: starting logical replication
worker for subscription "sub1"
2017-01-19 17:54:09.172 CET 21171 LOG: logical replication sync for
subscription sub1, table testt started
2017-01-19 17:54:09.190 CET 21171 LOG: logical replication
synchronization worker finished processing
TRAP: FailedAssertion("!(((array)->elemtype) == extra_data->type_id)",
File: "array_typanalyze.c", Line: 340)
2017-01-19 17:54:20.110 CET 224190 LOG: server process (PID 21183) was
terminated by signal 6: Aborted
2017-01-19 17:54:20.110 CET 224190 DETAIL: Failed process was running:
autovacuum: ANALYZE pg_catalog.pg_subscription
2017-01-19 17:54:20.110 CET 224190 LOG: terminating any other active
server processes
2017-01-19 17:54:20.110 CET 224198 WARNING: terminating connection
because of crash of another server process
2017-01-19 17:54:20.110 CET 224198 DETAIL: The postmaster has commanded
this server process to roll back the current transaction and exit,
because another server process exited abnormally and possibly corrupted
shared memory.
2017-01-19 17:54:20.110 CET 224198 HINT: In a moment you should be able
to reconnect to the database and repeat your command.
2017-01-19 17:54:20.111 CET 224190 LOG: all server processes
terminated; reinitializing
2017-01-19 17:54:20.143 CET 21184 LOG: database system was interrupted;
last known up at 2017-01-19 17:38:48 CET
2017-01-19 17:54:20.179 CET 21184 LOG: recovered replication state of
node 1 to 0/2CEBF08
2017-01-19 17:54:20.179 CET 21184 LOG: database system was not properly
shut down; automatic recovery in progress
2017-01-19 17:54:20.181 CET 21184 LOG: redo starts at 0/2513E88
2017-01-19 17:54:20.184 CET 21184 LOG: invalid record length at
0/2546980: wanted 24, got 0
2017-01-19 17:54:20.184 CET 21184 LOG: redo done at 0/2546918
2017-01-19 17:54:20.184 CET 21184 LOG: last completed transaction was
at log time 2017-01-19 17:54:09.191697+01
2017-01-19 17:54:20.191 CET 21184 LOG: MultiXact member wraparound
protections are now enabled
2017-01-19 17:54:20.193 CET 224190 LOG: database system is ready to
accept connections
2017-01-19 17:54:20.193 CET 21188 LOG: autovacuum launcher started
2017-01-19 17:54:20.194 CET 21190 LOG: logical replication launcher
started
2017-01-19 17:54:20.194 CET 21190 LOG: starting logical replication
worker for subscription "sub1"
2017-01-19 17:54:20.202 CET 21191 LOG: logical replication apply for
subscription sub1 started

Could probably be whittled down to something shorter but I hope it's
still easily reproduced.

thanks,

Erik Rijkers

setup of the 2 instances:

#---------------- ./instances.sh
#!/bin/bash
port1=6972
port2=6973
project1=logical_replication
project2=logical_replication2
# pg_stuff_dir=$HOME/pg_stuff
pg_stuff_dir=/var/data1/pg_stuff
PATH1=$pg_stuff_dir/pg_installations/pgsql.$project1/bin:$PATH
PATH2=$pg_stuff_dir/pg_installations/pgsql.$project2/bin:$PATH
server_dir1=$pg_stuff_dir/pg_installations/pgsql.$project1
server_dir2=$pg_stuff_dir/pg_installations/pgsql.$project2
data_dir1=$server_dir1/data
data_dir2=$server_dir2/data
options1="
-c wal_level=logical
-c max_replication_slots=10
-c max_worker_processes=12
-c max_logical_replication_workers=10
-c max_wal_senders=10
-c logging_collector=on
-c log_directory=$server_dir1
-c log_filename=logfile.${project1} "

options2="
-c wal_level=replica
-c max_replication_slots=10
-c max_worker_processes=12
-c max_logical_replication_workers=10
-c max_wal_senders=10
-c logging_collector=on
-c log_directory=$server_dir2
-c log_filename=logfile.${project2} "
which postgres
export PATH=$PATH1; postgres -D $data_dir1 -p $port1 ${options1} &
export PATH=$PATH2; postgres -D $data_dir2 -p $port2 ${options2} &
#---------------- ./instances.sh end

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

#199Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Erik Rijkers (#198)
1 attachment(s)
Re: Logical Replication WIP - FailedAssertion, File: "array_typanalyze.c", Line: 340

On 19/01/17 18:44, Erik Rijkers wrote:

Could probably be whittled down to something shorter but I hope it's
still easily reproduced.

Just analyze on the pg_subscription is enough. Looks like it's the
name[] type, when I change it to text[] like in the attached patch it
works fine for me.

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

Attachments:

pg_subscription-analyze-fix.difftext/x-diff; name=pg_subscription-analyze-fix.diffDownload
diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h
index ccb8880..0ad7b0e 100644
--- a/src/include/catalog/pg_subscription.h
+++ b/src/include/catalog/pg_subscription.h
@@ -41,7 +41,7 @@ CATALOG(pg_subscription,6100) BKI_SHARED_RELATION BKI_ROWTYPE_OID(6101) BKI_SCHE
 	text		subconninfo;	/* Connection string to the publisher */
 	NameData	subslotname;	/* Slot name on publisher */
 
-	name		subpublications[1];	/* List of publications subscribed to */
+	text		subpublications[1];	/* List of publications subscribed to */
 #endif
 } FormData_pg_subscription;
 
#200Erik Rijkers
er@xs4all.nl
In reply to: Petr Jelinek (#199)
Re: Logical Replication WIP - FailedAssertion, File: "array_typanalyze.c", Line: 340

On 2017-01-19 19:12, Petr Jelinek wrote:

On 19/01/17 18:44, Erik Rijkers wrote:

Could probably be whittled down to something shorter but I hope it's
still easily reproduced.

Just analyze on the pg_subscription is enough.

heh. Ah well, I did find it :)

Can you give the current patch set? I am failing to get a compilable
set.

In the following order they apply, but then fail during compile.

0001-Add-PUBLICATION-catalogs-and-DDL-v18.patch
0002-Add-SUBSCRIPTION-catalog-and-DDL-v18.patch
0003-Define-logical-replication-protocol-and-output-plugi-v18.patch
0004-Add-logical-replication-workers-v18fixed.patch
0006-Add-RENAME-support-for-PUBLICATIONs-and-SUBSCRIPTION.patch
0001-Logical-replication-support-for-initial-data-copy-v3.patch
pg_subscription-analyze-fix.diff

The compile fails with:

In file included from ../../../../src/include/postgres.h:47:0,
from worker.c:27:
worker.c: In function ‘create_estate_for_relation’:
../../../../src/include/c.h:203:14: warning: passing argument 4 of
‘InitResultRelInfo’ makes pointer from integer without a cast
[-Wint-conversion]
#define true ((bool) 1)
^
worker.c:187:53: note: in expansion of macro ‘true’
InitResultRelInfo(resultRelInfo, rel->localrel, 1, true, NULL, 0);
^~~~
In file included from ../../../../src/include/funcapi.h:21:0,
from worker.c:31:
../../../../src/include/executor/executor.h:189:13: note: expected
‘Relation {aka struct RelationData *}’ but argument is of type ‘char’
extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
^~~~~~~~~~~~~~~~~
worker.c:187:59: warning: passing argument 5 of ‘InitResultRelInfo’
makes integer from pointer without a cast [-Wint-conversion]
InitResultRelInfo(resultRelInfo, rel->localrel, 1, true, NULL, 0);
^~~~
In file included from ../../../../src/include/funcapi.h:21:0,
from worker.c:31:
../../../../src/include/executor/executor.h:189:13: note: expected ‘int’
but argument is of type ‘void *’
extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
^~~~~~~~~~~~~~~~~
worker.c:187:2: error: too many arguments to function
‘InitResultRelInfo’
InitResultRelInfo(resultRelInfo, rel->localrel, 1, true, NULL, 0);
^~~~~~~~~~~~~~~~~
In file included from ../../../../src/include/funcapi.h:21:0,
from worker.c:31:
../../../../src/include/executor/executor.h:189:13: note: declared here
extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
^~~~~~~~~~~~~~~~~
make[4]: *** [worker.o] Error 1
make[4]: *** Waiting for unfinished jobs....
make[3]: *** [logical-recursive] Error 2
make[2]: *** [replication-recursive] Error 2
make[2]: *** Waiting for unfinished jobs....
^[make[1]: *** [all-backend-recurse] Error 2
make: *** [all-src-recurse] Error 2

but perhaps that patchset itself is incorrect, or the order in which I
applied them.

Can you please put them in the right order? (I tried already a few...)

thanks,

Erik Rijkers

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

#201Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Petr Jelinek (#197)
5 attachment(s)
Re: Logical Replication WIP

Hi,

There were some conflicting changes committed today so I rebased the
patch on top of them.

Other than that nothing much has changed, I removed the separate sync
commit patch, included the rename patch in the patchset and fixed the
bug around pg_subscription catalog reported by Erik Rijkers.

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

Attachments:

0001-Add-PUBLICATION-catalogs-and-DDL-v19.patch.gzapplication/gzip; name=0001-Add-PUBLICATION-catalogs-and-DDL-v19.patch.gzDownload
�_5�X0001-Add-PUBLICATION-catalogs-and-DDL-v19.patch�=ks�H���_����r"�eK��(�xlg�����<���I�(�%qL�>��.��@7M6)�v&��u�D|4�h�������=�kV���^��k�v�~��t����_�z}��s������:�����t�[��kv��������l����,/|'[.��N���f������c���=x�����X���F��wnF��������X�����,�]����������3��p�Y��b''�[[�fs�Y����p�I�����$�5�_4����8S���/����D<�����v�sS�/��=VU�p������i�������
0?��i���u���u<�����L9��'�y�]+���q����k4�@������Y�=`���,��-P�T�E����H��
xf`��A��l�A�3��f
����k4V����<���~��2��b|g�{P�?�L���b]$����e��:�d�!.�A}4V��!L���q7G�=�����'��\u_�~�Wf~� |�1qx�pT��"
�37�����a����:%����C��_Nc��\ �: �Gl8:(���|#y�����d�?����D
# �G�cG���Ee�����Y�9�m��3�/��%^����E�D����>���
2�l;�/|���j�$t��i`������s������M*��j;~�(f5�\K&
���q�QY;�z:�v��;N��h�����-��$��5{��@���.��C��p#;
0s�hy��tK0����6����N�+%3l�tb����y���E>����.��Eh�,�B��:���4Oa���Xl��@���.+U��yB_n�N��I�+4��<CvM�
�0�
��;v�kR�!�	��3���$���6��J���$`����$���U�c�kPI�Lj`ka"F�~����>xR���C��#�������0���&������B���j����0O����;�J[q�R�����P����u�+�R��X��s��a��7x�g�*�`�v���p�����[L��B$��3���
)���+��Z�u������]%�*��@@a�VWZ�J����z��������5�3;bF�&�������^�u��A�e��=z� ��Z�[����{���������w[�K
��w���[�k�������[�����}.Q7}�/����a�������s��m�?y��T��^��&�wX���*[�Hn����V��n�(M`,�X[���:�`k6"@X"������]��BiL��-}`$��W��}R���h��P�-,B_��j5����]�>��adG__�Dq��!u"0��?���+bC?�*\��P�S�I%8�	7s�����3�s#�]Cc"!Q�]� �0r�O���)�f�&�eC6b;��x��*��,�b�s���{�G�x�
���c]�
����xu�$���'��5�]\'�%�N<kYG������u��V�����!vQ����U�����k*	������Q�V�7l��p����d!�,8N�x������%	��L!���7%�U���
��]<��z���T#�@N%�*�Lj�jM�+���|�9!�n����9��4 ��4���
����5(�t���1A^��h�vda��.���{��_��4��@E���2OD*����=<��^����y>h����^4��}��	G�>���� �ML�����������=~#v���������n�!�^P�n�>]c�N8�k�u���]Va�'�yb�D|����t���E���$8���X���
����!Q��f�5�7��F���
=6��i6���U�MBA$�0�2 �}��6��
�`�R�VX}<�-s��>ifp���/]��<"�_��Iy[��k�P�?�Y�c�a�M��i���'4���X���Ve���d/e���_u0������t���+�Y�s�:������B�����g�4�&(��I�K]5����n3��(.�+Wr,J�E���q2�L`
�w��x���N��t��Nw�3@���)�7�%{}��A3�����a���o���g���8��6�^��k�D����V3j����e�[�T��gA�f����lKFReQ��P�5t����p�$T�cG���o9����Y2QfsOH��R����}9�e���B�3B������������x~:��]�06�eT@5�1I�h0P[*�
s�`����l����1:����yD��]�z���LJ���:Iu=�'�����~!��7���A�F�+7�7��habf�<Q�y$���`��N ����U��97tuCk�cR�����nb�iy��dE9S��?�O�Vko�k�O�kfJ�(4���B4c:�	���>z9�r���-(�8DF3�;���w� �"x�,;�{�����9��9���������cT6�����{.�x>���r�C�76��E�)=����@p��D��w	`��������^{�%�*t������l$�L����]
^��T�"a���\R)d� ��J���"�l��%d^�E�:A�2���zIK��N+j�R�u�,�n��s�{����5;y���z�jq�=����H|�S��
V%c�S�r�N�XB��*�^��@i����~�B��]5�h��h��/BZ��[$����k�����t�i�i[����N�@��U�����m+���������W^���9�,��=���F�IN�w��KA^�k���^�6�PlR
�q�
UZ^����(�]H��K�0R����S����A� p��<�����vv��[�����!r���
w3�F���		kZ�����$�+c�w)Vrm��������C�4Q��1��Ef
��<�@B��%-�������ar�%������N��F�������>�B�����9d?���m��������j��'��t�����"���3
����@S�&�~bb��}a��#Q�KZP�'*��bA1������������e��%�}�� ���i���|O��\_^==�d]C�%��$�I�Fi���E�4������wF`n�N��9Zr&��
I����\Q^��=W/�����%C�JDy���d��(F��$C�zCc�R�d�OPp�e�����P7�"�C@v"�!YR� ��k>�,&A����d��Sm*K=��%,
K<����{;�g�Kh�b��m_V^L��4�OU>�Q��I"_(�)E�x����E������Ui�i��U
�bM�d} W�(3,�	a-�;�u0`.��J���� l�	���3�f�u��

�(�-�T����xD��KSi���b?A�3P�p��IU��>�.���L��*��X������`�tA�"������[�fUr�������BW5;�:,��>�f�0��l=(u\���M���q����V
<����B�yS)�P����,���j2��=�>�+�\<!��I�*.xxrU��!U��&l����l�c��G�
X���~60�.������M��a��8���p.w������iJc��Fi]`���g[�\/���P�S=L�i���f��"�k�X�����4�����:#�PV���1��Mh�q��Eg��&]���0���$������@�a�>p���x��^���F��o��.z�L�
_�p=	�EQ|,��!��L�*5/�XW�'�N���
�v����8��ps�[/AQUQ.C�d�d(�0<$E���U9
���$�@�4Y
��*MQA�6O!f��o�.=af�L�6�����������q��3�����������dH���D�N:d��,�U��t�)o:
A<�u�����8����hU��B�d��E����Te,�a��
K��z*���0��X���"Y�)#�,�nZ���C�dd�[��l��=��?&$����u���4=,���*u!������l~��V_m��Fp�|z#��m�I�5L5��F�v�$�%{�n��,X�G�_/��$���,p��1��h��$03V��kmC���q:Lf{�V���.b����������������@���&��	��H�V�MRl��	����/�I��M�M6��n�c��������M���8����ad8	G"�n�A�/�H$�����:H)z;�NGN�'��H����Gts����"Oq��O@������g���-�F�@��OF#��J�SI��S��Fl�����E��Gn�����3R��z����������zV��l�r�L/p��MM�>�l35t����D���A�f�V����o���YC@P3�����$T����H�\P����'����T�D�,����C���aB�J/�������1;�$��>��������4(i���q����Q10������	�C��y�4�%��>W���4�W�+("������=�v��;�I�����`K��4�(�h	�FS�	�G��S�1qV\�{A`V���[K4�phBb��]�@U��wprN"G{�V�{[�����/����D��
3�������|�Nnr���%�B��^����Q�������ut�O>�������6��I!)�M��	���Oh��^���P!��^�c:�SJ���u�*)��l��\N����J��O������������	����P_M~�ag�.���H*�8���/�Ih�L��J�'I��1}W)|-y��\���
������=;��lt3b���K�������������k�	��^���T%���Pp1�\T������T>��pF�`��q6�P�x������L���0�DL�c2����	G,S��������mrF�^�1OC�d!���&O�������nBFv��21I���� s������L2����S�SP^�(m�LV����<���Q����{[�r�Y5�Z?�dS7�e��Xc�������X�Y>��~D��)����U����m�z�����W�xTQT8�j!�"5�������]Rx�5y��,z�4Q�V,}�1A���u�l�P�`{tu��BG������� ��E�L�n��
C�5^%���9	��14�Q��!tB��p����"���r���	�}�laD���H���0��U�f4�|����,r.�B�������J�-�����aVs>
fT���x$�S`s�jW�pe
c)����|}
�H�Z�=M���O��;m����iM�`���`���[�<��,�K���=:��G�&�)?��0����!�o���7$��;��q��r��bs�a�:tQ,VF�f������YIdi[�R4Gf��Uy��#Nv{�����or/p�g�^!���3W,GV��2w���(a�7�&�������y`�5�
:�=U
k=�-"��s��������G�mr&M+�4.�f�
~������Xz�\�^y�����OS�&~��c���<�ksi?^���<��Y���#��RP�7��v���{!����.��n�W��1����1����6�����yR�D1��	]��^���������#���*�CW��ov�U��K�yM��@G���<�*�V����;{�+��2_x�akrkg7V�0�b�cy�Yo�j��������_!.C���1V���>�M�3\G�;90-;x��}�`��0�n��!B�91G������\�������	)��q
,���:�����<w/Qdz*jN��[y��p�t�
q?�h>�Z��i��'�����p���AjR��Y�3=�K�(�R��:�	I^K35�u{����`�kX���L���TZUfoO��G�d��x�ddp�S�^�k��i�0^����~���vvq������@p5v=��~�,U��c��nL~���g����_����������/�k�K32WC���Z[c��?(i���������7������lB��P �B��������=|c�A�`[��Dt��`<^2:\�����P�����,����tK�-xf�V����U����#~���&���Y������<{��0�����&�q*���)!��j��\���hN`�`��e�P��g�b����`Q�&i���i�ex�_�n�$����qf��G'F/�e�^��U���Ig��mmnQ/8��D�������%������,�����w�O����������B���$���	�<��(i�4_>,5b	v����;�2����r��UZ;�=s��9g��}��__�6_�.��}yP7Oz;\t���)GJ95��*��.
=�zK�|�B��$I}w�v�Y���u�Ozj�{:�<��	�a�
@\����AL$���4b��?@�~B��	"��/2���7Awp��y�2W(�:4�u�;���9/���;�	��5"_�5�2�t���%�(��`;��v:�;�����4�vp|X9K�j}tp���N'�t��������2���T�P\�����*��5���Y�������K��l�Jt�e���IF.��@�62s�u�q��p��dQW���H���:�D�:��H�T�h���B 3��
��[C3+Y����f�	U�C�Y���d
��?��U�*����G����F�-�����F��<9�p�����j�]����uZgg��"y��o5�jK��QzK���>u��M�V�F�N��f�S�c�8��u��W��V�3!]e���\����$K��[��G`7H\��`���ipK�&���5�����@�����`���
����'��x���*��HV������^V=K�m��p;�0}�o�z6��*�s��b9&���Q���;zBI��	���`�
y/~�M
mg!��.`!lhzP�7
m����`2��tJ����:������R����~���dM<�H<�?Y�^{��;0�G��A�U-V<'B�#��C0���I�j�V�a�����^�+����U�.��0� d��D	���J�����.M|�&f���by�NF��rq��+��V�S�4����"�(s*�j�z�������3ax�epom�I�d�;0�Yg��!�>L]�����3���+4��E�������t������=�����\.�|�!��x�>]L�.b�� .�T�iO sp���w��AHtE6�	�)��E��@�u&��8�6/A�c�ne���x��������@��09���
ye��g�����D�Y@��?�3#�WP3rT;$�cz��E�gO	��D�f����mv�kv^��n6]�89iJ��h�^�O���
�3���	g?�$���`�x����\�o�\�1�=�_~T��yk�^����%�ri����$wXz6�[��|��Q�S��w�|%�M����:�8����1Y$T���k��K�#��]|C3�t���M�����������!��o��gz�H��H0*_�[��b���^��0��M��0^�/F�4*H�lG=S��i]^���Dy���V^
���TB@mH�@E�=��/�(�b����{����B��w�wW����N�kVC��@����q{�TNL�Q���nmMw�YSgI?eE=kX�n'�[T���.��Y�N��]�Y�R+}�2��Q�x����<�:��U���pr���e>���:�hB[m`o�X��0C����8�RQ!����x��`�&y��\���IC	o\����:p�]=c������$�;]t�e�{)f��E�A��DOT���
�l���oFr��o��~,^-�;EPP��s����w������Mj�������0` >.�H��6��WK
��|?�K��S���)�
����R7�A�NL���X�	cJ����U�x.u�A�g����y/���o��?�����!�j�R�`� rz��C.l��!�\�E�J/O��H��|���k�;;������v�����r)9I�gB�k:[a���
��
J��o�_��H���4y9����
~�%7���,D}�Az��>�7P��}��K"���+��~���H�X.��Q-��!Q��+_��_]_� ���u�"Q������eW������d�I��~x-���D���������8��x���'��7����pv��-�v�JTv��+G���VAq�������mr�����SWp��e���0�����%�,��i�I��!������n*
��Xr!`����	�������/��~����d4|�����z�����%���$���E�����P�e.L����a�f���Z���Z��\��;:q�)h�f���?lp��+�Mg	��8*|9X���e�]��*�L#�S� �����6���	:�	���_���t�e���`V�oA?�&r
��.�����iz�P������cY�iH�� ���h�����j����BzU��`$���
'�]W�vO%w5�m�>�q�X����frXL�j�@<m�D�%�����D	b0���I1�(�*��U��\?<��|x���d���<hO''���L��<�n#-�o|���{[=�1w��0$5������
�o$��l�qxX+����j� ����
b�����������m�!3.o�A��j��U��|��$\4gQ3u\qS%��!wbR�����������L����L9��O��.�sQ�`5*U������o��Yk����2HV�(���a}p8�a6�
��@A�?�{�?���*-5�|��@���h\��]0��W�G��I4�_Lnn��0�UH-��g�hL�i�V��9�Kg7K��8F���.����Q_�9`�16�~`�`�W�a0�$,! �C0���R���x~�N���mF�$����N�� ��4�������(�R�4~��4�_�\mr�&i�A.6�+���������o�M
��<�Y���R��y�XIHQ�`5�.'��$�P�K�;_���fMF��P�zK�Z#�+U��|�4B����r���V�Qtj8�(���:�@����t��3��H�h�������d�46������Z)H��5i��?��@��6��Ttp�L�����o@�����dhP�!?��ln�����q�:�� (}�����|WF�$r<<��d��|�i����3��piMt<:�ky{�Rk�p�3�<U�2�K��%�hD|5{�.�3�OM��+��U7����y���y���k^\i��;��������=�0��7�?+i�f�&4��B�����{��j&���<p1�*�~�hI���CMdI�%@.:��(�?G�M�������r*�
u}
j�/������J�`�
�b�ZM�,�f�C�6��<���i��	X� ]�F4��@�M����>�o#��	�/�����4}�&M��/a}���)��0�c��t9�Sp����U��mu{���f_~�h\J�����I,x��:D���%~[�YmP��%����`"�w`��y]���L��[�\R)^�	��P(����P�^fc�yK�]m���$p9���1���!$W����+��w	s�*q6��-�-��!�<u%�0$q��y���,`��3R-^O�}� �����G5��I*�@�|kKr�0��2X.c���T�A��@&YS���|_�N���S��I�]u����i�{��>�c�`�\���+���z��}����5�c�����>�?����T������Z�� 4)$S���z0� ���X`�#�	��"z�:���F�#�!��d����)�_��-��o'�f��Wv)����c�Pr5:M� �5pZ����2�w��w[��`8��Zg�'���-}Q�l����������HL]�{�����M&�Z��CTT��}m]�Zd
��l �L���63r�q�,��.�2���G��	���pS�Xm������g0:'�n�=Q��l
2���&}�����N���}
�Ea`��xKP x+h��i�1�&�
L��	[q,5��O}�B�+�G�����h��n
��"i��8I����Y�5������tF6W{��R|��P�X&�3c����bt��i�
_��~i�t��j^�J��~���^7M����QM����#L�d�!�U�#F�
)"R'0��5IWGV�o^
�q��,BT�8H�M�!�����)�D3%�c�@��l#�A.�)������Zt�B	�we���3��
F��s�'X���m~fK�A�M��n��J������-��������������g��#�Qa�6�b"�,�P�h��E,�vw���0X���vn������ul�!���b�]?��;>�4���G�Szh��>�w4�3Su���H��v�h���4����m���t��U�W73d���cn�~���w9��t�C��C��T��loE�mg`�K��f�����JA�/�=$������s�������.�|����xs7aa�������Z�K~�,k~��M�	V���BN���]���~�u���+e1�4�����
�y������3�"�+�9�a��`�^I���4n����-��|��t$����,����f�������b�dg������q=i�!���<d6T�X�1��1*��7�p��g���t\m�F����J�<!�6�Nn�2���{�q7��.������o��s�%����������FDc
��������?�bmeI�r���;�A���$]f"������w^��p���[����6]������] ��@s(��K�H�<���>�|����J�79C��sN��nt4N�����z���o���m�$,��C,6�[P�������},F���t2;hq�B��iU�e�l1�h������
�����"�h����b��W�S�>o#(~�nr��X����NB���fUk�)��2I��mj�9��$c��S���TB<��p�[���T������`���Rh�g)��3�?�B
����}�KV!�Ysv��,�li�<��:Z��>�����@^��[�������s]��%
���f�X��'��f�At�dz�2+��G�Rb��h��~k�H��Hv4��rL��<�`vJ��us]�T����lT6K���&	������S��}P�'����������	��q{l,�z5��^f�&3���0�Ab�m:��!��8Za*4��$�p��*u�
j��y����E|��H����p��I�����6��G��^}���BpO�Z~�?��8i�zdY���2Ri�����
��I�=�yter�z�4���������j5����$�A8����_�/�c,��0&�v������k�����*�	�=�����,0�,<;	�S���������r5�]4I�c�hC��8=#L�\	�Ue�U����ju��)A�Aq�E�{���t4��|B�qu�T�/WA�.�8�rVmA�F�rz�~���+��!�wL��-�����y���$�;��E��pf�rD���8@/*���������Oc��c5%{��sD�3��
���b���L�z��K��q�j�����'������
�$>%�JS�.��
��Kqp�/����c��ieQ�F�B��[~����x�$^C@fX�LtvQ���A��6{�W5�K�'����^1S	�S�ex�Ce�'
Z(����-�YG��s����@!]�%���i����G������ceo��5�xP;��R�|<8>�n���%�����Q��iX������[#���\�S��2}�/rh%NG���f��*8���*{����g����v8U�����^�
���j��i����)y,��FC��C����A���'C�	H8�9L!��ojB'*��=�Xe~���n��������R��4c�������z\�JA�\����o�TG��$U���Q���b�p�S�~�j~�Y	��2���q8\-&��� X,&�bG4�������&*MA�J� ��P���/��BtH��f��KL1A��M������]��M�ei�U1��D�������|���i��B��B�~��n���[�N.q	9��%��8X�C-���4eT���v����g��%��M��<h� ����N�dF����_�&kPd%+�L�\�k����R�08>�����z��l�9����ye�;$~��)^�o���9����,2����]��8!�z�����@&v��A�gIy�s���*���|Lf�M�-��"�N�V7?�
��_��%�THm�Ab�@�pR����'U$@��\2�"v���1���(�������:�"����W�	sj�w�������(����$�fU���`���:�e���[���P�`��	#R,K�Xe����UH�|p���	B�r�YUq��(N``x��C��{xXa��K8D6����nYp�g��w��"�gQ
�(Q�N���8���"�8��]�q����aw�a�QwU8����������<��?��}~��\�4�����iY����r/
���8�-�����Z�#�1H|R�6����7
^������~xx�u �j��P3U�X����j}LdAp�^]��]������j*����J(�����`<<�I��`J�*�%A�L�4T-7(e���9�A|�E:����m)�7z����d�����W����n!�6�v���%�2���f��zt���J������yF�<�5��Y��z6�����fs��1"I:�[�hJ���3U@��������S	���o����C�~D����#���	]�'�@��"/�%����K�c����5J���7���3<s��*n��Z	�������X��?_������������7���{���\�2���9�	�	�
�e��9@2�0�h'����D���.��E�����n�q��b|W<-%M���G�(@M��'
y���x��{����:t�\���y8�t�g���
�@~1��`?��h�-�����loV����^��Y�2�%�e��������M���8t��t?�,?�2.���3��c��>�����YL�Dk��J��d^���d�U�m���C�����g��|iW����`O��@�g�$��h�,
]�%��[k�r��W���LfL9����U���h����aQ��+49��4���3����_=�C�U�Mm�K)�������� �q��&�u�w������8�����ty#�-[�?\�$�ZF3��Z��l,�{���G��(����	}���
���L��Y���0�H��.�E�f�����pRP��J����C)A,<P�������L��LD4�@�&��z(%��I�h���
�
�	[�hq"/ TeQ�pT�"���	�g%��d�L����m������CqC�j?j��K&�kC��;��P*���	�B��N�t�M
����f���gVo��1�v�;��k?
b�0�;f��c��:����TR�}B�k���*����tQ�g�3$q�:�����AO#h`���:n}�/��a�u]+��i�����mjiC�������S�B�%�������L���V�v����e,����u���"u�_�j�� g�U��]��yfq���wfB��6X������:.f�@���Zb����W�H;pf��2���U��D�����h0$���/�)����@-���V!��Wl.f�LV5�m����&�K�Z�[�ng��|����~��+n'{[8�u����#J'o}��d��q����
 ��DF�.06����5�Q�
�c~bMw�)1}
�������i�~0L��]��=+o'����9!��I��-�V�I�w��>�rI���;UA�1���%�i+���K�`
������\�WH��n�1Gk cel��/*���j�>�Nk�6z�������Q�&�S�*���XG�����#���q��61e���-[�i��[�Hr�4���Gl�F�)U� ��0�?�����s�np��h���sF��&x,oQ�{Scb,@v�L����P(YS=�����sH�x��=�ro��}?$��T�l0������\;W����&u�5u�;��������{��E���������D7{D#��t&;q-	��3� I'��rXyH�����_��1�8D��yV�����b��f�kL�uD*��9����w��UN�������.�;P/-�#����^rW#)��V�T*��,EA�h�qO���0�U!yC�B�g��m4��F
�.���^iG�9���R��)u�����pgX@MP��q�2W;�k��}#���*s�e���w�X&����[����@�a�����/��%�p	�;�v��z��Y���qY�I2�e3X��w�[�=���e���|$>����{��]�0G���A<(����`*��`h�i����k�0���L��H:D���(���T����U��i~{���z�~���
�(,m<q��R%�)��>3�?m^D�9��>ek������6���Y��B��J�6_5������N�ed(������/r�?����X��L�pQ��t�=B&W������\G�4�$#�n�A���8f��	�)��
��B�*���6��/
=9��� \�~l�����d�q:�1� `��i0�cm�s��+	p9$��H[`[s�$����C��&J�F"������qRl�!�sa�l��.
�h�����+F|"�*%�C��p��O�b%�RF��|Xg swj�s�Yw�1��\�)�8�Hv���] _#Y�Lq�F#YR����v��{��S`�Oza�f���
g�������U�������0�N����_�b$2t*���N���z�����o�=������vi�d�9&f����Nr��sx'o��YL �O���?�����%����?Y���F��s{��\�A6T��|6�����������=4��/����e��v�4?���P����2��y3�+_����)��F�L{%s������M�ek���K'�%o�����e������R^��k��<ti�L<��@��ib`qK��+�ex�����������`�����8ic=8����p"��a��B�}l����_)��X��*l	�b��@B��w*{
���M�w�c��t�N�]���40��t����,�QB�Wrs��`�,��X�F���3�V�`0���4�`�Ed������FA���rz���!d����=Q�w�p�AB��as{�%��E��\��W`���X���z2��2o���d	�F<d�,E���9�W��d���������>����|�,��������t6$b�Q�����q��F��M�����b������f����~��b~19SQh.�_�������v6YR,�w��M�K*�3c)W����0�'�Vs�w��������U�����N1(�m��F�{q����&3�><1�J�"�A/��nx_w:/�;��O�*�sx;���t���-�:�f��A8�����]��E\�1�_�uk�����q��Pc�t�z)	���u�j�.tZr����Q������k�G(nB�<��#:"���J@���?f�J� V%��J����o�=���|@�t����bQnf{�|�]����'T�4s����Aq7*�b�8�(	���5}�ZR�� ��]c�J?�k��{CF+�\\���u��9�QRQ�}5�r�Q]�\m�0b���x�^�!�T����0��v:a�9�1������%���A����c����$������8��+�|����������u���S1�b��zA^<Ej��)��iks�K2���P���"�6�J
������_���N<���G`�P��#l��$���GM��~l��<��V*�3m3N�P�����|��N-7A$��*�mE��-31$n���)B�q	l��@�!�(;y����n}�'��I���0�C2�����`���m���7PM�a���1�o���a��	�nR�#aqn[u�Hl�R�
��	O��{T�}�H�����QU�K��v��PopjyB]���tR*����`�����`���9#h�s��]	!�lBW�C��T�hZG���������U�����ET,��$"Xx����0'2�K4��
S'���"8��,)�_-���u���/?���"��u��l�x��=�����	G/4;��y�<���*�H�b4C��:�v���S�Z��x�$���8�b����Bm��H!n���E�4���{�L��
-�Yv4x��O��dG'��e����P�����K��!�<����<SNL|�J�+O�����0*/,z:
ff.�
G��
��qP*����A}��yv'9���j*�Z>8�Xi������+����Lf��"�2:���8���dB��-�7#r�<7X�:�
�d��.#�����}v�<c3�}�
��"��F�!$���X&5;����h�AXV�d���C&c��������y,��j�`����p@
]�n�Jr	�rK������V�����[n���,.L.�0K�MQ8�1d�x�/�Qo5���[�t�PH�	Z(K,����f�\5kS0L������l���*��S?��������P�J����p�����7'���o�����\�������#�s}�0**�|�O�2HDMk��y��&�&�4�/��
U������������N`PVq=�����)HCy5
f�oW���b>�Y}-�����"m�=;:�d?p	����������b"�y	�{��<�u�r��&(���Q7%�eV���R�9���I����=����$��"�����W���or�<o�����
��O.N�\��/����d����LkLn�8�.���`�.�{�&�F��x��@���7�Z���i��v��$�~���>?���K�#��c���2��-��] �u���A0A	F����6��������.�����9}����s��z��V�Z_%W��e�,��g;49g�u�h�cI���l@E}�?�XM�p������'NM���Ra�P�Y��f�����e��-`3j���#�}f`�G�����#@;�S]��?
�J�.g���<����� �������7$�^����&@�d�
{� ��TRH���������	R\|��#���R������_o8�D���)%��*���	#M��*�6e�G���	W���
�Lj�"Ur��H0�")!!�^<����_��l�Qc������N�f�&���=�9�I|��@1�S��X�*W���+���T������ �1J��f��u�)�!���5d�P�Jq4n����g���X� �b"���u�g�+z��cxKy���4*25x"����	�k��I���}��$4���Y�U�y~��p�T��@��L�������I����B�-nt��t�Kd�����og��_�6_�5�<�,��<��O��O���G�������1��O��i_T*�b�
��"�/5��S�(U�O��	M`�tL����,��U
��C�vuw����xX��w3w�����`�������A���|�}s(������
�y�������(������Z������E�v*a��J
���g���+!�z��h6���^2�)��"�~L�@i%�v�;0)�����/�����.T!�������oJ������6@L�
8�yS������Y@�� �~�YE<�k���g�6����a��j��LNq*%����������d�O"�9{���h�������V�)k��3)+��o�]�>A��2�&��r�T�����z��N�R���f��;gC�A�����I��N����%)T�NR���SEn��5XHa�PD`'�(<�*�D�N'�����jA�q,q^��X�5��#����F�[�����,�?����V����E}4��?�_��p
b8%�b�*��'�oO����[[N�KAZ��]���F<����V�����S��b�#�R���}�'gJ�GlM`��`��o�{��_~�	���[)�OC6\�=�O ��{�����4�����L'�����#������,�1r�����(���@:���9�����2���|y��g`j�E4���j��A��/�7���T��Pp����qj������
�%���&�
��e��>���|6B$qC�������_���@J�\h]������?�owN��y��7.���7AO�.7�4O��XC~ku�����&|�3Z}��:��F��'{[�[�#�]�����]w`:���:���?���������n���@��Z�>�j�#i �l�����*B����0��$��G�DNPf)2�
S4�u"9B��>N�>�=0�����=R����=��]��������z��aO�icwf�!��y0��/qP)��z��XE1H"��H%���������T�{����*;/�@5O�O��V5d��?U��O5������b�$��C>V��+�%X�<�5�L��b$��r^L
��94p8m���ZQ�u2}@��{�e��Sam��.�v�o��}���w���
@`����kr�y<_Lf�qa�����#�;�D�����R���
��D;�&�)]���d:k$�@����:�/���Bg�
�4oZs�$����#Oug�����h5���#�-��p�|D7�xG���e�~�c���23�^������6�����u�G;O��)�j�k^H��k�Z����'��W�3IaO{����E4��k�[;�y��D��k����M��R�*�r��E��/>tJV�}�XJ���@�a�k������0��Z��T>|i���3�+�,����P���wY2��,�,����l���-����.e��V�*�_����y��w�k��J��g��Qd�H��S�E�O�����kX�'��1�0�_�t�5���0���0�
)�n!6�PK��X���su����a����!�O�lT�~��N�V��![{�=;q~M�8�:��i�e�C+���U���~	���uS��!C�q���|������^\�~��vi�:�v��4U7��������w3k�f�2�)�x����q'��?�>����qR[w]���)����d���O�Z���G6�Hm�-��L��-c��,Z�q3����GP���"�������p3|������{�D@;������������~��Z�����0�Mx4e|�?4�A���j%��~���2���#J 7���������/������������l�f����h�R���^��-T�����J�R*�����JN�D���=Q�NP����2�5�p��2���������p��d��uo�� �F(�P�[��+0�Vf���1]��)�:�J��q�K��*#1�S,ah_D3��������U�u��]��J7�t�����R�WK��WLp���qG�K������@J���X[�lz�eZ8�0�s^s�h��xpHi��*��1��J�A����i����Q�K��|6
kX$����$�+�
����B��F&�L��Ku]��w�=_j8�V!+��;��5��=l6c�"���������/�6Mt�u�t��e���,TUzl&���7p�(�.a_N�>4kB5��b��+�%
��rSH��t�OT����xl4Z��0�<��0���,�_4z�����d���|�m:�eI��j����y*�����8�8��t��J��7��������l��P�ZC�x�zP�7OmAyr��89iv���f�u���H��LUTa[im�`����v�~o�����~{�V�ez�zu�X�����?�,�����	��_���nt����Z�����s|}���Z���j��q�w*r~��K�>��nB����Y�w������<�<L��H�T�=2�	�V�n��6�B�_�93�����9&�O�g��G��G�R�RKn-�G���f�|�p}���\_��#-�:�Q�r��������^K�A`����Hf;�k�i+�����d��Z�"0!��K����O�+)E�����2V� ^?�$:.�� <�&{<�S����hB1;��%���Y��G�2DyI�#���b�$1�������R9��-f���K�����!v�L��!WJJt����+[L=b���{���j�Z���F�E�e���
{0�_������!��M6��q��=�1�h�����W�����r>�EtXR�?�������7��Bp���(�#��\��1�p���I���Z����B�������.���`N�����h��*��q�#Zzv.���&S��-�8�����;��c��[qk�������gFCX�Bnc�h������}|�������QX�YA�N�[c���=8���������K�T���>�
��J��%Bd<��k������l���m�{kB��n��Q�?h|�o��0�0+��g�}��������I����!E��Uvn��E|F�^�SS��8N��I����6���@������]�!'S��P�Q�1���^�Q��v�b�ar2s����'`U�k
�Sb����&8��:'��[��)�P�T1hs)�����9I���h�r���XDo%��x������G��+&�h,T<?��=�����&��1���������R� �U���f���M>7cUD��7�iOp�_;�#��o���;�uQNUy�~�g����-���]!��RD�:����e�Z
���<"u����a�\D��gz�s8H�!Z%���x�dp�0�X�q����%�:o��55�����6e�)�<���C�z�����1��������CJ��c�'��A�~hW�GE���3��N�/`�.��hq�������rx�� ��P���S\��K���Z��R�g5i���X
�v0!�X�d7��s���q�ig�-?�}5�M$�	
�}��	��
�����o���C�7�����(2Z�(uk����P.W*Gm��v2��N��l�w��:n�����l�_�&RH�(K\C�����u:#X��tpMn��k�)�R��R	7����|�95��[z��[��Hgg�6�;/����+��KO}+��o�'��g|���-:�IL�z)�H����&�D���RO�	�i0)����)���y�d�x?��"9����Go�QF���_79-�Zx�=�� ����A��ZU��B�����3 *(��~\/���=3"�k(��,�'��k�Zs`Ynbs;l�����D�y�Q��x5��,���w}���z��E�`9��_A
���!�FO���� �wd��1����-�E3,z��B�T|E��XX�K<8c��������z���1��`�V�Y�Nnex	��xH���POMc#�>���������zm?�
@MY
��x{��|���Hz\t�\����h���g��V���P��	B�%"���^�����A��7!7HfI�����,?BlT�|@�a�.�����Y�B-���=���|�����lN�����k"E��et��������:����A��� �K��2r��7��M���;�N�<=;Y�s����Q��h\y�x���U��P��>��*���<���[�vh����s}��WK/�CR5;+I]��8�4���`��VwsY1�<E�8����~�T:
�����j�5�H��E{�>����n�%Y�H�9(4$�
)����aK���I��P %lxO�A�.���6�~a�LE1d!�������a��B���o�m��pK�/�=
�4��\X��(�Lv����dC������g���0�#��h�����-�?�B�y-�St 1��O��r���(��"����q5���/��12�����R)
�>-��������W�U�:�Od���D� &�b������_��� A���E�������aQ02�F�V����W�_��y2�*vO^7/��?�O����f��R�6z
�}�qu����G�F��n��}����u
A��Z�g\O���;N�<D���V(FYY2*�[�.�1�D�{uV�?����C�����K���[���&��?��$�AL*2����#!�
>Hi�'���4s���\�j{�F���������LWK/�u�d#�2�-��VA����
�Y��x����R*�kw1����\���>nW�8��E�!7�7�h�G��]:�j���I �
��Y��2N����8r���7��&��S"��h�����\�S���t��=�/�l��	 ��N9���GR;O��l����^w9"��8Q��2�-?k�����#�I�~N&wt[}�8���P�S���Rq������G���|1gmJt�G���S�T�qB��3^>�1I�eZP�?Y�-�i����%��s����Pk��C�W���yl������w�\���@'�_*L,B(�n�:G>�sl�uap-������"O�T[�e�D��y���tQ~�%d�mS��EL%��#]��v�a
o����?���u�V�:��&%���Too�w�L,��Bt�I���B��)ef���#���N���]_����_�9�������:A`���j���_�n��`/�[�6���Q��|����>�j��o���B�i�������s��:�����u���eU0�"�J\�*�b{*r���^���j��io}*�*���[�����b��T����T��7�;���b?�(g�rY�:�U��Qjp����O�g�c���v�o9YY�������\]}{��V!��D�O�{6��#��������J���j�
_&�����1
�bne�r�0�������D�Y�������;�����X��F�R�u]*���K����K��u��<�.���L��iH9��y����}�s)��y��V�o%���o3���Pk�Ib�b�'�H�v����<�s�����&����M��?��%
�X��J���9�Db+����_�������ft���P��2���<J����i�E��Y����A
�����f�L,�����l�3��R��������>N
��5������������I����e��E�RJ�=�K2�6�l{�z&b����(T��S)
3��g2��������9�����y�zIY�76�|hs�H��l���P�>���)���n���q _X;j���6QU��jw{}P��z#{V}O�+��O+�z����)��w7Qm������g\�U$'�3>E����"��0���RC|�������0�bq�J�as
���b��T�����a�F��6t�%��9"C}�X���T�N�Lf�?&�����D�^c��i���H�&��tls1v�x��8G��l��?�i�M/���W}����������xg��Cy�%
�(�4%���Sx�$?h��&G���=��Sd(*`��3I��C-����X�J�EB�����p�);(�-0b�>������N������~�z����<%U�6PoY��[���������nx�|���������a�77�^����N�)M���:�����+<X�Z<@��][M"��;������X2\�?�}�ZH�K�r K;�w�v ��\���]�@ZA���5��8���%E��f�O��}UT�*�K����(q�|�T��N�@H#�^�|�
�����<W� ��s��A!�-�����2)9F��b:���u(}���X�t9�����3�I�e��p���{h=��]�Y�-�%��o�?��$��G���N>�~d#���3�A�I��i^�f�`p�T#P���p�����F����
������l$���~O�|�`q�_�o���X���dQ����Ne'\�{����H�?����\=��x}��'�I����$���N�<M^��E$�.��;���'��{dQ��6Lu����~w��G��o���4���a��D4����<mt~x�����-��|�I(ctKy�!;&��T�uv���|�F�D��(�<�@- m�d�����k9�
?ph5��w M�:���f��S$E���99w���n�,^&�c�@1��{��Y�_��U�>�R���(2����_��1X�2Kb��}��n�Fr7#�I�����6�����;���j9��J�quT�b>x[�w����������<��R�w�bV�
�u�g��+���V�o�O���j������Y��X�'�C�<����`�%A�p����ZK�x2�UR;��`gxy�*f@Q��~\��m�%t�	L�������<S�sn�<���C��4$�b�xK����0E��dt8��,��_��0�O�}�~d��#m[�!�~/-�2����m:��#�*7	���?��A
-������zCAA@��ra�`��{�$A�Y>F��0p�Sx�v{���p���e8����D��d��Hi����������1a�3���S������7��
��u�OT_v/:���b?Lu�,��J@����Nv���T����F��=?����w���������V�O$�z�'��r���!��]��Q���|����8Z,�l���4����R�hT?8k�%���EU�S���
�sL��GC���h1Y��{�<E��"C
b��:�k5��sq�}�J�t��(���pp>��Q;HN�*KN���aVy��9���V�=RN@�V���g������1�b��� Lp�I�Y���{��C:��6�V�u*�	���b�cb��>S�������P6��X^�y����HP���n2���1��&Cqq?}Jk9��I�U�4���Yo�L'�N,��������S�NVQ�����J����0���N��"U
mN�H0�C>��_,�x��1��S�|���D���>T�]��}�87*�UaG�vx*���<��|�O��E�S�*��-D��G<H�T��*��(����R�7:gM�P�<-�}|����D�����9t�����C,�����5���s!� �����q+��y����!'o�������V���|~��L'oByt���r�,��8�GU��Qu
��}�,���3��#�k��_F�xx�U6�������o�i�}P���l��5���
0R���A;��>	�)������e�w���5?	�ubJ�k�{�o'�X�����F�Z�x����������
���U'���Y>]��x�Q�����(d{2P�$A��l���!�~�����=}��z��\�1}P��?���J�vR���Q�������x$WO����.Z�{*��N,����j��4�
���n�'}g���j�B����G?Yl7��)��3���hj�wr(��l��T.b�������
�}O=�������j�n���B-��tu������kVG/�z����}���
�]����m��u���:�T���B�kI1�� ����k�'�~��$�����@
�����qv����b Z�t
��"�*���8�k{1y�3Y��2k$�%O#�����a��h`���`r]e�C�{$��������z?@%{W;�z7Qr��a7�b�i��(��V�3
���Y�o���A���u0������-�[�q���mL`���JJ��L�Hr���T��z��R����
D�\�8�>�������R����(C}�6M(M�B�uF��G�/�a�����T��������4��
���i������i���/���r������SN��q���?����Si�+����6z��;c�{��m,���5�n�Q����6���p�Q�'�fY�_{�j��P����%Iz��*X�_�;���>^�w���
���8eJ��a���j<>8�d��m=Xe�RP�#
-?kG�%S{�m�i�&���1��d*GYu(�6S�d���A[��=G�(����R���B����a�d�*�-���k}�j����(�o�m��X���$[�7_����[��k����p�e,��@/������S���w�v�����<���AT��&D&�������k�Py�?���E}�D��bk���5kO��~�\�|Ww���Vn��v�P���U-a����(��m{������7��[L��w��=�?�C�����(���;MA�i���E�r��kTB����o��e3���K�HTj����������d����G�g����X9>���2�=��<0�l�n���%%�1m�	:f2m�Jp�m���Q�9�m)�%��	��1�]�@lx�H!�+Z]Q���j�����,������=KE�S�m�h��g���Z�J{��=��#q���c����0-	����� ���d�+�?>X������H,��-(�S��V��|�%M��n+l�9��h���"��\[T|\gM�����%^��oE~tg�Z13���t�_���c�����8��l�Ln"�"�N;T�r
wy�
�CJ��i��`ep��u����:&B^�q�W�c�*�����?���h^��Dw���6�8��E���
�8��
�8�x;�c��CS���?�w1���^��6(��hPDF5��l=[�k�&�^����{�9Av�G����[�	h��)S�H?J��tz�����L%��]v�3N���pUMcn)I�Ix���Dk$�������4��,o�N���\��/�K��H=	^�A/>�H����tt�@��%�x^�2O��'����-�O�����������!S�9v�����S�cO��U�Ss7V����E���@;U~�4��M�IbW&�K6^�$��p�u���~�
�����t��<Xqg�$�����g��<�X�t�2>�98Y�L�c����A_(i���\��i�xY=;
Q��=d���i�	J���h-x����9Q�"��HY�Hm.`�4�K���)�������)IE�M�<�pQ���vB�������bg�h"Nr
k:1,�'t���T'�n o������x&����O�����V�$c=@i2+��9���c��=�PUJ7,�� nM�Y=�jA����z�T���b�
I�O�u���?-���lR�
����d+�����u$�I�oP{�n�#��
*�
.o5GS��<`'�4������S�*%U�Dw��a5���Z�t���5]dQ���K
 �)VLJu��T�=���.-_�F.&�-�����a�i�J	����Ic�����:�;�Xu�DBp�d�PM7fr���Q��%*/6�T$�D���5x����m�n_y�����
d
�����n
R���RtM)W��b>�j���P�J�|����(%��m��������k���S��#�D,����Xn���;�i�{�i}��������y����*/��A0>:��J���Q�����~\Ba�+���cy��^c������#��Z���\>�� cvS��i�"B#���F���?���^�s�]~�%;���x���[�:AZU�
/�U[�]=B�5����t��o���,F1�����g���h�y�@_p��.��K
�b�z����3�1*y�*9�Jr'g�T���>��}�,-��2;@#���N��[�����}���-e|?<,W*�fU��Z�K!���������u��)/��
hb0VB)�Ee�2���dX�n������������Y���'���1	�_=�M����U�u����6;����e�>�����e�������nOi������qJD]t��a��K������5V�
{�.���Gu�J�7i�w�
�� �1^
b�@PX�S�R�b�����<����2��0i<����eW~r�cT�1"@�����H>��U�r����8;�4� 0(�ym��+����5;]�^z�4�X�#\��N���y�������F��],��sU��*7��k^����N��*���rS��u��WP��<m��(�&�nlq���|�N�q��?����r�F�-���*���P+�����o���'T�C����o*NvH�j�T�J�?
Dz?\��F�$\�~�������C�$a��|�)} /<cX�vC��-��V�}0�������/������~
2�K���+�������X!L_�Ba��V��������'��!mH�g��_���v�Z�dr&Ua\+P�����:X����h-b�m�@�Z����$���������'�����Z��Q�D�K@��^0�r���xa;�
��d�d_d+�2"6b�v*H�U�#;}�����@����4xe/���������^��M��GKD��U6����OBlwtI��N��X��E�{�R�4]qP����)��6t�
�
q�������L}ng�/��N���>a����W_�_�y����P�x�
��	���w~(xX��Q�$����rK���;9wkgW���~G�����r�u���k��a�!�,�����p<�~��K�/o%``��FW�)fR�F|�Z�%)������z��)��@�SS��*�L���=i�6�s�iv��-����C[fFP�������{O��{���W��{B����xO��{���� ����\F>pK6�CF��Py������ruON�]�R��������Y��N1��
�i��LK����9���G:��Ve�Ua�0��K���Z;<>L����!1����Pt�
	v�4A�VN@/��>9ot�}���kV
9&�"���DQ�uA��_��������t���h��c�U3������ ����7�$z��eW���;���E	����Ms�9�(��ZS�W�~(	Ku\*��am08^��V�koU�T�������O��f���%�ae�f��*�~$�m"�a�/��(��r�(@�T~��c*9>���g�vPQ}���U�4�%��M����cT����~�+���8���<��������,��_�R)��Y�g�,r������!;-�&s��nk>�u�Q��.gRVnp�w�)A�jDU��p�=L�|���C�u��i�6���U��A�{��[?W�Gc&ER8�CLu�������L���C1�m����`��	i�7���Sx��<�6!A���h�I�t�R���A�T���qpXYO�L����A_�	K�I,x�P���#�0y;�� �(��=�]��"�Y�1Z"�:��6���+I�*�R4������!h�Bsuv����^�f�����X��o��U���b{[l�Z�%�U�e��<���~�F�`�0����"�?�gEAU�W����}��~�n}�@*hAT�5���*���������1L���D�����K�����
@�.����b��p�0���nH��3����6��|d2���F�XL��U��$(�����
b����r�6�� \�H((T
���>�<����H�F�	��BdI��������s.��h��^.���=����X��h*�����J�q��pT#�������k(�3�r�,�]Ir�%�d�{�?m���S�2H�f�.c�+q�I�Xp�W����I4�_��Pa�#*����j�R/bdh������3�.����F�;X��E����i�(:�
\h�i^�&hR����'����3���7��|���c�@�i�������)( ������p%*+���%Y�[�9���������W^��cj��-i�'��JWg����bR%��%�����B�����7�A������A����\�mCv2[F�(���)z�����e��	QH����r���%(������M����	l��$�!�����@���(�a�������k���d�0�1AFc��"�����n��x%94)AGws���8O�����E��]�r-�frUo���X-1Tr�32�"������`TJ�%P���[���Z��v��9y~S*�mJ���MuHm�:��H/s1%��I�Q�<�P@��g��!��Z��v��Y�����])[�Ce�!2^@<�L2��Y$%[�
|��!����d��e��;s�%�L��Jc�JNO���bemE�)[[��5�n���%����_[��ok�`mMB7|�T@u�5��.����y*F�<"~�!��{�?(��T��	�X�N{'�}z���9������6�v��.�!���%]_��{����r[A���IRr?zcu�na�����9Lyw_��{'�[4�S��tym^��V{�s;�34�����<���~�	�4U��R��U,�P�d��EK���F���Z����*&;�V.$uI�Vw��SA2.�z�'��;���0�'hn( P����`X>��)$�@��pP�'�	<t��`�@�n���.�'���Ah0��:����K���|L:�8!��@P�g
0
���e��K�:��!(��xy��]�3YX����X������c��t�1�G`k^|k�a��0���g,X��ReD�9�d�}��qh��� ��Q�>�2+nt�Uk��a�?���������;�R���/�eD	�O�����n��lH���:<��\�v��#]d3�br�`v�_���2�'����'RO��;�i���dJ��T~��&d7��w�[{�o�[#�X��.��b���'g�L��o�K�S,n�����yKVHw��=���%<����	"��1%���*�����8(d�����$�-��M4$���!����n���^�����*�VA�z�/�O�0����� *��V���
S�6��h����i6%Oy�����v������]AY���Q�T�������#��,�;����`��)�g�=d��4��+C����U��u�����6T�����hqc:v�������8�Hu��j��2R�������I4OnV���?�D�����'�7�9E~�&W�6���oES�/.�
���Xg��q����.Y�����[q pj�B�����4����Sb�V��rKVU�9rT�`�
1EUs���Q��+d��
�c���*�m��)�����H���D��
mK�|�����������������9f�~�E��W"�#yk���p$}#���<����������V5�8��	����<���y�����R&����G��Ri�x\�9�s��i*��A����l�����������;��m���]��������D��SUy�I�e4G��L��J��������Y�����������u�XfE�7�������d~�?�������F��`���S�L,���"������ ��L�r�`P?*��Am48���R�-U���R�Z�3�����JY>�X~��y"Ho��+��\�a0�
=
%P���u�h�q#�������K����l��%�5�l$���'P�K���C��3�����B�5�����R������h�V�b�d���Z]���q/��-8���y|q��;�U��!)��O���q.��4C��m'������JA1���hN(���,�bM�Q1�F|^�K���X�I�+�u���nk��g��.o�Z���*f�w��K�W�X�.��v��"����,�v_7:����w����Y��:��*��rv�j9����az�Z%�?����l���������6�5�Rd�����>N,%2��!���ENB�P�������i�]Z���Pd�r�}��f��Px�����������]S����������_��#��u�'�i��F���f��a�����)Ya(�q/����[��\P��'�7(1����H����MIY3z"~\L�]��MzP`N����|7����s;�E���
b�����fB
�M�`�����-�mD�y��)�C�V���R0����u�1��t�Q�����7�B��NK��:��%�hk�{��R����y$c�
�6�Ss���X����D�9~�n<�o�����|_����X�D�2���2K�A}?V��/�2������1�c����/G�v�.��
B��8�������X2 �G�|L����XKn�"|��,������h��9�5��~��Sj(��I{�Q�C��Q�Mx,�E1%2 �n��?��.SC!���r���
�x�#�P�oZj��o���Y�)Ik;~N�Q�-�u�J��NN��m8��[�u<�FMKrT|)gq�%��2B�N�>Z! .�=�4zC
�;2����QS� ��.��%����������������:����A��(���
��`6�}�"�������B���d@}�&[�$�����	�k�~k��Q�$�,�\5dK��l�a�3���9�Y%������3�:����'*�p(1]~v}�{����z� D�$G;A�#e�<1��z����T�����p�Xda�U0�c��C�1���tL���R�B��N��-y�vu���f�T����p�]�_�&�O%T�&�\4�������E��V����E���=�����._{A�#�!���y���j��o%�����nt��8(��+�����d����n�d����rXo�7�����@���Z��{}��@p��M���� ���r����O�z������F�yrr��J�d����j&w0���El�6�Ea���%l��r41j�7�-��Co��#�����@m2��7����i�����8h����w�^���0Y��&g����l�x/�z�c�M�c�r�1��v?��� ����t��������kg����k�������^op�����f����X6�VD$�p�
��]�����������l��-�~�V~#
�(��(��X� ��"$B��V�'^��3����5�D��\l7���W�~r���d*���}��;��$ns,4�x�n�L5+9d��iw^����u�
�:i�F"H�,{�������
 ����Hc*_���pc*��:���^�����`a���n��x�����7�o*n�%7����hO�����
O}�|SUS�Vg�1�:H,��@�(u(5���v��?&}W�Ww%��m�c�S�B�uNTH[��R2���}��Ss��E�n��k �[r����,��w)���|�9�s���YB0�|���N�-
������
���6���Q?~���<�Q���0
&3� 9"g�}��=Y�������(��`�xFlQ��6��������[��F����E
|���Z�*�����#�|b�:��sp��;�e�Vd7�=�h����,i@���<mHF�����=	��1�V��IH�{S�������j�{|W����u��Wj���&%8kg�e���Y~,@68B`d=�N����B"�����
��,.^o'����1���m
���ML�������~���Y�
�oj�75�fm��>\�lM7����@��\w2�s��)�����5�������Sd7�}�z��C�wF�I����<�4�Np�d������#2f��2f"�0f��7cV�h���1�>Z�~��jJ�������n����2$�l������i�
_V����c��_r�_r���7F�,���#��}�%TV^��H����u�-���)U+��W��e�e��:iJb���a�]rS���,�3������!��'�BY{�k)���������0�=i�����B���,r����>F����W���e�^�4�)m5�����*�PH�)��y��,n0�/�)R*x47�'�J��W=����%�!X-o%�Y	�Lr��_�$�������#�nD�G�H.r���@�%���6�I��
J>c����P�=�,�iY���`2;��^���Ho0���n&Fx}����J�(;(������Fp���&���!������T&;�����L�:�[���Y�QCi��7���n���A�������D���<YY%�
F���!xO������=���M�j�{��FT�q�6~2���%.�|
1��*H=�����"�����~�h��Z������!��������p���F+���A%^���$}��R��<<���A����+bJ2����c4~�
^��EIQ,Qr �;4jz"�(�g`�8Y��t,�H~Cm<W�@��7!�jQ���\��*c�DGE���k�Ax��D���7N9��R��
��XD��!��[��@�����A�8� Q!��5\I����Sm#k����V�nf���2d:�����6�d��L�m��lb������oc��{���=Z���,�����j }�����za�����W�Y���z7�{�B�L�h��hiUf���w�����G��r@�hJ��j���(Ih?��K�O������X]
0002-Add-SUBSCRIPTION-catalog-and-DDL-v19.patch.gzapplication/gzip; name=0002-Add-SUBSCRIPTION-catalog-and-DDL-v19.patch.gzDownload
0003-Define-logical-replication-protocol-and-output-plugi-v19.patch.gzapplication/gzip; name=0003-Define-logical-replication-protocol-and-output-plugi-v19.patch.gzDownload
0004-Add-logical-replication-workers-v19.patch.gzapplication/gzip; name=0004-Add-logical-replication-workers-v19.patch.gzDownload
�`5�X0004-Add-logical-replication-workers-v19.patch��yW�0���)N4wld!$�Z�2W������8y��xh����`Y��������K&��x&��Z�N��:����C����v��{����������fo�����7o����N}w_��#�
����67���������Kh�[uN'���A4
�������8�O��������`~������o���
�~}�Pk{jm�[n�:�v����.���S;����^O
����$��4������$Y^^___V����L����`JP%��/U���W�Uk����]����;u������d:�k�Sy6���W*oe�-@Zw�����UVk�/��0E�x�J'����d�>����$\�9�����$$i�g���S���������t'�8�:�>��X�t����F���I�1��$n���Z��~eA���.�����(����?a�N�r<�^���;�%~�Ty3]9|vg�X������[�I����4�FyS���������l�t���mF4�5�
��a@�lt�y'A}k;3|���:��+f������m@�����j��3��6L�n�����5;�����z���u��P�A$���j���g@���xO��v�����<	����;���7��`�lo��X`>�b�QjB	 rpnD�.��c/�v�t���0�vw���!����i�r�v��� 
�� �/9�c�������0J���n��e�f���;��BC���mX�n����wNe����T�KW�h~�������,{�`�~�v���9xgv��_{+�uNe�37����)�l�L����Z�T��r��9�����V��R�q��h�r�����	#(��|��q�T�]Q��_y&�9�a^�u��8�6.��9�{am<,�]��kOB��n���
�4�@��4���7pw/m���it=�'y����W���%�/��������+O7@(h���7���>����+C���o��wc�����}��;
g�(�$����=��p��%��U�6vT/��d}uY1��@8	a
6�vvc�sk.���Z~�Ru{*Ru}"V^��~JmK?�������!n�����=*�-z�*�1�Q!o3����������W��0[l�H��������Wao����^��w�w�2�K�^�}_����Z��:���IH������Z��tL���M]��J����t2�N�m{����>�n8�T7MANJ���:4��L�= >j
����N�����EK*�2�����G~�$����A�U�h����J�Zw��<���7d��:��	L{��;&��?�\L)��
�G�4]��t8n;	Ck~1T��O�\(��A+��)��0\��@����NuaV����h��.�Ww������AMcX�(1����3L'2��j�Ni�fp��d���PmD��/��8��5�^G>y����s��:��$zp��v����Ys�G��h
�F��x0���\���|��8xP�O��tr��)��������_�*�x����%�N&����zj���GKk��Ob8	G]�,��J���w-=K���� ?M@\�t�V7�,��9��������$���N��W%���~�i�}%�|s?���Z��������Qr�rw^#jl�������g�g?��8��"&�� H�w@�aG����l�N�F��N����[��7A�]0��jn
�`���iU��O�RLf�i4�y���<���j�Vi�7v��"�L0�lL�)���(�$*u���]�(�������^4Q���,��������]0��Jv����,��&	��)@�v8H�m-^�:@P����QOWNU����s`0R�&y6����6{%��aahR%�*H�m8����1>�
Ek=�����y�Q�ZQ�sA'<[��m)�v��uY��0��s�9�U�J��D��&i�R���<O�a�s��
��(��7�]�����hzcr�K���Q?l��E���!`4b}�#��T|KLU'���`<�Yt�g
���$E�����m��K����`sM����N;/����gQ����-�H$���!t�{��!����w?2;���n�W�:5�����d�h���Y��g=�z�:'w+e'���)y)������A�_�m�m�t��JN%�z��� �b�%��O��ogW��e���u�8U+�������B2
����i��Ke���T]3Es��[�H��B��� �5��a8y���Y����;}k�4k.r���5��ih���[��;��^�����l���������%�`�������<������Z�����z�^^��*`k�����q�gs��i<n�e6���>��3\�7����S�����3�>/O��h���`��+���	�E	C�ah.!�v���C�mM�l/[�sg�r�#VH3��4'��j���n}{�OK���m�f��5-��x��	�[��n��k��������M0����J�L��������\�O��� ���3�&��!g^��I�T1�E����7%���B���fc�������E�;U�%3���/��6�[��Mh�JDs�F#fA�d�2���.I���no��
��$ ���u�FL���1~�n�w����mCF!���?O����	����a����!q��������j�#i��&��Y�pb��"R.�$���x,����Ys�1��<Z�.������%���N�
��~4I����+G��0��k��c�Bu���H��$V��aNN�D�����T)��+�bU�m�`�
|�FxW�M8�����?���2��a!T� ���\h��t�-b9\9�
A�
z8-�Z���������&�]�����7^V�"�N�Ha�Z�SUqPw�$r�l)����"����ZS��������K����E2�d���a�0��8��R��L��P���V*��N����		�m����I3T2��V�L`3�r%�����Z �@����M�k
-�������M7�\m�[�s����C`��@���C@B�,�"��Clg��P`�������=�<�����hB���k�!�l�.���9��I0�-�D����K��������g�D�h�����A�������w�m�<���g���d�����9�;��6!Y#TN�:�7���5}}���bCx+�Fs����&�vH��x6N<�c��N�& �rp���0`�.�Bc.4�M�T���N�7�;���N��t���`k��I���F��a:j��ZAh���CT��,�v�X�'<J|��rk��M8J@����H ��z<��$��y*���
��tz�k�
OS9Y�	�9wMCW��{U��E������(��^#z��Dix�K�tn���V%q������0G����*�V�����g��bc���k�1����6{��� a!2;��,��8�t��=�.t��O5���Y]�g�f_��p����w=�D�{�y'`}AzK�����G�b:�i���3q�������7�����6!�2"�wZp�_��"��g4��{�z1@~*
_�~���B�<���V������Z��|��O�����&s�
�cCv�<G��t�6�Hj��K���=�1"y%|�
�S����>�Pd����Y���ON�����FK��" ��wo��Y�A4��K�x�f]fNn4C��x����S�X~�<k5.�`U������U��Y�E�<n�4��f�� �M�L���aAD$t�0?8�gX)|����	��� �����-f`�3�e���yt������Q$�-L,�9g��<�$i�dg���mF���
��1V�
k�z��,��p(w��!�����C���*M����o27����u.�!�J����[e��%�_��p�O������a�%���rm<?Onp�^}2����$|�/w�`H�Kw�e�ZgEc�/_����=>���T��>�<�0���KWNz����9n��x��!��HOk�6G��i���1lm(����
1�R$�(������?�}���ny���-mYb6��J�}�����J(sf��n��5���]
����%���Q�P��T0b�p��8���;�*�Jf�B�5`1e�Ax'���l���M�?�m.�@�<hY�p���9�����N����@RL"<2P~�u+j@K4��N�^��N�hREh�����z�a�u~�n
G���D��=Q�7S��_��NI9�e�"��Cv�rAoN�wQ@����
�LT��������:�����!�<�"H�����c:��!8�n����?�� 
�cvf�u��N��?���:�#]�Y=�����J�n��w�!|zp���^�*w� ���8����C���M��2�rU}���dx����A�
��**�4w+|A���]������/F��$���x0Mcg�m��g�pL�s,J�������
���b& ��v1�:Z���`�4��HH���[��I{}���'#��'���O��JTkUu�g:�2%����+�9���]���m)s ��C��7�2H�G�6���Fk������w�XJC���Q��r��O��&�l	�c����k�x'�:t�JV�kuEW��cP����x��������g���}�S�E����hK<��H?-b��
����.r���,1+�����aL�3���V@��=�!(��
��m`�/��A��svi�������m�����D�N��@��2C���0����O���Wq �m#"���Y�L�2G2��a�$��������@cw�,���p���Ix$�����sA�A�lz)B�;�)
�'���	��x���o�a�`-T��q8�3a�n�{m���������e���+��<E#�)�s�N�P����;���5�g/��8!�`�h�b�������D�Z��iK[���X�p�	����K�X�g<��w�1�A�b`Jm>*�O7LI����>W���'�F�R��d�������q��!5M]���2��#�=�!�;��'����| �u���N�l�s���=�t
v��3�Iw0����S`�=�}����-	�$)u���\���^�(s�z��(9gY��a���Zx�	.�h������Vy����b�7�|�Z	��
h�KLI/��o��^��0����6�0y�&%���f�3�BT���.�Qqj����	�m��Tr��s.�X�Uxt����,}��,��}5Z,����N
�I�yilMyEMm�����vc"{����Y��a����X5� ���=`�
Wc�f��Ue|
��x6]�Q�c�V�d��h�_&`���y\9tW��D��7N���H03�T�Ch�K=]�>a{gWk��F��x��f�,��Se}�|sQ�LK��BW����������3����mJ�A�!HlU �B
��71Q�yl�W�3���*��mp�=��.�ANf�e�w@L�A����B�mA)��?C7�t%�(!B���(�e���2��x���<�!sj1/�n�v}y�$�,	�`yTL�h�l. JY����s�f���e�c��`xt���n�~�A�\��W��y�H��"���V���X��iU���q�3xH���4�_������ZL����t$�JO'���V�:�Vo'������.�ku�c1��)���C.��e�F�e����������Ie�V?*!M���7|@I?���(�6�;	H����rKv�4�����k�i�"���+���`i�����T�F�0q,��!�����J�����5�}�	j(=�0O��Bz���L��O��ea��W��3KyU0���MC�q��4��3����o�(���v���R��v���.�B������Q9��\���"lz.0'$��G��o2�"�i�L�=TGL$�EOI4p
��V"#$���	�o�B%�T���)���?��`%��n�B�7 ���M���4C��@U����+6�4G�3��oCTx]:�:\����/o�S86F����1?����(��X.gm;pDez��p�z fz.��k@����e�s�7��G����p.�FwM��������w��m����z�� ��j�s������7�l�����16<��������&T�������Z��"�%���m|�QGnp�����j��������9����	f�l���Zfc��s����qZM���=r
���1G#�Qw����C�9]�������{���8�=sw��.��WS���g�kv?���6������Fl�Lg#��`zb�tG����2&���1H6����!�c�-�L���������������u
u���z�21���� ��|����u�C��3���=��o���=���X�Y�������1�`KE�y�s'�-\��m�'�20��Cl��2��a
��Dk�I=$y[
���{T��%
�,���b`�A�A9~Nq9RH�� ������ek���9]����
`\�W����5���������`v�LI�)I�p��b�U]�~��n5������%���A�l��'��!��@qS6���������j������=x�� *<���B�>��3������9t�@u�w�kq�l�r�jg�+����h�"bS\DM7�E��e7��S�Z|q��;����Zm��z�e^��r�t�E(���w�����~�0����6�����y���w��?dg�������}��u���c*���I����DJ�0���X�h��:��	��8T�p<~���?�J�4��1v�zI7����D�h�h���C!��m�����]�i�@��B��L���%J���r����*T��F,��]-�����	�.b��:�&�
�m�X[�����c�/*`��p D�6�S�<���$@�%%�c���q�p�E4����R�+&�)E����CT�:-��Ek7������e���A����������z�G��c���q,xu:#�EO����Ar���h|��G���v�Q���~�nP����y�X�s�pt{��? ��/�I+,r�r��x.�0��5K����c3\Z������o���d4g���e�NH�uLB0���q��T�~�?��w�W&���H�6?}fO{�\CNQ����dO��Xyhj�@�������=NH�k�n�DB���V}��������2�Kn���f�������q��:J�!%�z����kt���=��w�d4���d"H����D���NL��#�A.�������>���XJ�_���Qp�PJv��/�����|G�����;}v!����Y�d�R���l��WN3�r��=�S�Rd�����A}�V�	�{�z���W��P����Q�/������C���������
O�rT[\B��?!q�hZ���8���
��w�ng�V;8��ou�%�*k,xe��;]x
��1��Q7���I���3�8�������(�#�no���\����#�"�2�
��0��V�o��(jX��l�i��������_]�M��g�,�E&P�d�=J�^�}'��S�s!���d*F�LY��`������7{��;���o��r��'�Pq;&����Ff�t�o��c#�A�u�xW������8L�`�S,Fn�\#�"F�h���o^�7��6�o;{����m�������U�r���l�w��������l����8RbW0
o�9�>[����T�.��~�"���TD�%���>��m���e��!7�'��A4�A3K6��v{JU�-'h�b�����u����������o�j��~}{��o������_���"������x�j�%�a4�����uZ!�aNj	,�VZ�&�(l�Y�����KJ�\,�3��o�b
>��L����_yG��e�t8m���������`�B_i_^\�$9/Zb��_��+��}��$'�
Uc���Eo���P�\%Nh��}.�$�M�����3u����I1Fv�2�u*&x��������������b�,l(���Eq�����T���2D�j6�
�L[�XE���C�TeU%\���Im�T�������\�x}y�"���F�����8E�������J���P�]�L�mV�QR�U�s������\(k`��If~����+�_��3oY�*=,���h���Z���Q@z�1���\��������\�a]�kJRNv-�izT}�_��wv��v��)i+g���&j�
�K�K$MM���v��7�����a+<Q2)�(]�-����|�����q!&�u��d����z���U"�7a0nw #U&���.���{�6�!����D������M�6���YPa^�`H���N+��$��^0�K����U������������<����m��zU��B��c�e�9��d-�x�=`��A��A���#LDW�Hf��H]Lb��W�5[O�FJ�����-C�Y?��z'o��]2�����e8����Uu�������u�����:�������B�9oI?�^4;��M����[A��:��rI��c��i�du���Y_�|Q�j)�<1��+\���J����}�ZE�������K�������u�Hj�V���eS�v������S�}\�m[���SL��-d�V�)�Hx����w�T(r�,�d1Y�vt��� �D�����`�����7a���+Tm��:�l������������!a����1��?'q�����?g��j�;�[ �������B�5��@
��q���=�S����qOMH�����\_{����t�M�}>)(�����f������@�l	�|���9��j��n}g���9m�`�S�poNL(J_���hz1F�b��IU�~�_1z�T�6�:��Qp���#u�������,N��u�c�����s�G���<G]����:5y4�	�4�lE�sEg��`�������KNx�Cq�x4�uX�I�,�C�bo��\�c�b�4�h���OB;{�A[_�V�+��3utz��b(�!W��,-M��l22}6�J'/$��4���QA�j�q�s�����'�6���5��gJ|"�:�]�%0����y�k&�
����p���(�m�������8
�i/�>��h�Q���y��p[S�&,�6DK��:�p�-�� �k$���S��7����Z���e�Z�0�����,Zi\^�_V���R%�L�@����q�
t8�6�N��Y����n��W�v�
�X��p���
����~d�?��=�ycr�s���H9�����JV���%oi�+����:��M\Y���I���
�J������Gzxr������*�9�|`jd�M�V�`s?���% �
~�s�Y�V�>&����Ga�*���
"0�5Z8%��������Kql���D����WHV���<a{�
'*a����P��������e�#�e
JOJ��B	C��� ���&Y�'c���6e�x����w���pG]|����d�Q�������RL��H�������x|7���������om������+b/�1Z��cd�1�sZ�90�FU�W=�(���,a8�x2�3,F��r�'s]pE��/�(G9j_}��B��BT�����et_��������up��k�%���d��9I����Am�>���t���Z����E���r�}�������a�o�]"9�s��!}�}I��s�c,�����6��e����	I[x�	de���;�0J�n����������$��
�a��0�58� dR���7x�{?�!
����1����qq�C�/n�!&�����\�p��R������m��:L]C����8Z���0�����E���Iw��p�8���)��;1F� ���O�J�q�F
�~ )q�|���[]^�X��H�-Tl<�eA%�����PN^(L�q���8���(�##b��Q��*�'>	�	R=����	7�%�A*��xT�������;�!0��#r�}��a��a���Kyrb�=6�x�oU�>������������<;n�p�8iWUz����������m[7]�9��B�����H@�W,��*�"&=e���[�@�P�
oe�q0���7
K�UX��+�n>Q����3r�;���
=]~m
Pe	q�$�B������-(p}�d���Q��xL���o�U�����������t�����q��������$^�aI�3����tV��/l��8�9L�z��5J���/��L��(v#�{� 
i���q�0CX���X�������PW�jm8`+�wU1l\�U*����N�5���w��?�Y�q�c���*5��'xB#��D�$����*�U�>��Ef����a��o&�|rU=2B,C������&������������*��jpk	����C��4 �������i��������"�x�hCf�T\�� ���t{X�6�KOQ�K�f��a-y�&=����v��w�P�#��H#cU��������OG|�����
������xb�O�0�uc����w��(�TQ�X-=��������[W�s��S�)a�`�����L���&�/a�x������������DLw�z���9E9��(>�K
S����n�����4\1Ac83Yd:����?��o��"�I3������TU��.�����zh����{
>�P�����K�l��:��iDX,|�3~;6U�y�c�R�����$V�&xW��=�&�;cO���������b�Dx�7q��X��Ij�#����w:+K\
�8����8\5J����#��z<�R�	�TSa�������Pp[���j������bP��H�X#��eL��	��	�{�������5���b�Q�5�Z���� ���U�����y1���U����|��.���n���"T��:��Y�c�����h�����Q?P�*��O3
|�P+���8����������(��F�>M<�!�;P���C�����
R�7�������?�\�i��#���uLv��i��������O4��ql���,Z�r���R �R�H�^���e
�
<�0��~�__�^�/��-1U�(v#�7��Pd��T�J�QH#��m� uk�@c��,�9��[�`�F9!��d�2xr�5>6�}0��WQ�.W�G��h�*��E�;
�$��<��}��
�1���2;U�F�����jA������w��<�U�Wj�0k#����"V��Z<N�L�I����@�8�I�L�J�w/BF���<������~1��?���j��I��k���Px�V9~r��%��j_�[����I�������y����s�7Yq�U�3��4�T!��R����d*��W8�Y.�1]��lE:�d
ym�F&�N
?G����k�Xz��v�3%�t���'�q���	+x��q~ f��
Pl�����9��	����:��K�(AV
�A�w����'!�K�OX.R2.c�- \�D�L��-/�ar9���eG�u0��g���P�P���h���3�����:���v�Z}�_�
j-j	�#�R�{�d�o	��[�����h������U���UB�I*L9����5�AGE�*�{Z�R�;"�����a���%�*-hTJ����C ���V�7�Ba�v,�D����A�d�(��,,Y!�Oe�z���0��~<�����������=�1���F�I���dD�D�r�Y��NP��w��T���|��t��L	O�dy�E����������E{�Q^0J����N�����%��A�S����l&$'W��JUUF 6�,g
���������p��_V����T�6���$n��$V�k�Q�j
��L`_J��hJ��8w�E���Y4�1�R+��E�sr��S,���[
�����
M	�sd�2�%c|�Y2oJ�]���(����WAl+�5�7S1�0q���:,j���L�5%���6YR�/nR��i����<���I�}��H�%{�10����s��_r�_r�_r�_r�_r��B��9bh�����*���8{����&.�����B����ZC2(F���Yd�N�1���F`Y�#���p<dL�������p��{��a"��5:s(��q��IU�5�R��R�MXL���������0CL���&���G��3�}�?�6�2�0m���X��5��|�u	df�X����nQ���tlZ�S~�Nn)m��9��=��yv��O�?�'EF�l�|z��L���������.��(~���0�JvZX���<�\P�������E��L!���U���U��g�����&�?�9�pbw��W4Y��B3;��AV��7C;��)^mf����������]���5{%���?�&��8.�l������Y�e�8�������X�A��Yw�k0���%
�<��0�_}_E�*���`���c��HL�����=�&��(���pv�0�@g 6�z�Dy�6���Z�y�`���}A*��Yu����J�Q	a�2�`�P�D����B�x� �li�?;�)Fm�'!�9�����I�RK��w�K(���DZr�
M��#�y�������~2���}���Qr����#����1t�Q�����GR������%�$��d���������I4
?�4r���o�C\WkZjt�������^��
#	~�,��t���N��r��MV���������k����������G�iwL��B��"����3L|/�<g��6��Q���)G����3����2g�j�^���%�\d��K��s0�B��)�����\wg�V|��3��y~��>��y�^~�k�iX��T?����s^���sf�����9c3����������~�k��s^�����jup�]�U�_��Yv��[3�/����i~�_�_���3!�e�������&gr��\K���pn���������Z�wp��,����J��8������[������4F���|�Tk&�SIY���Cj��^2,x��oh��c�7S�d����!F8$�z�������wx�.+1K�>�L�;�(��1�H<���A�X'�2y�(��	�����
g��y[rE{�1 v����3	P�L��������
yd��,��W<���_���a����,�m���~"��b��;��s2t3�'w�,��b\���,p���������-
�z^c��IP��������@V\/����&��I&�fo���f"�t��d����fr��"�������91�������_���HU4�����2t�t`+�s+U������C�P���.Z]�Qm����\�a]�&u��~y�"���p����p6��t�p�Y�\K��N��ot�b\���ox������:��~����j����n���5m�RYS���monS|;���nb��aF6�N�:�P���%p�E����U��/8��oO�o.��l�6��e�QA�5X��7��
��hg�<�v-�
���;i��>;��4��iV���dc%�(E�^\��8�e�J�f	d����8����8rh�
t0��`2�`oj!���S9��v;��fN��pZw�����m����tt�	��#{f;�"���q~��D����a����s>�{�G:����$�lK6K4a*	,�\w�!�
���' p�o���OT_�U�lqB�Y(8�0Ue�b2� 5b_�������b�T�8�������Ie�������z��$y"P�_jMuk�0�����=��J�S���d�%J�i:�����
Z��WC�%'���+t���Z�av���}8�����J�0.��u��fd��=k��{���������AI��������m�8���w2T@�s����Q�C���rX�i�x�����X�z�QN�s��D���=����(�|��1��k�m�>D��&��0���K�Q<�c3��U�P�*5��tiZ�H�hu��+1�P���*���	����"�a"��Ec�P]���' �������)�>�73��O�*�.��Z4�s	��H�	;��a�4i��-T\>B��6�i�lg����V5��4�Jk��o����N��:�&�/����$��(�m��@�D�'�;eL�N�O���+�N�������?�\����LG<vgE(%.��x��(%�w�Y����4�NL���|���G��N����l�o����gZ�>�=N`�Uz�ksT��Ep�Srm��V�p�lo��rrqv��\C�#oY/)���N!5��
��L�7uETx�U��b��Q���b�������5�m�_"�A��`��E���y�t�sI��D�N�Ur`#��oU�������/y-����Gd4�Y�����"4�k���{�����,��=8���r��}�e�����������t�w�i��b��6��j�����X����;��ll�%���8V�
���,�Zi]^^�EI�kZ'�WZ��6����j�6N�g
�����������������|�\���}���)�j��r-�����Lc��
dW�q<)��Zq�`��*���@�v���}�RW���r�f��P2���)����8�{b����GxR��g�'R9zHb|$3������K���4P,R5Pt���~�q���r^h��R�P1t/�/q�m
��N��=�[m�Eu��7��@Q%{w�����p�����g2�r�r��*����s�<
:��nSN���p���:<�����������>��e�W���]4�<�UF�>�4�P����;�f��:i���	 ��;(�fFRC��3
P�G�>�=��ncV������=�-d��S��,\t8�p�1W�F���
���p�U���w���k ��$L�g_GP���u�5��%���H�}ts���V����4m�fx=�D�bB�"4��'�!��i�72���p0I���,��������FeN�>g11&e��	�yj���G��>zG�wx�/��jJ�4r��������oS|���'hm���pu������M�Ua����P�.��s�x�=�+�7��8%�(+j�l=�j����N�/���66GY�.���3b���;��xyr����}����Q��X��op��K��^���p3��z7�i�k:�0|a:H�2p�'a<����1���xk��Z��+�_�?#��L�� ����s�� ��L*&hr��~�ffT�?�^(�wgwkg����{NWN�������[9�q�k7
wIL�FH� k�������o�/��bA����p8�j��:�L�K�Q��R��gIqc��Y�mF�����m00�4f���F�v�81g�v��e�4'�E�E����QJi�i��r5�9���>�(*�
�s�iK��9������0�&/S�x����It=2�K�`�s�%`!��ec�J������d����� 9�/��Z^�u���y����4�)y_��n<��?x�d�.�P`��QU^%m�����
�x��Y6�;�g5��d�c���N8�
���u���M�(p�����(T�����O��g�(��/����N����lu��r[���,X�����e��i����3��ZFU9�D�D��G��-z�2�H�q8y%�!��G��tiI�h~[��4�����Z��?�<i��|q�h^^��>m��h\b���y���r��G�C*�]�d�����}4�[�.E�h����Q��E������%]��v����qy�0���w	��C��z�[�R��������x�~����B^����`bo��&�����H�5�����c�@������e<�60�s���(��Vcuc8.|2'��X[^KO����T�F6��
`C$L��H�8D���C�,��)�^����@�}?5���W�u�R���LM���}��	���gS��Jhm���J��S_�'���(2I����`���.:d�W$= �#u�^��9�����I����:3��C�"�U�������{�/�+P���p�(�R��������R
:X���ER����eax��P@5���6���������"�E��(4�"{4���Z��v�8m��AY-�F�C;�)��[5�O��G��-��Z�;%�Q���L(�rQ��CZ�Z@����a�������7JGxr����`J$b�XOF���rw0< �N�{i\��._]IrQh��5nN�@�=d_�=��lp�Cw��I��8�~&z�U�f�-]��<�����C�E~O��x����0,���fKZ�c�i��A�Eb�
��oC��H����E�j�S������]7Jo^�@��Y�.9����$&���K�A��*�"gER��oE����������9��S��<:�?�/��iD���%��-�e��<���
plGo6��-?���[�Z�,C�(��Rr2b�`K��0u����]M~zan��/����&
aV�UH�V��o��W�������$4��P�S�/O���{������YA��u���'��������v6�#m����R��w��s��x���#=3.����G�K<��I��E���<�E��Nq�A9I������d9ek�g���.���
�Cyq}��[$DHt&k�>�u��<yb�����]^:��q�}���e�yL������
����CVzX��yHU�`��P���q�����2�# !�u�I�J���}M��1M�|�����[M�<�	Q�Q��{��][�������4����|��k��1Z�kw<��8��z}�L��(�/aQ�Hb�*5��f^2��������&�
K�d������Nh�p����z�V�����8��]HM00&"�	��������Y�7h�}u�o\����������uuz�B������wN�:
�U���p~�=��x}��*��y
2�p��$V�u��0	�Qgo4p}
6x���"E��&[1��h�Df(@+<$S#��r���P�,%X^��x����I8?�M�=�g�E���l��������0C���kk��K�[�WA
��bG����Y�H"�T�� �N%y��Tc�\��\���
Q|K�wD���w/o\��L�2��<��T�;��w[��Rx��P��z6��x��sv�Y�Ak������������{���i������'������}��n3��l�D��Dd���Sv#�xh���1��3��������^L��T.��
�
���{��[#b�I���=�������l�z}��O��H�~8j4��r����P�y8R�S�+4��x�#8H��J���n�~$R�n�6dY��l
'Exk�x��v����KG���
B��u��/'&�/��fI�y�tCB��oP,��J���X`�_j� �oP���P�b�@��h��!�H�opL��
z�q����Y����.<�a����Ph%���c<�l
���K�y�7�6�����$4� �eU,�
xg�@�4"L�Q�;�|���ZD��������S�i)##�����?z�8� }��I~_�������!=��k��,���i��}xt�h�Ek0�^}�8;n^�8l�z�5H���i��E���^;BN�2��e��K
���������p��/�>h4����+�(�'0.L-v�8cQ+�!����n�cw��px���qv���9�CU>*(�0��E��U;�\+��`�0���t����~�AN�����3l�'�BaD�o�3C��.'x�����!s�T�+he��x<�*�io|e�<��:3��Y�k���M<�9�0��w))%��0��=-���#U���h9�7��b�<�����<
�2�x�D����F�����/�De���������PG�c��a���n��5_�f�a#3M���* ��� ~e��%��l,��&~4���u���z�7�eXD�-}��xZ�g��_�3Y��(��l8:0�a��0��!�;�ji8'��#��H�|�4"��v�3������xA�d�������)K�o�%w��GVd�A�������c|j53�g�m�d���[��>�	6l����P._���r��^t������EiD�����r��0�<;���W��o��l[.���(�{2����Y�	���"�.��|�\�,9RT�?�����4�\��y��������Q:|r�+�F�pW4���L
��;wj�b�J0��f\eE��jF�hB��&���;���Q�����*�����]l_��w2�Y�Y���r6N����"u�d�.r�TV�5t��L^A#��/�E��%e9����Kr���Y��T������)b��Ub��V�����I�-1�(�����d �7�����Q��NRi)�]��^�V�0��:�`|;+��A��[���&(x6��kDB�/b �s;��d7�F�"��B�^��c��dtCq��#�]cU�ieY���>�m��1��of�����������1��	G.7& ������$J�i�1�c;.uz���	��/�^��_�Esy��9��������P�����B�8��N�e���r~���K�,�V������9���>[�����:TK������9���1�Pk4Kb���������<����2]�X��:�������3��R%T2
���������#)��c��1�p7G?g2�i��72R����V%���p�NYh�:����S6ei{��$'��X�W��QI��{WbT+�/9#���eM����:��	�,��2�mh�1�~�	CM�x�����\�[�\�L�k���e�.�U
V�����B_��\��GFGZ��O�a:�����^�oJ���|�M[�k�7�f8%
���|QL|I)�_�.�i�@BHF��B�	f���iE c�HS1�G�5l9���JQ5�V�:5�2Vz��R����J�i5�������w�Il<0����������&d.c���c:�G�����TV����������7�M'w6N�&S����[�K��
�@�o�l���uN�P8�� ��&M�d|4��������5�=��B��rJi`��oG���M���**�0���oV����'?�h�S������`N�B�.yA�Y������~[Z����� ��	�Kh����R^���.�3<sk�~\�I���n����r1������!���Y��������������>�`�����!�*�	�
��
��,��5W���N�	����n^��i�����0�kP+��d��qC��w6sD��*W��[�t��r�A0f%]�����6^[/�i��������g���
[��D��8Wh��0�h`5�>E�������E���y�\�b������UPg�5���kc&�o��w����wfq[o��a�P�q-X��g��gb��pM��d]#��g��ID���
3���o`��)�i��z}�G7�/�r�L��-�\���vcw#�E�� dm��s(�&�)�WB.tqK�b2V:0�C���4���e=�\tC���t����m8
@�"�1����]���FI1��!��`~C����t���f�i*��|M�*pf�'��(�%#b�	����}0�q#]���H(�+.�^�[{�U�-��,��W���N`+b�^�*�[��z�-����\����0�>X��Os�bF��6'����f.�������Pa�[X���p���7�)�,{@��t`���z9r"%&2&���
q,]^[���$��o����
z�n�����a��Q��G���F�-$�0j�Gi������h�7�yV6�?j�pV�Q��G�D<z*�����8I9P�Lb�r>L����da��<�X���$%��,
��Ma?��/���"���DA
�`��qB2�'�,�.WE�V�y�d�|�Qx�fr(���������/a��l��W����9�Xd��`�����P����0���c���1���SmEJ��s��q
�I`��^^�����_|C�6GlA}�1��6y���e��������\#�����&QVM!>������5*J�a�w�P�����WI��z�b���/�aK�}kJ�+/M�v����J�����G:�����HO$�������-��7��E�B��k]z�[	uA����r�UPCd2H�I�}�n?�;�����q�-�`Z �7uf������m�d
,fn�QJ!B�m��3Me���=�.=�������yml��59�#
�1It~i�6s�S)�f�jVfc����3��f��1Z�AaX�����@sN�n%���(�5�!�r!����}�x�`�[8$��!��dqc#$�~V����g��0���n�^����
/�ks�OV�K)���p��+����~�;�v���+�x\��;lnT���k���V�l{��]���D�k�&	E�d$�dt����'c��I����u��i����@������Mz�.���:��^M��}O�����wJ��Y�o��B�����n�0vn�J�����x����93�
k�u���l��T4��jZMAi�0�[i��g�m�CXz7���Q�������^�D�dj��3��sm�cH3��Z�:m���9�@��aXF
����*�g�E���H�;)����������Z������^Q��X<]��)�Ww��|�e2C8�L|��u���b�[b�v�%L�t����o��Q��"��/Y�R�O��j[�Yu�����U��"��b����������Ku=/��:�Q����o�c��/a�n�T��
���d�������
�J+:��d�������DE]���G,G�|pu�o������ck�R�5r0��h��Y 7�����*KRG�m��U� �yds���VJ|+s�9Er���P���������y�_�K�l��>�������c�x
�wsxj��G�nw��O'�Af�!c�q�
<q�H���1���\��M�h�j�ic�P���u�F�����a�.rr��K�~���;�,���	�����Kuz[(X�� (�?�����@&$�M8���������!�8��.:��{��.g4�K��4%S5����VC�L1����4��_���tY����o0K^�����2���X����/���������������������l���Wwc�la8�������V����e��E�z���g ���6��6
�UR�]�
�[4���|�0g�[C���"����r<|dL�
��H��Z@�2<���'a����k�J�g�UVOoM���v���z��1��%���_��-T���J��HrW���U�Y� �v�oAp��(e�?DPC�
M�7�ta�hU�Q����������q�![&���@��1��7�ySK�h]w./��������Z��,f`�IF*��cfF���\�/
�E �ry�VT"�|���	��A]�,-u���������q�i������s�mN���T�t�fZV�P\�m/�]T�`���~�n����wE��0�f��u,)�Q�T����SN[��������\��������W����
�8i����������qt~v�����t��e�y-@8��8��"�j�$�C��O�W6�gp�����}���7�PCN���I)����m����#9U+s�Iop����nK8�.^��7������\r�R�P�d�|:�B��M
^z��p��&@}0
�@+�'zV
-Na��<\>J�&�h�%�I%]skK����26����-�`d���k)�-�'���Ks�e���{
��h�����N�lE�s�3L�9 X�V��^u��@)�~��9��k�������T���Z��Q(A�����	�G�_H���w=��W�$G�0�bo���In8<)�J� ����)EN��W_bJ�h�&��4R�9-[�;���)�w���XP����Zu��	�Poz��e��8My��j��5��k{$&R�U��1C������qybJ�*�h���8��(���9�9|�Q���[vhp��]��gR)u5��l/��S���xB����"N��&fJ5=#4���3���T'�q���lP\���9}
9�����	�p����0=�s^���R��� {D�,|2��R8v8���:=���QsY�/�-?�/�x@��4:��"(���'	�l�IRH� ��Lw=>&����������n���+1Pg[�j)��;\�{!�R�P�4�������I�S6����'�a�T������|�bX���S�=�#���JT�V�$q7�A���7r��������'&��Ss��2'(��A��d9�c�N:?>>�F��X���P���`�YW �~z~�����_PU�u��������u��9�������q�����z�y����x���m���vvg�NX0�Q��p��9�Y:a���y.<��cm+�
���"���1��z��>�SkT�-j��%i4�my��_v�G�����<���h�6}�)B����:�W0y�������9g��#V*{�Va������U��vL��5���qI��k��u�X5��5��fL�t�@	#��(����F���H��$
:�aV��iRh�Uu�)n�t��*G6=b&���C��M��� �`�c�E��@
�>��I���SC�!��z�o���h�|{�
�t�v��ysy~�J��>w)�9��|�Rx����AA�iqH�Q>v����Ld���ft	���J�|�]��MOO���l8J�� �*�!C6����dM��n�E�D�}'�����W�Wl�;I/4#�g��Y�K8Z@������Y���u�.m���l�[q:�J������l�����(�0yL�Y�pf��^Gf��9s��+��}%�xn�!�����H|���M$pbwX�T����)��e8e��?����2���$�9��Klg�(�������������6r����4djU��m8�c\U�I(�Aox�eQ��Q�����M�<�)2�(�u&��5�lTI�`49���{��8��ft��9��)�K
��F��Qd1��$
9�JK����vGG��0I7�(&�8���XVj�����<R-@�4����$�W0���F�CR]Udo�Z5g}�;c
�#��;��K(_J�CJ{�{�����)�U� \�\b�|R�D���5�si�����C��C�������yuzx�f+����7~dj��&���Y��B�nkX����?�������3`����?R�~q�F8������������-7��K������<��i
�+�8iZ���.�9�nS����dQ{���C���
l1���UK��a��a�5��O�[Q3`2�C�$�w ��9^|�����x����<{�FN�U=�<b���S�9�w��(�]2��W|8���Qza_�k���v{�[�����~#4	�<��JF�^����~�����<c�|T�5T���cQ�����(�	��T���y�Z?b����]�Qv�v��c�&X%�I�V�!�S�������%oK���E���^�.bg�����[��I���AY�\�������t�!�BF0s�\�A�+����-����k�%��r�M��[ps/�i����w������v�r���.�Q�hz�K8�0(���w����0s�V�m���?�M�2���|���,�������9�d�qI>�J��V����U��/�l��1O��!���. G~�k�����8��3�]pZ�>�E��������M�7��q�nT������\f1�2x�1���~�'�p�6���D�L�y�N�����!����DZ�Z �;�k���,7Q�E��
\9�@+~����vj�&����;�
�R;�`�GM�vQ.�2���Z�v�t�Z�����0�;sc!�S|U��7(A1V�8��M�������VV@yoL/�P�6JH�NW8Z�`�&�R:b�'�:�*n�GQ ��G?��8��������8��]B�gPc�,��Og�ON��������d�l��[�I���'G��wB.�
����eD/:Y���p:��E��(������{:����"�N��B�Na�n�{���d:r|��;��_���t�����@qTW��O���{r�|�61L�����=a����
Ci�Nu!�y���w2@�@}��,K|�Z�1A���C��F���:eM5����FnQ����|��)�1�,�F���s�7�s���se"������0(q�v&�a%��U0'	�A���7�x�fse�+=�C�����9*�YB��	�m�]�F�q�o���	�;�_o�����;;i������ �E�4�`Vay�8 ������0J�Ao����(H����qT�.�>��k�7�����}����T �l;��>����:�d
����lO6��TG�=���b���+2�O7D��1���llz�����@���E����<�@�=�������x�F9S�_�G{�:�����mt$�rz�m	�5��K6{!n����Kp��/�)%sg������Wg��}K�q����(�}U���%�����R��
��4�����u�S^���/�q������#*yE�B2�Y��_�r�C6�
N���6�g��~E�W���gi� �~�t����3��))�.��=�
��7�pzb2i
e=Y�S�ghM�_������$�k��{����?(�B,
�CV8��]��v�8���M�� ���3u|�l]��<�@��=�������1�hO���u�#��#�c+4c{�1�
w��j���g��K���P���
���<Fv'������2��O������V���I2s�^l�h���rSD�%]gh�f��9;y�}�AUq��x�
���H���L��)*Q��=�dI"I�u'!���b������$c����1�f�E����q8Hb��j.$�%o�)~�.��.�Z�/�q�Z��LW�e��VV=M������~�������^9W�dn�X�D���1�Tqu89�$��,������j'��Y��0\hv<��F��,���a��n{�mS�u�	�>��HF���,�o"��B�����}�D:�cA�.s���\��X����m�a�Jn����R}	�D3hD����('^u��������k�	n���
 y���)^�i������3uy�0V����v�fl��]C����9�@�T������0iOppm}gL��^�F�.��;1���p���rU��W�:^�Np������uf/��*9��l��RO��/�������F���M#�9<]��S���Z3�J�v���MFUmj��=Y�=l*������uR�0��3{����`r�=������.���d���U��^P]+�o��Bcd1�e�^�&�
V����|��5'�����
���J��!��G�A[���^�kJ�`�L)��-Z3'��o��g���g�� ��o��6Y^BM��s��v�2>2&��g�mG��'xk��@]eJ��m���c����+[����9\���S���
2��F#���^���WS��T�1�#��I��\��"d(���1���=�)�V�yn�����
Vu�5���V6���1��|����*����:�����x�����y�����(�Mb�N)\��|\ �F7UCz��@ r�8�����-2�5e"�����ll����H����Of���%�m"g�d�������A��XD����0��n���]��GX��?���k"p��\�h��g�1�;�-n
����?�oKxU�8���B�Cm�Kw����7tU=��D	�58�XS9==�� c��c�yYJ���#0��Y*��P6W�E��M~
)4�dr�����������L$kd�bq@b!�}�;�X��&�W��'BM�)<m�l�O�%�O��y��������o��]�����^�1�]��k(x�5���2�[5���?�T��Ey�I��9�0�R�����s�j���*��2S�u���	}hr��K����% G:�&��o�M�n�-V�K���C��$q�����S�A?��8���������
7P��]	`Z��3���~E���}$����������P�f8����hK�V�;�.����!�|����1�@y�-�p1�$|���1�v���5Q�A	I��#��C��<��������j�GH.�I��b�),�R&
�3%����4~)�~L~�i�mpp�J4Z��6L(�Q<Z�����"eN����K�����mj"cY�"�7�/��ao��1TT���S�c�����<$t��}�K��PhZ4�8��y��a���4V���2@'����m���G���0�t$_�a2G3yH���x)��kZ1��FL��f�S������0�e�'G>
��(����C	^~���%����d��.P��&+�3J~'&���x�<�U���Y�m�w������5��a��4���d(g#�K��W4����O�D�tM�z���#d���A#�9������[T�:'��U��th�-��&�*Wo�?cj��k36u�R��E�����&�I�����������?��E�#d�����<����DN
�uR~��RP��1�w �o4�'C�������U����S��I�&=^��s�8�{�99g�r�d$�X�rp�/�\�F���v�b���4�����7�������F�FS��Y�������d�W�c2T|,����WY?(��Vm��mc�w���B����gg��W�\
�������y����y��7:�	���nbZC<�y��ebh��������%+%��R9ee��c�8/d5��T�j�n,lBg%Z�����\�w��R��;4�q�#�X�5���m��'�R�9�l�����.��3����_��;Z��7{�3��\0���	1�7����j���h�Xo(�%�����*Z����)����I�_�s	��6_OM���f���,��5$g}��lv��&`J/V��'%N���K�7�0�p52��%������������0:��/�tM�������%#���P�=�X���0h'a��%4��b*����N�kQk0+���>�lD����)F����RReK�G�v����u�����+��ED�����
�p��d��0���<�%�x���&	��r�ByBKa��`P*�^�"�S	4]�z�K��x�a�D���DU|�(6G4�$�oCk"M���v����A�������-{Sq>���bp�Zm��?����}��6��	Qx	��YO��T%������]�%P�#��g��������5<����$$��?
oA�y����9����e)e�������3��X��A������� }?B�+�F��1k��l$7��e|�x��3h���/�=������6�)12&4f���e0GN��$+���<sGQ��B��@���%���`[��9����W@����w���.�y�jr7a��6TR�.�mb!\(�Z�C����('7e���������iP��1���������Mh-�!��&���i�SBtl�b�q�\@LLM�L�r���675O��-al�������AK����(���{s��*���cWE}	^Y�U
��1L����$������!8�s�(eb��4
���{m<�b����8�Sr�W0��j�	�I1�&��!z��Bf�<�� �eVX5��,��xR�v�	zk��<��9����a�!���3���6�h���-���8j��p��^}�i���F������,��@�S��q�0t�������HD� ;Q��V���#��4a�L'f9�����9��S������1�&_��V�<~������p�����:��pEQ����E8��<������]y\�6��eo�:9�m_�y�e�2�!�(�&K���7��/V�B��9P�D�16�Yn���[n�	_�d��3�4m,�%Z�]��?A�mR�%�,{���g�
���a�l\���T�����v{�(�"���6�}^�������]Wrb��UW^�v���,�U��3����f�����xG������ ��)b�,�E�K/T-$�� W��!x��1*h�_Xq�5|�����H��^Z(T���foy�;J��
M�cN��=.g��s$�#����c�j�����V���O���	����x6ub��������3�w~�L�Ev��&c��9��U���zA.��8��k�P.!���X��E�H��W�K~3����q����5���;�;�DJz����m��y9& w����*H����qz�T$�9R,:V�h�@�?���R�pr��?�������/^��#����"	��P�wb6���2�F�Hya��3��9J?�:c�����)��'�/#7p �/�U�����T1���'$|]@����+q���_�_�Y_�a55g���=����Y�����_�����/�q�nS�?&�������'�e�,��Q�6���~�X�f�B�*�)FNt�./g#���t�)���Y=s>_�w'k����_��\v�0t'9f�i��^��)�I�g3t�P/G���k�+�e�t��W�1s8U�b�T�V��~� 	���%������N�|D6�s�8�kB[���!�n��6��y^�ow�x���V���!����q^��k�H��\��b���v�F�m���6�|&���x�-��� �p�6�>��,������^I����.�)�K��R��d���y��u��K�9P�1�gT��Q�~�� S~/,$�d���F��H����O�� ����Y�����E&�h���\&����.d����P���r�Q�%S�M%�g����F���L��D�!7Q*�'v&o��zs�$�N�,37nX2�[<���I�F�b;dx
t������Ij���3�3F�?�r!��o������ty'�FHzm��1�e{�!v��g��8Og7�9���v8�5��8�������8R�#���rQi���n<�a��������FH��m�,!�0����f�RK�<*�V����yo�8��5������#��
��O/��z�~nHb����W�J���4�Zwf���C;���8n��BoL�7���r�����f�2�*��bI���j������laN?�^��h
(��J�aWYNK�����m�sqn�@�#�1������P0a����$�0Q"�]���S��%�jH+%!�0�][��](�s���!V�l�6�U'kR�:�����G%�p8��U�#�:�K�M��L%�TD��C��c���}�������T*q^��%�OD�����J���,����j
�p6B�i��#&C@'���Lp����4�R��y��@KO�����m�Z<0nU����r�E���C��VF:��bQ���Mpo��J���~t5��\�
�F� q��������o��2L�A��Hpzg5��(�OZA��|}RV����z�{i
���k�!����Y�b�.*�����D�hK
�
5����q��A�s7�9��n�B���b��^�a&�QZu/��	����W&��y�l*��!n��T�cI�Z���f�Z���������Q�q�>n�<|}r�>)���sJa<������,a������b�R�p���W�'~�e�s���x�n5_]5.O
ZAK 8���>.��#�:<i�[�G�7����t�u~B�3�lI����m��%Q���}��y3�������p_H�H����{~M� ,	3��P�D�p`U�������"���7~�B���OD$yu�<���}�8�����P����U�xz�N����R�M���3<JWy�������W���F*+C���&���=��^I����y�5����Fa�O�9j��������t_4Y*���A�KHa��L�}���
��
	�����hqT�I�Z}2b��y�8��=~(�':�?��$����nF�� q:D��"&=�}(�aV���D^���z)�����o��D�7��cH�����42�/���}1o�u8����U��9����JO����"}n��)kJ�a6da��d��N�e]��&��R::�������o��G��ov��~3o-����a��U���&����������%?H5�����gV��j�)9�������I�����gC���\]�b�P�	�G=��O�%?]j�#�w�i9�I������3
��#�if�ls��a�Y�$&��0��l������-��ghM������[_n��f��5=ew��������0Xuq���9^�n�y�<�&C
M���*��x����q/�A�q��n/p87:��� :9G=�
;�������;��Js��#S��p���p����h����U�f�/�s�hJ+741�05R57���;,{�o�'-bZ+N��[�jbze�`K����l�8i������s�^����1����8o]���U����;��>�X&����a����W���
\X����}xqq�#p��3��a8�G�;�A������N!���@�aW=�!����xARu%�c��cv���	��v�5!���27a01�-S��������be]��F�I[�=����j�1�^�p�U����CR8"�����`B)s9
�]3�p+%�X���w=�(�*���X�����$�v^u..�H�1SQ7��n{�J������ ���U��k��,hz���F�����HJ��
S�8�uLO
"�j�����&��h!|�+��������Es�4������h"	F�������S�u���V8����04;�����J��@��)����s��H�+Nzu����W���M0���o���`�y���.R�EW�����HB)�,��<�#��G���O+^�����+hz�����+|jn���u{�y:��'�_g�����������Qs�/�K%�e%=�C��-�m��]�� �I-�*�P!��p����>����x+�5�Hi��0y�����������A��z")�+W�lK�U�2��{��Z�������/Pe:5t���r.�T83�*Z;��&���������3�oI��1����r��,:����F�8Q5�b�%� b?�����������7��x^d+�W���-4~����������^w���s�@��siA>��k^�gq��t�kY�o����m�]�S3�3�!���#�E�Ob�B���`�w3���9��A��v�����
����V7�<��Cz�K�^�$0bCB�-
�q#$��`�������4R���v�����o���#����#�&Gr�����������#r���t<Ji�~<#�9F��W�K��p,��,����Pb��p�4���'5��n�H
���������"��I����		J|�� %�nr-63�[�l���e�����
X� y��&�3X�
\�'�s��@*Z�!�t%�3��"�����bH�����A�����_o��M_+3�����%���+�]�o���*o�V�����f�ej�7XA_Y��'mt\��#+�y8G����T�I��^���y�L��q����9�2�M8��7M�$}T:�2�����m�����
����wI�	xi�zb	4a�\��9�$��3�X����H���C�O%�ZJ����:Gx�Q��Q��}���6Ht=�ngrY�c))��
L�.��a
u����?�.�-�W0�7����(z�z���\�������x���M������.p8���������9x)����E��)�O�{�����%!�T���V��"��� ����C�]�m6���(n������Q�����
.^�����^VHL}��2c�F/�=���zK���zoF����B�^�h]6/�9������$�x�m�"��WZD��/��'W�Ko-T�VS����s�e�y8���YH����s�$�$)�[k����W`�n�9^,<
�c�����:^8�Q���X����O�5�����'�A��t����&Gj6��w����\�qEBW��F|`P5D>i�/�
J��O6>L'<v�|�"��gu9���u�n�:�w���hs����n���c��"�l�f�Q�C��~�,��5��^N��d#?��!9��cJfcul�x9O�h$�C���M�&y$���SI`��;�
���UL�'%�'�--����Og���������e9�	��[tj�rQ*��~���V�rnY��=��fEl[��i��I4
��b�B@�:i�K���}��+��1�1�p:
�m��mM�P��5�i������H�E�������l�P�,�/O�'�����z����-j$q�/�;=as\Rq*	F���rb����"���l�g�Z�3���pZ�AB���~��#�-q
!!l���y�*+(��^h#�� �|q�@�`��;��m�Vni���Q�u�q�j\U�h�[�V}���N��'>����	����^'I4J�l��!�XO$�!��&��,���^7<������������[,b�_��`q6����� +vv�J30:J%��� ��	l�����ocy�
(�V\`�^;�>�M�c�`��o%�zw!Ht�i����,���<����x�a�WU�:�����#�8F2��I�U!�����?�����B��l���d[I��+��r�FRj��I����}���i����9B��<����e�B~W��Te6^��-dp��1:I��c�S��*MWi)/�k��IK��"U\���y�a�g��"&��<�R����hTC`s��R������y
��$@IFcX�i�bY����l��_��>[�2�:��q��u�sG�����9LG��M�D~�o�����;A�&J	T�)�EK�e*7�
��i+��{
�K����id�-��9�rMb�8���������5��x'YE�m�X���g�pj���������Ji����>�T[E��i���]vf��R
tw���*�Y(w$��}6��}���A(��C���en���=(p�Y[��-�N	8��3{�
r2������b[&^O�qk(�Fa�e=�{��E*��G^����Z�-�t{�7�o�E<i��)]eU��[��V�n���� ����n
9������V�V�HN�3WsiI%m9#k���R�����v�dD>��6x�3��#_4��(W��M�������3�1�!���1K�/�������}��~�N�����g�A�l8�}��:��[�X�����f�_�m�;�N����vv�����qymmm�^��?���V�Z�Tk�YW��^)Cj�DR���z�M��^�Z�D~����/��e���].^+�
�G9��wc�Yh�y	8[��?5��������<������u�8ME��c�	�BEk<�
�h���h������E�T�x9��gA!���H�xq�w��6����V����o"����<�h�5"���)��DB9{�BN�$o�L�q��7$tur���4��g=�rI������N�i�G>�y����y�����>��`�ny����T90xT�[eO���1�-�Q�S���b}��.2I�3�KCi��?|����������r!�B$	���h�����������
��Zm�W{��b���D�.�B��z\�Sk�w~Y�-u�^����������nP�U�Y).;�x�%�W,�f���S�]_S�ZsK��W��%���8��A��w�.���b��t7���{8���a0��a;W��m~.&�;
��������A����i��*�Y�����Xe��_�Rz_�0�}��ZQa>��v�Q�N�-��
ol�n�mu�Xkwm�6���������9��j����Wr�g*@:_���#��Z��
�-�����\�&+�p>��'�[TB;Y�������Bxz������Z�{��
v;�	Onc�$(�8.�����Z������e�tHyw6��)���W�62����|�����:
�����z���c9�K;��kV�W���q������-^�HS�)n�7���!�_H���u���t� �����z�z4�]�������ho��>��	�_|�z�����Y�����l��.n1���q�t�����w��:�����.)�6��k%�o�4���R@r?9�g1N&j>�i��`
���C$
>����9"l[�l�/JXC}-���w�
�<�.�1����Q�X=���J7��^)Ie���x�dP���#��{[T �Z��#�����Bh����?_�S��{����)�d�� �^
�#���w�d��)�@*�����U��`����v�����t�p���f�K`�ug����l�7~H��E�t9�����._�����E�@�m�%h*�.����
����	v)�p���$s��"�b��3��z(�%$�PS�5�@���`�EHe���a���"`1���oEj�vC;�`��u���5(��`jJ�Do����)�.j?��i��������
���[�_���N���^l��;�<Z����4{	/�FRY���.�a�@t3_��O�����]l��S�����
�!����d�@\� ��|�$�8�H�������oi��MnE��	j ��CT�B�����MQ=M��SA���|h5���io1��zb�Gq���-�{��B`�k�!�#O���p���Z������`���Q��=�n����,n.Q����1j/�^�)�������@�XJ����N^f�7���d	�v-���bQN������l�p��3������k\��(�����M������&q)�-�{����ao�p$[��H����^K��<�u������N6��U�O��I��YJ������c��}���y��!���<��*�=FhG�_uQ��opS��	;	qZ��$
Mx��fn
�������d��*p��Kwt�%9L
�W^=����p�4[]��:(����O���((�~���Zv�O�����Q����e������}�JT�
n��X�����e�E������}��oh��
��������6���I�.8NG�t"��z���,������"22����i�nt`����9��������SQ�2�)����=�p�YG�Y
��U9������M`[pc4
�u�m�O������A/��w\����U��ER-�{+��$�����|�%�wL�s'����&��Q7B6�U��F/Hm�9��A�f��v	����xC��<Xs3�����/�5������e��OT��t1ge�V�]�x������|����(���0���,����{R9���y,��+�	�9d���e~�2���1�%*@	:�H������s|�(��{#W���.t�=�q�T��%����8�f� *�c�_�KV�����#���AW	���5���;[���q�P�?��v��x�F[�N��,�9�.&@���L���n&Z;p����H1��6��;�9��
1��B�v��[����B�69���I�#�-�m=���'���]��8^;������Jy2��'��P����i9�o���.��a��~6�FVe��A����r�o����������s��z�f��w�~��y%��|�����=�M���'H5��R����F�( ��as�����=������;�Xb(�(�Z������>*�)��~	tLyU�g�*~�b{u�!�o��_��:�����(I�]s�$��
�H-�����4z��x���H�jmwwkG�������6��������������h�|7<���2u.r������Y������Y��^T)M�:��1�-������~:6�M��k�a�t7�g��k�Z��w��No�[��w���^�U��@�%�S���.�w���|��2�]2������k�����0F�^�����w��_���j�H���&���;f
������0�a�M�E=�[�*1��D�}e�o��<726���c��F�g�������F�}�����������-~w
R&�����WN�����1��,�����+�I���}����
�T�fU���~qx�}���&�m�_��VIUK����K{O�A�W�[�����U��~�<{�~�]���,�?���4��`s$nJ7�2�A���)�����*3�����������:���@���.��>���Z��o�����`�iqB��}b^��0=�+|-J
�gj{��@U����o�S0���%�fg��k(]�R8N�������q�utYY�	n��b��s�d@�Q"�w��
z^�
k���7,����r�%�K���mm�������vuk�����Y����^���	}�b[��N\���������2�����z2D�I�}��7�;<L����}D�S�c��%����^S�dr��N60��h6��GDS���lZ�7�'&�+��fu@�^T�qm�E��E���e����������+�Y@v������
}�E)]�o�p�Z���m����'+ '|{LW�@?u��6����f&��r?�Y��u�<.�S�`;C*��-8��Lmk{���uW<�E�w	����)���`�W�m���f����j��hI��#�����f�yA�7�Q�\�)-�aD��=Ty��e|��#�PJ�Q�#w�Wa�f�L�%Oy
'j�G���u�t���[]��ye>�^�CT�G�^���R�6D��@����l��y����C�N�.2�xejF�"����r����SFX���A4���k}u��B�_UR�QE����7���/�m���]���<�m�/���<��qz�>=<�<�{`����12k{��9�����;�B�%����A����a��x����8���� s^Y4�+E����]��[�o�c�4E���������7��t+ou
7a0�����N)��5��K>��n��W�t�w*7
o�F9�7�qfr9�a7�BJIn=��b"��V�X_�X�,e/�c�FTQ/0e(����i���h���T6z��$a0�� oRRCK����m���:gLeC*���moaJ���4N�^�Lo��
�(�����O$���Mk�Apn�/4�tw��x4�i{��H�%�����!��o���s��8z}u~��N�RN �!9�����������Z�>���s��T��
)���0�]R�V��N� k���M������k����T���O?j4�U��R�y��~���y%oa��_V��:�����38"J��d/��#%�i��5=�l�O�.�G�+^�R�N
��e�����|$z=y�7��*������e����6���lyO����M�Vg�A���_������O��h�G�)�U���1LK�mO
�����q��
RA}s��U�D2������W��-���A<�T��&�llZ[NR���f����,�F����+a0s^���9����9���0�����d^p9�mt�+r�����q�EK
�	�����F����U�///�^Eg.��,D)��A'v^�HA�������=�������-��%M-Dta>���9D!l=�~�cmwS�,q�lN>	�5�
�uYF�9��:��-�����E���Z�A�P���n�*6�==�*`������\�p�M��J������f}�3�X��>�m����E0VJ �-�������	6?��pzr�����}d8���a-"�E��1������s���p�h�9�q_���r��	��i0�X�b�����8�%@�%r�H�yZ�o���@q#�c�P��U_��)<���@!�Fb�i��C��R����dgk���$�[��5���
S���/T��|�(�+��,��K�%�G2i�+��lz��D��P���3�%�_����������t4E	�J���t�U~��m?��`�����f���T��������<J5�|�+��)��Lf@q���?=?n19}��Z��$5f;����v�m��"* �i�@gx�����i���;=�B��n�����sE�\�T9��v�v?I1�8gJ���!����a��Mg�;�?��xs~�}��a_��������X7�0{v%���T����������~�� ��T��~Y
��M����u�e�Q� `� �5�w���������`��W���,d',������g��I\V���)�c4I��r�<�Lh;=4��dNGP0�8�34I�3+���cN�v1�b��k��mM���QD�dv��3U:Q���QK&�q-Kl�?��?�k���/y��$���N���@��~���D�	1�O��U��Y��S�����G������l�uf��?��7�R�\�@NF��:����%��n��@��Iz�4L%�� ��hUU�==�xY�JXc�����.(@�����b�c��Ugv�}7��Cu�g����?�v�K'qdn\1,CW.2b�����J���z�l���q]lZ�I=��L�����(������CR5�������V],w��]�:>9i�^�_^���R����Q�/)�|�yA�>������R!D�=n9�JS���Je��0O�<o|����zj���sN"��u8������A� ������}B��zf�\������CY���f�� 8�D3�v�����+ga�eb��a���y|/6<�����m}5F<�/��My�W�h��-:w&�<w�_�7f�yv��<;<q���W�V����3E6�@����aV���
�8��,A&qW�IpW#�YR[=����p��������]���hD��vu;d��k�N�St]E����]�o�y� Q1e�@.��G�6��!l\d�J�GX�"������$[�B�$���n�	�N���%����m<���%��I��Rq��6mj�������$+�t(����c���\���qRuc�4�w!Sb"��fr�-�-?�����/�sg��k,E��u0m'��o��tO�v�L9��B�*eR�������Y3dru9����x6���/_rR
J��L�����:���{�P��q�
w������Wcn�K	�-!<�|���2t�T��>#��N���m���{����V����A�r���=����@@�n� ��v�%��w��7�$j������!�n#}�����?u��:����"�-��KP7p
V��"x �;W]�8�/9�_c�����w�l�|�m���@e����mq]�h�"xD&�e�gA4rC�dW.�\��h��`.��46t���G��4���iFp���vO��g+���=�/�z�Pl�x(������
lj���c��>y��{����=���`{������
��C]��8)����rp��F�#�b��o:�|�� ������(�%��N.����T����p���U��m�f��-���&�An]	L������m��8��z�^Uy/�o��J�N��5]�X4��
*�2��560�	���3�Q:;���n�������>�n����s:���|�.>�hr���.���q��Zm�V[�$F�G�}�J���k�$��uH�����e#o���(����X��K�k�5ay��U<��l5m� j���:���M��*������T�l ��*�����6vm��s$�N����;���3��	m�KN��6r��hEp�"�&gq/����������[;����Z�qo��^%����B
/r��i�c��k�Aw �a����;������n��HB��8�/��z�l<(l�e����m={��5���^��Q������U� �H�}k��$��f�;�&m�Ag�a��W(�C����.��������3$1���pG�'�����*�;F63 ���9l�Yh������+I7B�����0� �$��
�"z��G�*.;)��:K��~��'��$jb<0��������]�KK��W�Hse���P>8��+�X8@��,��?�VHm�q��0'�U���JF�M�[�����r�y'J��t�Ul�m2R^�f+!����f�<�����EnA���Y�
��`>i���QoL�������H)�~?�n�o�j{�A��
�TI;)bUR��������'����B�A�'��+����r�L�����e�����r~z����E�;�~yy~�9�b�Ny`���l��dU%�X
���rN'!�)��t�����&w����mNB�AUa�Y��Ky�C��OjR�@Ve�gb~����~�����8T�vc�7�#W2���oP�_;�4�gD�Jd`�
9�]�Y�Z��X^�����FCLyI����<������\k����m�s�@�	�>r0f��N/���7�
=qm(��
��!��&�r&��'�RP�����3U���������v�%m���
�w@���iFe�����y���P��e��R.�6j�m���[d1���zx��]x)S����AW1S��Q�(�X`Mg6��q���r��l/y�Bn��f������c��$��(����(C���3�7)Xp{T?8����QYC;�gyM���ED��w��(D0�Q�����
}A�!G��R����P�[��`�4~��<l7�ZW GCCx�=�:7xb	�f6���5F�Uz���S���fS�/����5��	�X��E��e����Q���z�-��������k	�G����H���K2��7r�N���}�?���XZ��tzt=���i�1�h�|;�ip�9#�rH�Yz���'g�W�oYJ�0U<�!pr���6��%��� w�ETm�Q�b*(��I��C_��g�q��������BB�����>
���]0�����M��T[#���ud��:��xP�By�#�������=�5�+w?�#�E�p����=����5w1�9��
&���t�
��
*�D���������F!��7@���;��i����w�?�[��x�q���6��<4o"��*����J��[��i���[����&=:����>����zy���w���p�m4OB���Z�\A�o��_LY���]9v�PW�/NxOs���M��Zx�6�5�IS����`%�w����9J����T6�2f�
�������:q�{��@$.�%�]���Lb��\>�?�-��jl1���]��R����-f\�����G��33�OA���/0�J���~��4�+����o�W���(��T�B,T�}�0��N�'W���������2U��_�;=�3x,K�������!��z�Ih�z��ut��0#@������
|�0�:�gz4��S�L�`.�5pY�O�er����<���
f ���mR)`�V��.����1�!����aU=e}gpg�z-���`��"��$���-�8��T��4����*��^<
�
�9��	X�� ?�
0�z����(�b��s�!T�\.X{��9�V����DIE��_�l�V�C�pao��5�0c:���	.��.y�������qy��gW��$�`����������/6/������{6���w����L�+�;o��_q�����7t������E�E�V�����7y�dw�C����U�m�q�����uG[���6?��������;���l�������?�9XOLS�j������,��MP/�������)���?�SL\n7�e�>��O2���2���iMF=�'@M��A����)�z��Bv�o�����N3���H|0V!�f�?��'�6��lZ2�}�l,1{�I�1@M�cQ��_ ������AYjR�����1e�n�$����h��=��i�V��#*��o��6�D�h��K�1�P������A4@���-�<����pDTlz�s��l�����U<����hqL��(T�<��6�\n��u��������7�?��L�M��H�dO��XH��e�����*��&pH"?�}�?�������qS��r�������?�9��m4��s�I�S3`�X����8Y	?���/�4b����b��"��'������������O^7Z#8�������0d��\=�����p�/-&���������O'�2n�m���?fc�����	�����<�����|��\�_�_���+_4��N%)(���|8���IL�nA���Y<�������c��G���~i����'�u4��p��|��\s���C4U.��u�/xY�E��s��m��bl���s�T'������\���������w}#���,&Z�U<A���6�����n������w\���{=�f�]\�\i5~�j�������D��A��[��~\�&S`o�6�P��R�LE��C�&���j������\-k�LR-��KN�W0��~�]����P��Q�K`jlN�?^x��X{�W�������0B��������o-�y��D����b�q/��"P@�������0H��]U]=+o�y�X���d�u�&	5h�����u����n.����[=�w���1����D���Mn�t
.:5���<e�t�����Z6S�����[s��/E�y�#�2��n���r��J���?8Tn�^'������h�CE�����������<<{�pB/��xi?r�.����-�����z]T@��=����� 3*���R�m:-B��I�(�Z'�W����C�~H(\-����@�^�&N�d�>���O�'��A�#��ig`�cX�Epo>��*J8���:������������V��!>���y�����}��w���y��wu�[l�Ow��|�Vs�8R`������,���T�J�tW�Q��%jX��K��wXh�$�m���A;�*�&�	�a�&���R[:�.7������J����8����i�Rj[���D���<
~���������S�����`��n�b�������b��+]8�vMn9����
p�y�\\���(�uQ���($g$�@��L�,����j!F2�����}����g=]�c����7R���I4
��k��<x]����k�8X�U<|�Eop[���f���L�W-X�������Z�7���j�tJ�!RMf�T������?�s���o���/2��<��[�iy}n�m����3���<�W��#�.�&�T��=����h��J�^p$�?vY ,8)>f��Vxb��X��d��V��e�[`f�g�.[�b]�z�_��f�s�Y�v4����+-��#���Z|,DN�D>��%����:p/s��A�;�nh��r��A��`��JP��I�����C�g.��������6p
��L�t;�v�F�+M���4�k�%��x4Z�3�	��7���}��t������/K��H��<�%w��y��SE��{TrSX��u��*q?f�%g8n9�}���R����p8�U�k,#���O���cW����V��E�I�l�O����������#D�����}�(������~�?���`h�G������)����U�b�����Q�����1���:j���t���o0�hD�����t�������U���+�~Z'j]Q��w ]?dy9�{� C��n���6���������?ch��g<;_d<;�1����/V�ig�����|4Z�,����}����+�w�����o���+�nY�om���-[���]�7��&�$!��(����Y��X'��w��I)g]��la�
V!��*.�oh��L=\�B������b�����/�����*Hfe��"����~�$M,P���%x��,d.Xp���E�E����{�D���5,o�@
>��V?Uv��e�{%e�f=�~�����qym�����Z���~����u���;�t��~�}�y
,.�>�y������C��4�A�����`W��>�Z��~G����_���:|��P
F��<�>�v����Q�Y�����=��*<��Nym�>�~�6�j:lF�X
�-��V� �;�.B��0]^P�C��/]|�_z��_B�iv��t���	��[�^�����;��~�Z��/R�&�pBR�7z���C�F�A|����n5��3��
���V>a�����~�
P?�o+WwW�P�#3d��}W���G2�]xDP`��s�������+v�+�V�d��l�m��V������-��W�g��g|&��g��+U*��C�=�Pa���_fMV�~ SYd���Y�����W���g��&���m9@��	�h���������]�5ni�NK,�.~�+�`�o>^w�������f�q��.�3�<�-���_��Mjja[e����v�����m�J;������4�S�l�2�����a��=������]�P���w?�����Hu��$�Z���t�������� ��w6��}|h���)�F�`=��r��SU���V�t%�Ew[�|a��7�"��vm���������z�Mm�#�.Mm�b�������+1�&tG�R��z���n'gp������K�7j����>^q�gT�(���9��� 
��Z=o�a�9#X����C�6��T�����C�u�vv��N��V�t`k�h���J�Z�!�6h��u��I���<��v�����`��Z������y@s���y�i����o)2������
�	,��HC?�����E[TC���^h1JZ���.�:$a-���r����WxZ	����Q7;c�]��XX��z�i���U�v�PY�4���MT{nn�RFhS����e��+�b=>MV��VJ��t}���(>y Z[V�W��7��|DN6 Xa���|��`0Wl���kcZ��[qA��?I���/���Nu��W���c��a4`�����n�L	�/D�n�`���Q��'��X����X�
3�z�0q*;����L�W�}�f��:s�hF�j�N�-��X��_H�sX�������EE%_%�?�D5�����<U�yN���U���|7������h����cuky���-$��g����2�5�gF���Q�������7��9�t����jI����"����*���z�o���t�^�[�D�V��m�Otl�S��!����.�����y������I(�{j������d=��)��"b����<j�����������r4�Nn��uf4Te�:<��
w���r���[�J�f>�������E���}�#�]��B�N\`..|O��b��1�?���r��T�3�H3���Y�b����,�n���F�
s �z(�O���SXz1g�~������oT8t�����/��_���T&��bJ��R�'��<+���s���J��H��}�H��h��l����?������k��Z_W�[����������M�
0005-Add-RENAME-support-for-PUBLICATIONs-and-SUBSCRIPTION-v19.patch.gzapplication/gzip; name=0005-Add-RENAME-support-for-PUBLICATIONs-and-SUBSCRIPTION-v19.patch.gzDownload
#202Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Petr Jelinek (#201)
Re: Logical Replication WIP

On 1/19/17 5:01 PM, Petr Jelinek wrote:

There were some conflicting changes committed today so I rebased the
patch on top of them.

Other than that nothing much has changed, I removed the separate sync
commit patch, included the rename patch in the patchset and fixed the
bug around pg_subscription catalog reported by Erik Rijkers.

Committed. I haven't reviewed the rename patch yet, so I'll get back to
that later.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#203Fujii Masao
masao.fujii@gmail.com
In reply to: Peter Eisentraut (#202)
Re: Logical Replication WIP

On Fri, Jan 20, 2017 at 11:08 PM, Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:

On 1/19/17 5:01 PM, Petr Jelinek wrote:

There were some conflicting changes committed today so I rebased the
patch on top of them.

Other than that nothing much has changed, I removed the separate sync
commit patch, included the rename patch in the patchset and fixed the
bug around pg_subscription catalog reported by Erik Rijkers.

Committed.

Sorry I've not followed the discussion about logical replication at all, but
why does logical replication launcher need to start up by default?

$ initdb -D data
$ pg_ctl -D data start

When I ran the above commands, I got the following message and
found that the bgworker for logical replicatdion launcher was running.

LOG: logical replication launcher started

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

#204Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Peter Eisentraut (#202)
1 attachment(s)
Re: Logical Replication WIP

On 20/01/17 15:08, Peter Eisentraut wrote:

On 1/19/17 5:01 PM, Petr Jelinek wrote:

There were some conflicting changes committed today so I rebased the
patch on top of them.

Other than that nothing much has changed, I removed the separate sync
commit patch, included the rename patch in the patchset and fixed the
bug around pg_subscription catalog reported by Erik Rijkers.

Committed. I haven't reviewed the rename patch yet, so I'll get back to
that later.

Hi,

Thanks!

Here is fix for the dependency mess.

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

Attachments:

logical-replication-fix-owner-dependency-handling.difftext/x-diff; name=logical-replication-fix-owner-dependency-handling.diffDownload
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 21e523d..9791f43 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -236,6 +236,8 @@ CreatePublication(CreatePublicationStmt *stmt)
 
 	heap_close(rel, RowExclusiveLock);
 
+	recordDependencyOnOwner(PublicationRelationId, puboid, GetUserId());
+
 	InvokeObjectPostCreateHook(PublicationRelationId, puboid, 0);
 
 	return myself;
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 1448ee3..00f2a5f 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -313,6 +313,8 @@ CreateSubscription(CreateSubscriptionStmt *stmt)
 
 	ObjectAddressSet(myself, SubscriptionRelationId, subid);
 
+	recordDependencyOnOwner(SubscriptionRelationId, subid, GetUserId());
+
 	InvokeObjectPostCreateHook(SubscriptionRelationId, subid, 0);
 
 	return myself;
@@ -493,6 +495,10 @@ DropSubscription(DropSubscriptionStmt *stmt)
 
 	ReleaseSysCache(tup);
 
+	/* Clean up the depenencies. */
+	deleteSharedDependencyRecordsFor(SubscriptionRelationId, subid,
+									 InvalidOid);
+
 	/* Protect against launcher restarting the worker. */
 	LWLockAcquire(LogicalRepLauncherLock, LW_EXCLUSIVE);
 
#205Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Fujii Masao (#203)
Re: Logical Replication WIP

On 20/01/17 17:05, Fujii Masao wrote:

On Fri, Jan 20, 2017 at 11:08 PM, Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:

On 1/19/17 5:01 PM, Petr Jelinek wrote:

There were some conflicting changes committed today so I rebased the
patch on top of them.

Other than that nothing much has changed, I removed the separate sync
commit patch, included the rename patch in the patchset and fixed the
bug around pg_subscription catalog reported by Erik Rijkers.

Committed.

Sorry I've not followed the discussion about logical replication at all, but
why does logical replication launcher need to start up by default?

Because running subscriptions is allowed by default. You'd need to set
max_logical_replication_workers to 0 to disable that.

--
Petr Jelinek 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

#206Jaime Casanova
jaime.casanova@2ndquadrant.com
In reply to: Petr Jelinek (#205)
Re: Logical Replication WIP

On 20 January 2017 at 11:25, Petr Jelinek <petr.jelinek@2ndquadrant.com> wrote:

On 20/01/17 17:05, Fujii Masao wrote:

On Fri, Jan 20, 2017 at 11:08 PM, Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:

On 1/19/17 5:01 PM, Petr Jelinek wrote:

There were some conflicting changes committed today so I rebased the
patch on top of them.

Other than that nothing much has changed, I removed the separate sync
commit patch, included the rename patch in the patchset and fixed the
bug around pg_subscription catalog reported by Erik Rijkers.

Committed.

Sorry I've not followed the discussion about logical replication at all, but
why does logical replication launcher need to start up by default?

Because running subscriptions is allowed by default. You'd need to set
max_logical_replication_workers to 0 to disable that.

surely wal_level < logical shouldn't start a logical replication
launcher, and after an initdb wal_level is only replica

--
Jaime Casanova www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#207Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Jaime Casanova (#206)
Re: Logical Replication WIP

On 20/01/17 17:33, Jaime Casanova wrote:

On 20 January 2017 at 11:25, Petr Jelinek <petr.jelinek@2ndquadrant.com> wrote:

On 20/01/17 17:05, Fujii Masao wrote:

On Fri, Jan 20, 2017 at 11:08 PM, Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:

On 1/19/17 5:01 PM, Petr Jelinek wrote:

There were some conflicting changes committed today so I rebased the
patch on top of them.

Other than that nothing much has changed, I removed the separate sync
commit patch, included the rename patch in the patchset and fixed the
bug around pg_subscription catalog reported by Erik Rijkers.

Committed.

Sorry I've not followed the discussion about logical replication at all, but
why does logical replication launcher need to start up by default?

Because running subscriptions is allowed by default. You'd need to set
max_logical_replication_workers to 0 to disable that.

surely wal_level < logical shouldn't start a logical replication
launcher, and after an initdb wal_level is only replica

Launcher is needed for subscriptions, subscriptions don't depend on
wal_level.

--
Petr Jelinek 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

#208Jaime Casanova
jaime.casanova@2ndquadrant.com
In reply to: Petr Jelinek (#207)
Re: Logical Replication WIP

On 20 January 2017 at 11:39, Petr Jelinek <petr.jelinek@2ndquadrant.com> wrote:

On 20/01/17 17:33, Jaime Casanova wrote:

surely wal_level < logical shouldn't start a logical replication
launcher, and after an initdb wal_level is only replica

Launcher is needed for subscriptions, subscriptions don't depend on
wal_level.

mmm... ok, i need to read a little then. thanks

--
Jaime Casanova www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#209Robert Haas
robertmhaas@gmail.com
In reply to: Petr Jelinek (#207)
Re: Logical Replication WIP

On Fri, Jan 20, 2017 at 11:39 AM, Petr Jelinek
<petr.jelinek@2ndquadrant.com> wrote:

Launcher is needed for subscriptions, subscriptions don't depend on
wal_level.

I don't see how a subscription can do anything useful with 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

#210Craig Ringer
craig@2ndquadrant.com
In reply to: Robert Haas (#209)
Re: Logical Replication WIP

On 21 Jan. 2017 06:48, "Robert Haas" <robertmhaas@gmail.com> wrote:

On Fri, Jan 20, 2017 at 11:39 AM, Petr Jelinek
<petr.jelinek@2ndquadrant.com> wrote:

Launcher is needed for subscriptions, subscriptions don't depend on
wal_level.

I don't see how a subscription can do anything useful with wal_level <
logical?

The upstream must have it set to logical so we can decide the change stream.

The downstream need not. It's an independent instance.

#211Robert Haas
robertmhaas@gmail.com
In reply to: Craig Ringer (#210)
Re: Logical Replication WIP

On Fri, Jan 20, 2017 at 2:57 PM, Craig Ringer <craig@2ndquadrant.com> wrote:

I don't see how a subscription can do anything useful with wal_level <
logical?

The upstream must have it set to logical so we can decide the change stream.

The downstream need not. It's an independent instance.

/me facepalms.

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

#212Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Petr Jelinek (#204)
3 attachment(s)
Re: Logical Replication WIP

On 20/01/17 17:23, Petr Jelinek wrote:

On 20/01/17 15:08, Peter Eisentraut wrote:

On 1/19/17 5:01 PM, Petr Jelinek wrote:

There were some conflicting changes committed today so I rebased the
patch on top of them.

Other than that nothing much has changed, I removed the separate sync
commit patch, included the rename patch in the patchset and fixed the
bug around pg_subscription catalog reported by Erik Rijkers.

Committed. I haven't reviewed the rename patch yet, so I'll get back to
that later.

Hi,

Thanks!

Here is fix for the dependency mess.

Álvaro pointed out off list couple of issues with how we handle
interruption of commands that connect to walsender.

a) The libpqwalreceiver.c does blocking connect so it's impossible to
cancel CREATE SUBSCRIPTION which is stuck on connect. This is btw
preexisting problem and applies to walreceiver as well. I rewrote the
connect function to use asynchronous API (patch 0001).

b) We can cancel in middle of the command (when stuck in
libpqrcv_PQexec) but the connection to walsender stays open which in
case we are waiting for snapshot can mean that it will stay idle in
transaction. I added PG_TRY wrapper which disconnects on error around
this (patch 0002).

And finally, while testing these two I found bug in walsender StringInfo
initialization (or lack there of). There are 3 static StringInfo buffers
that are initialized in WalSndLoop. Problem with that is that they can
be in some rare scenarios used from CreateReplicationSlot (and IMHO
StartLogicalReplication) before WalSndLoop is called which causes
segfault of walsender. This is rare because it only happens when
downstream closes connection during logical decoding initialization.

Since it's not exactly straight forward to find when these need to be
initialized based on commands, I decided to move the initialization code
to exec_replication_command() since that's always called before anything
so that makes it much less error prone (patch 0003).

The 0003 should be backpatched all the way to 9.4 where multiple
commands started using those buffers.

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

Attachments:

0001-Use-asynchronous-connect-API-in-libpqwalreceiver.patchapplication/x-patch; name=0001-Use-asynchronous-connect-API-in-libpqwalreceiver.patchDownload
From 0c18831557c963bdd4d03ef5d8f3c4ace1a51d0e Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Fri, 20 Jan 2017 21:20:56 +0100
Subject: [PATCH 1/3] Use asynchronous connect API in libpqwalreceiver

---
 src/backend/postmaster/pgstat.c                    |  4 +-
 .../libpqwalreceiver/libpqwalreceiver.c            | 58 +++++++++++++++++++++-
 src/include/pgstat.h                               |  2 +-
 3 files changed, 59 insertions(+), 5 deletions(-)

diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 7176cf1..19ad6b5 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -3340,8 +3340,8 @@ pgstat_get_wait_client(WaitEventClient w)
 		case WAIT_EVENT_WAL_RECEIVER_WAIT_START:
 			event_name = "WalReceiverWaitStart";
 			break;
-		case WAIT_EVENT_LIBPQWALRECEIVER_READ:
-			event_name = "LibPQWalReceiverRead";
+		case WAIT_EVENT_LIBPQWALRECEIVER:
+			event_name = "LibPQWalReceiver";
 			break;
 		case WAIT_EVENT_WAL_SENDER_WAIT_WAL:
 			event_name = "WalSenderWaitForWAL";
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 7df3698..8dc51d4 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -112,6 +112,7 @@ libpqrcv_connect(const char *conninfo, bool logical, const char *appname,
 				 char **err)
 {
 	WalReceiverConn *conn;
+	PostgresPollingStatusType status;
 	const char *keys[5];
 	const char *vals[5];
 	int			i = 0;
@@ -138,7 +139,60 @@ libpqrcv_connect(const char *conninfo, bool logical, const char *appname,
 	vals[i] = NULL;
 
 	conn = palloc0(sizeof(WalReceiverConn));
-	conn->streamConn = PQconnectdbParams(keys, vals, /* expand_dbname = */ true);
+	conn->streamConn = PQconnectStartParams(keys, vals,
+											/* expand_dbname = */ true);
+	/* Check for conn status. */
+	if (PQstatus(conn->streamConn) == CONNECTION_BAD)
+	{
+		*err = pstrdup(PQerrorMessage(conn->streamConn));
+		return NULL;
+	}
+
+	/* Poll connection. */
+	do
+	{
+		int			rc;
+		int			extra_flag;
+
+		/* Determine which function we should use for Polling. */
+		status = PQconnectPoll(conn->streamConn);
+
+		/* Next action based upon status value. */
+		switch (status)
+		{
+			case PGRES_POLLING_READING:
+				extra_flag = WL_SOCKET_READABLE;
+				/* pass through */
+			case PGRES_POLLING_WRITING:
+				extra_flag = WL_SOCKET_WRITEABLE;
+
+				ResetLatch(&MyProc->procLatch);
+				rc = WaitLatchOrSocket(&MyProc->procLatch,
+									   WL_POSTMASTER_DEATH |
+									   WL_LATCH_SET | extra_flag,
+									   PQsocket(conn->streamConn),
+									   0,
+									   WAIT_EVENT_LIBPQWALRECEIVER);
+				if (rc & WL_POSTMASTER_DEATH)
+					exit(1);
+
+				/* Interrupted. */
+				if (rc & WL_LATCH_SET)
+				{
+					CHECK_FOR_INTERRUPTS();
+					break;
+				}
+				break;
+
+			/* Either can continue polling or finished one way or another. */
+			default:
+				break;
+		}
+
+		/* Loop until we have OK or FAILED status. */
+	} while (status != PGRES_POLLING_OK && status != PGRES_POLLING_FAILED);
+
+	/* Check the status. */
 	if (PQstatus(conn->streamConn) != CONNECTION_OK)
 	{
 		*err = pstrdup(PQerrorMessage(conn->streamConn));
@@ -508,7 +562,7 @@ libpqrcv_PQexec(PGconn *streamConn, const char *query)
 								   WL_LATCH_SET,
 								   PQsocket(streamConn),
 								   0,
-								   WAIT_EVENT_LIBPQWALRECEIVER_READ);
+								   WAIT_EVENT_LIBPQWALRECEIVER);
 			if (rc & WL_POSTMASTER_DEATH)
 				exit(1);
 
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index de8225b..e088474 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -764,7 +764,7 @@ typedef enum
 	WAIT_EVENT_CLIENT_WRITE,
 	WAIT_EVENT_SSL_OPEN_SERVER,
 	WAIT_EVENT_WAL_RECEIVER_WAIT_START,
-	WAIT_EVENT_LIBPQWALRECEIVER_READ,
+	WAIT_EVENT_LIBPQWALRECEIVER,
 	WAIT_EVENT_WAL_SENDER_WAIT_WAL,
 	WAIT_EVENT_WAL_SENDER_WRITE_DATA
 } WaitEventClient;
-- 
2.7.4

0002-Close-replication-connection-when-slot-creation-gets.patchapplication/x-patch; name=0002-Close-replication-connection-when-slot-creation-gets.patchDownload
From df4d4f9d5fe9651c6c8f4176e515ce8a99cde252 Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Fri, 20 Jan 2017 22:22:33 +0100
Subject: [PATCH 2/3] Close replication connection when slot creation gets
 canceled

---
 src/backend/commands/subscriptioncmds.c | 18 ++++++++++++++----
 1 file changed, 14 insertions(+), 4 deletions(-)

diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 00f2a5f..726c286 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -298,10 +298,20 @@ CreateSubscription(CreateSubscriptionStmt *stmt)
 			ereport(ERROR,
 					(errmsg("could not connect to the publisher: %s", err)));
 
-		walrcv_create_slot(wrconn, slotname, false, &lsn);
-		ereport(NOTICE,
-				(errmsg("created replication slot \"%s\" on publisher",
-						slotname)));
+		PG_TRY();
+		{
+			walrcv_create_slot(wrconn, slotname, false, &lsn);
+			ereport(NOTICE,
+					(errmsg("created replication slot \"%s\" on publisher",
+							slotname)));
+		}
+		PG_CATCH();
+		{
+			/* Close the connection in case of failure. */
+			walrcv_disconnect(wrconn);
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
 
 		/* And we are done with the remote side. */
 		walrcv_disconnect(wrconn);
-- 
2.7.4

0003-Always-initialize-stringinfo-buffers-in-walsender.patchapplication/x-patch; name=0003-Always-initialize-stringinfo-buffers-in-walsender.patchDownload
From ed076d5ccc03dbf47218b201f8c6c52df51e8027 Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Fri, 20 Jan 2017 22:22:53 +0100
Subject: [PATCH 3/3] Always initialize stringinfo buffers in walsender

---
 src/backend/replication/walsender.c | 25 +++++++++++++++----------
 1 file changed, 15 insertions(+), 10 deletions(-)

diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index f3082c3..a6e2655 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -149,6 +149,9 @@ static StringInfoData output_message;
 static StringInfoData reply_message;
 static StringInfoData tmpbuf;
 
+/* Did we init above buffers? */
+static bool initialized_buffers = false;
+
 /*
  * Timestamp of the last receipt of the reply from the standby. Set to 0 if
  * wal_sender_timeout doesn't need to be active.
@@ -816,8 +819,6 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL);
 	}
 
-	initStringInfo(&output_message);
-
 	if (cmd->kind == REPLICATION_KIND_LOGICAL)
 	{
 		LogicalDecodingContext *ctx;
@@ -1310,6 +1311,18 @@ exec_replication_command(const char *cmd_string)
 			(errmsg("received replication command: %s", cmd_string)));
 
 	/*
+	 * Allocate buffers that will be used for each outgoing and incoming
+	 * message.  Only do this once to avoid leaking memory.
+	 */
+	if (!initialized_buffers)
+	{
+		initStringInfo(&output_message);
+		initStringInfo(&reply_message);
+		initStringInfo(&tmpbuf);
+		initialized_buffers = true;
+	}
+
+	/*
 	 * CREATE_REPLICATION_SLOT ... LOGICAL exports a snapshot until the next
 	 * command arrives. Clean up the old stuff if there's anything.
 	 */
@@ -1802,14 +1815,6 @@ static void
 WalSndLoop(WalSndSendDataCallback send_data)
 {
 	/*
-	 * Allocate buffers that will be used for each outgoing and incoming
-	 * message.  We do this just once to reduce palloc overhead.
-	 */
-	initStringInfo(&output_message);
-	initStringInfo(&reply_message);
-	initStringInfo(&tmpbuf);
-
-	/*
 	 * Initialize the last reply timestamp. That enables timeout processing
 	 * from hereon.
 	 */
-- 
2.7.4

#213Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Petr Jelinek (#212)
5 attachment(s)
Re: Logical Replication WIP

On 20/01/17 22:30, Petr Jelinek wrote:

Since it's not exactly straight forward to find when these need to be
initialized based on commands, I decided to move the initialization code
to exec_replication_command() since that's always called before anything
so that makes it much less error prone (patch 0003).

The 0003 should be backpatched all the way to 9.4 where multiple
commands started using those buffers.

Actually there is better place, the WalSndInit().

Just to make it easier for PeterE (or whichever committer picks this up)
I attached all the logical replication followup fix/polish patches:

0001 - Changes the libpqrcv_connect to use async libpq api so that it
won't get stuck forever in case of connect is stuck. This is preexisting
bug that also affects walreceiver but it's less visible there as there
is no SQL interface to initiate connection there.

0002 - Close replication connection when CREATE SUBSCRIPTION gets
canceled (otherwise walsender on the other side may stay in idle in
transaction state).

0003 - Fixes buffer initialization in walsender that I found when
testing the above two. This one should be back-patched to 9.4 since it's
broken since then.

0004 - Fixes the foreign key issue reported by Thom Brown and also adds
tests for FK and trigger handling.

0005 - Adds support for renaming publications and subscriptions.

All rebased on top of current master (90992e0).

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

Attachments:

0001-Use-asynchronous-connect-API-in-libpqwalreceiver.patchapplication/x-patch; name=0001-Use-asynchronous-connect-API-in-libpqwalreceiver.patchDownload
From e2c30b258e97fbba51c8d0f6e12ac557cafb129a Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Fri, 20 Jan 2017 21:20:56 +0100
Subject: [PATCH 1/5] Use asynchronous connect API in libpqwalreceiver

---
 src/backend/postmaster/pgstat.c                    |  4 +-
 .../libpqwalreceiver/libpqwalreceiver.c            | 58 +++++++++++++++++++++-
 src/include/pgstat.h                               |  2 +-
 3 files changed, 59 insertions(+), 5 deletions(-)

diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 7176cf1..19ad6b5 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -3340,8 +3340,8 @@ pgstat_get_wait_client(WaitEventClient w)
 		case WAIT_EVENT_WAL_RECEIVER_WAIT_START:
 			event_name = "WalReceiverWaitStart";
 			break;
-		case WAIT_EVENT_LIBPQWALRECEIVER_READ:
-			event_name = "LibPQWalReceiverRead";
+		case WAIT_EVENT_LIBPQWALRECEIVER:
+			event_name = "LibPQWalReceiver";
 			break;
 		case WAIT_EVENT_WAL_SENDER_WAIT_WAL:
 			event_name = "WalSenderWaitForWAL";
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 7df3698..8dc51d4 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -112,6 +112,7 @@ libpqrcv_connect(const char *conninfo, bool logical, const char *appname,
 				 char **err)
 {
 	WalReceiverConn *conn;
+	PostgresPollingStatusType status;
 	const char *keys[5];
 	const char *vals[5];
 	int			i = 0;
@@ -138,7 +139,60 @@ libpqrcv_connect(const char *conninfo, bool logical, const char *appname,
 	vals[i] = NULL;
 
 	conn = palloc0(sizeof(WalReceiverConn));
-	conn->streamConn = PQconnectdbParams(keys, vals, /* expand_dbname = */ true);
+	conn->streamConn = PQconnectStartParams(keys, vals,
+											/* expand_dbname = */ true);
+	/* Check for conn status. */
+	if (PQstatus(conn->streamConn) == CONNECTION_BAD)
+	{
+		*err = pstrdup(PQerrorMessage(conn->streamConn));
+		return NULL;
+	}
+
+	/* Poll connection. */
+	do
+	{
+		int			rc;
+		int			extra_flag;
+
+		/* Determine which function we should use for Polling. */
+		status = PQconnectPoll(conn->streamConn);
+
+		/* Next action based upon status value. */
+		switch (status)
+		{
+			case PGRES_POLLING_READING:
+				extra_flag = WL_SOCKET_READABLE;
+				/* pass through */
+			case PGRES_POLLING_WRITING:
+				extra_flag = WL_SOCKET_WRITEABLE;
+
+				ResetLatch(&MyProc->procLatch);
+				rc = WaitLatchOrSocket(&MyProc->procLatch,
+									   WL_POSTMASTER_DEATH |
+									   WL_LATCH_SET | extra_flag,
+									   PQsocket(conn->streamConn),
+									   0,
+									   WAIT_EVENT_LIBPQWALRECEIVER);
+				if (rc & WL_POSTMASTER_DEATH)
+					exit(1);
+
+				/* Interrupted. */
+				if (rc & WL_LATCH_SET)
+				{
+					CHECK_FOR_INTERRUPTS();
+					break;
+				}
+				break;
+
+			/* Either can continue polling or finished one way or another. */
+			default:
+				break;
+		}
+
+		/* Loop until we have OK or FAILED status. */
+	} while (status != PGRES_POLLING_OK && status != PGRES_POLLING_FAILED);
+
+	/* Check the status. */
 	if (PQstatus(conn->streamConn) != CONNECTION_OK)
 	{
 		*err = pstrdup(PQerrorMessage(conn->streamConn));
@@ -508,7 +562,7 @@ libpqrcv_PQexec(PGconn *streamConn, const char *query)
 								   WL_LATCH_SET,
 								   PQsocket(streamConn),
 								   0,
-								   WAIT_EVENT_LIBPQWALRECEIVER_READ);
+								   WAIT_EVENT_LIBPQWALRECEIVER);
 			if (rc & WL_POSTMASTER_DEATH)
 				exit(1);
 
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index de8225b..e088474 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -764,7 +764,7 @@ typedef enum
 	WAIT_EVENT_CLIENT_WRITE,
 	WAIT_EVENT_SSL_OPEN_SERVER,
 	WAIT_EVENT_WAL_RECEIVER_WAIT_START,
-	WAIT_EVENT_LIBPQWALRECEIVER_READ,
+	WAIT_EVENT_LIBPQWALRECEIVER,
 	WAIT_EVENT_WAL_SENDER_WAIT_WAL,
 	WAIT_EVENT_WAL_SENDER_WRITE_DATA
 } WaitEventClient;
-- 
2.7.4

0002-Close-replication-connection-when-slot-creation-gets.patchapplication/x-patch; name=0002-Close-replication-connection-when-slot-creation-gets.patchDownload
From 0c843de52ceeb04bab5da774dc0ea8a56cfe08ba Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Fri, 20 Jan 2017 22:22:33 +0100
Subject: [PATCH 2/5] Close replication connection when slot creation gets
 canceled

---
 src/backend/commands/subscriptioncmds.c | 18 ++++++++++++++----
 1 file changed, 14 insertions(+), 4 deletions(-)

diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 2b6d322..e0add94 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -301,10 +301,20 @@ CreateSubscription(CreateSubscriptionStmt *stmt)
 			ereport(ERROR,
 					(errmsg("could not connect to the publisher: %s", err)));
 
-		walrcv_create_slot(wrconn, slotname, false, &lsn);
-		ereport(NOTICE,
-				(errmsg("created replication slot \"%s\" on publisher",
-						slotname)));
+		PG_TRY();
+		{
+			walrcv_create_slot(wrconn, slotname, false, &lsn);
+			ereport(NOTICE,
+					(errmsg("created replication slot \"%s\" on publisher",
+							slotname)));
+		}
+		PG_CATCH();
+		{
+			/* Close the connection in case of failure. */
+			walrcv_disconnect(wrconn);
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
 
 		/* And we are done with the remote side. */
 		walrcv_disconnect(wrconn);
-- 
2.7.4

0003-Always-initialize-stringinfo-buffers-in-walsender.patchapplication/x-patch; name=0003-Always-initialize-stringinfo-buffers-in-walsender.patchDownload
From c7a2748c05dea330b166743680f7f668a904e181 Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Fri, 20 Jan 2017 22:22:53 +0100
Subject: [PATCH 3/5] Always initialize stringinfo buffers in walsender

---
 src/backend/replication/walsender.c | 18 ++++++++----------
 1 file changed, 8 insertions(+), 10 deletions(-)

diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index f3082c3..e03642a 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -241,6 +241,14 @@ InitWalSender(void)
 	 */
 	MarkPostmasterChildWalSender();
 	SendPostmasterSignal(PMSIGNAL_ADVANCE_STATE_MACHINE);
+
+	/*
+	 * Allocate buffers that will be used for each outgoing and incoming
+	 * message.
+	 */
+	initStringInfo(&output_message);
+	initStringInfo(&reply_message);
+	initStringInfo(&tmpbuf);
 }
 
 /*
@@ -816,8 +824,6 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL);
 	}
 
-	initStringInfo(&output_message);
-
 	if (cmd->kind == REPLICATION_KIND_LOGICAL)
 	{
 		LogicalDecodingContext *ctx;
@@ -1802,14 +1808,6 @@ static void
 WalSndLoop(WalSndSendDataCallback send_data)
 {
 	/*
-	 * Allocate buffers that will be used for each outgoing and incoming
-	 * message.  We do this just once to reduce palloc overhead.
-	 */
-	initStringInfo(&output_message);
-	initStringInfo(&reply_message);
-	initStringInfo(&tmpbuf);
-
-	/*
 	 * Initialize the last reply timestamp. That enables timeout processing
 	 * from hereon.
 	 */
-- 
2.7.4

0004-Fix-after-trigger-execution-in-logical-replication.patchapplication/x-patch; name=0004-Fix-after-trigger-execution-in-logical-replication.patchDownload
From d90701b04887c4bcb21e11d7ec44af65bb180f67 Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Sun, 22 Jan 2017 23:16:57 +0100
Subject: [PATCH 4/5] Fix after trigger execution in logical replication

---
 src/backend/replication/logical/worker.c   |  15 ++++
 src/test/subscription/t/003_constraints.pl | 113 +++++++++++++++++++++++++++++
 2 files changed, 128 insertions(+)
 create mode 100644 src/test/subscription/t/003_constraints.pl

diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 7d86736..6229bef 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -176,6 +176,9 @@ create_estate_for_relation(LogicalRepRelMapEntry *rel)
 	if (resultRelInfo->ri_TrigDesc)
 		estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate);
 
+	/* Prepare to catch AFTER triggers. */
+	AfterTriggerBeginQuery();
+
 	return estate;
 }
 
@@ -536,6 +539,10 @@ apply_handle_insert(StringInfo s)
 	/* Cleanup. */
 	ExecCloseIndices(estate->es_result_relation_info);
 	PopActiveSnapshot();
+
+	/* Handle queued AFTER triggers. */
+	AfterTriggerEndQuery(estate);
+
 	ExecResetTupleTable(estate->es_tupleTable, false);
 	FreeExecutorState(estate);
 
@@ -676,6 +683,10 @@ apply_handle_update(StringInfo s)
 	/* Cleanup. */
 	ExecCloseIndices(estate->es_result_relation_info);
 	PopActiveSnapshot();
+
+	/* Handle queued AFTER triggers. */
+	AfterTriggerEndQuery(estate);
+
 	EvalPlanQualEnd(&epqstate);
 	ExecResetTupleTable(estate->es_tupleTable, false);
 	FreeExecutorState(estate);
@@ -763,6 +774,10 @@ apply_handle_delete(StringInfo s)
 	/* Cleanup. */
 	ExecCloseIndices(estate->es_result_relation_info);
 	PopActiveSnapshot();
+
+	/* Handle queued AFTER triggers. */
+	AfterTriggerEndQuery(estate);
+
 	EvalPlanQualEnd(&epqstate);
 	ExecResetTupleTable(estate->es_tupleTable, false);
 	FreeExecutorState(estate);
diff --git a/src/test/subscription/t/003_constraints.pl b/src/test/subscription/t/003_constraints.pl
new file mode 100644
index 0000000..203322f
--- /dev/null
+++ b/src/test/subscription/t/003_constraints.pl
@@ -0,0 +1,113 @@
+# Basic logical replication test
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 4;
+
+# Initialize publisher node
+my $node_publisher = get_new_node('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = get_new_node('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Setup structure on publisher
+$node_publisher->safe_psql('postgres',
+	"CREATE TABLE tab_fk (bid int PRIMARY KEY);");
+$node_publisher->safe_psql('postgres',
+	"CREATE TABLE tab_fk_ref (id int PRIMARY KEY, bid int REFERENCES tab_fk (bid));");
+
+# Setup structure on subscriber
+$node_subscriber->safe_psql('postgres',
+	"CREATE TABLE tab_fk (bid int PRIMARY KEY);");
+$node_subscriber->safe_psql('postgres',
+	"CREATE TABLE tab_fk_ref (id int PRIMARY KEY, bid int REFERENCES tab_fk (bid));");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+	"CREATE PUBLICATION tap_pub FOR ALL TABLES;");
+
+my $appname = 'tap_sub';
+$node_subscriber->safe_psql('postgres',
+	"CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr application_name=$appname' PUBLICATION tap_pub");
+
+# Wait for subscriber to finish initialization
+my $caughtup_query =
+"SELECT pg_current_xlog_location() <= replay_location FROM pg_stat_replication WHERE application_name = '$appname';";
+$node_publisher->poll_query_until('postgres', $caughtup_query)
+  or die "Timed out while waiting for subscriber to catch up";
+
+$node_publisher->safe_psql('postgres',
+	"INSERT INTO tab_fk (bid) VALUES (1);");
+$node_publisher->safe_psql('postgres',
+	"INSERT INTO tab_fk_ref (id, bid) VALUES (1,1);");
+
+$node_publisher->poll_query_until('postgres', $caughtup_query)
+  or die "Timed out while waiting for subscriber to catch up";
+
+# Check data on subscriber
+my $result =
+  $node_subscriber->safe_psql('postgres', "SELECT count(*), min(bid), max(bid) FROM tab_fk");
+is($result, qq(1|1|1), 'check replicated tab_fk inserts on subscriber');
+
+$result =
+  $node_subscriber->safe_psql('postgres', "SELECT count(*), min(bid), max(bid) FROM tab_fk_ref");
+is($result, qq(1|1|1), 'check replicated tab_fk_ref inserts on subscriber');
+
+# Drop the fk on provider
+$node_publisher->safe_psql('postgres',
+	"DROP TABLE tab_fk CASCADE;");
+
+# Insert data
+$node_publisher->safe_psql('postgres',
+	"INSERT INTO tab_fk_ref (id, bid) VALUES (2,2);");
+
+$node_publisher->poll_query_until('postgres', $caughtup_query)
+  or die "Timed out while waiting for subscriber to catch up";
+
+# FK is not enforced on subscriber
+$result =
+  $node_subscriber->safe_psql('postgres', "SELECT count(*), min(bid), max(bid) FROM tab_fk_ref");
+is($result, qq(2|1|2), 'check FK ignored on subscriber');
+
+# Add replica trigger
+$node_subscriber->safe_psql('postgres', qq{
+CREATE FUNCTION filter_basic_dml_fn() RETURNS TRIGGER AS \$\$
+BEGIN
+    IF (TG_OP = 'INSERT') THEN
+        IF (NEW.id < 10) THEN
+            RETURN NEW;
+        ELSE
+            RETURN NULL;
+        END IF;
+    ELSE
+        RAISE WARNING 'Unknown action';
+        RETURN NULL;
+    END IF;
+END;
+\$\$ LANGUAGE plpgsql;
+CREATE TRIGGER filter_basic_dml_trg
+BEFORE INSERT ON tab_fk_ref
+FOR EACH ROW EXECUTE PROCEDURE filter_basic_dml_fn();
+ALTER TABLE tab_fk_ref ENABLE REPLICA TRIGGER filter_basic_dml_trg;
+});
+
+# Insert data
+$node_publisher->safe_psql('postgres',
+	"INSERT INTO tab_fk_ref (id, bid) VALUES (10,10);");
+
+$node_publisher->poll_query_until('postgres', $caughtup_query)
+  or die "Timed out while waiting for subscriber to catch up";
+
+# The row should be skipped on subscriber
+$result =
+  $node_subscriber->safe_psql('postgres', "SELECT count(*), min(bid), max(bid) FROM tab_fk_ref");
+is($result, qq(2|1|2), 'check skipped insert on subscriber');
+
+$node_subscriber->stop('fast');
+$node_publisher->stop('fast');
-- 
2.7.4

0005-Add-RENAME-support-for-PUBLICATIONs-and-SUBSCRIPTION.patchapplication/x-patch; name=0005-Add-RENAME-support-for-PUBLICATIONs-and-SUBSCRIPTION.patchDownload
From faf97e6f698de1f2d6fd8f286033a6bf723d0ec7 Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Thu, 19 Jan 2017 00:59:01 +0100
Subject: [PATCH 5/5] Add RENAME support for PUBLICATIONs and SUBSCRIPTIONs

---
 src/backend/commands/alter.c               |  6 ++++
 src/backend/commands/publicationcmds.c     | 50 ++++++++++++++++++++++++++++++
 src/backend/commands/subscriptioncmds.c    | 50 ++++++++++++++++++++++++++++++
 src/backend/parser/gram.y                  | 18 +++++++++++
 src/backend/replication/logical/worker.c   | 16 +++++++++-
 src/bin/psql/tab-complete.c                |  6 ++--
 src/include/commands/publicationcmds.h     |  2 ++
 src/include/commands/subscriptioncmds.h    |  2 ++
 src/test/regress/expected/publication.out  | 10 +++++-
 src/test/regress/expected/subscription.out | 10 +++++-
 src/test/regress/sql/publication.sql       |  6 +++-
 src/test/regress/sql/subscription.sql      |  6 +++-
 src/test/subscription/t/001_rep_changes.pl | 11 ++++++-
 13 files changed, 185 insertions(+), 8 deletions(-)

diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 768fcc8..1a4154c 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -351,6 +351,12 @@ ExecRenameStmt(RenameStmt *stmt)
 		case OBJECT_TYPE:
 			return RenameType(stmt);
 
+		case OBJECT_PUBLICATION:
+			return RenamePublication(stmt);
+
+		case OBJECT_SUBSCRIPTION:
+			return RenameSubscription(stmt);
+
 		case OBJECT_AGGREGATE:
 		case OBJECT_COLLATION:
 		case OBJECT_CONVERSION:
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 63dcc10..5bad7fc 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -754,3 +754,53 @@ AlterPublicationOwner_oid(Oid subid, Oid newOwnerId)
 
 	heap_close(rel, RowExclusiveLock);
 }
+
+/*
+ * Rename the publication.
+ */
+ObjectAddress
+RenamePublication(RenameStmt *stmt)
+{
+	Oid			pubid;
+	HeapTuple	tup;
+	Relation	rel;
+	ObjectAddress address;
+
+	rel = heap_open(PublicationRelationId, RowExclusiveLock);
+
+	tup = SearchSysCacheCopy1(PUBLICATIONNAME,
+							  CStringGetDatum(stmt->subname));
+
+	if (!HeapTupleIsValid(tup))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("publication \"%s\" does not exist", stmt->subname)));
+
+	/* make sure the new name doesn't exist */
+	if (OidIsValid(get_publication_oid(stmt->newname, true)))
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_SCHEMA),
+				 errmsg("publication \"%s\" already exists", stmt->newname)));
+
+	pubid = HeapTupleGetOid(tup);
+
+	/* Must be owner. */
+	if (!pg_publication_ownercheck(pubid, GetUserId()))
+		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PUBLICATION,
+					   stmt->subname);
+
+	/* rename */
+	namestrcpy(&(((Form_pg_publication) GETSTRUCT(tup))->pubname),
+			   stmt->newname);
+	simple_heap_update(rel, &tup->t_self, tup);
+	CatalogUpdateIndexes(rel, tup);
+
+	InvokeObjectPostAlterHook(PublicationRelationId, pubid, 0);
+
+	ObjectAddressSet(address, PublicationRelationId, pubid);
+
+	heap_close(rel, NoLock);
+	heap_freetuple(tup);
+
+	return address;
+}
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index e0add94..a5a057e 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -657,3 +657,53 @@ AlterSubscriptionOwner_oid(Oid subid, Oid newOwnerId)
 
 	heap_close(rel, RowExclusiveLock);
 }
+
+/*
+ * Rename the subscription.
+ */
+ObjectAddress
+RenameSubscription(RenameStmt *stmt)
+{
+	Oid			subid;
+	HeapTuple	tup;
+	Relation	rel;
+	ObjectAddress address;
+
+	rel = heap_open(SubscriptionRelationId, RowExclusiveLock);
+
+	tup = SearchSysCacheCopy2(SUBSCRIPTIONNAME, MyDatabaseId,
+							  CStringGetDatum(stmt->subname));
+
+	if (!HeapTupleIsValid(tup))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("subscription \"%s\" does not exist", stmt->subname)));
+
+	/* make sure the new name doesn't exist */
+	if (OidIsValid(get_subscription_oid(stmt->newname, true)))
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_SCHEMA),
+				 errmsg("subscription \"%s\" already exists", stmt->newname)));
+
+	subid = HeapTupleGetOid(tup);
+
+	/* Must be owner. */
+	if (!pg_subscription_ownercheck(subid, GetUserId()))
+		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_SUBSCRIPTION,
+					   stmt->subname);
+
+	/* rename */
+	namestrcpy(&(((Form_pg_subscription) GETSTRUCT(tup))->subname),
+			   stmt->newname);
+	simple_heap_update(rel, &tup->t_self, tup);
+	CatalogUpdateIndexes(rel, tup);
+
+	InvokeObjectPostAlterHook(SubscriptionRelationId, subid, 0);
+
+	ObjectAddressSet(address, SubscriptionRelationId, subid);
+
+	heap_close(rel, NoLock);
+	heap_freetuple(tup);
+
+	return address;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a8e35fe..712dfdd 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -8475,6 +8475,24 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			| ALTER PUBLICATION name RENAME TO name
+				{
+					RenameStmt *n = makeNode(RenameStmt);
+					n->renameType = OBJECT_PUBLICATION;
+					n->subname = $3;
+					n->newname = $6;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+			| ALTER SUBSCRIPTION name RENAME TO name
+				{
+					RenameStmt *n = makeNode(RenameStmt);
+					n->renameType = OBJECT_SUBSCRIPTION;
+					n->subname = $3;
+					n->newname = $6;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 		;
 
 opt_column: COLUMN									{ $$ = COLUMN; }
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 6229bef..2a7e6f5 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -1264,6 +1264,21 @@ reread_subscription(void)
 	}
 
 	/*
+	 * Exit if subscription name was changed (it's used for
+	 * fallback_application_name). The launcher will start new worker.
+	 */
+	if (strcmp(newsub->name, MySubscription->name) != 0)
+	{
+		ereport(LOG,
+				(errmsg("logical replication worker for subscription \"%s\" will "
+						"restart because subscription was renamed",
+						MySubscription->name)));
+
+		walrcv_disconnect(wrconn);
+		proc_exit(0);
+	}
+
+	/*
 	 * Exit if the subscription was removed.
 	 * This normally should not happen as the worker gets killed
 	 * during DROP SUBSCRIPTION.
@@ -1297,7 +1312,6 @@ reread_subscription(void)
 
 	/* Check for other changes that should never happen too. */
 	if (newsub->dbid != MySubscription->dbid ||
-		strcmp(newsub->name, MySubscription->name) != 0 ||
 		strcmp(newsub->slotname, MySubscription->slotname) != 0)
 	{
 		elog(ERROR, "subscription %u changed unexpectedly",
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index d6fffcf..a8256e8 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1438,7 +1438,8 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER PUBLICATION <name> ...*/
 	else if (Matches3("ALTER","PUBLICATION",MatchAny))
 	{
-		COMPLETE_WITH_LIST5("WITH", "ADD TABLE", "SET TABLE", "DROP TABLE", "OWNER TO");
+		COMPLETE_WITH_LIST6("WITH", "ADD TABLE", "SET TABLE", "DROP TABLE",
+							"OWNER TO", "RENAME TO");
 	}
 	/* ALTER PUBLICATION <name> .. WITH ( ... */
 	else if (HeadMatches3("ALTER", "PUBLICATION",MatchAny) && TailMatches2("WITH", "("))
@@ -1449,7 +1450,8 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER SUBSCRIPTION <name> ... */
 	else if (Matches3("ALTER","SUBSCRIPTION",MatchAny))
 	{
-		COMPLETE_WITH_LIST6("WITH", "CONNECTION", "SET PUBLICATION", "ENABLE", "DISABLE", "OWNER TO");
+		COMPLETE_WITH_LIST7("WITH", "CONNECTION", "SET PUBLICATION", "ENABLE",
+							"DISABLE", "OWNER TO", "RENAME TO");
 	}
 	else if (HeadMatches3("ALTER", "SUBSCRIPTION", MatchAny) && TailMatches2("WITH", "("))
 	{
diff --git a/src/include/commands/publicationcmds.h b/src/include/commands/publicationcmds.h
index cdacfa6..7053373 100644
--- a/src/include/commands/publicationcmds.h
+++ b/src/include/commands/publicationcmds.h
@@ -26,4 +26,6 @@ extern void RemovePublicationRelById(Oid proid);
 extern ObjectAddress AlterPublicationOwner(const char *name, Oid newOwnerId);
 extern void AlterPublicationOwner_oid(Oid pubid, Oid newOwnerId);
 
+extern ObjectAddress RenamePublication(RenameStmt *stmt);
+
 #endif   /* PUBLICATIONCMDS_H */
diff --git a/src/include/commands/subscriptioncmds.h b/src/include/commands/subscriptioncmds.h
index 87c1a27..65c8238 100644
--- a/src/include/commands/subscriptioncmds.h
+++ b/src/include/commands/subscriptioncmds.h
@@ -25,4 +25,6 @@ extern void DropSubscription(DropSubscriptionStmt *stmt);
 extern ObjectAddress AlterSubscriptionOwner(const char *name, Oid newOwnerId);
 extern void AlterSubscriptionOwner_oid(Oid subid, Oid newOwnerId);
 
+extern ObjectAddress RenameSubscription(RenameStmt *stmt);
+
 #endif   /* SUBSCRIPTIONCMDS_H */
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 5784b0f..6416fbb 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -148,7 +148,15 @@ DROP TABLE testpub_tbl1;
  t       | t       | t
 (1 row)
 
-DROP PUBLICATION testpub_default;
+ALTER PUBLICATION testpub_default RENAME TO testpub_foo;
+\dRp testpub_foo
+                         List of publications
+    Name     |          Owner           | Inserts | Updates | Deletes 
+-------------+--------------------------+---------+---------+---------
+ testpub_foo | regress_publication_user | t       | t       | t
+(1 row)
+
+DROP PUBLICATION testpub_foo;
 DROP PUBLICATION testpib_ins_trunct;
 DROP PUBLICATION testpub_fortbl;
 DROP SCHEMA pub_test CASCADE;
diff --git a/src/test/regress/expected/subscription.out b/src/test/regress/expected/subscription.out
index 2ccec98..cb1ab4e 100644
--- a/src/test/regress/expected/subscription.out
+++ b/src/test/regress/expected/subscription.out
@@ -61,6 +61,14 @@ ALTER SUBSCRIPTION testsub DISABLE;
 (1 row)
 
 COMMIT;
-DROP SUBSCRIPTION testsub NODROP SLOT;
+ALTER SUBSCRIPTION testsub RENAME TO testsub_foo;
+\dRs
+                         List of subscriptions
+    Name     |           Owner           | Enabled |    Publication     
+-------------+---------------------------+---------+--------------------
+ testsub_foo | regress_subscription_user | f       | {testpub,testpub1}
+(1 row)
+
+DROP SUBSCRIPTION testsub_foo NODROP SLOT;
 RESET SESSION AUTHORIZATION;
 DROP ROLE regress_subscription_user;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 8779788..9563ea1 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -73,7 +73,11 @@ DROP TABLE testpub_tbl1;
 
 \dRp+ testpub_default
 
-DROP PUBLICATION testpub_default;
+ALTER PUBLICATION testpub_default RENAME TO testpub_foo;
+
+\dRp testpub_foo
+
+DROP PUBLICATION testpub_foo;
 DROP PUBLICATION testpib_ins_trunct;
 DROP PUBLICATION testpub_fortbl;
 
diff --git a/src/test/regress/sql/subscription.sql b/src/test/regress/sql/subscription.sql
index 68c17d5..fce6069 100644
--- a/src/test/regress/sql/subscription.sql
+++ b/src/test/regress/sql/subscription.sql
@@ -38,7 +38,11 @@ ALTER SUBSCRIPTION testsub DISABLE;
 
 COMMIT;
 
-DROP SUBSCRIPTION testsub NODROP SLOT;
+ALTER SUBSCRIPTION testsub RENAME TO testsub_foo;
+
+\dRs
+
+DROP SUBSCRIPTION testsub_foo NODROP SLOT;
 
 RESET SESSION AUTHORIZATION;
 DROP ROLE regress_subscription_user;
diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl
index b51740b..a9c4b01 100644
--- a/src/test/subscription/t/001_rep_changes.pl
+++ b/src/test/subscription/t/001_rep_changes.pl
@@ -169,8 +169,17 @@ $result =
   $node_subscriber->safe_psql('postgres', "SELECT count(*), min(a), max(a) FROM tab_full");
 is($result, qq(11|0|100), 'check replicated insert after alter publication');
 
+# check restart on rename
+$oldpid = $node_publisher->safe_psql('postgres',
+	"SELECT pid FROM pg_stat_replication WHERE application_name = '$appname';");
+$node_subscriber->safe_psql('postgres',
+	"ALTER SUBSCRIPTION tap_sub RENAME TO tap_sub_renamed");
+$node_publisher->poll_query_until('postgres',
+	"SELECT pid != $oldpid FROM pg_stat_replication WHERE application_name = '$appname';")
+  or die "Timed out while waiting for apply to restart";
+
 # check all the cleanup
-$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub");
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_renamed");
 
 $result =
   $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM pg_subscription");
-- 
2.7.4

#214Thom Brown
thom@linux.com
In reply to: Petr Jelinek (#213)
Re: Logical Replication WIP

On 23 January 2017 at 01:11, Petr Jelinek <petr.jelinek@2ndquadrant.com> wrote:

On 20/01/17 22:30, Petr Jelinek wrote:

Since it's not exactly straight forward to find when these need to be
initialized based on commands, I decided to move the initialization code
to exec_replication_command() since that's always called before anything
so that makes it much less error prone (patch 0003).

The 0003 should be backpatched all the way to 9.4 where multiple
commands started using those buffers.

Actually there is better place, the WalSndInit().

Just to make it easier for PeterE (or whichever committer picks this up)
I attached all the logical replication followup fix/polish patches:

0001 - Changes the libpqrcv_connect to use async libpq api so that it
won't get stuck forever in case of connect is stuck. This is preexisting
bug that also affects walreceiver but it's less visible there as there
is no SQL interface to initiate connection there.

0002 - Close replication connection when CREATE SUBSCRIPTION gets
canceled (otherwise walsender on the other side may stay in idle in
transaction state).

0003 - Fixes buffer initialization in walsender that I found when
testing the above two. This one should be back-patched to 9.4 since it's
broken since then.

0004 - Fixes the foreign key issue reported by Thom Brown and also adds
tests for FK and trigger handling.

This fixes the problem for me. Thanks.

0005 - Adds support for renaming publications and subscriptions.

Works for me.

I haven't tested the first 3.

Regards

Thom

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

#215Fujii Masao
masao.fujii@gmail.com
In reply to: Petr Jelinek (#207)
Re: Logical Replication WIP

On Sat, Jan 21, 2017 at 1:39 AM, Petr Jelinek
<petr.jelinek@2ndquadrant.com> wrote:

On 20/01/17 17:33, Jaime Casanova wrote:

On 20 January 2017 at 11:25, Petr Jelinek <petr.jelinek@2ndquadrant.com> wrote:

On 20/01/17 17:05, Fujii Masao wrote:

On Fri, Jan 20, 2017 at 11:08 PM, Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:

On 1/19/17 5:01 PM, Petr Jelinek wrote:

There were some conflicting changes committed today so I rebased the
patch on top of them.

Other than that nothing much has changed, I removed the separate sync
commit patch, included the rename patch in the patchset and fixed the
bug around pg_subscription catalog reported by Erik Rijkers.

Committed.

Sorry I've not followed the discussion about logical replication at all, but
why does logical replication launcher need to start up by default?

Because running subscriptions is allowed by default. You'd need to set
max_logical_replication_workers to 0 to disable that.

surely wal_level < logical shouldn't start a logical replication
launcher, and after an initdb wal_level is only replica

Launcher is needed for subscriptions, subscriptions don't depend on
wal_level.

But why did you enable only subscription by default while publication is
disabled by default (i.e., wal_level != logical)? I think that it's better to
enable both by default OR disable both by default.

While I was reading the logical rep code, I found that
logicalrep_worker_launch returns *without* releasing LogicalRepWorkerLock
when there is no unused worker slot. This seems a bug.

/* Report this after the initial starting message for consistency. */
if (max_replication_slots == 0)
ereport(ERROR,
(errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
errmsg("cannot start logical replication workers when
max_replication_slots = 0")));

logicalrep_worker_launch checks max_replication_slots as above.
Why does it need to check that setting value in the *subscriber* side?
Maybe I'm missing something here, but ISTM that the subscription uses
one replication slot in *publisher* side but doesn't use in *subscriber* side.

* The apply worker may spawn additional workers (sync) for initial data
* synchronization of tables.

The above header comment in logical/worker.c is true?

The copyright in each file that the commit of logical rep added needs to
be updated.

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

#216Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Fujii Masao (#215)
1 attachment(s)
Re: Logical Replication WIP

On 23/01/17 17:19, Fujii Masao wrote:

On Sat, Jan 21, 2017 at 1:39 AM, Petr Jelinek
<petr.jelinek@2ndquadrant.com> wrote:

On 20/01/17 17:33, Jaime Casanova wrote:

On 20 January 2017 at 11:25, Petr Jelinek <petr.jelinek@2ndquadrant.com> wrote:

On 20/01/17 17:05, Fujii Masao wrote:

On Fri, Jan 20, 2017 at 11:08 PM, Peter Eisentraut
<peter.eisentraut@2ndquadrant.com> wrote:

On 1/19/17 5:01 PM, Petr Jelinek wrote:

There were some conflicting changes committed today so I rebased the
patch on top of them.

Other than that nothing much has changed, I removed the separate sync
commit patch, included the rename patch in the patchset and fixed the
bug around pg_subscription catalog reported by Erik Rijkers.

Committed.

Sorry I've not followed the discussion about logical replication at all, but
why does logical replication launcher need to start up by default?

Because running subscriptions is allowed by default. You'd need to set
max_logical_replication_workers to 0 to disable that.

surely wal_level < logical shouldn't start a logical replication
launcher, and after an initdb wal_level is only replica

Launcher is needed for subscriptions, subscriptions don't depend on
wal_level.

But why did you enable only subscription by default while publication is
disabled by default (i.e., wal_level != logical)? I think that it's better to
enable both by default OR disable both by default.

That depends, the wal_level = logical by default was deemed to not be
worth the potential overhead in the thread about wal_level thread. There
is no such overhead associated with enabling subscription, one could say
that it's less work this way to setup whole thing. But I guess it's up
for a debate.

While I was reading the logical rep code, I found that
logicalrep_worker_launch returns *without* releasing LogicalRepWorkerLock
when there is no unused worker slot. This seems a bug.

True, fix attached.

/* Report this after the initial starting message for consistency. */
if (max_replication_slots == 0)
ereport(ERROR,
(errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
errmsg("cannot start logical replication workers when
max_replication_slots = 0")));

logicalrep_worker_launch checks max_replication_slots as above.
Why does it need to check that setting value in the *subscriber* side?
Maybe I'm missing something here, but ISTM that the subscription uses
one replication slot in *publisher* side but doesn't use in *subscriber* side.

Because replication origins are also limited by the
max_replication_slots and they are required for subscription to work (I
am not quite sure why it's the case, I guess we wanted to save GUC).

* The apply worker may spawn additional workers (sync) for initial data
* synchronization of tables.

The above header comment in logical/worker.c is true?

Hmm not yet, there is separate patch for it in CF, I guess it got
through the cracks while rebasing.

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

Attachments:

0001-Release-lock-on-failure-to-launch-replication-worker.patchapplication/x-patch; name=0001-Release-lock-on-failure-to-launch-replication-worker.patchDownload
From 1492ef374e3de60c112fe8e09225a788aa548755 Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Mon, 23 Jan 2017 17:50:27 +0100
Subject: [PATCH] Release lock on failure to launch replication worker

---
 src/backend/replication/logical/launcher.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index d9ad66d..cb415f8 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -261,6 +261,7 @@ logicalrep_worker_launch(Oid dbid, Oid subid, const char *subname, Oid userid)
 	/* Bail if not found */
 	if (worker == NULL)
 	{
+		LWLockRelease(LogicalRepWorkerLock);
 		ereport(WARNING,
 				(errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
 				 errmsg("out of logical replication workers slots"),
-- 
2.7.4

#217Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Petr Jelinek (#213)
Re: Logical Replication WIP

On 1/22/17 8:11 PM, Petr Jelinek wrote:

0001 - Changes the libpqrcv_connect to use async libpq api so that it
won't get stuck forever in case of connect is stuck. This is preexisting
bug that also affects walreceiver but it's less visible there as there
is no SQL interface to initiate connection there.

Probably a mistake here:

+                       case PGRES_POLLING_READING:
+                               extra_flag = WL_SOCKET_READABLE;
+                               /* pass through */
+                       case PGRES_POLLING_WRITING:
+                               extra_flag = WL_SOCKET_WRITEABLE;

extra_flag gets overwritten in the reading case.

Please elaborate in the commit message what this change is for.

0002 - Close replication connection when CREATE SUBSCRIPTION gets
canceled (otherwise walsender on the other side may stay in idle in
transaction state).

committed

0003 - Fixes buffer initialization in walsender that I found when
testing the above two. This one should be back-patched to 9.4 since it's
broken since then.

Can you explain more in which code path this problem occurs?

I think we should get rid of the global variables and give each function
its own buffer that it initializes the first time through. Otherwise
we'll keep having to worry about this.

0004 - Fixes the foreign key issue reported by Thom Brown and also adds
tests for FK and trigger handling.

I think the trigger handing should go into execReplication.c.

0005 - Adds support for renaming publications and subscriptions.

Could those not be handled in the generic rename support in
ExecRenameStmt()?

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#218Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Fujii Masao (#215)
Re: Logical Replication WIP

On 1/23/17 11:19 AM, Fujii Masao wrote:

The copyright in each file that the commit of logical rep added needs to
be updated.

I have fixed that.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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

#219Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Peter Eisentraut (#217)
4 attachment(s)
Re: Logical Replication WIP

On 25/01/17 18:16, Peter Eisentraut wrote:

On 1/22/17 8:11 PM, Petr Jelinek wrote:

0001 - Changes the libpqrcv_connect to use async libpq api so that it
won't get stuck forever in case of connect is stuck. This is preexisting
bug that also affects walreceiver but it's less visible there as there
is no SQL interface to initiate connection there.

Probably a mistake here:

+                       case PGRES_POLLING_READING:
+                               extra_flag = WL_SOCKET_READABLE;
+                               /* pass through */
+                       case PGRES_POLLING_WRITING:
+                               extra_flag = WL_SOCKET_WRITEABLE;

extra_flag gets overwritten in the reading case.

Eh, reworked that to just if statement as switch does not really buy us
anything there.

Please elaborate in the commit message what this change is for.

Okay.

0002 - Close replication connection when CREATE SUBSCRIPTION gets
canceled (otherwise walsender on the other side may stay in idle in
transaction state).

committed

Thanks!

0003 - Fixes buffer initialization in walsender that I found when
testing the above two. This one should be back-patched to 9.4 since it's
broken since then.

Can you explain more in which code path this problem occurs?

With existing code base, anything that calls WalSndWaitForWal (it calls
ProcessRepliesIfAny()) which is called from logical_read_xlog_page which
is given as callback to logical decoding in CreateReplicationSlot and
StartLogicalReplication.

The reason why I decided to put it into init is that following up all
the paths to where buffers are used is rather complicated due to various
callbacks so if anybody else starts poking around in the future it might
get easily broken again if we don't initialize those unconditionally
(plus the memory footprint is few kB and in usual use of WalSender they
will eventually be initialized anyway as they are needed for streaming).

I think we should get rid of the global variables and give each function
its own buffer that it initializes the first time through. Otherwise
we'll keep having to worry about this.

Because of above, it would mean some refactoring in logical decoding
APIs not just in WalSender so that would not be backpatchable (and in
general it's much bigger patch then).

0004 - Fixes the foreign key issue reported by Thom Brown and also adds
tests for FK and trigger handling.

I think the trigger handing should go into execReplication.c.

Not in the current state, eventually (and I am afraid that PG11 material
at this point as we still have partitioned tables support and initial
data copy to finish in this release) we'll want to move all the executor
state code to execReplication.c and do less of reinitialization but in
the current code the trigger stuff belongs to worker IMHO.

0005 - Adds support for renaming publications and subscriptions.

Could those not be handled in the generic rename support in
ExecRenameStmt()?

Yes seems they can.

Attached updated version of the uncommitted patches.

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

Attachments:

0001-Use-asynchronous-connect-API-in-libpqwalreceiver-v2.patchapplication/x-patch; name=0001-Use-asynchronous-connect-API-in-libpqwalreceiver-v2.patchDownload
From 980ce872862e7a9abcbec14864721103507b5136 Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Fri, 20 Jan 2017 21:20:56 +0100
Subject: [PATCH 1/4] Use asynchronous connect API in libpqwalreceiver

This makes the connection attempt from CREATE SUBSCRIPTION and from
WalReceiver interruptable by user in case the libpq connection is
hanging. The previous coding required immediate shutdown (SIGQUIT) of
PostgreSQL in that situation.
---
 src/backend/postmaster/pgstat.c                    |  4 +-
 .../libpqwalreceiver/libpqwalreceiver.c            | 51 +++++++++++++++++++++-
 src/include/pgstat.h                               |  2 +-
 3 files changed, 52 insertions(+), 5 deletions(-)

diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 7176cf1..19ad6b5 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -3340,8 +3340,8 @@ pgstat_get_wait_client(WaitEventClient w)
 		case WAIT_EVENT_WAL_RECEIVER_WAIT_START:
 			event_name = "WalReceiverWaitStart";
 			break;
-		case WAIT_EVENT_LIBPQWALRECEIVER_READ:
-			event_name = "LibPQWalReceiverRead";
+		case WAIT_EVENT_LIBPQWALRECEIVER:
+			event_name = "LibPQWalReceiver";
 			break;
 		case WAIT_EVENT_WAL_SENDER_WAIT_WAL:
 			event_name = "WalSenderWaitForWAL";
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 44a89c7..536324c 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -112,6 +112,7 @@ libpqrcv_connect(const char *conninfo, bool logical, const char *appname,
 				 char **err)
 {
 	WalReceiverConn *conn;
+	PostgresPollingStatusType status;
 	const char *keys[5];
 	const char *vals[5];
 	int			i = 0;
@@ -138,7 +139,53 @@ libpqrcv_connect(const char *conninfo, bool logical, const char *appname,
 	vals[i] = NULL;
 
 	conn = palloc0(sizeof(WalReceiverConn));
-	conn->streamConn = PQconnectdbParams(keys, vals, /* expand_dbname = */ true);
+	conn->streamConn = PQconnectStartParams(keys, vals,
+											/* expand_dbname = */ true);
+	/* Check for conn status. */
+	if (PQstatus(conn->streamConn) == CONNECTION_BAD)
+	{
+		*err = pstrdup(PQerrorMessage(conn->streamConn));
+		return NULL;
+	}
+
+	/* Poll connection. */
+	do
+	{
+		int		rc;
+
+		/* Determine current state of the connection. */
+		status = PQconnectPoll(conn->streamConn);
+
+		/* Sleep a bit if waiting for socket. */
+		if (status == PGRES_POLLING_READING ||
+			status == PGRES_POLLING_WRITING)
+		{
+			int		extra_flag;
+
+			extra_flag = PGRES_POLLING_READING ? WL_SOCKET_READABLE :
+				WL_SOCKET_WRITEABLE;
+
+			ResetLatch(&MyProc->procLatch);
+			rc = WaitLatchOrSocket(&MyProc->procLatch,
+								   WL_POSTMASTER_DEATH |
+								   WL_LATCH_SET | extra_flag,
+								   PQsocket(conn->streamConn),
+								   0,
+								   WAIT_EVENT_LIBPQWALRECEIVER);
+
+			/* Emergency bailout. */
+			if (rc & WL_POSTMASTER_DEATH)
+				exit(1);
+
+			/* Interrupted. */
+			if (rc & WL_LATCH_SET)
+				CHECK_FOR_INTERRUPTS();
+		}
+
+		/* Otherwise loop until we have OK or FAILED status. */
+	} while (status != PGRES_POLLING_OK && status != PGRES_POLLING_FAILED);
+
+	/* Check the status. */
 	if (PQstatus(conn->streamConn) != CONNECTION_OK)
 	{
 		*err = pstrdup(PQerrorMessage(conn->streamConn));
@@ -521,7 +568,7 @@ libpqrcv_PQexec(PGconn *streamConn, const char *query)
 								   WL_LATCH_SET,
 								   PQsocket(streamConn),
 								   0,
-								   WAIT_EVENT_LIBPQWALRECEIVER_READ);
+								   WAIT_EVENT_LIBPQWALRECEIVER);
 			if (rc & WL_POSTMASTER_DEATH)
 				exit(1);
 
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index de8225b..e088474 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -764,7 +764,7 @@ typedef enum
 	WAIT_EVENT_CLIENT_WRITE,
 	WAIT_EVENT_SSL_OPEN_SERVER,
 	WAIT_EVENT_WAL_RECEIVER_WAIT_START,
-	WAIT_EVENT_LIBPQWALRECEIVER_READ,
+	WAIT_EVENT_LIBPQWALRECEIVER,
 	WAIT_EVENT_WAL_SENDER_WAIT_WAL,
 	WAIT_EVENT_WAL_SENDER_WRITE_DATA
 } WaitEventClient;
-- 
2.7.4

0002-Always-initialize-stringinfo-buffers-in-walsender-v2.patchapplication/x-patch; name=0002-Always-initialize-stringinfo-buffers-in-walsender-v2.patchDownload
From a4003b904e9edb6f8043c33b65ce6791feb6cce3 Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Fri, 20 Jan 2017 22:22:53 +0100
Subject: [PATCH 2/4] Always initialize stringinfo buffers in walsender

---
 src/backend/replication/walsender.c | 18 ++++++++----------
 1 file changed, 8 insertions(+), 10 deletions(-)

diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 5909b7d..8dd7a6b 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -244,6 +244,14 @@ InitWalSender(void)
 	 */
 	MarkPostmasterChildWalSender();
 	SendPostmasterSignal(PMSIGNAL_ADVANCE_STATE_MACHINE);
+
+	/*
+	 * Allocate buffers that will be used for each outgoing and incoming
+	 * message.
+	 */
+	initStringInfo(&output_message);
+	initStringInfo(&reply_message);
+	initStringInfo(&tmpbuf);
 }
 
 /*
@@ -819,8 +827,6 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 							  cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL);
 	}
 
-	initStringInfo(&output_message);
-
 	if (cmd->kind == REPLICATION_KIND_LOGICAL)
 	{
 		LogicalDecodingContext *ctx;
@@ -1814,14 +1820,6 @@ static void
 WalSndLoop(WalSndSendDataCallback send_data)
 {
 	/*
-	 * Allocate buffers that will be used for each outgoing and incoming
-	 * message.  We do this just once to reduce palloc overhead.
-	 */
-	initStringInfo(&output_message);
-	initStringInfo(&reply_message);
-	initStringInfo(&tmpbuf);
-
-	/*
 	 * Initialize the last reply timestamp. That enables timeout processing
 	 * from hereon.
 	 */
-- 
2.7.4

0003-Fix-after-trigger-execution-in-logical-replication-v2.patchapplication/x-patch; name=0003-Fix-after-trigger-execution-in-logical-replication-v2.patchDownload
From 85d8ef1ef6881660547870bcce96b2693d4f9530 Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Sun, 22 Jan 2017 23:16:57 +0100
Subject: [PATCH 3/4] Fix after trigger execution in logical replication

---
 src/backend/replication/logical/worker.c   |  15 ++++
 src/test/subscription/t/003_constraints.pl | 113 +++++++++++++++++++++++++++++
 2 files changed, 128 insertions(+)
 create mode 100644 src/test/subscription/t/003_constraints.pl

diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 9383960..293140c 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -176,6 +176,9 @@ create_estate_for_relation(LogicalRepRelMapEntry *rel)
 	if (resultRelInfo->ri_TrigDesc)
 		estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate);
 
+	/* Prepare to catch AFTER triggers. */
+	AfterTriggerBeginQuery();
+
 	return estate;
 }
 
@@ -536,6 +539,10 @@ apply_handle_insert(StringInfo s)
 	/* Cleanup. */
 	ExecCloseIndices(estate->es_result_relation_info);
 	PopActiveSnapshot();
+
+	/* Handle queued AFTER triggers. */
+	AfterTriggerEndQuery(estate);
+
 	ExecResetTupleTable(estate->es_tupleTable, false);
 	FreeExecutorState(estate);
 
@@ -676,6 +683,10 @@ apply_handle_update(StringInfo s)
 	/* Cleanup. */
 	ExecCloseIndices(estate->es_result_relation_info);
 	PopActiveSnapshot();
+
+	/* Handle queued AFTER triggers. */
+	AfterTriggerEndQuery(estate);
+
 	EvalPlanQualEnd(&epqstate);
 	ExecResetTupleTable(estate->es_tupleTable, false);
 	FreeExecutorState(estate);
@@ -763,6 +774,10 @@ apply_handle_delete(StringInfo s)
 	/* Cleanup. */
 	ExecCloseIndices(estate->es_result_relation_info);
 	PopActiveSnapshot();
+
+	/* Handle queued AFTER triggers. */
+	AfterTriggerEndQuery(estate);
+
 	EvalPlanQualEnd(&epqstate);
 	ExecResetTupleTable(estate->es_tupleTable, false);
 	FreeExecutorState(estate);
diff --git a/src/test/subscription/t/003_constraints.pl b/src/test/subscription/t/003_constraints.pl
new file mode 100644
index 0000000..203322f
--- /dev/null
+++ b/src/test/subscription/t/003_constraints.pl
@@ -0,0 +1,113 @@
+# Basic logical replication test
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 4;
+
+# Initialize publisher node
+my $node_publisher = get_new_node('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = get_new_node('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Setup structure on publisher
+$node_publisher->safe_psql('postgres',
+	"CREATE TABLE tab_fk (bid int PRIMARY KEY);");
+$node_publisher->safe_psql('postgres',
+	"CREATE TABLE tab_fk_ref (id int PRIMARY KEY, bid int REFERENCES tab_fk (bid));");
+
+# Setup structure on subscriber
+$node_subscriber->safe_psql('postgres',
+	"CREATE TABLE tab_fk (bid int PRIMARY KEY);");
+$node_subscriber->safe_psql('postgres',
+	"CREATE TABLE tab_fk_ref (id int PRIMARY KEY, bid int REFERENCES tab_fk (bid));");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+	"CREATE PUBLICATION tap_pub FOR ALL TABLES;");
+
+my $appname = 'tap_sub';
+$node_subscriber->safe_psql('postgres',
+	"CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr application_name=$appname' PUBLICATION tap_pub");
+
+# Wait for subscriber to finish initialization
+my $caughtup_query =
+"SELECT pg_current_xlog_location() <= replay_location FROM pg_stat_replication WHERE application_name = '$appname';";
+$node_publisher->poll_query_until('postgres', $caughtup_query)
+  or die "Timed out while waiting for subscriber to catch up";
+
+$node_publisher->safe_psql('postgres',
+	"INSERT INTO tab_fk (bid) VALUES (1);");
+$node_publisher->safe_psql('postgres',
+	"INSERT INTO tab_fk_ref (id, bid) VALUES (1,1);");
+
+$node_publisher->poll_query_until('postgres', $caughtup_query)
+  or die "Timed out while waiting for subscriber to catch up";
+
+# Check data on subscriber
+my $result =
+  $node_subscriber->safe_psql('postgres', "SELECT count(*), min(bid), max(bid) FROM tab_fk");
+is($result, qq(1|1|1), 'check replicated tab_fk inserts on subscriber');
+
+$result =
+  $node_subscriber->safe_psql('postgres', "SELECT count(*), min(bid), max(bid) FROM tab_fk_ref");
+is($result, qq(1|1|1), 'check replicated tab_fk_ref inserts on subscriber');
+
+# Drop the fk on provider
+$node_publisher->safe_psql('postgres',
+	"DROP TABLE tab_fk CASCADE;");
+
+# Insert data
+$node_publisher->safe_psql('postgres',
+	"INSERT INTO tab_fk_ref (id, bid) VALUES (2,2);");
+
+$node_publisher->poll_query_until('postgres', $caughtup_query)
+  or die "Timed out while waiting for subscriber to catch up";
+
+# FK is not enforced on subscriber
+$result =
+  $node_subscriber->safe_psql('postgres', "SELECT count(*), min(bid), max(bid) FROM tab_fk_ref");
+is($result, qq(2|1|2), 'check FK ignored on subscriber');
+
+# Add replica trigger
+$node_subscriber->safe_psql('postgres', qq{
+CREATE FUNCTION filter_basic_dml_fn() RETURNS TRIGGER AS \$\$
+BEGIN
+    IF (TG_OP = 'INSERT') THEN
+        IF (NEW.id < 10) THEN
+            RETURN NEW;
+        ELSE
+            RETURN NULL;
+        END IF;
+    ELSE
+        RAISE WARNING 'Unknown action';
+        RETURN NULL;
+    END IF;
+END;
+\$\$ LANGUAGE plpgsql;
+CREATE TRIGGER filter_basic_dml_trg
+BEFORE INSERT ON tab_fk_ref
+FOR EACH ROW EXECUTE PROCEDURE filter_basic_dml_fn();
+ALTER TABLE tab_fk_ref ENABLE REPLICA TRIGGER filter_basic_dml_trg;
+});
+
+# Insert data
+$node_publisher->safe_psql('postgres',
+	"INSERT INTO tab_fk_ref (id, bid) VALUES (10,10);");
+
+$node_publisher->poll_query_until('postgres', $caughtup_query)
+  or die "Timed out while waiting for subscriber to catch up";
+
+# The row should be skipped on subscriber
+$result =
+  $node_subscriber->safe_psql('postgres', "SELECT count(*), min(bid), max(bid) FROM tab_fk_ref");
+is($result, qq(2|1|2), 'check skipped insert on subscriber');
+
+$node_subscriber->stop('fast');
+$node_publisher->stop('fast');
-- 
2.7.4

0004-Add-RENAME-support-for-PUBLICATIONs-and-SUBSCRIPTION-v2.patchapplication/x-patch; name=0004-Add-RENAME-support-for-PUBLICATIONs-and-SUBSCRIPTION-v2.patchDownload
From 754b061abd52e75a9af8c1119ec7cef08d9632c4 Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Thu, 19 Jan 2017 00:59:01 +0100
Subject: [PATCH 4/4] Add RENAME support for PUBLICATIONs and SUBSCRIPTIONs

---
 src/backend/commands/alter.c               | 15 +++++++++++++++
 src/backend/parser/gram.y                  | 18 ++++++++++++++++++
 src/backend/replication/logical/worker.c   | 16 +++++++++++++++-
 src/bin/psql/tab-complete.c                |  6 ++++--
 src/test/regress/expected/publication.out  | 10 +++++++++-
 src/test/regress/expected/subscription.out | 10 +++++++++-
 src/test/regress/sql/publication.sql       |  6 +++++-
 src/test/regress/sql/subscription.sql      |  6 +++++-
 src/test/subscription/t/001_rep_changes.pl | 11 ++++++++++-
 9 files changed, 90 insertions(+), 8 deletions(-)

diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 768fcc8..8498880 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -32,6 +32,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_proc.h"
+#include "catalog/pg_subscription.h"
 #include "catalog/pg_ts_config.h"
 #include "catalog/pg_ts_dict.h"
 #include "catalog/pg_ts_parser.h"
@@ -90,6 +91,12 @@ report_name_conflict(Oid classId, const char *name)
 		case LanguageRelationId:
 			msgfmt = gettext_noop("language \"%s\" already exists");
 			break;
+		case PublicationRelationId:
+			msgfmt = gettext_noop("publication \"%s\" already exists");
+			break;
+		case SubscriptionRelationId:
+			msgfmt = gettext_noop("subscription \"%s\" already exists");
+			break;
 		default:
 			elog(ERROR, "unsupported object class %u", classId);
 			break;
@@ -256,6 +263,12 @@ AlterObjectRename_internal(Relation rel, Oid objectId, const char *new_name)
 		IsThereOpFamilyInNamespace(new_name, opf->opfmethod,
 								   opf->opfnamespace);
 	}
+	else if (classId == SubscriptionRelationId)
+	{
+		if (SearchSysCacheExists2(SUBSCRIPTIONNAME, MyDatabaseId,
+								  CStringGetDatum(new_name)))
+			report_name_conflict(classId, new_name);
+	}
 	else if (nameCacheId >= 0)
 	{
 		if (OidIsValid(namespaceId))
@@ -365,6 +378,8 @@ ExecRenameStmt(RenameStmt *stmt)
 		case OBJECT_TSDICTIONARY:
 		case OBJECT_TSPARSER:
 		case OBJECT_TSTEMPLATE:
+		case OBJECT_PUBLICATION:
+		case OBJECT_SUBSCRIPTION:
 			{
 				ObjectAddress address;
 				Relation	catalog;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a4edea0..03b9bc3 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -8475,6 +8475,24 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			| ALTER PUBLICATION name RENAME TO name
+				{
+					RenameStmt *n = makeNode(RenameStmt);
+					n->renameType = OBJECT_PUBLICATION;
+					n->object = list_make1(makeString($3));
+					n->newname = $6;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+			| ALTER SUBSCRIPTION name RENAME TO name
+				{
+					RenameStmt *n = makeNode(RenameStmt);
+					n->renameType = OBJECT_SUBSCRIPTION;
+					n->object = list_make1(makeString($3));
+					n->newname = $6;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 		;
 
 opt_column: COLUMN									{ $$ = COLUMN; }
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 293140c..3aba7b6 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -1265,6 +1265,21 @@ reread_subscription(void)
 	}
 
 	/*
+	 * Exit if subscription name was changed (it's used for
+	 * fallback_application_name). The launcher will start new worker.
+	 */
+	if (strcmp(newsub->name, MySubscription->name) != 0)
+	{
+		ereport(LOG,
+				(errmsg("logical replication worker for subscription \"%s\" will "
+						"restart because subscription was renamed",
+						MySubscription->name)));
+
+		walrcv_disconnect(wrconn);
+		proc_exit(0);
+	}
+
+	/*
 	 * Exit if publication list was changed. The launcher will start
 	 * new worker.
 	 */
@@ -1297,7 +1312,6 @@ reread_subscription(void)
 
 	/* Check for other changes that should never happen too. */
 	if (newsub->dbid != MySubscription->dbid ||
-		strcmp(newsub->name, MySubscription->name) != 0 ||
 		strcmp(newsub->slotname, MySubscription->slotname) != 0)
 	{
 		elog(ERROR, "subscription %u changed unexpectedly",
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index d6fffcf..a8256e8 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1438,7 +1438,8 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER PUBLICATION <name> ...*/
 	else if (Matches3("ALTER","PUBLICATION",MatchAny))
 	{
-		COMPLETE_WITH_LIST5("WITH", "ADD TABLE", "SET TABLE", "DROP TABLE", "OWNER TO");
+		COMPLETE_WITH_LIST6("WITH", "ADD TABLE", "SET TABLE", "DROP TABLE",
+							"OWNER TO", "RENAME TO");
 	}
 	/* ALTER PUBLICATION <name> .. WITH ( ... */
 	else if (HeadMatches3("ALTER", "PUBLICATION",MatchAny) && TailMatches2("WITH", "("))
@@ -1449,7 +1450,8 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER SUBSCRIPTION <name> ... */
 	else if (Matches3("ALTER","SUBSCRIPTION",MatchAny))
 	{
-		COMPLETE_WITH_LIST6("WITH", "CONNECTION", "SET PUBLICATION", "ENABLE", "DISABLE", "OWNER TO");
+		COMPLETE_WITH_LIST7("WITH", "CONNECTION", "SET PUBLICATION", "ENABLE",
+							"DISABLE", "OWNER TO", "RENAME TO");
 	}
 	else if (HeadMatches3("ALTER", "SUBSCRIPTION", MatchAny) && TailMatches2("WITH", "("))
 	{
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 5784b0f..6416fbb 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -148,7 +148,15 @@ DROP TABLE testpub_tbl1;
  t       | t       | t
 (1 row)
 
-DROP PUBLICATION testpub_default;
+ALTER PUBLICATION testpub_default RENAME TO testpub_foo;
+\dRp testpub_foo
+                         List of publications
+    Name     |          Owner           | Inserts | Updates | Deletes 
+-------------+--------------------------+---------+---------+---------
+ testpub_foo | regress_publication_user | t       | t       | t
+(1 row)
+
+DROP PUBLICATION testpub_foo;
 DROP PUBLICATION testpib_ins_trunct;
 DROP PUBLICATION testpub_fortbl;
 DROP SCHEMA pub_test CASCADE;
diff --git a/src/test/regress/expected/subscription.out b/src/test/regress/expected/subscription.out
index 2ccec98..cb1ab4e 100644
--- a/src/test/regress/expected/subscription.out
+++ b/src/test/regress/expected/subscription.out
@@ -61,6 +61,14 @@ ALTER SUBSCRIPTION testsub DISABLE;
 (1 row)
 
 COMMIT;
-DROP SUBSCRIPTION testsub NODROP SLOT;
+ALTER SUBSCRIPTION testsub RENAME TO testsub_foo;
+\dRs
+                         List of subscriptions
+    Name     |           Owner           | Enabled |    Publication     
+-------------+---------------------------+---------+--------------------
+ testsub_foo | regress_subscription_user | f       | {testpub,testpub1}
+(1 row)
+
+DROP SUBSCRIPTION testsub_foo NODROP SLOT;
 RESET SESSION AUTHORIZATION;
 DROP ROLE regress_subscription_user;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 8779788..9563ea1 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -73,7 +73,11 @@ DROP TABLE testpub_tbl1;
 
 \dRp+ testpub_default
 
-DROP PUBLICATION testpub_default;
+ALTER PUBLICATION testpub_default RENAME TO testpub_foo;
+
+\dRp testpub_foo
+
+DROP PUBLICATION testpub_foo;
 DROP PUBLICATION testpib_ins_trunct;
 DROP PUBLICATION testpub_fortbl;
 
diff --git a/src/test/regress/sql/subscription.sql b/src/test/regress/sql/subscription.sql
index 68c17d5..fce6069 100644
--- a/src/test/regress/sql/subscription.sql
+++ b/src/test/regress/sql/subscription.sql
@@ -38,7 +38,11 @@ ALTER SUBSCRIPTION testsub DISABLE;
 
 COMMIT;
 
-DROP SUBSCRIPTION testsub NODROP SLOT;
+ALTER SUBSCRIPTION testsub RENAME TO testsub_foo;
+
+\dRs
+
+DROP SUBSCRIPTION testsub_foo NODROP SLOT;
 
 RESET SESSION AUTHORIZATION;
 DROP ROLE regress_subscription_user;
diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl
index b51740b..a9c4b01 100644
--- a/src/test/subscription/t/001_rep_changes.pl
+++ b/src/test/subscription/t/001_rep_changes.pl
@@ -169,8 +169,17 @@ $result =
   $node_subscriber->safe_psql('postgres', "SELECT count(*), min(a), max(a) FROM tab_full");
 is($result, qq(11|0|100), 'check replicated insert after alter publication');
 
+# check restart on rename
+$oldpid = $node_publisher->safe_psql('postgres',
+	"SELECT pid FROM pg_stat_replication WHERE application_name = '$appname';");
+$node_subscriber->safe_psql('postgres',
+	"ALTER SUBSCRIPTION tap_sub RENAME TO tap_sub_renamed");
+$node_publisher->poll_query_until('postgres',
+	"SELECT pid != $oldpid FROM pg_stat_replication WHERE application_name = '$appname';")
+  or die "Timed out while waiting for apply to restart";
+
 # check all the cleanup
-$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub");
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_renamed");
 
 $result =
   $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM pg_subscription");
-- 
2.7.4

#220Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Petr Jelinek (#219)
3 attachment(s)
Re: Logical Replication WIP

Hi,

I updated these patches for current HEAD and removed the string
initialization in walsender as Fuji Masao committed similar fix in meantime.

I also found typo/thinko in the first patch which is now fixed.

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

Attachments:

0001-Use-asynchronous-connect-API-in-libpqwalreceiver.patchtext/x-patch; name=0001-Use-asynchronous-connect-API-in-libpqwalreceiver.patchDownload
From 8e191350acc8da89e94b784806f4487e2e9c5970 Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Fri, 20 Jan 2017 21:20:56 +0100
Subject: [PATCH 1/3] Use asynchronous connect API in libpqwalreceiver

This makes the connection attempt from CREATE SUBSCRIPTION and from
WalReceiver interruptable by user in case the libpq connection is
hanging. The previous coding required immediate shutdown (SIGQUIT) of
PostgreSQL in that situation.
---
 src/backend/postmaster/pgstat.c                    |  4 +-
 .../libpqwalreceiver/libpqwalreceiver.c            | 51 +++++++++++++++++++++-
 src/include/pgstat.h                               |  2 +-
 3 files changed, 52 insertions(+), 5 deletions(-)

diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index ada374c..2fb9a8b 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -3340,8 +3340,8 @@ pgstat_get_wait_client(WaitEventClient w)
 		case WAIT_EVENT_WAL_RECEIVER_WAIT_START:
 			event_name = "WalReceiverWaitStart";
 			break;
-		case WAIT_EVENT_LIBPQWALRECEIVER_READ:
-			event_name = "LibPQWalReceiverRead";
+		case WAIT_EVENT_LIBPQWALRECEIVER:
+			event_name = "LibPQWalReceiver";
 			break;
 		case WAIT_EVENT_WAL_SENDER_WAIT_WAL:
 			event_name = "WalSenderWaitForWAL";
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 44a89c7..9366421 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -112,6 +112,7 @@ libpqrcv_connect(const char *conninfo, bool logical, const char *appname,
 				 char **err)
 {
 	WalReceiverConn *conn;
+	PostgresPollingStatusType status;
 	const char *keys[5];
 	const char *vals[5];
 	int			i = 0;
@@ -138,7 +139,53 @@ libpqrcv_connect(const char *conninfo, bool logical, const char *appname,
 	vals[i] = NULL;
 
 	conn = palloc0(sizeof(WalReceiverConn));
-	conn->streamConn = PQconnectdbParams(keys, vals, /* expand_dbname = */ true);
+	conn->streamConn = PQconnectStartParams(keys, vals,
+											/* expand_dbname = */ true);
+	/* Check for conn status. */
+	if (PQstatus(conn->streamConn) == CONNECTION_BAD)
+	{
+		*err = pstrdup(PQerrorMessage(conn->streamConn));
+		return NULL;
+	}
+
+	/* Poll connection. */
+	do
+	{
+		int		rc;
+
+		/* Determine current state of the connection. */
+		status = PQconnectPoll(conn->streamConn);
+
+		/* Sleep a bit if waiting for socket. */
+		if (status == PGRES_POLLING_READING ||
+			status == PGRES_POLLING_WRITING)
+		{
+			int		extra_flag;
+
+			extra_flag = status == PGRES_POLLING_READING ? WL_SOCKET_READABLE :
+				WL_SOCKET_WRITEABLE;
+
+			ResetLatch(&MyProc->procLatch);
+			rc = WaitLatchOrSocket(&MyProc->procLatch,
+								   WL_POSTMASTER_DEATH |
+								   WL_LATCH_SET | extra_flag,
+								   PQsocket(conn->streamConn),
+								   0,
+								   WAIT_EVENT_LIBPQWALRECEIVER);
+
+			/* Emergency bailout. */
+			if (rc & WL_POSTMASTER_DEATH)
+				exit(1);
+
+			/* Interrupted. */
+			if (rc & WL_LATCH_SET)
+				CHECK_FOR_INTERRUPTS();
+		}
+
+		/* Otherwise loop until we have OK or FAILED status. */
+	} while (status != PGRES_POLLING_OK && status != PGRES_POLLING_FAILED);
+
+	/* Check the status. */
 	if (PQstatus(conn->streamConn) != CONNECTION_OK)
 	{
 		*err = pstrdup(PQerrorMessage(conn->streamConn));
@@ -521,7 +568,7 @@ libpqrcv_PQexec(PGconn *streamConn, const char *query)
 								   WL_LATCH_SET,
 								   PQsocket(streamConn),
 								   0,
-								   WAIT_EVENT_LIBPQWALRECEIVER_READ);
+								   WAIT_EVENT_LIBPQWALRECEIVER);
 			if (rc & WL_POSTMASTER_DEATH)
 				exit(1);
 
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 8b710ec..0062fb8 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -764,7 +764,7 @@ typedef enum
 	WAIT_EVENT_CLIENT_WRITE,
 	WAIT_EVENT_SSL_OPEN_SERVER,
 	WAIT_EVENT_WAL_RECEIVER_WAIT_START,
-	WAIT_EVENT_LIBPQWALRECEIVER_READ,
+	WAIT_EVENT_LIBPQWALRECEIVER,
 	WAIT_EVENT_WAL_SENDER_WAIT_WAL,
 	WAIT_EVENT_WAL_SENDER_WRITE_DATA
 } WaitEventClient;
-- 
2.7.4

0002-Fix-after-trigger-execution-in-logical-replication.patchtext/x-patch; name=0002-Fix-after-trigger-execution-in-logical-replication.patchDownload
From 7fdb56566c69fd22c21a863a13d5d3ad35604729 Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Sun, 22 Jan 2017 23:16:57 +0100
Subject: [PATCH 2/3] Fix after trigger execution in logical replication

---
 src/backend/replication/logical/worker.c   |  15 ++++
 src/test/subscription/t/003_constraints.pl | 113 +++++++++++++++++++++++++++++
 2 files changed, 128 insertions(+)
 create mode 100644 src/test/subscription/t/003_constraints.pl

diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 563886b..30eb088 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -173,6 +173,9 @@ create_estate_for_relation(LogicalRepRelMapEntry *rel)
 	if (resultRelInfo->ri_TrigDesc)
 		estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate);
 
+	/* Prepare to catch AFTER triggers. */
+	AfterTriggerBeginQuery();
+
 	return estate;
 }
 
@@ -533,6 +536,10 @@ apply_handle_insert(StringInfo s)
 	/* Cleanup. */
 	ExecCloseIndices(estate->es_result_relation_info);
 	PopActiveSnapshot();
+
+	/* Handle queued AFTER triggers. */
+	AfterTriggerEndQuery(estate);
+
 	ExecResetTupleTable(estate->es_tupleTable, false);
 	FreeExecutorState(estate);
 
@@ -673,6 +680,10 @@ apply_handle_update(StringInfo s)
 	/* Cleanup. */
 	ExecCloseIndices(estate->es_result_relation_info);
 	PopActiveSnapshot();
+
+	/* Handle queued AFTER triggers. */
+	AfterTriggerEndQuery(estate);
+
 	EvalPlanQualEnd(&epqstate);
 	ExecResetTupleTable(estate->es_tupleTable, false);
 	FreeExecutorState(estate);
@@ -760,6 +771,10 @@ apply_handle_delete(StringInfo s)
 	/* Cleanup. */
 	ExecCloseIndices(estate->es_result_relation_info);
 	PopActiveSnapshot();
+
+	/* Handle queued AFTER triggers. */
+	AfterTriggerEndQuery(estate);
+
 	EvalPlanQualEnd(&epqstate);
 	ExecResetTupleTable(estate->es_tupleTable, false);
 	FreeExecutorState(estate);
diff --git a/src/test/subscription/t/003_constraints.pl b/src/test/subscription/t/003_constraints.pl
new file mode 100644
index 0000000..203322f
--- /dev/null
+++ b/src/test/subscription/t/003_constraints.pl
@@ -0,0 +1,113 @@
+# Basic logical replication test
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 4;
+
+# Initialize publisher node
+my $node_publisher = get_new_node('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = get_new_node('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Setup structure on publisher
+$node_publisher->safe_psql('postgres',
+	"CREATE TABLE tab_fk (bid int PRIMARY KEY);");
+$node_publisher->safe_psql('postgres',
+	"CREATE TABLE tab_fk_ref (id int PRIMARY KEY, bid int REFERENCES tab_fk (bid));");
+
+# Setup structure on subscriber
+$node_subscriber->safe_psql('postgres',
+	"CREATE TABLE tab_fk (bid int PRIMARY KEY);");
+$node_subscriber->safe_psql('postgres',
+	"CREATE TABLE tab_fk_ref (id int PRIMARY KEY, bid int REFERENCES tab_fk (bid));");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+	"CREATE PUBLICATION tap_pub FOR ALL TABLES;");
+
+my $appname = 'tap_sub';
+$node_subscriber->safe_psql('postgres',
+	"CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr application_name=$appname' PUBLICATION tap_pub");
+
+# Wait for subscriber to finish initialization
+my $caughtup_query =
+"SELECT pg_current_xlog_location() <= replay_location FROM pg_stat_replication WHERE application_name = '$appname';";
+$node_publisher->poll_query_until('postgres', $caughtup_query)
+  or die "Timed out while waiting for subscriber to catch up";
+
+$node_publisher->safe_psql('postgres',
+	"INSERT INTO tab_fk (bid) VALUES (1);");
+$node_publisher->safe_psql('postgres',
+	"INSERT INTO tab_fk_ref (id, bid) VALUES (1,1);");
+
+$node_publisher->poll_query_until('postgres', $caughtup_query)
+  or die "Timed out while waiting for subscriber to catch up";
+
+# Check data on subscriber
+my $result =
+  $node_subscriber->safe_psql('postgres', "SELECT count(*), min(bid), max(bid) FROM tab_fk");
+is($result, qq(1|1|1), 'check replicated tab_fk inserts on subscriber');
+
+$result =
+  $node_subscriber->safe_psql('postgres', "SELECT count(*), min(bid), max(bid) FROM tab_fk_ref");
+is($result, qq(1|1|1), 'check replicated tab_fk_ref inserts on subscriber');
+
+# Drop the fk on provider
+$node_publisher->safe_psql('postgres',
+	"DROP TABLE tab_fk CASCADE;");
+
+# Insert data
+$node_publisher->safe_psql('postgres',
+	"INSERT INTO tab_fk_ref (id, bid) VALUES (2,2);");
+
+$node_publisher->poll_query_until('postgres', $caughtup_query)
+  or die "Timed out while waiting for subscriber to catch up";
+
+# FK is not enforced on subscriber
+$result =
+  $node_subscriber->safe_psql('postgres', "SELECT count(*), min(bid), max(bid) FROM tab_fk_ref");
+is($result, qq(2|1|2), 'check FK ignored on subscriber');
+
+# Add replica trigger
+$node_subscriber->safe_psql('postgres', qq{
+CREATE FUNCTION filter_basic_dml_fn() RETURNS TRIGGER AS \$\$
+BEGIN
+    IF (TG_OP = 'INSERT') THEN
+        IF (NEW.id < 10) THEN
+            RETURN NEW;
+        ELSE
+            RETURN NULL;
+        END IF;
+    ELSE
+        RAISE WARNING 'Unknown action';
+        RETURN NULL;
+    END IF;
+END;
+\$\$ LANGUAGE plpgsql;
+CREATE TRIGGER filter_basic_dml_trg
+BEFORE INSERT ON tab_fk_ref
+FOR EACH ROW EXECUTE PROCEDURE filter_basic_dml_fn();
+ALTER TABLE tab_fk_ref ENABLE REPLICA TRIGGER filter_basic_dml_trg;
+});
+
+# Insert data
+$node_publisher->safe_psql('postgres',
+	"INSERT INTO tab_fk_ref (id, bid) VALUES (10,10);");
+
+$node_publisher->poll_query_until('postgres', $caughtup_query)
+  or die "Timed out while waiting for subscriber to catch up";
+
+# The row should be skipped on subscriber
+$result =
+  $node_subscriber->safe_psql('postgres', "SELECT count(*), min(bid), max(bid) FROM tab_fk_ref");
+is($result, qq(2|1|2), 'check skipped insert on subscriber');
+
+$node_subscriber->stop('fast');
+$node_publisher->stop('fast');
-- 
2.7.4

0003-Add-RENAME-support-for-PUBLICATIONs-and-SUBSCRIPTION.patchtext/x-patch; name=0003-Add-RENAME-support-for-PUBLICATIONs-and-SUBSCRIPTION.patchDownload
From 14ce90301495f9342953d0fe3ed7e544f829b747 Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Thu, 19 Jan 2017 00:59:01 +0100
Subject: [PATCH 3/3] Add RENAME support for PUBLICATIONs and SUBSCRIPTIONs

---
 src/backend/commands/alter.c               | 15 +++++++++++++++
 src/backend/parser/gram.y                  | 18 ++++++++++++++++++
 src/backend/replication/logical/worker.c   | 16 +++++++++++++++-
 src/bin/psql/tab-complete.c                |  6 ++++--
 src/test/regress/expected/publication.out  | 10 +++++++++-
 src/test/regress/expected/subscription.out | 10 +++++++++-
 src/test/regress/sql/publication.sql       |  6 +++++-
 src/test/regress/sql/subscription.sql      |  6 +++++-
 src/test/subscription/t/001_rep_changes.pl | 11 ++++++++++-
 9 files changed, 90 insertions(+), 8 deletions(-)

diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index d6195e4..8ff5dd5 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -32,6 +32,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_proc.h"
+#include "catalog/pg_subscription.h"
 #include "catalog/pg_ts_config.h"
 #include "catalog/pg_ts_dict.h"
 #include "catalog/pg_ts_parser.h"
@@ -90,6 +91,12 @@ report_name_conflict(Oid classId, const char *name)
 		case LanguageRelationId:
 			msgfmt = gettext_noop("language \"%s\" already exists");
 			break;
+		case PublicationRelationId:
+			msgfmt = gettext_noop("publication \"%s\" already exists");
+			break;
+		case SubscriptionRelationId:
+			msgfmt = gettext_noop("subscription \"%s\" already exists");
+			break;
 		default:
 			elog(ERROR, "unsupported object class %u", classId);
 			break;
@@ -256,6 +263,12 @@ AlterObjectRename_internal(Relation rel, Oid objectId, const char *new_name)
 		IsThereOpFamilyInNamespace(new_name, opf->opfmethod,
 								   opf->opfnamespace);
 	}
+	else if (classId == SubscriptionRelationId)
+	{
+		if (SearchSysCacheExists2(SUBSCRIPTIONNAME, MyDatabaseId,
+								  CStringGetDatum(new_name)))
+			report_name_conflict(classId, new_name);
+	}
 	else if (nameCacheId >= 0)
 	{
 		if (OidIsValid(namespaceId))
@@ -364,6 +377,8 @@ ExecRenameStmt(RenameStmt *stmt)
 		case OBJECT_TSDICTIONARY:
 		case OBJECT_TSPARSER:
 		case OBJECT_TSTEMPLATE:
+		case OBJECT_PUBLICATION:
+		case OBJECT_SUBSCRIPTION:
 			{
 				ObjectAddress address;
 				Relation	catalog;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 6c6d21b..8ffde05 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -8497,6 +8497,24 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			| ALTER PUBLICATION name RENAME TO name
+				{
+					RenameStmt *n = makeNode(RenameStmt);
+					n->renameType = OBJECT_PUBLICATION;
+					n->object = list_make1(makeString($3));
+					n->newname = $6;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+			| ALTER SUBSCRIPTION name RENAME TO name
+				{
+					RenameStmt *n = makeNode(RenameStmt);
+					n->renameType = OBJECT_SUBSCRIPTION;
+					n->object = list_make1(makeString($3));
+					n->newname = $6;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 		;
 
 opt_column: COLUMN									{ $$ = COLUMN; }
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 30eb088..7b2efa1 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -1262,6 +1262,21 @@ reread_subscription(void)
 	}
 
 	/*
+	 * Exit if subscription name was changed (it's used for
+	 * fallback_application_name). The launcher will start new worker.
+	 */
+	if (strcmp(newsub->name, MySubscription->name) != 0)
+	{
+		ereport(LOG,
+				(errmsg("logical replication worker for subscription \"%s\" will "
+						"restart because subscription was renamed",
+						MySubscription->name)));
+
+		walrcv_disconnect(wrconn);
+		proc_exit(0);
+	}
+
+	/*
 	 * Exit if publication list was changed. The launcher will start
 	 * new worker.
 	 */
@@ -1294,7 +1309,6 @@ reread_subscription(void)
 
 	/* Check for other changes that should never happen too. */
 	if (newsub->dbid != MySubscription->dbid ||
-		strcmp(newsub->name, MySubscription->name) != 0 ||
 		strcmp(newsub->slotname, MySubscription->slotname) != 0)
 	{
 		elog(ERROR, "subscription %u changed unexpectedly",
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 94814c2..7084f78 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1438,7 +1438,8 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER PUBLICATION <name> ...*/
 	else if (Matches3("ALTER","PUBLICATION",MatchAny))
 	{
-		COMPLETE_WITH_LIST5("WITH", "ADD TABLE", "SET TABLE", "DROP TABLE", "OWNER TO");
+		COMPLETE_WITH_LIST6("WITH", "ADD TABLE", "SET TABLE", "DROP TABLE",
+							"OWNER TO", "RENAME TO");
 	}
 	/* ALTER PUBLICATION <name> .. WITH ( ... */
 	else if (HeadMatches3("ALTER", "PUBLICATION",MatchAny) && TailMatches2("WITH", "("))
@@ -1449,7 +1450,8 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER SUBSCRIPTION <name> ... */
 	else if (Matches3("ALTER","SUBSCRIPTION",MatchAny))
 	{
-		COMPLETE_WITH_LIST6("WITH", "CONNECTION", "SET PUBLICATION", "ENABLE", "DISABLE", "OWNER TO");
+		COMPLETE_WITH_LIST7("WITH", "CONNECTION", "SET PUBLICATION", "ENABLE",
+							"DISABLE", "OWNER TO", "RENAME TO");
 	}
 	else if (HeadMatches3("ALTER", "SUBSCRIPTION", MatchAny) && TailMatches2("WITH", "("))
 	{
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 5784b0f..6416fbb 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -148,7 +148,15 @@ DROP TABLE testpub_tbl1;
  t       | t       | t
 (1 row)
 
-DROP PUBLICATION testpub_default;
+ALTER PUBLICATION testpub_default RENAME TO testpub_foo;
+\dRp testpub_foo
+                         List of publications
+    Name     |          Owner           | Inserts | Updates | Deletes 
+-------------+--------------------------+---------+---------+---------
+ testpub_foo | regress_publication_user | t       | t       | t
+(1 row)
+
+DROP PUBLICATION testpub_foo;
 DROP PUBLICATION testpib_ins_trunct;
 DROP PUBLICATION testpub_fortbl;
 DROP SCHEMA pub_test CASCADE;
diff --git a/src/test/regress/expected/subscription.out b/src/test/regress/expected/subscription.out
index 2ccec98..cb1ab4e 100644
--- a/src/test/regress/expected/subscription.out
+++ b/src/test/regress/expected/subscription.out
@@ -61,6 +61,14 @@ ALTER SUBSCRIPTION testsub DISABLE;
 (1 row)
 
 COMMIT;
-DROP SUBSCRIPTION testsub NODROP SLOT;
+ALTER SUBSCRIPTION testsub RENAME TO testsub_foo;
+\dRs
+                         List of subscriptions
+    Name     |           Owner           | Enabled |    Publication     
+-------------+---------------------------+---------+--------------------
+ testsub_foo | regress_subscription_user | f       | {testpub,testpub1}
+(1 row)
+
+DROP SUBSCRIPTION testsub_foo NODROP SLOT;
 RESET SESSION AUTHORIZATION;
 DROP ROLE regress_subscription_user;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 8779788..9563ea1 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -73,7 +73,11 @@ DROP TABLE testpub_tbl1;
 
 \dRp+ testpub_default
 
-DROP PUBLICATION testpub_default;
+ALTER PUBLICATION testpub_default RENAME TO testpub_foo;
+
+\dRp testpub_foo
+
+DROP PUBLICATION testpub_foo;
 DROP PUBLICATION testpib_ins_trunct;
 DROP PUBLICATION testpub_fortbl;
 
diff --git a/src/test/regress/sql/subscription.sql b/src/test/regress/sql/subscription.sql
index 68c17d5..fce6069 100644
--- a/src/test/regress/sql/subscription.sql
+++ b/src/test/regress/sql/subscription.sql
@@ -38,7 +38,11 @@ ALTER SUBSCRIPTION testsub DISABLE;
 
 COMMIT;
 
-DROP SUBSCRIPTION testsub NODROP SLOT;
+ALTER SUBSCRIPTION testsub RENAME TO testsub_foo;
+
+\dRs
+
+DROP SUBSCRIPTION testsub_foo NODROP SLOT;
 
 RESET SESSION AUTHORIZATION;
 DROP ROLE regress_subscription_user;
diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl
index fa50e49..fffb3c5 100644
--- a/src/test/subscription/t/001_rep_changes.pl
+++ b/src/test/subscription/t/001_rep_changes.pl
@@ -169,8 +169,17 @@ $result =
   $node_subscriber->safe_psql('postgres', "SELECT count(*), min(a), max(a) FROM tab_full");
 is($result, qq(11|0|100), 'check replicated insert after alter publication');
 
+# check restart on rename
+$oldpid = $node_publisher->safe_psql('postgres',
+	"SELECT pid FROM pg_stat_replication WHERE application_name = '$appname';");
+$node_subscriber->safe_psql('postgres',
+	"ALTER SUBSCRIPTION tap_sub RENAME TO tap_sub_renamed");
+$node_publisher->poll_query_until('postgres',
+	"SELECT pid != $oldpid FROM pg_stat_replication WHERE application_name = '$appname';")
+  or die "Timed out while waiting for apply to restart";
+
 # check all the cleanup
-$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub");
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_renamed");
 
 $result =
   $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM pg_subscription");
-- 
2.7.4

#221Petr Jelinek
petr.jelinek@2ndquadrant.com
In reply to: Petr Jelinek (#220)
3 attachment(s)
Re: Logical Replication WIP

On 22/02/17 12:24, Petr Jelinek wrote:

Hi,

I updated these patches for current HEAD and removed the string
initialization in walsender as Fuji Masao committed similar fix in meantime.

I also found typo/thinko in the first patch which is now fixed.

And of course I missed the xlog->wal rename, sigh. Fixed.

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

Attachments:

0001-Use-asynchronous-connect-API-in-libpqwalreceiver.patchtext/x-patch; name=0001-Use-asynchronous-connect-API-in-libpqwalreceiver.patchDownload
From d8216ac470cb0722c536f1094791ce532dda2e4d Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Fri, 20 Jan 2017 21:20:56 +0100
Subject: [PATCH 1/3] Use asynchronous connect API in libpqwalreceiver

This makes the connection attempt from CREATE SUBSCRIPTION and from
WalReceiver interruptable by user in case the libpq connection is
hanging. The previous coding required immediate shutdown (SIGQUIT) of
PostgreSQL in that situation.
---
 src/backend/postmaster/pgstat.c                    |  4 +-
 .../libpqwalreceiver/libpqwalreceiver.c            | 51 +++++++++++++++++++++-
 src/include/pgstat.h                               |  2 +-
 3 files changed, 52 insertions(+), 5 deletions(-)

diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index ada374c..2fb9a8b 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -3340,8 +3340,8 @@ pgstat_get_wait_client(WaitEventClient w)
 		case WAIT_EVENT_WAL_RECEIVER_WAIT_START:
 			event_name = "WalReceiverWaitStart";
 			break;
-		case WAIT_EVENT_LIBPQWALRECEIVER_READ:
-			event_name = "LibPQWalReceiverRead";
+		case WAIT_EVENT_LIBPQWALRECEIVER:
+			event_name = "LibPQWalReceiver";
 			break;
 		case WAIT_EVENT_WAL_SENDER_WAIT_WAL:
 			event_name = "WalSenderWaitForWAL";
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 44a89c7..9366421 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -112,6 +112,7 @@ libpqrcv_connect(const char *conninfo, bool logical, const char *appname,
 				 char **err)
 {
 	WalReceiverConn *conn;
+	PostgresPollingStatusType status;
 	const char *keys[5];
 	const char *vals[5];
 	int			i = 0;
@@ -138,7 +139,53 @@ libpqrcv_connect(const char *conninfo, bool logical, const char *appname,
 	vals[i] = NULL;
 
 	conn = palloc0(sizeof(WalReceiverConn));
-	conn->streamConn = PQconnectdbParams(keys, vals, /* expand_dbname = */ true);
+	conn->streamConn = PQconnectStartParams(keys, vals,
+											/* expand_dbname = */ true);
+	/* Check for conn status. */
+	if (PQstatus(conn->streamConn) == CONNECTION_BAD)
+	{
+		*err = pstrdup(PQerrorMessage(conn->streamConn));
+		return NULL;
+	}
+
+	/* Poll connection. */
+	do
+	{
+		int		rc;
+
+		/* Determine current state of the connection. */
+		status = PQconnectPoll(conn->streamConn);
+
+		/* Sleep a bit if waiting for socket. */
+		if (status == PGRES_POLLING_READING ||
+			status == PGRES_POLLING_WRITING)
+		{
+			int		extra_flag;
+
+			extra_flag = status == PGRES_POLLING_READING ? WL_SOCKET_READABLE :
+				WL_SOCKET_WRITEABLE;
+
+			ResetLatch(&MyProc->procLatch);
+			rc = WaitLatchOrSocket(&MyProc->procLatch,
+								   WL_POSTMASTER_DEATH |
+								   WL_LATCH_SET | extra_flag,
+								   PQsocket(conn->streamConn),
+								   0,
+								   WAIT_EVENT_LIBPQWALRECEIVER);
+
+			/* Emergency bailout. */
+			if (rc & WL_POSTMASTER_DEATH)
+				exit(1);
+
+			/* Interrupted. */
+			if (rc & WL_LATCH_SET)
+				CHECK_FOR_INTERRUPTS();
+		}
+
+		/* Otherwise loop until we have OK or FAILED status. */
+	} while (status != PGRES_POLLING_OK && status != PGRES_POLLING_FAILED);
+
+	/* Check the status. */
 	if (PQstatus(conn->streamConn) != CONNECTION_OK)
 	{
 		*err = pstrdup(PQerrorMessage(conn->streamConn));
@@ -521,7 +568,7 @@ libpqrcv_PQexec(PGconn *streamConn, const char *query)
 								   WL_LATCH_SET,
 								   PQsocket(streamConn),
 								   0,
-								   WAIT_EVENT_LIBPQWALRECEIVER_READ);
+								   WAIT_EVENT_LIBPQWALRECEIVER);
 			if (rc & WL_POSTMASTER_DEATH)
 				exit(1);
 
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 8b710ec..0062fb8 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -764,7 +764,7 @@ typedef enum
 	WAIT_EVENT_CLIENT_WRITE,
 	WAIT_EVENT_SSL_OPEN_SERVER,
 	WAIT_EVENT_WAL_RECEIVER_WAIT_START,
-	WAIT_EVENT_LIBPQWALRECEIVER_READ,
+	WAIT_EVENT_LIBPQWALRECEIVER,
 	WAIT_EVENT_WAL_SENDER_WAIT_WAL,
 	WAIT_EVENT_WAL_SENDER_WRITE_DATA
 } WaitEventClient;
-- 
2.7.4

0002-Fix-after-trigger-execution-in-logical-replication.patchtext/x-patch; name=0002-Fix-after-trigger-execution-in-logical-replication.patchDownload
From be69a9bc19089ec093448c10ea685ccc048ec42c Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Sun, 22 Jan 2017 23:16:57 +0100
Subject: [PATCH 2/3] Fix after trigger execution in logical replication

---
 src/backend/replication/logical/worker.c   |  15 ++++
 src/test/subscription/t/003_constraints.pl | 113 +++++++++++++++++++++++++++++
 2 files changed, 128 insertions(+)
 create mode 100644 src/test/subscription/t/003_constraints.pl

diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 563886b..30eb088 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -173,6 +173,9 @@ create_estate_for_relation(LogicalRepRelMapEntry *rel)
 	if (resultRelInfo->ri_TrigDesc)
 		estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate);
 
+	/* Prepare to catch AFTER triggers. */
+	AfterTriggerBeginQuery();
+
 	return estate;
 }
 
@@ -533,6 +536,10 @@ apply_handle_insert(StringInfo s)
 	/* Cleanup. */
 	ExecCloseIndices(estate->es_result_relation_info);
 	PopActiveSnapshot();
+
+	/* Handle queued AFTER triggers. */
+	AfterTriggerEndQuery(estate);
+
 	ExecResetTupleTable(estate->es_tupleTable, false);
 	FreeExecutorState(estate);
 
@@ -673,6 +680,10 @@ apply_handle_update(StringInfo s)
 	/* Cleanup. */
 	ExecCloseIndices(estate->es_result_relation_info);
 	PopActiveSnapshot();
+
+	/* Handle queued AFTER triggers. */
+	AfterTriggerEndQuery(estate);
+
 	EvalPlanQualEnd(&epqstate);
 	ExecResetTupleTable(estate->es_tupleTable, false);
 	FreeExecutorState(estate);
@@ -760,6 +771,10 @@ apply_handle_delete(StringInfo s)
 	/* Cleanup. */
 	ExecCloseIndices(estate->es_result_relation_info);
 	PopActiveSnapshot();
+
+	/* Handle queued AFTER triggers. */
+	AfterTriggerEndQuery(estate);
+
 	EvalPlanQualEnd(&epqstate);
 	ExecResetTupleTable(estate->es_tupleTable, false);
 	FreeExecutorState(estate);
diff --git a/src/test/subscription/t/003_constraints.pl b/src/test/subscription/t/003_constraints.pl
new file mode 100644
index 0000000..17d4565
--- /dev/null
+++ b/src/test/subscription/t/003_constraints.pl
@@ -0,0 +1,113 @@
+# Basic logical replication test
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 4;
+
+# Initialize publisher node
+my $node_publisher = get_new_node('publisher');
+$node_publisher->init(allows_streaming => 'logical');
+$node_publisher->start;
+
+# Create subscriber node
+my $node_subscriber = get_new_node('subscriber');
+$node_subscriber->init(allows_streaming => 'logical');
+$node_subscriber->start;
+
+# Setup structure on publisher
+$node_publisher->safe_psql('postgres',
+	"CREATE TABLE tab_fk (bid int PRIMARY KEY);");
+$node_publisher->safe_psql('postgres',
+	"CREATE TABLE tab_fk_ref (id int PRIMARY KEY, bid int REFERENCES tab_fk (bid));");
+
+# Setup structure on subscriber
+$node_subscriber->safe_psql('postgres',
+	"CREATE TABLE tab_fk (bid int PRIMARY KEY);");
+$node_subscriber->safe_psql('postgres',
+	"CREATE TABLE tab_fk_ref (id int PRIMARY KEY, bid int REFERENCES tab_fk (bid));");
+
+# Setup logical replication
+my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
+$node_publisher->safe_psql('postgres',
+	"CREATE PUBLICATION tap_pub FOR ALL TABLES;");
+
+my $appname = 'tap_sub';
+$node_subscriber->safe_psql('postgres',
+	"CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr application_name=$appname' PUBLICATION tap_pub");
+
+# Wait for subscriber to finish initialization
+my $caughtup_query =
+"SELECT pg_current_wal_location() <= replay_location FROM pg_stat_replication WHERE application_name = '$appname';";
+$node_publisher->poll_query_until('postgres', $caughtup_query)
+  or die "Timed out while waiting for subscriber to catch up";
+
+$node_publisher->safe_psql('postgres',
+	"INSERT INTO tab_fk (bid) VALUES (1);");
+$node_publisher->safe_psql('postgres',
+	"INSERT INTO tab_fk_ref (id, bid) VALUES (1,1);");
+
+$node_publisher->poll_query_until('postgres', $caughtup_query)
+  or die "Timed out while waiting for subscriber to catch up";
+
+# Check data on subscriber
+my $result =
+  $node_subscriber->safe_psql('postgres', "SELECT count(*), min(bid), max(bid) FROM tab_fk");
+is($result, qq(1|1|1), 'check replicated tab_fk inserts on subscriber');
+
+$result =
+  $node_subscriber->safe_psql('postgres', "SELECT count(*), min(bid), max(bid) FROM tab_fk_ref");
+is($result, qq(1|1|1), 'check replicated tab_fk_ref inserts on subscriber');
+
+# Drop the fk on provider
+$node_publisher->safe_psql('postgres',
+	"DROP TABLE tab_fk CASCADE;");
+
+# Insert data
+$node_publisher->safe_psql('postgres',
+	"INSERT INTO tab_fk_ref (id, bid) VALUES (2,2);");
+
+$node_publisher->poll_query_until('postgres', $caughtup_query)
+  or die "Timed out while waiting for subscriber to catch up";
+
+# FK is not enforced on subscriber
+$result =
+  $node_subscriber->safe_psql('postgres', "SELECT count(*), min(bid), max(bid) FROM tab_fk_ref");
+is($result, qq(2|1|2), 'check FK ignored on subscriber');
+
+# Add replica trigger
+$node_subscriber->safe_psql('postgres', qq{
+CREATE FUNCTION filter_basic_dml_fn() RETURNS TRIGGER AS \$\$
+BEGIN
+    IF (TG_OP = 'INSERT') THEN
+        IF (NEW.id < 10) THEN
+            RETURN NEW;
+        ELSE
+            RETURN NULL;
+        END IF;
+    ELSE
+        RAISE WARNING 'Unknown action';
+        RETURN NULL;
+    END IF;
+END;
+\$\$ LANGUAGE plpgsql;
+CREATE TRIGGER filter_basic_dml_trg
+BEFORE INSERT ON tab_fk_ref
+FOR EACH ROW EXECUTE PROCEDURE filter_basic_dml_fn();
+ALTER TABLE tab_fk_ref ENABLE REPLICA TRIGGER filter_basic_dml_trg;
+});
+
+# Insert data
+$node_publisher->safe_psql('postgres',
+	"INSERT INTO tab_fk_ref (id, bid) VALUES (10,10);");
+
+$node_publisher->poll_query_until('postgres', $caughtup_query)
+  or die "Timed out while waiting for subscriber to catch up";
+
+# The row should be skipped on subscriber
+$result =
+  $node_subscriber->safe_psql('postgres', "SELECT count(*), min(bid), max(bid) FROM tab_fk_ref");
+is($result, qq(2|1|2), 'check skipped insert on subscriber');
+
+$node_subscriber->stop('fast');
+$node_publisher->stop('fast');
-- 
2.7.4

0003-Add-RENAME-support-for-PUBLICATIONs-and-SUBSCRIPTION.patchtext/x-patch; name=0003-Add-RENAME-support-for-PUBLICATIONs-and-SUBSCRIPTION.patchDownload
From 7fb6e2a1419e3e3e43c7259707ba83b9f57ef53e Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Thu, 19 Jan 2017 00:59:01 +0100
Subject: [PATCH 3/3] Add RENAME support for PUBLICATIONs and SUBSCRIPTIONs

---
 src/backend/commands/alter.c               | 15 +++++++++++++++
 src/backend/parser/gram.y                  | 18 ++++++++++++++++++
 src/backend/replication/logical/worker.c   | 16 +++++++++++++++-
 src/bin/psql/tab-complete.c                |  6 ++++--
 src/test/regress/expected/publication.out  | 10 +++++++++-
 src/test/regress/expected/subscription.out | 10 +++++++++-
 src/test/regress/sql/publication.sql       |  6 +++++-
 src/test/regress/sql/subscription.sql      |  6 +++++-
 src/test/subscription/t/001_rep_changes.pl | 11 ++++++++++-
 9 files changed, 90 insertions(+), 8 deletions(-)

diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index d6195e4..8ff5dd5 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -32,6 +32,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_proc.h"
+#include "catalog/pg_subscription.h"
 #include "catalog/pg_ts_config.h"
 #include "catalog/pg_ts_dict.h"
 #include "catalog/pg_ts_parser.h"
@@ -90,6 +91,12 @@ report_name_conflict(Oid classId, const char *name)
 		case LanguageRelationId:
 			msgfmt = gettext_noop("language \"%s\" already exists");
 			break;
+		case PublicationRelationId:
+			msgfmt = gettext_noop("publication \"%s\" already exists");
+			break;
+		case SubscriptionRelationId:
+			msgfmt = gettext_noop("subscription \"%s\" already exists");
+			break;
 		default:
 			elog(ERROR, "unsupported object class %u", classId);
 			break;
@@ -256,6 +263,12 @@ AlterObjectRename_internal(Relation rel, Oid objectId, const char *new_name)
 		IsThereOpFamilyInNamespace(new_name, opf->opfmethod,
 								   opf->opfnamespace);
 	}
+	else if (classId == SubscriptionRelationId)
+	{
+		if (SearchSysCacheExists2(SUBSCRIPTIONNAME, MyDatabaseId,
+								  CStringGetDatum(new_name)))
+			report_name_conflict(classId, new_name);
+	}
 	else if (nameCacheId >= 0)
 	{
 		if (OidIsValid(namespaceId))
@@ -364,6 +377,8 @@ ExecRenameStmt(RenameStmt *stmt)
 		case OBJECT_TSDICTIONARY:
 		case OBJECT_TSPARSER:
 		case OBJECT_TSTEMPLATE:
+		case OBJECT_PUBLICATION:
+		case OBJECT_SUBSCRIPTION:
 			{
 				ObjectAddress address;
 				Relation	catalog;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 6c6d21b..8ffde05 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -8497,6 +8497,24 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			| ALTER PUBLICATION name RENAME TO name
+				{
+					RenameStmt *n = makeNode(RenameStmt);
+					n->renameType = OBJECT_PUBLICATION;
+					n->object = list_make1(makeString($3));
+					n->newname = $6;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+			| ALTER SUBSCRIPTION name RENAME TO name
+				{
+					RenameStmt *n = makeNode(RenameStmt);
+					n->renameType = OBJECT_SUBSCRIPTION;
+					n->object = list_make1(makeString($3));
+					n->newname = $6;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 		;
 
 opt_column: COLUMN									{ $$ = COLUMN; }
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 30eb088..7b2efa1 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -1262,6 +1262,21 @@ reread_subscription(void)
 	}
 
 	/*
+	 * Exit if subscription name was changed (it's used for
+	 * fallback_application_name). The launcher will start new worker.
+	 */
+	if (strcmp(newsub->name, MySubscription->name) != 0)
+	{
+		ereport(LOG,
+				(errmsg("logical replication worker for subscription \"%s\" will "
+						"restart because subscription was renamed",
+						MySubscription->name)));
+
+		walrcv_disconnect(wrconn);
+		proc_exit(0);
+	}
+
+	/*
 	 * Exit if publication list was changed. The launcher will start
 	 * new worker.
 	 */
@@ -1294,7 +1309,6 @@ reread_subscription(void)
 
 	/* Check for other changes that should never happen too. */
 	if (newsub->dbid != MySubscription->dbid ||
-		strcmp(newsub->name, MySubscription->name) != 0 ||
 		strcmp(newsub->slotname, MySubscription->slotname) != 0)
 	{
 		elog(ERROR, "subscription %u changed unexpectedly",
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 94814c2..7084f78 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1438,7 +1438,8 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER PUBLICATION <name> ...*/
 	else if (Matches3("ALTER","PUBLICATION",MatchAny))
 	{
-		COMPLETE_WITH_LIST5("WITH", "ADD TABLE", "SET TABLE", "DROP TABLE", "OWNER TO");
+		COMPLETE_WITH_LIST6("WITH", "ADD TABLE", "SET TABLE", "DROP TABLE",
+							"OWNER TO", "RENAME TO");
 	}
 	/* ALTER PUBLICATION <name> .. WITH ( ... */
 	else if (HeadMatches3("ALTER", "PUBLICATION",MatchAny) && TailMatches2("WITH", "("))
@@ -1449,7 +1450,8 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER SUBSCRIPTION <name> ... */
 	else if (Matches3("ALTER","SUBSCRIPTION",MatchAny))
 	{
-		COMPLETE_WITH_LIST6("WITH", "CONNECTION", "SET PUBLICATION", "ENABLE", "DISABLE", "OWNER TO");
+		COMPLETE_WITH_LIST7("WITH", "CONNECTION", "SET PUBLICATION", "ENABLE",
+							"DISABLE", "OWNER TO", "RENAME TO");
 	}
 	else if (HeadMatches3("ALTER", "SUBSCRIPTION", MatchAny) && TailMatches2("WITH", "("))
 	{
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 5784b0f..6416fbb 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -148,7 +148,15 @@ DROP TABLE testpub_tbl1;
  t       | t       | t
 (1 row)
 
-DROP PUBLICATION testpub_default;
+ALTER PUBLICATION testpub_default RENAME TO testpub_foo;
+\dRp testpub_foo
+                         List of publications
+    Name     |          Owner           | Inserts | Updates | Deletes 
+-------------+--------------------------+---------+---------+---------
+ testpub_foo | regress_publication_user | t       | t       | t
+(1 row)
+
+DROP PUBLICATION testpub_foo;
 DROP PUBLICATION testpib_ins_trunct;
 DROP PUBLICATION testpub_fortbl;
 DROP SCHEMA pub_test CASCADE;
diff --git a/src/test/regress/expected/subscription.out b/src/test/regress/expected/subscription.out
index 2ccec98..cb1ab4e 100644
--- a/src/test/regress/expected/subscription.out
+++ b/src/test/regress/expected/subscription.out
@@ -61,6 +61,14 @@ ALTER SUBSCRIPTION testsub DISABLE;
 (1 row)
 
 COMMIT;
-DROP SUBSCRIPTION testsub NODROP SLOT;
+ALTER SUBSCRIPTION testsub RENAME TO testsub_foo;
+\dRs
+                         List of subscriptions
+    Name     |           Owner           | Enabled |    Publication     
+-------------+---------------------------+---------+--------------------
+ testsub_foo | regress_subscription_user | f       | {testpub,testpub1}
+(1 row)
+
+DROP SUBSCRIPTION testsub_foo NODROP SLOT;
 RESET SESSION AUTHORIZATION;
 DROP ROLE regress_subscription_user;
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 8779788..9563ea1 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -73,7 +73,11 @@ DROP TABLE testpub_tbl1;
 
 \dRp+ testpub_default
 
-DROP PUBLICATION testpub_default;
+ALTER PUBLICATION testpub_default RENAME TO testpub_foo;
+
+\dRp testpub_foo
+
+DROP PUBLICATION testpub_foo;
 DROP PUBLICATION testpib_ins_trunct;
 DROP PUBLICATION testpub_fortbl;
 
diff --git a/src/test/regress/sql/subscription.sql b/src/test/regress/sql/subscription.sql
index 68c17d5..fce6069 100644
--- a/src/test/regress/sql/subscription.sql
+++ b/src/test/regress/sql/subscription.sql
@@ -38,7 +38,11 @@ ALTER SUBSCRIPTION testsub DISABLE;
 
 COMMIT;
 
-DROP SUBSCRIPTION testsub NODROP SLOT;
+ALTER SUBSCRIPTION testsub RENAME TO testsub_foo;
+
+\dRs
+
+DROP SUBSCRIPTION testsub_foo NODROP SLOT;
 
 RESET SESSION AUTHORIZATION;
 DROP ROLE regress_subscription_user;
diff --git a/src/test/subscription/t/001_rep_changes.pl b/src/test/subscription/t/001_rep_changes.pl
index fa50e49..fffb3c5 100644
--- a/src/test/subscription/t/001_rep_changes.pl
+++ b/src/test/subscription/t/001_rep_changes.pl
@@ -169,8 +169,17 @@ $result =
   $node_subscriber->safe_psql('postgres', "SELECT count(*), min(a), max(a) FROM tab_full");
 is($result, qq(11|0|100), 'check replicated insert after alter publication');
 
+# check restart on rename
+$oldpid = $node_publisher->safe_psql('postgres',
+	"SELECT pid FROM pg_stat_replication WHERE application_name = '$appname';");
+$node_subscriber->safe_psql('postgres',
+	"ALTER SUBSCRIPTION tap_sub RENAME TO tap_sub_renamed");
+$node_publisher->poll_query_until('postgres',
+	"SELECT pid != $oldpid FROM pg_stat_replication WHERE application_name = '$appname';")
+  or die "Timed out while waiting for apply to restart";
+
 # check all the cleanup
-$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub");
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_renamed");
 
 $result =
   $node_subscriber->safe_psql('postgres', "SELECT count(*) FROM pg_subscription");
-- 
2.7.4

#222Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Petr Jelinek (#221)
Re: Logical Replication WIP

On 2/22/17 07:00, Petr Jelinek wrote:

On 22/02/17 12:24, Petr Jelinek wrote:

Hi,

I updated these patches for current HEAD and removed the string
initialization in walsender as Fuji Masao committed similar fix in meantime.

I also found typo/thinko in the first patch which is now fixed.

And of course I missed the xlog->wal rename, sigh. Fixed.

all three committed

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, 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