发布订阅支持冲突处理

This commit is contained in:
chenxiaobin19
2023-08-15 10:45:36 +08:00
parent 99c8bdebe8
commit b95efca6ef
21 changed files with 767 additions and 253 deletions

View File

@ -199,87 +199,7 @@ static bool pg_decode_filter(LogicalDecodingContext* ctx, RepOriginId origin_id)
return true;
return false;
}
static void tuple_to_stringinfo(Relation relation, StringInfo s, TupleDesc tupdesc, HeapTuple tuple, bool isOld)
{
if ((tuple->tupTableType == HEAP_TUPLE) && (HEAP_TUPLE_IS_COMPRESSED(tuple->t_data) ||
(int)HeapTupleHeaderGetNatts(tuple->t_data, tupdesc) > tupdesc->natts)) {
return;
}
Oid oid;
/* print oid of tuple, it's not included in the TupleDesc */
if ((oid = HeapTupleHeaderGetOid(tuple->t_data)) != InvalidOid) {
appendStringInfo(s, " oid[oid]:%u", oid);
}
/* print all columns individually */
for (int natt = 0; natt < tupdesc->natts; natt++) {
Form_pg_attribute attr; /* the attribute itself */
Oid typoutput; /* output function */
bool typisvarlena = false;
Datum origval; /* possibly toasted Datum */
bool isnull = true; /* column is null? */
attr = &tupdesc->attrs[natt];
/*
* don't print dropped columns, we can't be sure everything is
* available for them
*/
if (attr->attisdropped)
continue;
/*
* Don't print system columns, oid will already have been printed if
* present.
*/
if (attr->attnum < 0 || (isOld && !IsRelationReplidentKey(relation, attr->attnum)))
continue;
Oid typid = attr->atttypid; /* type of current attribute */
/* get Datum from tuple */
if (tuple->tupTableType == HEAP_TUPLE) {
origval = heap_getattr(tuple, natt + 1, tupdesc, &isnull);
} else {
origval = uheap_getattr((UHeapTuple)tuple, natt + 1, tupdesc, &isnull);
}
/* print attribute name */
appendStringInfoChar(s, ' ');
appendStringInfoString(s, quote_identifier(NameStr(attr->attname)));
/* print attribute type */
appendStringInfoChar(s, '[');
char* type_name = format_type_be(typid);
if (strlen(type_name) == strlen("clob") && strncmp(type_name, "clob", strlen("clob")) == 0) {
errno_t rc = strcpy_s(type_name, sizeof("clob"), "text");
securec_check_c(rc, "\0", "\0");
}
appendStringInfoString(s, type_name);
appendStringInfoChar(s, ']');
/* query output function */
getTypeOutputInfo(typid, &typoutput, &typisvarlena);
/* print separator */
appendStringInfoChar(s, ':');
/* print data */
if (isnull)
appendStringInfoString(s, "null");
else if (typisvarlena && VARATT_IS_EXTERNAL_ONDISK_B(origval))
appendStringInfoString(s, "unchanged-toast-datum");
else if (!typisvarlena)
PrintLiteral(s, typid, OidOutputFunctionCall(typoutput, origval));
else {
Datum val; /* definitely detoasted Datum */
val = PointerGetDatum(PG_DETOAST_DATUM(origval));
PrintLiteral(s, typid, OidOutputFunctionCall(typoutput, val));
}
}
}
/*
* callback for individual changed tuples
*/

View File

@ -25,6 +25,7 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> CONNECTION
ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> SET PUBLICATION <replaceable class="parameter">publication_name</replaceable> [, ...]
ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> REFRESH PUBLICATION [ WITH ( <replaceable class="parameter">refresh_option</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> ENABLE
ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> DISABLE
ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">subscription_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> OWNER TO <replaceable>new_owner</replaceable>
ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
@ -134,6 +135,16 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
</listitem>
</varlistentry>
<varlistentry>
<term><literal>DISABLE</literal></term>
<listitem>
<para>
Disables a running subscription, stopping the logical replication
worker at the end of the transaction.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>SET ( <replaceable class="parameter">subscription_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
<listitem>

View File

@ -751,6 +751,7 @@ light_comm|bool|0,0|NULL|NULL|
ignore_standby_lsn_window|int|0,2147483647|ms|NULL|
ignore_feedback_xmin_window|int|0,2147483647|ms|NULL|
ss_enable_bcast_snapshot|bool|0,0|NULL|NULL|
subscription_conflict_resolution|enum|error,apply_remote,keep_local|NULL|NULL|
[cmserver]
log_dir|string|0,0|NULL|NULL|
log_file_size|int|0,2047|MB|NULL|

View File

@ -103,6 +103,14 @@ Subscription *GetSubscription(Oid subid, bool missing_ok)
sub->binary = DatumGetBool(datum);
}
/* Get skiplsn */
datum = SysCacheGetAttr(SUBSCRIPTIONOID, tup, Anum_pg_subscription_subskiplsn, &isnull);
if (unlikely(isnull)) {
sub->skiplsn = InvalidXLogRecPtr;
} else {
sub->skiplsn = TextDatumGetLsn(datum);
}
ReleaseSysCache(tup);
return sub;
@ -237,7 +245,7 @@ static List *textarray_to_stringlist(ArrayType *textarray)
return res;
}
static Datum LsnGetTextDatum(XLogRecPtr lsn)
Datum LsnGetTextDatum(XLogRecPtr lsn)
{
char clsn[MAXFNAMELEN];
int ret = snprintf_s(clsn, sizeof(clsn), sizeof(clsn) - 1, "%X/%X", (uint32)(lsn >> 32), (uint32)lsn);
@ -246,7 +254,7 @@ static Datum LsnGetTextDatum(XLogRecPtr lsn)
return CStringGetTextDatum(clsn);
}
static XLogRecPtr TextDatumGetLsn(Datum datum)
XLogRecPtr TextDatumGetLsn(Datum datum)
{
XLogRecPtr lsn;
uint32 lsn_hi;

View File

@ -18854,6 +18854,16 @@ AlterSubscriptionStmt:
n->options = list_make1(makeDefElem("enabled",
(Node *)makeInteger(TRUE)));
$$ = (Node *)n;
}
| ALTER SUBSCRIPTION name DISABLE_P
{
AlterSubscriptionStmt *n =
makeNode(AlterSubscriptionStmt);
n->refresh = false;
n->subname = $3;
n->options = list_make1(makeDefElem("enabled",
(Node *)makeInteger(FALSE)));
$$ = (Node *)n;
} ;
/*****************************************************************************

View File

@ -293,6 +293,13 @@ static const struct config_enum_entry repl_auth_mode_options[] = {
{NULL, 0, false}
};
static const struct config_enum_entry ConflictResolvers[] = {
{"error", RESOLVE_ERROR, false},
{"apply_remote", RESOLVE_APPLY_REMOTE, false},
{"keep_local", RESOLVE_KEEP_LOCAL, false},
{NULL, 0, false}
};
/*
* Although only "on", "off", "remote_write", and "local" are documented, we
* accept all the likely variants of "on" and "off".
@ -4723,6 +4730,18 @@ static void InitStorageConfigureNamesEnum()
NULL,
NULL,
NULL},
{{"subscription_conflict_resolution",
PGC_SIGHUP,
NODE_SINGLENODE,
REPLICATION,
gettext_noop("Sets method used for conflict resolution for resolvable conflicts."),
NULL},
&u_sess->attr.attr_storage.subscription_conflict_resolution,
RESOLVE_ERROR,
ConflictResolvers,
NULL,
NULL,
NULL},
#ifndef ENABLE_MULTIPLE_NODES
{{"dcf_log_file_permission",
PGC_POSTMASTER,

View File

@ -45,10 +45,46 @@
#include "utils/syscache.h"
#include "utils/array.h"
#include "utils/acl.h"
#include "utils/pg_lsn.h"
#include "access/tableam.h"
#include "libpq/libpq-fe.h"
#include "replication/slot.h"
/*
* Options that can be specified by the user in CREATE/ALTER SUBSCRIPTION
* command.
*/
#define SUBOPT_CONNINFO 0x00000001
#define SUBOPT_PUBLICATION 0x00000002
#define SUBOPT_ENABLED 0x00000004
#define SUBOPT_SLOT_NAME 0x00000008
#define SUBOPT_SYNCHRONOUS_COMMIT 0x00000010
#define SUBOPT_BINARY 0x00000020
#define SUBOPT_COPY_DATA 0x00000040
#define SUBOPT_CONNECT 0x00000080
#define SUBOPT_SKIPLSN 0x00000100
/* check if the 'val' has 'bits' set */
#define IsSet(val, bits) (((val) & (bits)) == (bits))
/*
* Structure to hold a bitmap representing the user-provided CREATE/ALTER
* SUBSCRIPTION command options and the parsed/default values of each of them.
*/
typedef struct SubOpts
{
bits32 specified_opts;
char *conninfo;
List *publications;
bool enabled;
char *slot_name;
char *synchronous_commit;
bool binary;
bool copy_data;
bool connect;
XLogRecPtr skiplsn;
} SubOpts;
static bool ConnectPublisher(char* conninfo, char* slotname);
static void CreateSlotInPublisherAndInsertSubRel(char *slotname, Oid subid, List *publications,
bool *copy_data, bool create_slot);
@ -61,120 +97,127 @@ static bool CheckPublicationsExistOnPublisher(List *publications);
* Common option parsing function for CREATE and ALTER SUBSCRIPTION commands.
*
* Since not all options can be specified in both commands, this function
* will report an error on options if the target output pointer is NULL to
* accommodate that.
* will report an error mutually exclusive options are specified.
*
* Caller is expected to have cleared 'opts'.
*/
static void parse_subscription_options(const List *options, char **conninfo, List **publications, bool *enabled_given,
bool *enabled, bool *slot_name_given, char **slot_name, char **synchronous_commit, bool *binary_given, bool *binary,
bool *copy_data_given, bool *copy_data, bool *connect_given, bool *connect)
static void parse_subscription_options(const List *stmt_options, bits32 supported_opts, SubOpts *opts)
{
ListCell *lc;
if (conninfo) {
*conninfo = NULL;
}
if (publications) {
*publications = NIL;
}
if (enabled) {
*enabled_given = false;
}
if (slot_name) {
*slot_name_given = false;
*slot_name = NULL;
}
if (synchronous_commit) {
*synchronous_commit = NULL;
}
if (binary) {
*binary_given = false;
*binary = false;
}
/* caller must expect some option */
Assert(supported_opts != 0);
if (copy_data) {
*copy_data_given = false;
*copy_data = true;
/* Set default values for the boolean supported options. */
if (IsSet(supported_opts, SUBOPT_BINARY)) {
opts->binary = false;
}
if (connect) {
*connect_given = false;
*connect = true;
if (IsSet(supported_opts, SUBOPT_COPY_DATA)) {
opts->copy_data = true;
}
if (IsSet(supported_opts, SUBOPT_CONNECT)) {
opts->connect = true;
}
/* Parse options */
foreach (lc, options) {
foreach (lc, stmt_options) {
DefElem *defel = (DefElem *)lfirst(lc);
if (strcmp(defel->defname, "conninfo") == 0 && conninfo) {
if (*conninfo) {
if (IsSet(supported_opts, SUBOPT_CONNINFO) && strcmp(defel->defname, "conninfo") == 0) {
if (IsSet(opts->specified_opts, SUBOPT_CONNINFO)) {
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options")));
}
*conninfo = defGetString(defel);
} else if (strcmp(defel->defname, "publication") == 0 && publications) {
if (*publications) {
opts->specified_opts |= SUBOPT_CONNINFO;
opts->conninfo = defGetString(defel);
} else if (IsSet(supported_opts, SUBOPT_PUBLICATION) && strcmp(defel->defname, "publication") == 0) {
if (IsSet(opts->specified_opts, SUBOPT_PUBLICATION)) {
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options")));
}
*publications = defGetStringList(defel);
} else if (strcmp(defel->defname, "enabled") == 0 && enabled) {
if (*enabled_given) {
opts->specified_opts |= SUBOPT_PUBLICATION;
opts->publications = defGetStringList(defel);
} else if (IsSet(supported_opts, SUBOPT_ENABLED) && strcmp(defel->defname, "enabled") == 0) {
if (IsSet(opts->specified_opts, SUBOPT_ENABLED)) {
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options")));
}
*enabled_given = true;
*enabled = defGetBoolean(defel);
} else if (strcmp(defel->defname, "slot_name") == 0 && slot_name) {
if (*slot_name_given) {
opts->specified_opts |= SUBOPT_ENABLED;
opts->enabled = defGetBoolean(defel);
} else if (IsSet(supported_opts, SUBOPT_SLOT_NAME) && strcmp(defel->defname, "slot_name") == 0) {
if (IsSet(opts->specified_opts, SUBOPT_SLOT_NAME)) {
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options")));
}
*slot_name_given = true;
*slot_name = defGetString(defel);
opts->specified_opts |= SUBOPT_SLOT_NAME;
opts->slot_name = defGetString(defel);
/* Setting slot_name = NONE is treated as no slot name. */
if (strcmp(*slot_name, "none") == 0) {
*slot_name = NULL;
if (strcmp(opts->slot_name, "none") == 0) {
opts->slot_name = NULL;
} else {
ReplicationSlotValidateName(*slot_name, ERROR);
ReplicationSlotValidateName(opts->slot_name, ERROR);
}
} else if (strcmp(defel->defname, "synchronous_commit") == 0 && synchronous_commit) {
if (*synchronous_commit) {
} else if (IsSet(supported_opts, SUBOPT_SYNCHRONOUS_COMMIT) &&
strcmp(defel->defname, "synchronous_commit") == 0) {
if (IsSet(opts->specified_opts, SUBOPT_SYNCHRONOUS_COMMIT)) {
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options")));
}
*synchronous_commit = defGetString(defel);
opts->specified_opts |= SUBOPT_SYNCHRONOUS_COMMIT;
opts->synchronous_commit = defGetString(defel);
/* Test if the given value is valid for synchronous_commit GUC. */
(void)set_config_option("synchronous_commit", *synchronous_commit, PGC_BACKEND, PGC_S_TEST, GUC_ACTION_SET,
false, 0, false);
} else if (strcmp(defel->defname, "binary") == 0 && binary) {
if (*binary_given) {
(void)set_config_option("synchronous_commit", opts->synchronous_commit, PGC_BACKEND, PGC_S_TEST,
GUC_ACTION_SET, false, 0, false);
} else if (IsSet(supported_opts, SUBOPT_BINARY) && strcmp(defel->defname, "binary") == 0) {
if (IsSet(opts->specified_opts, SUBOPT_BINARY)) {
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
}
*binary_given = true;
*binary = defGetBoolean(defel);
} else if (strcmp(defel->defname, "copy_data") == 0 && copy_data) {
if (*copy_data_given) {
opts->specified_opts |= SUBOPT_BINARY;
opts->binary = defGetBoolean(defel);
} else if (IsSet(supported_opts, SUBOPT_COPY_DATA) && strcmp(defel->defname, "copy_data") == 0) {
if (IsSet(opts->specified_opts, SUBOPT_COPY_DATA)) {
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
}
*copy_data_given = true;
*copy_data = defGetBoolean(defel);
} else if (strcmp(defel->defname, "connect") == 0 && connect) {
if (*connect_given) {
opts->specified_opts |= SUBOPT_COPY_DATA;
opts->copy_data = defGetBoolean(defel);
} else if (IsSet(supported_opts, SUBOPT_CONNECT) && strcmp(defel->defname, "connect") == 0) {
if (IsSet(opts->specified_opts, SUBOPT_CONNECT)) {
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
}
*connect_given = true;
*connect = defGetBoolean(defel);
opts->specified_opts = SUBOPT_CONNECT;
opts->connect = defGetBoolean(defel);
} else if (IsSet(supported_opts, SUBOPT_SKIPLSN) && strcmp(defel->defname, "skiplsn") == 0) {
if (IsSet(opts->specified_opts, SUBOPT_SKIPLSN)) {
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
}
char *lsn_str = defGetString(defel);
opts->specified_opts = SUBOPT_SKIPLSN;
/* Setting lsn = 'NONE' is treated as resetting LSN */
if (strcmp(lsn_str, "none") == 0) {
opts->skiplsn = InvalidXLogRecPtr;
} else {
/* Parse the argument as LSN */
opts->skiplsn = DatumGetLSN(DirectFunctionCall1(pg_lsn_in, CStringGetDatum(lsn_str)));
if (XLogRecPtrIsInvalid(opts->skiplsn))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid WAL location (LSN): %s", lsn_str)));
}
} else {
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR), errmsg("unrecognized subscription parameter: %s", defel->defname)));
@ -185,38 +228,39 @@ static void parse_subscription_options(const List *options, char **conninfo, Lis
* We've been explicitly asked to not connect, that requires some
* additional processing.
*/
if (connect && !*connect) {
if (IsSet(supported_opts, SUBOPT_CONNECT) && !opts->connect) {
/* Check for incompatible options from the user. */
if (*enabled_given && *enabled)
if (IsSet(opts->specified_opts, SUBOPT_ENABLED) && opts->enabled)
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("%s and %s are mutually exclusive options",
"connect = false", "enabled = true")));
if (*copy_data_given && *copy_data)
if (IsSet(opts->specified_opts, SUBOPT_COPY_DATA) && opts->copy_data)
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("%s and %s are mutually exclusive options",
"connect = false", "copy_data = true")));
/* Change the defaults of other options. */
*enabled = false;
*copy_data = false;
opts->enabled = false;
opts->copy_data = false;
}
/*
* Do additional checking for disallowed combination when
* slot_name = NONE was used.
*/
if (slot_name && *slot_name_given && !*slot_name) {
if (enabled && *enabled_given && *enabled) {
if (!opts->slot_name && IsSet(supported_opts, SUBOPT_SLOT_NAME) && IsSet(opts->specified_opts, SUBOPT_SLOT_NAME)) {
if (opts->enabled && IsSet(supported_opts, SUBOPT_ENABLED) && IsSet(opts->specified_opts, SUBOPT_ENABLED)) {
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("slot_name = NONE and enabled = true are mutually exclusive options")));
}
if (enabled && !*enabled_given && *enabled) {
if (opts->enabled && IsSet(supported_opts, SUBOPT_ENABLED) && !IsSet(opts->specified_opts, SUBOPT_ENABLED)) {
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("subscription with slot_name = NONE must also set enabled = false")));
}
}
if (copy_data && *copy_data && u_sess->attr.attr_storage.max_sync_workers_per_subscription == 0) {
if (IsSet(supported_opts, SUBOPT_COPY_DATA) && IsSet(opts->specified_opts, SUBOPT_COPY_DATA) &&
u_sess->attr.attr_storage.max_sync_workers_per_subscription == 0) {
ereport(WARNING, (errmsg("you need to set max_sync_workers_per_subscription because it is zero but "
"copy_data is true")));
}
@ -451,27 +495,20 @@ ObjectAddress CreateSubscription(CreateSubscriptionStmt *stmt, bool isTopLevel)
Datum values[Natts_pg_subscription];
Oid owner = GetUserId();
HeapTuple tup;
bool enabled_given = false;
bool enabled = true;
char *synchronous_commit;
char *slotname;
bool slotname_given;
bool binary;
bool binary_given;
bool copy_data;
bool copy_data_given;
bool connect;
bool connect_given;
char originname[NAMEDATALEN];
List *publications;
bits32 supported_opts;
SubOpts opts = {0};
opts.enabled = true;
int rc;
/*
* Parse and check options.
* Connection and publication should not be specified here.
*/
parse_subscription_options(stmt->options, NULL, NULL, &enabled_given, &enabled, &slotname_given, &slotname,
&synchronous_commit, &binary_given, &binary, &copy_data_given, &copy_data, &connect_given, &connect);
supported_opts = (SUBOPT_ENABLED | SUBOPT_SLOT_NAME | SUBOPT_SYNCHRONOUS_COMMIT | SUBOPT_BINARY |
SUBOPT_COPY_DATA | SUBOPT_CONNECT);
parse_subscription_options(stmt->options, supported_opts, &opts);
/*
* Since creating a replication slot is not transactional, rolling back
@ -479,7 +516,7 @@ ObjectAddress CreateSubscription(CreateSubscriptionStmt *stmt, bool isTopLevel)
* CREATE SUBSCRIPTION inside a transaction block if creating a
* replication slot.
*/
if (enabled)
if (opts.enabled)
PreventTransactionChain(isTopLevel, "CREATE SUBSCRIPTION ... WITH (enabled = true)");
if (!superuser())
@ -495,8 +532,8 @@ ObjectAddress CreateSubscription(CreateSubscriptionStmt *stmt, bool isTopLevel)
}
/* The default for synchronous_commit of subscriptions is off. */
if (synchronous_commit == NULL) {
synchronous_commit = "off";
if (opts.synchronous_commit == NULL) {
opts.synchronous_commit = "off";
}
publications = stmt->publication;
@ -513,28 +550,29 @@ ObjectAddress CreateSubscription(CreateSubscriptionStmt *stmt, bool isTopLevel)
values[Anum_pg_subscription_subdbid - 1] = ObjectIdGetDatum(u_sess->proc_cxt.MyDatabaseId);
values[Anum_pg_subscription_subname - 1] = DirectFunctionCall1(namein, CStringGetDatum(stmt->subname));
values[Anum_pg_subscription_subowner - 1] = ObjectIdGetDatum(owner);
values[Anum_pg_subscription_subenabled - 1] = BoolGetDatum(enabled);
values[Anum_pg_subscription_subbinary - 1] = BoolGetDatum(binary);
values[Anum_pg_subscription_subenabled - 1] = BoolGetDatum(opts.enabled);
values[Anum_pg_subscription_subbinary - 1] = BoolGetDatum(opts.binary);
/* encrypt conninfo */
char *encryptConninfo = EncryptOrDecryptConninfo(stmt->conninfo, 'E');
values[Anum_pg_subscription_subconninfo - 1] = CStringGetTextDatum(encryptConninfo);
if (enabled) {
if (!slotname_given) {
slotname = stmt->subname;
if (opts.enabled) {
if (!IsSet(opts.specified_opts, SUBOPT_SLOT_NAME)) {
opts.slot_name = stmt->subname;
}
values[Anum_pg_subscription_subslotname - 1] = DirectFunctionCall1(namein, CStringGetDatum(slotname));
values[Anum_pg_subscription_subslotname - 1] = DirectFunctionCall1(namein, CStringGetDatum(opts.slot_name));
} else {
if (slotname_given && slotname) {
if (IsSet(opts.specified_opts, SUBOPT_SLOT_NAME) && opts.slot_name) {
ereport(WARNING, (errmsg("When enabled=false, it is dangerous to set slot_name. "
"This will cause wal log accumulation on the publisher, "
"so slot_name will be forcibly set to NULL.")));
}
nulls[Anum_pg_subscription_subslotname - 1] = true;
}
values[Anum_pg_subscription_subsynccommit - 1] = CStringGetTextDatum(synchronous_commit);
values[Anum_pg_subscription_subsynccommit - 1] = CStringGetTextDatum(opts.synchronous_commit);
values[Anum_pg_subscription_subpublications - 1] = publicationListToArray(publications);
values[Anum_pg_subscription_subskiplsn - 1] = LsnGetTextDatum(InvalidXLogRecPtr);
tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
@ -553,8 +591,8 @@ ObjectAddress CreateSubscription(CreateSubscriptionStmt *stmt, bool isTopLevel)
* Connect to remote side to execute requested commands and fetch table
* info.
*/
if (connect) {
if (!AttemptConnectPublisher(encryptConninfo, slotname, true)) {
if (opts.connect) {
if (!AttemptConnectPublisher(encryptConninfo, opts.slot_name, true)) {
ereport(ERROR, (errcode(ERRCODE_CONNECTION_FAILURE), errmsg("Failed to connect to publisher.")));
}
@ -567,8 +605,8 @@ ObjectAddress CreateSubscription(CreateSubscriptionStmt *stmt, bool isTopLevel)
* If requested, create the replication slot on remote side for our
* newly created subscription.
*/
Assert(!enabled || slotname);
CreateSlotInPublisherAndInsertSubRel(slotname, subid, publications, &copy_data, enabled);
Assert(!opts.enabled || opts.slot_name);
CreateSlotInPublisherAndInsertSubRel(opts.slot_name, subid, publications, &opts.copy_data, opts.enabled);
(WalReceiverFuncTable[GET_FUNC_IDX]).walrcv_disconnect();
} else {
@ -582,7 +620,7 @@ ObjectAddress CreateSubscription(CreateSubscriptionStmt *stmt, bool isTopLevel)
securec_check(rc, "", "");
/* Don't wake up logical replication launcher unnecessarily */
if (enabled) {
if (opts.enabled) {
ApplyLauncherWakeupAtCommit();
}
@ -793,17 +831,6 @@ ObjectAddress AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel)
Datum values[Natts_pg_subscription];
HeapTuple tup;
Oid subid;
bool enabled_given = false;
bool enabled = false;
bool binary_given = false;
bool binary = false;
char *synchronous_commit = NULL;
char *conninfo = NULL;
char *slot_name = NULL;
bool slotname_given = false;
bool copy_data = false;
bool copy_data_given = false;
List *publications = NIL;
Subscription *sub = NULL;
int rc;
bool checkConn = false;
@ -812,6 +839,8 @@ ObjectAddress AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel)
bool needFreeConninfo = false;
char *finalSlotName = NULL;
char *encryptConninfo = NULL;
bits32 supported_opts;
SubOpts opts = {0};
rel = heap_open(SubscriptionRelationId, RowExclusiveLock);
@ -831,17 +860,18 @@ ObjectAddress AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel)
/* Lock the subscription so nobody else can do anything with it. */
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
enabled = sub->enabled;
opts.enabled = sub->enabled;
finalSlotName = sub->name;
encryptConninfo = sub->conninfo;
/* Parse options. */
if (!stmt->refresh) {
parse_subscription_options(stmt->options, &conninfo, &publications, &enabled_given, &enabled, &slotname_given,
&slot_name, &synchronous_commit, &binary_given, &binary, NULL, NULL, NULL, NULL);
supported_opts = (SUBOPT_CONNINFO | SUBOPT_PUBLICATION | SUBOPT_ENABLED | SUBOPT_SLOT_NAME |
SUBOPT_SYNCHRONOUS_COMMIT | SUBOPT_BINARY | SUBOPT_SKIPLSN);
parse_subscription_options(stmt->options, supported_opts, &opts);
} else {
parse_subscription_options(stmt->options, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
&copy_data_given, &copy_data, NULL, NULL);
supported_opts = SUBOPT_COPY_DATA;
parse_subscription_options(stmt->options, supported_opts, &opts);
PreventTransactionChain(isTopLevel, "ALTER SUBSCRIPTION ... REFRESH");
}
@ -854,38 +884,39 @@ ObjectAddress AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel)
rc = memset_s(replaces, sizeof(replaces), false, sizeof(replaces));
securec_check(rc, "", "");
if (enabled_given && enabled != sub->enabled) {
values[Anum_pg_subscription_subenabled - 1] = BoolGetDatum(enabled);
if (IsSet(opts.specified_opts, SUBOPT_ENABLED) && opts.enabled != sub->enabled) {
values[Anum_pg_subscription_subenabled - 1] = BoolGetDatum(opts.enabled);
replaces[Anum_pg_subscription_subenabled - 1] = true;
}
if (conninfo) {
if (opts.conninfo) {
/* Check the connection info string. */
libpqrcv_check_conninfo(conninfo);
encryptConninfo = EncryptOrDecryptConninfo(conninfo, 'E');
rc = memset_s(conninfo, strlen(conninfo), 0, strlen(conninfo));
libpqrcv_check_conninfo(opts.conninfo);
encryptConninfo = EncryptOrDecryptConninfo(opts.conninfo, 'E');
rc = memset_s(opts.conninfo, strlen(opts.conninfo), 0, strlen(opts.conninfo));
securec_check(rc, "\0", "\0");
values[Anum_pg_subscription_subconninfo - 1] = CStringGetTextDatum(encryptConninfo);
replaces[Anum_pg_subscription_subconninfo - 1] = true;
needFreeConninfo = true;
/* need to check whether new conninfo can be used to connect to new publisher */
if (sub->enabled || (enabled_given && enabled)) {
if (sub->enabled || (IsSet(opts.specified_opts, SUBOPT_ENABLED) && opts.enabled)) {
checkConn = true;
}
}
if (slotname_given) {
if (sub->enabled && !slot_name) {
if (IsSet(opts.specified_opts, SUBOPT_SLOT_NAME)) {
if (sub->enabled && !opts.slot_name) {
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR), errmsg("cannot set slot_name = NONE for enabled subscription")));
}
/* change to non-null value */
if (slot_name) {
if (sub->enabled || (enabled_given && enabled)) {
values[Anum_pg_subscription_subslotname - 1] = DirectFunctionCall1(namein, CStringGetDatum(slot_name));
if (opts.slot_name) {
if (sub->enabled || (IsSet(opts.specified_opts, SUBOPT_ENABLED) && opts.enabled)) {
values[Anum_pg_subscription_subslotname - 1] = DirectFunctionCall1(namein,
CStringGetDatum(opts.slot_name));
/* if old slotname is null or same as new slot name, then we need to validate the new slot name */
validateSlot = sub->slotname == NULL || strcmp(slot_name, sub->slotname) != 0;
finalSlotName = slot_name;
validateSlot = sub->slotname == NULL || strcmp(opts.slot_name, sub->slotname) != 0;
finalSlotName = opts.slot_name;
} else {
ereport(ERROR, (errmsg("Currently enabled=false, cannot change slot_name to a non-null value.")));
}
@ -896,30 +927,34 @@ ObjectAddress AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel)
* when enable this subscription but slot_name is not specified,
* it will be set to default value subname.
*/
if (!sub->enabled && enabled_given && enabled) {
if (!sub->enabled && IsSet(opts.specified_opts, SUBOPT_ENABLED) && opts.enabled) {
values[Anum_pg_subscription_subslotname - 1] = DirectFunctionCall1(namein, CStringGetDatum(sub->name));
} else {
nulls[Anum_pg_subscription_subslotname - 1] = true;
}
}
replaces[Anum_pg_subscription_subslotname - 1] = true;
} else if (!sub->enabled && enabled_given && enabled) {
} else if (!sub->enabled && IsSet(opts.specified_opts, SUBOPT_ENABLED) && opts.enabled) {
values[Anum_pg_subscription_subslotname - 1] = DirectFunctionCall1(namein, CStringGetDatum(sub->name));
replaces[Anum_pg_subscription_subslotname - 1] = true;
}
if (synchronous_commit) {
values[Anum_pg_subscription_subsynccommit - 1] = CStringGetTextDatum(synchronous_commit);
if (opts.synchronous_commit) {
values[Anum_pg_subscription_subsynccommit - 1] = CStringGetTextDatum(opts.synchronous_commit);
replaces[Anum_pg_subscription_subsynccommit - 1] = true;
}
if (binary_given) {
values[Anum_pg_subscription_subbinary - 1] = BoolGetDatum(binary);
if (IsSet(opts.specified_opts, SUBOPT_BINARY)) {
values[Anum_pg_subscription_subbinary - 1] = BoolGetDatum(opts.binary);
replaces[Anum_pg_subscription_subbinary - 1] = true;
}
if (publications != NIL) {
values[Anum_pg_subscription_subpublications - 1] = publicationListToArray(publications);
if (opts.publications != NIL) {
values[Anum_pg_subscription_subpublications - 1] = publicationListToArray(opts.publications);
replaces[Anum_pg_subscription_subpublications - 1] = true;
} else {
publications = sub->publications;
opts.publications = sub->publications;
}
if (IsSet(opts.specified_opts, SUBOPT_SKIPLSN)) {
values[Anum_pg_subscription_subskiplsn - 1] = LsnGetTextDatum(opts.skiplsn);
replaces[Anum_pg_subscription_subskiplsn - 1] = true;
}
tup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls, replaces);
@ -938,8 +973,9 @@ ObjectAddress AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel)
/* case 0: keep the subscription enabled, it's over here */
/* case 1: deactivating subscription */
if (sub->enabled && !enabled) {
ereport(ERROR, (errmsg("If you want to deactivate this subscription, use DROP SUBSCRIPTION.")));
if (sub->enabled && !opts.enabled) {
ereport(WARNING, (errmsg("The subscription will be disabled, take care of the xlog would be accumulate "
"because the slot of subscription wouldn't be advanced")));
}
/* case 2: keep the subscription active */
@ -947,7 +983,7 @@ ObjectAddress AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel)
if (!AttemptConnectPublisher(encryptConninfo, finalSlotName, true)) {
ereport(ERROR, (errcode(ERRCODE_CONNECTION_FAILURE), errmsg( "Failed to connect to publisher.")));
}
if (!CheckPublicationsExistOnPublisher(publications)) {
if (!CheckPublicationsExistOnPublisher(opts.publications)) {
(WalReceiverFuncTable[GET_FUNC_IDX]).walrcv_disconnect();
ereport(ERROR, (errmsg("There are some publications not exist on the publisher.")));
}
@ -958,7 +994,7 @@ ObjectAddress AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel)
* enabling subscription, but slot hasn't been created,
* then mark createSlot to true.
*/
if (!sub->enabled && enabled && (!sub->slotname || !*(sub->slotname))) {
if (!sub->enabled && opts.enabled && (!sub->slotname || !*(sub->slotname))) {
createSlot = true;
}
@ -968,13 +1004,13 @@ ObjectAddress AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel)
checkConn ? "The new conninfo cannot connect to new publisher." : "Failed to connect to publisher.")));
}
if (!CheckPublicationsExistOnPublisher(publications)) {
if (!CheckPublicationsExistOnPublisher(opts.publications)) {
(WalReceiverFuncTable[GET_FUNC_IDX]).walrcv_disconnect();
ereport(ERROR, (errmsg("There are some publications not exist on the publisher.")));
}
if (createSlot) {
CreateSlotInPublisherAndInsertSubRel(finalSlotName, subid, publications, NULL, true);
CreateSlotInPublisherAndInsertSubRel(finalSlotName, subid, opts.publications, NULL, true);
}
/* no need to validate replication slot if the slot is created just by ourself */
@ -984,6 +1020,8 @@ ObjectAddress AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel)
(WalReceiverFuncTable[GET_FUNC_IDX]).walrcv_disconnect();
ApplyLauncherWakeupAtCommit();
} else if (!sub->enabled && opts.enabled) {
ApplyLauncherWakeupAtCommit();
}
if (stmt->refresh) {
@ -991,7 +1029,7 @@ ObjectAddress AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel)
ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("ALTER SUBSCRIPTION ... REFRESH is not allowed for disabled subscriptions")));
}
AlterSubscription_refresh(sub, copy_data);
AlterSubscription_refresh(sub, opts.copy_data);
}
if (needFreeConninfo) {

View File

@ -53,7 +53,8 @@ static bool RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode, Tuple
* This is not generic routine, it expects the idxrel to be replication
* identity of a rel and meet all limitations associated with that.
*/
static bool build_replindex_scan_key(ScanKey skey, Relation rel, Relation idxrel, TupleTableSlot *searchslot)
static bool build_replindex_scan_key(ScanKey skey, Relation rel, Relation idxrel, TupleTableSlot *searchslot,
EState *estate)
{
int attoff;
bool isnull;
@ -66,6 +67,16 @@ static bool build_replindex_scan_key(ScanKey skey, Relation rel, Relation idxrel
Assert(!isnull);
opclass = (oidvector *)DatumGetPointer(indclassDatum);
List* expressionsState = NIL;
ExprContext* econtext = GetPerTupleExprContext(estate);
econtext->ecxt_scantuple = searchslot;
ListCell* indexpr_item = NULL;
if (idxrel->rd_indexprs != NIL) {
expressionsState = ExecPrepareExprList(idxrel->rd_indexprs, estate);
indexpr_item = list_head(expressionsState);
}
/* Build scankey for every attribute in the index. */
for (attoff = 0; attoff < IndexRelationGetNumberOfKeyAttributes(idxrel); attoff++) {
Oid op;
@ -92,14 +103,34 @@ static bool build_replindex_scan_key(ScanKey skey, Relation rel, Relation idxrel
regop = get_opcode(op);
/* Initialize the scankey. */
ScanKeyInit(&skey[attoff], pkattno, BTEqualStrategyNumber, regop, searchslot->tts_values[mainattno - 1]);
skey[attoff].sk_collation = idxrel->rd_indcollation[attoff];
if (mainattno != 0) {
/* Initialize the scankey. */
ScanKeyInit(&skey[attoff], pkattno, BTEqualStrategyNumber, regop, searchslot->tts_values[mainattno - 1]);
skey[attoff].sk_collation = idxrel->rd_indcollation[attoff];
/* Check for null value. */
if (searchslot->tts_isnull[mainattno - 1]) {
hasnulls = true;
skey[attoff].sk_flags |= SK_ISNULL;
/* Check for null value. */
if (searchslot->tts_isnull[mainattno - 1]) {
hasnulls = true;
skey[attoff].sk_flags |= SK_ISNULL;
}
} else {
if (idxrel->rd_indexprs == NIL) {
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("wrong number of index expressions")));
} else {
bool isNull = false;
Datum datum = ExecEvalExprSwitchContext((ExprState*)lfirst(indexpr_item), econtext, &isNull, NULL);
indexpr_item = lnext(indexpr_item);
ScanKeyInit(&skey[attoff], pkattno, BTEqualStrategyNumber, regop, datum);
skey[attoff].sk_collation = idxrel->rd_indcollation[attoff];
/* Check for null value. */
if (isNull) {
hasnulls = true;
skey[attoff].sk_flags |= SK_ISNULL;
}
}
}
}
@ -307,7 +338,7 @@ static bool RelationFindReplTupleByIndex(EState *estate, Relation rel, Relation
scan->isUpsert = true;
/* Build scan key. */
build_replindex_scan_key(skey, targetRel, idxrel, searchslot);
build_replindex_scan_key(skey, targetRel, idxrel, searchslot, estate);
while (true) {
found = false;

View File

@ -72,13 +72,23 @@ ParallelReorderBufferTXN *ParallelReorderBufferGetOldestTXN(ParallelReorderBuffe
return txn;
}
void tuple_to_stringinfo(Relation relation, StringInfo s, TupleDesc tupdesc, HeapTuple tuple, bool isOld)
void tuple_to_stringinfo(Relation relation, StringInfo s, TupleDesc tupdesc, HeapTuple tuple, bool isOld,
bool printOid)
{
if ((tuple->tupTableType == HEAP_TUPLE) && (HEAP_TUPLE_IS_COMPRESSED(tuple->t_data) ||
(int)HeapTupleHeaderGetNatts(tuple->t_data, tupdesc) > tupdesc->natts)) {
return;
}
if (printOid) {
Oid oid;
/* print oid of tuple, it's not included in the TupleDesc */
if ((oid = HeapTupleHeaderGetOid(tuple->t_data)) != InvalidOid) {
appendStringInfo(s, " oid[oid]:%u", oid);
}
}
/* print all columns individually */
for (int natt = 0; natt < tupdesc->natts; natt++) {
Form_pg_attribute attr; /* the attribute itself */
@ -90,6 +100,12 @@ void tuple_to_stringinfo(Relation relation, StringInfo s, TupleDesc tupdesc, Hea
attr = &tupdesc->attrs[natt];
/*
* Don't print dropped columns, we can't be sure everything is
* available for them.
* Don't print system columns, oid will already have been printed if
* present.
*/
if (attr->attisdropped || attr->attnum < 0 || (isOld && !IsRelationReplidentKey(relation, attr->attnum)))
continue;

View File

@ -115,6 +115,10 @@ static void ApplyWorkerProcessMsg(char type, StringInfo s, XLogRecPtr *lastRcv);
static void apply_dispatch(StringInfo s);
static void apply_handle_conninfo(StringInfo s);
static void UpdateConninfo(char* standbysInfo);
static Oid find_conflict_tuple(EState *estate, TupleTableSlot *remoteslot, TupleTableSlot *localslot,
FakeRelationPartition *fakeRelInfo, TupleTableSlot *originslot = NULL);
static void IsSkippingChanges(XLogRecPtr finish_lsn);
static void StopSkippingChanges();
/*
* Should this worker apply changes for given relation.
@ -478,6 +482,8 @@ static void apply_handle_begin(StringInfo s)
t_thrd.applyworker_cxt.remoteFinalLsn = begin_data.final_lsn;
t_thrd.applyworker_cxt.curRemoteCsn = begin_data.csn;
IsSkippingChanges(begin_data.final_lsn);
pgstat_report_activity(STATE_RUNNING, NULL);
}
@ -510,6 +516,10 @@ static void apply_handle_commit(StringInfo s)
/* Process any tables that are being synchronized in parallel. */
process_syncing_tables(commit_data.end_lsn);
if (t_thrd.applyworker_cxt.isSkipTransaction) {
StopSkippingChanges();
}
pgstat_report_activity(STATE_IDLE, NULL);
}
@ -570,6 +580,69 @@ static Oid GetRelationIdentityOrPK(Relation rel)
return idxoid;
}
/*
* Find the tuple in a table using any unique index and returns the conflicting
* index's oid, if any conflict found.
*
* *originslot* contains the old tuple during UPDATE, if conflict with it which
* to be updated, ignore it.
*/
Oid find_conflict_tuple(EState *estate, TupleTableSlot *remoteslot, TupleTableSlot *localslot,
FakeRelationPartition *fakeRelInfo, TupleTableSlot *originslot)
{
Oid replidxoid = InvalidOid;
bool found = false;
ResultRelInfo* relinfo = estate->es_result_relation_info;
/* Check the replica identity index first */
replidxoid = RelationGetReplicaIndex(relinfo->ri_RelationDesc);
found = RelationFindReplTuple(estate, relinfo->ri_RelationDesc, replidxoid, LockTupleExclusive, remoteslot,
localslot, fakeRelInfo);
if (found) {
if (originslot != NULL && ItemPointerCompare(tableam_tops_get_t_self(relinfo->ri_RelationDesc,
localslot->tts_tuple), tableam_tops_get_t_self(relinfo->ri_RelationDesc,
originslot->tts_tuple)) == 0) {
/* If conflict with the tuple to be updated, ignore it. */
found = false;
} else {
return replidxoid;
}
}
for (int i = 0; i < relinfo->ri_NumIndices; i++) {
IndexInfo *ii = relinfo->ri_IndexRelationInfo[i];
Relation idxrel;
Oid idxoid = InvalidOid;
if (!ii->ii_Unique) {
continue;
}
idxrel = relinfo->ri_IndexRelationDescs[i];
idxoid = RelationGetRelid(idxrel);
if (idxoid == replidxoid) {
continue;
}
found = RelationFindReplTuple(estate, relinfo->ri_RelationDesc, idxoid, LockTupleExclusive, remoteslot,
localslot, fakeRelInfo);
if (found) {
if (originslot != NULL && ItemPointerCompare(tableam_tops_get_t_self(relinfo->ri_RelationDesc,
localslot->tts_tuple), tableam_tops_get_t_self(relinfo->ri_RelationDesc,
originslot->tts_tuple)) == 0) {
/* If conflict with the tuple to be updated, ignore it. */
found = false;
} else {
return idxoid;
}
}
}
return InvalidOid;
}
/*
* Handle INSERT message.
*/
@ -582,6 +655,13 @@ static void apply_handle_insert(StringInfo s)
TupleTableSlot *remoteslot;
MemoryContext oldctx;
FakeRelationPartition fakeRelInfo;
TupleTableSlot *localslot;
EPQState epqstate;
Oid conflictIndexOid = InvalidOid;
if (t_thrd.applyworker_cxt.isSkipTransaction) {
return;
}
ensure_transaction();
@ -600,6 +680,8 @@ static void apply_handle_insert(StringInfo s)
estate = create_estate_for_relation(rel);
remoteslot = ExecInitExtraTupleSlot(estate, rel->localrel->rd_tam_ops);
ExecSetSlotDescriptor(remoteslot, RelationGetDescr(rel->localrel));
localslot = ExecInitExtraTupleSlot(estate, rel->localrel->rd_tam_ops);
ExecSetSlotDescriptor(localslot, RelationGetDescr(rel->localrel));
/* Input functions may need an active snapshot, so get one */
PushActiveSnapshot(GetTransactionSnapshot());
@ -611,11 +693,62 @@ static void apply_handle_insert(StringInfo s)
ExecOpenIndices(estate->es_result_relation_info, false);
/* Get fake relation and partition for patitioned table */
GetFakeRelAndPart(estate, rel->localrel, remoteslot, &fakeRelInfo);
if ((conflictIndexOid = find_conflict_tuple(estate, remoteslot, localslot, &fakeRelInfo)) != InvalidOid) {
StringInfoData localtup, remotetup;
initStringInfo(&localtup);
tuple_to_stringinfo(rel->localrel, &localtup, RelationGetDescr(rel->localrel),
(HeapTuple)localslot->tts_tuple, false);
/* Do the insert. */
ExecSimpleRelationInsert(estate, remoteslot, &fakeRelInfo);
initStringInfo(&remotetup);
tuple_to_stringinfo(rel->localrel, &remotetup, RelationGetDescr(rel->localrel),
(HeapTuple)tableam_tslot_get_tuple_from_slot(rel->localrel, remoteslot), false);
switch (u_sess->attr.attr_storage.subscription_conflict_resolution) {
case RESOLVE_ERROR:
ereport(ERROR, (errmsg("CONFLICT: remote insert on relation %s (local index %s). Resolution: error.",
RelationGetRelationName(rel->localrel), get_rel_name(conflictIndexOid)),
errdetail("local tuple: %s, remote tuple: %s, origin: pg_%u, commit_lsn: %X/%X", localtup.data,
remotetup.data, t_thrd.applyworker_cxt.curWorker->subid,
(uint32)(t_thrd.applyworker_cxt.remoteFinalLsn >> BITS_PER_INT),
(uint32)t_thrd.applyworker_cxt.remoteFinalLsn)));
break;
case RESOLVE_APPLY_REMOTE:
ereport(LOG, (errmsg("CONFLICT: remote insert on relation %s (local index %s). "
"Resolution: apply_remote.",
RelationGetRelationName(rel->localrel), get_rel_name(conflictIndexOid)),
errdetail("local tuple: %s, remote tuple: %s, origin: pg_%u, commit_lsn: %X/%X", localtup.data,
remotetup.data, t_thrd.applyworker_cxt.curWorker->subid,
(uint32)(t_thrd.applyworker_cxt.remoteFinalLsn >> BITS_PER_INT),
(uint32)t_thrd.applyworker_cxt.remoteFinalLsn)));
EvalPlanQualInit(&epqstate, estate, NULL, NIL, -1);
EvalPlanQualSetSlot(&epqstate, remoteslot);
/* Do the actual update. */
ExecSimpleRelationUpdate(estate, &epqstate, localslot, remoteslot, &fakeRelInfo);
EvalPlanQualEnd(&epqstate);
break;
case RESOLVE_KEEP_LOCAL:
ereport(LOG, (errmsg("CONFLICT: remote insert on relation %s (local index %s). "
"Resolution: keep_local.",
RelationGetRelationName(rel->localrel), get_rel_name(conflictIndexOid)),
errdetail("local tuple: %s, remote tuple: %s, origin: pg_%u, commit_lsn: %X/%X", localtup.data,
remotetup.data, t_thrd.applyworker_cxt.curWorker->subid,
(uint32)(t_thrd.applyworker_cxt.remoteFinalLsn >> BITS_PER_INT),
(uint32)t_thrd.applyworker_cxt.remoteFinalLsn)));
break;
default:
ereport(ERROR, (errmsg("wrong parameter value for subscription_conflict_resolution")));
break;
}
FreeStringInfo(&localtup);
FreeStringInfo(&remotetup);
} else {
/* Get fake relation and partition for patitioned table */
GetFakeRelAndPart(estate, rel->localrel, remoteslot, &fakeRelInfo);
/* Do the insert. */
ExecSimpleRelationInsert(estate, remoteslot, &fakeRelInfo);
}
/* Cleanup. */
ExecCloseIndices(estate->es_result_relation_info);
@ -701,10 +834,16 @@ static void apply_handle_update(StringInfo s)
bool has_oldtup;
TupleTableSlot *localslot;
TupleTableSlot *remoteslot;
TupleTableSlot *conflictLocalSlot;
RangeTblEntry *target_rte = NULL;
bool found = false;
MemoryContext oldctx;
FakeRelationPartition fakeRelInfo;
Oid conflictIndexOid = InvalidOid;
if (t_thrd.applyworker_cxt.isSkipTransaction) {
return;
}
ensure_transaction();
@ -728,6 +867,8 @@ static void apply_handle_update(StringInfo s)
ExecSetSlotDescriptor(remoteslot, RelationGetDescr(rel->localrel));
localslot = ExecInitExtraTupleSlot(estate, rel->localrel->rd_tam_ops);
ExecSetSlotDescriptor(localslot, RelationGetDescr(rel->localrel));
conflictLocalSlot = ExecInitExtraTupleSlot(estate, rel->localrel->rd_tam_ops);
ExecSetSlotDescriptor(conflictLocalSlot, RelationGetDescr(rel->localrel));
EvalPlanQualInit(&epqstate, estate, NULL, NIL, -1);
/*
@ -783,10 +924,64 @@ static void apply_handle_update(StringInfo s)
slot_modify_data(remoteslot, localslot, rel, &newtup);
MemoryContextSwitchTo(oldctx);
EvalPlanQualSetSlot(&epqstate, remoteslot);
if ((conflictIndexOid = find_conflict_tuple(estate, remoteslot, conflictLocalSlot, &fakeRelInfo, localslot))
!= InvalidOid) {
StringInfoData localtup, remotetup;
initStringInfo(&localtup);
tuple_to_stringinfo(rel->localrel, &localtup, RelationGetDescr(rel->localrel),
(HeapTuple)conflictLocalSlot->tts_tuple, false);
/* Do the actual update. */
ExecSimpleRelationUpdate(estate, &epqstate, localslot, remoteslot, &fakeRelInfo);
initStringInfo(&remotetup);
tuple_to_stringinfo(rel->localrel, &remotetup, RelationGetDescr(rel->localrel),
(HeapTuple)tableam_tslot_get_tuple_from_slot(rel->localrel, remoteslot), false);
switch (u_sess->attr.attr_storage.subscription_conflict_resolution) {
case RESOLVE_ERROR:
ereport(ERROR, (errmsg("CONFLICT: remote update on relation %s (local index %s). "
"Resolution: error.",
RelationGetRelationName(rel->localrel), get_rel_name(conflictIndexOid)),
errdetail("local tuple: %s, remote tuple: %s, origin: pg_%u, commit_lsn: %X/%X", localtup.data,
remotetup.data, t_thrd.applyworker_cxt.curWorker->subid,
(uint32)(t_thrd.applyworker_cxt.remoteFinalLsn >> BITS_PER_INT),
(uint32)t_thrd.applyworker_cxt.remoteFinalLsn)));
break;
case RESOLVE_APPLY_REMOTE:
ereport(LOG, (errmsg("CONFLICT: remote update on relation %s (local index %s). "
"Resolution: apply_remote.",
RelationGetRelationName(rel->localrel), get_rel_name(conflictIndexOid)),
errdetail("local tuple: %s, remote tuple: %s, origin: pg_%u, commit_lsn: %X/%X", localtup.data,
remotetup.data, t_thrd.applyworker_cxt.curWorker->subid,
(uint32)(t_thrd.applyworker_cxt.remoteFinalLsn >> BITS_PER_INT),
(uint32)t_thrd.applyworker_cxt.remoteFinalLsn)));
/* first delete the conflict tuple */
EvalPlanQualSetSlot(&epqstate, conflictLocalSlot);
ExecSimpleRelationDelete(estate, &epqstate, conflictLocalSlot, &fakeRelInfo);
EvalPlanQualSetSlot(&epqstate, remoteslot);
/* Do the actual update. */
ExecSimpleRelationUpdate(estate, &epqstate, localslot, remoteslot, &fakeRelInfo);
break;
case RESOLVE_KEEP_LOCAL:
ereport(LOG, (errmsg("CONFLICT: remote update on relation %s (local index %s). "
"Resolution: keep_local.",
RelationGetRelationName(rel->localrel), get_rel_name(conflictIndexOid)),
errdetail("local tuple: %s, remote tuple: %s, origin: pg_%u, commit_lsn: %X/%X", localtup.data,
remotetup.data, t_thrd.applyworker_cxt.curWorker->subid,
(uint32)(t_thrd.applyworker_cxt.remoteFinalLsn >> BITS_PER_INT),
(uint32)t_thrd.applyworker_cxt.remoteFinalLsn)));
break;
default:
ereport(ERROR, (errmsg("wrong parameter value for subscription_conflict_resolution")));
break;
}
FreeStringInfo(&localtup);
FreeStringInfo(&remotetup);
} else {
EvalPlanQualSetSlot(&epqstate, remoteslot);
/* Do the actual update. */
ExecSimpleRelationUpdate(estate, &epqstate, localslot, remoteslot, &fakeRelInfo);
}
} else {
/*
* The tuple to be updated could not be found.
@ -824,6 +1019,10 @@ static void apply_handle_delete(StringInfo s)
MemoryContext oldctx;
FakeRelationPartition fakeRelInfo;
if (t_thrd.applyworker_cxt.isSkipTransaction) {
return;
}
ensure_transaction();
relid = logicalrep_read_delete(s, &oldtup);
@ -1828,3 +2027,78 @@ bool IsLogicalWorker(void)
{
return t_thrd.applyworker_cxt.curWorker != NULL;
}
/*
* Start skipping changes of the transaction if the given LSN matches the
* LSN specified by subscription's skiplsn.
*/
static void IsSkippingChanges(XLogRecPtr finish_lsn)
{
/*
* Quick return if it's not requested to skip this transaction. This
* function is called for every remote transaction and we assume that
* skipping the transaction is not used often.
*/
if (likely(XLogRecPtrIsInvalid(t_thrd.applyworker_cxt.mySubscription->skiplsn) ||
t_thrd.applyworker_cxt.mySubscription->skiplsn != finish_lsn)) {
return;
}
t_thrd.applyworker_cxt.isSkipTransaction = true;
}
static void StopSkippingChanges()
{
t_thrd.applyworker_cxt.isSkipTransaction = false;
/*
* Quick return if it's not requested to skip this transaction. This
* function is called for every remote transaction and we assume that
* skipping the transaction is not used often.
*/
if (!IsTransactionState()) {
StartTransactionCommand();
}
HeapTuple tup;
Relation rel;
bool nulls[Natts_pg_subscription];
bool replaces[Natts_pg_subscription];
Datum values[Natts_pg_subscription];
errno_t rc = 0;
/*
* Protect subskiplsn of pg_subscription from being concurrently updated
* while clearing it.
*/
LockSharedObject(SubscriptionRelationId, t_thrd.applyworker_cxt.mySubscription->oid, 0, AccessShareLock);
rel = heap_open(SubscriptionRelationId, RowExclusiveLock);
/* Fetch the existing tuple. */
tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(t_thrd.applyworker_cxt.mySubscription->oid));
if (!HeapTupleIsValid(tup)) {
ereport(ERROR, (errmsg("subscription \"%s\" does not exist", t_thrd.applyworker_cxt.mySubscription->name)));
}
rc = memset_s(values, sizeof(values),0, sizeof(values));
securec_check_c(rc, "\0", "\0");
rc = memset_s(nulls, sizeof(nulls),false, sizeof(nulls));
securec_check_c(rc, "\0", "\0");
rc = memset_s(replaces, sizeof(replaces), false, sizeof(replaces));
securec_check_c(rc, "\0", "\0");
/* reset subskiplsn */
values[Anum_pg_subscription_subskiplsn - 1] = LsnGetTextDatum(InvalidXLogRecPtr);
replaces[Anum_pg_subscription_subskiplsn - 1] = true;
tup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls, replaces);
/* Update the catalog. */
simple_heap_update(rel, &tup->t_self, tup);
CatalogUpdateIndexes(rel, tup);
heap_freetuple(tup);
heap_close(rel, NoLock);
CommitTransactionCommand();
}

View File

@ -52,13 +52,15 @@ CATALOG(pg_subscription,6126) BKI_SHARED_RELATION BKI_ROWTYPE_OID(6128) BKI_SCHE
text subpublications[1]; /* List of publications subscribed to */
bool subbinary; /* True if the subscription wants the
* publisher to send data in binary */
text subskiplsn; /* All changes finished at this LSN are
* skipped */
#endif
}
FormData_pg_subscription;
typedef FormData_pg_subscription *Form_pg_subscription;
#define Natts_pg_subscription 9
#define Natts_pg_subscription 10
#define Anum_pg_subscription_subdbid 1
#define Anum_pg_subscription_subname 2
#define Anum_pg_subscription_subowner 3
@ -68,6 +70,7 @@ typedef FormData_pg_subscription *Form_pg_subscription;
#define Anum_pg_subscription_subsynccommit 7
#define Anum_pg_subscription_subpublications 8
#define Anum_pg_subscription_subbinary 9
#define Anum_pg_subscription_subskiplsn 10
typedef struct Subscription {
@ -81,6 +84,8 @@ typedef struct Subscription {
char *synccommit; /* Synchronous commit setting for worker */
List *publications; /* List of publication names to subscribe to */
bool binary; /* Indicates if the subscription wants data in binary format */
XLogRecPtr skiplsn; /* All changes finished at this LSN are
* skipped */
} Subscription;
@ -91,6 +96,8 @@ extern char *get_subscription_name(Oid subid, bool missing_ok);
extern int CountDBSubscriptions(Oid dbid);
extern void ClearListContent(List *list);
extern Datum LsnGetTextDatum(XLogRecPtr lsn);
extern XLogRecPtr TextDatumGetLsn(Datum datum);
#endif /* PG_SUBSCRIPTION_H */

View File

@ -266,6 +266,7 @@ typedef struct knl_session_attr_storage {
int logical_sender_timeout;
int ignore_standby_lsn_window;
int ignore_feedback_xmin_window;
int subscription_conflict_resolution;
} knl_session_attr_storage;
#endif /* SRC_INCLUDE_KNL_KNL_SESSION_ATTR_STORAGE */

View File

@ -3335,6 +3335,7 @@ typedef struct knl_t_apply_worker_context {
List *tableStates;
XLogRecPtr remoteFinalLsn;
CommitSeqNo curRemoteCsn;
bool isSkipTransaction;
} knl_t_apply_worker_context;
typedef struct knl_t_publication_context {

View File

@ -358,5 +358,7 @@ extern void FreeLogicalLog(ParallelReorderBuffer *rb, logicalLog *logChange, int
extern bool LogicalDecodeParseOptionsDefault(const char* defaultStr, void **options);
extern DecodeOptionsDefault* LogicalDecodeGetOptionsDefault();
template <typename T> void LogicalDecodeReportLostChanges(const T *iterstate);
extern void tuple_to_stringinfo(Relation relation, StringInfo s, TupleDesc tupdesc, HeapTuple tuple, bool isOld,
bool printOid = false);
#endif

View File

@ -214,6 +214,13 @@ typedef enum replauthmode{
REPL_AUTH_UUID /* uuid auth */
} ReplAuthMode;
typedef enum
{
RESOLVE_ERROR,
RESOLVE_APPLY_REMOTE,
RESOLVE_KEEP_LOCAL
} PGLogicalResolveOption;
extern bool data_catchup;
extern bool wal_catchup;
extern BuildMode build_mode;

View File

@ -70,6 +70,14 @@ ALTER SUBSCRIPTION testsub owner to regress_subscription_user2;
-- alter subbinary to true
ALTER SUBSCRIPTION testsub SET (binary=true);
select subname, subbinary from pg_subscription where subname='testsub';
-- set subskiplsn
ALTER SUBSCRIPTION testsub SET (skiplsn = '0/ABCDEF');
select subname, subskiplsn from pg_subscription where subname='testsub';
ALTER SUBSCRIPTION testsub SET (skiplsn = '0/ABCDEFGH');
ALTER SUBSCRIPTION testsub SET (skiplsn = 'none');
select subname, subskiplsn from pg_subscription where subname='testsub';
-- disable test
ALTER SUBSCRIPTION testsub DISABLE;
--rename
ALTER SUBSCRIPTION testsub rename to testsub_rename;
--- inside a transaction block

View File

@ -662,6 +662,7 @@ select name,vartype,unit,min_val,max_val from pg_settings where name <> 'qunit_c
stats_temp_directory | string | | |
stream_cluster_run_mode | enum | | |
string_hash_compatible | bool | | |
subscription_conflict_resolution | enum | | |
support_batch_bind | bool | | |
support_extended_features | bool | | |
sync_config_strategy | enum | | |

View File

@ -19,6 +19,7 @@ ALTER SUBSCRIPTION name CONNECTION 'conninfo'
ALTER SUBSCRIPTION name SET PUBLICATION publication_name [, ...]
ALTER SUBSCRIPTION name REFRESH PUBLICATION [ WITH ( refresh_option [= value] [, ... ] ) ]
ALTER SUBSCRIPTION name ENABLE
ALTER SUBSCRIPTION name DISABLE
ALTER SUBSCRIPTION name SET ( subscription_parameter [= value] [, ... ] )
ALTER SUBSCRIPTION name OWNER TO new_owner
ALTER SUBSCRIPTION name RENAME TO new_name
@ -153,6 +154,25 @@ select subname, subbinary from pg_subscription where subname='testsub';
testsub | t
(1 row)
-- set subskiplsn
ALTER SUBSCRIPTION testsub SET (skiplsn = '0/ABCDEF');
select subname, subskiplsn from pg_subscription where subname='testsub';
subname | subskiplsn
---------+------------
testsub | 0/ABCDEF
(1 row)
ALTER SUBSCRIPTION testsub SET (skiplsn = '0/ABCDEFGH');
ERROR: invalid input syntax for type pg_lsn: "0/ABCDEFGH"
ALTER SUBSCRIPTION testsub SET (skiplsn = 'none');
select subname, subskiplsn from pg_subscription where subname='testsub';
subname | subskiplsn
---------+------------
testsub | 0/0
(1 row)
-- disable test
ALTER SUBSCRIPTION testsub DISABLE;
--rename
ALTER SUBSCRIPTION testsub rename to testsub_rename;
--- inside a transaction block
@ -337,12 +357,15 @@ SELECT object_name,detail_info FROM pg_query_audit('2022-01-13 9:30:00', '2031-1
testsub | ALTER SUBSCRIPTION testsub SET (synchronous_commit=on);
testsub | ALTER SUBSCRIPTION testsub owner to regress_subscription_user2;
testsub | ALTER SUBSCRIPTION testsub SET (binary=true);
testsub | ALTER SUBSCRIPTION testsub SET (skiplsn = '0/ABCDEF');
testsub | ALTER SUBSCRIPTION testsub SET (skiplsn = 'none');
testsub | ALTER SUBSCRIPTION testsub DISABLE;
testsub | ALTER SUBSCRIPTION testsub rename to testsub_rename;
sub_len_999 | CREATE SUBSCRIPTION sub_len_999 CONNECTION *********************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************PUBLICATION insert_only WITH (connect = false);
testsub_rename | DROP SUBSCRIPTION IF EXISTS testsub_rename;
testsub_maskconninfo | DROP SUBSCRIPTION IF EXISTS testsub_maskconninfo;
sub_len_999 | DROP SUBSCRIPTION IF EXISTS sub_len_999;
(17 rows)
(20 rows)
--clear audit log
SELECT pg_delete_audit('1012-11-10', '3012-11-11');

View File

@ -10,4 +10,6 @@ sync
encoding
ddl
matviews
change_wal_level
change_wal_level
skiplsn
disable

View File

@ -0,0 +1,65 @@
#!/bin/sh
source $1/env_utils.sh $1 $2
case_db="disable_db"
function test_1() {
echo "create database and tables."
exec_sql $db $pub_node1_port "CREATE DATABASE $case_db"
exec_sql $db $sub_node1_port "CREATE DATABASE $case_db"
# Create some preexisting content on publisher
exec_sql $case_db $pub_node1_port "CREATE TABLE tab_rep (a int primary key, b int)"
# Setup structure on subscriber
exec_sql $case_db $sub_node1_port "CREATE TABLE tab_rep (a int primary key, b int)"
# Setup logical replication
echo "create publication and subscription."
publisher_connstr="port=$pub_node1_port host=$g_local_ip dbname=$case_db user=$username password=$passwd"
exec_sql $case_db $pub_node1_port "CREATE PUBLICATION tap_pub FOR ALL TABLES"
exec_sql $case_db $sub_node1_port "CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr' PUBLICATION tap_pub"
# Wait for initial table sync to finish
wait_for_subscription_sync $case_db $sub_node1_port
exec_sql $case_db $pub_node1_port "insert into tab_rep values (1,1)"
wait_for_catchup $case_db $pub_node1_port "tap_sub"
exec_sql $case_db $sub_node1_port "alter subscription tap_sub disable"
exec_sql $case_db $pub_node1_port "insert into tab_rep values (2,2)"
if [ "$(exec_sql $case_db $sub_node1_port "SELECT * FROM tab_rep")" = "1|1" ]; then
echo "check data not sync after disable subscription success"
else
echo "$failed_keyword when check data not sync after disable subscription"
exit 1
fi
exec_sql $case_db $sub_node1_port "alter subscription tap_sub enable"
wait_for_catchup $case_db $pub_node1_port "tap_sub"
if [ "$(exec_sql $case_db $sub_node1_port "SELECT * FROM tab_rep")" = "1|1
2|2" ]; then
echo "check data not sync after enable subscription success"
else
echo "$failed_keyword when check data not sync after enable subscription"
exit 1
fi
}
function tear_down() {
exec_sql $case_db $sub_node1_port "DROP SUBSCRIPTION IF EXISTS tap_sub"
exec_sql $case_db $pub_node1_port "DROP PUBLICATION IF EXISTS tap_pub"
exec_sql $db $sub_node1_port "DROP DATABASE $case_db"
exec_sql $db $pub_node1_port "DROP DATABASE $case_db"
echo "tear down"
}
test_1
tear_down

View File

@ -0,0 +1,69 @@
#!/bin/sh
source $1/env_utils.sh $1 $2
case_db="skiplsn_db"
function test_1() {
echo "create database and tables."
exec_sql $db $pub_node1_port "CREATE DATABASE $case_db"
exec_sql $db $sub_node1_port "CREATE DATABASE $case_db"
# Create some preexisting content on publisher
exec_sql $case_db $pub_node1_port "CREATE TABLE tab_rep (a int primary key, b int)"
# Setup structure on subscriber
exec_sql $case_db $sub_node1_port "CREATE TABLE tab_rep (a int primary key, b int)"
# Setup logical replication
echo "create publication and subscription."
publisher_connstr="port=$pub_node1_port host=$g_local_ip dbname=$case_db user=$username password=$passwd"
exec_sql $case_db $pub_node1_port "CREATE PUBLICATION tap_pub FOR ALL TABLES"
exec_sql $case_db $sub_node1_port "CREATE SUBSCRIPTION tap_sub CONNECTION '$publisher_connstr' PUBLICATION tap_pub"
# Wait for initial table sync to finish
wait_for_subscription_sync $case_db $sub_node1_port
exec_sql $case_db $sub_node1_port "insert into tab_rep values (1,1)"
logfile=$(get_log_file "sub_datanode1")
location=$(awk 'END{print NR}' $logfile)
exec_sql $case_db $pub_node1_port "insert into tab_rep values (1,2)"
content=$(tail -n +$location $logfile)
commitlsn=$(expr "$content" : '.*commit_lsn:\s\([0-9|/|ABCDEF]*\).*')
while [ -z $commitlsn ]
do
content=$(tail -n +$location $logfile)
commitlsn=$(expr "$content" : '.*commit_lsn:\s\([0-9|/|ABCDEF]*\).*')
done
exec_sql $case_db $sub_node1_port "alter subscription tap_sub set (skiplsn = '$commitlsn')"
exec_sql $case_db $pub_node1_port "insert into tab_rep values (2, 2)"
wait_for_catchup $case_db $pub_node1_port "tap_sub"
if [ "$(exec_sql $case_db $sub_node1_port "SELECT * FROM tab_rep")" = "1|1
2|2" ]; then
echo "check data sync after skip conflict success"
else
echo "$failed_keyword when check data sync after skip conflict"
exit 1
fi
}
function tear_down() {
exec_sql $case_db $sub_node1_port "DROP SUBSCRIPTION IF EXISTS tap_sub"
exec_sql $case_db $pub_node1_port "DROP PUBLICATION IF EXISTS tap_pub"
exec_sql $db $sub_node1_port "DROP DATABASE $case_db"
exec_sql $db $pub_node1_port "DROP DATABASE $case_db"
echo "tear down"
}
test_1
tear_down