And here is a version for 9.1. This omits the code changes directly relevant to DROP INDEX CONCURRENTLY, but includes the changes to avoid transactional updates of the pg_index row during CREATE CONCURRENTLY, as well as the changes to prevent use of not-valid or not-ready indexes in places where it matters. I also chose to keep on using the IndexIsValid and IndexIsReady macros, so as to avoid unnecessary divergences of the branches.
I think this much of the patch needs to go into all supported branches. regards, tom lane
diff --git a/src/backend/access/heap/README.HOT b/src/backend/access/heap/README.HOT index f12cad44e56c363e62fa617497bbedfe1ba8c1fe..7cbc6a1d7e7328364938ef5d69bebe865524d7f8 100644 *** a/src/backend/access/heap/README.HOT --- b/src/backend/access/heap/README.HOT *************** from the index, as well as ensuring that *** 386,391 **** --- 386,397 ---- rows in a broken HOT chain (the first condition is stronger than the second). Finally, we can mark the index valid for searches. + Note that we do not need to set pg_index.indcheckxmin in this code path, + because we have outwaited any transactions that would need to avoid using + the index. (indcheckxmin is only needed because non-concurrent CREATE + INDEX doesn't want to wait; its stronger lock would create too much risk of + deadlock if it did.) + Limitations and Restrictions ---------------------------- diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 2e023d5ab56ac4efe1ab243739b48149e67a7408..129a1ac11f0e79408961abd73f0a33395569c5d5 100644 *** a/src/backend/catalog/index.c --- b/src/backend/catalog/index.c *************** static void ResetReindexPending(void); *** 129,134 **** --- 129,138 ---- * See whether an existing relation has a primary key. * * Caller must have suitable lock on the relation. + * + * Note: we intentionally do not check IndexIsValid here; that's because this + * is used to enforce the rule that there can be only one indisprimary index, + * and we want that to be true even if said index is invalid. */ static bool relationHasPrimaryKey(Relation rel) *************** index_constraint_create(Relation heapRel *** 1247,1254 **** * Note: since this is a transactional update, it's unsafe against * concurrent SnapshotNow scans of pg_index. When making an existing * index into a constraint, caller must have a table lock that prevents ! * concurrent table updates, and there is a risk that concurrent readers ! * of the table will miss seeing this index at all. */ if (update_pgindex && (mark_as_primary || deferrable)) { --- 1251,1259 ---- * Note: since this is a transactional update, it's unsafe against * concurrent SnapshotNow scans of pg_index. When making an existing * index into a constraint, caller must have a table lock that prevents ! * concurrent table updates; if it's less than a full exclusive lock, ! * there is a risk that concurrent readers of the table will miss seeing ! * this index at all. */ if (update_pgindex && (mark_as_primary || deferrable)) { *************** BuildIndexInfo(Relation index) *** 1450,1456 **** /* other info */ ii->ii_Unique = indexStruct->indisunique; ! ii->ii_ReadyForInserts = indexStruct->indisready; /* initialize index-build state to default */ ii->ii_Concurrent = false; --- 1455,1461 ---- /* other info */ ii->ii_Unique = indexStruct->indisunique; ! ii->ii_ReadyForInserts = IndexIsReady(indexStruct); /* initialize index-build state to default */ ii->ii_Concurrent = false; *************** index_build(Relation heapRelation, *** 1789,1796 **** * index's usability horizon. Moreover, we *must not* try to change the * index's pg_index entry while reindexing pg_index itself, and this * optimization nicely prevents that. */ ! if (indexInfo->ii_BrokenHotChain && !isreindex) { Oid indexId = RelationGetRelid(indexRelation); Relation pg_index; --- 1794,1813 ---- * index's usability horizon. Moreover, we *must not* try to change the * index's pg_index entry while reindexing pg_index itself, and this * optimization nicely prevents that. + * + * We also need not set indcheckxmin during a concurrent index build, + * because we won't set indisvalid true until all transactions that care + * about the broken HOT chains are gone. + * + * Therefore, this code path can only be taken during non-concurrent + * CREATE INDEX. Thus the fact that heap_update will set the pg_index + * tuple's xmin doesn't matter, because that tuple was created in the + * current transaction anyway. That also means we don't need to worry + * about any concurrent readers of the tuple; no other transaction can see + * it yet. */ ! if (indexInfo->ii_BrokenHotChain && !isreindex && ! !indexInfo->ii_Concurrent) { Oid indexId = RelationGetRelid(indexRelation); Relation pg_index; *************** validate_index_heapscan(Relation heapRel *** 2754,2759 **** --- 2771,2835 ---- /* + * index_set_state_flags - adjust pg_index state flags + * + * This is used during CREATE INDEX CONCURRENTLY to adjust the pg_index + * flags that denote the index's state. We must use an in-place update of + * the pg_index tuple, because we do not have exclusive lock on the parent + * table and so other sessions might concurrently be doing SnapshotNow scans + * of pg_index to identify the table's indexes. A transactional update would + * risk somebody not seeing the index at all. Because the update is not + * transactional and will not roll back on error, this must only be used as + * the last step in a transaction that has not made any transactional catalog + * updates! + * + * Note that heap_inplace_update does send a cache inval message for the + * tuple, so other sessions will hear about the update as soon as we commit. + */ + void + index_set_state_flags(Oid indexId, IndexStateFlagsAction action) + { + Relation pg_index; + HeapTuple indexTuple; + Form_pg_index indexForm; + + /* Assert that current xact hasn't done any transactional updates */ + Assert(GetTopTransactionIdIfAny() == InvalidTransactionId); + + /* Open pg_index and fetch a writable copy of the index's tuple */ + pg_index = heap_open(IndexRelationId, RowExclusiveLock); + + indexTuple = SearchSysCacheCopy1(INDEXRELID, + ObjectIdGetDatum(indexId)); + if (!HeapTupleIsValid(indexTuple)) + elog(ERROR, "cache lookup failed for index %u", indexId); + indexForm = (Form_pg_index) GETSTRUCT(indexTuple); + + /* Perform the requested state change on the copy */ + switch (action) + { + case INDEX_CREATE_SET_READY: + /* Set indisready during a CREATE INDEX CONCURRENTLY sequence */ + Assert(!indexForm->indisready); + Assert(!indexForm->indisvalid); + indexForm->indisready = true; + break; + case INDEX_CREATE_SET_VALID: + /* Set indisvalid during a CREATE INDEX CONCURRENTLY sequence */ + Assert(indexForm->indisready); + Assert(!indexForm->indisvalid); + indexForm->indisvalid = true; + break; + } + + /* ... and write it back in-place */ + heap_inplace_update(pg_index, indexTuple); + + heap_close(pg_index, RowExclusiveLock); + } + + + /* * IndexGetRelation: given an index's relation OID, get the OID of the * relation it is an index on. Uses the system cache. */ *************** void *** 2782,2793 **** reindex_index(Oid indexId, bool skip_constraint_checks) { Relation iRel, ! heapRelation, ! pg_index; Oid heapId; IndexInfo *indexInfo; - HeapTuple indexTuple; - Form_pg_index indexForm; volatile bool skipped_constraint = false; /* --- 2858,2866 ---- reindex_index(Oid indexId, bool skip_constraint_checks) { Relation iRel, ! heapRelation; Oid heapId; IndexInfo *indexInfo; volatile bool skipped_constraint = false; /* *************** reindex_index(Oid indexId, bool skip_con *** 2867,2891 **** * * We can also reset indcheckxmin, because we have now done a * non-concurrent index build, *except* in the case where index_build ! * found some still-broken HOT chains. If it did, we normally leave ! * indcheckxmin alone (note that index_build won't have changed it, ! * because this is a reindex). But if the index was invalid or not ready ! * and there were broken HOT chains, it seems best to force indcheckxmin ! * true, because the normal argument that the HOT chains couldn't conflict ! * with the index is suspect for an invalid index. * ! * Note that it is important to not update the pg_index entry if we don't ! * have to, because updating it will move the index's usability horizon ! * (recorded as the tuple's xmin value) if indcheckxmin is true. We don't ! * really want REINDEX to move the usability horizon forward ever, but we ! * have no choice if we are to fix indisvalid or indisready. Of course, ! * clearing indcheckxmin eliminates the issue, so we're happy to do that ! * if we can. Another reason for caution here is that while reindexing ! * pg_index itself, we must not try to update it. We assume that ! * pg_index's indexes will always have these flags in their clean state. */ if (!skipped_constraint) { pg_index = heap_open(IndexRelationId, RowExclusiveLock); indexTuple = SearchSysCacheCopy1(INDEXRELID, --- 2940,2978 ---- * * We can also reset indcheckxmin, because we have now done a * non-concurrent index build, *except* in the case where index_build ! * found some still-broken HOT chains. If it did, and we don't have to ! * change any of the other flags, we just leave indcheckxmin alone (note ! * that index_build won't have changed it, because this is a reindex). ! * This is okay and desirable because not updating the tuple leaves the ! * index's usability horizon (recorded as the tuple's xmin value) the same ! * as it was. * ! * But, if the index was invalid/not-ready and there were broken HOT ! * chains, we had better force indcheckxmin true, because the normal ! * argument that the HOT chains couldn't conflict with the index is ! * suspect for an invalid index. In this case advancing the usability ! * horizon is appropriate. ! * ! * Note that if we have to update the tuple, there is a risk of concurrent ! * transactions not seeing it during their SnapshotNow scans of pg_index. ! * While not especially desirable, this is safe because no such ! * transaction could be trying to update the table (since we have ! * ShareLock on it). The worst case is that someone might transiently ! * fail to use the index for a query --- but it was probably unusable ! * before anyway, if we are updating the tuple. ! * ! * Another reason for avoiding unnecessary updates here is that while ! * reindexing pg_index itself, we must not try to update tuples in it. ! * pg_index's indexes should always have these flags in their clean state, ! * so that won't happen. */ if (!skipped_constraint) { + Relation pg_index; + HeapTuple indexTuple; + Form_pg_index indexForm; + bool index_bad; + pg_index = heap_open(IndexRelationId, RowExclusiveLock); indexTuple = SearchSysCacheCopy1(INDEXRELID, *************** reindex_index(Oid indexId, bool skip_con *** 2894,2910 **** elog(ERROR, "cache lookup failed for index %u", indexId); indexForm = (Form_pg_index) GETSTRUCT(indexTuple); ! if (!indexForm->indisvalid || !indexForm->indisready || (indexForm->indcheckxmin && !indexInfo->ii_BrokenHotChain)) { if (!indexInfo->ii_BrokenHotChain) indexForm->indcheckxmin = false; ! else if (!indexForm->indisvalid || !indexForm->indisready) indexForm->indcheckxmin = true; indexForm->indisvalid = true; indexForm->indisready = true; simple_heap_update(pg_index, &indexTuple->t_self, indexTuple); CatalogUpdateIndexes(pg_index, indexTuple); } heap_close(pg_index, RowExclusiveLock); --- 2981,3008 ---- elog(ERROR, "cache lookup failed for index %u", indexId); indexForm = (Form_pg_index) GETSTRUCT(indexTuple); ! index_bad = (!indexForm->indisvalid || ! !indexForm->indisready); ! if (index_bad || (indexForm->indcheckxmin && !indexInfo->ii_BrokenHotChain)) { if (!indexInfo->ii_BrokenHotChain) indexForm->indcheckxmin = false; ! else if (index_bad) indexForm->indcheckxmin = true; indexForm->indisvalid = true; indexForm->indisready = true; simple_heap_update(pg_index, &indexTuple->t_self, indexTuple); CatalogUpdateIndexes(pg_index, indexTuple); + + /* + * Invalidate the relcache for the table, so that after we commit + * all sessions will refresh the table's index list. This ensures + * that if anyone misses seeing the pg_index row during this + * update, they'll refresh their list before attempting any update + * on the table. + */ + CacheInvalidateRelcache(heapRelation); } heap_close(pg_index, RowExclusiveLock); diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index 5dec4298e2c735438861739ed0b3adc87e1eea6d..81e72825269220e66488c93fef7b26411fe58f9b 100644 *** a/src/backend/commands/cluster.c --- b/src/backend/commands/cluster.c *************** check_index_is_clusterable(Relation OldH *** 454,460 **** * might put recently-dead tuples out-of-order in the new table, and there * is little harm in that.) */ ! if (!OldIndex->rd_index->indisvalid) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot cluster on invalid index \"%s\"", --- 454,460 ---- * might put recently-dead tuples out-of-order in the new table, and there * is little harm in that.) */ ! if (!IndexIsValid(OldIndex->rd_index)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot cluster on invalid index \"%s\"", *************** check_index_is_clusterable(Relation OldH *** 468,473 **** --- 468,478 ---- * mark_index_clustered: mark the specified index as the one clustered on * * With indexOid == InvalidOid, will mark all indexes of rel not-clustered. + * + * Note: we do transactional updates of the pg_index rows, which are unsafe + * against concurrent SnapshotNow scans of pg_index. Therefore this is unsafe + * to execute with less than full exclusive lock on the parent table; + * otherwise concurrent executions of RelationGetIndexList could miss indexes. */ void mark_index_clustered(Relation rel, Oid indexOid) *************** mark_index_clustered(Relation rel, Oid i *** 523,528 **** --- 528,536 ---- } else if (thisIndexOid == indexOid) { + /* this was checked earlier, but let's be real sure */ + if (!IndexIsValid(indexForm)) + elog(ERROR, "cannot cluster on invalid index %u", indexOid); indexForm->indisclustered = true; simple_heap_update(pg_index, &indexTuple->t_self, indexTuple); CatalogUpdateIndexes(pg_index, indexTuple); diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index b7c021d943a99e96ff5aab93caff7aecc83df66f..a08ad1324ba34e9c06d91cad50c4beb3d345892e 100644 *** a/src/backend/commands/indexcmds.c --- b/src/backend/commands/indexcmds.c *************** DefineIndex(RangeVar *heapRelation, *** 148,156 **** LockRelId heaprelid; LOCKTAG heaplocktag; Snapshot snapshot; - Relation pg_index; - HeapTuple indexTuple; - Form_pg_index indexForm; int i; /* --- 148,153 ---- *************** DefineIndex(RangeVar *heapRelation, *** 531,553 **** * commit this transaction, any new transactions that open the table must * insert new entries into the index for insertions and non-HOT updates. */ ! pg_index = heap_open(IndexRelationId, RowExclusiveLock); ! ! indexTuple = SearchSysCacheCopy1(INDEXRELID, ! ObjectIdGetDatum(indexRelationId)); ! if (!HeapTupleIsValid(indexTuple)) ! elog(ERROR, "cache lookup failed for index %u", indexRelationId); ! indexForm = (Form_pg_index) GETSTRUCT(indexTuple); ! ! Assert(!indexForm->indisready); ! Assert(!indexForm->indisvalid); ! ! indexForm->indisready = true; ! ! simple_heap_update(pg_index, &indexTuple->t_self, indexTuple); ! CatalogUpdateIndexes(pg_index, indexTuple); ! ! heap_close(pg_index, RowExclusiveLock); /* we can do away with our snapshot */ PopActiveSnapshot(); --- 528,534 ---- * commit this transaction, any new transactions that open the table must * insert new entries into the index for insertions and non-HOT updates. */ ! index_set_state_flags(indexRelationId, INDEX_CREATE_SET_READY); /* we can do away with our snapshot */ PopActiveSnapshot(); *************** DefineIndex(RangeVar *heapRelation, *** 671,693 **** /* * Index can now be marked valid -- update its pg_index entry */ ! pg_index = heap_open(IndexRelationId, RowExclusiveLock); ! ! indexTuple = SearchSysCacheCopy1(INDEXRELID, ! ObjectIdGetDatum(indexRelationId)); ! if (!HeapTupleIsValid(indexTuple)) ! elog(ERROR, "cache lookup failed for index %u", indexRelationId); ! indexForm = (Form_pg_index) GETSTRUCT(indexTuple); ! ! Assert(indexForm->indisready); ! Assert(!indexForm->indisvalid); ! ! indexForm->indisvalid = true; ! ! simple_heap_update(pg_index, &indexTuple->t_self, indexTuple); ! CatalogUpdateIndexes(pg_index, indexTuple); ! ! heap_close(pg_index, RowExclusiveLock); /* * The pg_index update will cause backends (including this one) to update --- 652,658 ---- /* * Index can now be marked valid -- update its pg_index entry */ ! index_set_state_flags(indexRelationId, INDEX_CREATE_SET_VALID); /* * The pg_index update will cause backends (including this one) to update *************** DefineIndex(RangeVar *heapRelation, *** 695,701 **** * relcache inval on the parent table to force replanning of cached plans. * Otherwise existing sessions might fail to use the new index where it * would be useful. (Note that our earlier commits did not create reasons ! * to replan; relcache flush on the index itself was sufficient.) */ CacheInvalidateRelcacheByRelid(heaprelid.relId); --- 660,666 ---- * relcache inval on the parent table to force replanning of cached plans. * Otherwise existing sessions might fail to use the new index where it * would be useful. (Note that our earlier commits did not create reasons ! * to replan; so relcache flush on the index itself was sufficient.) */ CacheInvalidateRelcacheByRelid(heaprelid.relId); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 99fb892c1941955d1ae6acc15d68a5a5a50ad641..d7bb20fa2ec17aba06b61b24d31185fd06eecc0f 100644 *** a/src/backend/commands/tablecmds.c --- b/src/backend/commands/tablecmds.c *************** ATExecDropNotNull(Relation rel, const ch *** 4528,4533 **** --- 4528,4535 ---- /* * Check that the attribute is not in a primary key + * + * Note: we'll throw error even if the pkey index is not valid. */ /* Loop over all indexes on the relation */ *************** transformFkeyGetPrimaryKey(Relation pkre *** 5876,5882 **** /* * Get the list of index OIDs for the table from the relcache, and look up * each one in the pg_index syscache until we find one marked primary key ! * (hopefully there isn't more than one such). */ *indexOid = InvalidOid; --- 5878,5884 ---- /* * Get the list of index OIDs for the table from the relcache, and look up * each one in the pg_index syscache until we find one marked primary key ! * (hopefully there isn't more than one such). Insist it's valid, too. */ *indexOid = InvalidOid; *************** transformFkeyGetPrimaryKey(Relation pkre *** 5890,5896 **** if (!HeapTupleIsValid(indexTuple)) elog(ERROR, "cache lookup failed for index %u", indexoid); indexStruct = (Form_pg_index) GETSTRUCT(indexTuple); ! if (indexStruct->indisprimary) { /* * Refuse to use a deferrable primary key. This is per SQL spec, --- 5892,5898 ---- if (!HeapTupleIsValid(indexTuple)) elog(ERROR, "cache lookup failed for index %u", indexoid); indexStruct = (Form_pg_index) GETSTRUCT(indexTuple); ! if (indexStruct->indisprimary && IndexIsValid(indexStruct)) { /* * Refuse to use a deferrable primary key. This is per SQL spec, *************** transformFkeyCheckAttrs(Relation pkrel, *** 5988,5997 **** /* * Must have the right number of columns; must be unique and not a ! * partial index; forget it if there are any expressions, too */ if (indexStruct->indnatts == numattrs && indexStruct->indisunique && heap_attisnull(indexTuple, Anum_pg_index_indpred) && heap_attisnull(indexTuple, Anum_pg_index_indexprs)) { --- 5990,6001 ---- /* * Must have the right number of columns; must be unique and not a ! * partial index; forget it if there are any expressions, too. Invalid ! * indexes are out as well. */ if (indexStruct->indnatts == numattrs && indexStruct->indisunique && + IndexIsValid(indexStruct) && heap_attisnull(indexTuple, Anum_pg_index_indpred) && heap_attisnull(indexTuple, Anum_pg_index_indexprs)) { diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index fdd9fb4336ec52828adb5e92c17a5fa10aaa2652..bfdf8e38b1af6edb03e64c4dc50fb7e1638e8bf0 100644 *** a/src/backend/commands/vacuum.c --- b/src/backend/commands/vacuum.c *************** vacuum_rel(Oid relid, VacuumStmt *vacstm *** 1078,1086 **** /* ! * Open all the indexes of the given relation, obtaining the specified kind ! * of lock on each. Return an array of Relation pointers for the indexes ! * into *Irel, and the number of indexes into *nindexes. */ void vac_open_indexes(Relation relation, LOCKMODE lockmode, --- 1078,1093 ---- /* ! * Open all the vacuumable indexes of the given relation, obtaining the ! * specified kind of lock on each. Return an array of Relation pointers for ! * the indexes into *Irel, and the number of indexes into *nindexes. ! * ! * We consider an index vacuumable if it is marked insertable (IndexIsReady). ! * If it isn't, probably a CREATE INDEX CONCURRENTLY command failed early in ! * execution, and what we have is too corrupt to be processable. We will ! * vacuum even if the index isn't indisvalid; this is important because in a ! * unique index, uniqueness checks will be performed anyway and had better not ! * hit dangling index pointers. */ void vac_open_indexes(Relation relation, LOCKMODE lockmode, *************** vac_open_indexes(Relation relation, LOCK *** 1094,1114 **** indexoidlist = RelationGetIndexList(relation); ! *nindexes = list_length(indexoidlist); ! if (*nindexes > 0) ! *Irel = (Relation *) palloc(*nindexes * sizeof(Relation)); else *Irel = NULL; i = 0; foreach(indexoidscan, indexoidlist) { Oid indexoid = lfirst_oid(indexoidscan); ! (*Irel)[i++] = index_open(indexoid, lockmode); } list_free(indexoidlist); } --- 1101,1130 ---- indexoidlist = RelationGetIndexList(relation); ! /* allocate enough memory for all indexes */ ! i = list_length(indexoidlist); ! if (i > 0) ! *Irel = (Relation *) palloc(i * sizeof(Relation)); else *Irel = NULL; + /* collect just the ready indexes */ i = 0; foreach(indexoidscan, indexoidlist) { Oid indexoid = lfirst_oid(indexoidscan); + Relation indrel; ! indrel = index_open(indexoid, lockmode); ! if (IndexIsReady(indrel->rd_index)) ! (*Irel)[i++] = indrel; ! else ! index_close(indrel, lockmode); } + *nindexes = i; + list_free(indexoidlist); } diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index 9f959c539e880ad90704b74205d2f1d91cd6d2f4..bfacc6c30acf6c16565a6b711590ec22969ecfb0 100644 *** a/src/backend/executor/execUtils.c --- b/src/backend/executor/execUtils.c *************** ExecOpenIndices(ResultRelInfo *resultRel *** 907,912 **** --- 907,915 ---- /* * For each index, open the index relation and save pg_index info. We * acquire RowExclusiveLock, signifying we will update the index. + * + * Note: we do this even if the index is not IndexIsReady; it's not worth + * the trouble to optimize for the case where it isn't. */ i = 0; foreach(l, indexoidlist) diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 77e701d2fc932d5bbfabccab3b552d038828fbac..d0a3f72351d236ba2b76b7e7c9cd47abd6a56ee5 100644 *** a/src/backend/optimizer/util/plancat.c --- b/src/backend/optimizer/util/plancat.c *************** get_relation_info(PlannerInfo *root, Oid *** 166,174 **** * Ignore invalid indexes, since they can't safely be used for * queries. Note that this is OK because the data structure we * are constructing is only used by the planner --- the executor ! * still needs to insert into "invalid" indexes! */ ! if (!index->indisvalid) { index_close(indexRelation, NoLock); continue; --- 166,175 ---- * Ignore invalid indexes, since they can't safely be used for * queries. Note that this is OK because the data structure we * are constructing is only used by the planner --- the executor ! * still needs to insert into "invalid" indexes, if they're marked ! * IndexIsReady. */ ! if (!IndexIsValid(index)) { index_close(indexRelation, NoLock); continue; diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index ae0ef91eb7fdb078f1187886fbb0523ae994e51c..9c39bacc64fb45979b678115942d456b8d2a6878 100644 *** a/src/backend/parser/parse_utilcmd.c --- b/src/backend/parser/parse_utilcmd.c *************** transformIndexConstraint(Constraint *con *** 1525,1542 **** index_name, RelationGetRelationName(heap_rel)), parser_errposition(cxt->pstate, constraint->location))); ! if (!index_form->indisvalid) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("index \"%s\" is not valid", index_name), parser_errposition(cxt->pstate, constraint->location))); - if (!index_form->indisready) - ereport(ERROR, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("index \"%s\" is not ready", index_name), - parser_errposition(cxt->pstate, constraint->location))); - if (!index_form->indisunique) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), --- 1525,1536 ---- index_name, RelationGetRelationName(heap_rel)), parser_errposition(cxt->pstate, constraint->location))); ! if (!IndexIsValid(index_form)) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("index \"%s\" is not valid", index_name), parser_errposition(cxt->pstate, constraint->location))); if (!index_form->indisunique) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 6026599a9bc5e089d4c89db3458a8994dc70d949..12a02b8c09b22bb21af897609129fdbb5d6e0433 100644 *** a/src/backend/utils/cache/relcache.c --- b/src/backend/utils/cache/relcache.c *************** RelationReloadIndexInfo(Relation relatio *** 1731,1739 **** --- 1731,1752 ---- RelationGetRelid(relation)); index = (Form_pg_index) GETSTRUCT(tuple); + /* + * Basically, let's just copy all the bool fields. There are one or + * two of these that can't actually change in the current code, but + * it's not worth it to track exactly which ones they are. None of + * the array fields are allowed to change, though. + */ + relation->rd_index->indisunique = index->indisunique; + relation->rd_index->indisprimary = index->indisprimary; + relation->rd_index->indisexclusion = index->indisexclusion; + relation->rd_index->indimmediate = index->indimmediate; + relation->rd_index->indisclustered = index->indisclustered; relation->rd_index->indisvalid = index->indisvalid; relation->rd_index->indcheckxmin = index->indcheckxmin; relation->rd_index->indisready = index->indisready; + + /* Copy xmin too, as that is needed to make sense of indcheckxmin */ HeapTupleHeaderSetXmin(relation->rd_indextuple->t_data, HeapTupleHeaderGetXmin(tuple->t_data)); *************** RelationGetIndexList(Relation relation) *** 3355,3361 **** result = insert_ordered_oid(result, index->indexrelid); /* Check to see if it is a unique, non-partial btree index on OID */ ! if (index->indnatts == 1 && index->indisunique && index->indimmediate && index->indkey.values[0] == ObjectIdAttributeNumber && index->indclass.values[0] == OID_BTREE_OPS_OID && --- 3368,3375 ---- result = insert_ordered_oid(result, index->indexrelid); /* Check to see if it is a unique, non-partial btree index on OID */ ! if (IndexIsValid(index) && ! index->indnatts == 1 && index->indisunique && index->indimmediate && index->indkey.values[0] == ObjectIdAttributeNumber && index->indclass.values[0] == OID_BTREE_OPS_OID && *************** RelationGetIndexAttrBitmap(Relation rela *** 3662,3667 **** --- 3676,3686 ---- /* * For each index, add referenced attributes to indexattrs. + * + * Note: we consider all indexes returned by RelationGetIndexList, even if + * they are not indisready or indisvalid. This is important because an + * index for which CREATE INDEX CONCURRENTLY has just started must be + * included in HOT-safety decisions (see README.HOT). */ indexattrs = NULL; foreach(l, indexoidlist) diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h index 4172f0e4006ddd989d13c993edef3be72b48dbc4..9c9987e8765408d3365642686ed4291ed61913be 100644 *** a/src/include/catalog/index.h --- b/src/include/catalog/index.h *************** typedef void (*IndexBuildCallback) (Rela *** 27,32 **** --- 27,39 ---- bool tupleIsAlive, void *state); + /* Action code for index_set_state_flags */ + typedef enum + { + INDEX_CREATE_SET_READY, + INDEX_CREATE_SET_VALID + } IndexStateFlagsAction; + extern void index_check_primary_key(Relation heapRel, IndexInfo *indexInfo, *************** extern double IndexBuildHeapScan(Relatio *** 88,93 **** --- 95,102 ---- extern void validate_index(Oid heapId, Oid indexId, Snapshot snapshot); + extern void index_set_state_flags(Oid indexId, IndexStateFlagsAction action); + extern void reindex_index(Oid indexId, bool skip_constraint_checks); /* Flag bits for reindex_relation(): */ diff --git a/src/include/catalog/pg_index.h b/src/include/catalog/pg_index.h index 9f446a5547ec7633fcea73c91c32acfcfe2552f0..dc372b82e26117bedf18dde58bfc95c9e3c9eeb9 100644 *** a/src/include/catalog/pg_index.h --- b/src/include/catalog/pg_index.h *************** typedef FormData_pg_index *Form_pg_index *** 92,95 **** --- 92,103 ---- #define INDOPTION_DESC 0x0001 /* values are in reverse order */ #define INDOPTION_NULLS_FIRST 0x0002 /* NULLs are first instead of last */ + /* + * Use of these macros is recommended over direct examination of the state + * flag columns where possible; this allows source code compatibility with + * 9.2 and up. + */ + #define IndexIsValid(indexForm) ((indexForm)->indisvalid) + #define IndexIsReady(indexForm) ((indexForm)->indisready) + #endif /* PG_INDEX_H */
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers