/************* tabjson C++ Program Source Code File (.CPP) *************/
/* PROGRAM NAME: tabjson     Version 1.1                               */
/*  (C) Copyright to the author Olivier BERTRAND          2014 - 2015  */
/*  This program are the JSON class DB execution routines.             */
/***********************************************************************/

/***********************************************************************/
/*  Include relevant sections of the MariaDB header file.              */
/***********************************************************************/
#include <my_global.h>

/***********************************************************************/
/*  Include application header files:                                  */
/*  global.h    is header containing all global declarations.          */
/*  plgdbsem.h  is header containing the DB application declarations.  */
/*  tdbdos.h    is header containing the TDBDOS declarations.          */
/*  json.h      is header containing the JSON classes declarations.    */
/***********************************************************************/
#include "global.h"
#include "plgdbsem.h"
//#include "xtable.h"
//#include "mycat.h"                           // for FNC_COL
#include "maputil.h"
#include "filamtxt.h"
#include "tabdos.h"
//#include "resource.h"                        // for IDS_COLUMNS
#include "tabjson.h"
#include "filamap.h"
#if defined(ZIP_SUPPORT)
#include "filamzip.h"
#endif   // ZIP_SUPPORT
#include "tabmul.h"
#include "checklvl.h"
#include "resource.h"
#include "mycat.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);

/***********************************************************************/
/*  Make the document tree from a file.                                */
/***********************************************************************/
PJSON MakeJsonTree(PGLOBAL g, char *fn, char *objn, int pty, 
                   PJAR& doc, DWORD& drc)
{
  char   *p, *memory, *objpath, *key;
  int     len, i = 0;
  HANDLE  hFile;
	MEMMAP  mm;
  PJSON   jsp, top;
  PJOB    objp = NULL;
  PJAR    arp = NULL;
  PJVAL   val = NULL;

	/*********************************************************************/
	/*  Create the mapping file object.                                  */
	/*********************************************************************/
	hFile = CreateFileMap(g, fn, &mm, MODE_READ, false);

	if (hFile == INVALID_HANDLE_VALUE) {
    drc = GetLastError();

    if (!*g->Message)
      sprintf(g->Message, MSG(OPEN_MODE_ERROR), "map", (int)drc, fn);
                
	  return NULL;
  	} // endif hFile

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

	if (!len) {      				// Empty file
    CloseFileHandle(hFile);
    CloseMemMap(memory, len);
    drc = ENOENT;
  	return NULL;
	} else if (!memory) {
    sprintf(g->Message, MSG(MAP_VIEW_ERROR), fn, drc);
    CloseFileHandle(hFile);
    return NULL;
  } // endif Memory

  CloseFileHandle(hFile);                    // Not used anymore

  /*********************************************************************/
  /*  Parse the json file and allocate its tree structure.             */
  /*********************************************************************/
  g->Message[0] = 0;
  jsp = top = ParseJson(g, memory, len, pty);
  CloseMemMap(memory, len);
  drc = EBADF;

  if (!jsp && g->Message[0])
    return NULL;

  objpath = PlugDup(g, objn);    // NULL if !objn

  /*********************************************************************/
  /*  Find the table in the tree structure.                            */
  /*********************************************************************/
  for (doc = NULL; jsp && objpath; objpath = p) {
    if ((p = strchr(objpath, ':')))
      *p++ = 0;

    if (*objpath != '[') {         // objpass is a key
      if (jsp->GetType() != TYPE_JOB) {
        strcpy(g->Message, "Table path does no match json file");
        return NULL;
        } // endif Type

      key = objpath;
      objp = jsp->GetObject();
      arp = NULL;
      val = objp->GetValue(key);

      if (!val || !(jsp = val->GetJson())) {
        sprintf(g->Message, "Cannot find object key %s", key);
        return NULL;
        } // endif val

    } else if (objpath[strlen(objpath)-1] == ']') {
      if (jsp->GetType() != TYPE_JAR) {
        strcpy(g->Message, "Table path does no match json file");
        return NULL;
        } // endif Type

      arp = jsp->GetArray();
      objp = NULL;
      i = atoi(objpath+1) - 1;
      val = arp->GetValue(i);

      if (!val) {
        sprintf(g->Message, "Cannot find array value %d", i);
        return NULL;
        } // endif val

    } else {
      sprintf(g->Message, "Invalid Table path %s", objn);
      return NULL;
    } // endif objpath

    jsp = val->GetJson();
    } // endfor objpath

  if (jsp && jsp->GetType() == TYPE_JAR)
    doc = jsp->GetArray();
  else { 
    // The table is void or is just one object or one value
    doc = new(g) JARRAY;

    if (val) {
      doc->AddValue(g, val);
      doc->InitArray(g);
    } else if (jsp) {
      doc->AddValue(g, new(g) JVALUE(jsp));
      doc->InitArray(g);
    } // endif val

    if (objp)
      objp->SetValue(g, new(g) JVALUE(doc), key);
    else if (arp)
      arp->SetValue(g, new(g) JVALUE(doc), i);
    else
      top = doc;

  } // endif jsp

  return top;
} // end of MakeJsonTree

typedef struct _jncol {
  struct _jncol *Next;
  char *Name;
  char *Fmt;
  int   Type;
  int   Len;
  int   Scale;
  bool  Cbn;
  bool  Found;
} JCOL, *PJCL;

/***********************************************************************/
/* JSONColumns: construct the result blocks containing the description */
/* of all the columns of a table contained inside a JSON file.         */
/***********************************************************************/
PQRYRES JSONColumns(PGLOBAL g, char *dp, const char *fn, char *objn,
                    int pretty, int lrecl, int lvl, bool info)
{
  static int  buftyp[] = {TYPE_STRING, TYPE_SHORT, TYPE_STRING, TYPE_INT, 
                          TYPE_INT, TYPE_SHORT, TYPE_SHORT, TYPE_STRING};
  static XFLD fldtyp[] = {FLD_NAME, FLD_TYPE, FLD_TYPENAME, FLD_PREC, 
                          FLD_LENGTH, FLD_SCALE, FLD_NULL, FLD_FORMAT};
  static unsigned int length[] = {0, 6, 8, 10, 10, 6, 6, 0};
  char    filename[_MAX_PATH], colname[65], fmt[129], *buf;
  int     i, j, n = 0;
  int     ncol = sizeof(buftyp) / sizeof(int);
  FILE   *infile;
  DWORD   drc;
  PVAL    valp;
  JCOL    jcol;
  PJCL    jcp, fjcp = NULL, pjcp = NULL;
  PJPR   *jrp, jpp;
  PJSON   jsp;
  PJVAL   jvp;
  PJOB    row;
  PJAR    doc;
  PQRYRES qrp;
  PCOLRES crp;

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

  if (trace)
    htrc("File %s pretty=%d lvl=%d lrecl=%d\n", 
          SVP(fn), pretty, lvl, lrecl);

  /*********************************************************************/
  /*  Open the input file.                                             */
  /*********************************************************************/
  if (!fn) {
    strcpy(g->Message, MSG(MISSING_FNAME));
    return NULL;
  } else
    PlugSetPath(filename, fn, dp);           

  if (pretty == 2) {
    if (!MakeJsonTree(g, filename, objn, pretty, doc, drc))
      return NULL;

    jsp = (doc) ? doc->GetValue(0) : NULL;
  } else {
    if (!lrecl) {
      sprintf(g->Message, "LRECL must be specified for pretty=%d", pretty);
      return NULL;
    } else if (!(buf = (char*)PlugSubAlloc(g, NULL, lrecl))) {
      sprintf(g->Message, "Alloc error, lrecl=%d", lrecl);
      return NULL;
    } // endif buf

    if (!(infile = global_fopen(g, MSGID_CANNOT_OPEN, filename, "r")))
      return NULL;

    // Read first record
    for (i = 0; i <= pretty; i++)
      if (!fgets(buf, lrecl, infile)) {
        if (feof(infile)) {
          strcpy(g->Message, "Void json table");
          return NULL;
          } // endif fgets

        sprintf(g->Message, MSG(READ_ERROR), filename, strerror(0));
        return NULL;
        } // endif fgets

      jsp = ParseJson(g, buf, strlen(buf), pretty, NULL);
    } // endif pretty

  if (!(row = (jsp) ? jsp->GetObject() : NULL)) {
    strcpy(g->Message, "Can only retrieve columns from object rows");
    return NULL;
    } // endif row

  jcol.Next = NULL;
  jcol.Found = true;
  colname[64] = 0;
  fmt[128] = 0;
  jrp = (PJPR*)PlugSubAlloc(g, NULL, sizeof(PJPR) * lvl);

  /*********************************************************************/
  /*  Analyse the JSON tree and define columns.                        */
  /*********************************************************************/
  for (i = 1; ; i++) {
    for (jpp = row->GetFirst(); jpp; jpp = jpp->GetNext()) {
      for (j = 0; j < lvl; j++)
        jrp[j] = NULL;

     more:
      strncpy(colname, jpp->GetKey(), 64);
      *fmt = 0;
      j = 0;
      jvp = jpp->GetVal();

     retry:
      if ((valp = jvp ? jvp->GetValue() : NULL)) {
        jcol.Type = valp->GetType();
        jcol.Len = valp->GetValLen();
        jcol.Scale = valp->GetValPrec();
        jcol.Cbn = valp->IsNull();
      } else if (!jvp || jvp->IsNull()) {
        jcol.Type = TYPE_UNKNOWN;
        jcol.Len = jcol.Scale = 0;
        jcol.Cbn = true;
      } else  if (j < lvl) {
        if (!*fmt)
          strcpy(fmt, colname);

        jsp = jvp->GetJson();

        switch (jsp->GetType()) {
          case TYPE_JOB:
            if (!jrp[j])
              jrp[j] = jsp->GetFirst();

            strncat(strncat(fmt, ":", 128), jrp[j]->GetKey(), 128);
            strncat(strncat(colname, "_", 64), jrp[j]->GetKey(), 64);
            jvp = jrp[j]->GetVal();
            j++;
            break;
          case TYPE_JAR:
            strncat(fmt, ":", 128);
            jvp = jsp->GetValue(0);
            break;
          default:
            sprintf(g->Message, "Logical error after %s", fmt);
            goto err;
          } // endswitch jsp

        goto retry;
      } else {
        jcol.Type = TYPE_STRING;
        jcol.Len = 256;
        jcol.Scale = 0;
        jcol.Cbn = true;
      } // endif's

      // Check whether this column was already found
      for (jcp = fjcp; jcp; jcp = jcp->Next)
        if (!strcmp(colname, jcp->Name))
          break;
  
      if (jcp) {
        if (jcp->Type != jcol.Type)
          jcp->Type = TYPE_STRING;

        if (*fmt && (!jcp->Fmt || strlen(jcp->Fmt) < strlen(fmt))) {
          jcp->Fmt = PlugDup(g, fmt);
          length[7] = MY_MAX(length[7], strlen(fmt));
          } // endif *fmt

        jcp->Len = MY_MAX(jcp->Len, jcol.Len);
        jcp->Scale = MY_MAX(jcp->Scale, jcol.Scale);
        jcp->Cbn |= jcol.Cbn;
        jcp->Found = true;
      } else {
        // New column
        jcp = (PJCL)PlugSubAlloc(g, NULL, sizeof(JCOL));
        *jcp = jcol;
        jcp->Cbn |= (i > 1);
        jcp->Name = PlugDup(g, colname);
        length[0] = MY_MAX(length[0], strlen(colname));
  
        if (*fmt) {
          jcp->Fmt = PlugDup(g, fmt);
          length[7] = MY_MAX(length[7], strlen(fmt));
        } else
          jcp->Fmt = NULL;
  
        if (pjcp) {
          jcp->Next = pjcp->Next;
          pjcp->Next = jcp;
        } else
          fjcp = jcp;

        n++;
      } // endif jcp

      pjcp = jcp;

      for (j = lvl - 1; j >= 0; j--)
        if (jrp[j] && (jrp[j] = jrp[j]->GetNext()))
          goto more;

      } // endfor jpp

    // Missing column can be null
    for (jcp = fjcp; jcp; jcp = jcp->Next) {
      jcp->Cbn |= !jcp->Found;
      jcp->Found = false;
      } // endfor jcp

    if (pretty != 2) {
      // Read next record
      if (!fgets(buf, lrecl, infile)) {
        if (!feof(infile)) {
          sprintf(g->Message, MSG(READ_ERROR), filename, strerror(0));
          return NULL;
        } else
          jsp = NULL;

      } else if (pretty == 1 && strlen(buf) == 1 && *buf == ']') {
        jsp = NULL;
      } else
        jsp = ParseJson(g, buf, strlen(buf), pretty, NULL);

    } else
      jsp = doc->GetValue(i);

    if (!(row = (jsp) ? jsp->GetObject() : NULL))
      break;

    } // endor i

  if (pretty != 2)
    fclose(infile);

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

  /*********************************************************************/
  /*  Allocate the structures used to refer to the result set.         */
  /*********************************************************************/
  qrp = PlgAllocResult(g, ncol, n, IDS_COLUMNS + 3,
                          buftyp, fldtyp, length, false, false);

  crp = qrp->Colresp->Next->Next->Next->Next->Next->Next;
  crp->Name = "Nullable";
  crp->Next->Name = "Jpath";

  if (info || !qrp)
    return qrp;

  qrp->Nblin = n;

  /*********************************************************************/
  /*  Now get the results into blocks.                                 */
  /*********************************************************************/
  for (i = 0, jcp = fjcp; jcp; i++, jcp = jcp->Next) {
    if (jcp->Type == TYPE_UNKNOWN)            // Void column
      jcp->Type = TYPE_STRING;

    crp = qrp->Colresp;                    // Column Name
    crp->Kdata->SetValue(jcp->Name, i);
    crp = crp->Next;                       // Data Type
    crp->Kdata->SetValue(jcp->Type, i);
    crp = crp->Next;                       // Type Name
    crp->Kdata->SetValue(GetTypeName(jcp->Type), i);
    crp = crp->Next;                       // Precision
    crp->Kdata->SetValue(jcp->Len, i);
    crp = crp->Next;                       // Length
    crp->Kdata->SetValue(jcp->Len, i);
    crp = crp->Next;                       // Scale (precision)
    crp->Kdata->SetValue(jcp->Scale, i);
    crp = crp->Next;                       // Nullable
    crp->Kdata->SetValue(jcp->Cbn ? 1 : 0, i);
    crp = crp->Next;                       // Field format

    if (crp->Kdata)
      crp->Kdata->SetValue(jcp->Fmt, i);

    } // endfor i

  /*********************************************************************/
  /*  Return the result pointer.                                       */
  /*********************************************************************/
  return qrp;

err:
  if (pretty != 2)
    fclose(infile);

  return NULL;
  } // end of JSONColumns

/* -------------------------- Class JSONDEF -------------------------- */

JSONDEF::JSONDEF(void)
{
  Jmode = MODE_OBJECT;
  Objname = NULL;
  Xcol = NULL;
  Pretty = 2;
  Limit = 1;
  Level = 0;
  ReadMode = 0;
} // end of JSONDEF constructor

/***********************************************************************/
/*  DefineAM: define specific AM block values.                         */
/***********************************************************************/
bool JSONDEF::DefineAM(PGLOBAL g, LPCSTR am, int poff)
{
  Jmode = (JMODE)GetIntCatInfo("Jmode", MODE_OBJECT);
  Objname = GetStringCatInfo(g, "Object", NULL);
  Xcol = GetStringCatInfo(g, "Expand", NULL);
  Pretty = GetIntCatInfo("Pretty", 2);
  Level = GetIntCatInfo("Level", 0);
  Limit = GetIntCatInfo("Limit", 10);
  return DOSDEF::DefineAM(g, "DOS", poff);
} // end of DefineAM

/***********************************************************************/
/*  GetTable: makes a new Table Description Block.                     */
/***********************************************************************/
PTDB JSONDEF::GetTable(PGLOBAL g, MODE m)
{
  if (Catfunc == FNC_COL)
    return new(g)TDBJCL(this);

  PTDBASE tdbp;
  PTXF    txfp = NULL;

  // JSN not used for pretty=1 for insert or delete
  if (!Pretty || (Pretty == 1 && (m == MODE_READ || m == MODE_UPDATE))) {
    USETEMP tmp = UseTemp();
    bool    map = Mapped && m != MODE_INSERT &&
                !(tmp != TMP_NO && m == MODE_UPDATE) &&
                !(tmp == TMP_FORCE &&
                (m == MODE_UPDATE || m == MODE_DELETE));

    if (Compressed) {
#if defined(ZIP_SUPPORT)
      if (Compressed == 1)
        txfp = new(g) ZIPFAM(this);
      else
        txfp = new(g) ZLBFAM(this);
#else   // !ZIP_SUPPORT
      sprintf(g->Message, MSG(NO_FEAT_SUPPORT), "ZIP");
      return NULL;
#endif  // !ZIP_SUPPORT
    } else if (map)
      txfp = new(g) MAPFAM(this);
    else
      txfp = new(g) DOSFAM(this);

    // Txfp must be set for TDBDOS
    tdbp = new(g) TDBJSN(this, txfp);
  } else {
    txfp = new(g) DOSFAM(this);
    tdbp = new(g) TDBJSON(this, txfp);
  } // endif Pretty

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

  return tdbp;
} // end of GetTable

/* --------------------------- Class TDBJSN -------------------------- */

/***********************************************************************/
/*  Implementation of the TDBJSN class.                                */
/***********************************************************************/
TDBJSN::TDBJSN(PJDEF tdp, PTXF txfp) : TDBDOS(tdp, txfp)
  {
  Row = NULL;
  Colp = NULL;
  Jmode = tdp->Jmode;
  Xcol = tdp->Xcol;
  Fpos = -1;
//Spos = 0;
  N = 0;
  Limit = tdp->Limit;
  NextSame = 0;
  SameRow = 0;
  Xval = -1;
  Pretty = tdp->Pretty;
  Strict = tdp->Strict;
  Comma = false;
  } // end of TDBJSN standard constructor

TDBJSN::TDBJSN(TDBJSN *tdbp) : TDBDOS(NULL, tdbp)
  {
  Row = tdbp->Row;
  Colp = tdbp->Colp;
  Jmode = tdbp->Jmode;
  Xcol = tdbp->Xcol;
  Fpos = tdbp->Fpos;
//Spos = tdbp->Spos;
  N = tdbp->N;
  Limit = tdbp->Limit;
  NextSame = tdbp->NextSame;
  SameRow = tdbp->SameRow;
  Xval = tdbp->Xval;
  Pretty = tdbp->Pretty;
  Strict = tdbp->Strict;
  Comma = tdbp->Comma;
  } // end of TDBJSN copy constructor

// Used for update
PTDB TDBJSN::CopyOne(PTABS t)
  {
  PTDB    tp;
  PJCOL   cp1, cp2;
  PGLOBAL g = t->G;

  tp = new(g) TDBJSN(this);

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

  return tp;
  } // end of CopyOne

/***********************************************************************/
/*  Allocate JSN column description block.                             */
/***********************************************************************/
PCOL TDBJSN::MakeCol(PGLOBAL g, PCOLDEF cdp, PCOL cprec, int n)
  {
  PJCOL colp = new(g) JSONCOL(g, cdp, this, cprec, n);

  return (colp->ParseJpath(g)) ? NULL : colp;
  } // end of MakeCol

/***********************************************************************/
/*  InsertSpecialColumn: Put a special column ahead of the column list.*/
/***********************************************************************/
PCOL TDBJSN::InsertSpecialColumn(PGLOBAL g, PCOL colp)
  {
  if (!colp->IsSpecial())
    return NULL;

//if (Xcol && ((SPCBLK*)colp)->GetRnm())
//  colp->SetKey(0);               // Rownum is no more a key

  colp->SetNext(Columns);
  Columns = colp;
  return colp;
  } // end of InsertSpecialColumn

/***********************************************************************/
/*  JSON Cardinality: returns table size in number of rows.            */
/***********************************************************************/
int TDBJSN::Cardinality(PGLOBAL g)
  {
  if (!g)
    return 0;
  else if (Cardinal < 0)
    Cardinal = TDBDOS::Cardinality(g);

  return Cardinal;
  } // end of Cardinality

/***********************************************************************/
/*  JSON GetMaxSize: returns file size estimate in number of lines.    */
/***********************************************************************/
int TDBJSN::GetMaxSize(PGLOBAL g)
  {
  if (MaxSize < 0)
    MaxSize = TDBDOS::GetMaxSize(g) * ((Xcol) ? Limit : 1);

  return MaxSize;
  } // end of GetMaxSize

/***********************************************************************/
/*  OpenDB: Data Base open routine for JSN access method.              */
/***********************************************************************/
bool TDBJSN::OpenDB(PGLOBAL g)
  {
  if (Use == USE_OPEN) {
    /*******************************************************************/
    /*  Table already open replace it at its beginning.                */
    /*******************************************************************/
    Fpos= -1;
//  Spos = 0;
    NextSame = 0;
    SameRow = 0;
  } else {
    /*******************************************************************/
    /*  First opening.                                                 */
    /*******************************************************************/
    if (Mode == MODE_INSERT)
      switch (Jmode) {
        case MODE_OBJECT: Row = new(g) JOBJECT; break;
        case MODE_ARRAY:  Row = new(g) JARRAY;  break;
        case MODE_VALUE:  Row = new(g) JVALUE;  break;
        default:
          sprintf(g->Message, "Invalid Jmode %d", Jmode);
          return true;
        } // endswitch Jmode

  } // endif Use

  return TDBDOS::OpenDB(g);
  } // 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 TDBJSN::SkipHeader(PGLOBAL g)
  {
  int  len = GetFileLength(g);
  bool rc = false;

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

#if defined(WIN32)
#define  Ending  2
#else   // !WIN32
#define  Ending  1
#endif  // !WIN32

  if (Pretty == 1) {
    if (Mode == MODE_INSERT || Mode == MODE_DELETE) {
      // Mode Insert and delete are no more handled here
      assert(false);
    } else if (len) // !Insert && !Delete
      rc = (Txfp->SkipRecord(g, false) == RC_FX || Txfp->RecordPos(g));

    } // endif Pretty

  return rc;
  } // end of SkipHeader

/***********************************************************************/
/*  ReadDB: Data Base read routine for JSN access method.              */
/***********************************************************************/
int TDBJSN::ReadDB(PGLOBAL g)
  {
  int   rc;

  N++;

  if (NextSame) {
    SameRow = NextSame;
    NextSame = 0;
    return RC_OK;
  } else if ((rc = TDBDOS::ReadDB(g)) == RC_OK)
    if (!IsRead() && ((rc = ReadBuffer(g)) != RC_OK)) {
      // Deferred reading failed
    } else if (!(Row = ParseJson(g, To_Line, 
                             strlen(To_Line), Pretty, &Comma))) {
      rc = (Pretty == 1 && !strcmp(To_Line, "]")) ? RC_EF : RC_FX;
    } else {
      SameRow = 0;
      Fpos++;
      rc = RC_OK;
    } // endif's

  return rc;
  } // end of ReadDB

/***********************************************************************/
/*  PrepareWriting: Prepare the line for WriteDB.                      */
/***********************************************************************/
  bool TDBJSN::PrepareWriting(PGLOBAL g)
  {
  PSZ s = Serialize(g, Row, NULL, Pretty);

  if (s) {
    if (Comma)
      strcat(s, ",");

    if ((signed)strlen(s) > Lrecl) {
      sprintf(g->Message, "Line would be truncated (lrecl=%d)", Lrecl);
      return true;
    } else
      strcpy(To_Line, s);

    Row->Clear();
    return false;
  } else
    return true;

  } // end of PrepareWriting

/* ---------------------------- JSONCOL ------------------------------ */

/***********************************************************************/
/*  JSONCOL public constructor.                                        */
/***********************************************************************/
JSONCOL::JSONCOL(PGLOBAL g, PCOLDEF cdp, PTDB tdbp, PCOL cprec, int i)
       : DOSCOL(g, cdp, tdbp, cprec, i, "DOS")
  {
  Tjp = (TDBJSN *)(tdbp->GetOrig() ? tdbp->GetOrig() : tdbp);
  Jpath = cdp->GetFmt();
  MulVal = NULL;
  Nodes = NULL;
  Nod = 0;
  Xnod = -1;
  Xpd = false;
  Parsed = false;
  } // end of JSONCOL constructor

/***********************************************************************/
/*  JSONCOL constructor used for copying columns.                      */
/*  tdbp is the pointer to the new table descriptor.                   */
/***********************************************************************/
JSONCOL::JSONCOL(JSONCOL *col1, PTDB tdbp) : DOSCOL(col1, tdbp)
  {
  Tjp = col1->Tjp;
  Jpath = col1->Jpath;
  MulVal = col1->MulVal;
  Nodes = col1->Nodes;
  Nod = col1->Nod;
  Xnod = col1->Xnod;
  Xpd = col1->Xpd;
  Parsed = col1->Parsed;
  } // end of JSONCOL copy constructor

/***********************************************************************/
/*  SetBuffer: prepare a column block for write operation.             */
/***********************************************************************/
bool JSONCOL::SetBuffer(PGLOBAL g, PVAL value, bool ok, bool check)
  {
  if (DOSCOL::SetBuffer(g, value, ok, check))
    return true;

  // Parse the json path
  if (ParseJpath(g))
    return true;

  Tjp = (TDBJSN*)To_Tdb;
  return false;
  } // end of SetBuffer

/***********************************************************************/
/*  Check whether this object is expanded.                             */
/***********************************************************************/
bool JSONCOL::CheckExpand(PGLOBAL g, int i, PSZ nm, bool b)
  {
  if ((Tjp->Xcol && nm && !strcmp(nm, Tjp->Xcol) &&
      (Tjp->Xval < 0 || Tjp->Xval == i)) || Xpd) {
    Xpd = true;              // Expandable object
    Nodes[i].Op = OP_EXP;
  } else if (b) {
    strcpy(g->Message, "Cannot expand more than one branch");
    return true;
  } // endif Xcol

  return false;
  } // end of CheckExpand

/***********************************************************************/
/*  Analyse array processing options.                                  */
/***********************************************************************/
bool JSONCOL::SetArrayOptions(PGLOBAL g, char *p, int i, PSZ nm)
  {
  int    n = (int)strlen(p);
  bool   dg = true, b = false;
  PJNODE jnp = &Nodes[i];

  if (*p) {
    if (p[--n] == ']') {
      p[n--] = 0;
      p++;
    } else {
      // Wrong array specification
      sprintf(g->Message,
        "Invalid array specification %s for %s", p, Name);
      return true;
    } // endif p

  } else
    b = true;

  // To check whether a numeric Rank was specified
  for (int k = 0; dg && p[k]; k++)
    dg = isdigit(p[k]) > 0;

  if (!n) {
    // Default specifications
    if (CheckExpand(g, i, nm, false))
      return true;
    else if (jnp->Op != OP_EXP) {
      if (b) {
        // Return 1st value
        jnp->Rank = 1;
        jnp->Op = OP_EQ;
      } else if (!Value->IsTypeNum()) {
        jnp->CncVal = AllocateValue(g, (void*)", ", TYPE_STRING);
        jnp->Op = OP_CNC;
      } else
        jnp->Op = OP_ADD;

      } // endif OP

  } else if (dg) {
    if (atoi(p) > 0) {
      // Return nth value
      jnp->Rank = atoi(p);
      jnp->Op = OP_EQ;
    } else // Ignore array
      jnp->Op = OP_NULL;

  } else if (n == 1) {
    // Set the Op value;
    switch (*p) {
      case '+': jnp->Op = OP_ADD;  break;
      case '*': jnp->Op = OP_MULT; break;
      case '>': jnp->Op = OP_MAX;  break;
      case '<': jnp->Op = OP_MIN;  break;
      case '!': jnp->Op = OP_SEP;  break; // Average
      case '#': jnp->Op = OP_NUM;  break;
      case 'x':
      case 'X': // Expand this array
        if (!Tjp->Xcol && nm) {  
          Xpd = true;
          jnp->Op = OP_EXP;
          Tjp->Xval = i;
          Tjp->Xcol = nm;
        } else if (CheckExpand(g, i, nm, true))
          return true;

        break;
      default:
        sprintf(g->Message,
          "Invalid function specification %c for %s", *p, Name);
        return true;
      } // endswitch *p

  } else if (*p == '"' && p[n - 1] == '"') {
    // This is a concat specification
    jnp->Op = OP_CNC;

    if (n > 2) {
      // Set concat intermediate string
      p[n - 1] = 0;
      jnp->CncVal = AllocateValue(g, p + 1, TYPE_STRING);
      } // endif n

  } else {
    sprintf(g->Message, "Wrong array specification for %s", Name);
    return true;
  } // endif's

  // For calculated arrays, a local Value must be used
  switch (jnp->Op) {
    case OP_NUM:
      jnp->Valp = AllocateValue(g, TYPE_INT);
      break;
    case OP_ADD:
    case OP_MULT:
    case OP_SEP:
      if (!IsTypeChar(Buf_Type))
        jnp->Valp = AllocateValue(g, Buf_Type, 0, GetPrecision());
      else
        jnp->Valp = AllocateValue(g, TYPE_DOUBLE, 0, 2);

      break;
    case OP_MIN:
    case OP_MAX:
      jnp->Valp = AllocateValue(g, Buf_Type, Long, GetPrecision());
      break;
    case OP_CNC:
      if (IsTypeChar(Buf_Type))
        jnp->Valp = AllocateValue(g, TYPE_STRING, Long, GetPrecision());
      else
        jnp->Valp = AllocateValue(g, TYPE_STRING, 512);

      break;
    default:
      break;
  } // endswitch Op

  if (jnp->Valp)
    MulVal = AllocateValue(g, jnp->Valp);

  return false;
  } // end of SetArrayOptions

/***********************************************************************/
/*  Parse the eventual passed Jpath information.                       */
/*  This information can be specified in the Fieldfmt column option    */
/*  when creating the table. It permits to indicate the position of    */
/*  the node corresponding to that column.                             */
/***********************************************************************/
bool JSONCOL::ParseJpath(PGLOBAL g)
  {
  char  *p, *p2 = NULL, *pbuf = NULL;
  int    i; 
  bool   mul = false;

  if (Parsed)
    return false;                       // Already done
  else if (InitValue(g))
    return true;
  else if (!Jpath)
    Jpath = Name;

  pbuf = PlugDup(g, Jpath);

  // The Jpath must be analyzed
  for (i = 0, p = pbuf; (p = strchr(p, ':')); i++, p++)
    Nod++;                         // One path node found

  Nodes = (PJNODE)PlugSubAlloc(g, NULL, (++Nod) * sizeof(JNODE));
  memset(Nodes, 0, (Nod) * sizeof(JNODE));

  // Analyze the Jpath for this column
  for (i = 0, p = pbuf; i < Nod; i++, p = (p2 ? p2 + 1 : p + strlen(p))) {
    if ((p2 = strchr(p, ':'))) 
      *p2 = 0;

    // Jpath must be explicit
    if (*p == 0 || *p == '[') {
      // Analyse intermediate array processing
      if (SetArrayOptions(g, p, i, Nodes[i-1].Key))
        return true;

    } else if (*p == '*') {
      // Return JSON
      Nodes[i].Op = OP_XX;
    } else {
      Nodes[i].Key = p;
      Nodes[i].Op = OP_EXIST;
    } // endif's

    } // endfor i, p

  MulVal = AllocateValue(g, Value);
  Parsed = true;
  return false;
  } // end of ParseJpath

/***********************************************************************/
/*  MakeJson: Serialize the json item and set value to it.             */
/***********************************************************************/
PVAL JSONCOL::MakeJson(PGLOBAL g, PJSON jsp)
  {
  if (Value->IsTypeNum()) {
    strcpy(g->Message, "Cannot make Json for a numeric column");
    Value->Reset();
  } else
    Value->SetValue_psz(Serialize(g, jsp, NULL, 0));

  return Value;
  } // end of MakeJson

/***********************************************************************/
/*  SetValue: Set a value from a JVALUE contains.                      */
/***********************************************************************/
void JSONCOL::SetJsonValue(PGLOBAL g, PVAL vp, PJVAL val, int n)
  {
  if (val) {
    switch (val->GetValType()) {
      case TYPE_STRG:
      case TYPE_INTG:
      case TYPE_DBL:
        vp->SetValue_pval(val->GetValue());
        break;
      case TYPE_BOOL:
        if (vp->IsTypeNum())
          vp->SetValue(val->GetInteger() ? 1 : 0);
        else
          vp->SetValue_psz((PSZ)(val->GetInteger() ? "true" : "false"));
    
        break;
      case TYPE_JAR:
        SetJsonValue(g, vp, val->GetArray()->GetValue(0), n);
        break;
      case TYPE_JOB:
//      if (!vp->IsTypeNum() || !Strict) {
          vp->SetValue_psz(val->GetObject()->GetText(g, NULL));
          break;
//        } // endif Type
     
      default:
        vp->Reset();
      } // endswitch Type

  } else
    vp->Reset();

  } // end of SetJsonValue

/***********************************************************************/
/*  ReadColumn:                                                        */
/***********************************************************************/
void JSONCOL::ReadColumn(PGLOBAL g)
  {
  if (!Tjp->SameRow || Xnod >= Tjp->SameRow)
    Value->SetValue_pval(GetColumnValue(g, Tjp->Row, 0));

  } // end of ReadColumn

/***********************************************************************/
/*  GetColumnValue:                                                    */
/***********************************************************************/
PVAL JSONCOL::GetColumnValue(PGLOBAL g, PJSON row, int i)
  {
  int   n = Nod - 1;
  bool  expd = false;
  PJAR  arp;
  PJVAL val = NULL;

  for (; i < Nod && row; i++) {
    if (Nodes[i].Op == OP_NUM) {
      Value->SetValue(row->GetType() == TYPE_JAR ? row->size() : 1);
      return(Value);
    } else if (Nodes[i].Op == OP_XX) {
      return MakeJson(g, row);
    } else switch (row->GetType()) {
      case TYPE_JOB:
        if (!Nodes[i].Key) {
          // Expected Array was not there
          if (i < Nod-1)
            continue;
          else
            val = new(g) JVALUE(row);

        } else
          val = ((PJOB)row)->GetValue(Nodes[i].Key);

        break;
      case TYPE_JAR:
        arp = (PJAR)row;

        if (!Nodes[i].Key) {
          if (Nodes[i].Op != OP_NULL) {
            if (Nodes[i].Rank) {
              val = arp->GetValue(Nodes[i].Rank - 1);
            } else if (Nodes[i].Op == OP_EXP) {
              return ExpandArray(g, arp, i);
            } else
              return CalculateArray(g, arp, i);

          } else
            val = NULL;

        } else if (i < Nod-1) {
          strcpy(g->Message, "Unexpected array");
          val = NULL;          // Not an expected array
        } else
          val = arp->GetValue(0);

        break;
      case TYPE_JVAL:
        val = (PJVAL)row;
        break;
      default:
        sprintf(g->Message, "Invalid row JSON type %d", row->GetType());
        val = NULL;
      } // endswitch Type

    if (i < Nod-1)
      row = (val) ? val->GetJson() : NULL;

    } // endfor i

  SetJsonValue(g, Value, val, n);
  return Value;
  } // end of GetColumnValue

/***********************************************************************/
/*  ExpandArray:                                                       */
/***********************************************************************/
PVAL JSONCOL::ExpandArray(PGLOBAL g, PJAR arp, int n)
  {
  int    ars;
  PJVAL  jvp;
  JVALUE jval;

  ars = MY_MIN(Tjp->Limit, arp->size());

  if (!(jvp = arp->GetValue(Nodes[n].Nx))) {
    strcpy(g->Message, "Logical error expanding array");
    longjmp(g->jumper[g->jump_level], 666);
    } // endif jvp

  if (n < Nod - 1 && jvp->GetJson()) {
    jval.SetValue(GetColumnValue(g, jvp->GetJson(), n + 1));
    jvp = &jval;
    } // endif n

  if (n >= Tjp->NextSame) {
    if (++Nodes[n].Nx == ars) {
      Nodes[n].Nx = 0;
      Xnod = 0;
    } else
      Xnod = n;

    Tjp->NextSame = Xnod;
    } // endif NextSame 

  SetJsonValue(g, Value, jvp, n);
  return Value;
  } // end of ExpandArray

/***********************************************************************/
/*  CalculateArray:                                                    */
/***********************************************************************/
PVAL JSONCOL::CalculateArray(PGLOBAL g, PJAR arp, int n)
  {
  int    i, ars, nv = 0, nextsame = Tjp->NextSame;
  bool   err;
  OPVAL  op = Nodes[n].Op;
  PVAL   val[2], vp = Nodes[n].Valp;
  PJVAL  jvrp, jvp;
  JVALUE jval;

  vp->Reset();
  ars = MY_MIN(Tjp->Limit, arp->size());

  for (i = 0; i < ars; i++) {
    jvrp = arp->GetValue(i);

    do {
      if (n < Nod - 1 && jvrp->GetJson()) {
        Tjp->NextSame = nextsame;
        jval.SetValue(GetColumnValue(g, jvrp->GetJson(), n + 1));
        jvp = &jval;
      } else
        jvp = jvrp;
  
      if (!nv++) {
        SetJsonValue(g, vp, jvp, n);
        continue;
      } else
        SetJsonValue(g, MulVal, jvp, n);
  
      if (!MulVal->IsZero()) {
        switch (op) {
          case OP_CNC:
            if (Nodes[n].CncVal) {
              val[0] = Nodes[n].CncVal;
              err = vp->Compute(g, val, 1, op);
              } // endif CncVal
  
            val[0] = MulVal;
            err = vp->Compute(g, val, 1, op);
            break;
//        case OP_NUM:
          case OP_SEP:
            val[0] = Nodes[n].Valp;
            val[1] = MulVal;
            err = vp->Compute(g, val, 2, OP_ADD);
            break;
          default:
            val[0] = Nodes[n].Valp;
            val[1] = MulVal;
            err = vp->Compute(g, val, 2, op);
          } // endswitch Op

        if (err)
          vp->Reset();
    
        } // endif Zero

      } while (Tjp->NextSame > nextsame);

    } // endfor i

  if (op == OP_SEP) {
    // Calculate average
    MulVal->SetValue(nv);
    val[0] = vp;
    val[1] = MulVal;

    if (vp->Compute(g, val, 2, OP_DIV))
      vp->Reset();

    } // endif Op

  Tjp->NextSame = nextsame;
  return vp;
  } // end of CalculateArray

/***********************************************************************/
/*  GetRow: Get the object containing this column.                     */
/***********************************************************************/
PJSON JSONCOL::GetRow(PGLOBAL g)
  {
  PJVAL val;
  PJAR  arp;
  PJSON nwr, row = Tjp->Row;

  for (int i = 0; i < Nod-1 && row; i++) {
    if (Nodes[i+1].Op == OP_XX)
      break;
    else switch (row->GetType()) {
      case TYPE_JOB:
        if (!Nodes[i].Key)
          // Expected Array was not there
          continue;

        val = ((PJOB)row)->GetValue(Nodes[i].Key);
        break;
      case TYPE_JAR:
        if (!Nodes[i].Key) {
          if (Nodes[i].Op != OP_NULL) {
            arp = (PJAR)row;

            if (Nodes[i].Rank)
              val = arp->GetValue(Nodes[i].Rank - 1);
            else
              val = arp->GetValue(Nodes[i].Nx);

          } else
            val = NULL;

        } else {
          strcpy(g->Message, "Unexpected array");
          val = NULL;          // Not an expected array
        } // endif Nodes

        break;
      case TYPE_JVAL:
        val = (PJVAL)row;
        break;
      default:
        sprintf(g->Message, "Invalid row JSON type %d", row->GetType());
        val = NULL;
      } // endswitch Type

    if (val) {
      row = val->GetJson();
    } else {
      // Construct missing objects
      for (i++; row && i < Nod; i++) {
        if (Nodes[i].Op == OP_XX)
          break;
        else if (!Nodes[i].Key)
          // Construct intermediate array
          nwr = new(g) JARRAY;
        else
          nwr = new(g) JOBJECT;

        if (row->GetType() == TYPE_JOB) {
          ((PJOB)row)->SetValue(g, new(g) JVALUE(nwr), Nodes[i-1].Key);
        } else if (row->GetType() == TYPE_JAR) {
          ((PJAR)row)->AddValue(g, new(g) JVALUE(nwr));
          ((PJAR)row)->InitArray(g);
        } else {
          strcpy(g->Message, "Wrong type when writing new row");
          nwr = NULL;
        } // endif's

        row = nwr;
        } // endfor i

      break;
    } // endelse

    } // endfor i

  return row;
  } // end of GetRow

/***********************************************************************/
/*  WriteColumn:                                                       */
/***********************************************************************/
void JSONCOL::WriteColumn(PGLOBAL g)
  {
  /*********************************************************************/
  /*  Check whether this node must be written.                         */
  /*********************************************************************/
  if (Value != To_Val)
    Value->SetValue_pval(To_Val, FALSE);    // Convert the updated value

  /*********************************************************************/
  /*  On INSERT Null values are represented by no node.                */
  /*********************************************************************/
  if (Value->IsNull() && Tjp->Mode == MODE_INSERT)
    return;

  char *s;
  PJOB  objp = NULL;
  PJAR  arp = NULL;
  PJVAL jvp = NULL;
  PJSON jsp, row = GetRow(g);
  JTYP  type = row->GetType();

  switch (row->GetType()) {
    case TYPE_JOB:  objp = (PJOB)row;  break;
    case TYPE_JAR:  arp  = (PJAR)row;  break;
    case TYPE_JVAL: jvp  = (PJVAL)row; break;
    default: row = NULL;     // ???????????????????????????
    } // endswitch Type

  if (row) switch (Buf_Type) {
    case TYPE_STRING:
      if (Nodes[Nod-1].Op == OP_XX) {
        s = Value->GetCharValue();

        if (!(jsp = ParseJson(g, s, (int)strlen(s), 0))) {
          strcpy(g->Message, s);
          longjmp(g->jumper[g->jump_level], 666);
          } // endif jsp

        if (arp) {
          if (Nod > 1 && Nodes[Nod-2].Rank)
            arp->SetValue(g, new(g) JVALUE(jsp), Nodes[Nod-2].Rank-1);
          else
            arp->AddValue(g, new(g) JVALUE(jsp));

          arp->InitArray(g);
        } else if (objp) {
          if (Nod > 1 && Nodes[Nod-2].Key)
            objp->SetValue(g, new(g) JVALUE(jsp), Nodes[Nod-2].Key);

        } else if (jvp)
          jvp->SetValue(jsp);

        break;
        } // endif Op

      // Passthru
    case TYPE_DATE:
    case TYPE_INT:
    case TYPE_DOUBLE:
      if (arp) {
        if (Nodes[Nod-1].Rank)
          arp->SetValue(g, new(g) JVALUE(g, Value), Nodes[Nod-1].Rank-1);
        else
          arp->AddValue(g, new(g) JVALUE(g, Value));

        arp->InitArray(g);
      } else if (objp) {
        if (Nodes[Nod-1].Key)
          objp->SetValue(g, new(g) JVALUE(g, Value), Nodes[Nod-1].Key);

      } else if (jvp)
        jvp->SetValue(Value);

      break;
    default:                  // ??????????
      sprintf(g->Message, "Invalid column type %d", Buf_Type);
    } // endswitch Type

  } // end of WriteColumn

/* -------------------------- Class TDBJSON -------------------------- */

/***********************************************************************/
/*  Implementation of the TDBJSON class.                               */
/***********************************************************************/
TDBJSON::TDBJSON(PJDEF tdp, PTXF txfp) : TDBJSN(tdp, txfp)
  {
  Top = NULL;
  Doc = NULL;
  Objname = tdp->Objname;
  Multiple = tdp->Multiple;
  Done = Changed = false;
  } // end of TDBJSON standard constructor

TDBJSON::TDBJSON(PJTDB tdbp) : TDBJSN(tdbp)
  {
  Top = tdbp->Top;
  Doc = tdbp->Doc;
  Objname = tdbp->Objname;
  Multiple = tdbp->Multiple;
  Done = tdbp->Done;
  Changed = tdbp->Changed;
  } // end of TDBJSON copy constructor

// Used for update
PTDB TDBJSON::CopyOne(PTABS t)
  {
  PTDB    tp;
  PJCOL   cp1, cp2;
  PGLOBAL g = t->G;

  tp = new(g) TDBJSON(this);

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

  return tp;
  } // end of CopyOne

/***********************************************************************/
/*  Make the document tree from a file.                                */
/***********************************************************************/
int TDBJSON::MakeNewDoc(PGLOBAL g)
  {
  // Create a void table that will be populated
  Doc = new(g) JARRAY;

  if (Objname) {
    // Parse and allocate Objname item(s)
    char *p;
    char *objpath = (char*)PlugSubAlloc(g, NULL, strlen(Objname)+1);
    int   i;
    PJOB  objp;
    PJAR  arp;
    PJVAL val = NULL;

    strcpy(objpath, Objname);
    Top = NULL;

    for (; objpath; objpath = p) {
      if ((p = strchr(objpath, ':')))
        *p++ = 0;

      if (*objpath != '[') {
        objp = new(g) JOBJECT;

        if (!Top)
          Top = objp;

        if (val)
          val->SetValue(objp);

        val = new(g) JVALUE;
        objp->SetValue(g, val, objpath);
      } else if (objpath[strlen(objpath)-1] == ']') {
        arp = new(g) JARRAY;

        if (!Top)
          Top = arp;

        if (val)
          val->SetValue(arp);

        val = new(g) JVALUE;
        i = atoi(objpath+1) - 1;
        arp->SetValue(g, val, i);
        arp->InitArray(g);
      } else {
        sprintf(g->Message, "Invalid Table path %s", Objname);
        return RC_FX;
      } // endif objpath

      } // endfor p

    val->SetValue(Doc);
  } else
    Top = Doc;

  return RC_OK;
  } // end of MakeNewDoc

/***********************************************************************/
/*  Make the document tree from a file.                                */
/***********************************************************************/
int TDBJSON::MakeDocument(PGLOBAL g)
  {
  char    filename[_MAX_PATH];
  DWORD   drc;

  if (Done)
    return RC_OK;
  else
    Done = true;

  // Now open the JSON file
  PlugSetPath(filename, Txfp->To_File, GetPath());

	/*********************************************************************/
	/*  Get top of the parsed tree and the inside table document.        */
	/*********************************************************************/
  Top = MakeJsonTree(g, filename, Objname, Pretty, Doc, drc);

  if (!Top) {
    if (drc != ENOENT || Mode != MODE_INSERT)
      return RC_FX;

    return MakeNewDoc(g);
    } // endif !Top

  return RC_OK;
  } // end of MakeDocument

#if 0
/***********************************************************************/
/*  Make the document tree from a file.                                */
/***********************************************************************/
int TDBJSON::MakeDocument(PGLOBAL g)
  {
  char   *p, *memory, *objpath, *key, filename[_MAX_PATH];
  int     len, i = 0;
  HANDLE  hFile;
	MEMMAP  mm;
  PJSON   jsp;
  PJOB    objp = NULL;
  PJAR    arp = NULL;
  PJVAL   val = NULL;

  if (Done)
    return RC_OK;
  else
    Done = true;

  // Now open the JSON file
  PlugSetPath(filename, Txfp->To_File, GetPath());

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

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

    if (drc != ENOENT || Mode != MODE_INSERT) {
      if (!(*g->Message))
        sprintf(g->Message, MSG(OPEN_MODE_ERROR),
                "map", (int)drc, filename);
                
  	  return RC_FX;
    } else
      return MakeNewDoc(g);

  	} // endif hFile

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

	if (!len) {      				// Empty file
    CloseFileHandle(hFile);
    CloseMemMap(memory, len);

    if (Mode == MODE_INSERT)
  		return MakeNewDoc(g);

		} // endif len

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

  CloseFileHandle(hFile);                    // Not used anymore
  hFile = INVALID_HANDLE_VALUE;              // For Fblock

  /*********************************************************************/
  /*  Parse the json file and allocate its tree structure.             */
  /*********************************************************************/
  g->Message[0] = 0;
  jsp = Top = ParseJson(g, memory, len, Pretty);
  CloseMemMap(memory, len);

  if (!jsp && g->Message[0])
    return RC_FX;

  objpath = PlugDup(g, Objname);

  /*********************************************************************/
  /*  Find the table in the tree structure.                            */
  /*********************************************************************/
  for (; jsp && objpath; objpath = p) {
    if ((p = strchr(objpath, ':')))
      *p++ = 0;

    if (*objpath != '[') {         // objpass is a key
      if (jsp->GetType() != TYPE_JOB) {
        strcpy(g->Message, "Table path does no match json file");
        return RC_FX;
        } // endif Type

      key = objpath;
      objp = jsp->GetObject();
      arp = NULL;
      val = objp->GetValue(key);

      if (!val || !(jsp = val->GetJson())) {
        sprintf(g->Message, "Cannot find object key %s", key);
        return RC_FX;
        } // endif val

    } else if (objpath[strlen(objpath)-1] == ']') {
      if (jsp->GetType() != TYPE_JAR) {
        strcpy(g->Message, "Table path does no match json file");
        return RC_FX;
        } // endif Type

      arp = jsp->GetArray();
      objp = NULL;
      i = atoi(objpath+1) - 1;
      val = arp->GetValue(i);

      if (!val) {
        sprintf(g->Message, "Cannot find array value %d", i);
        return RC_FX;
        } // endif val

    } else {
      sprintf(g->Message, "Invalid Table path %s", Objname);
      return RC_FX;
    } // endif objpath

    jsp = val->GetJson();
    } // endfor objpath

  if (jsp && jsp->GetType() == TYPE_JAR)
    Doc = jsp->GetArray();
  else { 
    // The table is void or is just one object or one value
    Doc = new(g) JARRAY;

    if (val) {
      Doc->AddValue(g, val);
      Doc->InitArray(g);
    } else if (jsp) {
      Doc->AddValue(g, new(g) JVALUE(jsp));
      Doc->InitArray(g);
    } // endif val

    if (objp)
      objp->SetValue(g, new(g) JVALUE(Doc), key);
    else if (arp)
      arp->SetValue(g, new(g) JVALUE(Doc), i);
    else
      Top = Doc;

  } // endif jsp

  return RC_OK;
  } // end of MakeDocument
#endif // 0

/***********************************************************************/
/*  JSON Cardinality: returns table size in number of rows.            */
/***********************************************************************/
int TDBJSON::Cardinality(PGLOBAL g)
  {
  if (!g)
    return (Xcol || Multiple) ? 0 : 1;
  else if (Cardinal < 0)
    if (!Multiple) {
      if (MakeDocument(g) == RC_OK)
        Cardinal = Doc->size();

    } else
      return 10;

  return Cardinal;
  } // end of Cardinality

/***********************************************************************/
/*  JSON GetMaxSize: returns table size estimate in number of rows.    */
/***********************************************************************/
int TDBJSON::GetMaxSize(PGLOBAL g)
  {
  if (MaxSize < 0)
    MaxSize = Cardinality(g) * ((Xcol) ? Limit : 1);

  return MaxSize;
  } // end of GetMaxSize

/***********************************************************************/
/*  ResetSize: call by TDBMUL when calculating size estimate.          */
/***********************************************************************/
void TDBJSON::ResetSize(void)
  {
  MaxSize = Cardinal = -1;
  Fpos = -1;
  N = 0;
  Done = false;
  } // end of ResetSize

/***********************************************************************/
/*  TDBJSON is not indexable.                                          */
/***********************************************************************/
int TDBJSON::MakeIndex(PGLOBAL g, PIXDEF pxdf, bool add)
  {
  if (pxdf) {
    strcpy(g->Message, "JSON not indexable when pretty = 2");
    return RC_FX;
  } else
    return RC_OK;

  } // end of MakeIndex 

/***********************************************************************/
/*  Return the position in the table.                                  */
/***********************************************************************/
int TDBJSON::GetRecpos(void)
  {
#if 0
  union {
    uint Rpos;
    BYTE Spos[4];
    };

  Rpos = htonl(Fpos);
  Spos[0] = (BYTE)NextSame;
  return Rpos;
#endif // 0
  return Fpos;
  } // end of GetRecpos

/***********************************************************************/
/*  Set the position in the table.                                  */
/***********************************************************************/
bool TDBJSON::SetRecpos(PGLOBAL g, int recpos)
  {
#if 0
  union {
    uint Rpos;
    BYTE Spos[4];
    };

  Rpos = recpos;
  NextSame = Spos[0];
  Spos[0] = 0;
  Fpos = (signed)ntohl(Rpos);

//if (Fpos != (signed)ntohl(Rpos)) {
//  Fpos = ntohl(Rpos);
//  same = false;
//} else
//  same = true;
#endif // 0

  Fpos = recpos - 1;
  return false;
  } // end of SetRecpos

/***********************************************************************/
/*  JSON Access Method opening routine.                                */
/***********************************************************************/
bool TDBJSON::OpenDB(PGLOBAL g)
  {
  if (Use == USE_OPEN) {
    /*******************************************************************/
    /*  Table already open replace it at its beginning.                */
    /*******************************************************************/
    Fpos= -1;
    NextSame = false;
    SameRow = 0;
    return false;
    } // endif use

  /*********************************************************************/
  /*  OpenDB: initialize the JSON file processing.                     */
  /*********************************************************************/
  if (MakeDocument(g) != RC_OK)
    return true;

  if (Mode == MODE_INSERT)
    switch (Jmode) {
      case MODE_OBJECT: Row = new(g) JOBJECT; break;
      case MODE_ARRAY:  Row = new(g) JARRAY;  break;
      case MODE_VALUE:  Row = new(g) JVALUE;  break;
      default:
        sprintf(g->Message, "Invalid Jmode %d", Jmode);
        return true;
      } // endswitch Jmode

  Use = USE_OPEN;
  return false;
  } // end of OpenDB

/***********************************************************************/
/*  ReadDB: Data Base read routine for JSON access method.             */
/***********************************************************************/
int TDBJSON::ReadDB(PGLOBAL g)
  {
  int   rc;

  N++;

  if (NextSame) {
    SameRow = NextSame;
    NextSame = false;
    rc = RC_OK;
  } else if (++Fpos < (signed)Doc->size()) {
    Row = Doc->GetValue(Fpos);

    if (Row->GetType() == TYPE_JVAL)
      Row = ((PJVAL)Row)->GetJson();

    SameRow = 0;
    rc = RC_OK;
  } else
    rc = RC_EF;

  return rc;
  } // end of ReadDB

/***********************************************************************/
/*  WriteDB: Data Base write routine for JSON access method.           */
/***********************************************************************/
int TDBJSON::WriteDB(PGLOBAL g)
  {
  if (Jmode == MODE_OBJECT) {
    PJVAL vp = new(g) JVALUE(Row);

    if (Mode == MODE_INSERT) {
      Doc->AddValue(g, vp);
      Row = new(g) JOBJECT;
    } else if (Doc->SetValue(g, vp, Fpos))
      return RC_FX;

  } else if (Jmode == MODE_ARRAY) {
    PJVAL vp = new(g) JVALUE(Row);

    if (Mode == MODE_INSERT) {
      Doc->AddValue(g, vp);
      Row = new(g) JARRAY;
    } else if (Doc->SetValue(g, vp, Fpos))
      return RC_FX;

  } else { // if (Jmode == MODE_VALUE)
    if (Mode == MODE_INSERT) {
      Doc->AddValue(g, (PJVAL)Row);
      Row = new(g) JVALUE;
    } else if (Doc->SetValue(g, (PJVAL)Row, Fpos))
      return RC_FX;

  } // endif Jmode

  Changed = true;
  return RC_OK;
  } // end of WriteDB

/***********************************************************************/
/*  Data Base delete line routine for JSON access method.              */
/***********************************************************************/
int TDBJSON::DeleteDB(PGLOBAL g, int irc)
  {
  if (irc == RC_OK) {
    // Deleted current row
    if (Doc->DeleteValue(Fpos)) {
      sprintf(g->Message, "Value %d does not exist", Fpos + 1);
      return RC_FX;
      } // endif Delete

    Changed = true;
  } else if (irc == RC_FX)
    // Delete all
    for (int i = 0; i < Doc->size(); i++) {
      Doc->DeleteValue(i);
      Changed = true;
      } // endfor i

  return RC_OK;
  } // end of DeleteDB

/***********************************************************************/
/*  Data Base close routine for JSON access methods.                   */
/***********************************************************************/
void TDBJSON::CloseDB(PGLOBAL g)
  {
  if (!Changed)
    return;

  // Save the modified document
  char  filename[_MAX_PATH];
  PSZ   msg;
  FILE *fop;

  Doc->InitArray(g);

  // We used the file name relative to recorded datapath
  PlugSetPath(filename, ((PJDEF)To_Def)->Fn, GetPath());

  // Serialize the modified table
  if (!(fop = fopen(filename, "wb"))) {
    sprintf(g->Message, MSG(OPEN_MODE_ERROR),
            "w", (int)errno, filename);
    strcat(strcat(g->Message, ": "), strerror(errno));
  } else if ((msg = Serialize(g, Top, fop, Pretty)))
    puts(msg);

  } // end of CloseDB

/* ---------------------------TDBJCL class --------------------------- */

/***********************************************************************/
/*  TDBJCL class constructor.                                          */
/***********************************************************************/
TDBJCL::TDBJCL(PJDEF tdp) : TDBCAT(tdp)
  {
  Fn  = tdp->GetFn();
  Objn = tdp->Objname;
  Pretty = tdp->Pretty;
  Lrecl = tdp->Lrecl;
  lvl = tdp->Level;
  } // end of TDBJCL constructor

/***********************************************************************/
/*  GetResult: Get the list the JSON file columns.                     */
/***********************************************************************/
PQRYRES TDBJCL::GetResult(PGLOBAL g)
  {
  return JSONColumns(g, ((PTABDEF)To_Def)->GetPath(), Fn, Objn, 
                         Pretty, Lrecl, lvl, false);
  } // end of GetResult

/* --------------------------- End of json --------------------------- */