/*********** File AM Vct C++ Program Source Code File (.CPP) ***********/
/* PROGRAM NAME: FILAMVCT                                              */
/* -------------                                                       */
/*  Version 2.4                                                        */
/*                                                                     */
/* COPYRIGHT:                                                          */
/* ----------                                                          */
/*  (C) Copyright to the author Olivier BERTRAND          2005-2013    */
/*                                                                     */
/* WHAT THIS PROGRAM DOES:                                             */
/* -----------------------                                             */
/*  This program are the VCT file access method classes.               */
/*  Added in version 2:      F                                          */
/*  - Split Vec format.                                                */
/*  - Partial delete.                                                  */
/*  - Use of tempfile for update.                                       */
/*                                                                     */
/***********************************************************************/

/***********************************************************************/
/*  Include relevant MariaDB header file.                  */
/***********************************************************************/
#include "my_global.h"
#if defined(WIN32)
#include <io.h>
#include <fcntl.h>
#if defined(__BORLANDC__)
#define __MFC_COMPAT__                   // To define min/max as macro
#endif   // __BORLAND__
//#include <windows.h>
#include <sys/stat.h>
#else   // !WIN32              F
#if defined(UNIX)
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
//#define strerror(X) _strerror(X)
#define NO_ERROR 0
#else   // !UNIX
#include <io.h>
#endif  // !UNIX
#include <fcntl.h>
#endif  // !WIN32

/***********************************************************************/
/*  Include application header files:                                  */
/*  global.h    is header containing all global declarations.          */
/*  plgdbsem.h  is header containing the DB application declarations.  */
/*  tabdos.h    is header containing the TABDOS class declarations.    */
/***********************************************************************/
#include "global.h"
#include "osutil.h"            // Unuseful for WIN32
#include "plgdbsem.h"
#include "valblk.h"
#include "filamfix.h"
#include "tabdos.h"
#include "tabvct.h"
#include "maputil.h"
#include "filamvct.h"

#ifndef INVALID_SET_FILE_POINTER
#define INVALID_SET_FILE_POINTER  ((DWORD)-1)
#endif

extern int num_read, num_there;                          // Statistics
static int num_write;
extern "C" int trace;

#if defined(UNIX)
// Add dummy strerror  (NGC)
char *strerror(int num);
#endif   // UNIX

/***********************************************************************/
/*  Header containing block info for not split VEC tables.             */
/*  Block and last values can be calculated from NumRec and Nrec.      */
/*  This is better than directly storing Block and Last because it     */
/*  make possible to use the same file with tables having a different  */
/*  block size value (Element -> Nrec)                                 */
/*  Note: can be in a separate file if header=1 or a true header (2)   */
/***********************************************************************/
typedef struct _vecheader {
//int Block;              /* The number of used blocks                 */
//int Last;               /* The number of used records in last block  */
  int MaxRec;             /* Max number of records (True vector format)*/
  int NumRec;             /* Number of valid records in the table      */
  } VECHEADER;

/***********************************************************************/
/*  Char VCT column blocks are right filled with blanks (blank = true) */
/*  Conversion of block values allowed conditionally for insert only.  */
/***********************************************************************/
PVBLK AllocValBlock(PGLOBAL, void *, int, int, int, int,
                    bool check = true, bool blank = true);

/* -------------------------- Class VCTFAM --------------------------- */

/***********************************************************************/
/*  Implementation of the VCTFAM class.                                */
/***********************************************************************/
VCTFAM::VCTFAM(PVCTDEF tdp) : FIXFAM((PDOSDEF)tdp)
  {
  Last = tdp->GetLast();
  MaxBlk = (tdp->GetEstimate() > 0) ?
          ((tdp->GetEstimate() - 1) / Nrec + 1) : 0;
  NewBlock = NULL;
  AddBlock = false;
  Split = false;

  if ((Header = (MaxBlk) ? tdp->Header : 0))
    Block = Last = -1;

  Bsize = Nrec;
  CurNum = Nrec - 1;
  Colfn = NULL;
  Tempat = NULL;
  Clens = NULL;
  Deplac = NULL;
  Isnum = NULL;
  Ncol = 0;
  } // end of VCTFAM standard constructor

VCTFAM::VCTFAM(PVCTFAM txfp) : FIXFAM(txfp)
  {
  MaxBlk = txfp->MaxBlk;
  NewBlock = NULL;
  AddBlock = false;
  Split = txfp->Split;
  Header = txfp->Header;
  Bsize = txfp->Bsize;
  Colfn = txfp->Colfn;
  Tempat = txfp->Tempat;
  Clens = txfp->Clens;
  Deplac = txfp->Deplac;
  Isnum = txfp->Isnum;
  Ncol = txfp->Ncol;
  } // end of VCTFAM copy constructor

/***********************************************************************/
/*  Reset read/write position values.                                  */
/***********************************************************************/
void VCTFAM::Reset(void)
  {
  FIXFAM::Reset();
  NewBlock = NULL;
  AddBlock = false;
  CurNum = Nrec - 1;
  } // end of Reset

/***********************************************************************/
/*  Get the Headlen, Block and Last info from the file header.         */
/***********************************************************************/
int VCTFAM::GetBlockInfo(PGLOBAL g)
  {
  char      filename[_MAX_PATH];
  int       h, k, n;
  VECHEADER vh;

  if (Header < 1 || Header > 3 || !MaxBlk) {
    sprintf(g->Message, "Invalid header value %d", Header);
    return -1;
  } else
    n = (Header == 1) ? (int)sizeof(VECHEADER) : 0;

  PlugSetPath(filename, To_File, Tdbp->GetPath());

  if (Header == 2)
    strcat(PlugRemoveType(filename, filename), ".blk");

  if ((h = global_open(g, MSGID_CANNOT_OPEN, filename, O_RDONLY)) == -1
      || !_filelength(h)) {
    // Consider this is a void table
    Last = Nrec; 
    Block = 0;

    if (h != -1)
      close(h);

    return n;
  } else if (Header == 3)
    k = lseek(h, -(int)sizeof(VECHEADER), SEEK_END);
    
  if ((k = read(h, &vh, sizeof(vh))) != sizeof(vh)) {
    sprintf(g->Message, "Error reading header file %s", filename);
    n = -1;
  } else if (MaxBlk * Nrec != vh.MaxRec) {
    sprintf(g->Message, "MaxRec=%d doesn't match MaxBlk=%d Nrec=%d",
                        vh.MaxRec, MaxBlk, Nrec);
    n = -1;
  } else {     
    Block = (vh.NumRec > 0) ? (vh.NumRec + Nrec - 1) / Nrec : 0;
    Last  = (vh.NumRec + Nrec - 1) % Nrec + 1;
  } // endif s

  close(h);
  return n;
  } // end of GetBlockInfo

/***********************************************************************/
/*  Get the Headlen, Block and Last info from the file header.         */
/***********************************************************************/
bool VCTFAM::SetBlockInfo(PGLOBAL g)
  {
  char      filename[_MAX_PATH];
  bool      rc = false;
  int       k;
  size_t    n;
  VECHEADER vh;
  FILE     *s;

  PlugSetPath(filename, To_File, Tdbp->GetPath());

  if (Header != 2) {
    if (Stream) {
      s = Stream;

      if (Header == 1)
        k = fseek(s, 0, SEEK_SET);

    } else
      s= global_fopen(g, MSGID_CANNOT_OPEN, filename, "r+b");

  } else {      // Header == 2
    strcat(PlugRemoveType(filename, filename), ".blk");
    s= global_fopen(g, MSGID_CANNOT_OPEN, filename, "wb");
  } // endif Header

  if (!s) {
    sprintf(g->Message, "Error opening header file %s", filename);
    return true;
  } else if (Header == 3)
    k = fseek(s, -(int)sizeof(VECHEADER), SEEK_END);

  vh.MaxRec = MaxBlk * Bsize;
  vh.NumRec = (Block - 1) * Nrec + Last;

  if ((n = fwrite(&vh, sizeof(vh), 1, s)) != 1) {
    sprintf(g->Message, "Error writing header file %s", filename);
    rc = true;
    } // endif fread

  if (Header == 2 || !Stream)
    fclose(s);

  return rc;
  } // end of SetBlockInfo

/***********************************************************************/
/*  Use BlockTest to reduce the table estimated size.                  */
/***********************************************************************/
int VCTFAM::MaxBlkSize(PGLOBAL g, int s)
  {
  int  savcur = CurBlk;
  int size;

  // Roughly estimate the table size as the sum of blocks
  // that can contain good rows
  for (size = 0, CurBlk = 0; CurBlk < Block; CurBlk++)
    size += (CurBlk == Block - 1) ? Last : Nrec;

  CurBlk = savcur;
  return size;
  } // end of MaxBlkSize

/***********************************************************************/
/*  VCT Cardinality: returns table cardinality in number of rows.      */
/*  This function can be called with a null argument to test the       */
/*  availability of Cardinality implementation (1 yes, 0 no).          */
/***********************************************************************/
int VCTFAM::Cardinality(PGLOBAL g)
  {
  if (!g)
    return 1;

  if (Block < 0)
    if (Split) {
      // Separate column files and no pre setting of Block and Last
      // This allows to see a table modified externally, but Block
      // and Last must be set from the file cardinality.
      // Only happens when called by sub classes.
      char    filename[_MAX_PATH];
      PSZ     savfn = To_File;
      int    len, clen, card = -1;
      PCOLDEF cdp = Tdbp->GetDef()->GetCols();
 
      if (!Colfn) {
        // Prepare the column file name pattern
        Colfn = (char*)PlugSubAlloc(g, NULL, _MAX_PATH);
        Ncol = ((VCTDEF*)Tdbp->GetDef())->MakeFnPattern(Colfn);
        } // endif Colfn
 
      // Use the first column file to calculate the cardinality
      clen = cdp->GetClen();
      sprintf(filename, Colfn, 1);
      To_File = filename;
      len = GetFileLength(g);
      To_File = savfn;
 
      if (len >= 0) {
        if (!(len % clen))
          card = len / clen;           // Fixed length file
        else
          sprintf(g->Message, MSG(NOT_FIXED_LEN), To_File, len, clen);

      if (trace)
        htrc(" Computed max_K=%d Filen=%d Clen=%d\n", card, len, clen);

      } else
        card = 0;
  
      // Set number of blocks for later use
      Block = (card > 0) ? (card + Nrec - 1) / Nrec : 0;
      Last = (card + Nrec - 1) % Nrec + 1;
      return card;
    } else {
      // Vector table having Block and Last info in a Header (file)
      if ((Headlen = GetBlockInfo(g)) < 0)
        return -1;             // Error

    } // endif split

  return (int)((Block - 1) * Nrec + Last);
  } // end of Cardinality

/***********************************************************************/
/*  GetRowID: return the RowID of last read record.                    */
/***********************************************************************/
int VCTFAM::GetRowID(void)
  {
  return 1 + ((CurBlk < Block) ? CurNum + Nrec * CurBlk
                                : (Block - 1) * Nrec + Last);
  } // end of GetRowID

/***********************************************************************/
/*  VCT Create an empty file for Vector formatted tables.              */
/***********************************************************************/
bool VCTFAM::MakeEmptyFile(PGLOBAL g, char *fn)
  {
  // Vector formatted file: this will create an empty file of the
  // required length if it does not exists yet.
  char filename[_MAX_PATH], c = 0;
  int  h, n;

  PlugSetPath(filename, fn, Tdbp->GetPath());
#if defined(WIN32)
  h= global_open(g, MSGID_OPEN_EMPTY_FILE, filename, _O_CREAT | _O_WRONLY, S_IREAD | S_IWRITE);
#else   // !WIN32
  h= global_open(g, MSGID_OPEN_EMPTY_FILE, filename,  O_CREAT |  O_WRONLY, S_IREAD | S_IWRITE);
#endif  // !WIN32

  if (h == -1)
    return true;

  n = (Header == 1 || Header == 3) ? sizeof(VECHEADER) : 0;

  if (lseek(h, n + MaxBlk * Nrec * Lrecl - 1, SEEK_SET) == -1) {
    sprintf(g->Message, MSG(MAKE_EMPTY_FILE), To_File, strerror(errno));
    close(h);
    return true;
    } // endif h

  write(h, &c, 1);     // This actually fills the empty file
  close(h);
  return false;
  } // end of MakeEmptyFile

/***********************************************************************/
/*  VCT Access Method opening routine.                                 */
/*  New method now that this routine is called recursively (last table */
/*  first in reverse order): index blocks are immediately linked to    */
/*  join block of next table if it exists or else are discarted.       */
/***********************************************************************/
bool VCTFAM::OpenTableFile(PGLOBAL g)
  {
  char    opmode[4], filename[_MAX_PATH];
  MODE    mode = Tdbp->GetMode();
  PDBUSER dbuserp = PlgGetUser(g);

  /*********************************************************************/
  /*  Update block info if necessary.                                  */
  /*********************************************************************/
  if (Block < 0)
    if ((Headlen = GetBlockInfo(g)) < 0)
      return true;

  /*********************************************************************/
  /*  Open according to input/output mode required.                    */
  /*********************************************************************/
  switch (mode) {
    case MODE_READ:
      strcpy(opmode, "rb");
      break;
    case MODE_DELETE:
      if (!Tdbp->GetNext()) {
        // Store the number of deleted lines
        DelRows = Cardinality(g);

        // This will delete the whole file
        strcpy(opmode, "wb");
        break;
        } // endif

      // Selective delete, pass thru
    case MODE_UPDATE:
      UseTemp = Tdbp->IsUsingTemp(g);
      strcpy(opmode, (UseTemp) ? "rb" : "r+b");
      break;
    case MODE_INSERT:
      if (MaxBlk) {
        if (!Block)
          if (MakeEmptyFile(g, To_File))
            return true;

        strcpy(opmode, "r+b");   // Required to update empty blocks
      } else if (Last == Nrec)
        strcpy(opmode, "ab");
      else
        strcpy(opmode, "r+b");   // Required to update the last block

      break;
    default:
      sprintf(g->Message, MSG(BAD_OPEN_MODE), mode);
      return true;
    } // endswitch Mode

  /*********************************************************************/
  /*  Use conventionnal input/output functions.                        */
  /*********************************************************************/
  PlugSetPath(filename, To_File, Tdbp->GetPath());

  if (!(Stream = PlugOpenFile(g, filename, opmode))) {
    if (trace)
      htrc("%s\n", g->Message);

    return (mode == MODE_READ && errno == ENOENT)
            ? PushWarning(g, Tdbp) : true;
    } // endif Stream

  if (trace)
    htrc("File %s is open in mode %s\n", filename, opmode);

  To_Fb = dbuserp->Openlist;     // Keep track of File block

  if (!strcmp(opmode, "wb"))
    // This will stop the process by
    // causing GetProgMax to return 0.
    return ResetTableSize(g, 0, Nrec);

  num_read = num_there = num_write = 0;
  
  //  Allocate the table and column block buffer
  return AllocateBuffer(g);
  } // end of OpenTableFile

/***********************************************************************/
/*  Allocate the block buffers for columns used in the query.          */
/***********************************************************************/
bool VCTFAM::AllocateBuffer(PGLOBAL g)
  {
  MODE    mode = Tdbp->GetMode();
  PDOSDEF defp = (PDOSDEF)Tdbp->GetDef();
  PCOLDEF cdp;
  PVCTCOL cp = (PVCTCOL)Tdbp->GetColumns();

  if (mode == MODE_INSERT) {
    bool chk = PlgGetUser(g)->Check & CHK_TYPE;

    NewBlock = (char*)PlugSubAlloc(g, NULL, Blksize);

    for (cdp = defp->GetCols(); cdp; cdp = cdp->GetNext())
      memset(NewBlock + Nrec * cdp->GetPoff(),
            (IsTypeNum(cdp->GetType()) ? 0 : ' '),
                        Nrec * cdp->GetClen());

    for (; cp; cp = (PVCTCOL)cp->Next)
      cp->Blk = AllocValBlock(g, NewBlock + Nrec * cp->Deplac,
                              cp->Buf_Type, Nrec, cp->Format.Length,
                                                  cp->Format.Prec, chk);

    return InitInsert(g);    // Initialize inserting
  } else {
    if (UseTemp || mode == MODE_DELETE) {
      // Allocate all that is needed to move lines
      int i = 0, n = (MaxBlk) ? MaxBlk : 1;

      if (!Ncol)
        for (cdp = defp->GetCols(); cdp; cdp = cdp->GetNext())
          Ncol++;

      Clens = (int*)PlugSubAlloc(g, NULL, Ncol * sizeof(int));
      Deplac = (int*)PlugSubAlloc(g, NULL, Ncol * sizeof(int));
      Isnum = (bool*)PlugSubAlloc(g, NULL, Ncol * sizeof(bool));

      for (cdp = defp->GetCols(); cdp; i++, cdp = cdp->GetNext()) {
        Clens[i] = cdp->GetClen();
        Deplac[i] = Headlen + cdp->GetPoff() * n * Nrec;
        Isnum[i] = IsTypeNum(cdp->GetType());
        Buflen = max(Buflen, cdp->GetClen());
        } // endfor cdp

      if (!UseTemp || MaxBlk) {
        Buflen *= Nrec;
        To_Buf = (char*)PlugSubAlloc(g, NULL, Buflen);
      } else
        NewBlock = (char*)PlugSubAlloc(g, NULL, Blksize);

      } // endif mode

    for (; cp; cp = (PVCTCOL)cp->Next)
      if (!cp->IsSpecial())            // Not a pseudo column
        cp->Blk = AllocValBlock(g, NULL, cp->Buf_Type, Nrec,
                                cp->Format.Length, cp->Format.Prec);

  } //endif mode

  return false;
  } // end of AllocateBuffer

/***********************************************************************/
/*  Do initial action when inserting.                                  */
/***********************************************************************/
bool VCTFAM::InitInsert(PGLOBAL g)
  {
  // We come here in MODE_INSERT only
  if (Last == Nrec) {
    CurBlk = Block;
    CurNum = 0;
    AddBlock = !MaxBlk;
  } else {
    int     rc;
    PVCTCOL cp = (PVCTCOL)Tdbp->GetColumns();

    // The starting point must be at the end of file as for append.
    CurBlk = Block - 1;
    CurNum = Last;

    //  Prepare error return
    if (g->jump_level == MAX_JUMP) {
      strcpy(g->Message, MSG(TOO_MANY_JUMPS));
      return true;
      } // endif

    if ((rc = setjmp(g->jumper[++g->jump_level])) != 0) {
      g->jump_level--;
      return true;
      } // endif

    // Last block must be updated by new values
    for (; cp; cp = (PVCTCOL)cp->Next)
      cp->ReadBlock(g);

    g->jump_level--;
  } // endif Last

  // We are not currently using a temporary file for Insert
  T_Stream = Stream;
  return false;
  } // end of InitInsert

/***********************************************************************/
/*  ReadBuffer: Read one line for a VCT file.                          */
/***********************************************************************/
int VCTFAM::ReadBuffer(PGLOBAL g)
  {
  int  rc = RC_OK;
  MODE mode = Tdbp->GetMode();

  if (Placed)
    Placed = false;
  else if ((++CurNum) >= ((CurBlk < Block - 1) ? Nrec : Last)) {
    /*******************************************************************/
    /*  New block.                                                     */
    /*******************************************************************/
    CurNum = 0;

    if (++CurBlk == Block)
      return RC_EF;                        // End of file

    num_there++;
    } // endif CurNum

  if (OldBlk != CurBlk) {
    if (mode == MODE_UPDATE) {
      /*****************************************************************/
      /*  Flush the eventually modified column buffers in old blocks   */
      /*  and read the blocks to modify attached to Set columns.       */
      /*****************************************************************/
      if (MoveLines(g))               // For VECFAM
        return RC_FX;

      for (PVCTCOL colp = (PVCTCOL)Tdbp->GetSetCols();
                   colp; colp = (PVCTCOL)colp->Next) {
        colp->WriteBlock(g);
        colp->ReadBlock(g);
        } // endfor colp

      } // endif mode

    OldBlk = CurBlk;             // Last block actually read
    } // endif oldblk

  if (trace)
    htrc(" Read: CurNum=%d CurBlk=%d rc=%d\n", CurNum, CurBlk, RC_OK);

  return rc;
  } // end of ReadBuffer

/***********************************************************************/
/*  Data Base write routine for VCT access method.                     */
/***********************************************************************/
int VCTFAM::WriteBuffer(PGLOBAL g)
  {
  if (trace)
    htrc("VCT WriteBuffer: R%d Mode=%d CurNum=%d CurBlk=%d\n",
          Tdbp->GetTdb_No(), Tdbp->GetMode(), CurNum, CurBlk);

  if (Tdbp->GetMode() == MODE_UPDATE) {
    // Mode Update is done in ReadDB, we just initialize it here
    if (!T_Stream) {
      if (UseTemp) {
        if (OpenTempFile(g))
          return RC_FX;

        // Most of the time, not all table columns are updated.
        // This why we must completely pre-fill the temporary file.
        Fpos = (MaxBlk) ? (Block - 1) * Nrec + Last
                        : Block * Nrec;    // To write last lock

        if (MoveIntermediateLines(g))
          return RC_FX;

      } else
        T_Stream = Stream;

      } // endif T_Stream

  } else {
    // Mode Insert
    if (MaxBlk && CurBlk == MaxBlk) {
      strcpy(g->Message, MSG(TRUNC_BY_ESTIM));
      return RC_EF;       // Too many lines for vector formatted table
      } // endif MaxBlk

    if (Closing || ++CurNum == Nrec) {
      PVCTCOL cp = (PVCTCOL)Tdbp->GetColumns();

      if (!AddBlock) {
        // Write back the updated last block values
        for (; cp; cp = (PVCTCOL)cp->Next)
          cp->WriteBlock(g);

        if (!Closing && !MaxBlk) {
          // For VCT tables, future blocks must be added
          char filename[_MAX_PATH];

          // Close the file and reopen it in mode Insert
          fclose(Stream);
          PlugSetPath(filename, To_File, Tdbp->GetPath());

          if (!(Stream= global_fopen(g, MSGID_OPEN_MODE_STRERROR, filename, "ab"))) {
            Closing = true;          // Tell CloseDB of error
            return RC_FX;
            } // endif Stream

          AddBlock = true;
          } // endif Closing

      } else {
        // Here we must add a new block to the file
        if (Closing)
          // Reset the overwritten columns for last block extra records
          for (; cp; cp = (PVCTCOL)cp->Next)
            memset(NewBlock + Nrec * cp->Deplac + Last * cp->Clen,
                   (cp->Buf_Type == TYPE_STRING) ? ' ' : '\0',
                   (Nrec - Last) * cp->Clen);

        if ((size_t)Nrec !=
             fwrite(NewBlock, (size_t)Lrecl, (size_t)Nrec, Stream)) {
          sprintf(g->Message, MSG(WRITE_STRERROR), To_File, strerror(errno));
          return RC_FX;
          } // endif

      } // endif AddBlock

      if (!Closing) {
        CurBlk++;
        CurNum = 0;
        } // endif Closing

      } // endif Closing || CurNum

  } // endif Mode

  return RC_OK;
  } // end of WriteBuffer

/***********************************************************************/
/*  Data Base delete line routine for VCT access method.               */
/*  Note: lines are moved directly in the files (ooops...)             */
/*  Using temp file depends on the Check setting, false by default.    */
/***********************************************************************/
int VCTFAM::DeleteRecords(PGLOBAL g, int irc)
  {
  bool eof = false;
  
  if (trace)
    htrc("VCT DeleteDB: rc=%d UseTemp=%d Fpos=%d Tpos=%d Spos=%d\n",
          irc, UseTemp, Fpos, Tpos, Spos);

  if (irc != RC_OK) {
    /*******************************************************************/
    /*  EOF: position Fpos at the end-of-file position.                */
    /*******************************************************************/
    Fpos = (Block - 1) * Nrec + Last;
    
    if (trace)
      htrc("Fpos placed at file end=%d\n", Fpos);

    eof = UseTemp && !MaxBlk;
  } else     // Fpos is the Deleted line position
    Fpos = CurBlk * Nrec + CurNum;

  if (Tpos == Spos) {
    if (UseTemp) {
      /*****************************************************************/
      /*  Open the temporary file, Spos is at the beginning of file.   */
      /*****************************************************************/
      if (OpenTempFile(g))
        return RC_FX;

    } else {
      /*****************************************************************/
      /*  First line to delete. Move of eventual preceeding lines is   */
      /*  not required here, just the setting of future Spos and Tpos. */
      /*****************************************************************/
      T_Stream = Stream;
      Spos = Tpos = Fpos;
    } // endif UseTemp

    } // endif Tpos == Spos

  /*********************************************************************/
  /*  Move any intermediate lines.                                     */
  /*********************************************************************/
  if (MoveIntermediateLines(g, &eof))
    return RC_FX;

  if (irc == RC_OK) {
    /*******************************************************************/
    /*  Reposition the file pointer and set Spos.                      */
    /*******************************************************************/
#ifdef _DEBUG
    assert(Spos == Fpos);
#endif
    Spos++;          // New start position is on next line

    if (trace)
      htrc("after: Tpos=%d Spos=%d\n", Tpos, Spos);

  } else {
    /*******************************************************************/
    /*  Last call after EOF has been reached.                          */
    /*  Update the Block and Last values.                              */
    /*******************************************************************/
    Block = (Tpos > 0) ? (Tpos + Nrec - 1) / Nrec : 0;
    Last = (Tpos + Nrec - 1) % Nrec + 1;

    if (!UseTemp) {   // The UseTemp case is treated in CloseTableFile
      if (!MaxBlk) {
        /***************************************************************/
        /*  Because the chsize functionality is only accessible with a */
        /*  system call we must close the file and reopen it with the  */
        /*  open function (_fopen for MS ??) this is still to be       */
        /*  checked for compatibility with Text files and other OS's.  */
        /***************************************************************/
        char filename[_MAX_PATH];
        int rc, h;

        rc = CleanUnusedSpace(g);           // Clean last block
        rc = PlugCloseFile(g, To_Fb);
        Stream = NULL;                      // For SetBlockInfo
        PlugSetPath(filename, To_File, Tdbp->GetPath());

        if ((h= global_open(g, MSGID_OPEN_STRERROR, filename, O_WRONLY)) <= 0)
          return RC_FX;

        /***************************************************************/
        /*  Remove extra blocks.                                       */
        /***************************************************************/
#if defined(UNIX)
        if (ftruncate(h, (off_t)(Headlen + Block * Blksize))) {
          sprintf(g->Message, MSG(TRUNCATE_ERROR), strerror(errno));
          close(h);
          return RC_FX;
          } // endif
#else
        if (chsize(h, Headlen + Block * Blksize)) {
          sprintf(g->Message, MSG(CHSIZE_ERROR), strerror(errno));
          close(h);
          return RC_FX;
          } // endif
#endif

        close(h);

        if (trace)
          htrc("done, h=%d irc=%d\n", h, irc);

      } else
        // Clean the unused space in the file, this is required when
        // inserting again with a partial column list.
        if (CleanUnusedSpace(g))
          return RC_FX;

      if (ResetTableSize(g, Block, Last))
        return RC_FX;

      } // endif UseTemp

  } // endif irc

  return RC_OK;                                      // All is correct
  } // end of DeleteRecords

/***********************************************************************/
/*  Open a temporary file used while updating or deleting.             */
/***********************************************************************/
bool VCTFAM::OpenTempFile(PGLOBAL g)
  {
  char *opmode, tempname[_MAX_PATH];
  bool  rc = false;

  /*********************************************************************/
  /*  Open the temporary file, Spos is at the beginning of file.       */
  /*********************************************************************/
  PlugSetPath(tempname, To_File, Tdbp->GetPath());
  strcat(PlugRemoveType(tempname, tempname), ".t");

  if (MaxBlk) {
    if (MakeEmptyFile(g, tempname))
      return true;

    opmode = "r+b";
  } else
    opmode = "wb";

  if (!(T_Stream = PlugOpenFile(g, tempname, opmode))) {
    if (trace)
      htrc("%s\n", g->Message);

    rc = true;
  } else
    To_Fbt = PlgGetUser(g)->Openlist;

  return rc;
  } // end of OpenTempFile

/***********************************************************************/
/*  Move intermediate deleted or updated lines.                        */
/***********************************************************************/
bool VCTFAM::MoveIntermediateLines(PGLOBAL g, bool *b)
  {
  int    i, dep, off;
  int   n;
  bool   eof = (b) ? *b : false;
  size_t req, len;

  for (n = Fpos - Spos; n > 0 || eof; n -= req) {
    /*******************************************************************/
    /*  Non consecutive line to delete. Move intermediate lines.       */
    /*******************************************************************/
    if (!MaxBlk)
      req = (size_t)min(n, Nrec - max(Spos % Nrec, Tpos % Nrec));
    else
      req = (size_t)min(n, Nrec);

    if (req) for (i = 0; i < Ncol; i++) {
      if (MaxBlk) {
        dep = Deplac[i];
        off = Spos * Clens[i];
      } else {
        if (UseTemp)
          To_Buf = NewBlock + Deplac[i] + (Tpos % Nrec) * Clens[i];

        dep = Deplac[i] + (Spos / Nrec) * Blksize;
        off = (Spos % Nrec) * Clens[i];
      } // endif MaxBlk

      if (fseek(Stream, dep + off, SEEK_SET)) {
        sprintf(g->Message, MSG(READ_SEEK_ERROR), strerror(errno));
        return true;
        } // endif

      len = fread(To_Buf, Clens[i], req, Stream);

      if (trace)
        htrc("after read req=%d len=%d\n", req, len);

      if (len != req) {
        sprintf(g->Message, MSG(DEL_READ_ERROR), (int) req, (int) len);
        return true;
        } // endif len

      if (!UseTemp || MaxBlk) {
        if (MaxBlk) {
          dep = Deplac[i];
          off = Tpos * Clens[i];
        } else {
          dep = Deplac[i] + (Tpos / Nrec) * Blksize;
          off = (Tpos % Nrec) * Clens[i];
        } // endif MaxBlk

        if (fseek(T_Stream, dep + off, SEEK_SET)) {
          sprintf(g->Message, MSG(WRITE_SEEK_ERR), strerror(errno));
          return true;
          } // endif

        if ((len = fwrite(To_Buf, Clens[i], req, T_Stream)) != req) {
          sprintf(g->Message, MSG(DEL_WRITE_ERROR), strerror(errno));
          return true;
          } // endif

        } // endif UseTemp

      if (trace)
        htrc("after write pos=%d\n", ftell(Stream));

      } // endfor i

    Tpos += (int)req;
    Spos += (int)req;

    if (UseTemp && !MaxBlk && (Tpos % Nrec == 0 || (eof && Spos == Fpos))) {
      // Write the full or last block to the temporary file
      if ((dep = Nrec - (Tpos % Nrec)) < Nrec)
        // Clean the last block in case of future insert,
        // must be done here because T_Stream was open in write only.
        for (i = 0; i < Ncol; i++) {
          To_Buf = NewBlock + Deplac[i] + (Tpos % Nrec) * Clens[i];
          memset(To_Buf, (Isnum[i]) ? 0 : ' ', dep * Clens[i]);
          } // endfor i

      // Write a new block in the temporary file
      len = (size_t)Blksize;

      if (fwrite(NewBlock, 1, len, T_Stream) != len) {
        sprintf(g->Message, MSG(DEL_WRITE_ERROR), strerror(errno));
        return true;
        } // endif

      if (Spos == Fpos)
        eof = false;

      } // endif UseTemp

    if (trace)
      htrc("loop: Tpos=%d Spos=%d\n", Tpos, Spos);

    } // endfor n

  return false;
  } // end of MoveIntermediateLines

/***********************************************************************/
/*  Clean deleted space in a VCT or Vec table file.                    */
/***********************************************************************/
bool VCTFAM::CleanUnusedSpace(PGLOBAL g)
  {
  int     i, dep;
  int    n;
  size_t  req, len;

  if (!MaxBlk) {
    /*******************************************************************/
    /*  Clean last block of the VCT table file.                        */
    /*******************************************************************/
    assert(!UseTemp);

    if (!(n = Nrec - Last))
      return false;

    dep = (Block - 1) * Blksize;
    req = (size_t)n;

    for (i = 0; i < Ncol; i++) {
      memset(To_Buf, (Isnum[i]) ? 0 : ' ', n * Clens[i]);

      if (fseek(Stream, dep + Deplac[i] + Last * Clens[i], SEEK_SET)) {
        sprintf(g->Message, MSG(WRITE_SEEK_ERR), strerror(errno));
        return true;
        } // endif

      if ((len = fwrite(To_Buf, Clens[i], req, Stream)) != req) {
        sprintf(g->Message, MSG(DEL_WRITE_ERROR), strerror(errno));
        return true;
        } // endif

      } // endfor i

  } else for (n = Fpos - Tpos; n > 0; n -= req) {
    /*******************************************************************/
    /*  Fill VEC file remaining lines with 0's.                        */
    /*  Note: this seems to work even column blocks have been made     */
    /*  with Blanks = true. Perhaps should it be set to false for VEC. */
    /*******************************************************************/
    req = (size_t)min(n, Nrec);
    memset(To_Buf, 0, Buflen);

    for (i = 0; i < Ncol; i++) {
      if (fseek(T_Stream, Deplac[i] + Tpos * Clens[i], SEEK_SET)) {
        sprintf(g->Message, MSG(WRITE_SEEK_ERR), strerror(errno));
        return true;
        } // endif

      if ((len = fwrite(To_Buf, Clens[i], req, T_Stream)) != req) {
        sprintf(g->Message, MSG(DEL_WRITE_ERROR), strerror(errno));
        return true;
        } // endif

      } // endfor i

    Tpos += (int)req;
    } // endfor n

  return false;
  } // end of CleanUnusedSpace

/***********************************************************************/
/*  Data Base close routine for VCT access method.                     */
/***********************************************************************/
void VCTFAM::CloseTableFile(PGLOBAL g)
  {
  int  rc = 0, wrc = RC_OK;
  MODE mode = Tdbp->GetMode();

  if (mode == MODE_INSERT) {
    if (Closing)
      wrc = RC_FX;                  // Last write was in error
    else
      if (CurNum) {
        // Some more inserted lines remain to be written
        Last = CurNum;
        Block = CurBlk + 1;
        Closing = true;
        wrc = WriteBuffer(g);
      } else {
        Last = Nrec;
        Block = CurBlk;
        wrc = RC_OK;
      } // endif CurNum

    if (wrc != RC_FX) {
      rc = ResetTableSize(g, Block, Last);
    } else if (AddBlock) {
      // Last block was not written
      rc = ResetTableSize(g, CurBlk, Nrec);
      longjmp(g->jumper[g->jump_level], 44);
    } // endif

  } else if (mode == MODE_UPDATE) {
    // Write back to file any pending modifications
    for (PVCTCOL colp = (PVCTCOL)((PTDBVCT)Tdbp)->To_SetCols;
                 colp; colp = (PVCTCOL)colp->Next)
      colp->WriteBlock(g);

    if (UseTemp && T_Stream) {
      rc = RenameTempFile(g);

      if (Header) {
        // Header must be set because it was not set in temp file
        Stream = T_Stream = NULL;      // For SetBlockInfo
        rc = SetBlockInfo(g);
        } // endif Header

      } // endif UseTemp

  } else if (mode == MODE_DELETE && UseTemp && T_Stream) {
    if (MaxBlk)
      rc = CleanUnusedSpace(g);

    if ((rc = RenameTempFile(g)) != RC_FX) {
      Stream = T_Stream = NULL;      // For SetBlockInfo
      rc = ResetTableSize(g, Block, Last);
      } // endif rc

  } // endif's mode

  if (!(UseTemp && T_Stream))
    rc = PlugCloseFile(g, To_Fb);

  if (trace)
    htrc("VCT CloseTableFile: closing %s wrc=%d rc=%d\n",
          To_File, wrc, rc);

  Stream = NULL;
  } // end of CloseTableFile

/***********************************************************************/
/*  Data Base close routine for VCT access method.                     */
/***********************************************************************/
bool VCTFAM::ResetTableSize(PGLOBAL g, int block, int last)
  {
  bool rc = false;

  // Set Block and Last values for TDBVCT::MakeBlockValues
  Block = block;
  Last = last;

  if (!Split) {
    if (!Header) {
      // Update catalog values for Block and Last
      PVCTDEF defp = (PVCTDEF)Tdbp->GetDef();
      LPCSTR  name = Tdbp->GetName();
      PCATLG  cat = PlgGetCatalog(g);
  
      defp->SetBlock(Block);
      defp->SetLast(Last);
  
      if (!cat->SetIntCatInfo("Blocks", Block) ||
          !cat->SetIntCatInfo("Last", Last)) {
        sprintf(g->Message, MSG(UPDATE_ERROR), "Header");
        rc = true;
        } // endif

    } else
      rc = SetBlockInfo(g);

    } // endif Split

  Tdbp->ResetSize();
  return rc;
  } // end of ResetTableSize

/***********************************************************************/
/*  Rewind routine for VCT access method.                              */
/***********************************************************************/
void VCTFAM::Rewind(void)
  {
  // In mode update we need to read Set Column blocks
  if (Tdbp->GetMode() == MODE_UPDATE)
    OldBlk = -1;

  // Initialize so block optimization is called for 1st block
  CurBlk = -1;
  CurNum = Nrec - 1;
//rewind(Stream);             will be placed by fseek
  } // end of Rewind

/***********************************************************************/
/*  ReadBlock: Read column values from current block.                  */
/***********************************************************************/
bool VCTFAM::ReadBlock(PGLOBAL g, PVCTCOL colp)
  {
  int     len;
  size_t  n;

  /*********************************************************************/
  /*  Calculate the offset and size of the block to read.              */
  /*********************************************************************/
  if (MaxBlk)                                 // True vector format
    len = Headlen + Nrec * (colp->Deplac * MaxBlk + colp->Clen * CurBlk);
  else                                        // Blocked vector format
    len = Nrec * (colp->Deplac + Lrecl * CurBlk);

  if (trace)
    htrc("len=%d Nrec=%d Deplac=%d Lrecl=%d CurBlk=%d maxblk=%d\n",
          len, Nrec, colp->Deplac, Lrecl, CurBlk, MaxBlk);

  if (fseek(Stream, len, SEEK_SET)) {
    sprintf(g->Message, MSG(FSEEK_ERROR), strerror(errno));
    return true;
    } // endif

  n = fread(colp->Blk->GetValPointer(), (size_t)colp->Clen,
                                        (size_t)Nrec, Stream);

  if (n != (size_t)Nrec) {
    if (errno == NO_ERROR)
      sprintf(g->Message, MSG(BAD_READ_NUMBER), (int) n, To_File);
    else
      sprintf(g->Message, MSG(READ_ERROR),
              To_File, strerror(errno));

    if (trace)
      htrc(" Read error: %s\n", g->Message);

    return true;
    } // endif

  if (trace)
    num_read++;

  return false;
  } // end of ReadBlock

/***********************************************************************/
/*  WriteBlock: Write back current column values for one block.        */
/*  Note: the test of Status is meant to prevent physical writing of   */
/*  the block during the checking loop in mode Update. It is set to    */
/*  BUF_EMPTY when reopening the table between the two loops.          */
/***********************************************************************/
bool VCTFAM::WriteBlock(PGLOBAL g, PVCTCOL colp)
  {
  int   len;
  size_t n;

  /*********************************************************************/
  /*  Calculate the offset and size of the block to write.             */
  /*********************************************************************/
  if (MaxBlk)                               // File has Vector format
    len = Headlen 
        + Nrec * (colp->Deplac * MaxBlk + colp->Clen * colp->ColBlk);
  else                                      // Old VCT format
    len = Nrec * (colp->Deplac + Lrecl * colp->ColBlk);

  if (trace)
    htrc("modif=%d len=%d Nrec=%d Deplac=%d Lrecl=%d colblk=%d\n",
          Modif, len, Nrec, colp->Deplac, Lrecl, colp->ColBlk);

  if (fseek(T_Stream, len, SEEK_SET)) {
    sprintf(g->Message, MSG(FSEEK_ERROR), strerror(errno));
    return true;
    } // endif

  // Here Nrec was changed to CurNum in mode Insert,
  // this is the true number of records to write,
  // this also avoid writing garbage in the file for true vector tables.
  n = (Tdbp->GetMode() == MODE_INSERT) ? CurNum : Nrec;

  if (n != fwrite(colp->Blk->GetValPointer(),
                            (size_t)colp->Clen, n, T_Stream)) {
    sprintf(g->Message, MSG(WRITE_STRERROR),
            (UseTemp) ? To_Fbt->Fname : To_File, strerror(errno));
            
    if (trace)
      htrc("Write error: %s\n", strerror(errno));

    return true;
    } // endif

#if defined(UNIX)
  fflush(T_Stream); //NGC
#endif

#ifdef _DEBUG
  num_write++;
#endif

  return false;
  } // end of WriteBlock

/* -------------------------- Class VCMFAM --------------------------- */

/***********************************************************************/
/*  Implementation of the VCMFAM class.                                */
/***********************************************************************/
VCMFAM::VCMFAM(PVCTDEF tdp) : VCTFAM((PVCTDEF)tdp)
  {
  Memory = NULL;
  Memcol = NULL;
  } // end of VCMFAM standard constructor

VCMFAM::VCMFAM(PVCMFAM txfp) : VCTFAM(txfp)
  {
  Memory = txfp->Memory;
  Memcol = txfp->Memcol;
  } // end of VCMFAM copy constructor

/***********************************************************************/
/*  Mapped VCT Access Method opening routine.                          */
/*  New method now that this routine is called recursively (last table */
/*  first in reverse order): index blocks are immediately linked to    */
/*  join block of next table if it exists or else are discarted.       */
/***********************************************************************/
bool VCMFAM::OpenTableFile(PGLOBAL g)
  {
  char    filename[_MAX_PATH];
  int    len;
  MODE    mode = Tdbp->GetMode();
  PFBLOCK fp = NULL;
  PDBUSER dbuserp = (PDBUSER)g->Activityp->Aptr;

  /*********************************************************************/
  /*  Update block info if necessary.                                  */
  /*********************************************************************/
  if (Block < 0)
    if ((Headlen = GetBlockInfo(g)) < 0)
      return true;

  /*********************************************************************/
  /*  We used the file name relative to recorded datapath.             */
  /*********************************************************************/
  PlugSetPath(filename, To_File, Tdbp->GetPath());

  /*********************************************************************/
  /*  The whole file will be mapped so we can use it as if it were     */
  /*  entirely read into virtual memory.                               */
  /*  Firstly we check whether this file have been already mapped.     */
  /*********************************************************************/
  if (mode == MODE_READ) {
    for (fp = dbuserp->Openlist; fp; fp = fp->Next)
      if (fp->Type == TYPE_FB_MAP && !stricmp(fp->Fname, filename)
                     && fp->Count && fp->Mode == mode)
        break;

    if (trace)
      htrc("Mapping VCM file, fp=%p cnt=%d\n", fp, fp->Count);

  } else
    fp = NULL;

  if (fp) {
    /*******************************************************************/
    /*  File already mapped. Just increment use count and get pointer. */
    /*******************************************************************/
    fp->Count++;
    Memory = fp->Memory;
    len = fp->Length;
  } else {
    /*******************************************************************/
    /*  If required, delete the whole file if no filtering is implied. */
    /*******************************************************************/
    bool   del;
    HANDLE hFile;
    MEMMAP mm;
    MODE   mapmode = mode;

    if (mode == MODE_INSERT) {
      if (MaxBlk) {
        if (!Block)
          if (MakeEmptyFile(g, To_File))
            return true;

        // Inserting will be like updating the file
        mapmode = MODE_UPDATE;
      } else {
        strcpy(g->Message, "MAP Insert is for VEC Estimate tables only");
        return true;
      } // endif MaxBlk

      } // endif mode

    del = mode == MODE_DELETE && !Tdbp->GetNext();

    if (del) {
      DelRows = Cardinality(g);

      // This will stop the process by causing GetProgMax to return 0.
//    ResetTableSize(g, 0, Nrec);          must be done later
      } // endif del

    /*******************************************************************/
    /*  Create the mapping file object.                                */
    /*******************************************************************/
    hFile = CreateFileMap(g, filename, &mm, mapmode, del);

    if (hFile == INVALID_HANDLE_VALUE) {
      DWORD rc = GetLastError();

      if (!(*g->Message))
        sprintf(g->Message, MSG(OPEN_MODE_ERROR),
                "map", (int) rc, filename);

      if (trace)
        htrc("%s\n", g->Message);

      return (mode == MODE_READ && rc == ENOENT)
              ? PushWarning(g, Tdbp) : true;
      } // endif hFile

    /*******************************************************************/
    /*  Get the file size (assuming file is smaller than 4 GB)         */
    /*******************************************************************/
    len = mm.lenL;
    Memory = (char *)mm.memory;

    if (!len) {             // Empty or deleted file
      CloseFileHandle(hFile);
      bool rc = ResetTableSize(g, 0, Nrec);
      return (mapmode == MODE_UPDATE) ? true : rc;
      } // endif len

    if (!Memory) {
      CloseFileHandle(hFile);
      sprintf(g->Message, MSG(MAP_VIEW_ERROR),
                          filename, GetLastError());
      return true;
      } // endif Memory

    if (mode != MODE_DELETE) {
      CloseFileHandle(hFile);                    // Not used anymore
      hFile = INVALID_HANDLE_VALUE;              // For Fblock
      } // endif Mode

    /*******************************************************************/
    /*  Link a Fblock. This make possible to reuse already opened maps */
    /*  and also to automatically unmap them in case of error g->jump. */
    /*  Note: block can already exist for previously closed file.      */
    /*******************************************************************/
    fp = (PFBLOCK)PlugSubAlloc(g, NULL, sizeof(FBLOCK));
    fp->Type = TYPE_FB_MAP;
    fp->Fname = (char*)PlugSubAlloc(g, NULL, strlen(filename) + 1);
    strcpy((char*)fp->Fname, filename);
    fp->Next = dbuserp->Openlist;
    dbuserp->Openlist = fp;
    fp->Count = 1;
    fp->Length = len;
    fp->Memory = Memory;
    fp->Mode = mode;
    fp->File = NULL;
    fp->Handle = hFile;                // Used for Delete
  } // endif fp

  To_Fb = fp;                               // Useful when closing

  if (trace)
    htrc("fp=%p count=%d MapView=%p len=%d Top=%p\n",
          fp, fp->Count, Memory, len);

  return AllocateBuffer(g);
  } // end of OpenTableFile

/***********************************************************************/
/*  Allocate the block buffers for columns used in the query.          */
/*  Give a dummy value (1) to prevent allocating the value block.     */
/*  It will be set pointing into the memory map of the file.           */
/*  Note: Memcol must be set for all columns because it can be used    */
/*  for set columns in Update. Clens values are used only in Delete.   */
/***********************************************************************/
bool VCMFAM::AllocateBuffer(PGLOBAL g)
  {
  int     m, i = 0;
  bool    b = Tdbp->GetMode() == MODE_DELETE;
  PVCTCOL cp;
  PCOLDEF cdp;
  PDOSDEF defp = (PDOSDEF)Tdbp->GetDef();

  // Calculate the number of columns
  if (!Ncol)
    for (cdp = defp->GetCols(); cdp; cdp = cdp->GetNext())
      Ncol++;

  // To store the start position of each column
  Memcol = (char**)PlugSubAlloc(g, NULL, Ncol * sizeof(char*));
  m = (MaxBlk) ? MaxBlk : 1;

  // We will need all column sizes and type for Delete
  if (b) {
    Clens = (int*)PlugSubAlloc(g, NULL, Ncol * sizeof(int));
    Isnum = (bool*)PlugSubAlloc(g, NULL, Ncol * sizeof(bool));
    } // endif b

  for (cdp = defp->GetCols(); i < Ncol; i++, cdp = cdp->GetNext()) {
    if (b) {
      Clens[i] = cdp->GetClen();
      Isnum[i] = IsTypeNum(cdp->GetType());
      } // endif b

    Memcol[i] = Memory + Headlen + cdp->GetPoff() * m * Nrec;
    } // endfor cdp

  for (cp = (PVCTCOL)Tdbp->GetColumns(); cp; cp = (PVCTCOL)cp->Next)
    if (!cp->IsSpecial()) {            // Not a pseudo column
      cp->Blk = AllocValBlock(g, (void*)1, cp->Buf_Type, Nrec,
                              cp->Format.Length, cp->Format.Prec);
      cp->AddStatus(BUF_MAPPED);
      } // endif IsSpecial

  if (Tdbp->GetMode() == MODE_INSERT)
    return InitInsert(g);

  return false;
  } // end of AllocateBuffer

/***********************************************************************/
/*  Do initial action when inserting.                                  */
/***********************************************************************/
bool VCMFAM::InitInsert(PGLOBAL g)
  {
  int     rc;
  PVCTCOL cp = (PVCTCOL)Tdbp->GetColumns();

  // We come here in MODE_INSERT only
  if (Last == Nrec) {
    CurBlk = Block;
    CurNum = 0;
    AddBlock = !MaxBlk;
  } else {
    // The starting point must be at the end of file as for append.
    CurBlk = Block - 1;
    CurNum = Last;
  } // endif Last

  //  Prepare error return
  if (g->jump_level == MAX_JUMP) {
    strcpy(g->Message, MSG(TOO_MANY_JUMPS));
    return true;
    } // endif

  if ((rc = setjmp(g->jumper[++g->jump_level])) != 0) {
    g->jump_level--;
    return true;
    } // endif

  // Initialize the column block pointer
  for (; cp; cp = (PVCTCOL)cp->Next)
    cp->ReadBlock(g);

  g->jump_level--;
  return false;
  } // end of InitInsert

/***********************************************************************/
/*  Data Base write routine for VMP access method.                     */
/***********************************************************************/
int VCMFAM::WriteBuffer(PGLOBAL g)
  {
  if (trace)
    htrc("VCM WriteBuffer: R%d Mode=%d CurNum=%d CurBlk=%d\n",
          Tdbp->GetTdb_No(), Tdbp->GetMode(), CurNum, CurBlk);

  // Mode Update being done in ReadDB we process here Insert mode only.
  if (Tdbp->GetMode() == MODE_INSERT) {
    if (CurBlk == MaxBlk) {
      strcpy(g->Message, MSG(TRUNC_BY_ESTIM));
      return RC_EF;       // Too many lines for vector formatted table
      } // endif MaxBlk

    if (Closing || ++CurNum == Nrec) {
      PVCTCOL cp = (PVCTCOL)Tdbp->GetColumns();

      // Write back the updated last block values
      for (; cp; cp = (PVCTCOL)cp->Next)
        cp->WriteBlock(g);

      if (!Closing) {
        CurBlk++;
        CurNum = 0;

        // Re-initialize the column block pointer
        for (cp = (PVCTCOL)Tdbp->GetColumns(); cp; cp = (PVCTCOL)cp->Next)
          cp->ReadBlock(g);

        } // endif Closing

      } // endif Closing || CurNum

    } // endif Mode

  return RC_OK;
  } // end of WriteBuffer

/***********************************************************************/
/*  Data Base delete line routine for VMP access method.               */
/*  Lines between deleted lines are moved in the mapfile view.         */
/***********************************************************************/
int VCMFAM::DeleteRecords(PGLOBAL g, int irc)
  {
  int  i;
  int m, n;

  if (trace)
    htrc("VCM DeleteDB: irc=%d tobuf=%p Tpos=%p Spos=%p\n",
                        irc, To_Buf, Tpos, Spos);

  if (irc != RC_OK) {
    /*******************************************************************/
    /*  EOF: position Fpos at the top of map position.                 */
    /*******************************************************************/
    Fpos = (Block - 1) * Nrec + Last;
    
    if (trace)
      htrc("Fpos placed at file top=%p\n", Fpos);

  } else     // Fpos is the Deleted line position
    Fpos = CurBlk * Nrec + CurNum;

  if (Tpos == Spos)
    /*******************************************************************/
    /*  First line to delete. Move of eventual preceeding lines is     */
    /*  not required here, just setting of future Spos and Tpos.       */
    /*******************************************************************/
    Tpos = Fpos;                               // Spos is set below
  else if (Fpos > Spos) {
    /*******************************************************************/
    /*  Non consecutive line to delete. Move intermediate lines.       */
    /*******************************************************************/
    if (!MaxBlk) {
      // Old VCT format, moving must respect block limits
      char *ps, *pt;
      int   req, soff, toff;

      for (n = Fpos - Spos; n > 0; n -= req) {
        soff = Spos % Nrec;
        toff = Tpos % Nrec;
        req = (size_t)min(n, Nrec - max(soff, toff));

        for (i = 0; i < Ncol; i++) {
          ps = Memcol[i] + (Spos / Nrec) * Blksize + soff * Clens[i];
          pt = Memcol[i] + (Tpos / Nrec) * Blksize + toff * Clens[i];
          memmove(pt, ps, req * Clens[i]);
          } // endfor i

        Tpos += req;
        Spos += req;
        } // endfor n

    } else {
      // True vector format, all is simple...
      n = Fpos - Spos;

      for (i = 0; i < Ncol; i++) {
        m = Clens[i];
        memmove(Memcol[i] + Tpos * m, Memcol[i] + Spos * m, n * m);
        } // endfor i

      Tpos += n;
    } // endif MaxBlk

    if (trace)
      htrc("move %d bytes\n", n);

    } // endif n

  if (irc == RC_OK) {
    Spos = Fpos + 1;                               // New start position

    if (trace)
      htrc("after: Tpos=%p Spos=%p\n", Tpos, Spos);

  } else {
    /*******************************************************************/
    /*  Last call after EOF has been reached. Reset the Block and      */
    /*  Last values for TDBVCT::MakeBlockValues.                       */
    /*******************************************************************/
    Block = (Tpos > 0) ? (Tpos + Nrec - 1) / Nrec : 0;
    Last = (Tpos + Nrec - 1) % Nrec + 1;

    if (!MaxBlk) {
      PFBLOCK fp = To_Fb;

      // Clean the unused part of the last block
      m = (Block - 1) * Blksize;
      n = Nrec - Last;

      for (i = 0; i < Ncol; i++)
        memset(Memcol[i] + m + Last * Clens[i],
            (Isnum[i]) ? 0 : ' ', n * Clens[i]);

      // We must Unmap the view and use the saved file handle
      // to put an EOF at the end of the last block of the file.
      CloseMemMap(fp->Memory, (size_t)fp->Length);
      fp->Count = 0;                            // Avoid doing it twice

      // Remove extra blocks
      n = Block * Blksize;

#if defined(WIN32)
      DWORD drc = SetFilePointer(fp->Handle, n, NULL, FILE_BEGIN);

      if (drc == 0xFFFFFFFF) {
        sprintf(g->Message, MSG(FUNCTION_ERROR),
                            "SetFilePointer", GetLastError());
        CloseHandle(fp->Handle);
        return RC_FX;
        } // endif

      if (trace)
        htrc("done, Tpos=%p newsize=%d drc=%d\n", Tpos, n, drc);

      if (!SetEndOfFile(fp->Handle)) {
        sprintf(g->Message, MSG(FUNCTION_ERROR),
                            "SetEndOfFile", GetLastError());
        CloseHandle(fp->Handle);
        return RC_FX;
        } // endif

      CloseHandle(fp->Handle);
#else    // UNIX
      if (ftruncate(fp->Handle, (off_t)n)) {
        sprintf(g->Message, MSG(TRUNCATE_ERROR), strerror(errno));
        close(fp->Handle);
        return RC_FX;
        } // endif

      close(fp->Handle);
#endif   // UNIX
    } else
      // True vector table, Table file size does not change.
      // Just clean the unused part of the file.
      for (n = Fpos - Tpos, i = 0; i < Ncol; i++)
        memset(Memcol[i] + Tpos * Clens[i], 0, n * Clens[i]);

    // Reset Last and Block values in the catalog
    PlugCloseFile(g, To_Fb);      // in case of Header
    ResetTableSize(g, Block, Last);
  } // endif irc

  return RC_OK;                                      // All is correct
  } // end of DeleteRecords

/***********************************************************************/
/*  Data Base close routine for VMP access method.                     */
/***********************************************************************/
void VCMFAM::CloseTableFile(PGLOBAL g)
  {
  int  rc = 0, wrc = RC_OK;
  MODE mode = Tdbp->GetMode();

  if (mode == MODE_INSERT) {
    if (!Closing) {
      if (CurNum) {
        // Some more inserted lines remain to be written
        Last = CurNum;
        Block = CurBlk + 1;
        Closing = true;
        wrc = WriteBuffer(g);
      } else {
        Last = Nrec;
        Block = CurBlk;
        wrc = RC_OK;
      } // endif CurNum

    } else
      wrc = RC_FX;                  // Last write was in error

    PlugCloseFile(g, To_Fb);

    if (wrc != RC_FX)
      rc = ResetTableSize(g, Block, Last);

  } else if (mode != MODE_DELETE)
    PlugCloseFile(g, To_Fb);

  } // end of CloseTableFile

/***********************************************************************/
/*  ReadBlock: Read column values from current block.                  */
/***********************************************************************/
bool VCMFAM::ReadBlock(PGLOBAL g, PVCTCOL colp)
  {
  char *mempos;
  int   i = colp->Index - 1;
  int  n = Nrec * ((MaxBlk || Split) ? colp->Clen : Lrecl);

  /*********************************************************************/
  /*  Calculate the start position of the column block to read.        */
  /*********************************************************************/
  mempos = Memcol[i] + n * CurBlk;

  if (trace)
    htrc("mempos=%p i=%d Nrec=%d Clen=%d CurBlk=%d\n",
          mempos, i, Nrec, colp->Clen, CurBlk);

  if (colp->GetStatus(BUF_MAPPED))
    colp->Blk->SetValPointer(mempos);

  if (trace)
    num_read++;

  return false;
  } // end of ReadBlock

/***********************************************************************/
/*  WriteBlock: Write back current column values for one block.        */
/*  Note: there is nothing to do because we are working directly into  */
/*  the mapped file, except when checking for Update but in this case  */
/*  we do not want to write back the modifications either.             */
/***********************************************************************/
bool VCMFAM::WriteBlock(PGLOBAL g, PVCTCOL colp)
  {
#if defined(_DEBUG)
  char *mempos;
  int   i = colp->Index - 1;
  int   n = Nrec * colp->Clen;

  /*********************************************************************/
  /*  Calculate the offset and size of the block to write.             */
  /*********************************************************************/
  mempos = Memcol[i] + n * CurBlk;

  if (trace)
    htrc("modif=%d mempos=%p i=%d Nrec=%d Clen=%d colblk=%d\n",
          Modif, mempos, i, Nrec, colp->Clen, colp->ColBlk);

#endif // _DEBUG

  return false;
  } // end of WriteBlock

/* -------------------------- Class VECFAM --------------------------- */

/***********************************************************************/
/*  Implementation of the VECFAM class.                                */
/***********************************************************************/
VECFAM::VECFAM(PVCTDEF tdp) : VCTFAM((PVCTDEF)tdp)
  {
  Streams = NULL;
  To_Fbs = NULL;
  To_Bufs = NULL;
  Split = true;
  Block = Last = -1;
  InitUpdate = false;
  } // end of VECFAM standard constructor

VECFAM::VECFAM(PVECFAM txfp) : VCTFAM(txfp)
  {
  Streams = txfp->Streams;
  To_Fbs = txfp->To_Fbs;
  Clens = txfp->Clens;
  To_Bufs = txfp->To_Bufs;
  InitUpdate = txfp->InitUpdate;
  } // end of VECFAM copy constructor

/***********************************************************************/
/*  VEC Access Method opening routine.                                 */
/*  New method now that this routine is called recursively (last table */
/*  first in reverse order): index blocks are immediately linked to    */
/*  join block of next table if it exists or else are discarted.       */
/***********************************************************************/
bool VECFAM::OpenTableFile(PGLOBAL g)
  {
  char    opmode[4];
  int     i;
  bool    b;
  PCOLDEF cdp;
  PVCTCOL cp;
  MODE    mode = Tdbp->GetMode();
  PDOSDEF defp = (PDOSDEF)Tdbp->GetDef();

  /*********************************************************************/
  /*  Call Cardinality to set Block and Last values in case it was not */
  /*  already called (this happens indeed in test xmode)               */
  /*********************************************************************/
  Cardinality(g);

  /*********************************************************************/
  /*  Open according to input/output mode required.                    */
  /*********************************************************************/
  switch (mode) {
    case MODE_READ:
      strcpy(opmode, "rb");
      break;
    case MODE_DELETE:
      if (!Tdbp->GetNext()) {
        // Store the number of deleted lines
        DelRows = Cardinality(g);

        // This will delete the whole file
        strcpy(opmode, "wb");

        // This will stop the process by causing GetProgMax to return 0.
        ResetTableSize(g, 0, Nrec);
        break;
        } // endif filter

      // Selective delete, pass thru
    case MODE_UPDATE:
      UseTemp = Tdbp->IsUsingTemp(g);
      strcpy(opmode, (UseTemp) ? "r": "r+");
      break;
    case MODE_INSERT:
      strcpy(opmode, "ab");
      break;
    default:
      sprintf(g->Message, MSG(BAD_OPEN_MODE), mode);
      return true;
    } // endswitch Mode

  /*********************************************************************/
  /*  Initialize the array of file structures.                         */
  /*********************************************************************/
  if (!Colfn) {
    // Prepare the column file name pattern and set Ncol
    Colfn = (char*)PlugSubAlloc(g, NULL, _MAX_PATH);
    Ncol = ((VCTDEF*)Tdbp->GetDef())->MakeFnPattern(Colfn);
    } // endif Colfn

  Streams = (FILE* *)PlugSubAlloc(g, NULL, Ncol * sizeof(FILE*));
  To_Fbs = (PFBLOCK *)PlugSubAlloc(g, NULL, Ncol * sizeof(PFBLOCK));

  for (i = 0; i < Ncol; i++) {
    Streams[i] = NULL;
    To_Fbs[i] = NULL;
    } // endif i

  /*********************************************************************/
  /*  Open the files corresponding to columns used in the query.       */
  /*********************************************************************/
  if (mode == MODE_INSERT || mode == MODE_DELETE) {
    // All columns must be written or deleted
    for (i = 0, cdp = defp->GetCols(); cdp; i++, cdp = cdp->GetNext())
      if (OpenColumnFile(g, opmode, i))
        return true;

    // Check for void table or missing columns
    for (b = !Streams[0], i = 1; i < Ncol; i++)
      if (b != !Streams[i])
        return true;

  } else {
    /*******************************************************************/
    /*  Open the files corresponding to updated columns of the query.  */
    /*******************************************************************/
    for (cp = (PVCTCOL)((PTDBVCT)Tdbp)->To_SetCols; cp;
         cp = (PVCTCOL)cp->Next)
      if (OpenColumnFile(g, opmode, cp->Index - 1))
        return true;

    // Open in read only mode the used columns not already open
    for (cp = (PVCTCOL)Tdbp->GetColumns(); cp; cp = (PVCTCOL)cp->Next)
      if (!cp->IsSpecial() && !Streams[cp->Index - 1])
        if (OpenColumnFile(g, "rb", cp->Index - 1))
          return true;

    // Check for void table or missing columns
    for (i = 0, cp = (PVCTCOL)Tdbp->GetColumns(); cp;
                cp = (PVCTCOL)cp->Next)
      if (!i++)
        b = !Streams[cp->Index - 1];
      else if (b != !Streams[cp->Index - 1])
        return true;

  } // endif mode

  /*********************************************************************/
  /*  Allocate the table and column block buffer.                      */
  /*********************************************************************/
  return (b) ? false : AllocateBuffer(g);
  } // end of OpenTableFile

/***********************************************************************/
/*  Open the file corresponding to one column.                         */
/***********************************************************************/
bool VECFAM::OpenColumnFile(PGLOBAL g, char *opmode, int i)
  {
  char    filename[_MAX_PATH];
  PDBUSER dup = PlgGetUser(g);

  sprintf(filename, Colfn, i+1);

  if (!(Streams[i] = PlugOpenFile(g, filename, opmode))) {
    if (trace)
      htrc("%s\n", g->Message);

    return (Tdbp->GetMode() == MODE_READ && errno == ENOENT)
            ? PushWarning(g, Tdbp) : true;
    } // endif Streams

  if (trace)
    htrc("File %s is open in mode %s\n", filename, opmode);

  To_Fbs[i] = dup->Openlist;       // Keep track of File blocks
  return false;
  } // end of OpenColumnFile

/***********************************************************************/
/*  Allocate the block buffers for columns used in the query.          */
/***********************************************************************/
bool VECFAM::AllocateBuffer(PGLOBAL g)
  {
  int     i;
  PVCTCOL cp;
  PCOLDEF cdp;
  PTDBVCT tdbp = (PTDBVCT)Tdbp;
  MODE    mode = tdbp->GetMode();
  PDOSDEF defp = (PDOSDEF)tdbp->GetDef();

  if (mode != MODE_READ) {
    // Allocate what is needed by all modes except Read
    T_Streams = (FILE* *)PlugSubAlloc(g, NULL, Ncol * sizeof(FILE*));
    Clens = (int*)PlugSubAlloc(g, NULL, Ncol * sizeof(int));

    // Give default values
    for (i = 0; i < Ncol; i++) {
      T_Streams[i] = Streams[i];
      Clens[i] = 0;
      } // endfor i

    } // endif mode

  if (mode == MODE_INSERT) {
    bool    chk = PlgGetUser(g)->Check & CHK_TYPE;

    To_Bufs = (void**)PlugSubAlloc(g, NULL, Ncol * sizeof(void*));
    cdp = defp->GetCols();

    for (i = 0; cdp && i < Ncol; i++, cdp = cdp->GetNext()) {
      Clens[i] = cdp->GetClen();
      To_Bufs[i] = PlugSubAlloc(g, NULL, Nrec * Clens[i]);

      if (cdp->GetType() == TYPE_STRING)
        memset(To_Bufs[i], ' ', Nrec * Clens[i]);
      else
        memset(To_Bufs[i],   0, Nrec * Clens[i]);

      } // endfor cdp

    for (cp = (PVCTCOL)tdbp->Columns; cp; cp = (PVCTCOL)cp->Next)
      cp->Blk = AllocValBlock(g, To_Bufs[cp->Index - 1],
                              cp->Buf_Type, Nrec, cp->Format.Length,
                                                  cp->Format.Prec, chk);

    return InitInsert(g);
  } else {
    if (UseTemp || mode == MODE_DELETE) {
      // Allocate all that is needed to move lines and make Temp
      if (UseTemp) {
        Tempat = (char*)PlugSubAlloc(g, NULL, _MAX_PATH);
        strcpy(Tempat, Colfn);
        PlugSetPath(Tempat, Tempat, Tdbp->GetPath());
        strcat(PlugRemoveType(Tempat, Tempat), ".t");
        T_Fbs = (PFBLOCK *)PlugSubAlloc(g, NULL, Ncol * sizeof(PFBLOCK));
        } // endif UseTemp

      if (UseTemp)
        for (i = 0; i < Ncol; i++) {
          T_Streams[i] = (mode == MODE_UPDATE) ? (FILE*)1 : NULL;
          T_Fbs[i] = NULL;
          } // endfor i

      if (mode == MODE_DELETE) {  // All columns are moved
        cdp = defp->GetCols();

        for (i = 0; cdp && i < Ncol; i++, cdp = cdp->GetNext()) {
          Clens[i] = cdp->GetClen();
          Buflen = max(Buflen, cdp->GetClen());
          } // endfor cdp

      } else {  // Mode Update, only some columns are updated
        for (cp = (PVCTCOL)tdbp->To_SetCols; cp; cp = (PVCTCOL)cp->Next) {
          i = cp->Index -1;

          if (UseTemp)
            T_Streams[i] = NULL;   // Mark the streams to open

          Clens[i] = cp->Clen;
          Buflen = max(Buflen, cp->Clen);
          } // endfor cp

        InitUpdate = true;         // To be initialized
      } // endif mode

      To_Buf = (char*)PlugSubAlloc(g, NULL, Buflen * Nrec);
      } // endif mode

    // Finally allocate column buffers for all modes
    for (cp = (PVCTCOL)tdbp->Columns; cp; cp = (PVCTCOL)cp->Next)
      if (!cp->IsSpecial())            // Not a pseudo column
          cp->Blk = AllocValBlock(g, NULL, cp->Buf_Type, Nrec,
                                  cp->Format.Length, cp->Format.Prec);

  } // endif mode

  return false;
  } // end of AllocateBuffer

/***********************************************************************/
/*  Do initial action when inserting.                                  */
/***********************************************************************/
bool VECFAM::InitInsert(PGLOBAL g)
  {
  // We come here in MODE_INSERT only
  CurBlk = 0;
  CurNum = 0;
  AddBlock = true;
  return false;
  } // end of InitInsert

/***********************************************************************/
/*  Reset buffer access according to indexing and to mode.             */
/*  >>>>>>>>>>>>>> TO BE RE-VISITED AND CHECKED <<<<<<<<<<<<<<<<<<<<<< */
/***********************************************************************/
void VECFAM::ResetBuffer(PGLOBAL g)
  {
  /*********************************************************************/
  /*  If access is random, performances can be much better when the    */
  /*  reads are done on only one row, except for small tables that can */
  /*  be entirely read in one block. If the index is just used as a    */
  /*  bitmap filter, as for Update or Delete, reading will be          */
  /*  sequential and we better keep block reading.                     */
  /*********************************************************************/
  if (Tdbp->GetKindex() && Block > 1 && Tdbp->GetMode() == MODE_READ) {
    Nrec = 1;                       // Better for random access
    Rbuf = 0;
    OldBlk = -2;                    // Has no meaning anymore
    Block = Tdbp->Cardinality(g);   // Blocks are one line now
    Last = 1;                       // Probably unuseful
    } // endif Mode

  } // end of ResetBuffer

/***********************************************************************/
/*  Data Base write routine for VCT access method.                     */
/***********************************************************************/
int VECFAM::WriteBuffer(PGLOBAL g)
  {
  if (trace)
    htrc("VCT WriteBuffer: R%d Mode=%d CurNum=%d CurBlk=%d\n",
          Tdbp->GetTdb_No(), Tdbp->GetMode(), CurNum, CurBlk);

  if (Tdbp->GetMode() == MODE_INSERT) {
    if (Closing || ++CurNum == Nrec) {
      // Here we must add a new blocks to the files
      int    i;
      size_t n = (size_t)CurNum;

      for (i = 0; i < Ncol; i++)
        if (n != fwrite(To_Bufs[i], (size_t)Clens[i], n, Streams[i])) {
          sprintf(g->Message, MSG(WRITE_STRERROR), To_File, strerror(errno));
          return RC_FX;
          } // endif

      if (!Closing) {
        CurBlk++;
        CurNum = 0;
        } // endif Closing

      } // endif Closing || CurNum

  } else              // Mode Update
    // Writing updates being done in ReadDB we do initialization only.
    if (InitUpdate) {
      if (OpenTempFile(g))
        return RC_FX;

      InitUpdate = false;                 // Done
      } // endif InitUpdate

  return RC_OK;
  } // end of WriteBuffer

/***********************************************************************/
/*  Data Base delete line routine for split vertical access methods.   */
/*  Note: lines are moved directly in the files (ooops...)             */
/***********************************************************************/
int VECFAM::DeleteRecords(PGLOBAL g, int irc)
  {
  /*********************************************************************/
  /*  There is an alternative here:                                    */
  /*  1 - use a temporary file in which are copied all not deleted     */
  /*      lines, at the end the original file will be deleted and      */
  /*      the temporary file renamed to the original file name.        */
  /*  2 - directly move the not deleted lines inside the original      */
  /*      file, and at the end erase all trailing records.             */
  /*  This depends on the Check setting, false by default.             */
  /*********************************************************************/
  if (trace)
    htrc("VEC DeleteDB: rc=%d UseTemp=%d Fpos=%d Tpos=%d Spos=%d\n",
          irc, UseTemp, Fpos, Tpos, Spos);

  if (irc != RC_OK) {
    /*******************************************************************/
    /*  EOF: position Fpos at the end-of-file position.                */
    /*******************************************************************/
    Fpos = Cardinality(g);
    
    if (trace)
      htrc("Fpos placed at file end=%d\n", Fpos);

  } else     // Fpos is the Deleted line position
    Fpos = CurBlk * Nrec + CurNum;

  if (Tpos == Spos)
    // First line to delete
    if (UseTemp) {
      /*****************************************************************/
      /*  Open the temporary files, Spos is at the beginning of file.  */
      /*****************************************************************/
      if (OpenTempFile(g))
        return RC_FX;

    } else
      /*****************************************************************/
      /*  Move of eventual preceeding lines is not required here.      */
      /*  Set the future Tpos, and give Spos a value to block copying. */
      /*****************************************************************/
      Spos = Tpos = Fpos;

  /*********************************************************************/
  /*  Move any intermediate lines.                                     */
  /*********************************************************************/
  if (MoveIntermediateLines(g))
    return RC_FX;

  if (irc == RC_OK) {
#ifdef _DEBUG
    assert(Spos == Fpos);
#endif
    Spos++;          // New start position is on next line

    if (trace)
      htrc("after: Tpos=%d Spos=%d\n", Tpos, Spos);

  } else {
    /*******************************************************************/
    /*  Last call after EOF has been reached.                          */
    /*******************************************************************/
    if (!UseTemp) {
      /*****************************************************************/
      /* Because the chsize functionality is only accessible with a    */
      /* system call we must close the file and reopen it with the     */
      /* open function (_fopen for MS??) this is still to be checked   */
      /* for compatibility with other OS's.                            */
      /*****************************************************************/
      char filename[_MAX_PATH];
      int  h, rc;                            // File handle, return code

      for (int i = 0; i < Ncol; i++) {
        sprintf(filename, Colfn, i + 1);
        rc = PlugCloseFile(g, To_Fbs[i]);

        if ((h= global_open(g, MSGID_OPEN_STRERROR, filename, O_WRONLY)) <= 0)
          return RC_FX;

        /***************************************************************/
        /*  Remove extra records.                                      */
        /***************************************************************/
#if defined(UNIX)
        if (ftruncate(h, (off_t)(Tpos * Clens[i]))) {
          sprintf(g->Message, MSG(TRUNCATE_ERROR), strerror(errno));
          close(h);
          return RC_FX;
          } // endif
#else
        if (chsize(h, Tpos * Clens[i])) {
          sprintf(g->Message, MSG(CHSIZE_ERROR), strerror(errno));
          close(h);
          return RC_FX;
          } // endif
#endif

        close(h);

        if (trace)
          htrc("done, h=%d irc=%d\n", h, irc);

        } // endfor i

    } else        // UseTemp
      // Ok, now delete old files and rename new temp files
      if (RenameTempFile(g) == RC_FX)
        return RC_FX;

    // Reset these values for TDBVCT::MakeBlockValues
    Block = (Tpos > 0) ? (Tpos + Nrec - 1) / Nrec : 0;
    Last = (Tpos + Nrec - 1) % Nrec + 1;

    if (ResetTableSize(g, Block, Last))
      return RC_FX;

  } // endif irc

  return RC_OK;                                      // All is correct
  } // end of DeleteRecords

/***********************************************************************/
/*  Open temporary files used while updating or deleting.              */
/*  Note: the files not updated have been given a T_Stream value of 1. */
/***********************************************************************/
bool VECFAM::OpenTempFile(PGLOBAL g)
  {
  char tempname[_MAX_PATH];

  for (int i = 0; i < Ncol; i++)
    if (!T_Streams[i]) {
      /*****************************************************************/
      /*  Open the temporary file, Spos is at the beginning of file.   */
      /*****************************************************************/
      sprintf(tempname, Tempat, i+1);

      if (!(T_Streams[i] = PlugOpenFile(g, tempname, "wb"))) {
        if (trace)
          htrc("%s\n", g->Message);

        return true;
      } else
        T_Fbs[i] = PlgGetUser(g)->Openlist;

    } else       // This is a column that is not updated
      T_Streams[i] = NULL;        // For RenameTempFile

  return false;
  } // end of OpenTempFile

/***********************************************************************/
/*  Move intermediate updated lines before writing blocks.             */
/***********************************************************************/
bool VECFAM::MoveLines(PGLOBAL g)
  {
  if (UseTemp && !InitUpdate) {    // Don't do it in check pass
    Fpos = OldBlk * Nrec;

    if (MoveIntermediateLines(g)) {
      Closing = true;           // ???
      return true;
      } // endif UseTemp

//  Spos = Fpos + Nrec;
    } // endif UseTemp
  return false;

  } // end of MoveLines

/***********************************************************************/
/*  Move intermediate deleted or updated lines.                        */
/***********************************************************************/
bool VECFAM::MoveIntermediateLines(PGLOBAL g, bool *bn)
  {
  int    i;
  int   n;
  bool   b = false;
  size_t req, len;

  for (n = Fpos - Spos; n > 0; n -= Nrec) {
    /*******************************************************************/
    /*  Non consecutive line to delete. Move intermediate lines.       */
    /*******************************************************************/
    req = (size_t)min(n, Nrec);

    for (i = 0; i < Ncol; i++) {
      if (!T_Streams[i])
        continue;             // Non updated column

      if (!UseTemp || !b)
        if (fseek(Streams[i], Spos * Clens[i], SEEK_SET)) {
          sprintf(g->Message, MSG(READ_SEEK_ERROR), strerror(errno));
          return true;
          } // endif

      len = fread(To_Buf, Clens[i], req, Streams[i]);

      if (trace)
        htrc("after read req=%d len=%d\n", req, len);

      if (len != req) {
        sprintf(g->Message, MSG(DEL_READ_ERROR), (int) req, (int) len);
        return true;
        } // endif len

      if (!UseTemp)
        if (fseek(T_Streams[i], Tpos * Clens[i], SEEK_SET)) {
          sprintf(g->Message, MSG(WRITE_SEEK_ERR), strerror(errno));
          return true;
          } // endif

      if ((len = fwrite(To_Buf, Clens[i], req, T_Streams[i])) != req) {
        sprintf(g->Message, MSG(DEL_WRITE_ERROR), strerror(errno));
        return true;
        } // endif

      if (trace)
        htrc("after write pos=%d\n", ftell(Streams[i]));

      } // endfor i

    Tpos += (int)req;
    Spos += (int)req;

    if (trace)
      htrc("loop: Tpos=%d Spos=%d\n", Tpos, Spos);

    b = true;
    } // endfor n

  return false;
  } // end of MoveIntermediate Lines

/***********************************************************************/
/*  Delete the old files and rename the new temporary files.           */
/***********************************************************************/
int VECFAM::RenameTempFile(PGLOBAL g)
  {
  char *tempname, filetemp[_MAX_PATH], filename[_MAX_PATH];
  int   rc = RC_OK;

  // Close all files.
  // This loop is necessary because, in case of join,
  // the table files can have been open several times.
  for (PFBLOCK fb = PlgGetUser(g)->Openlist; fb; fb = fb->Next)
    rc = PlugCloseFile(g, fb);

  for (int i = 0; i < Ncol && rc == RC_OK; i++) {
    if (!T_Fbs[i])
      continue;

    tempname = (char*)T_Fbs[i]->Fname;
    sprintf(filename, Colfn, i+1);
    PlugSetPath(filename, filename, Tdbp->GetPath());
    strcat(PlugRemoveType(filetemp, filename), ".ttt");
    remove(filetemp);   // May still be there from previous error

    if (rename(filename, filetemp)) {    // Save file for security
      sprintf(g->Message, MSG(RENAME_ERROR),
              filename, filetemp, strerror(errno));
      rc = RC_FX;
    } else if (rename(tempname, filename)) {
      sprintf(g->Message, MSG(RENAME_ERROR),
              tempname, filename, strerror(errno));
      rc = rename(filetemp, filename);   // Restore saved file
      rc = RC_FX;
    } else if (remove(filetemp)) {
      sprintf(g->Message, MSG(REMOVE_ERROR),
              filetemp, strerror(errno));
      rc = RC_INFO;                      // Acceptable
    } // endif's

    } // endfor i

  return rc;
  } // end of RenameTempFile

/***********************************************************************/
/*  Data Base close routine for VEC access method.                     */
/***********************************************************************/
void VECFAM::CloseTableFile(PGLOBAL g)
  {
  int  rc = 0, wrc = RC_OK;
  MODE mode = Tdbp->GetMode();

  if (mode == MODE_INSERT) {
    if (Closing)
      wrc = RC_FX;                  // Last write was in error
    else
      if (CurNum) {
        // Some more inserted lines remain to be written
        Last += (CurBlk * Nrec + CurNum -1);
        Block += (Last / Nrec);
        Last = Last % Nrec + 1;
        Closing = true;
        wrc = WriteBuffer(g);
      } else {
        Block += CurBlk;
        wrc = RC_OK;
      } // endif CurNum

    if (wrc != RC_FX)
      rc = ResetTableSize(g, Block, Last);
    else
      longjmp(g->jumper[g->jump_level], 44);

  } else if (mode == MODE_UPDATE) {
    if (UseTemp && !InitUpdate) {
      // Write any intermediate lines to temp file
      Fpos = OldBlk * Nrec;
      wrc = MoveIntermediateLines(g);
//    Spos = Fpos + Nrec;
      } // endif UseTemp

    // Write back to file any pending modifications
    if (wrc == RC_OK)
      for (PVCTCOL colp = (PVCTCOL)((PTDBVCT)Tdbp)->To_SetCols;
                   colp; colp = (PVCTCOL)colp->Next)
        colp->WriteBlock(g);

    if (wrc == RC_OK && UseTemp && !InitUpdate) {
      // Write any intermediate lines to temp file
      Fpos = (Block - 1) * Nrec + Last;
      wrc = MoveIntermediateLines(g);
      } // endif UseTemp

  } // endif's mode

  if (UseTemp && !InitUpdate) {
    // If they are errors, leave files unchanged
    if (wrc == RC_OK)
      rc = RenameTempFile(g);
    else
      longjmp(g->jumper[g->jump_level], 44);

  } else if (Streams)
    for (int i = 0; i < Ncol; i++)
      if (Streams[i]) {
        rc = PlugCloseFile(g, To_Fbs[i]);
        Streams[i] = NULL;
        To_Fbs[i] = NULL;
        } // endif Streams

  if (trace)
    htrc("VCT CloseTableFile: closing %s wrc=%d rc=%d\n", To_File, wrc, rc);

  } // end of CloseTableFile

/***********************************************************************/
/*  ReadBlock: Read column values from current block.                  */
/***********************************************************************/
bool VECFAM::ReadBlock(PGLOBAL g, PVCTCOL colp)
  {
  int     i, len;
  size_t  n;

  /*********************************************************************/
  /*  Calculate the offset and size of the block to read.              */
  /*********************************************************************/
  len = Nrec * colp->Clen * CurBlk;
  i = colp->Index - 1;

  if (trace)
    htrc("len=%d i=%d Nrec=%d Deplac=%d Lrecl=%d CurBlk=%d\n",
          len, i, Nrec, colp->Deplac, Lrecl, CurBlk);

  if (fseek(Streams[i], len, SEEK_SET)) {
    sprintf(g->Message, MSG(FSEEK_ERROR), strerror(errno));
    return true;
    } // endif

  n = fread(colp->Blk->GetValPointer(), (size_t)colp->Clen,
                                        (size_t)Nrec, Streams[i]);

  if (n != (size_t)Nrec && (CurBlk+1 != Block || n != (size_t)Last)) {
    char fn[_MAX_PATH];

    sprintf(fn, Colfn, colp->Index);
#if defined(WIN32)
    if (feof(Streams[i]))
#else   // !WIN32
    if (errno == NO_ERROR)
#endif  // !WIN32
      sprintf(g->Message, MSG(BAD_READ_NUMBER), (int) n, fn);
    else
      sprintf(g->Message, MSG(READ_ERROR),
              fn, strerror(errno));

    if (trace)
      htrc(" Read error: %s\n", g->Message);

    return true;
    } // endif

  if (trace)
    num_read++;

  return false;
  } // end of ReadBlock

/***********************************************************************/
/*  WriteBlock: Write back current column values for one block.        */
/*  Note: the test of Status is meant to prevent physical writing of   */
/*  the block during the checking loop in mode Update. It is set to    */
/*  BUF_EMPTY when reopening the table between the two loops.          */
/***********************************************************************/
bool VECFAM::WriteBlock(PGLOBAL g, PVCTCOL colp)
  {
  int   i, len;
  size_t n;

  /*********************************************************************/
  /*  Calculate the offset and size of the block to write.             */
  /*********************************************************************/
  len = Nrec * colp->Clen * colp->ColBlk;
  i = colp->Index - 1;

  if (trace)
    htrc("modif=%d len=%d i=%d Nrec=%d Deplac=%d Lrecl=%d colblk=%d\n",
          Modif, len, i, Nrec, colp->Deplac, Lrecl, colp->ColBlk);

  if (Tdbp->GetMode() == MODE_UPDATE && !UseTemp)
    if (fseek(T_Streams[i], len, SEEK_SET)) {
      sprintf(g->Message, MSG(FSEEK_ERROR), strerror(errno));
      return true;
      } // endif

  // Here Nrec was changed to CurNum in mode Insert,
  // this is the true number of records to write,
  // this also avoid writing garbage in the file for true vector tables.
  n = (Tdbp->GetMode() == MODE_INSERT) ? CurNum
    : (colp->ColBlk == Block - 1) ? Last : Nrec;

  if (n != fwrite(colp->Blk->GetValPointer(),
                            (size_t)colp->Clen, n, T_Streams[i])) {
    char fn[_MAX_PATH];

    sprintf(fn, (UseTemp) ? Tempat : Colfn, colp->Index);
    sprintf(g->Message, MSG(WRITE_STRERROR), fn, strerror(errno));
    
    if (trace)
      htrc("Write error: %s\n", strerror(errno));

    return true;
  } else
    Spos = Fpos + n;

#if defined(UNIX)
  fflush(Streams[i]); //NGC
#endif
  return false;
  } // end of WriteBlock

/* -------------------------- Class VMPFAM --------------------------- */

/***********************************************************************/
/*  Implementation of the VMPFAM class.                                */
/***********************************************************************/
VMPFAM::VMPFAM(PVCTDEF tdp) : VCMFAM((PVCTDEF)tdp)
  {
  To_Fbs = NULL;
  Split = true;
  Block = Last = -1;
  } // end of VMPFAM standard constructor

VMPFAM::VMPFAM(PVMPFAM txfp) : VCMFAM(txfp)
  {
  To_Fbs = txfp->To_Fbs;
  } // end of VMPFAM copy constructor

/***********************************************************************/
/*  VCT Access Method opening routine.                                 */
/*  New method now that this routine is called recursively (last table */
/*  first in reverse order): index blocks are immediately linked to    */
/*  join block of next table if it exists or else are discarted.       */
/***********************************************************************/
bool VMPFAM::OpenTableFile(PGLOBAL g)
  {
  int     i;
  bool    b;
  MODE    mode = Tdbp->GetMode();
  PCOLDEF cdp;
  PVCTCOL cp;
  PDOSDEF defp = (PDOSDEF)Tdbp->GetDef();

  if (mode == MODE_DELETE && !Tdbp->GetNext()) {
    DelRows = Cardinality(g);

    // This will stop the process by causing GetProgMax to return 0.
    ResetTableSize(g, 0, Nrec);
  } else
    Cardinality(g);        // See comment in VECFAM::OpenTbleFile


  /*********************************************************************/
  /*  Prepare the filename pattern for column files and set Ncol.      */
  /*********************************************************************/
  if (!Colfn) {
    // Prepare the column file name pattern
    Colfn = (char*)PlugSubAlloc(g, NULL, _MAX_PATH);
    Ncol = ((VCTDEF*)Tdbp->GetDef())->MakeFnPattern(Colfn);
    } // endif Colfn

  /*********************************************************************/
  /*  Initialize the array of file structures.                         */
  /*********************************************************************/
  Memcol = (char* *)PlugSubAlloc(g, NULL, Ncol * sizeof(char *));
  To_Fbs = (PFBLOCK *)PlugSubAlloc(g, NULL, Ncol * sizeof(PFBLOCK));

  for (i = 0; i < Ncol; i++) {
    Memcol[i] = NULL;
    To_Fbs[i] = NULL;
    } // endif i

  /*********************************************************************/
  /*  Open the files corresponding to columns used in the query.       */
  /*********************************************************************/
  if (mode == MODE_DELETE) {
    // All columns are used in Delete mode
    for (i = 0, cdp = defp->GetCols(); cdp; i++, cdp = cdp->GetNext())
      if (MapColumnFile(g, mode, i))
        return true;

  } else {
    /*******************************************************************/
    /*  Open the files corresponding updated columns of the query.     */
    /*******************************************************************/
    for (cp = (PVCTCOL)((PTDBVCT)Tdbp)->To_SetCols; cp;
         cp = (PVCTCOL)cp->Next)
      if (MapColumnFile(g, MODE_UPDATE, cp->Index - 1))
        return true;

    /*******************************************************************/
    /* Open other non already open used columns (except pseudos)      */
    /*******************************************************************/
    for (cp = (PVCTCOL)Tdbp->GetColumns(); cp; cp = (PVCTCOL)cp->Next)
      if (!cp->IsSpecial() && !Memcol[cp->Index - 1])
        if (MapColumnFile(g, MODE_READ, cp->Index - 1))
          return true;

  } // endif mode

  /*********************************************************************/
  /* Check for void table or missing columns                           */
  /*********************************************************************/
  for (b = !Memcol[0], i = 1; i < Ncol; i++)
    if (b != !Memcol[i])
      return true;

  /*********************************************************************/
  /*  Allocate the table and column block buffer.                      */
  /*********************************************************************/
  return (b) ? false : AllocateBuffer(g);
  } // end of OpenTableFile

/***********************************************************************/
/*  Open the file corresponding to one column.                         */
/***********************************************************************/
bool VMPFAM::MapColumnFile(PGLOBAL g, MODE mode, int i)
  {
  char    filename[_MAX_PATH];
  int    len;
  HANDLE  hFile;
  MEMMAP  mm;
  PFBLOCK fp;
  PDBUSER dup = PlgGetUser(g);

  sprintf(filename, Colfn, i+1);

  /*********************************************************************/
  /*  The whole file will be mapped so we can use it as                */
  /*  if it were entirely read into virtual memory.                    */
  /*  Firstly we check whether this file have been already mapped.     */
  /*********************************************************************/
  if (mode == MODE_READ) {
    for (fp = dup->Openlist; fp; fp = fp->Next)
      if (fp->Type == TYPE_FB_MAP && !stricmp(fp->Fname, filename)
                     && fp->Count && fp->Mode == mode)
        break;

    if (trace)
      htrc("Mapping file, fp=%p\n", fp);

  } else
    fp = NULL;

  if (fp) {
    /*******************************************************************/
    /*  File already mapped. Just increment use count and get pointer. */
    /*******************************************************************/
    fp->Count++;
    Memcol[i] = fp->Memory;
    len = fp->Length;
  } else {
    /*******************************************************************/
    /*  Create the mapping file object.                                */
    /*******************************************************************/
    hFile = CreateFileMap(g, filename, &mm, mode, DelRows);

    if (hFile == INVALID_HANDLE_VALUE) {
      DWORD rc = GetLastError();

      if (!(*g->Message))
        sprintf(g->Message, MSG(OPEN_MODE_ERROR),
                "map", (int) rc, filename);
      if (trace)
        htrc("%s\n", g->Message);

      return (mode == MODE_READ && rc == ENOENT)
              ? PushWarning(g, Tdbp) : true;
      } // endif hFile

    /*****************************************************************/
    /*  Get the file size (assuming file is smaller than 4 GB)       */
    /*****************************************************************/
    len = mm.lenL;
    Memcol[i] = (char *)mm.memory;

    if (!len) {             // Empty or deleted file
      CloseFileHandle(hFile);
      ResetTableSize(g, 0, Nrec);
      return false;
      } // endif len

    if (!Memcol[i]) {
      CloseFileHandle(hFile);
      sprintf(g->Message, MSG(MAP_VIEW_ERROR),
                          filename, GetLastError());
      return true;
      } // endif Memory

    if (mode != MODE_DELETE) {
      CloseFileHandle(hFile);                    // Not used anymore
      hFile = INVALID_HANDLE_VALUE;              // For Fblock
      } // endif Mode

    /*******************************************************************/
    /*  Link a Fblock. This make possible to reuse already opened maps */
    /*  and also to automatically unmap them in case of error g->jump. */
    /*  Note: block can already exist for previously closed file.      */
    /*******************************************************************/
    fp = (PFBLOCK)PlugSubAlloc(g, NULL, sizeof(FBLOCK));
    fp->Type = TYPE_FB_MAP;
    fp->Fname = (char*)PlugSubAlloc(g, NULL, strlen(filename) + 1);
    strcpy((char*)fp->Fname, filename);
    fp->Next = dup->Openlist;
    dup->Openlist = fp;
    fp->Count = 1;
    fp->Length = len;
    fp->Memory = Memcol[i];
    fp->Mode = mode;
    fp->File = NULL;
    fp->Handle = hFile;                        // Used for Delete
  } // endif fp

  To_Fbs[i] = fp;                              // Useful when closing

  if (trace)
    htrc("fp=%p count=%d MapView=%p len=%d\n",
          fp, fp->Count, Memcol[i], len);

  return false;
  } // end of MapColumnFile

/***********************************************************************/
/*  Allocate the block buffers for columns used in the query.          */
/*  Give a dummy value (1) to prevent allocating the value block.     */
/*  It will be set pointing into the memory map of the file.           */
/***********************************************************************/
bool VMPFAM::AllocateBuffer(PGLOBAL g)
  {
  PVCTCOL cp;

  if (Tdbp->GetMode() == MODE_DELETE) {
    PCOLDEF cdp = Tdbp->GetDef()->GetCols();

    Clens = (int*)PlugSubAlloc(g, NULL, Ncol * sizeof(int));

    for (int i = 0; cdp && i < Ncol; i++, cdp = cdp->GetNext())
      Clens[i] = cdp->GetClen();

    } // endif mode

  for (cp = (PVCTCOL)Tdbp->GetColumns(); cp; cp = (PVCTCOL)cp->Next)
    if (!cp->IsSpecial()) {            // Not a pseudo column
      cp->Blk = AllocValBlock(g, (void*)1, cp->Buf_Type, Nrec,
                              cp->Format.Length, cp->Format.Prec);
      cp->AddStatus(BUF_MAPPED);
      } // endif IsSpecial

  return false;
  } // end of AllocateBuffer

/***********************************************************************/
/*  Data Base delete line routine for VMP access method.               */
/*  Lines between deleted lines are moved in the mapfile view.         */
/***********************************************************************/
int VMPFAM::DeleteRecords(PGLOBAL g, int irc)
  {
  int  i;
  int m, n;

  if (trace)
    htrc("VMP DeleteDB: irc=%d tobuf=%p Tpos=%p Spos=%p\n",
                        irc, To_Buf, Tpos, Spos);

  if (irc != RC_OK) {
    /*******************************************************************/
    /*  EOF: position Fpos at the top of map position.                 */
    /*******************************************************************/
    Fpos = (Block - 1) * Nrec + Last;
    
    if (trace)
      htrc("Fpos placed at file top=%p\n", Fpos);

  } else     // Fpos is the Deleted line position
    Fpos = CurBlk * Nrec + CurNum;

  if (Tpos == Spos)
    /*******************************************************************/
    /*  First line to delete. Move of eventual preceeding lines is     */
    /*  not required here, just setting of future Spos and Tpos.       */
    /*******************************************************************/
    Tpos = Fpos;                               // Spos is set below
  else if ((n = Fpos - Spos) > 0) {
    /*******************************************************************/
    /*  Non consecutive line to delete. Move intermediate lines.       */
    /*******************************************************************/
    for (i = 0; i < Ncol; i++) {
      m = Clens[i];
      memmove(Memcol[i] + Tpos * m, Memcol[i] + Spos * m, m * n);
      } // endif i

    Tpos += n;

    if (trace)
      htrc("move %d bytes\n", n);

    } // endif n

  if (irc == RC_OK) {
    Spos = Fpos + 1;                               // New start position

    if (trace)
      htrc("after: Tpos=%p Spos=%p\n", Tpos, Spos);

  } else {
    /*******************************************************************/
    /*  Last call after EOF has been reached.                          */
    /*  We must firstly Unmap the view and use the saved file handle   */
    /*  to put an EOF at the end of the copied part of the file.       */
    /*******************************************************************/
    PFBLOCK fp;

    for (i = 0; i < Ncol; i++) {
      fp = To_Fbs[i];
      CloseMemMap(fp->Memory, (size_t)fp->Length);
      fp->Count = 0;                             // Avoid doing it twice

      /*****************************************************************/
      /*  Remove extra records.                                        */
      /*****************************************************************/
      n = Tpos * Clens[i];

#if defined(WIN32)
      DWORD drc = SetFilePointer(fp->Handle, n, NULL, FILE_BEGIN);

      if (drc == 0xFFFFFFFF) {
        sprintf(g->Message, MSG(FUNCTION_ERROR),
                            "SetFilePointer", GetLastError());
        CloseHandle(fp->Handle);
        return RC_FX;
        } // endif

      if (trace)
        htrc("done, Tpos=%p newsize=%d drc=%d\n", Tpos, n, drc);

      if (!SetEndOfFile(fp->Handle)) {
        sprintf(g->Message, MSG(FUNCTION_ERROR),
                            "SetEndOfFile", GetLastError());
        CloseHandle(fp->Handle);
        return RC_FX;
        } // endif

      CloseHandle(fp->Handle);
#else    // UNIX
      if (ftruncate(fp->Handle, (off_t)n)) {
        sprintf(g->Message, MSG(TRUNCATE_ERROR), strerror(errno));
        close(fp->Handle);
        return RC_FX;
        } // endif

      close(fp->Handle);
#endif   // UNIX
      } // endfor i

  } // endif irc

  return RC_OK;                                      // All is correct
  } // end of DeleteRecords

/***********************************************************************/
/*  Data Base close routine for VMP access method.                     */
/***********************************************************************/
void VMPFAM::CloseTableFile(PGLOBAL g)
  {
  if (Tdbp->GetMode() == MODE_DELETE) {
    // Set Block and Nrec values for TDBVCT::MakeBlockValues
    Block = (Tpos > 0) ? (Tpos + Nrec - 1) / Nrec : 0;
    Last = (Tpos + Nrec - 1) % Nrec + 1;
    ResetTableSize(g, Block, Last);
  } else if (Tdbp->GetMode() == MODE_INSERT)
    assert(false);

  for (int i = 0; i < Ncol; i++)
    PlugCloseFile(g, To_Fbs[i]);

  } // end of CloseTableFile

/* -------------------------- Class BGVFAM --------------------------- */

/***********************************************************************/
/*  Implementation of the BGVFAM class.                                */
/***********************************************************************/
// Constructors
BGVFAM::BGVFAM(PVCTDEF tdp) : VCTFAM(tdp)
  {
  Hfile = INVALID_HANDLE_VALUE;
  Tfile = INVALID_HANDLE_VALUE;
  BigDep = NULL;
  } // end of BGVFAM constructor

BGVFAM::BGVFAM(PBGVFAM txfp) : VCTFAM(txfp)
  {
  Hfile = txfp->Hfile;
  Tfile = txfp->Tfile;
  BigDep= txfp->BigDep;
  } // end of BGVFAM copy constructor

/***********************************************************************/
/*  Set current position in a big file.                                */
/***********************************************************************/
bool BGVFAM::BigSeek(PGLOBAL g, HANDLE h, BIGINT pos, bool b)
  {
#if defined(WIN32)
  char          buf[256];
  DWORD         drc, m = (b) ? FILE_END : FILE_BEGIN;
  LARGE_INTEGER of;

  of.QuadPart = pos;
  of.LowPart = SetFilePointer(h, of.LowPart, &of.HighPart, m);

  if (of.LowPart == INVALID_SET_FILE_POINTER &&
           (drc = GetLastError()) != NO_ERROR) {
    FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
                  FORMAT_MESSAGE_IGNORE_INSERTS, NULL, drc, 0,
                  (LPTSTR)buf, sizeof(buf), NULL);
    sprintf(g->Message, MSG(SFP_ERROR), buf);
    return true;
    } // endif
#else   // !WIN32
  if (lseek64(h, pos, (b) ? SEEK_END : SEEK_SET) < 0) {
    sprintf(g->Message, MSG(ERROR_IN_LSK), errno);
    return true;
    } // endif
#endif  // !WIN32

  return false;
  } // end of BigSeek

/***********************************************************************/
/*  Read from a big file.                                              */
/***********************************************************************/
bool BGVFAM::BigRead(PGLOBAL g, HANDLE h, void *inbuf, int req)
  {
  bool rc = false;

#if defined(WIN32)
  DWORD nbr, drc, len = (DWORD)req;
  bool  brc = ReadFile(h, inbuf, len, &nbr, NULL);

  if (trace)
    htrc("after read req=%d brc=%d nbr=%d\n", req, brc, nbr);

  if (!brc || nbr != len) {
    char buf[256];  // , *fn = (h == Hfile) ? To_File : "Tempfile";

    if (brc)
      strcpy(buf, MSG(BAD_BYTE_READ));
    else {
      drc = GetLastError();
      FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
                    FORMAT_MESSAGE_IGNORE_INSERTS, NULL, drc, 0,
                    (LPTSTR)buf, sizeof(buf), NULL);
      } // endelse brc

    sprintf(g->Message, MSG(READ_ERROR), To_File, buf);
    
    if (trace)
      htrc("BIGREAD: %s\n", g->Message);

    rc = true;
    } // endif brc || nbr
#else   // !WIN32
  size_t  len = (size_t)req;
  ssize_t nbr = read(h, inbuf, len);

  if (nbr != (ssize_t)len) {
    const char *fn = (h == Hfile) ? To_File : "Tempfile";

    sprintf(g->Message, MSG(READ_ERROR), fn, strerror(errno));
    
    if (trace)
      htrc("BIGREAD: nbr=%d len=%d errno=%d %s\n",
                     nbr, len, errno, g->Message);

    rc = true;
    } // endif nbr
#endif  // !WIN32

  return rc;
  } // end of BigRead

/***********************************************************************/
/*  Write into a big file.                                             */
/***********************************************************************/
bool BGVFAM::BigWrite(PGLOBAL g, HANDLE h, void *inbuf, int req)
  {
  bool rc = false;

#if defined(WIN32)
  DWORD nbw, drc, len = (DWORD)req;
  bool  brc = WriteFile(h, inbuf, len, &nbw, NULL);

  if (trace)
    htrc("after write req=%d brc=%d nbw=%d\n", req, brc, nbw);

  if (!brc || nbw != len) {
    char buf[256], *fn = (h == Hfile) ? To_File : "Tempfile";

    if (brc)
      strcpy(buf, MSG(BAD_BYTE_NUM));
    else {
      drc = GetLastError();
      FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
                    FORMAT_MESSAGE_IGNORE_INSERTS, NULL, drc, 0,
                    (LPTSTR)buf, sizeof(buf), NULL);
      } // endelse brc

    sprintf(g->Message, MSG(WRITE_STRERROR), fn, buf);

    if (trace)
      htrc("BIGWRITE: nbw=%d len=%d errno=%d %s\n",
                      nbw, len, drc, g->Message);

    rc = true;
    } // endif brc || nbw
#else   // !WIN32
  size_t  len = (size_t)req;
  ssize_t nbw = write(h, inbuf, len);

  if (nbw != (ssize_t)len) {
    const char *fn = (h == Hfile) ? To_File : "Tempfile";

    sprintf(g->Message, MSG(WRITE_STRERROR), fn, strerror(errno));
    
    if (trace)
      htrc("BIGWRITE: nbw=%d len=%d errno=%d %s\n",
                      nbw, len, errno, g->Message);

    rc = true;
    } // endif nbr
#endif  // !WIN32

  return rc;
  } // end of BigWrite

/***********************************************************************/
/*  Get the Headlen, Block and Last info from the file header.         */
/***********************************************************************/
int BGVFAM::GetBlockInfo(PGLOBAL g)
  {
  char      filename[_MAX_PATH];
  int       n;
  bool      b;
  VECHEADER vh;
  HANDLE    h;

  if (Header < 1 || Header > 3 || !MaxBlk) {
    sprintf(g->Message, "Invalid header value %d", Header);
    return -1;
  } else
    n = (Header == 1) ? (int)sizeof(VECHEADER) : 0;

  PlugSetPath(filename, To_File, Tdbp->GetPath());

  if (Header == 2)
    strcat(PlugRemoveType(filename, filename), ".blk");

#if defined(WIN32)
  LARGE_INTEGER len;

  h = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL,
                 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

  if (h != INVALID_HANDLE_VALUE) {
    // Get the size of the file (can be greater than 4 GB)
    len.LowPart = GetFileSize(h, (LPDWORD)&len.HighPart);
    } // endif h

  if (h == INVALID_HANDLE_VALUE || !len.QuadPart) {
#else   // !WIN32
  h = open64(filename, O_RDONLY, 0);

  if (h == INVALID_HANDLE_VALUE || !_filelength(h)) {
#endif  // !WIN32
    // Consider this is a void table
    if (trace)
      htrc("Void table h=%d\n", h);
      
    Last = Nrec; 
    Block = 0;

    if (h != INVALID_HANDLE_VALUE)
      CloseFileHandle(h);

    return n;
  } else if (Header == 3) 
    b =  BigSeek(g, h, -(BIGINT)sizeof(vh), true);
    
  if (BigRead(g, h, &vh, sizeof(vh))) {
    sprintf(g->Message, "Error reading header file %s", filename);
    n = -1;
  } else if (MaxBlk * Nrec != vh.MaxRec) {
    sprintf(g->Message, "MaxRec=%d doesn't match MaxBlk=%d Nrec=%d",
                        vh.MaxRec, MaxBlk, Nrec);
    n = -1;
  } else {
    Block = (vh.NumRec > 0) ? (vh.NumRec + Nrec - 1) / Nrec : 0;
    Last  = (vh.NumRec + Nrec - 1) % Nrec + 1;
    
    if (trace)
      htrc("Block=%d Last=%d\n", Block, Last);
      
  } // endif's

  CloseFileHandle(h);
  return n;
  } // end of GetBlockInfo

/***********************************************************************/
/*  Set the MaxRec and NumRec info in the file header.                 */
/***********************************************************************/
bool BGVFAM::SetBlockInfo(PGLOBAL g)
  {
  char      filename[_MAX_PATH];
  bool      bk, b = false, rc = false;
  VECHEADER vh;
  HANDLE    h = INVALID_HANDLE_VALUE;

  PlugSetPath(filename, To_File, Tdbp->GetPath());

  if (Header != 2) {
    if (Hfile != INVALID_HANDLE_VALUE) {
      h = Hfile;

      if (Header == 1)
        bk = BigSeek(g, h, (BIGINT)0);

    } else
      b = true;

  } else       // Header == 2
    strcat(PlugRemoveType(filename, filename), ".blk");

  if (h == INVALID_HANDLE_VALUE) {
#if defined(WIN32)
    DWORD creation = (b) ? OPEN_EXISTING : TRUNCATE_EXISTING;

    h = CreateFile(filename, GENERIC_READ | GENERIC_WRITE, 0,
                   NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL);

#else   // !WIN32
    int oflag = (b) ?  O_RDWR : O_RDWR | O_TRUNC;

    h = open64(filename, oflag, 0);
#endif  // !WIN32

    if (h == INVALID_HANDLE_VALUE) {
      sprintf(g->Message, "Error opening header file %s", filename);
      return true;
      } // endif h

    } // endif h

  if (Header == 3)
    bk = BigSeek(g, h, -(BIGINT)sizeof(vh), true);

  vh.MaxRec = MaxBlk * Bsize;
  vh.NumRec = (Block - 1) * Nrec + Last;

  if (BigWrite(g, h, &vh, sizeof(vh))) {
    sprintf(g->Message, "Error writing header file %s", filename);
    rc = true;
    } // endif fread

  if (Header == 2 || Hfile == INVALID_HANDLE_VALUE)
    CloseFileHandle(h);

  return rc;
  } // end of SetBlockInfo

/***********************************************************************/
/*  VEC Create an empty file for new Vector formatted tables.          */
/***********************************************************************/
bool BGVFAM::MakeEmptyFile(PGLOBAL g, char *fn)
  {
  // Vector formatted file this will create an empty file of the
  // required length if it does not exists yet.
  char          filename[_MAX_PATH], c = 0;
  int           n = (Header == 1 || Header == 3) ? sizeof(VECHEADER) : 0;

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

#if defined(WIN32)
  char         *p;
  DWORD         rc;
  bool          brc;
  LARGE_INTEGER of;
  HANDLE        h;

  h = CreateFile(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
                           FILE_ATTRIBUTE_NORMAL, NULL);

  if (h == INVALID_HANDLE_VALUE) {
    p = MSG(OPENING);
    goto err;
    } // endif h

  of.QuadPart = (BIGINT)n + (BIGINT)MaxBlk * (BIGINT)Blksize - (BIGINT)1;

  if (trace)
    htrc("MEF: of=%lld n=%d maxblk=%d blksize=%d\n",
               of.QuadPart, n, MaxBlk, Blksize);

  of.LowPart = SetFilePointer(h, of.LowPart,
                                &of.HighPart, FILE_BEGIN);

  if (of.LowPart == INVALID_SET_FILE_POINTER &&
           GetLastError() != NO_ERROR) {
    p = MSG(MAKING);
    goto err;
    } // endif

  brc = WriteFile(h, &c, 1, &rc, NULL);

  if (!brc || rc != 1) {
    p = MSG(WRITING);
    goto err;
    } // endif

  CloseHandle(h);
  return false;

 err:
  rc = GetLastError();
  sprintf(g->Message, MSG(EMPTY_FILE), p, filename);
  FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
                FORMAT_MESSAGE_IGNORE_INSERTS, NULL, rc, 0,
                (LPTSTR)filename, sizeof(filename), NULL);
  strcat(g->Message, filename);

  if (h != INVALID_HANDLE_VALUE)
    CloseHandle(h);

  return true;
#else   // !WIN32
  int    h;
  BIGINT pos;

  h= open64(filename, O_CREAT | O_WRONLY, S_IREAD | S_IWRITE);

  if (h == -1)
    return true;
    
  pos = (BIGINT)n + (BIGINT)MaxBlk * (BIGINT)Blksize - (BIGINT)1;

  if (trace)
    htrc("MEF: pos=%lld n=%d maxblk=%d blksize=%d\n",
               pos, n, MaxBlk, Blksize);

  if (lseek64(h, pos, SEEK_SET) < 0) {
    sprintf(g->Message, MSG(MAKE_EMPTY_FILE), To_File, strerror(errno));
    close(h);
    return true;
    } // endif h

  write(h, &c, 1);     // This actually fills the empty file
  close(h);
  return false;
#endif  // !WIN32
  } // end of MakeEmptyFile

/***********************************************************************/
/*  Vopen function: opens a file using Windows or Unix API's.          */
/***********************************************************************/
bool BGVFAM::OpenTableFile(PGLOBAL g)
  {
  char    filename[_MAX_PATH];
  bool    del = false;
  MODE    mode = Tdbp->GetMode();
  PDBUSER dbuserp = PlgGetUser(g);

  if ((To_Fb && To_Fb->Count) || Hfile != INVALID_HANDLE_VALUE) {
    sprintf(g->Message, MSG(FILE_OPEN_YET), To_File);
    return true;
    } // endif

  /*********************************************************************/
  /*  Update block info if necessary.                                  */
  /*********************************************************************/
  if (Block < 0)
    if ((Headlen = GetBlockInfo(g)) < 0)
      return true;

  PlugSetPath(filename, To_File, Tdbp->GetPath());

  if (trace)
    htrc("OpenTableFile: filename=%s mode=%d Last=%d\n",
                         filename, mode, Last);

#if defined(WIN32)
  DWORD access, creation, share = 0, rc = 0;

  /*********************************************************************/
  /*  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_INSERT:
      if (MaxBlk) {
        if (!Block)
          if (MakeEmptyFile(g, To_File))
            return true;

        // Required to update empty blocks
        access = GENERIC_READ | GENERIC_WRITE;
      } else if (Last == Nrec)
        access = GENERIC_WRITE;
      else
        // Required to update the last block
        access = GENERIC_READ | GENERIC_WRITE;

      creation = OPEN_ALWAYS;
      break;
    case MODE_DELETE:
      if (!Tdbp->GetNext()) {
        // Store the number of deleted lines
        DelRows = Cardinality(g);

        // This will stop the process by
        // causing GetProgMax to return 0.
//      ResetTableSize(g, 0, Nrec);     must be done later
        del = true;

        // This will delete the whole file
        access = GENERIC_READ | GENERIC_WRITE;
        creation = TRUNCATE_EXISTING;
        break;
        } // endif

      // Selective delete, pass thru
    case MODE_UPDATE:
      if ((UseTemp = Tdbp->IsUsingTemp(g)))
        access = GENERIC_READ;
      else
        access = GENERIC_READ | GENERIC_WRITE;

      creation = OPEN_EXISTING;
      break;
    default:
      sprintf(g->Message, MSG(BAD_OPEN_MODE), mode);
      return true;
    } // endswitch

  /*********************************************************************/
  /*  Use specific Windows API functions.                              */
  /*********************************************************************/
  Hfile = CreateFile(filename, access, share, NULL, creation,
                               FILE_ATTRIBUTE_NORMAL, NULL);

  if (Hfile == INVALID_HANDLE_VALUE) {
    rc = GetLastError();
    sprintf(g->Message, MSG(OPEN_ERROR), rc, mode, filename);
    FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
                  FORMAT_MESSAGE_IGNORE_INSERTS, NULL, rc, 0,
                  (LPTSTR)filename, sizeof(filename), NULL);
    strcat(g->Message, filename);
    } // endif Hfile

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

  if (mode == MODE_INSERT) {
    /*******************************************************************/
    /* In Insert mode we must position the cursor at end of file.      */
    /*******************************************************************/
    LARGE_INTEGER of;

    of.QuadPart = (BIGINT)0;
    of.LowPart = SetFilePointer(Hfile, of.LowPart,
                                      &of.HighPart, FILE_END);

    if (of.LowPart == INVALID_SET_FILE_POINTER &&
       (rc = GetLastError()) != NO_ERROR) {
      sprintf(g->Message, MSG(ERROR_IN_SFP), rc);
      CloseHandle(Hfile);
      Hfile = INVALID_HANDLE_VALUE;
      } // endif

    } // endif Mode

#else   // UNIX
  /*********************************************************************/
  /*  The open() function has a transitional interface for 64-bit      */
  /*  file  offsets. Note that using open64() is equivalent to using   */
  /*  open() with O_LARGEFILE set in oflag (see Xopen in tabfix.cpp).  */
  /*********************************************************************/
  int    rc = 0;
  int    oflag;
  mode_t pmd = 0;

  /*********************************************************************/
  /*  Create the file object according to access mode                  */
  /*********************************************************************/
  switch (mode) {
    case MODE_READ:
      oflag = O_RDONLY;
      break;
    case MODE_INSERT:
      if (MaxBlk) {
        if (!Block)
          if (MakeEmptyFile(g, To_File))
            return true;

        // Required to update empty blocks
        oflag = O_RDWR;
      } else if (Last == Nrec)
        oflag = O_WRONLY | O_CREAT | O_APPEND;
      else
        // Required to update the last block
        oflag = O_RDWR | O_CREAT | O_APPEND;

      pmd = S_IREAD | S_IWRITE;
      break;
    case MODE_DELETE:
      // This is temporary until a partial delete is implemented
      if (!Tdbp->GetNext()) {
        // Store the number of deleted lines
        DelRows = Cardinality(g);
        del = true;

        // This will delete the whole file and provoque ReadDB to
        // return immediately.
        oflag = O_RDWR | O_TRUNC;
        strcpy(g->Message, MSG(NO_VCT_DELETE));
        break;
        } // endif

      // Selective delete, pass thru
    case MODE_UPDATE:
      UseTemp = Tdbp->IsUsingTemp(g);
      oflag = (UseTemp) ? O_RDONLY : O_RDWR;
      break;
    default:
      sprintf(g->Message, MSG(BAD_OPEN_MODE), mode);
      return true;
    } // endswitch

  Hfile = open64(filename, oflag, pmd); // Enable file size > 2G

  if (Hfile == INVALID_HANDLE_VALUE) {
    rc = errno;
    sprintf(g->Message, MSG(OPEN_ERROR), rc, mode, filename);
    strcat(g->Message, strerror(errno));
    } // endif Hfile

  if (trace)
    htrc(" rc=%d oflag=%p mode=%p handle=%d fn=%s\n",
           rc, oflag, mode, Hfile, filename);
#endif  // UNIX

  if (!rc) {
    if (!To_Fb) {
      To_Fb = (PFBLOCK)PlugSubAlloc(g, NULL, sizeof(FBLOCK));
      To_Fb->Fname = To_File;
      To_Fb->Type = TYPE_FB_HANDLE;
      To_Fb->Memory = NULL;
      To_Fb->Length = 0;
      To_Fb->File = NULL;
      To_Fb->Next = dbuserp->Openlist;
      dbuserp->Openlist = To_Fb;
      } // endif To_Fb

    To_Fb->Count = 1;
    To_Fb->Mode = mode;
    To_Fb->Handle = Hfile;

    if (trace)
      htrc("File %s is open in mode %d\n", filename, mode);

    if (del)
      // This will stop the process by
      // causing GetProgMax to return 0.
      return ResetTableSize(g, 0, Nrec);

    /*********************************************************************/
    /*  Allocate the table and column block buffers.                     */
    /*********************************************************************/
    return AllocateBuffer(g);
  } else
    return (mode == MODE_READ && rc == ENOENT)
            ? PushWarning(g, Tdbp) : true;

  } // end of OpenTableFile

/***********************************************************************/
/*  Allocate the block buffers for columns used in the query.          */
/***********************************************************************/
bool BGVFAM::AllocateBuffer(PGLOBAL g)
  {
  MODE    mode = Tdbp->GetMode();
  PDOSDEF defp = (PDOSDEF)Tdbp->GetDef();
  PCOLDEF cdp;
  PVCTCOL cp = (PVCTCOL)Tdbp->GetColumns();

  if (mode == MODE_INSERT) {
    if (!NewBlock) {
      // Not reopening after inserting the last block
      bool chk = PlgGetUser(g)->Check & CHK_TYPE;

      NewBlock = (char*)PlugSubAlloc(g, NULL, Blksize);

      for (cdp = defp->GetCols(); cdp; cdp = cdp->GetNext())
        memset(NewBlock + Nrec * cdp->GetPoff(),
              (IsTypeNum(cdp->GetType()) ? 0 : ' '),
                          Nrec * cdp->GetClen());

      for (; cp; cp = (PVCTCOL)cp->Next)
        cp->Blk = AllocValBlock(g, NewBlock + Nrec * cp->Deplac,
                                cp->Buf_Type, Nrec, cp->Format.Length,
                                                    cp->Format.Prec, chk);

      InitInsert(g);    // Initialize inserting

      // Currently we don't use a temporary file for inserting
      Tfile = Hfile;
      } // endif NewBlock

  } else {
    if (UseTemp || mode == MODE_DELETE) {
      // Allocate all that is needed to move lines
      int i = 0;

      if (!Ncol)
        for (cdp = defp->GetCols(); cdp; cdp = cdp->GetNext())
          Ncol++;

      if (MaxBlk)
        BigDep = (BIGINT*)PlugSubAlloc(g, NULL, Ncol * sizeof(BIGINT));
      else
        Deplac = (int*)PlugSubAlloc(g, NULL, Ncol * sizeof(int));

      Clens = (int*)PlugSubAlloc(g, NULL, Ncol * sizeof(int));
      Isnum = (bool*)PlugSubAlloc(g, NULL, Ncol * sizeof(bool));

      for (cdp = defp->GetCols(); cdp; i++, cdp = cdp->GetNext()) {
        if (MaxBlk)
          BigDep[i] = (BIGINT)Headlen
                    + (BIGINT)(cdp->GetPoff() * Nrec) * (BIGINT)MaxBlk;
        else
          Deplac[i] = cdp->GetPoff() * Nrec;

        Clens[i] = cdp->GetClen();
        Isnum[i] = IsTypeNum(cdp->GetType());
        Buflen = max(Buflen, cdp->GetClen());
        } // endfor cdp

      if (!UseTemp || MaxBlk) {
        Buflen *= Nrec;
        To_Buf = (char*)PlugSubAlloc(g, NULL, Buflen);
      } else
        NewBlock = (char*)PlugSubAlloc(g, NULL, Blksize);

      } // endif mode

    for (; cp; cp = (PVCTCOL)cp->Next)
      if (!cp->IsSpecial())            // Not a pseudo column
        cp->Blk = AllocValBlock(g, NULL, cp->Buf_Type, Nrec,
                                cp->Format.Length, cp->Format.Prec);

  } //endif mode

  return false;
  } // end of AllocateBuffer

/***********************************************************************/
/*  Data Base write routine for huge VCT access method.                */
/***********************************************************************/
int BGVFAM::WriteBuffer(PGLOBAL g)
  {
  if (trace)
    htrc("BGV WriteDB: R%d Mode=%d CurNum=%d CurBlk=%d\n",
          Tdbp->GetTdb_No(), Tdbp->GetMode(), CurNum, CurBlk);

  if (Tdbp->GetMode() == MODE_UPDATE) {
    // Mode Update is done in ReadDB, we just initialize it here
    if (Tfile == INVALID_HANDLE_VALUE) {
      if (UseTemp) {
        if (OpenTempFile(g))
          return RC_FX;

        // Most of the time, not all table columns are updated.
        // This why we must completely pre-fill the temporary file.
        Fpos = (MaxBlk) ? (Block - 1) * Nrec + Last
                        : Block * Nrec;   // To write last lock

        if (MoveIntermediateLines(g))
          return RC_FX;

      } else
        Tfile = Hfile;

      } // endif Tfile

  } else {
    // Mode Insert
    if (MaxBlk && CurBlk == MaxBlk) {
      strcpy(g->Message, MSG(TRUNC_BY_ESTIM));
      return RC_EF;       // Too many lines for a Vector formatted table
      } // endif MaxBlk

    if (Closing || ++CurNum == Nrec) {
      PVCTCOL cp = (PVCTCOL)Tdbp->GetColumns();

      if (!AddBlock) {
        // Write back the updated last block values
        for (; cp; cp = (PVCTCOL)cp->Next)
          cp->WriteBlock(g);

        if (!Closing && !MaxBlk) {
          // Close the VCT file and reopen it in mode Insert
//#if defined(WIN32)  //OB
//          CloseHandle(Hfile);
//#else    // UNIX
//          close(Hfile);
//#endif   // UNIX
          CloseFileHandle(Hfile);
          Hfile = INVALID_HANDLE_VALUE;
          To_Fb->Count = 0;
          Last = Nrec;               // Tested in OpenTableFile

          if (OpenTableFile(g)) {
            Closing = true;          // Tell CloseDB of error
            return RC_FX;
            } // endif Vopen

          AddBlock = true;
          } // endif Closing

      } else {
        // Here we must add a new block to the VCT file
        if (Closing)
          // Reset the overwritten columns for last block extra records
          for (; cp; cp = (PVCTCOL)cp->Next)
            memset(NewBlock + Nrec * cp->Deplac + Last * cp->Clen,
                   (cp->Buf_Type == TYPE_STRING) ? ' ' : '\0',
                   (Nrec - Last) * cp->Clen);

        if (BigWrite(g, Hfile, NewBlock, Blksize))
          return RC_FX;

      } // endif AddBlock

      if (!Closing) {
        CurBlk++;
        CurNum = 0;
        } // endif Closing

      } // endif

    } // endif Mode

  return RC_OK;
  } // end of WriteBuffer

/***********************************************************************/
/*  Data Base delete line routine for BGVFAM access method.            */
/***********************************************************************/
int BGVFAM::DeleteRecords(PGLOBAL g, int irc)
  {
  bool eof = false;

  /*********************************************************************/
  /*  There is an alternative here depending on UseTemp:               */
  /*  1 - use a temporary file in which are copied all not deleted     */
  /*      lines, at the end the original file will be deleted and      */
  /*      the temporary file renamed to the original file name.        */
  /*  2 - directly move the not deleted lines inside the original      */
  /*      file, and at the end erase all trailing records.             */
  /*********************************************************************/
  if (trace)
    htrc("BGV DeleteDB: irc=%d UseTemp=%d Fpos=%d Tpos=%d Spos=%d\n",
                        irc, UseTemp, Fpos, Tpos, Spos);

  if (irc != RC_OK) {
    /*******************************************************************/
    /*  EOF: position Fpos at the end-of-file position.                */
    /*******************************************************************/
    Fpos = (Block - 1) * Nrec + Last;
    
    if (trace)
      htrc("Fpos placed at file end=%d\n", Fpos);

    eof = UseTemp && !MaxBlk;
  } else     // Fpos is the deleted line position
    Fpos = CurBlk * Nrec + CurNum;

  if (Tpos == Spos) {
    if (UseTemp) {
      /*****************************************************************/
      /*  Open the temporary file, Spos is at the beginning of file.   */
      /*****************************************************************/
      if (OpenTempFile(g))
        return RC_FX;

    } else {
      /*****************************************************************/
      /*  Move of eventual preceeding lines is not required here.      */
      /*  Set the target file as being the source file itself.         */
      /*  Set the future Tpos, and give Spos a value to block copying. */
      /*****************************************************************/
      Tfile = Hfile;
      Spos = Tpos = Fpos;
    } // endif UseTemp

    } // endif Tpos == Spos

  /*********************************************************************/
  /*  Move any intermediate lines.                                     */
  /*********************************************************************/
  if (MoveIntermediateLines(g, &eof))
    return RC_FX;

  if (irc == RC_OK) {
#ifdef _DEBUG
    assert(Spos == Fpos);
#endif
    Spos++;          // New start position is on next line

    if (trace)
      htrc("after: Tpos=%d Spos=%d\n", Tpos, Spos);

  } else {
    /*******************************************************************/
    /*  Last call after EOF has been reached.                          */
    /*******************************************************************/
    Block = (Tpos > 0) ? (Tpos + Nrec - 1) / Nrec : 0;
    Last = (Tpos + Nrec - 1) % Nrec + 1;

    if (!UseTemp) {    // The UseTemp case is treated in CloseTableFile
      if (!MaxBlk) {
        if (Last < Nrec)            // Clean last block
          if (CleanUnusedSpace(g))
            return RC_FX;

        /***************************************************************/
        /*  Remove extra records.                                      */
        /***************************************************************/
#if defined(WIN32)
        BIGINT pos = (BIGINT)Block * (BIGINT)Blksize;

        if (BigSeek(g, Hfile, pos))
          return RC_FX;

        if (!SetEndOfFile(Hfile)) {
          DWORD drc = GetLastError();

          sprintf(g->Message, MSG(SETEOF_ERROR), drc);
          return RC_FX;
          } // endif error
#else   // !WIN32
        if (ftruncate64(Hfile, (BIGINT)(Tpos * Lrecl))) {
          sprintf(g->Message, MSG(TRUNCATE_ERROR), strerror(errno));
          return RC_FX;
          } // endif
#endif  // !WIN32
      } else // MaxBlk
        // Clean the unused space in the file, this is required when
        // inserting again with a partial column list.
        if (CleanUnusedSpace(g))
          return RC_FX;

      if (ResetTableSize(g, Block, Last))
       return RC_FX;

      } // endif UseTemp

  } // endif irc

  return RC_OK;                                      // All is correct
  } // end of DeleteRecords

/***********************************************************************/
/*  Open a temporary file used while updating or deleting.             */
/***********************************************************************/
bool BGVFAM::OpenTempFile(PGLOBAL g)
  {
  char   *tempname;
  PDBUSER dup = PlgGetUser(g);

  /*********************************************************************/
  /*  Open the temporary file, Spos is at the beginning of file.       */
  /*********************************************************************/
  tempname = (char*)PlugSubAlloc(g, NULL, _MAX_PATH);
  PlugSetPath(tempname, To_File, Tdbp->GetPath());
  strcat(PlugRemoveType(tempname, tempname), ".t");

  if (!MaxBlk)
    remove(tempname);       // Be sure it does not exist yet
  else if (MakeEmptyFile(g, tempname))
    return true;

#if defined(WIN32)
  DWORD access = (MaxBlk) ? OPEN_EXISTING : CREATE_NEW;

  Tfile = CreateFile(tempname, GENERIC_WRITE, 0, NULL,
                     access, FILE_ATTRIBUTE_NORMAL, NULL);

  if (Tfile == INVALID_HANDLE_VALUE) {
    DWORD rc = GetLastError();
    sprintf(g->Message, MSG(OPEN_ERROR), rc, MODE_DELETE, tempname);
    FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
              FORMAT_MESSAGE_IGNORE_INSERTS, NULL, rc, 0,
              (LPTSTR)tempname, _MAX_PATH, NULL);
    strcat(g->Message, tempname);
    return true;
    } // endif Tfile
#else    // UNIX
  int oflag = (MaxBlk) ? O_WRONLY : O_WRONLY | O_TRUNC;

  Tfile = open64(tempname, oflag, S_IWRITE);

  if (Tfile == INVALID_HANDLE_VALUE) {
    int rc = errno;
    sprintf(g->Message, MSG(OPEN_ERROR), rc, MODE_INSERT, tempname);
    strcat(g->Message, strerror(errno));
    return true;
    } //endif Tfile
#endif   // UNIX

  To_Fbt = (PFBLOCK)PlugSubAlloc(g, NULL, sizeof(FBLOCK));
  To_Fbt->Fname = tempname;
  To_Fbt->Type = TYPE_FB_HANDLE;
  To_Fbt->Memory = NULL;
  To_Fbt->Length = 0;
  To_Fbt->File = NULL;
  To_Fbt->Next = dup->Openlist;
  To_Fbt->Count = 1;
  To_Fbt->Mode = MODE_INSERT;
  To_Fbt->Handle = Tfile;
  dup->Openlist = To_Fbt;
  return false;
  } // end of OpenTempFile

/***********************************************************************/
/*  Move intermediate deleted or updated lines.                        */
/***********************************************************************/
bool BGVFAM::MoveIntermediateLines(PGLOBAL g, bool *b)
  {
  int    i, n, req, dep;
  bool   eof = (b) ? *b : false;
  BIGINT pos;

  for (n = Fpos - Spos; n > 0 || eof; n -= req) {
    /*******************************************************************/
    /*  Non consecutive line to delete. Move intermediate lines.       */
    /*******************************************************************/
    if (!MaxBlk)
      req = (DWORD)min(n, Nrec - max(Spos % Nrec, Tpos % Nrec));
    else
      req = (DWORD)min(n, Nrec);

    if (req) for (i = 0; i < Ncol; i++) {
      if (!MaxBlk) {
        if (UseTemp)
          To_Buf = NewBlock + Deplac[i] + (Tpos % Nrec) * Clens[i];

        pos = (BIGINT)Deplac[i] + (BIGINT)((Spos % Nrec) * Clens[i])
            + (BIGINT)(Spos / Nrec) * (BIGINT)Blksize;
      } else
        pos = BigDep[i] + (BIGINT)Spos * (BIGINT)Clens[i];

      if (BigSeek(g, Hfile, pos))
        return true;

      if (BigRead(g, Hfile, To_Buf, req * Clens[i]))
        return true;

      if (!UseTemp || MaxBlk) {
        if (!MaxBlk)
          pos = (BIGINT)Deplac[i] + (BIGINT)((Tpos % Nrec) * Clens[i])
              + (BIGINT)(Tpos / Nrec) * (BIGINT)Blksize;
        else
          pos = BigDep[i] + (BIGINT)Tpos * (BIGINT)Clens[i];

        if (BigSeek(g, Tfile, pos))
          return true;

        if (BigWrite(g, Tfile, To_Buf, req * Clens[i]))
          return true;

        } // endif UseTemp

      } // endfor i

    Tpos += (int)req;
    Spos += (int)req;

    if (UseTemp && !MaxBlk && (!(Tpos % Nrec) || (eof && Spos == Fpos))) {
      // Write the full or last block to the temporary file
      if ((dep = Nrec - (Tpos % Nrec)) < Nrec)
        // Clean the last block in case of future insert, must be
        // done here because Tfile was open in write only.
        for (i = 0; i < Ncol; i++) {
          To_Buf = NewBlock + Deplac[i] + (Tpos % Nrec) * Clens[i];
          memset(To_Buf, (Isnum[i]) ? 0 : ' ', dep * Clens[i]);
          } // endfor i

      if (BigWrite(g, Tfile, NewBlock, Blksize))
        return true;

      if (Spos == Fpos)
        eof = false;

      } // endif Usetemp...

    if (trace)
      htrc("loop: Tpos=%d Spos=%d\n", Tpos, Spos);

    } // endfor n

  return false;
  } // end of MoveIntermediateLines

/***********************************************************************/
/*  Clean deleted space in a huge VCT or Vec table file.               */
/***********************************************************************/
bool BGVFAM::CleanUnusedSpace(PGLOBAL g)
  {
  int    i;
  int   n;
  BIGINT pos, dep;

  if (!MaxBlk) {
    /*******************************************************************/
    /*  Clean last block of the VCT table file.                        */
    /*******************************************************************/
    assert(!UseTemp); // This case is handled in MoveIntermediateLines

    if (!(n = Nrec - Last))
      return false;

    dep = (BIGINT)((Block - 1) * Blksize);

    for (i = 0; i < Ncol; i++) {
      memset(To_Buf, (Isnum[i]) ? 0 : ' ', n * Clens[i]);
      pos = dep + (BIGINT)(Deplac[i] + Last * Clens[i]);

      if (BigSeek(g, Hfile, pos))
        return true;

      if (BigWrite(g, Hfile, To_Buf, n * Clens[i]))
        return true;

      } // endfor i

  } else {
    int  req;

    memset(To_Buf, 0, Buflen);

    for (n = Fpos - Tpos; n > 0; n -= req) {
      /*****************************************************************/
      /*  Fill VEC file remaining lines with 0's.                      */
      /*  This seems to work even column blocks have been made with    */
      /*  Blanks = true. Perhaps should it be set to false for VEC.    */
      /*****************************************************************/
      req = min(n, Nrec);

      for (i = 0; i < Ncol; i++) {
        pos = BigDep[i] + (BIGINT)Tpos * (BIGINT)Clens[i];

        if (BigSeek(g, Tfile, pos))
          return true;

        if (BigWrite(g, Tfile, To_Buf, req * Clens[i]))
          return true;

        } // endfor i

      Tpos += req;
      } // endfor n

  } // endif MaxBlk

  return false;
  } // end of CleanUnusedSpace

/***********************************************************************/
/*  Data Base close routine for huge VEC access method.                */
/***********************************************************************/
void BGVFAM::CloseTableFile(PGLOBAL g)
  {
  int  rc = 0, wrc = RC_OK;
  MODE mode = Tdbp->GetMode();

  if (mode == MODE_INSERT) {
    if (Closing)
      wrc = RC_FX;                  // Last write was in error
    else
      if (CurNum) {
        // Some more inserted lines remain to be written
        Last = CurNum;
        Block = CurBlk + 1;
        Closing = true;
        wrc = WriteBuffer(g);
      } else {
        Last = Nrec;
        Block = CurBlk;
        wrc = RC_OK;
      } // endif CurNum

    if (wrc != RC_FX) {
      rc = ResetTableSize(g, Block, Last);
    } else if (AddBlock) {
      // Last block was not written
      rc = ResetTableSize(g, CurBlk, Nrec);
      longjmp(g->jumper[g->jump_level], 44);
    } // endif

  } else if (mode == MODE_UPDATE) {
    // Write back to file any pending modifications
    for (PVCTCOL colp = (PVCTCOL)((PTDBVCT)Tdbp)->GetSetCols();
                 colp; colp = (PVCTCOL)colp->Next)
      colp->WriteBlock(g);

    if (UseTemp && Tfile) {
      rc = RenameTempFile(g);
      Hfile = Tfile = INVALID_HANDLE_VALUE;

      if (Header)
        // Header must be set because it was not set in temp file
        rc = SetBlockInfo(g);

      } // endif UseTemp

  } else if (mode == MODE_DELETE && UseTemp && Tfile) {
    if (MaxBlk)
      rc = CleanUnusedSpace(g);

    if ((rc = RenameTempFile(g)) != RC_FX) {
      Hfile = Tfile = INVALID_HANDLE_VALUE;    // For SetBlockInfo
      rc = ResetTableSize(g, Block, Last);
      } // endif rc

  } // endif's mode

  if (Hfile != INVALID_HANDLE_VALUE)
    rc = PlugCloseFile(g, To_Fb);

  if (trace)
    htrc("BGV CloseTableFile: closing %s wrc=%d rc=%d\n",
          To_File, wrc, rc);

  Hfile = INVALID_HANDLE_VALUE;
  } // end of CloseDB

/***********************************************************************/
/*  Rewind routine for huge VCT access method.                         */
/***********************************************************************/
void BGVFAM::Rewind(void)
  {
  // In mode update we need to read Set Column blocks
  if (Tdbp->GetMode() == MODE_UPDATE)
    OldBlk = -1;

  // Initialize so block optimization is called for 1st block
  CurBlk = -1;
  CurNum = Nrec - 1;

#if 0 // This is probably unuseful as the file is directly accessed
#if defined(WIN32)  //OB
  SetFilePointer(Hfile, 0, NULL, FILE_BEGIN);
#else    // UNIX
  lseek64(Hfile, 0, SEEK_SET);
#endif   // UNIX
#endif // 0
  } // end of Rewind

/***********************************************************************/
/*  ReadBlock: Read column values from current block.                  */
/***********************************************************************/
bool BGVFAM::ReadBlock(PGLOBAL g, PVCTCOL colp)
  {
  BIGINT pos;

  /*********************************************************************/
  /*  Calculate the offset and size of the block to read.              */
  /*********************************************************************/
  if (MaxBlk)                                 // File has Vector format
    pos = (BIGINT)Nrec * ((BIGINT)colp->Deplac * (BIGINT)MaxBlk
        + (BIGINT)colp->Clen * (BIGINT)CurBlk) + (BIGINT)Headlen;
  else                                        // Old VCT format
    pos = (BIGINT)Nrec * ((BIGINT)colp->Deplac
        + (BIGINT)Lrecl * (BIGINT)CurBlk);

  if (trace)
    htrc("RB: offset=%lld Nrec=%d Deplac=%d Lrecl=%d CurBlk=%d MaxBlk=%d\n",
          pos, Nrec, colp->Deplac, Lrecl, CurBlk, MaxBlk);

  if (BigSeek(g, Hfile, pos))
    return true;

  if (BigRead(g, Hfile, colp->Blk->GetValPointer(), colp->Clen * Nrec))
    return true;

  if (trace)
    num_read++;

  return false;
  } // end of ReadBlock

/***********************************************************************/
/*  WriteBlock: Write back current column values for one block.        */
/*  Note: the test of Status is meant to prevent physical writing of   */
/*  the block during the checking loop in mode Update. It is set to    */
/*  BUF_EMPTY when reopening the table between the two loops.          */
/***********************************************************************/
bool BGVFAM::WriteBlock(PGLOBAL g, PVCTCOL colp)
  {
  int    len;
  BIGINT pos;

  /*********************************************************************/
  /*  Calculate the offset and size of the block to write.             */
  /*********************************************************************/
  if (MaxBlk)                               // File has Vector format
    pos = (BIGINT)Nrec * ((BIGINT)colp->Deplac * (BIGINT)MaxBlk
        + (BIGINT)colp->Clen * (BIGINT)colp->ColBlk) + (BIGINT)Headlen;
  else                                      // Old VCT format
    pos = (BIGINT)Nrec * ((BIGINT)colp->Deplac
        + (BIGINT)Lrecl * (BIGINT)colp->ColBlk);

  if (trace)
    htrc("WB: offset=%lld Nrec=%d Deplac=%d Lrecl=%d ColBlk=%d\n",
          pos, Nrec, colp->Deplac, Lrecl, colp->ColBlk);

  if (BigSeek(g, Tfile, pos))
    return true;

//len = colp->Clen * Nrec;    see comment in VCTFAM
  len = colp->Clen * ((Tdbp->GetMode() == MODE_INSERT) ? CurNum : Nrec);

  if (BigWrite(g, Tfile, colp->Blk->GetValPointer(), len))
    return true;

  return false;
  } // end of WriteBlock

/* ----------------------- End of FilAMVct --------------------------- */
