Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  xindex.cpp   Sprache: C

 

/***************** Xindex C++ Class Xindex Code (.CPP) *****************/
/*  Name: XINDEX.CPP  Version 3.0                                      */
/*                                                                     */
/*  (C) Copyright to the author Olivier BERTRAND          2004-2017    */
/*                                                                     */
/*  This file contains the class XINDEX implementation code.           */
/***********************************************************************/

/***********************************************************************/
/*  Include relevant sections of the System header files.              */
/***********************************************************************/
#include "my_global.h"
#if defined(_WIN32)
#include <io.h>
#include <fcntl.h>
#include <errno.h>
//#include <windows.h>
#else   // !_WIN32
#if defined(UNIX)
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <unistd.h>
#else   // !UNIX
#include <io.h>
#endif  // !UNIX
#include <fcntl.h>
#endif  // !_WIN32

/***********************************************************************/
/*  Include required application header files                          */
/*  global.h    is header containing all global Plug declarations.     */
/*  plgdbsem.h  is header containing the DB applic. declarations.      */
/*  kindex.h    is header containing the KINDEX class definition.      */
/***********************************************************************/
#include "global.h"
#include "plgdbsem.h"
#include "osutil.h"
#include "maputil.h"
//nclude "filter.h"
#include "tabcol.h"
#include "xindex.h"
#include "xobject.h"
//nclude "scalfnc.h"
//nclude "array.h"
#include "filamtxt.h"
#include "tabdos.h"
#include "m_string.h"
#if defined(VCT_SUPPORT)
#include "tabvct.h"
#endif   // VCT_SUPPORT

/***********************************************************************/
/*  Macro or external routine definition                               */
/***********************************************************************/
#define NZ 8
#define NW 5
#define MAX_INDX 10
#ifndef INVALID_SET_FILE_POINTER
#define INVALID_SET_FILE_POINTER  0xFFFFFFFF
#endif

/***********************************************************************/
/*  DB external variables.                                             */
/***********************************************************************/
extern MBLOCK Nmblk;                /* Used to initialize MBLOCK's     */
#if defined(XMAP)
extern my_bool xmap;
#endif   // XMAP

/***********************************************************************/
/*  Last two parameters are true to enable type checking, and last one */
/*  to have rows filled by blanks to be compatible with QRY blocks.    */
/***********************************************************************/
PVBLK AllocValBlock(PGLOBAL, void *, int, int, int, int,
                    bool check = true, bool blank = true, bool un = false);

/***********************************************************************/
/*  Check whether we have to create/update permanent indexes.          */
/***********************************************************************/
int PlgMakeIndex(PGLOBAL g, PSZ name, PIXDEF pxdf, bool add)
  {
  int     rc;
  PTABLE  tablep;
  PTDB    tdbp;
  PCATLG  cat = PlgGetCatalog(g, true);

  /*********************************************************************/
  /*  Open a new table in mode read and with only the keys columns.    */
  /*********************************************************************/
  tablep = new(g) XTAB(name);

  if (!(tdbp = cat->GetTable(g, tablep)))
    rc = RC_NF;
  else if (!tdbp->GetDef()->Indexable()) {
    snprintf(g->Message, sizeof(g->Message), MSG(TABLE_NO_INDEX), name);
    rc = RC_NF;
  } else if ((rc = ((PTDBASE)tdbp)->MakeIndex(g, pxdf, add)) == RC_INFO)
    rc = RC_OK;            // No or remote index

  return rc;
  } // end of PlgMakeIndex

/* -------------------------- Class INDEXDEF ------------------------- */

/***********************************************************************/
/*  INDEXDEF Constructor.                                              */
/***********************************************************************/
INDEXDEF::INDEXDEF(char *name, bool uniq, int n)
  {
//To_Def = NULL;
  Next = NULL;
  ToKeyParts = NULL;
  Name = name;
  Unique = uniq;
  Invalid = false;
  AutoInc = false;
  Dynamic = false;
  Mapped = false;
  Nparts = 0;
  ID = n;
//Offset = 0;
//Offhigh = 0;
//Size = 0;
  MaxSame = 1;
  } // end of INDEXDEF constructor

/***********************************************************************/
/*  Set the max same values for each column after making the index.    */
/***********************************************************************/
void INDEXDEF::SetMxsame(PXINDEX x)
  {
  PKPDEF  kdp;
  PXCOL   xcp;

  for (kdp = ToKeyParts, xcp = x->To_KeyCol;
       kdp && xcp; kdp = kdp->Next, xcp = xcp->Next)
    kdp->Mxsame = xcp->Mxs;
  } // end of SetMxsame

/* -------------------------- Class KPARTDEF ------------------------- */

/***********************************************************************/
/*  KPARTDEF Constructor.                                              */
/***********************************************************************/
KPARTDEF::KPARTDEF(PSZ name, int n)
  {
  Next = NULL;
  Name = name;
  Mxsame = 0;
  Ncol = n;
  Klen = 0;
  } // end of KPARTDEF constructor

/* -------------------------- XXBASE Class --------------------------- */

/***********************************************************************/
/*  XXBASE public constructor.                                         */
/***********************************************************************/
XXBASE::XXBASE(PTDBDOS tbxp, bool b) : CSORT(b),
        To_Rec((int*&)Record.Memp)
  {
  Tbxp = tbxp;
  Record = Nmblk;
  Cur_K = -1;
  Old_K = -1;
  Num_K = 0;
  Ndif = 0;
  Bot = Top = Inf = Sup = 0;
  Op = OP_EQ;
  To_KeyCol = NULL;
  Mul = false;
  Srtd = false;
  Dynamic = false;
  Val_K = -1;
  Nblk = Sblk = 0;
  Thresh = 7;
  ID = -1;
  Nth = 0;
  } // end of XXBASE constructor

/***********************************************************************/
/*  Make file output of XINDEX contents.                               */
/***********************************************************************/
void XXBASE::Printf(PGLOBAL, FILE *f, uint n)
  {
  char m[64];

  memset(m, ' ', n);                    // Make margin string
  m[n] = '\0';
  fprintf(f, "%sXINDEX: Tbxp=%p Num=%d\n", m, Tbxp, Num_K);
  } // end of Printf

/***********************************************************************/
/*  Make string output of XINDEX contents.                             */
/***********************************************************************/
void XXBASE::Prints(PGLOBAL, char *ps, uint z)
  {
  *ps = '\0';
  strncat(ps, "Xindex", z);
  } // end of Prints

/* -------------------------- XINDEX Class --------------------------- */

/***********************************************************************/
/*  XINDEX public constructor.                                         */
/***********************************************************************/
XINDEX::XINDEX(PTDBDOS tdbp, PIXDEF xdp, PXLOAD pxp, PCOL *cp, PXOB *xp, int k)
      : XXBASE(tdbp, !xdp->IsUnique())
  {
  Xdp = xdp;
  ID = xdp->GetID();
  Tdbp = tdbp;
  X = pxp;
  To_LastCol = NULL;
  To_LastVal = NULL;
  To_Cols = cp;
  To_Vals = xp;
  Mul = !xdp->IsUnique();
  Srtd = false;
  Nk = xdp->GetNparts();
  Nval = (k) ? k : Nk;
  Incr = 0;
//Defoff = xdp->GetOffset();
//Defhigh = xdp->GetOffhigh();
//Size = xdp->GetSize();
  MaxSame = xdp->GetMaxSame();
  } // end of XINDEX constructor

/***********************************************************************/
/*  XINDEX Reset: re-initialize a Xindex block.                        */
/***********************************************************************/
void XINDEX::Reset(void)
  {
  for (PXCOL kp = To_KeyCol; kp; kp = kp->Next)
    kp->Val_K = kp->Ndf;

  Cur_K = Num_K;
  Old_K = -1;  // Needed to avoid not setting CurBlk for Update
  Op = (Op == OP_FIRST  || Op == OP_NEXT)   ? OP_FIRST  :
       (Op == OP_FSTDIF || Op == OP_NXTDIF) ? OP_FSTDIF : OP_EQ;
  Nth = 0;
  } // end of Reset

/***********************************************************************/
/*  XINDEX Close: terminate index and free all allocated data.         */
/*  Do not reset values that are used at return to make.               */
/***********************************************************************/
void XINDEX::Close(void)
  {
  // Close file or view of file
  if (X)
    X->Close();

  // De-allocate data
  PlgDBfree(Record);
  PlgDBfree(Index);
  PlgDBfree(Offset);

  for (PXCOL kcp = To_KeyCol; kcp; kcp = kcp->Next) {
    // Column values cannot be retrieved from key anymore
    if (kcp->Colp)
      kcp->Colp->SetKcol(NULL);

    // De-allocate Key data
    kcp->FreeData();
    } // endfor kcp

  } // end of Close

/***********************************************************************/
/*  XINDEX compare routine for C Quick/Insertion sort.                 */
/***********************************************************************/
int XINDEX::Qcompare(int *i1, int *i2)
  {
  int  k;
  PXCOL kcp;

  for (kcp = To_KeyCol, k = 0; kcp; kcp = kcp->Next)
    if ((k = kcp->Compare(*i1, *i2)))
      break;

//num_comp++;
  return k;
  } // end of Qcompare

/***********************************************************************/
/*  AddColumns: here we try to determine whether it is worthwhile to   */
/*  add to the keys the values of the columns selected for this table. */
/*  Sure enough, it is done while records are read and permit to avoid */
/*  reading the table while doing the join (Dynamic index only)        */
/***********************************************************************/
bool XINDEX::AddColumns(void)
  {
  if (!Dynamic)
    return false;     // Not applying to static index
  else if (IsMul())
    return false;     // Not done yet for multiple index
#if defined(VCT_SUPPORT)
 else if (Tbxp->GetAmType() == TYPE_AM_VCT && ((PTDBVCT)Tbxp)->IsSplit())
    return false;     // This would require to read additional files
#endif   // VCT_SUPPORT
 else
    return true;

  } // end of AddColumns

/***********************************************************************/
/*  Make: Make and index on key column(s).                             */
/***********************************************************************/
bool XINDEX::Make(PGLOBAL g, PIXDEF sxp)
  {
  /*********************************************************************/
  /*  Table can be accessed through an index.                          */
  /*********************************************************************/
  int     k, nk = Nk, rc = RC_OK;
  int    *bof, i, j, n, ndf, nkey;
  PKPDEF  kdfp = Xdp->GetToKeyParts();
  bool    brc = false;
  PCOL    colp;
  PFIL    filp = Tdbp->GetFilter();
  PXCOL   kp, addcolp, prev = NULL, kcp = NULL;
//PDBUSER dup = (PDBUSER)g->Activityp->Aptr;

#if defined(_DEBUG)
  assert(X || Nk == 1);
#endif   // _DEBUG

  /*********************************************************************/
  /*  Allocate the storage that will contain the keys and the file     */
  /*  positions corresponding to them.                                 */
  /*********************************************************************/
  if ((n = Tdbp->GetMaxSize(g)) < 0)
    return true;
  else if (!n) {
    Num_K = Ndif = 0;
    MaxSame = 1;

    // The if condition was suppressed because this may be an existing
    // index that is now void because all table lines were deleted.
//  if (sxp)
      goto nox;            // Truncate eventually existing index file
//  else
//    return false;

    } // endif n

  if (trace(1))
    htrc("XINDEX Make: n=%d\n", n);

  // File position must be stored
  Record.Size = n * sizeof(int);

  if (!PlgDBalloc(g, NULL, Record)) {
    snprintf(g->Message, sizeof(g->Message), MSG(MEM_ALLOC_ERR), "index", n);
    goto err;    // Error
    } // endif

  /*********************************************************************/
  /*  Allocate the KXYCOL blocks used to store column values.          */
  /*********************************************************************/
  for (k = 0; k < Nk; k++) {
    colp = To_Cols[k];

    if (!kdfp) {
      snprintf(g->Message, sizeof(g->Message), MSG(INT_COL_ERROR),
                          (colp) ? colp->GetName() : "???");
      goto err;    // Error
      } // endif kdfp

    kcp = new(g) KXYCOL(this);

    if (kcp->Init(g, colp, n, true, kdfp->Klen))
      goto err;    // Error

    if (prev) {
      kcp->Previous = prev;
      prev->Next = kcp;
    } else
      To_KeyCol = kcp;

    prev = kcp;
    kdfp = kdfp->Next;
    } // endfor k

  To_LastCol = prev;

  if (AddColumns()) {
    PCOL kolp = To_Cols[0];    // Temporary while imposing Nk = 1

    i = 0;

    // Allocate the accompanying
    for (colp = Tbxp->GetColumns(); colp; colp = colp->GetNext()) {
      // Count how many columns to add
//    for (k = 0; k < Nk; k++)
//      if (colp == To_Cols[k])
//        break;

//    if (k == nk)
      if (colp != kolp)
        i++;

      } // endfor colp

    if (i && i < 10)                  // Should be a parameter
      for (colp = Tbxp->GetColumns(); colp; colp = colp->GetNext()) {
//      for (k = 0; k < Nk; k++)
//        if (colp == To_Cols[k])
//          break;

//      if (k < nk)
        if (colp == kolp)
          continue;                   // This is a key column

        kcp = new(g) KXYCOL(this);

        if (kcp->Init(g, colp, n, true, 0))
          return true;

        if (trace(1))
          htrc("Adding colp=%p Buf_Type=%d size=%d\n",
                colp, colp->GetResultType(), n);

        nk++;
        prev->Next = kcp;
        prev = kcp;
        } // endfor colp

    } // endif AddColumns

#if 0
  /*********************************************************************/
  /*  Get the starting information for progress.                       */
  /*********************************************************************/
  dup->Step = (char*)PlugSubAlloc(g, NULL, 128);
  snprintf((char*)dup->Step, 128, MSG(BUILD_INDEX), Xdp->GetName(), Tdbp->Name);
  dup->ProgMax = Tdbp->GetProgMax(g);
  dup->ProgCur = 0;
#endif // 0

  /*********************************************************************/
  /*  Standard init: read the file and construct the index table.      */
  /*  Note: reading will be sequential as To_Kindex is not set.        */
  /*********************************************************************/
  for (i = nkey = 0; rc != RC_EF; i++) {
#if 0
    if (!dup->Step) {
      strcpy(g->Message, MSG(QUERY_CANCELLED));
   throw 99;
 } // endif Step
#endif // 0

    /*******************************************************************/
    /*  Read a valid record from table file.                           */
    /*******************************************************************/
    rc = Tdbp->ReadDB(g);

    // Update progress information
//  dup->ProgCur = Tdbp->GetProgCur();

    // Check return code and do whatever must be done according to it
    switch (rc) {
      case RC_OK:
        if (ApplyFilter(g, filp))
          break;

        // fall through
      case RC_NF:
        continue;
      case RC_EF:
        goto end_of_file;
      default:
        snprintf(g->Message, sizeof(g->Message), MSG(RC_READING), rc, Tdbp->Name);
        goto err;
      } // endswitch rc

    /*******************************************************************/
    /*  Get and Store the file position of the last read record for    */
    /*  future direct access.                                          */
    /*******************************************************************/
    if (nkey == n) {
      snprintf(g->Message, sizeof(g->Message), MSG(TOO_MANY_KEYS), nkey);
      return true;
    } else
      To_Rec[nkey] = Tdbp->GetRecpos();

    if (trace(2))
      htrc("Make: To_Rec[%d]=%d\n", nkey, To_Rec[nkey]); 

    /*******************************************************************/
    /*  Get the keys and place them in the key blocks.                 */
    /*******************************************************************/
    for (k = 0, kcp = To_KeyCol;
         k < nk && kcp;
         k++, kcp = kcp->Next) {
//    colp = To_Cols[k];
      colp = kcp->Colp;

      if (!colp->GetStatus(BUF_READ))
        colp->ReadColumn(g);
      else
        colp->Reset();

      kcp->SetValue(colp, nkey);
      } // endfor k

    nkey++;                    // A new valid key was found
    } // endfor i

 end_of_file:

  // Update progress information
//dup->ProgCur = Tdbp->GetProgMax(g);

  /*********************************************************************/
  /* Record the Index size and eventually resize memory allocation.    */
  /*********************************************************************/
  if ((Num_K = nkey) < n) {
    PlgDBrealloc(g, NULL, Record, Num_K * sizeof(int));

    for (kcp = To_KeyCol; kcp; kcp = kcp->Next)
      kcp->ReAlloc(g, Num_K);

    } // endif Num_K

  /*********************************************************************/
  /*  Sort the index so we can use an optimized Find algorithm.        */
  /*  Note: for a unique index we use the non conservative sort        */
  /*  version because normally all index values are different.         */
  /*  This was set at CSORT class construction.                        */
  /*  For all indexes, an offset array is made so we can check the     */
  /*  uniqueness of unique indexes.                                    */
  /*********************************************************************/
  Index.Size = Num_K * sizeof(int);

  if (!PlgDBalloc(g, NULL, Index)) {
    snprintf(g->Message, sizeof(g->Message), MSG(MEM_ALLOC_ERR), "index", Num_K);
    goto err;    // Error
    } // endif alloc

  Offset.Size = (Num_K + 1) * sizeof(int);

  if (!PlgDBalloc(g, NULL, Offset)) {
    snprintf(g->Message, sizeof(g->Message), MSG(MEM_ALLOC_ERR), "offset", Num_K + 1);
    goto err;    // Error
    } // endif alloc

  // We must separate keys and added columns before sorting
  addcolp = To_LastCol->Next;
  To_LastCol->Next = NULL;

  // Call the sort program, it returns the number of distinct values
  if ((Ndif = Qsort(g, Num_K)) < 0)
    goto err;       // Error during sort

  if (trace(1))
    htrc("Make: Nk=%d n=%d Num_K=%d Ndif=%d addcolp=%p BlkFil=%p X=%p\n",
          Nk, n, Num_K, Ndif, addcolp, Tdbp->To_BlkFil, X);

  // Check whether the unique index is unique indeed
  if (!Mul)
  {
    if (Ndif < Num_K) {
      strcpy(g->Message, MSG(INDEX_NOT_UNIQ));
      brc = true;
      goto err;
    } else
      PlgDBfree(Offset);           // Not used anymore
  }

  // Restore kcp list
  To_LastCol->Next = addcolp;

  // Use the index to physically reorder the xindex
  Srtd = Reorder(g);

  if (Ndif < Num_K) {
    // Resize the offset array
    PlgDBrealloc(g, NULL, Offset, (Ndif + 1) * sizeof(int));

    // Initial value of MaxSame
    MaxSame = Pof[1] - Pof[0];

    // Resize the Key array by only keeping the distinct values
    for (i = 1; i < Ndif; i++) {
      for (kcp = To_KeyCol; kcp; kcp = kcp->Next)
        kcp->Move(i, Pof[i]);

      MaxSame = MY_MAX(MaxSame, Pof[i + 1] - Pof[i]);
      } // endfor i

    for (kcp = To_KeyCol; kcp; kcp = kcp->Next)
      kcp->ReAlloc(g, Ndif);

  } else {
    Mul = false;                   // Current index is unique
    PlgDBfree(Offset);             // Not used anymore
    MaxSame = 1;                   // Reset it when remaking an index
  } // endif Ndif

  /*********************************************************************/
  /*  Now do the reduction of the index. Indeed a multi-column index   */
  /*  can be used for only some of the first columns. For instance if  */
  /*  an index is defined for column A, B, C PlugDB can use it for     */
  /*  only the column A or the columns A, B.                           */
  /*  What we do here is to reduce the data so column A will contain   */
  /*  only the sorted distinct values of A, B will contain data such   */
  /*  as only distinct values of A,B are stored etc.                   */
  /*  This implies that for each column set an offset array is made    */
  /*  except if the subset originally contains unique values.          */
  /*********************************************************************/
  // Update progress information
//dup->Step = STEP(REDUCE_INDEX);

  ndf = Ndif;
  To_LastCol->Mxs = MaxSame;

  for (kcp = To_LastCol->Previous; kcp; kcp = kcp->Previous) {
    if (!(bof = kcp->MakeOffset(g, ndf)))
      goto err;
    else
      *bof = 0;

    for (n = 0, i = j = 1; i < ndf; i++)
      for (kp = kcp; kp; kp = kp->Previous)
        if (kp->Compare(n, i)) {
          // Values are not equal to last ones
          bof[j++] = n = i;
          break;
          } // endif Compare

    if (j < ndf) {
      // Sub-index is multiple
      bof[j] = ndf;
      ndf = j;                  // New number of distinct values

      // Resize the Key array by only keeping the distinct values
      for (kp = kcp; kp; kp = kp->Previous) {
        for (i = 1; i < ndf; i++)
          kp->Move(i, bof[i]);

        kp->ReAlloc(g, ndf);
        } // endif kcp

      // Resize the offset array
      kcp->MakeOffset(g, ndf);

      // Calculate the max same value for this column
      kcp->Mxs = ColMaxSame(kcp);
    } else {
      // Current sub-index is unique
      kcp->MakeOffset(g, 0);   // The offset is not used anymore
      kcp->Mxs = 1;            // Unique
    } // endif j

    } // endfor kcp

  /*********************************************************************/
  /*  For sorted columns and fixed record size, file position can be   */
  /*  calculated, so the Record array can be discarted.                */
  /*  Not true for DBF tables because of eventual soft deleted lines.  */
  /*  Note: for Num_K = 1 any non null value is Ok.                    */
  /*********************************************************************/
  if (Srtd && !filp && Tdbp->Ftype != RECFM_VAR && Tdbp->Ftype != RECFM_CSV
                    && Tdbp->Txfp->GetAmType() != TYPE_AM_DBF) {
    Incr = (Num_K > 1) ? To_Rec[1] : Num_K;
    PlgDBfree(Record);
    } // endif Srtd

  /*********************************************************************/
  /*  Check whether a two-tier find algorithm can be implemented.      */
  /*  It is currently implemented only for single key indexes.         */
  /*********************************************************************/
  if (Nk == 1 && ndf >= 65536) {
    // Implement a two-tier find algorithm
    for (Sblk = 256; (Sblk * Sblk * 4) < ndf; Sblk *= 2) ;

    Nblk = (ndf -1) / Sblk + 1;

    if (To_KeyCol->MakeBlockArray(g, Nblk, Sblk))
      goto err;    // Error

    } // endif Num_K

 nox:
  /*********************************************************************/
  /*  No valid record read yet for secondary file.                     */
  /*********************************************************************/
  Cur_K = Num_K;

  /*********************************************************************/
  /*  Save the xindex so it has not to be recalculated.                */
  /*********************************************************************/
  if (X) {
    if (SaveIndex(g, sxp))
      brc = true;

  } else {                     // Dynamic index
    // Indicate that key column values can be found from KEYCOL's
    for (kcp = To_KeyCol; kcp; kcp = kcp->Next)
      kcp->Colp->SetKcol(kcp);

    Tdbp->SetFilter(NULL);     // Not used anymore
  } // endif X

 err:
  // We don't need the index anymore
  if (X || brc)
    Close();

  if (brc)
    printf("%s\n", g->Message);

  return brc;
  } // end of Make

/***********************************************************************/
/*  Return the max size of the intermediate column.                    */
/***********************************************************************/
int XINDEX::ColMaxSame(PXCOL kp)
  {
  int *kof, i, ck1, ck2, ckn = 1;
  PXCOL kcp;

  // Calculate the max same value for this column
  for (i = 0; i < kp->Ndf; i++) {
    ck1 = i;
    ck2 = i + 1;

    for (kcp = kp; kcp; kcp = kcp->Next) {
      if (!(kof = (kcp->Next) ? kcp->Kof : Pof))
        break;

      ck1 = kof[ck1];
      ck2 = kof[ck2];
      } // endfor kcp

    ckn = MY_MAX(ckn, ck2 - ck1);
    } // endfor i

  return ckn;
  } // end of ColMaxSame

/***********************************************************************/
/*  Reorder: use the sort index to reorder the data in storage so      */
/*  it will be physically sorted and sort index can be removed.        */
/***********************************************************************/
bool XINDEX::Reorder(PGLOBAL g __attribute__((unused)))
  {
  int i, j, k, n;
  bool          sorted = true;
  PXCOL         kcp;
#if 0
  PDBUSER       dup = (PDBUSER)g->Activityp->Aptr;

  if (Num_K > 500000) {
    // Update progress information
    dup->Step = STEP(REORDER_INDEX);
    dup->ProgMax = Num_K;
    dup->ProgCur = 0;
  } else
    dup = NULL;
#endif // 0

  if (!Pex)
    return Srtd;

  for (i = 0; i < Num_K; i++) {
    if (Pex[i] == Num_K) {        // Already moved
      continue;
    } else if (Pex[i] == i) {     // Already placed
//    if (dup)
//      dup->ProgCur++;

      continue;
    } // endif's Pex

    sorted = false;

    for (kcp = To_KeyCol; kcp; kcp = kcp->Next)
      kcp->Save(i);

    n = To_Rec[i];

    for (j = i;; j = k) {
      k = Pex[j];
      Pex[j] = Num_K;           // Mark position as set

      if (k == i) {
        for (kcp = To_KeyCol; kcp; kcp = kcp->Next)
          kcp->Restore(j);

        To_Rec[j] = n;
        break;                  // end of loop
      } else {
        for (kcp = To_KeyCol; kcp; kcp = kcp->Next)
          kcp->Move(j, k);      // Move k to j

        To_Rec[j] = To_Rec[k];
      } // endif k

//    if (dup)
//      dup->ProgCur++;

      } // endfor j

    } // endfor i

  // The index is not used anymore
  PlgDBfree(Index);
  return sorted;
  } // end of Reorder

/***********************************************************************/
/*  Save the index values for this table.                              */
/*  The problem here is to avoid name duplication, because more than   */
/*  one data file can have the same name (but different types) and/or  */
/*  the same data file can be used with different block sizes. This is */
/*  why we use Ofn that defaults to the file name but can be set to a  */
/*  different name if necessary.                                       */
/***********************************************************************/
bool XINDEX::SaveIndex(PGLOBAL g, PIXDEF sxp)
  {
  PCSZ    ftype;
  char    fn[_MAX_PATH];
  int     n[NZ], nof = (Mul) ? (Ndif + 1) : 0;
  int     id = -1, size = 0;
  bool    sep, rc = false;
  PXCOL   kcp = To_KeyCol;
  PDOSDEF defp = (PDOSDEF)Tdbp->To_Def;
//PDBUSER dup = PlgGetUser(g);

//dup->Step = STEP(SAVING_INDEX);
//dup->ProgMax = 15 + 16 * Nk;
//dup->ProgCur = 0;

  switch (Tdbp->Ftype) {
    case RECFM_VAR: ftype = ".dnx"; break;
    case RECFM_FIX: ftype = ".fnx"; break;
    case RECFM_BIN: ftype = ".bnx"; break;
    case RECFM_VCT: ftype = ".vnx"; break;
  case RECFM_CSV: ftype = ".cnx"; break;
  case RECFM_DBF: ftype = ".dbx"; break;
    default:
      snprintf(g->Message, sizeof(g->Message), MSG(INVALID_FTYPE), Tdbp->Ftype);
      return true;
    } // endswitch Ftype

  if ((sep = defp->GetBoolCatInfo("SepIndex", false))) {
    // Index is saved in a separate file
#if defined(_WIN32)
    char drive[_MAX_DRIVE];
#else
    char *drive = NULL;
#endif
    char direc[_MAX_DIR];
    char fname[_MAX_FNAME];

    _splitpath(defp->GetOfn(), drive, direc, fname, NULL);
    safe_strcat(fname, sizeof(fname), "_");
    safe_strcat(fname, sizeof(fname), Xdp->GetName());
    _makepath(fn, drive, direc, fname, ftype);
    sxp = NULL;
  } else {
    id = ID;
    strcat(PlugRemoveType(fn, strcpy(fn, defp->GetOfn())), ftype);
  } // endif sep

  PlugSetPath(fn, fn, Tdbp->GetPath());

  if (X->Open(g, fn, id, (sxp) ? MODE_INSERT : MODE_WRITE)) {
    printf("%s\n", g->Message);
    return true;
    } // endif Open

  if (!Ndif)
    goto end;                // Void index

  /*********************************************************************/
  /*  Write the index values on the index file.                        */
  /*********************************************************************/
  n[0] = ID + MAX_INDX;       // To check validity
  n[1] = Nk;                  // The number of indexed columns
  n[2] = nof;                 // The offset array size or 0
  n[3] = Num_K;               // The index size
  n[4] = Incr;                // Increment of record positions
  n[5] = Nblk; n[6] = Sblk;
  n[7] = Srtd ? 1 : 0;        // Values are sorted in the file

  if (trace(1)) {
    htrc("Saving index %s\n", Xdp->GetName());
    htrc("ID=%d Nk=%d nof=%d Num_K=%d Incr=%d Nblk=%d Sblk=%d Srtd=%d\n",
          ID, Nk, nof, Num_K, Incr, Nblk, Sblk, Srtd);
    } // endif trace

  size = X->Write(g, n, NZ, sizeof(int), rc);
//dup->ProgCur = 1;

  if (Mul)             // Write the offset array
    size += X->Write(g, Pof, nof, sizeof(int), rc);

//dup->ProgCur = 5;

  if (!Incr)           // Write the record position array(s)
    size += X->Write(g, To_Rec, Num_K, sizeof(int), rc);

//dup->ProgCur = 15;

  for (; kcp; kcp = kcp->Next) {
    n[0] = kcp->Ndf;                 // Number of distinct sub-values
    n[1] = (kcp->Kof) ? kcp->Ndf + 1 : 0;     // 0 if unique
    n[2] = (kcp == To_KeyCol) ? Nblk : 0;
    n[3] = kcp->Klen;                // To be checked later
    n[4] = kcp->Type;                // To be checked later

    size += X->Write(g, n, NW, sizeof(int), rc);
//  dup->ProgCur += 1;

    if (n[2])
      size += X->Write(g, kcp->To_Bkeys, Nblk, kcp->Klen, rc);

//  dup->ProgCur += 5;

    size += X->Write(g, kcp->To_Keys, n[0], kcp->Klen, rc);
//  dup->ProgCur += 5;

    if (n[1])
      size += X->Write(g, kcp->Kof, n[1], sizeof(int), rc);

//  dup->ProgCur += 5;
    } // endfor kcp

  if (trace(1))
    htrc("Index %s saved, Size=%d\n", Xdp->GetName(), size);

 end:
  X->Close(fn, id);
  return rc;
  } // end of SaveIndex

/***********************************************************************/
/*  Init: Open and Initialize a Key Index.                             */
/***********************************************************************/
bool XINDEX::Init(PGLOBAL g)
  {
#if defined(XMAP)
  if (xmap)
    return MapInit(g);
#endif   // XMAP

  /*********************************************************************/
  /*  Table will be accessed through an index table.                   */
  /*  If sorting is required, this will be done later.                 */
  /*********************************************************************/
  PCSZ    ftype;
  char    fn[_MAX_PATH];
  int     k, n, nv[NZ], id = -1;
  bool    estim = false;
  PCOL    colp;
  PXCOL   prev = NULL, kcp = NULL;
  PDOSDEF defp = (PDOSDEF)Tdbp->To_Def;

  /*********************************************************************/
  /*  Get the estimated table size.                                    */
  /*  Note: for fixed tables we must use cardinality to avoid the call */
  /*  to MaxBlkSize that could reduce the cardinality value.           */
  /*********************************************************************/
  if (Tdbp->Cardinality(NULL)) {
    // For DBF tables, Cardinality includes bad or soft deleted lines
    // that are not included in the index, and can be larger then the
    // index size.
    estim = (Tdbp->Ftype == RECFM_DBF || Tdbp->Txfp->GetAmType() == TYPE_AM_ZIP);
    n = Tdbp->Cardinality(g);      // n is exact table size
  } else {
    // Variable table not optimized
    estim = true;                  // n is an estimate of the size
    n = Tdbp->GetMaxSize(g);
  } // endif Cardinality

  if (n <= 0)
    return !(n == 0);             // n < 0 error, n = 0 void table

  /*********************************************************************/
  /*  Get the first key column.                                        */
  /*********************************************************************/
  if (!Nk || !To_Cols || (!To_Vals && Op != OP_FIRST && Op != OP_FSTDIF)) {
    strcpy(g->Message, MSG(NO_KEY_COL));
    return true;    // Error
  } else
    colp = To_Cols[0];

  switch (Tdbp->Ftype) {
    case RECFM_VAR: ftype = ".dnx"; break;
    case RECFM_FIX: ftype = ".fnx"; break;
    case RECFM_BIN: ftype = ".bnx"; break;
    case RECFM_VCT: ftype = ".vnx"; break;
  case RECFM_CSV: ftype = ".cnx"; break;
  case RECFM_DBF: ftype = ".dbx"; break;
    default:
      snprintf(g->Message, sizeof(g->Message), MSG(INVALID_FTYPE), Tdbp->Ftype);
      return true;
    } // endswitch Ftype

  if (defp->SepIndex()) {
    // Index was saved in a separate file
#if defined(_WIN32)
    char drive[_MAX_DRIVE];
#else
    char *drive = NULL;
#endif
    char direc[_MAX_DIR];
    char fname[_MAX_FNAME];

    _splitpath(defp->GetOfn(), drive, direc, fname, NULL);
    safe_strcat(fname, sizeof(fname), "_");
    safe_strcat(fname, sizeof(fname), Xdp->GetName());
    _makepath(fn, drive, direc, fname, ftype);
  } else {
    id = ID;
    strcat(PlugRemoveType(fn, strcpy(fn, defp->GetOfn())), ftype);
  } // endif sep

  PlugSetPath(fn, fn, Tdbp->GetPath());

  if (trace(1))
    htrc("Index %s file: %s\n", Xdp->GetName(), fn);

  /*********************************************************************/
  /*  Open the index file and check its validity.                      */
  /*********************************************************************/
  if (X->Open(g, fn, id, MODE_READ))
    goto err;               // No saved values

  //  Now start the reading process.
  if (X->Read(g, nv, NZ - 1, sizeof(int)))
    goto err;

  if (nv[0] >= MAX_INDX) {
    // New index format
    if (X->Read(g, nv + 7, 1, sizeof(int)))
      goto err;

    Srtd = nv[7] != 0;
    nv[0] -= MAX_INDX;
  } else
    Srtd = false;

  if (trace(1))
    htrc("nv=%d %d %d %d %d %d %d (%d)\n",
          nv[0], nv[1], nv[2], nv[3], nv[4], nv[5], nv[6], Srtd);

  // The test on ID was suppressed because MariaDB can change an index ID
  // when other indexes are added or deleted
  if (/*nv[0] != ID ||*/ nv[1] != Nk) {
    snprintf(g->Message, sizeof(g->Message), MSG(BAD_INDEX_FILE), fn);

    if (trace(1))
      htrc("nv[0]=%d ID=%d nv[1]=%d Nk=%d\n", nv[0], ID, nv[1], Nk);

    goto err;
    } // endif

  if (nv[2]) {
    Mul = true;
    Ndif = nv[2];

    // Allocate the storage that will contain the offset array
    Offset.Size = Ndif * sizeof(int);

    if (!PlgDBalloc(g, NULL, Offset)) {
      snprintf(g->Message, sizeof(g->Message), MSG(MEM_ALLOC_ERR), "offset", Ndif);
      goto err;
      } // endif

    if (X->Read(g, Pof, Ndif, sizeof(int)))
      goto err;

    Ndif--;   // nv[2] is offset size, equal to Ndif + 1
  } else {
    Mul = false;
    Ndif = nv[3];
  } // endif nv[2]

  if (nv[3] < n && estim)
    n = nv[3];              // n was just an evaluated max value

  if (nv[3] != n) {
    snprintf(g->Message, sizeof(g->Message), MSG(OPT_NOT_MATCH), fn);
    goto err;
    } // endif

  Num_K = nv[3];
  Incr = nv[4];
  Nblk = nv[5];
  Sblk = nv[6];

  if (!Incr) {
    /*******************************************************************/
    /*  Allocate the storage that will contain the file positions.     */
    /*******************************************************************/
    Record.Size = Num_K * sizeof(int);

    if (!PlgDBalloc(g, NULL, Record)) {
      snprintf(g->Message, sizeof(g->Message), MSG(MEM_ALLOC_ERR), "index", Num_K);
      goto err;
      } // endif

    if (X->Read(g, To_Rec, Num_K, sizeof(int)))
      goto err;

  } else
    Srtd = true;    // Sorted positions can be calculated

  /*********************************************************************/
  /*  Allocate the KXYCOL blocks used to store column values.          */
  /*********************************************************************/
  for (k = 0; k < Nk; k++) {
    if (k == Nval)
      To_LastVal = prev;

    if (X->Read(g, nv, NW, sizeof(int)))
      goto err;

    colp = To_Cols[k];

    if (nv[4] != colp->GetResultType() || !colp->GetValue() ||
       (nv[3] != colp->GetValue()->GetClen() && nv[4] != TYPE_STRING)) {
      snprintf(g->Message, sizeof(g->Message), MSG(XCOL_MISMATCH), colp->GetName());
      goto err;    // Error
      } // endif GetKey

    kcp = new(g) KXYCOL(this);

    if (kcp->Init(g, colp, nv[0], true, (int)nv[3]))
      goto err;    // Error

    /*******************************************************************/
    /*  Read the index values from the index file.                     */
    /*******************************************************************/
    if (k == 0 && Nblk) {
      if (kcp->MakeBlockArray(g, Nblk, 0))
        goto err;

      // Read block values
      if (X->Read(g, kcp->To_Bkeys, Nblk, kcp->Klen))
        goto err;

      } // endif Nblk

    // Read the entire (small) index
    if (X->Read(g, kcp->To_Keys, nv[0], kcp->Klen))
      goto err;

    if (nv[1]) {
      if (!kcp->MakeOffset(g, nv[1] - 1))
        goto err;

      // Read the offset array
      if (X->Read(g, kcp->Kof, nv[1], sizeof(int)))
        goto err;

      } // endif n[1]

    if (!kcp->Prefix)
      // Indicate that the key column value can be found from KXYCOL
      colp->SetKcol(kcp);

    if (prev) {
      kcp->Previous = prev;
      prev->Next = kcp;
    } else
      To_KeyCol = kcp;

    prev = kcp;
    } // endfor k

  To_LastCol = prev;

  if (Mul && prev) {
    // Last key offset is the index offset
    kcp->Koff = Offset;
    kcp->Koff.Sub = true;
    } // endif Mul

  X->Close();

  /*********************************************************************/
  /*  No valid record read yet for secondary file.                     */
  /*********************************************************************/
  Cur_K = Num_K;
  return false;

err:
  Close();
  return true;
  } // end of Init

#if defined(XMAP)
/***********************************************************************/
/*  Init: Open and Initialize a Key Index.                             */
/***********************************************************************/
bool XINDEX::MapInit(PGLOBAL g)
  {
  /*********************************************************************/
  /*  Table will be accessed through an index table.                   */
  /*  If sorting is required, this will be done later.                 */
  /*********************************************************************/
  const char *ftype;
  BYTE   *mbase;
  char    fn[_MAX_PATH];
  int    *nv, nv0, k, n, id = -1;
  bool    estim;
  PCOL    colp;
  PXCOL   prev = NULL, kcp = NULL;
  PDOSDEF defp = (PDOSDEF)Tdbp->To_Def;

  /*********************************************************************/
  /*  Get the estimated table size.                                    */
  /*  Note: for fixed tables we must use cardinality to avoid the call */
  /*  to MaxBlkSize that could reduce the cardinality value.           */
  /*********************************************************************/
  if (Tdbp->Cardinality(NULL)) {
    // For DBF tables, Cardinality includes bad or soft deleted lines
    // that are not included in the index, and can be larger then the
    // index size.
    estim = (Tdbp->Ftype == RECFM_DBF);
    n = Tdbp->Cardinality(g);      // n is exact table size
  } else {
    // Variable table not optimized
    estim = true;                  // n is an estimate of the size
    n = Tdbp->GetMaxSize(g);
  } // endif Cardinality

  if (n <= 0)
    return !(n == 0);             // n < 0 error, n = 0 void table

  /*********************************************************************/
  /*  Get the first key column.                                        */
  /*********************************************************************/
  if (!Nk || !To_Cols || (!To_Vals && Op != OP_FIRST && Op != OP_FSTDIF)) {
    strcpy(g->Message, MSG(NO_KEY_COL));
    return true;    // Error
  } else
    colp = To_Cols[0];

  switch (Tdbp->Ftype) {
    case RECFM_VAR: ftype = ".dnx"; break;
    case RECFM_FIX: ftype = ".fnx"; break;
    case RECFM_BIN: ftype = ".bnx"; break;
    case RECFM_VCT: ftype = ".vnx"; break;
  case RECFM_CSV: ftype = ".cnx"; break;
  case RECFM_DBF: ftype = ".dbx"; break;
    default:
      snprintf(g->Message, sizeof(g->Message), MSG(INVALID_FTYPE), Tdbp->Ftype);
      return true;
    } // endswitch Ftype

  if (defp->SepIndex()) {
    // Index was save in a separate file
#if defined(_WIN32)
    char drive[_MAX_DRIVE];
#else
    char *drive = NULL;
#endif
    char direc[_MAX_DIR];
    char fname[_MAX_FNAME];

    _splitpath(defp->GetOfn(), drive, direc, fname, NULL);
    safe_strcat(fname, sizeof(fname), "_");
    safe_strcat(fname, sizeof(fname), Xdp->GetName());
    _makepath(fn, drive, direc, fname, ftype);
  } else {
    id = ID;
    strcat(PlugRemoveType(fn, strcpy(fn, defp->GetOfn())), ftype);
  } // endif SepIndex

  PlugSetPath(fn, fn, Tdbp->GetPath());

  if (trace(1))
    htrc("Index %s file: %s\n", Xdp->GetName(), fn);

  /*********************************************************************/
  /*  Get a view on the part of the index file containing this index.  */
  /*********************************************************************/
  if (!(mbase = (BYTE*)X->FileView(g, fn)))
    goto err;

  if (id >= 0) {
    // Get offset from the header
    IOFF *noff = (IOFF*)mbase;

    // Position the memory base at the offset of this index
    mbase += noff[id].v.Low;
    } // endif id

  //  Now start the mapping process.
  nv = (int*)mbase;

  if (nv[0] >= MAX_INDX) {
    // New index format
    Srtd = nv[7] != 0;
    nv0 = nv[0] - MAX_INDX;
    mbase += NZ * sizeof(int);
  } else {
    Srtd = false;
    mbase += (NZ - 1) * sizeof(int);
  nv0 = nv[0];
  } // endif nv

  if (trace(1))
    htrc("nv=%d %d %d %d %d %d %d %d\n",
          nv0, nv[1], nv[2], nv[3], nv[4], nv[5], nv[6], Srtd);

  // The test on ID was suppressed because MariaDB can change an index ID
  // when other indexes are added or deleted
  if (/*nv0 != ID ||*/ nv[1] != Nk) {
    // Not this index
    snprintf(g->Message, sizeof(g->Message), MSG(BAD_INDEX_FILE), fn);

    if (trace(1))
      htrc("nv0=%d ID=%d nv[1]=%d Nk=%d\n", nv0, ID, nv[1], Nk);

    goto err;
    } // endif nv

  if (nv[2]) {
    // Set the offset array memory block
    Offset.Memp = mbase;
    Offset.Size = nv[2] * sizeof(int);
    Offset.Sub = true;
    Mul = true;
    Ndif = nv[2] - 1;
    mbase += Offset.Size;
  } else {
    Mul = false;
    Ndif = nv[3];
  } // endif nv[2]

  if (nv[3] < n && estim)
    n = nv[3];              // n was just an evaluated max value

  if (nv[3] != n) {
    snprintf(g->Message, sizeof(g->Message), MSG(OPT_NOT_MATCH), fn);
    goto err;
    } // endif

  Num_K = nv[3];
  Incr = nv[4];
  Nblk = nv[5];
  Sblk = nv[6];

  if (!Incr) {
    /*******************************************************************/
    /*  Point to the storage that contains the file positions.         */
    /*******************************************************************/
    Record.Size = Num_K * sizeof(int);
    Record.Memp = mbase;
    Record.Sub = true;
    mbase += Record.Size;
  } else
    Srtd = true;    // Sorted positions can be calculated

  /*********************************************************************/
  /*  Allocate the KXYCOL blocks used to store column values.          */
  /*********************************************************************/
  for (k = 0; k < Nk; k++) {
    if (k == Nval)
      To_LastVal = prev;

    nv = (int*)mbase;
    mbase += (NW * sizeof(int));

    colp = To_Cols[k];

    if (nv[4] != colp->GetResultType() || !colp->GetValue() ||
       (nv[3] != colp->GetValue()->GetClen() && nv[4] != TYPE_STRING)) {
      snprintf(g->Message, sizeof(g->Message), MSG(XCOL_MISMATCH), colp->GetName());
      goto err;    // Error
      } // endif GetKey

    kcp = new(g) KXYCOL(this);

    if (!(mbase = kcp->MapInit(g, colp, nv, mbase)))
      goto err;

    if (!kcp->Prefix)
      // Indicate that the key column value can be found from KXYCOL
      colp->SetKcol(kcp);

    if (prev) {
      kcp->Previous = prev;
      prev->Next = kcp;
    } else
      To_KeyCol = kcp;

    prev = kcp;
    } // endfor k

  To_LastCol = prev;

  if (Mul && prev)
    // Last key offset is the index offset
    kcp->Koff = Offset;

  /*********************************************************************/
  /*  No valid record read yet for secondary file.                     */
  /*********************************************************************/
  Cur_K = Num_K;
  return false;

err:
  Close();
  return true;
  } // end of MapInit
#endif   // XMAP

/***********************************************************************/
/*  Get Ndif and Num_K from the index file.                            */
/***********************************************************************/
bool XINDEX::GetAllSizes(PGLOBAL g,/* int &ndif,*/ int &numk)
  {
  PCSZ    ftype;
  char    fn[_MAX_PATH];
  int     nv[NZ], id = -1; // n
//bool    estim = false;
  bool    rc = true;
  PDOSDEF defp = (PDOSDEF)Tdbp->To_Def;

//  ndif = numk = 0;
  numk = 0;

#if 0
  /*********************************************************************/
  /*  Get the estimated table size.                                    */
  /*  Note: for fixed tables we must use cardinality to avoid the call */
  /*  to MaxBlkSize that could reduce the cardinality value.           */
  /*********************************************************************/
  if (Tdbp->Cardinality(NULL)) {
    // For DBF tables, Cardinality includes bad or soft deleted lines
    // that are not included in the index, and can be larger then the
    // index size.
    estim = (Tdbp->Ftype == RECFM_DBF);
    n = Tdbp->Cardinality(g);      // n is exact table size
  } else {
    // Variable table not optimized
    estim = true;                  // n is an estimate of the size
    n = Tdbp->GetMaxSize(g);
  } // endif Cardinality

  if (n <= 0)
    return !(n == 0);             // n < 0 error, n = 0 void table

  /*********************************************************************/
  /*  Check the key part number.                                       */
  /*********************************************************************/
  if (!Nk) {
    strcpy(g->Message, MSG(NO_KEY_COL));
    return true;    // Error
    } // endif Nk
#endif // 0

  switch (Tdbp->Ftype) {
    case RECFM_VAR: ftype = ".dnx"; break;
    case RECFM_FIX: ftype = ".fnx"; break;
    case RECFM_BIN: ftype = ".bnx"; break;
    case RECFM_VCT: ftype = ".vnx"; break;
  case RECFM_CSV: ftype = ".cnx"; break;
  case RECFM_DBF: ftype = ".dbx"; break;
    default:
      snprintf(g->Message, sizeof(g->Message), MSG(INVALID_FTYPE), Tdbp->Ftype);
      return true;
    } // endswitch Ftype

  if (defp->SepIndex()) {
    // Index was saved in a separate file
#if defined(_WIN32)
    char drive[_MAX_DRIVE];
#else
    char *drive = NULL;
#endif
    char direc[_MAX_DIR];
    char fname[_MAX_FNAME];

    _splitpath(defp->GetOfn(), drive, direc, fname, NULL);
    safe_strcat(fname, sizeof(fname), "_");
    safe_strcat(fname, sizeof(fname), Xdp->GetName());
    _makepath(fn, drive, direc, fname, ftype);
  } else {
    id = ID;
    strcat(PlugRemoveType(fn, strcpy(fn, defp->GetOfn())), ftype);
  } // endif sep

  PlugSetPath(fn, fn, Tdbp->GetPath());

  if (trace(1))
    htrc("Index %s file: %s\n", Xdp->GetName(), fn);

  /*********************************************************************/
  /*  Open the index file and check its validity.                      */
  /*********************************************************************/
  if (X->Open(g, fn, id, MODE_READ))
    goto err;               // No saved values

  // Get offset from XDB file
//if (X->Seek(g, Defoff, Defhigh, SEEK_SET))
//  goto err;

  //  Now start the reading process.
  if (X->Read(g, nv, NZ, sizeof(int)))
    goto err;

  if (trace(1))
    htrc("nv=%d %d %d %d\n", nv[0], nv[1], nv[2], nv[3]);

  // The test on ID was suppressed because MariaDB can change an index ID
  // when other indexes are added or deleted
  if (/*nv[0] != ID ||*/ nv[1] != Nk) {
    snprintf(g->Message, sizeof(g->Message), MSG(BAD_INDEX_FILE), fn);

    if (trace(1))
      htrc("nv[0]=%d ID=%d nv[1]=%d Nk=%d\n", nv[0], ID, nv[1], Nk);

    goto err;
    } // endif

#if 0
  if (nv[2]) {
    Mul = true;
    Ndif = nv[2] - 1;  // nv[2] is offset size, equal to Ndif + 1
  } else {
    Mul = false;
    Ndif = nv[3];
  } // endif nv[2]

  if (nv[3] < n && estim)
    n = nv[3];              // n was just an evaluated max value

  if (nv[3] != n) {
    snprintf(g->Message, sizeof(g->Message), MSG(OPT_NOT_MATCH), fn);
    goto err;
    } // endif
#endif // 0

  Num_K = nv[3];

#if 0
  if (Nk > 1) {
    if (nv[2] && X->Seek(g, nv[2] * sizeof(int), 0, SEEK_CUR))
      goto err;

    if (!nv[4] && X->Seek(g, Num_K * sizeof(int), 0, SEEK_CUR))
      goto err;

    if (X->Read(g, nv, NW, sizeof(int)))
      goto err;

    PCOL colp = *To_Cols;

    if (nv[4] != colp->GetResultType()  ||
       (nv[3] != colp->GetValue()->GetClen() && nv[4] != TYPE_STRING)) {
      snprintf(g->Message, sizeof(g->Message), MSG(XCOL_MISMATCH), colp->GetName());
      goto err;    // Error
      } // endif GetKey

    Ndif = nv[0];
    } // endif Nk
#endif // 0

  /*********************************************************************/
  /*  Set size values.                                                 */
  /*********************************************************************/
//ndif = Ndif;
  numk = Num_K;
  rc = false;

err:
  X->Close();
  return rc;
  } // end of GetAllSizes

/***********************************************************************/
/*  RANGE: Tell how many records exist for a given value, for an array */
/*  of values, or in a given value range.                              */
/***********************************************************************/
int XINDEX::Range(PGLOBAL g, int limit, bool incl)
  {
  int  i, k, n = 0;
  PXOB *xp = To_Vals;
  PXCOL kp = To_KeyCol;
  OPVAL op = Op;

  switch (limit) {
    case 1: Op = (incl) ? OP_GE : OP_GT; break;
    case 2: Op = (incl) ? OP_GT : OP_GE; break;
    default: return 0;
    } // endswitch limit

  /*********************************************************************/
  /*  Currently only range of constant values with an EQ operator is   */
  /*  implemented.  Find the number of rows for each given values.     */
  /*********************************************************************/
  if (xp[0]->GetType() == TYPE_CONST) {
    for (i = 0; kp; kp = kp->Next) {
      kp->Valp->SetValue_pval(xp[i]->GetValue(), !kp->Prefix);
      if (++i == Nval) break;
      } // endfor kp

    if ((k = FastFind()) < Num_K)
      n = k;
//      if (limit)
//        n = (Mul) ? k : kp->Val_K;
//      else
//        n = (Mul) ? Pof[kp->Val_K + 1] - k : 1;

  } else {
    strcpy(g->Message, MSG(RANGE_NO_JOIN));
    n = -1;                        // Logical error
  } // endif'f Type

  Op = op;
  return n;
  } // end of Range

/***********************************************************************/
/*  Return the size of the group (equal values) of the current value.  */
/***********************************************************************/
int XINDEX::GroupSize(void)
  {
#if defined(_DEBUG)
  assert(To_LastCol->Val_K >= 0 && To_LastCol->Val_K < Ndif);
#endif   // _DEBUG

  if (Nval == Nk)
    return (Pof) ? Pof[To_LastCol->Val_K + 1] - Pof[To_LastCol->Val_K]
                 : 1;

#if defined(_DEBUG)
  assert(To_LastVal);
#endif   // _DEBUG

  // Index whose only some columns are used
  int ck1, ck2;

  ck1 = To_LastVal->Val_K;
  ck2 = ck1 + 1;

#if defined(_DEBUG)
  assert(ck1 >= 0 && ck1 < To_LastVal->Ndf);
#endif   // _DEBUG

  for (PXCOL kcp = To_LastVal; kcp; kcp = kcp->Next) {
    ck1 = (kcp->Kof) ? kcp->Kof[ck1] : ck1;
    ck2 = (kcp->Kof) ? kcp->Kof[ck2] : ck2;
    } // endfor kcp

  return ck2 - ck1;
  } // end of GroupSize

/***********************************************************************/
/*  Find Cur_K and Val_K's of the next distinct value of the index.    */
/*  Returns false if Ok, true if there are no more different values.   */
/***********************************************************************/
bool XINDEX::NextValDif(void)
  {
  int  curk;
  PXCOL kcp = (To_LastVal) ? To_LastVal : To_LastCol;

  if (++kcp->Val_K < kcp->Ndf) {
    Cur_K = curk = kcp->Val_K;

    // (Cur_K return is currently not used by SQLGBX)
    for (PXCOL kp = kcp; kp; kp = kp->Next)
      Cur_K = (kp->Kof) ? kp->Kof[Cur_K] : Cur_K;

  } else
    return true;

  for (kcp = kcp->Previous; kcp; kcp = kcp->Previous) {
    if (kcp->Kof && curk < kcp->Kof[kcp->Val_K + 1])
      break;                  // all previous columns have same value

    curk = ++kcp->Val_K;      // This is a break, get new column value
    } // endfor kcp

  return false;
  } // end of NextValDif

/***********************************************************************/
/*  XINDEX: Find Cur_K and Val_K's of next index entry.                */
/*  If eq is true next values must be equal to last ones up to Nval.   */
/*  Returns false if Ok, true if there are no more (equal) values.     */
/***********************************************************************/
bool XINDEX::NextVal(bool eq)
  {
  int  n, neq = Nk + 1, curk;
  PXCOL kcp;

  if (Cur_K == Num_K)
    return true;
  else
    curk = ++Cur_K;

  for (n = Nk, kcp = To_LastCol; kcp; n--, kcp = kcp->Previous) {
    if (kcp->Kof) {
      if (curk == kcp->Kof[kcp->Val_K + 1])
        neq = n;

    } else {
#ifdef _DEBUG
      assert(curk == kcp->Val_K + 1);
#endif // _DEBUG
      neq = n;
    } // endif Kof

#ifdef _DEBUG
    assert(kcp->Val_K < kcp->Ndf);
#endif // _DEBUG

    // If this is not a break...
    if (neq > n)
      break;                  // all previous columns have same value

    curk = ++kcp->Val_K;      // This is a break, get new column value
    } // endfor kcp

  // Return true if no more values or, in case of "equal" values,
  // if the last used column value has changed
  return (Cur_K == Num_K || (eq && neq <= Nval));
  } // end of NextVal

/***********************************************************************/
/*  XINDEX: Find Cur_K and Val_K's of previous index entry.            */
/*  Returns false if Ok, true if there are no more values.             */
/***********************************************************************/
bool XINDEX::PrevVal(void)
  {
  int  n, neq = Nk + 1, curk;
  PXCOL kcp;

  if (Cur_K == 0)
    return true;
  else
    curk = --Cur_K;

  for (n = Nk, kcp = To_LastCol; kcp; n--, kcp = kcp->Previous) {
    if (kcp->Kof) {
      if (curk < kcp->Kof[kcp->Val_K])
        neq = n;

    } else {
#ifdef _DEBUG
      assert(curk == kcp->Val_K -1);
#endif // _DEBUG
      neq = n;
    } // endif Kof

#ifdef _DEBUG
    assert(kcp->Val_K >= 0);
#endif // _DEBUG

    // If this is not a break...
    if (neq > n)
      break;                  // all previous columns have same value

    curk = --kcp->Val_K;      // This is a break, get new column value
    } // endfor kcp

  return false;
  } // end of PrevVal

/***********************************************************************/
/*  XINDEX: Fetch a physical or logical record.                        */
/***********************************************************************/
int XINDEX::Fetch(PGLOBAL g)
  {
  int  n;
  PXCOL kp;

  if (Num_K == 0)
    return -1;                   // means end of file

  if (trace(2))
    htrc("XINDEX Fetch: Op=%d\n", Op);

  /*********************************************************************/
  /*  Table read through a sorted index.                               */
  /*********************************************************************/
  switch (Op) {
    case OP_NEXT:                 // Read next
      if (NextVal(false))
        return -1;                // End of indexed file

      break;
    case OP_FIRST:                // Read first
      for (Cur_K = 0, kp = To_KeyCol; kp; kp = kp->Next)
        kp->Val_K = 0;

      Op = OP_NEXT;
      break;
    case OP_SAME:                 // Read next same
      // Logically the key values should be the same as before
      if (NextVal(true)) {
        Op = OP_EQ;
        return -2;                // no more equal values
        } // endif NextVal

      break;
    case OP_NXTDIF:               // Read next dif
//      while (!NextVal(true)) ;

//      if (Cur_K >= Num_K)
//        return -1;              // End of indexed file
      if (NextValDif())
        return -1;                // End of indexed file

      break;
    case OP_FSTDIF:               // Read first diff
      for (Cur_K = 0, kp = To_KeyCol; kp; kp = kp->Next)
        kp->Val_K = 0;

      Op = (Mul || Nval < Nk) ? OP_NXTDIF : OP_NEXT;
      break;
    case OP_LAST:                 // Read last key
      for (Cur_K = Num_K - 1, kp = To_KeyCol; kp; kp = kp->Next)
        kp->Val_K = kp->Kblp->GetNval() - 1;

      Op = OP_NEXT;
      break;
    case OP_PREV:                 // Read previous
      if (PrevVal())
        return -1;                // End of indexed file

      break;
    default:                      // Should be OP_EQ
//    if (Tbxp->Key_Rank < 0) {
        /***************************************************************/
        /*  Look for the first key equal to the link column values     */
        /*  and return its rank whithin the index table.               */
        /***************************************************************/
        for (n = 0, kp = To_KeyCol; n < Nval && kp; n++, kp = kp->Next)
          if (kp->InitFind(g, To_Vals[n]))
            return -1;               // No more constant values

        Nth++;

        if (trace(2))
          htrc("Fetch: Looking for new value Nth=%d\n", Nth);

        Cur_K = FastFind();

        if (Cur_K >= Num_K)
          /*************************************************************/
          /* Rank not whithin index table, signal record not found.    */
          /*************************************************************/
          return -2;

        else if (Mul || Nval < Nk)
          Op = OP_SAME;

    } // endswitch Op

  /*********************************************************************/
  /*  If rank is equal to stored rank, record is already there.        */
  /*********************************************************************/
  if (Cur_K == Old_K)
    return -3;                   // Means record already there
  else
    Old_K = Cur_K;                // Store rank of newly read record

  /*********************************************************************/
  /*  Return the position of the required record.                      */
  /*********************************************************************/
  return (Incr) ? Cur_K * Incr : To_Rec[Cur_K];
  } // end of Fetch

/***********************************************************************/
/*  FastFind: Returns the index of matching record in a join using an  */
/*  optimized algorithm based on dichotomie and optimized comparing.   */
/***********************************************************************/
int XINDEX::FastFind(void)
  {
  int  curk, sup, inf, i= 0, k, n = 2;
  PXCOL kp, kcp;

//assert((int)nv == Nval);

  if (Nblk && Op == OP_EQ) {
    // Look in block values to find in which block to search
    sup = Nblk;
    inf = -1;

    while (n && sup - inf > 1) {
      i = (inf + sup) >> 1;

      n = To_KeyCol->CompBval(i);

      if (n < 0)
        sup = i;
      else
        inf = i;

      } // endwhile

    if (inf < 0)
      return Num_K;

//  i = inf;
    inf *= Sblk;

    if ((sup = inf + Sblk) > To_KeyCol->Ndf)
      sup = To_KeyCol->Ndf;

    inf--;
  } else {
    inf = -1;
    sup = To_KeyCol->Ndf;
  } // endif Nblk

  if (trace(4))
    htrc("XINDEX FastFind: Nblk=%d Op=%d inf=%d sup=%d\n",
                           Nblk, Op, inf, sup); 

  for (k = 0, kcp = To_KeyCol; kcp; kcp = kcp->Next) {
    while (sup - inf > 1) {
      i = (inf + sup) >> 1;

      n = kcp->CompVal(i);

      if      (n < 0)
        sup = i;
      else if (n > 0)
        inf = i;
      else
        break;

      } // endwhile

    if (n) {
      if (Op != OP_EQ) {
        // Currently only OP_GT or OP_GE
        kcp->Val_K = curk = sup;

        // Check for value changes in previous key parts
        for (kp = kcp->Previous; kp; kp = kp->Previous)
          if (kp->Kof && curk < kp->Kof[kp->Val_K + 1])
            break;
          else
            curk = ++kp->Val_K;

        n = 0;
        } // endif Op

      break;
      } // endif n

    kcp->Val_K = i;

    if (++k == Nval) {
      if (Op == OP_GT) {            // n is always 0
        curk = ++kcp->Val_K;        // Increment value by 1

        // Check for value changes in previous key parts
        for (kp = kcp->Previous; kp; kp = kp->Previous)
          if (kp->Kof && curk < kp->Kof[kp->Val_K + 1])
            break;                  // Not changed
          else
            curk = ++kp->Val_K;

        } // endif Op

      break;      // So kcp remains pointing the last tested block
      } // endif k

    if (kcp->Kof) {
      inf = kcp->Kof[i] - 1;
      sup = kcp->Kof[i + 1];
    } else {
      inf = i - 1;
      sup = i + 1;
    } // endif Kof

    } // endfor k, kcp

  if (n) {
    // Record not found
    for (kcp = To_KeyCol; kcp; kcp = kcp->Next)
      kcp->Val_K = kcp->Ndf;       // Not a valid value

    return Num_K;
    } // endif n

  for (curk = kcp->Val_K; kcp; kcp = kcp->Next) {
    kcp->Val_K = curk;
    curk = (kcp->Kof) ? kcp->Kof[kcp->Val_K] : kcp->Val_K;
    } // endfor kcp

  if (trace(4))
    htrc("XINDEX FastFind: curk=%d\n", curk);

  return curk;
  } // end of FastFind

/* -------------------------- XINDXS Class --------------------------- */

/***********************************************************************/
/*  XINDXS public constructor.                                         */
/***********************************************************************/
XINDXS::XINDXS(PTDBDOS tdbp, PIXDEF xdp, PXLOAD pxp, PCOL *cp, PXOB *xp)
      : XINDEX(tdbp, xdp, pxp, cp, xp)
  {
  Srtd = To_Cols[0]->GetOpt() == 2;
  } // end of XINDXS constructor

/***********************************************************************/
/*  XINDXS compare routine for C Quick/Insertion sort.                 */
/***********************************************************************/
int XINDXS::Qcompare(int *i1, int *i2)
  {
//num_comp++;
  return To_KeyCol->Compare(*i1, *i2);
  } // end of Qcompare

/***********************************************************************/
/*  Range: Tell how many records exist for given value(s):             */
/*  If limit=0 return range for these values.                          */
/*  If limit=1 return the start of range.                              */
/*  If limit=2 return the end of range.                                */
/***********************************************************************/
int XINDXS::Range(PGLOBAL g, int limit, bool incl)
  {
  int  k, n = 0;
  PXOB  xp = To_Vals[0];
  PXCOL kp = To_KeyCol;
  OPVAL op = Op;

// In case single column index doesn't exist return
  if (!kp)
    return 0;

  switch (limit) {
    case 1: Op = (incl) ? OP_GE : OP_GT; break;
    case 2: Op = (incl) ? OP_GT : OP_GE; break;
    default: Op = OP_EQ;
    } // endswitch limit

  /*********************************************************************/
  /*  Currently only range of constant values with an EQ operator is   */
  /*  implemented.  Find the number of rows for each given values.     */
  /*********************************************************************/
  if (xp->GetType() == TYPE_CONST) {
    kp->Valp->SetValue_pval(xp->GetValue(), !kp->Prefix);
    k = FastFind();

    if (k < Num_K || Op != OP_EQ)
    {
      if (limit)
        n = (Mul) ? k : kp->Val_K;
      else
        n = (Mul) ? Pof[kp->Val_K + 1] - k : 1;
    }
  } else {
    strcpy(g->Message, MSG(RANGE_NO_JOIN));
    n = -1;                        // Logical error
  } // endif'f Type

  Op = op;
  return n;
  } // end of Range

/***********************************************************************/
/*  Return the size of the group (equal values) of the current value.  */
/***********************************************************************/
int XINDXS::GroupSize(void)
  {
#if defined(_DEBUG)
  assert(To_KeyCol->Val_K >= 0 && To_KeyCol->Val_K < Ndif);
#endif   // _DEBUG
  return (Pof) ? Pof[To_KeyCol->Val_K + 1] - Pof[To_KeyCol->Val_K] : 1;
  } // end of GroupSize

/***********************************************************************/
/*  XINDXS: Find Cur_K and Val_K of previous index value.              */
/*  Returns false if Ok, true if there are no more values.             */
/***********************************************************************/
bool XINDXS::PrevVal(void)
  {
  if (--Cur_K < 0)
    return true;

  if (Mul) {
    if (Cur_K < Pof[To_KeyCol->Val_K])
      To_KeyCol->Val_K--;

  } else
    To_KeyCol->Val_K = Cur_K;

  return false;
  } // end of PrevVal

/***********************************************************************/
/*  XINDXS: Find Cur_K and Val_K of next index value.                  */
/*  If b is true next value must be equal to last one.                 */
/*  Returns false if Ok, true if there are no more (equal) values.     */
/***********************************************************************/
bool XINDXS::NextVal(bool eq)
  {
  bool rc;

  if (To_KeyCol->Val_K == Ndif)
    return true;

  if (Mul) {
    int limit = Pof[To_KeyCol->Val_K + 1];

#ifdef _DEBUG
    assert(Cur_K < limit);
    assert(To_KeyCol->Val_K < Ndif);
#endif // _DEBUG

    if (++Cur_K == limit) {
      To_KeyCol->Val_K++;
      rc = (eq || limit == Num_K);
    } else
      rc = false;

  } else
    rc = (To_KeyCol->Val_K = ++Cur_K) == Num_K || eq;

  return rc;
  } // end of NextVal

/***********************************************************************/
/*  XINDXS: Fetch a physical or logical record.                        */
/***********************************************************************/
int XINDXS::Fetch(PGLOBAL g)
  {
  if (Num_K == 0)
    return -1;                   // means end of file

  if (trace(2))
    htrc("XINDXS Fetch: Op=%d\n", Op);

  /*********************************************************************/
  /*  Table read through a sorted index.                               */
  /*********************************************************************/
  switch (Op) {
    case OP_NEXT:                // Read next
      if (NextVal(false))
        return -1;               // End of indexed file

      break;
    case OP_FIRST:               // Read first
      To_KeyCol->Val_K = Cur_K = 0;
      Op = OP_NEXT;
      break;
    case OP_SAME:                 // Read next same
      if (!Mul || NextVal(true)) {
        Op = OP_EQ;
        return -2;               // No more equal values
        } // endif Mul

      break;
    case OP_NXTDIF:              // Read next dif
      if (++To_KeyCol->Val_K == Ndif)
        return -1;               // End of indexed file

      Cur_K = Pof[To_KeyCol->Val_K];
      break;
    case OP_FSTDIF:               // Read first diff
      To_KeyCol->Val_K = Cur_K = 0;
      Op = (Mul) ? OP_NXTDIF : OP_NEXT;
      break;
    case OP_LAST:                // Read first
      Cur_K = Num_K - 1;
      To_KeyCol->Val_K = Ndif - 1;
      Op = OP_PREV;
      break;
    case OP_PREV:                // Read previous
      if (PrevVal())
        return -1;               // End of indexed file

      break;
    default:                     // Should be OP_EQ
      /*****************************************************************/
      /*  Look for the first key equal to the link column values       */
      /*  and return its rank whithin the index table.                 */
      /*****************************************************************/
      if (To_KeyCol->InitFind(g, To_Vals[0]))
        return -1;                 // No more constant values
      else
        Nth++;

      if (trace(2))
        htrc("Fetch: Looking for new value Nth=%d\n", Nth);

      Cur_K = FastFind();

      if (Cur_K >= Num_K)
        // Rank not whithin index table, signal record not found
        return -2;
      else if (Mul)
        Op = OP_SAME;

    } // endswitch Op

  /*********************************************************************/
  /*  If rank is equal to stored rank, record is already there.        */
  /*********************************************************************/
  if (Cur_K == Old_K)
    return -3;                   // Means record already there
  else
    Old_K = Cur_K;                // Store rank of newly read record

  /*********************************************************************/
  /*  Return the position of the required record.                      */
  /*********************************************************************/
  return (Incr) ? Cur_K * Incr : To_Rec[Cur_K];
  } // end of Fetch

/***********************************************************************/
/*  FastFind: Returns the index of matching indexed record using an    */
/*  optimized algorithm based on dichotomie and optimized comparing.   */
/***********************************************************************/
int XINDXS::FastFind(void)
  {
  int   sup, inf, i= 0, n = 2;
  PXCOL kcp = To_KeyCol;

  if (Nblk && Op == OP_EQ) {
    // Look in block values to find in which block to search
    sup = Nblk;
    inf = -1;

    while (n && sup - inf > 1) {
      i = (inf + sup) >> 1;

      n = kcp->CompBval(i);

      if (n < 0)
        sup = i;
      else
        inf = i;

      } // endwhile

    if (inf < 0)
      return Num_K;

    inf *= Sblk;

    if ((sup = inf + Sblk) > Ndif)
      sup = Ndif;

    inf--;
  } else {
    inf = -1;
    sup = Ndif;
  } // endif Nblk

  if (trace(4))
    htrc("XINDXS FastFind: Nblk=%d Op=%d inf=%d sup=%d\n",
                           Nblk, Op, inf, sup); 

  while (sup - inf > 1) {
    i = (inf + sup) >> 1;

    n = kcp->CompVal(i);

    if      (n < 0)
      sup = i;
    else if (n > 0)
      inf = i;
    else
      break;

    } // endwhile

  if (!n && Op == OP_GT) {
    ++i;
  } else if (n && Op != OP_EQ) {
    // Currently only OP_GT or OP_GE
    i = sup;
    n = 0;
  } // endif sup

  if (trace(4))
    htrc("XINDXS FastFind: n=%d i=%d\n", n, i);

  // Loop on kcp because of dynamic indexing
  for (; kcp; kcp = kcp->Next)
    kcp->Val_K = i;                 // Used by FillValue

  return ((n) ? Num_K : (Mul) ? Pof[i] : i);
  } // end of FastFind

/* -------------------------- XLOAD Class --------------------------- */

/***********************************************************************/
/*  XLOAD constructor.                                                 */
/***********************************************************************/
XLOAD::XLOAD(void)
  {
  Hfile = INVALID_HANDLE_VALUE;
  NewOff.Val = 0LL;
} // end of XLOAD constructor

/***********************************************************************/
/*  Close the index huge file.                                         */
/***********************************************************************/
void XLOAD::Close(void)
  {
  if (Hfile != INVALID_HANDLE_VALUE) {
    CloseFileHandle(Hfile);
    Hfile = INVALID_HANDLE_VALUE;
    } // endif Hfile

  } // end of Close

/* --------------------------- XFILE Class --------------------------- */

/***********************************************************************/
/*  XFILE constructor.                                                 */
/***********************************************************************/
XFILE::XFILE(void) : XLOAD()
  {
  Xfile = NULL;
#if defined(XMAP)
  Mmp = NULL;
#endif   // XMAP
  } // end of XFILE constructor

/***********************************************************************/
/*  Xopen function: opens a file using native API's.                   */
/***********************************************************************/
bool XFILE::Open(PGLOBAL g, char *filename, int id, MODE mode)
  {
  PCSZ pmod;
  bool rc;
  IOFF noff[MAX_INDX];

  /*********************************************************************/
  /*  Open the index file according to mode.                           */
  /*********************************************************************/
  switch (mode) {
    case MODE_READ:   pmod = "rb"; break;
    case MODE_WRITE:  pmod = "wb"; break;
    case MODE_INSERT: pmod = "ab"; break;
    default:
      snprintf(g->Message, sizeof(g->Message), MSG(BAD_FUNC_MODE), "Xopen", mode);
      return true;
    } // endswitch mode

  if (!(Xfile= global_fopen(g, MSGID_OPEN_ERROR_AND_STRERROR, filename, pmod))) {
    if (trace(1))
      htrc("Open: %s\n", g->Message);

    return true;
    } // endif Xfile

  if (mode == MODE_INSERT) {
    /*******************************************************************/
    /* Position the cursor at end of file so ftell returns file size.  */
    /*******************************************************************/
    if (fseek(Xfile, 0, SEEK_END)) {
      snprintf(g->Message, sizeof(g->Message), MSG(FUNC_ERRNO), errno, "Xseek");
      return true;
      } // endif

    NewOff.v.Low = (int)ftell(Xfile);

    if (trace(1))
      htrc("XFILE Open: NewOff.v.Low=%d\n", NewOff.v.Low);

  } else if (mode == MODE_WRITE) {
    if (id >= 0) {
      // New not sep index file. Write the header.
      memset(noff, 0, sizeof(noff));
      Write(g, noff, sizeof(IOFF), MAX_INDX, rc);
      fseek(Xfile, 0, SEEK_END);
      NewOff.v.Low = (int)ftell(Xfile);

      if (trace(1))
        htrc("XFILE Open: NewOff.v.Low=%d\n", NewOff.v.Low);

      } // endif id

  } else if (mode == MODE_READ && id >= 0) {
    // Get offset from the header
    if (fread(noff, sizeof(IOFF), MAX_INDX, Xfile) != MAX_INDX) {
      snprintf(g->Message, sizeof(g->Message), MSG(XFILE_READERR), errno);
      return true;
      } // endif MAX_INDX

      if (trace(1))
        htrc("XFILE Open: noff[%d].v.Low=%d\n", id, noff[id].v.Low);

    // Position the cursor at the offset of this index
    if (fseek(Xfile, noff[id].v.Low, SEEK_SET)) {
      snprintf(g->Message, sizeof(g->Message), MSG(FUNC_ERRNO), errno, "Xseek");
      return true;
      } // endif

  } // endif mode

  return false;
  } // end of Open

/***********************************************************************/
/*  Move into an index file.                                           */
/***********************************************************************/
bool XFILE::Seek(PGLOBAL g, int low, int high __attribute__((unused)),
                            int origin)
  {
#if defined(_DEBUG)
  assert(high == 0);
#endif  // !_DEBUG

  if (fseek(Xfile, low, origin)) {
    snprintf(g->Message, sizeof(g->Message), MSG(FUNC_ERRNO), errno, "Xseek");
    return true;
    } // endif

  return false;
  } // end of Seek

/***********************************************************************/
/*  Read from the index file.                                          */
/***********************************************************************/
bool XFILE::Read(PGLOBAL g, void *buf, int n, int size)
  {
  if (fread(buf, size, n, Xfile) != (size_t)n) {
    snprintf(g->Message, sizeof(g->Message), MSG(XFILE_READERR), errno);
    return true;
    } // endif size

  return false;
  } // end of Read

/***********************************************************************/
/*  Write on index file, set rc and return the number of bytes written */
/***********************************************************************/
int XFILE::Write(PGLOBAL g, void *buf, int n, int size, bool& rc)
  {
  int niw = (int)fwrite(buf, size, n, Xfile);

  if (niw != n) {
    snprintf(g->Message, sizeof(g->Message), MSG(XFILE_WRITERR), strerror(errno));
    rc = true;
    } // endif size

  return niw * size;
  } // end of Write

/***********************************************************************/
/*  Update the file header and close the index file.                   */
/***********************************************************************/
void XFILE::Close(char *fn, int id)
  {
  if (id >= 0 && fn && Xfile) {
    fclose(Xfile);

    if ((Xfile = fopen(fn, "r+b")))
      if (!fseek(Xfile, id * sizeof(IOFF), SEEK_SET))
        fwrite(&NewOff,  sizeof(int), 2, Xfile);

    } // endif id

  Close();
  } // end of Close

/***********************************************************************/
/*  Close the index file.                                              */
/***********************************************************************/
void XFILE::Close(void)
  {
  XLOAD::Close();

  if (Xfile) {
    fclose(Xfile);
    Xfile = NULL;
    } // endif Xfile

#if defined(XMAP)
  if (Mmp && CloseMemMap(Mmp->memory, Mmp->lenL))
    printf("Error closing mapped index\n");
#endif   // XMAP
  } // end of Close

#if defined(XMAP)
  /*********************************************************************/
  /*  Map the entire index file.                                       */
  /*********************************************************************/
void *XFILE::FileView(PGLOBAL g, char *fn)
  {
  HANDLE  h;

  Mmp = (MMP)PlugSubAlloc(g, NULL, sizeof(MEMMAP));
  h = CreateFileMap(g, fn, Mmp, MODE_READ, false);

  if (h == INVALID_HANDLE_VALUE || (!Mmp->lenH && !Mmp->lenL)) {
    if (!(*g->Message))
      strcpy(g->Message, MSG(FILE_MAP_ERR));

    CloseFileHandle(h);                    // Not used anymore
    return NULL;               // No saved values
    } // endif h

  CloseFileHandle(h);                    // Not used anymore
  return Mmp->memory;
  } // end of FileView
#endif // XMAP

/* -------------------------- XHUGE Class --------------------------- */

/***********************************************************************/
/*  Xopen function: opens a file using native API's.                   */
/***********************************************************************/
bool XHUGE::Open(PGLOBAL g, char *filename, int id, MODE mode)
  {
  IOFF noff[MAX_INDX];

  if (Hfile != INVALID_HANDLE_VALUE) {
    snprintf(g->Message, sizeof(g->Message), MSG(FILE_OPEN_YET), filename);
    return true;
    } // endif

  if (trace(1))
    htrc(" Xopen: filename=%s id=%d mode=%d\n", filename, id, mode);

#if defined(_WIN32)
  LONG  high = 0;
  DWORD rc, drc, access, share, creation;

  /*********************************************************************/
  /*  Create the file object according to access mode                  */
  /*********************************************************************/
  switch (mode) {
    case MODE_READ:
      access = GENERIC_READ;
      share = FILE_SHARE_READ;
      creation = OPEN_EXISTING;
      break;
    case MODE_WRITE:
      access = GENERIC_WRITE;
      share = 0;
      creation = CREATE_ALWAYS;
      break;
    case MODE_INSERT:
      access = GENERIC_WRITE;
      share = 0;
      creation = OPEN_EXISTING;
      break;
    default:
      snprintf(g->Message, sizeof(g->Message), MSG(BAD_FUNC_MODE), "Xopen", mode);
      return true;
    } // endswitch

  Hfile = CreateFile(filename, access, share, NULL, creation,
                               FILE_ATTRIBUTE_NORMAL, NULL);

  if (Hfile == INVALID_HANDLE_VALUE) {
    rc = GetLastError();
    snprintf(g->Message, sizeof(g->Message), MSG(OPEN_ERROR), rc, mode, filename);
    FormatMessage(FORMAT_MESSAGE_FLAGS, NULL, rc, 0,
                  (LPTSTR)filename, sizeof(filename), NULL);
    strcat(g->Message, filename);
    return true;
    } // endif Hfile

  if (trace(1))
    htrc(" access=%p share=%p creation=%d handle=%p fn=%s\n",
         access, share, creation, Hfile, filename);

  if (mode == MODE_INSERT) {
    /*******************************************************************/
    /* In Insert mode we must position the cursor at end of file.      */
    /*******************************************************************/
    rc = SetFilePointer(Hfile, 0, &high, FILE_END);

    if (rc == INVALID_SET_FILE_POINTER && (drc = GetLastError()) != NO_ERROR) {
      snprintf(g->Message, sizeof(g->Message), MSG(ERROR_IN_SFP), drc);
      CloseHandle(Hfile);
      Hfile = INVALID_HANDLE_VALUE;
      return true;
      } // endif

    NewOff.v.Low = (int)rc;
    NewOff.v.High = (int)high;
  } else if (mode == MODE_WRITE) {
    if (id >= 0) {
      // New not sep index file. Write the header.
      memset(noff, 0, sizeof(noff));
      rc = WriteFile(Hfile, noff, sizeof(noff), &drc, NULL);
      NewOff.v.Low = (int)drc;
      } // endif id

  } else if (mode == MODE_READ && id >= 0) {
    // Get offset from the header
    rc = ReadFile(Hfile, noff, sizeof(noff), &drc, NULL);

    if (!rc) {
      snprintf(g->Message, sizeof(g->Message), MSG(XFILE_READERR), GetLastError());
      return true;
      } // endif rc

    // Position the cursor at the offset of this index
    rc = SetFilePointer(Hfile, noff[id].v.Low,
                       (PLONG)&noff[id].v.High, FILE_BEGIN);

    if (rc == INVALID_SET_FILE_POINTER) {
      snprintf(g->Message, sizeof(g->Message), MSG(FUNC_ERRNO), GetLastError(), "SetFilePointer");
      return true;
      } // endif

  } // endif Mode

#else   // UNIX
  int    oflag = O_LARGEFILE;         // Enable file size > 2G
  mode_t pmod = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;

  /*********************************************************************/
  /*  Create the file object according to access mode                  */
  /*********************************************************************/
  switch (mode) {
    case MODE_READ:
      oflag |= O_RDONLY;
      break;
    case MODE_WRITE:
      oflag |= O_WRONLY | O_CREAT | O_TRUNC;
//    pmod = S_IREAD | S_IWRITE;
      break;
    case MODE_INSERT:
      oflag |= (O_WRONLY | O_APPEND);
      break;
    default:
      snprintf(g->Message, sizeof(g->Message), MSG(BAD_FUNC_MODE), "Xopen", mode);
      return true;
    } // endswitch

  Hfile= global_open(g, MSGID_OPEN_ERROR_AND_STRERROR, filename, oflag, pmod);

  if (Hfile == INVALID_HANDLE_VALUE) {
    /*rc = errno;*/
    if (trace(1))
      htrc("Open: %s\n", g->Message);

    return true;
    } // endif Hfile

  if (trace(1))
    htrc(" oflag=%p mode=%d handle=%d fn=%s\n", 
           oflag, mode, Hfile, filename);

  if (mode == MODE_INSERT) {
    /*******************************************************************/
    /* Position the cursor at end of file so ftell returns file size.  */
    /*******************************************************************/
    if (!(NewOff.Val = (longlong)lseek64(Hfile, 0LL, SEEK_END))) {
      snprintf(g->Message, sizeof(g->Message), MSG(FUNC_ERRNO), errno, "Seek");
      return true;
      } // endif

    if (trace(1))
      htrc("INSERT: NewOff=%lld\n", NewOff.Val);

  } else if (mode == MODE_WRITE) {
    if (id >= 0) {
      // New not sep index file. Write the header.
      memset(noff, 0, sizeof(noff));
      NewOff.v.Low = write(Hfile, &noff, sizeof(noff));
      } // endif id

    if (trace(1))
      htrc("WRITE: NewOff=%lld\n", NewOff.Val);

  } else if (mode == MODE_READ && id >= 0) {
    // Get offset from the header
    if (read(Hfile, noff, sizeof(noff)) != sizeof(noff)) {
      snprintf(g->Message, sizeof(g->Message), MSG(READ_ERROR), "Index file", strerror(errno));
      return true;
      } // endif read
      
   if (trace(1))
      htrc("noff[%d]=%lld\n", id, noff[id].Val);

    // Position the cursor at the offset of this index
    if (lseek64(Hfile, noff[id].Val, SEEK_SET) < 0) {
      snprintf(g->Message, sizeof(g->Message), "(XHUGE)lseek64: %s (%lld)", strerror(errno), noff[id].Val);
      printf("%s\n", g->Message);
//    snprintf(g->Message, sizeof(g->Message), MSG(FUNC_ERRNO), errno, "Hseek");
      return true;
      } // endif lseek64

  } // endif mode
#endif  // UNIX

  return false;
  } // end of Open

/***********************************************************************/
/*  Go to position in a huge file.                                     */
/***********************************************************************/
bool XHUGE::Seek(PGLOBAL g, int low, int high, int origin)
  {
#if defined(_WIN32)
  LONG  hi = high;
  DWORD rc = SetFilePointer(Hfile, low, &hi, origin);

  if (rc == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) {
    snprintf(g->Message, sizeof(g->Message), MSG(FUNC_ERROR), "Xseek");
    return true;
    } // endif

#else // UNIX
  off64_t pos = (off64_t)low
              + (off64_t)high * ((off64_t)0x100 * (off64_t)0x1000000);

  if (lseek64(Hfile, pos, origin) < 0) {
    snprintf(g->Message, sizeof(g->Message), MSG(ERROR_IN_LSK), errno);

    if (trace(1))
      htrc("lseek64 error %d\n", errno);

    return true;
    } // endif lseek64

  if (trace(1))
    htrc("Seek: low=%d high=%d\n", low, high);
#endif // UNIX

  return false;
  } // end of Seek

/***********************************************************************/
/*  Read from a huge index file.                                       */
/***********************************************************************/
bool XHUGE::Read(PGLOBAL g, void *buf, int n, int size)
  {
  bool rc = false;

#if defined(_WIN32)
  bool    brc;
  DWORD   nbr, count = (DWORD)(n * size);

  brc = ReadFile(Hfile, buf, count, &nbr, NULL);

  if (brc) {
    if (nbr != count) {
      strcpy(g->Message, MSG(EOF_INDEX_FILE));
      rc = true;
      } // endif nbr

  } else {
    char  buf[256];
    DWORD drc = GetLastError();

    FormatMessage(FORMAT_MESSAGE_FLAGS, NULL, drc, 0,
                  (LPTSTR)buf, sizeof(buf), NULL);
    snprintf(g->Message, sizeof(g->Message), MSG(READ_ERROR), "index file", buf);
    rc = true;
  } // endif brc
#else    // UNIX
  ssize_t count = (ssize_t)(n * size);

  if (trace(1))
    htrc("Hfile=%d n=%d size=%d count=%d\n", Hfile, n, size, count);

  if (read(Hfile, buf, count) != count) {
    snprintf(g->Message, sizeof(g->Message), MSG(READ_ERROR), "Index file", strerror(errno));

    if (trace(1))
      htrc("read error %d\n", errno);

    rc = true;
    } // endif nbr
#endif   // UNIX

  return rc;
  } // end of Read

/***********************************************************************/
/*  Write on a huge index file.                                        */
/***********************************************************************/
int XHUGE::Write(PGLOBAL g, void *buf, int n, int size, bool& rc)
  {
#if defined(_WIN32)
  bool    brc;
  DWORD   nbw, count = (DWORD)n * (DWORD) size;

  brc = WriteFile(Hfile, buf, count, &nbw, NULL);

  if (!brc) {
    char msg[256];
    DWORD drc = GetLastError();

    FormatMessage(FORMAT_MESSAGE_FLAGS, NULL, drc, 0,
                  (LPTSTR)msg, sizeof(msg), NULL);
    snprintf(g->Message, sizeof(g->Message), MSG(WRITING_ERROR), "index file", msg);
    rc = true;
    } // endif size

  return (int)nbw;
#else    // UNIX
  ssize_t nbw;
  size_t  count = (size_t)n * (size_t)size;

  nbw = write(Hfile, buf, count);

  if (nbw != (signed)count) {
    snprintf(g->Message, sizeof(g->Message), MSG(WRITING_ERROR),
                        "index file", strerror(errno));
    rc = true;
    } // endif nbw

  return (int)nbw;
#endif   // UNIX
  } // end of Write

/***********************************************************************/
/*  Update the file header and close the index file.                   */
/***********************************************************************/
void XHUGE::Close(char *fn, int id)
  {
  if (trace(1))
    htrc("XHUGE::Close: fn=%s id=%d NewOff=%lld\n", fn, id, NewOff.Val);

#if defined(_WIN32)
  if (id >= 0 && fn) {
    CloseFileHandle(Hfile);
    Hfile = CreateFile(fn, GENERIC_READ | GENERIC_WRITE, 0, NULL,
                       OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

    if (Hfile != INVALID_HANDLE_VALUE)
      if (SetFilePointer(Hfile, id * sizeof(IOFF), NULL, FILE_BEGIN)
              != INVALID_SET_FILE_POINTER) {
        DWORD nbw;

        WriteFile(Hfile, &NewOff, sizeof(IOFF), &nbw, NULL);
        } // endif SetFilePointer

    } // endif id
#else   // !_WIN32
  if (id >= 0 && fn) {
    if (Hfile != INVALID_HANDLE_VALUE) {
      if (lseek64(Hfile, id * sizeof(IOFF), SEEK_SET) >= 0) {
        ssize_t nbw = write(Hfile, &NewOff, sizeof(IOFF));
     
        if (nbw != (signed)sizeof(IOFF))
          htrc("Error writing index file header: %s\n", strerror(errno));
      
      } else
        htrc("(XHUGE::Close)lseek64: %s (%d)\n", strerror(errno), id);
   
    } else
      htrc("(XHUGE)error reopening %s: %s\n", fn, strerror(errno));

    } // endif id
#endif  // !_WIN32

  XLOAD::Close();
  } // end of Close

#if defined(XMAP)
/***********************************************************************/
/*  Don't know whether this is possible for huge files.                */
/***********************************************************************/
void *XHUGE::FileView(PGLOBAL g, char *)
  {
  strcpy(g->Message, MSG(NO_PART_MAP));
  return NULL;
  } // end of FileView
#endif   // XMAP

/* -------------------------- XXROW Class --------------------------- */

/***********************************************************************/
/*  XXROW Public Constructor.                                          */
/***********************************************************************/
XXROW::XXROW(PTDBDOS tdbp) : XXBASE(tdbp, false)
  {
  Srtd = true;
  Tdbp = tdbp;
  Valp = NULL;
  } // end of XXROW constructor

/***********************************************************************/
/*  XXROW Reset: re-initialize a Kindex block.                         */
/***********************************************************************/
void XXROW::Reset(void)
  {
#if defined(_DEBUG)
  assert(Tdbp->GetLink());                // This a join index
#endif   // _DEBUG
  } // end of Reset

/***********************************************************************/
/*  Init: Open and Initialize a Key Index.                             */
/***********************************************************************/
bool XXROW::Init(PGLOBAL g)
  {
  /*********************************************************************/
  /*  Table will be accessed through an index table.                   */
  /*  To_Link should not be NULL.                                      */
  /*********************************************************************/
  if (!Tdbp->GetLink() || Tbxp->GetKnum() != 1)
    return true;

  if ((*Tdbp->GetLink())->GetResultType() != TYPE_INT) {
    strcpy(g->Message, MSG(TYPE_MISMATCH));
    return true;
  } else
    Valp = (*Tdbp->GetLink())->GetValue();

  if ((Num_K = Tbxp->Cardinality(g)) < 0)
    return true;                   // Not a fixed file

  /*********************************************************************/
  /*  The entire table is indexed, no need to construct the index.     */
  /*********************************************************************/
  Cur_K = Num_K;
  return false;
  } // end of Init

/***********************************************************************/
/*  RANGE: Tell how many record exist in a given value range.          */
/***********************************************************************/
int XXROW::Range(PGLOBAL, int limit, bool incl)
  {
  int  n = Valp->GetIntValue();

  switch (limit) {
    case 1: n += ((incl) ? 0 : 1); break;
    case 2: n += ((incl) ? 1 : 0); break;
    default: n = 1;
    } // endswitch limit

  return n;
  } // end of Range

/***********************************************************************/
/*  XXROW: Fetch a physical or logical record.                         */
/***********************************************************************/
int XXROW::Fetch(PGLOBAL)
  {
  if (Num_K == 0)
    return -1;       // means end of file

  /*********************************************************************/
  /*  Look for a key equal to the link column of previous table,       */
  /*  and return its rank whithin the index table.                     */
  /*********************************************************************/
  Cur_K = FastFind();

  if (Cur_K >= Num_K)
    /*******************************************************************/
    /* Rank not whithin index table, signal record not found.          */
    /*******************************************************************/
    return -2;      // Means record not found

  /*********************************************************************/
  /*  If rank is equal to stored rank, record is already there.        */
  /*********************************************************************/
  if (Cur_K == Old_K)
    return -3;                   // Means record already there
  else
    Old_K = Cur_K;                // Store rank of newly read record

  return Cur_K;
  } // end of Fetch

/***********************************************************************/
/*  FastFind: Returns the index of matching record in a join.          */
/***********************************************************************/
int XXROW::FastFind(void)
  {
  int n = Valp->GetIntValue();

  if (n < 0)
    return (Op == OP_EQ) ? (-1) : 0;
  else if (n > Num_K)
    return Num_K;
  else
    return (Op == OP_GT) ? n : (n - 1);

  } // end of FastFind

/* ------------------------- KXYCOL Classes -------------------------- */

/***********************************************************************/
/*  KXYCOL public constructor.                                         */
/***********************************************************************/
KXYCOL::KXYCOL(PKXBASE kp) : To_Keys(Keys.Memp),
        To_Bkeys(Bkeys.Memp), Kof((CPINT&)Koff.Memp)
  {
  Next = NULL;
  Previous = NULL;
  Kxp = kp;
  Colp = NULL;
  IsSorted = false;
  Asc = true;
  Keys = Nmblk;
  Kblp = NULL;
  Bkeys = Nmblk;
  Blkp = NULL;
  Valp = NULL;
  Klen = 0;
  Kprec = 0;
  Type = TYPE_ERROR;
  Prefix = false;
  Koff = Nmblk;
  Val_K = 0;
  Ndf = 0;
  Mxs = 0;
  } // end of KXYCOL constructor

/***********************************************************************/
/*  KXYCOL Init: initialize and allocate storage.                      */
/*  Key length kln can be smaller than column length for CHAR columns. */
/***********************************************************************/
bool KXYCOL::Init(PGLOBAL g, PCOL colp, int n, bool sm, int kln)
  {
  int  len = colp->GetLength(), prec = colp->GetScale();
 bool un = colp->IsUnsigned();

  // Currently no indexing on NULL columns
  if (colp->IsNullable() && kln) {
    snprintf(g->Message, sizeof(g->Message), "Cannot index nullable column %s", colp->GetName());
    return true;
    } // endif nullable

  if (kln && len > kln && colp->GetResultType() == TYPE_STRING) {
    len = kln;
    Prefix = true;
    } // endif kln

  if (trace(1))
    htrc("KCOL(%p) Init: col=%s n=%d type=%d sm=%d\n",
         this, colp->GetName(), n, colp->GetResultType(), sm);

  // Allocate the Value object used when moving items
  Type = colp->GetResultType();

  if (!(Valp = AllocateValue(g, Type, len, prec, un)))
    return true;

  Klen = Valp->GetClen();
  Keys.Size = (size_t)n * (size_t)Klen;

  if (!PlgDBalloc(g, NULL, Keys)) {
    snprintf(g->Message, sizeof(g->Message), MSG(KEY_ALLOC_ERROR), Klen, n);
    return true;    // Error
    } // endif

  // Allocate the Valblock. The last parameter is to have rows filled
  // by blanks (if true) or keep the zero ending char (if false).
  // Currently we set it to true to be compatible with QRY blocks,
  // and the one before last is to enable length/type checking, set to
  // true if not a prefix key.
  Kblp = AllocValBlock(g, To_Keys, Type, n, len, prec, !Prefix, true, un);
  Asc = sm;                    // Sort mode: Asc=true  Desc=false
  Ndf = n;

  // Store this information to avoid sorting when already done
  if (Asc)
    IsSorted = colp->GetOpt() == 2;

//SetNulls(colp->IsNullable()); for when null columns will be indexable
  Colp = colp;
  return false;
  } // end of Init

#if defined(XMAP)
/***********************************************************************/
/*  KXYCOL MapInit: initialize and address storage.                    */
/*  Key length kln can be smaller than column length for CHAR columns. */
/***********************************************************************/
BYTE* KXYCOL::MapInit(PGLOBAL g, PCOL colp, int *n, BYTE *m)
  {
  int  len = colp->GetLength(), prec = colp->GetScale();
 bool un = colp->IsUnsigned();

  if (n[3] && colp->GetLength() > n[3]
           && colp->GetResultType() == TYPE_STRING) {
    len = n[3];
    Prefix = true;
    } // endif kln

  Type = colp->GetResultType();

  if (trace(1))
    htrc("MapInit(%p): colp=%p type=%d n=%d len=%d m=%p\n",
         this, colp, Type, n[0], len, m);

  // Allocate the Value object used when moving items
  Valp = AllocateValue(g, Type, len, prec, un);
  Klen = Valp->GetClen();

  if (n[2]) {
    Bkeys.Size = n[2] * Klen;
    Bkeys.Memp = m;
    Bkeys.Sub = true;

    // Allocate the Valblk containing initial block key values
    Blkp = AllocValBlock(g, To_Bkeys, Type, n[2], len, prec, true, true, un);
    } // endif nb

  Keys.Size = n[0] * Klen;
  Keys.Memp = m + Bkeys.Size;
  Keys.Sub = true;

  // Allocate the Valblock. Last two parameters are to have rows filled
  // by blanks (if true) or keep the zero ending char (if false).
  // Currently we set it to true to be compatible with QRY blocks,
  // and last one to enable type checking (no conversion).
  Kblp = AllocValBlock(g, To_Keys, Type, n[0], len, prec, !Prefix, true, un);

  if (n[1]) {
    Koff.Size = n[1] * sizeof(int);
    Koff.Memp = m + Bkeys.Size + Keys.Size;
    Koff.Sub = true;
    } // endif n[1]

  Ndf = n[0];
//IsSorted = colp->GetOpt() < 0;
  IsSorted = false;
  Colp = colp;
  return m + Bkeys.Size + Keys.Size + Koff.Size;
  } // end of MapInit
#endif // XMAP

/***********************************************************************/
/*  Allocate the offset block used by intermediate key columns.        */
/***********************************************************************/
int *KXYCOL::MakeOffset(PGLOBAL g, int n)
  {
  if (!Kof) {
    // Calculate the initial size of the offset
    Koff.Size = (n + 1) * sizeof(int);

    // Allocate the required memory
    if (!PlgDBalloc(g, NULL, Koff)) {
      strcpy(g->Message, MSG(KEY_ALLOC_ERR));
      return NULL;    // Error
     } // endif

  } else if (n) {
    // This is a reallocation call
    PlgDBrealloc(g, NULL, Koff, (n + 1) * sizeof(int));
  } else
    PlgDBfree(Koff);

  return (int*)Kof;
  } // end of MakeOffset

/***********************************************************************/
/*  Make a front end array of key values that are the first value of   */
/*  each blocks (of size n). This to reduce paging in FastFind.        */
/***********************************************************************/
bool KXYCOL::MakeBlockArray(PGLOBAL g, int nb, int size)
  {
  int i, k;

  // Calculate the size of the block array in the index
  Bkeys.Size = nb * Klen;

  // Allocate the required memory
  if (!PlgDBalloc(g, NULL, Bkeys)) {
    snprintf(g->Message, sizeof(g->Message), MSG(KEY_ALLOC_ERROR), Klen, nb);
    return true;    // Error
    } // endif

  // Allocate the Valblk used to contains initial block key values
  Blkp = AllocValBlock(g, To_Bkeys, Type, nb, Klen, Kprec);

  // Populate the array with values
  for (i = k = 0; i < nb; i++, k += size)
    Blkp->SetValue(Kblp, i, k);

  return false;
  } // end of MakeBlockArray

/***********************************************************************/
/*  KXYCOL SetValue: read column value for nth array element.           */
/***********************************************************************/
void KXYCOL::SetValue(PCOL colp, int i)
  {
#if defined(_DEBUG)
  assert (Kblp != NULL);
#endif

  Kblp->SetValue(colp->GetValue(), i);
  } // end of SetValue

/***********************************************************************/
/*  InitFind: initialize finding the rank of column value in index.    */
/***********************************************************************/
bool KXYCOL::InitFind(PGLOBAL g, PXOB xp)
  {
  if (xp->GetType() == TYPE_CONST) {
    if (Kxp->Nth)
      return true;

    Valp->SetValue_pval(xp->GetValue(), !Prefix);
  } else {
    xp->Reset();
    xp->Eval(g);
    Valp->SetValue_pval(xp->GetValue(), false);
  } // endif Type

  if (trace(2)) {
    char buf[32];

    htrc("KCOL InitFind: value=%s\n", Valp->GetCharString(buf));
    } // endif trace

  return false;
  } // end of InitFind

#if 0
/***********************************************************************/
/*  InitBinFind: initialize Value to the value pointed by vp.          */
/***********************************************************************/
void KXYCOL::InitBinFind(void *vp)
  {
  Valp->SetBinValue(vp);
  } // end of InitBinFind
#endif // 0

/***********************************************************************/
/*  KXYCOL FillValue: called by COLBLK::Eval when a column value is    */
/*  already in storage in the corresponding KXYCOL.                    */
/***********************************************************************/
void KXYCOL::FillValue(PVAL valp)
  {
  valp->SetValue_pvblk(Kblp, Val_K);

  // Set null when applicable (NIY)
//if (valp->GetNullable())
//  valp->SetNull(valp->IsZero());

  } // end of FillValue

/***********************************************************************/
/*  KXYCOL: Compare routine for one numeric value.                     */
/***********************************************************************/
int KXYCOL::Compare(int i1, int i2)
  {
  // Do the actual comparison between values.
  int k = Kblp->CompVal(i1, i2);

  if (trace(4))
    htrc("Compare done result=%d\n", k);

  return (Asc) ? k : -k;
  } // end of Compare

/***********************************************************************/
/*  KXYCOL: Compare the ith key to the stored Value.                   */
/***********************************************************************/
int KXYCOL::CompVal(int i)
  {
  // Do the actual comparison between numerical values.
  if (trace(4)) {
    int k = (int)Kblp->CompVal(Valp, (int)i);

    htrc("Compare done result=%d\n", k);
    return k;
  } else
    return Kblp->CompVal(Valp, i);

  } // end of CompVal

/***********************************************************************/
/*  KXYCOL: Compare the key to the stored block value.                 */
/***********************************************************************/
int KXYCOL::CompBval(int i)
  {
  // Do the actual comparison between key values.
  return Blkp->CompVal(Valp, i);
  } // end of CompBval

/***********************************************************************/
/*  KXYCOL ReAlloc: ReAlloc To_Data if it is not suballocated.         */
/***********************************************************************/
void KXYCOL::ReAlloc(PGLOBAL g, int n)
  {
  PlgDBrealloc(g, NULL, Keys, n * Klen);
  Kblp->ReAlloc(To_Keys, n);
  Ndf = n;
  } // end of ReAlloc

/***********************************************************************/
/*  KXYCOL FreeData: Free To_Keys if it is not suballocated.           */
/***********************************************************************/
void KXYCOL::FreeData(void)
  {
  PlgDBfree(Keys);
  Kblp = NULL;
  PlgDBfree(Bkeys);
  Blkp = NULL;
  PlgDBfree(Koff);
  Ndf = 0;
  } // end of FreeData

Messung V0.5 in Prozent
C=68 H=86 G=77

¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.43Angebot  (Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-10-08) ¤

*Eine klare Vorstellung vom Zielzustand






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=1126438
#Domains=1897691