发布订阅支持冲突处理
This commit is contained in:
@ -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
|
||||
*/
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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|
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
} ;
|
||||
|
||||
/*****************************************************************************
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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, ©_data_given, ©_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, ©_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,
|
||||
©_data_given, ©_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) {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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 */
|
||||
|
||||
@ -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 */
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 | | |
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -10,4 +10,6 @@ sync
|
||||
encoding
|
||||
ddl
|
||||
matviews
|
||||
change_wal_level
|
||||
change_wal_level
|
||||
skiplsn
|
||||
disable
|
||||
65
src/test/subscription/testcase/disable.sh
Normal file
65
src/test/subscription/testcase/disable.sh
Normal 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
|
||||
69
src/test/subscription/testcase/skiplsn.sh
Normal file
69
src/test/subscription/testcase/skiplsn.sh
Normal 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
|
||||
Reference in New Issue
Block a user