/************* TabDos C++ Program Source Code File (.CPP) **************/
/* PROGRAM NAME: TABDOS                                                */
/* -------------                                                       */
/*  Version 4.8                                                        */
/*                                                                     */
/* COPYRIGHT:                                                          */
/* ----------                                                          */
/*  (C) Copyright to the author Olivier BERTRAND          1998-2012    */
/*                                                                     */
/* WHAT THIS PROGRAM DOES:                                             */
/* -----------------------                                             */
/*  This program are the DOS tables classes.                           */
/*                                                                     */
/***********************************************************************/

/***********************************************************************/
/*  Include relevant sections of the System header files.              */
/***********************************************************************/
#include "my_global.h"
#if defined(WIN32)
#include <io.h>
#include <sys\timeb.h>                   // For testing only
#include <fcntl.h>
#include <errno.h>
#if defined(__BORLANDC__)
#define __MFC_COMPAT__                   // To define min/max as macro
#endif   // __BORLANDC__
//#include <windows.h>
#else   // !WIN32
#if defined(UNIX)
#include <errno.h>
#include <unistd.h>
#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.  */
/*  filamtxt.h  is header containing the file AM classes declarations. */
/***********************************************************************/
#include "global.h"
#include "osutil.h"
#include "plgdbsem.h"
#include "catalog.h"
#include "mycat.h"
#include "xindex.h"
#include "filamap.h"
#include "filamfix.h"
#include "filamdbf.h"
#if defined(ZIP_SUPPORT)
#include "filamzip.h"
#endif   // ZIP_SUPPORT
#include "tabdos.h"
#include "tabfix.h"
#include "tabmul.h"

#define PLGINI    "plugdb.ini"          // Configuration settings file

#if defined(UNIX)
#define _fileno fileno
#define _O_RDONLY O_RDONLY
#endif

/***********************************************************************/
/*  DB static variables.                                               */
/***********************************************************************/
int num_read, num_there, num_eq[2];                 // Statistics
extern "C" char plgini[_MAX_PATH];
extern "C" int  trace;

/***********************************************************************/
/*  Min and Max blocks contains zero ended fields (blank = false).     */
/*  No conversion of block values (check = true).                      */
/***********************************************************************/
PVBLK AllocValBlock(PGLOBAL, void *, int, int, int len = 0, int prec = 0,
                    bool check = true, bool blank = false);

/* --------------------------- Class DOSDEF -------------------------- */

/***********************************************************************/
/*  Constructor.                                                       */
/***********************************************************************/
DOSDEF::DOSDEF(void)
  {
  Pseudo = 3;
  Fn = NULL;
  Ofn = NULL;
  To_Indx = NULL;
  Recfm = RECFM_VAR;
  Mapped = false;
  Padded = false;
  Huge = false;
  Accept = false;
  Eof = false;
  To_Pos = NULL;
  Compressed = 0;
  Lrecl = 0;
  AvgLen = 0;
  Block = 0;
  Last = 0;
  Blksize = 0;
  Maxerr = 0;
  ReadMode = 0;
  Ending = 0;
//Mtime = 0;
  } // end of DOSDEF constructor

/***********************************************************************/
/*  DeleteTableFile: Delete DOS/UNIX table files using platform API.   */
/*  If the table file is protected (declared as read/only) we still    */
/*  erase the the eventual optimize and index files but return true.   */
/***********************************************************************/
bool DOSDEF::DeleteTableFile(PGLOBAL g)
  {
  char    filename[_MAX_PATH];
  bool    rc = false;

  // Now delete the table file itself if not protected
  if (!IsReadOnly()) {
    rc = Erase(filename);
  } else
    rc =true;

  return rc;                               // Return true if error
  } // end of DeleteTableFile

/***********************************************************************/
/*  Erase: This was made a separate routine because a strange thing    */
/*  happened when DeleteTablefile was defined for the VCTDEF class:    */
/*  when called from Catalog, the DOSDEF routine was still called even */
/*  when the class was VCTDEF. It also minimizes the specific code.    */
/***********************************************************************/
bool DOSDEF::Erase(char *filename)
  {
  bool rc;

  PlugSetPath(filename, Fn, GetPath());
#if defined(WIN32)
  rc = !DeleteFile(filename);
#else    // UNIX
  rc = remove(filename);
#endif   // UNIX

  return rc;                                  // Return true if error
  } // end of Erase

/***********************************************************************/
/*  DeleteIndexFile: Delete DOS/UNIX index file(s) using platform API. */
/***********************************************************************/
bool DOSDEF::DeleteIndexFile(PGLOBAL g, PIXDEF pxdf)
  {
  char   *ftype;
  char    filename[_MAX_PATH];
  bool    sep, rc = false;

  if (!To_Indx)
    return false;           // No index

  // If true indexes are in separate files
  sep = Cat->GetBoolCatInfo("SepIndex", false); 

  if (!sep && pxdf) {
    strcpy(g->Message, MSG(NO_RECOV_SPACE));
    return true;
    } // endif sep

  switch (Recfm) {
    case RECFM_VAR: ftype = ".dnx"; break;
    case RECFM_FIX: ftype = ".fnx"; break;
    case RECFM_BIN: ftype = ".bnx"; break;
    case RECFM_VCT: ftype = ".vnx"; break;
    case RECFM_DBF: ftype = ".dbx"; break;
    default:
      sprintf(g->Message, MSG(BAD_RECFM_VAL), Recfm);
      return true;
    } // endswitch Ftype

  /*********************************************************************/
  /*  Check for existence of an index file.                            */
  /*********************************************************************/
  if (sep) {
    // Indexes are save in separate files
#if !defined(UNIX)
    char drive[_MAX_DRIVE];
#else
    char *drive = NULL;
#endif
    char direc[_MAX_DIR];
    char fname[_MAX_FNAME];

    for (; pxdf; pxdf = pxdf->GetNext()) {
      _splitpath(Ofn, drive, direc, fname, NULL);
      strcat(strcat(fname, "_"), pxdf->GetName());
      _makepath(filename, drive, direc, fname, ftype);
      PlugSetPath(filename, filename, GetPath());
#if defined(WIN32)
      rc |= !DeleteFile(filename);
#else    // UNIX
      rc |= remove(filename);
#endif   // UNIX
      } // endfor pxdf

  } else {  // !sep
    // Drop all indexes, delete the common file
    PlugSetPath(filename, Ofn, GetPath());
    strcat(PlugRemoveType(filename, filename), ftype);
#if defined(WIN32)
    rc = !DeleteFile(filename);
#else    // UNIX
    rc = remove(filename);
#endif   // UNIX
  } // endif sep

  if (rc)
    sprintf(g->Message, MSG(DEL_FILE_ERR), filename);

  return rc;                        // Return true if error
  } // end of DeleteIndexFile

/***********************************************************************/
/*  DefineAM: define specific AM block values from XDB file.           */
/***********************************************************************/
bool DOSDEF::DefineAM(PGLOBAL g, LPCSTR am, int poff)
  {
  char   buf[8];
  bool   map = (am && (*am == 'M' || *am == 'm'));
  LPCSTR dfm = (am && (*am == 'F' || *am == 'f')) ? "F"
             : (am && (*am == 'B' || *am == 'b')) ? "B"
             : (am && !stricmp(am, "DBF"))        ? "D" : "V";

  Desc = Fn = Cat->GetStringCatInfo(g, "Filename", NULL);
  Ofn = Cat->GetStringCatInfo(g, "Optname", Fn);
  Cat->GetCharCatInfo("Recfm", (PSZ)dfm, buf, sizeof(buf));
  Recfm = (toupper(*buf) == 'F') ? RECFM_FIX :
          (toupper(*buf) == 'B') ? RECFM_BIN :
          (toupper(*buf) == 'D') ? RECFM_DBF : RECFM_VAR;
  Lrecl = Cat->GetIntCatInfo("Lrecl", 0);

  if (Recfm != RECFM_DBF)
    Compressed = Cat->GetIntCatInfo("Compressed", 0);

  Mapped = Cat->GetBoolCatInfo("Mapped", map);
  Block = Cat->GetIntCatInfo("Blocks", 0);
  Last = Cat->GetIntCatInfo("Last", 0);
  Ending = Cat->GetIntCatInfo("Ending", CRLF);

  if (Recfm == RECFM_FIX || Recfm == RECFM_BIN) {
    Huge = Cat->GetBoolCatInfo("Huge", Cat->GetDefHuge());
    Padded = Cat->GetBoolCatInfo("Padded", false);
    Blksize = Cat->GetIntCatInfo("Blksize", 0);
    Eof = (Cat->GetIntCatInfo("EOF", 0) != 0);
  } else if (Recfm == RECFM_DBF) {
    Maxerr = Cat->GetIntCatInfo("Maxerr", 0);
    Accept = (Cat->GetIntCatInfo("Accept", 0) != 0);
    ReadMode = Cat->GetIntCatInfo("Readmode", 0);
  } else // (Recfm == RECFM_VAR)
    AvgLen = Cat->GetIntCatInfo("Avglen", 0);

  // Ignore wrong Index definitions for catalog commands
  return (Cat->GetIndexInfo(g, this) /*&& !Cat->GetCatFnc()*/);
  } // end of DefineAM

/***********************************************************************/
/*  InvalidateIndex: mark all indexes as invalid.                      */
/***********************************************************************/
bool DOSDEF::InvalidateIndex(PGLOBAL g)
  {
  if (To_Indx)
    for (PIXDEF xp = To_Indx; xp; xp = xp->Next)
      xp->Invalid = true;

  return false;
  } // end of InvalidateIndex

/***********************************************************************/
/*  GetTable: makes a new Table Description Block.                     */
/***********************************************************************/
PTDB DOSDEF::GetTable(PGLOBAL g, MODE mode)
  {
  // Mapping not used for insert
  USETEMP tmp = PlgGetUser(g)->UseTemp;
  bool    map = Mapped && mode != MODE_INSERT &&
                !(tmp != TMP_NO && Recfm == RECFM_VAR
                                && mode == MODE_UPDATE) &&
                !(tmp == TMP_FORCE &&
                (mode == MODE_UPDATE || mode == MODE_DELETE));
  PTXF    txfp;
  PTDBASE tdbp;

  /*********************************************************************/
  /*  Allocate table and file processing class of the proper type.     */
  /*  Column blocks will be allocated only when needed.                */
  /*********************************************************************/
  if (Recfm == RECFM_DBF) {
    if (Catfunc == FNC_NO) {
      if (map)
        txfp = new(g) DBMFAM(this);
      else
        txfp = new(g) DBFFAM(this);

      tdbp = new(g) TDBFIX(this, txfp);
    } else                   // Catfunc should be 'C'
      tdbp = new(g) TDBDCL(this);

  } else if (Recfm != RECFM_VAR && Compressed < 2) {
    if (Huge)
      txfp = new(g) BGXFAM(this);
    else if (map)
      txfp = new(g) MPXFAM(this);
#if defined(ZIP_SUPPORT)
    else if (Compressed)
      txfp = new(g) ZIXFAM(this);
#endif   // ZIP_SUPPORT
    else
      txfp = new(g) FIXFAM(this);

    tdbp = new(g) TDBFIX(this, txfp);
  } else {
#if defined(ZIP_SUPPORT)
    if (Compressed) {
      if (Compressed == 1)
        txfp = new(g) ZIPFAM(this);
      else {
        strcpy(g->Message, "Compress 2 not supported yet");
//      txfp = new(g) ZLBFAM(defp);
        return NULL;
      } // endelse

    } else
#endif   // ZIP_SUPPORT
    if (map)
      txfp = new(g) MAPFAM(this);
    else
      txfp = new(g) DOSFAM(this);

    // Txfp must be set even for not multiple tables because
    // it is needed when calling Cardinality in GetBlockValues.
    tdbp = new(g) TDBDOS(this, txfp);
  } // endif Recfm

  if (Multiple)
    tdbp = new(g) TDBMUL(tdbp);

  return tdbp;
  } // end of GetTable

/* ------------------------ Class TDBDOS ----------------------------- */

/***********************************************************************/
/*  Implementation of the TDBDOS class. This is the common class that  */
/*  contain all that is common between the TDBDOS and TDBMAP classes.  */
/***********************************************************************/
TDBDOS::TDBDOS(PDOSDEF tdp, PTXF txfp) : TDBASE(tdp)
  {
  if ((Txfp = txfp))
    Txfp->SetTdbp(this);

  Lrecl = tdp->Lrecl;
  AvgLen = tdp->AvgLen;
  Ftype = tdp->Recfm;
  To_Line = NULL;
  Cardinal = -1;
  } // end of TDBDOS standard constructor

TDBDOS::TDBDOS(PGLOBAL g, PTDBDOS tdbp) : TDBASE(tdbp)
  {
  Txfp = (g) ? tdbp->Txfp->Duplicate(g) : tdbp->Txfp;
  Lrecl = tdbp->Lrecl;
  AvgLen = tdbp->AvgLen;
  Ftype = tdbp->Ftype;
  To_Line = tdbp->To_Line;
  Cardinal = tdbp->Cardinal;
  } // end of TDBDOS copy constructor

// Method
PTDB TDBDOS::CopyOne(PTABS t)
  {
  PTDB    tp;
  PDOSCOL cp1, cp2;
  PGLOBAL g = t->G;

  tp = new(g) TDBDOS(g, this);

  for (cp1 = (PDOSCOL)Columns; cp1; cp1 = (PDOSCOL)cp1->GetNext()) {
    cp2 = new(g) DOSCOL(cp1, tp);  // Make a copy
    NewPointer(t, cp1, cp2);
    } // endfor cp1

  return tp;
  } // end of CopyOne

/***********************************************************************/
/*  Allocate DOS column description block.                             */
/***********************************************************************/
PCOL TDBDOS::MakeCol(PGLOBAL g, PCOLDEF cdp, PCOL cprec, int n)
  {
  return new(g) DOSCOL(g, cdp, this, cprec, n);
  } // end of MakeCol

/***********************************************************************/
/*  Print debug information.                                           */
/***********************************************************************/
void TDBDOS::PrintAM(FILE *f, char *m)
  {
  fprintf(f, "%s AM(%d): mode=%d\n", m, GetAmType(), Mode);

  if (Txfp->To_File)
    fprintf(f, "%s  File: %s\n", m, Txfp->To_File);

  } // end of PrintAM

/***********************************************************************/
/*  Remake the indexes after the table was modified.                   */
/***********************************************************************/
int TDBDOS::ResetTableOpt(PGLOBAL g, bool dox)
  {
  int  rc = RC_OK;

  MaxSize = -1;                        // Size must be recalculated
  Cardinal = -1;                       // as well as Cardinality

  if (dox) {
    // Remake eventual indexes
    if (Mode != MODE_UPDATE)
      To_SetCols = NULL;                // Only used on Update

    Columns = NULL;                     // Not used anymore
    Txfp->Reset();                      // New start
    Use = USE_READY;                    // So the table can be reopened
    Mode = MODE_READ;                   // New mode

    if (!(PlgGetUser(g)->Check & CHK_OPT)) {
      // After the table was modified the indexes
      // are invalid and we should mark them as such...
      rc = ((PDOSDEF)To_Def)->InvalidateIndex(g);
    } else
      // ... or we should remake them.
      rc = MakeIndex(g, NULL, false);

    } // endif dox

  return rc;
  } // end of ResetTableOpt

/***********************************************************************/
/*  Check whether we have to create/update permanent indexes.          */
/***********************************************************************/
int TDBDOS::MakeIndex(PGLOBAL g, PIXDEF pxdf, bool add)
  {
  int     k, n;
  bool    fixed, doit, sep, b = (pxdf != NULL);
  PCOL   *keycols, colp;
  PIXDEF  xdp, sxp = NULL;
  PKPDEF  kdp;
  PDOSDEF dfp;
//PCOLDEF cdp;
  PXINDEX x;
  PXLOAD  pxp;
  PCATLG  cat = PlgGetCatalog(g);

  Mode = MODE_READ;
  Use = USE_READY;
  dfp = (PDOSDEF)To_Def;
  fixed = Cardinality(g) >= 0;

  // Are we are called from CreateTable or CreateIndex?
  if (pxdf) {
    if (!add && dfp->GetIndx()) {
      strcpy(g->Message, MSG(INDX_EXIST_YET));
      return RC_FX;
      } // endif To_Indx

    if (add && dfp->GetIndx()) {
      for (sxp = dfp->GetIndx(); sxp; sxp = sxp->GetNext())
        if (!stricmp(sxp->GetName(), pxdf->GetName())) {
          sprintf(g->Message, MSG(INDEX_YET_ON), pxdf->GetName(), Name);
          return RC_FX;
        } else if (!sxp->GetNext())
          break;

      sxp->SetNext(pxdf);
//    first = false;
    } else
      dfp->SetIndx(pxdf);

//  pxdf->SetDef(dfp);
  } else if (!(pxdf = dfp->GetIndx()))
    return RC_INFO;              // No index to make

  // Allocate all columns that will be used by indexes.
  // This must be done before opening the table so specific
  // column initialization can be done ( in particular by TDBVCT)
  for (n = 0, xdp = pxdf; xdp; xdp = xdp->GetNext())
    for (kdp = xdp->GetToKeyParts(); kdp; kdp = kdp->GetNext()) {
      if (!(colp = ColDB(g, kdp->GetName(), 0))) {
        sprintf(g->Message, MSG(INDX_COL_NOTIN), kdp->GetName(), Name);
        goto err;
        } // endif colp

      colp->InitValue(g);
      n = max(n, xdp->GetNparts());
      } // endfor kdp

  keycols = (PCOL*)PlugSubAlloc(g, NULL, n * sizeof(PCOL));
  sep = cat->GetBoolCatInfo("SepIndex", false);

  /*********************************************************************/
  /*  Construct and save the defined indexes.                          */
  /*********************************************************************/
  for (xdp = pxdf; xdp; xdp = xdp->GetNext())
    if (!OpenDB(g)) {
      if (xdp->IsAuto() && fixed)
        // Auto increment key and fixed file: use an XXROW index
        continue;      // XXROW index doesn't need to be made

      // On Update, redo only indexes that are modified
      doit = !To_SetCols;
      n = 0;

      if (sxp)
        xdp->SetID(sxp->GetID() + 1);

      for (kdp = xdp->GetToKeyParts(); kdp; kdp = kdp->GetNext()) {
        // Check whether this column was updated
        for (colp = To_SetCols; !doit && colp; colp = colp->GetNext())
          if (!stricmp(kdp->GetName(), colp->GetName()))
            doit = true;

        keycols[n++] = ColDB(g, kdp->GetName(), 0);
        } // endfor kdp

      // If no indexed columns were updated, don't remake the index
      // if indexes are in separate files.
      if (!doit && sep)
        continue;

      k = xdp->GetNparts();

      // Make the index and save it
      if (dfp->Huge)
        pxp = new(g) XHUGE;
      else
        pxp = new(g) XFILE;

      if (k == 1)            // Simple index
        x = new(g) XINDXS(this, xdp, pxp, keycols);
      else                   // Multi-Column index
        x = new(g) XINDEX(this, xdp, pxp, keycols);

      if (!x->Make(g, sxp)) {
        // Retreive define values from the index
        xdp->SetMaxSame(x->GetMaxSame());
//      xdp->SetSize(x->GetSize());

        // store KXYCOL Mxs in KPARTDEF Mxsame
        xdp->SetMxsame(x);

#if defined(TRACE)
        printf("Make done...\n");
#endif   // TRACE

//      if (x->GetSize() > 0)
          sxp = xdp;

        xdp->SetInvalid(false);
      } else
        goto err;

    } else
      return RC_INFO;     // Error or Physical table does not exist

  if (Use == USE_OPEN)
    CloseDB(g);

  return RC_OK;

err:
  if (sxp)
    sxp->SetNext(NULL);
  else
    dfp->SetIndx(NULL);

  return RC_FX;
  } // end of MakeIndex

/***********************************************************************/
/*  DOS GetProgMax: get the max value for progress information.        */
/***********************************************************************/
int TDBDOS::GetProgMax(PGLOBAL g)
  {
  return (To_Kindex) ? GetMaxSize(g) : GetFileLength(g);
  } // end of GetProgMax

/***********************************************************************/
/*  DOS GetProgCur: get the current value for progress information.    */
/***********************************************************************/
int TDBDOS::GetProgCur(void)
  {
  return (To_Kindex) ? To_Kindex->GetCur_K() + 1 : GetRecpos();
  } // end of GetProgCur

/***********************************************************************/
/*  RowNumber: return the ordinal number of the current row.           */
/***********************************************************************/
int TDBDOS::RowNumber(PGLOBAL g, bool b)
  {
  if (To_Kindex) {
    /*******************************************************************/
    /*  Don't know how to retrieve RowID from file address.            */
    /*******************************************************************/
    sprintf(g->Message, MSG(NO_ROWID_FOR_AM),
                        GetAmName(g, Txfp->GetAmType()));
    return 0;
  } else
    return Txfp->GetRowID();

  } // end of RowNumber

/***********************************************************************/
/*  DOS 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 TDBDOS::Cardinality(PGLOBAL g)
  {
  if (!g)
    return Txfp->Cardinality(g);

  if (Cardinal < 0)
    Cardinal = Txfp->Cardinality(g);

  return Cardinal;
  } // end of Cardinality

/***********************************************************************/
/*  DOS GetMaxSize: returns file size estimate in number of lines.     */
/*  This function covers variable record length files.                 */
/***********************************************************************/
int TDBDOS::GetMaxSize(PGLOBAL g)
  {
  if (MaxSize >= 0)
    return MaxSize;

  if (!Cardinality(NULL)) {
    int len = GetFileLength(g);

    if (len >= 0) {
      if (trace)
        htrc("Estimating lines len=%d ending=%d\n",
              len, ((PDOSDEF)To_Def)->Ending);

      /*****************************************************************/
      /*  Estimate the number of lines in the table (if not known) by  */
      /*  dividing the file length by the minimum line length assuming */
      /*  only the last column can be of variable length. This will be */
      /*  a ceiling estimate (as last column is never totally absent). */
      /*****************************************************************/
      int rec = ((PDOSDEF)To_Def)->Ending;     // +2: CRLF +1: LF

      if (AvgLen <= 0)     // No given average estimate
        rec += EstimatedLength(g);
      else   // A lower estimate was given for the average record length
        rec += (int)AvgLen;

      if (trace)
        htrc(" Filen=%d min_rec=%d\n", len, rec);
              
      MaxSize = (len + rec - 1) / rec;

      if (trace)
        htrc(" Estimated max_K=%d\n", MaxSize);

      } // endif len

  } else
    MaxSize = Cardinality(g);

  return MaxSize;
  } // end of GetMaxSize

/***********************************************************************/
/*  DOS EstimatedLength. Returns an estimated minimum line length.     */
/***********************************************************************/
int TDBDOS::EstimatedLength(PGLOBAL g)
  {
  int     dep = 0;
  PCOLDEF cdp = To_Def->GetCols();

  if (!cdp->GetNext()) {
    // One column table, we are going to return a ridiculous
    // result if we set dep to 1
    dep = 1 + cdp->GetLong() / 20;           // Why 20 ?????
  } else for (; cdp; cdp = cdp->GetNext())
    dep = max(dep, cdp->GetOffset());

  return (int)dep;
  } // end of Estimated Length

/***********************************************************************/
/*  DOS tables favor the use temporary files for Update.               */
/***********************************************************************/
bool TDBDOS::IsUsingTemp(PGLOBAL g)
  {
  USETEMP usetemp = PlgGetUser(g)->UseTemp;

  return (usetemp == TMP_YES || usetemp == TMP_FORCE ||
         (usetemp == TMP_AUTO && Mode == MODE_UPDATE));
  } // end of IsUsingTemp

/***********************************************************************/
/*  DOS 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 TDBDOS::OpenDB(PGLOBAL g)
  {
  if (trace)
    htrc("DOS OpenDB: tdbp=%p tdb=R%d use=%d mode=%d\n",
          this, Tdb_No, Use, Mode);

  if (Use == USE_OPEN) {
    /*******************************************************************/
    /*  Table already open, just replace it at its beginning.          */
    /*******************************************************************/
    Txfp->Rewind();         // see comment in Work.log

    if (SkipHeader(g))
      return true;

    return false;
    } // endif use

  if (Mode == MODE_DELETE && !Next && Txfp->GetAmType() != TYPE_AM_DOS) {
    // Delete all lines. Not handled in MAP or block mode
    Txfp = new(g) DOSFAM((PDOSDEF)To_Def);
    Txfp->SetTdbp(this);
  } else if (Txfp->Blocked && (Mode == MODE_DELETE ||
      (Mode == MODE_UPDATE && PlgGetUser(g)->UseTemp != TMP_NO))) {
    /*******************************************************************/
    /*  Delete is not currently handled in block mode neither Update   */
    /*  when using a temporary file.                                   */
    /*******************************************************************/
    if (Txfp->GetAmType() == TYPE_AM_MAP && Mode == MODE_DELETE)
      Txfp = new(g) MAPFAM((PDOSDEF)To_Def);
#if defined(ZIP_SUPPORT)
    else if (Txfp->GetAmType() == TYPE_AM_ZIP)
      Txfp = new(g) ZIPFAM((PDOSDEF)To_Def);
#endif   // ZIP_SUPPORT
    else if (Txfp->GetAmType() != TYPE_AM_DOS)
      Txfp = new(g) DOSFAM((PDOSDEF)To_Def);

    Txfp->SetTdbp(this);
    } // endif Mode

  /*********************************************************************/
  /*  Open according to logical input/output mode required.            */
  /*  Use conventionnal input/output functions.                        */
  /*  Treat files as binary in Delete mode (for line moving)           */
  /*********************************************************************/
  if (Txfp->OpenTableFile(g))
    return true;

  Use = USE_OPEN;       // Do it now in case we are recursively called

  /*********************************************************************/
  /*  Allocate the line buffer plus a null character.                  */
  /*********************************************************************/
  To_Line = (char*)PlugSubAlloc(g, NULL, Lrecl + 1);

  if (Mode == MODE_INSERT) {
    // Spaces between fields must be filled with blanks
    memset(To_Line, ' ', Lrecl);
    To_Line[Lrecl] = '\0';
  } else
    memset(To_Line, 0, Lrecl + 1);

  if (trace)
    htrc("OpenDos: R%hd mode=%d To_Line=%p\n", Tdb_No, Mode, To_Line);

  if (SkipHeader(g))         // When called from CSV/FMT files
    return true;

  /*********************************************************************/
  /*  Reset statistics values.                                         */
  /*********************************************************************/
  num_read = num_there = num_eq[0] = num_eq[1] = 0;
  return false;
  } // end of OpenDB

/***********************************************************************/
/*  ReadDB: Data Base read routine for DOS access method.              */
/***********************************************************************/
int TDBDOS::ReadDB(PGLOBAL g)
  {
  if (trace > 1)
    htrc("DOS ReadDB: R%d Mode=%d key=%p link=%p Kindex=%p To_Line=%p\n",
          GetTdb_No(), Mode, To_Key_Col, To_Link, To_Kindex, To_Line);

  if (To_Kindex) {
    /*******************************************************************/
    /*  Reading is by an index table.                                  */
    /*******************************************************************/
    int recpos = To_Kindex->Fetch(g);

    switch (recpos) {
      case -1:           // End of file reached
        return RC_EF;
      case -2:           // No match for join
        return RC_NF;
      case -3:           // Same record as last non null one
        num_there++;
        return RC_OK;
      default:
        /***************************************************************/
        /*  Set the file position according to record to read.         */
        /***************************************************************/
        if (SetRecpos(g, recpos))
          return RC_FX;

        if (trace > 1)
          htrc("File position is now %d\n", GetRecpos());

        if (Mode == MODE_READ)
          /*************************************************************/
          /*  Defer physical reading until one column setting needs it */
          /*  as it can be a big saving on joins where no other column */
          /*  than the keys are used, so reading is unnecessary.       */
          /*************************************************************/
          if (Txfp->DeferReading())
            return RC_OK;

      } // endswitch recpos

    } // endif To_Kindex

  if (trace > 1)
    htrc(" ReadDB: this=%p To_Line=%p\n", this, To_Line);

  /*********************************************************************/
  /*  Now start the reading process.                                   */
  /*********************************************************************/
  return ReadBuffer(g);
  } // end of ReadDB

/***********************************************************************/
/*  WriteDB: Data Base write routine for DOS access method.            */
/***********************************************************************/
int TDBDOS::WriteDB(PGLOBAL g)
  {
  if (trace > 1)
    htrc("DOS WriteDB: R%d Mode=%d \n", Tdb_No, Mode);

  if (!Ftype && (Mode == MODE_INSERT || Txfp->GetUseTemp())) {
    char *p;

    /*******************************************************************/
    /*  Suppress trailing blanks.                                      */
    /*  Also suppress eventual null from last line.                    */
    /*******************************************************************/
    for (p = To_Line + Lrecl -1; p >= To_Line; p--)
      if (*p && *p != ' ')
        break;

    *(++p) = '\0';
    } // endif Mode

  if (trace > 1)
    htrc("Write: line is='%s'\n", To_Line);

  // Now start the writing process
  return Txfp->WriteBuffer(g);
  } // end of WriteDB

/***********************************************************************/
/*  Data Base delete line routine for DOS (and FIX) access method.     */
/*  RC_FX means delete all. Nothing to do here (was done at open).     */
/***********************************************************************/
int TDBDOS::DeleteDB(PGLOBAL g, int irc)
  {
    return (irc == RC_FX) ? RC_OK : Txfp->DeleteRecords(g, irc);
  } // end of DeleteDB

/***********************************************************************/
/*  Data Base close routine for DOS access method.                     */
/***********************************************************************/
void TDBDOS::CloseDB(PGLOBAL g)
  {
  if (To_Kindex) {
    To_Kindex->Close();
    To_Kindex = NULL;
    } // endif

  Txfp->CloseTableFile(g);
  } // end of CloseDB

// ------------------------ DOSCOL functions ----------------------------

/***********************************************************************/
/*  DOSCOL public constructor (also called by MAPCOL).                 */
/***********************************************************************/
DOSCOL::DOSCOL(PGLOBAL g, PCOLDEF cdp, PTDB tp, PCOL cp, int i, PSZ am)
  : COLBLK(cdp, tp, i)
  {
  char *p;
  int   prec = Format.Prec;
  PTXF  txfp = ((PTDBDOS)tp)->Txfp;

  assert(cdp);

  if (cp) {
    Next = cp->GetNext();
    cp->SetNext(this);
  } else {
    Next = tp->GetColumns();
    tp->SetColumns(this);
  } // endif cprec

  // Set additional Dos access method information for column.
  Deplac = cdp->GetOffset();
  Long = cdp->GetLong();
  To_Val = NULL;

  OldVal = NULL;                  // Currently used only in MinMax
  Ldz = false;
  Nod = false;
  Dcm = -1;
  p = cdp->GetFmt();

  if (p && IsTypeNum(Buf_Type)) {
    // Formatted numeric value
    for (; p && *p && isalpha(*p); p++)
      switch (toupper(*p)) {
        case 'Z':                 // Have leading zeros
          Ldz = true;
          break;
        case 'N':                 // Have no decimal point
          Nod = true;
          break;
        } // endswitch p

    // Set number of decimal digits
    Dcm = (*p) ? atoi(p) : GetPrecision();
    } // endif fmt

  if (trace)
    htrc(" making new %sCOL C%d %s at %p\n", am, Index, Name, this);

  } // end of DOSCOL constructor

/***********************************************************************/
/*  DOSCOL constructor used for copying columns.                       */
/*  tdbp is the pointer to the new table descriptor.                   */
/***********************************************************************/
DOSCOL::DOSCOL(DOSCOL *col1, PTDB tdbp) : COLBLK(col1, tdbp)
  {
  Deplac = col1->Deplac;
  Long = col1->Long;
  To_Val = col1->To_Val;
  Ldz = col1->Ldz;
  Nod = col1->Nod;
  Dcm = col1->Dcm;
  OldVal = col1->OldVal;
  Buf = col1->Buf;
  } // end of DOSCOL copy constructor

/***********************************************************************/
/*  VarSize: This function tells UpdateDB whether or not the block     */
/*  optimization file must be redone if this column is updated, even   */
/*  it is not sorted or clustered. This applies to the last column of  */
/*  a variable length table that is blocked, because if it is updated  */
/*  using a temporary file, the block size may be modified.            */
/***********************************************************************/
bool DOSCOL::VarSize(void)
  {
  PTDBDOS tdbp = (PTDBDOS)To_Tdb;
  PTXF    txfp = tdbp->Txfp;

  if (Cdp && !Cdp->GetNext()               // Must be the last column
          && tdbp->Ftype == RECFM_VAR      // of a DOS variable length
          && txfp->Blocked                 // blocked table
          && txfp->GetUseTemp())           // using a temporary file.
    return true;
  else
    return false;

  } // end VarSize

/***********************************************************************/
/*  SetBuffer: prepare a column block for write operation.             */
/***********************************************************************/
bool DOSCOL::SetBuffer(PGLOBAL g, PVAL value, bool ok, bool check)
  {
  if (!(To_Val = value)) {
    sprintf(g->Message, MSG(VALUE_ERROR), Name);
    return true;
  } else if (Buf_Type == value->GetType()) {
    // Values are of the (good) column type
    if (Buf_Type == TYPE_DATE) {
      // If any of the date values is formatted
      // output format must be set for the receiving table
      if (GetDomain() || ((DTVAL *)value)->IsFormatted())
        goto newval;          // This will make a new value;

    } else if (Buf_Type == TYPE_FLOAT)
      // Float values must be written with the correct (column) precision
      // Note: maybe this should be forced by ShowValue instead of this ?
      value->SetPrec(GetPrecision());

    Value = value;            // Directly access the external value
  } else {
    // Values are not of the (good) column type
    if (check) {
      sprintf(g->Message, MSG(TYPE_VALUE_ERR), Name,
              GetTypeName(Buf_Type), GetTypeName(value->GetType()));
      return true;
      } // endif check

 newval:
    if (InitValue(g))         // Allocate the matching value block
      return true;

  } // endif's Value, Buf_Type

  // Allocate the buffer used in WriteColumn for numeric columns
  if (IsTypeNum(Buf_Type))
    Buf = (char*)PlugSubAlloc(g, NULL, max(32, Long + Dcm + 1));

  // Because Colblk's have been made from a copy of the original TDB in
  // case of Update, we must reset them to point to the original one.
  if (To_Tdb->GetOrig())
    To_Tdb = (PTDB)To_Tdb->GetOrig();

  // Set the Column
  Status = (ok) ? BUF_EMPTY : BUF_NO;
  return false;
  } // end of SetBuffer

/***********************************************************************/
/*  ReadColumn: what this routine does is to access the last line      */
/*  read from the corresponding table, extract from it the field       */
/*  corresponding to this column and convert it to buffer type.        */
/***********************************************************************/
void DOSCOL::ReadColumn(PGLOBAL g)
  {
  char   *p;
  int     i, rc;
  int     field;
  double  dval;
  PTDBDOS tdbp = (PTDBDOS)To_Tdb;

  if (trace > 1)
    htrc(
      "DOS ReadColumn: col %s R%d coluse=%.4X status=%.4X buf_type=%d\n",
         Name, tdbp->GetTdb_No(), ColUse, Status, Buf_Type);

  /*********************************************************************/
  /*  If physical reading of the line was deferred, do it now.         */
  /*********************************************************************/
  if (!tdbp->IsRead())
    if ((rc = tdbp->ReadBuffer(g)) != RC_OK) {
      if (rc == RC_EF)
        sprintf(g->Message, MSG(INV_DEF_READ), rc);

      longjmp(g->jumper[g->jump_level], 11);
      } // endif

  p = tdbp->To_Line + Deplac;
  field = Long;

  switch (tdbp->Ftype) {
    case RECFM_VAR:
      /*****************************************************************/
      /*  For a variable length file, check if the field exists.       */
      /*****************************************************************/
      if (strlen(tdbp->To_Line) < (unsigned)Deplac)
        field = 0;

    case RECFM_FIX:            // Fixed length text file
    case RECFM_DBF:            // Fixed length DBase file
      if (Nod) switch (Buf_Type) {
        case TYPE_INT:
        case TYPE_SHORT:
        case TYPE_TINY:
        case TYPE_BIGINT:
          Value->SetValue_char(p, field - Dcm);
          break;
        case TYPE_FLOAT:
          Value->SetValue_char(p, field);
          dval = Value->GetFloatValue();

          for (i = 0; i < Dcm; i++)
            dval /= 10.0;

          Value->SetValue(dval);
          break;
        default:
          Value->SetValue_char(p, field);
          break;
        } // endswitch Buf_Type

      else
        Value->SetValue_char(p, field);

      break;
    default:
      sprintf(g->Message, MSG(BAD_RECFM), tdbp->Ftype);
      longjmp(g->jumper[g->jump_level], 34);
    } // endswitch Ftype

  // Set null when applicable
  if (Nullable)
    Value->SetNull(Value->IsZero());

  } // end of ReadColumn

/***********************************************************************/
/*  WriteColumn: what this routine does is to access the last line     */
/*  read from the corresponding table, and rewrite the field           */
/*  corresponding to this column from the column buffer and type.      */
/***********************************************************************/
void DOSCOL::WriteColumn(PGLOBAL g)
  {
  char   *p, *p2, fmt[32];
  int     i, k, len, field;
  PTDBDOS tdbp = (PTDBDOS)To_Tdb;

  if (trace > 1)
    htrc("DOS WriteColumn: col %s R%d coluse=%.4X status=%.4X\n",
          Name, tdbp->GetTdb_No(), ColUse, Status);

  p = tdbp->To_Line + Deplac;

  if (trace > 1)
    htrc("Lrecl=%d deplac=%d int=%d\n", tdbp->Lrecl, Deplac, Long);

  field = Long;

  if (tdbp->Ftype == RECFM_VAR && tdbp->Mode == MODE_UPDATE) {
    len = (signed)strlen(tdbp->To_Line);

    if (tdbp->IsUsingTemp(g))
      // Because of eventual missing field(s) the buffer must be reset
      memset(tdbp->To_Line + len, ' ', tdbp->Lrecl - len);
    else
      // The size actually available must be recalculated
      field = min(len - Deplac, Long);

    } // endif Ftype

  if (trace > 1)
    htrc("Long=%d field=%d coltype=%d colval=%p\n",
          Long, field, Buf_Type, Value);

  /*********************************************************************/
  /*  Get the string representation of Value according to column type. */
  /*********************************************************************/
  if (Value != To_Val)
    Value->SetValue_pval(To_Val, false);    // Convert the updated value

  /*********************************************************************/
  /*  This test is only useful for compressed(2) tables.               */
  /*********************************************************************/
  if (tdbp->Ftype != RECFM_BIN) {
    if (Ldz || Nod || Dcm >= 0) {
      switch (Buf_Type) {
        case TYPE_SHORT:
          strcpy(fmt, (Ldz) ? "%0*hd" : "%*.hd");
          i = 0;

          if (Nod)
            for (; i < Dcm; i++)
              strcat(fmt, "0");

          len = sprintf(Buf, fmt, field - i, Value->GetShortValue());
          break;
        case TYPE_INT:
          strcpy(fmt, (Ldz) ? "%0*d" : "%*.d");
          i = 0;

          if (Nod)
            for (; i < Dcm; i++)
              strcat(fmt, "0");

          len = sprintf(Buf, fmt, field - i, Value->GetIntValue());
          break;
        case TYPE_TINY:
          strcpy(fmt, (Ldz) ? "%0*d" : "%*.d");
          i = 0;

          if (Nod)
            for (; i < Dcm; i++)
              strcat(fmt, "0");

          len = sprintf(Buf, fmt, field - i, Value->GetTinyValue());
          break;
        case TYPE_FLOAT:
          strcpy(fmt, (Ldz) ? "%0*.*lf" : "%*.*lf");
          sprintf(Buf, fmt, field + ((Nod && Dcm) ? 1 : 0),
                  Dcm, Value->GetFloatValue());
          len = strlen(Buf);

          if (Nod && Dcm)
            for (i = k = 0; i < len; i++, k++)
              if (Buf[i] != ' ') {
                if (Buf[i] == '.' || Buf[i] == ',')
                  k++;

                Buf[i] = Buf[k];
                } // endif Buf(i)

          len = strlen(Buf);
          break;
        } // endswitch BufType

      p2 = Buf;
    } else                 // Standard PlugDB format
      p2 = Value->ShowValue(Buf, field);

    if (trace)
      htrc("new length(%p)=%d\n", p2, strlen(p2));

    if ((len = strlen(p2)) > field) {
      sprintf(g->Message, MSG(VALUE_TOO_LONG), p2, Name, field);
      longjmp(g->jumper[g->jump_level], 31);
      } // endif

    if (trace > 1)
      htrc("buffer=%s\n", p2);

    /*******************************************************************/
    /*  Updating must be done only when not in checking pass.          */
    /*******************************************************************/
    if (Status) {
      memset(p, ' ', field);
      memcpy(p, p2, len);

      if (trace > 1)
        htrc(" col write: '%.*s'\n", len, p);

      } // endif Use

  } else    // BIN compressed table
    /*******************************************************************/
    /*  Check if updating is Ok, meaning col value is not too long.    */
    /*  Updating to be done only during the second pass (Status=true)  */
    /*******************************************************************/
    if (Value->GetBinValue(p, Long, Status)) {
      sprintf(g->Message, MSG(BIN_F_TOO_LONG),
                          Name, Value->GetSize(), Long);
      longjmp(g->jumper[g->jump_level], 31);
      } // endif

  } // end of WriteColumn

/***********************************************************************/
/*  Make file output of a Dos column descriptor block.                 */
/***********************************************************************/
void DOSCOL::Print(PGLOBAL g, FILE *f, uint n)
  {
  COLBLK::Print(g, f, n);
  } // end of Print

/* ------------------------------------------------------------------- */

