-- File: RandomIndexTest.mesa
-- Contents: program to add and delete random numbers of CedarDB index entries
-- Last edited by
  -- Rick Cattell on September 20, 1982 2:38 pm

DIRECTORY
  DBStorage,
  DBTuplesConcrete,
  DBView,
  DBViewPrivate,
  IO,
  Process,
  Random,
  Rope,
  UserExec,
  UserExecExtras;
  
RandomIndexTest: PROGRAM IMPORTS
  DBStorage, DBView, DBViewPrivate, IO, Process, Random,
  Rope, UserExec, UserExecExtras  =

BEGIN ROPE: TYPE = Rope.ROPE;

in, out: IO.Handle;

keys: LIST OF ROPE← NIL;
value: DBStorage.TupleHandle;
keyListLength: INT← 0;
nIterations, maxInsertions: INT;
cycleCount: INT;
stopAtThisCycle: INT← 0;
pleaseStop: BOOL← FALSE;
paddingStringLength: INT← 5;
paddingString: ROPE;
index: DBStorage.IndexHandle;
prefixArray: ARRAY [0..50) OF ROPE;
prefixCount: INT← 10;
averageCommitFrequency: INT;

WhatNow: SIGNAL = CODE;

SystemTSTuple: TYPE = REF tupleSet DBTuplesConcrete.TupleObject;

TickTock: PROC =
  BEGIN
  cycleCount← cycleCount+1;
  IF Random.Choose[0, averageCommitFrequency] = 0 THEN 
    BEGIN -- close and re-set-up database
    out.PutF["Closing & Re-opening..."];
    DBView.CloseDatabase[];
    []← DBView.OpenDatabase[databaseName: dbName, version: OldOnly];
    value← index← DBViewPrivate.systemIndex;
    END;
  IF cycleCount=stopAtThisCycle THEN SIGNAL WhatNow;
  END;

GenRandomKey: PROC RETURNS [ROPE] =
  -- prefix random key with a randomly chosen rope, and follow with padding
  BEGIN
  prefix: ROPE ←
    IF prefixCount#0 THEN prefixArray[Random.Choose[0, prefixCount-1]]
    ELSE NIL;
  RETURN[IO.PutFR[
    "%g%g%g",
    IO.rope[prefix],
    IO.int[Random.Next[]],
    IO.rope[paddingString] ]];
  END;

GenPlainRandomKey: PROC RETURNS [ROPE] =
  -- same as above but without prefix, or padding after 
  {RETURN[IO.PutFR[, IO.int[Random.Next[]] ]]};



PlayWithIndex: PROC =
  BEGIN  
  out.PutF["Testing index...\n"];
  THROUGH [1 .. nIterations] DO 
    AddSomeEntries[];
    DeleteSomeEntries[];
    ENDLOOP;
  CheckEntries[];
  END;

AddSomeEntries: PROC =
    BEGIN
    key: ROPE;
    nToInsert: INT← Random.Choose[0, maxInsertions];	
    FOR j: INT IN [0..nToInsert) DO
      TickTock[];
      IF pleaseStop THEN RETURN;
      key← GenRandomKey[];
      DBStorage.InsertIntoIndex[index, key, value];
      keyListLength← keyListLength + 1;
      keys← CONS[key, keys];
      IF j MOD 10 = 0 THEN out.PutChar['+];
      ENDLOOP;
    out.PutF["... Added %g index entries (now %g)\n", IO.int[nToInsert], IO.int[keyListLength]];
    END;
  
DeleteSomeEntries: PROC =
    BEGIN
    prevKey: LIST OF ROPE;
    mDeleted, estToDelete: INT;
    estToDelete← Random.Choose[0, keyListLength];
    --  Delete approx estToDelete entries by deleting each one
    -- with probability of estToDelete/keyListLength:
    mDeleted← 0;
    prevKey← NIL;
    FOR keysT: LIST OF ROPE← keys, keysT.rest UNTIL keysT=NIL DO
      IF Random.Choose[0, keyListLength]<estToDelete THEN
        BEGIN
        TickTock[];
        IF mDeleted MOD 10 = 0 THEN out.PutChar['-];
        mDeleted← mDeleted+1;
        keyListLength← keyListLength-1;
        DBStorage.DeleteFromIndex[index, keysT.first, value];
        IF prevKey=NIL THEN
          keys← keys.rest -- first elt of list deleted
        ELSE
          prevKey.rest← keysT.rest; -- remove it from keys array
        END
      ELSE
        prevKey← keysT;
      ENDLOOP;
    out.PutF["... Deleted %g index entries (now %g)\n", IO.int[mDeleted], IO.int[keyListLength]];
    END;

CheckEntries: PROC =
  BEGIN
  count: INT← 0;
  ish: DBStorage.IndexScanHandle;
  t: DBStorage.TupleHandle;
  out.PutF["Checking contents of index..."];
  FOR keysT: LIST OF ROPE← keys, keysT.rest UNTIL keysT=NIL DO
    ish← DBStorage.OpenScanIndex[index, [keysT.first, keysT.first, TRUE, TRUE]];
    IF (t←DBStorage.NextScanIndex[ish])= NIL THEN ERROR; -- lost an index entry!
    IF NOT DBView.Eq[t, value] THEN ERROR; -- wrong value associated with it
    DBStorage.CloseScanIndex[ish];
    ENDLOOP;
  -- Check to see there are the right number of entries in the index
  ish← DBStorage.OpenScanIndex[index, ["", "\177", TRUE, TRUE]];
  UNTIL DBStorage.NextScanIndex[ish]=NIL DO
    count← count+1 ENDLOOP;
  DBStorage.CloseScanIndex[ish];
  -- Check that right number of entries in B-tree
  IF count#keyListLength+2 THEN ERROR;  -- add 2 because using owner index, contains 2 system entries 
  END;

AskForInt: PROC [question: ROPE, default: INT← -1] RETURNS [v: INT] =
  -- Ask user question, return int with default value if user hits CR.
  BEGIN
  IF default=-1 THEN
    out.PutF["%g? ", IO.rope[question]]
  ELSE
    out.PutF["%g [%g]? ", IO.rope[question], IO.int[default]];
  IF default#-1 AND in.PeekChar[]='\n THEN v← default
  ELSE v← in.GetInt[];
  []← in.GetChar[]; -- skip over the CR
  RETURN[v]
  END;

Main: PROC =
  BEGIN ENABLE Process.Aborted => GOTO Abort;
  keys← NIL;
  cycleCount← 15;
  keyListLength← 0;
  []← Random.Init[];
  FOR i: INT IN [0..prefixCount) DO
    prefixArray[i]← GenPlainRandomKey[] ENDLOOP;
  [in, out]← IO.CreateTTYStreams["IndexTest"];
  in← IO.CreateEditedStream[in, out];
  out.PutF["Random DBIndex Test Program: Creating database..."];
  []← DBView.OpenDatabase[databaseName: dbName, version: NewOnly];
  index← DBViewPrivate.systemIndex; -- use the system index already created by DBView
  value← index; -- just use the index tuple itself as a value to store, it doesn't matter for now!
  UserExecExtras.DoIt[UserExec.GetExecHandle[], "← DBIndexImpl.CheckFlag← TRUE"];
  UserExecExtras.DoIt[UserExec.GetExecHandle[], "← DBIndexImpl.deletionTurnedOn← TRUE"];
  nIterations← AskForInt["\nHow many iterations", 10];
  maxInsertions← AskForInt["Max insertions per iteration", 300];
  stopAtThisCycle← AskForInt["Stop at cycle", 99999];
  averageCommitFrequency← AskForInt["Mean re-open frequency", 300];
  paddingStringLength← AskForInt["Key padding", 5];
  paddingString← NIL;
  FOR j: INT IN [0.. paddingStringLength) DO
    paddingString← paddingString.Concat[" ..."] ENDLOOP;
  PlayWithIndex;
  out.PutF["Deleting index..."];
  DBStorage.DestroyIndex[index];
  out.PutF["Closing database..."];
  DBView.CloseDatabase[];
  out.PutF["Done.  Bye.\n"];
  EXITS Abort => {out.PutF["Aborting..."]; DBView.CloseDatabase[]; out.PutF["Done.  Bye.\n"]};
  END;

dbName: ROPE ← "[Local]IndexTest";

Start: PROC =
  {Process.Detach[FORK Main[]]};
  
Start[];

END.

To run:

run ExtraCedar
run CedarDB
compile RandomIndexTest
run RandomImpl
run RandomIndexTest