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 e3b1eef..3fa3d71 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..9e91b41 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; @@ -3504,6 +3508,8 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, 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..8b114ab 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); } 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 128744c..bbf06d2 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 08b646d..1f57eb5 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -104,6 +104,7 @@ static void UpdateIndexRelation(Oid indexoid, Oid heapoid, int16 *coloptions, bool primary, bool isexclusion, + bool isindirect, bool immediate, bool isvalid); static void index_update_stats(Relation rel, @@ -554,6 +555,7 @@ UpdateIndexRelation(Oid indexoid, int16 *coloptions, bool primary, bool isexclusion, + bool isindirect, bool immediate, bool isvalid) { @@ -623,6 +625,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(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); @@ -681,6 +684,7 @@ UpdateIndexRelation(Oid indexoid, * coloptions: array of per-index-column indoption settings * reloptions: AM-specific options * isprimary: index is a PRIMARY KEY + * isindirect: index is an indirect one * isconstraint: index is owned by PRIMARY KEY, UNIQUE, or EXCLUSION constraint * deferrable: constraint is DEFERRABLE * initdeferred: constraint is INITIALLY DEFERRED @@ -710,6 +714,7 @@ index_create(Relation heapRelation, int16 *coloptions, Datum reloptions, bool isprimary, + bool isindirect, bool isconstraint, bool deferrable, bool initdeferred, @@ -769,6 +774,21 @@ 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 (isindirect && + IsSystemRelation(heapRelation)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("indirect index creation on system catalog tables is not supported"))); + if (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. */ @@ -916,7 +936,7 @@ index_create(Relation heapRelation, */ UpdateIndexRelation(indexRelationId, heapRelationId, indexInfo, collationObjectId, classObjectId, coloptions, - isprimary, is_exclusion, + isprimary, is_exclusion, isindirect, !deferrable, !concurrent); @@ -1011,6 +1031,14 @@ index_create(Relation heapRelation, Assert(!initdeferred); } + /* Store dependency on primary key index, if needed */ + if (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 +1709,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 +3190,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 +3594,10 @@ reindex_relation(Oid relid, int flags, int options) /* Ensure rd_indexattr is valid; see comments for RelationSetIndexList */ if (is_pg_class) + { + elog(ERROR, "FIXME must fix this case"); (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/catalog/toasting.c b/src/backend/catalog/toasting.c index 564e10e..caa25ba 100644 --- a/src/backend/catalog/toasting.c +++ b/src/backend/catalog/toasting.c @@ -331,7 +331,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, BTREE_AM_OID, rel->rd_rel->reltablespace, collationObjectId, classObjectId, coloptions, (Datum) 0, - true, false, false, false, + true, false, false, false, false, true, false, false, true, false); heap_close(toast_rel, NoLock); diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index dc1f79f..13f28d3 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -1535,14 +1535,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 b4140eb..b9e9e20 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -2535,7 +2535,7 @@ CopyFrom(CopyState cstate) if (resultRelInfo->ri_NumIndices > 0) recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false, NULL, - NIL); + NIL, NULL); /* AFTER ROW INSERT Triggers */ ExecARInsertTriggers(estate, resultRelInfo, tuple, @@ -2649,7 +2649,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/indexcmds.c b/src/backend/commands/indexcmds.c index 85817c6..17fbf2d 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -519,6 +519,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; @@ -653,7 +658,7 @@ DefineIndex(Oid relationId, indexInfo, indexColNames, accessMethodId, tablespaceId, collationObjectId, classObjectId, - coloptions, reloptions, stmt->primary, + coloptions, reloptions, stmt->primary, stmt->isindirect, stmt->isconstraint, stmt->deferrable, stmt->initdeferred, allowSystemTableMods, skip_build || stmt->concurrent, 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/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index efb0c5e..2503711 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -449,7 +449,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) @@ -495,7 +496,8 @@ ExecInsert(ModifyTableState *mtstate, if (resultRelInfo->ri_NumIndices > 0) recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false, NULL, - arbiterIndexes); + arbiterIndexes, + NULL); } } @@ -895,6 +897,7 @@ ExecUpdate(ItemPointer tupleid, else { LockTupleMode lockmode; + Bitmapset *unchangedAttrs = NULL; /* * Constraints might reference the tableoid column, so initialize @@ -938,7 +941,9 @@ lreplace:; estate->es_output_cid, estate->es_crosscheck_snapshot, true /* wait for commit */ , - &hufd, &lockmode); + &hufd, + &unchangedAttrs, + &lockmode); switch (result) { case HeapTupleSelfUpdated: @@ -1028,7 +1033,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 71714bc..9880321 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -3145,6 +3145,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); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 29a090f..b91d924 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1271,6 +1271,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 ae86954..4f3b864 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2449,6 +2449,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); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 5547fc8..cecd6d4 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -410,7 +410,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 @@ -596,7 +596,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 @@ -6651,25 +6652,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; @@ -6677,25 +6679,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; @@ -6710,6 +6713,11 @@ opt_unique: | /*EMPTY*/ { $$ = FALSE; } ; +opt_indirect: + INDIRECT { $$ = TRUE; } + | /*EMPTY*/ { $$ = FALSE; } + ; + opt_concurrently: CONCURRENTLY { $$ = TRUE; } | /*EMPTY*/ { $$ = FALSE; } @@ -13775,6 +13783,7 @@ unreserved_keyword: | INCREMENT | INDEX | INDEXES + | INDIRECT | INHERIT | INHERITS | INLINE_P diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 79e0b1f..939ea34 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -266,7 +266,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, @@ -2032,6 +2032,7 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc) bms_free(relation->rd_indexattr); 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) @@ -3942,6 +3943,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 @@ -3955,7 +3997,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 @@ -4031,7 +4074,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, @@ -4104,12 +4148,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); @@ -4146,7 +4190,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_keyattr, rd_indirectattr or rd_idattr. */ void RelationSetIndexList(Relation relation, List *indexIds, Oid oidIndex) @@ -4375,6 +4419,7 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind) Bitmapset *indexattrs; /* indexed 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; @@ -4391,6 +4436,8 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind) return bms_copy(relation->rd_keyattr); 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); } @@ -4419,7 +4466,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 @@ -4431,6 +4478,7 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind) indexattrs = NULL; uindexattrs = NULL; idindexattrs = NULL; + indirectattrs = NULL; foreach(l, indexoidlist) { Oid indexOid = lfirst_oid(l); @@ -4439,6 +4487,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); @@ -4453,6 +4503,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++) { @@ -4460,8 +4513,10 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind) if (attrnum != 0) { - indexattrs = bms_add_member(indexattrs, - attrnum - FirstLowInvalidHeapAttributeNumber); + /* FIXME the name of this is now a lie. Must fix! */ + if (!isIndirect) + indexattrs = bms_add_member(indexattrs, + attrnum - FirstLowInvalidHeapAttributeNumber); if (isKey) uindexattrs = bms_add_member(uindexattrs, @@ -4470,11 +4525,18 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind) if (isIDKey) idindexattrs = bms_add_member(idindexattrs, attrnum - FirstLowInvalidHeapAttributeNumber); + + if (isIndirect) + indirectattrs = bms_add_member(indirectattrs, + 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); + 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); @@ -4491,6 +4553,8 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind) 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 @@ -4503,6 +4567,7 @@ 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_indirectattr = bms_copy(indirectattrs); MemoryContextSwitchTo(oldcxt); /* We return our original working copy for caller to play with */ @@ -4514,6 +4579,8 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind) 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; @@ -5059,6 +5126,7 @@ load_relcache_init_file(bool shared) rel->rd_indexattr = 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..c247b89 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); 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..2f21db2 100644 --- a/src/include/catalog/index.h +++ b/src/include/catalog/index.h @@ -37,6 +37,17 @@ typedef enum INDEX_DROP_SET_DEAD } IndexStateFlagsAction; +static inline void +FAKE_CTID_FROM_PKVALUES(ItemPointer iptr, int16 pkNumKeys, Datum *pkvalues) +{ + /* XXX should eventually support more than one column */ + Assert(pkNumKeys == 1); + + ItemPointerSet(iptr, + GET_4_BYTES(pkvalues[0]), + GET_2_BYTES(pkvalues[0] << 32)); + Assert(GET_2_BYTES(pkvalues[0] << 48) == 0); +} extern void index_check_primary_key(Relation heapRel, IndexInfo *indexInfo, @@ -55,6 +66,7 @@ extern Oid index_create(Relation heapRelation, int16 *coloptions, Datum reloptions, bool isprimary, + bool isindirect, bool isconstraint, bool deferrable, bool initdeferred, 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/executor.h b/src/include/executor/executor.h index 136276b..39230c1 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -367,7 +367,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/nodes/execnodes.h b/src/include/nodes/execnodes.h index f6f73f3..f6c4655 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -48,6 +48,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? @@ -72,6 +73,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; diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 6de2cab..e4ee0af 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2449,6 +2449,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/parser/kwlist.h b/src/include/parser/kwlist.h index 17ffef5..04b7221 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -194,6 +194,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 ed14442..71c4af7 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -103,6 +103,7 @@ typedef struct RelationData Bitmapset *rd_indexattr; /* identifies columns used in 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..a0dbb23 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,8 @@ typedef enum IndexAttrBitmapKind { INDEX_ATTR_BITMAP_ALL, INDEX_ATTR_BITMAP_KEY, - INDEX_ATTR_BITMAP_IDENTITY_KEY + INDEX_ATTR_BITMAP_IDENTITY_KEY, + INDEX_ATTR_BITMAP_INDIRECT_INDEXES } IndexAttrBitmapKind; extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation,