/************* TabFix C++ Program Source Code File (.CPP) **************/
/* PROGRAM NAME: TABFIX                                                */
/* -------------                                                       */
/*  Version 4.9.2                                                      */
/*                                                                     */
/* COPYRIGHT:                                                          */
/* ----------                                                          */
/*  (C) Copyright to the author Olivier BERTRAND          1998-2017    */
/*                                                                     */
/* WHAT THIS PROGRAM DOES:                                             */
/* -----------------------                                             */
/*  This program are the TDBFIX class DB routines.                     */
/*                                                                     */
/***********************************************************************/

/***********************************************************************/
/*  Include relevant section of system dependant header files.         */
/***********************************************************************/
#include "my_global.h"
#if defined(_WIN32)
#include <io.h>
#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 <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#else   // !UNIX
#include <io.h>
#endif  // !UNIX
#include <fcntl.h>
#endif  // !_WIN32

/***********************************************************************/
/*  Include application header files:                                  */
/***********************************************************************/
#include "global.h"      // global declares
#include "plgdbsem.h"    // DB application declares
#include "filamfix.h"
#include "filamdbf.h"
#include "tabfix.h"      // TDBFIX, FIXCOL classes declares
#include "array.h"
#include "blkfil.h"

/***********************************************************************/
/*  DB static variables.                                               */
/***********************************************************************/
extern int num_read, num_there, num_eq[2];               // Statistics
char BINCOL::Endian = 'H';

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

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

/***********************************************************************/
/*  Implementation of the TDBFIX class.                                */
/***********************************************************************/
TDBFIX::TDBFIX(PDOSDEF tdp, PTXF txfp) : TDBDOS(tdp, txfp)
  {
  Teds = tdp->Teds;              // For BIN tables
  } // end of TDBFIX standard constructor

TDBFIX::TDBFIX(PGLOBAL g, PTDBFIX tdbp) : TDBDOS(g, tdbp)
  {
  Teds = tdbp->Teds;
  } // end of TDBFIX copy constructor

// Method
PTDB TDBFIX::Clone(PTABS t)
  {
  PTDB    tp;
  PGLOBAL g = t->G;

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

  if (Ftype == RECFM_VAR || Ftype == RECFM_FIX) {
    // File is text
    PDOSCOL cp1, cp2;

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

  } else {
    // File is binary
    PBINCOL cp1, cp2;

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

  } // endif Ftype

  return tp;
  } // end of Clone

/***********************************************************************/
/*  Reset read/write position values.                                  */
/***********************************************************************/
void TDBFIX::ResetDB(void)
  {
  TDBDOS::ResetDB();
  } // end of ResetDB

/***********************************************************************/
/*  Allocate FIX (DOS) or BIN column description block.                */
/***********************************************************************/
PCOL TDBFIX::MakeCol(PGLOBAL g, PCOLDEF cdp, PCOL cprec, int n)
  {
  if (Ftype == RECFM_BIN)
    return new(g) BINCOL(g, cdp, this, cprec, n);
  else
    return new(g) DOSCOL(g, cdp, this, cprec, n);

  } // end of MakeCol

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

  To_Filter = NULL;                     // Disable filtering
//To_BlkIdx = NULL;                     // and block filtering
  To_BlkFil = NULL;                     // and index filtering
  Cardinality(g);                       // If called by create
  RestoreNrec();                        // May have been modified
  MaxSize = -1;                         // Size must be recalculated
  Cardinal = -1;                        // as well as Cardinality

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

  if (dop) {
    Columns = NULL;                     // Not used anymore
    Txfp->Reset();
//  OldBlk = CurBlk = -1;
//  ReadBlks = CurNum = Rbuf = Modif = 0;
    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
    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 indexes.
      rc = MakeIndex(g, NULL, FALSE);

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

  return rc;
  } // end of ResetTableOpt

/***********************************************************************/
/*  Reset the Nrec and BlkSize values that can have been modified.     */
/***********************************************************************/
void TDBFIX::RestoreNrec(void)
  {
  if (!Txfp->Padded) {
    Txfp->Nrec = (To_Def && To_Def->GetElemt()) ? To_Def->GetElemt()
                                                : DOS_BUFF_LEN;
    Txfp->Blksize = Txfp->Nrec * Txfp->Lrecl;

    if (Cardinal >= 0)
      Txfp->Block = (Cardinal > 0) 
                  ? (Cardinal + Txfp->Nrec - 1) / Txfp->Nrec : 0;

    } // endif Padded

  } // end of RestoreNrec

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

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

  return Cardinal;
  } // end of Cardinality

/***********************************************************************/
/*  FIX GetMaxSize: returns file size in number of lines.              */
/***********************************************************************/
int TDBFIX::GetMaxSize(PGLOBAL g)
  {
  if (MaxSize < 0) {
    MaxSize = Cardinality(g);

    if (MaxSize > 0 && (To_BlkFil = InitBlockFilter(g, To_Filter))
                    && !To_BlkFil->Correlated()) {
      // Use BlockTest to reduce the estimated size
      MaxSize = Txfp->MaxBlkSize(g, MaxSize);
      ResetBlockFilter(g);
      } // endif To_BlkFil

    } // endif MaxSize

  return MaxSize;
  } // end of GetMaxSize

/***********************************************************************/
/*  FIX ResetSize: Must reset Headlen for DBF tables only.             */
/***********************************************************************/
void TDBFIX::ResetSize(void)
  {
  if (Txfp->GetAmType() == TYPE_AM_DBF)
    Txfp->Headlen = 0;

  MaxSize = Cardinal = -1;
  } // end of ResetSize

/***********************************************************************/
/*  FIX GetProgMax: get the max value for progress information.        */
/***********************************************************************/
int TDBFIX::GetProgMax(PGLOBAL g)
  {
  return Cardinality(g);
  } // end of GetProgMax

/***********************************************************************/
/*  RowNumber: return the ordinal number of the current row.           */
/***********************************************************************/
int TDBFIX::RowNumber(PGLOBAL g, bool b)
  {
  if (Txfp->GetAmType() == TYPE_AM_DBF) {
    if (!b && To_Kindex) {
      /*****************************************************************/
      /*  Don't know how to retrieve Rows from DBF file address        */
      /*  because of eventual deleted lines still in the file.         */
      /*****************************************************************/
      snprintf(g->Message, sizeof(g->Message), MSG(NO_ROWID_FOR_AM),
                          GetAmName(g, Txfp->GetAmType()));
      return 0;
      } // endif To_Kindex

    if (!b)
      return Txfp->GetRows();

    } // endif DBF

  return Txfp->GetRowID();
  } // end of RowNumber

/***********************************************************************/
/*  FIX tables don't use temporary files except if specified as do it. */
/***********************************************************************/
bool TDBFIX::IsUsingTemp(PGLOBAL)
  {
  // Not ready yet to handle using a temporary file with mapping
  // or while deleting from DBF files.
  return ((UseTemp() == TMP_YES && Txfp->GetAmType() != TYPE_AM_MAP &&
         !(Mode == MODE_DELETE && Txfp->GetAmType() == TYPE_AM_DBF)) ||
           UseTemp() == TMP_FORCE || UseTemp() == TMP_TEST);
  } // end of IsUsingTemp

/***********************************************************************/
/*  FIX Access Method opening routine (also used by the BIN a.m.)      */
/*  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 TDBFIX::OpenDB(PGLOBAL g)
  {
  if (trace(1))
    htrc("FIX OpenDB: tdbp=%p tdb=R%d use=%d key=%p mode=%d Ftype=%d\n",
      this, Tdb_No, Use, To_Key_Col, Mode, Ftype);

  if (Use == USE_OPEN) {
    /*******************************************************************/
    /*  Table already open, just replace it at its beginning.          */
    /*******************************************************************/
    if (To_Kindex)
      /*****************************************************************/
      /*  Table is to be accessed through a sorted index table.        */
      /*****************************************************************/
      To_Kindex->Reset();
    else
      Txfp->Rewind();       // see comment in Work.log

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

  if (Mode == MODE_DELETE && Txfp->GetAmType() == TYPE_AM_MAP &&
                   (!Next || UseTemp() == TMP_FORCE)) {
    // Delete all lines or using temp. Not handled in MAP mode
    Txfp = new(g) FIXFAM((PDOSDEF)To_Def);
    Txfp->SetTdbp(this);
    } // endif Mode

  /*********************************************************************/
  /*  Call Cardinality to calculate Block in the case of Func queries. */
  /*  and also in the case of multiple tables.                         */
  /*********************************************************************/
  if (Cardinality(g) < 0)
    return true;           

  /*********************************************************************/
  /*  Open according to required logical input/output mode.            */
  /*  Use conventionnal input/output functions.                        */
  /*  Treat fixed length text files as binary.                         */
  /*********************************************************************/
  if (Txfp->OpenTableFile(g))
    return true;

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

  /*********************************************************************/
  /*  Initialize To_Line at the beginning of the block buffer.         */
  /*********************************************************************/
  To_Line = Txfp->GetBuf();                       // For WriteDB

  /*********************************************************************/
  /*  Allocate the block filter tree if evaluation is possible.        */
  /*********************************************************************/
  To_BlkFil = InitBlockFilter(g, To_Filter);

  if (trace(1))
    htrc("OpenFix: R%hd mode=%d BlkFil=%p\n", Tdb_No, Mode, To_BlkFil);

  /*********************************************************************/
  /*  Reset buffer access according to indexing and to mode.           */
  /*********************************************************************/
  Txfp->ResetBuffer(g);

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

/***********************************************************************/
/*  WriteDB: Data Base write routine for FIX access method.            */
/***********************************************************************/
int TDBFIX::WriteDB(PGLOBAL g)
  {
  return Txfp->WriteBuffer(g);
  } // end of WriteDB

// ------------------------ BINCOL functions ----------------------------

/***********************************************************************/
/*  BINCOL public constructor.                                         */
/***********************************************************************/
BINCOL::BINCOL(PGLOBAL g, PCOLDEF cdp, PTDB tp, PCOL cp, int i, PCSZ am)
  : DOSCOL(g, cdp, tp, cp, i, am)
  {
  char c, *fmt = cdp->GetFmt();

  Fmt = GetDomain() ? 'C' : 'X';
  Buff = NULL;
  Eds = ((PTDBFIX)tp)->Teds;
  N = 0;
  M = GetTypeSize(Buf_Type, sizeof(longlong));
  Lim = 0;

  if (fmt) {
    for (N = 0, i = 0; fmt[i]; i++) {
      c = toupper(fmt[i]);

      if (isdigit(c))
        N = (N * 10 + (c - '0'));
      else if (c == 'L' || c == 'B' || c == 'H')
        Eds = c;
      else
        Fmt = c;

      } // endfor i

    // M is the size of the source value
    switch (Fmt) {
      case 'C': Eds = 0;              break;
      case 'X':                       break;
      case 'S': M = sizeof(short);    break;
      case 'T': M = sizeof(char);     break;
      case 'I': M = sizeof(int);      break;
      case 'G': M = sizeof(longlong); break;
      case 'R': // Real
      case 'F': M = sizeof(float);    break;
      case 'D': M = sizeof(double);   break;
      default:
        snprintf(g->Message, sizeof(g->Message), MSG(BAD_BIN_FMT), Fmt, Name);
				throw 11;
		} // endswitch Fmt

  } else if (IsTypeChar(Buf_Type))
    Eds = 0;

  if (Eds) {
    // This is a byte order specification
    if (!N)
      N = M;

    if (Eds != 'L' && Eds != 'B')
      Eds = Endian;

    if (N != M || Eds != Endian || IsTypeChar(Buf_Type)) {
      Buff = (char*)PlugSubAlloc(g, NULL, M);
      memset(Buff, 0, M);
      Lim = MY_MIN(N, M);
    } else
      Eds = 0;      // New format is a no op

    } // endif Eds

  } // end of BINCOL constructor

/***********************************************************************/
/*  BINCOL constructor used for copying columns.                       */
/*  tdbp is the pointer to the new table descriptor.                   */
/***********************************************************************/
BINCOL::BINCOL(BINCOL *col1, PTDB tdbp) : DOSCOL(col1, tdbp)
  {
  Eds = col1->Eds;
  Fmt = col1->Fmt;
  N = col1->N;
  M = col1->M;
  Lim = col1->Lim;
  } // end of BINCOL copy constructor

/***********************************************************************/
/*  Set Endian according to the host setting.                          */
/***********************************************************************/
void BINCOL::SetEndian(void)
  {
  union {
    short S;
    char  C[sizeof(short)];
    };

  S = 1;
  Endian = (C[0] == 1) ? 'L' : 'B';
  } // end of SetEndian

/***********************************************************************/
/*  ReadColumn: what this routine does is to access the last line      */
/*  read from the corresponding table and extract from it the field    */
/*  corresponding to this column.                                      */
/***********************************************************************/
void BINCOL::ReadColumn(PGLOBAL g)
  {
  char   *p = NULL;
  int     rc;
  PTDBFIX tdbp = (PTDBFIX)To_Tdb;

  if (trace(2))
    htrc("BIN 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;

  /*********************************************************************/
  /*  Set Value from the line field.                                   */
  /*********************************************************************/
  if (Eds) {
    for (int i = 0; i < Lim; i++)
      if (Eds == 'B' && Endian == 'L')
        Buff[i] = p[N - i - 1];
      else if (Eds == 'L' && Endian == 'B')
        Buff[M - i - 1] = p[i];
      else if (Endian == 'B')
        Buff[M - i - 1] = p[N - i - 1];
      else
        Buff[i] = p[i];

    p = Buff;
    } // endif Eds

  switch (Fmt) {
    case 'X':                 // Standard not converted values
      if (Eds && IsTypeChar(Buf_Type))
        Value->SetValueNonAligned<longlong>(p);
      else
        Value->SetBinValue(p);

      break;
    case 'S':                 // Short integer
      Value->SetValueNonAligned<short>(p);
      break;
    case 'T':                 // Tiny integer
      Value->SetValue(*p);
      break;
    case 'I':                 // Integer
      Value->SetValueNonAligned<int>(p);
      break;
    case 'G':                 // Large (great) integer
      Value->SetValueNonAligned<longlong>(p);
      break;
    case 'F':                 // Float
    case 'R':                 // Real
      Value->SetValueNonAligned<float>(p);
      break;
    case 'D':                 // Double
      Value->SetValueNonAligned<double>(p);
      break;
    case 'C':                 // Text
      if (Value->SetValue_char(p, Long)) {
        snprintf(g->Message, sizeof(g->Message), "Out of range value for column %s at row %d",
                Name, tdbp->RowNumber(g));
        PushWarning(g, tdbp);
        } // endif SetValue_char

      break;
    default:
      snprintf(g->Message, sizeof(g->Message), MSG(BAD_BIN_FMT), Fmt, Name);
			throw 11;
	} // endswitch Fmt

  // 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.               */
/***********************************************************************/
void BINCOL::WriteColumn(PGLOBAL g)
  {
  char    *p, *s;
  longlong n;
  PTDBFIX  tdbp = (PTDBFIX)To_Tdb;

  if (trace(1)) {
    htrc("BIN WriteColumn: col %s R%d coluse=%.4X status=%.4X",
          Name, tdbp->GetTdb_No(), ColUse, Status);
    htrc(" Lrecl=%d\n", tdbp->Lrecl);
    htrc("Long=%d deplac=%d coltype=%d ftype=%c\n",
          Long, Deplac, Buf_Type, *Format.Type);
    } // endif trace

  /*********************************************************************/
  /*  Check whether the new value has to be converted to Buf_Type.     */
  /*********************************************************************/
  if (Value != To_Val)
    Value->SetValue_pval(To_Val, false);    // Convert the updated value

  p = (Eds) ? Buff : tdbp->To_Line + Deplac;

  /*********************************************************************/
  /*  Check whether updating is Ok, meaning col value is not too long. */
  /*  Updating will be done only during the second pass (Status=true)  */
  /*  Conversion occurs if the external format Fmt is specified.       */
  /*********************************************************************/
  switch (Fmt) {
    case 'X':
      // Standard not converted values
			if (Eds && IsTypeChar(Buf_Type)) {
				if (Status)
					Value->GetValueNonAligned<longlong>(p, Value->GetBigintValue());
			} else if (Value->GetBinValue(p, Long, Status)) {
        snprintf(g->Message, sizeof(g->Message), MSG(BIN_F_TOO_LONG),
                            Name, Value->GetSize(), Long);
				throw 31;
			} // endif p

      break;
    case 'S':                 // Short integer
      n = Value->GetBigintValue();

      if (n > 32767LL || n < -32768LL) {
        snprintf(g->Message, sizeof(g->Message), MSG(VALUE_TOO_BIG), n, Name);
				throw 31;
			} else if (Status)
				Value->GetValueNonAligned<short>(p, (short)n);

      break;
    case 'T':                 // Tiny integer
      n = Value->GetBigintValue();

      if (n > 255LL || n < -256LL) {
        snprintf(g->Message, sizeof(g->Message), MSG(VALUE_TOO_BIG), n, Name);
				throw 31;
			} else if (Status)
        *p = (char)n;

      break;
    case 'I':                 // Integer
      n = Value->GetBigintValue();

      if (n > INT_MAX || n < INT_MIN) {
        snprintf(g->Message, sizeof(g->Message), MSG(VALUE_TOO_BIG), n, Name);
				throw 31;
			} else if (Status)
				Value->GetValueNonAligned<int>(p, (int)n);

      break;
    case 'G':                 // Large (great) integer
      if (Status)
        *(longlong *)p = Value->GetBigintValue();

      break;
    case 'F':                 // Float
    case 'R':                 // Real
      if (Status)
				Value->GetValueNonAligned<float>(p, (float)Value->GetFloatValue());

      break;
    case 'D':                 // Double
      if (Status)
				Value->GetValueNonAligned<double>(p, Value->GetFloatValue());

      break;
    case 'C':                 // Characters
      if ((n = (signed)strlen(Value->GetCharString(Buf))) > Long) {
        snprintf(g->Message, sizeof(g->Message), MSG(BIN_F_TOO_LONG), Name, (int) n, Long);
				throw 31;
			} // endif n

      if (Status) {
        s = Value->GetCharString(Buf);
        memset(p, ' ', Long);
        memcpy(p, s, strlen(s));
        } // endif Status

      break;
    default:
      snprintf(g->Message, sizeof(g->Message), MSG(BAD_BIN_FMT), Fmt, Name);
			throw 31;
	} // endswitch Fmt

  if (Eds && Status) {
    p = tdbp->To_Line + Deplac;

    for (int i = 0; i < Lim; i++)
      if (Eds == 'B' && Endian == 'L')
        p[N - i - 1] = Buff[i];
      else if (Eds == 'L' && Endian == 'B')
        p[i] = Buff[M - i - 1];
      else if (Endian == 'B')
        p[N - i - 1] = Buff[M - i - 1];
      else
        p[i] = Buff[i];

    } // endif Eds

  } // end of WriteColumn

/* ------------------------ End of TabFix ---------------------------- */