v36-0002-Review-comment-fixes-for-Add-configurable-confli.patch
application/octet-stream
Filename: v36-0002-Review-comment-fixes-for-Add-configurable-confli.patch
Type: application/octet-stream
Part: 4
From b7b969a90e03ccebb349c2abf28e65efc1047ff2 Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Mon, 18 May 2026 10:34:43 +0000
Subject: [PATCH v36 02/10] Review comment fixes for "Add configurable conflict
log table for Logical Replication"
Review comment fixes for "Add configurable conflict log table for
Logical Replication"
---
src/backend/catalog/aclchk.c | 61 +++---
src/backend/catalog/catalog.c | 10 +-
src/backend/catalog/heap.c | 37 ++--
src/backend/catalog/namespace.c | 6 +-
src/backend/catalog/pg_publication.c | 16 +-
src/backend/commands/subscriptioncmds.c | 189 +++--------------
src/backend/commands/tablecmds.c | 2 +-
src/backend/executor/execMain.c | 5 +-
src/backend/replication/logical/conflict.c | 170 +++++++++++++++-
src/include/catalog/catalog.h | 2 +-
src/include/catalog/pg_subscription.h | 16 +-
src/include/commands/subscriptioncmds.h | 2 -
src/include/replication/conflict.h | 9 +-
src/test/regress/expected/subscription.out | 225 +++++++++++----------
src/test/regress/sql/subscription.sql | 38 ++--
15 files changed, 427 insertions(+), 361 deletions(-)
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 84ef5304e22..e583187c7a6 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3337,33 +3337,42 @@ pg_class_aclmask_ext(Oid table_oid, Oid roleid, AclMode mask,
classForm = (Form_pg_class) GETSTRUCT(tuple);
- /*
- * Deny anyone permission to update a system catalog unless
- * pg_authid.rolsuper is set.
- *
- * As of 7.4 we have some updatable system views; those shouldn't be
- * protected in this way. Assume the view rules can take care of
- * themselves. ACL_USAGE is if we ever have system sequences.
- *
- * For conflict log tables, we allow non-superusers to perform DELETE
- * and TRUNCATE for maintenance, while still restricting INSERT,
- * UPDATE, and USAGE.
- */
- if ((mask & (ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE | ACL_USAGE)) &&
- IsConflictClass(classForm) &&
- !superuser_arg(roleid))
- mask &= ~(ACL_INSERT | ACL_UPDATE | ACL_USAGE);
- else if ((mask & (ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE | ACL_USAGE)) &&
- IsSystemClass(table_oid, classForm) &&
- classForm->relkind != RELKIND_VIEW &&
- !superuser_arg(roleid))
- mask &= ~(ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE | ACL_USAGE);
-
- /*
- * Otherwise, superusers bypass all permission-checking.
- */
- if (superuser_arg(roleid))
+ if (!superuser_arg(roleid))
+ {
+ if (mask & (ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE | ACL_USAGE))
+ {
+ if (IsConflictLogTableClass(classForm))
+ {
+ /*
+ * For conflict log tables, allow non-superusers to perform
+ * DELETE and TRUNCATE for cleanup and maintenance. Also allow
+ * INSERT and UPDATE to pass ACL checks so that later checks
+ * can raise the dedicated "cannot modify or insert data into
+ * conflict log table" error instead of a generic permission
+ * denied error. Still restrict USAGE for non-superusers.
+ */
+ mask &= ~(ACL_USAGE);
+ }
+ else if (IsSystemClass(table_oid, classForm) &&
+ classForm->relkind != RELKIND_VIEW)
+ {
+ /*
+ * Deny anyone permission to update a system catalog unless
+ * pg_authid.rolsuper is set.
+ *
+ * As of 7.4 we have some updatable system views; those
+ * shouldn't be protected in this way. Assume the view rules
+ * can take care of themselves. ACL_USAGE is if we ever have
+ * system sequences.
+ */
+ mask &= ~(ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE |
+ ACL_USAGE);
+ }
+ }
+ }
+ else
{
+ /* Superusers bypass all permission-checking. */
ReleaseSysCache(tuple);
return mask;
}
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 4578cd07140..a321543cc0a 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -88,7 +88,7 @@ IsSystemClass(Oid relid, Form_pg_class reltuple)
/* IsCatalogRelationOid is a bit faster, so test that first */
return (IsCatalogRelationOid(relid) ||
IsToastClass(reltuple) ||
- IsConflictClass(reltuple));
+ IsConflictLogTableClass(reltuple));
}
/*
@@ -233,11 +233,13 @@ IsToastClass(Form_pg_class reltuple)
}
/*
- * IsConflictClass - Check if the given pg_class tuple belongs to the conflict
- * namespace.
+ * IsConflictLogTableClass
+ * True iff namespace is pg_conflict.
+ *
+ * Does not perform any catalog accesses.
*/
bool
-IsConflictClass(Form_pg_class reltuple)
+IsConflictLogTableClass(Form_pg_class reltuple)
{
Oid relnamespace = reltuple->relnamespace;
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0daf98a4405..6c80c9bece9 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -305,24 +305,35 @@ heap_create(const char *relname,
Assert(OidIsValid(relid));
/*
- * Don't allow creating relations in pg_catalog directly, even though it
- * is allowed to move user defined relations there. Semantics with search
- * paths including pg_catalog are too confusing for now.
+ * Don't allow creating relations in pg_catalog/pg_conflict directly, even
+ * though it is allowed to move user defined relations there. Semantics
+ * with search paths including pg_catalog are too confusing for now.
*
* But allow creating indexes on relations in pg_catalog even if
* allow_system_table_mods = off, upper layers already guarantee it's on a
* user defined relation, not a system one.
*/
- if (!allow_system_table_mods &&
- ((IsCatalogNamespace(relnamespace) && relkind != RELKIND_INDEX) ||
- IsToastNamespace(relnamespace) ||
- IsConflictNamespace(relnamespace)) &&
- IsNormalProcessingMode())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to create \"%s.%s\"",
- get_namespace_name(relnamespace), relname),
- errdetail("System catalog modifications are currently disallowed.")));
+ if (!allow_system_table_mods && IsNormalProcessingMode())
+ {
+ if ((IsCatalogNamespace(relnamespace) && relkind != RELKIND_INDEX) ||
+ IsToastNamespace(relnamespace))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to create \"%s.%s\"",
+ get_namespace_name(relnamespace), relname),
+ errdetail("System catalog modifications are currently disallowed.")));
+ }
+
+ if (IsConflictNamespace(relnamespace))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to create \"%s.%s\"",
+ get_namespace_name(relnamespace), relname),
+ errdetail("Conflict schema modifications are currently disallowed.")));
+ }
+ }
*relfrozenxid = InvalidTransactionId;
*relminmxid = InvalidMultiXactId;
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index c35fcf57fd4..b327c6d86fe 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -3524,7 +3524,7 @@ LookupCreationNamespace(const char *nspname)
* Common checks on switching namespaces.
*
* We complain if either the old or new namespaces is a temporary schema,
- * temporary toast schema, the TOAST schema, or the CONFLICT schema.
+ * temporary toast schema, the TOAST schema, or the pg_conflict schema.
*/
void
CheckSetNamespace(Oid oldNspOid, Oid nspOid)
@@ -3541,11 +3541,11 @@ CheckSetNamespace(Oid oldNspOid, Oid nspOid)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot move objects into or out of TOAST schema")));
- /* similarly for CONFLICT schema */
+ /* similarly for pg_conflict schema */
if (nspOid == PG_CONFLICT_NAMESPACE || oldNspOid == PG_CONFLICT_NAMESPACE)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot move objects into or out of CONFLICT schema")));
+ errmsg("cannot move objects into or out of pg_conflict schema")));
}
/*
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index c680356a10b..93791210e35 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -92,6 +92,13 @@ check_publication_add_relation(PublicationRelInfo *pri)
errmsg(errormsg, relname),
errdetail("This operation is not supported for system tables.")));
+ /* Can't be conflict log table */
+ if (IsConflictNamespace(RelationGetNamespace(targetrel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg(errormsg, relname),
+ errdetail("This operation is not supported for conflict log tables.")));
+
/* UNLOGGED and TEMP relations cannot be part of publication. */
if (targetrel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
ereport(ERROR,
@@ -103,13 +110,6 @@ check_publication_add_relation(PublicationRelInfo *pri)
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(errormsg, relname),
errdetail("This operation is not supported for unlogged tables.")));
-
- /* Can't be conflict log table */
- if (IsConflictNamespace(RelationGetNamespace(targetrel)))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg(errormsg, relname),
- errdetail("This operation is not supported for conflict log tables.")));
}
/*
@@ -165,7 +165,7 @@ is_publishable_class(Oid relid, Form_pg_class reltuple)
reltuple->relkind == RELKIND_PARTITIONED_TABLE ||
reltuple->relkind == RELKIND_SEQUENCE) &&
!IsCatalogRelationOid(relid) &&
- !IsConflictClass(reltuple) &&
+ !IsConflictLogTableClass(reltuple) &&
reltuple->relpersistence == RELPERSISTENCE_PERMANENT &&
relid >= FirstNormalObjectId;
}
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index c10f6bf73b0..7597b513e2c 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -145,7 +145,6 @@ static List *merge_publications(List *oldpublist, List *newpublist, bool addpub,
static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err);
static void CheckAlterSubOption(Subscription *sub, const char *option,
bool slot_needs_update, bool isTopLevel);
-static Oid create_conflict_log_table(Oid subid, char *subname, Oid subowner);
/*
* Common option parsing function for CREATE and ALTER SUBSCRIPTION commands.
@@ -838,7 +837,6 @@ CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
values[Anum_pg_subscription_suborigin - 1] =
CStringGetTextDatum(opts.origin);
- /* Always set the destination, default will be 'log'. */
values[Anum_pg_subscription_subconflictlogdest - 1] =
CStringGetTextDatum(ConflictLogDestNames[opts.conflictlogdest]);
@@ -1829,6 +1827,9 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
else if (!want_table && has_oldtable)
{
ObjectAddress object;
+ char *conflictrelname;
+
+ conflictrelname = get_rel_name(sub->conflictlogrelid);
/*
* Conflict log tables are recorded as internal
@@ -1848,6 +1849,11 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
PERFORM_DELETION_INTERNAL |
PERFORM_DELETION_SKIP_ORIGINAL);
+ ereport(NOTICE,
+ errmsg("dropped conflict log table \"%s\" for subscription \"%s\"",
+ get_qualified_objname(PG_CONFLICT_NAMESPACE, conflictrelname),
+ sub->name));
+
values[Anum_pg_subscription_subconflictlogrelid - 1] =
ObjectIdGetDatum(InvalidOid);
replaces[Anum_pg_subscription_subconflictlogrelid - 1] =
@@ -2282,6 +2288,7 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
HeapTuple tup;
Oid subid;
Oid subowner;
+ Oid subconflictlogrelid;
Datum datum;
bool isnull;
char *subname;
@@ -2328,6 +2335,7 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
form = (Form_pg_subscription) GETSTRUCT(tup);
subid = form->oid;
subowner = form->subowner;
+ subconflictlogrelid = form->subconflictlogrelid;
must_use_password = !superuser_arg(subowner) && form->subpasswordrequired;
/* must be owner */
@@ -2482,18 +2490,28 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
deleteDependencyRecordsFor(SubscriptionRelationId, subid, false);
deleteSharedDependencyRecordsFor(SubscriptionRelationId, subid, 0);
- /*
- * Conflict log tables are recorded as internal dependencies of the
- * subscription. We must drop the dependent objects before the
- * subscription itself is removed. By using
- * PERFORM_DELETION_SKIP_ORIGINAL, we ensure that only the conflict log
- * table is reaped while the subscription remains for the final deletion
- * step.
- */
- ObjectAddressSet(object, SubscriptionRelationId, subid);
- performDeletion(&object, DROP_CASCADE,
- PERFORM_DELETION_INTERNAL |
- PERFORM_DELETION_SKIP_ORIGINAL);
+ if (OidIsValid(subconflictlogrelid))
+ {
+ char *conflictrelname = get_rel_name(subconflictlogrelid);
+
+ /*
+ * Conflict log tables are recorded as internal dependencies of the
+ * subscription. We must drop the dependent objects before the
+ * subscription itself is removed. By using
+ * PERFORM_DELETION_SKIP_ORIGINAL, we ensure that only the conflict log
+ * table is reaped while the subscription remains for the final
+ * deletion step.
+ */
+ ObjectAddressSet(object, SubscriptionRelationId, subid);
+ performDeletion(&object, DROP_CASCADE,
+ PERFORM_DELETION_INTERNAL |
+ PERFORM_DELETION_SKIP_ORIGINAL);
+
+ ereport(NOTICE,
+ errmsg("dropped conflict log table \"%s\" for subscription \"%s\"",
+ get_qualified_objname(PG_CONFLICT_NAMESPACE, conflictrelname),
+ subname));
+ }
/* Remove any associated relation synchronization states. */
RemoveSubscriptionRel(subid, InvalidOid);
@@ -3534,146 +3552,3 @@ defGetStreamingMode(DefElem *def)
def->defname)));
return LOGICALREP_STREAM_OFF; /* keep compiler quiet */
}
-
-/*
- * Builds the TupleDesc for the conflict log table.
- */
-static TupleDesc
-create_conflict_log_table_tupdesc(void)
-{
- TupleDesc tupdesc;
-
- tupdesc = CreateTemplateTupleDesc(MAX_CONFLICT_ATTR_NUM);
-
- for (int i = 0; i < MAX_CONFLICT_ATTR_NUM; i++)
- {
- Oid type_oid = ConflictLogSchema[i].atttypid;
-
- /*
- * Special handling for the JSON array type for proper
- * TupleDescInitEntry call.
- */
- if (type_oid == JSONARRAYOID)
- type_oid = get_array_type(JSONOID);
-
- TupleDescInitEntry(tupdesc, i + 1,
- ConflictLogSchema[i].attname,
- type_oid,
- -1, 0);
- }
-
- TupleDescFinalize(tupdesc);
-
- return tupdesc;
-}
-
-/*
- * Create a structured conflict log table for a subscription.
- *
- * The table is created within the system-managed 'pg_conflict' namespace to
- * prevent users from manually dropping or altering it. This also prevents
- * accidental name collisions with user-created tables with the same name.
- *
- * The table name is generated automatically using the subscription's OID
- * (e.g., "pg_conflict_log_<subid>") to ensure uniqueness within the cluster
- * and to avoid collisions during subscription renames.
- */
-static Oid
-create_conflict_log_table(Oid subid, char *subname, Oid subowner)
-{
- TupleDesc tupdesc;
- Oid relid;
- ObjectAddress myself;
- ObjectAddress subaddr;
- char relname[NAMEDATALEN];
-
- snprintf(relname, NAMEDATALEN, "pg_conflict_log_%u", subid);
-
- /*
- * Check for an existing table with the sname name in the pg_conflict namespace.
- * A collision should not occur under normal operation, but we must handle cases
- * where a table has been created manually.
- */
- if (OidIsValid(get_relname_relid(relname, PG_CONFLICT_NAMESPACE)))
- ereport(ERROR,
- (errcode(ERRCODE_DUPLICATE_TABLE),
- errmsg("conflict log table pg_conflict.\"%s\" already exists", relname),
- errhint("A table with the same name already exists. "
- "To proceed, drop the existing table and retry.")));
-
- /* Build the tuple descriptor for the new table. */
- tupdesc = create_conflict_log_table_tupdesc();
-
- /* Create conflict log table. */
- relid = heap_create_with_catalog(relname,
- PG_CONFLICT_NAMESPACE,
- 0, /* tablespace */
- InvalidOid, /* relid */
- InvalidOid, /* reltypeid */
- InvalidOid, /* reloftypeid */
- subowner,
- HEAP_TABLE_AM_OID,
- tupdesc,
- NIL,
- RELKIND_RELATION,
- RELPERSISTENCE_PERMANENT,
- false, /* shared_relation */
- false, /* mapped_relation */
- ONCOMMIT_NOOP,
- (Datum) 0, /* reloptions */
- false, /* use_user_acl */
- true, /* allow_system_table_mods */
- true, /* is_internal */
- InvalidOid, /* relrewrite */
- NULL); /* typaddress */
-
- /*
- * Establish an internal dependency between the conflict log table and
- * the subscription.
- *
- * We use DEPENDENCY_INTERNAL to signify that the table's lifecycle is
- * strictly tied to the subscription, similar to how a TOAST table relates
- * to its main table or a sequence relates to an identity column.
- *
- * This ensures the conflict log table is automatically reaped during a
- * DROP SUBSCRIPTION via performDeletion().
- */
- ObjectAddressSet(myself, RelationRelationId, relid);
- ObjectAddressSet(subaddr, SubscriptionRelationId, subid);
- recordDependencyOn(&myself, &subaddr, DEPENDENCY_INTERNAL);
-
- /* Release tuple descriptor memory. */
- FreeTupleDesc(tupdesc);
-
- ereport(NOTICE,
- (errmsg("created conflict log table \"%s\" for subscription \"%s\"",
- get_qualified_objname(PG_CONFLICT_NAMESPACE, relname),
- subname)));
-
- return relid;
-}
-
-/*
- * GetLogDestination
- *
- * Convert string to enum by comparing against standardized labels.
- */
-ConflictLogDest
-GetLogDestination(const char *dest)
-{
- /* Empty string or NULL defaults to LOG. */
- if (dest == NULL || dest[0] == '\0' || pg_strcasecmp(dest, "log") == 0)
- return CONFLICT_LOG_DEST_LOG;
-
- if (pg_strcasecmp(dest, "table") == 0)
- return CONFLICT_LOG_DEST_TABLE;
-
- if (pg_strcasecmp(dest, "all") == 0)
- return CONFLICT_LOG_DEST_ALL;
-
- /* Unrecognized string. */
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("unrecognized conflict_log_destination value: \"%s\"", dest),
- errhint("Valid values are \"log\", \"table\", and \"all\".")));
-}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index adf6b0f01d9..ee1687e8676 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2461,7 +2461,7 @@ truncate_check_rel(Oid relid, Form_pg_class reltuple)
* to permit users to manually prune these logs to manage disk space.
*/
if (!allowSystemTableMods && IsSystemClass(relid, reltuple) &&
- !IsConflictClass(reltuple)
+ !IsConflictLogTableClass(reltuple)
&& (!IsBinaryUpgrade ||
(relid != LargeObjectRelationId &&
relid != LargeObjectMetadataRelationId)))
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 345640fe41d..d6adffef001 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1201,7 +1201,7 @@ CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation,
if (IsConflictNamespace(RelationGetNamespace(resultRel)) &&
operation != CMD_DELETE)
ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot modify or insert data into conflict log table \"%s\"",
RelationGetRelationName(resultRel)),
errdetail("Conflict log tables are system-managed and only support cleanup via DELETE or TRUNCATE.")));
@@ -1279,8 +1279,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
/*
* Conflict log tables are managed by the system to record logical
- * replication conflicts. We do not allow locking rows in CONFLICT
- * relations.
+ * replication conflicts.
*/
if (IsConflictNamespace(RelationGetNamespace(rel)))
ereport(ERROR,
diff --git a/src/backend/replication/logical/conflict.c b/src/backend/replication/logical/conflict.c
index d038e265ca9..da7586185ff 100644
--- a/src/backend/replication/logical/conflict.c
+++ b/src/backend/replication/logical/conflict.c
@@ -17,6 +17,11 @@
#include "access/commit_ts.h"
#include "access/genam.h"
#include "access/tableam.h"
+#include "catalog/dependency.h"
+#include "catalog/heap.h"
+#include "catalog/pg_am.h"
+#include "catalog/pg_namespace.h"
+#include "commands/subscriptioncmds.h"
#include "executor/executor.h"
#include "pgstat.h"
#include "replication/conflict.h"
@@ -24,12 +29,26 @@
#include "storage/lmgr.h"
#include "utils/lsyscache.h"
+/*
+ * String representations for the supported conflict logging destinations.
+ */
const char *const ConflictLogDestNames[] = {
[CONFLICT_LOG_DEST_LOG] = "log",
[CONFLICT_LOG_DEST_TABLE] = "table",
[CONFLICT_LOG_DEST_ALL] = "all"
};
+StaticAssertDecl(lengthof(ConflictLogDestNames) == 3,
+ "ConflictLogDestNames length mismatch");
+
+ /*
+ * Schema definition for conflict log tables.
+ *
+ * Defines the fixed schema of the per-subscription conflict log table created
+ * in the pg_conflict namespace. Each entry specifies the column name and its
+ * type OID; the table is created in this column order by
+ * create_conflict_log_table().
+ */
const ConflictLogColumnDef ConflictLogSchema[] = {
{ .attname = "relid", .atttypid = OIDOID },
{ .attname = "schemaname", .atttypid = TEXTOID },
@@ -39,15 +58,14 @@ const ConflictLogColumnDef ConflictLogSchema[] = {
{ .attname = "remote_commit_lsn",.atttypid = LSNOID },
{ .attname = "remote_commit_ts", .atttypid = TIMESTAMPTZOID },
{ .attname = "remote_origin", .atttypid = TEXTOID },
- { .attname = "replica_identity", .atttypid = JSONOID },
{ .attname = "remote_tuple", .atttypid = JSONOID },
+ { .attname = "replica_identity", .atttypid = JSONOID },
{ .attname = "local_conflicts", .atttypid = JSONARRAYOID }
};
-StaticAssertDecl(lengthof(ConflictLogSchema) == MAX_CONFLICT_ATTR_NUM,
+StaticAssertDecl(lengthof(ConflictLogSchema) == NUM_CONFLICT_ATTRS,
"ConflictLogSchema length mismatch");
-StaticAssertDecl(lengthof(ConflictLogDestNames) == 3,
- "ConflictLogDestNames length mismatch");
+
static const char *const ConflictTypeNames[] = {
[CT_INSERT_EXISTS] = "insert_exists",
@@ -79,6 +97,150 @@ static void get_tuple_desc(EState *estate, ResultRelInfo *relinfo,
static char *build_index_value_desc(EState *estate, Relation localrel,
TupleTableSlot *slot, Oid indexoid);
+/*
+ * Builds the TupleDesc for the conflict log table.
+ */
+static TupleDesc
+create_conflict_log_table_tupdesc(void)
+{
+ TupleDesc tupdesc;
+
+ tupdesc = CreateTemplateTupleDesc(NUM_CONFLICT_ATTRS);
+
+ for (int i = 0; i < NUM_CONFLICT_ATTRS; i++)
+ {
+ Oid type_oid = ConflictLogSchema[i].atttypid;
+
+ /*
+ * Special handling for the JSON array type for proper
+ * TupleDescInitEntry call.
+ */
+ if (type_oid == JSONARRAYOID)
+ type_oid = get_array_type(JSONOID);
+
+ TupleDescInitEntry(tupdesc, i + 1,
+ ConflictLogSchema[i].attname,
+ type_oid,
+ -1, 0);
+ }
+
+ TupleDescFinalize(tupdesc);
+
+ return tupdesc;
+}
+
+/*
+ * Create a structured conflict log table for a subscription.
+ *
+ * The table is created within the system-managed 'pg_conflict' namespace to
+ * prevent users from manually dropping or altering it. This also prevents
+ * accidental name collisions with user-created tables with the same name.
+ *
+ * The table name is generated automatically using the subscription's OID
+ * (e.g., "pg_conflict_log_for_subid_<subid>") to ensure uniqueness within the
+ * cluster and to avoid collisions during subscription renames.
+ */
+Oid
+create_conflict_log_table(Oid subid, char *subname, Oid subowner)
+{
+ TupleDesc tupdesc;
+ Oid relid;
+ ObjectAddress myself;
+ ObjectAddress subaddr;
+ char relname[NAMEDATALEN];
+
+ snprintf(relname, NAMEDATALEN, "pg_conflict_log_for_subid_%u", subid);
+
+ /*
+ * Check for an existing table with the same name in the pg_conflict namespace.
+ * A collision should not occur under normal operation, but we must handle cases
+ * where a table has been created manually when allow_system_tables_mods is
+ * ON.
+ */
+ if (OidIsValid(get_relname_relid(relname, PG_CONFLICT_NAMESPACE)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_TABLE),
+ errmsg("conflict log table pg_conflict.\"%s\" already exists", relname),
+ errhint("To proceed, drop the existing table and retry.")));
+
+ /* Build the tuple descriptor for the new table. */
+ tupdesc = create_conflict_log_table_tupdesc();
+
+ /* Create conflict log table. */
+ relid = heap_create_with_catalog(relname,
+ PG_CONFLICT_NAMESPACE,
+ 0, /* tablespace */
+ InvalidOid, /* relid */
+ InvalidOid, /* reltypeid */
+ InvalidOid, /* reloftypeid */
+ subowner,
+ HEAP_TABLE_AM_OID,
+ tupdesc,
+ NIL,
+ RELKIND_RELATION,
+ RELPERSISTENCE_PERMANENT,
+ false, /* shared_relation */
+ false, /* mapped_relation */
+ ONCOMMIT_NOOP,
+ (Datum) 0, /* reloptions */
+ false, /* use_user_acl */
+ true, /* allow_system_table_mods */
+ true, /* is_internal */
+ InvalidOid, /* relrewrite */
+ NULL); /* typaddress */
+ Assert(relid != InvalidOid);
+
+ /*
+ * Establish an internal dependency between the conflict log table and
+ * the subscription.
+ *
+ * We use DEPENDENCY_INTERNAL to signify that the table's lifecycle is
+ * strictly tied to the subscription, similar to how a TOAST table relates
+ * to its main table or a sequence relates to an identity column.
+ *
+ * This ensures the conflict log table is automatically reaped during a
+ * DROP SUBSCRIPTION via performDeletion().
+ */
+ ObjectAddressSet(myself, RelationRelationId, relid);
+ ObjectAddressSet(subaddr, SubscriptionRelationId, subid);
+ recordDependencyOn(&myself, &subaddr, DEPENDENCY_INTERNAL);
+
+ /* Release tuple descriptor memory. */
+ FreeTupleDesc(tupdesc);
+
+ ereport(NOTICE,
+ (errmsg("created conflict log table \"%s\" for subscription \"%s\"",
+ get_qualified_objname(PG_CONFLICT_NAMESPACE, relname),
+ subname)));
+
+ return relid;
+}
+
+/*
+ * GetLogDestination
+ *
+ * Convert string to enum by comparing against standardized labels.
+ */
+ConflictLogDest
+GetLogDestination(const char *dest)
+{
+ /* Empty string or NULL defaults to LOG. */
+ if (dest == NULL || dest[0] == '\0' || pg_strcasecmp(dest, "log") == 0)
+ return CONFLICT_LOG_DEST_LOG;
+
+ if (pg_strcasecmp(dest, "table") == 0)
+ return CONFLICT_LOG_DEST_TABLE;
+
+ if (pg_strcasecmp(dest, "all") == 0)
+ return CONFLICT_LOG_DEST_ALL;
+
+ /* Unrecognized string. */
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unrecognized conflict_log_destination value: \"%s\"", dest),
+ errhint("Valid values are \"log\", \"table\", and \"all\".")));
+}
+
/*
* Get the xmin and commit timestamp data (origin and timestamp) associated
* with the provided local row.
diff --git a/src/include/catalog/catalog.h b/src/include/catalog/catalog.h
index 8193229f2e2..cd05974b86c 100644
--- a/src/include/catalog/catalog.h
+++ b/src/include/catalog/catalog.h
@@ -25,7 +25,7 @@ extern bool IsInplaceUpdateRelation(Relation relation);
extern bool IsSystemClass(Oid relid, Form_pg_class reltuple);
extern bool IsToastClass(Form_pg_class reltuple);
-extern bool IsConflictClass(Form_pg_class reltuple);
+extern bool IsConflictLogTableClass(Form_pg_class reltuple);
extern bool IsCatalogRelationOid(Oid relid);
extern bool IsCatalogTextUniqueIndexOid(Oid relid);
diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h
index 5f214d3586b..cc31b4d00bc 100644
--- a/src/include/catalog/pg_subscription.h
+++ b/src/include/catalog/pg_subscription.h
@@ -97,6 +97,14 @@ CATALOG(pg_subscription,6100,SubscriptionRelationId) BKI_SHARED_RELATION BKI_ROW
Oid subconflictlogrelid; /* Relid of the conflict log table. */
#ifdef CATALOG_VARLEN /* variable-length fields start here */
+ /*
+ * Strategy for logging replication conflicts:
+ * 'log' - server log only,
+ * 'table' - conflict log table only,
+ * 'all' - both log and table.
+ */
+ text subconflictlogdest BKI_FORCE_NOT_NULL;
+
/* Connection string to the publisher */
text subconninfo; /* Set if connecting with connection string */
@@ -112,14 +120,6 @@ CATALOG(pg_subscription,6100,SubscriptionRelationId) BKI_SHARED_RELATION BKI_ROW
/* List of publications subscribed to */
text subpublications[1] BKI_FORCE_NOT_NULL;
- /*
- * Strategy for logging replication conflicts:
- * 'log' - server log only,
- * 'table' - conflict log table only,
- * 'all' - both log and table.
- */
- text subconflictlogdest BKI_FORCE_NOT_NULL;
-
/* Only publish data originating from the specified origin */
text suborigin BKI_DEFAULT(LOGICALREP_ORIGIN_ANY);
#endif
diff --git a/src/include/commands/subscriptioncmds.h b/src/include/commands/subscriptioncmds.h
index a895127f8fe..f30ac546e97 100644
--- a/src/include/commands/subscriptioncmds.h
+++ b/src/include/commands/subscriptioncmds.h
@@ -37,6 +37,4 @@ extern void CheckSubDeadTupleRetention(bool check_guc, bool sub_disabled,
bool retention_active,
bool max_retention_set);
-extern ConflictLogDest GetLogDestination(const char *dest);
-
#endif /* SUBSCRIPTIONCMDS_H */
diff --git a/src/include/replication/conflict.h b/src/include/replication/conflict.h
index 00a9cbec264..54bc97c183a 100644
--- a/src/include/replication/conflict.h
+++ b/src/include/replication/conflict.h
@@ -94,6 +94,11 @@ typedef enum ConflictLogDest
CONFLICT_LOG_DEST_ALL /* Both log and table */
} ConflictLogDest;
+#define CONFLICTS_LOGGED_TO_TABLE(dest) \
+ ((dest == CONFLICT_LOG_DEST_TABLE) || (dest == CONFLICT_LOG_DEST_ALL))
+#define CONFLICTS_LOGGED_TO_FILE(dest) \
+ ((dest == CONFLICT_LOG_DEST_LOG) || (dest == CONFLICT_LOG_DEST_ALL))
+
/*
* Array mapping for converting internal enum to string.
*/
@@ -109,8 +114,10 @@ typedef struct ConflictLogColumnDef
/* The single source of truth for the conflict log table schema */
extern PGDLLIMPORT const ConflictLogColumnDef ConflictLogSchema[];
-#define MAX_CONFLICT_ATTR_NUM 11
+#define NUM_CONFLICT_ATTRS 11
+extern Oid create_conflict_log_table(Oid subid, char *subname, Oid subowner);
+extern ConflictLogDest GetLogDestination(const char *dest);
extern bool GetTupleTransactionInfo(TupleTableSlot *localslot,
TransactionId *xmin,
ReplOriginId *localorigin,
diff --git a/src/test/regress/expected/subscription.out b/src/test/regress/expected/subscription.out
index 85f9c60f449..1ba2f96d781 100644
--- a/src/test/regress/expected/subscription.out
+++ b/src/test/regress/expected/subscription.out
@@ -124,18 +124,18 @@ CREATE SUBSCRIPTION regress_testsub4 CONNECTION 'dbname=regress_doesnotexist' PU
WARNING: subscription was created, but is not connected
HINT: To initiate replication, you must manually create the replication slot, enable the subscription, and alter the subscription to refresh publications.
\dRs+ regress_testsub4
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description | Conflict log destination | Conflict log table
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+-----------------------------+------------------+------------+-------------+--------------------------+--------------------
- regress_testsub4 | regress_subscription_user | f | {testpub} | f | parallel | d | f | none | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist | -1 | 0/00000000 | | log | -
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+-----------------------------+------------------+------------+-------------
+ regress_testsub4 | regress_subscription_user | f | {testpub} | f | parallel | d | f | none | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist | -1 | 0/00000000 |
(1 row)
ALTER SUBSCRIPTION regress_testsub4 SET (origin = any);
\dRs+ regress_testsub4
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description | Conflict log destination | Conflict log table
-------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+-----------------------------+------------------+------------+-------------+--------------------------+--------------------
- regress_testsub4 | regress_subscription_user | f | {testpub} | f | parallel | d | f | any | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist | -1 | 0/00000000 | | log | -
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description
+------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+-----------------------------+------------------+------------+-------------
+ regress_testsub4 | regress_subscription_user | f | {testpub} | f | parallel | d | f | any | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist | -1 | 0/00000000 |
(1 row)
DROP SUBSCRIPTION regress_testsub3;
@@ -200,10 +200,10 @@ ALTER SUBSCRIPTION regress_testsub CONNECTION 'foobar';
ERROR: invalid connection string syntax: missing "=" after "foobar" in connection info string
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description | Conflict log destination | Conflict log table
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+-----------------------------+------------------+------------+-------------------+--------------------------+--------------------
- regress_testsub | regress_subscription_user | f | {testpub} | f | parallel | d | f | any | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist | -1 | 0/00000000 | test subscription | log | -
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+-----------------------------+------------------+------------+-------------------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | parallel | d | f | any | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist | -1 | 0/00000000 | test subscription
(1 row)
ALTER SUBSCRIPTION regress_testsub SET PUBLICATION testpub2, testpub3 WITH (refresh = false);
@@ -212,10 +212,10 @@ ALTER SUBSCRIPTION regress_testsub SET (slot_name = 'newname');
ALTER SUBSCRIPTION regress_testsub SET (password_required = false);
ALTER SUBSCRIPTION regress_testsub SET (run_as_owner = true);
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description | Conflict log destination | Conflict log table
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+------------------------------+------------------+------------+-------------------+--------------------------+--------------------
- regress_testsub | regress_subscription_user | f | {testpub2,testpub3} | f | parallel | d | f | any | f | t | f | | f | 0 | f | off | dbname=regress_doesnotexist2 | -1 | 0/00000000 | test subscription | log | -
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+------------------------------+------------------+------------+-------------------
+ regress_testsub | regress_subscription_user | f | {testpub2,testpub3} | f | parallel | d | f | any | f | t | f | | f | 0 | f | off | dbname=regress_doesnotexist2 | -1 | 0/00000000 | test subscription
(1 row)
ALTER SUBSCRIPTION regress_testsub SET (password_required = true);
@@ -231,10 +231,10 @@ ERROR: unrecognized subscription parameter: "create_slot"
-- ok
ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/12345');
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description | Conflict log destination | Conflict log table
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+------------------------------+------------------+------------+-------------------+--------------------------+--------------------
- regress_testsub | regress_subscription_user | f | {testpub2,testpub3} | f | parallel | d | f | any | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist2 | -1 | 0/00012345 | test subscription | log | -
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+------------------------------+------------------+------------+-------------------
+ regress_testsub | regress_subscription_user | f | {testpub2,testpub3} | f | parallel | d | f | any | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist2 | -1 | 0/00012345 | test subscription
(1 row)
-- ok - with lsn = NONE
@@ -243,10 +243,10 @@ ALTER SUBSCRIPTION regress_testsub SKIP (lsn = NONE);
ALTER SUBSCRIPTION regress_testsub SKIP (lsn = '0/0');
ERROR: invalid WAL location (LSN): 0/0
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description | Conflict log destination | Conflict log table
------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+------------------------------+------------------+------------+-------------------+--------------------------+--------------------
- regress_testsub | regress_subscription_user | f | {testpub2,testpub3} | f | parallel | d | f | any | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist2 | -1 | 0/00000000 | test subscription | log | -
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description
+-----------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+------------------------------+------------------+------------+-------------------
+ regress_testsub | regress_subscription_user | f | {testpub2,testpub3} | f | parallel | d | f | any | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist2 | -1 | 0/00000000 | test subscription
(1 row)
BEGIN;
@@ -282,10 +282,10 @@ ALTER SUBSCRIPTION regress_testsub_foo SET (wal_receiver_timeout = '80s');
ALTER SUBSCRIPTION regress_testsub_foo SET (wal_receiver_timeout = 'foobar');
ERROR: invalid value for parameter "wal_receiver_timeout": "foobar"
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description | Conflict log destination | Conflict log table
----------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+------------------------------+------------------+------------+-------------------+--------------------------+--------------------
- regress_testsub_foo | regress_subscription_user | f | {testpub2,testpub3} | f | parallel | d | f | any | t | f | f | | f | 0 | f | local | dbname=regress_doesnotexist2 | 80s | 0/00000000 | test subscription | log | -
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description
+---------------------+---------------------------+---------+---------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+------------------------------+------------------+------------+-------------------
+ regress_testsub_foo | regress_subscription_user | f | {testpub2,testpub3} | f | parallel | d | f | any | t | f | f | | f | 0 | f | local | dbname=regress_doesnotexist2 | 80s | 0/00000000 | test subscription
(1 row)
-- rename back to keep the rest simple
@@ -314,19 +314,19 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
WARNING: subscription was created, but is not connected
HINT: To initiate replication, you must manually create the replication slot, enable the subscription, and alter the subscription to refresh publications.
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description | Conflict log destination | Conflict log table
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+-----------------------------+------------------+------------+-------------+--------------------------+--------------------
- regress_testsub | regress_subscription_user | f | {testpub} | t | parallel | d | f | any | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist | -1 | 0/00000000 | | log | -
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+-----------------------------+------------------+------------+-------------
+ regress_testsub | regress_subscription_user | f | {testpub} | t | parallel | d | f | any | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist | -1 | 0/00000000 |
(1 row)
ALTER SUBSCRIPTION regress_testsub SET (binary = false);
ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description | Conflict log destination | Conflict log table
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+-----------------------------+------------------+------------+-------------+--------------------------+--------------------
- regress_testsub | regress_subscription_user | f | {testpub} | f | parallel | d | f | any | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist | -1 | 0/00000000 | | log | -
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+-----------------------------+------------------+------------+-------------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | parallel | d | f | any | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist | -1 | 0/00000000 |
(1 row)
DROP SUBSCRIPTION regress_testsub;
@@ -338,27 +338,27 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
WARNING: subscription was created, but is not connected
HINT: To initiate replication, you must manually create the replication slot, enable the subscription, and alter the subscription to refresh publications.
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description | Conflict log destination | Conflict log table
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+-----------------------------+------------------+------------+-------------+--------------------------+--------------------
- regress_testsub | regress_subscription_user | f | {testpub} | f | on | d | f | any | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist | -1 | 0/00000000 | | log | -
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+-----------------------------+------------------+------------+-------------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | on | d | f | any | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist | -1 | 0/00000000 |
(1 row)
ALTER SUBSCRIPTION regress_testsub SET (streaming = parallel);
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description | Conflict log destination | Conflict log table
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+-----------------------------+------------------+------------+-------------+--------------------------+--------------------
- regress_testsub | regress_subscription_user | f | {testpub} | f | parallel | d | f | any | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist | -1 | 0/00000000 | | log | -
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+-----------------------------+------------------+------------+-------------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | parallel | d | f | any | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist | -1 | 0/00000000 |
(1 row)
ALTER SUBSCRIPTION regress_testsub SET (streaming = false);
ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description | Conflict log destination | Conflict log table
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+-----------------------------+------------------+------------+-------------+--------------------------+--------------------
- regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | f | any | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist | -1 | 0/00000000 | | log | -
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+-----------------------------+------------------+------------+-------------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | f | any | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist | -1 | 0/00000000 |
(1 row)
-- fail - publication already exists
@@ -373,10 +373,10 @@ ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refr
ALTER SUBSCRIPTION regress_testsub ADD PUBLICATION testpub1, testpub2 WITH (refresh = false);
ERROR: publication "testpub1" is already in subscription "regress_testsub"
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description | Conflict log destination | Conflict log table
------------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+-----------------------------+------------------+------------+-------------+--------------------------+--------------------
- regress_testsub | regress_subscription_user | f | {testpub,testpub1,testpub2} | f | off | d | f | any | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist | -1 | 0/00000000 | | log | -
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description
+-----------------+---------------------------+---------+-----------------------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+-----------------------------+------------------+------------+-------------
+ regress_testsub | regress_subscription_user | f | {testpub,testpub1,testpub2} | f | off | d | f | any | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist | -1 | 0/00000000 |
(1 row)
-- fail - publication used more than once
@@ -391,10 +391,10 @@ ERROR: publication "testpub3" is not in subscription "regress_testsub"
-- ok - delete publications
ALTER SUBSCRIPTION regress_testsub DROP PUBLICATION testpub1, testpub2 WITH (refresh = false);
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description | Conflict log destination | Conflict log table
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+-----------------------------+------------------+------------+-------------+--------------------------+--------------------
- regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | f | any | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist | -1 | 0/00000000 | | log | -
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+-----------------------------+------------------+------------+-------------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | off | d | f | any | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist | -1 | 0/00000000 |
(1 row)
DROP SUBSCRIPTION regress_testsub;
@@ -430,19 +430,19 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
WARNING: subscription was created, but is not connected
HINT: To initiate replication, you must manually create the replication slot, enable the subscription, and alter the subscription to refresh publications.
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description | Conflict log destination | Conflict log table
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+-----------------------------+------------------+------------+-------------+--------------------------+--------------------
- regress_testsub | regress_subscription_user | f | {testpub} | f | parallel | p | f | any | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist | -1 | 0/00000000 | | log | -
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+-----------------------------+------------------+------------+-------------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | parallel | p | f | any | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist | -1 | 0/00000000 |
(1 row)
-- we can alter streaming when two_phase enabled
ALTER SUBSCRIPTION regress_testsub SET (streaming = true);
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description | Conflict log destination | Conflict log table
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+-----------------------------+------------------+------------+-------------+--------------------------+--------------------
- regress_testsub | regress_subscription_user | f | {testpub} | f | on | p | f | any | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist | -1 | 0/00000000 | | log | -
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+-----------------------------+------------------+------------+-------------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | on | p | f | any | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist | -1 | 0/00000000 |
(1 row)
ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -452,10 +452,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
WARNING: subscription was created, but is not connected
HINT: To initiate replication, you must manually create the replication slot, enable the subscription, and alter the subscription to refresh publications.
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description | Conflict log destination | Conflict log table
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+-----------------------------+------------------+------------+-------------+--------------------------+--------------------
- regress_testsub | regress_subscription_user | f | {testpub} | f | on | p | f | any | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist | -1 | 0/00000000 | | log | -
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+-----------------------------+------------------+------------+-------------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | on | p | f | any | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist | -1 | 0/00000000 |
(1 row)
ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -468,18 +468,18 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
WARNING: subscription was created, but is not connected
HINT: To initiate replication, you must manually create the replication slot, enable the subscription, and alter the subscription to refresh publications.
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description | Conflict log destination | Conflict log table
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+-----------------------------+------------------+------------+-------------+--------------------------+--------------------
- regress_testsub | regress_subscription_user | f | {testpub} | f | parallel | d | f | any | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist | -1 | 0/00000000 | | log | -
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+-----------------------------+------------------+------------+-------------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | parallel | d | f | any | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist | -1 | 0/00000000 |
(1 row)
ALTER SUBSCRIPTION regress_testsub SET (disable_on_error = true);
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description | Conflict log destination | Conflict log table
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+-----------------------------+------------------+------------+-------------+--------------------------+--------------------
- regress_testsub | regress_subscription_user | f | {testpub} | f | parallel | d | t | any | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist | -1 | 0/00000000 | | log | -
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+-----------------------------+------------------+------------+-------------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | parallel | d | t | any | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist | -1 | 0/00000000 |
(1 row)
ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -492,10 +492,10 @@ CREATE SUBSCRIPTION regress_testsub CONNECTION 'dbname=regress_doesnotexist' PUB
WARNING: subscription was created, but is not connected
HINT: To initiate replication, you must manually create the replication slot, enable the subscription, and alter the subscription to refresh publications.
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description | Conflict log destination | Conflict log table
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+-----------------------------+------------------+------------+-------------+--------------------------+--------------------
- regress_testsub | regress_subscription_user | f | {testpub} | f | parallel | d | f | any | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist | -1 | 0/00000000 | | log | -
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+-----------------------------+------------------+------------+-------------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | parallel | d | f | any | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist | -1 | 0/00000000 |
(1 row)
ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -509,19 +509,19 @@ NOTICE: max_retention_duration is ineffective when retain_dead_tuples is disabl
WARNING: subscription was created, but is not connected
HINT: To initiate replication, you must manually create the replication slot, enable the subscription, and alter the subscription to refresh publications.
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description | Conflict log destination | Conflict log table
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+-----------------------------+------------------+------------+-------------+--------------------------+--------------------
- regress_testsub | regress_subscription_user | f | {testpub} | f | parallel | d | f | any | t | f | f | | f | 1000 | f | off | dbname=regress_doesnotexist | -1 | 0/00000000 | | log | -
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+-----------------------------+------------------+------------+-------------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | parallel | d | f | any | t | f | f | | f | 1000 | f | off | dbname=regress_doesnotexist | -1 | 0/00000000 |
(1 row)
-- ok
ALTER SUBSCRIPTION regress_testsub SET (max_retention_duration = 0);
\dRs+
- List of subscriptions
- Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description | Conflict log destination | Conflict log table
------------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+-----------------------------+------------------+------------+-------------+--------------------------+--------------------
- regress_testsub | regress_subscription_user | f | {testpub} | f | parallel | d | f | any | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist | -1 | 0/00000000 | | log | -
+ List of subscriptions
+ Name | Owner | Enabled | Publication | Binary | Streaming | Two-phase commit | Disable on error | Origin | Password required | Run as owner? | Failover | Server | Retain dead tuples | Max retention duration | Retention active | Synchronous commit | Conninfo | Receiver timeout | Skip LSN | Description
+-----------------+---------------------------+---------+-------------+--------+-----------+------------------+------------------+--------+-------------------+---------------+----------+--------+--------------------+------------------------+------------------+--------------------+-----------------------------+------------------+------------+-------------
+ regress_testsub | regress_subscription_user | f | {testpub} | f | parallel | d | f | any | t | f | f | | f | 0 | f | off | dbname=regress_doesnotexist | -1 | 0/00000000 |
(1 row)
ALTER SUBSCRIPTION regress_testsub SET (slot_name = NONE);
@@ -585,7 +585,7 @@ SET client_min_messages = WARNING;
CREATE SUBSCRIPTION regress_conflict_fail CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, conflict_log_destination = 'invalid');
ERROR: unrecognized conflict_log_destination value: "invalid"
HINT: Valid values are "log", "table", and "all".
--- verify subconflictlogdest is 'log' and relid is 0 (InvalidOid) for default case
+-- verify subconflictlogdest is 'log' and subconflictlogrelid is 0 (InvalidOid) for default case
CREATE SUBSCRIPTION regress_conflict_log_default CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false);
WARNING: subscription was created, but is not connected
HINT: To initiate replication, you must manually create the replication slot, enable the subscription, and alter the subscription to refresh publications.
@@ -607,11 +607,11 @@ FROM pg_subscription WHERE subname = 'regress_conflict_empty_str';
regress_conflict_empty_str | log | 0
(1 row)
--- this should generate an internal conflict log table named pg_conflict_log_$subid$
+-- this should generate an internal conflict log table named pg_conflict_log_for_subid_$subid$
CREATE SUBSCRIPTION regress_conflict_test1 CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, conflict_log_destination = 'table');
WARNING: subscription was created, but is not connected
HINT: To initiate replication, you must manually create the replication slot, enable the subscription, and alter the subscription to refresh publications.
--- check metadata in pg_subscription: destination should be 'table' and relid valid
+-- check metadata in pg_subscription: destination should be 'table' and subconflictlogrelid valid
SELECT subname, subconflictlogdest, subconflictlogrelid > 0 AS has_relid
FROM pg_subscription WHERE subname = 'regress_conflict_test1';
subname | subconflictlogdest | has_relid
@@ -623,7 +623,7 @@ FROM pg_subscription WHERE subname = 'regress_conflict_test1';
-- and it is located in the 'pg_conflict' namespace
SELECT n.nspname, (c.oid = s.subconflictlogrelid) AS "oid_matches"
FROM pg_class c
-JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+JOIN pg_subscription s ON c.relname = 'pg_conflict_log_for_subid_' || s.oid
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE s.subname = 'regress_conflict_test1';
nspname | oid_matches
@@ -635,7 +635,7 @@ WHERE s.subname = 'regress_conflict_test1';
SELECT a.attnum, a.attname
FROM pg_attribute a
JOIN pg_class c ON a.attrelid = c.oid
-JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+JOIN pg_subscription s ON c.relname = 'pg_conflict_log_for_subid_' || s.oid
WHERE s.subname = 'regress_conflict_test1' AND a.attnum > 0
ORDER BY a.attnum;
attnum | attname
@@ -648,8 +648,8 @@ WHERE s.subname = 'regress_conflict_test1' AND a.attnum > 0
6 | remote_commit_lsn
7 | remote_commit_ts
8 | remote_origin
- 9 | replica_identity
- 10 | remote_tuple
+ 9 | remote_tuple
+ 10 | replica_identity
11 | local_conflicts
(11 rows)
@@ -686,7 +686,7 @@ FROM pg_subscription WHERE subname = 'regress_conflict_test2';
(1 row)
-- transition from 'table' to 'log'
--- should drop the table and clear relid
+-- should drop the table and clear subconflictlogrelid
ALTER SUBSCRIPTION regress_conflict_test2 SET (conflict_log_destination = 'log');
SELECT subconflictlogdest, subconflictlogrelid
FROM pg_subscription WHERE subname = 'regress_conflict_test2';
@@ -698,7 +698,7 @@ FROM pg_subscription WHERE subname = 'regress_conflict_test2';
-- verify the physical table is gone
SELECT count(*)
FROM pg_class c
-JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+JOIN pg_subscription s ON c.relname = 'pg_conflict_log_for_subid_' || s.oid
WHERE s.subname = 'regress_conflict_test2';
count
-------
@@ -738,7 +738,7 @@ ALTER SUBSCRIPTION regress_conflict_test1 SET (conflict_log_destination = 'table
SET client_min_messages = NOTICE;
DO $$
BEGIN
- EXECUTE 'DROP TABLE ' || (SELECT 'pg_conflict.pg_conflict_log_' || oid FROM pg_subscription WHERE subname = 'regress_conflict_test1');
+ EXECUTE 'DROP TABLE ' || (SELECT 'pg_conflict.pg_conflict_log_for_subid_' || oid FROM pg_subscription WHERE subname = 'regress_conflict_test1');
EXCEPTION WHEN insufficient_privilege THEN
RAISE NOTICE 'captured expected error: insufficient_privilege';
END $$;
@@ -747,7 +747,8 @@ NOTICE: captured expected error: insufficient_privilege
ALTER SUBSCRIPTION regress_conflict_test1 DISABLE;
ALTER SUBSCRIPTION regress_conflict_test1 SET (slot_name = NONE);
-- Verify the table OID for reap check
-SELECT 'pg_conflict_log_' || oid AS internal_tablename FROM pg_subscription WHERE subname = 'regress_conflict_test1' \gset
+SELECT 'pg_conflict_log_for_subid_' || oid AS internal_tablename FROM pg_subscription WHERE subname = 'regress_conflict_test1' \gset
+SET client_min_messages = WARNING;
DROP SUBSCRIPTION regress_conflict_test1;
-- should return NULL, meaning the conflict log table was reaped via dependency
SELECT to_regclass(:'internal_tablename');
@@ -759,7 +760,6 @@ SELECT to_regclass(:'internal_tablename');
--
-- Additional Namespace and Table Protection Tests
--
-SET client_min_messages = WARNING;
-- Setup: Ensure we have a subscription with a conflict log table
CREATE SUBSCRIPTION regress_conflict_protection_test CONNECTION 'dbname=regress_doesnotexist'
PUBLICATION testpub WITH (connect = false, conflict_log_destination = 'table');
@@ -774,7 +774,7 @@ DECLARE
tab_name text;
BEGIN
SELECT 'pg_conflict.' || relname INTO tab_name
- FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+ FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_for_subid_' || s.oid
WHERE s.subname = 'regress_conflict_protection_test';
RAISE NOTICE 'Attempting ALTER TABLE on internal conflict log table';
@@ -792,14 +792,14 @@ DECLARE
tab_name text;
BEGIN
SELECT 'pg_conflict.' || relname INTO tab_name
- FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+ FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_for_subid_' || s.oid
WHERE s.subname = 'regress_conflict_protection_test';
EXECUTE 'INSERT INTO ' || tab_name || ' (relname) VALUES (''mytest'')';
-EXCEPTION WHEN insufficient_privilege THEN
- RAISE NOTICE 'captured expected error: insufficient_privilege during INSERT';
+EXCEPTION WHEN wrong_object_type THEN
+ RAISE NOTICE 'captured expected error: wrong_object_type during INSERT';
END $$;
-NOTICE: captured expected error: insufficient_privilege during INSERT
+NOTICE: captured expected error: wrong_object_type during INSERT
-- Test Manual UPDATE on conflict log table
-- This should fail because the table is system-managed
-- Hiding the OID in the error message by catching the exception
@@ -808,19 +808,19 @@ DECLARE
tab_name text;
BEGIN
SELECT 'pg_conflict.' || relname INTO tab_name
- FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+ FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_for_subid_' || s.oid
WHERE s.subname = 'regress_conflict_protection_test';
EXECUTE 'UPDATE ' || tab_name || ' SET relname = ''mytest'' ';
-EXCEPTION WHEN insufficient_privilege THEN
- RAISE NOTICE 'captured expected error: insufficient_privilege during UPDATE';
+EXCEPTION WHEN wrong_object_type THEN
+ RAISE NOTICE 'captured expected error: wrong_object_type during UPDATE';
END $$;
-NOTICE: captured expected error: insufficient_privilege during UPDATE
+NOTICE: captured expected error: wrong_object_type during UPDATE
-- Trying to perform TRUNCATE/DELETE on the internal conflict log table
-- This should be allowed so that user can perform cleanup
SELECT 'pg_conflict.' || relname AS conflict_tab
FROM pg_class c
-JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+JOIN pg_subscription s ON c.relname = 'pg_conflict_log_for_subid_' || s.oid
WHERE s.subname = 'regress_conflict_protection_test' \gset
TRUNCATE :conflict_tab;
DELETE FROM :conflict_tab;
@@ -828,13 +828,14 @@ DELETE FROM :conflict_tab;
-- This should fail as the namespace is reserved for conflict logs tables
CREATE TABLE pg_conflict.manual_table (id int);
ERROR: permission denied to create "pg_conflict.manual_table"
-DETAIL: System catalog modifications are currently disallowed.
+DETAIL: Conflict schema modifications are currently disallowed.
-- Moving an existing table into the pg_conflict namespace
-- Users should not be able to move their own tables within this namespace
CREATE TABLE public.test_move (id int);
ALTER TABLE public.test_move SET SCHEMA pg_conflict;
-ERROR: cannot move objects into or out of CONFLICT schema
+ERROR: cannot move objects into or out of pg_conflict schema
DROP TABLE public.test_move;
+SET client_min_messages = WARNING;
-- Clean up remaining test subscription
ALTER SUBSCRIPTION regress_conflict_log_default DISABLE;
ALTER SUBSCRIPTION regress_conflict_log_default SET (slot_name = NONE);
diff --git a/src/test/regress/sql/subscription.sql b/src/test/regress/sql/subscription.sql
index d155f24fdbb..76c07f64ef3 100644
--- a/src/test/regress/sql/subscription.sql
+++ b/src/test/regress/sql/subscription.sql
@@ -442,7 +442,7 @@ SET client_min_messages = WARNING;
-- fail - unrecognized parameter value
CREATE SUBSCRIPTION regress_conflict_fail CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, conflict_log_destination = 'invalid');
--- verify subconflictlogdest is 'log' and relid is 0 (InvalidOid) for default case
+-- verify subconflictlogdest is 'log' and subconflictlogrelid is 0 (InvalidOid) for default case
CREATE SUBSCRIPTION regress_conflict_log_default CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false);
SELECT subname, subconflictlogdest, subconflictlogrelid
FROM pg_subscription WHERE subname = 'regress_conflict_log_default';
@@ -452,10 +452,10 @@ CREATE SUBSCRIPTION regress_conflict_empty_str CONNECTION 'dbname=regress_doesno
SELECT subname, subconflictlogdest, subconflictlogrelid
FROM pg_subscription WHERE subname = 'regress_conflict_empty_str';
--- this should generate an internal conflict log table named pg_conflict_log_$subid$
+-- this should generate an internal conflict log table named pg_conflict_log_for_subid_$subid$
CREATE SUBSCRIPTION regress_conflict_test1 CONNECTION 'dbname=regress_doesnotexist' PUBLICATION testpub WITH (connect = false, conflict_log_destination = 'table');
--- check metadata in pg_subscription: destination should be 'table' and relid valid
+-- check metadata in pg_subscription: destination should be 'table' and subconflictlogrelid valid
SELECT subname, subconflictlogdest, subconflictlogrelid > 0 AS has_relid
FROM pg_subscription WHERE subname = 'regress_conflict_test1';
@@ -463,7 +463,7 @@ FROM pg_subscription WHERE subname = 'regress_conflict_test1';
-- and it is located in the 'pg_conflict' namespace
SELECT n.nspname, (c.oid = s.subconflictlogrelid) AS "oid_matches"
FROM pg_class c
-JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+JOIN pg_subscription s ON c.relname = 'pg_conflict_log_for_subid_' || s.oid
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE s.subname = 'regress_conflict_test1';
@@ -471,7 +471,7 @@ WHERE s.subname = 'regress_conflict_test1';
SELECT a.attnum, a.attname
FROM pg_attribute a
JOIN pg_class c ON a.attrelid = c.oid
-JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+JOIN pg_subscription s ON c.relname = 'pg_conflict_log_for_subid_' || s.oid
WHERE s.subname = 'regress_conflict_test1' AND a.attnum > 0
ORDER BY a.attnum;
@@ -499,7 +499,7 @@ SELECT subconflictlogdest, subconflictlogrelid = :old_relid AS relid_unchanged
FROM pg_subscription WHERE subname = 'regress_conflict_test2';
-- transition from 'table' to 'log'
--- should drop the table and clear relid
+-- should drop the table and clear subconflictlogrelid
ALTER SUBSCRIPTION regress_conflict_test2 SET (conflict_log_destination = 'log');
SELECT subconflictlogdest, subconflictlogrelid
FROM pg_subscription WHERE subname = 'regress_conflict_test2';
@@ -507,7 +507,7 @@ FROM pg_subscription WHERE subname = 'regress_conflict_test2';
-- verify the physical table is gone
SELECT count(*)
FROM pg_class c
-JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+JOIN pg_subscription s ON c.relname = 'pg_conflict_log_for_subid_' || s.oid
WHERE s.subname = 'regress_conflict_test2';
--
@@ -541,7 +541,7 @@ ALTER SUBSCRIPTION regress_conflict_test1 SET (conflict_log_destination = 'table
SET client_min_messages = NOTICE;
DO $$
BEGIN
- EXECUTE 'DROP TABLE ' || (SELECT 'pg_conflict.pg_conflict_log_' || oid FROM pg_subscription WHERE subname = 'regress_conflict_test1');
+ EXECUTE 'DROP TABLE ' || (SELECT 'pg_conflict.pg_conflict_log_for_subid_' || oid FROM pg_subscription WHERE subname = 'regress_conflict_test1');
EXCEPTION WHEN insufficient_privilege THEN
RAISE NOTICE 'captured expected error: insufficient_privilege';
END $$;
@@ -551,8 +551,9 @@ ALTER SUBSCRIPTION regress_conflict_test1 DISABLE;
ALTER SUBSCRIPTION regress_conflict_test1 SET (slot_name = NONE);
-- Verify the table OID for reap check
-SELECT 'pg_conflict_log_' || oid AS internal_tablename FROM pg_subscription WHERE subname = 'regress_conflict_test1' \gset
+SELECT 'pg_conflict_log_for_subid_' || oid AS internal_tablename FROM pg_subscription WHERE subname = 'regress_conflict_test1' \gset
+SET client_min_messages = WARNING;
DROP SUBSCRIPTION regress_conflict_test1;
-- should return NULL, meaning the conflict log table was reaped via dependency
@@ -562,7 +563,6 @@ SELECT to_regclass(:'internal_tablename');
-- Additional Namespace and Table Protection Tests
--
-SET client_min_messages = WARNING;
-- Setup: Ensure we have a subscription with a conflict log table
CREATE SUBSCRIPTION regress_conflict_protection_test CONNECTION 'dbname=regress_doesnotexist'
PUBLICATION testpub WITH (connect = false, conflict_log_destination = 'table');
@@ -577,7 +577,7 @@ DECLARE
tab_name text;
BEGIN
SELECT 'pg_conflict.' || relname INTO tab_name
- FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+ FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_for_subid_' || s.oid
WHERE s.subname = 'regress_conflict_protection_test';
RAISE NOTICE 'Attempting ALTER TABLE on internal conflict log table';
@@ -594,12 +594,12 @@ DECLARE
tab_name text;
BEGIN
SELECT 'pg_conflict.' || relname INTO tab_name
- FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+ FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_for_subid_' || s.oid
WHERE s.subname = 'regress_conflict_protection_test';
EXECUTE 'INSERT INTO ' || tab_name || ' (relname) VALUES (''mytest'')';
-EXCEPTION WHEN insufficient_privilege THEN
- RAISE NOTICE 'captured expected error: insufficient_privilege during INSERT';
+EXCEPTION WHEN wrong_object_type THEN
+ RAISE NOTICE 'captured expected error: wrong_object_type during INSERT';
END $$;
-- Test Manual UPDATE on conflict log table
@@ -610,19 +610,19 @@ DECLARE
tab_name text;
BEGIN
SELECT 'pg_conflict.' || relname INTO tab_name
- FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+ FROM pg_class c JOIN pg_subscription s ON c.relname = 'pg_conflict_log_for_subid_' || s.oid
WHERE s.subname = 'regress_conflict_protection_test';
EXECUTE 'UPDATE ' || tab_name || ' SET relname = ''mytest'' ';
-EXCEPTION WHEN insufficient_privilege THEN
- RAISE NOTICE 'captured expected error: insufficient_privilege during UPDATE';
+EXCEPTION WHEN wrong_object_type THEN
+ RAISE NOTICE 'captured expected error: wrong_object_type during UPDATE';
END $$;
-- Trying to perform TRUNCATE/DELETE on the internal conflict log table
-- This should be allowed so that user can perform cleanup
SELECT 'pg_conflict.' || relname AS conflict_tab
FROM pg_class c
-JOIN pg_subscription s ON c.relname = 'pg_conflict_log_' || s.oid
+JOIN pg_subscription s ON c.relname = 'pg_conflict_log_for_subid_' || s.oid
WHERE s.subname = 'regress_conflict_protection_test' \gset
TRUNCATE :conflict_tab;
DELETE FROM :conflict_tab;
@@ -637,6 +637,8 @@ CREATE TABLE public.test_move (id int);
ALTER TABLE public.test_move SET SCHEMA pg_conflict;
DROP TABLE public.test_move;
+SET client_min_messages = WARNING;
+
-- Clean up remaining test subscription
ALTER SUBSCRIPTION regress_conflict_log_default DISABLE;
ALTER SUBSCRIPTION regress_conflict_log_default SET (slot_name = NONE);
--
2.53.0