From 79169a50a53da93ea5d2ba03239a884b4d660158 Mon Sep 17 00:00:00 2001 From: "Chao Li (Evan)" Date: Mon, 10 Nov 2025 15:28:03 +0800 Subject: [PATCH v1] Fallback default replication identity to full Author: Chao Li --- src/backend/access/heap/heapam.c | 7 ++--- src/backend/commands/publicationcmds.c | 5 ++-- src/backend/executor/execReplication.c | 2 +- src/backend/replication/logical/proto.c | 6 ++++- src/backend/replication/logical/relation.c | 26 +++++++++++++++++++ src/backend/utils/misc/guc_parameters.dat | 7 +++++ src/backend/utils/misc/postgresql.conf.sample | 2 ++ src/include/replication/logicalrelation.h | 4 +++ 8 files changed, 52 insertions(+), 7 deletions(-) diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 36fee9c994e..6c47d140bd1 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -45,6 +45,7 @@ #include "commands/vacuum.h" #include "pgstat.h" #include "port/pg_bitutils.h" +#include "replication/logicalrelation.h" #include "storage/lmgr.h" #include "storage/predicate.h" #include "storage/procarray.h" @@ -3119,7 +3120,7 @@ l1: if (old_key_tuple != NULL) { - if (relation->rd_rel->relreplident == REPLICA_IDENTITY_FULL) + if (logicalrep_identity_is_full(relation)) xlrec.flags |= XLH_DELETE_CONTAINS_OLD_TUPLE; else xlrec.flags |= XLH_DELETE_CONTAINS_OLD_KEY; @@ -8941,7 +8942,7 @@ log_heap_update(Relation reln, Buffer oldbuf, xlrec.flags |= XLH_UPDATE_CONTAINS_NEW_TUPLE; if (old_key_tuple) { - if (reln->rd_rel->relreplident == REPLICA_IDENTITY_FULL) + if (logicalrep_identity_is_full(reln)) xlrec.flags |= XLH_UPDATE_CONTAINS_OLD_TUPLE; else xlrec.flags |= XLH_UPDATE_CONTAINS_OLD_KEY; @@ -9167,7 +9168,7 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required, if (replident == REPLICA_IDENTITY_NOTHING) return NULL; - if (replident == REPLICA_IDENTITY_FULL) + if (logicalrep_identity_is_full(relation)) { /* * When logging the entire old tuple, it very well could contain diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c index 1faf3a8c372..d3fa42123e2 100644 --- a/src/backend/commands/publicationcmds.c +++ b/src/backend/commands/publicationcmds.c @@ -37,6 +37,7 @@ #include "parser/parse_clause.h" #include "parser/parse_collate.h" #include "parser/parse_relation.h" +#include "replication/logicalrelation.h" #include "rewrite/rewriteHandler.h" #include "storage/lmgr.h" #include "utils/acl.h" @@ -281,7 +282,7 @@ pub_rf_contains_invalid_column(Oid pubid, Relation relation, List *ancestors, * FULL means all columns are in the REPLICA IDENTITY, so all columns are * allowed in the row filter and we can skip the validation. */ - if (relation->rd_rel->relreplident == REPLICA_IDENTITY_FULL) + if (logicalrep_identity_is_full(relation)) return false; /* @@ -389,7 +390,7 @@ pub_contains_invalid_column(Oid pubid, Relation relation, List *ancestors, pub = GetPublication(pubid); check_and_fetch_column_list(pub, publish_as_relid, NULL, &columns); - if (relation->rd_rel->relreplident == REPLICA_IDENTITY_FULL) + if (logicalrep_identity_is_full(relation)) { /* With REPLICA IDENTITY FULL, no column list is allowed. */ *invalid_column_list = (columns != NULL); diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c index def32774c90..22863c80154 100644 --- a/src/backend/executor/execReplication.c +++ b/src/backend/executor/execReplication.c @@ -1088,7 +1088,7 @@ CheckCmdReplicaIdentity(Relation rel, CmdType cmd) return; /* REPLICA IDENTITY FULL is also good for UPDATE/DELETE. */ - if (rel->rd_rel->relreplident == REPLICA_IDENTITY_FULL) + if (logicalrep_identity_is_full(rel)) return; /* diff --git a/src/backend/replication/logical/proto.c b/src/backend/replication/logical/proto.c index f0a913892b9..aa23e6e75f9 100644 --- a/src/backend/replication/logical/proto.c +++ b/src/backend/replication/logical/proto.c @@ -17,6 +17,7 @@ #include "catalog/pg_type.h" #include "libpq/pqformat.h" #include "replication/logicalproto.h" +#include "replication/logicalrelation.h" #include "utils/lsyscache.h" #include "utils/syscache.h" @@ -669,6 +670,7 @@ logicalrep_write_rel(StringInfo out, TransactionId xid, Relation rel, PublishGencolsType include_gencols_type) { char *relname; + char relreplident = rel->rd_rel->relreplident; pq_sendbyte(out, LOGICAL_REP_MSG_RELATION); @@ -685,7 +687,9 @@ logicalrep_write_rel(StringInfo out, TransactionId xid, Relation rel, pq_sendstring(out, relname); /* send replica identity */ - pq_sendbyte(out, rel->rd_rel->relreplident); + if (logicalrep_identity_is_full(rel)) + relreplident = REPLICA_IDENTITY_FULL; + pq_sendbyte(out, relreplident); /* send the attribute info */ logicalrep_write_attrs(out, rel, columns, include_gencols_type); diff --git a/src/backend/replication/logical/relation.c b/src/backend/replication/logical/relation.c index 745fd3bab64..2c756fca469 100644 --- a/src/backend/replication/logical/relation.c +++ b/src/backend/replication/logical/relation.c @@ -30,6 +30,7 @@ #include "utils/lsyscache.h" #include "utils/syscache.h" +bool logical_replication_fallback_to_full_identity = false; static MemoryContext LogicalRepRelMapContext = NULL; @@ -958,3 +959,28 @@ FindLogicalRepLocalIndex(Relation localrel, LogicalRepRelation *remoterel, return InvalidOid; } + +/* + * logicalrep_identity_is_full + * + * Check whether the replica identity of the relation is full or not. + * When a table's replica identity is default, but there is no primary key, + * if logical_replication_fallback_to_full_identity is true, we consider the + * replica identity as full. This funciton should only be called on the + * publisher. + */ +bool +logicalrep_identity_is_full(Relation relation) +{ + Form_pg_class relform = RelationGetForm(relation); + + if (relform->relreplident == REPLICA_IDENTITY_FULL) + return true; + + if (relform->relreplident == REPLICA_IDENTITY_DEFAULT && + logical_replication_fallback_to_full_identity && + !OidIsValid(RelationGetReplicaIndex(relation))) + return true; + + return false; +} diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat index 1128167c025..a07edb05254 100644 --- a/src/backend/utils/misc/guc_parameters.dat +++ b/src/backend/utils/misc/guc_parameters.dat @@ -1833,6 +1833,13 @@ max => 'MAX_KILOBYTES', }, +{ name => 'logical_replication_fallback_to_full_identity', type => 'bool', context => 'PGC_SIGHUP', group => 'REPLICATION_SENDING', + short_desc => 'Use REPLICA IDENTITY FULL automatically when a table with DEFAULT identity has no primary key.', + long_desc => 'When enabled, logical replication will automatically send full-row data for tables that specify REPLICA IDENTITY DEFAULT but lack a primary key, instead of raising an error.', + variable => 'logical_replication_fallback_to_full_identity', + boot_val => 'false', +}, + { name => 'maintenance_io_concurrency', type => 'int', context => 'PGC_USERSET', group => 'RESOURCES_IO', short_desc => 'A variant of "effective_io_concurrency" that is used for maintenance work.', long_desc => '0 disables simultaneous requests.', diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index f62b61967ef..dc17caa2c2a 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -346,6 +346,8 @@ #wal_sender_timeout = 60s # in milliseconds; 0 disables #track_commit_timestamp = off # collect timestamp of transaction commit # (change requires restart) +#logical_replication_fallback_to_full_identity = off # fallback default + # replication identity to full if no primary key # - Primary Server - diff --git a/src/include/replication/logicalrelation.h b/src/include/replication/logicalrelation.h index 7a561a8e8d8..0c6299d3bca 100644 --- a/src/include/replication/logicalrelation.h +++ b/src/include/replication/logicalrelation.h @@ -16,6 +16,9 @@ #include "catalog/index.h" #include "replication/logicalproto.h" +/* GUC variables */ +extern PGDLLIMPORT bool logical_replication_fallback_to_full_identity; + typedef struct LogicalRepRelMapEntry { LogicalRepRelation remoterel; /* key is remoterel.remoteid */ @@ -48,6 +51,7 @@ extern LogicalRepRelMapEntry *logicalrep_partition_open(LogicalRepRelMapEntry *r Relation partrel, AttrMap *map); extern void logicalrep_rel_close(LogicalRepRelMapEntry *rel, LOCKMODE lockmode); +extern bool logicalrep_identity_is_full(Relation relation); extern bool IsIndexUsableForReplicaIdentityFull(Relation idxrel, AttrMap *attrmap); extern Oid GetRelationIdentityOrPK(Relation rel); -- 2.39.5 (Apple Git-154)