/************* TabDos C++ Program Source Code File (.CPP) **************/
/* PROGRAM NAME: TABDOS                                                */
/* -------------                                                       */
/*  Version 4.9.5                                                      */
/*                                                                     */
/* COPYRIGHT:                                                          */
/* ----------                                                          */
/*  (C) Copyright to the author Olivier BERTRAND          1998-2020    */
/*                                                                     */
/* 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(GZ_SUPPORT)
#include "filamgz.h"
#endif   // GZ_SUPPORT
#if defined(ZIP_SUPPORT)
#include "filamzip.h"
#endif   // ZIP_SUPPORT
#include "tabdos.h"
#include "tabfix.h"
#include "tabmul.h"
#include "array.h"
#include "blkfil.h"
#include "m_string.h"

/***********************************************************************/
/*  DB static variables.                                               */
/***********************************************************************/
int num_read, num_there, num_eq[2];                 // Statistics

/***********************************************************************/
/*  Size of optimize file header.                                      */
/***********************************************************************/
#define NZ         4

/***********************************************************************/
/*  External function.                                                 */
/***********************************************************************/
bool    ExactInfo(void);
USETEMP UseTemp(void);

/***********************************************************************/
/*  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, bool un= false);

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

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

/***********************************************************************/
/*  DefineAM: define specific AM block values from XDB file.           */
/***********************************************************************/
bool DOSDEF::DefineAM(PGLOBAL g, LPCSTR am, int)
  {
  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 && (*am == 'X' || *am == 'x')) ? "X"
		         : (am && !stricmp(am, "DBF"))        ? "D" : "V";

	if ((Zipped = GetBoolCatInfo("Zipped", false))) {
		Entry = GetStringCatInfo(g, "Entry", NULL);
		Mulentries = (Entry && *Entry) ? strchr(Entry, '*') || strchr(Entry, '?')
		                               : false;
		Mulentries = GetBoolCatInfo("Mulentries", Mulentries);
		Append = GetBoolCatInfo("Append", false);
		Pwd = GetStringCatInfo(g, "Password", NULL);
	}	// endif Zipped

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

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

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

	if (Ending <= 0) {
		Ending = (Recfm == RECFM_BIN || Recfm == RECFM_VCT) ? 0 : CRLF;
		SetIntCatInfo("Ending", Ending);
	} // endif ending

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

  // Ignore wrong Index definitions for catalog commands
  SetIndexInfo();
  return false;
  } // end of DefineAM

/***********************************************************************/
/*  Get the full path/name of the optization file.                     */
/***********************************************************************/
bool DOSDEF::GetOptFileName(PGLOBAL g, char *filename)
  {
  PCSZ ftype;

  switch (Recfm) {
    case RECFM_VAR: ftype = ".dop"; break;
    case RECFM_FIX: ftype = ".fop"; break;
    case RECFM_BIN: ftype = ".bop"; break;
    case RECFM_VCT: ftype = ".vop"; break;
		case RECFM_CSV: ftype = ".cop"; break;
		case RECFM_DBF: ftype = ".dbp"; break;
    default:
      snprintf(g->Message, sizeof(g->Message), MSG(INVALID_FTYPE), Recfm);
      return true;
    } // endswitch Ftype

  PlugSetPath(filename, Ofn, GetPath());
  strcat(PlugRemoveType(filename, filename), ftype);
  return false;
  } // end of GetOptFileName

/***********************************************************************/
/*  After an optimize error occurred, remove all set optimize values.   */
/***********************************************************************/
void DOSDEF::RemoveOptValues(PGLOBAL g)
  {
  char    filename[_MAX_PATH];
  PCOLDEF cdp;

  // Delete settings of optimized columns
  for (cdp = To_Cols; cdp; cdp = cdp->GetNext())
    if (cdp->GetOpt()) {
      cdp->SetMin(NULL);
      cdp->SetMax(NULL);
      cdp->SetNdv(0);
      cdp->SetNbm(0);
      cdp->SetDval(NULL);
      cdp->SetBmap(NULL);
      } // endif Opt

  // Delete block position setting for not fixed tables
  To_Pos = NULL;
  AllocBlks = 0;

  // Delete any eventually ill formed non matching optimization file
  if (!GetOptFileName(g, filename))
#if defined(_WIN32)
    DeleteFile(filename);
#else    // UNIX
    remove(filename);
#endif   // _WIN32

  Optimized = 0;
  } // end of RemoveOptValues

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

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

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

  if (!sep && pxdf) {
    safe_strcpy(g->Message, sizeof(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_CSV: ftype = ".cnx"; break;
		case RECFM_DBF: ftype = ".dbx"; break;
    default:
      snprintf(g->Message, sizeof(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(_WIN32)
    char drive[_MAX_DRIVE];
#else
    char *drive = NULL;
#endif
    char direc[_MAX_DIR];
    char fname[_MAX_FNAME];
    bool all = !pxdf;
    
    if (all)
      pxdf = To_Indx;

    for (; pxdf; pxdf = pxdf->GetNext()) {
      _splitpath(Ofn, drive, direc, fname, NULL);
      safe_strcat(fname, sizeof(fname), "_");
      safe_strcat(fname, sizeof(fname), pxdf->GetName());
      _makepath(filename, drive, direc, fname, ftype);
      PlugSetPath(filename, filename, GetPath());
#if defined(_WIN32)
      if (!DeleteFile(filename))
        rc |= (GetLastError() != ERROR_FILE_NOT_FOUND);
#else    // UNIX
      if (remove(filename))
        rc |= (errno != ENOENT);
#endif   // UNIX

      if (!all)
        break;

      } // endfor pxdf

  } else {  // !sep
    // Drop all indexes, delete the common file
    PlugSetPath(filename, Ofn, GetPath());
    safe_strcat(PlugRemoveType(filename, filename), sizeof(filename), ftype);
#if defined(_WIN32)
    if (!DeleteFile(filename))
      rc = (GetLastError() != ERROR_FILE_NOT_FOUND);
#else    // UNIX
    if (remove(filename))
      rc = (errno != ENOENT);
#endif   // UNIX
  } // endif sep

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

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

/***********************************************************************/
/*  InvalidateIndex: mark all indexes as invalid.                      */
/***********************************************************************/
bool DOSDEF::InvalidateIndex(PGLOBAL)
  {
  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 = 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 = NULL;
  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 (Zipped) {
				if (mode == MODE_READ || mode == MODE_ANY || mode == MODE_ALTER) {
					txfp = new(g) UZDFAM(this);
				}	else {
					safe_strcpy(g->Message, sizeof(g->Message), "Zipped DBF tables are read only");
					return NULL;
				}	// endif's mode

			} else if (map)
				txfp = new(g) DBMFAM(this);
			else
				txfp = new(g) DBFFAM(this);

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

	} else if (Zipped) {
#if defined(ZIP_SUPPORT)
		if (Recfm == RECFM_VAR) {
			if (mode == MODE_READ || mode == MODE_ANY || mode == MODE_ALTER) {
				txfp = new(g) UNZFAM(this);
			} else if (mode == MODE_INSERT) {
				txfp = new(g) ZIPFAM(this);
			} else {
				safe_strcpy(g->Message, sizeof(g->Message), "UPDATE/DELETE not supported for ZIP");
				return NULL;
			}	// endif's mode

			tdbp = new(g) TDBDOS(this, txfp);
		} else {
			if (mode == MODE_READ || mode == MODE_ANY || mode == MODE_ALTER) {
				txfp = new(g) UZXFAM(this);
			} else if (mode == MODE_INSERT) {
				txfp = new(g) ZPXFAM(this);
			} else {
				safe_strcpy(g->Message, sizeof(g->Message), "UPDATE/DELETE not supported for ZIP");
				return NULL;
			}	// endif's mode

			tdbp = new(g)TDBFIX(this, txfp);
		} // endif Recfm

#else   // !ZIP_SUPPORT
		snprintf(g->Message, sizeof(g->Message), MSG(NO_FEAT_SUPPORT), "ZIP");
		return NULL;
#endif  // !ZIP_SUPPORT
  } else if (Recfm != RECFM_VAR && Compressed < 2) {
    if (Huge)
      txfp = new(g) BGXFAM(this);
    else if (map)
      txfp = new(g) MPXFAM(this);
    else if (Compressed) {
#if defined(GZ_SUPPORT)
      txfp = new(g) GZXFAM(this);
#else   // !GZ_SUPPORT
      snprintf(g->Message, sizeof(g->Message), MSG(NO_FEAT_SUPPORT), "GZ");
      return NULL;
#endif  // !GZ_SUPPORT
    } else
      txfp = new(g) FIXFAM(this);

    tdbp = new(g) TDBFIX(this, txfp);
  } else {
    if (Compressed) {
#if defined(GZ_SUPPORT)
      if (Compressed == 1)
        txfp = new(g) GZFAM(this);
      else
        txfp = new(g) ZLBFAM(this);

#else   // !GZ_SUPPORT
      snprintf(g->Message, sizeof(g->Message), MSG(NO_FEAT_SUPPORT), "GZ");
      return NULL;
#endif  // !GZ_SUPPORT
    } else 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);
  else
    /*******************************************************************/
    /*  For block tables, get eventually saved optimization values.    */
    /*******************************************************************/
    if (tdbp->GetBlockValues(g)) {
      PushWarning(g, tdbp);
//    return NULL;            // causes a crash when deleting index
    } else if (Recfm == RECFM_VAR || Compressed > 1) {
      if (IsOptimized()) {
        if      (map) {
          txfp = new(g) MBKFAM(this);
        } else if (Compressed) {
#if defined(GZ_SUPPORT)
          if (Compressed == 1)
            txfp = new(g) ZBKFAM(this);
          else {
            txfp->SetBlkPos(To_Pos);
            ((PZLBFAM)txfp)->SetOptimized(To_Pos != NULL);
            } // endelse
#else
          snprintf(g->Message, sizeof(g->Message), MSG(NO_FEAT_SUPPORT), "GZ");
          return NULL;
#endif
        } else
          txfp = new(g) BLKFAM(this);

        ((PTDBDOS)tdbp)->SetTxfp(txfp);
        } // endif Optimized

      } // endif Recfm

  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;
//To_BlkIdx = NULL;
  To_BlkFil = NULL;
  SavFil = NULL;
//Xeval = 0;
  Beval = 0;
  Abort = false;
  Indxd = false;
  } // 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;
//To_BlkIdx = tdbp->To_BlkIdx;
  To_BlkFil = tdbp->To_BlkFil;
  SavFil = tdbp->SavFil;
//Xeval = tdbp->Xeval;
  Beval = tdbp->Beval;
  Abort = tdbp->Abort;
  Indxd = tdbp->Indxd;
  } // end of TDBDOS copy constructor

// Method
PTDB TDBDOS::Clone(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 Clone

/***********************************************************************/
/*  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 dop, bool dox)
  {
  int  prc = RC_OK, rc = RC_OK;

  if (!GetFileLength(g)) {
    // Void table, delete all opt and index files
    PDOSDEF defp = (PDOSDEF)To_Def;

    defp->RemoveOptValues(g);
    return (defp->DeleteIndexFile(g, NULL)) ? RC_INFO : RC_OK;
    } // endif GetFileLength

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

  To_Filter = NULL;                     // Disable filtering
//To_BlkIdx = NULL;                     // and index filtering
  To_BlkFil = NULL;                     // and block filtering

  // After the table was modified the indexes
  // are invalid and we should mark them as such...
  (void)((PDOSDEF)To_Def)->InvalidateIndex(g);

  if (dop) {
    Columns = NULL;                     // Not used anymore

    if (Txfp->Blocked) {
      // MakeBlockValues must be executed in non blocked mode
      // except for ZLIB access method.
      if        (Txfp->GetAmType() == TYPE_AM_MAP) {
        Txfp = new(g) MAPFAM((PDOSDEF)To_Def);
#if defined(GZ_SUPPORT)
      } else if (Txfp->GetAmType() == TYPE_AM_GZ) {
        Txfp = new(g) GZFAM((PDOSDEF)To_Def);
      } else if (Txfp->GetAmType() == TYPE_AM_ZLIB) {
        Txfp->Reset();
        ((PZLBFAM)Txfp)->SetOptimized(false);
#endif   // GZ_SUPPORT
			} else if (Txfp->GetAmType() == TYPE_AM_BLK)
        Txfp = new(g) DOSFAM((PDOSDEF)To_Def);

      Txfp->SetTdbp(this);
    } else
      Txfp->Reset();

    Use = USE_READY;                    // So the table can be reopened
    Mode = MODE_ANY;                    // Just to be clean
    rc = MakeBlockValues(g);            // Redo optimization
    } // endif dop

  if (dox && (rc == RC_OK || rc == RC_INFO)) {
    // Remake eventual indexes
//  if (Mode != MODE_UPDATE)
      To_SetCols = NULL;                // Positions are changed

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

    if (PlgGetUser(g)->Check & CHK_OPT)
      // We must remake all indexes.
      rc = MakeIndex(g, NULL, false);

    rc = (rc == RC_INFO) ? prc : rc;
    } // endif dox

  return rc;
  } // end of ResetTableOpt

/***********************************************************************/
/*  Calculate the block sizes so block I/O can be used and also the    */
/*  Min/Max values for clustered/sorted table columns.                 */
/***********************************************************************/
int TDBDOS::MakeBlockValues(PGLOBAL g)
  {
  int        i, lg, nrec, rc, n = 0;
  int        curnum, curblk, block, savndv, savnbm;
  void      *savmin, *savmax;
  bool       blocked, xdb2 = false;
//POOLHEADER save;
  PCOLDEF    cdp;
  PDOSDEF    defp = (PDOSDEF)To_Def;
  PDOSCOL    colp = NULL;
  PDBUSER    dup = PlgGetUser(g);
//void      *memp = cat->GetDescp();
  (void) defp->GetCat();                        // XXX Should be removed?


  if ((nrec = defp->GetElemt()) < 2) {
    if (!To_Def->Partitioned()) {
      // This may be wrong to do in some cases
      safe_strcpy(g->Message, sizeof(g->Message), MSG(TABLE_NOT_OPT));
      return RC_INFO;                   // Not to be optimized
    } else
      return RC_OK;

  } else if (GetMaxSize(g) == 0 || !(dup->Check & CHK_OPT)) {
    // Suppress the opt file firstly if the table is void,
    // secondly when it was modified with OPTIMIZATION unchecked
    // because it is no more valid.
    defp->RemoveOptValues(g);           // Erase opt file
    return RC_OK;                       // void table
  } else if (MaxSize < 0)
    return RC_FX;

  defp->SetOptimized(0);

  // Estimate the number of needed blocks
	if ((block = (int)((MaxSize + (int)nrec - 1) / (int)nrec)) < 2) {
		// This may be wrong to do in some cases
		defp->RemoveOptValues(g);
		safe_strcpy(g->Message, sizeof(g->Message), MSG(TABLE_NOT_OPT));
		return RC_INFO;                   // Not to be optimized
	}	// endif block

  // We have to use local variables because Txfp->CurBlk is set
  // to Rows+1 by unblocked variable length table access methods.
  curblk = -1;
  curnum = nrec - 1;
//last = 0;
  Txfp->Block = block;                  // This is useful mainly for
  Txfp->CurBlk = curblk;                // blocked tables (ZLBFAM), for
  Txfp->CurNum = curnum;                // others it is just to be clean.

  /*********************************************************************/
  /*  Allocate the array of block starting positions.                  */
  /*********************************************************************/
//if (memp)
//  save = *(PPOOLHEADER)memp;

  Txfp->BlkPos = (int*)PlugSubAlloc(g, NULL, (block + 1) * sizeof(int));

  /*********************************************************************/
  /*  Allocate the blocks for clustered columns.                       */
  /*********************************************************************/
  blocked = Txfp->Blocked;         // Save
  Txfp->Blocked = true;            // So column block can be allocated

  for (cdp = defp->GetCols(), i = 1; cdp; cdp = cdp->GetNext(), i++)
    if (cdp->GetOpt()) {
      lg = cdp->GetClen();

      if (cdp->GetFreq() && cdp->GetFreq() <= dup->Maxbmp) {
        cdp->SetXdb2(true);
        savndv = cdp->GetNdv();
        cdp->SetNdv(0);              // Reset Dval number of values
        xdb2 = true;
        savmax = cdp->GetDval();
        cdp->SetDval(PlugSubAlloc(g, NULL, cdp->GetFreq() * lg));
        savnbm = cdp->GetNbm();
        cdp->SetNbm(0);              // Prevent Bmap allocation
//      savmin = cdp->GetBmap();
//      cdp->SetBmap(PlugSubAlloc(g, NULL, block * sizeof(int)));

        if (trace(1))
          htrc("Dval(%p) Bmap(%p) col(%d) %s Block=%d lg=%d\n",
              cdp->GetDval(), cdp->GetBmap(), i, cdp->GetName(), block, lg);

        // colp will be initialized with proper Dval VALBLK
        colp = (PDOSCOL)MakeCol(g, cdp, colp, i);
        colp->InitValue(g);          // Allocate column value buffer
        cdp->SetNbm(savnbm);
//      cdp->SetBmap(savmin);        // Can be reused if the new size
        cdp->SetDval(savmax);        // is not greater than this one.
        cdp->SetNdv(savndv);
      } else {
        cdp->SetXdb2(false);         // Maxbmp may have been reset
        savmin = cdp->GetMin();
        savmax = cdp->GetMax();
        cdp->SetMin(PlugSubAlloc(g, NULL, block * lg));
        cdp->SetMax(PlugSubAlloc(g, NULL, block * lg));

        // Valgrind complains if there are uninitialised bytes
        // after the null character ending
        if (IsTypeChar(cdp->GetType())) {
          memset(cdp->GetMin(), 0, block * lg);
          memset(cdp->GetMax(), 0, block * lg);
          } // endif Type

        if (trace(1))
          htrc("min(%p) max(%p) col(%d) %s Block=%d lg=%d\n",
              cdp->GetMin(), cdp->GetMax(), i, cdp->GetName(), block, lg);

        // colp will be initialized with proper opt VALBLK's
        colp = (PDOSCOL)MakeCol(g, cdp, colp, i);
        colp->InitValue(g);          // Allocate column value buffer
        cdp->SetMin(savmin);         // Can be reused if the number
        cdp->SetMax(savmax);         // of blocks does not change.
      } // endif Freq

      } // endif Clustered

  // No optimised columns. Still useful for blocked variable tables.
  if (!colp && defp->Recfm != RECFM_VAR) {
    safe_strcpy(g->Message, sizeof(g->Message), "No optimised columns");
    return RC_INFO;
    } // endif colp

  Txfp->Blocked = blocked;

  /*********************************************************************/
  /*  Now do calculate the optimization values.                        */
  /*********************************************************************/
  Mode = MODE_READ;

  if (OpenDB(g))
    return RC_FX;

  if (xdb2) {
    /*********************************************************************/
    /*  Retrieve the distinct values of XDB2 columns.                    */
    /*********************************************************************/
    if (GetDistinctColumnValues(g, nrec))
      return RC_FX;

    OpenDB(g);                   // Rewind the table file
    } // endif xdb2

#if defined(PROG_INFO)
  /*********************************************************************/
  /*  Initialize progress information                                  */
  /*********************************************************************/
  char   *p = (char *)PlugSubAlloc(g, NULL, 24 + strlen(Name));

  snprintf(p,  24 + strlen(Name), "%s%s", MSG(OPTIMIZING), Name);
  dup->Step = p;
  dup->ProgMax = GetProgMax(g);
  dup->ProgCur = 0;
#endif   // SOCKET_MODE  ||         THREAD

  /*********************************************************************/
  /*  Make block starting pos and min/max values of cluster columns.   */
  /*********************************************************************/
  while ((rc = ReadDB(g)) == RC_OK) {
    if (blocked) {
      // A blocked FAM class handles CurNum and CurBlk (ZLBFAM)
      if (!Txfp->CurNum)
        Txfp->BlkPos[Txfp->CurBlk] = Txfp->GetPos();

    } else {
      if (++curnum >= nrec) {
        if (++curblk >= block) {
          safe_strcpy(g->Message, sizeof(g->Message), MSG(BAD_BLK_ESTIM));
          goto err;
        } else
          curnum = 0;

        // Get block starting position
        Txfp->BlkPos[curblk] = Txfp->GetPos();
        } // endif CurNum

//    last = curnum + 1;              // curnum is zero based
      Txfp->CurBlk = curblk;          // Used in COLDOS::SetMinMax
      Txfp->CurNum = curnum;          // Used in COLDOS::SetMinMax
    } // endif blocked

    /*******************************************************************/
    /*  Now calculate the min and max values for the cluster columns.  */
    /*******************************************************************/
    for (colp = (PDOSCOL)Columns; colp; colp = (PDOSCOL)colp->GetNext())
      if (colp->Clustered == 2) {
        if (colp->SetBitMap(g))
          goto err;

      } else
        if (colp->SetMinMax(g))
          goto err;                   // Currently: column is not sorted

#if defined(PROG_INFO)
    if (!dup->Step) {
      safe_strcpy(g->Message, sizeof(g->Message), MSG(OPT_CANCELLED));
      goto err;
    } else
      dup->ProgCur = GetProgCur();
#endif   // PROG_INFO

    n++;           // Used to calculate block and last
    } // endwhile

  if (rc == RC_EF) {
    Txfp->Nrec = nrec;

#if 0 // No good because Curblk and CurNum after EOF are different
      // depending on whether the file is mapped or not mapped.
    if (blocked) {
//    Txfp->Block = Txfp->CurBlk + 1;
      Txfp->Last = (Txfp->CurNum) ? Txfp->CurNum : nrec;
//    Txfp->Last = (Txfp->CurNum) ? Txfp->CurNum + 1 : nrec;
      Txfp->Block = Txfp->CurBlk + (Txfp->Last == nrec ? 0 : 1);
    } else {
      Txfp->Block = curblk + 1;
      Txfp->Last = last;
    } // endif blocked
#endif // 0

    // New values of Block and Last
    Txfp->Block = (n + nrec - 1) / nrec;
    Txfp->Last = (n % nrec) ? (n % nrec) : nrec; 

    // This is needed to be able to calculate the last block size
    Txfp->BlkPos[Txfp->Block] = Txfp->GetNextPos();
  } else
    goto err;

  /*********************************************************************/
  /*  Save the optimization values for this table.                     */
  /*********************************************************************/
  if (!SaveBlockValues(g)) {
    defp->Block = Txfp->Block;
    defp->Last = Txfp->Last;
    CloseDB(g);
    defp->SetIntCatInfo("Blocks", Txfp->Block);
    defp->SetIntCatInfo("Last", Txfp->Last);
    return RC_OK;
    } // endif SaveBlockValues

 err:
  // Restore Desc memory suballocation
//if (memp)
//  *(PPOOLHEADER)memp = save;

  defp->RemoveOptValues(g);
  CloseDB(g);
  return RC_FX;
  } // end of MakeBlockValues

/***********************************************************************/
/*  Save the block and Min/Max values for this table.                  */
/*  The problem here is to avoid name duplication, because more than   */
/*  one data file can have the same name (but different types) and/or  */
/*  the same data file can be used with different block sizes. This is */
/*  why we use Ofn that defaults to the file name but can be set to a  */
/*  different name if necessary.                                       */
/***********************************************************************/
bool TDBDOS::SaveBlockValues(PGLOBAL g)
  {
  char    filename[_MAX_PATH];
  int     lg, n[NZ + 2];
  size_t  nbk, ndv, nbm, block = Txfp->Block;
  bool    rc = false;
  FILE   *opfile;
  PDOSCOL colp;
  PDOSDEF defp = (PDOSDEF)To_Def;

  if (defp->GetOptFileName(g, filename))
    return true;

  if (!(opfile = fopen(filename, "wb"))) {
    snprintf(g->Message, sizeof(g->Message), MSG(OPEN_MODE_ERROR),
            "wb", (int)errno, filename);
    safe_strcat(g->Message, sizeof(g->Message), ": ");
    safe_strcat(g->Message, sizeof(g->Message), strerror(errno));

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

    return true;
    } // endif opfile

  memset(n, 0, sizeof(n));     // To avoid valgrind warning

  if (Ftype == RECFM_VAR || defp->Compressed == 2) {
    /*******************************************************************/
    /*  Write block starting positions into the opt file.              */
    /*******************************************************************/
    block++;
    lg = sizeof(int);
    n[0] = Txfp->Last; n[1] = lg; n[2] = Txfp->Nrec; n[3] = Txfp->Block;

    if (fwrite(n, sizeof(int), NZ, opfile) != NZ) {
      snprintf(g->Message, sizeof(g->Message), MSG(OPT_HEAD_WR_ERR), strerror(errno));
      rc = true;
      } // endif size

    if (fwrite(Txfp->BlkPos, lg, block, opfile) != block) {
      snprintf(g->Message, sizeof(g->Message), MSG(OPTBLK_WR_ERR), strerror(errno));
      rc = true;
      } // endif size

    block--;                       // = Txfp->Block;
    } // endif Ftype

  /*********************************************************************/
  /*  Write the Min/Max values into the opt file.                      */
  /*********************************************************************/
  for (colp = (PDOSCOL)Columns; colp; colp = (PDOSCOL)colp->Next) {
    lg = colp->Value->GetClen();

    //  Now start the writing process
    if (colp->Clustered == 2) {
      // New XDB2 block optimization. Will be recognized when reading
      // because the column index is negated.
      ndv = colp->Ndv; nbm = colp->Nbm;
      nbk = nbm * block;
      n[0] = -colp->Index; n[1] = lg; n[2] = Txfp->Nrec; n[3] = block;
      n[4] = ndv; n[5] = nbm;

      if (fwrite(n, sizeof(int), NZ + 2, opfile) != NZ + 2) {
        snprintf(g->Message, sizeof(g->Message), MSG(OPT_HEAD_WR_ERR), strerror(errno));
        rc = true;
        } // endif size

      if (fwrite(colp->Dval->GetValPointer(), lg, ndv, opfile) != ndv) {
        snprintf(g->Message, sizeof(g->Message), MSG(OPT_DVAL_WR_ERR), strerror(errno));
        rc = true;
        } // endif size

      if (fwrite(colp->Bmap->GetValPointer(), sizeof(int), nbk, opfile) != nbk) {
        snprintf(g->Message, sizeof(g->Message), MSG(OPT_BMAP_WR_ERR), strerror(errno));
        rc = true;
        } // endif size

    } else {
      n[0] = colp->Index; n[1] = lg; n[2] = Txfp->Nrec; n[3] = block;

      if (fwrite(n, sizeof(int), NZ, opfile) != NZ) {
        snprintf(g->Message, sizeof(g->Message), MSG(OPT_HEAD_WR_ERR), strerror(errno));
        rc = true;
        } // endif size

      if (fwrite(colp->Min->GetValPointer(), lg, block, opfile) != block) {
        snprintf(g->Message, sizeof(g->Message), MSG(OPT_MIN_WR_ERR), strerror(errno));
        rc = true;
        } // endif size

      if (fwrite(colp->Max->GetValPointer(), lg, block, opfile) != block) {
        snprintf(g->Message, sizeof(g->Message), MSG(OPT_MAX_WR_ERR), strerror(errno));
        rc = true;
        } // endif size

    } // endif Clustered

    } // endfor colp

  fclose(opfile);
  return rc;
  } // end of SaveBlockValues

/***********************************************************************/
/*  Read the Min/Max values for this table.                            */
/*  The problem here is to avoid name duplication, because more than   */
/*  one data file can have the same name (but different types) and/or  */
/*  the same data file can be used with different block sizes. This is */
/*  why we use Ofn that defaults to the file name but can be set to a  */
/*  different name if necessary.                                       */
/***********************************************************************/
bool TDBDOS::GetBlockValues(PGLOBAL g)
  {
  char       filename[_MAX_PATH];
  int        i, lg, n[NZ];
  int        nrec, block = 0, last = 0;
  int        len;
  bool       newblk = false;
  size_t     ndv, nbm, nbk, blk;
  FILE      *opfile;
  PCOLDEF    cdp;
  PDOSDEF    defp = (PDOSDEF)To_Def;
  PDBUSER    dup = PlgGetUser(g);

  (void) defp->GetCat();                        // XXX Should be removed?


#if 0
  if (Mode == MODE_INSERT && Txfp->GetAmType() == TYPE_AM_DOS)
    return false;
#endif   // _WIN32

	if (defp->Optimized || !(dup->Check & CHK_OPT))
    return false;                   // Already done or to be redone

  if (Ftype == RECFM_VAR || defp->Compressed == 2) {
    /*******************************************************************/
    /*  Variable length file that can be read by block.                */
    /*******************************************************************/
    nrec = (defp->GetElemt()) ? defp->GetElemt() : 1;
      
    if (nrec > 1) {
      // The table can be declared optimized if it is void.
      // This is useful to handle Insert in optimized mode.
      char filename[_MAX_PATH];
      int  h;
      int  flen = -1;

      PlugSetPath(filename, defp->Fn, GetPath());
      h = open(filename, O_RDONLY);
      flen = (h == -1 && errno == ENOENT) ? 0 : _filelength(h);

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

      if (!flen) {
        defp->SetOptimized(1);
        return false;
       } // endif flen

    } else
      return false;                 // Not optimisable

    cdp = defp->GetCols();
    i = 1;
  } else {
    /*******************************************************************/
    /*  Fixed length file. Opt file exists only for clustered columns. */
    /*******************************************************************/
    // Check for existence of clustered columns
    for (cdp = defp->GetCols(), i = 1; cdp; cdp = cdp->GetNext(), i++)
      if (cdp->GetOpt())
        break;

    if (!cdp)
      return false;            // No optimization needed

    if ((len = Cardinality(g)) < 0)
      return true;             // Table error
    else if (!len)
      return false;            // File does not exist yet

    block = Txfp->Block;       // Was set in Cardinality
    nrec = Txfp->Nrec;
  } // endif Ftype

  if (defp->GetOptFileName(g, filename))
    return true;

  if (!(opfile = fopen(filename, "rb")))
    return false;                   // No saved values

  if (Ftype == RECFM_VAR || defp->Compressed == 2) {
    /*******************************************************************/
    /*  Read block starting positions from the opt file.               */
    /*******************************************************************/
    lg = sizeof(int);

    if (fread(n, sizeof(int), NZ, opfile) != NZ) {
      snprintf(g->Message, sizeof(g->Message), MSG(OPT_HEAD_RD_ERR), strerror(errno));
      goto err;
      } // endif size

    if (n[1] != lg || n[2] != nrec) {
      snprintf(g->Message, sizeof(g->Message), MSG(OPT_NOT_MATCH), filename);
      goto err;
      } // endif

    last = n[0];
    block = n[3];
    blk = block + 1;

    defp->To_Pos = (int*)PlugSubAlloc(g, NULL, blk * lg);

    if (fread(defp->To_Pos, lg, blk, opfile) != blk) {
      snprintf(g->Message, sizeof(g->Message), MSG(OPTBLK_RD_ERR), strerror(errno));
      goto err;
      } // endif size

    } // endif Ftype

  /*********************************************************************/
  /*  Read the Min/Max values from the opt file.                       */
  /*********************************************************************/
  for (; cdp; cdp = cdp->GetNext(), i++)
    if (cdp->GetOpt()) {
      lg = cdp->GetClen();
      blk = block;

      //  Now start the reading process.
      if (fread(n, sizeof(int), NZ, opfile) != NZ) {
        snprintf(g->Message, sizeof(g->Message), MSG(OPT_HEAD_RD_ERR), strerror(errno));
        goto err;
        } // endif size

      if (n[0] == -i) {
        // Read the XDB2 opt values from the opt file
        if (n[1] != lg || n[2] != nrec || n[3] != block) {
          snprintf(g->Message, sizeof(g->Message), MSG(OPT_NOT_MATCH), filename);
          goto err;
          } // endif

        if (fread(n, sizeof(int), 2, opfile) != 2) {
          snprintf(g->Message, sizeof(g->Message), MSG(OPT_HEAD_RD_ERR), strerror(errno));
          goto err;
          } // endif fread

        ndv = n[0]; nbm = n[1]; nbk = nbm * blk;

        if (cdp->GetNdv() < (int)ndv || !cdp->GetDval())
          cdp->SetDval(PlugSubAlloc(g, NULL, ndv * lg));

        cdp->SetNdv((int)ndv);

        if (fread(cdp->GetDval(), lg, ndv, opfile) != ndv) {
          snprintf(g->Message, sizeof(g->Message), MSG(OPT_DVAL_RD_ERR), strerror(errno));
          goto err;
          } // endif size

        if (newblk || cdp->GetNbm() < (int)nbm || !cdp->GetBmap())
          cdp->SetBmap(PlugSubAlloc(g, NULL, nbk * sizeof(int)));

        cdp->SetNbm((int)nbm);

        if (fread(cdp->GetBmap(), sizeof(int), nbk, opfile) != nbk) {
          snprintf(g->Message, sizeof(g->Message), MSG(OPT_BMAP_RD_ERR), strerror(errno));
          goto err;
          } // endif size

        cdp->SetXdb2(true);
      } else {
        // Read the Min/Max values from the opt file
        if (n[0] != i || n[1] != lg || n[2] != nrec || n[3] != block) {
          snprintf(g->Message, sizeof(g->Message), MSG(OPT_NOT_MATCH), filename);
          goto err;
          } // endif

        if (newblk || !cdp->GetMin())
          cdp->SetMin(PlugSubAlloc(g, NULL, blk * lg));

        if (fread(cdp->GetMin(), lg, blk, opfile) != blk) {
          snprintf(g->Message, sizeof(g->Message), MSG(OPT_MIN_RD_ERR), strerror(errno));
          goto err;
          } // endif size

        if (newblk || !cdp->GetMax())
          cdp->SetMax(PlugSubAlloc(g, NULL, blk * lg));

        if (fread(cdp->GetMax(), lg, blk, opfile) != blk) {
          snprintf(g->Message, sizeof(g->Message), MSG(OPT_MAX_RD_ERR), strerror(errno));
          goto err;
          } // endif size

        cdp->SetXdb2(false);
      } // endif n[0] (XDB2)

      } // endif Clustered

  defp->SetBlock(block);
  defp->Last = last;     // For Cardinality
  defp->SetAllocBlks(block);
  defp->SetOptimized(1);
  fclose(opfile);
  MaxSize = -1;          // Can be refined later
  return false;

 err:
  defp->RemoveOptValues(g);
  fclose(opfile);

  // Ignore error if not in mode CHK_OPT
  return (PlgGetUser(g)->Check & CHK_OPT) != 0;
  } // end of GetBlockValues

/***********************************************************************/
/*  This fonction is used while making XDB2 block optimization.        */
/*  It constructs for each elligible columns, the sorted list of the   */
/*  distinct values existing in the column. This function uses an      */
/*  algorithm that permit to get several sets of distinct values by    */
/*  reading the table only once, which cannot be done using a standard */
/*  SQL query.                                                         */
/***********************************************************************/
bool TDBDOS::GetDistinctColumnValues(PGLOBAL g, int nrec)
  {
  char   *p;
  int     rc, blk, n = 0;
  PDOSCOL colp;
  PDBUSER dup = PlgGetUser(g);

  /*********************************************************************/
  /*  Initialize progress information                                  */
  /*********************************************************************/
  p = (char *)PlugSubAlloc(g, NULL, 48 + strlen(Name));
  snprintf(p, 48 + strlen(Name), "%s%s", MSG(GET_DIST_VALS), Name);
  dup->Step = p;
  dup->ProgMax = GetProgMax(g);
  dup->ProgCur = 0;

  while ((rc = ReadDB(g)) == RC_OK) {
    for (colp = (PDOSCOL)Columns; colp; colp = (PDOSCOL)colp->Next)
      if (colp->Clustered == 2)
        if (colp->AddDistinctValue(g))
          return true;                   // Too many distinct values

#if defined(SOCKET_MODE)
    if (SendProgress(dup)) {
      safe_strcpy(g->Message, sizeof(g->Message), MSG(OPT_CANCELLED));
      return true;
    } else
#elif defined(THREAD)
    if (!dup->Step) {
      safe_strcpy(g->Message, sizeof(g->Message), MSG(OPT_CANCELLED));
      return true;
    } else
#endif     // THREAD
      dup->ProgCur = GetProgCur();

    n++;
    } // endwhile

  if (rc != RC_EF)
    return true;

  // Reset the number of table blocks
//nrec = ((PDOSDEF)To_Def)->GetElemt(); (or default value)
  blk = (n + nrec - 1) / nrec;
  Txfp->Block = blk;                    // Useful mainly for ZLBFAM ???

  // Set Nbm, Bmap for XDB2 columns
  for (colp = (PDOSCOL)Columns; colp; colp = (PDOSCOL)colp->Next)
    if (colp->Clustered == 2) {
//    colp->Cdp->SetNdv(colp->Ndv);
      colp->Nbm = (colp->Ndv + MAXBMP - 1) / MAXBMP;
      colp->Bmap = AllocValBlock(g, NULL, TYPE_INT, colp->Nbm * blk);
      } // endif Clustered

  return false;
  } // end of GetDistinctColumnValues

/***********************************************************************/
/*  Analyze the filter and construct the Block Evaluation Filter.      */
/*  This is possible when a filter contains predicates implying a      */
/*  column marked as "clustered" or "sorted" matched to a constant     */
/*  argument. It is then possible by comparison against the smallest   */
/*  and largest column values in each block to determine whether the   */
/*  filter condition will be always true or always false for the block.*/
/***********************************************************************/
PBF TDBDOS::InitBlockFilter(PGLOBAL g, PFIL filp)
  {
  bool blk = Txfp->Blocked;

  if (To_BlkFil)
    return To_BlkFil;      // Already done
  else if (!filp)
    return NULL;
  else if (blk) {
    if (Txfp->GetAmType() == TYPE_AM_DBF)
      /*****************************************************************/
      /*  If RowID is used in this query, block optimization cannot be */
      /*  used because currently the file must be read sequentially.   */
      /*****************************************************************/
      for (PCOL cp = Columns; cp; cp = cp->GetNext())
        if (cp->GetAmType() == TYPE_AM_ROWID && !((RIDBLK*)cp)->GetRnm())
          return NULL;

    } // endif blk

  int  i, op = filp->GetOpc(), opm = filp->GetOpm();
  bool cnv[2];
  PCOL colp;
  PXOB arg[2] = {NULL,NULL};
  PBF *fp = NULL, bfp = NULL;

  switch (op) {
    case OP_EQ:
    case OP_NE:
    case OP_GT:
    case OP_GE:
    case OP_LT:
    case OP_LE:
      if (! opm) {
        for (i = 0; i < 2; i++) {
          arg[i] = filp->Arg(i);
          cnv[i] = filp->Conv(i);
          } // endfor i

        bfp = CheckBlockFilari(g, arg, op, cnv);
        break;
        } // endif !opm

      // if opm, pass thru
      // fall through
    case OP_IN:
      if (filp->GetArgType(0) == TYPE_COLBLK &&
          filp->GetArgType(1) == TYPE_ARRAY) {
        arg[0] = filp->Arg(0);
        arg[1] = filp->Arg(1);
        colp = (PCOL)arg[0];

        if (colp->GetTo_Tdb() == this) {
          // Block evaluation is possible for...
          if (colp->GetAmType() == TYPE_AM_ROWID) {
            // Special column ROWID and constant array, but
            // currently we don't know how to retrieve a RowID
            // from a DBF table that is not sequentially read.
//          if (Txfp->GetAmType() != TYPE_AM_DBF ||
//              ((RIDBLK*)arg[0])->GetRnm())
              bfp = new(g) BLKSPCIN(g, this, op, opm, arg, Txfp->Nrec);

          } else if (blk && Txfp->Nrec > 1 && colp->IsClustered())
          {
            // Clustered column and constant array
            if (colp->GetClustered() == 2)
              bfp = new(g) BLKFILIN2(g, this, op, opm, arg);
            else
              bfp = new(g) BLKFILIN(g, this, op, opm, arg);
          }
        } // endif this

#if 0
      } else if (filp->GetArgType(0) == TYPE_SCALF &&
                 filp->GetArgType(1) == TYPE_ARRAY) {
        arg[0] = filp->Arg(0);
        arg[1] = filp->Arg(1);

        if (((PSCALF)arg[0])->GetOp() == OP_ROW &&
            arg[1]->GetResultType() == TYPE_LIST) {
          PARRAY  par = (PARRAY)arg[1];
          LSTVAL *vlp = (LSTVAL*)par->GetValue();

          ((SFROW*)arg[0])->GetParms(n);

          if (n != vlp->GetN())
            return NULL;
          else
            n = par->GetNval();

          arg[1] = new(g) CONSTANT(vlp);
          fp = (PBF*)PlugSubAlloc(g, NULL, n * sizeof(PBF));
          cnv[0] = cnv[1] = false;

          if (op == OP_IN)
            op = OP_EQ;

          for (i = 0; i < n; i++) {
            par->GetNthValue(vlp, i);

            if (!(fp[i] = CheckBlockFilari(g, arg, op, cnv)))
              return NULL;

            } // endfor i

          bfp = new(g) BLKFILLOG(this, (opm == 2 ? OP_AND : OP_OR), fp, n);
          } // endif ROW
#endif // 0

      } // endif Type

      break;
    case OP_AND:
    case OP_OR:
      fp = (PBF*)PlugSubAlloc(g, NULL, 2 * sizeof(PBF));
      fp[0] = InitBlockFilter(g, (PFIL)(filp->Arg(0)));
      fp[1] = InitBlockFilter(g, (PFIL)(filp->Arg(1)));

      if (fp[0] || fp[1])
        bfp = new(g) BLKFILLOG(this, op, fp, 2);

      break;
    case OP_NOT:
      fp = (PBF*)PlugSubAlloc(g, NULL, sizeof(PBF));

      if ((*fp = InitBlockFilter(g, (PFIL)(filp->Arg(0)))))
        bfp = new(g) BLKFILLOG(this, op, fp, 1);

      break;
    case OP_LIKE:
    default:
      break;
    } // endswitch op

  return bfp;
  } // end of InitBlockFilter

/***********************************************************************/
/*  Analyze the passed arguments and construct the Block Filter.       */
/***********************************************************************/
PBF TDBDOS::CheckBlockFilari(PGLOBAL g, PXOB *arg, int op, bool *cnv)
  {
//int     i, n1, n2, ctype = TYPE_ERROR, n = 0, type[2] = {0,0};
//bool    conv = false, xdb2 = false, ok = false, b[2];
//PXOB   *xarg1, *xarg2 = NULL, xp[2];
  int     i, n = 0, type[2] = {0,0};
  bool    conv = false, xdb2 = false;
  PXOB    xp[2];
  PCOL    colp;
  PBF     bfp = NULL;

  for (i = 0; i < 2; i++) {
    switch (arg[i]->GetType()) {
      case TYPE_CONST:
        type[i] = 1;
 //     ctype = arg[i]->GetResultType();
        break;
      case TYPE_COLBLK:
        conv = cnv[i];
        colp = (PCOL)arg[i];

        if (colp->GetTo_Tdb() == this) {
          if (colp->GetAmType() == TYPE_AM_ROWID) {
            // Currently we don't know how to retrieve a RowID
            // from a DBF table that is not sequentially read.
//          if (Txfp->GetAmType() != TYPE_AM_DBF ||
//              ((RIDBLK*)arg[i])->GetRnm())
              type[i] = 5;

          } else if (Txfp->Blocked && Txfp->Nrec > 1 &&
                     colp->IsClustered()) {
            type[i] = 2;
            xdb2 = colp->GetClustered() == 2;
            } // endif Clustered

        } else if (colp->GetColUse(U_CORREL)) {
          // This is a column pointing to the outer query of a
          // correlated subquery, it has a constant value during
          // each execution of the subquery.
          type[i] = 1;
//        ctype = arg[i]->GetResultType();
        } // endif this

        break;
//    case TYPE_SCALF:
//      if (((PSCALF)arg[i])->GetOp() == OP_ROW) {
//        sfr[i] = (SFROW*)arg[i];
//        type[i] = 7;
//        } // endif Op

//      break;
      default:
        break;
      } // endswitch ArgType

    if (!type[i])
      break;

    n += type[i];
    } // endfor i

  if (n == 3 || n == 6) {
    if (conv) {
      // The constant has not the good type and will not match
      // the block min/max values. Warn and abort.
      snprintf(g->Message, sizeof(g->Message), "Block opt: %s", MSG(VALTYPE_NOMATCH));
      PushWarning(g, this);
      return NULL;
      } // endif Conv

    if (type[0] == 1) {
      // Make it always as Column-op-Value
      *xp = arg[0];
      arg[0] = arg[1];
      arg[1] = *xp;

      switch (op) {
        case OP_GT: op = OP_LT; break;
        case OP_GE: op = OP_LE; break;
        case OP_LT: op = OP_GT; break;
        case OP_LE: op = OP_GE; break;
        } // endswitch op

      } // endif

#if defined(_DEBUG)
//  assert(arg[0]->GetResultType() == ctype);
#endif

    if (n == 3) {
      if (xdb2) {
        if (((PDOSCOL)arg[0])->GetNbm() == 1)
          bfp = new(g) BLKFILAR2(g, this, op, arg);
        else    // Multiple bitmap made of several ULONG's
          bfp = new(g) BLKFILMR2(g, this, op, arg);
      } else
        bfp = new(g) BLKFILARI(g, this, op, arg);

    } else // n = 6
      bfp = new(g) BLKSPCARI(this, op, arg, Txfp->Nrec);

#if 0
  } else if (n == 8 || n == 14) {
    if (n == 8 && ctype != TYPE_LIST) {
      // Should never happen
      safe_strcpy(g->Message, sizeof(g->Message), "Block opt: bad constant");
			throw 99;
		} // endif Conv

    if (type[0] == 1) {
      // Make it always as Column-op-Value
      sfr[0] = sfr[1];
      arg[1] = arg[0];

      switch (op) {
        case OP_GT: op = OP_LT; break;
        case OP_GE: op = OP_LE; break;
        case OP_LT: op = OP_GT; break;
        case OP_LE: op = OP_GE; break;
        } // endswitch op

      } // endif

    xarg1 = sfr[0]->GetParms(n1);

    if (n == 8) {
      vlp = (LSTVAL*)arg[1]->GetValue();
      n2 = vlp->GetN();
      xp[1] = new(g) CONSTANT((PVAL)NULL);
    } else
      xarg2 = sfr[1]->GetParms(n2);

    if (n1 != n2)
      return NULL;             // Should we flag an error ?

    fp = (PBF*)PlugSubAlloc(g, NULL, n1 * sizeof(PBF));

    for (i = 0; i < n1; i++) {
      xp[0] = xarg1[i];

      if (n == 8)
        ((CONSTANT*)xp[1])->SetValue(vlp->GetSubVal(i));
      else
        xp[1] = xarg2[i];

      b[0] = b[1] = (xp[0]->GetResultType() != xp[1]->GetResultType());
      ok |= ((fp[i] = CheckBlockFilari(g, xp, op, b)) != NULL);
      } // endfor i

    if (ok)
      bfp = new(g) BLKFILLOG(this, OP_AND, fp, n1);
#endif // 0

  } // endif n

  return bfp;
  } // end of CheckBlockFilari

/***********************************************************************/
/*  ResetBlkFil: reset the block filter and restore filtering, or make */
/*  the block filter if To_Filter was not set when opening the table.  */
/***********************************************************************/
void TDBDOS::ResetBlockFilter(PGLOBAL g)
  {
  if (!To_BlkFil) {
    if (To_Filter)
      if ((To_BlkFil = InitBlockFilter(g, To_Filter))) {
        htrc("BlkFil=%p\n", To_BlkFil);
        MaxSize = -1;      // To be recalculated
        } // endif To_BlkFil
    
    return;
    } // endif To_BlkFil

  To_BlkFil->Reset(g);

  if (SavFil && !To_Filter) {
    // Restore filter if it was disabled by optimization
    To_Filter = SavFil;
    SavFil = NULL;
    } // endif

  Beval = 0;
  } // end of ResetBlockFilter

/***********************************************************************/
/*  Block optimization: evaluate the block index filter against        */
/*  the min and max values of this block and return:                   */
/*  RC_OK: if some records in the block can meet filter criteria.      */
/*  RC_NF: if no record in the block can meet filter criteria.         */
/*  RC_EF: if no record in the remaining file can meet filter criteria.*/
/*  In addition, temporarily supress filtering if all the records in   */
/*  the block meet filter criteria.                                    */
/***********************************************************************/
int TDBDOS::TestBlock(PGLOBAL g)
  {
  int rc = RC_OK;

  if (To_BlkFil && Beval != 2) {
    // Check for block filtering evaluation
    if (Beval == 1) {
      // Filter was removed for last block, restore it
      To_Filter = SavFil;
      SavFil = NULL;
      } // endif Beval

    // Check for valid records in new block
    switch (Beval = To_BlkFil->BlockEval(g)) {
      case -2:            // No more valid values in file
        rc = RC_EF;
        break;
      case -1:            // No valid values in block
        rc = RC_NF;
        break;
      case 1:             // All block values are valid
      case 2:             // All subsequent file values are Ok
        // Before suppressing the filter for the block(s) it is
        // necessary to reset the filtered columns to NOT_READ
        // so their new values are retrieved by the SELECT list.
        if (To_Filter) // Can be NULL when externally called (XDB)
          To_Filter->Reset();

        SavFil = To_Filter;
        To_Filter = NULL; // So remove filter
      } // endswitch Beval

    if (trace(1))
      htrc("BF Eval Beval=%d\n", Beval);

    } // endif To_BlkFil

  return rc;
  } // end of TestBlock

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

  Mode = MODE_READ;
  Use = USE_READY;
  dfp = (PDOSDEF)To_Def;

  if (!Cardinality(g)) {
    // Void table erase eventual index file(s)
    (void)dfp->DeleteIndexFile(g, NULL);
    return RC_OK;
  } else
    fixed = Ftype != RECFM_VAR;

  // Are we are called from CreateTable or CreateIndex?
  if (pxdf) {
    if (!add && dfp->GetIndx()) {
      safe_strcpy(g->Message, sizeof(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())) {
          snprintf(g->Message, sizeof(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

	try {
		// 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))) {
					snprintf(g->Message, sizeof(g->Message), MSG(INDX_COL_NOTIN), kdp->GetName(), Name);
					goto err;
				} else if (colp->GetResultType() == TYPE_DECIM) {
					snprintf(g->Message, sizeof(g->Message), "Decimal columns are not indexable yet");
					goto err;
				} // endif Type

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

		keycols = (PCOL*)PlugSubAlloc(g, NULL, n * sizeof(PCOL));
		sep = dfp->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

	} catch (int n) {
		if (trace(1))
			htrc("Exception %d: %s\n", n, g->Message);
		rc = RC_FX;
	} catch (const char *msg) {
		safe_strcpy(g->Message, sizeof(g->Message), msg);
		rc = RC_FX;
	} // end catch

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

  return rc;

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

  return RC_FX;
  } // end of MakeIndex

/***********************************************************************/
/*  Make a dynamic index.                                              */
/***********************************************************************/
bool TDBDOS::InitialyzeIndex(PGLOBAL g, volatile PIXDEF xdp, bool sorted)
{
  int     k;
  volatile bool dynamic;
  bool    brc;
  PCOL    colp;
  PCOLDEF cdp;
  PVAL    valp;
  PXLOAD  pxp;
  volatile PKXBASE kxp;
  PKPDEF  kdp;

  if (!xdp && !(xdp = To_Xdp)) {
    safe_strcpy(g->Message, sizeof(g->Message), "NULL dynamic index");
    return true;
  } else
    dynamic = To_Filter && xdp->IsUnique() && xdp->IsDynamic();
//  dynamic = To_Filter && xdp->IsDynamic();      NIY

  // Allocate the key columns definition block
  Knum = xdp->GetNparts();
  To_Key_Col = (PCOL*)PlugSubAlloc(g, NULL, Knum * sizeof(PCOL));

  // Get the key column description list
  for (k = 0, kdp = xdp->GetToKeyParts(); kdp; kdp = kdp->GetNext())
    if (!(colp = ColDB(g, kdp->GetName(), 0)) || colp->InitValue(g)) {
      snprintf(g->Message, sizeof(g->Message), "Wrong column %s", kdp->GetName());
      return true;
    } else
      To_Key_Col[k++] = colp;

#if defined(_DEBUG)
  if (k != Knum) {
    snprintf(g->Message, sizeof(g->Message), "Key part number mismatch for %s",
                        xdp->GetName());
    return 0;
    } // endif k
#endif   // _DEBUG

  // Allocate the pseudo constants that will contain the key values
  To_Link = (PXOB*)PlugSubAlloc(g, NULL, Knum * sizeof(PXOB));

  for (k = 0, kdp = xdp->GetToKeyParts(); kdp; k++, kdp = kdp->GetNext()) {
    if ((cdp = Key(k)->GetCdp()))
      valp = AllocateValue(g, cdp->GetType(), cdp->GetLength());
    else {                        // Special column ?
      colp = Key(k);
      valp = AllocateValue(g, colp->GetResultType(), colp->GetLength());
    } // endif cdp

    To_Link[k]= new(g) CONSTANT(valp);
    } // endfor k

  // Make the index on xdp
  if (!xdp->IsAuto()) {
    if (!dynamic) {
      if (((PDOSDEF)To_Def)->Huge)
        pxp = new(g) XHUGE;
      else
        pxp = new(g) XFILE;

    } else
      pxp = NULL;

    if (Knum == 1)            // Single index
      kxp = new(g) XINDXS(this, xdp, pxp, To_Key_Col, To_Link);
    else                      // Multi-Column index
      kxp = new(g) XINDEX(this, xdp, pxp, To_Key_Col, To_Link);

  } else                      // Column contains same values as ROWID
    kxp = new(g) XXROW(this);

	try {
    if (dynamic) {
      ResetBlockFilter(g);
      kxp->SetDynamic(dynamic);
      brc = kxp->Make(g, xdp);
    } else
      brc = kxp->Init(g);

    if (!brc) {
      if (Txfp->GetAmType() == TYPE_AM_BLK) {
        // Cannot use indexing in DOS block mode
        Txfp = new(g) DOSFAM((PBLKFAM)Txfp, (PDOSDEF)To_Def);
        Txfp->AllocateBuffer(g);
        To_BlkFil = NULL;
        } // endif AmType

      To_Kindex= kxp;

      if (!(sorted && To_Kindex->IsSorted()) &&
          ((Mode == MODE_UPDATE && IsUsingTemp(g)) ||
           (Mode == MODE_DELETE && Txfp->GetAmType() != TYPE_AM_DBF)))
        Indxd = true;

      } // endif brc

	} catch (int n) {
		if (trace(1))
			htrc("Exception %d: %s\n", n, g->Message);
		brc = true;
	} catch (const char *msg) {
		safe_strcpy(g->Message, sizeof(g->Message), msg);
		brc = true;
	} // end catch

	return brc;
} // end of InitialyzeIndex

/***********************************************************************/
/*  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)
  {
  if (To_Kindex) {
    /*******************************************************************/
    /*  Don't know how to retrieve RowID from file address.            */
    /*******************************************************************/
    snprintf(g->Message, sizeof(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)
  {
  int n = Txfp->Cardinality(NULL);

  if (!g)
    return (Mode == MODE_ANY) ? 1 : n;

  if (Cardinal < 0) {
    if (!Txfp->Blocked && n == 0) {
      // Info command, we try to return exact row number
      PDOSDEF dfp = (PDOSDEF)To_Def;
      PIXDEF  xdp = dfp->To_Indx;

      if (xdp && xdp->IsValid()) {
        // Cardinality can be retreived from one index
        PXLOAD  pxp;
    
        if (dfp->Huge)
          pxp = new(g) XHUGE;
        else
          pxp = new(g) XFILE;
    
        PXINDEX kxp = new(g) XINDEX(this, xdp, pxp, NULL, NULL);
    
        if (!(kxp->GetAllSizes(g, Cardinal)))
          return Cardinal;
    
        } // endif Mode

      if (Mode == MODE_ANY && ExactInfo()) {
        // Using index impossible or failed, do it the hard way
        Mode = MODE_READ;
        To_Line = (char*)PlugSubAlloc(g, NULL, (size_t)Lrecl + 1);
    
        if (Txfp->OpenTableFile(g))
          return (Cardinal = Txfp->Cardinality(g));
    
        for (Cardinal = 0; n != RC_EF;)
          if (!(n = Txfp->ReadBuffer(g)))
            Cardinal++;
    
        Txfp->CloseTableFile(g, false);
        Mode = MODE_ANY;
      } else {
        // Return the best estimate
        int len = GetFileLength(g);

        if (len >= 0) {
          int rec;

          if (trace(1))
            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 average record length. */
          /*************************************************************/
          rec = ((PDOSDEF)To_Def)->Ending;

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

          Cardinal = (len + rec - 1) / rec;

          if (trace(1))
            htrc("avglen=%d MaxSize%d\n", rec, Cardinal);

          } // endif len

      } // endif Mode

    } else
      Cardinal = Txfp->Cardinality(g);

    } // endif Cardinal

  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) {
      int rec;

      if (trace(1))
        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 minimum record length.           */
      /*****************************************************************/
      rec = EstimatedLength() + ((PDOSDEF)To_Def)->Ending;
      MaxSize = (len + rec - 1) / rec;

      if (trace(1))
        htrc("avglen=%d MaxSize%d\n", rec, MaxSize);

      } // endif len

  } else
    MaxSize = Cardinality(g);

  return MaxSize;
  } // end of GetMaxSize

/***********************************************************************/
/*  DOS EstimatedLength. Returns an estimated minimum line length.     */
/***********************************************************************/
int TDBDOS::EstimatedLength(void)
  {
  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())
		if (!(cdp->Flags & (U_VIRTUAL|U_SPECIAL)))
      dep = MY_MAX(dep, cdp->GetOffset());

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

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

  return (utp == TMP_YES || utp == TMP_FORCE ||
         (utp == 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(1))
    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.          */
    /*******************************************************************/
    if (!To_Kindex) {
      Txfp->Rewind();       // see comment in Work.log

      if (SkipHeader(g))
        return true;

    } else
      /*****************************************************************/
      /*  Table is to be accessed through a sorted index table.        */
      /*****************************************************************/
      To_Kindex->Reset();

    ResetBlockFilter(g);
    return false;
    } // endif use

  if (Mode == MODE_DELETE && !Next && Txfp->GetAmType() != TYPE_AM_DOS
#if defined(BSON_SUPPORT)
                                   && Txfp->GetAmType() != TYPE_AM_BIN
#endif   // BSON_SUPPORT
		                               && Txfp->GetAmType() != TYPE_AM_MGO) {
    // 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 && 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(GZ_SUPPORT)
    else if (Txfp->GetAmType() == TYPE_AM_GZ)
      Txfp = new(g) GZFAM((PDOSDEF)To_Def);
#endif   // GZ_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 block filter tree if evaluation is possible.        */
  /*********************************************************************/
  To_BlkFil = InitBlockFilter(g, To_Filter);

  /*********************************************************************/
	/*  Lrecl does not include line ending															 */
	/*********************************************************************/
	size_t linelen = Lrecl + ((PDOSDEF)To_Def)->Ending + 1;

  To_Line = (char*)PlugSubAlloc(g, NULL, linelen);

  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, linelen);

  if (trace(1))
    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(2))
    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 non null last 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(2))
          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(2))
    htrc(" ReadDB: this=%p To_Line=%p\n", this, To_Line);

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

/***********************************************************************/
/*  PrepareWriting: Prepare the line to write.                         */
/***********************************************************************/
bool TDBDOS::PrepareWriting(PGLOBAL)
  {
  if (Ftype == RECFM_VAR && (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

  return false;
  } // end of PrepareWriting

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

  // Make the line to write
  if (PrepareWriting(g))
    return RC_FX;

  if (trace(2))
    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, Abort);
  RestoreNrec();
  } // end of CloseDB

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

/***********************************************************************/
/*  DOSCOL public constructor (also called by MAPCOL).                 */
/***********************************************************************/
DOSCOL::DOSCOL(PGLOBAL g, PCOLDEF cdp, PTDB tp, PCOL cp, int i, PCSZ 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;
  Clustered = cdp->GetOpt();
  Sorted = (cdp->GetOpt() == 2) ? 1 : 0;
  Ndv = 0;                // Currently used only for XDB2
  Nbm = 0;                // Currently used only for XDB2
  Min = NULL;
  Max = NULL;
  Bmap = NULL;
  Dval = NULL;
  Buf = NULL;

  if (txfp && txfp->Blocked && Opt && (cdp->GetMin() || cdp->GetDval())) {
    int nblk = txfp->GetBlock();

    Clustered = (cdp->GetXdb2()) ? 2 : 1;
    Sorted = (cdp->GetOpt() > 1) ? 1 : 0;   // Currently ascending only

    if (Clustered == 1) {
      Min = AllocValBlock(g, cdp->GetMin(), Buf_Type, nblk, Long, prec);
      Max = AllocValBlock(g, cdp->GetMax(), Buf_Type, nblk, Long, prec);
    } else {        // Clustered == 2
      // Ndv is the number of distinct values in Dval. Ndv and Nbm
      // may be 0 when optimizing because Ndval is not filled yet,
      // but the size of the passed Dval memory block is Ok.
      Ndv = cdp->GetNdv();
      Dval = AllocValBlock(g, cdp->GetDval(), Buf_Type, Ndv, Long, prec);

      // Bmap cannot be allocated when optimizing, we must know Nbm first
      if ((Nbm = cdp->GetNbm()))
        Bmap = AllocValBlock(g, cdp->GetBmap(), TYPE_INT, Nbm * nblk);

    } // endif Clustered

    } // endif Opt

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

  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;
        case 'D':                 // Decimal separator
          Dsp = *(++p);
          break;
        } // endswitch p

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

  if (trace(1))
    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;
  Dsp = col1->Dsp;
  Nod = col1->Nod;
  Dcm = col1->Dcm;
  OldVal = col1->OldVal;
  Buf = col1->Buf;
  Clustered = col1->Clustered;
  Sorted = col1->Sorted;
  Min = col1->Min;
  Max = col1->Max;
  Bmap = col1->Bmap;
  Dval = col1->Dval;
  Ndv = col1->Ndv;
  Nbm = col1->Nbm;
  } // 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)) {
    snprintf(g->Message, sizeof(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_DOUBLE)
      // Float values must be written with the correct (column) precision
      // Note: maybe this should be forced by ShowValue instead of this ?
      value->SetPrec(GetScale());

    Value = value;            // Directly access the external value
  } else {
    // Values are not of the (good) column type
    if (check) {
      snprintf(g->Message, sizeof(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 (!Buf && IsTypeNum(Buf_Type))
		Buf = (char*)PlugSubAlloc(g, NULL, MY_MAX(64, Long + 1));
	else // Text columns do not need additional buffer
		Buf = (char*)Value->GetTo_Val();

  // 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 = NULL;
  int     i, rc;
  int     field;
  bool    err = false;
  double  dval;
  PTDBDOS tdbp = (PTDBDOS)To_Tdb;

  if (trace(2))
    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)
        snprintf(g->Message, sizeof(g->Message), MSG(INV_DEF_READ), rc);

			throw 11;
		} // endif

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

  /*********************************************************************/
  /*  For a variable length file, check if the field exists.           */
  /*********************************************************************/
  if ((tdbp->Ftype == RECFM_VAR || tdbp->Ftype == RECFM_CSV)
				&& strlen(tdbp->To_Line) < (unsigned)Deplac)
    field = 0;
  else if (Dsp)
    for(i = 0; i < field; i++)
      if (p[i] == Dsp)
        p[i] = '.';

  switch (tdbp->Ftype) {
    case RECFM_VAR:
    case RECFM_FIX:            // Fixed length text file
		case RECFM_CSV:            // Variable length CSV or FMT 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:
          err = Value->SetValue_char(p, field - Dcm);
          break;
        case TYPE_DOUBLE:
          if (!(err = Value->SetValue_char(p, field))) {
            dval = Value->GetFloatValue();

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

            Value->SetValue(dval);
          } // endif err

          break;
        default:
          err = Value->SetValue_char(p, field);

          if (!err && Buf_Type == TYPE_DECIM) {
            char* s = Value->GetCharValue();

            if (!(err = ((i = strlen(s)) >= Value->GetClen()))) {
              for (int d = Dcm + 1; d; i--, d--)
                s[i + 1] = s[i];

              s[i + 1] = '.';
            } // endif err

          } // endif DECIM

          break;
      } // endswitch Buf_Type

      else
        err = Value->SetValue_char(p, field);

      break;
    default:
      snprintf(g->Message, sizeof(g->Message), MSG(BAD_RECFM), tdbp->Ftype);
			throw 34;
	} // endswitch Ftype

  if (err) {
    snprintf(g->Message, sizeof(g->Message), "Out of range value for column %s at row %d",
      Name, tdbp->RowNumber(g));
    PushWarning(g, tdbp);
  } // endif err

  // 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, fmt[32];
  int     i, k, n, len, field;
  PTDBDOS tdbp = (PTDBDOS)To_Tdb;

  if (trace(2))
    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(2))
    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 = MY_MIN(len - Deplac, Long);

    } // endif Ftype

  if (trace(2))
    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:
          safe_strcpy(fmt, sizeof(fmt), (Ldz) ? "%0*hd" : "%*.hd");
          i = 0;

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

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

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

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

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

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

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

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

          len = strlen(Buf);
          break;
        default:
          snprintf(g->Message, sizeof(g->Message), "Invalid field format for column %s", Name);
					throw 31;
			} // endswitch BufType

			n = strlen(Buf);
    } else                 // Standard CONNECT format
      n = Value->ShowValue(Buf, field);

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

    if ((len = n) > field) {
			char *p = Value->GetCharString(Buf);

      snprintf(g->Message, sizeof(g->Message), MSG(VALUE_TOO_LONG), p, Name, field);
			throw 31;
		} else if (Dsp)
      for (i = 0; i < len; i++)
        if (Buf[i] == '.')
          Buf[i] = Dsp; 

    if (trace(2))
      htrc("buffer=%s\n", Buf);

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

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

    } // endif Status

  } 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)) {
      snprintf(g->Message, sizeof(g->Message), MSG(BIN_F_TOO_LONG),
                          Name, Value->GetSize(), Long);
      throw 31;
    } // endif

  } // end of WriteColumn

/***********************************************************************/
/*  SetMinMax: Calculate minimum and maximum values for one block.     */
/*  Note: TYPE_STRING is stored and processed with zero ended strings  */
/*  to be matching the way the FILTER Eval function processes them.    */
/***********************************************************************/
bool DOSCOL::SetMinMax(PGLOBAL g)
  {
  PTDBDOS tp = (PTDBDOS)To_Tdb;

  ReadColumn(g);           // Extract column value from current line

  if (CheckSorted(g))
    return true;

  if (!tp->Txfp->CurNum) {
    Min->SetValue(Value, tp->Txfp->CurBlk);
    Max->SetValue(Value, tp->Txfp->CurBlk);
  } else {
    Min->SetMin(Value, tp->Txfp->CurBlk);
    Max->SetMax(Value, tp->Txfp->CurBlk);
  } // endif CurNum

  return false;
  } // end of SetMinMax

/***********************************************************************/
/*  SetBitMap: Calculate the bit map of existing values in one block.  */
/*  Note: TYPE_STRING is processed with zero ended strings             */
/*  to be matching the way the FILTER Eval function processes them.    */
/***********************************************************************/
bool DOSCOL::SetBitMap(PGLOBAL g)
  {
  int     i, m, n;
  uint   *bmp;
  PTDBDOS tp = (PTDBDOS)To_Tdb;
  PDBUSER dup = PlgGetUser(g);

  n = tp->Txfp->CurNum;
  bmp = (uint*)Bmap->GetValPtr(Nbm * tp->Txfp->CurBlk);

  // Extract column value from current line
  ReadColumn(g);

  if (CheckSorted(g))
    return true;

  if (!n)                      // New block
    for (m = 0; m < Nbm; m++)
      bmp[m] = 0;             // Reset the new bit map

  if ((i = Dval->Find(Value)) < 0) {
    char buf[32];

    snprintf(g->Message, sizeof(g->Message), MSG(DVAL_NOTIN_LIST),
      Value->GetCharString(buf), Name);
    return true;
  } else if (i >= dup->Maxbmp) {
    snprintf(g->Message, sizeof(g->Message), MSG(OPT_LOGIC_ERR), i);
    return true;
  } else {
    m = i / MAXBMP;
#if defined(_DEBUG)
    assert (m < Nbm);
#endif   // _DEBUG
    bmp[m] |= (1 << (i % MAXBMP));
  } // endif's i

  return false;
  } // end of SetBitMap

/***********************************************************************/
/*  Checks whether a column declared as sorted is sorted indeed.       */
/***********************************************************************/
bool DOSCOL::CheckSorted(PGLOBAL g)
  {
  if (Sorted)
  {
    if (OldVal) {
      // Verify whether this column is sorted all right
      if (OldVal->CompareValue(Value) > 0) {
        // Column is no more in ascending order
        snprintf(g->Message, sizeof(g->Message), MSG(COL_NOT_SORTED), Name, To_Tdb->GetName());
        Sorted = false;
        return true;
      } else
        OldVal->SetValue_pval(Value);

    } else
      OldVal = AllocateValue(g, Value);
  }
  return false;
  } // end of CheckSorted

/***********************************************************************/
/*  AddDistinctValue: Check whether this value already exist in the    */
/*  list and if not add it to the distinct values list.                */
/***********************************************************************/
bool DOSCOL::AddDistinctValue(PGLOBAL g)
  {
  bool found = false;
  int  i, m, n;

  ReadColumn(g);           // Extract column value from current line

  // Perhaps a better algorithm can be used when Ndv gets bigger
  // Here we cannot use Find because we must get the index of where
  // to insert a new value if it is not found in the array.
  for (n = 0; n < Ndv; n++) {
    m = Dval->CompVal(Value, n);

    if (m > 0)
      continue;
    else if (!m)
      found = true;        // Already there

    break;
    } // endfor n

  if (!found) {
    // Check whether we have room for an additional value
    if (Ndv == Freq) {
      // Too many values because of wrong Freq setting
      snprintf(g->Message, sizeof(g->Message), MSG(BAD_FREQ_SET), Name);
      return true;
      } // endif Ndv

    // New value, add it to the list before the nth value
    Dval->SetNval(Ndv + 1);

    for (i = Ndv; i > n; i--)
      Dval->Move(i - 1, i);

    Dval->SetValue(Value, n);
    Ndv++;
    } // endif found

  return false;
  } // end of AddDistinctValue

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