Commit 665d1fad authored by Peter Eisentraut's avatar Peter Eisentraut

Logical replication

- Add PUBLICATION catalogs and DDL
- Add SUBSCRIPTION catalog and DDL
- Define logical replication protocol and output plugin
- Add logical replication workers

From: Petr Jelinek <petr@2ndquadrant.com>
Reviewed-by: default avatarSteve Singer <steve@ssinger.info>
Reviewed-by: default avatarAndres Freund <andres@anarazel.de>
Reviewed-by: default avatarErik Rijkers <er@xs4all.nl>
Reviewed-by: default avatarPeter Eisentraut <peter.eisentraut@2ndquadrant.com>
parent ba61a04b
This diff is collapsed.
......@@ -3411,6 +3411,47 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
</variablelist>
</sect2>
<sect2 id="runtime-config-replication-subscriber">
<title>Subscribers</title>
<para>
These settings control the behavior of a logical replication subscriber.
Their values on the publisher are irrelevant.
</para>
<para>
Note that <varname>wal_receiver_timeout</varname> and
<varname>wal_retrieve_retry_interval</varname> configuration parameters
affect the logical replication workers as well.
</para>
<variablelist>
<varlistentry id="guc-max-logical-replication-workers" xreflabel="max_logical_replication_workers">
<term><varname>max_logical_replication_workers</varname> (<type>int</type>)
<indexterm>
<primary><varname>max_logical_replication_workers</> configuration parameter</primary>
</indexterm>
</term>
<listitem>
<para>
Specifies maximum number of logical replication workers. This includes
both apply workers and table synchronization workers.
</para>
<para>
Logical replication workers are taken from the pool defined by
<varname>max_worker_processes</varname>.
</para>
<para>
The default value is 4.
</para>
</listitem>
</varlistentry>
</variablelist>
</sect2>
</sect1>
<sect1 id="runtime-config-query">
......
......@@ -50,6 +50,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">
......
......@@ -18762,7 +18762,7 @@ postgres=# SELECT * FROM pg_xlogfile_name_offset(pg_stop_backup());
</row>
<row>
<entry>
<entry id="pg-replication-origin-advance">
<indexterm>
<primary>pg_replication_origin_advance</primary>
</indexterm>
......
This diff is collapsed.
......@@ -308,6 +308,14 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser
</entry>
</row>
<row>
<entry><structname>pg_stat_subscription</><indexterm><primary>pg_stat_subscription</primary></indexterm></entry>
<entry>At least one row per subscription, showing information about
the subscription workers.
See <xref linkend="pg-stat-subscription"> for details.
</entry>
</row>
<row>
<entry><structname>pg_stat_ssl</><indexterm><primary>pg_stat_ssl</primary></indexterm></entry>
<entry>One row per connection (regular and replication), showing information about
......@@ -1545,6 +1553,72 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i
connected server.
</para>
<table id="pg-stat-subscription" xreflabel="pg_stat_subscription">
<title><structname>pg_stat_subscription</structname> View</title>
<tgroup cols="3">
<thead>
<row>
<entry>Column</entry>
<entry>Type</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry><structfield>subid</></entry>
<entry><type>oid</></entry>
<entry>OID of the subscription</entry>
</row>
<row>
<entry><structfield>subname</></entry>
<entry><type>text</></entry>
<entry>Name of the subscription</entry>
</row>
<row>
<entry><structfield>pid</></entry>
<entry><type>integer</></entry>
<entry>Process ID of the subscription worker process</entry>
</row>
<row>
<entry><structfield>received_lsn</></entry>
<entry><type>pg_lsn</></entry>
<entry>Last transaction log position received, the initial value of
this field being 0</entry>
</row>
<row>
<entry><structfield>last_msg_send_time</></entry>
<entry><type>timestamp with time zone</></entry>
<entry>Send time of last message received from origin WAL sender</entry>
</row>
<row>
<entry><structfield>last_msg_receipt_time</></entry>
<entry><type>timestamp with time zone</></entry>
<entry>Receipt time of last message received from origin WAL sender
</entry>
</row>
<row>
<entry><structfield>latest_end_lsn</></entry>
<entry><type>pg_lsn</></entry>
<entry>Last transaction log position reported to origin WAL sender
</entry>
</row>
<row>
<entry><structfield>latest_end_time</></entry>
<entry><type>timestamp with time zone</></entry>
<entry>Time of last transaction log position reported to origin WAL
sender</entry>
</row>
</tbody>
</tgroup>
</table>
<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).
</para>
<table id="pg-stat-ssl-view" xreflabel="pg_stat_ssl">
<title><structname>pg_stat_ssl</structname> View</title>
<tgroup cols="3">
......
......@@ -160,6 +160,7 @@
&monitoring;
&diskusage;
&wal;
&logical-replication;
&regress;
</part>
......
This diff is collapsed.
......@@ -26,11 +26,13 @@ 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">
<!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">
......@@ -72,11 +74,13 @@ 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">
<!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">
......@@ -116,11 +120,13 @@ 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">
<!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">
......
<!--
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>
PUBLISH INSERT | NOPUBLISH INSERT
| PUBLISH UPDATE | NOPUBLISH UPDATE
| PUBLISH DELETE | NOPUBLISH DELETE
ALTER PUBLICATION <replaceable class="PARAMETER">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_USER | SESSION_USER }
ALTER PUBLICATION <replaceable class="PARAMETER">name</replaceable> ADD TABLE <replaceable class="PARAMETER">table_name</replaceable> [, ...]
ALTER PUBLICATION <replaceable class="PARAMETER">name</replaceable> SET TABLE <replaceable class="PARAMETER">table_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 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>
To alter the owner, you must also be a direct or indirect member of the
new owning role. The new owner has to be a superuser
</para>
<para>
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>
<title>Parameters</title>
<variablelist>
<varlistentry>
<term><replaceable class="parameter">name</replaceable></term>
<listitem>
<para>
The name of an existing publication whose definition is to be altered.
</para>
</listitem>
</varlistentry>
<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>
<listitem>
<para>
These clauses alter properties originally set by
<xref linkend="SQL-CREATEPUBLICATION">. See there for more information.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
<para>
Name of an existing table.
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Examples</title>
<para>
Change the publication to not publish inserts:
<programlisting>
ALTER PUBLICATION noinsert WITH (NOPUBLISH INSERT);
</programlisting>
</para>
<para>
Add some tables to the publication:
<programlisting>
ALTER PUBLICATION mypublication ADD TABLE users, departments;
</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>
</simplelist>
</refsect1>
</refentry>
<!--
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>
SLOT NAME = slot_name
ALTER SUBSCRIPTION <replaceable class="PARAMETER">name</replaceable> OWNER TO { <replaceable>new_owner</replaceable> | CURRENT_USER | SESSION_USER }
ALTER SUBSCRIPTION <replaceable class="PARAMETER">name</replaceable> CONNECTION 'conninfo'
ALTER SUBSCRIPTION <replaceable class="PARAMETER">name</replaceable> SET 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
properties that can be specified
in <xref linkend="sql-createsubscription">.
</para>
<para>
To alter the owner, you must also be a direct or indirect member of the
new owning role. The new owner has to be a superuser
</para>
</refsect1>
<refsect1>
<title>Parameters</title>
<variablelist>
<varlistentry>
<term><replaceable class="parameter">name</replaceable></term>
<listitem>
<para>
The name of a subscription whose properties are to be altered.
</para>
</listitem>
</varlistentry>
<varlistentry>
<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 properties originally set by
<xref linkend="SQL-CREATESUBSCRIPTION">. See there for more
information.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>ENABLE</literal></term>
<listitem>
<para>
Enables the previously disabled subscription, starting the logical
replication worker at the end of transaction.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>DISABLE</literal></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 SET 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>
<!--
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 a new publication</refpurpose>
</refnamediv>
<refsynopsisdiv>
<synopsis>
CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
[ FOR TABLE <replaceable class="parameter">table_name</replaceable> [, ...]
| FOR ALL TABLES ]
[ 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
</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 whose data changes are
intended to be replicated through logical replication. See
<xref linkend="logical-replication-publication"> for details about how
publications fit into the 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>FOR TABLE</literal></term>
<listitem>
<para>
Specifies a list of tables to add to the publication.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>FOR ALL TABLES</literal></term>
<listitem>
<para>
Marks the publication as one that replicates changes for all tables in
the database, including tables created in the future.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>PUBLISH INSERT</literal></term>
<term><literal>NOPUBLISH INSERT</literal></term>
<listitem>
<para>
These clauses determine whether the new publication will send
the <command>INSERT</command> operations to the subscribers.
<literal>PUBLISH INSERT</literal> is the default.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>PUBLISH UPDATE</literal></term>
<term><literal>NOPUBLISH UPDATE</literal></term>
<listitem>
<para>
These clauses determine whether the new publication will send
the <command>UPDATE</command> operations to the subscribers.
<literal>PUBLISH UPDATE</literal> is the default.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>PUBLISH DELETE</literal></term>
<term><literal>NOPUBLISH DELETE</literal></term>
<listitem>
<para>
These clauses determine whether the new publication will send
the <command>DELETE</command> operations to the subscribers.
<literal>PUBLISH DELETE</literal> is the default.
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Notes</title>
<para>
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>
To create a publication, the invoking user must have the
<literal>CREATE</> privilege for the current database.
(Of course, superusers bypass this check.)
</para>
<para>
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 that publishes <command>UPDATE</command>
and/or <command>DELETE</command> operations must have
<literal>REPLICA IDENTITY</> defined. Otherwise those operations will be
disallowed on those tables.
</para>
<para>
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>
<command>TRUNCATE</command> and other <acronym>DDL</acronym> operations
are not published.
</para>
</refsect1>
<refsect1>
<title>Examples</title>
<para>
Create a simple publication that just publishes all DML for tables in it:
<programlisting>
CREATE PUBLICATION mypublication;
</programlisting>
</para>
<para>
Create an insert-only publication:
<programlisting>
CREATE PUBLICATION insert_only WITH (NOPUBLISH UPDATE, NOPUBLISH 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>
</simplelist>
</refsect1>
</refentry>
<!--
doc/src/sgml/ref/create_subscription.sgml
PostgreSQL documentation
-->
<refentry id="SQL-CREATESUBSCRIPTION">
<indexterm zone="sql-createsubscription">
<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 a new subscription</refpurpose>
</refnamediv>
<refsynopsisdiv>
<synopsis>
CREATE SUBSCRIPTION <replaceable class="PARAMETER">subscription_name</replaceable> CONNECTION 'conninfo' PUBLICATION { publication_name [, ...] } [ WITH ( <replaceable class="PARAMETER">option</replaceable> [, ... ] ) ]
<phrase>where <replaceable class="PARAMETER">option</replaceable> can be:</phrase>
| ENABLED | DISABLED
| CREATE SLOT | NOCREATE SLOT
| SLOT NAME = slot_name
</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.
</para>
<para>
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 is 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><literal>CONNECTION '<replaceable class="parameter">conninfo</replaceable>'</literal></term>
<listitem>
<para>
The connection string to the publisher.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>PUBLICATION <replaceable class="parameter">publication_name</replaceable></literal></term>
<listitem>
<para>
Name(s) of the publications on the publisher to subscribe to.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>ENABLED</literal></term>
<term><literal>DISABLED</literal></term>
<listitem>
<para>
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.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>CREATE SLOT</literal></term>
<term><literal>NOCREATE SLOT</literal></term>
<listitem>
<para>
Specifies whether the command should create the replication slot on the
publisher. <literal>CREATE SLOT</literal> is the default.
</para>
</listitem>
</varlistentry>
<varlistentry>
<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
<literal>subscription_name</> for slot name.
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Examples</title>
<para>
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:
<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 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
CONNECTION 'host=192.168.1.50 port=5432 user=foo dbname=foodb password=foopass'
PUBLICATION insert_only
WITH (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>
<!--
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 a publication</refpurpose>
</refnamediv>
<refsynopsisdiv>
<synopsis>
DROP PUBLICATION [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> [, ...] [ CASCADE | RESTRICT ]
</synopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>
<command>DROP PUBLICATION</command> removes an existing publication 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><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>
The name of an existing publication.
</para>
</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>
<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>
<!--
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 a subscription</refpurpose>
</refnamediv>
<refsynopsisdiv>
<synopsis>
DROP SUBSCRIPTION [ IF EXISTS ] <replaceable class="parameter">name</replaceable> [ <replaceable class="parameter">DROP SLOT</replaceable> | <replaceable class="parameter">NODROP SLOT</replaceable> ]
</synopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>
<command>DROP SUBSCRIPTION</command> removes a subscription from the
database cluster.
</para>
<para>
A subscription can only be dropped by a superuser.
</para>
<para>
The replication worker associated with the subscription will not stop until
after the transaction that issued this command has committed.
</para>
</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>
<varlistentry>
<term><replaceable class="parameter">DROP SLOT</replaceable></term>
<term><replaceable class="parameter">NODROP SLOT</replaceable></term>
<listitem>
<para>
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>
</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>
......@@ -755,6 +755,15 @@ PostgreSQL documentation
</listitem>
</varlistentry>
<varlistentry>
<term><option>--include-subscriptions</option></term>
<listitem>
<para>
Include logical replication subscriptions in the dump.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--inserts</option></term>
<listitem>
......@@ -789,6 +798,18 @@ PostgreSQL documentation
</listitem>
</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>
......
......@@ -1600,6 +1600,34 @@ testdb=&gt;
</listitem>
</varlistentry>
<varlistentry>
<term><literal>\dRp[+] [ <link linkend="APP-PSQL-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
<listitem>
<para>
Lists replication publications.
If <replaceable class="parameter">pattern</replaceable> is
specified, only those publications whose names match the pattern are
listed.
If <literal>+</literal> is appended to the command name, the tables
associated with each publication are shown as well.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>\dRs[+] [ <link linkend="APP-PSQL-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
<listitem>
<para>
Lists replication subscriptions.
If <replaceable class="parameter">pattern</replaceable> is
specified, only those subscriptions whose names match the pattern are
listed.
If <literal>+</literal> is appended to the command name, additional
properties of the subscriptions are shown.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>\dT[S+] [ <link linkend="APP-PSQL-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
<listitem>
......
......@@ -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;
......
......@@ -22,6 +22,7 @@ SUBDIRS = \
include \
interfaces \
backend/replication/libpqwalreceiver \
backend/replication/pgoutput \
fe_utils \
bin \
pl \
......
......@@ -42,6 +42,7 @@
#include "miscadmin.h"
#include "pgstat.h"
#include "replication/logical.h"
#include "replication/logicallauncher.h"
#include "replication/origin.h"
#include "replication/syncrep.h"
#include "replication/walsender.h"
......@@ -2135,6 +2136,7 @@ CommitTransaction(void)
AtEOXact_HashTables(true);
AtEOXact_PgStat(true);
AtEOXact_Snapshot(true);
AtCommit_ApplyLauncher();
pgstat_report_xact_timestamp(0);
CurrentResourceOwner = NULL;
......
......@@ -14,8 +14,9 @@ OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.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_range.o pg_db_role_setting.o pg_shdepend.o \
pg_type.o storage.o toasting.o
pg_operator.o pg_proc.o pg_publication.o pg_range.o \
pg_db_role_setting.o pg_shdepend.o pg_subscription.o pg_type.o \
storage.o toasting.o
BKIFILES = postgres.bki postgres.description postgres.shdescription
......@@ -42,7 +43,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_partitioned_table.h pg_range.h pg_transform.h \
pg_sequence.h \
pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \
toasting.h indexing.h \
)
......
......@@ -45,6 +45,7 @@
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_subscription.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
#include "catalog/pg_ts_config.h"
......@@ -3390,6 +3391,10 @@ static const char *const not_owner_msg[MAX_ACL_KIND] =
gettext_noop("must be owner of event trigger %s"),
/* ACL_KIND_EXTENSION */
gettext_noop("must be owner of extension %s"),
/* ACL_KIND_PUBLICATION */
gettext_noop("must be owner of publication %s"),
/* ACL_KIND_SUBSCRIPTION */
gettext_noop("must be owner of subscription %s"),
};
......@@ -5071,6 +5076,58 @@ pg_extension_ownercheck(Oid ext_oid, Oid roleid)
return has_privs_of_role(roleid, ownerId);
}
/*
* Ownership check for an publication (specified by OID).
*/
bool
pg_publication_ownercheck(Oid pub_oid, Oid roleid)
{
HeapTuple tuple;
Oid ownerId;
/* Superusers bypass all permission checking. */
if (superuser_arg(roleid))
return true;
tuple = SearchSysCache1(PUBLICATIONOID, ObjectIdGetDatum(pub_oid));
if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("publication with OID %u does not exist", pub_oid)));
ownerId = ((Form_pg_publication) GETSTRUCT(tuple))->pubowner;
ReleaseSysCache(tuple);
return has_privs_of_role(roleid, ownerId);
}
/*
* Ownership check for an subscription (specified by OID).
*/
bool
pg_subscription_ownercheck(Oid sub_oid, Oid roleid)
{
HeapTuple tuple;
Oid ownerId;
/* Superusers bypass all permission checking. */
if (superuser_arg(roleid))
return true;
tuple = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(sub_oid));
if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("subscription with OID %u does not exist", sub_oid)));
ownerId = ((Form_pg_subscription) GETSTRUCT(tuple))->subowner;
ReleaseSysCache(tuple);
return has_privs_of_role(roleid, ownerId);
}
/*
* Check whether specified role has CREATEROLE privilege (or is a superuser)
*
......
......@@ -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 ||
......
......@@ -48,7 +48,10 @@
#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_subscription.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_transform.h"
#include "catalog/pg_trigger.h"
......@@ -64,6 +67,7 @@
#include "commands/extension.h"
#include "commands/policy.h"
#include "commands/proclang.h"
#include "commands/publicationcmds.h"
#include "commands/schemacmds.h"
#include "commands/seclabel.h"
#include "commands/sequence.h"
......@@ -164,6 +168,9 @@ static const Oid object_classes[] = {
ExtensionRelationId, /* OCLASS_EXTENSION */
EventTriggerRelationId, /* OCLASS_EVENT_TRIGGER */
PolicyRelationId, /* OCLASS_POLICY */
PublicationRelationId, /* OCLASS_PUBLICATION */
PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */
SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */
TransformRelationId /* OCLASS_TRANSFORM */
};
......@@ -1244,6 +1251,14 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePolicyById(object->objectId);
break;
case OCLASS_PUBLICATION:
RemovePublicationById(object->objectId);
break;
case OCLASS_PUBLICATION_REL:
RemovePublicationRelById(object->objectId);
break;
case OCLASS_TRANSFORM:
DropTransformById(object->objectId);
break;
......@@ -2404,6 +2419,15 @@ getObjectClass(const ObjectAddress *object)
case PolicyRelationId:
return OCLASS_POLICY;
case PublicationRelationId:
return OCLASS_PUBLICATION;
case PublicationRelRelationId:
return OCLASS_PUBLICATION_REL;
case SubscriptionRelationId:
return OCLASS_SUBSCRIPTION;
case TransformRelationId:
return OCLASS_TRANSFORM;
}
......
......@@ -45,7 +45,10 @@
#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_subscription.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_transform.h"
#include "catalog/pg_trigger.h"
......@@ -450,6 +453,30 @@ static const ObjectPropertyType ObjectProperty[] =
Anum_pg_type_typacl,
ACL_KIND_TYPE,
true
},
{
PublicationRelationId,
PublicationObjectIndexId,
PUBLICATIONOID,
PUBLICATIONNAME,
Anum_pg_publication_pubname,
InvalidAttrNumber,
Anum_pg_publication_pubowner,
InvalidAttrNumber,
-1,
true
},
{
SubscriptionRelationId,
SubscriptionObjectIndexId,
SUBSCRIPTIONOID,
SUBSCRIPTIONNAME,
Anum_pg_subscription_subname,
InvalidAttrNumber,
Anum_pg_subscription_subowner,
InvalidAttrNumber,
-1,
true
}
};
......@@ -653,6 +680,18 @@ static const struct object_type_map
{
"policy", OBJECT_POLICY
},
/* OCLASS_PUBLICATION */
{
"publication", OBJECT_PUBLICATION
},
/* OCLASS_PUBLICATION_REL */
{
"publication relation", OBJECT_PUBLICATION_REL
},
/* OCLASS_SUBSCRIPTION */
{
"subscription", OBJECT_SUBSCRIPTION
},
/* OCLASS_TRANSFORM */
{
"transform", OBJECT_TRANSFORM
......@@ -688,6 +727,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 +854,8 @@ get_object_address(ObjectType objtype, List *objname, List *objargs,
case OBJECT_FOREIGN_SERVER:
case OBJECT_EVENT_TRIGGER:
case OBJECT_ACCESS_METHOD:
case OBJECT_PUBLICATION:
case OBJECT_SUBSCRIPTION:
address = get_object_address_unqualified(objtype,
objname, missing_ok);
break;
......@@ -926,6 +970,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 +1139,12 @@ 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;
case OBJECT_SUBSCRIPTION:
msg = gettext_noop("subscription name cannot be qualified");
break;
default:
elog(ERROR, "unrecognized objtype: %d", (int) objtype);
msg = NULL; /* placate compiler */
......@@ -1156,6 +1210,16 @@ 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;
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 */
......@@ -1743,6 +1807,51 @@ get_object_address_usermapping(List *objname, List *objargs, bool missing_ok)
return address;
}
/*
* 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,
Relation *relation, bool missing_ok)
{
ObjectAddress address;
char *pubname;
Publication *pub;
ObjectAddressSet(address, PublicationRelRelationId, 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.
*/
......@@ -2002,6 +2111,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)
......@@ -2183,6 +2293,16 @@ 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_SUBSCRIPTION:
if (!pg_subscription_ownercheck(address.objectId, roleid))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_SUBSCRIPTION,
NameListToString(objname));
break;
case OBJECT_TRANSFORM:
{
TypeName *typename = (TypeName *) linitial(objname);
......@@ -3191,6 +3311,41 @@ getObjectDescription(const ObjectAddress *object)
break;
}
case OCLASS_PUBLICATION:
{
appendStringInfo(&buffer, _("publication %s"),
get_publication_name(object->objectId));
break;
}
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
char *pubname;
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);
pubname = get_publication_name(prform->prpubid);
appendStringInfo(&buffer, _("publication table %s in publication %s"),
get_rel_name(prform->prrelid), pubname);
ReleaseSysCache(tup);
break;
}
case OCLASS_SUBSCRIPTION:
{
appendStringInfo(&buffer, _("subscription %s"),
get_subscription_name(object->objectId));
break;
}
default:
appendStringInfo(&buffer, "unrecognized object %u %u %d",
object->classId,
......@@ -3677,6 +3832,18 @@ 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;
case OCLASS_SUBSCRIPTION:
appendStringInfoString(&buffer, "subscription");
break;
default:
appendStringInfo(&buffer, "unrecognized %u", object->classId);
break;
......@@ -4648,6 +4815,58 @@ getObjectIdentityParts(const ObjectAddress *object,
}
break;
case OCLASS_PUBLICATION:
{
char *pubname;
pubname = get_publication_name(object->objectId);
appendStringInfoString(&buffer,
quote_identifier(pubname));
if (objname)
*objname = list_make1(pubname);
break;
}
case OCLASS_PUBLICATION_REL:
{
HeapTuple tup;
char *pubname;
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);
pubname = get_publication_name(prform->prpubid);
appendStringInfo(&buffer, _("publication table %s in publication %s"),
get_rel_name(prform->prrelid), pubname);
if (objname)
{
getRelationIdentity(&buffer, prform->prrelid, objname);
*objargs = list_make1(pubname);
}
ReleaseSysCache(tup);
break;
}
case OCLASS_SUBSCRIPTION:
{
char *subname;
subname = get_subscription_name(object->objectId);
appendStringInfoString(&buffer,
quote_identifier(subname));
if (objname)
*objname = list_make1(subname);
break;
}
default:
appendStringInfo(&buffer, "unrecognized object %u %u %d",
object->classId,
......
This diff is collapsed.
......@@ -39,6 +39,7 @@
#include "catalog/pg_opfamily.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_shdepend.h"
#include "catalog/pg_subscription.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_ts_config.h"
#include "catalog/pg_ts_dict.h"
......@@ -53,7 +54,9 @@
#include "commands/extension.h"
#include "commands/policy.h"
#include "commands/proclang.h"
#include "commands/publicationcmds.h"
#include "commands/schemacmds.h"
#include "commands/subscriptioncmds.h"
#include "commands/tablecmds.h"
#include "commands/typecmds.h"
#include "storage/lmgr.h"
......@@ -1406,6 +1409,14 @@ shdepReassignOwned(List *roleids, Oid newrole)
AlterEventTriggerOwner_oid(sdepForm->objid, newrole);
break;
case PublicationRelationId:
AlterPublicationOwner_oid(sdepForm->objid, newrole);
break;
case SubscriptionRelationId:
AlterSubscriptionOwner_oid(sdepForm->objid, newrole);
break;
/* Generic alter owner cases */
case CollationRelationId:
case ConversionRelationId:
......
/*-------------------------------------------------------------------------
*
* pg_subscription.c
* replication subscriptions
*
* Copyright (c) 2016, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/backend/catalog/pg_subscription.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "miscadmin.h"
#include "access/genam.h"
#include "access/heapam.h"
#include "access/htup_details.h"
#include "catalog/pg_type.h"
#include "catalog/pg_subscription.h"
#include "nodes/makefuncs.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/syscache.h"
static List *textarray_to_stringlist(ArrayType *textarray);
/*
* Fetch the subscription from the syscache.
*/
Subscription *
GetSubscription(Oid subid, bool missing_ok)
{
HeapTuple tup;
Subscription *sub;
Form_pg_subscription subform;
Datum datum;
bool isnull;
tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
if (!HeapTupleIsValid(tup))
{
if (missing_ok)
return NULL;
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->subdbid;
sub->name = pstrdup(NameStr(subform->subname));
sub->owner = subform->subowner;
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;
}
/*
* 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)
{
pfree(sub->name);
pfree(sub->conninfo);
pfree(sub->slotname);
list_free_deep(sub->publications);
pfree(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 = GetSysCacheOid2(SUBSCRIPTIONNAME, MyDatabaseId,
CStringGetDatum(subname));
if (!OidIsValid(oid) && !missing_ok)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("subscription \"%s\" does not exist", subname)));
return oid;
}
/*
* get_subscription_name - 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.
*/
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;
}
......@@ -248,6 +248,15 @@ CREATE VIEW pg_stats WITH (security_barrier) AS
REVOKE ALL on pg_statistic FROM public;
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.oid IN (SELECT relid FROM pg_get_publication_tables(P.pubname));
CREATE VIEW pg_locks AS
SELECT * FROM pg_lock_status() AS L;
......@@ -708,6 +717,20 @@ CREATE VIEW pg_stat_wal_receiver AS
FROM pg_stat_get_wal_receiver() s
WHERE s.pid IS NOT NULL;
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
FROM pg_subscription su
LEFT JOIN pg_stat_get_subscription(NULL) st
ON (st.subid = su.oid);
CREATE VIEW pg_stat_ssl AS
SELECT
S.pid,
......@@ -866,6 +889,8 @@ CREATE VIEW pg_replication_origin_status AS
REVOKE ALL ON pg_replication_origin_status FROM public;
REVOKE ALL ON pg_subscription FROM public;
--
-- We have a few function definitions in here, too.
-- At some point there might be enough to justify breaking them out into
......
......@@ -17,9 +17,9 @@ 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 \
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
policy.o portalcmds.o prepare.o proclang.o publicationcmds.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
......@@ -45,7 +45,9 @@
#include "commands/extension.h"
#include "commands/policy.h"
#include "commands/proclang.h"
#include "commands/publicationcmds.h"
#include "commands/schemacmds.h"
#include "commands/subscriptioncmds.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
#include "commands/trigger.h"
......@@ -770,6 +772,14 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
return AlterEventTriggerOwner(strVal(linitial(stmt->object)),
newowner);
case OBJECT_PUBLICATION:
return AlterPublicationOwner(strVal(linitial(stmt->object)),
newowner);
case OBJECT_SUBSCRIPTION:
return AlterSubscriptionOwner(strVal(linitial(stmt->object)),
newowner);
/* Generic cases */
case OBJECT_AGGREGATE:
case OBJECT_COLLATION:
......
......@@ -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
......@@ -874,6 +876,21 @@ dropdb(const char *dbname, bool missing_ok)
dbname),
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.
*/
......
......@@ -319,3 +319,31 @@ defGetTypeLength(DefElem *def)
def->defname, defGetString(def))));
return 0; /* keep compiler quiet */
}
/*
* Extract a list of string values (otherwise uninterpreted) from a DefElem.
*/
List *
defGetStringList(DefElem *def)
{
ListCell *cell;
if (def->arg == NULL)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("%s requires a parameter",
def->defname)));
if (nodeTag(def->arg) != T_List)
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(def->arg));
foreach(cell, (List *)def->arg)
{
Node *str = (Node *) lfirst(cell);
if (!IsA(str, String))
elog(ERROR, "unexpected node type in name list: %d",
(int) nodeTag(str));
}
return (List *) def->arg;
}
......@@ -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;
......
......@@ -106,11 +106,13 @@ static event_trigger_support_data event_trigger_support[] = {
{"OPERATOR CLASS", true},
{"OPERATOR FAMILY", true},
{"POLICY", true},
{"PUBLICATION", true},
{"ROLE", false},
{"RULE", true},
{"SCHEMA", true},
{"SEQUENCE", true},
{"SERVER", true},
{"SUBSCRIPTION", true},
{"TABLE", true},
{"TABLESPACE", false},
{"TRANSFORM", true},
......@@ -1103,9 +1105,12 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_OPERATOR:
case OBJECT_OPFAMILY:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
case OBJECT_RULE:
case OBJECT_SCHEMA:
case OBJECT_SEQUENCE:
case OBJECT_SUBSCRIPTION:
case OBJECT_TABCONSTRAINT:
case OBJECT_TABLE:
case OBJECT_TRANSFORM:
......@@ -1168,6 +1173,9 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_EXTENSION:
case OCLASS_POLICY:
case OCLASS_AM:
case OCLASS_PUBLICATION:
case OCLASS_PUBLICATION_REL:
case OCLASS_SUBSCRIPTION:
return true;
}
......
This diff is collapsed.
This diff is collapsed.
......@@ -12055,6 +12055,18 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
break;
}
/*
* Check that the table is not part any publication when changing to
* UNLOGGED as UNLOGGED tables can't be published.
*/
if (!toLogged &&
list_length(GetRelationPublications(RelationGetRelid(rel))) > 0)
ereport(ERROR,
(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
* permanent tables cannot reference unlogged ones. Self-referencing
......
......@@ -14,7 +14,7 @@ include $(top_builddir)/src/Makefile.global
OBJS = execAmi.o execCurrent.o execGrouping.o execIndexing.o execJunk.o \
execMain.o execParallel.o execProcnode.o execQual.o \
execScan.o execTuples.o \
execReplication.o execScan.o execTuples.o \
execUtils.o functions.o instrument.o nodeAppend.o nodeAgg.o \
nodeBitmapAnd.o nodeBitmapOr.o \
nodeBitmapHeapscan.o nodeBitmapIndexscan.o \
......
......@@ -43,6 +43,7 @@
#include "access/xact.h"
#include "catalog/namespace.h"
#include "catalog/partition.h"
#include "catalog/pg_publication.h"
#include "commands/matview.h"
#include "commands/trigger.h"
#include "executor/execdebug.h"
......@@ -1024,7 +1025,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
{
case RELKIND_RELATION:
case RELKIND_PARTITIONED_TABLE:
/* OK */
CheckCmdReplicaIdentity(resultRel, operation);
break;
case RELKIND_SEQUENCE:
ereport(ERROR,
......
This diff is collapsed.
......@@ -4286,6 +4286,69 @@ _copyPartitionCmd(const PartitionCmd *from)
return newnode;
}
static CreatePublicationStmt *
_copyCreatePublicationStmt(const CreatePublicationStmt *from)
{
CreatePublicationStmt *newnode = makeNode(CreatePublicationStmt);
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
COPY_NODE_FIELD(tables);
COPY_SCALAR_FIELD(for_all_tables);
return newnode;
}
static AlterPublicationStmt *
_copyAlterPublicationStmt(const AlterPublicationStmt *from)
{
AlterPublicationStmt *newnode = makeNode(AlterPublicationStmt);
COPY_STRING_FIELD(pubname);
COPY_NODE_FIELD(options);
COPY_NODE_FIELD(tables);
COPY_SCALAR_FIELD(for_all_tables);
COPY_SCALAR_FIELD(tableAction);
return newnode;
}
static CreateSubscriptionStmt *
_copyCreateSubscriptionStmt(const CreateSubscriptionStmt *from)
{
CreateSubscriptionStmt *newnode = makeNode(CreateSubscriptionStmt);
COPY_STRING_FIELD(subname);
COPY_STRING_FIELD(conninfo);
COPY_NODE_FIELD(publication);
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;
}
static DropSubscriptionStmt *
_copyDropSubscriptionStmt(const DropSubscriptionStmt *from)
{
DropSubscriptionStmt *newnode = makeNode(DropSubscriptionStmt);
COPY_STRING_FIELD(subname);
COPY_SCALAR_FIELD(drop_slot);
COPY_SCALAR_FIELD(missing_ok);
return newnode;
}
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
......@@ -5086,6 +5149,21 @@ 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_CreateSubscriptionStmt:
retval = _copyCreateSubscriptionStmt(from);
break;
case T_AlterSubscriptionStmt:
retval = _copyAlterSubscriptionStmt(from);
break;
case T_DropSubscriptionStmt:
retval = _copyDropSubscriptionStmt(from);
break;
case T_A_Expr:
retval = _copyAExpr(from);
break;
......
This diff is collapsed.
This diff is collapsed.
......@@ -20,6 +20,7 @@
#include "port/atomics.h"
#include "postmaster/bgworker_internals.h"
#include "postmaster/postmaster.h"
#include "replication/logicallauncher.h"
#include "storage/dsm.h"
#include "storage/ipc.h"
#include "storage/latch.h"
......@@ -107,6 +108,15 @@ struct BackgroundWorkerHandle
static BackgroundWorkerArray *BackgroundWorkerData;
/*
* List of workers that are allowed to be started outside of
* shared_preload_libraries.
*/
static const bgworker_main_type InternalBGWorkers[] = {
ApplyLauncherMain,
NULL
};
/*
* Calculate shared memory needed.
*/
......@@ -761,12 +771,23 @@ RegisterBackgroundWorker(BackgroundWorker *worker)
{
RegisteredBgWorker *rw;
static int numworkers = 0;
bool internal = false;
int i;
if (!IsUnderPostmaster)
ereport(DEBUG1,
(errmsg("registering background worker \"%s\"", worker->bgw_name)));
if (!process_shared_preload_libraries_in_progress)
for (i = 0; InternalBGWorkers[i]; i++)
{
if (worker->bgw_main == InternalBGWorkers[i])
{
internal = true;
break;
}
}
if (!process_shared_preload_libraries_in_progress && !internal)
{
if (!IsUnderPostmaster)
ereport(LOG,
......
......@@ -3303,6 +3303,12 @@ pgstat_get_wait_activity(WaitEventActivity w)
case WAIT_EVENT_WAL_WRITER_MAIN:
event_name = "WalWriterMain";
break;
case WAIT_EVENT_LOGICAL_LAUNCHER_MAIN:
event_name = "LogicalLauncherMain";
break;
case WAIT_EVENT_LOGICAL_APPLY_MAIN:
event_name = "LogicalApplyMain";
break;
/* no default case, so that compiler will warn */
}
......
......@@ -113,6 +113,7 @@
#include "postmaster/pgarch.h"
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
#include "replication/logicallauncher.h"
#include "replication/walsender.h"
#include "storage/fd.h"
#include "storage/ipc.h"
......@@ -941,6 +942,14 @@ PostmasterMain(int argc, char *argv[])
}
#endif
/*
* 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();
/*
* process any libraries that should be preloaded at postmaster start
*/
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment