Commit 46482432 authored by Amit Kapila's avatar Amit Kapila

Add support for streaming to built-in logical replication.

To add support for streaming of in-progress transactions into the
built-in logical replication, we need to do three things:

* Extend the logical replication protocol, so identify in-progress
transactions, and allow adding additional bits of information (e.g.
XID of subtransactions).

* Modify the output plugin (pgoutput) to implement the new stream
API callbacks, by leveraging the extended replication protocol.

* Modify the replication apply worker, to properly handle streamed
in-progress transaction by spilling the data to disk and then
replaying them on commit.

We however must explicitly disable streaming replication during
replication slot creation, even if the plugin supports it. We
don't need to replicate the changes accumulated during this phase,
and moreover we don't have a replication connection open so we
don't have where to send the data anyway.

Author: Tomas Vondra, Dilip Kumar and Amit Kapila
Reviewed-by: Amit Kapila, Kuntal Ghosh and Ajin Cherian
Tested-by: Neha Sharma, Mahendra Singh Thalor and Ajin Cherian
Discussion: https://postgr.es/m/688b0b7f-2f6c-d827-c27b-216a8e3ea700@2ndquadrant.com
parent 66f16306
...@@ -1509,6 +1509,22 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser ...@@ -1509,6 +1509,22 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser
<entry><literal>WALWrite</literal></entry> <entry><literal>WALWrite</literal></entry>
<entry>Waiting for a write to a WAL file.</entry> <entry>Waiting for a write to a WAL file.</entry>
</row> </row>
<row>
<entry><literal>LogicalChangesRead</literal></entry>
<entry>Waiting for a read from a logical changes file.</entry>
</row>
<row>
<entry><literal>LogicalChangesWrite</literal></entry>
<entry>Waiting for a write to a logical changes file.</entry>
</row>
<row>
<entry><literal>LogicalSubxactRead</literal></entry>
<entry>Waiting for a read from a logical subxact file.</entry>
</row>
<row>
<entry><literal>LogicalSubxactWrite</literal></entry>
<entry>Waiting for a write to a logical subxact file.</entry>
</row>
</tbody> </tbody>
</tgroup> </tgroup>
</table> </table>
......
...@@ -165,8 +165,9 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO < ...@@ -165,8 +165,9 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
<xref linkend="sql-createsubscription"/>. See there for more <xref linkend="sql-createsubscription"/>. See there for more
information. The parameters that can be altered information. The parameters that can be altered
are <literal>slot_name</literal>, are <literal>slot_name</literal>,
<literal>synchronous_commit</literal>, and <literal>synchronous_commit</literal>,
<literal>binary</literal>. <literal>binary</literal>, and
<literal>streaming</literal>.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
......
...@@ -228,6 +228,17 @@ CREATE SUBSCRIPTION <replaceable class="parameter">subscription_name</replaceabl ...@@ -228,6 +228,17 @@ CREATE SUBSCRIPTION <replaceable class="parameter">subscription_name</replaceabl
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><literal>streaming</literal> (<type>boolean</type>)</term>
<listitem>
<para>
Specifies whether streaming of in-progress transactions should
be enabled for this subscription. By default, all transactions
are fully decoded on the publisher, and only then sent to the
subscriber as a whole.
</para>
</listitem>
</varlistentry>
</variablelist></para> </variablelist></para>
</listitem> </listitem>
</varlistentry> </varlistentry>
......
...@@ -66,6 +66,7 @@ GetSubscription(Oid subid, bool missing_ok) ...@@ -66,6 +66,7 @@ GetSubscription(Oid subid, bool missing_ok)
sub->owner = subform->subowner; sub->owner = subform->subowner;
sub->enabled = subform->subenabled; sub->enabled = subform->subenabled;
sub->binary = subform->subbinary; sub->binary = subform->subbinary;
sub->stream = subform->substream;
/* Get conninfo */ /* Get conninfo */
datum = SysCacheGetAttr(SUBSCRIPTIONOID, datum = SysCacheGetAttr(SUBSCRIPTIONOID,
......
...@@ -1128,7 +1128,7 @@ REVOKE ALL ON pg_replication_origin_status FROM public; ...@@ -1128,7 +1128,7 @@ REVOKE ALL ON pg_replication_origin_status FROM public;
-- All columns of pg_subscription except subconninfo are readable. -- All columns of pg_subscription except subconninfo are readable.
REVOKE ALL ON pg_subscription FROM public; REVOKE ALL ON pg_subscription FROM public;
GRANT SELECT (subdbid, subname, subowner, subenabled, subbinary, subslotname, subpublications) GRANT SELECT (subdbid, subname, subowner, subenabled, subbinary, substream, subslotname, subpublications)
ON pg_subscription TO public; ON pg_subscription TO public;
......
...@@ -63,7 +63,8 @@ parse_subscription_options(List *options, ...@@ -63,7 +63,8 @@ parse_subscription_options(List *options,
bool *copy_data, bool *copy_data,
char **synchronous_commit, char **synchronous_commit,
bool *refresh, bool *refresh,
bool *binary_given, bool *binary) bool *binary_given, bool *binary,
bool *streaming_given, bool *streaming)
{ {
ListCell *lc; ListCell *lc;
bool connect_given = false; bool connect_given = false;
...@@ -99,6 +100,11 @@ parse_subscription_options(List *options, ...@@ -99,6 +100,11 @@ parse_subscription_options(List *options,
*binary_given = false; *binary_given = false;
*binary = false; *binary = false;
} }
if (streaming)
{
*streaming_given = false;
*streaming = false;
}
/* Parse options */ /* Parse options */
foreach(lc, options) foreach(lc, options)
...@@ -194,6 +200,16 @@ parse_subscription_options(List *options, ...@@ -194,6 +200,16 @@ parse_subscription_options(List *options,
*binary_given = true; *binary_given = true;
*binary = defGetBoolean(defel); *binary = defGetBoolean(defel);
} }
else if (strcmp(defel->defname, "streaming") == 0 && streaming)
{
if (*streaming_given)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
*streaming_given = true;
*streaming = defGetBoolean(defel);
}
else else
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR), (errcode(ERRCODE_SYNTAX_ERROR),
...@@ -337,6 +353,8 @@ CreateSubscription(CreateSubscriptionStmt *stmt, bool isTopLevel) ...@@ -337,6 +353,8 @@ CreateSubscription(CreateSubscriptionStmt *stmt, bool isTopLevel)
bool enabled_given; bool enabled_given;
bool enabled; bool enabled;
bool copy_data; bool copy_data;
bool streaming;
bool streaming_given;
char *synchronous_commit; char *synchronous_commit;
char *conninfo; char *conninfo;
char *slotname; char *slotname;
...@@ -360,7 +378,8 @@ CreateSubscription(CreateSubscriptionStmt *stmt, bool isTopLevel) ...@@ -360,7 +378,8 @@ CreateSubscription(CreateSubscriptionStmt *stmt, bool isTopLevel)
&copy_data, &copy_data,
&synchronous_commit, &synchronous_commit,
NULL, /* no "refresh" */ NULL, /* no "refresh" */
&binary_given, &binary); &binary_given, &binary,
&streaming_given, &streaming);
/* /*
* Since creating a replication slot is not transactional, rolling back * Since creating a replication slot is not transactional, rolling back
...@@ -427,6 +446,7 @@ CreateSubscription(CreateSubscriptionStmt *stmt, bool isTopLevel) ...@@ -427,6 +446,7 @@ CreateSubscription(CreateSubscriptionStmt *stmt, bool isTopLevel)
values[Anum_pg_subscription_subowner - 1] = ObjectIdGetDatum(owner); values[Anum_pg_subscription_subowner - 1] = ObjectIdGetDatum(owner);
values[Anum_pg_subscription_subenabled - 1] = BoolGetDatum(enabled); values[Anum_pg_subscription_subenabled - 1] = BoolGetDatum(enabled);
values[Anum_pg_subscription_subbinary - 1] = BoolGetDatum(binary); values[Anum_pg_subscription_subbinary - 1] = BoolGetDatum(binary);
values[Anum_pg_subscription_substream - 1] = BoolGetDatum(streaming);
values[Anum_pg_subscription_subconninfo - 1] = values[Anum_pg_subscription_subconninfo - 1] =
CStringGetTextDatum(conninfo); CStringGetTextDatum(conninfo);
if (slotname) if (slotname)
...@@ -698,6 +718,8 @@ AlterSubscription(AlterSubscriptionStmt *stmt) ...@@ -698,6 +718,8 @@ AlterSubscription(AlterSubscriptionStmt *stmt)
char *synchronous_commit; char *synchronous_commit;
bool binary_given; bool binary_given;
bool binary; bool binary;
bool streaming_given;
bool streaming;
parse_subscription_options(stmt->options, parse_subscription_options(stmt->options,
NULL, /* no "connect" */ NULL, /* no "connect" */
...@@ -707,7 +729,8 @@ AlterSubscription(AlterSubscriptionStmt *stmt) ...@@ -707,7 +729,8 @@ AlterSubscription(AlterSubscriptionStmt *stmt)
NULL, /* no "copy_data" */ NULL, /* no "copy_data" */
&synchronous_commit, &synchronous_commit,
NULL, /* no "refresh" */ NULL, /* no "refresh" */
&binary_given, &binary); &binary_given, &binary,
&streaming_given, &streaming);
if (slotname_given) if (slotname_given)
{ {
...@@ -739,6 +762,13 @@ AlterSubscription(AlterSubscriptionStmt *stmt) ...@@ -739,6 +762,13 @@ AlterSubscription(AlterSubscriptionStmt *stmt)
replaces[Anum_pg_subscription_subbinary - 1] = true; replaces[Anum_pg_subscription_subbinary - 1] = true;
} }
if (streaming_given)
{
values[Anum_pg_subscription_substream - 1] =
BoolGetDatum(streaming);
replaces[Anum_pg_subscription_substream - 1] = true;
}
update_tuple = true; update_tuple = true;
break; break;
} }
...@@ -756,7 +786,8 @@ AlterSubscription(AlterSubscriptionStmt *stmt) ...@@ -756,7 +786,8 @@ AlterSubscription(AlterSubscriptionStmt *stmt)
NULL, /* no "copy_data" */ NULL, /* no "copy_data" */
NULL, /* no "synchronous_commit" */ NULL, /* no "synchronous_commit" */
NULL, /* no "refresh" */ NULL, /* no "refresh" */
NULL, NULL); /* no "binary" */ NULL, NULL, /* no "binary" */
NULL, NULL); /* no streaming */
Assert(enabled_given); Assert(enabled_given);
if (!sub->slotname && enabled) if (!sub->slotname && enabled)
...@@ -800,8 +831,8 @@ AlterSubscription(AlterSubscriptionStmt *stmt) ...@@ -800,8 +831,8 @@ AlterSubscription(AlterSubscriptionStmt *stmt)
&copy_data, &copy_data,
NULL, /* no "synchronous_commit" */ NULL, /* no "synchronous_commit" */
&refresh, &refresh,
NULL, NULL); /* no "binary" */ NULL, NULL, /* no "binary" */
NULL, NULL); /* no "streaming" */
values[Anum_pg_subscription_subpublications - 1] = values[Anum_pg_subscription_subpublications - 1] =
publicationListToArray(stmt->publication); publicationListToArray(stmt->publication);
replaces[Anum_pg_subscription_subpublications - 1] = true; replaces[Anum_pg_subscription_subpublications - 1] = true;
...@@ -843,7 +874,8 @@ AlterSubscription(AlterSubscriptionStmt *stmt) ...@@ -843,7 +874,8 @@ AlterSubscription(AlterSubscriptionStmt *stmt)
&copy_data, &copy_data,
NULL, /* no "synchronous_commit" */ NULL, /* no "synchronous_commit" */
NULL, /* no "refresh" */ NULL, /* no "refresh" */
NULL, NULL); /* no "binary" */ NULL, NULL, /* no "binary" */
NULL, NULL); /* no "streaming" */
AlterSubscription_refresh(sub, copy_data); AlterSubscription_refresh(sub, copy_data);
......
...@@ -4141,6 +4141,18 @@ pgstat_get_wait_io(WaitEventIO w) ...@@ -4141,6 +4141,18 @@ pgstat_get_wait_io(WaitEventIO w)
case WAIT_EVENT_WAL_WRITE: case WAIT_EVENT_WAL_WRITE:
event_name = "WALWrite"; event_name = "WALWrite";
break; break;
case WAIT_EVENT_LOGICAL_CHANGES_READ:
event_name = "LogicalChangesRead";
break;
case WAIT_EVENT_LOGICAL_CHANGES_WRITE:
event_name = "LogicalChangesWrite";
break;
case WAIT_EVENT_LOGICAL_SUBXACT_READ:
event_name = "LogicalSubxactRead";
break;
case WAIT_EVENT_LOGICAL_SUBXACT_WRITE:
event_name = "LogicalSubxactWrite";
break;
/* no default case, so that compiler will warn */ /* no default case, so that compiler will warn */
} }
......
...@@ -425,6 +425,10 @@ libpqrcv_startstreaming(WalReceiverConn *conn, ...@@ -425,6 +425,10 @@ libpqrcv_startstreaming(WalReceiverConn *conn,
appendStringInfo(&cmd, "proto_version '%u'", appendStringInfo(&cmd, "proto_version '%u'",
options->proto.logical.proto_version); options->proto.logical.proto_version);
if (options->proto.logical.streaming &&
PQserverVersion(conn->streamConn) >= 140000)
appendStringInfo(&cmd, ", streaming 'on'");
pubnames = options->proto.logical.publication_names; pubnames = options->proto.logical.publication_names;
pubnames_str = stringlist_to_identifierstr(conn->streamConn, pubnames); pubnames_str = stringlist_to_identifierstr(conn->streamConn, pubnames);
if (!pubnames_str) if (!pubnames_str)
......
...@@ -138,10 +138,15 @@ logicalrep_read_origin(StringInfo in, XLogRecPtr *origin_lsn) ...@@ -138,10 +138,15 @@ logicalrep_read_origin(StringInfo in, XLogRecPtr *origin_lsn)
* Write INSERT to the output stream. * Write INSERT to the output stream.
*/ */
void void
logicalrep_write_insert(StringInfo out, Relation rel, HeapTuple newtuple, bool binary) logicalrep_write_insert(StringInfo out, TransactionId xid, Relation rel,
HeapTuple newtuple, bool binary)
{ {
pq_sendbyte(out, 'I'); /* action INSERT */ pq_sendbyte(out, 'I'); /* action INSERT */
/* transaction ID (if not valid, we're not streaming) */
if (TransactionIdIsValid(xid))
pq_sendint32(out, xid);
/* use Oid as relation identifier */ /* use Oid as relation identifier */
pq_sendint32(out, RelationGetRelid(rel)); pq_sendint32(out, RelationGetRelid(rel));
...@@ -177,8 +182,8 @@ logicalrep_read_insert(StringInfo in, LogicalRepTupleData *newtup) ...@@ -177,8 +182,8 @@ logicalrep_read_insert(StringInfo in, LogicalRepTupleData *newtup)
* Write UPDATE to the output stream. * Write UPDATE to the output stream.
*/ */
void void
logicalrep_write_update(StringInfo out, Relation rel, HeapTuple oldtuple, logicalrep_write_update(StringInfo out, TransactionId xid, Relation rel,
HeapTuple newtuple, bool binary) HeapTuple oldtuple, HeapTuple newtuple, bool binary)
{ {
pq_sendbyte(out, 'U'); /* action UPDATE */ pq_sendbyte(out, 'U'); /* action UPDATE */
...@@ -186,6 +191,10 @@ logicalrep_write_update(StringInfo out, Relation rel, HeapTuple oldtuple, ...@@ -186,6 +191,10 @@ logicalrep_write_update(StringInfo out, Relation rel, HeapTuple oldtuple,
rel->rd_rel->relreplident == REPLICA_IDENTITY_FULL || rel->rd_rel->relreplident == REPLICA_IDENTITY_FULL ||
rel->rd_rel->relreplident == REPLICA_IDENTITY_INDEX); rel->rd_rel->relreplident == REPLICA_IDENTITY_INDEX);
/* transaction ID (if not valid, we're not streaming) */
if (TransactionIdIsValid(xid))
pq_sendint32(out, xid);
/* use Oid as relation identifier */ /* use Oid as relation identifier */
pq_sendint32(out, RelationGetRelid(rel)); pq_sendint32(out, RelationGetRelid(rel));
...@@ -247,7 +256,8 @@ logicalrep_read_update(StringInfo in, bool *has_oldtuple, ...@@ -247,7 +256,8 @@ logicalrep_read_update(StringInfo in, bool *has_oldtuple,
* Write DELETE to the output stream. * Write DELETE to the output stream.
*/ */
void void
logicalrep_write_delete(StringInfo out, Relation rel, HeapTuple oldtuple, bool binary) logicalrep_write_delete(StringInfo out, TransactionId xid, Relation rel,
HeapTuple oldtuple, bool binary)
{ {
Assert(rel->rd_rel->relreplident == REPLICA_IDENTITY_DEFAULT || Assert(rel->rd_rel->relreplident == REPLICA_IDENTITY_DEFAULT ||
rel->rd_rel->relreplident == REPLICA_IDENTITY_FULL || rel->rd_rel->relreplident == REPLICA_IDENTITY_FULL ||
...@@ -255,6 +265,10 @@ logicalrep_write_delete(StringInfo out, Relation rel, HeapTuple oldtuple, bool b ...@@ -255,6 +265,10 @@ logicalrep_write_delete(StringInfo out, Relation rel, HeapTuple oldtuple, bool b
pq_sendbyte(out, 'D'); /* action DELETE */ pq_sendbyte(out, 'D'); /* action DELETE */
/* transaction ID (if not valid, we're not streaming) */
if (TransactionIdIsValid(xid))
pq_sendint32(out, xid);
/* use Oid as relation identifier */ /* use Oid as relation identifier */
pq_sendint32(out, RelationGetRelid(rel)); pq_sendint32(out, RelationGetRelid(rel));
...@@ -295,6 +309,7 @@ logicalrep_read_delete(StringInfo in, LogicalRepTupleData *oldtup) ...@@ -295,6 +309,7 @@ logicalrep_read_delete(StringInfo in, LogicalRepTupleData *oldtup)
*/ */
void void
logicalrep_write_truncate(StringInfo out, logicalrep_write_truncate(StringInfo out,
TransactionId xid,
int nrelids, int nrelids,
Oid relids[], Oid relids[],
bool cascade, bool restart_seqs) bool cascade, bool restart_seqs)
...@@ -304,6 +319,10 @@ logicalrep_write_truncate(StringInfo out, ...@@ -304,6 +319,10 @@ logicalrep_write_truncate(StringInfo out,
pq_sendbyte(out, 'T'); /* action TRUNCATE */ pq_sendbyte(out, 'T'); /* action TRUNCATE */
/* transaction ID (if not valid, we're not streaming) */
if (TransactionIdIsValid(xid))
pq_sendint32(out, xid);
pq_sendint32(out, nrelids); pq_sendint32(out, nrelids);
/* encode and send truncate flags */ /* encode and send truncate flags */
...@@ -346,12 +365,16 @@ logicalrep_read_truncate(StringInfo in, ...@@ -346,12 +365,16 @@ logicalrep_read_truncate(StringInfo in,
* Write relation description to the output stream. * Write relation description to the output stream.
*/ */
void void
logicalrep_write_rel(StringInfo out, Relation rel) logicalrep_write_rel(StringInfo out, TransactionId xid, Relation rel)
{ {
char *relname; char *relname;
pq_sendbyte(out, 'R'); /* sending RELATION */ pq_sendbyte(out, 'R'); /* sending RELATION */
/* transaction ID (if not valid, we're not streaming) */
if (TransactionIdIsValid(xid))
pq_sendint32(out, xid);
/* use Oid as relation identifier */ /* use Oid as relation identifier */
pq_sendint32(out, RelationGetRelid(rel)); pq_sendint32(out, RelationGetRelid(rel));
...@@ -396,7 +419,7 @@ logicalrep_read_rel(StringInfo in) ...@@ -396,7 +419,7 @@ logicalrep_read_rel(StringInfo in)
* This function will always write base type info. * This function will always write base type info.
*/ */
void void
logicalrep_write_typ(StringInfo out, Oid typoid) logicalrep_write_typ(StringInfo out, TransactionId xid, Oid typoid)
{ {
Oid basetypoid = getBaseType(typoid); Oid basetypoid = getBaseType(typoid);
HeapTuple tup; HeapTuple tup;
...@@ -404,6 +427,10 @@ logicalrep_write_typ(StringInfo out, Oid typoid) ...@@ -404,6 +427,10 @@ logicalrep_write_typ(StringInfo out, Oid typoid)
pq_sendbyte(out, 'Y'); /* sending TYPE */ pq_sendbyte(out, 'Y'); /* sending TYPE */
/* transaction ID (if not valid, we're not streaming) */
if (TransactionIdIsValid(xid))
pq_sendint32(out, xid);
tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(basetypoid)); tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(basetypoid));
if (!HeapTupleIsValid(tup)) if (!HeapTupleIsValid(tup))
elog(ERROR, "cache lookup failed for type %u", basetypoid); elog(ERROR, "cache lookup failed for type %u", basetypoid);
...@@ -720,3 +747,126 @@ logicalrep_read_namespace(StringInfo in) ...@@ -720,3 +747,126 @@ logicalrep_read_namespace(StringInfo in)
return nspname; return nspname;
} }
/*
* Write the information for the start stream message to the output stream.
*/
void
logicalrep_write_stream_start(StringInfo out,
TransactionId xid, bool first_segment)
{
pq_sendbyte(out, 'S'); /* action STREAM START */
Assert(TransactionIdIsValid(xid));
/* transaction ID (we're starting to stream, so must be valid) */
pq_sendint32(out, xid);
/* 1 if this is the first streaming segment for this xid */
pq_sendbyte(out, first_segment ? 1 : 0);
}
/*
* Read the information about the start stream message from output stream.
*/
TransactionId
logicalrep_read_stream_start(StringInfo in, bool *first_segment)
{
TransactionId xid;
Assert(first_segment);
xid = pq_getmsgint(in, 4);
*first_segment = (pq_getmsgbyte(in) == 1);
return xid;
}
/*
* Write the stop stream message to the output stream.
*/
void
logicalrep_write_stream_stop(StringInfo out)
{
pq_sendbyte(out, 'E'); /* action STREAM END */
}
/*
* Write STREAM COMMIT to the output stream.
*/
void
logicalrep_write_stream_commit(StringInfo out, ReorderBufferTXN *txn,
XLogRecPtr commit_lsn)
{
uint8 flags = 0;
pq_sendbyte(out, 'c'); /* action STREAM COMMIT */
Assert(TransactionIdIsValid(txn->xid));
/* transaction ID */
pq_sendint32(out, txn->xid);
/* send the flags field (unused for now) */
pq_sendbyte(out, flags);
/* send fields */
pq_sendint64(out, commit_lsn);
pq_sendint64(out, txn->end_lsn);
pq_sendint64(out, txn->commit_time);
}
/*
* Read STREAM COMMIT from the output stream.
*/
TransactionId
logicalrep_read_stream_commit(StringInfo in, LogicalRepCommitData *commit_data)
{
TransactionId xid;
uint8 flags;
xid = pq_getmsgint(in, 4);
/* read flags (unused for now) */
flags = pq_getmsgbyte(in);
if (flags != 0)
elog(ERROR, "unrecognized flags %u in commit message", flags);
/* read fields */
commit_data->commit_lsn = pq_getmsgint64(in);
commit_data->end_lsn = pq_getmsgint64(in);
commit_data->committime = pq_getmsgint64(in);
return xid;
}
/*
* Write STREAM ABORT to the output stream. Note that xid and subxid will be
* same for the top-level transaction abort.
*/
void
logicalrep_write_stream_abort(StringInfo out, TransactionId xid,
TransactionId subxid)
{
pq_sendbyte(out, 'A'); /* action STREAM ABORT */
Assert(TransactionIdIsValid(xid) && TransactionIdIsValid(subxid));
/* transaction ID */
pq_sendint32(out, xid);
pq_sendint32(out, subxid);
}
/*
* Read STREAM ABORT from the output stream.
*/
void
logicalrep_read_stream_abort(StringInfo in, TransactionId *xid,
TransactionId *subxid)
{
Assert(xid && subxid);
*xid = pq_getmsgint(in, 4);
*subxid = pq_getmsgint(in, 4);
}
This diff is collapsed.
This diff is collapsed.
...@@ -4202,6 +4202,7 @@ getSubscriptions(Archive *fout) ...@@ -4202,6 +4202,7 @@ getSubscriptions(Archive *fout)
int i_oid; int i_oid;
int i_subname; int i_subname;
int i_rolname; int i_rolname;
int i_substream;
int i_subconninfo; int i_subconninfo;
int i_subslotname; int i_subslotname;
int i_subsynccommit; int i_subsynccommit;
...@@ -4241,10 +4242,17 @@ getSubscriptions(Archive *fout) ...@@ -4241,10 +4242,17 @@ getSubscriptions(Archive *fout)
if (fout->remoteVersion >= 140000) if (fout->remoteVersion >= 140000)
appendPQExpBuffer(query, appendPQExpBuffer(query,
" s.subbinary\n"); " s.subbinary,\n");
else else
appendPQExpBuffer(query, appendPQExpBuffer(query,
" false AS subbinary\n"); " false AS subbinary,\n");
if (fout->remoteVersion >= 140000)
appendPQExpBuffer(query,
" s.substream\n");
else
appendPQExpBuffer(query,
" false AS substream\n");
appendPQExpBuffer(query, appendPQExpBuffer(query,
"FROM pg_subscription s\n" "FROM pg_subscription s\n"
...@@ -4264,6 +4272,7 @@ getSubscriptions(Archive *fout) ...@@ -4264,6 +4272,7 @@ getSubscriptions(Archive *fout)
i_subsynccommit = PQfnumber(res, "subsynccommit"); i_subsynccommit = PQfnumber(res, "subsynccommit");
i_subpublications = PQfnumber(res, "subpublications"); i_subpublications = PQfnumber(res, "subpublications");
i_subbinary = PQfnumber(res, "subbinary"); i_subbinary = PQfnumber(res, "subbinary");
i_substream = PQfnumber(res, "substream");
subinfo = pg_malloc(ntups * sizeof(SubscriptionInfo)); subinfo = pg_malloc(ntups * sizeof(SubscriptionInfo));
...@@ -4287,6 +4296,8 @@ getSubscriptions(Archive *fout) ...@@ -4287,6 +4296,8 @@ getSubscriptions(Archive *fout)
pg_strdup(PQgetvalue(res, i, i_subpublications)); pg_strdup(PQgetvalue(res, i, i_subpublications));
subinfo[i].subbinary = subinfo[i].subbinary =
pg_strdup(PQgetvalue(res, i, i_subbinary)); pg_strdup(PQgetvalue(res, i, i_subbinary));
subinfo[i].substream =
pg_strdup(PQgetvalue(res, i, i_substream));
if (strlen(subinfo[i].rolname) == 0) if (strlen(subinfo[i].rolname) == 0)
pg_log_warning("owner of subscription \"%s\" appears to be invalid", pg_log_warning("owner of subscription \"%s\" appears to be invalid",
...@@ -4358,6 +4369,9 @@ dumpSubscription(Archive *fout, SubscriptionInfo *subinfo) ...@@ -4358,6 +4369,9 @@ dumpSubscription(Archive *fout, SubscriptionInfo *subinfo)
if (strcmp(subinfo->subbinary, "t") == 0) if (strcmp(subinfo->subbinary, "t") == 0)
appendPQExpBuffer(query, ", binary = true"); appendPQExpBuffer(query, ", binary = true");
if (strcmp(subinfo->substream, "f") != 0)
appendPQExpBuffer(query, ", streaming = on");
if (strcmp(subinfo->subsynccommit, "off") != 0) if (strcmp(subinfo->subsynccommit, "off") != 0)
appendPQExpBuffer(query, ", synchronous_commit = %s", fmtId(subinfo->subsynccommit)); appendPQExpBuffer(query, ", synchronous_commit = %s", fmtId(subinfo->subsynccommit));
......
...@@ -626,6 +626,7 @@ typedef struct _SubscriptionInfo ...@@ -626,6 +626,7 @@ typedef struct _SubscriptionInfo
char *subconninfo; char *subconninfo;
char *subslotname; char *subslotname;
char *subbinary; char *subbinary;
char *substream;
char *subsynccommit; char *subsynccommit;
char *subpublications; char *subpublications;
} SubscriptionInfo; } SubscriptionInfo;
......
...@@ -5979,7 +5979,7 @@ describeSubscriptions(const char *pattern, bool verbose) ...@@ -5979,7 +5979,7 @@ describeSubscriptions(const char *pattern, bool verbose)
PGresult *res; PGresult *res;
printQueryOpt myopt = pset.popt; printQueryOpt myopt = pset.popt;
static const bool translate_columns[] = {false, false, false, false, static const bool translate_columns[] = {false, false, false, false,
false, false, false}; false, false, false, false};
if (pset.sversion < 100000) if (pset.sversion < 100000)
{ {
...@@ -6005,11 +6005,13 @@ describeSubscriptions(const char *pattern, bool verbose) ...@@ -6005,11 +6005,13 @@ describeSubscriptions(const char *pattern, bool verbose)
if (verbose) if (verbose)
{ {
/* Binary mode is only supported in v14 and higher */ /* Binary mode and streaming are only supported in v14 and higher */
if (pset.sversion >= 140000) if (pset.sversion >= 140000)
appendPQExpBuffer(&buf, appendPQExpBuffer(&buf,
", subbinary AS \"%s\"\n", ", subbinary AS \"%s\"\n"
gettext_noop("Binary")); ", substream AS \"%s\"\n",
gettext_noop("Binary"),
gettext_noop("Streaming"));
appendPQExpBuffer(&buf, appendPQExpBuffer(&buf,
", subsynccommit AS \"%s\"\n" ", subsynccommit AS \"%s\"\n"
......
...@@ -53,6 +53,6 @@ ...@@ -53,6 +53,6 @@
*/ */
/* yyyymmddN */ /* yyyymmddN */
#define CATALOG_VERSION_NO 202009021 #define CATALOG_VERSION_NO 202009031
#endif #endif
...@@ -51,6 +51,8 @@ CATALOG(pg_subscription,6100,SubscriptionRelationId) BKI_SHARED_RELATION BKI_ROW ...@@ -51,6 +51,8 @@ CATALOG(pg_subscription,6100,SubscriptionRelationId) BKI_SHARED_RELATION BKI_ROW
bool subbinary; /* True if the subscription wants the bool subbinary; /* True if the subscription wants the
* publisher to send data in binary */ * publisher to send data in binary */
bool substream; /* Stream in-progress transactions. */
#ifdef CATALOG_VARLEN /* variable-length fields start here */ #ifdef CATALOG_VARLEN /* variable-length fields start here */
/* Connection string to the publisher */ /* Connection string to the publisher */
text subconninfo BKI_FORCE_NOT_NULL; text subconninfo BKI_FORCE_NOT_NULL;
...@@ -78,6 +80,7 @@ typedef struct Subscription ...@@ -78,6 +80,7 @@ typedef struct Subscription
bool enabled; /* Indicates if the subscription is enabled */ bool enabled; /* Indicates if the subscription is enabled */
bool binary; /* Indicates if the subscription wants data in bool binary; /* Indicates if the subscription wants data in
* binary format */ * binary format */
bool stream; /* Allow streaming in-progress transactions. */
char *conninfo; /* Connection string to the publisher */ char *conninfo; /* Connection string to the publisher */
char *slotname; /* Name of the replication slot */ char *slotname; /* Name of the replication slot */
char *synccommit; /* Synchronous commit setting for worker */ char *synccommit; /* Synchronous commit setting for worker */
......
...@@ -982,7 +982,11 @@ typedef enum ...@@ -982,7 +982,11 @@ typedef enum
WAIT_EVENT_WAL_READ, WAIT_EVENT_WAL_READ,
WAIT_EVENT_WAL_SYNC, WAIT_EVENT_WAL_SYNC,
WAIT_EVENT_WAL_SYNC_METHOD_ASSIGN, WAIT_EVENT_WAL_SYNC_METHOD_ASSIGN,
WAIT_EVENT_WAL_WRITE WAIT_EVENT_WAL_WRITE,
WAIT_EVENT_LOGICAL_CHANGES_READ,
WAIT_EVENT_LOGICAL_CHANGES_WRITE,
WAIT_EVENT_LOGICAL_SUBXACT_READ,
WAIT_EVENT_LOGICAL_SUBXACT_WRITE
} WaitEventIO; } WaitEventIO;
/* ---------- /* ----------
......
...@@ -23,9 +23,13 @@ ...@@ -23,9 +23,13 @@
* we can support. LOGICALREP_PROTO_MIN_VERSION_NUM is the oldest version we * we can support. LOGICALREP_PROTO_MIN_VERSION_NUM is the oldest version we
* have backwards compatibility for. The client requests protocol version at * have backwards compatibility for. The client requests protocol version at
* connect time. * connect time.
*
* LOGICALREP_PROTO_STREAM_VERSION_NUM is the minimum protocol version with
* support for streaming large transactions.
*/ */
#define LOGICALREP_PROTO_MIN_VERSION_NUM 1 #define LOGICALREP_PROTO_MIN_VERSION_NUM 1
#define LOGICALREP_PROTO_VERSION_NUM 1 #define LOGICALREP_PROTO_STREAM_VERSION_NUM 2
#define LOGICALREP_PROTO_VERSION_NUM 2
/* /*
* This struct stores a tuple received via logical replication. * This struct stores a tuple received via logical replication.
...@@ -98,25 +102,45 @@ extern void logicalrep_read_commit(StringInfo in, ...@@ -98,25 +102,45 @@ extern void logicalrep_read_commit(StringInfo in,
extern void logicalrep_write_origin(StringInfo out, const char *origin, extern void logicalrep_write_origin(StringInfo out, const char *origin,
XLogRecPtr origin_lsn); XLogRecPtr origin_lsn);
extern char *logicalrep_read_origin(StringInfo in, XLogRecPtr *origin_lsn); extern char *logicalrep_read_origin(StringInfo in, XLogRecPtr *origin_lsn);
extern void logicalrep_write_insert(StringInfo out, Relation rel, extern void logicalrep_write_insert(StringInfo out, TransactionId xid,
HeapTuple newtuple, bool binary); Relation rel, HeapTuple newtuple,
bool binary);
extern LogicalRepRelId logicalrep_read_insert(StringInfo in, LogicalRepTupleData *newtup); extern LogicalRepRelId logicalrep_read_insert(StringInfo in, LogicalRepTupleData *newtup);
extern void logicalrep_write_update(StringInfo out, Relation rel, HeapTuple oldtuple, extern void logicalrep_write_update(StringInfo out, TransactionId xid,
Relation rel, HeapTuple oldtuple,
HeapTuple newtuple, bool binary); HeapTuple newtuple, bool binary);
extern LogicalRepRelId logicalrep_read_update(StringInfo in, extern LogicalRepRelId logicalrep_read_update(StringInfo in,
bool *has_oldtuple, LogicalRepTupleData *oldtup, bool *has_oldtuple, LogicalRepTupleData *oldtup,
LogicalRepTupleData *newtup); LogicalRepTupleData *newtup);
extern void logicalrep_write_delete(StringInfo out, Relation rel, extern void logicalrep_write_delete(StringInfo out, TransactionId xid,
HeapTuple oldtuple, bool binary); Relation rel, HeapTuple oldtuple,
bool binary);
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[], extern void logicalrep_write_truncate(StringInfo out, TransactionId xid,
int nrelids, Oid relids[],
bool cascade, bool restart_seqs); bool cascade, bool restart_seqs);
extern List *logicalrep_read_truncate(StringInfo in, extern List *logicalrep_read_truncate(StringInfo in,
bool *cascade, bool *restart_seqs); bool *cascade, bool *restart_seqs);
extern void logicalrep_write_rel(StringInfo out, Relation rel); extern void logicalrep_write_rel(StringInfo out, TransactionId xid,
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, TransactionId xid,
Oid typoid);
extern void logicalrep_read_typ(StringInfo out, LogicalRepTyp *ltyp); extern void logicalrep_read_typ(StringInfo out, LogicalRepTyp *ltyp);
extern void logicalrep_write_stream_start(StringInfo out, TransactionId xid,
bool first_segment);
extern TransactionId logicalrep_read_stream_start(StringInfo in,
bool *first_segment);
extern void logicalrep_write_stream_stop(StringInfo out);
extern TransactionId logicalrep_read_stream_stop(StringInfo in);
extern void logicalrep_write_stream_commit(StringInfo out, ReorderBufferTXN *txn,
XLogRecPtr commit_lsn);
extern TransactionId logicalrep_read_stream_commit(StringInfo out,
LogicalRepCommitData *commit_data);
extern void logicalrep_write_stream_abort(StringInfo out, TransactionId xid,
TransactionId subxid);
extern void logicalrep_read_stream_abort(StringInfo in, TransactionId *xid,
TransactionId *subxid);
#endif /* LOGICAL_PROTO_H */ #endif /* LOGICAL_PROTO_H */
...@@ -178,6 +178,7 @@ typedef struct ...@@ -178,6 +178,7 @@ typedef struct
uint32 proto_version; /* Logical protocol version */ uint32 proto_version; /* Logical protocol version */
List *publication_names; /* String list of publications */ List *publication_names; /* String list of publications */
bool binary; /* Ask publisher to use binary */ bool binary; /* Ask publisher to use binary */
bool streaming; /* Streaming of large transactions */
} logical; } logical;
} proto; } proto;
} WalRcvStreamOptions; } WalRcvStreamOptions;
......
...@@ -77,9 +77,9 @@ ERROR: invalid connection string syntax: missing "=" after "foobar" in connecti ...@@ -77,9 +77,9 @@ ERROR: invalid connection string syntax: missing "=" after "foobar" in connecti
\dRs+ \dRs+
List of subscriptions List of subscriptions
Name | Owner | Enabled | Publication | Binary | Synchronous commit | Conninfo Name | Owner | Enabled | Publication | Binary | Streaming | Synchronous commit | Conninfo
-----------------+---------------------------+---------+-------------+--------+--------------------+----------------------------- -----------------+---------------------------+---------+-------------+--------+-----------+--------------------+-----------------------------
regress_testsub | regress_subscription_user | f | {testpub} | f | off | dbname=regress_doesnotexist regress_testsub | regress_subscription_user | f | {testpub} | f | f | off | dbname=regress_doesnotexist
(1 row) (1 row)
ALTER SUBSCRIPTION regress_testsub SET PUBLICATION testpub2, testpub3 WITH (refresh = false); ALTER SUBSCRIPTION regress_testsub SET PUBLICATION testpub2, testpub3 WITH (refresh = false);
...@@ -92,9 +92,9 @@ ALTER SUBSCRIPTION regress_testsub SET (create_slot = false); ...@@ -92,9 +92,9 @@ ALTER SUBSCRIPTION regress_testsub SET (create_slot = false);
ERROR: unrecognized subscription parameter: "create_slot" ERROR: unrecognized subscription parameter: "create_slot"
\dRs+ \dRs+
List of subscriptions List of subscriptions
Name | Owner | Enabled | Publication | Binary | Synchronous commit | Conninfo Name | Owner | Enabled | Publication | Binary | Streaming | Synchronous commit | Conninfo
-----------------+---------------------------+---------+---------------------+--------+--------------------+------------------------------ -----------------+---------------------------+---------+---------------------+--------+-----------+--------------------+------------------------------
regress_testsub | regress_subscription_user | f | {testpub2,testpub3} | f | off | dbname=regress_doesnotexist2 regress_testsub | regress_subscription_user | f | {testpub2,testpub3} | f | f | off | dbname=regress_doesnotexist2
(1 row) (1 row)
BEGIN; BEGIN;
...@@ -127,9 +127,9 @@ ERROR: invalid value for parameter "synchronous_commit": "foobar" ...@@ -127,9 +127,9 @@ ERROR: invalid value for parameter "synchronous_commit": "foobar"
HINT: Available values: local, remote_write, remote_apply, on, off. HINT: Available values: local, remote_write, remote_apply, on, off.
\dRs+ \dRs+
List of subscriptions List of subscriptions
Name | Owner | Enabled | Publication | Binary | Synchronous commit | Conninfo Name | Owner | Enabled | Publication | Binary | Streaming | Synchronous commit | Conninfo
---------------------+---------------------------+---------+---------------------+--------+--------------------+------------------------------ ---------------------+---------------------------+---------+---------------------+--------+-----------+--------------------+------------------------------
regress_testsub_foo | regress_subscription_user | f | {testpub2,testpub3} | f | local | dbname=regress_doesnotexist2 regress_testsub_foo | regress_subscription_user | f | {testpub2,testpub3} | f | f | local | dbname=regress_doesnotexist2
(1 row) (1 row)
-- rename back to keep the rest simple -- rename back to keep the rest simple
...@@ -163,18 +163,41 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB ...@@ -163,18 +163,41 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
\dRs+ \dRs+
List of subscriptions List of subscriptions
Name | Owner | Enabled | Publication | Binary | Synchronous commit | Conninfo Name | Owner | Enabled | Publication | Binary | Streaming | Synchronous commit | Conninfo
-----------------+---------------------------+---------+-------------+--------+--------------------+----------------------------- -----------------+---------------------------+---------+-------------+--------+-----------+--------------------+-----------------------------
regress_testsub | regress_subscription_user | f | {testpub} | t | off | dbname=regress_doesnotexist regress_testsub | regress_subscription_user | f | {testpub} | t | f | off | dbname=regress_doesnotexist
(1 row) (1 row)
ALTER SUBSCRIPTION regress_testsub SET (binary = false); ALTER SUBSCRIPTION regress_testsub SET (binary = false);
ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE); ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
\dRs+ \dRs+
List of subscriptions List of subscriptions
Name | Owner | Enabled | Publication | Binary | Synchronous commit | Conninfo Name | Owner | Enabled | Publication | Binary | Streaming | Synchronous commit | Conninfo
-----------------+---------------------------+---------+-------------+--------+--------------------+----------------------------- -----------------+---------------------------+---------+-------------+--------+-----------+--------------------+-----------------------------
regress_testsub | regress_subscription_user | f | {testpub} | f | off | dbname=regress_doesnotexist regress_testsub | regress_subscription_user | f | {testpub} | f | f | off | dbname=regress_doesnotexist
(1 row)
DROP SUBSCRIPTION regress_testsub;
-- fail - streaming must be boolean
CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, streaming = foo);
ERROR: streaming requires a Boolean value
-- now it works
CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, streaming = true);
WARNING: tables were not subscribed, you will have to run ALTER SUBSCRIPTION ... REFRESH PUBLICATION to subscribe the tables
\dRs+
List of subscriptions
Name | Owner | Enabled | Publication | Binary | Streaming | Synchronous commit | Conninfo
-----------------+---------------------------+---------+-------------+--------+-----------+--------------------+-----------------------------
regress_testsub | regress_subscription_user | f | {testpub} | f | t | off | dbname=regress_doesnotexist
(1 row)
ALTER SUBSCRIPTION regress_testsub SET (streaming = false);
ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
\dRs+
List of subscriptions
Name | Owner | Enabled | Publication | Binary | Streaming | Synchronous commit | Conninfo
-----------------+---------------------------+---------+-------------+--------+-----------+--------------------+-----------------------------
regress_testsub | regress_subscription_user | f | {testpub} | f | f | off | dbname=regress_doesnotexist
(1 row) (1 row)
DROP SUBSCRIPTION regress_testsub; DROP SUBSCRIPTION regress_testsub;
......
...@@ -132,6 +132,21 @@ ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE); ...@@ -132,6 +132,21 @@ ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
DROP SUBSCRIPTION regress_testsub; DROP SUBSCRIPTION regress_testsub;
-- fail - streaming must be boolean
CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, streaming = foo);
-- now it works
CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, streaming = true);
\dRs+
ALTER SUBSCRIPTION regress_testsub SET (streaming = false);
ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
\dRs+
DROP SUBSCRIPTION regress_testsub;
RESET SESSION AUTHORIZATION; RESET SESSION AUTHORIZATION;
DROP ROLE regress_subscription_user; DROP ROLE regress_subscription_user;
DROP ROLE regress_subscription_user2; DROP ROLE regress_subscription_user2;
......
# Test streaming of simple large transaction
use strict;
use warnings;
use PostgresNode;
use TestLib;
use Test::More tests => 4;
# Create publisher node
my $node_publisher = get_new_node('publisher');
$node_publisher->init(allows_streaming => 'logical');
$node_publisher->append_conf('postgresql.conf', 'logical_decoding_work_mem = 64kB');
$node_publisher->start;
# Create subscriber node
my $node_subscriber = get_new_node('subscriber');
$node_subscriber->init(allows_streaming => 'logical');
$node_subscriber->start;
# Create some preexisting content on publisher
$node_publisher->safe_psql('postgres',
"CREATE TABLE test_tab (a int primary key, b varchar)");
$node_publisher->safe_psql('postgres',
"INSERT INTO test_tab VALUES (1, 'foo'), (2, 'bar')");
# Setup structure on subscriber
$node_subscriber->safe_psql('postgres', "CREATE TABLE test_tab (a int primary key, b text, c timestamptz DEFAULT now(), d bigint DEFAULT 999)");
# Setup logical replication
my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres';
$node_publisher->safe_psql('postgres', "CREATE PUBLICATION tap_pub FOR TABLE test_tab");
my $appname = 'tap_sub';
$node_subscriber->safe_psql('postgres',
"CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr application_name=$appname' PUBLICATION tap_pub WITH (streaming = on)"
);
$node_publisher->wait_for_catchup($appname);
# Also wait for initial table sync to finish
my $synced_query =
"SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');";
$node_subscriber->poll_query_until('postgres', $synced_query)
or die "Timed out while waiting for subscriber to synchronize data";
my $result =
$node_subscriber->safe_psql('postgres', "SELECT count(*), count(c), count(d = 999) FROM test_tab");
is($result, qq(2|2|2), 'check initial data was copied to subscriber');
# Insert, update and delete enough rows to exceed the 64kB limit.
$node_publisher->safe_psql('postgres', q{
BEGIN;
INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(3, 5000) s(i);
UPDATE test_tab SET b = md5(b) WHERE mod(a,2) = 0;
DELETE FROM test_tab WHERE mod(a,3) = 0;
COMMIT;
});
$node_publisher->wait_for_catchup($appname);
$result =
$node_subscriber->safe_psql('postgres', "SELECT count(*), count(c), count(d = 999) FROM test_tab");
is($result, qq(3334|3334|3334), 'check extra columns contain local defaults');
# Test the streaming in binary mode
$node_subscriber->safe_psql('postgres',
"ALTER SUBSCRIPTION tap_sub SET (binary = on)"
);
# Insert, update and delete enough rows to exceed the 64kB limit.
$node_publisher->safe_psql('postgres', q{
BEGIN;
INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(5001, 10000) s(i);
UPDATE test_tab SET b = md5(b) WHERE mod(a,2) = 0;
DELETE FROM test_tab WHERE mod(a,3) = 0;
COMMIT;
});
$node_publisher->wait_for_catchup($appname);
$result =
$node_subscriber->safe_psql('postgres', "SELECT count(*), count(c), count(d = 999) FROM test_tab");
is($result, qq(6667|6667|6667), 'check extra columns contain local defaults');
# Change the local values of the extra columns on the subscriber,
# update publisher, and check that subscriber retains the expected
# values. This is to ensure that non-streaming transactions behave
# properly after a streaming transaction.
$node_subscriber->safe_psql('postgres', "UPDATE test_tab SET c = 'epoch'::timestamptz + 987654321 * interval '1s'");
$node_publisher->safe_psql('postgres', "UPDATE test_tab SET b = md5(a::text)");
$node_publisher->wait_for_catchup($appname);
$result =
$node_subscriber->safe_psql('postgres', "SELECT count(*), count(extract(epoch from c) = 987654321), count(d = 999) FROM test_tab");
is($result, qq(6667|6667|6667), 'check extra columns contain locally changed data');
$node_subscriber->stop;
$node_publisher->stop;
...@@ -111,6 +111,7 @@ Append ...@@ -111,6 +111,7 @@ Append
AppendPath AppendPath
AppendRelInfo AppendRelInfo
AppendState AppendState
ApplySubXactData
Archive Archive
ArchiveEntryPtrType ArchiveEntryPtrType
ArchiveFormat ArchiveFormat
...@@ -2370,6 +2371,7 @@ StopList ...@@ -2370,6 +2371,7 @@ StopList
StopWorkersData StopWorkersData
StrategyNumber StrategyNumber
StreamCtl StreamCtl
StreamXidHash
StringInfo StringInfo
StringInfoData StringInfoData
StripnullState StripnullState
...@@ -2380,6 +2382,7 @@ SubPlanState ...@@ -2380,6 +2382,7 @@ SubPlanState
SubTransactionId SubTransactionId
SubXactCallback SubXactCallback
SubXactCallbackItem SubXactCallbackItem
SubXactInfo
SubXactEvent SubXactEvent
SubplanResultRelHashElem SubplanResultRelHashElem
SubqueryScan SubqueryScan
......
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