summaryrefslogtreecommitdiff
path: root/src/backend/catalog/index.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/catalog/index.c')
-rw-r--r--src/backend/catalog/index.c445
1 files changed, 206 insertions, 239 deletions
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 75302edfaf..d0f216475e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.265 2006/03/31 23:32:06 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.266 2006/05/10 23:18:39 tgl Exp $
*
*
* INTERFACE ROUTINES
@@ -61,8 +61,9 @@ static void UpdateIndexRelation(Oid indexoid, Oid heapoid,
IndexInfo *indexInfo,
Oid *classOids,
bool primary);
+static void index_update_stats(Relation rel, bool hasindex, bool isprimary,
+ Oid reltoastidxid, double reltuples);
static Oid IndexGetRelation(Oid indexId);
-static void UpdateStats(Oid relid, double reltuples);
/*
@@ -437,15 +438,26 @@ UpdateIndexRelation(Oid indexoid,
}
-/* ----------------------------------------------------------------
- * index_create
+/*
+ * index_create
*
- * indexRelationId is normally InvalidOid to let this routine
- * generate an OID for the index. During bootstrap it may be
+ * heapRelationId: OID of table to build index on
+ * indexRelationName: what it say
+ * indexRelationId: normally, pass InvalidOid to let this routine
+ * generate an OID for the index. During bootstrap this may be
* nonzero to specify a preselected OID.
+ * indexInfo: same info executor uses to insert into the index
+ * accessMethodObjectId: OID of index AM to use
+ * tableSpaceId: OID of tablespace to use
+ * classObjectId: array of index opclass OIDs, one per index column
+ * isprimary: index is a PRIMARY KEY
+ * istoast: index is a toast table's index
+ * isconstraint: index is owned by a PRIMARY KEY or UNIQUE constraint
+ * allow_system_table_mods: allow table to be a system catalog
+ * skip_build: true to skip the index_build() step for the moment; caller
+ * must do it later (typically via reindex_index())
*
* Returns OID of the created index.
- * ----------------------------------------------------------------
*/
Oid
index_create(Oid heapRelationId,
@@ -455,7 +467,8 @@ index_create(Oid heapRelationId,
Oid accessMethodObjectId,
Oid tableSpaceId,
Oid *classObjectId,
- bool primary,
+ bool isprimary,
+ bool istoast,
bool isconstraint,
bool allow_system_table_mods,
bool skip_build)
@@ -595,7 +608,7 @@ index_create(Oid heapRelationId,
* ----------------
*/
UpdateIndexRelation(indexRelationId, heapRelationId, indexInfo,
- classObjectId, primary);
+ classObjectId, isprimary);
/*
* Register constraint and dependencies for the index.
@@ -625,7 +638,7 @@ index_create(Oid heapRelationId,
char constraintType;
Oid conOid;
- if (primary)
+ if (isprimary)
constraintType = CONSTRAINT_PRIMARY;
else if (indexInfo->ii_Unique)
constraintType = CONSTRAINT_UNIQUE;
@@ -736,28 +749,40 @@ index_create(Oid heapRelationId,
* Similarly, if the caller specified skip_build then filling the index is
* delayed till later (ALTER TABLE can save work in some cases with this).
* Otherwise, we call the AM routine that constructs the index.
- *
- * In normal processing mode, the heap and index relations are closed, but
- * we continue to hold the ShareLock on the heap and the exclusive lock on
- * the index that we acquired above, until end of transaction.
*/
if (IsBootstrapProcessingMode())
{
index_register(heapRelationId, indexRelationId, indexInfo);
- /* XXX shouldn't we close the heap and index rels here? */
}
else if (skip_build)
{
- /* caller is responsible for filling the index later on */
- relation_close(indexRelation, NoLock);
- heap_close(heapRelation, NoLock);
+ /*
+ * Caller is responsible for filling the index later on. However,
+ * we'd better make sure that the heap relation is correctly marked
+ * as having an index.
+ */
+ index_update_stats(heapRelation,
+ true,
+ isprimary,
+ InvalidOid,
+ heapRelation->rd_rel->reltuples);
+ /* Make the above update visible */
+ CommandCounterIncrement();
}
else
{
- index_build(heapRelation, indexRelation, indexInfo);
- /* index_build closes the passed rels */
+ index_build(heapRelation, indexRelation, indexInfo,
+ isprimary, istoast);
}
+ /*
+ * Close the heap and index; but we keep the ShareLock on the heap and
+ * the exclusive lock on the index that we acquired above, until end of
+ * transaction.
+ */
+ index_close(indexRelation);
+ heap_close(heapRelation, NoLock);
+
return indexRelationId;
}
@@ -983,38 +1008,59 @@ FormIndexDatum(IndexInfo *indexInfo,
}
-/* ----------------
- * set relhasindex of relation's pg_class entry
+/*
+ * index_update_stats --- update pg_class entry after CREATE INDEX
*
- * If isprimary is TRUE, we are defining a primary index, so also set
- * relhaspkey to TRUE. Otherwise, leave relhaspkey alone.
+ * This routine updates the pg_class row of either an index or its parent
+ * relation after CREATE INDEX. Its rather bizarre API is designed to
+ * ensure we can do all the necessary work in just one update.
*
- * If reltoastidxid is not InvalidOid, also set reltoastidxid to that value.
- * This is only used for TOAST relations.
+ * hasindex: set relhasindex to this value
+ * isprimary: if true, set relhaspkey true; else no change
+ * reltoastidxid: if not InvalidOid, set reltoastidxid to this value;
+ * else no change
+ * reltuples: set reltuples to this value
+ *
+ * relpages is also updated (using RelationGetNumberOfBlocks()).
*
* NOTE: an important side-effect of this operation is that an SI invalidation
* message is sent out to all backends --- including me --- causing relcache
- * entries to be flushed or updated with the new hasindex data. This must
- * happen even if we find that no change is needed in the pg_class row.
- * ----------------
+ * entries to be flushed or updated with the new data. This must happen even
+ * if we find that no change is needed in the pg_class row. When updating
+ * a heap entry, this ensures that other backends find out about the new
+ * index. When updating an index, it's important because some index AMs
+ * expect a relcache flush to occur after REINDEX.
*/
-void
-setRelhasindex(Oid relid, bool hasindex, bool isprimary, Oid reltoastidxid)
+static void
+index_update_stats(Relation rel, bool hasindex, bool isprimary,
+ Oid reltoastidxid, double reltuples)
{
+ BlockNumber relpages = RelationGetNumberOfBlocks(rel);
+ Oid relid = RelationGetRelid(rel);
Relation pg_class;
HeapTuple tuple;
- Form_pg_class classtuple;
- bool dirty = false;
- HeapScanDesc pg_class_scan = NULL;
+ Form_pg_class rd_rel;
+ bool in_place_upd;
+ bool dirty;
/*
- * Find the tuple to update in pg_class. In bootstrap mode we can't use
- * heap_update, so cheat and overwrite the tuple in-place. In normal
- * processing, make a copy to scribble on.
+ * Find the tuple to update in pg_class. Normally we make a copy of the
+ * tuple using the syscache, modify it, and apply heap_update. But in
+ * bootstrap mode we can't use heap_update, so we use a nontransactional
+ * update, ie, overwrite the tuple in-place.
+ *
+ * We also must use an in-place update if reindexing pg_class itself,
+ * because the target index may presently not be part of the set of
+ * indexes that CatalogUpdateIndexes would update (see reindex_relation).
*/
pg_class = heap_open(RelationRelationId, RowExclusiveLock);
- if (!IsBootstrapProcessingMode())
+ in_place_upd = IsBootstrapProcessingMode() ||
+ ReindexIsProcessingHeap(RelationRelationId);
+
+restart:
+
+ if (!in_place_upd)
{
tuple = SearchSysCacheCopy(RELOID,
ObjectIdGetDatum(relid),
@@ -1022,6 +1068,8 @@ setRelhasindex(Oid relid, bool hasindex, bool isprimary, Oid reltoastidxid)
}
else
{
+ /* don't assume syscache will work */
+ HeapScanDesc pg_class_scan;
ScanKeyData key[1];
ScanKeyInit(&key[0],
@@ -1031,65 +1079,110 @@ setRelhasindex(Oid relid, bool hasindex, bool isprimary, Oid reltoastidxid)
pg_class_scan = heap_beginscan(pg_class, SnapshotNow, 1, key);
tuple = heap_getnext(pg_class_scan, ForwardScanDirection);
+ tuple = heap_copytuple(tuple);
+ heap_endscan(pg_class_scan);
}
if (!HeapTupleIsValid(tuple))
elog(ERROR, "could not find tuple for relation %u", relid);
- classtuple = (Form_pg_class) GETSTRUCT(tuple);
-
- /* Apply required updates */
+ rd_rel = (Form_pg_class) GETSTRUCT(tuple);
- if (pg_class_scan)
- LockBuffer(pg_class_scan->rs_cbuf, BUFFER_LOCK_EXCLUSIVE);
+ /* Apply required updates, if any, to copied tuple */
- if (classtuple->relhasindex != hasindex)
+ dirty = false;
+ if (rd_rel->relhasindex != hasindex)
{
- classtuple->relhasindex = hasindex;
+ rd_rel->relhasindex = hasindex;
dirty = true;
}
if (isprimary)
{
- if (!classtuple->relhaspkey)
+ if (!rd_rel->relhaspkey)
{
- classtuple->relhaspkey = true;
+ rd_rel->relhaspkey = true;
dirty = true;
}
}
if (OidIsValid(reltoastidxid))
{
- Assert(classtuple->relkind == RELKIND_TOASTVALUE);
- if (classtuple->reltoastidxid != reltoastidxid)
+ Assert(rd_rel->relkind == RELKIND_TOASTVALUE);
+ if (rd_rel->reltoastidxid != reltoastidxid)
{
- classtuple->reltoastidxid = reltoastidxid;
+ rd_rel->reltoastidxid = reltoastidxid;
dirty = true;
}
}
-
- if (pg_class_scan)
+ if (rd_rel->reltuples != (float4) reltuples)
{
- MarkBufferDirty(pg_class_scan->rs_cbuf);
- LockBuffer(pg_class_scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
- /* Send out shared cache inval if necessary */
- if (!IsBootstrapProcessingMode())
- CacheInvalidateHeapTuple(pg_class, tuple);
+ rd_rel->reltuples = (float4) reltuples;
+ dirty = true;
}
- else if (dirty)
+ if (rd_rel->relpages != (int32) relpages)
{
- simple_heap_update(pg_class, &tuple->t_self, tuple);
+ rd_rel->relpages = (int32) relpages;
+ dirty = true;
+ }
- /* Keep the catalog indexes up to date */
- CatalogUpdateIndexes(pg_class, tuple);
+ /*
+ * If anything changed, write out the tuple
+ */
+ if (dirty)
+ {
+ if (in_place_upd)
+ {
+ heap_inplace_update(pg_class, tuple);
+ }
+ else
+ {
+ /*
+ * Because PG allows concurrent CREATE INDEX commands, it's
+ * possible that someone else tries to update the pg_class
+ * row at about the same time we do. Hence, instead of using
+ * simple_heap_update(), we must use full heap_update() and
+ * cope with HeapTupleUpdated result. If we see that, just
+ * go back and try the whole update again.
+ */
+ HTSU_Result result;
+ ItemPointerData update_ctid;
+ TransactionId update_xmax;
+
+ result = heap_update(pg_class, &tuple->t_self, tuple,
+ &update_ctid, &update_xmax,
+ GetCurrentCommandId(), InvalidSnapshot,
+ true /* wait for commit */ );
+ switch (result)
+ {
+ case HeapTupleSelfUpdated:
+ /* Tuple was already updated in current command? */
+ elog(ERROR, "tuple already updated by self");
+ break;
+
+ case HeapTupleMayBeUpdated:
+ /* done successfully */
+ break;
+
+ case HeapTupleUpdated:
+ heap_freetuple(tuple);
+ /* Must do CCI so we can see the updated tuple */
+ CommandCounterIncrement();
+ goto restart;
+
+ default:
+ elog(ERROR, "unrecognized heap_update status: %u", result);
+ break;
+ }
+
+ /* Keep the catalog indexes up to date */
+ CatalogUpdateIndexes(pg_class, tuple);
+ }
}
else
{
- /* no need to change tuple, but force relcache rebuild anyway */
+ /* no need to change tuple, but force relcache inval anyway */
CacheInvalidateRelcacheByTuple(tuple);
}
- if (!pg_class_scan)
- heap_freetuple(tuple);
- else
- heap_endscan(pg_class_scan);
+ heap_freetuple(tuple);
heap_close(pg_class, RowExclusiveLock);
}
@@ -1164,176 +1257,30 @@ setNewRelfilenode(Relation relation)
/*
- * This is invoked by the various index AMs once they have finished
- * constructing an index. Constructing an index involves counting the
- * number of tuples in both the relation and the index, so we take
- * advantage of the opportunity to update pg_class to ensure that the
- * planner takes advantage of the index we just created. But, only
- * update statistics during normal index definitions, not for indices
- * on system catalogs created during bootstrap processing. We must
- * close the relations before updating statistics to guarantee that
- * the relcache entries are flushed when we increment the command
- * counter in UpdateStats(). But we do not release any locks on the
- * relations; those will be held until end of transaction.
- */
-void
-IndexCloseAndUpdateStats(Relation heap, double heapTuples,
- Relation index, double indexTuples)
-{
- Oid hrelid = RelationGetRelid(heap);
- Oid irelid = RelationGetRelid(index);
-
- if (!IsNormalProcessingMode())
- return;
-
- heap_close(heap, NoLock);
- index_close(index);
- UpdateStats(hrelid, heapTuples);
- UpdateStats(irelid, indexTuples);
-}
-
-
-/* ----------------
- * UpdateStats
- *
- * Update pg_class' relpages and reltuples statistics for the given relation
- * (which can be either a table or an index). Note that this is not used
- * in the context of VACUUM, only CREATE INDEX.
- * ----------------
- */
-static void
-UpdateStats(Oid relid, double reltuples)
-{
- Relation whichRel;
- Relation pg_class;
- HeapTuple tuple;
- BlockNumber relpages;
- Form_pg_class rd_rel;
- HeapScanDesc pg_class_scan = NULL;
- bool in_place_upd;
-
- /*
- * This routine handles updates for both the heap and index relation
- * statistics. In order to guarantee that we're able to *see* the index
- * relation tuple, we bump the command counter id here. The index
- * relation tuple was created in the current transaction.
- */
- CommandCounterIncrement();
-
- /*
- * CommandCounterIncrement() flushes invalid cache entries, including
- * those for the heap and index relations for which we're updating
- * statistics. Now that the cache is flushed, it's safe to open the
- * relation again. We need the relation open in order to figure out how
- * many blocks it contains.
- */
-
- /*
- * Grabbing lock here is probably redundant ...
- */
- whichRel = relation_open(relid, ShareLock);
-
- /*
- * Find the tuple to update in pg_class. Normally we make a copy of the
- * tuple using the syscache, modify it, and apply heap_update. But in
- * bootstrap mode we can't use heap_update, so we cheat and overwrite the
- * tuple in-place. (Note: as of PG 8.0 this isn't called during
- * bootstrap, but leave the code here for possible future use.)
- *
- * We also must cheat if reindexing pg_class itself, because the target
- * index may presently not be part of the set of indexes that
- * CatalogUpdateIndexes would update (see reindex_relation). In this case
- * the stats updates will not be WAL-logged and so could be lost in a
- * crash. This seems OK considering VACUUM does the same thing.
- */
- pg_class = heap_open(RelationRelationId, RowExclusiveLock);
-
- in_place_upd = IsBootstrapProcessingMode() ||
- ReindexIsProcessingHeap(RelationRelationId);
-
- if (!in_place_upd)
- {
- tuple = SearchSysCacheCopy(RELOID,
- ObjectIdGetDatum(relid),
- 0, 0, 0);
- }
- else
- {
- ScanKeyData key[1];
-
- ScanKeyInit(&key[0],
- ObjectIdAttributeNumber,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(relid));
-
- pg_class_scan = heap_beginscan(pg_class, SnapshotNow, 1, key);
- tuple = heap_getnext(pg_class_scan, ForwardScanDirection);
- }
-
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "could not find tuple for relation %u", relid);
- rd_rel = (Form_pg_class) GETSTRUCT(tuple);
-
- /*
- * Update statistics in pg_class, if they changed. (Avoiding an
- * unnecessary update is not just a tiny performance improvement; it also
- * reduces the window wherein concurrent CREATE INDEX commands may
- * conflict.)
- */
- relpages = RelationGetNumberOfBlocks(whichRel);
-
- if (rd_rel->relpages != (int32) relpages ||
- rd_rel->reltuples != (float4) reltuples)
- {
- if (in_place_upd)
- {
- /* Bootstrap or reindex case: overwrite fields in place. */
- LockBuffer(pg_class_scan->rs_cbuf, BUFFER_LOCK_EXCLUSIVE);
- rd_rel->relpages = (int32) relpages;
- rd_rel->reltuples = (float4) reltuples;
- MarkBufferDirty(pg_class_scan->rs_cbuf);
- LockBuffer(pg_class_scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
- if (!IsBootstrapProcessingMode())
- CacheInvalidateHeapTuple(pg_class, tuple);
- }
- else
- {
- /* During normal processing, must work harder. */
- rd_rel->relpages = (int32) relpages;
- rd_rel->reltuples = (float4) reltuples;
- simple_heap_update(pg_class, &tuple->t_self, tuple);
- CatalogUpdateIndexes(pg_class, tuple);
- }
- }
-
- if (in_place_upd)
- heap_endscan(pg_class_scan);
- else
- heap_freetuple(tuple);
-
- /*
- * We shouldn't have to do this, but we do... Modify the reldesc in place
- * with the new values so that the cache contains the latest copy. (XXX
- * is this really still necessary? The relcache will get fixed at next
- * CommandCounterIncrement, so why bother here?)
- */
- whichRel->rd_rel->relpages = (int32) relpages;
- whichRel->rd_rel->reltuples = (float4) reltuples;
-
- heap_close(pg_class, RowExclusiveLock);
- relation_close(whichRel, NoLock);
-}
-
-
-/*
* index_build - invoke access-method-specific index build procedure
+ *
+ * On entry, the index's catalog entries are valid, and its physical disk
+ * file has been created but is empty. We call the AM-specific build
+ * procedure to fill in the index contents. We then update the pg_class
+ * entries of the index and heap relation as needed, using statistics
+ * returned by ambuild as well as data passed by the caller.
+ *
+ * Note: when reindexing an existing index, isprimary and istoast can be
+ * false; the index is already properly marked and need not be re-marked.
+ *
+ * Note: before Postgres 8.2, the passed-in heap and index Relations
+ * were automatically closed by this routine. This is no longer the case.
+ * The caller opened 'em, and the caller should close 'em.
*/
void
index_build(Relation heapRelation,
Relation indexRelation,
- IndexInfo *indexInfo)
+ IndexInfo *indexInfo,
+ bool isprimary,
+ bool istoast)
{
RegProcedure procedure;
+ IndexBuildResult *stats;
/*
* sanity checks
@@ -1347,10 +1294,30 @@ index_build(Relation heapRelation,
/*
* Call the access method's build procedure
*/
- OidFunctionCall3(procedure,
- PointerGetDatum(heapRelation),
- PointerGetDatum(indexRelation),
- PointerGetDatum(indexInfo));
+ stats = (IndexBuildResult *)
+ DatumGetPointer(OidFunctionCall3(procedure,
+ PointerGetDatum(heapRelation),
+ PointerGetDatum(indexRelation),
+ PointerGetDatum(indexInfo)));
+ Assert(PointerIsValid(stats));
+
+ /*
+ * Update heap and index pg_class rows
+ */
+ index_update_stats(heapRelation,
+ true,
+ isprimary,
+ istoast ? RelationGetRelid(indexRelation) : InvalidOid,
+ stats->heap_tuples);
+
+ index_update_stats(indexRelation,
+ false,
+ false,
+ InvalidOid,
+ stats->index_tuples);
+
+ /* Make the updated versions visible */
+ CommandCounterIncrement();
}
@@ -1674,12 +1641,8 @@ reindex_index(Oid indexId)
}
/* Initialize the index and rebuild */
- index_build(heapRelation, iRel, indexInfo);
-
- /*
- * index_build will close both the heap and index relations (but not
- * give up the locks we hold on them). So we're done.
- */
+ /* Note: we do not need to re-establish pkey or toast settings */
+ index_build(heapRelation, iRel, indexInfo, false, false);
}
PG_CATCH();
{
@@ -1689,6 +1652,10 @@ reindex_index(Oid indexId)
}
PG_END_TRY();
ResetReindexProcessing();
+
+ /* Close rels, but keep locks */
+ index_close(iRel);
+ heap_close(heapRelation, NoLock);
}
/*