From 2de2b9917d43d598da56c996361d934c45e52df2 Mon Sep 17 00:00:00 2001 From: bdrouvotAWS Date: Fri, 25 Nov 2022 09:42:40 +0000 Subject: [PATCH v27] Add info in WAL records in preparation for logical slot conflict handling. When a WAL replay on standby indicates that a catalog table tuple is to be deleted by an xid that is greater than a logical slot's catalog_xmin, then that means the slot's catalog_xmin conflicts with the xid, and we need to handle the conflict. While subsequent commits will do the actual conflict handling, this commit adds a new field onCatalogTable in such WAL records, that is true for catalog tables, so as to arrange for conflict handling. Author: Andres Freund (in an older version), Amit Khandekar, Bertrand Drouvot Reviewed-By: Bertrand Drouvot, Andres Freund, Robert Haas, Fabrizio de Royes Mello --- contrib/test_decoding/expected/ddl.out | 29 +++++++++++++ contrib/test_decoding/sql/ddl.sql | 7 ++++ doc/src/sgml/catalogs.sgml | 11 +++++ src/backend/access/common/reloptions.c | 2 +- src/backend/access/gist/gistxlog.c | 1 + src/backend/access/hash/hashinsert.c | 1 + src/backend/access/heap/heapam.c | 4 +- src/backend/access/heap/pruneheap.c | 1 + src/backend/access/heap/visibilitymap.c | 3 +- src/backend/access/nbtree/nbtpage.c | 2 + src/backend/access/spgist/spgvacuum.c | 1 + src/backend/catalog/index.c | 14 +++++-- src/backend/commands/tablecmds.c | 55 ++++++++++++++++++++++++- src/include/access/gistxlog.h | 2 + src/include/access/hash_xlog.h | 1 + src/include/access/heapam_xlog.h | 5 ++- src/include/access/nbtxlog.h | 2 + src/include/access/spgxlog.h | 1 + src/include/catalog/pg_index.h | 2 + src/include/utils/rel.h | 34 +++++++++++++++ 20 files changed, 169 insertions(+), 9 deletions(-) 11.8% contrib/test_decoding/expected/ 7.0% contrib/test_decoding/sql/ 6.8% doc/src/sgml/ 7.8% src/backend/access/heap/ 5.8% src/backend/access/ 6.3% src/backend/catalog/ 23.9% src/backend/commands/ 6.1% src/include/access/ 22.6% src/include/utils/ diff --git a/contrib/test_decoding/expected/ddl.out b/contrib/test_decoding/expected/ddl.out index 9a28b5ddc5..40cf2f4dc4 100644 --- a/contrib/test_decoding/expected/ddl.out +++ b/contrib/test_decoding/expected/ddl.out @@ -483,6 +483,7 @@ CREATE TABLE replication_metadata ( ) WITH (user_catalog_table = true) ; +CREATE INDEX replication_metadata_idx1 on replication_metadata(relation); \d+ replication_metadata Table "public.replication_metadata" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description @@ -492,8 +493,15 @@ WITH (user_catalog_table = true) options | text[] | | | | extended | | Indexes: "replication_metadata_pkey" PRIMARY KEY, btree (id) + "replication_metadata_idx1" btree (relation) Options: user_catalog_table=true +SELECT bool_and(indisusercatalog) from pg_index where indrelid = 'replication_metadata'::regclass; + bool_and +---------- + t +(1 row) + INSERT INTO replication_metadata(relation, options) VALUES ('foo', ARRAY['a', 'b']); ALTER TABLE replication_metadata RESET (user_catalog_table); @@ -506,6 +514,13 @@ ALTER TABLE replication_metadata RESET (user_catalog_table); options | text[] | | | | extended | | Indexes: "replication_metadata_pkey" PRIMARY KEY, btree (id) + "replication_metadata_idx1" btree (relation) + +SELECT bool_or(indisusercatalog) from pg_index where indrelid = 'replication_metadata'::regclass; + bool_or +--------- + f +(1 row) INSERT INTO replication_metadata(relation, options) VALUES ('bar', ARRAY['a', 'b']); @@ -519,8 +534,15 @@ ALTER TABLE replication_metadata SET (user_catalog_table = true); options | text[] | | | | extended | | Indexes: "replication_metadata_pkey" PRIMARY KEY, btree (id) + "replication_metadata_idx1" btree (relation) Options: user_catalog_table=true +SELECT bool_and(indisusercatalog) from pg_index where indrelid = 'replication_metadata'::regclass; + bool_and +---------- + t +(1 row) + INSERT INTO replication_metadata(relation, options) VALUES ('blub', NULL); -- make sure rewrites don't work @@ -538,8 +560,15 @@ ALTER TABLE replication_metadata SET (user_catalog_table = false); rewritemeornot | integer | | | | plain | | Indexes: "replication_metadata_pkey" PRIMARY KEY, btree (id) + "replication_metadata_idx1" btree (relation) Options: user_catalog_table=false +SELECT bool_or(indisusercatalog) from pg_index where indrelid = 'replication_metadata'::regclass; + bool_or +--------- + f +(1 row) + INSERT INTO replication_metadata(relation, options) VALUES ('zaphod', NULL); SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); diff --git a/contrib/test_decoding/sql/ddl.sql b/contrib/test_decoding/sql/ddl.sql index 4f76bed72c..85ddd4be03 100644 --- a/contrib/test_decoding/sql/ddl.sql +++ b/contrib/test_decoding/sql/ddl.sql @@ -276,19 +276,25 @@ CREATE TABLE replication_metadata ( ) WITH (user_catalog_table = true) ; + +CREATE INDEX replication_metadata_idx1 on replication_metadata(relation); + \d+ replication_metadata +SELECT bool_and(indisusercatalog) from pg_index where indrelid = 'replication_metadata'::regclass; INSERT INTO replication_metadata(relation, options) VALUES ('foo', ARRAY['a', 'b']); ALTER TABLE replication_metadata RESET (user_catalog_table); \d+ replication_metadata +SELECT bool_or(indisusercatalog) from pg_index where indrelid = 'replication_metadata'::regclass; INSERT INTO replication_metadata(relation, options) VALUES ('bar', ARRAY['a', 'b']); ALTER TABLE replication_metadata SET (user_catalog_table = true); \d+ replication_metadata +SELECT bool_and(indisusercatalog) from pg_index where indrelid = 'replication_metadata'::regclass; INSERT INTO replication_metadata(relation, options) VALUES ('blub', NULL); @@ -299,6 +305,7 @@ ALTER TABLE replication_metadata ALTER COLUMN rewritemeornot TYPE text; ALTER TABLE replication_metadata SET (user_catalog_table = false); \d+ replication_metadata +SELECT bool_or(indisusercatalog) from pg_index where indrelid = 'replication_metadata'::regclass; INSERT INTO replication_metadata(relation, options) VALUES ('zaphod', NULL); diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 9ed2b020b7..18d6b99cac 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -4437,6 +4437,17 @@ SCRAM-SHA-256$<iteration count>:&l + + + indisusercatalog bool + + + If true, the index is linked to a table that is declared as an additional + catalog table for purposes of logical replication (means has user_catalog_table) + set to true. + + + indisreplident bool diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 75b7344891..4b41f5e68d 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -120,7 +120,7 @@ static relopt_bool boolRelOpts[] = RELOPT_KIND_HEAP, AccessExclusiveLock }, - false + HEAP_DEFAULT_USER_CATALOG_TABLE }, { { diff --git a/src/backend/access/gist/gistxlog.c b/src/backend/access/gist/gistxlog.c index cb5affa3d2..65fc18554a 100644 --- a/src/backend/access/gist/gistxlog.c +++ b/src/backend/access/gist/gistxlog.c @@ -608,6 +608,7 @@ gistXLogPageReuse(Relation rel, BlockNumber blkno, FullTransactionId deleteXid) */ /* XLOG stuff */ + xlrec_reuse.onCatalogTable = IndexIsAccessibleInLogicalDecoding(rel); xlrec_reuse.locator = rel->rd_locator; xlrec_reuse.block = blkno; xlrec_reuse.snapshotConflictHorizon = deleteXid; diff --git a/src/backend/access/hash/hashinsert.c b/src/backend/access/hash/hashinsert.c index 9a921e341e..99d5d53d21 100644 --- a/src/backend/access/hash/hashinsert.c +++ b/src/backend/access/hash/hashinsert.c @@ -432,6 +432,7 @@ _hash_vacuum_one_page(Relation rel, Relation hrel, Buffer metabuf, Buffer buf) xl_hash_vacuum_one_page xlrec; XLogRecPtr recptr; + xlrec.onCatalogTable = RelationIsAccessibleInLogicalDecoding(hrel); xlrec.snapshotConflictHorizon = snapshotConflictHorizon; xlrec.ntuples = ndeletable; diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 747db50376..7d48b9214b 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -6831,6 +6831,7 @@ heap_freeze_execute_prepared(Relation rel, Buffer buffer, snapshotConflictHorizon = FreezeLimit; TransactionIdRetreat(snapshotConflictHorizon); + xlrec.onCatalogTable = RelationIsAccessibleInLogicalDecoding(rel); xlrec.snapshotConflictHorizon = snapshotConflictHorizon; xlrec.nplans = nplans; @@ -8248,7 +8249,7 @@ bottomup_sort_and_shrink(TM_IndexDeleteOp *delstate) * update the heap page's LSN. */ XLogRecPtr -log_heap_visible(RelFileLocator rlocator, Buffer heap_buffer, Buffer vm_buffer, +log_heap_visible(Relation rel, Buffer heap_buffer, Buffer vm_buffer, TransactionId snapshotConflictHorizon, uint8 vmflags) { xl_heap_visible xlrec; @@ -8258,6 +8259,7 @@ log_heap_visible(RelFileLocator rlocator, Buffer heap_buffer, Buffer vm_buffer, Assert(BufferIsValid(heap_buffer)); Assert(BufferIsValid(vm_buffer)); + xlrec.onCatalogTable = RelationIsAccessibleInLogicalDecoding(rel); xlrec.snapshotConflictHorizon = snapshotConflictHorizon; xlrec.flags = vmflags; XLogBeginInsert(); diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c index 91c5f5e9ef..c2244ccaa3 100644 --- a/src/backend/access/heap/pruneheap.c +++ b/src/backend/access/heap/pruneheap.c @@ -418,6 +418,7 @@ heap_page_prune(Relation relation, Buffer buffer, xl_heap_prune xlrec; XLogRecPtr recptr; + xlrec.onCatalogTable = RelationIsAccessibleInLogicalDecoding(relation); xlrec.snapshotConflictHorizon = prstate.snapshotConflictHorizon; xlrec.nredirected = prstate.nredirected; xlrec.ndead = prstate.ndead; diff --git a/src/backend/access/heap/visibilitymap.c b/src/backend/access/heap/visibilitymap.c index 4ed70275e2..0bd73f4d9f 100644 --- a/src/backend/access/heap/visibilitymap.c +++ b/src/backend/access/heap/visibilitymap.c @@ -283,8 +283,7 @@ visibilitymap_set(Relation rel, BlockNumber heapBlk, Buffer heapBuf, if (XLogRecPtrIsInvalid(recptr)) { Assert(!InRecovery); - recptr = log_heap_visible(rel->rd_locator, heapBuf, vmBuf, - cutoff_xid, flags); + recptr = log_heap_visible(rel, heapBuf, vmBuf, cutoff_xid, flags); /* * If data checksums are enabled (or wal_log_hints=on), we diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c index 65aa44893c..1e5bf7513e 100644 --- a/src/backend/access/nbtree/nbtpage.c +++ b/src/backend/access/nbtree/nbtpage.c @@ -836,6 +836,7 @@ _bt_log_reuse_page(Relation rel, BlockNumber blkno, FullTransactionId safexid) */ /* XLOG stuff */ + xlrec_reuse.onCatalogTable = IndexIsAccessibleInLogicalDecoding(rel); xlrec_reuse.locator = rel->rd_locator; xlrec_reuse.block = blkno; xlrec_reuse.snapshotConflictHorizon = safexid; @@ -1358,6 +1359,7 @@ _bt_delitems_delete(Relation rel, Buffer buf, XLogRecPtr recptr; xl_btree_delete xlrec_delete; + xlrec_delete.onCatalogTable = IndexIsAccessibleInLogicalDecoding(rel); xlrec_delete.snapshotConflictHorizon = snapshotConflictHorizon; xlrec_delete.ndeleted = ndeletable; xlrec_delete.nupdated = nupdatable; diff --git a/src/backend/access/spgist/spgvacuum.c b/src/backend/access/spgist/spgvacuum.c index ad90b213b9..000133aad6 100644 --- a/src/backend/access/spgist/spgvacuum.c +++ b/src/backend/access/spgist/spgvacuum.c @@ -503,6 +503,7 @@ vacuumRedirectAndPlaceholder(Relation index, Buffer buffer) spgxlogVacuumRedirect xlrec; GlobalVisState *vistest; + xlrec.onCatalogTable = IndexIsAccessibleInLogicalDecoding(index); xlrec.nToPlaceholder = 0; xlrec.snapshotConflictHorizon = InvalidTransactionId; diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 61f1d3926a..f6b2c9ac71 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -123,7 +123,8 @@ static void UpdateIndexRelation(Oid indexoid, Oid heapoid, bool isexclusion, bool immediate, bool isvalid, - bool isready); + bool isready, + bool is_user_catalog); static void index_update_stats(Relation rel, bool hasindex, double reltuples); @@ -545,7 +546,8 @@ UpdateIndexRelation(Oid indexoid, bool isexclusion, bool immediate, bool isvalid, - bool isready) + bool isready, + bool is_user_catalog) { int2vector *indkey; oidvector *indcollation; @@ -622,6 +624,7 @@ UpdateIndexRelation(Oid indexoid, values[Anum_pg_index_indcheckxmin - 1] = BoolGetDatum(false); values[Anum_pg_index_indisready - 1] = BoolGetDatum(isready); values[Anum_pg_index_indislive - 1] = BoolGetDatum(true); + values[Anum_pg_index_indisusercatalog - 1] = BoolGetDatum(is_user_catalog); values[Anum_pg_index_indisreplident - 1] = BoolGetDatum(false); values[Anum_pg_index_indkey - 1] = PointerGetDatum(indkey); values[Anum_pg_index_indcollation - 1] = PointerGetDatum(indcollation); @@ -735,6 +738,7 @@ index_create(Relation heapRelation, TransactionId relfrozenxid; MultiXactId relminmxid; bool create_storage = !RelFileNumberIsValid(relFileNumber); + bool isusercatalog = false; /* constraint flags can only be set when a constraint is requested */ Assert((constr_flags == 0) || @@ -1014,13 +1018,17 @@ index_create(Relation heapRelation, * (Or, could define a rule to maintain the predicate) --Nels, Feb '92 * ---------------- */ + if (heapRelation->rd_options) + isusercatalog = ((StdRdOptions *) (heapRelation)->rd_options)->user_catalog_table; + UpdateIndexRelation(indexRelationId, heapRelationId, parentIndexRelid, indexInfo, collationObjectId, classObjectId, coloptions, isprimary, is_exclusion, (constr_flags & INDEX_CONSTR_CREATE_DEFERRABLE) == 0, !concurrent && !invalid, - !concurrent); + !concurrent, + isusercatalog); /* * Register relcache invalidation on the indexes' heap relation, to diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 845208d662..faa1fcc07d 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -103,6 +103,7 @@ #include "utils/syscache.h" #include "utils/timestamp.h" #include "utils/typcache.h" +#include "utils/rel.h" /* * ON COMMIT action list @@ -14183,6 +14184,10 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation, Datum repl_val[Natts_pg_class]; bool repl_null[Natts_pg_class]; bool repl_repl[Natts_pg_class]; + ListCell *cell; + List *rel_options; + bool catalog_table_val = HEAP_DEFAULT_USER_CATALOG_TABLE; + bool catalog_table = false; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; if (defList == NIL && operation != AT_ReplaceRelOptions) @@ -14249,7 +14254,6 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation, { Query *view_query = get_view_query(rel); List *view_options = untransformRelOptions(newOptions); - ListCell *cell; bool check_option = false; foreach(cell, view_options) @@ -14277,6 +14281,20 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation, } } + /* If user_catalog_table is part of the new options, record its new value */ + rel_options = untransformRelOptions(newOptions); + + foreach(cell, rel_options) + { + DefElem *defel = (DefElem *) lfirst(cell); + + if (strcmp(defel->defname, "user_catalog_table") == 0) + { + catalog_table = true; + catalog_table_val = defGetBoolean(defel); + } + } + /* * All we need do here is update the pg_class row; the new options will be * propagated into relcaches during post-commit cache inval. @@ -14303,6 +14321,41 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation, ReleaseSysCache(tuple); + /* Update the indexes if there is a need to */ + if (catalog_table || operation == AT_ResetRelOptions) + { + Relation pg_index; + HeapTuple pg_index_tuple; + Form_pg_index pg_index_form; + ListCell *index; + + pg_index = table_open(IndexRelationId, RowExclusiveLock); + + foreach(index, RelationGetIndexList(rel)) + { + Oid thisIndexOid = lfirst_oid(index); + + pg_index_tuple = SearchSysCacheCopy1(INDEXRELID, + ObjectIdGetDatum(thisIndexOid)); + if (!HeapTupleIsValid(pg_index_tuple)) + elog(ERROR, "cache lookup failed for index %u", thisIndexOid); + pg_index_form = (Form_pg_index) GETSTRUCT(pg_index_tuple); + + /* Modify the index only if user_catalog_table differ */ + if (catalog_table_val != pg_index_form->indisusercatalog) + { + pg_index_form->indisusercatalog = catalog_table_val; + CatalogTupleUpdate(pg_index, &pg_index_tuple->t_self, pg_index_tuple); + InvokeObjectPostAlterHookArg(IndexRelationId, thisIndexOid, 0, + InvalidOid, true); + } + + heap_freetuple(pg_index_tuple); + } + + table_close(pg_index, RowExclusiveLock); + } + /* repeat the whole exercise for the toast table, if there's one */ if (OidIsValid(rel->rd_rel->reltoastrelid)) { diff --git a/src/include/access/gistxlog.h b/src/include/access/gistxlog.h index 33f1c7e31b..f924f9f7be 100644 --- a/src/include/access/gistxlog.h +++ b/src/include/access/gistxlog.h @@ -49,6 +49,7 @@ typedef struct gistxlogPageUpdate */ typedef struct gistxlogDelete { + bool onCatalogTable; TransactionId snapshotConflictHorizon; uint16 ntodelete; /* number of deleted offsets */ @@ -97,6 +98,7 @@ typedef struct gistxlogPageDelete */ typedef struct gistxlogPageReuse { + bool onCatalogTable; RelFileLocator locator; BlockNumber block; FullTransactionId snapshotConflictHorizon; diff --git a/src/include/access/hash_xlog.h b/src/include/access/hash_xlog.h index 6dafb4a598..b16e7038e2 100644 --- a/src/include/access/hash_xlog.h +++ b/src/include/access/hash_xlog.h @@ -250,6 +250,7 @@ typedef struct xl_hash_init_bitmap_page */ typedef struct xl_hash_vacuum_one_page { + bool onCatalogTable; TransactionId snapshotConflictHorizon; int ntuples; diff --git a/src/include/access/heapam_xlog.h b/src/include/access/heapam_xlog.h index 5c77290eec..dec62e8bed 100644 --- a/src/include/access/heapam_xlog.h +++ b/src/include/access/heapam_xlog.h @@ -242,6 +242,7 @@ typedef struct xl_heap_update */ typedef struct xl_heap_prune { + bool onCatalogTable; TransactionId snapshotConflictHorizon; uint16 nredirected; uint16 ndead; @@ -342,6 +343,7 @@ typedef struct xl_heap_freeze_plan */ typedef struct xl_heap_freeze_page { + bool onCatalogTable; TransactionId snapshotConflictHorizon; uint16 nplans; @@ -359,6 +361,7 @@ typedef struct xl_heap_freeze_page */ typedef struct xl_heap_visible { + bool onCatalogTable; TransactionId snapshotConflictHorizon; uint8 flags; } xl_heap_visible; @@ -408,7 +411,7 @@ extern void heap2_desc(StringInfo buf, XLogReaderState *record); extern const char *heap2_identify(uint8 info); extern void heap_xlog_logical_rewrite(XLogReaderState *r); -extern XLogRecPtr log_heap_visible(RelFileLocator rlocator, Buffer heap_buffer, +extern XLogRecPtr log_heap_visible(Relation rel, Buffer heap_buffer, Buffer vm_buffer, TransactionId snapshotConflictHorizon, uint8 vmflags); diff --git a/src/include/access/nbtxlog.h b/src/include/access/nbtxlog.h index 3b2d959c69..f8ae3827d3 100644 --- a/src/include/access/nbtxlog.h +++ b/src/include/access/nbtxlog.h @@ -185,6 +185,7 @@ typedef struct xl_btree_dedup */ typedef struct xl_btree_reuse_page { + bool onCatalogTable; RelFileLocator locator; BlockNumber block; FullTransactionId snapshotConflictHorizon; @@ -232,6 +233,7 @@ typedef struct xl_btree_vacuum typedef struct xl_btree_delete { + bool onCatalogTable; TransactionId snapshotConflictHorizon; uint16 ndeleted; uint16 nupdated; diff --git a/src/include/access/spgxlog.h b/src/include/access/spgxlog.h index 82332cb694..8b4ab0e206 100644 --- a/src/include/access/spgxlog.h +++ b/src/include/access/spgxlog.h @@ -237,6 +237,7 @@ typedef struct spgxlogVacuumRoot typedef struct spgxlogVacuumRedirect { + bool onCatalogTable; uint16 nToPlaceholder; /* number of redirects to make placeholders */ OffsetNumber firstPlaceholder; /* first placeholder tuple to remove */ TransactionId snapshotConflictHorizon; /* newest XID of removed redirects */ diff --git a/src/include/catalog/pg_index.h b/src/include/catalog/pg_index.h index f853846ee1..dd16431378 100644 --- a/src/include/catalog/pg_index.h +++ b/src/include/catalog/pg_index.h @@ -43,6 +43,8 @@ CATALOG(pg_index,2610,IndexRelationId) BKI_SCHEMA_MACRO bool indcheckxmin; /* must we wait for xmin to be old? */ bool indisready; /* is this index ready for inserts? */ bool indislive; /* is this index alive at all? */ + bool indisusercatalog; /* is this index linked to a user catalog + * relation? */ bool indisreplident; /* is this index the identity for replication? */ /* variable-length fields start here, but we allow direct access to indkey */ diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index f383a2fca9..9b77e23c29 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -27,6 +27,7 @@ #include "storage/smgr.h" #include "utils/relcache.h" #include "utils/reltrigger.h" +#include "catalog/catalog.h" /* @@ -343,6 +344,7 @@ typedef struct StdRdOptions #define HEAP_MIN_FILLFACTOR 10 #define HEAP_DEFAULT_FILLFACTOR 100 +#define HEAP_DEFAULT_USER_CATALOG_TABLE false /* * RelationGetToastTupleTarget @@ -378,6 +380,9 @@ typedef struct StdRdOptions * RelationIsUsedAsCatalogTable * Returns whether the relation should be treated as a catalog table * from the pov of logical decoding. Note multiple eval of argument! + * This definition should not invoke anything that performs catalog + * access; otherwise it may cause infinite recursion. Check the comments + * in RelationIsAccessibleInLogicalDecoding() for details. */ #define RelationIsUsedAsCatalogTable(relation) \ ((relation)->rd_options && \ @@ -678,12 +683,41 @@ RelationCloseSmgr(Relation relation) * RelationIsAccessibleInLogicalDecoding * True if we need to log enough information to have access via * decoding snapshot. + * This definition should not invoke anything that performs catalog + * access. Otherwise, e.g. logging a WAL entry for catalog relation may + * invoke this function, which will in turn do catalog access, which may + * in turn cause another similar WAL entry to be logged, leading to + * infinite recursion. */ #define RelationIsAccessibleInLogicalDecoding(relation) \ (XLogLogicalInfoActive() && \ RelationNeedsWAL(relation) && \ (IsCatalogRelation(relation) || RelationIsUsedAsCatalogTable(relation))) +/* + * IndexIsUserCatalog + * True if index is linked to a user catalog relation. + */ +#define IndexIsUserCatalog(relation) \ + (AssertMacro(relation->rd_rel->relkind == RELKIND_INDEX), \ + (relation)->rd_index->indisusercatalog) + +/* + * IndexIsAccessibleInLogicalDecoding + * True if we need to log enough information to have access via + * decoding snapshot. + * This definition should not invoke anything that performs catalog + * access. Otherwise, e.g. logging a WAL entry for catalog relation may + * invoke this function, which will in turn do catalog access, which may + * in turn cause another similar WAL entry to be logged, leading to + * infinite recursion. + */ +#define IndexIsAccessibleInLogicalDecoding(relation) \ + (AssertMacro(relation->rd_rel->relkind == RELKIND_INDEX), \ + XLogLogicalInfoActive() && \ + RelationNeedsWAL(relation) && \ + (IsCatalogRelation(relation) || IndexIsUserCatalog(relation))) + /* * RelationIsLogicallyLogged * True if we need to log enough information to extract the data from the -- 2.34.1