diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/executor/execReplication.c | 53 | ||||
-rw-r--r-- | src/backend/replication/logical/relation.c | 51 | ||||
-rw-r--r-- | src/backend/utils/cache/lsyscache.c | 22 | ||||
-rw-r--r-- | src/include/executor/executor.h | 1 | ||||
-rw-r--r-- | src/include/utils/lsyscache.h | 1 | ||||
-rw-r--r-- | src/test/subscription/t/032_subscribe_use_index.pl | 68 |
6 files changed, 183 insertions, 13 deletions
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c index af093428813..e7765242270 100644 --- a/src/backend/executor/execReplication.c +++ b/src/backend/executor/execReplication.c @@ -19,6 +19,7 @@ #include "access/tableam.h" #include "access/transam.h" #include "access/xact.h" +#include "catalog/pg_am_d.h" #include "commands/trigger.h" #include "executor/executor.h" #include "executor/nodeModifyTable.h" @@ -42,6 +43,49 @@ static bool tuples_equal(TupleTableSlot *slot1, TupleTableSlot *slot2, TypeCacheEntry **eq); /* + * Returns the fixed strategy number, if any, of the equality operator for the + * given index access method, otherwise, InvalidStrategy. + * + * Currently, only Btree and Hash indexes are supported. The other index access + * methods don't have a fixed strategy for equality operation - instead, the + * support routines of each operator class interpret the strategy numbers + * according to the operator class's definition. + */ +StrategyNumber +get_equal_strategy_number_for_am(Oid am) +{ + int ret; + + switch (am) + { + case BTREE_AM_OID: + ret = BTEqualStrategyNumber; + break; + case HASH_AM_OID: + ret = HTEqualStrategyNumber; + break; + default: + /* XXX: Only Btree and Hash indexes are supported */ + ret = InvalidStrategy; + break; + } + + return ret; +} + +/* + * Return the appropriate strategy number which corresponds to the equality + * operator. + */ +static StrategyNumber +get_equal_strategy_number(Oid opclass) +{ + Oid am = get_opclass_method(opclass); + + return get_equal_strategy_number_for_am(am); +} + +/* * Setup a ScanKey for a search in the relation 'rel' for a tuple 'key' that * is setup to match 'rel' (*NOT* idxrel!). * @@ -77,6 +121,7 @@ build_replindex_scan_key(ScanKey skey, Relation rel, Relation idxrel, Oid opfamily; RegProcedure regop; int table_attno = indkey->values[index_attoff]; + StrategyNumber eq_strategy; if (!AttributeNumberIsValid(table_attno)) { @@ -93,20 +138,22 @@ build_replindex_scan_key(ScanKey skey, Relation rel, Relation idxrel, */ optype = get_opclass_input_type(opclass->values[index_attoff]); opfamily = get_opclass_family(opclass->values[index_attoff]); + eq_strategy = get_equal_strategy_number(opclass->values[index_attoff]); operator = get_opfamily_member(opfamily, optype, optype, - BTEqualStrategyNumber); + eq_strategy); + if (!OidIsValid(operator)) elog(ERROR, "missing operator %d(%u,%u) in opfamily %u", - BTEqualStrategyNumber, optype, optype, opfamily); + eq_strategy, optype, optype, opfamily); regop = get_opcode(operator); /* Initialize the scankey. */ ScanKeyInit(&skey[skey_attoff], index_attoff + 1, - BTEqualStrategyNumber, + eq_strategy, regop, searchslot->tts_values[table_attno - 1]); diff --git a/src/backend/replication/logical/relation.c b/src/backend/replication/logical/relation.c index c545b90636f..ed57e5d2b6f 100644 --- a/src/backend/replication/logical/relation.c +++ b/src/backend/replication/logical/relation.c @@ -17,6 +17,9 @@ #include "postgres.h" +#ifdef USE_ASSERT_CHECKING +#include "access/amapi.h" +#endif #include "access/genam.h" #include "access/table.h" #include "catalog/namespace.h" @@ -779,7 +782,7 @@ RemoteRelContainsLeftMostColumnOnIdx(IndexInfo *indexInfo, AttrMap *attrmap) /* * Returns the oid of an index that can be used by the apply worker to scan - * the relation. The index must be btree, non-partial, and the leftmost + * 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. @@ -791,11 +794,11 @@ RemoteRelContainsLeftMostColumnOnIdx(IndexInfo *indexInfo, AttrMap *attrmap) * compare the tuples for non-PK/RI index scans. See * RelationFindReplTupleByIndex(). * - * XXX: There are no fundamental problems for supporting non-btree indexes. - * We mostly need to relax the limitations in RelationFindReplTupleByIndex(). - * 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. + * 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. * * We expect to call this function when REPLICA IDENTITY FULL is defined for * the remote relation. @@ -834,15 +837,43 @@ FindUsableIndexForReplicaIdentityFull(Relation localrel, AttrMap *attrmap) /* * Returns true if the index is usable for replica identity full. For details, * see FindUsableIndexForReplicaIdentityFull. + * + * Currently, only Btree and Hash indexes can be returned as usable. This + * is due to following reasons: + * + * 1) Other index access methods don't have a fixed strategy for equality + * operation. Refer get_equal_strategy_number_for_am(). + * + * 2) For indexes other than PK and REPLICA IDENTITY, we need to match the + * local and remote tuples. The equality routine tuples_equal() cannot accept + * a datatype (e.g. point or box) that does not have a default operator class + * for Btree or Hash. + * + * XXX: Note that BRIN and GIN indexes do not implement "amgettuple" which + * will be used later to fetch the tuples. See RelationFindReplTupleByIndex(). */ bool IsIndexUsableForReplicaIdentityFull(IndexInfo *indexInfo) { - bool is_btree = (indexInfo->ii_Am == BTREE_AM_OID); - bool is_partial = (indexInfo->ii_Predicate != NIL); - bool is_only_on_expression = IsIndexOnlyOnExpression(indexInfo); + /* Ensure that the index access method has a valid equal strategy */ + if (get_equal_strategy_number_for_am(indexInfo->ii_Am) == InvalidStrategy) + return false; + if (indexInfo->ii_Predicate != NIL) + return false; + if (IsIndexOnlyOnExpression(indexInfo)) + return false; + +#ifdef USE_ASSERT_CHECKING + { + IndexAmRoutine *amroutine; - return is_btree && !is_partial && !is_only_on_expression; + /* The given index access method must implement amgettuple. */ + amroutine = GetIndexAmRoutineByAmId(indexInfo->ii_Am, false); + Assert(amroutine->amgettuple != NULL); + } +#endif + + return true; } /* diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index 60978f9415b..2c01a86c292 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -1255,6 +1255,28 @@ get_opclass_opfamily_and_input_type(Oid opclass, Oid *opfamily, Oid *opcintype) return true; } +/* + * get_opclass_method + * + * Returns the OID of the index access method the opclass belongs to. + */ +Oid +get_opclass_method(Oid opclass) +{ + HeapTuple tp; + Form_pg_opclass cla_tup; + Oid result; + + tp = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for opclass %u", opclass); + cla_tup = (Form_pg_opclass) GETSTRUCT(tp); + + result = cla_tup->opcmethod; + ReleaseSysCache(tp); + return result; +} + /* ---------- OPERATOR CACHE ---------- */ /* diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index ac02247947e..c677e490d76 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -646,6 +646,7 @@ extern void check_exclusion_constraint(Relation heap, Relation index, /* * prototypes from functions in execReplication.c */ +extern StrategyNumber get_equal_strategy_number_for_am(Oid am); extern bool RelationFindReplTupleByIndex(Relation rel, Oid idxoid, LockTupleMode lockmode, TupleTableSlot *searchslot, diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h index 4f5418b9728..f5fdbfe116b 100644 --- a/src/include/utils/lsyscache.h +++ b/src/include/utils/lsyscache.h @@ -106,6 +106,7 @@ extern Oid get_opclass_family(Oid opclass); extern Oid get_opclass_input_type(Oid opclass); extern bool get_opclass_opfamily_and_input_type(Oid opclass, Oid *opfamily, Oid *opcintype); +extern Oid get_opclass_method(Oid opclass); extern RegProcedure get_opcode(Oid opno); extern char *get_opname(Oid opno); extern Oid get_op_rettype(Oid opno); diff --git a/src/test/subscription/t/032_subscribe_use_index.pl b/src/test/subscription/t/032_subscribe_use_index.pl index 576eec6a578..880ef2d57a6 100644 --- a/src/test/subscription/t/032_subscribe_use_index.pl +++ b/src/test/subscription/t/032_subscribe_use_index.pl @@ -478,6 +478,74 @@ $node_subscriber->safe_psql('postgres', "DROP TABLE test_replica_id_full"); # data # ============================================================================= +# ============================================================================= +# Testcase start: Subscription can use hash index +# + +# create tables on pub and sub +$node_publisher->safe_psql('postgres', + "CREATE TABLE test_replica_id_full (x int, y text)"); +$node_publisher->safe_psql('postgres', + "ALTER TABLE test_replica_id_full REPLICA IDENTITY FULL"); +$node_subscriber->safe_psql('postgres', + "CREATE TABLE test_replica_id_full (x int, y text)"); +$node_subscriber->safe_psql('postgres', + "CREATE INDEX test_replica_id_full_idx ON test_replica_id_full USING HASH (x)"); + +# insert some initial data +$node_publisher->safe_psql('postgres', + "INSERT INTO test_replica_id_full SELECT i, (i%10)::text FROM generate_series(0,10) i" +); + +# create pub/sub +$node_publisher->safe_psql('postgres', + "CREATE PUBLICATION tap_pub_rep_full FOR TABLE test_replica_id_full"); +$node_subscriber->safe_psql('postgres', + "CREATE SUBSCRIPTION tap_sub_rep_full CONNECTION '$publisher_connstr application_name=$appname' PUBLICATION tap_pub_rep_full" +); + +# wait for initial table synchronization to finish +$node_subscriber->wait_for_subscription_sync($node_publisher, $appname); + +# delete 2 rows +$node_publisher->safe_psql('postgres', + "DELETE FROM test_replica_id_full WHERE x IN (5, 6)"); + +# update 2 rows +$node_publisher->safe_psql('postgres', + "UPDATE test_replica_id_full SET x = 100, y = '200' WHERE x IN (1, 2)"); + +# wait until the index is used on the subscriber +$node_publisher->wait_for_catchup($appname); +$node_subscriber->poll_query_until('postgres', + q{select (idx_scan = 4) from pg_stat_all_indexes where indexrelname = 'test_replica_id_full_idx';} + ) + or die + "Timed out while waiting for check subscriber tap_sub_rep_full deletes 2 rows and updates 2 rows via index"; + +# make sure that the subscriber has the correct data after the UPDATE +$result = $node_subscriber->safe_psql('postgres', + "select count(*) from test_replica_id_full WHERE (x = 100 and y = '200')" +); +is($result, qq(2), + 'ensure subscriber has the correct data at the end of the test'); + +# make sure that the subscriber has the correct data after the first DELETE +$result = $node_subscriber->safe_psql('postgres', + "select count(*) from test_replica_id_full where x in (5, 6)"); +is($result, qq(0), + 'ensure subscriber has the correct data at the end of the test'); + +# cleanup pub +$node_publisher->safe_psql('postgres', "DROP PUBLICATION tap_pub_rep_full"); +$node_publisher->safe_psql('postgres', "DROP TABLE test_replica_id_full"); +# cleanup sub +$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION tap_sub_rep_full"); +$node_subscriber->safe_psql('postgres', "DROP TABLE test_replica_id_full"); + +# Testcase end: Subscription can use hash index +# ============================================================================= + $node_subscriber->stop('fast'); $node_publisher->stop('fast'); |