/* Copyright (c) 2008, 2014, 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 Street, Fifth Floor, Boston, MA 02110-1301, USA */

#include <NDBT.hpp>
#include <NDBT_Test.hpp>
#include <NdbRestarter.hpp>

#define CHECKNOTNULL(p) if ((p) == NULL) {          \
    ndbout << "Error at line " << __LINE__ << endl; \
    NDB_ERR(trans->getNdbError());                      \
    trans->close();                                 \
    return NDBT_FAILED; }

#define CHECKEQUAL(v, e) if ((e) != (v)) {            \
    ndbout << "Error at line " << __LINE__ <<         \
      " expected " << v << endl;                      \
    NDB_ERR(trans->getNdbError());                        \
    trans->close();                                   \
    return NDBT_FAILED; }


/* Setup memory as a long Varchar with 2 bytes of
 * length information
 */
Uint32 setLongVarchar(char* where, const char* what, Uint32 sz)
{
  where[0]=sz & 0xff;
  where[1]=(sz >> 8) & 0xff;
  memcpy(&where[2], what, sz);
  return (sz + 2);
}


/* Activate the given error insert in TC block
 * This is used for error insertion where a TCKEYREQ
 * is required to activate the error
 */
int activateErrorInsert(NdbTransaction* trans, 
                        const NdbRecord* record,
                        const NdbDictionary::Table* tab,
                        const char* buf,
                        NdbRestarter* restarter, 
                        Uint32 val)
{
  /* We insert the error twice to avoid what appear to be
   * races between the error insert and the subsequent
   * tests
   * Alternatively we could sleep here.
   */
  if (restarter->insertErrorInAllNodes(val) != 0){
    g_err << "error insert 1 (" << val << ") failed" << endl;
    return NDBT_FAILED;
  }
  if (restarter->insertErrorInAllNodes(val) != 0){
    g_err << "error insert 2 (" << val << ") failed" << endl;
    return NDBT_FAILED;
  }

  NdbOperation* insert= trans->getNdbOperation(tab);
  
  CHECKNOTNULL(insert);

  CHECKEQUAL(0, insert->insertTuple());

  CHECKEQUAL(0, insert->equal((Uint32) 0, 
                              NdbDictionary::getValuePtr
                              (record,
                               buf,
                               0)));
  CHECKEQUAL(0, insert->setValue(1,
                                 NdbDictionary::getValuePtr
                                 (record,
                                  buf,
                                  1)));

  CHECKEQUAL(0, trans->execute(NdbTransaction::NoCommit));

  CHECKEQUAL(0, trans->getNdbError().code);

  return NDBT_OK;
}
    
/* Test for correct behaviour using primary key operations
 * when an NDBD node's SegmentedSection pool is exhausted.
 */
int testSegmentedSectionPk(NDBT_Context* ctx, NDBT_Step* step){
  /*
   * Signal type       Exhausted @              How
   * -----------------------------------------------------
   * Long TCKEYREQ     Initial import           Consume + send
   * Long TCKEYREQ     Initial import, not first
   *                     TCKEYREQ in batch      Consume + send
   * Long TCKEYREQ     Initial import, not last
   *                     TCKEYREQ in batch      Consume + send
   * No testing of short TCKEYREQ variants as they cannot be
   * generated in mysql-5.1-telco-6.4+
   * TODO : Add short variant testing to testUpgrade.
   */

  /* We just run on one table */
  if (strcmp(ctx->getTab()->getName(), "WIDE_2COL") != 0)
    return NDBT_OK;

  const Uint32 maxRowBytes= NDB_MAX_TUPLE_SIZE_IN_WORDS * sizeof(Uint32);
  const Uint32 srcBuffBytes= NDBT_Tables::MaxVarTypeKeyBytes;
  const Uint32 maxAttrBytes= NDBT_Tables::MaxKeyMaxVarTypeAttrBytes;
  char smallKey[50];
  char srcBuff[srcBuffBytes];
  char smallRowBuf[maxRowBytes];
  char bigKeyRowBuf[maxRowBytes];
  char bigAttrRowBuf[maxRowBytes];

  /* Small key for hinting to same TC */
  Uint32 smallKeySize= setLongVarchar(&smallKey[0],
                                      "ShortKey",
                                      8);

  /* Large value source */
  memset(srcBuff, 'B', srcBuffBytes);

  const NdbRecord* record= ctx->getTab()->getDefaultRecord();

  /* Setup buffers
   * Small row buffer with small key and small data
   */ 
  setLongVarchar(NdbDictionary::getValuePtr(record,
                                            smallRowBuf,
                                            0),
                 "ShortKey",
                 8);
  NdbDictionary::setNull(record, smallRowBuf, 0, false);

  setLongVarchar(NdbDictionary::getValuePtr(record,
                                            smallRowBuf,
                                            1),
                 "ShortData",
                 9);
  NdbDictionary::setNull(record, smallRowBuf, 1, false);

  /* Big key buffer with big key and small data*/
  setLongVarchar(NdbDictionary::getValuePtr(record,
                                            bigKeyRowBuf,
                                            0),
                 &srcBuff[0],
                 srcBuffBytes);
  NdbDictionary::setNull(record, bigKeyRowBuf, 0, false);

  setLongVarchar(NdbDictionary::getValuePtr(record,
                                            bigKeyRowBuf,
                                            1),
                 "ShortData",
                 9);
  NdbDictionary::setNull(record, bigKeyRowBuf, 1, false);

  /* Big AttrInfo buffer with small key and big data */
  setLongVarchar(NdbDictionary::getValuePtr(record,
                                            bigAttrRowBuf,
                                            0),
                 "ShortKey", 
                 8);
  NdbDictionary::setNull(record, bigAttrRowBuf, 0, false);

  setLongVarchar(NdbDictionary::getValuePtr(record,
                                            bigAttrRowBuf,
                                            1),
                 &srcBuff[0],
                 maxAttrBytes);
  NdbDictionary::setNull(record, bigAttrRowBuf, 1, false);

  NdbRestarter restarter;
  Ndb* pNdb= GETNDB(step);

  /* Start a transaction on a specific node */
  NdbTransaction* trans= pNdb->startTransaction(ctx->getTab(),
                                                &smallKey[0],
                                                smallKeySize);
  CHECKNOTNULL(trans);

  /* Activate error insert 8065 in this transaction, limits
   * any single import/append to 1 section
   */
  CHECKEQUAL(NDBT_OK, activateErrorInsert(trans, 
                                          record, 
                                          ctx->getTab(),
                                          smallRowBuf, 
                                          &restarter, 
                                          8065));

  /* Ok, let's try an insert with a key bigger than 1 section.
   * Since it's part of the same transaction, it'll go via
   * the same TC.
   */
  const NdbOperation* bigInsert = trans->insertTuple(record, bigKeyRowBuf);

  CHECKNOTNULL(bigInsert);

  CHECKEQUAL(-1, trans->execute(NdbTransaction::NoCommit));

  /* ZGET_DATABUF_ERR expected */
  CHECKEQUAL(218, trans->getNdbError().code)

  trans->close();
  
  /* Ok, now a long TCKEYREQ to the same TC - this
   * has slightly different abort handling since no other
   * operations exist in this new transaction.
   * We also change it so that import overflow occurs 
   * on the AttrInfo section
   */
  /* Start transaction on the same node */
  CHECKNOTNULL(trans= pNdb->startTransaction(ctx->getTab(),
                                             &smallKey[0],
                                             smallKeySize));


  CHECKNOTNULL(bigInsert = trans->insertTuple(record, bigAttrRowBuf));

  CHECKEQUAL(-1,trans->execute(NdbTransaction::NoCommit));

  /* ZGET_DATABUF_ERR expected */
  CHECKEQUAL(218, trans->getNdbError().code);

  trans->close();

  /* Ok, now a long TCKEYREQ where we run out of SegmentedSections
   * on the first TCKEYREQ, but there are other TCKEYREQs following
   * in the same batch.  Check that abort handling is correct
   */
    /* Start transaction on the same node */
  CHECKNOTNULL(trans= pNdb->startTransaction(ctx->getTab(),
                                             &smallKey[0],
                                             smallKeySize));
  /* First op in batch, will cause overflow */
  CHECKNOTNULL(bigInsert = trans->insertTuple(record, bigAttrRowBuf));
  
  /* Second op in batch, what happens to it? */
  const NdbOperation* secondOp;
  CHECKNOTNULL(secondOp = trans->insertTuple(record, bigAttrRowBuf));


  CHECKEQUAL(-1,trans->execute(NdbTransaction::NoCommit));

  /* ZGET_DATABUF_ERR expected */
  CHECKEQUAL(218, trans->getNdbError().code);

  trans->close();

  /* Now try with a 'short' TCKEYREQ, generated using the old Api 
   * with a big key value
   */
  /* Start transaction on the same node */
  CHECKNOTNULL(trans= pNdb->startTransaction(ctx->getTab(),
                                             &smallKey[0],
                                             smallKeySize));
  
  NdbOperation* bigInsertOldApi;
  CHECKNOTNULL(bigInsertOldApi= trans->getNdbOperation(ctx->getTab()));

  CHECKEQUAL(0, bigInsertOldApi->insertTuple());
  CHECKEQUAL(0, bigInsertOldApi->equal((Uint32)0, 
                                       NdbDictionary::getValuePtr
                                       (record,
                                        bigKeyRowBuf,
                                        0)));
  CHECKEQUAL(0, bigInsertOldApi->setValue(1, 
                                          NdbDictionary::getValuePtr
                                          (record,
                                           bigKeyRowBuf,
                                           1)));

  CHECKEQUAL(-1, trans->execute(NdbTransaction::NoCommit));

  /* ZGET_DATABUF_ERR expected */
  CHECKEQUAL(218, trans->getNdbError().code)

  trans->close();

  /* Now try with a 'short' TCKEYREQ, generated using the old Api 
   * with a big data value
   */
  CHECKNOTNULL(trans= pNdb->startTransaction(ctx->getTab(),
                                             &smallKey[0],
                                             smallKeySize));
  
  CHECKNOTNULL(bigInsertOldApi= trans->getNdbOperation(ctx->getTab()));

  CHECKEQUAL(0, bigInsertOldApi->insertTuple());
  CHECKEQUAL(0, bigInsertOldApi->equal((Uint32)0, 
                                       NdbDictionary::getValuePtr
                                       (record,
                                        bigAttrRowBuf,
                                        0)));
  CHECKEQUAL(0, bigInsertOldApi->setValue(1, 
                                          NdbDictionary::getValuePtr
                                          (record,
                                           bigAttrRowBuf,
                                           1)));

  CHECKEQUAL(-1, trans->execute(NdbTransaction::NoCommit));

  /* ZGET_DATABUF_ERR expected */
  CHECKEQUAL(218, trans->getNdbError().code)

  trans->close();

  // TODO : Add code to testUpgrade
#if 0
  /*
   * Short TCKEYREQ    KeyInfo accumulate       Consume + send long
   *                     (TCKEYREQ + KEYINFO)
   * Short TCKEYREQ    AttrInfo accumulate      Consume + send short key
   *                                             + long AI
   *                      (TCKEYREQ + ATTRINFO)
   */
  /* Change error insert so that next TCKEYREQ will grab
   * all but one SegmentedSection so that we can then test SegmentedSection
   * exhaustion when importing the Key/AttrInfo words from the
   * TCKEYREQ signal itself.
   */
  restarter.insertErrorInAllNodes(8066);


  /* Now a 'short' TCKEYREQ, there will be space to import the
   * short key, but not the AttrInfo
   */
  /* Start transaction on same node */
  CHECKNOTNULL(trans= pNdb->startTransaction(ctx->getTab(),
                                             &smallKey[0],
                                             smallKeySize));
  
  CHECKNOTNULL(bigInsertOldApi= trans->getNdbOperation(ctx->getTab()));
  
  CHECKEQUAL(0, bigInsertOldApi->insertTuple());
  CHECKEQUAL(0, bigInsertOldApi->equal((Uint32)0, 
                                       NdbDictionary::getValuePtr
                                       (record,
                                        smallRowBuf,
                                        0)));
  CHECKEQUAL(0, bigInsertOldApi->setValue(1, NdbDictionary::getValuePtr
                                          (record,
                                           smallRowBuf,
                                           1)));

  CHECKEQUAL(-1, trans->execute(NdbTransaction::NoCommit));

  /* ZGET_DATABUF_ERR expected */
  CHECKEQUAL(218, trans->getNdbError().code)

  trans->close();

  /* Change error insert so that there are no SectionSegments 
   * This will cause failure when attempting to import the
   * KeyInfo from the TCKEYREQ
   */
  restarter.insertErrorInAllNodes(8067);

  /* Now a 'short' TCKEYREQ - there will be no space to import the key */
  CHECKNOTNULL(trans= pNdb->startTransaction(ctx->getTab(),
                                             &smallKey[0],
                                             smallKeySize));
  
  CHECKNOTNULL(bigInsertOldApi= trans->getNdbOperation(ctx->getTab()));
  
  CHECKEQUAL(0, bigInsertOldApi->insertTuple());
  CHECKEQUAL(0, bigInsertOldApi->equal((Uint32)0, 
                                       NdbDictionary::getValuePtr
                                       (record,
                                        smallRowBuf,
                                        0)));
  CHECKEQUAL(0, bigInsertOldApi->setValue(1, 
                                          NdbDictionary::getValuePtr
                                          (record,
                                           smallRowBuf,
                                           1)));

  CHECKEQUAL(-1, trans->execute(NdbTransaction::NoCommit));

  /* ZGET_DATABUF_ERR expected */
  CHECKEQUAL(218, trans->getNdbError().code)

  trans->close();  
#endif

  /* Finished with error insert, cleanup the error insertion
   * Error insert 8068 will free the hoarded segments
   */
  CHECKNOTNULL(trans= pNdb->startTransaction(ctx->getTab(),
                                             &smallKey[0],
                                             smallKeySize));

  CHECKEQUAL(NDBT_OK, activateErrorInsert(trans, 
                                          record, 
                                          ctx->getTab(),
                                          smallRowBuf, 
                                          &restarter, 
                                          8068));

  trans->execute(NdbTransaction::Rollback);
  
  CHECKEQUAL(0, trans->getNdbError().code);

  trans->close();

  return NDBT_OK;
}
  
/* Test for correct behaviour using unique key operations
 * when an NDBD node's SegmentedSection pool is exhausted.
 */
int testSegmentedSectionIx(NDBT_Context* ctx, NDBT_Step* step){
  /* 
   * Signal type       Exhausted @              How
   * -----------------------------------------------------
   * Long TCINDXREQ    Initial import           Consume + send 
   * Long TCINDXREQ    Build second TCKEYREQ    Consume + send short
   *                                             w. long base key
   */
  /* We will generate : 
   *   10 SS left : 
   *     Long IndexReq with too long Key/AttrInfo
   *    1 SS left :
   *     Long IndexReq read with short Key + Attrinfo to long 
   *       base table Key
   */
  /* We just run on one table */
  if (strcmp(ctx->getTab()->getName(), "WIDE_2COL_IX") != 0)
    return NDBT_OK;

  const char* indexName= "WIDE_2COL_IX$NDBT_IDX0";
  const Uint32 maxRowBytes= NDB_MAX_TUPLE_SIZE_IN_WORDS * sizeof(Uint32);
  const Uint32 srcBuffBytes= NDBT_Tables::MaxVarTypeKeyBytes;
  const Uint32 maxIndexKeyBytes= NDBT_Tables::MaxKeyMaxVarTypeAttrBytesIndex;
  /* We want to use 6 Segmented Sections, each of 60 32-bit words, including
   * a 2 byte length overhead
   * (We don't want to use 10 Segmented Sections as in some scenarios TUP 
   *  uses Segmented Sections when sending results, and if we use TUP on
   *  the same node, the exhaustion will occur in TUP, which is not what
   *  we're testing)
   */
  const Uint32 mediumPrimaryKeyBytes= (6* 60 * 4) - 2;
  char smallKey[50];
  char srcBuff[srcBuffBytes];
  char smallRowBuf[maxRowBytes];
  char bigKeyIxBuf[maxRowBytes];
  char bigAttrIxBuf[maxRowBytes];
  char bigKeyRowBuf[maxRowBytes];
  char resultSpace[maxRowBytes];

  /* Small key for hinting to same TC */
  Uint32 smallKeySize= setLongVarchar(&smallKey[0],
                                      "ShortKey",
                                      8);

  /* Large value source */
  memset(srcBuff, 'B', srcBuffBytes);

  Ndb* pNdb= GETNDB(step);

  const NdbRecord* baseRecord= ctx->getTab()->getDefaultRecord();
  const NdbRecord* ixRecord= pNdb->
    getDictionary()->getIndex(indexName,
                              ctx->getTab()->getName())->getDefaultRecord();

  /* Setup buffers
   * Small row buffer with short key and data in base table record format
   */ 
  setLongVarchar(NdbDictionary::getValuePtr(baseRecord,
                                            smallRowBuf,
                                            0),
                 "ShortKey",
                 8);
  NdbDictionary::setNull(baseRecord, smallRowBuf, 0, false);

  setLongVarchar(NdbDictionary::getValuePtr(baseRecord,
                                            smallRowBuf,
                                            1),
                 "ShortData",
                 9);
  NdbDictionary::setNull(baseRecord, smallRowBuf, 1, false);

  /* Big index key buffer
   * Big index key (normal row attribute) in index record format
   * Index's key is attrid 1 from the base table
   * This could get confusing !
   */
  
  setLongVarchar(NdbDictionary::getValuePtr(ixRecord,
                                            bigKeyIxBuf,
                                            1),
                 &srcBuff[0],
                 maxIndexKeyBytes);
  NdbDictionary::setNull(ixRecord, bigKeyIxBuf, 1, false);

  /* Big AttrInfo buffer
   * Small key and large attrinfo in base table record format */
  setLongVarchar(NdbDictionary::getValuePtr(baseRecord,
                                            bigAttrIxBuf,
                                            0),
                 "ShortIXKey", 
                 10);

  NdbDictionary::setNull(baseRecord, bigAttrIxBuf, 0, false);

  setLongVarchar(NdbDictionary::getValuePtr(baseRecord,
                                            bigAttrIxBuf,
                                            1),
                 &srcBuff[0],
                 maxIndexKeyBytes);
  NdbDictionary::setNull(baseRecord, bigAttrIxBuf, 1, false);

  /* Big key row buffer 
   * Medium sized key and small attrinfo (index key) in
   * base table record format
   */
  setLongVarchar(NdbDictionary::getValuePtr(baseRecord,
                                            bigKeyRowBuf,
                                            0),
                 &srcBuff[0], 
                 mediumPrimaryKeyBytes);

  NdbDictionary::setNull(baseRecord, bigKeyRowBuf, 0, false);

  setLongVarchar(NdbDictionary::getValuePtr(baseRecord,
                                            bigKeyRowBuf,
                                            1),
                 "ShortIXKey",
                 10);
  NdbDictionary::setNull(baseRecord, bigKeyRowBuf, 1, false);


  /* Start a transaction on a specific node */
  NdbTransaction* trans= pNdb->startTransaction(ctx->getTab(),
                                                &smallKey[0],
                                                smallKeySize);
  /* Insert a row in the base table with a big PK, and
   * small data (Unique IX key).  This is used later to lookup
   * a big PK and cause overflow when reading TRANSID_AI in TC.
   */
  CHECKNOTNULL(trans->insertTuple(baseRecord,
                                  bigKeyRowBuf));

  CHECKEQUAL(0, trans->execute(NdbTransaction::Commit));

  NdbRestarter restarter;
  /* Start a transaction on a specific node */
  trans= pNdb->startTransaction(ctx->getTab(),
                                &smallKey[0],
                                smallKeySize);
  CHECKNOTNULL(trans);

  /* Activate error insert 8065 in this transaction, limits any
   * single append/import to 10 sections.
   */
  CHECKEQUAL(NDBT_OK, activateErrorInsert(trans, 
                                          baseRecord, 
                                          ctx->getTab(),
                                          smallRowBuf, 
                                          &restarter, 
                                          8065));

  /* Ok, let's try an index read with a big index key.
   * Since it's part of the same transaction, it'll go via
   * the same TC.
   */
  const NdbOperation* bigRead= trans->readTuple(ixRecord,
                                                bigKeyIxBuf,
                                                baseRecord,
                                                resultSpace);

  CHECKNOTNULL(bigRead);

  CHECKEQUAL(-1, trans->execute(NdbTransaction::NoCommit));

  /* ZGET_DATABUF_ERR expected */
  CHECKEQUAL(218, trans->getNdbError().code)

  trans->close();

  
  /* Ok, now a long TCINDXREQ to the same TC - this
   * has slightly different abort handling since no other
   * operations exist in this new transaction.
   */
  /* Start a transaction on a specific node */
  CHECKNOTNULL(trans= pNdb->startTransaction(ctx->getTab(),
                                             &smallKey[0],
                                             smallKeySize));

  CHECKNOTNULL(trans->readTuple(ixRecord,
                                bigKeyIxBuf,
                                baseRecord,
                                resultSpace));
  
  CHECKEQUAL(-1, trans->execute(NdbTransaction::NoCommit));

  /* ZGET_DATABUF_ERR expected */
  CHECKEQUAL(218, trans->getNdbError().code);
  
  trans->close();

  /* Now a TCINDXREQ that overflows, but is not the last in the
   * batch, what happens to the other TCINDXREQ in the batch?
   */
  CHECKNOTNULL(trans= pNdb->startTransaction(ctx->getTab(),
                                             &smallKey[0],
                                             smallKeySize));

  CHECKNOTNULL(trans->readTuple(ixRecord,
                                bigKeyIxBuf,
                                baseRecord,
                                resultSpace));
  /* Another read */
  CHECKNOTNULL(trans->readTuple(ixRecord,
                                bigKeyIxBuf,
                                baseRecord,
                                resultSpace));
  
  CHECKEQUAL(-1, trans->execute(NdbTransaction::NoCommit));

  /* ZGET_DATABUF_ERR expected */
  CHECKEQUAL(218, trans->getNdbError().code);
  
  trans->close();


  /* Next we read a tuple with a large primary key via the unique
   * index.  The index read itself should be fine, but
   * pulling in the base table PK will cause abort due to overflow
   * handling TRANSID_AI
   */
  /* Start a transaction on a specific node */
  CHECKNOTNULL(trans= pNdb->startTransaction(ctx->getTab(),
                                             &smallKey[0],
                                             smallKeySize));

  /* Activate error insert 8066 in this transaction, limits a
   * single import/append to 1 section.
   * Note that the TRANSID_AI is received by TC as a short-signal
   * train, so no single append is large, but when the first
   * segment is used and append starts on the second, it will
   * fail.
   */
  CHECKEQUAL(NDBT_OK, activateErrorInsert(trans, 
                                          baseRecord, 
                                          ctx->getTab(),
                                          smallRowBuf, 
                                          &restarter, 
                                          8066));
  CHECKEQUAL(0, trans->execute(NdbTransaction::NoCommit));
  
  CHECKNOTNULL(bigRead= trans->readTuple(ixRecord,
                                         bigAttrIxBuf,
                                         baseRecord,
                                         resultSpace));

  CHECKEQUAL(-1, trans->execute(NdbTransaction::NoCommit));

  /* ZGET_DATABUF_ERR expected */
  CHECKEQUAL(218, trans->getNdbError().code)

  trans->close();

  // TODO Move short signal testing to testUpgrade
#if 0
  /*
   * Short TCINDXREQ   KeyInfo accumulate       Consume + send long
   *                     (TCINDXREQ + KEYINFO)
   * Short TCINDXREQ   AttrInfo accumulate      Consume + send short key
   *                                             + long AI
   *                     (TCINDXREQ + ATTRINFO)
   */
  /* Now try with a 'short' TCINDXREQ, generated using the old Api 
   * with a big index key value
   */
  CHECKNOTNULL(trans= pNdb->startTransaction(ctx->getTab(),
                                             &smallKey[0],
                                             smallKeySize));
  
  const NdbDictionary::Index* index;
  CHECKNOTNULL(index= pNdb->getDictionary()->
               getIndex(indexName,
                        ctx->getTab()->getName()));

  NdbIndexOperation* bigReadOldApi;
  CHECKNOTNULL(bigReadOldApi= trans->getNdbIndexOperation(index));

  CHECKEQUAL(0, bigReadOldApi->readTuple());
  /* We use the attribute id of the index, not the base table here */
  CHECKEQUAL(0, bigReadOldApi->equal((Uint32)0, 
                                     NdbDictionary::getValuePtr
                                     (ixRecord,
                                      bigKeyIxBuf,
                                      1)));

  CHECKNOTNULL(bigReadOldApi->getValue((Uint32)1));

  CHECKEQUAL(-1, trans->execute(NdbTransaction::NoCommit));

  /* ZGET_DATABUF_ERR expected */
  CHECKEQUAL(218, trans->getNdbError().code)

  trans->close();

  /* Now try with a 'short' TCINDXREQ, generated using the old Api 
   * with a big attrinfo value
   */
  CHECKNOTNULL(trans= pNdb->startTransaction(ctx->getTab(),
                                             &smallKey[0],
                                             smallKeySize));
  
  NdbIndexOperation* bigUpdateOldApi;
  CHECKNOTNULL(bigUpdateOldApi= trans->getNdbIndexOperation(index));

  CHECKEQUAL(0, bigUpdateOldApi->updateTuple());
  /* We use the attribute id of the index, not the base table here */
  CHECKEQUAL(0, bigUpdateOldApi->equal((Uint32)0, 
                                       NdbDictionary::getValuePtr
                                       (baseRecord,
                                        smallRowBuf,
                                        1)));

  CHECKEQUAL(0, bigUpdateOldApi->setValue((Uint32)1,
                                          NdbDictionary::getValuePtr
                                          (baseRecord,
                                           bigAttrIxBuf,
                                           1)));
  
  CHECKEQUAL(-1, trans->execute(NdbTransaction::NoCommit));

  /* ZGET_DATABUF_ERR expected */
  CHECKEQUAL(218, trans->getNdbError().code)

  trans->close();

  /* Change error insert so that next TCINDXREQ will grab
   * all but one SegmentedSection
   */
  restarter.insertErrorInAllNodes(8066);

  /* Now a short TCINDXREQ where the KeyInfo from the TCINDXREQ
   * can be imported, but the ATTRINFO can't
   */
  CHECKNOTNULL(trans= pNdb->startTransaction(ctx->getTab(),
                                             &smallKey[0],
                                             smallKeySize));
  
  CHECKNOTNULL(bigUpdateOldApi= trans->getNdbIndexOperation(index));

  CHECKEQUAL(0, bigUpdateOldApi->updateTuple());
  /* We use the attribute id of the index, not the base table here */
  CHECKEQUAL(0, bigUpdateOldApi->equal((Uint32)0, 
                                       NdbDictionary::getValuePtr
                                       (baseRecord,
                                        smallRowBuf,
                                        1)));

  CHECKEQUAL(0, bigUpdateOldApi->setValue((Uint32)1,
                                          NdbDictionary::getValuePtr
                                          (baseRecord,
                                           bigAttrIxBuf,
                                           1)));
  
  CHECKEQUAL(-1, trans->execute(NdbTransaction::NoCommit));

  /* ZGET_DATABUF_ERR expected */
  CHECKEQUAL(218, trans->getNdbError().code)

  trans->close();

  /* Change error insert so that there are no SectionSegments */
  restarter.insertErrorInAllNodes(8067);

  /* Now a short TCINDXREQ where the KeyInfo from the TCINDXREQ
   * can't be imported
   */
  CHECKNOTNULL(trans= pNdb->startTransaction(ctx->getTab(),
                                             &smallKey[0],
                                             smallKeySize));

  CHECKNOTNULL(bigUpdateOldApi= trans->getNdbIndexOperation(index));

  CHECKEQUAL(0, bigUpdateOldApi->updateTuple());
  /* We use the attribute id of the index, not the base table here */
  CHECKEQUAL(0, bigUpdateOldApi->equal((Uint32)0, 
                                       NdbDictionary::getValuePtr
                                       (baseRecord,
                                        smallRowBuf,
                                        1)));

  CHECKEQUAL(0, bigUpdateOldApi->setValue((Uint32)1,
                                          NdbDictionary::getValuePtr
                                          (baseRecord,
                                           bigAttrIxBuf,
                                           1)));
  
  CHECKEQUAL(-1, trans->execute(NdbTransaction::NoCommit));

  /* ZGET_DATABUF_ERR expected */
  CHECKEQUAL(218, trans->getNdbError().code)

  trans->close();

#endif  

  /* Finished with error insert, cleanup the error insertion */
  CHECKNOTNULL(trans= pNdb->startTransaction(ctx->getTab(),
                                             &smallKey[0],
                                             smallKeySize));

  CHECKEQUAL(NDBT_OK, activateErrorInsert(trans, 
                                          baseRecord, 
                                          ctx->getTab(),
                                          smallRowBuf, 
                                          &restarter, 
                                          8068));

  trans->execute(NdbTransaction::Rollback);
  
  CHECKEQUAL(0, trans->getNdbError().code);

  trans->close();

  return NDBT_OK;
}


int testSegmentedSectionScan(NDBT_Context* ctx, NDBT_Step* step){
  /* Test that TC handling of segmented section exhaustion is
   * correct
   * Since NDBAPI always send long requests, that is all that
   * we test
   */
    /* We just run on one table */
  if (strcmp(ctx->getTab()->getName(), "WIDE_2COL") != 0)
    return NDBT_OK;

  const Uint32 maxRowBytes= NDB_MAX_TUPLE_SIZE_IN_WORDS * sizeof(Uint32);
  char smallKey[50];
  char smallRowBuf[maxRowBytes];

  Uint32 smallKeySize= setLongVarchar(&smallKey[0],
                                      "ShortKey",
                                      8);

  const NdbRecord* record= ctx->getTab()->getDefaultRecord();

  /* Setup buffers
   * Small row buffer with small key and small data
   */ 
  setLongVarchar(NdbDictionary::getValuePtr(record,
                                            smallRowBuf,
                                            0),
                 "ShortKey",
                 8);
  NdbDictionary::setNull(record, smallRowBuf, 0, false);

  setLongVarchar(NdbDictionary::getValuePtr(record,
                                            smallRowBuf,
                                            1),
                 "ShortData",
                 9);
  NdbDictionary::setNull(record, smallRowBuf, 1, false);

  NdbRestarter restarter;
  Ndb* pNdb= GETNDB(step);

  /* Start a transaction on a specific node */
  NdbTransaction* trans= pNdb->startTransaction(ctx->getTab(),
                                                &smallKey[0],
                                                smallKeySize);
  CHECKNOTNULL(trans);

  /* Activate error insert 8066 in this transaction, limits a 
   * single import/append to 1 section.
   */
  CHECKEQUAL(NDBT_OK, activateErrorInsert(trans, 
                                          record, 
                                          ctx->getTab(),
                                          smallRowBuf, 
                                          &restarter, 
                                          8066));

  /* A scan will always send 2 long sections (Receiver Ids,
   * AttrInfo)
   * Let's start a scan with > 2400 bytes of
   * ATTRINFO and see what happens
   */
  NdbScanOperation* scan= trans->getNdbScanOperation(ctx->getTab());

  CHECKNOTNULL(scan);

  CHECKEQUAL(0, scan->readTuples());

  /* Create a particularly useless program */
  NdbInterpretedCode prog;

  for (Uint32 w=0; w < 2500; w++)
    CHECKEQUAL(0, prog.load_const_null(1));

  CHECKEQUAL(0, prog.interpret_exit_ok());
  CHECKEQUAL(0, prog.finalise());

  CHECKEQUAL(0, scan->setInterpretedCode(&prog));

  /* Api doesn't seem to wait for result of scan request */
  CHECKEQUAL(0, trans->execute(NdbTransaction::NoCommit));

  CHECKEQUAL(0, trans->getNdbError().code);

  CHECKEQUAL(-1, scan->nextResult());
  
  CHECKEQUAL(217, scan->getNdbError().code);

  trans->close();

  /* Finished with error insert, cleanup the error insertion */
  CHECKNOTNULL(trans= pNdb->startTransaction(ctx->getTab(),
                                             &smallKey[0],
                                             smallKeySize));

  CHECKEQUAL(NDBT_OK, activateErrorInsert(trans, 
                                          record, 
                                          ctx->getTab(),
                                          smallRowBuf, 
                                          &restarter, 
                                          8068));

  CHECKEQUAL(0, trans->execute(NdbTransaction::Rollback));
  
  CHECKEQUAL(0, trans->getNdbError().code);

  trans->close();

  return NDBT_OK;
}

int testDropSignalFragments(NDBT_Context* ctx, NDBT_Step* step){
  /* Segmented section exhaustion results in dropped signals
   * Fragmented signals split one logical signal over multiple
   * physical signals (to cope with the MAX_SIGNAL_LENGTH=32kB
   * limitation).
   * This testcase checks that when individual signals comprising
   * a fragmented signal (in this case SCANTABREQ) are dropped, the
   * system behaves correctly.
   * Correct behaviour is to behave in the same way as if the signal
   * was not fragmented, and for SCANTABREQ, to return a temporary
   * resource error.
   */
  NdbRestarter restarter;
  Ndb* pNdb= GETNDB(step);

  /* SEND > ((2 * MAX_SEND_MESSAGE_BYTESIZE) + SOME EXTRA) 
   * This way we get at least 3 fragments
   * However, as this is generally > 64kB, it's too much AttrInfo for
   * a ScanTabReq, so the 'success' case returns error 874
   */
  const Uint32 PROG_WORDS= 16500; 

  struct SubCase
  {
    Uint32 errorInsertCode;
    int expectedRc;
  };
  const Uint32 numSubCases= 5;
  const SubCase cases[numSubCases]= 
  /* Error insert   Scanrc */
    {{          0,     874},  // Normal, success which gives too much AI error
     {       8074,     217},  // Drop first fragment -> error 217
     {       8075,     217},  // Drop middle fragment(s) -> error 217
     {       8076,     217},  // Drop last fragment -> error 217
     {       8077,     217}}; // Drop all fragments -> error 217
  const Uint32 numIterations= 50;
  
  Uint32 buff[ PROG_WORDS + 10 ]; // 10 extra for final 'return' etc.

  for (Uint32 iteration=0; iteration < (numIterations * numSubCases); iteration++)
  {
    /* Start a transaction */
    NdbTransaction* trans= pNdb->startTransaction();
    CHECKNOTNULL(trans);

    SubCase subcase= cases[iteration % numSubCases];

    Uint32 errorInsertVal= subcase.errorInsertCode;
    // printf("Inserting error : %u\n", errorInsertVal);
    /* We insert the error twice, to bias races between
     * error-insert propagation and the succeeding scan
     * in favour of error insert winning!
     * This problem needs a more general fix
     */
    CHECKEQUAL(0, restarter.insertErrorInAllNodes(errorInsertVal));
    CHECKEQUAL(0, restarter.insertErrorInAllNodes(errorInsertVal));

    NdbScanOperation* scan= trans->getNdbScanOperation(ctx->getTab());
    
    CHECKNOTNULL(scan);
    
    CHECKEQUAL(0, scan->readTuples());
    
    /* Create a large program, to give a large SCANTABREQ */
    NdbInterpretedCode prog(ctx->getTab(), buff, PROG_WORDS + 10);
    
    for (Uint32 w=0; w < PROG_WORDS; w++)
      CHECKEQUAL(0, prog.load_const_null(1));
    
    CHECKEQUAL(0, prog.interpret_exit_ok());
    CHECKEQUAL(0, prog.finalise());
    
    CHECKEQUAL(0, scan->setInterpretedCode(&prog));
    
    /* Api doesn't seem to wait for result of scan request */
    CHECKEQUAL(0, trans->execute(NdbTransaction::NoCommit));
    
    CHECKEQUAL(0, trans->getNdbError().code);

    CHECKEQUAL(-1, scan->nextResult());
    
    int expectedResult= subcase.expectedRc;
    CHECKEQUAL(expectedResult, scan->getNdbError().code);

    scan->close();
    
    trans->close();
  }

  restarter.insertErrorInAllNodes(0);

  return NDBT_OK;
}

int create100Tables(NDBT_Context* ctx, NDBT_Step* step)
{
  Ndb* pNdb = GETNDB(step);
  const NdbDictionary::Table* pTab= ctx->getTab();
  
  /* Run as a 'T1' testcase - do nothing for other tables */
  if (strcmp(pTab->getName(), "T1") != 0)
    return NDBT_OK;

  for (Uint32 t=0; t < 100; t++)
  {
    char tabnameBuff[10];
    snprintf(tabnameBuff, sizeof(tabnameBuff), "TAB%u", t);
    
    NdbDictionary::Table tab;
    tab.setName(tabnameBuff);
    NdbDictionary::Column pk;
    pk.setName("PK");
    pk.setType(NdbDictionary::Column::Varchar);
    pk.setLength(20);
    pk.setNullable(false);
    pk.setPrimaryKey(true);
    tab.addColumn(pk);

    pNdb->getDictionary()->dropTable(tab.getName());
    if(pNdb->getDictionary()->createTable(tab) != 0)
    {
      ndbout << "Create table failed with error : "
             << pNdb->getDictionary()->getNdbError().code
             << " "
             << pNdb->getDictionary()->getNdbError().message
             << endl;
      return NDBT_FAILED;
    }
    
    ndbout << "Created table " << tabnameBuff << endl;
  }

  return NDBT_OK;
}

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

  /* Run as a 'T1' testcase - do nothing for other tables */
  if (strcmp(pTab->getName(), "T1") != 0)
    return NDBT_OK;
    
  for (Uint32 t=0; t < 100; t++)
  {
    char tabnameBuff[10];
    snprintf(tabnameBuff, sizeof(tabnameBuff), "TAB%u", t);
    
    if (pNdb->getDictionary()->dropTable(tabnameBuff) != 0)
    {
      ndbout << "Drop table failed with error : "
             << pNdb->getDictionary()->getNdbError().code
             << " "
             << pNdb->getDictionary()->getNdbError().message
             << endl;
    }
    else
    {
      ndbout << "Dropped table " << tabnameBuff << endl;
    }
  }
  
  return NDBT_OK;
}

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

  /* Run as a 'T1' testcase - do nothing for other tables */
  if (strcmp(pTab->getName(), "T1") != 0)
    return NDBT_OK;
    
  char tabnameBuff[10];
  snprintf(tabnameBuff, sizeof(tabnameBuff), "TAB%u", num);
  
  if (pNdb->getDictionary()->dropTable(tabnameBuff) != 0)
  {
    ndbout << "Drop table failed with error : "
           << pNdb->getDictionary()->getNdbError().code
           << " "
           << pNdb->getDictionary()->getNdbError().message
           << endl;
  }
  else
  {
    ndbout << "Dropped table " << tabnameBuff << endl;
  }
  
  return NDBT_OK;
}


enum Scenarios
{
//  NORMAL,  // Commented to save some time.
  DROP_TABLE,
  RESTART_MASTER,
  RESTART_SLAVE,
  NUM_SCENARIOS
};


enum Tasks
{
  WAIT = 0,
  DROP_TABLE_REQ = 1,
  MASTER_RESTART_REQ = 2,
  SLAVE_RESTART_REQ = 3
};

int testWorker(NDBT_Context* ctx, NDBT_Step* step)
{
  /* Run as a 'T1' testcase - do nothing for other tables */
  if (strcmp(ctx->getTab()->getName(), "T1") != 0)
    return NDBT_OK;

  /* Worker step to run in a separate thread for
   * blocking activities
   * Generally the blocking of the DIH table definition flush
   * blocks the completion of the drop table/node restarts,
   * so this must be done in a separate thread to avoid
   * deadlocks.
   */
  
  while (!ctx->isTestStopped())
  {
    ndbout_c("Worker : waiting for request...");
    ctx->getPropertyWait("DIHWritesRequest", 1);
  
    if (!ctx->isTestStopped())
    {
      Uint32 req = ctx->getProperty("DIHWritesRequestType", (Uint32)0);

      switch ((Tasks) req)
      {
      case DROP_TABLE_REQ:
      {
        /* Drop table */
        ndbout_c("Worker : dropping table");
        if (dropTable(ctx, step, 2) != NDBT_OK)
        {
          return NDBT_FAILED;
        }
        ndbout_c("Worker : table dropped.");
        break;
      }
      case MASTER_RESTART_REQ:
      {
        ndbout_c("Worker : restarting Master");
        
        NdbRestarter restarter;
        int master_nodeid = restarter.getMasterNodeId();
        ndbout_c("Worker : Restarting Master (%d)...", master_nodeid);
        if (restarter.restartOneDbNode2(master_nodeid, 
                                        NdbRestarter::NRRF_NOSTART |
                                        NdbRestarter::NRRF_FORCE |
                                        NdbRestarter::NRRF_ABORT) ||
            restarter.waitNodesNoStart(&master_nodeid, 1) ||
            restarter.startAll())
        {
          ndbout_c("Worker : Error restarting Master.");
          return NDBT_FAILED;
        }
        ndbout_c("Worker : Waiting for master to recover...");
        if (restarter.waitNodesStarted(&master_nodeid, 1))
        {
          ndbout_c("Worker : Error waiting for Master restart");
          return NDBT_FAILED;
        }
        ndbout_c("Worker : Master recovered.");
        break;
      }
      case SLAVE_RESTART_REQ:
      {
        NdbRestarter restarter;
        int slave_nodeid = restarter.getRandomNotMasterNodeId(rand());
        ndbout_c("Worker : Restarting non-master (%d)...", slave_nodeid);
        if (restarter.restartOneDbNode2(slave_nodeid, 
                                        NdbRestarter::NRRF_NOSTART |
                                        NdbRestarter::NRRF_FORCE |
                                        NdbRestarter::NRRF_ABORT) ||
            restarter.waitNodesNoStart(&slave_nodeid, 1) ||
            restarter.startAll())
        {
          ndbout_c("Worker : Error restarting Slave.");
          return NDBT_FAILED;
        }
        ndbout_c("Worker : Waiting for slave to recover...");
        if (restarter.waitNodesStarted(&slave_nodeid, 1))
        {
          ndbout_c("Worker : Error waiting for Slave restart");
          return NDBT_FAILED;
        }
        ndbout_c("Worker : Slave recovered.");
        break;
      }
      default:
      { 
        break;
      }
      }
    }
    ctx->setProperty("DIHWritesRequestType", (Uint32) 0);
    ctx->setProperty("DIHWritesRequest", (Uint32) 2);
  }
  
  ndbout_c("Worker, done.");
  return NDBT_OK;
}

int testSlowDihFileWrites(NDBT_Context* ctx, NDBT_Step* step)
{
  /* Testcase checks behaviour with slow flushing of DIH table definitions
   * This caused problems in the past by exhausting the DIH page pool
   * Now there's a concurrent operations limit.
   * Check that it behaves with many queued ops, parallel drop/node restarts
   */
  
  /* Run as a 'T1' testcase - do nothing for other tables */
  if (strcmp(ctx->getTab()->getName(), "T1") != 0)
    return NDBT_OK;

  /* 1. Activate slow write error insert
   * 2. Trigger LCP
   * 3. Wait some time, periodically producing info on 
   *    the internal state
   * 4. Perform some parallel action (drop table/node restarts)
   * 5. Wait some time, periodically producing info on 
   *    the internal state
   * 6. Clear the error insert
   * 7. Wait a little longer
   * 8. Done.
   */
  NdbRestarter restarter;

  for (Uint32 scenario = 0;  scenario < NUM_SCENARIOS; scenario++)
  {
    ndbout_c("Inserting error 7235");
    restarter.insertErrorInAllNodes(7235);
    
    ndbout_c("Triggering LCP");
    int dumpArg = 7099;
    restarter.dumpStateAllNodes(&dumpArg, 1);
    
    const Uint32 periodSeconds = 10;
    Uint32 waitPeriods = 6;
    dumpArg = 7032;
    
    for (Uint32 p=0; p<waitPeriods; p++)
    {
      if (p == 3)
      {
        switch ((Scenarios) scenario)
        {
        case DROP_TABLE:
        {
          /* Drop one of the early-created tables */
          ndbout_c("Requesting DROP TABLE");
          ctx->setProperty("DIHWritesRequestType", (Uint32) DROP_TABLE_REQ);
          ctx->setProperty("DIHWritesRequest", (Uint32) 1);
          break;
        }
        case RESTART_MASTER:
        {
          ndbout_c("Requesting Master restart");
          ctx->setProperty("DIHWritesRequestType", (Uint32) MASTER_RESTART_REQ);
          ctx->setProperty("DIHWritesRequest", (Uint32) 1);

          break;
        }
        case RESTART_SLAVE:
        {
          ndbout_c("Requesting Slave restart");
          ctx->setProperty("DIHWritesRequestType", (Uint32) SLAVE_RESTART_REQ);
          ctx->setProperty("DIHWritesRequest", (Uint32) 1);

          break;
        }
        default:
          break;
        }
      }

      ndbout_c("Dumping DIH page info to ndbd stdout");
      restarter.dumpStateAllNodes(&dumpArg, 1);
      NdbSleep_MilliSleep(periodSeconds * 1000);
    }
    
    ndbout_c("Clearing error insert...");
    restarter.insertErrorInAllNodes(0);
    
    waitPeriods = 2;
    for (Uint32 p=0; p<waitPeriods; p++)
    {
      ndbout_c("Dumping DIH page info to ndbd stdout");
      restarter.dumpStateAllNodes(&dumpArg, 1);
      NdbSleep_MilliSleep(periodSeconds * 1000);
    }
    
    ndbout_c("Waiting for worker to finish task...");
    ctx->getPropertyWait("DIHWritesRequest", 2);
    
    if (ctx->isTestStopped())
      return NDBT_OK;

    ndbout_c("Done.");
  }  

  /* Finish up */
  ctx->stopTest();

  return NDBT_OK;
}


NDBT_TESTSUITE(testLimits);

TESTCASE("ExhaustSegmentedSectionPk",
         "Test behaviour at Segmented Section exhaustion for PK"){
  INITIALIZER(testSegmentedSectionPk);
}

TESTCASE("ExhaustSegmentedSectionIX",
         "Test behaviour at Segmented Section exhaustion for Unique index"){
  INITIALIZER(testSegmentedSectionIx);
}
TESTCASE("ExhaustSegmentedSectionScan",
         "Test behaviour at Segmented Section exhaustion for Scan"){
  INITIALIZER(testSegmentedSectionScan);
}

TESTCASE("DropSignalFragments",
         "Test behaviour of Segmented Section exhaustion with fragmented signals"){
  INITIALIZER(testDropSignalFragments);
}

TESTCASE("SlowDihFileWrites",
         "Test behaviour of slow Dih table file writes")
{
  INITIALIZER(create100Tables);
  STEP(testWorker);
  STEP(testSlowDihFileWrites);
  FINALIZER(drop100Tables);
}

NDBT_TESTSUITE_END(testLimits);

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