From e666521d65c8c48594e37e2f38381385f746450b Mon Sep 17 00:00:00 2001 From: "Andrei V. Lepikhov" Date: Fri, 19 Dec 2025 16:31:52 +0100 Subject: [PATCH 1/5] Introduce quiet mode to reduce output verbosity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add spock.enable_quiet_mode GUC parameter to reduce message verbosity for cleaner output. When enabled, this parameter: 1. Downgrades DDL replication messages from INFO/WARNING to LOG level - "DDL statement replicated" (INFO → LOG) - "DDL statement replicated, but could be unsafe" (WARNING → LOG) - "This DDL statement will not be replicated" (WARNING → LOG) 2. Suppresses dependent object reporting in DROP CASCADE operations to reduce NOTICE message clutter The parameter defaults to false (disabled) for normal verbose output. Enable it by setting: spock.enable_quiet_mode = true This is useful for regression tests and production environments where less verbose output is desired. All diagnostic messages are preserved in the server log. --- include/spock.h | 2 ++ src/spock.c | 12 ++++++++++++ src/spock_dependency.c | 7 +++++-- src/spock_functions.c | 11 ++++++++--- 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/include/spock.h b/include/spock.h index a4bc2563..7eaf2251 100644 --- a/include/spock.h +++ b/include/spock.h @@ -51,6 +51,8 @@ extern int restart_delay_default; extern int restart_delay_on_exception; extern int spock_replay_queue_size; /* Deprecated - no longer used */ extern bool check_all_uc_indexes; +extern bool spock_enable_quiet_mode; + extern char *shorten_hash(const char *str, int maxlen); extern List *textarray_to_list(ArrayType *textarray); diff --git a/src/spock.c b/src/spock.c index 27c6ee38..47ad9b75 100644 --- a/src/spock.c +++ b/src/spock.c @@ -132,6 +132,7 @@ int restart_delay_default; int restart_delay_on_exception; int spock_replay_queue_size; /* Deprecated - no longer used */ bool check_all_uc_indexes = false; +bool spock_enable_quiet_mode = false; static emit_log_hook_type prev_emit_log_hook = NULL; static Checkpoint_hook_type prev_Checkpoint_hook = NULL; @@ -970,6 +971,17 @@ _PG_init(void) 0, NULL, NULL, NULL); + DefineCustomBoolVariable("spock.enable_quiet_mode", + "Reduce message verbosity for cleaner output", + "When enabled, downgrades DDL replication INFO/WARNING messages to LOG level " + "and suppresses dependent object reporting in DROP CASCADE operations. " + "Useful for regression tests and production environments where less verbose " + "output is desired.", + &spock_enable_quiet_mode, + false, PGC_SIGHUP, + 0, + NULL, NULL, NULL); + DefineCustomBoolVariable("spock.synchronous_commit", "spock specific synchronous commit value", NULL, diff --git a/src/spock_dependency.c b/src/spock_dependency.c index 89768537..7bb8af30 100644 --- a/src/spock_dependency.c +++ b/src/spock_dependency.c @@ -707,14 +707,17 @@ reportDependentObjects(const ObjectAddresses *targetObjects, * If no error is to be thrown, and the msglevel is too low to be shown to * either client or server log, there's no need to do any of the work. * + * In quiet mode (spock.enable_quiet_mode), skip reporting dependent + * objects to reduce output verbosity. + * * Note: this code doesn't know all there is to be known about elog * levels, but it works for NOTICE and DEBUG2, which are the only values * msglevel can currently have. We also assume we are running in a normal * operating environment. */ - if (behavior == DROP_CASCADE && + if (spock_enable_quiet_mode || (behavior == DROP_CASCADE && msglevel < my_client_min_messages && - (msglevel < my_log_min_messages || my_log_min_messages == LOG)) + (msglevel < my_log_min_messages || my_log_min_messages == LOG))) return; /* diff --git a/src/spock_functions.c b/src/spock_functions.c index 557c665d..34724ab5 100644 --- a/src/spock_functions.c +++ b/src/spock_functions.c @@ -2343,10 +2343,15 @@ spock_auto_replicate_ddl(const char *query, List *replication_sets, break; } + /* + * Report replication status. In quiet mode, downgrade INFO/WARNING to + * LOG level to reduce output verbosity. + */ if (warn) - elog(WARNING, "DDL statement replicated, but could be unsafe."); + elog(spock_enable_quiet_mode ? LOG : WARNING, + "DDL statement replicated, but could be unsafe."); else - elog(INFO, "DDL statement replicated."); + elog(spock_enable_quiet_mode ? LOG : INFO, "DDL statement replicated."); initStringInfo(&q); if (add_search_path) @@ -2371,7 +2376,7 @@ spock_auto_replicate_ddl(const char *query, List *replication_sets, return; skip_ddl: - elog(WARNING, "This DDL statement will not be replicated."); + elog(spock_enable_quiet_mode ? LOG : WARNING, "This DDL statement will not be replicated."); } From 001a73b6d217c1b2103d1fa3726fb88bf2ff0160 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 22 Dec 2025 08:01:29 +0000 Subject: [PATCH 2/5] Remove unused functions and smooth the slot_name routine. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These two changes to the Spock initial script are intended to make ‘make installcheck’ clearer by avoiding false-positive errors. There is no evidence of the md5_agg_sfunc and md5_agg usage anywhere in the code - it seems it was an attempt at Spock objects naming. Also, add to the spock_gen_slot_name declaration the ‘PARALLEL SAFE’ clause just to keep regression tests quiet. --- sql/spock--6.0.0-devel.sql | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/sql/spock--6.0.0-devel.sql b/sql/spock--6.0.0-devel.sql index 1ad9a9a8..8bafafa9 100644 --- a/sql/spock--6.0.0-devel.sql +++ b/sql/spock--6.0.0-devel.sql @@ -355,9 +355,13 @@ CREATE FUNCTION spock.node_info(OUT node_id oid, OUT node_name text, RETURNS record STABLE STRICT LANGUAGE c AS 'MODULE_PATHNAME', 'spock_node_info'; -CREATE FUNCTION spock.spock_gen_slot_name(name, name, name) -RETURNS name -IMMUTABLE STRICT LANGUAGE c AS 'MODULE_PATHNAME'; +CREATE FUNCTION spock.spock_gen_slot_name( + dbname name, + provider_node name, + subscription name +) RETURNS name +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE FUNCTION spock_version() RETURNS text LANGUAGE c AS 'MODULE_PATHNAME'; @@ -536,20 +540,6 @@ CREATE VIEW spock.lag_tracker AS LEFT JOIN spock.node n ON n.node_id = p.node_id GROUP BY origin.node_name, n.node_name; -CREATE FUNCTION spock.md5_agg_sfunc(text, anyelement) - RETURNS text - LANGUAGE sql -AS -$$ - SELECT md5($1 || $2::text) -$$; -CREATE AGGREGATE spock.md5_agg (ORDER BY anyelement) -( - STYPE = text, - SFUNC = spock.md5_agg_sfunc, - INITCOND = '' -); - -- ---------------------------------------------------------------------- -- Spock Read Only -- ---------------------------------------------------------------------- From 68f74d85f161a14c6aaabb65126734c0736ffa8c Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 22 Dec 2025 08:01:47 +0000 Subject: [PATCH 3/5] Two lock-related fixes. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Bug. In the spock_repset.c module functions replication_set_add_table and replication_set_add_seq should check locking on the table before calling another lock-acquiring routine. It prevents unnecessary calls and reduces the lock level. 2. Improvement. In the spock_queue.c module function queue_message insert a tuple into the spock.queue table. The pattern of this table usage is quite trivial and doesn’t include any concurrent updates. So, we may release the table lock before commit without harm, and let ‘make installcheck’ be more conventional. author: Andrei Lepikhov co-author: Claude --- src/spock_queue.c | 2 +- src/spock_repset.c | 22 ++++++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/spock_queue.c b/src/spock_queue.c index b6dc7210..20e43283 100644 --- a/src/spock_queue.c +++ b/src/spock_queue.c @@ -116,7 +116,7 @@ queue_message(List *replication_sets, Oid roleoid, char message_type, /* Cleanup. */ heap_freetuple(tup); - table_close(rel, NoLock); + table_close(rel, RowExclusiveLock); } diff --git a/src/spock_repset.c b/src/spock_repset.c index f0e2bcd1..596184e8 100644 --- a/src/spock_repset.c +++ b/src/spock_repset.c @@ -1087,9 +1087,18 @@ replication_set_add_table(Oid setid, Oid reloid, List *att_list, SpockRepSet *repset = get_replication_set(setid); ObjectAddress referenced; ObjectAddress myself; + LOCKTAG tag; /* Open the relation. */ - targetrel = table_open(reloid, ShareRowExclusiveLock); + SET_LOCKTAG_RELATION(tag, MyDatabaseId, reloid); +#if PG_VERSION_NUM < 170000 + if (!LockOrStrongerHeldByMe(&tag, AccessShareLock)) +#else + if (!LockHeldByMe(&tag, AccessShareLock, true)) +#endif + targetrel = table_open(reloid, AccessShareLock); + else + targetrel = table_open(reloid, NoLock); /* UNLOGGED and TEMP relations cannot be part of replication set. */ if (!RelationNeedsWAL(targetrel)) @@ -1183,9 +1192,18 @@ replication_set_add_seq(Oid setid, Oid seqoid) SpockRepSet *repset = get_replication_set(setid); ObjectAddress referenced; ObjectAddress myself; + LOCKTAG tag; /* Open the relation. */ - targetrel = table_open(seqoid, ShareRowExclusiveLock); + SET_LOCKTAG_RELATION(tag, MyDatabaseId, seqoid); +#if PG_VERSION_NUM < 170000 + if (!LockOrStrongerHeldByMe(&tag, AccessShareLock)) +#else + if (!LockHeldByMe(&tag, AccessShareLock, true)) +#endif + targetrel = table_open(seqoid, AccessShareLock); + else + targetrel = table_open(seqoid, NoLock); /* UNLOGGED and TEMP relations cannot be part of replication set. */ if (!RelationNeedsWAL(targetrel)) From ccc9891b46c17c1e36b044dc15d181d4a520cfab Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 22 Dec 2025 08:02:03 +0000 Subject: [PATCH 4/5] Add catalog relation check to delta_apply attribute detection PostgreSQL Core Patch Enhancement (pgXX-015-attoptions.diff): Added IsCatalogRelation() check in GetAttrDelta() function to prevent delta_apply attribute options from being applied to catalog relations. Rationale: - Catalog relations are never replicated via Logical Replication - No use case exists for delta_apply on system catalogs - Applying delta_apply to catalog tables would be meaningless - Early exit improves performance by avoiding unnecessary attribute scans Implementation: - Check added at the beginning of GetAttrDelta() - Returns NULL immediately if relation is a catalog - Includes clear comment explaining the rationale - No functional change for user tables Benefits: - Prevents accidental misconfiguration - Minor performance improvement for catalog operations - Clearer code intent through explicit check - Matches logical replication semantics Note: This patch is applied to PostgreSQL core during build to enable attribute-level options for delta_apply functionality. --- patches/15/pg15-015-attoptions.diff | 39 ++++++++++-------- patches/16/pg16-015-attoptions.diff | 41 ++++++++++-------- patches/17/pg17-015-attoptions.diff | 53 +++++++++++++----------- patches/18/pg18-015-attoptions.diff | 64 +++++++++++++++-------------- 4 files changed, 110 insertions(+), 87 deletions(-) diff --git a/patches/15/pg15-015-attoptions.diff b/patches/15/pg15-015-attoptions.diff index 7b63311a..53998f21 100644 --- a/patches/15/pg15-015-attoptions.diff +++ b/patches/15/pg15-015-attoptions.diff @@ -1,5 +1,5 @@ diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c -index 5b696043c5..16ec985928 100644 +index 620602fba2d..8eebc4cde65 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -168,6 +168,15 @@ static relopt_bool boolRelOpts[] = @@ -38,7 +38,7 @@ index 5b696043c5..16ec985928 100644 /* list terminator */ {{NULL}} }; -@@ -2076,7 +2098,9 @@ attribute_reloptions(Datum reloptions, bool validate) +@@ -2085,7 +2107,9 @@ attribute_reloptions(Datum reloptions, bool validate) { static const relopt_parse_elt tab[] = { {"n_distinct", RELOPT_TYPE_REAL, offsetof(AttributeOpts, n_distinct)}, @@ -50,10 +50,10 @@ index 5b696043c5..16ec985928 100644 return (bytea *) build_reloptions(reloptions, validate, diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c -index 88ab5f99c8..161333c5e0 100644 +index 64044de67b2..d2a86a98987 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c -@@ -66,6 +66,7 @@ +@@ -67,6 +67,7 @@ #include "storage/smgr.h" #include "storage/spin.h" #include "storage/standby.h" @@ -61,7 +61,7 @@ index 88ab5f99c8..161333c5e0 100644 #include "utils/datum.h" #include "utils/inval.h" #include "utils/lsyscache.h" -@@ -86,6 +87,7 @@ static void check_lock_if_inplace_updateable_rel(Relation relation, +@@ -88,6 +89,7 @@ static void check_lock_if_inplace_updateable_rel(Relation relation, HeapTuple newtup); static void check_inplace_rel_lock(HeapTuple oldtup); #endif @@ -69,7 +69,7 @@ index 88ab5f99c8..161333c5e0 100644 static Bitmapset *HeapDetermineColumnsInfo(Relation relation, Bitmapset *interesting_cols, Bitmapset *external_cols, -@@ -117,6 +119,7 @@ static void index_delete_sort(TM_IndexDeleteOp *delstate); +@@ -119,6 +121,7 @@ static void index_delete_sort(TM_IndexDeleteOp *delstate); static int bottomup_sort_and_shrink(TM_IndexDeleteOp *delstate); static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup); static HeapTuple ExtractReplicaIdentity(Relation rel, HeapTuple tup, bool key_required, @@ -77,7 +77,7 @@ index 88ab5f99c8..161333c5e0 100644 bool *copy); -@@ -2919,7 +2922,7 @@ l1: +@@ -2959,7 +2962,7 @@ l1: * Compute replica identity tuple before entering the critical section so * we don't PANIC upon a memory allocation failure. */ @@ -86,7 +86,7 @@ index 88ab5f99c8..161333c5e0 100644 /* * If this is the first possibly-multixact-able operation in the current -@@ -3150,6 +3153,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, +@@ -3190,6 +3193,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, Bitmapset *id_attrs; Bitmapset *interesting_attrs; Bitmapset *modified_attrs; @@ -94,7 +94,7 @@ index 88ab5f99c8..161333c5e0 100644 ItemId lp; HeapTupleData oldtup; HeapTuple heaptup; -@@ -3267,6 +3271,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, +@@ -3351,6 +3355,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, modified_attrs = HeapDetermineColumnsInfo(relation, interesting_attrs, id_attrs, &oldtup, newtup, &id_has_external); @@ -102,7 +102,7 @@ index 88ab5f99c8..161333c5e0 100644 /* * If we're not updating any "key" column, we can grab a weaker lock type. -@@ -3538,6 +3543,7 @@ l2: +@@ -3622,6 +3627,7 @@ l2: bms_free(key_attrs); bms_free(id_attrs); bms_free(modified_attrs); @@ -110,7 +110,7 @@ index 88ab5f99c8..161333c5e0 100644 bms_free(interesting_attrs); return result; } -@@ -3874,6 +3880,7 @@ l2: +@@ -3958,6 +3964,7 @@ l2: old_key_tuple = ExtractReplicaIdentity(relation, &oldtup, bms_overlap(modified_attrs, id_attrs) || id_has_external, @@ -118,7 +118,7 @@ index 88ab5f99c8..161333c5e0 100644 &old_key_copied); /* NO EREPORT(ERROR) from here till changes are logged */ -@@ -4023,6 +4030,7 @@ l2: +@@ -4107,6 +4114,7 @@ l2: bms_free(key_attrs); bms_free(id_attrs); bms_free(modified_attrs); @@ -126,7 +126,7 @@ index 88ab5f99c8..161333c5e0 100644 bms_free(interesting_attrs); return TM_Ok; -@@ -4195,6 +4203,26 @@ heap_attr_equals(TupleDesc tupdesc, int attrnum, Datum value1, Datum value2, +@@ -4279,6 +4287,33 @@ heap_attr_equals(TupleDesc tupdesc, int attrnum, Datum value1, Datum value2, } } @@ -138,6 +138,13 @@ index 88ab5f99c8..161333c5e0 100644 + TupleDesc tupdesc = RelationGetDescr(relation); + AttributeOpts *aopt; + ++ /* ++ * Catalog relations are never sent by LR, no chance to use the delta_apply ++ * feature ++ */ ++ if (IsCatalogRelation(relation)) ++ return NULL; ++ + for (attnum = 1; attnum <= tupdesc->natts; attnum++) + { + aopt = get_attribute_options(relation->rd_id, attnum); @@ -153,7 +160,7 @@ index 88ab5f99c8..161333c5e0 100644 /* * Check which columns are being updated. * -@@ -8913,6 +8941,7 @@ log_heap_new_cid(Relation relation, HeapTuple tup) +@@ -9069,6 +9104,7 @@ log_heap_new_cid(Relation relation, HeapTuple tup) */ static HeapTuple ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required, @@ -161,7 +168,7 @@ index 88ab5f99c8..161333c5e0 100644 bool *copy) { TupleDesc desc = RelationGetDescr(relation); -@@ -8945,13 +8974,16 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required, +@@ -9101,13 +9137,16 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required, } /* if the key isn't required and we're only logging the key, we're done */ @@ -180,7 +187,7 @@ index 88ab5f99c8..161333c5e0 100644 * If there's no defined replica identity columns, treat as !key_required. * (This case should not be reachable from heap_update, since that should diff --git a/src/include/utils/attoptcache.h b/src/include/utils/attoptcache.h -index ee37af9500..98b48a8fd8 100644 +index ee37af95001..98b48a8fd89 100644 --- a/src/include/utils/attoptcache.h +++ b/src/include/utils/attoptcache.h @@ -21,6 +21,8 @@ typedef struct AttributeOpts diff --git a/patches/16/pg16-015-attoptions.diff b/patches/16/pg16-015-attoptions.diff index ffea2382..53998f21 100644 --- a/patches/16/pg16-015-attoptions.diff +++ b/patches/16/pg16-015-attoptions.diff @@ -1,5 +1,5 @@ diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c -index 469de9bb49..0119e5aa7c 100644 +index 620602fba2d..8eebc4cde65 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -168,6 +168,15 @@ static relopt_bool boolRelOpts[] = @@ -38,7 +38,7 @@ index 469de9bb49..0119e5aa7c 100644 /* list terminator */ {{NULL}} }; -@@ -2072,7 +2094,9 @@ attribute_reloptions(Datum reloptions, bool validate) +@@ -2085,7 +2107,9 @@ attribute_reloptions(Datum reloptions, bool validate) { static const relopt_parse_elt tab[] = { {"n_distinct", RELOPT_TYPE_REAL, offsetof(AttributeOpts, n_distinct)}, @@ -50,7 +50,7 @@ index 469de9bb49..0119e5aa7c 100644 return (bytea *) build_reloptions(reloptions, validate, diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c -index 723e34e464..b2f845211d 100644 +index 64044de67b2..d2a86a98987 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -67,6 +67,7 @@ @@ -61,7 +61,7 @@ index 723e34e464..b2f845211d 100644 #include "utils/datum.h" #include "utils/inval.h" #include "utils/lsyscache.h" -@@ -87,6 +88,7 @@ static void check_lock_if_inplace_updateable_rel(Relation relation, +@@ -88,6 +89,7 @@ static void check_lock_if_inplace_updateable_rel(Relation relation, HeapTuple newtup); static void check_inplace_rel_lock(HeapTuple oldtup); #endif @@ -69,15 +69,15 @@ index 723e34e464..b2f845211d 100644 static Bitmapset *HeapDetermineColumnsInfo(Relation relation, Bitmapset *interesting_cols, Bitmapset *external_cols, -@@ -121,6 +123,7 @@ static void index_delete_sort(TM_IndexDeleteOp *delstate); +@@ -119,6 +121,7 @@ static void index_delete_sort(TM_IndexDeleteOp *delstate); static int bottomup_sort_and_shrink(TM_IndexDeleteOp *delstate); static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup); - static HeapTuple ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required, + static HeapTuple ExtractReplicaIdentity(Relation rel, HeapTuple tup, bool key_required, + Bitmapset *logged_old_attrs, bool *copy); -@@ -2780,7 +2783,7 @@ l1: +@@ -2959,7 +2962,7 @@ l1: * Compute replica identity tuple before entering the critical section so * we don't PANIC upon a memory allocation failure. */ @@ -86,7 +86,7 @@ index 723e34e464..b2f845211d 100644 /* * If this is the first possibly-multixact-able operation in the current -@@ -3013,6 +3016,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, +@@ -3190,6 +3193,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, Bitmapset *id_attrs; Bitmapset *interesting_attrs; Bitmapset *modified_attrs; @@ -94,7 +94,7 @@ index 723e34e464..b2f845211d 100644 ItemId lp; HeapTupleData oldtup; HeapTuple heaptup; -@@ -3135,6 +3139,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, +@@ -3351,6 +3355,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, modified_attrs = HeapDetermineColumnsInfo(relation, interesting_attrs, id_attrs, &oldtup, newtup, &id_has_external); @@ -102,7 +102,7 @@ index 723e34e464..b2f845211d 100644 /* * If we're not updating any "key" column, we can grab a weaker lock type. -@@ -3409,6 +3414,7 @@ l2: +@@ -3622,6 +3627,7 @@ l2: bms_free(key_attrs); bms_free(id_attrs); bms_free(modified_attrs); @@ -110,7 +110,7 @@ index 723e34e464..b2f845211d 100644 bms_free(interesting_attrs); return result; } -@@ -3758,6 +3764,7 @@ l2: +@@ -3958,6 +3964,7 @@ l2: old_key_tuple = ExtractReplicaIdentity(relation, &oldtup, bms_overlap(modified_attrs, id_attrs) || id_has_external, @@ -118,7 +118,7 @@ index 723e34e464..b2f845211d 100644 &old_key_copied); /* NO EREPORT(ERROR) from here till changes are logged */ -@@ -3924,6 +3931,7 @@ l2: +@@ -4107,6 +4114,7 @@ l2: bms_free(key_attrs); bms_free(id_attrs); bms_free(modified_attrs); @@ -126,7 +126,7 @@ index 723e34e464..b2f845211d 100644 bms_free(interesting_attrs); return TM_Ok; -@@ -4096,6 +4104,26 @@ heap_attr_equals(TupleDesc tupdesc, int attrnum, Datum value1, Datum value2, +@@ -4279,6 +4287,33 @@ heap_attr_equals(TupleDesc tupdesc, int attrnum, Datum value1, Datum value2, } } @@ -138,6 +138,13 @@ index 723e34e464..b2f845211d 100644 + TupleDesc tupdesc = RelationGetDescr(relation); + AttributeOpts *aopt; + ++ /* ++ * Catalog relations are never sent by LR, no chance to use the delta_apply ++ * feature ++ */ ++ if (IsCatalogRelation(relation)) ++ return NULL; ++ + for (attnum = 1; attnum <= tupdesc->natts; attnum++) + { + aopt = get_attribute_options(relation->rd_id, attnum); @@ -153,7 +160,7 @@ index 723e34e464..b2f845211d 100644 /* * Check which columns are being updated. * -@@ -9051,6 +9079,7 @@ log_heap_new_cid(Relation relation, HeapTuple tup) +@@ -9069,6 +9104,7 @@ log_heap_new_cid(Relation relation, HeapTuple tup) */ static HeapTuple ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required, @@ -161,7 +168,7 @@ index 723e34e464..b2f845211d 100644 bool *copy) { TupleDesc desc = RelationGetDescr(relation); -@@ -9083,13 +9112,16 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required, +@@ -9101,13 +9137,16 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required, } /* if the key isn't required and we're only logging the key, we're done */ @@ -180,7 +187,7 @@ index 723e34e464..b2f845211d 100644 * If there's no defined replica identity columns, treat as !key_required. * (This case should not be reachable from heap_update, since that should diff --git a/src/include/utils/attoptcache.h b/src/include/utils/attoptcache.h -index e4119b6aa2..6354a98157 100644 +index ee37af95001..98b48a8fd89 100644 --- a/src/include/utils/attoptcache.h +++ b/src/include/utils/attoptcache.h @@ -21,6 +21,8 @@ typedef struct AttributeOpts @@ -191,4 +198,4 @@ index e4119b6aa2..6354a98157 100644 + Oid delta_apply_function; } AttributeOpts; - extern AttributeOpts *get_attribute_options(Oid attrelid, int attnum); + extern AttributeOpts *get_attribute_options(Oid spcid, int attnum); diff --git a/patches/17/pg17-015-attoptions.diff b/patches/17/pg17-015-attoptions.diff index df156695..53998f21 100644 --- a/patches/17/pg17-015-attoptions.diff +++ b/patches/17/pg17-015-attoptions.diff @@ -1,8 +1,8 @@ diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c -index d6eb5d85599..4ef2e55cc44 100644 +index 620602fba2d..8eebc4cde65 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c -@@ -166,6 +166,15 @@ static relopt_bool boolRelOpts[] = +@@ -168,6 +168,15 @@ static relopt_bool boolRelOpts[] = }, true }, @@ -18,7 +18,7 @@ index d6eb5d85599..4ef2e55cc44 100644 /* list terminator */ {{NULL}} }; -@@ -546,6 +555,19 @@ static relopt_enum enumRelOpts[] = +@@ -548,6 +557,19 @@ static relopt_enum enumRelOpts[] = static relopt_string stringRelOpts[] = { @@ -38,7 +38,7 @@ index d6eb5d85599..4ef2e55cc44 100644 /* list terminator */ {{NULL}} }; -@@ -2070,7 +2092,9 @@ attribute_reloptions(Datum reloptions, bool validate) +@@ -2085,7 +2107,9 @@ attribute_reloptions(Datum reloptions, bool validate) { static const relopt_parse_elt tab[] = { {"n_distinct", RELOPT_TYPE_REAL, offsetof(AttributeOpts, n_distinct)}, @@ -50,18 +50,18 @@ index d6eb5d85599..4ef2e55cc44 100644 return (bytea *) build_reloptions(reloptions, validate, diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c -index 95e3be524a7..708d55b2618 100644 +index 64044de67b2..d2a86a98987 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c -@@ -64,6 +64,7 @@ - #include "storage/predicate.h" - #include "storage/procarray.h" +@@ -67,6 +67,7 @@ + #include "storage/smgr.h" + #include "storage/spin.h" #include "storage/standby.h" +#include "utils/attoptcache.h" #include "utils/datum.h" - #include "utils/injection_point.h" #include "utils/inval.h" -@@ -85,6 +86,7 @@ static void check_lock_if_inplace_updateable_rel(Relation relation, + #include "utils/lsyscache.h" +@@ -88,6 +89,7 @@ static void check_lock_if_inplace_updateable_rel(Relation relation, HeapTuple newtup); static void check_inplace_rel_lock(HeapTuple oldtup); #endif @@ -69,15 +69,15 @@ index 95e3be524a7..708d55b2618 100644 static Bitmapset *HeapDetermineColumnsInfo(Relation relation, Bitmapset *interesting_cols, Bitmapset *external_cols, -@@ -121,6 +123,7 @@ static void index_delete_sort(TM_IndexDeleteOp *delstate); +@@ -119,6 +121,7 @@ static void index_delete_sort(TM_IndexDeleteOp *delstate); static int bottomup_sort_and_shrink(TM_IndexDeleteOp *delstate); static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup); - static HeapTuple ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required, + static HeapTuple ExtractReplicaIdentity(Relation rel, HeapTuple tup, bool key_required, + Bitmapset *logged_old_attrs, bool *copy); -@@ -2938,7 +2941,7 @@ l1: +@@ -2959,7 +2962,7 @@ l1: * Compute replica identity tuple before entering the critical section so * we don't PANIC upon a memory allocation failure. */ @@ -86,7 +86,7 @@ index 95e3be524a7..708d55b2618 100644 /* * If this is the first possibly-multixact-able operation in the current -@@ -3171,6 +3174,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, +@@ -3190,6 +3193,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, Bitmapset *id_attrs; Bitmapset *interesting_attrs; Bitmapset *modified_attrs; @@ -94,7 +94,7 @@ index 95e3be524a7..708d55b2618 100644 ItemId lp; HeapTupleData oldtup; HeapTuple heaptup; -@@ -3338,6 +3342,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, +@@ -3351,6 +3355,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, modified_attrs = HeapDetermineColumnsInfo(relation, interesting_attrs, id_attrs, &oldtup, newtup, &id_has_external); @@ -102,7 +102,7 @@ index 95e3be524a7..708d55b2618 100644 /* * If we're not updating any "key" column, we can grab a weaker lock type. -@@ -3612,6 +3617,7 @@ l2: +@@ -3622,6 +3627,7 @@ l2: bms_free(key_attrs); bms_free(id_attrs); bms_free(modified_attrs); @@ -110,7 +110,7 @@ index 95e3be524a7..708d55b2618 100644 bms_free(interesting_attrs); return result; } -@@ -3961,6 +3967,7 @@ l2: +@@ -3958,6 +3964,7 @@ l2: old_key_tuple = ExtractReplicaIdentity(relation, &oldtup, bms_overlap(modified_attrs, id_attrs) || id_has_external, @@ -118,7 +118,7 @@ index 95e3be524a7..708d55b2618 100644 &old_key_copied); /* NO EREPORT(ERROR) from here till changes are logged */ -@@ -4127,6 +4134,7 @@ l2: +@@ -4107,6 +4114,7 @@ l2: bms_free(key_attrs); bms_free(id_attrs); bms_free(modified_attrs); @@ -126,7 +126,7 @@ index 95e3be524a7..708d55b2618 100644 bms_free(interesting_attrs); return TM_Ok; -@@ -4299,6 +4307,26 @@ heap_attr_equals(TupleDesc tupdesc, int attrnum, Datum value1, Datum value2, +@@ -4279,6 +4287,33 @@ heap_attr_equals(TupleDesc tupdesc, int attrnum, Datum value1, Datum value2, } } @@ -138,6 +138,13 @@ index 95e3be524a7..708d55b2618 100644 + TupleDesc tupdesc = RelationGetDescr(relation); + AttributeOpts *aopt; + ++ /* ++ * Catalog relations are never sent by LR, no chance to use the delta_apply ++ * feature ++ */ ++ if (IsCatalogRelation(relation)) ++ return NULL; ++ + for (attnum = 1; attnum <= tupdesc->natts; attnum++) + { + aopt = get_attribute_options(relation->rd_id, attnum); @@ -153,7 +160,7 @@ index 95e3be524a7..708d55b2618 100644 /* * Check which columns are being updated. * -@@ -9076,6 +9104,7 @@ log_heap_new_cid(Relation relation, HeapTuple tup) +@@ -9069,6 +9104,7 @@ log_heap_new_cid(Relation relation, HeapTuple tup) */ static HeapTuple ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required, @@ -161,7 +168,7 @@ index 95e3be524a7..708d55b2618 100644 bool *copy) { TupleDesc desc = RelationGetDescr(relation); -@@ -9108,13 +9137,16 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required, +@@ -9101,13 +9137,16 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required, } /* if the key isn't required and we're only logging the key, we're done */ @@ -180,7 +187,7 @@ index 95e3be524a7..708d55b2618 100644 * If there's no defined replica identity columns, treat as !key_required. * (This case should not be reachable from heap_update, since that should diff --git a/src/include/utils/attoptcache.h b/src/include/utils/attoptcache.h -index a1a9bfc0fb9..e9b6dfab474 100644 +index ee37af95001..98b48a8fd89 100644 --- a/src/include/utils/attoptcache.h +++ b/src/include/utils/attoptcache.h @@ -21,6 +21,8 @@ typedef struct AttributeOpts @@ -191,4 +198,4 @@ index a1a9bfc0fb9..e9b6dfab474 100644 + Oid delta_apply_function; } AttributeOpts; - extern AttributeOpts *get_attribute_options(Oid attrelid, int attnum); + extern AttributeOpts *get_attribute_options(Oid spcid, int attnum); diff --git a/patches/18/pg18-015-attoptions.diff b/patches/18/pg18-015-attoptions.diff index dd42114d..53998f21 100644 --- a/patches/18/pg18-015-attoptions.diff +++ b/patches/18/pg18-015-attoptions.diff @@ -1,8 +1,8 @@ diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c -index 50747c16396..6cdbbe1d0dd 100644 +index 620602fba2d..8eebc4cde65 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c -@@ -166,6 +166,15 @@ static relopt_bool boolRelOpts[] = +@@ -168,6 +168,15 @@ static relopt_bool boolRelOpts[] = }, true }, @@ -18,7 +18,7 @@ index 50747c16396..6cdbbe1d0dd 100644 /* list terminator */ {{NULL}} }; -@@ -557,6 +566,19 @@ static relopt_enum enumRelOpts[] = +@@ -548,6 +557,19 @@ static relopt_enum enumRelOpts[] = static relopt_string stringRelOpts[] = { @@ -38,7 +38,7 @@ index 50747c16396..6cdbbe1d0dd 100644 /* list terminator */ {{NULL}} }; -@@ -2106,7 +2128,9 @@ attribute_reloptions(Datum reloptions, bool validate) +@@ -2085,7 +2107,9 @@ attribute_reloptions(Datum reloptions, bool validate) { static const relopt_parse_elt tab[] = { {"n_distinct", RELOPT_TYPE_REAL, offsetof(AttributeOpts, n_distinct)}, @@ -50,18 +50,18 @@ index 50747c16396..6cdbbe1d0dd 100644 return (bytea *) build_reloptions(reloptions, validate, diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c -index 0dcd6ee817e..dcf26167cae 100644 +index 64044de67b2..d2a86a98987 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c -@@ -48,6 +48,7 @@ - #include "storage/lmgr.h" - #include "storage/predicate.h" - #include "storage/procarray.h" +@@ -67,6 +67,7 @@ + #include "storage/smgr.h" + #include "storage/spin.h" + #include "storage/standby.h" +#include "utils/attoptcache.h" #include "utils/datum.h" - #include "utils/injection_point.h" #include "utils/inval.h" -@@ -67,6 +68,7 @@ static void check_lock_if_inplace_updateable_rel(Relation relation, + #include "utils/lsyscache.h" +@@ -88,6 +89,7 @@ static void check_lock_if_inplace_updateable_rel(Relation relation, HeapTuple newtup); static void check_inplace_rel_lock(HeapTuple oldtup); #endif @@ -69,15 +69,15 @@ index 0dcd6ee817e..dcf26167cae 100644 static Bitmapset *HeapDetermineColumnsInfo(Relation relation, Bitmapset *interesting_cols, Bitmapset *external_cols, -@@ -104,6 +106,7 @@ static void index_delete_sort(TM_IndexDeleteOp *delstate); +@@ -119,6 +121,7 @@ static void index_delete_sort(TM_IndexDeleteOp *delstate); static int bottomup_sort_and_shrink(TM_IndexDeleteOp *delstate); static XLogRecPtr log_heap_new_cid(Relation relation, HeapTuple tup); - static HeapTuple ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required, + static HeapTuple ExtractReplicaIdentity(Relation rel, HeapTuple tup, bool key_required, + Bitmapset *logged_old_attrs, bool *copy); -@@ -3016,7 +3019,7 @@ l1: +@@ -2959,7 +2962,7 @@ l1: * Compute replica identity tuple before entering the critical section so * we don't PANIC upon a memory allocation failure. */ @@ -86,7 +86,7 @@ index 0dcd6ee817e..dcf26167cae 100644 /* * If this is the first possibly-multixact-able operation in the current -@@ -3249,6 +3252,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, +@@ -3190,6 +3193,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, Bitmapset *id_attrs; Bitmapset *interesting_attrs; Bitmapset *modified_attrs; @@ -94,20 +94,15 @@ index 0dcd6ee817e..dcf26167cae 100644 ItemId lp; HeapTupleData oldtup; HeapTuple heaptup; -@@ -3419,6 +3423,12 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, +@@ -3351,6 +3355,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, + modified_attrs = HeapDetermineColumnsInfo(relation, interesting_attrs, id_attrs, &oldtup, newtup, &id_has_external); ++ logged_old_attrs = HeapDetermineLogOldColumns(relation); -+ if (!IsCatalogRelationOid(relation->rd_id)) -+ logged_old_attrs = HeapDetermineLogOldColumns(relation); -+ else -+ /* No need to log old values for catalog tables */ -+ logged_old_attrs = NULL; -+ /* * If we're not updating any "key" column, we can grab a weaker lock type. - * This allows for more concurrency when we are running simultaneously -@@ -3692,6 +3702,7 @@ l2: +@@ -3622,6 +3627,7 @@ l2: bms_free(key_attrs); bms_free(id_attrs); bms_free(modified_attrs); @@ -115,7 +110,7 @@ index 0dcd6ee817e..dcf26167cae 100644 bms_free(interesting_attrs); return result; } -@@ -4041,6 +4052,7 @@ l2: +@@ -3958,6 +3964,7 @@ l2: old_key_tuple = ExtractReplicaIdentity(relation, &oldtup, bms_overlap(modified_attrs, id_attrs) || id_has_external, @@ -123,7 +118,7 @@ index 0dcd6ee817e..dcf26167cae 100644 &old_key_copied); /* NO EREPORT(ERROR) from here till changes are logged */ -@@ -4207,6 +4219,7 @@ l2: +@@ -4107,6 +4114,7 @@ l2: bms_free(key_attrs); bms_free(id_attrs); bms_free(modified_attrs); @@ -131,7 +126,7 @@ index 0dcd6ee817e..dcf26167cae 100644 bms_free(interesting_attrs); return TM_Ok; -@@ -4379,6 +4392,26 @@ heap_attr_equals(TupleDesc tupdesc, int attrnum, Datum value1, Datum value2, +@@ -4279,6 +4287,33 @@ heap_attr_equals(TupleDesc tupdesc, int attrnum, Datum value1, Datum value2, } } @@ -143,6 +138,13 @@ index 0dcd6ee817e..dcf26167cae 100644 + TupleDesc tupdesc = RelationGetDescr(relation); + AttributeOpts *aopt; + ++ /* ++ * Catalog relations are never sent by LR, no chance to use the delta_apply ++ * feature ++ */ ++ if (IsCatalogRelation(relation)) ++ return NULL; ++ + for (attnum = 1; attnum <= tupdesc->natts; attnum++) + { + aopt = get_attribute_options(relation->rd_id, attnum); @@ -158,7 +160,7 @@ index 0dcd6ee817e..dcf26167cae 100644 /* * Check which columns are being updated. * -@@ -9132,6 +9165,7 @@ log_heap_new_cid(Relation relation, HeapTuple tup) +@@ -9069,6 +9104,7 @@ log_heap_new_cid(Relation relation, HeapTuple tup) */ static HeapTuple ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required, @@ -166,7 +168,7 @@ index 0dcd6ee817e..dcf26167cae 100644 bool *copy) { TupleDesc desc = RelationGetDescr(relation); -@@ -9164,13 +9198,16 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required, +@@ -9101,13 +9137,16 @@ ExtractReplicaIdentity(Relation relation, HeapTuple tp, bool key_required, } /* if the key isn't required and we're only logging the key, we're done */ @@ -185,7 +187,7 @@ index 0dcd6ee817e..dcf26167cae 100644 * If there's no defined replica identity columns, treat as !key_required. * (This case should not be reachable from heap_update, since that should diff --git a/src/include/utils/attoptcache.h b/src/include/utils/attoptcache.h -index f684a772af5..6c965fede13 100644 +index ee37af95001..98b48a8fd89 100644 --- a/src/include/utils/attoptcache.h +++ b/src/include/utils/attoptcache.h @@ -21,6 +21,8 @@ typedef struct AttributeOpts @@ -196,4 +198,4 @@ index f684a772af5..6c965fede13 100644 + Oid delta_apply_function; } AttributeOpts; - extern AttributeOpts *get_attribute_options(Oid attrelid, int attnum); + extern AttributeOpts *get_attribute_options(Oid spcid, int attnum); From 4c8b424dab4e8d3fadcc2f504269dad29ee2c6b5 Mon Sep 17 00:00:00 2001 From: "Andrei V. Lepikhov" Date: Mon, 22 Dec 2025 10:15:49 +0100 Subject: [PATCH 5/5] Check the IF EXISTS clause in add_ddl_to_repset. In case of IF EXISTS / NOT EXISTS clause Postgres core utility call produces an INFO message and lets utility hooks do their job as usual. In the Spock case, we need to do the same and carefully process the clause so it doesn't produce an ERROR that breaks the convention. --- src/spock_autoddl.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/spock_autoddl.c b/src/spock_autoddl.c index 5aff9ea7..f5c94d8d 100644 --- a/src/spock_autoddl.c +++ b/src/spock_autoddl.c @@ -120,13 +120,14 @@ spock_autoddl_process(PlannedStmt *pstmt, void add_ddl_to_repset(Node *parsetree) { - Relation targetrel; - SpockRepSet *repset; + Relation targetrel; + SpockRepSet *repset; SpockLocalNode *node; - Oid reloid = InvalidOid; - RangeVar *relation = NULL; - List *reloids = NIL; - ListCell *lc; + Oid reloid = InvalidOid; + RangeVar *relation = NULL; + List *reloids = NIL; + ListCell *lc; + bool missing_ok = false; /* no need to proceed if spock_include_ddl_repset is off */ if (!spock_include_ddl_repset) @@ -163,6 +164,8 @@ add_ddl_to_repset(Node *parsetree) { return; } + + missing_ok = ((AlterTableStmt *) parsetree)->missing_ok; } else if (nodeTag(parsetree) == T_CreateStmt) relation = castNode(CreateStmt, parsetree)->relation; @@ -216,7 +219,15 @@ add_ddl_to_repset(Node *parsetree) if (OidIsValid(reloid)) targetrel = RelationIdGetRelation(reloid); else - targetrel = table_openrv(relation, AccessShareLock); + { + targetrel = table_openrv_extended(relation, AccessShareLock, missing_ok); + if (targetrel == NULL) + /* + * If relation doesn't exist - quietly exit. It is assumed that the core + * already produced an INFO message. + */ + return; + } reloid = RelationGetRelid(targetrel);