diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c index e7765242270..81f27042bc4 100644 --- a/src/backend/executor/execReplication.c +++ b/src/backend/executor/execReplication.c @@ -222,16 +222,7 @@ retry: if (!isIdxSafeToSkipDuplicates) { if (eq == NULL) - { -#ifdef USE_ASSERT_CHECKING - /* apply assertions only once for the input idxoid */ - IndexInfo *indexInfo = BuildIndexInfo(idxrel); - - Assert(IsIndexUsableForReplicaIdentityFull(indexInfo)); -#endif - eq = palloc0(sizeof(*eq) * outslot->tts_tupleDescriptor->natts); - } if (!tuples_equal(outslot, searchslot, eq)) continue; diff --git a/src/backend/replication/logical/relation.c b/src/backend/replication/logical/relation.c index ed57e5d2b6f..d62eefed138 100644 --- a/src/backend/replication/logical/relation.c +++ b/src/backend/replication/logical/relation.c @@ -734,71 +734,9 @@ logicalrep_partition_open(LogicalRepRelMapEntry *root, return entry; } -/* - * Returns true if the given index consists only of expressions such as: - * CREATE INDEX idx ON table(foo(col)); - * - * Returns false even if there is one column reference: - * CREATE INDEX idx ON table(foo(col), col_2); - */ -static bool -IsIndexOnlyOnExpression(IndexInfo *indexInfo) -{ - for (int i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++) - { - AttrNumber attnum = indexInfo->ii_IndexAttrNumbers[i]; - - if (AttributeNumberIsValid(attnum)) - return false; - } - - return true; -} - -/* - * Returns true if the attrmap contains the leftmost column of the index. - * Otherwise returns false. - * - * attrmap is a map of local attributes to remote ones. We can consult this - * map to check whether the local index attribute has a corresponding remote - * attribute. - */ -static bool -RemoteRelContainsLeftMostColumnOnIdx(IndexInfo *indexInfo, AttrMap *attrmap) -{ - AttrNumber keycol; - - Assert(indexInfo->ii_NumIndexAttrs >= 1); - - keycol = indexInfo->ii_IndexAttrNumbers[0]; - if (!AttributeNumberIsValid(keycol)) - return false; - - if (attrmap->maplen <= AttrNumberGetAttrOffset(keycol)) - return false; - - return attrmap->attnums[AttrNumberGetAttrOffset(keycol)] >= 0; -} - /* * Returns the oid of an index that can be used by the apply worker to scan - * the relation. The index must be btree or hash, non-partial, and the leftmost - * field must be a column (not an expression) that references the remote - * relation column. These limitations help to keep the index scan similar - * to PK/RI index scans. - * - * Note that the limitations of index scans for replica identity full only - * adheres to a subset of the limitations of PK/RI. For example, we support - * columns that are marked as [NULL] or we are not interested in the [NOT - * DEFERRABLE] aspect of constraints here. It works for us because we always - * compare the tuples for non-PK/RI index scans. See - * RelationFindReplTupleByIndex(). - * - * XXX: See IsIndexUsableForReplicaIdentityFull() to know the challenges in - * supporting indexes other than btree and hash. For partial indexes, the - * required changes are likely to be larger. If none of the tuples satisfy - * the expression for the index scan, we fall-back to sequential execution, - * which might not be a good idea in some cases. + * the relation. * * We expect to call this function when REPLICA IDENTITY FULL is defined for * the remote relation. @@ -815,19 +753,16 @@ FindUsableIndexForReplicaIdentityFull(Relation localrel, AttrMap *attrmap) { Oid idxoid = lfirst_oid(lc); bool isUsableIdx; - bool containsLeftMostCol; Relation idxRel; IndexInfo *idxInfo; idxRel = index_open(idxoid, AccessShareLock); idxInfo = BuildIndexInfo(idxRel); - isUsableIdx = IsIndexUsableForReplicaIdentityFull(idxInfo); - containsLeftMostCol = - RemoteRelContainsLeftMostColumnOnIdx(idxInfo, attrmap); + isUsableIdx = IsIndexUsableForReplicaIdentityFull(idxInfo, attrmap); index_close(idxRel, AccessShareLock); /* Return the first eligible index found */ - if (isUsableIdx && containsLeftMostCol) + if (isUsableIdx) return idxoid; } @@ -835,11 +770,24 @@ FindUsableIndexForReplicaIdentityFull(Relation localrel, AttrMap *attrmap) } /* - * Returns true if the index is usable for replica identity full. For details, - * see FindUsableIndexForReplicaIdentityFull. + * Returns true if the index is usable for replica identity full. * - * Currently, only Btree and Hash indexes can be returned as usable. This - * is due to following reasons: + * The index must be btree or hash, non-partial, and the leftmost field must be + * a column (not an expression) that references the remote relation column. These + * limitations help to keep the index scan similar to PK/RI index scans. + * + * attrmap is a map of local attributes to remote ones. We can consult this + * map to check whether the local index attribute has a corresponding remote + * attribute. + * + * Note that the limitations of index scans for replica identity full only + * adheres to a subset of the limitations of PK/RI. For example, we support + * columns that are marked as [NULL] or we are not interested in the [NOT + * DEFERRABLE] aspect of constraints here. It works for us because we always + * compare the tuples for non-PK/RI index scans. See + * RelationFindReplTupleByIndex(). + * + * The reasons why only Btree and Hash indexes can be considered as usable are: * * 1) Other index access methods don't have a fixed strategy for equality * operation. Refer get_equal_strategy_number_for_am(). @@ -851,16 +799,38 @@ FindUsableIndexForReplicaIdentityFull(Relation localrel, AttrMap *attrmap) * * XXX: Note that BRIN and GIN indexes do not implement "amgettuple" which * will be used later to fetch the tuples. See RelationFindReplTupleByIndex(). + * + * XXX: To support partial indexes, the required changes are likely to be larger. + * If none of the tuples satisfy the expression for the index scan, we fall-back + * to sequential execution, which might not be a good idea in some cases. */ bool -IsIndexUsableForReplicaIdentityFull(IndexInfo *indexInfo) +IsIndexUsableForReplicaIdentityFull(IndexInfo *indexInfo, AttrMap *attrmap) { + AttrNumber keycol; + /* Ensure that the index access method has a valid equal strategy */ if (get_equal_strategy_number_for_am(indexInfo->ii_Am) == InvalidStrategy) return false; + + /* The index must not be a partial index */ if (indexInfo->ii_Predicate != NIL) return false; - if (IsIndexOnlyOnExpression(indexInfo)) + + Assert(indexInfo->ii_NumIndexAttrs >= 1); + + /* The leftmost index field must not be an expression */ + keycol = indexInfo->ii_IndexAttrNumbers[0]; + if (!AttributeNumberIsValid(keycol)) + return false; + + /* + * And the leftmost index field must reference the remote relation column. + * This is because if it doesn't, the sequential scan is favorable over + * index scan in most cases. + */ + if (attrmap->maplen <= AttrNumberGetAttrOffset(keycol) || + attrmap->attnums[AttrNumberGetAttrOffset(keycol)] < 0) return false; #ifdef USE_ASSERT_CHECKING diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c index cb6659fc619..832b1cf7642 100644 --- a/src/backend/replication/logical/worker.c +++ b/src/backend/replication/logical/worker.c @@ -140,6 +140,7 @@ #include #include +#include "access/genam.h" #include "access/table.h" #include "access/tableam.h" #include "access/twophase.h" @@ -410,7 +411,7 @@ static void apply_handle_delete_internal(ApplyExecutionData *edata, ResultRelInfo *relinfo, TupleTableSlot *remoteslot, Oid localindexoid); -static bool FindReplTupleInLocalRel(EState *estate, Relation localrel, +static bool FindReplTupleInLocalRel(ApplyExecutionData *edata, Relation localrel, LogicalRepRelation *remoterel, Oid localidxoid, TupleTableSlot *remoteslot, @@ -2663,7 +2664,7 @@ apply_handle_update_internal(ApplyExecutionData *edata, EvalPlanQualInit(&epqstate, estate, NULL, NIL, -1, NIL); ExecOpenIndices(relinfo, false); - found = FindReplTupleInLocalRel(estate, localrel, + found = FindReplTupleInLocalRel(edata, localrel, &relmapentry->remoterel, localindexoid, remoteslot, &localslot); @@ -2816,7 +2817,7 @@ apply_handle_delete_internal(ApplyExecutionData *edata, EvalPlanQualInit(&epqstate, estate, NULL, NIL, -1, NIL); ExecOpenIndices(relinfo, false); - found = FindReplTupleInLocalRel(estate, localrel, remoterel, localindexoid, + found = FindReplTupleInLocalRel(edata, localrel, remoterel, localindexoid, remoteslot, &localslot); /* If found delete it. */ @@ -2855,12 +2856,13 @@ apply_handle_delete_internal(ApplyExecutionData *edata, * Local tuple, if found, is returned in '*localslot'. */ static bool -FindReplTupleInLocalRel(EState *estate, Relation localrel, +FindReplTupleInLocalRel(ApplyExecutionData *edata, Relation localrel, LogicalRepRelation *remoterel, Oid localidxoid, TupleTableSlot *remoteslot, TupleTableSlot **localslot) { + EState *estate = edata->estate; bool found; /* @@ -2875,9 +2877,21 @@ FindReplTupleInLocalRel(EState *estate, Relation localrel, (remoterel->replident == REPLICA_IDENTITY_FULL)); if (OidIsValid(localidxoid)) + { +#ifdef USE_ASSERT_CHECKING + Relation idxrel = index_open(localidxoid, AccessShareLock); + + /* Index must be PK, RI, or usable for REPLICA IDENTITY FULL tables */ + Assert(GetRelationIdentityOrPK(idxrel) == localidxoid || + IsIndexUsableForReplicaIdentityFull(BuildIndexInfo(idxrel), + edata->targetRel->attrmap)); + index_close(idxrel, AccessShareLock); +#endif + found = RelationFindReplTupleByIndex(localrel, localidxoid, LockTupleExclusive, remoteslot, *localslot); + } else found = RelationFindReplTupleSeq(localrel, LockTupleExclusive, remoteslot, *localslot); @@ -2995,7 +3009,7 @@ apply_handle_tuple_routing(ApplyExecutionData *edata, bool found; /* Get the matching local tuple from the partition. */ - found = FindReplTupleInLocalRel(estate, partrel, + found = FindReplTupleInLocalRel(edata, partrel, &part_entry->remoterel, part_entry->localindexoid, remoteslot_part, &localslot); diff --git a/src/include/replication/logicalrelation.h b/src/include/replication/logicalrelation.h index 921b9974db7..3f4d906d741 100644 --- a/src/include/replication/logicalrelation.h +++ b/src/include/replication/logicalrelation.h @@ -48,7 +48,7 @@ extern LogicalRepRelMapEntry *logicalrep_partition_open(LogicalRepRelMapEntry *r Relation partrel, AttrMap *map); extern void logicalrep_rel_close(LogicalRepRelMapEntry *rel, LOCKMODE lockmode); -extern bool IsIndexUsableForReplicaIdentityFull(IndexInfo *indexInfo); +extern bool IsIndexUsableForReplicaIdentityFull(IndexInfo *indexInfo, AttrMap *attrmap); extern Oid GetRelationIdentityOrPK(Relation rel); #endif /* LOGICALRELATION_H */