/* ----------------------------------------------------------------
 * testbtree.c --
 *	B-tree test code.
 * ----------------------------------------------------------------
 */

#include "fmgr.h"	/* for M_STATIC/M_DYNAMIC */

#include <stdio.h>

#include "c.h"

#include "attnum.h"
#include "attval.h"
#include "bufmgr.h"
#include "catname.h"
#include "datum.h"
#include "log.h"
#include "genam.h"
#include "heapam.h"
#include "itemptr.h"
#include "istrat.h"
#include "itup.h"
#include "name.h"
#include "oid.h"
#include "portal.h"
#include "sdir.h"
#include "tqual.h"
#include "valid.h"
#include "xcxt.h"

#include "btree.h"

RcsId("$Header: /private/postgres/src/test/RCS/testbtree.c,v 1.16 1992/03/04 14:10:47 hong Exp $");

/* ----------------
 *	constants
 * ----------------
 */

#define UninitializedQualification	0
#define SingleQualification		1
#define DoubleQualification		2
#define MarkedQualification		3

#define MarkedTuplesToProcess		64

static int		QualificationState;
ScanKeyEntryData	QualificationKeyData[2];

AttributeNumber		indexAttrNumber;
ObjectId	 	indexAttrClass;

/* ----------------------------------------------------------------
 *			   misc functions
 * ----------------------------------------------------------------
 */

/* ----------------
 *	DoCreateIndex
 * ----------------
 */

void FUNCTION
   DoCreateIndex(heapName, indexName, attribute, cls)
Name		heapName;
Name		indexName;
AttributeNumber attribute;
ObjectId	cls;
{
   AttributeNumber  attributeNumber[1];
   ObjectId	    attributeClass[1];

   elog(NOTICE, "DoCreateIndex... start");

   attributeNumber[0] = attribute;
   attributeClass[0] =  cls;

   RelationNameCreateIndexRelation(heapName,
				   indexName,
				   400 /* B-tree AM */,
				   1,
				   attributeNumber,
				   attributeClass,
				   0,
				   (Datum *) NULL);
   fflush(stdout);
   fflush(stderr);
   elog(NOTICE, "DoCreateIndex... end");

}

/* ----------------
 *	ShowResult
 * ----------------
 */

void FUNCTION
   ShowResult(result, heapRelation)
GeneralRetrieveIndexResult	result;
Relation			heapRelation;
{
   ItemPointer	pointer;

   Assert(GeneralRetrieveIndexResultIsValid(result));

   pointer = GeneralRetrieveIndexResultGetHeapItemPointer(result);

   printf("\t");

   if (!ItemPointerIsValid(pointer)) {
      printf("<invalid>\n");
   } else {
      HeapTuple	tuple;
      Buffer	buffer;

      printf("[b,p,o %d, %d, %3d] ",
	     ItemPointerGetBlockNumber(pointer),
	     ItemPointerSimpleGetPageNumber(pointer),
	     ItemPointerSimpleGetOffsetNumber(pointer));

      tuple =
	 RelationGetHeapTupleByItemPointer(heapRelation,
			NowTimeQual,
					   pointer,
					   &buffer);

      if (!HeapTupleIsValid(tuple)) {
	 printf("*NULL*\n");

      } else {
	 TupleDescriptor	descriptor;
	 AttributeValue		value;
	 Boolean		valueIsNull;

	 descriptor = RelationGetTupleDescriptor(heapRelation);

	 /* We might want to print more than just the index att, here */
	 value =
	    HeapTupleGetAttributeValue(tuple,
				       buffer,
				       indexAttrNumber,
				       descriptor,
				       &valueIsNull);

	 if (valueIsNull) {
	    printf("<NULL>\n");
	 } else {
	    printf("<0x%x(%d)>\n",
		   DatumGetObjectId(value), 
		   DatumGetObjectId(value));
	 }

	 ReleaseBuffer(buffer);
      }
   }
}

/* ----------------
 *	ShowScanKeyEntry
 * ----------------
 */

void FUNCTION
   ShowScanKeyEntry(entry)
ScanKeyEntry	entry;
{
   printf("Qualification is procedure 0x%x(%d) for %d with 0x%x\n",
	  entry->procedure,
	  entry->procedure,
	  DatumGetObjectId(entry->argument),
	  entry->flags);
}

/* ----------------------------------------------------------------
 *			browse functions
 * ----------------------------------------------------------------
 */

/* ----------------
 *	DoBTreeBrowse
 * ----------------
 */

/*ARGSUSED*/
void FUNCTION
   DoBTreeBrowse(indexRelation, heapRelation)
Relation	indexRelation;
Relation	heapRelation;
{
   BTreeNode   node;
   BlockNumber blockNumber;
   PageNumber  pageNumber;
   int cnt;

   puts("\n--- BTree Browse ---");
   puts("at the prompt, enter a block number and a page number");
   puts("separated by a space or \".\" alone to end\n");

   forever {
      Puts("browse> ");
      fflush(stdout);

      cnt = scanf("%ld %hd", &blockNumber, &pageNumber);
      if (cnt != 2) {
	 puts("\n--- ending BTree Browse ---");
	 fseek(stdin, 0, 2);
	 break;
      }

      node = RelationFormBTreeNode(indexRelation, blockNumber, pageNumber);
      DumpBTreeNode(node);
      BTreeNodeFree(node);
      putchar('\n');
   }
}

/* ----------------------------------------------------------------
 *			  scan functions
 * ----------------------------------------------------------------
 */

/* ----------------
 *	DoForwardScan
 * ----------------
 */

void FUNCTION
   DoForwardScan(indexRelation, heapRelation)
Relation	indexRelation;
Relation	heapRelation;
{
   IndexScanDesc		scan;
   GeneralRetrieveIndexResult	result;

   puts("A complete forward scan of the index reveals...");

   scan = RelationGetIndexScan(indexRelation, 0, 0, (ScanKey) NULL);

   for (;;) {
      result = AMgettuple(scan, 1);

      if (!GeneralRetrieveIndexResultIsValid(result))
	 break;

	 ShowResult(result, heapRelation);
   }

   AMendscan(scan);
}

/* ----------------
 *	DoBackwardScan
 * ----------------
 */

void FUNCTION
   DoBackwardScan(indexRelation, heapRelation)
Relation	indexRelation;
Relation	heapRelation;
{
   IndexScanDesc		scan;
   GeneralRetrieveIndexResult	result;

   puts("A complete reverse scan of the index reveals...");

   scan = RelationGetIndexScan(indexRelation, -1, 0, (ScanKey) NULL);

   for (;;) {
      result = AMgettuple(scan, -1);

      if (!GeneralRetrieveIndexResultIsValid(result))
	 break;

	 ShowResult(result, heapRelation);
   }

   AMendscan(scan);
}


/* ----------------
 *	DoSingleQualification
 * ----------------
 */

void FUNCTION
   DoSingleQualification(indexRelation, heapRelation, entry)
Relation	indexRelation;
Relation	heapRelation;
ScanKeyEntry	entry;
{
   IndexScanDesc		scan;
   GeneralRetrieveIndexResult	result;

   QualificationKeyData[0] = *entry;

   ShowScanKeyEntry(&QualificationKeyData[0]);
   puts("A scan of the index with one qualification reveals...");

   scan = RelationGetIndexScan(indexRelation, 0, 1,
			       (ScanKey)QualificationKeyData);

   for (;;) {
      result = AMgettuple(scan, 1);

      if (!GeneralRetrieveIndexResultIsValid(result))
	 break;

	 ShowResult(result, heapRelation);
   }

   AMendscan(scan);
}


/* ----------------
 *	DoDoubleQualification
 * ----------------
 */

void FUNCTION
   DoDoubleQualification(indexRelation, heapRelation, entry)
Relation	indexRelation;
Relation	heapRelation;
ScanKeyEntry	entry;
{
   IndexScanDesc		scan;
   GeneralRetrieveIndexResult	result;

   QualificationKeyData[1] = *entry;

   ShowScanKeyEntry(&QualificationKeyData[0]);
   ShowScanKeyEntry(&QualificationKeyData[1]);
   puts("A scan of the index with two qualifications reveals...");

   scan = RelationGetIndexScan(indexRelation, 0, 2,
			       (ScanKey)QualificationKeyData);
   for (;;) {
      result = AMgettuple(scan, 1);

      if (!GeneralRetrieveIndexResultIsValid(result))
	 break;

	 ShowResult(result, heapRelation);
   }
   AMendscan(scan);
}

/* ----------------
 *	DoMarkedQualification
 * ----------------
 */

void
DoMarkedQualification(indexRelation, heapRelation, entry)
   Relation	indexRelation;
   Relation	heapRelation;
   ScanKeyEntry	entry;
{
   IndexScanDesc		scan;
   GeneralRetrieveIndexResult	result;
   int				tuplesLeft;
   bool				markIsSet;
   int				whichWay;

   tuplesLeft = MarkedTuplesToProcess;
   markIsSet = false;

   srandom((int)time(0));

   QualificationKeyData[0] = *entry;

   ShowScanKeyEntry(&QualificationKeyData[0]);
   puts("A scan of the index reveals...");

   scan = RelationGetIndexScan(indexRelation, (bool) (0x1 & random()), 1,
			       (ScanKey)QualificationKeyData);

   while (tuplesLeft > 0) {
      if (!(random() & 0xf)) {
	 printf("RESTARTING SCAN\n");
	 AMrescan(scan, (bool) (0x1 & random()), (ScanKey)QualificationKeyData);
	 markIsSet = false;
      }
      if (!(random() & 0x3)) {
	 if (markIsSet) {
	    printf("RESTORING MARK\n");
	    AMrestrpos(scan);
	    if (!(0x1 & random())) {
	       markIsSet = false;
	    }
	 } else {
	    printf("SET MARK\n");
	    AMmarkpos(scan);
	    markIsSet = true;
	 }
      }

      if ((random() & 0x1) == 0x1)
	 whichWay = -1;		/* backward */
      else
	 whichWay = 1;		/* forward */

      result = AMgettuple(scan, whichWay);

      if (!GeneralRetrieveIndexResultIsValid(result)) {
	 puts("\t*NULL*");
      } else {
	 ShowResult(result, heapRelation);
      }

      tuplesLeft -= 1;
   }
   AMendscan(scan);
}

/* ----------------------------------------------------------------
 *	DoQualifiedScan
 * ----------------------------------------------------------------
 */

void
DoQualifiedScan(indexRelation, heapRelation, operation, flags, returnType)
   Relation	indexRelation;
   Relation	heapRelation;
   char		*operation;
   uint16		flags;
   ObjectId	returnType;
{
   ScanKeyEntry	scanKeyEntry;
   extern ScanKeyEntry	GetScanKeyEntry();

   scanKeyEntry = GetScanKeyEntry(indexRelation, operation);

   /*
    * Note: very dangerous to modify the reldesc, since it is cached.
    */
   scanKeyEntry->attributeNumber = 1;
   scanKeyEntry->flags = flags;
   scanKeyEntry->argument = ObjectIdGetDatum(returnType);

   switch (QualificationState) {
   case UninitializedQualification:
   case MarkedQualification:
      QualificationState = SingleQualification;
      DoSingleQualification(indexRelation, heapRelation,
			    scanKeyEntry);
      break;
   case SingleQualification:
      QualificationState = DoubleQualification;
      DoDoubleQualification(indexRelation, heapRelation,
			    scanKeyEntry);
      break;
   case DoubleQualification:
      QualificationState = MarkedQualification;
      DoMarkedQualification(indexRelation, heapRelation,
			    scanKeyEntry);
      break;
   default:
      fprintf(stderr, "testbtree: internal error!");
      exitpg(255);
   }
}

void
DoRuleLockInsertion(indexRelation)
	Relation	indexRelation;
{
	BTreeSearchKey		v_left, v_right;
	IndexTuple		itup_left, itup_right;
	TupleDescriptor		idesc;
	Datum			datum[1];
	Boolean			null[1];
	ScanKeyEntry		skeyEntry;
	ScanKeyData		myLeftScanKey[1];
	ScanKeyData		myRightScanKey[1];
	BTreeInsertDataData	id_left, id_right;

	idesc = RelationGetTupleDescriptor(indexRelation);

	id_left.type  = BTREE_LEAF_INSERT_DATA | BTREE_RLOCK_L_INSERT_DATA;
	id_right.type = BTREE_LEAF_INSERT_DATA | BTREE_RLOCK_R_INSERT_DATA;

	for (;;) {
		char opname[10];
		int opkey;

		/* clear out the input stream */
		fseek(stdin, 0, 2);

		fprintf(stdout, "Enter left operator/key pair: ");
		fflush(stdout);
		if (scanf("%s%d", &opname[0], &opkey) != 2)
			break;

		datum[0] = Int32GetDatum(opkey);
		null[0] = ' ';

		itup_left = FormIndexTuple(1, idesc, datum, null);
		id_left.indexTuple = itup_left;
		id_left.size = psize(itup_left);

		skeyEntry = GetScanKeyEntry(indexRelation, opname);
		myLeftScanKey[0].data[0] = *skeyEntry;
		myLeftScanKey[0].data[0].attributeNumber = indexAttrNumber;
		myLeftScanKey[0].data[0].argument = Int32GetDatum(opkey);

		v_left = RelationFormBTreeSearchKey(indexRelation,
						    &id_left,
						    1,
						    &myLeftScanKey[0],
						    NoMovementScanDirection,
						    (ItemPointer) NULL);

		fprintf(stdout, "Enter right operator/key pair: ");
		fflush(stdout);
		scanf("%s%d", &opname[0], &opkey);

		datum[0] = Int32GetDatum(opkey);
		null[0] = ' ';

		itup_right = FormIndexTuple(1, idesc, datum, null);
		id_right.indexTuple = itup_right;
		id_right.size = psize(itup_right);

		skeyEntry = GetScanKeyEntry(indexRelation, opname);
		myRightScanKey[0].data[0] = *skeyEntry;
		myRightScanKey[0].data[0].attributeNumber = indexAttrNumber;
		myRightScanKey[0].data[0].argument = Int32GetDatum(opkey);

		v_right = RelationFormBTreeSearchKey(indexRelation,
						     &id_right,
						     1,
						     &myRightScanKey[0],
						     NoMovementScanDirection,
						     (ItemPointer) NULL);

		BTreeRuleLockInsert(v_left, v_right);
	}
}

ScanKeyEntry
GetScanKeyEntry(indexRelation, operation)
	Relation	indexRelation;
	char		*operation;
{
	StrategyNumber       strategyNumber;
	IndexStrategy        indexStrategy;
	StrategyMap          strategyMap;
	ScanKeyEntry         scanKeyEntry;

	if (strcmp(operation, "<") == 0) {	
		strategyNumber = BTreeLessThanStrategyNumber;
	} else if (strcmp(operation, "<=") == 0) {
		strategyNumber = BTreeLessThanOrEqualStrategyNumber;
	} else if (strcmp(operation, "=") == 0) {
		strategyNumber = BTreeEqualStrategyNumber;
	} else if (strcmp(operation, ">=") == 0) {
		strategyNumber = BTreeGreaterThanOrEqualStrategyNumber;
	} else if (strcmp(operation, ">") == 0) {
		strategyNumber = BTreeGreaterThanStrategyNumber;
	} else {
		fprintf(stderr, "testbtree: unknown operation \"%s\"\n",
			operation);
		return ((ScanKeyEntry) NULL);
	}

	indexStrategy = RelationGetIndexStrategy(indexRelation);
	strategyMap = IndexStrategyGetStrategyMap(indexStrategy,
						  BTreeNumberOfStrategies, 1);
	scanKeyEntry = StrategyMapGetScanKeyEntry(strategyMap, strategyNumber);

	if (!RegProcedureIsValid(scanKeyEntry->procedure)) {
		fprintf(stderr, "testbtree: no procedure for strategy %d\n",
			strategyNumber);

		return ((ScanKeyEntry) NULL);
	}

	return (scanKeyEntry);
}


/* ----------------------------------------------------------------
 *			     TestMain
 * ----------------------------------------------------------------
 */

void FUNCTION
TestMain()
{
   static NameData	heapNameData;
   static NameData 	indexNameData;
          Relation	indexRelation;
          Relation	heapRelation;
   static int	        beenHere = 0;
	  Portal	portal;


   Relation		AMopenr();	/* XXX */


   portal = CreatePortal("<blank>");
   StartTransactionCommand(portal);

   if (beenHere == 0) {
      char *result;
      bool newIndex;
      bool done;

      /* discard any stuff on the input stream */
      fseek(stdin, 0, 2);

      fprintf(stdout, "Relation: ");
      fflush(stdout);

      if ((result = gets(&heapNameData.data[0])) == (char *) NULL) {
	 elog(NOTICE, "testbtree: no heap specified");
	 exitpg(0);
      }

      fprintf(stdout, "Index name: ");
      fflush(stdout);

      if ((result = gets(&indexNameData.data[0])) == (char *) NULL) {
	 elog(NOTICE, "testbtree: no index specified");
	 exitpg(0);
      }

      do {
	 char buf[20];

	 fprintf(stdout, "Is this a new index? (y/n) ");
	 fflush(stdout);

	 result = gets(&buf[0]);

	 if (result != (char *) NULL) {
	    if (strcmp(result, "y") == 0) {
	       newIndex = true;
	       done = true;
	    } else if (strcmp(result, "n") == 0) {
	       newIndex = false;
	       done = true;
	    } else {
	       fprintf(stdout, "*** y or n only ***\n");
	       done = false;
	    }
	 } else {
	    fprintf(stdout, "*** y or n only ***\n");
	    done = false;
	 }
      } while (!done);

      fprintf(stdout, "Attribute number for index: ");
      fflush(stdout);

      if (scanf("%hd", &indexAttrNumber) != 1) {
	 elog(NOTICE, "testbtree: attribute incorrectly specified");
	 exitpg(0);
      }

      if (newIndex) {
	 fprintf(stdout, "Operator class (421 = int2, 426 = int4): ");
	 fflush(stdout);

         if (scanf("%hd", &indexAttrClass) != 1) {
	    elog(NOTICE, "testbtree: class incorrectly specified");
	    exitpg(0);
         }
	 if (indexAttrClass != 421 && indexAttrClass != 426) {
	    elog(NOTICE, "No such class, assuming int2.");
	    indexAttrClass = 426;
	 }

	 fseek(stdin, 0, 2);

	 beenHere = 1;

	 DoCreateIndex(&heapNameData, &indexNameData,
		       indexAttrNumber, indexAttrClass);

      }

      CommitTransactionCommand();
      StartTransactionCommand(portal);

      beenHere = 2;

   } else if (beenHere == 1) {
      elog(NOTICE, "testbtree: %s relation may exist--continuing!",
	   indexNameData.data);
      beenHere = 2;

   } else if (beenHere > 1) {
      elog(FATAL, "testbtree: giving up!");
   }

   indexRelation = AMopenr(indexNameData.data);	/* XXX */
   DoRuleLockInsertion(indexRelation);
   AMclose(indexRelation);

   QualificationState = UninitializedQualification;

   indexRelation = AMopenr(indexNameData.data);	/* XXX */
   heapRelation = RelationNameOpenHeapRelation(heapNameData.data);

   RelationSetLockForRead(heapRelation);

   DoBTreeBrowse(indexRelation, heapRelation);
   DoForwardScan(indexRelation, heapRelation);
   DoBackwardScan(indexRelation, heapRelation);

   for (;;) {
      int		status;
      NameData		operatorNameData;
      ObjectId		returnType;

      puts("\nPlease enter an operator and an integer value");

      fseek(stdin, 0, 2);	/* discard any stuff on the input stream */
      status = scanf("%s%d", &(operatorNameData), &returnType);

      if (status <= 0) {
	 break;
      } else if (status != 2) {
	 elog(NOTICE, "testbtree: improper qualification specified");
	 exitpg(0);
      }

      DoQualifiedScan(indexRelation, heapRelation, &operatorNameData,
		      0, returnType);
   }

   RelationCloseHeapRelation(heapRelation);
   RelationCloseIndexRelation(indexRelation);

   puts("\nDone!");
   CommitTransactionCommand();
   exitpg(0);
}
