Logical replication support for TRUNCATE
Update the built-in logical replication system to make use of the previously added logical decoding for TRUNCATE support. Add the required truncate callback to pgoutput and a new logical replication protocol message. Publications get a new attribute to determine whether to replicate truncate actions. When updating a publication via pg_dump from an older version, this is not set, thus preserving the previous behavior. Author: Simon Riggs <simon@2ndquadrant.com> Author: Marco Nenciarini <marco.nenciarini@2ndquadrant.it> Author: Peter Eisentraut <peter.eisentraut@2ndquadrant.com> Reviewed-by: Petr Jelinek <petr.jelinek@2ndquadrant.com> Reviewed-by: Andres Freund <andres@anarazel.de> Reviewed-by: Alvaro Herrera <alvherre@alvh.no-ip.org>
This commit is contained in:
parent
5dfd1e5a66
commit
039eb6e92f
@ -5518,6 +5518,14 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
|
|||||||
<entry>If true, <command>DELETE</command> operations are replicated for
|
<entry>If true, <command>DELETE</command> operations are replicated for
|
||||||
tables in the publication.</entry>
|
tables in the publication.</entry>
|
||||||
</row>
|
</row>
|
||||||
|
|
||||||
|
<row>
|
||||||
|
<entry><structfield>pubtruncate</structfield></entry>
|
||||||
|
<entry><type>bool</type></entry>
|
||||||
|
<entry></entry>
|
||||||
|
<entry>If true, <command>TRUNCATE</command> operations are replicated for
|
||||||
|
tables in the publication.</entry>
|
||||||
|
</row>
|
||||||
</tbody>
|
</tbody>
|
||||||
</tgroup>
|
</tgroup>
|
||||||
</table>
|
</table>
|
||||||
|
@ -108,8 +108,8 @@
|
|||||||
|
|
||||||
<para>
|
<para>
|
||||||
Publications can choose to limit the changes they produce to
|
Publications can choose to limit the changes they produce to
|
||||||
any combination of <command>INSERT</command>, <command>UPDATE</command>, and
|
any combination of <command>INSERT</command>, <command>UPDATE</command>,
|
||||||
<command>DELETE</command>, similar to how triggers are fired by
|
<command>DELETE</command>, and <command>TRUNCATE</command>, similar to how triggers are fired by
|
||||||
particular event types. By default, all operation types are replicated.
|
particular event types. By default, all operation types are replicated.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
@ -364,15 +364,6 @@
|
|||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
|
|
||||||
<listitem>
|
|
||||||
<para>
|
|
||||||
<command>TRUNCATE</command> commands are not replicated. This can, of
|
|
||||||
course, be worked around by using <command>DELETE</command> instead. To
|
|
||||||
avoid accidental <command>TRUNCATE</command> invocations, you can revoke
|
|
||||||
the <literal>TRUNCATE</literal> privilege from tables.
|
|
||||||
</para>
|
|
||||||
</listitem>
|
|
||||||
|
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Large objects (see <xref linkend="largeobjects"/>) are not replicated.
|
Large objects (see <xref linkend="largeobjects"/>) are not replicated.
|
||||||
|
@ -6774,6 +6774,62 @@ Delete
|
|||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term>
|
||||||
|
Truncate
|
||||||
|
</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
|
||||||
|
<variablelist>
|
||||||
|
<varlistentry>
|
||||||
|
<term>
|
||||||
|
Byte1('T')
|
||||||
|
</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Identifies the message as a truncate message.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>
|
||||||
|
Int32
|
||||||
|
</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Number of relations
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>
|
||||||
|
Int8
|
||||||
|
</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Option bits for <command>TRUNCATE</command>:
|
||||||
|
1 for <literal>CASCADE</literal>, 2 for <literal>RESTART IDENTITY</literal>
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry>
|
||||||
|
<term>
|
||||||
|
Int32
|
||||||
|
</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
ID of the relation corresponding to the ID in the relation
|
||||||
|
message. This field is repeated for each relation.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
</variablelist>
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
</variablelist>
|
</variablelist>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
|
@ -106,10 +106,11 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
|
|||||||
This parameter determines which DML operations will be published by
|
This parameter determines which DML operations will be published by
|
||||||
the new publication to the subscribers. The value is
|
the new publication to the subscribers. The value is
|
||||||
comma-separated list of operations. The allowed operations are
|
comma-separated list of operations. The allowed operations are
|
||||||
<literal>insert</literal>, <literal>update</literal>, and
|
<literal>insert</literal>, <literal>update</literal>,
|
||||||
<literal>delete</literal>. The default is to publish all actions,
|
<literal>delete</literal>, and <literal>truncate</literal>.
|
||||||
|
The default is to publish all actions,
|
||||||
and so the default value for this option is
|
and so the default value for this option is
|
||||||
<literal>'insert, update, delete'</literal>.
|
<literal>'insert, update, delete, truncate'</literal>.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -168,8 +169,7 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
|
|||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
<command>TRUNCATE</command> and <acronym>DDL</acronym> operations
|
<acronym>DDL</acronym> operations are not published.
|
||||||
are not published.
|
|
||||||
</para>
|
</para>
|
||||||
</refsect1>
|
</refsect1>
|
||||||
|
|
||||||
|
@ -376,6 +376,7 @@ GetPublication(Oid pubid)
|
|||||||
pub->pubactions.pubinsert = pubform->pubinsert;
|
pub->pubactions.pubinsert = pubform->pubinsert;
|
||||||
pub->pubactions.pubupdate = pubform->pubupdate;
|
pub->pubactions.pubupdate = pubform->pubupdate;
|
||||||
pub->pubactions.pubdelete = pubform->pubdelete;
|
pub->pubactions.pubdelete = pubform->pubdelete;
|
||||||
|
pub->pubactions.pubtruncate = pubform->pubtruncate;
|
||||||
|
|
||||||
ReleaseSysCache(tup);
|
ReleaseSysCache(tup);
|
||||||
|
|
||||||
|
@ -62,7 +62,8 @@ parse_publication_options(List *options,
|
|||||||
bool *publish_given,
|
bool *publish_given,
|
||||||
bool *publish_insert,
|
bool *publish_insert,
|
||||||
bool *publish_update,
|
bool *publish_update,
|
||||||
bool *publish_delete)
|
bool *publish_delete,
|
||||||
|
bool *publish_truncate)
|
||||||
{
|
{
|
||||||
ListCell *lc;
|
ListCell *lc;
|
||||||
|
|
||||||
@ -72,6 +73,7 @@ parse_publication_options(List *options,
|
|||||||
*publish_insert = true;
|
*publish_insert = true;
|
||||||
*publish_update = true;
|
*publish_update = true;
|
||||||
*publish_delete = true;
|
*publish_delete = true;
|
||||||
|
*publish_truncate = true;
|
||||||
|
|
||||||
/* Parse options */
|
/* Parse options */
|
||||||
foreach(lc, options)
|
foreach(lc, options)
|
||||||
@ -96,6 +98,7 @@ parse_publication_options(List *options,
|
|||||||
*publish_insert = false;
|
*publish_insert = false;
|
||||||
*publish_update = false;
|
*publish_update = false;
|
||||||
*publish_delete = false;
|
*publish_delete = false;
|
||||||
|
*publish_truncate = false;
|
||||||
|
|
||||||
*publish_given = true;
|
*publish_given = true;
|
||||||
publish = defGetString(defel);
|
publish = defGetString(defel);
|
||||||
@ -116,6 +119,8 @@ parse_publication_options(List *options,
|
|||||||
*publish_update = true;
|
*publish_update = true;
|
||||||
else if (strcmp(publish_opt, "delete") == 0)
|
else if (strcmp(publish_opt, "delete") == 0)
|
||||||
*publish_delete = true;
|
*publish_delete = true;
|
||||||
|
else if (strcmp(publish_opt, "truncate") == 0)
|
||||||
|
*publish_truncate = true;
|
||||||
else
|
else
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||||
@ -145,6 +150,7 @@ CreatePublication(CreatePublicationStmt *stmt)
|
|||||||
bool publish_insert;
|
bool publish_insert;
|
||||||
bool publish_update;
|
bool publish_update;
|
||||||
bool publish_delete;
|
bool publish_delete;
|
||||||
|
bool publish_truncate;
|
||||||
AclResult aclresult;
|
AclResult aclresult;
|
||||||
|
|
||||||
/* must have CREATE privilege on database */
|
/* must have CREATE privilege on database */
|
||||||
@ -181,7 +187,8 @@ CreatePublication(CreatePublicationStmt *stmt)
|
|||||||
|
|
||||||
parse_publication_options(stmt->options,
|
parse_publication_options(stmt->options,
|
||||||
&publish_given, &publish_insert,
|
&publish_given, &publish_insert,
|
||||||
&publish_update, &publish_delete);
|
&publish_update, &publish_delete,
|
||||||
|
&publish_truncate);
|
||||||
|
|
||||||
values[Anum_pg_publication_puballtables - 1] =
|
values[Anum_pg_publication_puballtables - 1] =
|
||||||
BoolGetDatum(stmt->for_all_tables);
|
BoolGetDatum(stmt->for_all_tables);
|
||||||
@ -191,6 +198,8 @@ CreatePublication(CreatePublicationStmt *stmt)
|
|||||||
BoolGetDatum(publish_update);
|
BoolGetDatum(publish_update);
|
||||||
values[Anum_pg_publication_pubdelete - 1] =
|
values[Anum_pg_publication_pubdelete - 1] =
|
||||||
BoolGetDatum(publish_delete);
|
BoolGetDatum(publish_delete);
|
||||||
|
values[Anum_pg_publication_pubtruncate - 1] =
|
||||||
|
BoolGetDatum(publish_truncate);
|
||||||
|
|
||||||
tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
|
tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
|
||||||
|
|
||||||
@ -237,11 +246,13 @@ AlterPublicationOptions(AlterPublicationStmt *stmt, Relation rel,
|
|||||||
bool publish_insert;
|
bool publish_insert;
|
||||||
bool publish_update;
|
bool publish_update;
|
||||||
bool publish_delete;
|
bool publish_delete;
|
||||||
|
bool publish_truncate;
|
||||||
ObjectAddress obj;
|
ObjectAddress obj;
|
||||||
|
|
||||||
parse_publication_options(stmt->options,
|
parse_publication_options(stmt->options,
|
||||||
&publish_given, &publish_insert,
|
&publish_given, &publish_insert,
|
||||||
&publish_update, &publish_delete);
|
&publish_update, &publish_delete,
|
||||||
|
&publish_truncate);
|
||||||
|
|
||||||
/* Everything ok, form a new tuple. */
|
/* Everything ok, form a new tuple. */
|
||||||
memset(values, 0, sizeof(values));
|
memset(values, 0, sizeof(values));
|
||||||
@ -258,6 +269,9 @@ AlterPublicationOptions(AlterPublicationStmt *stmt, Relation rel,
|
|||||||
|
|
||||||
values[Anum_pg_publication_pubdelete - 1] = BoolGetDatum(publish_delete);
|
values[Anum_pg_publication_pubdelete - 1] = BoolGetDatum(publish_delete);
|
||||||
replaces[Anum_pg_publication_pubdelete - 1] = true;
|
replaces[Anum_pg_publication_pubdelete - 1] = true;
|
||||||
|
|
||||||
|
values[Anum_pg_publication_pubtruncate - 1] = BoolGetDatum(publish_truncate);
|
||||||
|
replaces[Anum_pg_publication_pubtruncate - 1] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
tup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls,
|
tup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls,
|
||||||
|
@ -26,6 +26,9 @@
|
|||||||
*/
|
*/
|
||||||
#define LOGICALREP_IS_REPLICA_IDENTITY 1
|
#define LOGICALREP_IS_REPLICA_IDENTITY 1
|
||||||
|
|
||||||
|
#define TRUNCATE_CASCADE (1<<0)
|
||||||
|
#define TRUNCATE_RESTART_SEQS (1<<1)
|
||||||
|
|
||||||
static void logicalrep_write_attrs(StringInfo out, Relation rel);
|
static void logicalrep_write_attrs(StringInfo out, Relation rel);
|
||||||
static void logicalrep_write_tuple(StringInfo out, Relation rel,
|
static void logicalrep_write_tuple(StringInfo out, Relation rel,
|
||||||
HeapTuple tuple);
|
HeapTuple tuple);
|
||||||
@ -292,6 +295,58 @@ logicalrep_read_delete(StringInfo in, LogicalRepTupleData *oldtup)
|
|||||||
return relid;
|
return relid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Write TRUNCATE to the output stream.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
logicalrep_write_truncate(StringInfo out,
|
||||||
|
int nrelids,
|
||||||
|
Oid relids[],
|
||||||
|
bool cascade, bool restart_seqs)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
uint8 flags = 0;
|
||||||
|
|
||||||
|
pq_sendbyte(out, 'T'); /* action TRUNCATE */
|
||||||
|
|
||||||
|
pq_sendint32(out, nrelids);
|
||||||
|
|
||||||
|
/* encode and send truncate flags */
|
||||||
|
if (cascade)
|
||||||
|
flags |= TRUNCATE_CASCADE;
|
||||||
|
if (restart_seqs)
|
||||||
|
flags |= TRUNCATE_RESTART_SEQS;
|
||||||
|
pq_sendint8(out, flags);
|
||||||
|
|
||||||
|
for (i = 0; i < nrelids; i++)
|
||||||
|
pq_sendint32(out, relids[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read TRUNCATE from stream.
|
||||||
|
*/
|
||||||
|
List *
|
||||||
|
logicalrep_read_truncate(StringInfo in,
|
||||||
|
bool *cascade, bool *restart_seqs)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
int nrelids;
|
||||||
|
List *relids = NIL;
|
||||||
|
uint8 flags;
|
||||||
|
|
||||||
|
nrelids = pq_getmsgint(in, 4);
|
||||||
|
|
||||||
|
/* read and decode truncate flags */
|
||||||
|
flags = pq_getmsgint(in, 1);
|
||||||
|
*cascade = (flags & TRUNCATE_CASCADE) > 0;
|
||||||
|
*restart_seqs = (flags & TRUNCATE_RESTART_SEQS) > 0;
|
||||||
|
|
||||||
|
for (i = 0; i < nrelids; i++)
|
||||||
|
relids = lappend_oid(relids, pq_getmsgint(in, 4));
|
||||||
|
|
||||||
|
return relids;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Write relation description to the output stream.
|
* Write relation description to the output stream.
|
||||||
*/
|
*/
|
||||||
|
@ -30,10 +30,12 @@
|
|||||||
#include "access/xact.h"
|
#include "access/xact.h"
|
||||||
#include "access/xlog_internal.h"
|
#include "access/xlog_internal.h"
|
||||||
|
|
||||||
|
#include "catalog/catalog.h"
|
||||||
#include "catalog/namespace.h"
|
#include "catalog/namespace.h"
|
||||||
#include "catalog/pg_subscription.h"
|
#include "catalog/pg_subscription.h"
|
||||||
#include "catalog/pg_subscription_rel.h"
|
#include "catalog/pg_subscription_rel.h"
|
||||||
|
|
||||||
|
#include "commands/tablecmds.h"
|
||||||
#include "commands/trigger.h"
|
#include "commands/trigger.h"
|
||||||
|
|
||||||
#include "executor/executor.h"
|
#include "executor/executor.h"
|
||||||
@ -83,6 +85,7 @@
|
|||||||
#include "utils/inval.h"
|
#include "utils/inval.h"
|
||||||
#include "utils/lsyscache.h"
|
#include "utils/lsyscache.h"
|
||||||
#include "utils/memutils.h"
|
#include "utils/memutils.h"
|
||||||
|
#include "utils/rel.h"
|
||||||
#include "utils/timeout.h"
|
#include "utils/timeout.h"
|
||||||
#include "utils/tqual.h"
|
#include "utils/tqual.h"
|
||||||
#include "utils/syscache.h"
|
#include "utils/syscache.h"
|
||||||
@ -888,6 +891,67 @@ apply_handle_delete(StringInfo s)
|
|||||||
CommandCounterIncrement();
|
CommandCounterIncrement();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Handle TRUNCATE message.
|
||||||
|
*
|
||||||
|
* TODO: FDW support
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
apply_handle_truncate(StringInfo s)
|
||||||
|
{
|
||||||
|
bool cascade = false;
|
||||||
|
bool restart_seqs = false;
|
||||||
|
List *remote_relids = NIL;
|
||||||
|
List *remote_rels = NIL;
|
||||||
|
List *rels = NIL;
|
||||||
|
List *relids = NIL;
|
||||||
|
List *relids_logged = NIL;
|
||||||
|
ListCell *lc;
|
||||||
|
|
||||||
|
ensure_transaction();
|
||||||
|
|
||||||
|
remote_relids = logicalrep_read_truncate(s, &cascade, &restart_seqs);
|
||||||
|
|
||||||
|
foreach(lc, remote_relids)
|
||||||
|
{
|
||||||
|
LogicalRepRelId relid = lfirst_oid(lc);
|
||||||
|
LogicalRepRelMapEntry *rel;
|
||||||
|
|
||||||
|
rel = logicalrep_rel_open(relid, RowExclusiveLock);
|
||||||
|
if (!should_apply_changes_for_rel(rel))
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* The relation can't become interesting in the middle of the
|
||||||
|
* transaction so it's safe to unlock it.
|
||||||
|
*/
|
||||||
|
logicalrep_rel_close(rel, RowExclusiveLock);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
remote_rels = lappend(remote_rels, rel);
|
||||||
|
rels = lappend(rels, rel->localrel);
|
||||||
|
relids = lappend_oid(relids, rel->localreloid);
|
||||||
|
if (RelationIsLogicallyLogged(rel->localrel))
|
||||||
|
relids_logged = lappend_oid(relids, rel->localreloid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Even if we used CASCADE on the upstream master we explicitly
|
||||||
|
* default to replaying changes without further cascading.
|
||||||
|
* This might be later changeable with a user specified option.
|
||||||
|
*/
|
||||||
|
ExecuteTruncateGuts(rels, relids, relids_logged, DROP_RESTRICT, restart_seqs);
|
||||||
|
|
||||||
|
foreach(lc, remote_rels)
|
||||||
|
{
|
||||||
|
LogicalRepRelMapEntry *rel = lfirst(lc);
|
||||||
|
|
||||||
|
logicalrep_rel_close(rel, NoLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
CommandCounterIncrement();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Logical replication protocol message dispatcher.
|
* Logical replication protocol message dispatcher.
|
||||||
@ -919,6 +983,10 @@ apply_dispatch(StringInfo s)
|
|||||||
case 'D':
|
case 'D':
|
||||||
apply_handle_delete(s);
|
apply_handle_delete(s);
|
||||||
break;
|
break;
|
||||||
|
/* TRUNCATE */
|
||||||
|
case 'T':
|
||||||
|
apply_handle_truncate(s);
|
||||||
|
break;
|
||||||
/* RELATION */
|
/* RELATION */
|
||||||
case 'R':
|
case 'R':
|
||||||
apply_handle_relation(s);
|
apply_handle_relation(s);
|
||||||
|
@ -39,6 +39,9 @@ static void pgoutput_commit_txn(LogicalDecodingContext *ctx,
|
|||||||
static void pgoutput_change(LogicalDecodingContext *ctx,
|
static void pgoutput_change(LogicalDecodingContext *ctx,
|
||||||
ReorderBufferTXN *txn, Relation rel,
|
ReorderBufferTXN *txn, Relation rel,
|
||||||
ReorderBufferChange *change);
|
ReorderBufferChange *change);
|
||||||
|
static void pgoutput_truncate(LogicalDecodingContext *ctx,
|
||||||
|
ReorderBufferTXN *txn, int nrelations, Relation relations[],
|
||||||
|
ReorderBufferChange *change);
|
||||||
static bool pgoutput_origin_filter(LogicalDecodingContext *ctx,
|
static bool pgoutput_origin_filter(LogicalDecodingContext *ctx,
|
||||||
RepOriginId origin_id);
|
RepOriginId origin_id);
|
||||||
|
|
||||||
@ -77,6 +80,7 @@ _PG_output_plugin_init(OutputPluginCallbacks *cb)
|
|||||||
cb->startup_cb = pgoutput_startup;
|
cb->startup_cb = pgoutput_startup;
|
||||||
cb->begin_cb = pgoutput_begin_txn;
|
cb->begin_cb = pgoutput_begin_txn;
|
||||||
cb->change_cb = pgoutput_change;
|
cb->change_cb = pgoutput_change;
|
||||||
|
cb->truncate_cb = pgoutput_truncate;
|
||||||
cb->commit_cb = pgoutput_commit_txn;
|
cb->commit_cb = pgoutput_commit_txn;
|
||||||
cb->filter_by_origin_cb = pgoutput_origin_filter;
|
cb->filter_by_origin_cb = pgoutput_origin_filter;
|
||||||
cb->shutdown_cb = pgoutput_shutdown;
|
cb->shutdown_cb = pgoutput_shutdown;
|
||||||
@ -250,6 +254,46 @@ pgoutput_commit_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
|
|||||||
OutputPluginWrite(ctx, true);
|
OutputPluginWrite(ctx, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Write the relation schema if the current schema hasn't been sent yet.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
maybe_send_schema(LogicalDecodingContext *ctx,
|
||||||
|
Relation relation, RelationSyncEntry *relentry)
|
||||||
|
{
|
||||||
|
if (!relentry->schema_sent)
|
||||||
|
{
|
||||||
|
TupleDesc desc;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
desc = RelationGetDescr(relation);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Write out type info if needed. We do that only for user created
|
||||||
|
* types.
|
||||||
|
*/
|
||||||
|
for (i = 0; i < desc->natts; i++)
|
||||||
|
{
|
||||||
|
Form_pg_attribute att = TupleDescAttr(desc, i);
|
||||||
|
|
||||||
|
if (att->attisdropped)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (att->atttypid < FirstNormalObjectId)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
OutputPluginPrepareWrite(ctx, false);
|
||||||
|
logicalrep_write_typ(ctx->out, att->atttypid);
|
||||||
|
OutputPluginWrite(ctx, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputPluginPrepareWrite(ctx, false);
|
||||||
|
logicalrep_write_rel(ctx->out, relation);
|
||||||
|
OutputPluginWrite(ctx, false);
|
||||||
|
relentry->schema_sent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Sends the decoded DML over wire.
|
* Sends the decoded DML over wire.
|
||||||
*/
|
*/
|
||||||
@ -288,40 +332,7 @@ pgoutput_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
|
|||||||
/* Avoid leaking memory by using and resetting our own context */
|
/* Avoid leaking memory by using and resetting our own context */
|
||||||
old = MemoryContextSwitchTo(data->context);
|
old = MemoryContextSwitchTo(data->context);
|
||||||
|
|
||||||
/*
|
maybe_send_schema(ctx, relation, relentry);
|
||||||
* Write the relation schema if the current schema haven't been sent yet.
|
|
||||||
*/
|
|
||||||
if (!relentry->schema_sent)
|
|
||||||
{
|
|
||||||
TupleDesc desc;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
desc = RelationGetDescr(relation);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Write out type info if needed. We do that only for user created
|
|
||||||
* types.
|
|
||||||
*/
|
|
||||||
for (i = 0; i < desc->natts; i++)
|
|
||||||
{
|
|
||||||
Form_pg_attribute att = TupleDescAttr(desc, i);
|
|
||||||
|
|
||||||
if (att->attisdropped)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (att->atttypid < FirstNormalObjectId)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
OutputPluginPrepareWrite(ctx, false);
|
|
||||||
logicalrep_write_typ(ctx->out, att->atttypid);
|
|
||||||
OutputPluginWrite(ctx, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
OutputPluginPrepareWrite(ctx, false);
|
|
||||||
logicalrep_write_rel(ctx->out, relation);
|
|
||||||
OutputPluginWrite(ctx, false);
|
|
||||||
relentry->schema_sent = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Send the data */
|
/* Send the data */
|
||||||
switch (change->action)
|
switch (change->action)
|
||||||
@ -363,6 +374,51 @@ pgoutput_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
|
|||||||
MemoryContextReset(data->context);
|
MemoryContextReset(data->context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
pgoutput_truncate(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
|
||||||
|
int nrelations, Relation relations[], ReorderBufferChange *change)
|
||||||
|
{
|
||||||
|
PGOutputData *data = (PGOutputData *) ctx->output_plugin_private;
|
||||||
|
MemoryContext old;
|
||||||
|
RelationSyncEntry *relentry;
|
||||||
|
int i;
|
||||||
|
int nrelids;
|
||||||
|
Oid *relids;
|
||||||
|
|
||||||
|
old = MemoryContextSwitchTo(data->context);
|
||||||
|
|
||||||
|
relids = palloc0(nrelations * sizeof(Oid));
|
||||||
|
nrelids = 0;
|
||||||
|
|
||||||
|
for (i = 0; i < nrelations; i++)
|
||||||
|
{
|
||||||
|
Relation relation = relations[i];
|
||||||
|
Oid relid = RelationGetRelid(relation);
|
||||||
|
|
||||||
|
if (!is_publishable_relation(relation))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
relentry = get_rel_sync_entry(data, relid);
|
||||||
|
|
||||||
|
if (!relentry->pubactions.pubtruncate)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
relids[nrelids++] = relid;
|
||||||
|
maybe_send_schema(ctx, relation, relentry);
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputPluginPrepareWrite(ctx, true);
|
||||||
|
logicalrep_write_truncate(ctx->out,
|
||||||
|
nrelids,
|
||||||
|
relids,
|
||||||
|
change->data.truncate.cascade,
|
||||||
|
change->data.truncate.restart_seqs);
|
||||||
|
OutputPluginWrite(ctx, true);
|
||||||
|
|
||||||
|
MemoryContextSwitchTo(old);
|
||||||
|
MemoryContextReset(data->context);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Currently we always forward.
|
* Currently we always forward.
|
||||||
*/
|
*/
|
||||||
@ -504,7 +560,7 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
|
|||||||
* we only need to consider ones that the subscriber requested.
|
* we only need to consider ones that the subscriber requested.
|
||||||
*/
|
*/
|
||||||
entry->pubactions.pubinsert = entry->pubactions.pubupdate =
|
entry->pubactions.pubinsert = entry->pubactions.pubupdate =
|
||||||
entry->pubactions.pubdelete = false;
|
entry->pubactions.pubdelete = entry->pubactions.pubtruncate = false;
|
||||||
|
|
||||||
foreach(lc, data->publications)
|
foreach(lc, data->publications)
|
||||||
{
|
{
|
||||||
@ -515,10 +571,11 @@ get_rel_sync_entry(PGOutputData *data, Oid relid)
|
|||||||
entry->pubactions.pubinsert |= pub->pubactions.pubinsert;
|
entry->pubactions.pubinsert |= pub->pubactions.pubinsert;
|
||||||
entry->pubactions.pubupdate |= pub->pubactions.pubupdate;
|
entry->pubactions.pubupdate |= pub->pubactions.pubupdate;
|
||||||
entry->pubactions.pubdelete |= pub->pubactions.pubdelete;
|
entry->pubactions.pubdelete |= pub->pubactions.pubdelete;
|
||||||
|
entry->pubactions.pubtruncate |= pub->pubactions.pubtruncate;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entry->pubactions.pubinsert && entry->pubactions.pubupdate &&
|
if (entry->pubactions.pubinsert && entry->pubactions.pubupdate &&
|
||||||
entry->pubactions.pubdelete)
|
entry->pubactions.pubdelete && entry->pubactions.pubtruncate)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
3
src/backend/utils/cache/relcache.c
vendored
3
src/backend/utils/cache/relcache.c
vendored
@ -5339,6 +5339,7 @@ GetRelationPublicationActions(Relation relation)
|
|||||||
pubactions->pubinsert |= pubform->pubinsert;
|
pubactions->pubinsert |= pubform->pubinsert;
|
||||||
pubactions->pubupdate |= pubform->pubupdate;
|
pubactions->pubupdate |= pubform->pubupdate;
|
||||||
pubactions->pubdelete |= pubform->pubdelete;
|
pubactions->pubdelete |= pubform->pubdelete;
|
||||||
|
pubactions->pubtruncate |= pubform->pubtruncate;
|
||||||
|
|
||||||
ReleaseSysCache(tup);
|
ReleaseSysCache(tup);
|
||||||
|
|
||||||
@ -5347,7 +5348,7 @@ GetRelationPublicationActions(Relation relation)
|
|||||||
* other publications.
|
* other publications.
|
||||||
*/
|
*/
|
||||||
if (pubactions->pubinsert && pubactions->pubupdate &&
|
if (pubactions->pubinsert && pubactions->pubupdate &&
|
||||||
pubactions->pubdelete)
|
pubactions->pubdelete && pubactions->pubtruncate)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3712,6 +3712,7 @@ getPublications(Archive *fout)
|
|||||||
int i_pubinsert;
|
int i_pubinsert;
|
||||||
int i_pubupdate;
|
int i_pubupdate;
|
||||||
int i_pubdelete;
|
int i_pubdelete;
|
||||||
|
int i_pubtruncate;
|
||||||
int i,
|
int i,
|
||||||
ntups;
|
ntups;
|
||||||
|
|
||||||
@ -3723,10 +3724,18 @@ getPublications(Archive *fout)
|
|||||||
resetPQExpBuffer(query);
|
resetPQExpBuffer(query);
|
||||||
|
|
||||||
/* Get the publications. */
|
/* Get the publications. */
|
||||||
|
if (fout->remoteVersion >= 110000)
|
||||||
appendPQExpBuffer(query,
|
appendPQExpBuffer(query,
|
||||||
"SELECT p.tableoid, p.oid, p.pubname, "
|
"SELECT p.tableoid, p.oid, p.pubname, "
|
||||||
"(%s p.pubowner) AS rolname, "
|
"(%s p.pubowner) AS rolname, "
|
||||||
"p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete "
|
"p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate "
|
||||||
|
"FROM pg_publication p",
|
||||||
|
username_subquery);
|
||||||
|
else
|
||||||
|
appendPQExpBuffer(query,
|
||||||
|
"SELECT p.tableoid, p.oid, p.pubname, "
|
||||||
|
"(%s p.pubowner) AS rolname, "
|
||||||
|
"p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, false AS pubtruncate "
|
||||||
"FROM pg_publication p",
|
"FROM pg_publication p",
|
||||||
username_subquery);
|
username_subquery);
|
||||||
|
|
||||||
@ -3742,6 +3751,7 @@ getPublications(Archive *fout)
|
|||||||
i_pubinsert = PQfnumber(res, "pubinsert");
|
i_pubinsert = PQfnumber(res, "pubinsert");
|
||||||
i_pubupdate = PQfnumber(res, "pubupdate");
|
i_pubupdate = PQfnumber(res, "pubupdate");
|
||||||
i_pubdelete = PQfnumber(res, "pubdelete");
|
i_pubdelete = PQfnumber(res, "pubdelete");
|
||||||
|
i_pubtruncate = PQfnumber(res, "pubtruncate");
|
||||||
|
|
||||||
pubinfo = pg_malloc(ntups * sizeof(PublicationInfo));
|
pubinfo = pg_malloc(ntups * sizeof(PublicationInfo));
|
||||||
|
|
||||||
@ -3762,6 +3772,8 @@ getPublications(Archive *fout)
|
|||||||
(strcmp(PQgetvalue(res, i, i_pubupdate), "t") == 0);
|
(strcmp(PQgetvalue(res, i, i_pubupdate), "t") == 0);
|
||||||
pubinfo[i].pubdelete =
|
pubinfo[i].pubdelete =
|
||||||
(strcmp(PQgetvalue(res, i, i_pubdelete), "t") == 0);
|
(strcmp(PQgetvalue(res, i, i_pubdelete), "t") == 0);
|
||||||
|
pubinfo[i].pubtruncate =
|
||||||
|
(strcmp(PQgetvalue(res, i, i_pubtruncate), "t") == 0);
|
||||||
|
|
||||||
if (strlen(pubinfo[i].rolname) == 0)
|
if (strlen(pubinfo[i].rolname) == 0)
|
||||||
write_msg(NULL, "WARNING: owner of publication \"%s\" appears to be invalid\n",
|
write_msg(NULL, "WARNING: owner of publication \"%s\" appears to be invalid\n",
|
||||||
@ -3829,6 +3841,15 @@ dumpPublication(Archive *fout, PublicationInfo *pubinfo)
|
|||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pubinfo->pubtruncate)
|
||||||
|
{
|
||||||
|
if (!first)
|
||||||
|
appendPQExpBufferStr(query, ", ");
|
||||||
|
|
||||||
|
appendPQExpBufferStr(query, "truncate");
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
|
||||||
appendPQExpBufferStr(query, "');\n");
|
appendPQExpBufferStr(query, "');\n");
|
||||||
|
|
||||||
ArchiveEntry(fout, pubinfo->dobj.catId, pubinfo->dobj.dumpId,
|
ArchiveEntry(fout, pubinfo->dobj.catId, pubinfo->dobj.dumpId,
|
||||||
|
@ -595,6 +595,7 @@ typedef struct _PublicationInfo
|
|||||||
bool pubinsert;
|
bool pubinsert;
|
||||||
bool pubupdate;
|
bool pubupdate;
|
||||||
bool pubdelete;
|
bool pubdelete;
|
||||||
|
bool pubtruncate;
|
||||||
} PublicationInfo;
|
} PublicationInfo;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -2038,7 +2038,7 @@ qr/CREATE TRANSFORM FOR integer LANGUAGE sql \(FROM SQL WITH FUNCTION pg_catalog
|
|||||||
create_order => 50,
|
create_order => 50,
|
||||||
create_sql => 'CREATE PUBLICATION pub1;',
|
create_sql => 'CREATE PUBLICATION pub1;',
|
||||||
regexp => qr/^
|
regexp => qr/^
|
||||||
\QCREATE PUBLICATION pub1 WITH (publish = 'insert, update, delete');\E
|
\QCREATE PUBLICATION pub1 WITH (publish = 'insert, update, delete, truncate');\E
|
||||||
/xm,
|
/xm,
|
||||||
like => {
|
like => {
|
||||||
%full_runs,
|
%full_runs,
|
||||||
|
@ -5187,7 +5187,7 @@ listPublications(const char *pattern)
|
|||||||
PQExpBufferData buf;
|
PQExpBufferData buf;
|
||||||
PGresult *res;
|
PGresult *res;
|
||||||
printQueryOpt myopt = pset.popt;
|
printQueryOpt myopt = pset.popt;
|
||||||
static const bool translate_columns[] = {false, false, false, false, false, false};
|
static const bool translate_columns[] = {false, false, false, false, false, false, false};
|
||||||
|
|
||||||
if (pset.sversion < 100000)
|
if (pset.sversion < 100000)
|
||||||
{
|
{
|
||||||
@ -5207,13 +5207,17 @@ listPublications(const char *pattern)
|
|||||||
" puballtables AS \"%s\",\n"
|
" puballtables AS \"%s\",\n"
|
||||||
" pubinsert AS \"%s\",\n"
|
" pubinsert AS \"%s\",\n"
|
||||||
" pubupdate AS \"%s\",\n"
|
" pubupdate AS \"%s\",\n"
|
||||||
" pubdelete AS \"%s\"\n",
|
" pubdelete AS \"%s\"",
|
||||||
gettext_noop("Name"),
|
gettext_noop("Name"),
|
||||||
gettext_noop("Owner"),
|
gettext_noop("Owner"),
|
||||||
gettext_noop("All tables"),
|
gettext_noop("All tables"),
|
||||||
gettext_noop("Inserts"),
|
gettext_noop("Inserts"),
|
||||||
gettext_noop("Updates"),
|
gettext_noop("Updates"),
|
||||||
gettext_noop("Deletes"));
|
gettext_noop("Deletes"));
|
||||||
|
if (pset.sversion >= 110000)
|
||||||
|
appendPQExpBuffer(&buf,
|
||||||
|
",\n pubtruncate AS \"%s\"",
|
||||||
|
gettext_noop("Truncates"));
|
||||||
|
|
||||||
appendPQExpBufferStr(&buf,
|
appendPQExpBufferStr(&buf,
|
||||||
"\nFROM pg_catalog.pg_publication\n");
|
"\nFROM pg_catalog.pg_publication\n");
|
||||||
@ -5254,6 +5258,7 @@ describePublications(const char *pattern)
|
|||||||
PQExpBufferData buf;
|
PQExpBufferData buf;
|
||||||
int i;
|
int i;
|
||||||
PGresult *res;
|
PGresult *res;
|
||||||
|
bool has_pubtruncate;
|
||||||
|
|
||||||
if (pset.sversion < 100000)
|
if (pset.sversion < 100000)
|
||||||
{
|
{
|
||||||
@ -5265,13 +5270,19 @@ describePublications(const char *pattern)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
has_pubtruncate = (pset.sversion >= 110000);
|
||||||
|
|
||||||
initPQExpBuffer(&buf);
|
initPQExpBuffer(&buf);
|
||||||
|
|
||||||
printfPQExpBuffer(&buf,
|
printfPQExpBuffer(&buf,
|
||||||
"SELECT oid, pubname,\n"
|
"SELECT oid, pubname,\n"
|
||||||
" pg_catalog.pg_get_userbyid(pubowner) AS owner,\n"
|
" pg_catalog.pg_get_userbyid(pubowner) AS owner,\n"
|
||||||
" puballtables, pubinsert, pubupdate, pubdelete\n"
|
" puballtables, pubinsert, pubupdate, pubdelete");
|
||||||
"FROM pg_catalog.pg_publication\n");
|
if (has_pubtruncate)
|
||||||
|
appendPQExpBuffer(&buf,
|
||||||
|
", pubtruncate");
|
||||||
|
appendPQExpBuffer(&buf,
|
||||||
|
"\nFROM pg_catalog.pg_publication\n");
|
||||||
|
|
||||||
processSQLNamePattern(pset.db, &buf, pattern, false, false,
|
processSQLNamePattern(pset.db, &buf, pattern, false, false,
|
||||||
NULL, "pubname", NULL,
|
NULL, "pubname", NULL,
|
||||||
@ -5317,6 +5328,9 @@ describePublications(const char *pattern)
|
|||||||
printTableOpt myopt = pset.popt.topt;
|
printTableOpt myopt = pset.popt.topt;
|
||||||
printTableContent cont;
|
printTableContent cont;
|
||||||
|
|
||||||
|
if (has_pubtruncate)
|
||||||
|
ncols++;
|
||||||
|
|
||||||
initPQExpBuffer(&title);
|
initPQExpBuffer(&title);
|
||||||
printfPQExpBuffer(&title, _("Publication %s"), pubname);
|
printfPQExpBuffer(&title, _("Publication %s"), pubname);
|
||||||
printTableInit(&cont, &myopt, title.data, ncols, nrows);
|
printTableInit(&cont, &myopt, title.data, ncols, nrows);
|
||||||
@ -5326,12 +5340,16 @@ describePublications(const char *pattern)
|
|||||||
printTableAddHeader(&cont, gettext_noop("Inserts"), true, align);
|
printTableAddHeader(&cont, gettext_noop("Inserts"), true, align);
|
||||||
printTableAddHeader(&cont, gettext_noop("Updates"), true, align);
|
printTableAddHeader(&cont, gettext_noop("Updates"), true, align);
|
||||||
printTableAddHeader(&cont, gettext_noop("Deletes"), true, align);
|
printTableAddHeader(&cont, gettext_noop("Deletes"), true, align);
|
||||||
|
if (has_pubtruncate)
|
||||||
|
printTableAddHeader(&cont, gettext_noop("Truncates"), true, align);
|
||||||
|
|
||||||
printTableAddCell(&cont, PQgetvalue(res, i, 2), false, false);
|
printTableAddCell(&cont, PQgetvalue(res, i, 2), false, false);
|
||||||
printTableAddCell(&cont, PQgetvalue(res, i, 3), false, false);
|
printTableAddCell(&cont, PQgetvalue(res, i, 3), false, false);
|
||||||
printTableAddCell(&cont, PQgetvalue(res, i, 4), false, false);
|
printTableAddCell(&cont, PQgetvalue(res, i, 4), false, false);
|
||||||
printTableAddCell(&cont, PQgetvalue(res, i, 5), false, false);
|
printTableAddCell(&cont, PQgetvalue(res, i, 5), false, false);
|
||||||
printTableAddCell(&cont, PQgetvalue(res, i, 6), false, false);
|
printTableAddCell(&cont, PQgetvalue(res, i, 6), false, false);
|
||||||
|
if (has_pubtruncate)
|
||||||
|
printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false);
|
||||||
|
|
||||||
if (!puballtables)
|
if (!puballtables)
|
||||||
{
|
{
|
||||||
|
@ -53,6 +53,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/* yyyymmddN */
|
/* yyyymmddN */
|
||||||
#define CATALOG_VERSION_NO 201804061
|
#define CATALOG_VERSION_NO 201804071
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -49,6 +49,9 @@ CATALOG(pg_publication,6104)
|
|||||||
/* true if deletes are published */
|
/* true if deletes are published */
|
||||||
bool pubdelete;
|
bool pubdelete;
|
||||||
|
|
||||||
|
/* true if truncates are published */
|
||||||
|
bool pubtruncate;
|
||||||
|
|
||||||
} FormData_pg_publication;
|
} FormData_pg_publication;
|
||||||
|
|
||||||
/* ----------------
|
/* ----------------
|
||||||
@ -63,19 +66,21 @@ typedef FormData_pg_publication *Form_pg_publication;
|
|||||||
* ----------------
|
* ----------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define Natts_pg_publication 6
|
#define Natts_pg_publication 7
|
||||||
#define Anum_pg_publication_pubname 1
|
#define Anum_pg_publication_pubname 1
|
||||||
#define Anum_pg_publication_pubowner 2
|
#define Anum_pg_publication_pubowner 2
|
||||||
#define Anum_pg_publication_puballtables 3
|
#define Anum_pg_publication_puballtables 3
|
||||||
#define Anum_pg_publication_pubinsert 4
|
#define Anum_pg_publication_pubinsert 4
|
||||||
#define Anum_pg_publication_pubupdate 5
|
#define Anum_pg_publication_pubupdate 5
|
||||||
#define Anum_pg_publication_pubdelete 6
|
#define Anum_pg_publication_pubdelete 6
|
||||||
|
#define Anum_pg_publication_pubtruncate 7
|
||||||
|
|
||||||
typedef struct PublicationActions
|
typedef struct PublicationActions
|
||||||
{
|
{
|
||||||
bool pubinsert;
|
bool pubinsert;
|
||||||
bool pubupdate;
|
bool pubupdate;
|
||||||
bool pubdelete;
|
bool pubdelete;
|
||||||
|
bool pubtruncate;
|
||||||
} PublicationActions;
|
} PublicationActions;
|
||||||
|
|
||||||
typedef struct Publication
|
typedef struct Publication
|
||||||
|
@ -97,6 +97,10 @@ extern void logicalrep_write_delete(StringInfo out, Relation rel,
|
|||||||
HeapTuple oldtuple);
|
HeapTuple oldtuple);
|
||||||
extern LogicalRepRelId logicalrep_read_delete(StringInfo in,
|
extern LogicalRepRelId logicalrep_read_delete(StringInfo in,
|
||||||
LogicalRepTupleData *oldtup);
|
LogicalRepTupleData *oldtup);
|
||||||
|
extern void logicalrep_write_truncate(StringInfo out, int nrelids, Oid relids[],
|
||||||
|
bool cascade, bool restart_seqs);
|
||||||
|
extern List *logicalrep_read_truncate(StringInfo in,
|
||||||
|
bool *cascade, bool *restart_seqs);
|
||||||
extern void logicalrep_write_rel(StringInfo out, Relation rel);
|
extern void logicalrep_write_rel(StringInfo out, Relation rel);
|
||||||
extern LogicalRepRelation *logicalrep_read_rel(StringInfo in);
|
extern LogicalRepRelation *logicalrep_read_rel(StringInfo in);
|
||||||
extern void logicalrep_write_typ(StringInfo out, Oid typoid);
|
extern void logicalrep_write_typ(StringInfo out, Oid typoid);
|
||||||
|
@ -22,19 +22,19 @@ CREATE PUBLICATION testpub_xxx WITH (publish = 'cluster, vacuum');
|
|||||||
ERROR: unrecognized "publish" value: "cluster"
|
ERROR: unrecognized "publish" value: "cluster"
|
||||||
\dRp
|
\dRp
|
||||||
List of publications
|
List of publications
|
||||||
Name | Owner | All tables | Inserts | Updates | Deletes
|
Name | Owner | All tables | Inserts | Updates | Deletes | Truncates
|
||||||
--------------------+--------------------------+------------+---------+---------+---------
|
--------------------+--------------------------+------------+---------+---------+---------+-----------
|
||||||
testpib_ins_trunct | regress_publication_user | f | t | f | f
|
testpib_ins_trunct | regress_publication_user | f | t | f | f | f
|
||||||
testpub_default | regress_publication_user | f | f | t | f
|
testpub_default | regress_publication_user | f | f | t | f | f
|
||||||
(2 rows)
|
(2 rows)
|
||||||
|
|
||||||
ALTER PUBLICATION testpub_default SET (publish = 'insert, update, delete');
|
ALTER PUBLICATION testpub_default SET (publish = 'insert, update, delete');
|
||||||
\dRp
|
\dRp
|
||||||
List of publications
|
List of publications
|
||||||
Name | Owner | All tables | Inserts | Updates | Deletes
|
Name | Owner | All tables | Inserts | Updates | Deletes | Truncates
|
||||||
--------------------+--------------------------+------------+---------+---------+---------
|
--------------------+--------------------------+------------+---------+---------+---------+-----------
|
||||||
testpib_ins_trunct | regress_publication_user | f | t | f | f
|
testpib_ins_trunct | regress_publication_user | f | t | f | f | f
|
||||||
testpub_default | regress_publication_user | f | t | t | t
|
testpub_default | regress_publication_user | f | t | t | t | f
|
||||||
(2 rows)
|
(2 rows)
|
||||||
|
|
||||||
--- adding tables
|
--- adding tables
|
||||||
@ -77,9 +77,9 @@ Publications:
|
|||||||
|
|
||||||
\dRp+ testpub_foralltables
|
\dRp+ testpub_foralltables
|
||||||
Publication testpub_foralltables
|
Publication testpub_foralltables
|
||||||
Owner | All tables | Inserts | Updates | Deletes
|
Owner | All tables | Inserts | Updates | Deletes | Truncates
|
||||||
--------------------------+------------+---------+---------+---------
|
--------------------------+------------+---------+---------+---------+-----------
|
||||||
regress_publication_user | t | t | t | f
|
regress_publication_user | t | t | t | f | f
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
DROP TABLE testpub_tbl2;
|
DROP TABLE testpub_tbl2;
|
||||||
@ -90,18 +90,18 @@ CREATE PUBLICATION testpub3 FOR TABLE testpub_tbl3;
|
|||||||
CREATE PUBLICATION testpub4 FOR TABLE ONLY testpub_tbl3;
|
CREATE PUBLICATION testpub4 FOR TABLE ONLY testpub_tbl3;
|
||||||
\dRp+ testpub3
|
\dRp+ testpub3
|
||||||
Publication testpub3
|
Publication testpub3
|
||||||
Owner | All tables | Inserts | Updates | Deletes
|
Owner | All tables | Inserts | Updates | Deletes | Truncates
|
||||||
--------------------------+------------+---------+---------+---------
|
--------------------------+------------+---------+---------+---------+-----------
|
||||||
regress_publication_user | f | t | t | t
|
regress_publication_user | f | t | t | t | t
|
||||||
Tables:
|
Tables:
|
||||||
"public.testpub_tbl3"
|
"public.testpub_tbl3"
|
||||||
"public.testpub_tbl3a"
|
"public.testpub_tbl3a"
|
||||||
|
|
||||||
\dRp+ testpub4
|
\dRp+ testpub4
|
||||||
Publication testpub4
|
Publication testpub4
|
||||||
Owner | All tables | Inserts | Updates | Deletes
|
Owner | All tables | Inserts | Updates | Deletes | Truncates
|
||||||
--------------------------+------------+---------+---------+---------
|
--------------------------+------------+---------+---------+---------+-----------
|
||||||
regress_publication_user | f | t | t | t
|
regress_publication_user | f | t | t | t | t
|
||||||
Tables:
|
Tables:
|
||||||
"public.testpub_tbl3"
|
"public.testpub_tbl3"
|
||||||
|
|
||||||
@ -120,9 +120,9 @@ CREATE PUBLICATION testpub_fortbl FOR TABLE testpub_tbl1;
|
|||||||
ERROR: publication "testpub_fortbl" already exists
|
ERROR: publication "testpub_fortbl" already exists
|
||||||
\dRp+ testpub_fortbl
|
\dRp+ testpub_fortbl
|
||||||
Publication testpub_fortbl
|
Publication testpub_fortbl
|
||||||
Owner | All tables | Inserts | Updates | Deletes
|
Owner | All tables | Inserts | Updates | Deletes | Truncates
|
||||||
--------------------------+------------+---------+---------+---------
|
--------------------------+------------+---------+---------+---------+-----------
|
||||||
regress_publication_user | f | t | t | t
|
regress_publication_user | f | t | t | t | t
|
||||||
Tables:
|
Tables:
|
||||||
"pub_test.testpub_nopk"
|
"pub_test.testpub_nopk"
|
||||||
"public.testpub_tbl1"
|
"public.testpub_tbl1"
|
||||||
@ -166,9 +166,9 @@ Publications:
|
|||||||
|
|
||||||
\dRp+ testpub_default
|
\dRp+ testpub_default
|
||||||
Publication testpub_default
|
Publication testpub_default
|
||||||
Owner | All tables | Inserts | Updates | Deletes
|
Owner | All tables | Inserts | Updates | Deletes | Truncates
|
||||||
--------------------------+------------+---------+---------+---------
|
--------------------------+------------+---------+---------+---------+-----------
|
||||||
regress_publication_user | f | t | t | t
|
regress_publication_user | f | t | t | t | f
|
||||||
Tables:
|
Tables:
|
||||||
"pub_test.testpub_nopk"
|
"pub_test.testpub_nopk"
|
||||||
"public.testpub_tbl1"
|
"public.testpub_tbl1"
|
||||||
@ -211,9 +211,9 @@ DROP VIEW testpub_view;
|
|||||||
DROP TABLE testpub_tbl1;
|
DROP TABLE testpub_tbl1;
|
||||||
\dRp+ testpub_default
|
\dRp+ testpub_default
|
||||||
Publication testpub_default
|
Publication testpub_default
|
||||||
Owner | All tables | Inserts | Updates | Deletes
|
Owner | All tables | Inserts | Updates | Deletes | Truncates
|
||||||
--------------------------+------------+---------+---------+---------
|
--------------------------+------------+---------+---------+---------+-----------
|
||||||
regress_publication_user | f | t | t | t
|
regress_publication_user | f | t | t | t | f
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
-- fail - must be owner of publication
|
-- fail - must be owner of publication
|
||||||
@ -224,9 +224,9 @@ RESET ROLE;
|
|||||||
ALTER PUBLICATION testpub_default RENAME TO testpub_foo;
|
ALTER PUBLICATION testpub_default RENAME TO testpub_foo;
|
||||||
\dRp testpub_foo
|
\dRp testpub_foo
|
||||||
List of publications
|
List of publications
|
||||||
Name | Owner | All tables | Inserts | Updates | Deletes
|
Name | Owner | All tables | Inserts | Updates | Deletes | Truncates
|
||||||
-------------+--------------------------+------------+---------+---------+---------
|
-------------+--------------------------+------------+---------+---------+---------+-----------
|
||||||
testpub_foo | regress_publication_user | f | t | t | t
|
testpub_foo | regress_publication_user | f | t | t | t | f
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
-- rename back to keep the rest simple
|
-- rename back to keep the rest simple
|
||||||
@ -234,9 +234,9 @@ ALTER PUBLICATION testpub_foo RENAME TO testpub_default;
|
|||||||
ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
|
ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
|
||||||
\dRp testpub_default
|
\dRp testpub_default
|
||||||
List of publications
|
List of publications
|
||||||
Name | Owner | All tables | Inserts | Updates | Deletes
|
Name | Owner | All tables | Inserts | Updates | Deletes | Truncates
|
||||||
-----------------+---------------------------+------------+---------+---------+---------
|
-----------------+---------------------------+------------+---------+---------+---------+-----------
|
||||||
testpub_default | regress_publication_user2 | f | t | t | t
|
testpub_default | regress_publication_user2 | f | t | t | t | f
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
DROP PUBLICATION testpub_default;
|
DROP PUBLICATION testpub_default;
|
||||||
|
161
src/test/subscription/t/010_truncate.pl
Normal file
161
src/test/subscription/t/010_truncate.pl
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
# Test TRUNCATE
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use PostgresNode;
|
||||||
|
use TestLib;
|
||||||
|
use Test::More tests => 9;
|
||||||
|
|
||||||
|
# setup
|
||||||
|
|
||||||
|
my $node_publisher = get_new_node('publisher');
|
||||||
|
$node_publisher->init(allows_streaming => 'logical');
|
||||||
|
$node_publisher->start;
|
||||||
|
|
||||||
|
my $node_subscriber = get_new_node('subscriber');
|
||||||
|
$node_subscriber->init(allows_streaming => 'logical');
|
||||||
|
$node_subscriber->start;
|
||||||
|
|
||||||
|
my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
|
||||||
|
|
||||||
|
$node_publisher->safe_psql('postgres',
|
||||||
|
"CREATE TABLE tab1 (a int PRIMARY KEY)");
|
||||||
|
|
||||||
|
$node_subscriber->safe_psql('postgres',
|
||||||
|
"CREATE TABLE tab1 (a int PRIMARY KEY)");
|
||||||
|
|
||||||
|
$node_publisher->safe_psql('postgres',
|
||||||
|
"CREATE TABLE tab2 (a int PRIMARY KEY)");
|
||||||
|
|
||||||
|
$node_subscriber->safe_psql('postgres',
|
||||||
|
"CREATE TABLE tab2 (a int PRIMARY KEY)");
|
||||||
|
|
||||||
|
$node_publisher->safe_psql('postgres',
|
||||||
|
"CREATE TABLE tab3 (a int PRIMARY KEY)");
|
||||||
|
|
||||||
|
$node_subscriber->safe_psql('postgres',
|
||||||
|
"CREATE TABLE tab3 (a int PRIMARY KEY)");
|
||||||
|
|
||||||
|
$node_publisher->safe_psql('postgres',
|
||||||
|
"CREATE TABLE tab4 (x int PRIMARY KEY, y int REFERENCES tab3)");
|
||||||
|
|
||||||
|
$node_subscriber->safe_psql('postgres',
|
||||||
|
"CREATE TABLE tab4 (x int PRIMARY KEY, y int REFERENCES tab3)");
|
||||||
|
|
||||||
|
$node_subscriber->safe_psql('postgres',
|
||||||
|
"CREATE SEQUENCE seq1 OWNED BY tab1.a"
|
||||||
|
);
|
||||||
|
$node_subscriber->safe_psql('postgres',
|
||||||
|
"ALTER SEQUENCE seq1 START 101"
|
||||||
|
);
|
||||||
|
|
||||||
|
$node_publisher->safe_psql('postgres',
|
||||||
|
"CREATE PUBLICATION pub1 FOR TABLE tab1");
|
||||||
|
$node_publisher->safe_psql('postgres',
|
||||||
|
"CREATE PUBLICATION pub2 FOR TABLE tab2 WITH (publish = insert)");
|
||||||
|
$node_publisher->safe_psql('postgres',
|
||||||
|
"CREATE PUBLICATION pub3 FOR TABLE tab3, tab4");
|
||||||
|
$node_subscriber->safe_psql('postgres',
|
||||||
|
"CREATE SUBSCRIPTION sub1 CONNECTION '$publisher_connstr application_name=sub1' PUBLICATION pub1");
|
||||||
|
$node_subscriber->safe_psql('postgres',
|
||||||
|
"CREATE SUBSCRIPTION sub2 CONNECTION '$publisher_connstr application_name=sub2' PUBLICATION pub2");
|
||||||
|
$node_subscriber->safe_psql('postgres',
|
||||||
|
"CREATE SUBSCRIPTION sub3 CONNECTION '$publisher_connstr application_name=sub3' PUBLICATION pub3");
|
||||||
|
|
||||||
|
$node_publisher->wait_for_catchup('sub1');
|
||||||
|
|
||||||
|
# insert data to truncate
|
||||||
|
|
||||||
|
$node_subscriber->safe_psql('postgres', "INSERT INTO tab1 VALUES (1), (2), (3)");
|
||||||
|
|
||||||
|
$node_publisher->wait_for_catchup('sub1');
|
||||||
|
|
||||||
|
# truncate and check
|
||||||
|
|
||||||
|
$node_publisher->safe_psql('postgres', "TRUNCATE tab1");
|
||||||
|
|
||||||
|
$node_publisher->wait_for_catchup('sub1');
|
||||||
|
|
||||||
|
my $result = $node_subscriber->safe_psql('postgres',
|
||||||
|
"SELECT count(*), min(a), max(a) FROM tab1");
|
||||||
|
is($result, qq(0||),
|
||||||
|
'truncate replicated');
|
||||||
|
|
||||||
|
$result = $node_subscriber->safe_psql('postgres',
|
||||||
|
"SELECT nextval('seq1')");
|
||||||
|
is($result, qq(1),
|
||||||
|
'sequence not restarted');
|
||||||
|
|
||||||
|
# truncate with restart identity
|
||||||
|
|
||||||
|
$node_publisher->safe_psql('postgres', "TRUNCATE tab1 RESTART IDENTITY");
|
||||||
|
|
||||||
|
$node_publisher->wait_for_catchup('sub1');
|
||||||
|
|
||||||
|
$result = $node_subscriber->safe_psql('postgres',
|
||||||
|
"SELECT nextval('seq1')");
|
||||||
|
is($result, qq(101),
|
||||||
|
'truncate restarted identities');
|
||||||
|
|
||||||
|
# test publication that does not replicate truncate
|
||||||
|
|
||||||
|
$node_subscriber->safe_psql('postgres', "INSERT INTO tab2 VALUES (1), (2), (3)");
|
||||||
|
|
||||||
|
$node_publisher->safe_psql('postgres', "TRUNCATE tab2");
|
||||||
|
|
||||||
|
$node_publisher->wait_for_catchup('sub2');
|
||||||
|
|
||||||
|
$result = $node_subscriber->safe_psql('postgres',
|
||||||
|
"SELECT count(*), min(a), max(a) FROM tab2");
|
||||||
|
is($result, qq(3|1|3),
|
||||||
|
'truncate not replicated');
|
||||||
|
|
||||||
|
$node_publisher->safe_psql('postgres',
|
||||||
|
"ALTER PUBLICATION pub2 SET (publish = 'insert, truncate')");
|
||||||
|
|
||||||
|
$node_publisher->safe_psql('postgres', "TRUNCATE tab2");
|
||||||
|
|
||||||
|
$node_publisher->wait_for_catchup('sub2');
|
||||||
|
|
||||||
|
$result = $node_subscriber->safe_psql('postgres',
|
||||||
|
"SELECT count(*), min(a), max(a) FROM tab2");
|
||||||
|
is($result, qq(0||),
|
||||||
|
'truncate replicated after publication change');
|
||||||
|
|
||||||
|
# test multiple tables connected by foreign keys
|
||||||
|
|
||||||
|
$node_subscriber->safe_psql('postgres', "INSERT INTO tab3 VALUES (1), (2), (3)");
|
||||||
|
$node_subscriber->safe_psql('postgres', "INSERT INTO tab4 VALUES (11, 1), (111, 1), (22, 2)");
|
||||||
|
|
||||||
|
$node_publisher->safe_psql('postgres', "TRUNCATE tab3, tab4");
|
||||||
|
|
||||||
|
$node_publisher->wait_for_catchup('sub3');
|
||||||
|
|
||||||
|
$result = $node_subscriber->safe_psql('postgres',
|
||||||
|
"SELECT count(*), min(a), max(a) FROM tab3");
|
||||||
|
is($result, qq(0||),
|
||||||
|
'truncate of multiple tables replicated');
|
||||||
|
$result = $node_subscriber->safe_psql('postgres',
|
||||||
|
"SELECT count(*), min(x), max(x) FROM tab4");
|
||||||
|
is($result, qq(0||),
|
||||||
|
'truncate of multiple tables replicated');
|
||||||
|
|
||||||
|
# test truncate of multiple tables, some of which are not published
|
||||||
|
|
||||||
|
$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION sub2");
|
||||||
|
$node_publisher->safe_psql('postgres', "DROP PUBLICATION pub2");
|
||||||
|
|
||||||
|
$node_subscriber->safe_psql('postgres', "INSERT INTO tab1 VALUES (1), (2), (3)");
|
||||||
|
$node_subscriber->safe_psql('postgres', "INSERT INTO tab2 VALUES (1), (2), (3)");
|
||||||
|
|
||||||
|
$node_publisher->safe_psql('postgres', "TRUNCATE tab1, tab2");
|
||||||
|
|
||||||
|
$node_publisher->wait_for_catchup('sub1');
|
||||||
|
|
||||||
|
$result = $node_subscriber->safe_psql('postgres',
|
||||||
|
"SELECT count(*), min(a), max(a) FROM tab1");
|
||||||
|
is($result, qq(0||),
|
||||||
|
'truncate of multiple tables some not published');
|
||||||
|
$result = $node_subscriber->safe_psql('postgres',
|
||||||
|
"SELECT count(*), min(a), max(a) FROM tab2");
|
||||||
|
is($result, qq(3|1|3),
|
||||||
|
'truncate of multiple tables some not published');
|
Loading…
x
Reference in New Issue
Block a user