diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 5a8d5b2267..6a994aa0cc 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.229 2004/05/05 04:48:45 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.230 2004/05/08 00:34:49 tgl Exp $ * * * INTERFACE ROUTINES @@ -1729,12 +1729,13 @@ reindex_index(Oid indexId) /* * reindex_relation - This routine is used to recreate all indexes - * of a relation (and its toast relation too, if any). + * of a relation (and optionally its toast relation too, if any). * - * Returns true if any indexes were rebuilt. + * Returns true if any indexes were rebuilt. Note that a + * CommandCounterIncrement will occur after each index rebuild. */ bool -reindex_relation(Oid relid) +reindex_relation(Oid relid, bool toast_too) { Relation rel; Oid toast_relid; @@ -1810,8 +1811,8 @@ reindex_relation(Oid relid) * If the relation has a secondary toast rel, reindex that too while we * still hold the lock on the master table. */ - if (toast_relid != InvalidOid) - result |= reindex_relation(toast_relid); + if (toast_too && OidIsValid(toast_relid)) + result |= reindex_relation(toast_relid, false); return result; } diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index ffd2aff236..168ae749fa 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/cluster.c,v 1.122 2004/05/06 16:10:57 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/cluster.c,v 1.123 2004/05/08 00:34:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -37,20 +37,6 @@ #include "utils/relcache.h" -/* - * We need one of these structs for each index in the relation to be - * clustered. It's basically the data needed by index_create() so - * we can rebuild the indexes on the new heap. - */ -typedef struct -{ - Oid indexOID; - char *indexName; - IndexInfo *indexInfo; - Oid accessMethodOID; - Oid *classOID; -} IndexAttrs; - /* * This struct is used to pass around the information on tables to be * clustered. We need this so we can make a list of them when invoked without @@ -64,6 +50,7 @@ typedef struct static void cluster_rel(RelToCluster *rv, bool recheck); +static void rebuild_relation(Relation OldHeap, Oid indexOid); static void copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex); static List *get_tables_to_cluster(MemoryContext cluster_context); @@ -411,30 +398,99 @@ check_index_is_clusterable(Relation OldHeap, Oid indexOid) } /* - * rebuild_relation: rebuild an existing relation + * mark_index_clustered: mark the specified index as the one clustered on * - * This is shared code between CLUSTER and TRUNCATE. In the TRUNCATE - * case, the new relation is built and left empty. In the CLUSTER case, - * it is filled with data read from the old relation in the order specified - * by the index. + * With indexOid == InvalidOid, will mark all indexes of rel not-clustered. + */ +void +mark_index_clustered(Relation rel, Oid indexOid) +{ + HeapTuple indexTuple; + Form_pg_index indexForm; + Relation pg_index; + List *index; + + /* + * If the index is already marked clustered, no need to do anything. + */ + if (OidIsValid(indexOid)) + { + indexTuple = SearchSysCache(INDEXRELID, + ObjectIdGetDatum(indexOid), + 0, 0, 0); + if (!HeapTupleIsValid(indexTuple)) + elog(ERROR, "cache lookup failed for index %u", indexOid); + indexForm = (Form_pg_index) GETSTRUCT(indexTuple); + + if (indexForm->indisclustered) + { + ReleaseSysCache(indexTuple); + return; + } + + ReleaseSysCache(indexTuple); + } + + /* + * Check each index of the relation and set/clear the bit as needed. + */ + pg_index = heap_openr(IndexRelationName, RowExclusiveLock); + + foreach(index, RelationGetIndexList(rel)) + { + Oid thisIndexOid = lfirsto(index); + + indexTuple = SearchSysCacheCopy(INDEXRELID, + ObjectIdGetDatum(thisIndexOid), + 0, 0, 0); + if (!HeapTupleIsValid(indexTuple)) + elog(ERROR, "cache lookup failed for index %u", thisIndexOid); + indexForm = (Form_pg_index) GETSTRUCT(indexTuple); + + /* + * Unset the bit if set. We know it's wrong because we checked + * this earlier. + */ + if (indexForm->indisclustered) + { + indexForm->indisclustered = false; + simple_heap_update(pg_index, &indexTuple->t_self, indexTuple); + CatalogUpdateIndexes(pg_index, indexTuple); + /* Ensure we see the update in the index's relcache entry */ + CacheInvalidateRelcacheByRelid(thisIndexOid); + } + else if (thisIndexOid == indexOid) + { + indexForm->indisclustered = true; + simple_heap_update(pg_index, &indexTuple->t_self, indexTuple); + CatalogUpdateIndexes(pg_index, indexTuple); + /* Ensure we see the update in the index's relcache entry */ + CacheInvalidateRelcacheByRelid(thisIndexOid); + } + heap_freetuple(indexTuple); + } + + heap_close(pg_index, RowExclusiveLock); +} + +/* + * rebuild_relation: rebuild an existing relation in index order * * OldHeap: table to rebuild --- must be opened and exclusive-locked! - * indexOid: index to cluster by, or InvalidOid in TRUNCATE case + * indexOid: index to cluster by * * NB: this routine closes OldHeap at the right time; caller should not. */ -void +static void rebuild_relation(Relation OldHeap, Oid indexOid) { Oid tableOid = RelationGetRelid(OldHeap); - Oid oldClusterIndex; - List *indexes; Oid OIDNewHeap; char NewHeapName[NAMEDATALEN]; ObjectAddress object; - /* Save the information about all indexes on the relation. */ - indexes = get_indexattr_list(OldHeap, &oldClusterIndex); + /* Mark the correct index as clustered */ + mark_index_clustered(OldHeap, indexOid); /* Close relcache entry, but keep lock until transaction commit */ heap_close(OldHeap, NoLock); @@ -459,8 +515,7 @@ rebuild_relation(Relation OldHeap, Oid indexOid) /* * Copy the heap data into the new table in the desired order. */ - if (OidIsValid(indexOid)) - copy_heap_data(OIDNewHeap, tableOid, indexOid); + copy_heap_data(OIDNewHeap, tableOid, indexOid); /* To make the new heap's data visible (probably not needed?). */ CommandCounterIncrement(); @@ -484,11 +539,11 @@ rebuild_relation(Relation OldHeap, Oid indexOid) /* performDeletion does CommandCounterIncrement at end */ /* - * Recreate each index on the relation. We do not need - * CommandCounterIncrement() because rebuild_indexes does it. + * Rebuild each index on the relation (but not the toast table, + * which is all-new at this point). We do not need + * CommandCounterIncrement() because reindex_relation does it. */ - rebuild_indexes(tableOid, indexes, - (OidIsValid(indexOid) ? indexOid : oldClusterIndex)); + reindex_relation(tableOid, false); } /* @@ -589,138 +644,6 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex) heap_close(NewHeap, NoLock); } -/* - * Get the necessary info about the indexes of the relation and - * return a list of IndexAttrs structures. Also, *OldClusterIndex - * is set to the OID of the existing clustered index, or InvalidOid - * if there is none. - */ -List * -get_indexattr_list(Relation OldHeap, Oid *OldClusterIndex) -{ - List *indexes = NIL; - List *indlist; - - *OldClusterIndex = InvalidOid; - - /* Ask the relcache to produce a list of the indexes of the old rel */ - foreach(indlist, RelationGetIndexList(OldHeap)) - { - Oid indexOID = lfirsto(indlist); - Relation oldIndex; - IndexAttrs *attrs; - - oldIndex = index_open(indexOID); - - attrs = (IndexAttrs *) palloc(sizeof(IndexAttrs)); - attrs->indexOID = indexOID; - attrs->indexName = pstrdup(NameStr(oldIndex->rd_rel->relname)); - attrs->accessMethodOID = oldIndex->rd_rel->relam; - attrs->indexInfo = BuildIndexInfo(oldIndex); - attrs->classOID = (Oid *) - palloc(sizeof(Oid) * attrs->indexInfo->ii_NumIndexAttrs); - memcpy(attrs->classOID, oldIndex->rd_index->indclass, - sizeof(Oid) * attrs->indexInfo->ii_NumIndexAttrs); - if (oldIndex->rd_index->indisclustered) - *OldClusterIndex = indexOID; - - index_close(oldIndex); - - indexes = lappend(indexes, attrs); - } - - return indexes; -} - -/* - * Create new indexes and swap the filenodes with old indexes. Then drop - * the new index (carrying the old index filenode along). - * - * OIDClusterIndex is the OID of the index to be marked as clustered, or - * InvalidOid if none should be marked clustered. - */ -void -rebuild_indexes(Oid OIDOldHeap, List *indexes, Oid OIDClusterIndex) -{ - List *elem; - - foreach(elem, indexes) - { - IndexAttrs *attrs = (IndexAttrs *) lfirst(elem); - Oid oldIndexOID = attrs->indexOID; - Oid newIndexOID; - char newIndexName[NAMEDATALEN]; - bool isclustered; - ObjectAddress object; - Form_pg_index index; - HeapTuple tuple; - Relation pg_index; - - /* Create the new index under a temporary name */ - snprintf(newIndexName, sizeof(newIndexName), - "pg_temp_%u", oldIndexOID); - - /* - * The new index will have primary and constraint status set to - * false, but since we will only use its filenode it doesn't - * matter: after the filenode swap the index will keep the - * constraint status of the old index. - */ - newIndexOID = index_create(OIDOldHeap, - newIndexName, - attrs->indexInfo, - attrs->accessMethodOID, - attrs->classOID, - false, - false, - allowSystemTableMods, - false); - CommandCounterIncrement(); - - /* Swap the filenodes. */ - swap_relfilenodes(oldIndexOID, newIndexOID); - - CommandCounterIncrement(); - - /* - * Make sure that indisclustered is correct: it should be set only - * for the index specified by the caller. - */ - isclustered = (oldIndexOID == OIDClusterIndex); - - pg_index = heap_openr(IndexRelationName, RowExclusiveLock); - tuple = SearchSysCacheCopy(INDEXRELID, - ObjectIdGetDatum(oldIndexOID), - 0, 0, 0); - if (!HeapTupleIsValid(tuple)) - elog(ERROR, "cache lookup failed for index %u", oldIndexOID); - index = (Form_pg_index) GETSTRUCT(tuple); - if (index->indisclustered != isclustered) - { - index->indisclustered = isclustered; - simple_heap_update(pg_index, &tuple->t_self, tuple); - CatalogUpdateIndexes(pg_index, tuple); - /* Ensure we see the update in the index's relcache entry */ - CacheInvalidateRelcacheByRelid(oldIndexOID); - } - heap_freetuple(tuple); - heap_close(pg_index, RowExclusiveLock); - - /* Destroy new index with old filenode */ - object.classId = RelOid_pg_class; - object.objectId = newIndexOID; - object.objectSubId = 0; - - /* - * The relation is local to our transaction and we know nothing - * depends on it, so DROP_RESTRICT should be OK. - */ - performDeletion(&object, DROP_RESTRICT); - - /* performDeletion does CommandCounterIncrement() at its end */ - } -} - /* * Swap the relfilenodes for two given relations. * diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index efcc70c685..448f99e1d3 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.118 2004/05/05 04:48:45 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.119 2004/05/08 00:34:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -826,7 +826,7 @@ ReindexTable(RangeVar *relation, bool force /* currently unused */ ) ReleaseSysCache(tuple); - if (!reindex_relation(heapOid)) + if (!reindex_relation(heapOid, true)) ereport(NOTICE, (errmsg("table \"%s\" has no indexes", relation->relname))); @@ -936,7 +936,7 @@ ReindexDatabase(const char *dbname, bool force /* currently unused */, StartTransactionCommand(); SetQuerySnapshot(); /* might be needed for functions in * indexes */ - if (reindex_relation(relid)) + if (reindex_relation(relid, true)) ereport(NOTICE, (errmsg("table \"%s\" was reindexed", get_rel_name(relid)))); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index d74e1e7ab3..5adafd630a 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.105 2004/05/07 00:24:57 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.106 2004/05/08 00:34:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -490,14 +490,13 @@ RemoveRelation(const RangeVar *relation, DropBehavior behavior) /* * TruncateRelation * Removes all the rows from a relation. - * - * Note: This routine only does safety and permissions checks; - * rebuild_relation in cluster.c does the actual work. */ void TruncateRelation(const RangeVar *relation) { Relation rel; + Oid heap_relid; + Oid toast_relid; /* Grab exclusive lock in preparation for truncate */ rel = heap_openrv(relation, AccessExclusiveLock); @@ -520,6 +519,16 @@ TruncateRelation(const RangeVar *relation) errmsg("permission denied: \"%s\" is a system catalog", RelationGetRelationName(rel)))); + /* + * We can never allow truncation of shared or nailed-in-cache relations, + * because we can't support changing their relfilenode values. + */ + if (rel->rd_rel->relisshared || rel->rd_isnailed) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot truncate system relation \"%s\"", + RelationGetRelationName(rel)))); + /* * Don't allow truncate on temp tables of other backends ... their * local buffer manager is not going to cope. @@ -535,17 +544,31 @@ TruncateRelation(const RangeVar *relation) heap_truncate_check_FKs(rel); /* - * Do the real work using the same technique as cluster, but without - * the data-copying portion + * Okay, here we go: create a new empty storage file for the relation, + * and assign it as the relfilenode value. The old storage file is + * scheduled for deletion at commit. */ - rebuild_relation(rel, InvalidOid); + setNewRelfilenode(rel); - /* NB: rebuild_relation does heap_close() */ + heap_relid = RelationGetRelid(rel); + toast_relid = rel->rd_rel->reltoastrelid; + + heap_close(rel, NoLock); /* - * You might think we need to truncate the rel's toast table here too, - * but actually we don't; it will have been rebuilt in an empty state. + * The same for the toast table, if any. */ + if (OidIsValid(toast_relid)) + { + rel = relation_open(toast_relid, AccessExclusiveLock); + setNewRelfilenode(rel); + heap_close(rel, NoLock); + } + + /* + * Reconstruct the indexes to match, and we're done. + */ + reindex_relation(heap_relid, true); } /*---------- @@ -2098,14 +2121,31 @@ ATRewriteTables(List **wqueue) /* Build a temporary relation and copy data */ Oid OIDNewHeap; char NewHeapName[NAMEDATALEN]; - List *indexes; - Oid oldClusterIndex; Relation OldHeap; ObjectAddress object; - /* Save the information about all indexes on the relation. */ OldHeap = heap_open(tab->relid, NoLock); - indexes = get_indexattr_list(OldHeap, &oldClusterIndex); + + /* + * We can never allow rewriting of shared or nailed-in-cache + * relations, because we can't support changing their relfilenode + * values. + */ + if (OldHeap->rd_rel->relisshared || OldHeap->rd_isnailed) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot rewrite system relation \"%s\"", + RelationGetRelationName(OldHeap)))); + + /* + * Don't allow rewrite on temp tables of other backends ... their + * local buffer manager is not going to cope. + */ + if (isOtherTempNamespace(RelationGetNamespace(OldHeap))) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot rewrite temporary tables of other sessions"))); + heap_close(OldHeap, NoLock); /* @@ -2146,10 +2186,11 @@ ATRewriteTables(List **wqueue) /* performDeletion does CommandCounterIncrement at end */ /* - * Rebuild each index on the relation. We do not need - * CommandCounterIncrement() because rebuild_indexes does it. + * Rebuild each index on the relation (but not the toast table, + * which is all-new anyway). We do not need + * CommandCounterIncrement() because reindex_relation does it. */ - rebuild_indexes(tab->relid, indexes, oldClusterIndex); + reindex_relation(tab->relid, false); } else { @@ -4991,11 +5032,7 @@ ATExecChangeOwner(Oid relationOid, int32 newOwnerSysId) static void ATExecClusterOn(Relation rel, const char *indexName) { - Relation pg_index; - List *index; Oid indexOid; - HeapTuple indexTuple; - Form_pg_index indexForm; indexOid = get_relname_relid(indexName, rel->rd_rel->relnamespace); @@ -5008,67 +5045,8 @@ ATExecClusterOn(Relation rel, const char *indexName) /* Check index is valid to cluster on */ check_index_is_clusterable(rel, indexOid); - indexTuple = SearchSysCache(INDEXRELID, - ObjectIdGetDatum(indexOid), - 0, 0, 0); - if (!HeapTupleIsValid(indexTuple)) - elog(ERROR, "cache lookup failed for index %u", indexOid); - indexForm = (Form_pg_index) GETSTRUCT(indexTuple); - - /* - * If this is the same index the relation was previously clustered on, - * no need to do anything. - */ - if (indexForm->indisclustered) - { - ReleaseSysCache(indexTuple); - return; - } - - ReleaseSysCache(indexTuple); - - pg_index = heap_openr(IndexRelationName, RowExclusiveLock); - - /* - * Now check each index in the relation and set the bit where needed. - */ - foreach(index, RelationGetIndexList(rel)) - { - HeapTuple idxtuple; - Form_pg_index idxForm; - - indexOid = lfirsto(index); - idxtuple = SearchSysCacheCopy(INDEXRELID, - ObjectIdGetDatum(indexOid), - 0, 0, 0); - if (!HeapTupleIsValid(idxtuple)) - elog(ERROR, "cache lookup failed for index %u", indexOid); - idxForm = (Form_pg_index) GETSTRUCT(idxtuple); - - /* - * Unset the bit if set. We know it's wrong because we checked - * this earlier. - */ - if (idxForm->indisclustered) - { - idxForm->indisclustered = false; - simple_heap_update(pg_index, &idxtuple->t_self, idxtuple); - CatalogUpdateIndexes(pg_index, idxtuple); - /* Ensure we see the update in the index's relcache entry */ - CacheInvalidateRelcacheByRelid(indexOid); - } - else if (idxForm->indexrelid == indexForm->indexrelid) - { - idxForm->indisclustered = true; - simple_heap_update(pg_index, &idxtuple->t_self, idxtuple); - CatalogUpdateIndexes(pg_index, idxtuple); - /* Ensure we see the update in the index's relcache entry */ - CacheInvalidateRelcacheByRelid(indexOid); - } - heap_freetuple(idxtuple); - } - - heap_close(pg_index, RowExclusiveLock); + /* And do the work */ + mark_index_clustered(rel, indexOid); } /* diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h index 13c367c902..841387effc 100644 --- a/src/include/catalog/index.h +++ b/src/include/catalog/index.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/index.h,v 1.55 2004/05/05 04:48:47 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/index.h,v 1.56 2004/05/08 00:34:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -68,6 +68,6 @@ extern double IndexBuildHeapScan(Relation heapRelation, void *callback_state); extern void reindex_index(Oid indexId); -extern bool reindex_relation(Oid relid); +extern bool reindex_relation(Oid relid, bool toast_too); #endif /* INDEX_H */ diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h index d97be66d73..820c952108 100644 --- a/src/include/commands/cluster.h +++ b/src/include/commands/cluster.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994-5, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/commands/cluster.h,v 1.22 2004/05/06 16:10:57 tgl Exp $ + * $PostgreSQL: pgsql/src/include/commands/cluster.h,v 1.23 2004/05/08 00:34:49 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -20,11 +20,8 @@ extern void cluster(ClusterStmt *stmt); extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid); -extern void rebuild_relation(Relation OldHeap, Oid indexOid); +extern void mark_index_clustered(Relation rel, Oid indexOid); extern Oid make_new_heap(Oid OIDOldHeap, const char *NewName); -extern List *get_indexattr_list(Relation OldHeap, Oid *OldClusterIndex); -extern void rebuild_indexes(Oid OIDOldHeap, List *indexes, - Oid OIDClusterIndex); extern void swap_relfilenodes(Oid r1, Oid r2); #endif /* CLUSTER_H */