/*-------------------------------------------------------------------------
 *
 * 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-2001, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994-5, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  $Header: /home/postgres/cvs_root/pgsql-r-v7.2/src/backend/commands/cluster.c,v 1.1.1.1 2002/02/07 17:51:43 bpalmer Exp $
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/genam.h"
#include "access/heapam.h"
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/pg_index.h"
#include "catalog/pg_proc.h"
#include "commands/cluster.h"
#include "commands/command.h"
#include "commands/rename.h"
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/syscache.h"
#include "utils/temprel.h"


static Oid	copy_heap(Oid OIDOldHeap, char *NewName, bool istemp);
static void copy_index(Oid OIDOldIndex, Oid OIDNewHeap, char *NewIndexName);
static void rebuildheap(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex);

/*
 * cluster
 *
 * STILL TO DO:
 *	 Create a list of all the other indexes on this relation. Because
 *	 the cluster will wreck all the tids, I'll need to destroy bogus
 *	 indexes. 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 indexes may blow out the space.
 */
void
cluster(char *oldrelname, char *oldindexname)
{
	Oid			OIDOldHeap,
				OIDOldIndex,
				OIDNewHeap;
	Relation	OldHeap,
				OldIndex;
	HeapTuple	tuple;
	bool		istemp;
	char		NewHeapName[NAMEDATALEN];
	char		NewIndexName[NAMEDATALEN];
	char		saveoldrelname[NAMEDATALEN];
	char		saveoldindexname[NAMEDATALEN];

	/*
	 * Copy the arguments into local storage, just to be safe.
	 */
	StrNCpy(saveoldrelname, oldrelname, NAMEDATALEN);
	StrNCpy(saveoldindexname, oldindexname, NAMEDATALEN);

	/*
	 * We grab exclusive access to the target rel and index for the
	 * duration of the transaction.
	 */
	OldHeap = heap_openr(saveoldrelname, AccessExclusiveLock);
	OIDOldHeap = RelationGetRelid(OldHeap);

	OldIndex = index_openr(saveoldindexname);
	LockRelation(OldIndex, AccessExclusiveLock);
	OIDOldIndex = RelationGetRelid(OldIndex);

	istemp = is_temp_rel_name(saveoldrelname);

	/*
	 * Check that index is in fact an index on the given relation
	 */
	tuple = SearchSysCache(INDEXRELID,
						   ObjectIdGetDatum(OIDOldIndex),
						   0, 0, 0);
	if (!HeapTupleIsValid(tuple))
		elog(ERROR, "CLUSTER: no pg_index entry for index %u",
			 OIDOldIndex);
	if (((Form_pg_index) GETSTRUCT(tuple))->indrelid != OIDOldHeap)
		elog(ERROR, "CLUSTER: \"%s\" is not an index for table \"%s\"",
			 saveoldindexname, saveoldrelname);
	ReleaseSysCache(tuple);

	/* Drop relcache refcnts, but do NOT give up the locks */
	heap_close(OldHeap, NoLock);
	index_close(OldIndex);

	/*
	 * Create the new heap with a temporary name.
	 */
	snprintf(NewHeapName, NAMEDATALEN, "temp_%u", OIDOldHeap);

	OIDNewHeap = copy_heap(OIDOldHeap, NewHeapName, istemp);

	/* We do not need CommandCounterIncrement() because copy_heap did it. */

	/*
	 * Copy the heap data into the new table in the desired order.
	 */
	rebuildheap(OIDNewHeap, OIDOldHeap, OIDOldIndex);

	/* To make the new heap's data visible. */
	CommandCounterIncrement();

	/* Create new index over the tuples of the new heap. */
	snprintf(NewIndexName, NAMEDATALEN, "temp_%u", OIDOldIndex);

	copy_index(OIDOldIndex, OIDNewHeap, NewIndexName);

	CommandCounterIncrement();

	/* Destroy old heap (along with its index) and rename new. */
	heap_drop_with_catalog(saveoldrelname, allowSystemTableMods);

	CommandCounterIncrement();

	renamerel(NewHeapName, saveoldrelname);

	/* This one might be unnecessary, but let's be safe. */
	CommandCounterIncrement();

	renamerel(NewIndexName, saveoldindexname);
}

static Oid
copy_heap(Oid OIDOldHeap, char *NewName, bool istemp)
{
	TupleDesc	OldHeapDesc,
				tupdesc;
	Oid			OIDNewHeap;
	Relation	OldHeap;

	OldHeap = heap_open(OIDOldHeap, AccessExclusiveLock);
	OldHeapDesc = RelationGetDescr(OldHeap);

	/*
	 * Need to make a copy of the tuple descriptor, since
	 * heap_create_with_catalog modifies it.
	 */
	tupdesc = CreateTupleDescCopyConstr(OldHeapDesc);

	OIDNewHeap = heap_create_with_catalog(NewName, tupdesc,
										  OldHeap->rd_rel->relkind,
										  OldHeap->rd_rel->relhasoids,
										  istemp,
										  allowSystemTableMods);

	/*
	 * Advance command counter so that the newly-created relation's
	 * catalog tuples will be visible to heap_open.
	 */
	CommandCounterIncrement();

	/*
	 * If necessary, create a TOAST table for the new relation. Note that
	 * AlterTableCreateToastTable ends with CommandCounterIncrement(), so
	 * that the TOAST table will be visible for insertion.
	 */
	AlterTableCreateToastTable(NewName, true);

	heap_close(OldHeap, NoLock);

	return OIDNewHeap;
}

static void
copy_index(Oid OIDOldIndex, Oid OIDNewHeap, char *NewIndexName)
{
	Relation	OldIndex,
				NewHeap;
	HeapTuple	Old_pg_index_Tuple,
				Old_pg_index_relation_Tuple;
	Form_pg_index Old_pg_index_Form;
	Form_pg_class Old_pg_index_relation_Form;
	IndexInfo  *indexInfo;

	NewHeap = heap_open(OIDNewHeap, AccessExclusiveLock);
	OldIndex = index_open(OIDOldIndex);

	/*
	 * Create a new index like the old one.  To do this I get the info
	 * from pg_index, and add a new index with a temporary name (that will
	 * be changed later).
	 *
	 * NOTE: index_create will cause the new index to be a temp relation if
	 * its parent table is, so we don't need to do anything special for
	 * the temp-table case here.
	 */
	Old_pg_index_Tuple = SearchSysCache(INDEXRELID,
										ObjectIdGetDatum(OIDOldIndex),
										0, 0, 0);
	Assert(Old_pg_index_Tuple);
	Old_pg_index_Form = (Form_pg_index) GETSTRUCT(Old_pg_index_Tuple);

	indexInfo = BuildIndexInfo(Old_pg_index_Tuple);

	Old_pg_index_relation_Tuple = SearchSysCache(RELOID,
										   ObjectIdGetDatum(OIDOldIndex),
												 0, 0, 0);
	Assert(Old_pg_index_relation_Tuple);
	Old_pg_index_relation_Form = (Form_pg_class) GETSTRUCT(Old_pg_index_relation_Tuple);

	index_create(RelationGetRelationName(NewHeap),
				 NewIndexName,
				 indexInfo,
				 Old_pg_index_relation_Form->relam,
				 Old_pg_index_Form->indclass,
				 Old_pg_index_Form->indisprimary,
				 allowSystemTableMods);

	setRelhasindex(OIDNewHeap, true,
				   Old_pg_index_Form->indisprimary, InvalidOid);

	ReleaseSysCache(Old_pg_index_Tuple);
	ReleaseSysCache(Old_pg_index_relation_Tuple);

	index_close(OldIndex);
	heap_close(NewHeap, NoLock);
}


static void
rebuildheap(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex)
{
	Relation	LocalNewHeap,
				LocalOldHeap,
				LocalOldIndex;
	IndexScanDesc ScanDesc;
	RetrieveIndexResult ScanResult;

	/*
	 * 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 = index_open(OIDOldIndex);

	ScanDesc = index_beginscan(LocalOldIndex, false, 0, (ScanKey) NULL);

	while ((ScanResult = index_getnext(ScanDesc, ForwardScanDirection)) != NULL)
	{
		HeapTupleData LocalHeapTuple;
		Buffer		LocalBuffer;

		CHECK_FOR_INTERRUPTS();

		LocalHeapTuple.t_self = ScanResult->heap_iptr;
		LocalHeapTuple.t_datamcxt = NULL;
		LocalHeapTuple.t_data = NULL;
		heap_fetch(LocalOldHeap, SnapshotNow, &LocalHeapTuple, &LocalBuffer,
				   ScanDesc);
		if (LocalHeapTuple.t_data != NULL)
		{
			/*
			 * We must copy the tuple because heap_insert() will overwrite
			 * the commit-status fields of the tuple it's handed, and the
			 * retrieved tuple will actually be in a disk buffer!  Thus,
			 * the source relation would get trashed, which is bad news if
			 * we abort later on.  (This was a bug in releases thru 7.0)
			 */
			HeapTuple	copiedTuple = heap_copytuple(&LocalHeapTuple);

			ReleaseBuffer(LocalBuffer);
			heap_insert(LocalNewHeap, copiedTuple);
			heap_freetuple(copiedTuple);
		}
		pfree(ScanResult);
	}

	index_endscan(ScanDesc);

	index_close(LocalOldIndex);
	heap_close(LocalOldHeap, NoLock);
	heap_close(LocalNewHeap, NoLock);
}
