diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index 1b45a4c..9f899c7 100644 --- a/src/backend/access/brin/brin.c +++ b/src/backend/access/brin/brin.c @@ -92,6 +92,7 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amstorage = true; amroutine->amclusterable = false; amroutine->ampredlocks = false; + amroutine->amcanindirect = false; amroutine->amkeytype = InvalidOid; amroutine->ambuild = brinbuild; diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c index f07eedc..1bc91d2 100644 --- a/src/backend/access/gin/ginutil.c +++ b/src/backend/access/gin/ginutil.c @@ -49,6 +49,7 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amstorage = true; amroutine->amclusterable = false; amroutine->ampredlocks = false; + amroutine->amcanindirect = false; amroutine->amkeytype = InvalidOid; amroutine->ambuild = ginbuild; diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c index b8aa9bc..4ec34d5 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -69,6 +69,7 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amstorage = true; amroutine->amclusterable = true; amroutine->ampredlocks = false; + amroutine->amcanindirect = false; amroutine->amkeytype = InvalidOid; amroutine->ambuild = gistbuild; diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c index 0eeb37d..7cfb776 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -66,6 +66,7 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amstorage = false; amroutine->amclusterable = false; amroutine->ampredlocks = false; + amroutine->amcanindirect = false; amroutine->amkeytype = INT4OID; amroutine->ambuild = hashbuild; diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index b019bc1..76518cc 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -96,10 +96,10 @@ static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf, HeapTuple newtup, HeapTuple old_key_tup, bool all_visible_cleared, bool new_all_visible_cleared); static void HeapSatisfiesHOTandKeyUpdate(Relation relation, - Bitmapset *hot_attrs, - Bitmapset *key_attrs, Bitmapset *id_attrs, + Bitmapset *hot_attrs, Bitmapset *key_attrs, + Bitmapset *id_attrs, Bitmapset *indirect_attrs, bool *satisfies_hot, bool *satisfies_key, - bool *satisfies_id, + bool *satisfies_id, Bitmapset **unchanged_attrs, HeapTuple oldtup, HeapTuple newtup); static bool heap_acquire_tuplock(Relation relation, ItemPointer tid, LockTupleMode mode, LockWaitPolicy wait_policy, @@ -3414,6 +3414,8 @@ simple_heap_delete(Relation relation, ItemPointer tid) * crosscheck - if not InvalidSnapshot, also check old tuple against this * wait - true if should wait for any conflicting update to commit/abort * hufd - output parameter, filled in failure cases (see below) + * unchanged_ind_cols - output parameter; bits set for unmodified columns + * that are indexed by indirect indexes * lockmode - output parameter, filled with lock mode acquired on tuple * * Normal, successful return value is HeapTupleMayBeUpdated, which @@ -3436,13 +3438,15 @@ simple_heap_delete(Relation relation, ItemPointer tid) HTSU_Result heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, CommandId cid, Snapshot crosscheck, bool wait, - HeapUpdateFailureData *hufd, LockTupleMode *lockmode) + HeapUpdateFailureData *hufd, Bitmapset **unchanged_ind_cols, + LockTupleMode *lockmode) { HTSU_Result result; TransactionId xid = GetCurrentTransactionId(); Bitmapset *hot_attrs; Bitmapset *key_attrs; Bitmapset *id_attrs; + Bitmapset *indirect_attrs; ItemId lp; HeapTupleData oldtup; HeapTuple heaptup; @@ -3500,10 +3504,12 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, * Note that we get a copy here, so we need not worry about relcache flush * happening midway through. */ - hot_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_ALL); + hot_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_HOT); key_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY); id_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_IDENTITY_KEY); + indirect_attrs = RelationGetIndexAttrBitmap(relation, + INDEX_ATTR_BITMAP_INDIRECT_INDEXES); block = ItemPointerGetBlockNumber(otid); buffer = ReadBuffer(relation, block); @@ -3561,9 +3567,11 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, * is updates that don't manipulate key columns, not those that * serendipitiously arrive at the same key values. */ - HeapSatisfiesHOTandKeyUpdate(relation, hot_attrs, key_attrs, id_attrs, + HeapSatisfiesHOTandKeyUpdate(relation, + hot_attrs, key_attrs, id_attrs, indirect_attrs, &satisfies_hot, &satisfies_key, - &satisfies_id, &oldtup, newtup); + &satisfies_id, unchanged_ind_cols, + &oldtup, newtup); if (satisfies_key) { *lockmode = LockTupleNoKeyExclusive; @@ -3803,6 +3811,7 @@ l2: bms_free(hot_attrs); bms_free(key_attrs); bms_free(id_attrs); + bms_free(indirect_attrs); return result; } @@ -4356,41 +4365,53 @@ heap_tuple_attr_equals(TupleDesc tupdesc, int attrnum, * Check which columns are being updated. * * This simultaneously checks conditions for HOT updates, for FOR KEY - * SHARE updates, and REPLICA IDENTITY concerns. Since much of the time they - * will be checking very similar sets of columns, and doing the same tests on - * them, it makes sense to optimize and do them together. + * SHARE updates, for REPLICA IDENTITY concerns, and for indirect indexing + * concerns. Since much of the time they will be checking very similar sets of + * columns, and doing the same tests on them, it makes sense to optimize and do + * them together. * - * We receive three bitmapsets comprising the three sets of columns we're + * We receive four bitmapsets comprising the four sets of columns we're * interested in. Note these are destructively modified; that is OK since * this is invoked at most once in heap_update. * - * hot_result is set to TRUE if it's okay to do a HOT update (i.e. it does not - * modified indexed columns); key_result is set to TRUE if the update does not - * modify columns used in the key; id_result is set to TRUE if the update does - * not modify columns in any index marked as the REPLICA IDENTITY. + * satisfies_hot is set to TRUE if it's okay to do a HOT update (i.e. it does + * not modified indexed columns); satisfies_key is set to TRUE if the update + * does not modify columns used in the key; satisfies_id is set to TRUE if the + * update does not modify columns in any index marked as the REPLICA IDENTITY. + * + * unchanged_attrs is an output bitmapset that has a bit set if the + * corresponding column is indexed by an indirect index and is not modified. + * Note that because system columns cannot be indexed by indirect indexes, + * these values are not shifted by FirstLowInvalidHeapAttributeNumber. */ static void HeapSatisfiesHOTandKeyUpdate(Relation relation, Bitmapset *hot_attrs, Bitmapset *key_attrs, Bitmapset *id_attrs, + Bitmapset *indirect_attrs, bool *satisfies_hot, bool *satisfies_key, - bool *satisfies_id, + bool *satisfies_id, Bitmapset **unchanged_attrs, HeapTuple oldtup, HeapTuple newtup) { int next_hot_attnum; int next_key_attnum; int next_id_attnum; + int next_indirect_attnum; bool hot_result = true; bool key_result = true; bool id_result = true; + bool collecting_indirect; /* If REPLICA IDENTITY is set to FULL, id_attrs will be empty. */ Assert(bms_is_subset(id_attrs, key_attrs)); - Assert(bms_is_subset(key_attrs, hot_attrs)); + + /* Waste no time on indirect indexes if there aren't any */ + collecting_indirect = (unchanged_attrs != NULL) && + !bms_is_empty(indirect_attrs); /* * If one of these sets contains no remaining bits, bms_first_member will * return -1, and after adding FirstLowInvalidHeapAttributeNumber (which - * is negative!) we'll get an attribute number that can't possibly be + * is negative!) we'll get an attribute number that can't possibly be * real, and thus won't match any actual attribute number. */ next_hot_attnum = bms_first_member(hot_attrs); @@ -4399,24 +4420,43 @@ HeapSatisfiesHOTandKeyUpdate(Relation relation, Bitmapset *hot_attrs, next_key_attnum += FirstLowInvalidHeapAttributeNumber; next_id_attnum = bms_first_member(id_attrs); next_id_attnum += FirstLowInvalidHeapAttributeNumber; + next_indirect_attnum = bms_first_member(indirect_attrs); + next_indirect_attnum += FirstLowInvalidHeapAttributeNumber; for (;;) { bool changed; int check_now; + /* Set to a value greater than what can be attained normally */ + check_now = MaxAttrNumber + 1; + /* - * Since the HOT attributes are a superset of the key attributes and - * the key attributes are a superset of the id attributes, this logic - * is guaranteed to identify the next column that needs to be checked. + * Since the key attributes are a superset of the id attributes, this + * logic is guaranteed to identify the next column that needs to be + * checked. We consider indirect attributes and HOT attributes + * separately because they aren't supersets of anything. */ - if (hot_result && next_hot_attnum > FirstLowInvalidHeapAttributeNumber) - check_now = next_hot_attnum; - else if (key_result && next_key_attnum > FirstLowInvalidHeapAttributeNumber) + if (key_result && next_key_attnum > FirstLowInvalidHeapAttributeNumber) check_now = next_key_attnum; else if (id_result && next_id_attnum > FirstLowInvalidHeapAttributeNumber) check_now = next_id_attnum; - else + + if (hot_result && next_hot_attnum > FirstLowInvalidHeapAttributeNumber) + { + if (next_hot_attnum < check_now) + check_now = next_hot_attnum; + } + + if (collecting_indirect) + { + Assert(next_indirect_attnum > 0); + if (next_indirect_attnum < check_now) + check_now = next_indirect_attnum; + } + + /* are we done? */ + if (check_now == MaxAttrNumber + 1) break; /* See whether it changed. */ @@ -4430,13 +4470,33 @@ HeapSatisfiesHOTandKeyUpdate(Relation relation, Bitmapset *hot_attrs, key_result = false; if (check_now == next_id_attnum) id_result = false; - - /* if all are false now, we can stop checking */ - if (!hot_result && !key_result && !id_result) - break; } /* + * Deal with indirect indexes. Set the bit in the output set if the + * column did not change, and compute the next column, which is + * necessary to determine whether we need to iterate again. + */ + if (collecting_indirect && check_now == next_indirect_attnum) + { + if (!changed) + *unchanged_attrs = bms_add_member(*unchanged_attrs, + check_now); + next_indirect_attnum = bms_first_member(indirect_attrs); + /* are we done with these? */ + if (next_indirect_attnum == -1) + collecting_indirect = false; + next_indirect_attnum += FirstLowInvalidHeapAttributeNumber; + } + + /* + * if HOT, key, id are all false now and we're not collecting + * unchanged indirect index columns, we can stop checking. + */ + if (!hot_result && !key_result && !id_result && !collecting_indirect) + break; + + /* * Advance the next attribute numbers for the sets that contain the * attribute we just checked. As we work our way through the columns, * the next_attnum values will rise; but when each set becomes empty, @@ -4483,7 +4543,7 @@ simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup) result = heap_update(relation, otid, tup, GetCurrentCommandId(true), InvalidSnapshot, true /* wait for commit */ , - &hufd, &lockmode); + &hufd, NULL, &lockmode); switch (result) { case HeapTupleSelfUpdated: diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c index fc4702c..07bf463 100644 --- a/src/backend/access/heap/tuptoaster.c +++ b/src/backend/access/heap/tuptoaster.c @@ -1614,6 +1614,7 @@ toast_save_datum(Relation rel, Datum value, /* Only index relations marked as ready can be updated */ if (IndexIsReady(toastidxs[i]->rd_index)) index_insert(toastidxs[i], t_values, t_isnull, + NULL, &(toasttup->t_self), toastrel, toastidxs[i]->rd_index->indisunique ? diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c index 54b71cb..cf18fa0 100644 --- a/src/backend/access/index/indexam.c +++ b/src/backend/access/index/indexam.c @@ -189,10 +189,13 @@ bool index_insert(Relation indexRelation, Datum *values, bool *isnull, + Datum *pkeyValues, ItemPointer heap_t_ctid, Relation heapRelation, IndexUniqueCheck checkUnique) { + ItemPointerData iptr; + RELATION_CHECKS; CHECK_REL_PROCEDURE(aminsert); @@ -201,8 +204,19 @@ index_insert(Relation indexRelation, (HeapTuple) NULL, InvalidBuffer); + /* + * Indirect indexes use a fake item pointer constructed from the primary + * key values; regular indexes store the actual heap item pointer. + */ + if (!indexRelation->rd_index->indisindirect) + ItemPointerCopy(heap_t_ctid, &iptr); + else + FAKE_CTID_FROM_PKVALUES(&iptr, + indexRelation->rd_index->indnatts, + pkeyValues); + return indexRelation->rd_amroutine->aminsert(indexRelation, values, isnull, - heap_t_ctid, heapRelation, + &iptr, heapRelation, checkUnique); } @@ -570,6 +584,56 @@ index_getnext(IndexScanDesc scan, ScanDirection direction) return NULL; /* failure exit */ } +/* + * Return the primary key values from the next tuple in a indirect-index scan + * + * pkvals is an output array which must have been allocated by caller. + */ +void +index_getnext_pkey(IndexScanDesc scan, ScanDirection direction, Datum *pkvals, + bool *isDone) +{ + bool found; + Datum pkval; + + SCAN_CHECKS; + CHECK_SCAN_PROCEDURE(amgettuple); + + /* + * The AM's amgettuple proc finds the next index entry matching the scan + * keys, and puts the primary key value into scan->xs_ctup.t_self, from + * which we extract it into the pkvals array. It should also set + * scan->xs_recheck and scan->xs_itup, though we pay no attention to those + * fields here. + */ + found = scan->indexRelation->rd_amroutine->amgettuple(scan, direction); + + /* Reset kill flag immediately for safety */ + scan->kill_prior_tuple = false; + + if (!found) + { + if (BufferIsValid(scan->xs_cbuf)) + { + ReleaseBuffer(scan->xs_cbuf); + scan->xs_cbuf = InvalidBuffer; + } + *isDone = true; + return; + } + *isDone = false; + + /* + * XXX this assumes that Datum is 8 bytes. Which it is in my machine .. but + * not elsewhere + */ + /* XXX see FAKE_CTID_FROM_PKVALUES */ + pkval = (((Datum) scan->xs_ctup.t_self.ip_posid) >> 1) | + (((Datum) scan->xs_ctup.t_self.ip_blkid.bi_hi) << 15) | + (((Datum) scan->xs_ctup.t_self.ip_blkid.bi_lo >> 1) << 32); + pkvals[0] = pkval; +} + /* ---------------- * index_getbitmap - get all tuples at once from an index scan * diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c index ef69290..eb4beef 100644 --- a/src/backend/access/nbtree/nbtinsert.c +++ b/src/backend/access/nbtree/nbtinsert.c @@ -92,7 +92,9 @@ static void _bt_vacuum_one_page(Relation rel, Buffer buffer, Relation heapRel); * By here, itup is filled in, including the TID. * * If checkUnique is UNIQUE_CHECK_NO or UNIQUE_CHECK_PARTIAL, this - * will allow duplicates. Otherwise (UNIQUE_CHECK_YES or + * will allow duplicates. If it's UNIQUE_CHECK_INSERT_SINGLETON, the value + * will only be inserted if there isn't already a tuple with that value. + * Otherwise (UNIQUE_CHECK_YES or * UNIQUE_CHECK_EXISTING) it will throw error for a duplicate. * For UNIQUE_CHECK_EXISTING we merely run the duplicate check, and * don't actually insert. @@ -100,8 +102,8 @@ static void _bt_vacuum_one_page(Relation rel, Buffer buffer, Relation heapRel); * The result value is only significant for UNIQUE_CHECK_PARTIAL: * it must be TRUE if the entry is known unique, else FALSE. * (In the current implementation we'll also return TRUE after a - * successful UNIQUE_CHECK_YES or UNIQUE_CHECK_EXISTING call, but - * that's just a coding artifact.) + * successful UNIQUE_CHECK_YES, UNIQUE_CHECK_EXISTING or + * UNIQUE_CHECK_INSERT_SINGLETON call, but that's just a coding artifact.) */ bool _bt_doinsert(Relation rel, IndexTuple itup, @@ -138,6 +140,21 @@ top: true, stack, BT_WRITE, NULL); /* + * In insert-singleton mode, we must return without doing anything if the + * value we're inserting already exists. + */ +#if 0 + if (checkUnique == UNIQUE_CHECK_INSERT_SINGLETON) + { + offset = _bt_binsrch( .. ); + if (offset is valid and contains a tuple matching the scankey) + return true; + /* otherwise fall through to insert */ + } +#endif + + + /* * If we're not allowing duplicates, make sure the key isn't already in * the index. * @@ -158,7 +175,8 @@ top: * let the tuple in and return false for possibly non-unique, or true for * definitely unique. */ - if (checkUnique != UNIQUE_CHECK_NO) + if (checkUnique != UNIQUE_CHECK_NO && + checkUnique != UNIQUE_CHECK_INSERT_SINGLETON) { TransactionId xwait; uint32 speculativeToken; @@ -167,6 +185,10 @@ top: xwait = _bt_check_unique(rel, itup, heapRel, buf, offset, itup_scankey, checkUnique, &is_unique, &speculativeToken); + if (checkUnique == UNIQUE_CHECK_INSERT_SINGLETON && + TransactionIdIsValid(xwait)) + return true; + if (TransactionIdIsValid(xwait)) { /* Have to wait for the other guy ... */ diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c index a264b92..29c6ae6 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -37,6 +37,7 @@ typedef struct { bool isUnique; bool haveDead; + bool isIndirect; Relation heapRel; BTSpool *spool; @@ -45,6 +46,8 @@ typedef struct * put into spool2 instead of spool in order to avoid uniqueness check. */ BTSpool *spool2; + int16 pkNumKeys; + AttrNumber pkAttnums[INDEX_MAX_KEYS]; double indtuples; } BTBuildState; @@ -98,6 +101,7 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amstorage = false; amroutine->amclusterable = true; amroutine->ampredlocks = true; + amroutine->amcanindirect = true; amroutine->amkeytype = InvalidOid; amroutine->ambuild = btbuild; @@ -136,6 +140,25 @@ btbuild(Relation heap, Relation index, IndexInfo *indexInfo) buildstate.heapRel = heap; buildstate.spool = NULL; buildstate.spool2 = NULL; + buildstate.isIndirect = indexInfo->ii_IsIndirect; + if (indexInfo->ii_IsIndirect) + { + Oid pkOid; + Relation pkRel; + int i; + + pkOid = RelationGetPrimaryKey(heap); + pkRel = index_open(pkOid, AccessShareLock); + + buildstate.pkNumKeys = pkRel->rd_index->indnatts; + for (i = 0; i < buildstate.pkNumKeys; i++) + buildstate.pkAttnums[i] = pkRel->rd_index->indkey.values[i]; + index_close(pkRel, AccessShareLock); + } + else + { + buildstate.pkNumKeys = 0; + } buildstate.indtuples = 0; #ifdef BTREE_BUILD_STATS @@ -213,18 +236,42 @@ btbuildCallback(Relation index, void *state) { BTBuildState *buildstate = (BTBuildState *) state; + ItemPointerData iptr; + + if (buildstate->isIndirect) + { + Datum pkValues[INDEX_MAX_KEYS]; + int i; + bool isnull; + + /* + * XXX WAG: this is very slow in the general case, but OK if PK column + * is first. + */ + for (i = 0; i < buildstate->pkNumKeys; i++) + { + pkValues[i] = heap_getattr(htup, + buildstate->pkAttnums[i], + RelationGetDescr(buildstate->heapRel), + &isnull); + Assert(!isnull); + } + FAKE_CTID_FROM_PKVALUES(&iptr, buildstate->pkNumKeys, pkValues); + } + else + ItemPointerCopy(&htup->t_self, &iptr); /* * insert the index tuple into the appropriate spool file for subsequent * processing */ if (tupleIsAlive || buildstate->spool2 == NULL) - _bt_spool(buildstate->spool, &htup->t_self, values, isnull); + _bt_spool(buildstate->spool, &iptr, values, isnull); else { /* dead tuples are put into spool2 */ buildstate->haveDead = true; - _bt_spool(buildstate->spool2, &htup->t_self, values, isnull); + _bt_spool(buildstate->spool2, &iptr, values, isnull); } buildstate->indtuples += 1; diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c index d570ae5..9378919 100644 --- a/src/backend/access/spgist/spgutils.c +++ b/src/backend/access/spgist/spgutils.c @@ -48,6 +48,7 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amstorage = false; amroutine->amclusterable = false; amroutine->ampredlocks = false; + amroutine->amcanindirect = false; amroutine->amkeytype = InvalidOid; amroutine->ambuild = spgbuild; diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 08b0989..5ac79b5 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -623,6 +623,7 @@ UpdateIndexRelation(Oid indexoid, values[Anum_pg_index_indrelid - 1] = ObjectIdGetDatum(heapoid); values[Anum_pg_index_indnatts - 1] = Int16GetDatum(indexInfo->ii_NumIndexAttrs); values[Anum_pg_index_indisunique - 1] = BoolGetDatum(indexInfo->ii_Unique); + values[Anum_pg_index_indisindirect - 1] = BoolGetDatum(indexInfo->ii_IsIndirect); values[Anum_pg_index_indisprimary - 1] = BoolGetDatum(primary); values[Anum_pg_index_indisexclusion - 1] = BoolGetDatum(isexclusion); values[Anum_pg_index_indimmediate - 1] = BoolGetDatum(immediate); @@ -769,6 +770,20 @@ index_create(Relation heapRelation, errmsg("concurrent index creation on system catalog tables is not supported"))); /* + * indirect indexes are forbidden on system catalogs, and they obviously cannot + * be primary keys either. + */ + if (indexInfo->ii_IsIndirect && IsSystemRelation(heapRelation)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("indirect index creation on system catalog tables is not supported"))); + if (indexInfo->ii_IsIndirect && isprimary) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("primary key indexes cannot be indirect"))); + /* XXX other restrictions needed? */ + + /* * This case is currently not supported, but there's no way to ask for it * in the grammar anyway, so it can't happen. */ @@ -1011,6 +1026,14 @@ index_create(Relation heapRelation, Assert(!initdeferred); } + /* Store dependency on primary key index, if needed */ + if (indexInfo->ii_IsIndirect) + { + ObjectAddressSet(referenced, RelationRelationId, + RelationGetPrimaryKey(heapRelation)); + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + } + /* Store dependency on collations */ /* The default collation is pinned, so don't bother recording it */ for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++) @@ -1681,6 +1704,7 @@ BuildIndexInfo(Relation index) /* other info */ ii->ii_Unique = indexStruct->indisunique; + ii->ii_IsIndirect = indexStruct->indisindirect; ii->ii_ReadyForInserts = IndexIsReady(indexStruct); /* assume not doing speculative insertion for now */ ii->ii_UniqueOps = NULL; @@ -3161,6 +3185,7 @@ validate_index_heapscan(Relation heapRelation, index_insert(indexRelation, values, isnull, + NULL, /* FIXME need to PK values here */ &rootTuple, heapRelation, indexInfo->ii_Unique ? @@ -3564,7 +3589,9 @@ reindex_relation(Oid relid, int flags, int options) /* Ensure rd_indexattr is valid; see comments for RelationSetIndexList */ if (is_pg_class) + { (void) RelationGetIndexAttrBitmap(rel, INDEX_ATTR_BITMAP_ALL); + } PG_TRY(); { diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c index b9fe102..f00f446 100644 --- a/src/backend/catalog/indexing.c +++ b/src/backend/catalog/indexing.c @@ -136,6 +136,7 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple) index_insert(relationDescs[i], /* index relation */ values, /* array of index Datums */ isnull, /* is-null flags */ + NULL, /* catalogs never had indirect indexes */ &(heapTuple->t_self), /* tid of heap tuple */ heapRelation, relationDescs[i]->rd_index->indisunique ? diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index 2131226..414ec61 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -1531,14 +1531,14 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, reindex_relation(OIDOldHeap, reindex_flags, 0); /* - * If the relation being rebuild is pg_class, swap_relation_files() + * If the relation being rebuilt is pg_class, swap_relation_files() * couldn't update pg_class's own pg_class entry (check comments in * swap_relation_files()), thus relfrozenxid was not updated. That's * annoying because a potential reason for doing a VACUUM FULL is a * imminent or actual anti-wraparound shutdown. So, now that we can - * access the new relation using it's indices, update relfrozenxid. + * access the new relation using its indices, update relfrozenxid. * pg_class doesn't have a toast relation, so we don't need to update the - * corresponding toast relation. Not that there's little point moving all + * corresponding toast relation. Note that there's little point moving all * relfrozenxid updates here since swap_relation_files() needs to write to * pg_class for non-mapped relations anyway. */ diff --git a/src/backend/commands/constraint.c b/src/backend/commands/constraint.c index 26f9114..5f1a7b7 100644 --- a/src/backend/commands/constraint.c +++ b/src/backend/commands/constraint.c @@ -164,7 +164,7 @@ unique_key_recheck(PG_FUNCTION_ARGS) * correct even if t_self is now dead, because that is the TID the * index will know about. */ - index_insert(indexRel, values, isnull, &(new_row->t_self), + index_insert(indexRel, values, isnull, NULL, &(new_row->t_self), trigdata->tg_relation, UNIQUE_CHECK_EXISTING); } else diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index d590165..9c8b96d 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -2646,7 +2646,8 @@ CopyFrom(CopyState cstate) estate, false, NULL, - NIL); + NIL, + NULL); /* AFTER ROW INSERT Triggers */ ExecARInsertTriggers(estate, resultRelInfo, tuple, @@ -2793,7 +2794,7 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid, ExecStoreTuple(bufferedTuples[i], myslot, InvalidBuffer, false); recheckIndexes = ExecInsertIndexTuples(myslot, &(bufferedTuples[i]->t_self), - estate, false, NULL, NIL); + estate, false, NULL, NIL, NULL); ExecARInsertTriggers(estate, resultRelInfo, bufferedTuples[i], recheckIndexes); diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 0a669d9..853f155 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -884,6 +884,9 @@ ExplainNode(PlanState *planstate, List *ancestors, case T_IndexScan: pname = sname = "Index Scan"; break; + case T_IndirectIndexScan: + pname = sname = "Indirect Index Scan"; + break; case T_IndexOnlyScan: pname = sname = "Index Only Scan"; break; @@ -1099,6 +1102,15 @@ ExplainNode(PlanState *planstate, List *ancestors, ExplainScanTarget((Scan *) indexscan, es); } break; + case T_IndirectIndexScan: + { + IndirectIndexScan *indexscan = (IndirectIndexScan *) plan; + ExplainIndexScanDetails(indexscan->indexid, + indexscan->indexorderdir, + es); + ExplainScanTarget((Scan *) indexscan, es); + } + break; case T_IndexOnlyScan: { IndexOnlyScan *indexonlyscan = (IndexOnlyScan *) plan; @@ -1301,6 +1313,19 @@ ExplainNode(PlanState *planstate, List *ancestors, show_instrumentation_count("Rows Removed by Filter", 1, planstate, es); break; + case T_IndirectIndexScan: + show_scan_qual(((IndirectIndexScan *) plan)->indexqualorig, + "Index Cond", planstate, ancestors, es); + if (((IndirectIndexScan *) plan)->indexqualorig) + show_instrumentation_count("Rows Removed by Index Recheck", 2, + planstate, es); + show_scan_qual(((IndirectIndexScan *) plan)->indexorderbyorig, + "Order By", planstate, ancestors, es); + show_scan_qual(plan->qual, "Filter", planstate, ancestors, es); + if (plan->qual) + show_instrumentation_count("Rows Removed by Filter", 1, + planstate, es); + break; case T_IndexOnlyScan: show_scan_qual(((IndexOnlyScan *) plan)->indexqual, "Index Cond", planstate, ancestors, es); diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index eeb2b1f..4a7f79b 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -522,6 +522,11 @@ DefineIndex(Oid relationId, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("access method \"%s\" does not support exclusion constraints", accessMethodName))); + if (stmt->isindirect && !amRoutine->amcanindirect) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("access method \"%s\" does not support indirect indexes", + accessMethodName))); amcanorder = amRoutine->amcanorder; amoptions = amRoutine->amoptions; @@ -557,6 +562,7 @@ DefineIndex(Oid relationId, indexInfo->ii_ExclusionProcs = NULL; indexInfo->ii_ExclusionStrats = NULL; indexInfo->ii_Unique = stmt->unique; + indexInfo->ii_IsIndirect = stmt->isindirect; /* In a concurrent build, mark it not-ready-for-inserts */ indexInfo->ii_ReadyForInserts = !stmt->concurrent; indexInfo->ii_Concurrent = stmt->concurrent; diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile index 51edd4c..799b4b6 100644 --- a/src/backend/executor/Makefile +++ b/src/backend/executor/Makefile @@ -19,6 +19,7 @@ OBJS = execAmi.o execCurrent.o execGrouping.o execIndexing.o execJunk.o \ nodeBitmapAnd.o nodeBitmapOr.o \ nodeBitmapHeapscan.o nodeBitmapIndexscan.o nodeCustom.o nodeGather.o \ nodeHash.o nodeHashjoin.o nodeIndexscan.o nodeIndexonlyscan.o \ + nodeIndirectIndexscan.o \ nodeLimit.o nodeLockRows.o \ nodeMaterial.o nodeMergeAppend.o nodeMergejoin.o nodeModifyTable.o \ nodeNestloop.o nodeFunctionscan.o nodeRecursiveunion.o nodeResult.o \ diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c index 2587ef7..bcae700 100644 --- a/src/backend/executor/execAmi.c +++ b/src/backend/executor/execAmi.c @@ -32,6 +32,7 @@ #include "executor/nodeHashjoin.h" #include "executor/nodeIndexonlyscan.h" #include "executor/nodeIndexscan.h" +#include "executor/nodeIndirectIndexscan.h" #include "executor/nodeLimit.h" #include "executor/nodeLockRows.h" #include "executor/nodeMaterial.h" @@ -170,6 +171,10 @@ ExecReScan(PlanState *node) ExecReScanIndexScan((IndexScanState *) node); break; + case T_IndirectIndexScanState: + ExecReScanIndirectIndexScan((IndirectIndexScanState *) node); + break; + case T_IndexOnlyScanState: ExecReScanIndexOnlyScan((IndexOnlyScanState *) node); break; @@ -300,6 +305,10 @@ ExecMarkPos(PlanState *node) ExecIndexMarkPos((IndexScanState *) node); break; + case T_IndirectIndexScanState: + ExecIndirectIndexMarkPos((IndirectIndexScanState *) node); + break; + case T_IndexOnlyScanState: ExecIndexOnlyMarkPos((IndexOnlyScanState *) node); break; @@ -349,6 +358,9 @@ ExecRestrPos(PlanState *node) ExecIndexRestrPos((IndexScanState *) node); break; + case T_IndirectIndexScanState: + ExecIndirectIndexRestrPos((IndirectIndexScanState *) node); + case T_IndexOnlyScanState: ExecIndexOnlyRestrPos((IndexOnlyScanState *) node); break; @@ -393,6 +405,7 @@ ExecSupportsMarkRestore(Path *pathnode) switch (pathnode->pathtype) { case T_IndexScan: + case T_IndirectIndexScan: case T_IndexOnlyScan: case T_Material: case T_Sort: @@ -491,6 +504,11 @@ ExecSupportsBackwardScan(Plan *node) return IndexSupportsBackwardScan(((IndexScan *) node)->indexid) && TargetListSupportsBackwardScan(node->targetlist); + case T_IndirectIndexScan: + /* FIXME this is expected to work, but is untested */ + return IndexSupportsBackwardScan(((IndirectIndexScan *) node)->indexid) && + TargetListSupportsBackwardScan(node->targetlist); + case T_IndexOnlyScan: return IndexSupportsBackwardScan(((IndexOnlyScan *) node)->indexid) && TargetListSupportsBackwardScan(node->targetlist); @@ -537,8 +555,8 @@ TargetListSupportsBackwardScan(List *targetlist) } /* - * An IndexScan or IndexOnlyScan node supports backward scan only if the - * index's AM does. + * An IndexScan, IndirectIndexScan or IndexOnlyScan node supports backward scan + * only if the index's AM does. */ static bool IndexSupportsBackwardScan(Oid indexid) diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c index 009c1b7..302fc60 100644 --- a/src/backend/executor/execIndexing.c +++ b/src/backend/executor/execIndexing.c @@ -273,7 +273,8 @@ ExecInsertIndexTuples(TupleTableSlot *slot, EState *estate, bool noDupErr, bool *specConflict, - List *arbiterIndexes) + List *arbiterIndexes, + Bitmapset *unchangedAttrs) { List *result = NIL; ResultRelInfo *resultRelInfo; @@ -285,6 +286,7 @@ ExecInsertIndexTuples(TupleTableSlot *slot, ExprContext *econtext; Datum values[INDEX_MAX_KEYS]; bool isnull[INDEX_MAX_KEYS]; + Datum pkeyValues[INDEX_MAX_KEYS]; /* * Get information from the result relation info structure. @@ -348,6 +350,27 @@ ExecInsertIndexTuples(TupleTableSlot *slot, } /* + * For indirect indexes, verify whether the indexed attributes have + * changed; if they have not, skip the insertion. + */ + if (indexInfo->ii_IsIndirect) + { + int j; + bool may_skip_insertion = true; + + for (j = 0; j < indexInfo->ii_NumIndexAttrs; j++) + { + if (bms_is_member(indexInfo->ii_KeyAttrNumbers[j], unchangedAttrs)) + continue; + may_skip_insertion = false; + } + + /* may skip insertion if no indexed attribute changed value */ + if (may_skip_insertion) + continue; + } + + /* * FormIndexDatum fills in its values and isnull parameters with the * appropriate values for the column(s) of the index. */ @@ -357,6 +380,11 @@ ExecInsertIndexTuples(TupleTableSlot *slot, values, isnull); + /* If this is the primary key, save the values for later */ + if (i == 0) + /* XXX probably need datumCopy here instead */ + memcpy(pkeyValues, values, sizeof(Datum) * INDEX_MAX_KEYS); + /* Check whether to apply noDupErr to this index */ applyNoDupErr = noDupErr && (arbiterIndexes == NIL || @@ -389,6 +417,7 @@ ExecInsertIndexTuples(TupleTableSlot *slot, index_insert(indexRelation, /* index relation */ values, /* array of index Datums */ isnull, /* null flags */ + pkeyValues, /* values of primary key */ tupleid, /* tid of heap tuple */ heapRelation, /* heap relation */ checkUnique); /* type of uniqueness check to do */ diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c index 554244f..9df9160 100644 --- a/src/backend/executor/execProcnode.c +++ b/src/backend/executor/execProcnode.c @@ -93,6 +93,7 @@ #include "executor/nodeHashjoin.h" #include "executor/nodeIndexonlyscan.h" #include "executor/nodeIndexscan.h" +#include "executor/nodeIndirectIndexscan.h" #include "executor/nodeLimit.h" #include "executor/nodeLockRows.h" #include "executor/nodeMaterial.h" @@ -203,6 +204,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags) estate, eflags); break; + case T_IndirectIndexScan: + result = (PlanState *) ExecInitIndirectIndexScan((IndirectIndexScan *) node, + estate, eflags); + break; + case T_IndexOnlyScan: result = (PlanState *) ExecInitIndexOnlyScan((IndexOnlyScan *) node, estate, eflags); @@ -427,6 +433,10 @@ ExecProcNode(PlanState *node) result = ExecIndexScan((IndexScanState *) node); break; + case T_IndirectIndexScanState: + result = ExecIndirectIndexScan((IndirectIndexScanState *) node); + break; + case T_IndexOnlyScanState: result = ExecIndexOnlyScan((IndexOnlyScanState *) node); break; @@ -677,6 +687,10 @@ ExecEndNode(PlanState *node) ExecEndIndexScan((IndexScanState *) node); break; + case T_IndirectIndexScanState: + ExecEndIndirectIndexScan((IndirectIndexScanState *) node); + break; + case T_IndexOnlyScanState: ExecEndIndexOnlyScan((IndexOnlyScanState *) node); break; diff --git a/src/backend/executor/nodeIndirectIndexscan.c b/src/backend/executor/nodeIndirectIndexscan.c new file mode 100644 index 0000000..d8ecd19 --- /dev/null +++ b/src/backend/executor/nodeIndirectIndexscan.c @@ -0,0 +1,488 @@ +/*------------------------------------------------------------------------- + * + * nodeIndirectIndexscan.c + * Routines to support indirect index scans + * + * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/executor/nodeIndirectIndexscan.c + * + *------------------------------------------------------------------------- + */ +/* + * INTERFACE ROUTINES + * ExecIndirectIndexScan scans an index + * IndirectIndexNext retrieve next tuple + * ExecInitIndirectIndexScan creates and initializes state info. + * ExecReScanIndirectIndexScan rescans the indexed relation. + * ExecEndIndirectIndexScan releases all storage. + * ExecIndirectIndexMarkPos marks scan position. + * ExecIndirectIndexRestrPos restores scan position. + */ +#include "postgres.h" + +#include "access/relscan.h" +#include "access/visibilitymap.h" +#include "executor/execdebug.h" +#include "executor/nodeIndirectIndexscan.h" +#include "executor/nodeIndexscan.h" +#include "storage/bufmgr.h" +#include "storage/predicate.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/rel.h" + + +/* ---------------------------------------------------------------- + * IndirectIndexNext + * + * Retrieve a tuple from the IndirectIndexScan node's index. + * ---------------------------------------------------------------- + */ +static TupleTableSlot * +IndirectIndexNext(IndirectIndexScanState *node) +{ + EState *estate; + ScanDirection direction; + TupleTableSlot *slot; + + /* + * extract necessary information from index scan node + */ + estate = node->ss.ps.state; + direction = estate->es_direction; + /* flip direction if this is an overall backward scan */ + if (ScanDirectionIsBackward(((IndirectIndexScan *) node->ss.ps.plan)->indexorderdir)) + { + if (ScanDirectionIsForward(direction)) + direction = BackwardScanDirection; + else if (ScanDirectionIsBackward(direction)) + direction = ForwardScanDirection; + } + //econtext = node->ss.ps.ps_ExprContext; + slot = node->ss.ss_ScanTupleSlot; + + /*---------- + * OK, now that we have what we need, fetch the next tuple. + * + * if we don't have a current indirect tuple: + * obtain tuple from indirect index into slot + * if no tuple is obtained, return "we're done" + * apply rescan on PK, set tuple as scankeys + * obtain tuple from PK + * if no tuple is obtained: + * clear the indirect slot + * start over + * return tuple + *---------- + */ + for (;;) + { + HeapTuple pktuple; + + /* + * If we haven't read a tuple from the indirect index, do so now and + * install it as scan keys into the primary key scan. + */ + if (!node->iiss_HaveIndirectTuple) + { + ScanKeyData pkkeys[INDEX_MAX_KEYS]; + Datum pkvalues[INDEX_MAX_KEYS]; + int nkeys = node->iiss_PKRelationDesc->rd_index->indnatts; + bool isDone; + int i; + + /* + * Obtain one tuple from the indirect index. If there are no more + * tuples in the indirect index, then the scan is done. + */ + index_getnext_pkey(node->iiss_IndScanDesc, direction, pkvalues, &isDone); + if (isDone) + return ExecClearTuple(slot); /* no more tuples */ + + node->iiss_HaveIndirectTuple = true; + + /* Install scan keys, and get the primary key scan started */ + for (i = 0; i < nkeys; i++) + { + ScanKeyInit(&pkkeys[i], + i + 1, + BTEqualStrategyNumber, + node->iiss_EqualFuncs[i], + pkvalues[i]); + } + + index_rescan(node->iiss_PKScanDesc, pkkeys, nkeys, + NULL, 0); + } + + /* + * By now, we have a valid tuple from the indirect index and need to + * read (more?) tuples from the PK to return. + */ + pktuple = index_getnext(node->iiss_PKScanDesc, ForwardScanDirection); + if (pktuple == NULL) + { + /* + * If no tuples are obtained from the PK for the current tuple, + * start over making sure that the indirect scan will advance. + */ + node->iiss_HaveIndirectTuple = false; + continue; + } + + // FIXME here we apply ExecQual(), perhaps + + return ExecStoreTuple(pktuple, slot, InvalidBuffer, false); + } + + return ExecClearTuple(slot); +} + +/* + * IndirectIndexRecheck -- access method routine to recheck a tuple in + * EvalPlanQual + */ +static bool +IndirectIndexRecheck(IndirectIndexScanState *node, TupleTableSlot *slot) +{ + elog(ERROR, "EvalPlanQual recheck is not supported in indirect index scans"); + return false; /* keep compiler quiet */ +} + +TupleTableSlot * +ExecIndirectIndexScan(IndirectIndexScanState *node) +{ + /* + * If we have runtime keys and they've not already been set up, do it now. + */ + if (node->iiss_NumRuntimeKeys != 0 && !node->iiss_RuntimeKeysReady) + ExecReScan((PlanState *) node); + + Assert(node->iiss_NumOrderByKeys == 0); /* FIXME and test */ + return ExecScan(&node->ss, + (ExecScanAccessMtd) IndirectIndexNext, + (ExecScanRecheckMtd) IndirectIndexRecheck); +} + +/* ---------------------------------------------------------------- + * ExecReScanIndirectIndexScan(node) + * + * Recalculates the values of any scan keys whose value depends on + * information known at runtime, then rescans the indexed relation. + * + * Updating the scan key was formerly done separately in + * ExecUpdateIndexScanKeys. Integrating it into ReScan makes + * rescans of indices and relations/general streams more uniform. + * ---------------------------------------------------------------- + */ +void +ExecReScanIndirectIndexScan(IndirectIndexScanState *node) +{ + /* + * If we are doing runtime key calculations (ie, any of the index key + * values weren't simple Consts), compute the new key values. But first, + * reset the context so we don't leak memory as each outer tuple is + * scanned. Note this assumes that we will recalculate *all* runtime keys + * on each call. + */ + if (node->iiss_NumRuntimeKeys != 0) + { + ExprContext *econtext = node->iiss_RuntimeContext; + + ResetExprContext(econtext); + ExecIndexEvalRuntimeKeys(econtext, + node->iiss_RuntimeKeys, + node->iiss_NumRuntimeKeys); + } + node->iiss_RuntimeKeysReady = true; + + /* XXX We don't support reordering of results */ + + /* reset index scan */ + Assert(node->iiss_NumOrderByKeys == 0); /* FIXME and test */ + index_rescan(node->iiss_IndScanDesc, + node->iiss_ScanKeys, node->iiss_NumScanKeys, + node->iiss_OrderByKeys, node->iiss_NumOrderByKeys); + + ExecScanReScan(&node->ss); +} + +/* ---------------------------------------------------------------- + * ExecEndIndirectIndexScan + * ---------------------------------------------------------------- + */ +void +ExecEndIndirectIndexScan(IndirectIndexScanState *node) +{ + Relation indexRelationDesc; + Relation pkRelationDesc; + IndexScanDesc indexScanDesc; + IndexScanDesc pkScanDesc; + Relation relation; + + /* + * extract information from the node + */ + indexRelationDesc = node->iiss_IndRelationDesc; + pkRelationDesc = node->iiss_PKRelationDesc; + indexScanDesc = node->iiss_IndScanDesc; + pkScanDesc = node->iiss_PKScanDesc; + relation = node->ss.ss_currentRelation; + + /* clear out tuple table slots */ + ExecClearTuple(node->ss.ps.ps_ResultTupleSlot); + ExecClearTuple(node->ss.ss_ScanTupleSlot); + + /* close the index relations (no-op if we didn't open them) */ + if (indexScanDesc) + index_endscan(indexScanDesc); + if (pkScanDesc) + index_endscan(pkScanDesc); + if (indexRelationDesc) + index_close(indexRelationDesc, NoLock); + if (pkRelationDesc) + index_close(pkRelationDesc, NoLock); + + /* close the heap relation */ + ExecCloseScanRelation(relation); +} + +/* ---------------------------------------------------------------- + * ExecIndirectIndexMarkPos + * ---------------------------------------------------------------- + */ +void +ExecIndirectIndexMarkPos(IndirectIndexScanState *node) +{ + index_markpos(node->iiss_IndScanDesc); + node->iiss_HaveIndirectTuple = false; /* necessary? */ +} + +/* ---------------------------------------------------------------- + * ExecIndirectIndexRestrPos + * ---------------------------------------------------------------- + */ +void +ExecIndirectIndexRestrPos(IndirectIndexScanState *node) +{ + index_restrpos(node->iiss_IndScanDesc); + node->iiss_HaveIndirectTuple = false; /* necessary? */ +} + +/* ---------------------------------------------------------------- + * ExecInitIndirectIndexScan + * + * Initializes the index scan's state information, creates + * scan keys, and opens the base and index relations. + * + * Note: indirect index scans have 3 sets of state information + * because we have to keep track of the base relation, the + * indirect index relation itself, and the primary key index + * relation. + * ---------------------------------------------------------------- + */ +IndirectIndexScanState * +ExecInitIndirectIndexScan(IndirectIndexScan *node, EState *estate, int eflags) +{ + IndirectIndexScanState *indexstate; + Relation currentRelation; + bool relistarget; + int i; + + /* + * create state structure + */ + indexstate = makeNode(IndirectIndexScanState); + indexstate->ss.ps.plan = (Plan *) node; + indexstate->ss.ps.state = estate; + + /* + * Miscellaneous initialization + * + * create expression context for node + */ + ExecAssignExprContext(estate, &indexstate->ss.ps); + + indexstate->ss.ps.ps_TupFromTlist = false; + + /* + * initialize child expressions + */ + indexstate->ss.ps.targetlist = (List *) + ExecInitExpr((Expr *) node->scan.plan.targetlist, + (PlanState *) indexstate); + indexstate->ss.ps.qual = (List *) + ExecInitExpr((Expr *) node->scan.plan.qual, + (PlanState *) indexstate); + indexstate->indexqual = (List *) + ExecInitExpr((Expr *) node->indexqual, + (PlanState *) indexstate); + + /* + * tuple table initialization + */ + ExecInitResultTupleSlot(estate, &indexstate->ss.ps); + ExecInitScanTupleSlot(estate, &indexstate->ss); + + /* + * open the base relation and acquire appropriate lock on it. + */ + currentRelation = ExecOpenScanRelation(estate, node->scan.scanrelid, eflags); + + indexstate->ss.ss_currentRelation = currentRelation; + indexstate->ss.ss_currentScanDesc = NULL; /* no heap scan here */ + + /* + * get the scan type from the relation descriptor. + */ + ExecAssignScanType(&indexstate->ss, RelationGetDescr(currentRelation)); + + /* + * Initialize result tuple type and projection info. + */ + ExecAssignResultTypeFromTL(&indexstate->ss.ps); + ExecAssignScanProjectionInfo(&indexstate->ss); + + /* + * If we are just doing EXPLAIN (ie, aren't going to run the plan), stop + * here. This allows an index-advisor plugin to EXPLAIN a plan containing + * references to nonexistent indexes. + */ + if (eflags & EXEC_FLAG_EXPLAIN_ONLY) + return indexstate; + + /* + * Open the index relations. + * + * If the parent table is one of the target relations of the query, then + * InitPlan already opened and write-locked the indexes, so we can avoid + * taking another lock here. Otherwise we need normal reader's locks. + */ + relistarget = ExecRelationIsTargetRelation(estate, node->scan.scanrelid); + indexstate->iiss_IndRelationDesc = + index_open(node->indexid, relistarget ? NoLock : AccessShareLock); + indexstate->iiss_PKRelationDesc = + index_open(node->pkindexid, relistarget ? NoLock : AccessShareLock); + + /* + * Initialize index-specific scan state + */ + indexstate->iiss_HaveIndirectTuple = false; + indexstate->iiss_RuntimeKeysReady = false; + indexstate->iiss_RuntimeKeys = NULL; + indexstate->iiss_NumRuntimeKeys = 0; + + /* + * build the index scan keys from the index qualification + */ + ExecIndexBuildScanKeys((PlanState *) indexstate, + indexstate->iiss_IndRelationDesc, + node->indexqual, + false, + &indexstate->iiss_ScanKeys, + &indexstate->iiss_NumScanKeys, + &indexstate->iiss_RuntimeKeys, + &indexstate->iiss_NumRuntimeKeys, + NULL, /* no ArrayKeys */ + NULL); + + /* + * any ORDER BY exprs have to be turned into scankeys in the same way + */ + Assert(indexstate->iiss_NumOrderByKeys == 0); /* FIXME and test */ + ExecIndexBuildScanKeys((PlanState *) indexstate, + indexstate->iiss_IndRelationDesc, + node->indexorderby, + true, + &indexstate->iiss_OrderByKeys, + &indexstate->iiss_NumOrderByKeys, + &indexstate->iiss_RuntimeKeys, + &indexstate->iiss_NumRuntimeKeys, + NULL, /* no ArrayKeys */ + NULL); + + /* + * For the PK scan, initialize the equality func OIDs of each column's + * datatype. + */ + indexstate->iiss_EqualFuncs = palloc(sizeof(Oid) * + indexstate->iiss_PKRelationDesc->rd_index->indnatts); + for (i = 0; i < indexstate->iiss_PKRelationDesc->rd_index->indnatts; i++) + { + AttrNumber attnum; + Oid opfamily; + Oid datatype; + Oid eq_op; + Oid eq_proc; + + attnum = indexstate->iiss_PKRelationDesc->rd_index->indkey.values[i]; + datatype = currentRelation->rd_att->attrs[attnum - 1]->atttypid; + opfamily = indexstate->iiss_PKRelationDesc->rd_opfamily[i]; + eq_op = get_opfamily_member(opfamily, datatype, datatype, + BTEqualStrategyNumber); + if (!OidIsValid(eq_op)) + elog(ERROR, "missing operator %d(%u,%u) in opfamily %u", + BTEqualStrategyNumber, datatype, datatype, opfamily); + eq_proc = get_opcode(eq_op); + if (!OidIsValid(eq_proc)) + elog(ERROR, "missing oprcode for operator %u", eq_op); + indexstate->iiss_EqualFuncs[i] = eq_proc; + } + + /* XXX initialize sort support? See nodeIndexscan.c */ + + /* + * If we have runtime keys, we need an ExprContext to evaluate them. The + * node's standard context won't do because we want to reset that context + * for every tuple. So, build another context just like the other one... + * -tgl 7/11/00 + */ + if (indexstate->iiss_NumRuntimeKeys != 0) + { + ExprContext *stdecontext = indexstate->ss.ps.ps_ExprContext; + + ExecAssignExprContext(estate, &indexstate->ss.ps); + indexstate->iiss_RuntimeContext = indexstate->ss.ps.ps_ExprContext; + indexstate->ss.ps.ps_ExprContext = stdecontext; + } + else + { + indexstate->iiss_RuntimeContext = NULL; + } + + /* + * Initialize scan descriptors. + */ + indexstate->iiss_IndScanDesc = + index_beginscan(currentRelation, + indexstate->iiss_IndRelationDesc, + estate->es_snapshot, + indexstate->iiss_NumScanKeys, + indexstate->iiss_NumOrderByKeys); + indexstate->iiss_PKScanDesc = + index_beginscan(currentRelation, + indexstate->iiss_PKRelationDesc, + estate->es_snapshot, + indexstate->iiss_PKRelationDesc->rd_index->indnatts, + 0); + + /* + * If no run-time keys to calculate, go ahead and pass the scankeys to the + * index AM. + */ + if (indexstate->iiss_NumRuntimeKeys == 0) + index_rescan(indexstate->iiss_IndScanDesc, + indexstate->iiss_ScanKeys, + indexstate->iiss_NumScanKeys, + indexstate->iiss_OrderByKeys, + indexstate->iiss_NumOrderByKeys); + + /* + * all done. + */ + return indexstate; +} diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index a954610..f6d8497 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -500,7 +500,8 @@ ExecInsert(ModifyTableState *mtstate, /* insert index entries for tuple */ recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self), estate, true, &specConflict, - arbiterIndexes); + arbiterIndexes, + NULL); /* adjust the tuple's state accordingly */ if (!specConflict) @@ -546,7 +547,8 @@ ExecInsert(ModifyTableState *mtstate, if (resultRelInfo->ri_NumIndices > 0) recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false, NULL, - arbiterIndexes); + arbiterIndexes, + NULL); } } @@ -952,6 +954,7 @@ ExecUpdate(ItemPointer tupleid, else { LockTupleMode lockmode; + Bitmapset *unchangedAttrs = NULL; /* * Constraints might reference the tableoid column, so initialize @@ -995,7 +998,9 @@ lreplace:; estate->es_output_cid, estate->es_crosscheck_snapshot, true /* wait for commit */ , - &hufd, &lockmode); + &hufd, + &unchangedAttrs, + &lockmode); switch (result) { case HeapTupleSelfUpdated: @@ -1085,7 +1090,8 @@ lreplace:; */ if (resultRelInfo->ri_NumIndices > 0 && !HeapTupleIsHeapOnly(tuple)) recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self), - estate, false, NULL, NIL); + estate, false, NULL, NIL, + unchangedAttrs); } if (canSetTag) diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index d973225..c0b6651 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -437,6 +437,34 @@ _copyIndexScan(const IndexScan *from) } /* + * _copyIndirectIndexScan + */ +static IndirectIndexScan * +_copyIndirectIndexScan(const IndirectIndexScan *from) +{ + IndirectIndexScan *newnode = makeNode(IndirectIndexScan); + + /* + * copy node superclass fields + */ + CopyScanFields((const Scan *) from, (Scan *) newnode); + + /* + * copy remainder of node + */ + COPY_SCALAR_FIELD(indexid); + COPY_SCALAR_FIELD(pkindexid); + COPY_NODE_FIELD(indexqual); + COPY_NODE_FIELD(indexqualorig); + COPY_NODE_FIELD(indexorderby); + COPY_NODE_FIELD(indexorderbyorig); + COPY_NODE_FIELD(indexorderbyops); + COPY_SCALAR_FIELD(indexorderdir); + + return newnode; +} + +/* * _copyIndexOnlyScan */ static IndexOnlyScan * @@ -3158,6 +3186,7 @@ _copyIndexStmt(const IndexStmt *from) COPY_SCALAR_FIELD(indexOid); COPY_SCALAR_FIELD(oldNode); COPY_SCALAR_FIELD(unique); + COPY_SCALAR_FIELD(isindirect); COPY_SCALAR_FIELD(primary); COPY_SCALAR_FIELD(isconstraint); COPY_SCALAR_FIELD(deferrable); @@ -4431,6 +4460,9 @@ copyObject(const void *from) case T_IndexScan: retval = _copyIndexScan(from); break; + case T_IndirectIndexScan: + retval = _copyIndirectIndexScan(from); + break; case T_IndexOnlyScan: retval = _copyIndexOnlyScan(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index edc1797..a60add1 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1273,6 +1273,7 @@ _equalIndexStmt(const IndexStmt *a, const IndexStmt *b) COMPARE_SCALAR_FIELD(indexOid); COMPARE_SCALAR_FIELD(oldNode); COMPARE_SCALAR_FIELD(unique); + COMPARE_SCALAR_FIELD(isindirect); COMPARE_SCALAR_FIELD(primary); COMPARE_SCALAR_FIELD(isconstraint); COMPARE_SCALAR_FIELD(deferrable); diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 7258c03..bbc5973 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -489,6 +489,23 @@ _outIndexScan(StringInfo str, const IndexScan *node) } static void +_outIndirectIndexScan(StringInfo str, const IndirectIndexScan *node) +{ + WRITE_NODE_TYPE("INDIRECTINDEXSCAN"); + + _outScanInfo(str, (const Scan *) node); + + WRITE_OID_FIELD(indexid); + WRITE_OID_FIELD(pkindexid); + WRITE_NODE_FIELD(indexqual); + WRITE_NODE_FIELD(indexqualorig); + WRITE_NODE_FIELD(indexorderby); + WRITE_NODE_FIELD(indexorderbyorig); + WRITE_NODE_FIELD(indexorderbyops); + WRITE_ENUM_FIELD(indexorderdir, ScanDirection); +} + +static void _outIndexOnlyScan(StringInfo str, const IndexOnlyScan *node) { WRITE_NODE_TYPE("INDEXONLYSCAN"); @@ -2451,6 +2468,7 @@ _outIndexStmt(StringInfo str, const IndexStmt *node) WRITE_OID_FIELD(indexOid); WRITE_OID_FIELD(oldNode); WRITE_BOOL_FIELD(unique); + WRITE_BOOL_FIELD(isindirect); WRITE_BOOL_FIELD(primary); WRITE_BOOL_FIELD(isconstraint); WRITE_BOOL_FIELD(deferrable); @@ -3389,6 +3407,9 @@ outNode(StringInfo str, const void *obj) case T_IndexScan: _outIndexScan(str, obj); break; + case T_IndirectIndexScan: + _outIndirectIndexScan(str, obj); + break; case T_IndexOnlyScan: _outIndexOnlyScan(str, obj); break; diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index d608530..a290008 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -1671,6 +1671,28 @@ _readIndexScan(void) } /* + * _readIndirectIndexScan + */ +static IndirectIndexScan * +_readIndirectIndexScan(void) +{ + READ_LOCALS(IndirectIndexScan); + + ReadCommonScan(&local_node->scan); + + READ_OID_FIELD(indexid); + READ_OID_FIELD(pkindexid); + READ_NODE_FIELD(indexqual); + READ_NODE_FIELD(indexqualorig); + READ_NODE_FIELD(indexorderby); + READ_NODE_FIELD(indexorderbyorig); + READ_NODE_FIELD(indexorderbyops); + READ_ENUM_FIELD(indexorderdir, ScanDirection); + + READ_DONE(); +} + +/* * _readIndexOnlyScan */ static IndexOnlyScan * @@ -2465,6 +2487,8 @@ parseNodeString(void) return_value = _readSampleScan(); else if (MATCH("INDEXSCAN", 9)) return_value = _readIndexScan(); + else if (MATCH("INDIRECTINDEXSCAN", 17)) + return_value = _readIndirectIndexScan(); else if (MATCH("INDEXONLYSCAN", 13)) return_value = _readIndexOnlyScan(); else if (MATCH("BITMAPINDEXSCAN", 15)) diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index 2952bfb..5ff3ebe 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -776,9 +776,10 @@ get_index_paths(PlannerInfo *root, RelOptInfo *rel, * and those we mustn't submit to add_path here.) * * Also, pick out the ones that are usable as bitmap scans. For that, we - * must discard indexes that don't support bitmap scans, and we also are - * only interested in paths that have some selectivity; we should discard - * anything that was generated solely for ordering purposes. + * must discard indexes that don't support bitmap scans as well as + * indirect indexes, and we also are only interested in paths that have + * some selectivity; we should discard anything that was generated solely + * for ordering purposes. */ foreach(lc, indexpaths) { @@ -788,6 +789,7 @@ get_index_paths(PlannerInfo *root, RelOptInfo *rel, add_path(rel, (Path *) ipath); if (index->amhasgetbitmap && + !index->indirect && (ipath->path.pathkeys == NIL || ipath->indexselectivity < 1.0)) *bitindexpaths = lappend(*bitindexpaths, ipath); @@ -800,6 +802,7 @@ get_index_paths(PlannerInfo *root, RelOptInfo *rel, */ if (skip_nonnative_saop) { + Assert(!index->indirect); /* what to do here? */ indexpaths = build_index_paths(root, rel, index, clauses, false, @@ -1119,6 +1122,10 @@ build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel, if (!index->amhasgetbitmap) continue; + /* Ignore indirect indexes too */ + if (index->indirect) + continue; + /* * Ignore partial indexes that do not match the query. If a partial * index is marked predOK then we know it's OK. Otherwise, we have to @@ -1798,6 +1805,10 @@ check_index_only(RelOptInfo *rel, IndexOptInfo *index) if (!enable_indexonlyscan) return false; + /* Can't use indirect indexes for index-only scans */ + if (index->indirect) + return false; + /* * Check that all needed attributes of the relation are available from the * index. diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index ad49674..52ec437 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -166,6 +166,17 @@ static IndexScan *make_indexscan(List *qptlist, List *qpqual, Index scanrelid, List *indexorderby, List *indexorderbyorig, List *indexorderbyops, ScanDirection indexscandir); +static IndirectIndexScan *make_indirectindexscan(List *qptlist, + List *qpqual, + Index scanrelid, + Oid indexid, + Oid pkindexid, + List *indexqual, + List *indexqualorig, + List *indexorderby, + List *indexorderbyorig, + List *indexorderbyops, + ScanDirection indexscandir); static IndexOnlyScan *make_indexonlyscan(List *qptlist, List *qpqual, Index scanrelid, Oid indexid, List *indexqual, List *indexorderby, @@ -2494,6 +2505,8 @@ create_indexscan_plan(PlannerInfo *root, /* Finally ready to build the plan node */ if (indexonly) + { + Assert(!best_path->indexinfo->indirect); /* maybe someday */ scan_plan = (Scan *) make_indexonlyscan(tlist, qpqual, baserelid, @@ -2502,6 +2515,23 @@ create_indexscan_plan(PlannerInfo *root, fixed_indexorderbys, best_path->indexinfo->indextlist, best_path->indexscandir); + } + else if (best_path->indexinfo->indirect) + { + Oid pkindexoid = best_path->indexinfo->pkindex->indexoid; + + scan_plan = (Scan *) make_indirectindexscan(tlist, + qpqual, + baserelid, + indexoid, + pkindexoid, + fixed_indexquals, + stripped_indexquals, + fixed_indexorderbys, + indexorderbys, + indexorderbyops, + best_path->indexscandir); + } else scan_plan = (Scan *) make_indexscan(tlist, qpqual, @@ -4720,6 +4750,39 @@ make_indexscan(List *qptlist, return node; } +static IndirectIndexScan * +make_indirectindexscan(List *qptlist, + List *qpqual, + Index scanrelid, + Oid indexid, + Oid pkindexid, + List *indexqual, + List *indexqualorig, + List *indexorderby, + List *indexorderbyorig, + List *indexorderbyops, + ScanDirection indexscandir) +{ + IndirectIndexScan *node = makeNode(IndirectIndexScan); + Plan *plan = &node->scan.plan; + + plan->targetlist = qptlist; + plan->qual = qpqual; + plan->lefttree = NULL; + plan->righttree = NULL; + node->scan.scanrelid = scanrelid; + node->indexid = indexid; + node->pkindexid = pkindexid; + node->indexqual = indexqual; + node->indexqualorig = indexqualorig; + node->indexorderby = indexorderby; + node->indexorderbyorig = indexorderbyorig; + node->indexorderbyops = indexorderbyops; + node->indexorderdir = indexscandir; + + return node; +} + static IndexOnlyScan * make_indexonlyscan(List *qptlist, List *qpqual, diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 2fe1c8c..8401c14 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -490,6 +490,22 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) fix_scan_list(root, splan->indexorderbyorig, rtoffset); } break; + case T_IndirectIndexScan: + { + IndirectIndexScan *splan = (IndirectIndexScan *) plan; + splan->scan.scanrelid += rtoffset; + splan->scan.plan.targetlist = + fix_scan_list(root, splan->scan.plan.targetlist, rtoffset); + splan->indexqual = + fix_scan_list(root, splan->indexqual, rtoffset); + splan->indexqualorig = + fix_scan_list(root, splan->indexqualorig, rtoffset); + splan->indexorderby = + fix_scan_list(root, splan->indexorderby, rtoffset); + splan->indexorderbyorig = + fix_scan_list(root, splan->indexorderbyorig, rtoffset); + } + break; case T_IndexOnlyScan: { IndexOnlyScan *splan = (IndexOnlyScan *) plan; diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index 3171743..aa5190c 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -2327,6 +2327,13 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params, context.paramids = bms_add_members(context.paramids, scan_params); break; + case T_IndirectIndexScan: + finalize_primnode((Node *) ((IndirectIndexScan *) plan)->indexqual, + &context); + finalize_primnode((Node *) ((IndirectIndexScan *) plan)->indexorderby, + &context); + break; + case T_IndexOnlyScan: finalize_primnode((Node *) ((IndexOnlyScan *) plan)->indexqual, &context); diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 72272d9..fa78e33 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -152,6 +152,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, List *indexoidlist; ListCell *l; LOCKMODE lmode; + IndexOptInfo *pkinfo = NULL; indexoidlist = RelationGetIndexList(relation); @@ -234,6 +235,12 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, } info->relam = indexRelation->rd_rel->relam; + info->indirect = index->indisindirect; + if (info->indirect) + { + Assert(pkinfo != NULL); + info->pkindex = pkinfo; + } /* We copy just the fields we need, not all of rd_amroutine */ amroutine = indexRelation->rd_amroutine; @@ -354,6 +361,10 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, info->immediate = index->indimmediate; info->hypothetical = false; + /* remember primary key for indirect indexes */ + if (index->indisprimary) + pkinfo = info; + /* * Estimate the index size. If it's not a partial index, we lock * the number-of-tuples estimate to equal the parent table; if it diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 2ed7b52..5a2601d 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -418,7 +418,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type overlay_placing substr_from substr_for %type opt_instead -%type opt_unique opt_concurrently opt_verbose opt_full +%type opt_unique opt_indirect opt_concurrently opt_verbose opt_full %type opt_freeze opt_default opt_recheck %type opt_binary opt_oids copy_delimiter @@ -615,7 +615,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); HANDLER HAVING HEADER_P HOLD HOUR_P IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P - INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P + INCLUDING INCREMENT INDEX INDEXES INDIRECT + INHERIT INHERITS INITIALLY INLINE_P INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION @@ -7017,25 +7018,26 @@ defacl_privilege_target: * willing to make TABLESPACE a fully reserved word. *****************************************************************************/ -IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_index_name +IndexStmt: CREATE opt_unique opt_indirect INDEX opt_concurrently opt_index_name ON qualified_name access_method_clause '(' index_params ')' opt_reloptions OptTableSpace where_clause { IndexStmt *n = makeNode(IndexStmt); n->unique = $2; - n->concurrent = $4; - n->idxname = $5; - n->relation = $7; - n->accessMethod = $8; - n->indexParams = $10; - n->options = $12; - n->tableSpace = $13; - n->whereClause = $14; + n->concurrent = $5; + n->idxname = $6; + n->relation = $8; + n->accessMethod = $9; + n->indexParams = $11; + n->options = $13; + n->tableSpace = $14; + n->whereClause = $15; n->excludeOpNames = NIL; n->idxcomment = NULL; n->indexOid = InvalidOid; n->oldNode = InvalidOid; n->primary = false; + n->isindirect = $3; n->isconstraint = false; n->deferrable = false; n->initdeferred = false; @@ -7043,25 +7045,26 @@ IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_index_name n->if_not_exists = false; $$ = (Node *)n; } - | CREATE opt_unique INDEX opt_concurrently IF_P NOT EXISTS index_name + | CREATE opt_unique opt_indirect INDEX opt_concurrently IF_P NOT EXISTS index_name ON qualified_name access_method_clause '(' index_params ')' opt_reloptions OptTableSpace where_clause { IndexStmt *n = makeNode(IndexStmt); n->unique = $2; - n->concurrent = $4; - n->idxname = $8; - n->relation = $10; - n->accessMethod = $11; - n->indexParams = $13; - n->options = $15; - n->tableSpace = $16; - n->whereClause = $17; + n->concurrent = $5; + n->idxname = $9; + n->relation = $11; + n->accessMethod = $12; + n->indexParams = $14; + n->options = $16; + n->tableSpace = $17; + n->whereClause = $18; n->excludeOpNames = NIL; n->idxcomment = NULL; n->indexOid = InvalidOid; n->oldNode = InvalidOid; n->primary = false; + n->isindirect = $3; n->isconstraint = false; n->deferrable = false; n->initdeferred = false; @@ -7076,6 +7079,11 @@ opt_unique: | /*EMPTY*/ { $$ = FALSE; } ; +opt_indirect: + INDIRECT { $$ = TRUE; } + | /*EMPTY*/ { $$ = FALSE; } + ; + opt_concurrently: CONCURRENTLY { $$ = TRUE; } | /*EMPTY*/ { $$ = FALSE; } @@ -14107,6 +14115,7 @@ unreserved_keyword: | INCREMENT | INDEX | INDEXES + | INDIRECT | INHERIT | INHERITS | INLINE_P diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 4e2ba19..14b0de7 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -1248,7 +1248,8 @@ pg_get_indexdef_worker(Oid indexrelid, int colno, if (!attrsOnly) { if (!isConstraint) - appendStringInfo(&buf, "CREATE %sINDEX %s ON %s USING %s (", + appendStringInfo(&buf, "CREATE %s%sINDEX %s ON %s USING %s (", + idxrec->indisindirect ? "INDIRECT " : "", idxrec->indisunique ? "UNIQUE " : "", quote_identifier(NameStr(idxrelrec->relname)), generate_relation_name(indrelid, NIL), diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c index 4973396..d980e10 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -5029,6 +5029,10 @@ get_actual_variable_range(PlannerInfo *root, VariableStatData *vardata, if (index->relam != BTREE_AM_OID) continue; + /* Ignore indirect indexes */ + if (index->indirect) + continue; + /* * Ignore partial indexes --- we only want stats that cover the entire * relation. diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 2a68359..1e2ff56 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -271,7 +271,7 @@ static TupleDesc GetPgIndexDescriptor(void); static void AttrDefaultFetch(Relation relation); static void CheckConstraintFetch(Relation relation); static int CheckConstraintCmp(const void *a, const void *b); -static List *insert_ordered_oid(List *list, Oid datum); +static List *insert_ordered_oid(List *list, Oid datum, bool must_be_first); static void InitIndexAmRoutine(Relation relation); static void IndexSupportInitialize(oidvector *indclass, RegProcedure *indexSupport, @@ -2335,8 +2335,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc) list_free_deep(relation->rd_fkeylist); list_free(relation->rd_indexlist); bms_free(relation->rd_indexattr); + bms_free(relation->rd_hotattr); bms_free(relation->rd_keyattr); bms_free(relation->rd_idattr); + bms_free(relation->rd_indirectattr); if (relation->rd_options) pfree(relation->rd_options); if (relation->rd_indextuple) @@ -4288,6 +4290,47 @@ RelationGetFKeyList(Relation relation) } /* + * Return the relation's primary key OID. + * + * Surely this can be made better ... + */ +Oid +RelationGetPrimaryKey(Relation relation) +{ + Relation indrel; + SysScanDesc indscan; + ScanKeyData skey; + HeapTuple htup; + Oid pkid = InvalidOid; + + /* Currently we just scan pg_index every time this is called */ + ScanKeyInit(&skey, + Anum_pg_index_indrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationGetRelid(relation))); + + indrel = heap_open(IndexRelationId, AccessShareLock); + indscan = systable_beginscan(indrel, IndexIndrelidIndexId, true, + NULL, 1, &skey); + while (HeapTupleIsValid(htup = systable_getnext(indscan))) + { + Form_pg_index index = (Form_pg_index) GETSTRUCT(htup); + + if (!IndexIsLive(index)) + continue; + if (!index->indisprimary) + continue; + pkid = index->indexrelid; + break; + } + + systable_endscan(indscan); + heap_close(indrel, AccessShareLock); + + return pkid; +} + +/* * RelationGetIndexList -- get a list of OIDs of indexes on this relation * * The index list is created only if someone requests it. We scan pg_index @@ -4301,7 +4344,8 @@ RelationGetFKeyList(Relation relation) * Such indexes are expected to be dropped momentarily, and should not be * touched at all by any caller of this function. * - * The returned list is guaranteed to be sorted in order by OID. This is + * The returned list is guaranteed to be sorted in order by OID, except that + * the primary key is always in front. This is * needed by the executor, since for index types that we obtain exclusive * locks on when updating the index, all backends must lock the indexes in * the same order or we will get deadlocks (see ExecOpenIndices()). Any @@ -4377,7 +4421,8 @@ RelationGetIndexList(Relation relation) continue; /* Add index's OID to result list in the proper order */ - result = insert_ordered_oid(result, index->indexrelid); + result = insert_ordered_oid(result, index->indexrelid, + index->indisprimary); /* * indclass cannot be referenced directly through the C struct, @@ -4450,12 +4495,12 @@ RelationGetIndexList(Relation relation) * indexes... */ static List * -insert_ordered_oid(List *list, Oid datum) +insert_ordered_oid(List *list, Oid datum, bool must_be_first) { ListCell *prev; /* Does the datum belong at the front? */ - if (list == NIL || datum < linitial_oid(list)) + if (list == NIL || datum < linitial_oid(list) || must_be_first) return lcons_oid(datum, list); /* No, so find the entry it belongs after */ prev = list_head(list); @@ -4492,7 +4537,7 @@ insert_ordered_oid(List *list, Oid datum) * to ensure that a correct rd_indexattr set has been cached before first * calling RelationSetIndexList; else a subsequent inquiry might cause a * wrong rd_indexattr set to get computed and cached. Likewise, we do not - * touch rd_keyattr or rd_idattr. + * touch rd_hotattr, rd_keyattr, rd_indirectattr or rd_idattr. */ void RelationSetIndexList(Relation relation, List *indexIds, Oid oidIndex) @@ -4703,9 +4748,7 @@ RelationGetIndexPredicate(Relation relation) * simple index keys, but attributes used in expressions and partial-index * predicates.) * - * Depending on attrKind, a bitmap covering the attnums for all index columns, - * for all potential foreign key columns, or for all columns in the configured - * replica identity index is returned. + * A bitmap covering attnums is returned, depending on attrKind. * * Attribute numbers are offset by FirstLowInvalidHeapAttributeNumber so that * we can include system attributes (e.g., OID) in the bitmap representation. @@ -4721,8 +4764,10 @@ Bitmapset * RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind) { Bitmapset *indexattrs; /* indexed columns */ + Bitmapset *hotattrs; /* HOT-index-relevant columns */ Bitmapset *uindexattrs; /* columns in unique indexes */ Bitmapset *idindexattrs; /* columns in the replica identity */ + Bitmapset *indirectattrs; /* columns in indirect indexes */ List *indexoidlist; Oid relreplindex; ListCell *l; @@ -4737,8 +4782,12 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind) return bms_copy(relation->rd_indexattr); case INDEX_ATTR_BITMAP_KEY: return bms_copy(relation->rd_keyattr); + case INDEX_ATTR_BITMAP_HOT: + return bms_copy(relation->rd_hotattr); case INDEX_ATTR_BITMAP_IDENTITY_KEY: return bms_copy(relation->rd_idattr); + case INDEX_ATTR_BITMAP_INDIRECT_INDEXES: + return bms_copy(relation->rd_indirectattr); default: elog(ERROR, "unknown attrKind %u", attrKind); } @@ -4767,7 +4816,7 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind) relreplindex = relation->rd_replidindex; /* - * For each index, add referenced attributes to indexattrs. + * For each index, add referenced attributes to the attribute bitmaps. * * Note: we consider all indexes returned by RelationGetIndexList, even if * they are not indisready or indisvalid. This is important because an @@ -4777,8 +4826,10 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind) * won't be returned at all by RelationGetIndexList. */ indexattrs = NULL; + hotattrs = NULL; uindexattrs = NULL; idindexattrs = NULL; + indirectattrs = NULL; foreach(l, indexoidlist) { Oid indexOid = lfirst_oid(l); @@ -4787,6 +4838,8 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind) int i; bool isKey; /* candidate key */ bool isIDKey; /* replica identity index */ + bool isIndirect; /* an indirect index */ + Bitmapset *exprattrs = NULL; indexDesc = index_open(indexOid, AccessShareLock); @@ -4801,6 +4854,9 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind) /* Is this index the configured (or default) replica identity? */ isIDKey = (indexOid == relreplindex); + /* Is this an indirect index? */ + isIndirect = indexInfo->ii_IsIndirect; + /* Collect simple attribute references */ for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++) { @@ -4809,7 +4865,7 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind) if (attrnum != 0) { indexattrs = bms_add_member(indexattrs, - attrnum - FirstLowInvalidHeapAttributeNumber); + attrnum - FirstLowInvalidHeapAttributeNumber); if (isKey) uindexattrs = bms_add_member(uindexattrs, @@ -4818,14 +4874,31 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind) if (isIDKey) idindexattrs = bms_add_member(idindexattrs, attrnum - FirstLowInvalidHeapAttributeNumber); + + if (isIndirect) + indirectattrs = bms_add_member(indirectattrs, + attrnum - FirstLowInvalidHeapAttributeNumber); + else + hotattrs = bms_add_member(hotattrs, + attrnum - FirstLowInvalidHeapAttributeNumber); } } /* Collect all attributes used in expressions, too */ - pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &indexattrs); + pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &exprattrs); + indexattrs = bms_add_members(indexattrs, exprattrs); + /* these don't affect KEY (unique) indexes */ + hotattrs = bms_add_members(hotattrs, exprattrs); + indirectattrs = bms_add_members(indirectattrs, exprattrs); + bms_free(exprattrs); /* Collect all attributes in the index predicate, too */ - pull_varattnos((Node *) indexInfo->ii_Predicate, 1, &indexattrs); + exprattrs = NULL; + pull_varattnos((Node *) indexInfo->ii_Predicate, 1, &exprattrs); + indexattrs = bms_add_members(indexattrs, exprattrs); + hotattrs = bms_add_members(hotattrs, exprattrs); + /* these don't affect KEY or indirect indexes */ + bms_free(exprattrs); index_close(indexDesc, AccessShareLock); } @@ -4835,10 +4908,14 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind) /* Don't leak the old values of these bitmaps, if any */ bms_free(relation->rd_indexattr); relation->rd_indexattr = NULL; + bms_free(relation->rd_hotattr); + relation->rd_hotattr = NULL; bms_free(relation->rd_keyattr); relation->rd_keyattr = NULL; bms_free(relation->rd_idattr); relation->rd_idattr = NULL; + bms_free(relation->rd_indirectattr); + relation->rd_indirectattr = NULL; /* * Now save copies of the bitmaps in the relcache entry. We intentionally @@ -4851,6 +4928,8 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind) relation->rd_keyattr = bms_copy(uindexattrs); relation->rd_idattr = bms_copy(idindexattrs); relation->rd_indexattr = bms_copy(indexattrs); + relation->rd_hotattr = bms_copy(hotattrs); + relation->rd_indirectattr = bms_copy(indirectattrs); MemoryContextSwitchTo(oldcxt); /* We return our original working copy for caller to play with */ @@ -4858,10 +4937,14 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind) { case INDEX_ATTR_BITMAP_ALL: return indexattrs; + case INDEX_ATTR_BITMAP_HOT: + return hotattrs; case INDEX_ATTR_BITMAP_KEY: return uindexattrs; case INDEX_ATTR_BITMAP_IDENTITY_KEY: return idindexattrs; + case INDEX_ATTR_BITMAP_INDIRECT_INDEXES: + return indirectattrs; default: elog(ERROR, "unknown attrKind %u", attrKind); return NULL; @@ -5409,8 +5492,10 @@ load_relcache_init_file(bool shared) rel->rd_oidindex = InvalidOid; rel->rd_replidindex = InvalidOid; rel->rd_indexattr = NULL; + rel->rd_hotattr = NULL; rel->rd_keyattr = NULL; rel->rd_idattr = NULL; + rel->rd_indirectattr = NULL; rel->rd_createSubid = InvalidSubTransactionId; rel->rd_newRelfilenodeSubid = InvalidSubTransactionId; rel->rd_amcache = NULL; diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h index 1036cca..9f28c7d 100644 --- a/src/include/access/amapi.h +++ b/src/include/access/amapi.h @@ -175,6 +175,8 @@ typedef struct IndexAmRoutine bool amclusterable; /* does AM handle predicate locks? */ bool ampredlocks; + /* does AM support indirect indexes? */ + bool amcanindirect; /* type of data stored in index, or InvalidOid if variable */ Oid amkeytype; diff --git a/src/include/access/genam.h b/src/include/access/genam.h index 81907d5..e2aa3a5 100644 --- a/src/include/access/genam.h +++ b/src/include/access/genam.h @@ -102,13 +102,17 @@ typedef struct SysScanDescData *SysScanDesc; * call is made with UNIQUE_CHECK_EXISTING. The tuple is already in the * index in this case, so it should not be inserted again. Rather, just * check for conflicting live tuples (possibly blocking). + * + * UNIQUE_CHECK_INSERT_SINGLETON only inserts if there isn't already an + * index tuple. This is supported for indirect indexes. */ typedef enum IndexUniqueCheck { UNIQUE_CHECK_NO, /* Don't do any uniqueness checking */ UNIQUE_CHECK_YES, /* Enforce uniqueness at insertion time */ UNIQUE_CHECK_PARTIAL, /* Test uniqueness, but no error */ - UNIQUE_CHECK_EXISTING /* Check if existing tuple is unique */ + UNIQUE_CHECK_EXISTING, /* Check if existing tuple is unique */ + UNIQUE_CHECK_INSERT_SINGLETON /* Only insert if value doesn't exist */ } IndexUniqueCheck; @@ -127,6 +131,7 @@ extern void index_close(Relation relation, LOCKMODE lockmode); extern bool index_insert(Relation indexRelation, Datum *values, bool *isnull, + Datum *pkeyValues, ItemPointer heap_t_ctid, Relation heapRelation, IndexUniqueCheck checkUnique); @@ -146,7 +151,10 @@ extern void index_markpos(IndexScanDesc scan); extern void index_restrpos(IndexScanDesc scan); extern ItemPointer index_getnext_tid(IndexScanDesc scan, ScanDirection direction); +extern void index_getnext_pkey(IndexScanDesc scan, ScanDirection direction, + Datum *pkvals, bool *isDone); extern HeapTuple index_fetch_heap(IndexScanDesc scan); + extern HeapTuple index_getnext(IndexScanDesc scan, ScanDirection direction); extern int64 index_getbitmap(IndexScanDesc scan, TIDBitmap *bitmap); diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index 0d12bbb..99af368 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -16,6 +16,7 @@ #include "access/sdir.h" #include "access/skey.h" +#include "nodes/bitmapset.h" #include "nodes/lockoptions.h" #include "nodes/primnodes.h" #include "storage/bufpage.h" @@ -160,7 +161,8 @@ extern void heap_abort_speculative(Relation relation, HeapTuple tuple); extern HTSU_Result heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, CommandId cid, Snapshot crosscheck, bool wait, - HeapUpdateFailureData *hufd, LockTupleMode *lockmode); + HeapUpdateFailureData *hufd, Bitmapset **unchanged_attrs, + LockTupleMode *lockmode); extern HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple, CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy, bool follow_update, diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h index 37e6ef3..0a70e08 100644 --- a/src/include/catalog/index.h +++ b/src/include/catalog/index.h @@ -37,6 +37,21 @@ typedef enum INDEX_DROP_SET_DEAD } IndexStateFlagsAction; +static inline void +FAKE_CTID_FROM_PKVALUES(ItemPointer iptr, int16 pkNumKeys, Datum *pkvalues) +{ + /* We should support more than one column in the PK */ + Assert(pkNumKeys == 1); + + /* + * Because some overzealous checks, we can't have all-zeroes offnum + * nor blknum, so we always set their lowest-order bit to 1; make sure + * to ignore that bit when reading back the value from the TID. + */ + iptr->ip_posid = ((pkvalues[0] & 0x7fff) << 1) | 1; + iptr->ip_blkid.bi_hi = (pkvalues[0] >> 15) & 0xffff; + iptr->ip_blkid.bi_lo = ((pkvalues[0] >> 31) & 0xffff) | 1; +} extern void index_check_primary_key(Relation heapRel, IndexInfo *indexInfo, diff --git a/src/include/catalog/pg_index.h b/src/include/catalog/pg_index.h index ee97c5d..144d204 100644 --- a/src/include/catalog/pg_index.h +++ b/src/include/catalog/pg_index.h @@ -34,6 +34,7 @@ CATALOG(pg_index,2610) BKI_WITHOUT_OIDS BKI_SCHEMA_MACRO Oid indrelid; /* OID of the relation it indexes */ int16 indnatts; /* number of columns in index */ bool indisunique; /* is this a unique index? */ + bool indisindirect; /* is this an indirect index? */ bool indisprimary; /* is this index for primary key? */ bool indisexclusion; /* is this index for exclusion constraint? */ bool indimmediate; /* is uniqueness enforced immediately? */ @@ -70,26 +71,27 @@ typedef FormData_pg_index *Form_pg_index; * compiler constants for pg_index * ---------------- */ -#define Natts_pg_index 19 +#define Natts_pg_index 20 #define Anum_pg_index_indexrelid 1 #define Anum_pg_index_indrelid 2 #define Anum_pg_index_indnatts 3 #define Anum_pg_index_indisunique 4 -#define Anum_pg_index_indisprimary 5 -#define Anum_pg_index_indisexclusion 6 -#define Anum_pg_index_indimmediate 7 -#define Anum_pg_index_indisclustered 8 -#define Anum_pg_index_indisvalid 9 -#define Anum_pg_index_indcheckxmin 10 -#define Anum_pg_index_indisready 11 -#define Anum_pg_index_indislive 12 -#define Anum_pg_index_indisreplident 13 -#define Anum_pg_index_indkey 14 -#define Anum_pg_index_indcollation 15 -#define Anum_pg_index_indclass 16 -#define Anum_pg_index_indoption 17 -#define Anum_pg_index_indexprs 18 -#define Anum_pg_index_indpred 19 +#define Anum_pg_index_indisindirect 5 +#define Anum_pg_index_indisprimary 6 +#define Anum_pg_index_indisexclusion 7 +#define Anum_pg_index_indimmediate 8 +#define Anum_pg_index_indisclustered 9 +#define Anum_pg_index_indisvalid 10 +#define Anum_pg_index_indcheckxmin 11 +#define Anum_pg_index_indisready 12 +#define Anum_pg_index_indislive 13 +#define Anum_pg_index_indisreplident 14 +#define Anum_pg_index_indkey 15 +#define Anum_pg_index_indcollation 16 +#define Anum_pg_index_indclass 17 +#define Anum_pg_index_indoption 18 +#define Anum_pg_index_indexprs 19 +#define Anum_pg_index_indpred 20 /* * Index AMs that support ordered scans must support these two indoption diff --git a/src/include/executor/execdebug.h b/src/include/executor/execdebug.h index 950a0bc..e662a69 100644 --- a/src/include/executor/execdebug.h +++ b/src/include/executor/execdebug.h @@ -17,6 +17,7 @@ #ifndef EXECDEBUG_H #define EXECDEBUG_H +#include "access/printtup.h" #include "executor/executor.h" #include "nodes/print.h" diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index b74fa5e..3ec0b52 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -378,7 +378,7 @@ extern void ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative); extern void ExecCloseIndices(ResultRelInfo *resultRelInfo); extern List *ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid, EState *estate, bool noDupErr, bool *specConflict, - List *arbiterIndexes); + List *arbiterIndexes, Bitmapset *unchangedAttrs); extern bool ExecCheckIndexConstraints(TupleTableSlot *slot, EState *estate, ItemPointer conflictTid, List *arbiterIndexes); extern void check_exclusion_constraint(Relation heap, Relation index, diff --git a/src/include/executor/nodeIndirectIndexscan.h b/src/include/executor/nodeIndirectIndexscan.h new file mode 100644 index 0000000..46eea32 --- /dev/null +++ b/src/include/executor/nodeIndirectIndexscan.h @@ -0,0 +1,26 @@ +/*------------------------------------------------------------------------- + * + * nodeIndirectIndexscan.h + * + * + * + * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/executor/nodeIndirectIndexscan.h + * + *------------------------------------------------------------------------- + */ +#ifndef NODEINDIRECTINDEXSCAN_H +#define NODEINDIRECTINDEXSCAN_H + +#include "nodes/execnodes.h" + +extern IndirectIndexScanState *ExecInitIndirectIndexScan(IndirectIndexScan *node, EState *estate, int eflags); +extern TupleTableSlot *ExecIndirectIndexScan(IndirectIndexScanState *node); +extern void ExecEndIndirectIndexScan(IndirectIndexScanState *node); +extern void ExecIndirectIndexMarkPos(IndirectIndexScanState *node); +extern void ExecIndirectIndexRestrPos(IndirectIndexScanState *node); +extern void ExecReScanIndirectIndexScan(IndirectIndexScanState *node); + +#endif /* NODEINDIRECTINDEXSCAN_H */ diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 5c3b868..e07f441 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -49,6 +49,7 @@ * UniqueProcs * UniqueStrats * Unique is it a unique index? + * IsIndirect is it an indirect index? * ReadyForInserts is it valid for inserts? * Concurrent are we doing a concurrent index build? * BrokenHotChain did we detect any broken HOT chains? @@ -73,6 +74,7 @@ typedef struct IndexInfo Oid *ii_UniqueProcs; /* array with one entry per column */ uint16 *ii_UniqueStrats; /* array with one entry per column */ bool ii_Unique; + bool ii_IsIndirect; bool ii_ReadyForInserts; bool ii_Concurrent; bool ii_BrokenHotChain; @@ -1378,6 +1380,45 @@ typedef struct IndexScanState } IndexScanState; /* ---------------- + * IndirectIndexScanState information + * + * indexqual execution state for indexqual expressions + * ScanKeys Skey structures for index quals + * NumScanKeys number of ScanKeys + * IndRelationDesc indirect index relation descriptor + * IndScanDesc indirect index scan descriptor + * PKRelationDesc primary key index relation descriptor + * PKScanDesc primary key index scan descriptor + * IndirectSlot tuple obtained from indirect index + * OrderByKeys Skey structures for index ordering operators + * NumOrderByKeys number of OrderByKeys + * RuntimeKeys info about Skeys that must be evaluated at runtime + * NumRuntimeKeys number of RuntimeKeys + * RuntimeKeysReady true if runtime Skeys have been computed + * RuntimeContext expr context for evaling runtime Skeys + * ---------------- + */ +typedef struct IndirectIndexScanState +{ + ScanState ss; /* its first field is NodeTag */ + List *indexqual; + ScanKey iiss_ScanKeys; + int iiss_NumScanKeys; + Relation iiss_IndRelationDesc; + IndexScanDesc iiss_IndScanDesc; + Relation iiss_PKRelationDesc; + IndexScanDesc iiss_PKScanDesc; + bool iiss_HaveIndirectTuple; + ScanKey iiss_OrderByKeys; + int iiss_NumOrderByKeys; + IndexRuntimeKeyInfo *iiss_RuntimeKeys; + int iiss_NumRuntimeKeys; + bool iiss_RuntimeKeysReady; + Oid *iiss_EqualFuncs; + ExprContext *iiss_RuntimeContext; +} IndirectIndexScanState; + +/* ---------------- * IndexOnlyScanState information * * indexqual execution state for indexqual expressions diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index c514d3f..b57f235 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -53,6 +53,7 @@ typedef enum NodeTag T_SeqScan, T_SampleScan, T_IndexScan, + T_IndirectIndexScan, T_IndexOnlyScan, T_BitmapIndexScan, T_BitmapHeapScan, @@ -101,6 +102,7 @@ typedef enum NodeTag T_SeqScanState, T_SampleScanState, T_IndexScanState, + T_IndirectIndexScanState, T_IndexOnlyScanState, T_BitmapIndexScanState, T_BitmapHeapScanState, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index fc532fb..40e1547 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2558,6 +2558,7 @@ typedef struct IndexStmt Oid indexOid; /* OID of an existing index, if any */ Oid oldNode; /* relfilenode of existing storage, if any */ bool unique; /* is index unique? */ + bool isindirect; /* is index indirect? */ bool primary; /* is index a primary key? */ bool isconstraint; /* is it for a pkey/unique constraint? */ bool deferrable; /* is the constraint DEFERRABLE? */ diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index e2fbc7d..c1e6a95 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -353,6 +353,27 @@ typedef struct IndexScan } IndexScan; /* ---------------- + * indirect index scan node + * + * IndirectIndexScan is very similar to IndexScan, but it specifies a + * scan of an indirect index. In addition to the fields in IndexScan, + * it has the OID of the primary key that this index references. + * ---------------- + */ +typedef struct IndirectIndexScan +{ + Scan scan; + Oid indexid; /* OID of indirect index */ + Oid pkindexid; /* OID of the primary key it references */ + List *indexqual; /* list of index quals */ + List *indexqualorig; /* the same in original form */ + List *indexorderby; /* list of index ORDER BY exprs */ + List *indexorderbyorig; /* the same in original form */ + List *indexorderbyops; /* OIDs of sort ops for ORDER BY exprs */ + ScanDirection indexorderdir; /* forward or backward or don't care */ +} IndirectIndexScan; + +/* ---------------- * index-only scan node * * IndexOnlyScan is very similar to IndexScan, but it specifies an diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 3a1255a..82cf1d6 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -581,6 +581,7 @@ typedef struct IndexOptInfo Oid indexoid; /* OID of the index relation */ Oid reltablespace; /* tablespace of index (not table) */ RelOptInfo *rel; /* back-link to index's table */ + struct IndexOptInfo *pkindex; /* link to referenced index, if indirect */ /* index-size statistics (from pg_class and elsewhere) */ BlockNumber pages; /* number of disk pages in index */ @@ -613,6 +614,7 @@ typedef struct IndexOptInfo bool predOK; /* true if index predicate matches query */ bool unique; /* true if a unique index */ bool immediate; /* is uniqueness enforced immediately? */ + bool indirect; /* true if index is indirect */ bool hypothetical; /* true if index doesn't really exist */ /* Remaining fields are copied from the index AM's API struct: */ diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 581ff6e..dbcc892 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -196,6 +196,7 @@ PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD) PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD) PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD) PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD) +PG_KEYWORD("indirect", INDIRECT, UNRESERVED_KEYWORD) PG_KEYWORD("inherit", INHERIT, UNRESERVED_KEYWORD) PG_KEYWORD("inherits", INHERITS, UNRESERVED_KEYWORD) PG_KEYWORD("initially", INITIALLY, RESERVED_KEYWORD) diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index cd7ea1d..b046f12 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -135,9 +135,11 @@ typedef struct RelationData Oid rd_replidindex; /* OID of replica identity index, if any */ /* data managed by RelationGetIndexAttrBitmap: */ - Bitmapset *rd_indexattr; /* identifies columns used in indexes */ + Bitmapset *rd_indexattr; /* identifies columns used in any index */ + Bitmapset *rd_hotattr; /* columns used in HOT-relevant indexes */ Bitmapset *rd_keyattr; /* cols that can be ref'd by foreign keys */ Bitmapset *rd_idattr; /* included in replica identity index */ + Bitmapset *rd_indirectattr; /* cols part of any indirect index */ /* * rd_options is set whenever rd_rel is loaded into the relcache entry. diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h index 6ea7dd2..0e5df4c 100644 --- a/src/include/utils/relcache.h +++ b/src/include/utils/relcache.h @@ -39,6 +39,7 @@ extern void RelationClose(Relation relation); */ extern List *RelationGetFKeyList(Relation relation); extern List *RelationGetIndexList(Relation relation); +extern Oid RelationGetPrimaryKey(Relation relation); extern Oid RelationGetOidIndex(Relation relation); extern Oid RelationGetReplicaIndex(Relation relation); extern List *RelationGetIndexExpressions(Relation relation); @@ -48,7 +49,9 @@ typedef enum IndexAttrBitmapKind { INDEX_ATTR_BITMAP_ALL, INDEX_ATTR_BITMAP_KEY, - INDEX_ATTR_BITMAP_IDENTITY_KEY + INDEX_ATTR_BITMAP_HOT, + INDEX_ATTR_BITMAP_IDENTITY_KEY, + INDEX_ATTR_BITMAP_INDIRECT_INDEXES } IndexAttrBitmapKind; extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation,