/*
   Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
*/

#include <NDBT_Test.hpp>
#include <NDBT_ReturnCodes.h>
#include <HugoTransactions.hpp>
#include <UtilTransactions.hpp>
#include <NdbRestarter.hpp>
#include <signaldata/DictTabInfo.hpp>
#include <Bitmask.hpp>
#include <random.h>
#include <signaldata/DumpStateOrd.hpp>

/**
 * TODO 
 *  dirtyWrite, write, dirtyUpdate
 *  delete should be visible to same transaction
 *  
 */
int runLoadTable2(NDBT_Context* ctx, NDBT_Step* step)
{
  int records = ctx->getNumRecords();
  HugoTransactions hugoTrans(*ctx->getTab());
  if (hugoTrans.loadTable(GETNDB(step), records, 512, false, 0, true) != 0){
    return NDBT_FAILED;
  }
  return NDBT_OK;
}

int runLoadTable(NDBT_Context* ctx, NDBT_Step* step)
{
  int records = ctx->getNumRecords();
  HugoTransactions hugoTrans(*ctx->getTab());
  if (hugoTrans.loadTable(GETNDB(step), records) != 0){
    return NDBT_FAILED;
  }
  return NDBT_OK;
}

int runInsert(NDBT_Context* ctx, NDBT_Step* step){

  int records = ctx->getNumRecords();
  HugoTransactions hugoTrans(*ctx->getTab());
  // Insert records, dont allow any 
  // errors(except temporary) while inserting
  if (hugoTrans.loadTable(GETNDB(step), records, 1, false) != 0){
    return NDBT_FAILED;
  }
  return NDBT_OK;
}

int runInsertTwice(NDBT_Context* ctx, NDBT_Step* step){

  int records = ctx->getNumRecords();
  HugoTransactions hugoTrans(*ctx->getTab());
  // Insert records, expect primary key violation 630
  if (hugoTrans.loadTable(GETNDB(step), records, 1, false) != 630){
    return NDBT_FAILED;
  }
  return NDBT_OK;
}

int runVerifyInsert(NDBT_Context* ctx, NDBT_Step* step){
  int records = ctx->getNumRecords();
  
  HugoTransactions hugoTrans(*ctx->getTab());
  if (hugoTrans.pkDelRecords(GETNDB(step),  records, 1, false) != 0){
    return NDBT_FAILED;
  }
  return NDBT_OK;
}

int runInsertUntilStopped(NDBT_Context* ctx, NDBT_Step* step){
  int records = ctx->getNumRecords();
  int i = 0;
  HugoTransactions hugoTrans(*ctx->getTab());
  while (ctx->isTestStopped() == false) {
    g_info << i << ": ";    
    if (hugoTrans.loadTable(GETNDB(step), records) != 0){
      g_info << endl;
      return NDBT_FAILED;
    }
    i++;
  }
  g_info << endl;
  return NDBT_OK;
}

int runClearTable(NDBT_Context* ctx, NDBT_Step* step){
  int records = ctx->getNumRecords();
  int batchSize = ctx->getProperty("BatchSize", 1);
  
  HugoTransactions hugoTrans(*ctx->getTab());
  if (hugoTrans.pkDelRecords(GETNDB(step),  records, batchSize) != 0){
    return NDBT_FAILED;
  }
  return NDBT_OK;
}

int runPkDelete(NDBT_Context* ctx, NDBT_Step* step){
  int loops = ctx->getNumLoops();
  int records = ctx->getNumRecords();

  int i = 0;
  HugoTransactions hugoTrans(*ctx->getTab());
  while (i<loops) {
    g_info << i << ": ";
    if (hugoTrans.pkDelRecords(GETNDB(step),  records) != 0){
      g_info << endl;
      return NDBT_FAILED;
    }
    // Load table, don't allow any primary key violations
    if (hugoTrans.loadTable(GETNDB(step), records, 512, false) != 0){
      g_info << endl;
      return NDBT_FAILED;
    }
    i++;
  }  
  g_info << endl;
  return NDBT_OK;
}


int runPkRead(NDBT_Context* ctx, NDBT_Step* step){
  int loops = ctx->getNumLoops();
  int records = ctx->getNumRecords();
  int batchSize = ctx->getProperty("BatchSize", 1);
  int lm = ctx->getProperty("LockMode", NdbOperation::LM_Read);
  int i = 0;
  HugoTransactions hugoTrans(*ctx->getTab());
  while (i<loops) {
    g_info << i << ": ";
    if (hugoTrans.pkReadRecords(GETNDB(step), records, batchSize,
                                (NdbOperation::LockMode)lm) != NDBT_OK){
      g_info << endl;
      return NDBT_FAILED;
    }
    i++;
  }
  g_info << endl;
  return NDBT_OK;
}

int runPkReadUntilStopped(NDBT_Context* ctx, NDBT_Step* step){
  int records = ctx->getNumRecords();
  int batchSize = ctx->getProperty("BatchSize", 1);
  int i = 0;
  HugoTransactions hugoTrans(*ctx->getTab());
  while (ctx->isTestStopped() == false) {
    g_info << i << ": ";
    if (hugoTrans.pkReadRecords(GETNDB(step), records, batchSize) != 0){
      g_info << endl;
      return NDBT_FAILED;
    }
    i++;
  }
  g_info << endl;
  return NDBT_OK;
}

int runPkUpdate(NDBT_Context* ctx, NDBT_Step* step){
  int loops = ctx->getNumLoops();
  int records = ctx->getNumRecords();
  int batchSize = ctx->getProperty("BatchSize", 1);
  int i = 0;
  HugoTransactions hugoTrans(*ctx->getTab());
  while (i<loops) {
    g_info << "|- " << i << ": ";
    if (hugoTrans.pkUpdateRecords(GETNDB(step), records, batchSize) != 0){
      g_info << endl;
      return NDBT_FAILED;
    }
    i++;
  }
  g_info << endl;
  return NDBT_OK;
}

int runPkUpdateUntilStopped(NDBT_Context* ctx, NDBT_Step* step){
  int records = ctx->getNumRecords();
  int batchSize = ctx->getProperty("BatchSize", 1);
  int i = 0;
  HugoTransactions hugoTrans(*ctx->getTab());
  while (ctx->isTestStopped()) {
    g_info << i << ": ";
    if (hugoTrans.pkUpdateRecords(GETNDB(step), records, batchSize) != 0){
      g_info << endl;
      return NDBT_FAILED;
    }
    i++;
  }
  g_info << endl;
  return NDBT_OK;
}

int runLocker(NDBT_Context* ctx, NDBT_Step* step){
  int result = NDBT_OK;
  int records = ctx->getNumRecords();
  HugoTransactions hugoTrans(*ctx->getTab());
  
  if (hugoTrans.lockRecords(GETNDB(step), records, 10, 500) != 0){
    result = NDBT_FAILED;
  }
  ctx->stopTest();
  
  return result;
}

int
runInsertOne(NDBT_Context* ctx, NDBT_Step* step){
  
  if(ctx->getProperty("InsertCommitted", (Uint32)0) != 0){
    abort();
  }

  while(ctx->getProperty("Read1Performed", (Uint32)0) == 0){
    NdbSleep_MilliSleep(20);
  }
  
  HugoTransactions hugoTrans(*ctx->getTab());
  
  if (hugoTrans.loadTable(GETNDB(step), 1, 1) != 0){
    return NDBT_FAILED;
  }

  ctx->setProperty("InsertCommitted", 1);

  NdbSleep_SecSleep(2);

  return NDBT_OK;
}

static
int
readOneNoCommit(Ndb* pNdb, NdbConnection* pTrans, 
		const NdbDictionary::Table* tab,NDBT_ResultRow * row){
  int a;
  NdbOperation * pOp = pTrans->getNdbOperation(tab->getName());
  if (pOp == NULL){
    ERR(pTrans->getNdbError());
    return NDBT_FAILED;
  }
  
  HugoTransactions tmp(*tab);

  int check = pOp->readTuple();
  if( check == -1 ) {
    ERR(pTrans->getNdbError());
    return NDBT_FAILED;
  }
  
  // Define primary keys
  for(a = 0; a<tab->getNoOfColumns(); a++){
    if (tab->getColumn(a)->getPrimaryKey() == true){
      if(tmp.equalForAttr(pOp, a, 0) != 0){
	ERR(pTrans->getNdbError());
	return NDBT_FAILED;
      }
    }
  }
  
  // Define attributes to read  
  for(a = 0; a<tab->getNoOfColumns(); a++){
    if((row->attributeStore(a) = 
	pOp->getValue(tab->getColumn(a)->getName())) == 0) {
      ERR(pTrans->getNdbError());
      return NDBT_FAILED;
    }
  }

  check = pTrans->execute(NoCommit);     
  if( check == -1 ) {
    const NdbError err = pTrans->getNdbError(); 
    ERR(err);
    return err.code;
  }
  return NDBT_OK;
}

int
runReadOne(NDBT_Context* ctx, NDBT_Step* step){

  Ndb* pNdb = GETNDB(step);
  const NdbDictionary::Table* tab = ctx->getTab();
  NDBT_ResultRow row1(*tab);
  NDBT_ResultRow row2(*tab);  

  if(ctx->getProperty("Read1Performed", (Uint32)0) != 0){
    abort();
  }

  if(ctx->getProperty("InsertCommitted", (Uint32)0) != 0){
    abort();
  }
  
  NdbConnection * pTrans = pNdb->startTransaction();
  if (pTrans == NULL) {
    abort();
  }    

  // Read a record with NoCommit
  // Since the record isn't inserted yet it wil return 626
  const int res1 = readOneNoCommit(pNdb, pTrans, tab, &row1);
  g_info << "|- res1 = " << res1 << endl;

  ctx->setProperty("Read1Performed", 1);
  
  while(ctx->getProperty("InsertCommitted", (Uint32)0) == 0 && 
	!ctx->isTestStopped()){
    g_info << "|- Waiting for insert" << endl;
    NdbSleep_MilliSleep(20);
  }
  
  if(ctx->isTestStopped()){
    abort();
  }

  // Now the record should have been inserted
  // Read it once again in the same transaction
  // Should also reutrn 626 if reads are consistent

  // NOTE! Currently it's not possible to start a new operation
  // on a transaction that has returned an error code
  // This is wat fail in this test
  // MASV 20030624
  const int res2 = readOneNoCommit(pNdb, pTrans, tab, &row2);

  pTrans->execute(Commit);
  pNdb->closeTransaction(pTrans);
  g_info << "|- res2 = " << res2 << endl;

  if (res2 == 626 && res1 == res2)    
    return NDBT_OK;
  else
    return NDBT_FAILED;
}

int runFillTable(NDBT_Context* ctx, NDBT_Step* step){
  int batch = 512; //4096;
  HugoTransactions hugoTrans(*ctx->getTab());
  if (hugoTrans.fillTable(GETNDB(step), batch ) != 0){
    return NDBT_FAILED;
  }
  return NDBT_OK;
}

int runClearTable2(NDBT_Context* ctx, NDBT_Step* step){
  int records = ctx->getNumRecords();
  
  UtilTransactions utilTrans(*ctx->getTab());
  if (utilTrans.clearTable2(GETNDB(step), records, 240) != 0){
    return NDBT_FAILED;
  }
  return NDBT_OK;
}

#define CHECK(b) if (!(b)) { \
  ndbout << "ERR: "<< step->getName() \
         << " failed on line " << __LINE__ << endl; \
  result = NDBT_FAILED; \
  break; }

int runNoCommitSleep(NDBT_Context* ctx, NDBT_Step* step){
  int result = NDBT_OK;
  HugoOperations hugoOps(*ctx->getTab());
  Ndb* pNdb = GETNDB(step);
  int sleepTime = 100; // ms
  for (int i = 2; i < 8; i++){

    CHECK(hugoOps.startTransaction(pNdb) == 0);
    CHECK(hugoOps.pkReadRecord(pNdb, 1, 1, NdbOperation::LM_Exclusive) == 0);
    CHECK(hugoOps.execute_NoCommit(pNdb) == 0);

    ndbout << i <<": Sleeping for " << sleepTime << " ms" << endl;
    NdbSleep_MilliSleep(sleepTime);

    // Dont care about result of these ops
    hugoOps.pkReadRecord(pNdb, 1, 1, NdbOperation::LM_Exclusive);
    hugoOps.closeTransaction(pNdb);

    sleepTime = sleepTime *i;
  }

  hugoOps.closeTransaction(pNdb);

  return result;
}

int runCommit626(NDBT_Context* ctx, NDBT_Step* step){
  int result = NDBT_OK;
  HugoOperations hugoOps(*ctx->getTab());
  Ndb* pNdb = GETNDB(step);

  do{
    // Commit transaction
    CHECK(hugoOps.startTransaction(pNdb) == 0);
    CHECK(hugoOps.pkReadRecord(pNdb, 1, 1, NdbOperation::LM_Exclusive) == 0);
    CHECK(hugoOps.execute_Commit(pNdb) == 626);
    CHECK(hugoOps.closeTransaction(pNdb) == 0);

    // Commit transaction
    // Multiple operations
    CHECK(hugoOps.startTransaction(pNdb) == 0);
    CHECK(hugoOps.pkReadRecord(pNdb, 1, 1, NdbOperation::LM_Exclusive) == 0);
    CHECK(hugoOps.pkReadRecord(pNdb, 2, 1, NdbOperation::LM_Exclusive) == 0);
    CHECK(hugoOps.pkReadRecord(pNdb, 3, 1, NdbOperation::LM_Exclusive) == 0);
    CHECK(hugoOps.execute_Commit(pNdb) == 626);
  }while(false);

  hugoOps.closeTransaction(pNdb);
  
  return result;
}

int runCommit630(NDBT_Context* ctx, NDBT_Step* step){
  int result = NDBT_OK;
  HugoOperations hugoOps(*ctx->getTab());
  Ndb* pNdb = GETNDB(step);

  do{
    // Commit transaction
    CHECK(hugoOps.startTransaction(pNdb) == 0);
    CHECK(hugoOps.pkInsertRecord(pNdb, 1) == 0);
    CHECK(hugoOps.execute_Commit(pNdb) == 630);
  }while(false);

  hugoOps.closeTransaction(pNdb);

  return result;
}

int runCommit_TryCommit626(NDBT_Context* ctx, NDBT_Step* step){
  int result = NDBT_OK;
  HugoOperations hugoOps(*ctx->getTab());
  Ndb* pNdb = GETNDB(step);

  do{
    // Commit transaction, TryCommit
    CHECK(hugoOps.startTransaction(pNdb) == 0);
    CHECK(hugoOps.pkReadRecord(pNdb, 1, 1, NdbOperation::LM_Exclusive) == 0);
    CHECK(hugoOps.execute_Commit(pNdb, TryCommit) == 626);
    CHECK(hugoOps.closeTransaction(pNdb) == 0);

    // Commit transaction, TryCommit
    // Several operations in one transaction
    // The insert is OK
    CHECK(hugoOps.startTransaction(pNdb) == 0);
    CHECK(hugoOps.pkReadRecord(pNdb, 1, 1, NdbOperation::LM_Exclusive) == 0);
    CHECK(hugoOps.pkReadRecord(pNdb, 2, 1, NdbOperation::LM_Exclusive) == 0);
    CHECK(hugoOps.pkReadRecord(pNdb, 3, 1, NdbOperation::LM_Exclusive) == 0);
    CHECK(hugoOps.pkInsertRecord(pNdb, 1) == 0);
    CHECK(hugoOps.pkReadRecord(pNdb, 4, 1, NdbOperation::LM_Exclusive) == 0);
    CHECK(hugoOps.execute_Commit(pNdb, TryCommit) == 626);
  }while(false);

  hugoOps.closeTransaction(pNdb);

  return result;
}

int runCommit_TryCommit630(NDBT_Context* ctx, NDBT_Step* step){
  int result = NDBT_OK;
  HugoOperations hugoOps(*ctx->getTab());
  Ndb* pNdb = GETNDB(step);
  
  do{
    // Commit transaction, TryCommit
    CHECK(hugoOps.startTransaction(pNdb) == 0);
    CHECK(hugoOps.pkInsertRecord(pNdb, 1) == 0);
    CHECK(hugoOps.execute_Commit(pNdb, TryCommit) == 630);
  }while(false);
  
  hugoOps.closeTransaction(pNdb);
  
  return result;
}

int runCommit_CommitAsMuchAsPossible626(NDBT_Context* ctx, NDBT_Step* step){
  int result = NDBT_OK;
  HugoOperations hugoOps(*ctx->getTab());
  Ndb* pNdb = GETNDB(step);

  do{
    // Commit transaction, CommitAsMuchAsPossible
    CHECK(hugoOps.startTransaction(pNdb) == 0);
    CHECK(hugoOps.pkReadRecord(pNdb, 1, 1, NdbOperation::LM_Exclusive) == 0);
    CHECK(hugoOps.execute_Commit(pNdb, CommitAsMuchAsPossible) == 626);
    CHECK(hugoOps.closeTransaction(pNdb) == 0);

    // Commit transaction, CommitAsMuchAsPossible
    CHECK(hugoOps.startTransaction(pNdb) == 0);
    CHECK(hugoOps.pkReadRecord(pNdb, 2, 1, NdbOperation::LM_Exclusive) == 0);
    CHECK(hugoOps.pkReadRecord(pNdb, 3, 1, NdbOperation::LM_Exclusive) == 0);
    CHECK(hugoOps.pkInsertRecord(pNdb, 1) == 0);
    CHECK(hugoOps.execute_Commit(pNdb, CommitAsMuchAsPossible) == 626);
    CHECK(hugoOps.closeTransaction(pNdb) == 0);

    CHECK(hugoOps.startTransaction(pNdb) == 0);
    CHECK(hugoOps.pkReadRecord(pNdb, 1) == 0);
    CHECK(hugoOps.execute_Commit(pNdb) == 0);
    CHECK(hugoOps.closeTransaction(pNdb) == 0);
  } while(false);

  hugoOps.closeTransaction(pNdb);

  return result;
}

int runCommit_CommitAsMuchAsPossible630(NDBT_Context* ctx, NDBT_Step* step){
  int result = NDBT_OK;
  HugoOperations hugoOps(*ctx->getTab());
  Ndb* pNdb = GETNDB(step);

  do{
    // Commit transaction, CommitAsMuchAsPossible
    CHECK(hugoOps.startTransaction(pNdb) == 0);
    CHECK(hugoOps.pkInsertRecord(pNdb, 1) == 0);
    CHECK(hugoOps.pkDeleteRecord(pNdb, 2) == 0);
    CHECK(hugoOps.execute_Commit(pNdb, CommitAsMuchAsPossible) == 630);
    CHECK(hugoOps.closeTransaction(pNdb) == 0);

    CHECK(hugoOps.startTransaction(pNdb) == 0);
    CHECK(hugoOps.pkReadRecord(pNdb, 2) == 0);
    CHECK(hugoOps.execute_Commit(pNdb) == 626);
  } while(false);

  hugoOps.closeTransaction(pNdb);
  
  return result;
}

int runNoCommit626(NDBT_Context* ctx, NDBT_Step* step){
  int result = NDBT_OK;
  HugoOperations hugoOps(*ctx->getTab());
  Ndb* pNdb = GETNDB(step);

  do{
    // No commit transaction, readTuple
    CHECK(hugoOps.startTransaction(pNdb) == 0);
    CHECK(hugoOps.pkReadRecord(pNdb, 1, 1, NdbOperation::LM_Read) == 0);
    CHECK(hugoOps.execute_NoCommit(pNdb) == 626);
    CHECK(hugoOps.closeTransaction(pNdb) == 0);

    // No commit transaction, readTupleExcluive
    CHECK(hugoOps.startTransaction(pNdb) == 0);
    CHECK(hugoOps.pkReadRecord(pNdb, 1, 1, NdbOperation::LM_Exclusive) == 0);
    CHECK(hugoOps.execute_NoCommit(pNdb) == 626);
  }while(false);

  hugoOps.closeTransaction(pNdb);
  
  return result;
}

int runNoCommit630(NDBT_Context* ctx, NDBT_Step* step){
  int result = NDBT_OK;
  HugoOperations hugoOps(*ctx->getTab());
  Ndb* pNdb = GETNDB(step);

  do{
    // No commit transaction
    CHECK(hugoOps.startTransaction(pNdb) == 0);
    CHECK(hugoOps.pkInsertRecord(pNdb, 1) == 0);
    CHECK(hugoOps.execute_NoCommit(pNdb) == 630);
  }while(false);

  hugoOps.closeTransaction(pNdb);
  
  return result;
}

int runNoCommitRollback626(NDBT_Context* ctx, NDBT_Step* step){
  int result = NDBT_OK;
  HugoOperations hugoOps(*ctx->getTab());
  Ndb* pNdb = GETNDB(step);

  do{
    // No commit transaction, rollback
    CHECK(hugoOps.startTransaction(pNdb) == 0);
    CHECK(hugoOps.pkReadRecord(pNdb, 1, 1, NdbOperation::LM_Exclusive) == 0);
    CHECK(hugoOps.execute_NoCommit(pNdb) == 626);
    CHECK(hugoOps.execute_Rollback(pNdb) == 0);
    CHECK(hugoOps.closeTransaction(pNdb) == 0);

    // No commit transaction, rollback
    // Multiple operations
    CHECK(hugoOps.startTransaction(pNdb) == 0);
    CHECK(hugoOps.pkReadRecord(pNdb, 1, 1, NdbOperation::LM_Exclusive) == 0);
    CHECK(hugoOps.pkReadRecord(pNdb, 2, 1, NdbOperation::LM_Exclusive) == 0);
    CHECK(hugoOps.pkReadRecord(pNdb, 3, 1, NdbOperation::LM_Exclusive) == 0);
    CHECK(hugoOps.pkReadRecord(pNdb, 4, 1, NdbOperation::LM_Exclusive) == 0);
    CHECK(hugoOps.execute_NoCommit(pNdb) == 626);
    CHECK(hugoOps.execute_Rollback(pNdb) == 0);
  }while(false);

  hugoOps.closeTransaction(pNdb);
  
  return result;
}

int runNoCommitRollback630(NDBT_Context* ctx, NDBT_Step* step){
  int result = NDBT_OK;
  HugoOperations hugoOps(*ctx->getTab());
  Ndb* pNdb = GETNDB(step);

  do{
    // No commit transaction, rollback
    CHECK(hugoOps.startTransaction(pNdb) == 0);
    CHECK(hugoOps.pkInsertRecord(pNdb, 1) == 0);
    CHECK(hugoOps.execute_NoCommit(pNdb) == 630);
    CHECK(hugoOps.execute_Rollback(pNdb) == 0);
  }while(false);

  hugoOps.closeTransaction(pNdb);

  return result;
}


int runNoCommitAndClose(NDBT_Context* ctx, NDBT_Step* step){
  int i, result = NDBT_OK;
  HugoOperations hugoOps(*ctx->getTab());
  Ndb* pNdb = GETNDB(step);

  do{
    // Read 
    CHECK(hugoOps.startTransaction(pNdb) == 0);  
    for (i = 0; i < 10; i++)
      CHECK(hugoOps.pkReadRecord(pNdb, i, 1, NdbOperation::LM_Exclusive) == 0);
    CHECK(hugoOps.execute_NoCommit(pNdb) == 0);
    CHECK(hugoOps.closeTransaction(pNdb) == 0);
  
    // Update
    CHECK(hugoOps.startTransaction(pNdb) == 0);  
    for (i = 0; i < 10; i++)
      CHECK(hugoOps.pkUpdateRecord(pNdb, i) == 0);
    CHECK(hugoOps.execute_NoCommit(pNdb) == 0);
    CHECK(hugoOps.closeTransaction(pNdb) == 0);
  
    // Delete
    CHECK(hugoOps.startTransaction(pNdb) == 0);  
    for (i = 0; i < 10; i++)
      CHECK(hugoOps.pkDeleteRecord(pNdb, i) == 0);
    CHECK(hugoOps.execute_NoCommit(pNdb) == 0);
    CHECK(hugoOps.closeTransaction(pNdb) == 0);

    // Try to insert, record should already exist
    CHECK(hugoOps.startTransaction(pNdb) == 0);  
    for (i = 0; i < 10; i++)
      CHECK(hugoOps.pkInsertRecord(pNdb, i) == 0);
    CHECK(hugoOps.execute_Commit(pNdb) == 630);
    CHECK(hugoOps.closeTransaction(pNdb) == 0);

  }while(false);

  hugoOps.closeTransaction(pNdb);

  return result;
}



int runCheckRollbackDelete(NDBT_Context* ctx, NDBT_Step* step){
  int result = NDBT_OK;
  HugoOperations hugoOps(*ctx->getTab());
  Ndb* pNdb = GETNDB(step);

  do{

    // Read value and save it for later
    CHECK(hugoOps.startTransaction(pNdb) == 0);  
    CHECK(hugoOps.pkReadRecord(pNdb, 5) == 0);
    CHECK(hugoOps.execute_Commit(pNdb) == 0);
    CHECK(hugoOps.saveCopyOfRecord() == NDBT_OK);
    CHECK(hugoOps.closeTransaction(pNdb) == 0);

    // Delete record 5
    CHECK(hugoOps.startTransaction(pNdb) == 0);  
    CHECK(hugoOps.pkDeleteRecord(pNdb, 5) == 0);
    CHECK(hugoOps.execute_NoCommit(pNdb) == 0);

    // Check record is deleted
    CHECK(hugoOps.pkReadRecord(pNdb, 5, 1, NdbOperation::LM_Exclusive) == 0);
    CHECK(hugoOps.execute_NoCommit(pNdb) == 626);
    CHECK(hugoOps.execute_Rollback(pNdb) == 0);

    CHECK(hugoOps.closeTransaction(pNdb) == 0);

    // Check record is not deleted
    CHECK(hugoOps.startTransaction(pNdb) == 0);  
    CHECK(hugoOps.pkReadRecord(pNdb, 5, 1, NdbOperation::LM_Exclusive) == 0);
    CHECK(hugoOps.execute_Commit(pNdb) == 0);
    CHECK(hugoOps.closeTransaction(pNdb) == 0);

    // Check record is back to original value
    CHECK(hugoOps.startTransaction(pNdb) == 0);  
    CHECK(hugoOps.pkReadRecord(pNdb, 5, 1, NdbOperation::LM_Exclusive) == 0);
    CHECK(hugoOps.execute_Commit(pNdb) == 0);
    CHECK(hugoOps.compareRecordToCopy() == NDBT_OK);


  }while(false);

  hugoOps.closeTransaction(pNdb);

  return result;
}

int runCheckRollbackUpdate(NDBT_Context* ctx, NDBT_Step* step){
  int result = NDBT_OK;
  HugoOperations hugoOps(*ctx->getTab());
  Ndb* pNdb = GETNDB(step);
  int numRecords = 5;
  do{
    
    // Read value and save it for later
    CHECK(hugoOps.startTransaction(pNdb) == 0);  
    CHECK(hugoOps.pkReadRecord(pNdb, 1, numRecords) == 0);
    CHECK(hugoOps.execute_Commit(pNdb) == 0);
    CHECK(hugoOps.verifyUpdatesValue(0) == NDBT_OK); // Update value 0
    CHECK(hugoOps.closeTransaction(pNdb) == 0);

    // Update  record 5
    CHECK(hugoOps.startTransaction(pNdb) == 0);  
    CHECK(hugoOps.pkUpdateRecord(pNdb, 1, numRecords, 5) == 0);// Updates value 5
    CHECK(hugoOps.execute_NoCommit(pNdb) == 0);
  
    // Check record is updated
    CHECK(hugoOps.pkReadRecord(pNdb, 1, numRecords, NdbOperation::LM_Exclusive) == 0);
    CHECK(hugoOps.execute_NoCommit(pNdb) == 0);
    CHECK(hugoOps.verifyUpdatesValue(5) == NDBT_OK); // Updates value 5
    CHECK(hugoOps.execute_Rollback(pNdb) == 0);

    CHECK(hugoOps.closeTransaction(pNdb) == 0);

    // Check record is back to original value
    CHECK(hugoOps.startTransaction(pNdb) == 0);  
    CHECK(hugoOps.pkReadRecord(pNdb, 1, numRecords, NdbOperation::LM_Exclusive) == 0);
    CHECK(hugoOps.execute_Commit(pNdb) == 0);
    CHECK(hugoOps.verifyUpdatesValue(0) == NDBT_OK); // Updates value 0

  }while(false);

  hugoOps.closeTransaction(pNdb);

  return result;
}

int runCheckRollbackDeleteMultiple(NDBT_Context* ctx, NDBT_Step* step){
  int result = NDBT_OK;
  HugoOperations hugoOps(*ctx->getTab());
  Ndb* pNdb = GETNDB(step);

  do{
    // Read value and save it for later
    CHECK(hugoOps.startTransaction(pNdb) == 0);  
    CHECK(hugoOps.pkReadRecord(pNdb, 5, 10) == 0);
    CHECK(hugoOps.execute_Commit(pNdb) == 0);
    CHECK(hugoOps.verifyUpdatesValue(0) == NDBT_OK);
    CHECK(hugoOps.closeTransaction(pNdb) == 0);
    
    Uint32 updatesValue = 0;
    Uint32 j;
    for(Uint32 i = 0; i<1; i++){
      // Read  record 5 - 10
      CHECK(hugoOps.startTransaction(pNdb) == 0);  
      CHECK(hugoOps.pkReadRecord(pNdb, 5, 10, NdbOperation::LM_Exclusive) == 0);
      CHECK(hugoOps.execute_NoCommit(pNdb) == 0);
      
      for(j = 0; j<10; j++){
	// Update  record 5 - 10
	updatesValue++;
	CHECK(hugoOps.pkUpdateRecord(pNdb, 5, 10, updatesValue) == 0);
	CHECK(hugoOps.execute_NoCommit(pNdb) == 0);

	CHECK(hugoOps.pkReadRecord(pNdb, 5, 10, NdbOperation::LM_Exclusive) == 0);
	CHECK(hugoOps.execute_NoCommit(pNdb) == 0);
	CHECK(hugoOps.verifyUpdatesValue(updatesValue) == 0);
      }      
      
      for(j = 0; j<10; j++){
	// Delete record 5 - 10 times
	CHECK(hugoOps.pkDeleteRecord(pNdb, 5, 10) == 0);
	CHECK(hugoOps.execute_NoCommit(pNdb) == 0);

#if 0
	// Check records are deleted
	CHECK(hugoOps.pkReadRecord(pNdb, 5, 10, NdbOperation::LM_Exclusive) == 0);
	CHECK(hugoOps.execute_NoCommit(pNdb) == 626);
#endif

	updatesValue++;
	CHECK(hugoOps.pkInsertRecord(pNdb, 5, 10, updatesValue) == 0);
	CHECK(hugoOps.execute_NoCommit(pNdb) == 0);
	
	CHECK(hugoOps.pkReadRecord(pNdb, 5, 10, NdbOperation::LM_Exclusive) == 0);
	CHECK(hugoOps.execute_NoCommit(pNdb) == 0);
	CHECK(hugoOps.verifyUpdatesValue(updatesValue) == 0);
      }

      CHECK(hugoOps.pkDeleteRecord(pNdb, 5, 10) == 0);
      CHECK(hugoOps.execute_NoCommit(pNdb) == 0);

      // Check records are deleted
      CHECK(hugoOps.pkReadRecord(pNdb, 5, 10, NdbOperation::LM_Exclusive) == 0);
      CHECK(hugoOps.execute_NoCommit(pNdb) == 626);
      CHECK(hugoOps.execute_Rollback(pNdb) == 0);
      
      CHECK(hugoOps.closeTransaction(pNdb) == 0);
    }
    
    // Check records are not deleted
    // after rollback
    CHECK(hugoOps.startTransaction(pNdb) == 0);  
    CHECK(hugoOps.pkReadRecord(pNdb, 5, 10, NdbOperation::LM_Exclusive) == 0);
    CHECK(hugoOps.execute_Commit(pNdb) == 0);
    CHECK(hugoOps.verifyUpdatesValue(0) == NDBT_OK);
    
  }while(false);

  hugoOps.closeTransaction(pNdb);

  return result;
}


int runCheckImplicitRollbackDelete(NDBT_Context* ctx, NDBT_Step* step){
  int result = NDBT_OK;
  HugoOperations hugoOps(*ctx->getTab());
  Ndb* pNdb = GETNDB(step);

  do{
    // Read  record 5
    CHECK(hugoOps.startTransaction(pNdb) == 0);  
    CHECK(hugoOps.pkReadRecord(pNdb, 5, 1, NdbOperation::LM_Exclusive) == 0);
    CHECK(hugoOps.execute_NoCommit(pNdb) == 0);
    CHECK(hugoOps.closeTransaction(pNdb) == 0);
    
    // Update  record 5
    CHECK(hugoOps.startTransaction(pNdb) == 0);  
    CHECK(hugoOps.pkUpdateRecord(pNdb, 5) == 0);
    CHECK(hugoOps.execute_NoCommit(pNdb) == 0);
    CHECK(hugoOps.closeTransaction(pNdb) == 0);
  
    // Delete record 5
    CHECK(hugoOps.startTransaction(pNdb) == 0);  
    CHECK(hugoOps.pkDeleteRecord(pNdb, 5) == 0);
    CHECK(hugoOps.execute_NoCommit(pNdb) == 0);
    CHECK(hugoOps.closeTransaction(pNdb) == 0);

    // Check record is not deleted
    // Close transaction should have rollbacked
    CHECK(hugoOps.startTransaction(pNdb) == 0);  
    CHECK(hugoOps.pkReadRecord(pNdb, 5, 1, NdbOperation::LM_Exclusive) == 0);
    CHECK(hugoOps.execute_Commit(pNdb) == 0);
  }while(false);

  hugoOps.closeTransaction(pNdb);

  return result;
}

int runCheckCommitDelete(NDBT_Context* ctx, NDBT_Step* step){
  int result = NDBT_OK;
  HugoOperations hugoOps(*ctx->getTab());
  Ndb* pNdb = GETNDB(step);

  do{
    // Read  10 records
    CHECK(hugoOps.startTransaction(pNdb) == 0);  
    CHECK(hugoOps.pkReadRecord(pNdb, 5, 10, NdbOperation::LM_Exclusive) == 0);
    CHECK(hugoOps.execute_NoCommit(pNdb) == 0);
  
    // Update 10 records
    CHECK(hugoOps.pkUpdateRecord(pNdb, 5, 10) == 0);
    CHECK(hugoOps.execute_NoCommit(pNdb) == 0);
  
    // Delete 10 records
    CHECK(hugoOps.pkDeleteRecord(pNdb, 5, 10) == 0);
    CHECK(hugoOps.execute_NoCommit(pNdb) == 0);

    CHECK(hugoOps.execute_Commit(pNdb) == 0);
    CHECK(hugoOps.closeTransaction(pNdb) == 0);

    // Check record's are deleted
    CHECK(hugoOps.startTransaction(pNdb) == 0);  
    CHECK(hugoOps.pkReadRecord(pNdb, 5, 10, NdbOperation::LM_Exclusive) == 0);
    CHECK(hugoOps.execute_Commit(pNdb) == 626);

  }while(false);

  hugoOps.closeTransaction(pNdb);

  return result;
}

int runRollbackNothing(NDBT_Context* ctx, NDBT_Step* step){
  int result = NDBT_OK;
  HugoOperations hugoOps(*ctx->getTab());
  Ndb* pNdb = GETNDB(step);

  do{
    // Delete record 5 - 15
    CHECK(hugoOps.startTransaction(pNdb) == 0);  
    CHECK(hugoOps.pkDeleteRecord(pNdb, 5, 10) == 0);
    // Rollback 
    CHECK(hugoOps.execute_Rollback(pNdb) == 0);
    CHECK(hugoOps.closeTransaction(pNdb) == 0);

    // Check records are not deleted
    CHECK(hugoOps.startTransaction(pNdb) == 0);  
    CHECK(hugoOps.pkReadRecord(pNdb, 5, 10, NdbOperation::LM_Exclusive) == 0);
    CHECK(hugoOps.execute_Commit(pNdb) == 0);
    CHECK(hugoOps.closeTransaction(pNdb) == 0);  

    CHECK(hugoOps.startTransaction(pNdb) == 0);  
    CHECK(hugoOps.execute_Rollback(pNdb) == 0);

  }while(false);

  hugoOps.closeTransaction(pNdb);

  return result;
}

int runMassiveRollback(NDBT_Context* ctx, NDBT_Step* step){

  NdbRestarter restarter;
  const int records = 4 * restarter.getNumDbNodes();

  HugoTransactions hugoTrans(*ctx->getTab());
  if (hugoTrans.loadTable(GETNDB(step), records) != 0){
    return NDBT_FAILED;
  }
  
  int result = NDBT_OK;
  HugoOperations hugoOps(*ctx->getTab());
  Ndb* pNdb = GETNDB(step);

  const Uint32 OPS_PER_TRANS = 256;
  const Uint32 OPS_TOTAL = 4096;

  for(int row = 0; row < records; row++){
    int res;
    CHECK(hugoOps.startTransaction(pNdb) == 0);  
    for(Uint32 i = 0; i<OPS_TOTAL; i += OPS_PER_TRANS){
      for(Uint32 j = 0; j<OPS_PER_TRANS; j++){
	CHECK(hugoOps.pkUpdateRecord(pNdb, row, 1, i) == 0);
      }
      g_info << "Performed " << (i+OPS_PER_TRANS) << " updates on row: " << row
	     << endl;
      if(result != NDBT_OK){
	break;
      }
      res = hugoOps.execute_NoCommit(pNdb);
      if(res != 0){
	NdbError err = pNdb->getNdbError(res);
	CHECK(err.classification == NdbError::TimeoutExpired);
	break;
      }
    }
    if(result != NDBT_OK){
      break;
    }
    g_info << "executeRollback" << endl;
    CHECK(hugoOps.execute_Rollback(pNdb) == 0);
    CHECK(hugoOps.closeTransaction(pNdb) == 0);
  }
  
  hugoOps.closeTransaction(pNdb);
  return result;
}

int
runMassiveRollback2(NDBT_Context* ctx, NDBT_Step* step){

  HugoTransactions hugoTrans(*ctx->getTab());
  if (hugoTrans.loadTable(GETNDB(step), 1) != 0){
    return NDBT_FAILED;
  }

  int result = NDBT_OK;
  HugoOperations hugoOps(*ctx->getTab());
  Ndb* pNdb = GETNDB(step);

  const Uint32 OPS_TOTAL = 4096;
  const Uint32 LOOPS = 10;
  
  for(Uint32 loop = 0; loop<LOOPS; loop++){
    CHECK(hugoOps.startTransaction(pNdb) == 0);  
    for(Uint32 i = 0; i<OPS_TOTAL-1; i ++){
      if((i & 1) == 0){
	CHECK(hugoOps.pkUpdateRecord(pNdb, 0, 1, loop) == 0);
      } else {
	CHECK(hugoOps.pkUpdateRecord(pNdb, 1, 1, loop) == 0);
      }
    }
    CHECK(hugoOps.execute_Commit(pNdb) == 626);
    CHECK(hugoOps.execute_Rollback(pNdb) == 0);
    CHECK(hugoOps.closeTransaction(pNdb) == 0);
  }
  
  hugoOps.closeTransaction(pNdb);
  return result;
}

int
runMassiveRollback3(NDBT_Context* ctx, NDBT_Step* step){

  int result = NDBT_OK;
  HugoOperations hugoOps(*ctx->getTab());
  Ndb* pNdb = GETNDB(step);

  const Uint32 BATCH = 10;
  const Uint32 OPS_TOTAL = 50;
  const Uint32 LOOPS = 100;
  
  for(Uint32 loop = 0; loop<LOOPS; loop++)
  {
    CHECK(hugoOps.startTransaction(pNdb) == 0);  
    bool ok = true;
    for (Uint32 i = 0; i<OPS_TOTAL; i+= BATCH)
    {
      CHECK(hugoOps.pkInsertRecord(pNdb, i, BATCH, 0) == 0);
      if (hugoOps.execute_NoCommit(pNdb) != 0)
      {
	ok = false;
	break;
      }
    }
    hugoOps.execute_Rollback(pNdb);
    CHECK(hugoOps.closeTransaction(pNdb) == 0);
  }
  
  hugoOps.closeTransaction(pNdb);
  return result;
}

int
runMassiveRollback4(NDBT_Context* ctx, NDBT_Step* step){

  int result = NDBT_OK;
  HugoOperations hugoOps(*ctx->getTab());
  Ndb* pNdb = GETNDB(step);

  const Uint32 BATCH = 10;
  const Uint32 OPS_TOTAL = 20;
  const Uint32 LOOPS = 100;
  
  for(Uint32 loop = 0; loop<LOOPS; loop++)
  {
    CHECK(hugoOps.startTransaction(pNdb) == 0);  
    bool ok = true;
    for (Uint32 i = 0; i<OPS_TOTAL; i+= BATCH)
    {
      CHECK(hugoOps.pkInsertRecord(pNdb, i, BATCH, 0) == 0);
      CHECK(hugoOps.pkDeleteRecord(pNdb, i, BATCH) == 0);
      if (hugoOps.execute_NoCommit(pNdb) != 0)
      {
	ok = false;
	break;
      }
    }
    hugoOps.execute_Rollback(pNdb);
    CHECK(hugoOps.closeTransaction(pNdb) == 0);
  }
  
  hugoOps.closeTransaction(pNdb);
  return result;
}

/**
 * TUP errors
 */
struct TupError 
{
  enum Bits {
    TE_VARSIZE  = 0x1,
    TE_MULTI_OP = 0x2,
    TE_DISK     = 0x4,
    TE_REPLICA  = 0x8,
    TE_OI       = 0x10, // Ordered index
    TE_UI       = 0x20  // Unique hash index
  };
  int op;
  int error;
  int bits;
};

static
TupError 
f_tup_errors[] = 
{
  { NdbOperation::InsertRequest, 4014, 0 },       // Out of undo buffer
  { NdbOperation::InsertRequest, 4015, TupError::TE_DISK }, // Out of log space
  { NdbOperation::InsertRequest, 4016, 0 },       // AI Inconsistency
  { NdbOperation::InsertRequest, 4017, 0 },       // Out of memory
  { NdbOperation::InsertRequest, 4018, 0 },       // Null check error
  { NdbOperation::InsertRequest, 4019, TupError::TE_REPLICA }, //Alloc rowid error
  { NdbOperation::InsertRequest, 4020, TupError::TE_MULTI_OP }, // Size change error
  { NdbOperation::InsertRequest, 4021, TupError::TE_DISK },    // Out of disk space
  { NdbOperation::InsertRequest, 4022, TupError::TE_OI },
  { NdbOperation::InsertRequest, 4023, TupError::TE_OI },
  { NdbOperation::UpdateRequest, 4030, TupError::TE_UI },
  { -1, 0, 0 }
};

int
runTupErrors(NDBT_Context* ctx, NDBT_Step* step){

  NdbRestarter restarter;
  HugoTransactions hugoTrans(*ctx->getTab());
  HugoOperations hugoOps(*ctx->getTab());
  Ndb* pNdb = GETNDB(step);
  
  const NdbDictionary::Table * tab = ctx->getTab();
  int i;
  int bits = TupError::TE_MULTI_OP;
  for(i = 0; i<tab->getNoOfColumns(); i++)
  {
    if (tab->getColumn(i)->getArrayType() != NdbDictionary::Column::ArrayTypeFixed)
      bits |= TupError::TE_VARSIZE;
    if (tab->getColumn(i)->getStorageType()!= NdbDictionary::Column::StorageTypeMemory)
      bits |= TupError::TE_DISK;
  }

  if (restarter.getNumDbNodes() >= 2)
  {
    bits |= TupError::TE_REPLICA;
  }

  NdbDictionary::Dictionary::List l;
  pNdb->getDictionary()->listIndexes(l, tab->getName());
  for (i = 0; i<(int)l.count; i++)
  {
    if (DictTabInfo::isOrderedIndex(l.elements[i].type))
      bits |= TupError::TE_OI;
    if (DictTabInfo::isUniqueIndex(l.elements[i].type))
      bits |= TupError::TE_UI;
  }

  /**
   * Insert
   */
  for(i = 0; f_tup_errors[i].op != -1; i++)
  {
    if (f_tup_errors[i].op != NdbOperation::InsertRequest)
    {
      continue;
    }

    if ((f_tup_errors[i].bits & bits) != f_tup_errors[i].bits)
    {
      g_err << "Skipping " << f_tup_errors[i].error
            << " - req bits: " << hex << f_tup_errors[i].bits
            << " bits: " << hex << bits << endl;
      continue;
    }
    
    g_err << "Testing error insert: " << f_tup_errors[i].error << endl;
    restarter.insertErrorInAllNodes(f_tup_errors[i].error);
    if (f_tup_errors[i].bits & TupError::TE_MULTI_OP)
    {
      
    }
    else
    {
      hugoTrans.loadTable(pNdb, 5);
    }
    restarter.insertErrorInAllNodes(0);
    if (hugoTrans.clearTable(pNdb, 5) != 0)
    {
      return NDBT_FAILED;
    }      
  }

  /**
   * update
   */
  hugoTrans.loadTable(pNdb, 5);
  for(i = 0; f_tup_errors[i].op != -1; i++)
  {
    if (f_tup_errors[i].op != NdbOperation::UpdateRequest)
    {
      continue;
    }

    if ((f_tup_errors[i].bits & bits) != f_tup_errors[i].bits)
    {
      g_err << "Skipping " << f_tup_errors[i].error
            << " - req bits: " << hex << f_tup_errors[i].bits
            << " bits: " << hex << bits << endl;
      continue;
    }

    g_err << "Testing error insert: " << f_tup_errors[i].error << endl;
    restarter.insertErrorInAllNodes(f_tup_errors[i].error);
    if (f_tup_errors[i].bits & TupError::TE_MULTI_OP)
    {

    }
    else
    {
      hugoTrans.scanUpdateRecords(pNdb, 5);
    }
    restarter.insertErrorInAllNodes(0);
    if (hugoTrans.scanUpdateRecords(pNdb, 5) != 0)
    {
      return NDBT_FAILED;
    }
  }
  
  return NDBT_OK;
}

int
runInsertError(NDBT_Context* ctx, NDBT_Step* step){

  int result = NDBT_OK;
  HugoOperations hugoOp1(*ctx->getTab());
  HugoOperations hugoOp2(*ctx->getTab());
  Ndb* pNdb = GETNDB(step);

  NdbRestarter restarter;
  restarter.insertErrorInAllNodes(4017);
  const Uint32 LOOPS = 10;
  for (Uint32 i = 0; i<LOOPS; i++)
  {
    CHECK(hugoOp1.startTransaction(pNdb) == 0);  
    CHECK(hugoOp1.pkInsertRecord(pNdb, 1) == 0);
    
    CHECK(hugoOp2.startTransaction(pNdb) == 0);
    CHECK(hugoOp2.pkReadRecord(pNdb, 1, 1) == 0);
    
    CHECK(hugoOp1.execute_async_prepare(pNdb, NdbTransaction::Commit) == 0);
    CHECK(hugoOp2.execute_async_prepare(pNdb, NdbTransaction::Commit) == 0);
    hugoOp1.wait_async(pNdb);
    hugoOp2.wait_async(pNdb);
    CHECK(hugoOp1.closeTransaction(pNdb) == 0);
    CHECK(hugoOp2.closeTransaction(pNdb) == 0);
  }
  
  restarter.insertErrorInAllNodes(0);
  
  return result;
}

int
runInsertError2(NDBT_Context* ctx, NDBT_Step* step){
  int result = NDBT_OK;
  HugoOperations hugoOp1(*ctx->getTab());
  Ndb* pNdb = GETNDB(step);
  
  NdbRestarter restarter;
  restarter.insertErrorInAllNodes(4017);
  
  const Uint32 LOOPS = 1;
  for (Uint32 i = 0; i<LOOPS; i++)
  {
    CHECK(hugoOp1.startTransaction(pNdb) == 0);  
    CHECK(hugoOp1.pkInsertRecord(pNdb, 1) == 0);
    CHECK(hugoOp1.pkDeleteRecord(pNdb, 1) == 0);
    
    hugoOp1.execute_NoCommit(pNdb);
    CHECK(hugoOp1.closeTransaction(pNdb) == 0);
  }
  
  restarter.insertErrorInAllNodes(0);
  return NDBT_OK;
}
  
int
runBug25090(NDBT_Context* ctx, NDBT_Step* step){
  
  Ndb* pNdb = GETNDB(step);
  //NdbDictionary::Dictionary * dict = pNdb->getDictionary();

  HugoOperations ops(*ctx->getTab());
  
  int loops = ctx->getNumLoops();
  //const int rows = ctx->getNumRecords();
  
  while (loops--)
  {
    ops.startTransaction(pNdb);
    ops.pkReadRecord(pNdb, 1, 1);
    ops.execute_Commit(pNdb, AO_IgnoreError);
    sleep(10);
    ops.closeTransaction(pNdb);
  }
  
  return NDBT_OK;
}

int
runDeleteRead(NDBT_Context* ctx, NDBT_Step* step){
  
  Ndb* pNdb = GETNDB(step);

  const NdbDictionary::Table* tab = ctx->getTab();
  NDBT_ResultRow row(*ctx->getTab());
  HugoTransactions tmp(*ctx->getTab());

  int a;
  int loops = ctx->getNumLoops();
  //const int rows = ctx->getNumRecords();
  
  while (loops--)
  {
    NdbTransaction* pTrans = pNdb->startTransaction();
    NdbOperation* pOp = pTrans->getNdbOperation(tab->getName());
    pOp->deleteTuple();
    tmp.equalForRow(pOp, loops);
    
    // Define attributes to read  
    for(a = 0; a<tab->getNoOfColumns(); a++)
    {
      if((row.attributeStore(a) = pOp->getValue(tab->getColumn(a)->getName())) == 0) {
	ERR(pTrans->getNdbError());
	return NDBT_FAILED;
      }
    }

    pTrans->execute(Commit);
    pTrans->close();

    pTrans = pNdb->startTransaction();    
    pOp = pTrans->getNdbOperation(tab->getName());
    pOp->insertTuple();
    tmp.setValues(pOp, loops, 0);

    pOp = pTrans->getNdbOperation(tab->getName());
    pOp->deleteTuple();
    tmp.equalForRow(pOp, loops);
    for(a = 0; a<tab->getNoOfColumns(); a++)
    {
      if((row.attributeStore(a) = pOp->getValue(tab->getColumn(a)->getName())) == 0) 
      {
	ERR(pTrans->getNdbError());
	return NDBT_FAILED;
      }
    }
    if (pTrans->execute(Commit) != 0)
    {
      ERR(pTrans->getNdbError());
      return NDBT_FAILED;
    }

    pTrans->close();    
  }
  
  return NDBT_OK;
}

int
runBug27756(NDBT_Context* ctx, NDBT_Step* step)
{
  
  Ndb* pNdb = GETNDB(step);
  //NdbDictionary::Dictionary * dict = pNdb->getDictionary();
  
  HugoOperations ops(*ctx->getTab());

  int loops = ctx->getNumLoops();
  //const int rows = ctx->getNumRecords();
  
  Vector<Uint64> copies;
  while (loops--)
  {
    ops.startTransaction(pNdb);
    ops.pkInsertRecord(pNdb, 1, 1);
    ops.execute_NoCommit(pNdb);
    
    NdbTransaction* pTrans = ops.getTransaction();
    NdbOperation* op = pTrans->getNdbOperation(ctx->getTab()->getName());
    op->interpretedUpdateTuple();
    ops.equalForRow(op, 1);
    NdbRecAttr* attr = op->getValue(NdbDictionary::Column::COPY_ROWID);
    ops.execute_NoCommit(pNdb);
    
    copies.push_back(attr->u_64_value());
    ndbout_c("copy at: %llx", copies.back());
    ops.execute_NoCommit(pNdb);
    
    ops.pkDeleteRecord(pNdb, 1, 1);
    ops.execute_NoCommit(pNdb);
    
    if (loops & 1)
    {
      ops.execute_Rollback(pNdb);
      ops.closeTransaction(pNdb);
    }
    else
    {
      ops.execute_Commit(pNdb);
      ops.closeTransaction(pNdb);
      ops.clearTable(pNdb, 100);
    }
  }
  
  for (Uint32 i = 0; i<copies.size(); i++)
    if (copies[i] != copies.back())
    {
      ndbout_c("Memleak detected");
      return NDBT_FAILED;
    }
  
  return NDBT_OK;
}

int
runBug28073(NDBT_Context *ctx, NDBT_Step* step)
{
  int result = NDBT_OK;
  const NdbDictionary::Table *table= ctx->getTab();
  HugoOperations hugoOp1(*table);
  HugoOperations hugoOp2(*table);
  Ndb* pNdb = GETNDB(step);
  int loops = ctx->getNumLoops();
  bool inserted= false;

  while (loops--)
  {
    if (!inserted)
    {
      CHECK(hugoOp1.startTransaction(pNdb) == 0);
      CHECK(hugoOp1.pkInsertRecord(pNdb, 1, 1) == 0);
      CHECK(hugoOp1.execute_Commit(pNdb) == 0);
      CHECK(hugoOp1.closeTransaction(pNdb) == 0);
      inserted= 1;
    }

    // Use TC hint to hit the same node in both transactions.
    Uint32 key_val= 0;
    const char *key= (const char *)(&key_val);
    CHECK(hugoOp1.startTransaction(pNdb, table, key, 4) == 0);
    CHECK(hugoOp2.startTransaction(pNdb, table, key, 4) == 0);

    // First take 2*read lock on the tuple in transaction 1.
    for (Uint32 i= 0; i < 2; i++)
    {
      CHECK(hugoOp1.pkReadRecord(pNdb, 1, 1, NdbOperation::LM_Read) == 0);
      CHECK(hugoOp1.pkReadRecord(pNdb, 1, 1, NdbOperation::LM_Read) == 0);
    }
    CHECK(hugoOp1.execute_NoCommit(pNdb) == 0);

    // Now send ops in two transactions, one batch.
    // First 2*read in transaction 2.
    for (Uint32 i= 0; i < 2; i++)
    {
      CHECK(hugoOp2.pkReadRecord(pNdb, 1, 1, NdbOperation::LM_Read) == 0);
      CHECK(hugoOp2.pkReadRecord(pNdb, 1, 1, NdbOperation::LM_Read) == 0);
    }
    CHECK(hugoOp2.execute_async_prepare(pNdb, NdbTransaction::NoCommit) == 0);

    // Second op an update in transaction 1.
    CHECK(hugoOp1.pkUpdateRecord(pNdb, 1, 1) == 0);
    CHECK(hugoOp1.execute_async_prepare(pNdb, NdbTransaction::Commit) == 0);

    // Transaction 1 will now hang waiting on transaction 2 to commit before it
    // can upgrade its read lock to a write lock.
    // With the bug, we get a node failure due to watchdog timeout here.
    CHECK(hugoOp2.wait_async(pNdb) == 0);

    // Now commit transaction 2, we should see transaction 1 finish with the
    // update.
    CHECK(hugoOp2.execute_async_prepare(pNdb, NdbTransaction::Commit) == 0);
    CHECK(hugoOp2.wait_async(pNdb) == 0);
    // No error check, as transaction 1 may have terminated already.
    hugoOp1.wait_async(pNdb);

    CHECK(hugoOp1.closeTransaction(pNdb) == 0);
    CHECK(hugoOp2.closeTransaction(pNdb) == 0);
  }

  return result;
}

int
runBug20535(NDBT_Context* ctx, NDBT_Step* step)
{
  Ndb* pNdb = GETNDB(step);
  const NdbDictionary::Table * tab = ctx->getTab();

  bool hasDefault = false;
  for (Uint32 i = 0; i<(Uint32)tab->getNoOfColumns(); i++)
  {
    if (tab->getColumn(i)->getNullable() ||
        tab->getColumn(i)->getDefaultValue())
    {
      hasDefault = true;
      break;
    }
  }
  
  if (!hasDefault)
    return NDBT_OK;

  HugoTransactions hugoTrans(* tab);
  hugoTrans.loadTable(pNdb, 1);

  NdbTransaction* pTrans = pNdb->startTransaction();
  NdbOperation* pOp = pTrans->getNdbOperation(tab->getName());
  pOp->deleteTuple();
  hugoTrans.equalForRow(pOp, 0);
  if (pTrans->execute(NoCommit) != 0)
    return NDBT_FAILED;

  pOp = pTrans->getNdbOperation(tab->getName());
  pOp->insertTuple();
  hugoTrans.equalForRow(pOp, 0);
  for (Uint32 i = 0; i<(Uint32)tab->getNoOfColumns(); i++)
  {
    if (!tab->getColumn(i)->getPrimaryKey() &&
        !tab->getColumn(i)->getNullable() &&
        !tab->getColumn(i)->getDefaultValue())
    {
      hugoTrans.setValueForAttr(pOp, i, 0, 1);
    }
  }
  
  if (pTrans->execute(Commit) != 0)
    return NDBT_FAILED;
  
  pTrans->close();

  pTrans = pNdb->startTransaction();
  pOp = pTrans->getNdbOperation(tab->getName());
  pOp->readTuple();
  hugoTrans.equalForRow(pOp, 0);
  Vector<NdbRecAttr*> values;
  for (Uint32 i = 0; i<(Uint32)tab->getNoOfColumns(); i++)
  {
    if (!tab->getColumn(i)->getPrimaryKey() &&
        (tab->getColumn(i)->getNullable() ||
         tab->getColumn(i)->getDefaultValue()))
    {
      values.push_back(pOp->getValue(i));
    }
  }
  
  if (pTrans->execute(Commit) != 0)
    return NDBT_FAILED;

  bool defaultOk = true;
  for (unsigned int i = 0; i<values.size(); i++)
  {
    const NdbRecAttr* recAttr = values[i];
    const NdbDictionary::Column* col = recAttr->getColumn();
    unsigned int defaultLen = 0;
    const char* def = (const char*) col->getDefaultValue(&defaultLen);
      
    if (def)
    {
      /* Column has a native default, check that it was set */

      if (!recAttr->isNULL())
      {
        if (memcmp(def, recAttr->aRef(), defaultLen) != 0)
        {
          defaultOk = false;
          ndbout_c("column %s does not have correct default value",
                   recAttr->getColumn()->getName());
        }
      }
      else
      {
        defaultOk = false;
        ndbout_c("column %s is null, should have default value",
                 recAttr->getColumn()->getName());
      }
    }
    else
    {
      /* Column has Null as its default */      
      if (!recAttr->isNULL())
      {
        defaultOk = false;
        ndbout_c("column %s is not NULL", recAttr->getColumn()->getName());
      }
    }
  }
  
  pTrans->close();  
  
  if (defaultOk)
    return NDBT_OK;
  else
    return NDBT_FAILED;
}


int
runDDInsertFailUpdateBatch(NDBT_Context* ctx, NDBT_Step* step)
{
  Ndb* pNdb = GETNDB(step);
  NdbRestarter restarter;
  
  const NdbDictionary::Table * tab = ctx->getTab();
  
  int errCode = 0;
  int expectedError = 0;
  {
    bool tabHasDD = false;
    for(int i = 0; i<tab->getNoOfColumns(); i++)
    {
      tabHasDD |= (tab->getColumn(i)->getStorageType() == 
                   NdbDictionary::Column::StorageTypeDisk);
    }
    
    if (tabHasDD)
    {
      errCode = 4021;
      expectedError = 1601;
    }
    else
    {
      NdbDictionary::Dictionary::List l;
      pNdb->getDictionary()->listIndexes(l, tab->getName());
      for (Uint32 i = 0; i<l.count; i++)
      {
        if (DictTabInfo::isOrderedIndex(l.elements[i].type))
        {
          errCode = 4023;
          expectedError = 9999;
          break;
        }
      }
    }

    if (errCode == 0)
    {
      ndbout_c("Table %s has no disk attributes or ordered indexes, skipping",
               tab->getName());
      return NDBT_OK;
    }
  }

  HugoOperations hugoOps(*ctx->getTab());

  int result = NDBT_OK;
  
  for (Uint32 loop = 0; loop < 100; loop ++)
  {
    restarter.insertErrorInAllNodes(errCode);
    CHECK(hugoOps.startTransaction(pNdb) == 0);
    
    /* Create batch with insert op (which will fail due to disk allocation issue)
     * followed by update op on same pk
     * Transaction will abort due to insert failure, and reason should be
     * disk space exhaustion, not any issue with the update.
     */
    CHECK(hugoOps.pkInsertRecord(pNdb, loop, 1, 0) == 0);
    
    /* Add up to 16 updates after the insert */
    Uint32 numUpdates = 1 + (loop % 15);
    for (Uint32 updateCnt = 0; updateCnt < numUpdates; updateCnt++)
      CHECK(hugoOps.pkUpdateRecord(pNdb, loop, 1, 1+updateCnt) == 0);
    
    CHECK(hugoOps.execute_Commit(pNdb) != 0); /* Expect failure */
    
    NdbError err= hugoOps.getTransaction()->getNdbError();
    
    CHECK(err.code == expectedError);
    
    hugoOps.closeTransaction(pNdb);
  }  

  restarter.insertErrorInAllNodes(0);
  
  return result;
}

// Bug34348

#define chk1(b) \
  if (!(b)) { g_err << "ERR: " << step->getName() << " failed on line " << __LINE__ << endl; result = NDBT_FAILED; continue; }
#define chk2(b, e) \
  if (!(b)) { g_err << "ERR: " << step->getName() << " failed on line " << __LINE__ << ": " << e << endl; result = NDBT_FAILED; continue; }

const char* tabname_bug34348 = "TBug34348";

int
runBug34348insert(NDBT_Context* ctx, NDBT_Step* step,
                  HugoOperations& ops, int i, bool* rangeFull)
{
  const int rangeFullError = 633;
  Ndb* pNdb = GETNDB(step);
  int result = NDBT_OK;
  while (result == NDBT_OK)
  {
    int code = 0;
    chk2(ops.startTransaction(pNdb) == 0, ops.getNdbError());
    chk2(ops.pkInsertRecord(pNdb, i, 1) == 0, ops.getNdbError());
    chk2(ops.execute_Commit(pNdb) == 0 || (code = ops.getNdbError().code) == rangeFullError, ops.getNdbError());
    ops.closeTransaction(pNdb);
    *rangeFull = (code == rangeFullError);
    break;
  }
  return result;
}

int
runBug34348delete(NDBT_Context* ctx, NDBT_Step* step,
                  HugoOperations& ops, int i)
{
  Ndb* pNdb = GETNDB(step);
  int result = NDBT_OK;
  while (result == NDBT_OK)
  {
    chk2(ops.startTransaction(pNdb) == 0, ops.getNdbError());
    chk2(ops.pkDeleteRecord(pNdb, i, 1) == 0, ops.getNdbError());
    chk2(ops.execute_Commit(pNdb) == 0, ops.getNdbError());
    ops.closeTransaction(pNdb);
    break;
  }
  return result;
}

int
runBug34348(NDBT_Context* ctx, NDBT_Step* step)
{
  myRandom48Init((long)NdbTick_CurrentMillisecond());
  Ndb* pNdb = GETNDB(step);
  NdbDictionary::Dictionary* pDict = pNdb->getDictionary();
  NdbRestarter restarter;
  int result = NDBT_OK;
  const int loops = ctx->getNumLoops();
  const int errInsDBACC = 3002;
  const int errInsCLEAR = 0;
  Uint32* rowmask = 0;

  while (result == NDBT_OK)
  {
    chk1(restarter.insertErrorInAllNodes(errInsDBACC) == 0);
    ndbout << "error insert " << errInsDBACC << " done" << endl;

    const NdbDictionary::Table* pTab = 0;
    while (result == NDBT_OK)
    {
      (void)pDict->dropTable(tabname_bug34348);
      NdbDictionary::Table tab(tabname_bug34348);
      {
        NdbDictionary::Column col("a");
        col.setType(NdbDictionary::Column::Unsigned);
        col.setPrimaryKey(true);
        tab.addColumn(col);
      }
      {
        NdbDictionary::Column col("b");
        col.setType(NdbDictionary::Column::Unsigned);
        col.setNullable(false);
        tab.addColumn(col);
      }
      chk2(pDict->createTable(tab) == 0, pDict->getNdbError());
      chk2((pTab = pDict->getTable(tabname_bug34348)) != 0, pDict->getNdbError());
      break;
    }

    HugoOperations ops(*pTab);
    ops.setQuiet();

    int rowmaxprev = 0;
    int loop = 0;
    while (result == NDBT_OK && loop < loops)
    {
      ndbout << "loop:" << loop << endl;
      int rowcnt = 0;

      // fill up
      while (result == NDBT_OK)
      {
        bool rangeFull;
        chk1(runBug34348insert(ctx, step, ops, rowcnt, &rangeFull) == NDBT_OK);
        if (rangeFull)
        {
          // 360449 (1 fragment)
          ndbout << "dir range full at " << rowcnt << endl;
          break;
        }
        rowcnt++;
      }
      chk1(result == NDBT_OK);
      const int rowmax = rowcnt;

      if (loop == 0)
        rowmaxprev = rowmax;
      else
        chk2(rowmaxprev == rowmax, "rowmaxprev:" << rowmaxprev << " rowmax:" << rowmax);

      const int sz = (rowmax + 31) / 32;
      delete [] rowmask;
      rowmask = new Uint32 [sz];
      BitmaskImpl::clear(sz, rowmask);
      {
        int i;
        for (i = 0; i < rowmax; i++)
          BitmaskImpl::set(sz, rowmask, i);
      }

      // random delete until insert succeeds
      while (result == NDBT_OK)
      {
        int i = myRandom48(rowmax);
        if (!BitmaskImpl::get(sz, rowmask, i))
          continue;
        chk1(runBug34348delete(ctx, step, ops, i) == NDBT_OK);
        BitmaskImpl::clear(sz, rowmask, i);
        rowcnt--;
        bool rangeFull;
        chk1(runBug34348insert(ctx, step, ops, rowmax, &rangeFull) == NDBT_OK);
        if (!rangeFull)
        {
          chk1(runBug34348delete(ctx, step, ops, rowmax) == NDBT_OK);
          // 344063 (1 fragment)
          ndbout << "dir range released at " << rowcnt << endl;
          break;
        }
      }
      chk1(result == NDBT_OK);
      assert(BitmaskImpl::count(sz, rowmask)== (Uint32)rowcnt);

      // delete about 1/2 remaining
      while (result == NDBT_OK)
      {
        int i;
        for (i = 0; result == NDBT_OK && i < rowmax; i++)
        {
          if (!BitmaskImpl::get(sz, rowmask, i))
            continue;
          if (myRandom48(100) < 50)
            continue;
          chk1(runBug34348delete(ctx, step, ops, i) == NDBT_OK);
          BitmaskImpl::clear(sz, rowmask, i);
          rowcnt--;
        }
        ndbout << "deleted down to " << rowcnt << endl;
        break;
      }
      chk1(result == NDBT_OK);
      assert(BitmaskImpl::count(sz, rowmask)== (Uint32)rowcnt);

      // insert until full again
      while (result == NDBT_OK)
      {
        int i;
        for (i = 0; result == NDBT_OK && i < rowmax; i++)
        {
          if (BitmaskImpl::get(sz, rowmask, i))
            continue;
          bool rangeFull;
          chk1(runBug34348insert(ctx, step, ops, i, &rangeFull) == NDBT_OK);
          // assume all can be inserted back
          chk2(!rangeFull, "dir range full too early at " << rowcnt);
          BitmaskImpl::set(sz, rowmask, i);
          rowcnt++;
        }
        chk1(result == NDBT_OK);
        ndbout << "inserted all back to " << rowcnt << endl;
        break;
      }
      chk1(result == NDBT_OK);
      assert(BitmaskImpl::count(sz, rowmask)== (Uint32)rowcnt);

      // delete all
      while (result == NDBT_OK)
      {
        int i;
        for (i = 0; result == NDBT_OK && i < rowmax; i++)
        {
          if (!BitmaskImpl::get(sz, rowmask, i))
            continue;
          chk1(runBug34348delete(ctx, step, ops, i) == NDBT_OK);
          BitmaskImpl::clear(sz, rowmask, i);
          rowcnt--;
        }
        ndbout << "deleted all" << endl;
        break;
      }
      chk1(result == NDBT_OK);
      assert(BitmaskImpl::count(sz, rowmask)== (Uint32)rowcnt);
      assert(rowcnt == 0);

      loop++;
    }

    chk2(pDict->dropTable(tabname_bug34348) == 0, pDict->getNdbError());

    chk1(restarter.insertErrorInAllNodes(errInsCLEAR) == 0);
    ndbout << "error insert clear done" << endl;
    break;
  }

  if (result != NDBT_OK && restarter.insertErrorInAllNodes(errInsCLEAR) != 0)
    g_err << "error insert clear failed" << endl;

  delete [] rowmask;
  rowmask = 0;
  return result;
}

#define check(b, e) \
  if (!(b)) { g_err << "ERR: " << step->getName() << " failed on line " << __LINE__ << ": " << e.getNdbError() << endl; return NDBT_FAILED; }

int runUnlocker(NDBT_Context* ctx, NDBT_Step* step){
  int loops = ctx->getNumLoops();
  int records = ctx->getNumRecords();
  int batchSize = ctx->getProperty("Batchsize", 1);
  int doubleUnlock = ctx->getProperty("DoubleUnlock", (Uint32)0);
  int lm = ctx->getProperty("LockMode", NdbOperation::LM_Read);
  int i = 0;
  HugoOperations hugoOps(*ctx->getTab());
  Ndb* ndb = GETNDB(step);

  g_err << "Unlocker : ";
  g_err << "Loops = " << loops << " Records = " << records << " Batchsize = " 
        << batchSize << endl;

  while(i++ < loops) 
  {
    g_err << i << " ";

    check(hugoOps.startTransaction(ndb) == 0, (*ndb));

    const int maxRetries = 10;
    int retryAttempt = 0;
    int r = records;
    Vector<const NdbLockHandle*> lockHandles;

    while(r > 0)
    {
      int batchContents = MIN(r, batchSize);

      check(hugoOps.pkReadRecordLockHandle(ndb,
                                           lockHandles,
                                           records - r,
                                           batchContents,
                                           (NdbOperation::LockMode)lm) == 0, 
            hugoOps);

      r-= batchContents;

      if (hugoOps.execute_NoCommit(ndb) != 0)
      {
        NdbError err = hugoOps.getNdbError();
        if ((err.status == NdbError::TemporaryError) &&
            retryAttempt < maxRetries){
          ERR(err);
          NdbSleep_MilliSleep(50);
          retryAttempt++;
          lockHandles.clear();
          check(hugoOps.closeTransaction(ndb) == 0,
                hugoOps);
          check(hugoOps.startTransaction(ndb) == 0, (*ndb));
          continue;
        }
        ERR(err);
        return NDBT_FAILED;
      }

      check(hugoOps.pkUnlockRecord(ndb,
                                  lockHandles) == 0, 
            hugoOps);
      
      check(hugoOps.execute_NoCommit(ndb) == 0, 
            hugoOps);

      if (doubleUnlock)
      {
        NdbOperation::AbortOption ao;
        switch(rand() % 2)
        {
        case 0:
          ao = NdbOperation::AbortOnError;
          break;
        case 1:
        default:
          ao = NdbOperation::AO_IgnoreError;
          break;
        }

        g_err << "Double unlock, abort option is "
              << ao << endl;

        /* Failure scenario */
        check(hugoOps.pkUnlockRecord(ndb,
                                     lockHandles,
                                     0,    // offset
                                     ~(0), // NumRecords
                                     ao) == 0, 
              hugoOps);
        
        check(hugoOps.execute_NoCommit(ndb,
                                       DefaultAbortOption) != 0, 
              hugoOps);

        /* 417 = Bad operation reference */
        check(hugoOps.getNdbError().code == 417,
              hugoOps);

        
        if (ao == NdbOperation::AbortOnError)
        {
          /* Restart transaction and continue with next loop iteration */
          r = 0;
          lockHandles.clear();
          check(hugoOps.closeTransaction(ndb) == 0,
                hugoOps);
          check(hugoOps.startTransaction(ndb) == 0, (*ndb));
          
          continue;
        }
        /* Otherwise, IgnoreError, so let's attempt to
         * continue
         */
      }
      
      check(hugoOps.releaseLockHandles(ndb,
                                       lockHandles) == 0,
            hugoOps);
      
      lockHandles.clear();
    }
    
    switch(rand() % 3)
    {
    case 0:
      check(hugoOps.execute_Commit(ndb) == 0,
            hugoOps);
      break;
    case 1:
      check(hugoOps.execute_Rollback(ndb) == 0,
            hugoOps);
      break;
    default:
      /* Do nothing, just close */
      break;
    }
    
    check(hugoOps.closeTransaction(ndb) == 0,
          hugoOps);

  }

  g_err << endl;

  return NDBT_OK;
}

template class Vector<NdbRecAttr*>;

int
runBug54986(NDBT_Context* ctx, NDBT_Step* step)
{
  NdbRestarter restarter;
  Ndb* pNdb = GETNDB(step);
  NdbDictionary::Dictionary* pDict = pNdb->getDictionary();
  const NdbDictionary::Table * pTab = ctx->getTab();
  NdbDictionary::Table copy = *pTab;

  BaseString name;
  name.assfmt("%s_COPY", copy.getName());
  copy.setName(name.c_str());
  pDict->createTable(copy);
  const NdbDictionary::Table * copyTab = pDict->getTable(copy.getName());

  HugoTransactions hugoTrans(*pTab);
  hugoTrans.loadTable(pNdb, 20);
  hugoTrans.clearTable(pNdb);

  const Uint32 rows = 5000;

  HugoTransactions hugoTransCopy(*copyTab);
  hugoTransCopy.loadTable(pNdb, rows);

  {
    restarter.getNumDbNodes(); // connect
    int filter[] = { 15, NDB_MGM_EVENT_CATEGORY_CHECKPOINT, 0 };
    NdbLogEventHandle handle =
      ndb_mgm_create_logevent_handle(restarter.handle, filter);
    for (Uint32 i = 0; i<3; i++)
    {
      int dump[] = { DumpStateOrd::DihStartLcpImmediately };

      struct ndb_logevent event;

      restarter.dumpStateAllNodes(dump, 1);
      while(ndb_logevent_get_next(handle, &event, 0) >= 0 &&
            event.type != NDB_LE_LocalCheckpointStarted);
      while(ndb_logevent_get_next(handle, &event, 0) >= 0 &&
            event.type != NDB_LE_LocalCheckpointCompleted);
    }
    ndb_mgm_destroy_logevent_handle(&handle);
  }

  for (int i = 0; i<5; i++)
  {
    int val1 = DumpStateOrd::DihMaxTimeBetweenLCP;
    int val2 = 7099; // Force start

    restarter.dumpStateAllNodes(&val1, 1);
    int val[] = { DumpStateOrd::CmvmiSetRestartOnErrorInsert, 1 };
    restarter.dumpStateAllNodes(val, 2);

    restarter.insertErrorInAllNodes(932); // prevent arbit shutdown

    HugoTransactions hugoTrans(*pTab);
    hugoTrans.loadTable(pNdb, 20);

    restarter.dumpStateAllNodes(&val2, 1);

    NdbSleep_SecSleep(15);
    hugoTrans.clearTable(pNdb);

    hugoTransCopy.pkReadRecords(pNdb, rows);

    HugoOperations hugoOps(*pTab);
    hugoOps.startTransaction(pNdb);
    hugoOps.pkInsertRecord(pNdb, 1);
    hugoOps.execute_NoCommit(pNdb);

    restarter.insertErrorInAllNodes(5056);
    restarter.dumpStateAllNodes(&val2, 1);
    restarter.waitClusterNoStart();
    int vall = 11009;
    restarter.dumpStateAllNodes(&vall, 1);
    restarter.startAll();
    restarter.waitClusterStarted();
  }

  pDict->dropTable(copy.getName());

  // remove 25-page pgman
  restarter.restartAll(false, true, true);
  restarter.waitClusterNoStart();
  restarter.startAll();
  restarter.waitClusterStarted();
  return NDBT_OK;
}

int
runBug54944(NDBT_Context* ctx, NDBT_Step* step)
{
  Ndb* pNdb = GETNDB(step);
  const NdbDictionary::Table * pTab = ctx->getTab();
  NdbRestarter res;

  for (Uint32 i = 0; i<5; i++)
  {
    Uint32 rows = 5000 + i * 2000;
    HugoOperations hugoOps(*pTab);
    hugoOps.startTransaction(pNdb);

    for (Uint32 r = 0; r < rows; r++)
    {
      for (Uint32 b = 0; b<100; b++, r++)
      {
        hugoOps.pkInsertRecord(pNdb, r);
      }
      hugoOps.execute_NoCommit(pNdb);
    }

    res.insertErrorInAllNodes(8087);

    HugoTransactions hugoTrans(*pTab);
    hugoTrans.loadTableStartFrom(pNdb, 50000, 100);

    hugoOps.execute_Rollback(pNdb);
    hugoTrans.clearTable(pNdb);

    res.insertErrorInAllNodes(0);
  }
  return NDBT_OK;
}

int
runBug59496_scan(NDBT_Context* ctx, NDBT_Step* step)
{
  Ndb* pNdb = GETNDB(step);
  const NdbDictionary::Table * pTab = ctx->getTab();
  NdbRestarter res;
  int rowcount = ctx->getProperty("CHECK_ROWCOUNT", Uint32(0));
  int records = ctx->getNumRecords();
  if (rowcount == 0)
    records = 0;

  HugoTransactions hugoTrans(*pTab);
  while (!ctx->isTestStopped())
  {
    if (hugoTrans.scanReadRecords(pNdb,
                                  records, 0, 0,
                                  NdbOperation::LM_CommittedRead,
                                  (int)NdbScanOperation::SF_TupScan) != NDBT_OK)
      return NDBT_FAILED;
  }
  return NDBT_OK;
}

int
runBug59496_case1(NDBT_Context* ctx, NDBT_Step* step)
{
  Ndb* pNdb = GETNDB(step);
  NdbRestarter res;

  int loops = ctx->getNumLoops();
  int records = ctx->getNumRecords();

  HugoOperations hugoOps(*ctx->getTab());
  for (int i = 0; i < loops; i++)
  {
    hugoOps.startTransaction(pNdb);
    hugoOps.pkInsertRecord(pNdb, 0, records, 0);
    hugoOps.execute_NoCommit(pNdb);
    hugoOps.pkUpdateRecord(pNdb, 0, records, rand());
    hugoOps.execute_NoCommit(pNdb);
    hugoOps.pkUpdateRecord(pNdb, 0, records, rand());
    hugoOps.execute_NoCommit(pNdb);
    res.insertErrorInAllNodes(8089);
    hugoOps.execute_Commit(pNdb);
    res.insertErrorInAllNodes(0);
    hugoOps.closeTransaction(pNdb);
    hugoOps.clearTable(pNdb);
  }
  ctx->stopTest();
  return NDBT_OK;
}

int
runBug59496_case2(NDBT_Context* ctx, NDBT_Step* step)
{
  Ndb* pNdb = GETNDB(step);
  NdbRestarter res;

  int loops = ctx->getNumLoops();
  int records = ctx->getNumRecords();

  HugoOperations hugoOps(*ctx->getTab());
  for (int i = 0; i < loops; i++)
  {
    hugoOps.startTransaction(pNdb);
    hugoOps.pkDeleteRecord(pNdb, 0, records);
    hugoOps.execute_NoCommit(pNdb);
    hugoOps.pkInsertRecord(pNdb, 0, records, 0);
    hugoOps.execute_NoCommit(pNdb);

    res.insertErrorInAllNodes(8089);
    hugoOps.execute_Rollback(pNdb);
    res.insertErrorInAllNodes(0);

    hugoOps.closeTransaction(pNdb);
  }
  ctx->stopTest();
  return NDBT_OK;
}

#define CHK_RET_FAILED(x) if (!(x)) { ndbout_c("Failed on line: %u", __LINE__); return NDBT_FAILED; }

int
runTest899(NDBT_Context* ctx, NDBT_Step* step)
{
  Ndb* pNdb = GETNDB(step);
  const NdbDictionary::Table* pTab = ctx->getTab();

  const int rows = ctx->getNumRecords();
  const int loops = ctx->getNumLoops();
  const int batch = ctx->getProperty("Batch", Uint32(50));
  const int until_stopped = ctx->getProperty("UntilStopped");

  const NdbRecord * pRowRecord = pTab->getDefaultRecord();
  CHK_RET_FAILED(pRowRecord != 0);

  const Uint32 len = NdbDictionary::getRecordRowLength(pRowRecord);
  Uint8 * pRow = new Uint8[len];

  int count_ok = 0;
  int count_failed = 0;
  int count_899 = 0;
  for (int i = 0; i < loops || (until_stopped && !ctx->isTestStopped()); i++)
  {
    ndbout_c("loop: %d",i);
    int result = 0;
    for (int rowNo = 0; rowNo < rows;)
    {
      NdbTransaction* pTrans = pNdb->startTransaction();
      CHK_RET_FAILED(pTrans != 0);

      for (int b = 0; rowNo < rows && b < batch; rowNo++, b++)
      {
        bzero(pRow, len);

        HugoCalculator calc(* pTab);

        NdbOperation::OperationOptions opts;
        bzero(&opts, sizeof(opts));

        const NdbOperation* pOp = 0;
        switch(i % 2){
        case 0:
          calc.setValues(pRow, pRowRecord, rowNo, rand());
          pOp = pTrans->writeTuple(pRowRecord, (char*)pRow,
                                   pRowRecord, (char*)pRow,
                                   0,
                                   &opts,
                                   sizeof(opts));
          result = pTrans->execute(NoCommit);
          break;
        case 1:
          calc.setValues(pRow, pRowRecord, rowNo, rand());
          pOp = pTrans->deleteTuple(pRowRecord, (char*)pRow,
                                    pRowRecord, (char*)pRow,
                                    0,
                                    &opts,
                                    sizeof(opts));
          result = pTrans->execute(NoCommit, AO_IgnoreError);
          break;
        }

        CHK_RET_FAILED(pOp != 0);

        if (result != 0)
        {
          goto found_error;
        }
      }
      result = pTrans->execute(Commit);

      if (result != 0)
      {
    found_error:
        count_failed++;
        NdbError err = pTrans->getNdbError();
        if (! (err.status == NdbError::TemporaryError ||
               err.classification == NdbError::NoDataFound ||
               err.classification == NdbError::ConstraintViolation))
        {
          ndbout << err << endl;
        }
        CHK_RET_FAILED(err.status == NdbError::TemporaryError ||
                       err.classification == NdbError::NoDataFound ||
                       err.classification == NdbError::ConstraintViolation);
        if (err.code == 899)
        {
          count_899++;
          ndbout << err << endl;
        }
      }
      else
      {
        count_ok++;
      }
      pTrans->close();
    }
  }

  ndbout_c("count_ok: %d count_failed: %d (899: %d)",
           count_ok, count_failed, count_899);
  delete [] pRow;

  return count_899 == 0 ? NDBT_OK : NDBT_FAILED;
}

int
runInit899(NDBT_Context* ctx, NDBT_Step* step)
{
  NdbRestarter restarter;
  int val = DumpStateOrd::DihMinTimeBetweenLCP;
  restarter.dumpStateAllNodes(&val, 1);

  Ndb* pNdb = GETNDB(step);
  const NdbDictionary::Table* pTab = ctx->getTab();
  const NdbDictionary::Table * pTab2 = pNdb->getDictionary()->
    getTable(pTab->getName());

  int tableId = pTab2->getObjectId();
  int val2[] = { DumpStateOrd::BackupErrorInsert, 10042, tableId };

  for (int i = 0; i < restarter.getNumDbNodes(); i++)
  {
    if (i & 1)
    {
      int nodeId = restarter.getDbNodeId(i);
      ndbout_c("Setting slow LCP of table %d on node %d",
               tableId, nodeId);
      restarter.dumpStateOneNode(nodeId, val2, 3);
    }
  }

  return NDBT_OK;
}

int
runEnd899(NDBT_Context* ctx, NDBT_Step* step)
{
  // reset LCP speed
  NdbRestarter restarter;
  int val[] = { DumpStateOrd::DihMinTimeBetweenLCP, 0 };
  restarter.dumpStateAllNodes(val, 2);

  restarter.insertErrorInAllNodes(0);
  return NDBT_OK;
}


int initSubscription(NDBT_Context* ctx, NDBT_Step* step){
  /* Subscribe to events on the table, and put access
   * to the subscription somewhere handy
   */
  Ndb* pNdb = GETNDB(step);
  const NdbDictionary::Table& tab = *ctx->getTab();
  bool merge_events = false;
  bool report = false;

  char eventName[1024];
  sprintf(eventName,"%s_EVENT",tab.getName());

  NdbDictionary::Dictionary *myDict = pNdb->getDictionary();

  if (!myDict) {
    g_err << "Dictionary not found "
	  << pNdb->getNdbError().code << " "
	  << pNdb->getNdbError().message << endl;
    return NDBT_FAILED;
  }

  myDict->dropEvent(eventName);

  NdbDictionary::Event myEvent(eventName);
  myEvent.setTable(tab.getName());
  myEvent.addTableEvent(NdbDictionary::Event::TE_ALL);
  for(int a = 0; a < tab.getNoOfColumns(); a++){
    myEvent.addEventColumn(a);
  }
  myEvent.mergeEvents(merge_events);

  if (report)
    myEvent.setReport(NdbDictionary::Event::ER_SUBSCRIBE);

  int res = myDict->createEvent(myEvent); // Add event to database

  if (res == 0)
    myEvent.print();
  else if (myDict->getNdbError().classification ==
	   NdbError::SchemaObjectExists)
  {
    g_info << "Event creation failed event exists\n";
    res = myDict->dropEvent(eventName);
    if (res) {
      g_err << "Failed to drop event: "
	    << myDict->getNdbError().code << " : "
	    << myDict->getNdbError().message << endl;
      return NDBT_FAILED;
    }
    // try again
    res = myDict->createEvent(myEvent); // Add event to database
    if (res) {
      g_err << "Failed to create event (1): "
	    << myDict->getNdbError().code << " : "
	    << myDict->getNdbError().message << endl;
      return NDBT_FAILED;
    }
  }
  else
  {
    g_err << "Failed to create event (2): "
	  << myDict->getNdbError().code << " : "
	  << myDict->getNdbError().message << endl;
    return NDBT_FAILED;
  }

  return NDBT_OK;
}

int removeSubscription(NDBT_Context* ctx, NDBT_Step* step){
  /* Remove subscription created above */
  Ndb* pNdb = GETNDB(step);
  const NdbDictionary::Table& tab = *ctx->getTab();

  char eventName[1024];
  sprintf(eventName,"%s_EVENT",tab.getName());

  NdbDictionary::Dictionary *myDict = pNdb->getDictionary();

  if (!myDict) {
    g_err << "Dictionary not found "
	  << pNdb->getNdbError().code << " "
	  << pNdb->getNdbError().message << endl;
    return NDBT_FAILED;
  }

  myDict->dropEvent(eventName);

  return NDBT_OK;
}

int runVerifyRowCount(NDBT_Context* ctx, NDBT_Step* step)
{
  Ndb* ndb = GETNDB(step);

  /* Check that number of results returned by a normal scan
   * and per-fragment rowcount sum are equal
   */
  Uint32 rowCountSum = 0;
  Uint32 rowScanCount = 0;

  int result = NDBT_OK;
  do
  {
    NdbTransaction* trans = ndb->startTransaction();
    CHECK(trans != NULL);

    NdbScanOperation* scan = trans->getNdbScanOperation(ctx->getTab());
    CHECK(scan != NULL);

    CHECK(scan->readTuples(NdbScanOperation::LM_CommittedRead) == 0);

    NdbInterpretedCode code;

    CHECK(code.interpret_exit_last_row() == 0);
    CHECK(code.finalise() == 0);

    NdbRecAttr* rowCountRA = scan->getValue(NdbDictionary::Column::ROW_COUNT);
    CHECK(rowCountRA != NULL);
    CHECK(scan->setInterpretedCode(&code) == 0);

    CHECK(trans->execute(NoCommit) == 0);

    while (scan->nextResult() == 0)
      rowCountSum+= rowCountRA->u_32_value();

    trans->close();

    trans = ndb->startTransaction();
    CHECK(trans != NULL);

    scan = trans->getNdbScanOperation(ctx->getTab());
    CHECK(scan != NULL);

    CHECK(scan->readTuples(NdbScanOperation::LM_CommittedRead) == 0);

    rowCountRA = scan->getValue(NdbDictionary::Column::ROW_COUNT);
    CHECK(rowCountRA != NULL);

    CHECK(trans->execute(NoCommit) == 0);

    while (scan->nextResult() == 0)
      rowScanCount++;

    trans->close();
  }
  while(0);

  if (result == NDBT_OK)
  {
    ndbout_c("Sum of fragment row counts : %u  Number rows scanned : %u",
             rowCountSum,
             rowScanCount);

    if (rowCountSum != rowScanCount)
    {
      ndbout_c("MISMATCH");
      result = NDBT_FAILED;
    }
  }

  return result;
}

enum ApiEventType { Insert, Update, Delete };

template class Vector<ApiEventType>;

struct EventInfo
{
  ApiEventType type;
  int id;
  Uint64 gci;
};
template class Vector<EventInfo>;

int collectEvents(Ndb* ndb,
                  HugoCalculator& calc,
                  const NdbDictionary::Table& tab,
                  Vector<EventInfo>& receivedEvents,
                  int idCol,
                  int updateCol,
                  Vector<NdbRecAttr*>* beforeAttrs,
                  Vector<NdbRecAttr*>* afterAttrs)
{
  int MaxTimeouts = 5;
  while (true)
  {
    int res = ndb->pollEvents(1000);

    if (res > 0)
    {
      NdbEventOperation* pOp;
      while ((pOp = ndb->nextEvent()))
      {
        bool isDelete = (pOp->getEventType() == NdbDictionary::Event::TE_DELETE);
        Vector<NdbRecAttr*>* whichVersion =
          isDelete?
          beforeAttrs :
          afterAttrs;
        int id = (*whichVersion)[idCol]->u_32_value();
        Uint64 gci = pOp->getGCI();
        Uint32 anyValue = pOp->getAnyValue();
        Uint32 scenario = ((anyValue >> 24) & 0xff) -1;
        Uint32 optype = ((anyValue >> 16) & 0xff);
        Uint32 recNum = (anyValue & 0xffff);

        g_err << "# " << receivedEvents.size()
              << " GCI : " << (gci >> 32)
              << "/"
              << (gci & 0xffffffff)
              << " id : "
              << id
              << " scenario : " << scenario
              << " optype : " << optype
              << " record : " << recNum
              << "  ";

        /* Check event has self-consistent data */
        int updatesValue = (*whichVersion)[updateCol]->u_32_value();

        if ((*whichVersion)[updateCol]->isNULL() ||
            (*whichVersion)[idCol]->isNULL())
        {
          g_err << "Null update/id cols : REFRESH of !EXISTS  ";
        }

        g_err << "(Updates val = " << updatesValue << ")";

        for (int i=0; i < (int) whichVersion->size(); i++)
        {
          /* Check PK columns and also other columns for non-delete */
          if (!isDelete ||
              tab.getColumn(i)->getPrimaryKey())
          {
            NdbRecAttr* ra = (*whichVersion)[i];
            if (calc.verifyRecAttr(recNum, updatesValue, ra) != 0)
            {
              g_err << "Verify failed on recNum : " << recNum << " with updates value "
                    << updatesValue << " for column " << ra->getColumn()->getAttrId()
                    << endl;
              return NDBT_FAILED;
            }
          }
        }

        EventInfo ei;

        switch (pOp->getEventType())
        {
        case NdbDictionary::Event::TE_INSERT:
          g_err << " Insert event" << endl;
          ei.type = Insert;
          break;
        case NdbDictionary::Event::TE_DELETE:
          ei.type = Delete;
          g_err << " Delete event" << endl;
          break;
        case NdbDictionary::Event::TE_UPDATE:
          ei.type = Update;
          g_err << " Update event" << endl;
          break;
        default:
          g_err << " Event type : " << pOp->getEventType() << endl;
          abort();
          break;
        }

        ei.id = recNum;
        ei.gci = gci;

        receivedEvents.push_back(ei);
      }
    }
    else
    {
      if (--MaxTimeouts == 0)
      {
        break;
      }
    }
  }

  return NDBT_OK;
}

int verifyEvents(const Vector<EventInfo>& receivedEvents,
                 const Vector<ApiEventType>& expectedEvents,
                 int records)
{
  /* Now verify received events against expected
   * This is messy as events occurring in the same epoch are unordered
   * except via id, so we use id-duplicates to determine which event
   * sequence we're looking at.
   */
  g_err << "Received total of " << receivedEvents.size() << " events" << endl;
  Vector<Uint32> keys;
  Vector<Uint64> gcis;
  Uint32 z = 0;
  Uint64 z2 = 0;
  keys.fill(records, z);
  gcis.fill(records, z2);
  Uint64 currGci = 0;

  for (Uint32 e=0; e < receivedEvents.size(); e++)
  {
    EventInfo ei = receivedEvents[e];

    if (ei.gci != currGci)
    {
      if (ei.gci < currGci)
        abort();

      /* Epoch boundary */
      /* At this point, all id counts must be equal */
      for (int i=0; i < records; i++)
      {
        if (keys[i] != keys[0])
        {
          g_err << "Count for id " << i
                << " is " << keys[i]
                << " but should be " << keys[0] << endl;
          return NDBT_OK;
        }
      }

      currGci = ei.gci;
    }

    Uint32 eventIndex = keys[ei.id];
    keys[ei.id]++;

    ApiEventType et = expectedEvents[eventIndex];

    if (ei.type != et)
    {
      g_err << "Expected event of type " << et
            << " but found " << ei.type
            << " at expectedEvent " << eventIndex
            << " and event num " << e << endl;
      return NDBT_FAILED;
    }
  }

  return NDBT_OK;
}

int runRefreshTuple(NDBT_Context* ctx, NDBT_Step* step){
  int records = ctx->getNumRecords();
  Ndb* ndb = GETNDB(step);

  /* Now attempt to create EventOperation */
  NdbEventOperation* pOp;
  const NdbDictionary::Table& tab = *ctx->getTab();

  char eventName[1024];
  sprintf(eventName,"%s_EVENT",tab.getName());

  pOp = ndb->createEventOperation(eventName);
  if (pOp == NULL)
  {
    g_err << "Failed to create event operation\n";
    return NDBT_FAILED;
  }

  HugoCalculator calc(tab);
  Vector<NdbRecAttr*> eventAfterRecAttr;
  Vector<NdbRecAttr*> eventBeforeRecAttr;
  int updateCol = -1;
  int idCol = -1;

  /* Now request all attributes */
  for (int a = 0; a < tab.getNoOfColumns(); a++)
  {
    eventAfterRecAttr.push_back(pOp->getValue(tab.getColumn(a)->getName()));
    eventBeforeRecAttr.push_back(pOp->getPreValue(tab.getColumn(a)->getName()));
    if (calc.isIdCol(a))
      idCol = a;
    if (calc.isUpdateCol(a))
      updateCol = a;
  }

  /* Now execute the event */
  if (pOp->execute())
  {
    g_err << "Event operation execution failed : " << pOp->getNdbError() << endl;
    return NDBT_FAILED;
  }

  HugoOperations hugoOps(*ctx->getTab());
  int scenario = 0;

  Vector<ApiEventType> expectedEvents;

  for (scenario = 0; scenario < 2; scenario++)
  {
    g_err << "Scenario = " << scenario
          << " ( Refresh "
          << ((scenario == 0)? "before":"after")
          << " operations )" << endl;
    int optype = 0;
    bool done = false;
    int expectedError = 0;
    do
    {
      check(hugoOps.startTransaction(ndb) == 0, hugoOps);

      if (scenario == 0)
      {
        g_err << "Refresh before operations" << endl;
        int anyValue =
          ((1) << 8) |
          optype;
        check(hugoOps.pkRefreshRecord(ndb, 0, records, anyValue) == 0, hugoOps);
      }

      switch(optype)
      {
      case 0:
      {
        /* Refresh with no data present */
        g_err << "  Do nothing" << endl;
        expectedError = 0; /* Single refresh should always be fine */
        expectedEvents.push_back(Delete);
        break;
      }
      case 1:
      {
        /* [Refresh] Insert [Refresh] */
        g_err << "  Insert" << endl;
        check(hugoOps.pkInsertRecord(ndb, 0, records, 1) == 0, hugoOps);
        if (scenario == 0)
        {
          /* Tuple already existed error when we insert after refresh */
          expectedError = 630;
          expectedEvents.push_back(Delete);
        }
        else
        {
          expectedError = 0;
          expectedEvents.push_back(Insert);
        }
        /* Tuple already existed error when we insert after refresh */
        break;
      }
      case 2:
      {
        /* Refresh */
        g_err << "  Refresh" << endl;
        if (scenario == 0)
        {
          expectedEvents.push_back(Delete);
        }
        else
        {
          expectedEvents.push_back(Insert);
        }
        expectedError = 0;
        break;
      }
      case 3:
      {
        /* [Refresh] Update [Refresh] */
        g_err << "  Update" << endl;
        check(hugoOps.pkUpdateRecord(ndb, 0, records, 3) == 0, hugoOps);
        if (scenario == 0)
        {
          expectedError = 920;
          expectedEvents.push_back(Delete);
        }
        else
        {
          expectedError = 0;
          expectedEvents.push_back(Insert);
        }
        break;
      }
      case 4:
      {
        /* [Refresh] Delete [Refresh] */
        g_err << "  [Refresh] Delete [Refresh]" << endl;
        if (scenario == 0)
        {
          expectedError = 920;
          expectedEvents.push_back(Delete);
        }
        else
        {
          expectedError = 0;
          expectedEvents.push_back(Delete);
        }
        check(hugoOps.pkDeleteRecord(ndb, 0, records) == 0, hugoOps);
        break;
      }
      case 5:
      {
        g_err << "  Refresh" << endl;
        expectedError = 0;
        expectedEvents.push_back(Delete);
        /* Refresh with no data present */
        break;
      }
      case 6:
      {
        g_err << "  Double refresh" << endl;
        int anyValue =
          ((2) << 8) |
          optype;
        check(hugoOps.pkRefreshRecord(ndb, 0, records, anyValue) == 0, hugoOps);
        expectedError = 920; /* Row operation defined after refreshTuple() */
        expectedEvents.push_back(Delete);
      }
      default:
        done = true;
        break;
      }

      if (scenario == 1)
      {
        g_err << "Refresh after operations" << endl;
        int anyValue =
          ((4) << 8) |
          optype;
        check(hugoOps.pkRefreshRecord(ndb, 0, records, anyValue) == 0, hugoOps);
      }

      int rc = hugoOps.execute_Commit(ndb, AO_IgnoreError);
      check(rc == expectedError, hugoOps);

      check(hugoOps.closeTransaction(ndb) == 0, hugoOps);

      optype++;


      /* Now check fragment counts vs findable row counts */
      if (runVerifyRowCount(ctx, step) != NDBT_OK)
        return NDBT_FAILED;

    } while (!done);
  } // for scenario...

  /* Now check fragment counts vs findable row counts */
  if (runVerifyRowCount(ctx, step) != NDBT_OK)
    return NDBT_FAILED;

  /* Now let's dump and check the events */
  g_err << "Expecting the following sequence..." << endl;
  for (Uint32 i=0; i < expectedEvents.size(); i++)
  {
    g_err << i << ".  ";
    switch(expectedEvents[i])
    {
    case Insert:
      g_err << "Insert" << endl;
      break;
    case Update:
      g_err << "Update" << endl;
      break;
    case Delete:
      g_err << "Delete" << endl;
      break;
    default:
      abort();
    }
  }

  Vector<EventInfo> receivedEvents;

  int rc = collectEvents(ndb, calc, tab, receivedEvents, idCol, updateCol,
                         &eventBeforeRecAttr,
                         &eventAfterRecAttr);
  if (rc == NDBT_OK)
  {
    rc = verifyEvents(receivedEvents,
                      expectedEvents,
                      records);
  }

  if (ndb->dropEventOperation(pOp) != 0)
  {
    g_err << "Drop Event Operation failed : " << ndb->getNdbError() << endl;
    return NDBT_FAILED;
  }

  return rc;
};

enum PreRefreshOps
{
  PR_NONE,
  PR_INSERT,
  PR_INSERTDELETE,
  PR_DELETE
};

struct RefreshScenario
{
  const char*   name;
  bool          preExist;
  PreRefreshOps preRefreshOps;
};

static RefreshScenario refreshTests[] = {
  { "No row, No pre-ops",        false, PR_NONE         },
  { "No row, Insert pre-op",     false, PR_INSERT       },
  { "No row, Insert-Del pre-op", false, PR_INSERTDELETE },
  { "Row exists, No pre-ops",    true,  PR_NONE         },
  { "Row exists, Delete pre-op", true,  PR_DELETE       }
};

enum OpTypes
{
  OP_READ_C,
  OP_READ_S,
  OP_READ_E,
  OP_INSERT,
  OP_UPDATE,
  OP_WRITE,
  OP_DELETE,
  OP_LAST
};

const char* opTypeNames[] =
{
  "READ_C",
  "READ_S",
  "READ_E",
  "INSERT",
  "UPDATE",
  "WRITE",
  "DELETE"
};


int
runRefreshLocking(NDBT_Context* ctx, NDBT_Step* step)
{
  /* Check that refresh in various situations has the
   * locks we expect it to
   * Scenario combinations :
   *   Now row pre-existing | Row pre-existing
   *   Trans1 : Refresh | Insert-Refresh | Insert-Delete-Refresh
   *            Delete-Refresh
   *   Trans2 : Read [Committed|Shared|Exclusive] | Insert | Update
   *            Write | Delete
   *
   * Expectations : Read committed  always non-blocking
   *                Read committed sees pre-existing row
   *                All other trans2 operations deadlock
   */

  Ndb* ndb = GETNDB(step);
  Uint32 numScenarios = sizeof(refreshTests) / sizeof(refreshTests[0]);
  HugoTransactions hugoTrans(*ctx->getTab());

  for (Uint32 s = 0; s < numScenarios; s++)
  {
    RefreshScenario& scenario = refreshTests[s];

    if (scenario.preExist)
    {
      /* Create pre-existing tuple */
      if (hugoTrans.loadTable(ndb, 1) != 0)
      {
        g_err << "Pre-exist failed : " << hugoTrans.getNdbError() << endl;
        return NDBT_FAILED;
      }
    }

    if (hugoTrans.startTransaction(ndb) != 0)
    {
      g_err << "Start trans failed : " << hugoTrans.getNdbError() << endl;
      return NDBT_FAILED;
    }

    g_err << "Scenario : " << scenario.name << endl;

    /* Do pre-refresh ops */
    switch (scenario.preRefreshOps)
    {
    case PR_NONE:
      break;
    case PR_INSERT:
    case PR_INSERTDELETE:
      if (hugoTrans.pkInsertRecord(ndb, 0) != 0)
      {
        g_err << "Pre insert failed : " << hugoTrans.getNdbError() << endl;
        return NDBT_FAILED;
      }

      if (scenario.preRefreshOps == PR_INSERT)
        break;
    case PR_DELETE:
      if (hugoTrans.pkDeleteRecord(ndb, 0) != 0)
      {
        g_err << "Pre delete failed : " << hugoTrans.getNdbError() << endl;
        return NDBT_FAILED;
      }
      break;
    }

    /* Then refresh */
    if (hugoTrans.pkRefreshRecord(ndb, 0) != 0)
    {
      g_err << "Refresh failed : " << hugoTrans.getNdbError() << endl;
      return NDBT_FAILED;
    }

    /* Now execute */
    if (hugoTrans.execute_NoCommit(ndb) != 0)
    {
      g_err << "Execute failed : " << hugoTrans.getNdbError() << endl;
      return NDBT_FAILED;
    }

    {
      /* Now try ops from another transaction */
      HugoOperations hugoOps(*ctx->getTab());
      Uint32 ot = OP_READ_C;

      while (ot < OP_LAST)
      {
        if (hugoOps.startTransaction(ndb) != 0)
        {
          g_err << "Start trans2 failed : " << hugoOps.getNdbError() << endl;
          return NDBT_FAILED;
        }

        g_err << "Operation type : " << opTypeNames[ot] << endl;
        int res = 0;
        switch (ot)
        {
        case OP_READ_C:
          res = hugoOps.pkReadRecord(ndb,0,1,NdbOperation::LM_CommittedRead);
          break;
        case OP_READ_S:
          res = hugoOps.pkReadRecord(ndb,0,1,NdbOperation::LM_Read);
          break;
        case OP_READ_E:
          res = hugoOps.pkReadRecord(ndb,0,1,NdbOperation::LM_Exclusive);
          break;
        case OP_INSERT:
          res = hugoOps.pkInsertRecord(ndb, 0);
          break;
        case OP_UPDATE:
          res = hugoOps.pkUpdateRecord(ndb, 0);
          break;
        case OP_WRITE:
          res = hugoOps.pkWriteRecord(ndb, 0);
          break;
        case OP_DELETE:
          res = hugoOps.pkDeleteRecord(ndb, 0);
          break;
        case OP_LAST:
          abort();
        }

        hugoOps.execute_Commit(ndb);

        if ((ot == OP_READ_C) && (scenario.preExist))
        {
          if (hugoOps.getNdbError().code == 0)
          {
            g_err << "Read committed succeeded" << endl;
          }
          else
          {
            g_err << "UNEXPECTED : Read committed failed. " << hugoOps.getNdbError() << endl;
            return NDBT_FAILED;
          }
        }
        else
        {
          if (hugoOps.getNdbError().code == 0)
          {
            g_err << opTypeNames[ot] << " succeeded, should not have" << endl;
            return NDBT_FAILED;
          }
        }

        hugoOps.closeTransaction(ndb);

        ot = ot + 1;
      }

    }

    /* Close refresh transaction */
    hugoTrans.closeTransaction(ndb);

    if (scenario.preExist)
    {
      /* Cleanup pre-existing before next iteration */
      if (hugoTrans.pkDelRecords(ndb, 0) != 0)
      {
        g_err << "Delete pre existing failed : " << hugoTrans.getNdbError() << endl;
        return NDBT_FAILED;
      }
    }
  }

  return NDBT_OK;
}


NDBT_TESTSUITE(testBasic);
TESTCASE("PkInsert", 
	 "Verify that we can insert and delete from this table using PK"
	 "NOTE! No errors are allowed!" ){
  INITIALIZER(runInsert);
  VERIFIER(runVerifyInsert);
}
TESTCASE("PkRead", 
	   "Verify that we can insert, read and delete from this table using PK"){
  TC_PROPERTY("LockMode", NdbOperation::LM_Read);
  INITIALIZER(runLoadTable);
  STEP(runPkRead);
  FINALIZER(runClearTable);
}
TESTCASE("PkDirtyRead", 
	 "Verify that we can insert, dirty read and delete from this table using PK"){
  TC_PROPERTY("LockMode", NdbOperation::LM_Dirty);
  INITIALIZER(runLoadTable);
  STEP(runPkRead);
  FINALIZER(runClearTable);
}
TESTCASE("PkSimpleRead", 
	 "Verify that we can insert, simple read and delete from this table using PK"){
  TC_PROPERTY("LockMode", NdbOperation::LM_SimpleRead);
  INITIALIZER(runLoadTable);
  STEP(runPkRead);
  FINALIZER(runClearTable);
}
TESTCASE("PkUpdate", 
	   "Verify that we can insert, update and delete from this table using PK"){
  INITIALIZER(runLoadTable);
  STEP(runPkUpdate);
  FINALIZER(runClearTable);
}
TESTCASE("PkDelete", 
	 "Verify that we can delete from this table using PK"){
  INITIALIZER(runLoadTable);
  STEP(runPkDelete);
  FINALIZER(runClearTable);
}
TESTCASE("UpdateAndRead", 
	 "Verify that we can read and update at the same time"){
  INITIALIZER(runLoadTable);
  STEP(runPkRead);
  STEP(runPkRead);
  STEP(runPkRead);
  STEP(runPkUpdate);  
  STEP(runPkUpdate);
  STEP(runPkUpdate);
  FINALIZER(runClearTable);
}
TESTCASE("PkReadAndLocker", 
	 "Verify that we can read although there are "\
	 " a number of 1 second locks in the table"){
  INITIALIZER(runLoadTable);
  STEP(runPkReadUntilStopped);
  STEP(runLocker);
  FINALIZER(runClearTable);
}
TESTCASE("PkReadAndLocker2", 
	 "Verify that we can read and update although there are "\
	 " a number of 1 second locks in the table"){
  INITIALIZER(runLoadTable);
  STEP(runPkReadUntilStopped);
  STEP(runPkReadUntilStopped);
  STEP(runPkReadUntilStopped);
  STEP(runPkReadUntilStopped);
  STEP(runPkReadUntilStopped);
  STEP(runPkReadUntilStopped);
  STEP(runLocker);
  FINALIZER(runClearTable);
}
TESTCASE("PkReadUpdateAndLocker", 
	 "Verify that we can read and update although there are "\
	 " a number of 1 second locks in the table"){
  INITIALIZER(runLoadTable);
  STEP(runPkReadUntilStopped);
  STEP(runPkReadUntilStopped);
  STEP(runPkUpdateUntilStopped);
  STEP(runPkUpdateUntilStopped);
  STEP(runLocker);
  FINALIZER(runClearTable);
}
TESTCASE("ReadWithLocksAndInserts", 
	 "TR457: This test is added to verify that an insert of a records "\
	 "that is already in the database does not delete the record"){  
  INITIALIZER(runLoadTable);
  STEP(runPkReadUntilStopped);
  STEP(runPkReadUntilStopped);
  STEP(runLocker);
  STEP(runInsertUntilStopped);
  FINALIZER(runClearTable);
}
TESTCASE("PkInsertTwice", 
	 "Verify that we can't insert an already inserted record."
	 "Error should be returned" ){
  INITIALIZER(runLoadTable);
  STEP(runInsertTwice);
  FINALIZER(runClearTable);
}
TESTCASE("NoCommitSleep", 
	 "Verify what happens when a NoCommit transaction is aborted by "
	 "NDB because the application is sleeping" ){
  INITIALIZER(runLoadTable);
  INITIALIZER(runNoCommitSleep);
  FINALIZER(runClearTable2);
}
TESTCASE("Commit626", 
	 "Verify what happens when a Commit transaction is aborted by "
	 "NDB because the record does no exist" ){
  INITIALIZER(runClearTable2);
  INITIALIZER(runCommit626);
  FINALIZER(runClearTable2);
}
TESTCASE("CommitTry626", 
	 "Verify what happens when a Commit(TryCommit) \n"
	 "transaction is aborted by "
	 "NDB because the record does no exist" ){
  INITIALIZER(runClearTable2);
  INITIALIZER(runCommit_TryCommit626);
  FINALIZER(runClearTable2);
}
TESTCASE("CommitAsMuch626", 
	 "Verify what happens when a Commit(CommitAsMuchAsPossible) \n"
	 "transaction is aborted by\n"
	 "NDB because the record does no exist" ){
  INITIALIZER(runClearTable2);
  INITIALIZER(runCommit_CommitAsMuchAsPossible626);
  FINALIZER(runClearTable2);
}
TESTCASE("NoCommit626", 
	 "Verify what happens when a NoCommit transaction is aborted by "
	 "NDB because the record does no exist" ){
  INITIALIZER(runClearTable2);
  INITIALIZER(runNoCommit626);
  FINALIZER(runClearTable2);
}
TESTCASE("NoCommitRollback626", 
	 "Verify what happens when a NoCommit transaction is aborted by "
	 "NDB because the record does no exist and then we try to rollback\n"
	 "the transaction" ){
  INITIALIZER(runClearTable2);
  INITIALIZER(runNoCommitRollback626);
  FINALIZER(runClearTable2);
}
TESTCASE("Commit630", 
	 "Verify what happens when a Commit transaction is aborted by "
	 "NDB because the record already exist" ){
  INITIALIZER(runLoadTable);
  INITIALIZER(runCommit630);
  FINALIZER(runClearTable2);
}
TESTCASE("CommitTry630", 
	 "Verify what happens when a Commit(TryCommit) \n"
	 "transaction is aborted by "
	 "NDB because the record already exist" ){
  INITIALIZER(runLoadTable);
  INITIALIZER(runCommit_TryCommit630);
  FINALIZER(runClearTable2);
}
TESTCASE("CommitAsMuch630", 
	 "Verify what happens when a Commit(CommitAsMuchAsPossible) \n"
	 "transaction is aborted by\n"
	 "NDB because the record already exist" ){
  INITIALIZER(runLoadTable);
  INITIALIZER(runCommit_CommitAsMuchAsPossible630);
  FINALIZER(runClearTable2);
}
TESTCASE("NoCommit630", 
	 "Verify what happens when a NoCommit transaction is aborted by "
	 "NDB because the record already exist" ){
  INITIALIZER(runLoadTable);
  INITIALIZER(runNoCommit630);
  FINALIZER(runClearTable2);
}
TESTCASE("NoCommitRollback630", 
	 "Verify what happens when a NoCommit transaction is aborted by "
	 "NDB because the record already exist and then we try to rollback\n"
	 "the transaction" ){
  INITIALIZER(runLoadTable);
  INITIALIZER(runNoCommitRollback630);
  FINALIZER(runClearTable2);
}
TESTCASE("NoCommitAndClose", 
	 "Verify what happens when a NoCommit transaction is closed "
	 "without rolling back the transaction " ){
  INITIALIZER(runLoadTable);
  INITIALIZER(runNoCommitAndClose);
  FINALIZER(runClearTable2);
}
TESTCASE("RollbackDelete", 
	 "Test rollback of a no committed delete"){
  INITIALIZER(runLoadTable);
  INITIALIZER(runCheckRollbackDelete);
  FINALIZER(runClearTable2);
}
TESTCASE("RollbackUpdate", 
	 "Test rollback of a no committed update"){
  INITIALIZER(runLoadTable);
  INITIALIZER(runCheckRollbackUpdate);
  FINALIZER(runClearTable2);
}
TESTCASE("RollbackDeleteMultiple", 
	 "Test rollback of 10 non committed delete"){
  INITIALIZER(runLoadTable);
  INITIALIZER(runCheckRollbackDeleteMultiple);
  FINALIZER(runClearTable2);
}
TESTCASE("ImplicitRollbackDelete", 
	 "Test close transaction after a no commited delete\n"
	 "this would give an implicit rollback of the delete\n"){
  INITIALIZER(runLoadTable);
  INITIALIZER(runCheckImplicitRollbackDelete);
  FINALIZER(runClearTable2);
}
TESTCASE("CommitDelete", 
	 "Test close transaction after a no commited delete\n"
	 "this would give an implicit rollback of the delete\n"){
  INITIALIZER(runLoadTable);
  INITIALIZER(runCheckCommitDelete);
  FINALIZER(runClearTable2);
}
TESTCASE("RollbackNothing", 
	 "Test rollback of nothing"){
  INITIALIZER(runLoadTable);
  INITIALIZER(runRollbackNothing);
  FINALIZER(runClearTable2);
}
TESTCASE("MassiveRollback", 
	 "Test rollback of 4096 operations"){
  INITIALIZER(runClearTable2);
  INITIALIZER(runMassiveRollback);
  FINALIZER(runClearTable2);
}
TESTCASE("MassiveRollback2", 
	 "Test rollback of 4096 operations"){
  INITIALIZER(runClearTable2);
  INITIALIZER(runMassiveRollback2);
  FINALIZER(runClearTable2);
}
TESTCASE("MassiveRollback3", 
	 "Test rollback of 4096 operations"){
  INITIALIZER(runClearTable2);
  STEP(runMassiveRollback3);
  STEP(runMassiveRollback3);
  FINALIZER(runClearTable2);
}
TESTCASE("MassiveRollback4", 
	 "Test rollback of 4096 operations"){
  INITIALIZER(runClearTable2);
  STEP(runMassiveRollback4);
  STEP(runMassiveRollback4);
  FINALIZER(runClearTable2);
}
TESTCASE("MassiveTransaction",
         "Test very large insert transaction"){
  INITIALIZER(runLoadTable2);
  FINALIZER(runClearTable2);
}
TESTCASE("TupError", 
	 "Verify what happens when we fill the db" ){
  INITIALIZER(runTupErrors);
}
TESTCASE("InsertError", "" ){
  INITIALIZER(runInsertError);
}
TESTCASE("InsertError2", "" ){
  INITIALIZER(runInsertError2);
}
TESTCASE("Fill", 
	 "Verify what happens when we fill the db" ){
  STEP(runFillTable);
}
TESTCASE("Bug25090", 
	 "Verify what happens when we fill the db" ){
  STEP(runBug25090);
}
TESTCASE("DeleteRead", 
	 "Verify Delete+Read" ){
  INITIALIZER(runLoadTable);
  INITIALIZER(runDeleteRead);
  FINALIZER(runClearTable2);
}
TESTCASE("Bug27756", 
	 "Verify what happens when we fill the db" ){
  STEP(runBug27756);
}
TESTCASE("Bug28073", 
	 "Infinite loop in lock queue" ){
  STEP(runBug28073);
}
TESTCASE("Bug20535", 
	 "Verify what happens when we fill the db" ){
  STEP(runBug20535);
}
TESTCASE("DDInsertFailUpdateBatch",
         "Verify DD insert failure effect on other ops in batch on same PK"){
  STEP(runDDInsertFailUpdateBatch);
}

TESTCASE("Bug34348",
         "Test fragment directory range full in ACC.\n"
         "NOTE: If interrupted, must clear error insert 3002 manually"){
  STEP(runBug34348);
}
TESTCASE("UnlockBatch",
         "Test that batched unlock operations work ok"){
  TC_PROPERTY("Batchsize", 33);
  INITIALIZER(runLoadTable);
  STEP(runUnlocker);
  FINALIZER(runClearTable);
}
TESTCASE("DoubleUnlock",
         "Test that batched unlock operations work ok"){
  TC_PROPERTY("DoubleUnlock", 1);
  INITIALIZER(runLoadTable);
  STEP(runUnlocker);
  FINALIZER(runClearTable);
}
TESTCASE("UnlockUpdateBatch",
         "Test Unlock mixed with Update"){
  TC_PROPERTY("Batchsize", 32);
  INITIALIZER(runLoadTable);
  STEP(runUnlocker);
  STEP(runUnlocker);
  STEP(runLocker);
  STEP(runPkUpdate);
  STEP(runPkUpdate);
  STEP(runPkRead);
  FINALIZER(runClearTable);
}
TESTCASE("RefreshTuple",
         "Test refreshTuple() operation properties"){
  INITIALIZER(initSubscription);
  INITIALIZER(runRefreshTuple);
  FINALIZER(removeSubscription);
}
TESTCASE("Bug54986", "")
{
  INITIALIZER(runBug54986);
}
TESTCASE("Bug54944", "")
{
  INITIALIZER(runBug54944);
}
TESTCASE("Bug59496_case1", "")
{
  STEP(runBug59496_case1);
  STEPS(runBug59496_scan, 10);
}
TESTCASE("Bug59496_case2", "")
{
  TC_PROPERTY("CHECK_ROWCOUNT", 1);
  INITIALIZER(runLoadTable);
  STEP(runBug59496_case2);
  STEPS(runBug59496_scan, 10);
}
TESTCASE("899", "")
{
  INITIALIZER(runLoadTable);
  INITIALIZER(runInit899);
  STEP(runTest899);
  FINALIZER(runEnd899);
}
TESTCASE("RefreshLocking",
         "Test Refresh locking properties")
{
  INITIALIZER(runRefreshLocking);
}
NDBT_TESTSUITE_END(testBasic);

#if 0
TESTCASE("ReadConsistency",
	 "Check that a read within a transaction returns the " \
	 "same result no matter"){
  STEP(runInsertOne);
  STEP(runReadOne);
  FINALIZER(runClearTable2);
}
TESTCASE("Fill", 
	 "Verify what happens when we fill the db" ){
  INITIALIZER(runFillTable);
  INITIALIZER(runPkRead);
  FINALIZER(runClearTable2);
}
#endif

int main(int argc, const char** argv){
  ndb_init();
  NDBT_TESTSUITE_INSTANCE(testBasic);
  return testBasic.execute(argc, argv);
}



