/************* TabFmt C++ Program Source Code File (.CPP) **************/
/* PROGRAM NAME: TABFMT                                                */
/* -------------                                                       */
/*  Version 3.9.3                                                      */
/*                                                                     */
/* COPYRIGHT:                                                          */
/* ----------                                                          */
/*  (C) Copyright to the author Olivier BERTRAND          2001 - 2019  */
/*                                                                     */
/* WHAT THIS PROGRAM DOES:                                             */
/* -----------------------                                             */
/*  This program are the TABFMT classes DB execution routines.         */
/*  The base class CSV is comma separated files.                       */
/*  FMT (Formatted) files are those having a complex internal record   */
/*  format described in the Format keyword of their definition.        */
/***********************************************************************/

/***********************************************************************/
/*  Include relevant MariaDB header file.                              */
/***********************************************************************/
#include "my_global.h"

#if defined(_WIN32)
#include <io.h>
#include <fcntl.h>
#include <errno.h>
#include <locale.h>
#if defined(__BORLANDC__)
#define __MFC_COMPAT__                   // To define min/max as macro
#endif
//#include <windows.h>
#include "osutil.h"
#else
#if defined(UNIX)
#include <errno.h>
#include <unistd.h>
#include "osutil.h"
#else
#include <io.h>
#endif
#include <fcntl.h>
#endif

/***********************************************************************/
/*  Include application header files:                                  */
/*  global.h    is header containing all global declarations.          */
/*  plgdbsem.h  is header containing the DB application declarations.  */
/*  tabdos.h    is header containing the TABDOS class declarations.    */
/***********************************************************************/
#include "global.h"
#include "plgdbsem.h"
#include "mycat.h"
#include "filamap.h"
#if defined(GZ_SUPPORT)
#include "filamgz.h"
#endif   // GZ_SUPPORT
#if defined(ZIP_SUPPORT)
#include "filamzip.h"
#endif   // ZIP_SUPPORT
#include "tabfmt.h"
#include "tabmul.h"
#define  NO_FUNC
#include "plgcnx.h"                       // For DB types
#include "resource.h"
#include "m_string.h"

/***********************************************************************/
/*  This should be an option.                                          */
/***********************************************************************/
#define MAXCOL          200        /* Default max column nb in result  */
#define TYPE_UNKNOWN     12        /* Must be greater than other types */

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

/***********************************************************************/
/* CSVColumns: constructs the result blocks containing the description */
/* of all the columns of a CSV file that will be retrieved by #GetData.*/
/* Note: the algorithm to set the type is based on the internal values */
/* of types (TYPE_STRING < TYPE_DOUBLE < TYPE_INT) (1 < 2 < 7).        */
/* If these values are changed, this will have to be revisited.        */
/***********************************************************************/
PQRYRES CSVColumns(PGLOBAL g, PCSZ dp, PTOS topt, bool info)
  {
  static int  buftyp[] = {TYPE_STRING, TYPE_SHORT, TYPE_STRING,
                          TYPE_INT,   TYPE_INT, TYPE_SHORT};
  static XFLD fldtyp[] = {FLD_NAME, FLD_TYPE,   FLD_TYPENAME,
                          FLD_PREC, FLD_LENGTH, FLD_SCALE};
  static unsigned int length[] = {6, 6, 8, 10, 10, 6};
	const char *fn;
	char    sep, q;
	int     rc, mxr;
	bool    hdr;
  char   *p, *colname[MAXCOL], dechar, buf[16];
  int     i, imax, hmax, n, nerr, phase, blank, digit, dec, type;
  int     ncol = sizeof(buftyp) / sizeof(int);
  int     num_read = 0, num_max = 10000000;     // Statistics
  int     len[MAXCOL], typ[MAXCOL], prc[MAXCOL];
	PCSVDEF tdp;
	PTDBCSV tcvp;
	PTDBASE tdbp;
	PQRYRES qrp;
  PCOLRES crp;

  if (info) {
    imax = hmax = 0;
    length[0] = 128;
    goto skipit;
    } // endif info

	//if (GetIntegerTableOption(g, topt, "Multiple", 0)) {
	//	strcpy(g->Message, "Cannot find column definition for multiple table");
	//	return NULL;
	//}	// endif Multiple

//      num_max = atoi(p+1);             // Max num of record to test
  imax = hmax = nerr = 0;

  for (i = 0; i < MAXCOL; i++) {
    colname[i] = NULL;
    len[i] = 0;
    typ[i] = TYPE_UNKNOWN;
    prc[i] = 0;
    } // endfor i

  /*********************************************************************/
  /*  Get the CSV table description block.                             */
  /*********************************************************************/
	tdp = new(g) CSVDEF;
	tdp->Database = dp;

	if ((tdp->Zipped = GetBooleanTableOption(g, topt, "Zipped", false))) {
#if defined(ZIP_SUPPORT)
		tdp->Entry = GetStringTableOption(g, topt, "Entry", NULL);
		tdp->Mulentries = (tdp->Entry)
			              ? strchr(tdp->Entry, '*') || strchr(tdp->Entry, '?')
			              : GetBooleanTableOption(g, topt, "Mulentries", false);
#else   // !ZIP_SUPPORT
		safe_strcpy(g->Message, sizeof(g->Message), "ZIP not supported by this version");
		return NULL;
#endif  // !ZIP_SUPPORT
	} // endif // Zipped

	fn = tdp->Fn = GetStringTableOption(g, topt, "Filename", NULL);

	if (!tdp->Fn) {
		safe_strcpy(g->Message, sizeof(g->Message), MSG(MISSING_FNAME));
		return NULL;
	} // endif Fn

	if (!(tdp->Lrecl = GetIntegerTableOption(g, topt, "Lrecl", 0)))
		tdp->Lrecl = 4096;

	tdp->Multiple = GetIntegerTableOption(g, topt, "Multiple", 0);
	p = (char*)GetStringTableOption(g, topt, "Separator", ",");
	tdp->Sep = (strlen(p) == 2 && p[0] == '\\' && p[1] == 't') ? '\t' : *p;

#if defined(_WIN32)
	if (tdp->Sep == ',' || strnicmp(setlocale(LC_NUMERIC, NULL), "French", 6))
		dechar = '.';
	else
		dechar = ',';
#else   // !_WIN32
	dechar = '.';
#endif  // !_WIN32

	sep = tdp->Sep;
	tdp->Quoted = GetIntegerTableOption(g, topt, "Quoted", -1);
	p = (char*)GetStringTableOption(g, topt, "Qchar", "");
	tdp->Qot = *p;

	if (tdp->Qot && tdp->Quoted < 0)
		tdp->Quoted = 0;
	else if (!tdp->Qot && tdp->Quoted >= 0)
		tdp->Qot = '"';

	q = tdp->Qot;
	hdr = GetBooleanTableOption(g, topt, "Header", false);
	tdp->Maxerr = GetIntegerTableOption(g, topt, "Maxerr", 0);
	tdp->Accept = GetBooleanTableOption(g, topt, "Accept", false);

	if (tdp->Accept && tdp->Maxerr == 0)
		tdp->Maxerr = INT_MAX32;       // Accept all bad lines

	mxr = MY_MAX(0, tdp->Maxerr);

	if (trace(1))
		htrc("File %s Sep=%c Qot=%c Header=%d maxerr=%d\n",
		SVP(tdp->Fn), tdp->Sep, tdp->Qot, tdp->Header, tdp->Maxerr);

#if defined(ZIP_SUPPORT)
	if (tdp->Zipped)
		tcvp = new(g)TDBCSV(tdp, new(g)UNZFAM(tdp));
	else
#endif   // ZIP_SUPPORT
		tcvp = new(g) TDBCSV(tdp, new(g) DOSFAM(tdp));

	tcvp->SetMode(MODE_READ);

	if (tdp->Multiple) {
		tdbp = new(g)TDBMUL(tcvp);
		tdbp->SetMode(MODE_READ);
	} else
	  tdbp = tcvp;

	/*********************************************************************/
	/*  Open the CSV file.                                               */
	/*********************************************************************/
	if (tdbp->OpenDB(g))
		return NULL;

  if (hdr) {
    /*******************************************************************/
    /*  Make the column names from the first line.                     */
    /*******************************************************************/
    phase = 0;

    if ((rc = tdbp->ReadDB(g)) == RC_OK) {
			p = PlgDBDup(g, tcvp->To_Line);

      //skip leading blanks
      for (; *p == ' '; p++) ;

      if (q && *p == q) {
        // Header is quoted
        p++;
        phase = 1;
        } // endif q

      colname[0] = p;
    } else if (rc == RC_EF) {
      snprintf(g->Message, sizeof(g->Message), MSG(FILE_IS_EMPTY), fn);
      goto err;
		} else
			goto err;

    for (i = 1; *p; p++)
      if (phase == 1 && *p == q) {
        *p = '\0';
        phase = 0;
      } else if (*p == sep && !phase) {
        *p = '\0';

        //skip leading blanks
        for (; *(p+1) == ' '; p++) ;

        if (q && *(p+1) == q) {
          // Header is quoted
          p++;
          phase = 1;
          } // endif q

        colname[i++] = p + 1;
        } // endif sep

    num_read++;
    imax = hmax = i;

    for (i = 0; i < hmax; i++)
      length[0] = MY_MAX(length[0], strlen(colname[i]));

		tcvp->Header = true;			// In case of multiple table
    } // endif hdr

  for (num_read++; num_read <= num_max; num_read++) {
    /*******************************************************************/
    /*  Now start the reading process. Read one line.                  */
    /*******************************************************************/
		if ((rc = tdbp->ReadDB(g)) == RC_OK) {
    } else if (rc == RC_EF) {
      snprintf(g->Message, sizeof(g->Message), MSG(EOF_AFTER_LINE), num_read -1);
      break;
    } else {
      snprintf(g->Message, sizeof(g->Message), MSG(ERR_READING_REC), num_read, fn);
      goto err;
    } // endif's

    /*******************************************************************/
    /*  Make the test for field lengths.                               */
    /*******************************************************************/
    i = n = phase = blank = digit = dec = 0;

    for (p = tcvp->To_Line; *p; p++)
      if (*p == sep) {
        if (phase != 1) {
          if (i == MAXCOL - 1) {
            snprintf(g->Message, sizeof(g->Message), MSG(TOO_MANY_FIELDS), num_read, fn);
            goto err;
            } // endif i

          if (n) {
            len[i] = MY_MAX(len[i], n);
            type = (digit || (dec && n == 1)) ? TYPE_STRING
                 : (dec) ? TYPE_DOUBLE : TYPE_INT;
            typ[i] = MY_MIN(type, typ[i]);
            prc[i] = MY_MAX((typ[i] == TYPE_DOUBLE) ? (dec - 1) : 0, prc[i]);
            } // endif n

          i++;
          n = phase = blank = digit = dec = 0;
        } else          // phase == 1
          n++;

      } else if (*p == ' ') {
        if (phase < 2)
          n++;

        if (blank)
          digit = 1;

      } else if (*p == q) {
        if (phase == 0) {
          if (blank) {
            if (++nerr > mxr) {
              snprintf(g->Message, sizeof(g->Message), MSG(MISPLACED_QUOTE), num_read);
              goto err;
            } else
              goto skip;
          }

          n = 0;
          phase = digit = 1;
        } else if (phase == 1) {
          if (*(p+1) == q) {
            // This is currently not implemented for CSV tables
//          if (++nerr > mxr) {
//            snprintf(g->Message, sizeof(g->Message), MSG(QUOTE_IN_QUOTE), num_read);
//            goto err;
//          } else
//            goto skip;

            p++;
            n++;
          } else
            phase = 2;

        } else if (++nerr > mxr) {      // phase == 2
          snprintf(g->Message, sizeof(g->Message), MSG(MISPLACED_QUOTE), num_read);
          goto err;
        } else
          goto skip;

      } else {
        if (phase == 2) {
          if (++nerr > mxr) {
            snprintf(g->Message, sizeof(g->Message), MSG(MISPLACED_QUOTE), num_read);
            goto err;
          } else
            goto skip;
        }

        // isdigit cannot be used here because of debug assert
        if (!strchr("0123456789", *p)) {
          if (!digit && *p == dechar)
            dec = 1;                    // Decimal point found
          else if (blank || !(*p == '-' || *p == '+'))
            digit = 1;

        } else if (dec)
          dec++;                        // More decimals

        n++;
        blank = 1;
      } // endif's *p

    if (phase == 1) {
      if (++nerr > mxr) {
        snprintf(g->Message, sizeof(g->Message), MSG(UNBALANCE_QUOTE), num_read);
        goto err;
      } else
        goto skip;
    }

    if (n) {
      len[i] = MY_MAX(len[i], n);
      type = (digit || n == 0 || (dec && n == 1)) ? TYPE_STRING
           : (dec) ? TYPE_DOUBLE : TYPE_INT;
      typ[i] = MY_MIN(type, typ[i]);
      prc[i]  = MY_MAX((typ[i] == TYPE_DOUBLE) ? (dec - 1) : 0, prc[i]);
      } // endif n

    imax = MY_MAX(imax, i+1);
   skip: ;                  // Skip erroneous line
    } // endfor num_read

  if (trace(1)) {
    htrc("imax=%d Lengths:", imax);

    for (i = 0; i < imax; i++)
      htrc(" %d", len[i]);

    htrc("\n");
  } // endif trace

	tdbp->CloseDB(g);

 skipit:
  if (trace(1))
    htrc("CSVColumns: imax=%d hmax=%d len=%d\n",
                      imax, hmax, length[0]);

  /*********************************************************************/
  /*  Allocate the structures used to refer to the result set.         */
  /*********************************************************************/
  qrp = PlgAllocResult(g, ncol, imax, IDS_COLUMNS + 3,
                          buftyp, fldtyp, length, false, false);
  if (info || !qrp)
    return qrp;

  qrp->Nblin = imax;

  /*********************************************************************/
  /*  Now get the results into blocks.                                 */
  /*********************************************************************/
  for (i = 0; i < imax; i++) {
    if (i >= hmax) {
      sprintf(buf, "COL%.3d", i+1);
      p = buf;
    } else
      p = colname[i];

    if (typ[i] == TYPE_UNKNOWN)            // Void column
      typ[i] = TYPE_STRING;

    crp = qrp->Colresp;                    // Column Name
    crp->Kdata->SetValue(p, i);
    crp = crp->Next;                       // Data Type
    crp->Kdata->SetValue(typ[i], i);
    crp = crp->Next;                       // Type Name
    crp->Kdata->SetValue(GetTypeName(typ[i]), i);
    crp = crp->Next;                       // Precision
    crp->Kdata->SetValue(len[i], i);
    crp = crp->Next;                       // Length
    crp->Kdata->SetValue(len[i], i);
    crp = crp->Next;                       // Scale (precision)
    crp->Kdata->SetValue(prc[i], i);
    } // endfor i

  /*********************************************************************/
  /*  Return the result pointer for use by GetData routines.           */
  /*********************************************************************/
  return qrp;

 err:
  tdbp->CloseDB(g);
  return NULL;
  } // end of CSVCColumns

/* --------------------------- Class CSVDEF -------------------------- */

/***********************************************************************/
/*  CSVDEF constructor.                                                */
/***********************************************************************/
CSVDEF::CSVDEF(void)
  {
  Fmtd = Header = false;
//Maxerr = 0;
  Quoted = -1;
  Sep = ',';
  Qot = '\0';
  }  // end of CSVDEF constructor

/***********************************************************************/
/*  DefineAM: define specific AM block values from XDB file.           */
/***********************************************************************/
bool CSVDEF::DefineAM(PGLOBAL g, LPCSTR am, int poff)
  {
  char   buf[8];

  // Double check correctness of offset values
  if (Catfunc == FNC_NO)
    for (PCOLDEF cdp = To_Cols; cdp; cdp = cdp->GetNext())
      if (cdp->GetOffset() < 1 && !cdp->IsSpecial()) {
        safe_strcpy(g->Message, sizeof(g->Message), MSG(BAD_OFFSET_VAL));
        return true;
        } // endif Offset

  // Call DOSDEF DefineAM with am=CSV so FMT is not confused with FIX
  if (DOSDEF::DefineAM(g, "CSV", poff))
    return true;

	Recfm = RECFM_CSV;
  GetCharCatInfo("Separator", ",", buf, sizeof(buf));
  Sep = (strlen(buf) == 2 && buf[0] == '\\' && buf[1] == 't') ? '\t' : *buf;
  Quoted = GetIntCatInfo("Quoted", -1);
  GetCharCatInfo("Qchar", "", buf, sizeof(buf));
  Qot = *buf;

  if (Qot && Quoted < 0)
    Quoted = 0;
  else if (!Qot && Quoted >= 0)
    Qot = '"';

  Fmtd = (!Sep || (am && (*am == 'F' || *am == 'f')));
  Header = GetBoolCatInfo("Header", false);
  Maxerr = GetIntCatInfo("Maxerr", 0);
  Accept = GetBoolCatInfo("Accept", false);

  if (Accept && Maxerr == 0)
    Maxerr = INT_MAX32;       // Accept all bad lines

  return false;
  } // end of DefineAM

/***********************************************************************/
/*  GetTable: makes a new Table Description Block.                     */
/***********************************************************************/
PTDB CSVDEF::GetTable(PGLOBAL g, MODE mode)
  {
  PTDBASE tdbp;

  if (Catfunc != FNC_COL) {
    USETEMP tmp = UseTemp();
    bool    map = Mapped && mode != MODE_INSERT &&
                  !(tmp != TMP_NO && mode == MODE_UPDATE) &&
                  !(tmp == TMP_FORCE &&
                  (mode == MODE_UPDATE || mode == MODE_DELETE));
    PTXF    txfp;

    /*******************************************************************/
    /*  Allocate a file processing class of the proper type.           */
    /*******************************************************************/
		if (Zipped) {
#if defined(ZIP_SUPPORT)
			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
#else   // !ZIP_SUPPORT
			safe_strcpy(g->Message, sizeof(g->Message), "ZIP not supported");
			return NULL;
#endif  // !ZIP_SUPPORT
		} else if (map) {
      // Should be now compatible with UNIX
      txfp = new(g) MAPFAM(this);
    } else if (Compressed) {
#if defined(GZ_SUPPORT)
      if (Compressed == 1)
        txfp = new(g) GZFAM(this);
      else
        txfp = new(g) ZLBFAM(this);

#else   // !GZ_SUPPORT
        safe_strcpy(g->Message, sizeof(g->Message), "Compress not supported");
        return NULL;
#endif  // !GZ_SUPPORT
    } else
      txfp = new(g) DOSFAM(this);

    /*******************************************************************/
    /*  Allocate a TDB of the proper type.                             */
    /*  Column blocks will be allocated only when needed.              */
    /*******************************************************************/
    if (!Fmtd)
      tdbp = new(g) TDBCSV(this, txfp);
    else
      tdbp = new(g) TDBFMT(this, txfp);

    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 (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

      } // endelse

  } else
    tdbp = new(g)TDBCCL(this);

  return tdbp;
  } // end of GetTable

/* -------------------------- Class TDBCSV --------------------------- */

/***********************************************************************/
/*  Implementation of the TDBCSV class.                                */
/***********************************************************************/
TDBCSV::TDBCSV(PCSVDEF tdp, PTXF txfp) : TDBDOS(tdp, txfp)
  {
#if defined(_DEBUG)
  assert (tdp);
#endif
  Field  = NULL;
  Offset = NULL;
  Fldlen = NULL;
  Fields = 0;
  Nerr = 0;
  Quoted = tdp->Quoted;
  Maxerr = tdp->Maxerr;
  Accept = tdp->Accept;
  Header = tdp->Header;
  Sep = tdp->GetSep();
  Qot = tdp->GetQot();
  } // end of TDBCSV standard constructor

TDBCSV::TDBCSV(PGLOBAL g, PTDBCSV tdbp) : TDBDOS(g, tdbp)
  {
  Fields = tdbp->Fields;

  if (Fields) {
    if (tdbp->Offset)
      Offset = (int*)PlugSubAlloc(g, NULL, sizeof(int) * Fields);

    if (tdbp->Fldlen)
      Fldlen = (int*)PlugSubAlloc(g, NULL, sizeof(int) * Fields);

    Field = (PSZ *)PlugSubAlloc(g, NULL, sizeof(PSZ) * Fields);

    for (int i = 0; i < Fields; i++) {
      if (Offset)
        Offset[i] = tdbp->Offset[i];

      if (Fldlen)
        Fldlen[i] = tdbp->Fldlen[i];

      if (Field) {
        assert (Fldlen);
        Field[i] = (PSZ)PlugSubAlloc(g, NULL, Fldlen[i] + 1);
        Field[i][Fldlen[i]] = '\0';
        } // endif Field

      } // endfor i

  } else {
    Field  = NULL;
    Offset = NULL;
    Fldlen = NULL;
  } // endif Fields

  Nerr = tdbp->Nerr;
  Maxerr = tdbp->Maxerr;
  Quoted = tdbp->Quoted;
  Accept = tdbp->Accept;
  Header = tdbp->Header;
  Sep = tdbp->Sep;
  Qot = tdbp->Qot;
  } // end of TDBCSV copy constructor

// Method
PTDB TDBCSV::Clone(PTABS t)
  {
  PTDB    tp;
  PCSVCOL cp1, cp2;
  PGLOBAL g = t->G;        // Is this really useful ???

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

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

  return tp;
  } // end of Clone

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

/***********************************************************************/
/*  Check whether the number of errors is greater than the maximum.    */
/***********************************************************************/
bool TDBCSV::CheckErr(void)
  {
  return (++Nerr) > Maxerr;
  } // end of CheckErr

/***********************************************************************/
/*  CSV EstimatedLength. Returns an estimated minimum line length.     */
/***********************************************************************/
int TDBCSV::EstimatedLength(void)
  {
  int     n = 0;
  PCOLDEF cdp;

  if (trace(1))
    htrc("EstimatedLength: Fields=%d Columns=%p\n", Fields, Columns);

  for (cdp = To_Def->GetCols(); cdp; cdp = cdp->GetNext())
    if (!cdp->IsSpecial() && !cdp->IsVirtual())  // A true column
      n++;

  return --n;   // Number of separators if all fields are null
  } // end of Estimated Length

#if 0
/***********************************************************************/
/*  CSV tables needs the use temporary files for Update.               */
/***********************************************************************/
bool TDBCSV::IsUsingTemp(PGLOBAL g)
  {
  return (Use_Temp == TMP_YES || Use_Temp == TMP_FORCE ||
         (Use_Temp == TMP_AUTO && Mode == MODE_UPDATE));
  } // end of IsUsingTemp
#endif // 0  (Same as TDBDOS one)

/***********************************************************************/
/*  CSV Access Method opening routine.                                 */
/*  First allocate the Offset and Fldlen arrays according to the       */
/*  greatest field used in that query. Then call the DOS opening fnc.  */
/***********************************************************************/
bool TDBCSV::OpenDB(PGLOBAL g)
  {
  bool    rc = false;
  PCOLDEF cdp;
  PDOSDEF tdp = (PDOSDEF)To_Def;

  if (Use != USE_OPEN && (Columns || Mode == MODE_UPDATE)) {
    // Allocate the storage used to read (or write) records
    int     i, len;
    PCSVCOL colp;

    if (!Fields) {            // May have been set in TABFMT::OpenDB
      if (Mode != MODE_UPDATE && Mode != MODE_INSERT) {
        for (colp = (PCSVCOL)Columns; colp; colp = (PCSVCOL)colp->Next)
          if (!colp->IsSpecial() && !colp->IsVirtual())
            Fields = MY_MAX(Fields, (int)colp->Fldnum);

        if (Columns)
          Fields++;           // Fldnum was 0 based

      } else
        for (cdp = tdp->GetCols(); cdp; cdp = cdp->GetNext())
          if (!cdp->IsSpecial() && !cdp->IsVirtual())
            Fields++;
    }

    Offset = (int*)PlugSubAlloc(g, NULL, sizeof(int) * Fields);
    Fldlen = (int*)PlugSubAlloc(g, NULL, sizeof(int) * Fields);

    if (Mode == MODE_INSERT || Mode == MODE_UPDATE) {
      Field = (PSZ*)PlugSubAlloc(g, NULL, sizeof(PSZ) * Fields);
      Fldtyp = (bool*)PlugSubAlloc(g, NULL, sizeof(bool) * Fields);
      } // endif Mode

    for (i = 0; i < Fields; i++) {
      Offset[i] = 0;
      Fldlen[i] = 0;

      if (Field) {
        Field[i] = NULL;
        Fldtyp[i] = false;
        } // endif Field

      } // endfor i

    if (Field) {
      // Prepare writing fields
      if (Mode != MODE_UPDATE) {
        for (colp = (PCSVCOL)Columns; colp; colp = (PCSVCOL)colp->Next)
          if (!colp->IsSpecial() && !colp->IsVirtual()) {
            i = colp->Fldnum;
            len = colp->GetLength();
            Field[i] = (PSZ)PlugSubAlloc(g, NULL, len + 1);
            Field[i][len] = '\0';
            Fldlen[i] = len;
            Fldtyp[i] = IsTypeNum(colp->GetResultType());
            } // endif colp

      } else     // MODE_UPDATE
        for (cdp = tdp->GetCols(); cdp; cdp = cdp->GetNext())
          if (!cdp->IsSpecial() && !cdp->IsVirtual()) {
            i = cdp->GetOffset() - 1;
            len = cdp->GetLength();
            Field[i] = (PSZ)PlugSubAlloc(g, NULL, len + 1);
            Field[i][len] = '\0';
            Fldlen[i] = len;
            Fldtyp[i] = IsTypeNum(cdp->GetType());
            } // endif cdp
    }
  } // endif Use

  if (Header) {
    // Check that the Lrecl is at least equal to the header line length
    int     headlen = 0;
    PCOLDEF cdp;
    PDOSDEF tdp = (PDOSDEF)To_Def;

    for (cdp = tdp->GetCols(); cdp; cdp = cdp->GetNext())
      headlen += strlen(cdp->GetName()) + 3;  // 3 if names are quoted

    if (headlen > Lrecl) {
      Lrecl = headlen;
      Txfp->Lrecl = headlen;
      } // endif headlen

    } // endif Header

  Nerr = 0;
  rc = TDBDOS::OpenDB(g);

  if (!rc && Mode == MODE_UPDATE && To_Kindex)
    // Because KINDEX::Init is executed in mode READ, we must restore
    // the Fldlen array that was modified when reading the table file.
    for (cdp = tdp->GetCols(); cdp; cdp = cdp->GetNext())
      Fldlen[cdp->GetOffset() - 1] = cdp->GetLength();

  return rc;
  } // end of OpenDB

/***********************************************************************/
/*  SkipHeader: Physically skip first header line if applicable.       */
/*  This is called from TDBDOS::OpenDB and must be executed before     */
/*  Kindex construction if the file is accessed using an index.        */
/***********************************************************************/
bool TDBCSV::SkipHeader(PGLOBAL g)
  {
  int len = GetFileLength(g);
  bool rc = false;

#if defined(_DEBUG)
  if (len < 0)
    return true;
#endif   // _DEBUG

  if (Header) {
    if (Mode == MODE_INSERT) {
      if (!len) {
        // New file, the header line must be constructed and written
        int     i, n = 0;
        int    hlen = 0;
        bool    q = Qot && Quoted > 0;
        PCOLDEF cdp;
        char *pos, *end;

        // Estimate the length of the header list
        for (cdp = To_Def->GetCols(); cdp; cdp = cdp->GetNext()) {
          hlen += (1 + strlen(cdp->GetName()));
          hlen += ((q) ? 2 : 0);
          n++;            // Calculate the number of columns
          } // endfor cdp

        if (hlen > Lrecl) {
          snprintf(g->Message, sizeof(g->Message), MSG(LRECL_TOO_SMALL), hlen);
          return true;
          } // endif hlen

        // File is empty, write a header record
        memset(To_Line, 0, Lrecl);
        pos= To_Line;
        end= To_Line + Lrecl-1;

        // The column order in the file is given by the offset value
        for (i = 1; i <= n; i++)
          for (cdp = To_Def->GetCols(); cdp; cdp = cdp->GetNext())
            if (cdp->GetOffset() == i) {
              if (q && pos < end)
                *pos++= Qot;

              pos= strnmov(pos, cdp->GetName(), (size_t) (end-pos));

              if (q && pos < end)
                *pos++= Qot;

              if (i < n && pos < end)
                *pos++= Sep;

              } // endif Offset
        *pos= 0;

        rc = (Txfp->WriteBuffer(g) == RC_FX);
        } // endif !FileLength

    } else if (Mode == MODE_DELETE) {
      if (len)
        rc = (Txfp->SkipRecord(g, true) == RC_FX);

    } else if (len) // !Insert && !Delete
      rc = (Txfp->SkipRecord(g, false) == RC_FX || Txfp->RecordPos(g));

    } // endif Header

  return rc;
  } // end of SkipHeader

/***********************************************************************/
/*  ReadBuffer: Physical read routine for the CSV access method.       */
/***********************************************************************/
int TDBCSV::ReadBuffer(PGLOBAL g)
  {
  //char *p1, *p2, *p = NULL;
	char *p2, *p = NULL;
	int   i, n, len, rc = Txfp->ReadBuffer(g);
  bool  bad = false;

  if (trace(2))
    htrc("CSV: Row is '%s' rc=%d\n", To_Line, rc);

  if (rc != RC_OK || !Fields)
    return rc;
  else
    p2 = To_Line;

  // Find the offsets and lengths of the columns for this row
  for (i = 0; i < Fields; i++) {
    if (!bad) {
      if (Qot && *p2 == Qot) {                // Quoted field
        //for (n = 0, p1 = ++p2; (p = strchr(p1, Qot)); p1 = p + 2)
        //  if (*(p + 1) == Qot)
        //    n++;                              // Doubled internal quotes
        //  else
        //    break;                            // Final quote

				for (n = 0, p = ++p2; p; p++)
					if (*p == Qot || *p == '\\') {
						if (*(++p) == Qot)
							n++;														// Escaped internal quotes
						else if (*(p - 1) == Qot)
							break;													// Final quote
					}	// endif *p

        if (p) {
          //len = p++ - p2;
					len = (int)(p - p2 - 1);

//        if (Sep != ' ')
//          for (; *p == ' '; p++) ;          // Skip blanks

          if (*p != Sep && i != Fields - 1) { // Should be the separator
            if (CheckErr()) {
              snprintf(g->Message, sizeof(g->Message), MSG(MISSING_FIELD),
                                  i+1, Name, RowNumber(g));
              return RC_FX;
            } else if (Accept)
              bad = true;
            else
              return RC_NF;

            } // endif p

          if (n) {
            int j, k;

            // Suppress the escape of internal quotes
            for (j = k = 0; j < len; j++, k++) {
              if (p2[j] == Qot || (p2[j] == '\\' && p2[j + 1] == Qot))
                j++;                          // skip escape char
							else if (p2[j] == '\\')
								p2[k++] = p2[j++];						// avoid \\Qot

              p2[k] = p2[j];
              } // endfor i, j

            len -= n;
            } // endif n

        } else if (CheckErr()) {
          snprintf(g->Message, sizeof(g->Message), MSG(BAD_QUOTE_FIELD),
                              Name, i+1, RowNumber(g));
          return RC_FX;
        } else if (Accept) {
          len = strlen(p2);
          bad = true;
        } else
          return RC_NF;

      } else if ((p = strchr(p2, Sep)))
        len = (int)(p - p2);
      else if (i == Fields - 1)
        len = strlen(p2);
      else if (Accept && Maxerr == 0) {
        len = strlen(p2);
        bad = true;
      } else if (CheckErr()) {
        snprintf(g->Message, sizeof(g->Message), MSG(MISSING_FIELD), i+1, Name, RowNumber(g));
        return RC_FX;
      } else if (Accept) {
        len = strlen(p2);
        bad = true;
      } else
        return RC_NF;

    } else
      len = 0;

    Offset[i] = (int)(p2 - To_Line);

    if (Mode != MODE_UPDATE)
      Fldlen[i] = len;
    else if (len > Fldlen[i]) {
      snprintf(g->Message, sizeof(g->Message), MSG(FIELD_TOO_LONG), i+1, RowNumber(g));
      return RC_FX;
    } else {
      strncpy(Field[i], p2, len);
      Field[i][len] = '\0';
    } // endif Mode

    if (p)
      p2 = p + 1;

    } // endfor i

  return rc;
  } // end of ReadBuffer

/***********************************************************************/
/*  Prepare the line to write.                                         */
/***********************************************************************/
bool TDBCSV::PrepareWriting(PGLOBAL g)
  {
  char sep[2], qot[2];
  int  i, nlen, oldlen = strlen(To_Line);

  if (trace(2))
    htrc("CSV WriteDB: R%d Mode=%d key=%p link=%p\n",
          Tdb_No, Mode, To_Key_Col, To_Link);

  // Before writing the line we must check its length
  if ((nlen = CheckWrite(g)) < 0)
    return true;

  // Before writing the line we must make it
  sep[0] = Sep;
  sep[1] = '\0';
  qot[0] = Qot;
  qot[1] = '\0';
  *To_Line = '\0';

  for (i = 0; i < Fields; i++) {
    if (i)
      safe_strcat(To_Line, Lrecl, sep);

    if (Field[i]) {
      if (!strlen(Field[i])) {
        // Generally null fields are not quoted
        if (Quoted > 2) {
          // Except if explicitly required
          safe_strcat(To_Line, Lrecl, qot);
          safe_strcat(To_Line, Lrecl, qot);
        }

      } else if (Qot && (strchr(Field[i], Sep) || *Field[i] == Qot
              || Quoted > 1 || (Quoted == 1 && !Fldtyp[i]))) {
        if (strchr(Field[i], Qot)) {
          // Field contains quotes that must be doubled
          int j, k = strlen(To_Line), n = strlen(Field[i]);

          To_Line[k++] = Qot;

          for (j = 0; j < n; j++) {
            if (Field[i][j] == Qot)
              To_Line[k++] = Qot;

            To_Line[k++] = Field[i][j];
            } // endfor j

          To_Line[k++] = Qot;
          To_Line[k] = '\0';
        } else {
          safe_strcat(To_Line, Lrecl, qot);
          safe_strcat(To_Line, Lrecl, Field[i]);
          safe_strcat(To_Line, Lrecl, qot);
        }
      }

      else
        safe_strcat(To_Line, Lrecl, Field[i]);
    }
  } // endfor i

#if defined(_DEBUG)
  assert ((unsigned)nlen == strlen(To_Line));
#endif

  if (Mode == MODE_UPDATE && nlen < oldlen
                          && !((PDOSFAM)Txfp)->GetUseTemp()) {
    // In Update mode with no temp file, line length must not change
    To_Line[nlen] = Sep;

    for (nlen++; nlen < oldlen; nlen++)
      To_Line[nlen] = ' ';

    To_Line[nlen] = '\0';
    } // endif

  if (trace(2))
    htrc("Write: line is=%s", To_Line);

  return false;
  } // end of PrepareWriting

/***********************************************************************/
/*  Data Base write routine CSV file access method.                    */
/***********************************************************************/
int TDBCSV::WriteDB(PGLOBAL g)
  {
  // Before writing the line we must check and prepare it
  if (PrepareWriting(g))
    return RC_FX;

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

/***********************************************************************/
/*  Check whether a new line fit in the file lrecl size.               */
/***********************************************************************/
int TDBCSV::CheckWrite(PGLOBAL g)
  {
  int maxlen, n, nlen = (Fields - 1);

  if (trace(2))
    htrc("CheckWrite: R%d Mode=%d\n", Tdb_No, Mode);

  // Before writing the line we must check its length
  maxlen = (Mode == MODE_UPDATE && !Txfp->GetUseTemp())
         ? strlen(To_Line) : Lrecl;

  // Check whether record is too int
  for (int i = 0; i < Fields; i++)
  {
    if (Field[i]) {
      if (!(n = strlen(Field[i])))
        n += (Quoted > 2 ? 2 : 0);
      else if (strchr(Field[i], Sep) || (Qot && *Field[i] == Qot)
          || Quoted > 1 || (Quoted == 1 && !Fldtyp[i]))
      {
        if (!Qot) {
          snprintf(g->Message, sizeof(g->Message), MSG(SEP_IN_FIELD), i + 1);
          return -1;
        } else {
          // Quotes inside a quoted field must be doubled
          char *p1, *p2;

          for (p1 = Field[i]; (p2 = strchr(p1, Qot)); p1 = p2 + 1)
            n++;

          n += 2;        // Outside quotes
        } // endif
      }
      if ((nlen += n) > maxlen) {
        safe_strcpy(g->Message, sizeof(g->Message), MSG(LINE_TOO_LONG));
        return -1;
        } // endif nlen

      } // endif Field
  }
  return nlen;
  } // end of CheckWrite

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

/***********************************************************************/
/*  Implementation of the TDBFMT class.                                */
/***********************************************************************/
TDBFMT::TDBFMT(PGLOBAL g, PTDBFMT tdbp) : TDBCSV(g, tdbp)
  {
  FldFormat = tdbp->FldFormat;
  To_Fld = tdbp->To_Fld;
  FmtTest = tdbp->FmtTest;
  Linenum = tdbp->Linenum;
  } // end of TDBFMT copy constructor

// Method
PTDB TDBFMT::Clone(PTABS t)
  {
  PTDB    tp;
  PCSVCOL cp1, cp2;
//PFMTCOL cp1, cp2;
  PGLOBAL g = t->G;        // Is this really useful ???

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

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

  return tp;
  } // end of Clone

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

/***********************************************************************/
/*  FMT EstimatedLength. Returns an estimated minimum line length.     */
/*  The big problem here is how can we astimated that minimum ?        */
/***********************************************************************/
int TDBFMT::EstimatedLength(void)
  {
  // This is rather stupid !!!
  return ((PDOSDEF)To_Def)->GetEnding() + (int)((Lrecl / 10) + 1);
  } // end of EstimatedLength

/***********************************************************************/
/*  FMT Access Method opening routine.                                 */
/***********************************************************************/
bool TDBFMT::OpenDB(PGLOBAL g)
  {
  Linenum = 0;

  if (Mode == MODE_INSERT || Mode == MODE_UPDATE) {
    snprintf(g->Message, sizeof(g->Message), MSG(FMT_WRITE_NIY), "FMT");
    return true;                    // NIY
    } // endif Mode

  if (Use != USE_OPEN && Columns) {
    // Make the formats used to read records
    PSZ     pfm;
    int     i, n;
    PCSVCOL colp;
    PCOLDEF cdp;
    PDOSDEF tdp = (PDOSDEF)To_Def;

    for (colp = (PCSVCOL)Columns; colp; colp = (PCSVCOL)colp->Next)
      if (!colp->IsSpecial() && !colp->IsVirtual())  // a true column
        Fields = MY_MAX(Fields, (int)colp->Fldnum);

    if (Columns)
      Fields++;                // Fldnum was 0 based

    To_Fld = PlugSubAlloc(g, NULL, Lrecl + 1);
    FldFormat = (PSZ*)PlugSubAlloc(g, NULL, sizeof(PSZ) * Fields);
    memset(FldFormat, 0, sizeof(PSZ) * Fields);
    FmtTest = (int*)PlugSubAlloc(g, NULL, sizeof(int) * Fields);
    memset(FmtTest, 0, sizeof(int) * Fields);

    // Get the column formats
    for (cdp = tdp->GetCols(); cdp; cdp = cdp->GetNext())
      if (!cdp->IsSpecial() && !cdp->IsVirtual() 
                            && (i = cdp->GetOffset() - 1) < Fields) {
        if (!(pfm = cdp->GetFmt())) {
          snprintf(g->Message, sizeof(g->Message), MSG(NO_FLD_FORMAT), i + 1, Name);
          return true;
          } // endif pfm

        // Roughly check the Fmt format
        if ((n = strlen(pfm) - 2) < 4) {
          snprintf(g->Message, sizeof(g->Message), MSG(BAD_FLD_FORMAT), i + 1, Name);
          return true;
          } // endif n

        FldFormat[i] = (PSZ)PlugSubAlloc(g, NULL, n + 5);
        safe_strcpy(FldFormat[i], n + 5, pfm);

        if (!strcmp(pfm + n, "%m")) {
          // This is a field that can be missing. Flag it so it can
          // be handled with special processing.
          FldFormat[i][n+1] = 'n';  // To have sscanf normal processing
          FmtTest[i] = 2;
        } else if (i+1 < Fields && strcmp(pfm + n, "%n")) {
          // There are trailing characters after the field contents
          // add a marker for the next field start position.
          safe_strcat(FldFormat[i], n + 5, "%n");
          FmtTest[i] = 1;
        } // endif's

        } // endif i

    } // endif Use

  return TDBCSV::OpenDB(g);
  } // end of OpenDB

/***********************************************************************/
/*  ReadBuffer: Physical read routine for the FMT access method.       */
/***********************************************************************/
int TDBFMT::ReadBuffer(PGLOBAL g)
  {
  int  i, len, n, deb, fin, nwp, pos = 0, rc;
  bool bad = false;

  if ((rc = Txfp->ReadBuffer(g)) != RC_OK || !Fields)
    return rc;
  else
    ++Linenum;

  if (trace(2))
    htrc("FMT: Row %d is '%s' rc=%d\n", Linenum, To_Line, rc);

  // Find the offsets and lengths of the columns for this row
  for (i = 0; i < Fields; i++) {
    if (!bad) {
      deb = fin = -1;

      if (!FldFormat[i]) {
        n = 0;
      } else if (FmtTest[i] == 1) {
        nwp = -1;
        n = sscanf(To_Line + pos, FldFormat[i], &deb, To_Fld, &fin, &nwp);
      } else {
        n = sscanf(To_Line + pos, FldFormat[i], &deb, To_Fld, &fin);

        if (n != 1 && (deb >= 0 || i == Fields - 1) && FmtTest[i] == 2) {
          // Missing optional field, not an error
          n = 1;

          if (i == Fields - 1)
            fin = deb = 0;
          else
            fin = deb;

          } // endif n

        nwp = fin;
      } // endif i

      if (n != 1 || deb < 0 || fin < 0 || nwp < 0) {
        // This is to avoid a very strange sscanf bug occuring
        // with fields that ends with a null character.
        // This bug causes subsequent sscanf to return in error,
        // so next lines are not parsed correctly.
        sscanf("a", "%*c");       // Seems to reset things Ok

        if (CheckErr()) {
          snprintf(g->Message, sizeof(g->Message), MSG(BAD_LINEFLD_FMT), Linenum, i + 1, Name);
          return RC_FX;
        } else if (Accept)
          bad = true;
        else
          return RC_NF;

        } // endif n...

      } // endif !bad

    if (!bad) {
      Offset[i] = pos + deb;
      len = fin - deb;
    } else {
      nwp = 0;
      Offset[i] = pos;
      len = 0;
    } // endif bad

//  if (Mode != MODE_UPDATE)
      Fldlen[i] = len;
//  else if (len > Fldlen[i]) {
//    snprintf(g->Message, sizeof(g->Message), MSG(FIELD_TOO_LONG), i+1, To_Tdb->RowNumber(g));
//    return RC_FX;
//  } else {
//    strncpy(Field[i], To_Line + pos, len);
//    Field[i][len] = '\0';
//  } // endif Mode

    pos += nwp;
    } // endfor i

  if (bad)
    Nerr++;
  else
    sscanf("a", "%*c");             // Seems to reset things Ok

  return rc;
  } // end of ReadBuffer

/***********************************************************************/
/*  Data Base write routine FMT file access method.                    */
/***********************************************************************/
int TDBFMT::WriteDB(PGLOBAL g)
  {
  snprintf(g->Message, sizeof(g->Message), MSG(FMT_WRITE_NIY), "FMT");
  return RC_FX;                    // NIY
  } // end of WriteDB

// ------------------------ CSVCOL functions ----------------------------

/***********************************************************************/
/*  CSVCOL public constructor                                          */
/***********************************************************************/
CSVCOL::CSVCOL(PGLOBAL g, PCOLDEF cdp, PTDB tdbp, PCOL cprec, int i)
  : DOSCOL(g, cdp, tdbp, cprec, i, "CSV")
  {
  Fldnum = Deplac - 1;
  Deplac = 0;
  } // end of CSVCOL constructor

/***********************************************************************/
/*  CSVCOL constructor used for copying columns.                       */
/*  tdbp is the pointer to the new table descriptor.                   */
/***********************************************************************/
CSVCOL::CSVCOL(CSVCOL *col1, PTDB tdbp) : DOSCOL(col1, tdbp)
  {
  Fldnum = col1->Fldnum;
  } // end of CSVCOL 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 a blocked table,    */
/*  because if it is updated using a temporary file, the block size    */
/*  may be modified.                                                   */
/***********************************************************************/
bool CSVCOL::VarSize(void)
  {
  PTXF txfp = ((PTDBCSV)To_Tdb)->Txfp;

  if (txfp->IsBlocked() && txfp->GetUseTemp())
    // Blocked table using a temporary file
    return true;
  else
    return false;

  } // end VarSize

/***********************************************************************/
/*  ReadColumn: call DOSCOL::ReadColumn after having set the offet     */
/*  and length of the field to read as calculated by TDBCSV::ReadDB.   */
/***********************************************************************/
void CSVCOL::ReadColumn(PGLOBAL g)
  {
  int     rc;
  PTDBCSV tdbp = (PTDBCSV)To_Tdb;

  /*********************************************************************/
  /*  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 34;
		} // endif

  if (tdbp->Mode != MODE_UPDATE) {
    int colen = Long;                    // Column length

    // Set the field offset and length for this row
    Deplac = tdbp->Offset[Fldnum];       // Field offset
    Long   = tdbp->Fldlen[Fldnum];       // Field length

    if (trace(2))
      htrc("CSV ReadColumn %s Fldnum=%d offset=%d fldlen=%d\n",
            Name, Fldnum, Deplac, Long);

    if (Long > colen && tdbp->CheckErr()) {
      Long = colen;                       // Restore column length
      snprintf(g->Message, sizeof(g->Message), MSG(FLD_TOO_LNG_FOR),
              Fldnum + 1, Name, To_Tdb->RowNumber(g), tdbp->GetFile(g));
			throw 34;
		} // endif Long

    // Now do the reading
    DOSCOL::ReadColumn(g);

    // Restore column length
    Long = colen;
  } else {         // Mode Update
    // Field have been copied in TDB Field array
    PSZ fp = tdbp->Field[Fldnum];

    if (Dsp)
      for (int i = 0; fp[i]; i++)
        if (fp[i] == Dsp)
          fp[i] = '.';

    Value->SetValue_psz(fp);

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

  } // endif Mode

  } // end of ReadColumn

/***********************************************************************/
/*  WriteColumn: The column is written in TDBCSV matching Field.       */
/***********************************************************************/
void CSVCOL::WriteColumn(PGLOBAL g)
  {
  char   *p;
  int     n, flen;
  PTDBCSV tdbp = (PTDBCSV)To_Tdb;

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

  flen = GetLength();

  if (trace(2))
    htrc("Lrecl=%d Long=%d field=%d coltype=%d colval=%p\n",
          tdbp->Lrecl, Long, flen, Buf_Type, Value);

  /*********************************************************************/
  /*  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

  /*********************************************************************/
  /*  Get the string representation of the column value.               */
  /*********************************************************************/
  p = Value->GetCharString(Buf);
	n = strlen(p);

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

  if (n > flen) {
    snprintf(g->Message, sizeof(g->Message), MSG(BAD_FLD_LENGTH), Name, p, n,
                        tdbp->RowNumber(g), tdbp->GetFile(g));
		throw 34;
	} else if (Dsp)
    for (int i = 0; p[i]; i++)
      if (p[i] == '.')
        p[i] = Dsp; 

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

  /*********************************************************************/
  /*  Updating must be done also during the first pass so writing the  */
  /*  updated record can be checked for acceptable record length.      */
  /*********************************************************************/
  if (Fldnum < 0) {
    // This can happen for wrong offset value in XDB files
    snprintf(g->Message, sizeof(g->Message), MSG(BAD_FIELD_RANK), Fldnum + 1, Name);
		throw 34;
	} else
    strncpy(tdbp->Field[Fldnum], p, flen);

  if (trace(2))
    htrc(" col written: '%s'\n", p);

  } // end of WriteColumn

/* ---------------------------TDBCCL class --------------------------- */

/***********************************************************************/
/*  TDBCCL class constructor.                                          */
/***********************************************************************/
TDBCCL::TDBCCL(PCSVDEF tdp) : TDBCAT(tdp)
{
	Topt = tdp->GetTopt();
} // end of TDBCCL constructor

/***********************************************************************/
/*  GetResult: Get the list the CSV file columns.                      */
/***********************************************************************/
PQRYRES TDBCCL::GetResult(PGLOBAL g)
  {
  return CSVColumns(g, ((PTABDEF)To_Def)->GetPath(), Topt, false);
  } // end of GetResult

/* ------------------------ End of TabFmt ---------------------------- */
