/*------------------------------------------------------------------------- * * cluster.c * Paul Brown's implementation of cluster index. * * I am going to use the rename function as a model for this in the * parser and executor, and the vacuum code as an example in this * file. As I go - in contrast to the rest of postgres - there will * be BUCKETS of comments. This is to allow reviewers to understand * my (probably bogus) assumptions about the way this works. * [pbrown '94] * * Portions Copyright (c) 1996-2000, PostgreSQL, Inc * Portions Copyright (c) 1994-5, Regents of the University of California * * * IDENTIFICATION * $Header: /cvsroot/pgsql/src/backend/commands/cluster.c,v 1.50 2000/01/26 05:56:13 momjian Exp $ * *------------------------------------------------------------------------- */ #include "postgres.h" #include "access/genam.h" #include "access/heapam.h" #include "catalog/heap.h" #include "catalog/index.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "commands/cluster.h" #include "commands/rename.h" #include "optimizer/internal.h" #include "utils/builtins.h" #include "utils/syscache.h" static Relation copy_heap(Oid OIDOldHeap); static void copy_index(Oid OIDOldIndex, Oid OIDNewHeap); static void rebuildheap(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex); /* * cluster * * Check that the relation is a relation in the appropriate user * ACL. I will use the same security that limits users on the * renamerel() function. * * Check that the index specified is appropriate for the task * ( ie it's an index over this relation ). This is trickier. * * Create a list of all the other indicies on this relation. Because * the cluster will wreck all the tids, I'll need to destroy bogus * indicies. The user will have to re-create them. Not nice, but * I'm not a nice guy. The alternative is to try some kind of post * destroy re-build. This may be possible. I'll check out what the * index create functiond want in the way of paramaters. On the other * hand, re-creating n indicies may blow out the space. * * Create new (temporary) relations for the base heap and the new * index. * * Exclusively lock the relations. * * Create new clustered index and base heap relation. * */ void cluster(char *oldrelname, char *oldindexname) { Oid OIDOldHeap, OIDOldIndex, OIDNewHeap; Relation OldHeap, OldIndex; Relation NewHeap; char NewIndexName[NAMEDATALEN]; char NewHeapName[NAMEDATALEN]; char saveoldrelname[NAMEDATALEN]; char saveoldindexname[NAMEDATALEN]; /* * Save the old names because they will get lost when the old * relations are destroyed. */ strcpy(saveoldrelname, oldrelname); strcpy(saveoldindexname, oldindexname); /* * I'm going to force all checking back into the commands.c function. * * Get the list if indicies for this relation. If the index we want is * among them, do not add it to the 'kill' list, as it will be handled * by the 'clean up' code which commits this transaction. * * I'm not using the SysCache, because this will happen but once, and the * slow way is the sure way in this case. * */ /* * Like vacuum, cluster spans transactions, so I'm going to handle it * in the same way: commit and restart transactions where needed. * * We grab exclusive access to the target rel and index for the * duration of the initial transaction. */ OldHeap = heap_openr(oldrelname, AccessExclusiveLock); OIDOldHeap = RelationGetRelid(OldHeap); OldIndex = index_openr(oldindexname); /* Open old index relation */ LockRelation(OldIndex, AccessExclusiveLock); OIDOldIndex = RelationGetRelid(OldIndex); heap_close(OldHeap, NoLock); /* do NOT give up the locks */ index_close(OldIndex); /* * I need to build the copies of the heap and the index. The Commit() * between here is *very* bogus. If someone is appending stuff, they * will get the lock after being blocked and add rows which won't be * present in the new table. Bleagh! I'd be best to try and ensure * that no-one's in the tables for the entire duration of this process * with a pg_vlock. XXX Isn't the above comment now invalid? */ NewHeap = copy_heap(OIDOldHeap); OIDNewHeap = RelationGetRelid(NewHeap); strcpy(NewHeapName, RelationGetRelationName(NewHeap)); /* To make the new heap visible (which is until now empty). */ CommandCounterIncrement(); rebuildheap(OIDNewHeap, OIDOldHeap, OIDOldIndex); /* To flush the filled new heap (and the statistics about it). */ CommandCounterIncrement(); /* Create new index over the tuples of the new heap. */ copy_index(OIDOldIndex, OIDNewHeap); snprintf(NewIndexName, NAMEDATALEN, "temp_%x", OIDOldIndex); /* * make this really happen. Flush all the buffers. (Believe me, it is * necessary ... ended up in a mess without it.) */ CommitTransactionCommand(); StartTransactionCommand(); /* Destroy old heap (along with its index) and rename new. */ heap_drop_with_catalog(oldrelname); CommitTransactionCommand(); StartTransactionCommand(); renamerel(NewHeapName, saveoldrelname); TypeRename(NewHeapName, saveoldrelname); renamerel(NewIndexName, saveoldindexname); /* * Again flush all the buffers. XXX perhaps not needed? */ CommitTransactionCommand(); StartTransactionCommand(); } static Relation copy_heap(Oid OIDOldHeap) { char NewName[NAMEDATALEN]; TupleDesc OldHeapDesc, tupdesc; Oid OIDNewHeap; Relation NewHeap, OldHeap; /* * Create a new heap relation with a temporary name, which has the * same tuple description as the old one. */ snprintf(NewName, NAMEDATALEN, "temp_%x", OIDOldHeap); OldHeap = heap_open(OIDOldHeap, AccessExclusiveLock); OldHeapDesc = RelationGetDescr(OldHeap); /* * Need to make a copy of the tuple descriptor, * heap_create_with_catalog modifies it. */ tupdesc = CreateTupleDescCopy(OldHeapDesc); OIDNewHeap = heap_create_with_catalog(NewName, tupdesc, RELKIND_RELATION, false); if (!OidIsValid(OIDNewHeap)) elog(ERROR, "clusterheap: cannot create temporary heap relation\n"); /* XXX why are we bothering to do this: */ NewHeap = heap_open(OIDNewHeap, AccessExclusiveLock); heap_close(NewHeap, AccessExclusiveLock); heap_close(OldHeap, AccessExclusiveLock); return NewHeap; } static void copy_index(Oid OIDOldIndex, Oid OIDNewHeap) { Relation OldIndex, NewHeap; HeapTuple Old_pg_index_Tuple, Old_pg_index_relation_Tuple, pg_proc_Tuple; Form_pg_index Old_pg_index_Form; Form_pg_class Old_pg_index_relation_Form; Form_pg_proc pg_proc_Form; char *NewIndexName; AttrNumber *attnumP; int natts; FuncIndexInfo *finfo; NewHeap = heap_open(OIDNewHeap, AccessExclusiveLock); OldIndex = index_open(OIDOldIndex); /* * OK. Create a new (temporary) index for the one that's already here. * To do this I get the info from pg_index, re-build the FunctInfo if * I have to, and add a new index with a temporary name. */ Old_pg_index_Tuple = SearchSysCacheTuple(INDEXRELID, ObjectIdGetDatum(RelationGetRelid(OldIndex)), 0, 0, 0); Assert(Old_pg_index_Tuple); Old_pg_index_Form = (Form_pg_index) GETSTRUCT(Old_pg_index_Tuple); Old_pg_index_relation_Tuple = SearchSysCacheTuple(RELOID, ObjectIdGetDatum(RelationGetRelid(OldIndex)), 0, 0, 0); Assert(Old_pg_index_relation_Tuple); Old_pg_index_relation_Form = (Form_pg_class) GETSTRUCT(Old_pg_index_relation_Tuple); /* Set the name. */ NewIndexName = palloc(NAMEDATALEN); /* XXX */ snprintf(NewIndexName, NAMEDATALEN, "temp_%x", OIDOldIndex); /* * Ugly as it is, the only way I have of working out the number of * attribues is to count them. Mostly there'll be just one but I've * got to be sure. */ for (attnumP = &(Old_pg_index_Form->indkey[0]), natts = 0; natts < INDEX_MAX_KEYS && *attnumP != InvalidAttrNumber; attnumP++, natts++); /* * If this is a functional index, I need to rebuild the functional * component to pass it to the defining procedure. */ if (Old_pg_index_Form->indproc != InvalidOid) { finfo = (FuncIndexInfo *) palloc(sizeof(FuncIndexInfo)); FIgetnArgs(finfo) = natts; FIgetProcOid(finfo) = Old_pg_index_Form->indproc; pg_proc_Tuple = SearchSysCacheTuple(PROCOID, ObjectIdGetDatum(Old_pg_index_Form->indproc), 0, 0, 0); Assert(pg_proc_Tuple); pg_proc_Form = (Form_pg_proc) GETSTRUCT(pg_proc_Tuple); namecpy(&(finfo->funcName), &(pg_proc_Form->proname)); } else { finfo = (FuncIndexInfo *) NULL; natts = 1; } index_create(RelationGetRelationName(NewHeap), NewIndexName, finfo, NULL, /* type info is in the old index */ Old_pg_index_relation_Form->relam, natts, Old_pg_index_Form->indkey, Old_pg_index_Form->indclass, (uint16) 0, (Datum) NULL, NULL, Old_pg_index_Form->indislossy, Old_pg_index_Form->indisunique, Old_pg_index_Form->indisprimary); index_close(OldIndex); heap_close(NewHeap, AccessExclusiveLock); } static void rebuildheap(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex) { Relation LocalNewHeap, LocalOldHeap, LocalOldIndex; IndexScanDesc ScanDesc; RetrieveIndexResult ScanResult; HeapTupleData LocalHeapTuple; Buffer LocalBuffer; Oid OIDNewHeapInsert; /* * Open the relations I need. Scan through the OldHeap on the OldIndex * and insert each tuple into the NewHeap. */ LocalNewHeap = heap_open(OIDNewHeap, AccessExclusiveLock); LocalOldHeap = heap_open(OIDOldHeap, AccessExclusiveLock); LocalOldIndex = (Relation) index_open(OIDOldIndex); ScanDesc = index_beginscan(LocalOldIndex, false, 0, (ScanKey) NULL); while ((ScanResult = index_getnext(ScanDesc, ForwardScanDirection)) != NULL) { LocalHeapTuple.t_self = ScanResult->heap_iptr; LocalHeapTuple.t_datamcxt = NULL; LocalHeapTuple.t_data = NULL; heap_fetch(LocalOldHeap, SnapshotNow, &LocalHeapTuple, &LocalBuffer); OIDNewHeapInsert = heap_insert(LocalNewHeap, &LocalHeapTuple); pfree(ScanResult); ReleaseBuffer(LocalBuffer); } index_endscan(ScanDesc); index_close(LocalOldIndex); heap_close(LocalOldHeap, AccessExclusiveLock); heap_close(LocalNewHeap, AccessExclusiveLock); }