diff options
Diffstat (limited to 'storage/connect/tabjson.cpp')
-rw-r--r-- | storage/connect/tabjson.cpp | 2706 |
1 files changed, 2706 insertions, 0 deletions
diff --git a/storage/connect/tabjson.cpp b/storage/connect/tabjson.cpp new file mode 100644 index 00000000..27bdfee5 --- /dev/null +++ b/storage/connect/tabjson.cpp @@ -0,0 +1,2706 @@ +/************* tabjson C++ Program Source Code File (.CPP) *************/ +/* PROGRAM NAME: tabjson Version 1.9 */ +/* (C) Copyright to the author Olivier BERTRAND 2014 - 2021 */ +/* This program are the JSON class DB execution routines. */ +/***********************************************************************/ +#undef BSON_SUPPORT + +/***********************************************************************/ +/* Include relevant sections of the MariaDB header file. */ +/***********************************************************************/ +#include <my_global.h> +#include <mysqld.h> +#include <sql_error.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 "maputil.h" +#include "filamtxt.h" +#include "tabdos.h" +//#include "resource.h" // for IDS_COLUMNS +#include "tabjson.h" +#include "filamap.h" +#if defined(GZ_SUPPORT) +#include "filamgz.h" +#endif // GZ_SUPPORT +#if defined(ZIP_SUPPORT) +#include "filamzip.h" +#endif // ZIP_SUPPORT +#if defined(JAVA_SUPPORT) +#include "jmgfam.h" +#endif // JAVA_SUPPORT +#if defined(CMGO_SUPPORT) +#include "cmgfam.h" +#endif // CMGO_SUPPORT +#include "tabmul.h" +#include "checklvl.h" +#include "resource.h" +#include "mycat.h" // for FNC_COL + +/***********************************************************************/ +/* 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 functions. */ +/***********************************************************************/ +USETEMP UseTemp(void); +bool JsonAllPath(void); +int GetDefaultDepth(void); +char *GetJsonNull(void); +bool Stringified(PCSZ, char*); + +/***********************************************************************/ +/* JSONColumns: construct the result blocks containing the description */ +/* of all the columns of a table contained inside a JSON file. */ +/***********************************************************************/ +PQRYRES JSONColumns(PGLOBAL g, PCSZ db, PCSZ dsn, PTOS topt, 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}; + int i, n = 0; + int ncol = sizeof(buftyp) / sizeof(int); + PJCL jcp; + JSONDISC *pjdc = NULL; + PQRYRES qrp; + PCOLRES crp; + + if (info) { + length[0] = 128; + length[7] = 256; + goto skipit; + } // endif info + + if (GetIntegerTableOption(g, topt, "Multiple", 0)) { + safe_strcpy(g->Message, sizeof(g->Message), "Cannot find column definition for multiple table"); + return NULL; + } // endif Multiple + + pjdc = new(g) JSONDISC(g, length); + + if (!(n = pjdc->GetColumns(g, db, dsn, topt))) + return NULL; + + skipit: + if (trace(1)) + htrc("JSONColumns: 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 = PlugDup(g, "Nullable"); + crp->Next->Name = PlugDup(g, "Jpath"); + + if (info || !qrp) + return qrp; + + qrp->Nblin = n; + + /*********************************************************************/ + /* Now get the results into blocks. */ + /*********************************************************************/ + for (i = 0, jcp = pjdc->fjcp; jcp; i++, jcp = jcp->Next) { + if (jcp->Type == TYPE_UNKNOWN) + jcp->Type = TYPE_STRG; // Void column + + 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; + } // end of JSONColumns + +/* -------------------------- Class JSONDISC ------------------------- */ + +/***********************************************************************/ +/* Class used to get the columns of a JSON table. */ +/***********************************************************************/ +JSONDISC::JSONDISC(PGLOBAL g, uint *lg) +{ + length = lg; + jcp = fjcp = pjcp = NULL; + tdp = NULL; + tjnp = NULL; + jpp = NULL; + tjsp = NULL; + jsp = NULL; + row = NULL; + sep = NULL; + strfy = NULL; + i = n = bf = ncol = lvl = sz = limit = 0; + all = false; +} // end of JSONDISC constructor + +int JSONDISC::GetColumns(PGLOBAL g, PCSZ db, PCSZ dsn, PTOS topt) +{ + char filename[_MAX_PATH]; + size_t reclg = 0; + bool mgo = (GetTypeID(topt->type) == TAB_MONGO); + PGLOBAL G = NULL; + + lvl = GetIntegerTableOption(g, topt, "Level", GetDefaultDepth()); + lvl = GetIntegerTableOption(g, topt, "Depth", lvl); + sep = GetStringTableOption(g, topt, "Separator", "."); + strfy = GetStringTableOption(g, topt, "Stringify", NULL); + sz = GetIntegerTableOption(g, topt, "Jsize", 1024); + limit = GetIntegerTableOption(g, topt, "Limit", 50); + + /*********************************************************************/ + /* Open the input file. */ + /*********************************************************************/ + tdp = new(g) JSONDEF; +#if defined(ZIP_SUPPORT) + tdp->Entry = GetStringTableOption(g, topt, "Entry", NULL); + tdp->Zipped = GetBooleanTableOption(g, topt, "Zipped", false); +#endif // ZIP_SUPPORT + tdp->Fn = GetStringTableOption(g, topt, "Filename", NULL); + + if (!tdp->Fn && topt->http) { + tdp->Fn = GetStringTableOption(g, topt, "Subtype", NULL); + topt->subtype = NULL; + } // endif fn + + if (!(tdp->Database = SetPath(g, db))) + return 0; + + if ((tdp->Objname = GetStringTableOption(g, topt, "Object", NULL))) { + if (*tdp->Objname == '$') tdp->Objname++; + if (*tdp->Objname == '.') tdp->Objname++; + } // endif Objname + + tdp->Base = GetIntegerTableOption(g, topt, "Base", 0) ? 1 : 0; + tdp->Pretty = GetIntegerTableOption(g, topt, "Pretty", 2); + tdp->Xcol = GetStringTableOption(g, topt, "Expand", NULL); + tdp->Accept = GetBooleanTableOption(g, topt, "Accept", false); + tdp->Uri = (dsn && *dsn ? dsn : NULL); + + if (!tdp->Fn && !tdp->Uri) { + safe_strcpy(g->Message, sizeof(g->Message), MSG(MISSING_FNAME)); + return 0; + } else + topt->subtype = NULL; + + if (tdp->Fn) { + // We used the file name relative to recorded datapath + PlugSetPath(filename, tdp->Fn, tdp->GetPath()); + tdp->Fn = PlugDup(g, filename); + } // endif Fn + + if (trace(1)) + htrc("File %s objname=%s pretty=%d lvl=%d\n", + tdp->Fn, tdp->Objname, tdp->Pretty, lvl); + + if (tdp->Uri) { +#if defined(JAVA_SUPPORT) || defined(CMGO_SUPPORT) + tdp->Collname = GetStringTableOption(g, topt, "Tabname", NULL); + tdp->Schema = GetStringTableOption(g, topt, "Dbname", "test"); + tdp->Options = (PSZ)GetStringTableOption(g, topt, "Colist", "all"); + tdp->Pipe = GetBooleanTableOption(g, topt, "Pipeline", false); + tdp->Driver = (PSZ)GetStringTableOption(g, topt, "Driver", NULL); + tdp->Version = GetIntegerTableOption(g, topt, "Version", 3); + tdp->Wrapname = (PSZ)GetStringTableOption(g, topt, "Wrapper", + (tdp->Version == 2) ? "Mongo2Interface" : "Mongo3Interface"); + tdp->Pretty = 0; +#else // !MONGO_SUPPORT + snprintf(g->Message, sizeof(g->Message), MSG(NO_FEAT_SUPPORT), "MONGO"); + return 0; +#endif // !MONGO_SUPPORT + } // endif Uri + + if (tdp->Pretty == 2) { + if (tdp->Zipped) { +#if defined(ZIP_SUPPORT) + tjsp = new(g) TDBJSON(tdp, new(g) UNZFAM(tdp)); +#else // !ZIP_SUPPORT + snprintf(g->Message, sizeof(g->Message), MSG(NO_FEAT_SUPPORT), "ZIP"); + return 0; +#endif // !ZIP_SUPPORT + } else + tjsp = new(g) TDBJSON(tdp, new(g) MAPFAM(tdp)); + + if (tjsp->MakeDocument(g)) + return 0; + + jsp = (tjsp->GetDoc()) ? tjsp->GetDoc()->GetArrayValue(0) : NULL; + } else { + if (!(tdp->Lrecl = GetIntegerTableOption(g, topt, "Lrecl", 0))) + { + if (!mgo && !tdp->Uri) { + snprintf(g->Message, sizeof(g->Message), "LRECL must be specified for pretty=%d", tdp->Pretty); + return 0; + } else + tdp->Lrecl = 8192; // Should be enough + } + + tdp->Ending = GetIntegerTableOption(g, topt, "Ending", CRLF); + + if (tdp->Zipped) { +#if defined(ZIP_SUPPORT) + tjnp = new(g)TDBJSN(tdp, new(g) UNZFAM(tdp)); +#else // !ZIP_SUPPORT + snprintf(g->Message, sizeof(g->Message), MSG(NO_FEAT_SUPPORT), "ZIP"); + return NULL; +#endif // !ZIP_SUPPORT + } else if (tdp->Uri) { + if (tdp->Driver && toupper(*tdp->Driver) == 'C') { +#if defined(CMGO_SUPPORT) + tjnp = new(g) TDBJSN(tdp, new(g) CMGFAM(tdp)); +#else + snprintf(g->Message, sizeof(g->Message), "Mongo %s Driver not available", "C"); + return 0; +#endif + } else if (tdp->Driver && toupper(*tdp->Driver) == 'J') { +#if defined(JAVA_SUPPORT) + tjnp = new(g) TDBJSN(tdp, new(g) JMGFAM(tdp)); +#else + snprintf(g->Message, sizeof(g->Message), "Mongo %s Driver not available", "Java"); + return 0; +#endif + } else { // Driver not specified +#if defined(CMGO_SUPPORT) + tjnp = new(g) TDBJSN(tdp, new(g) CMGFAM(tdp)); +#elif defined(JAVA_SUPPORT) + tjnp = new(g) TDBJSN(tdp, new(g) JMGFAM(tdp)); +#else + snprintf(g->Message, sizeof(g->Message), MSG(NO_FEAT_SUPPORT), "MONGO"); + return 0; +#endif + } // endif Driver + + } else if (tdp->Pretty >= 0) + tjnp = new(g) TDBJSN(tdp, new(g) DOSFAM(tdp)); + else + tjnp = new(g) TDBJSN(tdp, new(g) BINFAM(tdp)); + + tjnp->SetMode(MODE_READ); + + // Allocate the parse work memory + G = PlugInit(NULL, (size_t)tdp->Lrecl * (tdp->Pretty >= 0 ? 10 : 2)); + tjnp->SetG(G); + + if (tjnp->OpenDB(g)) + return 0; + + switch (tjnp->ReadDB(g)) { + case RC_EF: + safe_strcpy(g->Message, sizeof(g->Message), "Void json table"); + case RC_FX: + goto err; + default: + if (tdp->Pretty != 2) + reclg = strlen(tjnp->To_Line); + + jsp = tjnp->Row; + } // endswitch ReadDB + + } // endif pretty + + if (!(row = (jsp) ? jsp->GetObject() : NULL)) { + safe_strcpy(g->Message, sizeof(g->Message), "Can only retrieve columns from object rows"); + goto err; + } // endif row + + all = GetBooleanTableOption(g, topt, "Fullarray", false); + jcol.Name = jcol.Fmt = NULL; + jcol.Next = NULL; + jcol.Found = true; + colname[0] = 0; + + if (!tdp->Uri) { + fmt[0] = '$'; + fmt[1] = '.'; + bf = 2; + } // endif Uri + + /*********************************************************************/ + /* Analyse the JSON tree and define columns. */ + /*********************************************************************/ + for (i = 1; ; i++) { + for (jpp = row->GetFirst(); jpp; jpp = jpp->Next) { + strncpy(colname, jpp->Key, 64); + fmt[bf] = 0; + + if (Find(g, jpp->Val, colname, MY_MIN(lvl, 0))) + goto err; + + } // endfor jpp + + // Missing column can be null + for (jcp = fjcp; jcp; jcp = jcp->Next) { + jcp->Cbn |= !jcp->Found; + jcp->Found = false; + } // endfor jcp + + if (tdp->Pretty != 2) { + // Read next record + switch (tjnp->ReadDB(g)) { + case RC_EF: + jsp = NULL; + break; + case RC_FX: + goto err; + default: + if (tdp->Pretty != 2 && reclg < strlen(tjnp->To_Line)) + reclg = strlen(tjnp->To_Line); + + jsp = tjnp->Row; + } // endswitch ReadDB + + } else + jsp = tjsp->GetDoc()->GetArrayValue(i); + + if (!(row = (jsp) ? jsp->GetObject() : NULL)) + break; + + } // endfor i + + if (tdp->Pretty != 2) { + if (!topt->lrecl) + topt->lrecl = reclg + 10; + + tjnp->CloseDB(g); + } // endif Pretty + + return n; + +err: + if (tdp->Pretty != 2) + tjnp->CloseDB(g); + + return 0; +} // end of GetColumns + +bool JSONDISC::Find(PGLOBAL g, PJVAL jvp, PCSZ key, int j) +{ + char *p, *pc = colname + strlen(colname); + int ars; + size_t n; + PJOB job; + PJAR jar; + + if (jvp && jvp->DataType != TYPE_JSON) { + if (JsonAllPath() && !fmt[bf]) + safe_strcat(fmt, sizeof(fmt), colname); + + jcol.Type = jvp->DataType; + + switch (jvp->DataType) { + case TYPE_STRG: + case TYPE_DTM: + jcol.Len = (int)strlen(jvp->Strp); + break; + case TYPE_INTG: + case TYPE_BINT: + jcol.Len = (int)strlen(jvp->GetString(g)); + break; + case TYPE_DBL: + jcol.Len = (int)strlen(jvp->GetString(g)); + jcol.Scale = jvp->Nd; + break; + case TYPE_BOOL: + jcol.Len = 1; + break; + default: + jcol.Len = 0; + break; + } // endswitch Type + + jcol.Scale = jvp->Nd; + jcol.Cbn = jvp->DataType == TYPE_NULL; + } else if (!jvp || jvp->IsNull()) { + jcol.Type = TYPE_UNKNOWN; + jcol.Len = jcol.Scale = 0; + jcol.Cbn = true; + } else if (j < lvl && !Stringified(strfy, colname)) { + if (!fmt[bf]) + safe_strcat(fmt, sizeof(fmt), colname); + + p = fmt + strlen(fmt); + jsp = jvp->GetJson(); + + switch (jsp->GetType()) { + case TYPE_JOB: + job = (PJOB)jsp; + + for (PJPR jrp = job->GetFirst(); jrp; jrp = jrp->Next) { + PCSZ k = jrp->Key; + + if (*k != '$') { + n = sizeof(fmt) - strlen(fmt) -1; + strncat(strncat(fmt, sep, n), k, n - strlen(sep)); + n = sizeof(colname) - strlen(colname) - 1; + strncat(strncat(colname, "_", n), k, n - 1); + } // endif Key + + if (Find(g, jrp->Val, k, j + 1)) + return true; + + *p = *pc = 0; + } // endfor jrp + + return false; + case TYPE_JAR: + jar = (PJAR)jsp; + + if (all || (tdp->Xcol && !stricmp(tdp->Xcol, key))) + ars = MY_MIN(jar->GetSize(false), limit); + else + ars = MY_MIN(jar->GetSize(false), 1); + + for (int k = 0; k < ars; k++) { + n = sizeof(fmt) - (strlen(fmt) + 1); + + if (!tdp->Xcol || stricmp(tdp->Xcol, key)) { + sprintf(buf, "%d", k); + + if (tdp->Uri) { + strncat(strncat(fmt, sep, n), buf, n - strlen(sep)); + } else { + strncat(strncat(fmt, "[", n), buf, n - 1); + strncat(fmt, "]", n - (strlen(buf) + 1)); + } // endif uri + + if (all) { + n = sizeof(colname) - (strlen(colname) + 1); + strncat(strncat(colname, "_", n), buf, n - 1); + } // endif all + + } else + strncat(fmt, (tdp->Uri ? sep : "[*]"), n); + + if (Find(g, jar->GetArrayValue(k), "", j)) + return true; + + *p = *pc = 0; + } // endfor k + + return false; + default: + snprintf(g->Message, sizeof(g->Message), "Logical error after %s", fmt); + return true; + } // endswitch Type + + } else if (lvl >= 0) { + if (Stringified(strfy, colname)) { + if (!fmt[bf]) + safe_strcat(fmt, sizeof(fmt), colname); + + safe_strcat(fmt, sizeof(fmt), ".*"); + } else if (JsonAllPath() && !fmt[bf]) + safe_strcat(fmt, sizeof(fmt), colname); + + jcol.Type = TYPE_STRG; + jcol.Len = sz; + jcol.Scale = 0; + jcol.Cbn = true; + } else + return false; + + AddColumn(g); + return false; +} // end of Find + +void JSONDISC::AddColumn(PGLOBAL g) +{ + bool b = fmt[bf] != 0; // True if formatted + + // 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) { + if (jcp->Type == TYPE_UNKNOWN || jcp->Type == TYPE_NULL) + jcp->Type = jcol.Type; +// else if (jcol.Type != TYPE_UNKNOWN && jcol.Type != TYPE_VOID) +// jcp->Type = TYPE_STRING; + else if (jcp->Type != TYPE_STRG) + switch (jcol.Type) { + case TYPE_STRG: + case TYPE_DBL: + jcp->Type = jcol.Type; + break; + case TYPE_BINT: + if (jcp->Type == TYPE_INTG || jcp->Type == TYPE_BOOL) + jcp->Type = jcol.Type; + + break; + case TYPE_INTG: + if (jcp->Type == TYPE_BOOL) + jcp->Type = jcol.Type; + + break; + default: + break; + } // endswith Type + + } // endif Type + + if (b && (!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 if (jcol.Type != TYPE_UNKNOWN || tdp->Accept) { + // 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 (b) { + 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 + + if (jcp) + pjcp = jcp; + +} // end of AddColumn + + +/* -------------------------- Class JSONDEF -------------------------- */ + +JSONDEF::JSONDEF(void) +{ + Jmode = MODE_OBJECT; + Objname = NULL; + Xcol = NULL; + Pretty = 2; + Limit = 1; + Base = 0; + Strict = false; + Sep = '.'; + Uri = NULL; + Collname = Options = Filter = NULL; + Pipe = false; + Driver = NULL; + Version = 0; + Wrapname = NULL; +} // end of JSONDEF constructor + +/***********************************************************************/ +/* DefineAM: define specific AM block values. */ +/***********************************************************************/ +bool JSONDEF::DefineAM(PGLOBAL g, LPCSTR am, int poff) +{ + Schema = GetStringCatInfo(g, "DBname", Schema); + Jmode = (JMODE)GetIntCatInfo("Jmode", MODE_OBJECT); + + if ((Objname = GetStringCatInfo(g, "Object", NULL))) { + if (*Objname == '$') Objname++; + if (*Objname == '.') Objname++; + } // endif Objname + + Xcol = GetStringCatInfo(g, "Expand", NULL); + Pretty = GetIntCatInfo("Pretty", 2); + Limit = GetIntCatInfo("Limit", 50); + Base = GetIntCatInfo("Base", 0) ? 1 : 0; + Sep = *GetStringCatInfo(g, "Separator", "."); + Accept = GetBoolCatInfo("Accept", false); + + // Don't use url as MONGO uri when called from REST + if (stricmp(am, "REST") && (Uri = GetStringCatInfo(g, "Connect", NULL))) { +#if defined(JAVA_SUPPORT) || defined(CMGO_SUPPORT) + Collname = GetStringCatInfo(g, "Name", + (Catfunc & (FNC_TABLE | FNC_COL)) ? NULL : Name); + Collname = GetStringCatInfo(g, "Tabname", Collname); + Options = GetStringCatInfo(g, "Colist", Xcol ? "all" : NULL); + Filter = GetStringCatInfo(g, "Filter", NULL); + Pipe = GetBoolCatInfo("Pipeline", false); + Driver = GetStringCatInfo(g, "Driver", NULL); + Version = GetIntCatInfo("Version", 3); + Pretty = 0; +#if defined(JAVA_SUPPORT) + if (Version == 2) + Wrapname = GetStringCatInfo(g, "Wrapper", "Mongo2Interface"); + else + Wrapname = GetStringCatInfo(g, "Wrapper", "Mongo3Interface"); +#endif // JAVA_SUPPORT +#else // !MONGO_SUPPORT + snprintf(g->Message, sizeof(g->Message), MSG(NO_FEAT_SUPPORT), "MONGO"); + return true; +#endif // !MONGO_SUPPORT + } // endif Uri + + return DOSDEF::DefineAM(g, (Uri ? "XMGO" : "DOS"), poff); +} // end of DefineAM + +/***********************************************************************/ +/* GetTable: makes a new Table Description Block. */ +/***********************************************************************/ +PTDB JSONDEF::GetTable(PGLOBAL g, MODE m) +{ + if (trace(1)) + htrc("JSON GetTable Pretty=%d Uri=%s\n", Pretty, SVP(Uri)); + + 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 <= 0 || (Pretty == 1 && (m == MODE_READ || m == MODE_UPDATE))) { + USETEMP tmp = UseTemp(); + bool map = Mapped && Pretty >= 0 && m != MODE_INSERT && + !(tmp != TMP_NO && m == MODE_UPDATE) && + !(tmp == TMP_FORCE && + (m == MODE_UPDATE || m == MODE_DELETE)); + + if (Uri) { + if (Driver && toupper(*Driver) == 'C') { +#if defined(CMGO_SUPPORT) + txfp = new(g) CMGFAM(this); +#else + snprintf(g->Message, sizeof(g->Message), "Mongo %s Driver not available", "C"); + return NULL; +#endif + } else if (Driver && toupper(*Driver) == 'J') { +#if defined(JAVA_SUPPORT) + txfp = new(g) JMGFAM(this); +#else + snprintf(g->Message, sizeof(g->Message), "Mongo %s Driver not available", "Java"); + return NULL; +#endif + } else { // Driver not specified +#if defined(CMGO_SUPPORT) + txfp = new(g) CMGFAM(this); +#elif defined(JAVA_SUPPORT) + txfp = new(g) JMGFAM(this); +#else // !MONGO_SUPPORT + snprintf(g->Message, sizeof(g->Message), MSG(NO_FEAT_SUPPORT), "MONGO"); + return NULL; +#endif // !MONGO_SUPPORT + } // endif Driver + + Pretty = 4; // Not a file + } else if (Zipped) { +#if defined(ZIP_SUPPORT) + if (m == MODE_READ || m == MODE_ANY || m == MODE_ALTER) { + txfp = new(g) UNZFAM(this); + } else if (m == 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 m +#else // !ZIP_SUPPORT + snprintf(g->Message, sizeof(g->Message), MSG(NO_FEAT_SUPPORT), "ZIP"); + return NULL; +#endif // !ZIP_SUPPORT + } else if (Compressed) { +#if defined(GZ_SUPPORT) + if (Compressed == 1) + txfp = new(g) GZFAM(this); + else + txfp = new(g) ZLBFAM(this); +#else // !GZ_SUPPORT + snprintf(g->Message, sizeof(g->Message), MSG(NO_FEAT_SUPPORT), "GZ"); + return NULL; +#endif // !GZ_SUPPORT + } else if (map) + txfp = new(g) MAPFAM(this); + else if (Pretty < 0) // BJsonfile + txfp = new(g) BINFAM(this); + else + txfp = new(g) DOSFAM(this); + + // Txfp must be set for TDBJSN + tdbp = new(g) TDBJSN(this, txfp); + + if (Lrecl) { + // Allocate the parse work memory +#if 0 + PGLOBAL G = (PGLOBAL)PlugSubAlloc(g, NULL, sizeof(GLOBAL)); + memset(G, 0, sizeof(GLOBAL)); + G->Sarea_Size = (size_t)Lrecl * 10; + G->Sarea = PlugSubAlloc(g, NULL, G->Sarea_Size); + PlugSubSet(G->Sarea, G->Sarea_Size); + G->jump_level = 0; + ((TDBJSN*)tdbp)->G = G; +#endif // 0 + ((TDBJSN*)tdbp)->G = PlugInit(NULL, (size_t)Lrecl * (Pretty >= 0 ? 12 : 4)); + } else { + safe_strcpy(g->Message, sizeof(g->Message), "LRECL is not defined"); + return NULL; + } // endif Lrecl + + } else { + if (Zipped) { +#if defined(ZIP_SUPPORT) + if (m == MODE_READ || m == MODE_ANY || m == MODE_ALTER) { + txfp = new(g) UNZFAM(this); + } else if (m == MODE_INSERT) { + safe_strcpy(g->Message, sizeof(g->Message), "INSERT supported only for zipped JSON when pretty=0"); + return NULL; + } else { + safe_strcpy(g->Message, sizeof(g->Message), "UPDATE/DELETE not supported for ZIP"); + return NULL; + } // endif's m +#else // !ZIP_SUPPORT + snprintf(g->Message, sizeof(g->Message), MSG(NO_FEAT_SUPPORT), "ZIP"); + return NULL; +#endif // !ZIP_SUPPORT + } else + txfp = new(g) MAPFAM(this); + + tdbp = new(g) TDBJSON(this, txfp); + ((TDBJSON*)tdbp)->G = g; + } // endif Pretty + + if (Multiple) + tdbp = new(g) TDBMUL(tdbp); + + return tdbp; +} // end of GetTable + +/* --------------------------- Class TDBJSN -------------------------- */ + +/***********************************************************************/ +/* Implementation of the TDBJSN class (Pretty < 2) */ +/***********************************************************************/ +TDBJSN::TDBJSN(PJDEF tdp, PTXF txfp) : TDBDOS(tdp, txfp) +{ + G = NULL; + Top = NULL; + Row = NULL; + Val = NULL; + Colp = NULL; + + if (tdp) { + Jmode = tdp->Jmode; + Objname = tdp->Objname; + Xcol = tdp->Xcol; + Limit = tdp->Limit; + Pretty = tdp->Pretty; + B = tdp->Base ? 1 : 0; + Sep = tdp->Sep; + Strict = tdp->Strict; + } else { + Jmode = MODE_OBJECT; + Objname = NULL; + Xcol = NULL; + Limit = 1; + Pretty = 0; + B = 0; + Sep = '.'; + Strict = false; + } // endif tdp + + Fpos = -1; + N = M = 0; + NextSame = 0; + SameRow = 0; + Xval = -1; + Comma = false; +} // end of TDBJSN standard constructor + +TDBJSN::TDBJSN(TDBJSN* tdbp) : TDBDOS(NULL, tdbp) +{ + G = NULL; + Top = tdbp->Top; + Row = tdbp->Row; + Val = tdbp->Val; + Colp = tdbp->Colp; + Jmode = tdbp->Jmode; + Objname = tdbp->Objname; + Xcol = tdbp->Xcol; + Fpos = tdbp->Fpos; + N = tdbp->N; + M = tdbp->M; + Limit = tdbp->Limit; + NextSame = tdbp->NextSame; + SameRow = tdbp->SameRow; + Xval = tdbp->Xval; + B = tdbp->B; + Sep = tdbp->Sep; + Pretty = tdbp->Pretty; + Strict = tdbp->Strict; + Comma = tdbp->Comma; +} // end of TDBJSN copy constructor + +// Used for update +PTDB TDBJSN::Clone(PTABS t) +{ + G = NULL; + 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 Clone + +/***********************************************************************/ +/* 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(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 + +#if 0 +/***********************************************************************/ +/* 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); + + } // endif Cardinal + + 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 +#endif // 0 + +/***********************************************************************/ +/* JSON EstimatedLength. Returns an estimated minimum line length. */ +/***********************************************************************/ +int TDBJSN::EstimatedLength(void) +{ + if (AvgLen <= 0) + return (Lrecl ? Lrecl : 1024) / 8; // TODO: make it better + else + return AvgLen; + +} // end of Estimated Length + +/***********************************************************************/ +/* Find the row in the tree structure. */ +/***********************************************************************/ +PJSON TDBJSN::FindRow(PGLOBAL g) +{ + char *p, *objpath = PlugDup(g, Objname); + char *sep = (char*)(Sep == ':' ? ":[" : ".["); + bool bp = false, b = false; + PJSON jsp = Row; + PJVAL val = NULL; + + for (; jsp && objpath; objpath = p, bp = b) { + if ((p = strpbrk(objpath + 1, sep))) { + b = (*p == '['); + *p++ = 0; + } // endif p + + if (!bp && *objpath != '[' && !IsNum(objpath)) { // objpass is a key + val = (jsp->GetType() == TYPE_JOB) ? + jsp->GetObject()->GetKeyValue(objpath) : NULL; + } else { + if (bp || *objpath == '[') { + if (objpath[strlen(objpath) - 1] != ']') { + snprintf(g->Message, sizeof(g->Message), "Invalid Table path %s", Objname); + return NULL; + } else if (!bp) + objpath++; + + } // endif [ + + val = (jsp->GetType() == TYPE_JAR) ? + jsp->GetArray()->GetArrayValue(atoi(objpath) - B) : NULL; + } // endif objpath + + jsp = (val) ? val->GetJson() : NULL; + } // endfor objpath + + if (jsp && jsp->GetType() != TYPE_JOB) { + if (jsp->GetType() == TYPE_JAR) { + jsp = jsp->GetArray()->GetArrayValue(B); + + if (jsp->GetType() != TYPE_JOB) + jsp = NULL; + + } else + jsp = NULL; + + } // endif Type + + return jsp; +} // end of FindRow + +/***********************************************************************/ +/* 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; + 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: + snprintf(g->Message, sizeof(g->Message), "Invalid Jmode %d", Jmode); + return true; + } // endswitch Jmode + + } // endif Use + + if (Pretty < 0) { + /*******************************************************************/ + /* Binary BJSON table. */ + /*******************************************************************/ + xtrc(1, "JSN OpenDB: tdbp=%p tdb=R%d use=%d mode=%d\n", + this, Tdb_No, Use, Mode); + + if (Use == USE_OPEN) { + /*******************************************************************/ + /* Table already open, just replace it at its beginning. */ + /*******************************************************************/ + if (!To_Kindex) { + Txfp->Rewind(); // see comment in Work.log + } else // Table is to be accessed through a sorted index table + To_Kindex->Reset(); + + return false; + } // endif use + + /*********************************************************************/ + /* Open according to logical input/output mode required. */ + /* Use conventionnal input/output functions. */ + /*********************************************************************/ + if (Txfp->OpenTableFile(g)) + return true; + + Use = USE_OPEN; // Do it now in case we are recursively called + + /*********************************************************************/ + /* Lrecl is Ok. */ + /*********************************************************************/ + MODE mode = Mode; + + // Buffer must be allocated in g->Sarea + Mode = MODE_ANY; + Txfp->AllocateBuffer(g); + Mode = mode; + + //To_Line = (char*)PlugSubAlloc(g, NULL, linelen); + //memset(To_Line, 0, linelen); + To_Line = Txfp->GetBuf(); + xtrc(1, "OpenJSN: R%hd mode=%d To_Line=%p\n", Tdb_No, Mode, To_Line); + return false; + } else if (TDBDOS::OpenDB(g)) + return true; + + if (Xcol) + To_Filter = NULL; // Imcompatible + + return false; +} // 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 (Pretty == 1) { + if (Mode == MODE_INSERT || Mode == MODE_DELETE) { + // Mode Insert and delete are no more handled here + DBUG_ASSERT(false); + } else if (len > 0) // !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; + M++; + return RC_OK; + } else if ((rc = TDBDOS::ReadDB(g)) == RC_OK) { + if (!IsRead() && ((rc = ReadBuffer(g)) != RC_OK)) + return rc; // Deferred reading failed + + if (Pretty >= 0) { + // Recover the memory used for parsing + PlugSubSet(G->Sarea, G->Sarea_Size); + + if ((Row = ParseJson(G, To_Line, strlen(To_Line), &Pretty, &Comma))) { + Row = FindRow(g); + SameRow = 0; + Fpos++; + M = 1; + rc = RC_OK; + } else if (Pretty != 1 || strcmp(To_Line, "]")) { + safe_strcpy(g->Message, sizeof(g->Message), G->Message); + rc = RC_FX; + } else + rc = RC_EF; + + } else { + // Here we get a movable Json binary tree + PJSON jsp; + SWAP* swp; + + jsp = (PJSON)To_Line; + swp = new(g) SWAP(G, jsp); + swp->SwapJson(jsp, false); // Restore pointers from offsets + Row = jsp; + Row = FindRow(g); + SameRow = 0; + Fpos++; + M = 1; + rc = RC_OK; + } // endif Pretty + + } // endif ReadDB + + return rc; +} // end of ReadDB + +/***********************************************************************/ +/* Make the top tree from the object path. */ +/***********************************************************************/ +bool TDBJSN::MakeTopTree(PGLOBAL g, PJSON jsp) +{ + if (Objname) { + if (!Val) { + // Parse and allocate Objname item(s) + char *p, *objpath = PlugDup(g, Objname); + char *sep = (char*)(Sep == ':' ? ":[" : ".["); + int i; + bool bp = false, b = false; + PJOB objp; + PJAR arp; + PJVAL val = NULL; + + Top = NULL; + + for (; objpath; objpath = p, bp = b) { + if ((p = strpbrk(objpath + 1, sep))) { + b = (*p == '['); + *p++ = 0; + } // endif p + + if (!bp && *objpath != '[' && !IsNum(objpath)) { + objp = new(g) JOBJECT; + + if (!Top) + Top = objp; + + if (val) + val->SetValue(objp); + + val = new(g) JVALUE; + objp->SetKeyValue(g, val, objpath); + } else { + if (bp || *objpath == '[') { + // Old style + if (objpath[strlen(objpath) - 1] != ']') { + snprintf(g->Message, sizeof(g->Message), "Invalid Table path %s", Objname); + return true; + } else if (!bp) + objpath++; + + } // endif bp + + arp = new(g) JARRAY; + + if (!Top) + Top = arp; + + if (val) + val->SetValue(arp); + + val = new(g) JVALUE; + i = atoi(objpath) - B; + arp->SetArrayValue(g, val, i); + arp->InitArray(g); + } // endif objpath + + } // endfor p + + Val = val; + } // endif Val + + Val->SetValue(jsp); + } else + Top = jsp; + + return false; +} // end of MakeTopTree + +/***********************************************************************/ +/* PrepareWriting: Prepare the line for WriteDB. */ +/***********************************************************************/ +bool TDBJSN::PrepareWriting(PGLOBAL g) +{ + PSZ s; + + if (MakeTopTree(g, Row)) + return true; + + if ((s = Serialize(G, Top, NULL, Pretty))) { + if (Comma) + strcat(s, ","); + + if ((signed)strlen(s) > Lrecl) { + safe_strcpy(To_Line, Lrecl, s); + snprintf(g->Message, sizeof(g->Message), "Line truncated (lrecl=%d)", Lrecl); + return PushWarning(g, this); + } else + strcpy(To_Line, s); + + return false; + } else + return true; + +} // end of PrepareWriting + +/***********************************************************************/ +/* WriteDB: Data Base write routine for JSON access method. */ +/***********************************************************************/ +int TDBJSN::WriteDB(PGLOBAL g) +{ + int rc = TDBDOS::WriteDB(g); + + PlugSubSet(G->Sarea, G->Sarea_Size); + Row->Clear(); + return rc; +} // end of WriteDB + +/***********************************************************************/ +/* Data Base close routine for JSON access method. */ +/***********************************************************************/ +void TDBJSN::CloseDB(PGLOBAL g) +{ + TDBDOS::CloseDB(g); + G = PlugExit(G); +} // end of CloseDB + + /* ---------------------------- 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); + G = Tjp->G; + Jpath = cdp->GetFmt(); + MulVal = NULL; + Nodes = NULL; + Nod = 0; + Sep = Tjp->Sep; + Xnod = -1; + Xpd = false; + Parsed = false; + Warned = false; + Sgfy = 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) +{ + G = col1->G; + Tjp = col1->Tjp; + Jpath = col1->Jpath; + MulVal = col1->MulVal; + Nodes = col1->Nodes; + Nod = col1->Nod; + Sep = col1->Sep; + Xnod = col1->Xnod; + Xpd = col1->Xpd; + Parsed = col1->Parsed; + Warned = col1->Warned; + Sgfy = col1->Sgfy; +} // 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; + G = Tjp->G; + 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) { + safe_strcpy(g->Message, sizeof(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; + bool dg = true, b = false; + PJNODE jnp = &Nodes[i]; + + //if (*p == '[') p++; // Old syntax .[ or :[ + n = (int)strlen(p); + + if (*p) { + if (p[n - 1] == ']') { + p[--n] = 0; + } else if (!IsNum(p)) { + // Wrong array specification + snprintf(g->Message, sizeof(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 + dg = IsNum(p); + + if (!n) { + // Default specifications + if (CheckExpand(g, i, nm, false)) + return true; + else if (jnp->Op != OP_EXP) { + if (b) { + // Return 1st value (B is the index base) + jnp->Rank = Tjp->B; + 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) { + // Return nth value + jnp->Rank = atoi(p) - Tjp->B; + jnp->Op = OP_EQ; + } else if (n == 1) { + // Set the Op value; + if (Sep == ':') + switch (*p) { + case '*': *p = 'x'; break; + case 'x': + case 'X': *p = '*'; break; // Expand this array + default: break; + } // endswitch p + + switch (*p) { + case '+': jnp->Op = OP_ADD; break; + case 'x': 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 '*': // 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: + snprintf(g->Message, sizeof(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 { + snprintf(g->Message, sizeof(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, *p1 = NULL, *p2 = NULL, *pbuf = NULL; + int i; + bool a; + + if (Parsed) + return false; // Already done + else if (InitValue(g)) + return true; + else if (!Jpath) + Jpath = Name; + + if (To_Tdb->GetOrig()) { + // This is an updated column, get nodes from origin + for (PJCOL colp = (PJCOL)Tjp->GetColumns(); colp; + colp = (PJCOL)colp->GetNext()) + if (!stricmp(Name, colp->GetName())) { + Nod = colp->Nod; + Nodes = colp->Nodes; + Xpd = colp->Xpd; + goto fin; + } // endif Name + + snprintf(g->Message, sizeof(g->Message), "Cannot parse updated column %s", Name); + return true; + } // endif To_Orig + + pbuf = PlugDup(g, Jpath); + if (*pbuf == '$') pbuf++; + if (*pbuf == Sep) pbuf++; + if (*pbuf == '[') p1 = pbuf++; + + // Estimate the required number of nodes + for (i = 0, p = pbuf; (p = NextChr(p, Sep)); 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; p && i < Nod; i++, p = (p2 ? p2 : NULL)) { + a = (p1 != NULL); + p1 = strchr(p, '['); + p2 = strchr(p, Sep); + + if (!p2) + p2 = p1; + else if (p1) { + if (p1 < p2) + p2 = p1; + else if (p1 == p2 + 1) + *p2++ = 0; // Old syntax .[ or :[ + else + p1 = NULL; + + } // endif p1 + + if (p2) + *p2++ = 0; + + // Jpath must be explicit + if (a || *p == 0 || *p == '[' || IsNum(p)) { + // Analyse intermediate array processing + if (SetArrayOptions(g, p, i, Nodes[i - 1].Key)) + return true; + else if (Xpd && Tjp->Mode == MODE_DELETE) { + safe_strcpy(g->Message, sizeof(g->Message), "Cannot delete expanded columns"); + return true; + } // endif Xpd + + } 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 + + Nod = i; + +fin: + MulVal = AllocateValue(g, Value); + Parsed = true; + return false; +} // end of ParseJpath + +/***********************************************************************/ +/* Get Jpath converted to Mongo path. */ +/***********************************************************************/ +PSZ JSONCOL::GetJpath(PGLOBAL g, bool proj) +{ + if (Jpath) { + char *p1, *p2, *mgopath; + int i = 0; + + if (strcmp(Jpath, "*")) { + p1 = Jpath; + if (*p1 == '$') p1++; + if (*p1 == '.') p1++; + mgopath = PlugDup(g, p1); + } else { + Sgfy = true; + return NULL; + } // endif + + for (p1 = p2 = mgopath; *p1; p1++) + { + if (i) { // Inside [] + if (isdigit(*p1)) { + if (!proj) + *p2++ = *p1; + + } else if (*p1 == ']' && i == 1) { + if (proj && p1[1] == '.') + p1++; + + i = 0; + } else if (*p1 == '.' && i == 2) { + if (!proj) + *p2++ = '.'; + + i = 0; + } else if (!proj) + return NULL; + + } else switch (*p1) { + case ':': + case '.': + if (isdigit(p1[1])) + i = 2; + + *p2++ = '.'; + break; + case '[': + if (*(p2 - 1) != '.') + *p2++ = '.'; + + i = 1; + break; + case '*': + if (*(p2 - 1) == '.' && !*(p1 + 1)) { + p2--; // Suppress last :* + Sgfy = true; + break; + } // endif p2 + /* falls through */ + default: + *p2++ = *p1; + break; + } // endswitch p1; + } + + if (*(p2 - 1) == '.') + p2--; + + *p2 = 0; + return mgopath; + } else + return NULL; + +} // end of GetJpath + +/***********************************************************************/ +/* MakeJson: Serialize the json item and set value to it. */ +/***********************************************************************/ +PVAL JSONCOL::MakeJson(PGLOBAL g, PJSON jsp, int n) +{ + if (Value->IsTypeNum()) { + safe_strcpy(g->Message, sizeof(g->Message), "Cannot make Json for a numeric column"); + + if (!Warned) { + PushWarning(g, Tjp); + Warned = true; + } // endif Warned + + Value->Reset(); + return Value; +#if 0 + } else if (Value->GetType() == TYPE_BIN) { + if ((unsigned)Value->GetClen() >= sizeof(BSON)) { + ulong len = Tjp->Lrecl ? Tjp->Lrecl : 500; + PBSON bsp = JbinAlloc(g, NULL, len, jsp); + + safe_strcat(bsp->Msg, sizeof(bsp->Msg), " column"); + ((BINVAL*)Value)->SetBinValue(bsp, sizeof(BSON)); + } else { + safe_strcpy(g->Message, sizeof(g->Message), "Column size too small"); + Value->SetValue_char(NULL, 0); + } // endif Clen +#endif // 0 + } else if (n < Nod - 1) { + if (jsp->GetType() == TYPE_JAR) { + int ars = jsp->GetSize(false); + PJNODE jnp = &Nodes[n]; + PJAR jvp = new(g) JARRAY; + + for (jnp->Rank = 0; jnp->Rank < ars; jnp->Rank++) + jvp->AddArrayValue(g, GetRowValue(g, jsp, n)); + + jnp->Rank = 0; + jvp->InitArray(g); + jsp = jvp; + } else if (jsp->Type == TYPE_JOB) { + PJOB jvp = new(g) JOBJECT; + + for (PJPR prp = ((PJOB)jsp)->GetFirst(); prp; prp = prp->Next) + jvp->SetKeyValue(g, GetRowValue(g, prp->Val, n + 1), prp->Key); + + jsp = jvp; + } // endif Type + + } // endif + + Value->SetValue_psz(Serialize(g, jsp, NULL, 0)); + return Value; +} // end of MakeJson + +/***********************************************************************/ +/* GetRowValue: */ +/***********************************************************************/ +PJVAL JSONCOL::GetRowValue(PGLOBAL g, PJSON row, int i) +{ + PJVAL val = NULL; + + for (; i < Nod && row; i++) { + switch (row->GetType()) { + case TYPE_JOB: + val = (Nodes[i].Key) ? ((PJOB)row)->GetKeyValue(Nodes[i].Key) : NULL; + break; + case TYPE_JAR: + val = ((PJAR)row)->GetArrayValue(Nodes[i].Rank); + break; + case TYPE_JVAL: + val = (PJVAL)row; + break; + default: + snprintf(g->Message, sizeof(g->Message), "Invalid row JSON type %d", row->GetType()); + val = NULL; + } // endswitch Type + + if (i < Nod-1) + row = (val) ? val->GetJson() : NULL; + + } // endfor i + + return val; +} // end of GetRowValue + +/***********************************************************************/ +/* SetValue: Set a value from a JVALUE contains. */ +/***********************************************************************/ +void JSONCOL::SetJsonValue(PGLOBAL g, PVAL vp, PJVAL jvp) +{ + if (jvp) { + vp->SetNull(false); + + switch (jvp->GetValType()) { + case TYPE_STRG: + case TYPE_INTG: + case TYPE_BINT: + case TYPE_DBL: + case TYPE_DTM: + switch (vp->GetType()) { + case TYPE_STRING: + vp->SetValue_psz(jvp->GetString(g)); + break; + case TYPE_INT: + case TYPE_SHORT: + case TYPE_TINY: + vp->SetValue(jvp->GetInteger()); + break; + case TYPE_BIGINT: + vp->SetValue(jvp->GetBigint()); + break; + case TYPE_DOUBLE: + vp->SetValue(jvp->GetFloat()); + + if (jvp->GetValType() == TYPE_DBL) + vp->SetPrec(jvp->Nd); + + break; + case TYPE_DATE: + if (jvp->GetValType() == TYPE_STRG) { + PSZ dat = jvp->GetString(g); + + if (!IsNum(dat)) { + if (!((DTVAL*)vp)->IsFormatted()) + ((DTVAL*)vp)->SetFormat(g, "YYYY-MM-DDThh:mm:ssZ", 20, 0); + + vp->SetValue_psz(dat); + } else + vp->SetValue(atoi(dat)); + + } else + vp->SetValue(jvp->GetInteger()); + + break; + default: + snprintf(g->Message, sizeof(g->Message), "Unsupported column type %d\n", vp->GetType()); + throw 888; + } // endswitch Type + + break; + case TYPE_BOOL: + if (vp->IsTypeNum()) + vp->SetValue(jvp->GetInteger() ? 1 : 0); + else + vp->SetValue_psz((PSZ)(jvp->GetInteger() ? "true" : "false")); + + break; + case TYPE_JAR: +// SetJsonValue(g, vp, val->GetArray()->GetValue(0)); + vp->SetValue_psz(jvp->GetArray()->GetText(g, NULL)); + break; + case TYPE_JOB: +// if (!vp->IsTypeNum() || !Strict) { + vp->SetValue_psz(jvp->GetObject()->GetText(g, NULL)); + break; +// } // endif Type + + default: + vp->Reset(); + vp->SetNull(true); + } // endswitch Type + + } else { + vp->Reset(); + vp->SetNull(true); + } // endif val + +} // end of SetJsonValue + +/***********************************************************************/ +/* ReadColumn: */ +/***********************************************************************/ +void JSONCOL::ReadColumn(PGLOBAL g) +{ + if (!Tjp->SameRow || Xnod >= Tjp->SameRow) + Value->SetValue_pval(GetColumnValue(g, Tjp->Row, 0)); + +// if (Xpd && Value->IsNull() && !((PJDEF)Tjp->To_Def)->Accept) +// throw("Null expandable JSON value"); + + // Set null when applicable + if (!Nullable) + Value->SetNull(false); + +} // end of ReadColumn + +/***********************************************************************/ +/* GetColumnValue: */ +/***********************************************************************/ +PVAL JSONCOL::GetColumnValue(PGLOBAL g, PJSON row, int i) +{ + PJAR arp; + PJVAL val = NULL; + + for (; i < Nod && row; i++) { + if (Nodes[i].Op == OP_NUM) { + Value->SetValue(row->GetType() == TYPE_JAR ? ((PJAR)row)->size() : 1); + return(Value); + } else if (Nodes[i].Op == OP_XX) { + return MakeJson(G, row, i); + } else switch (row->GetType()) { + case TYPE_JOB: + if (!Nodes[i].Key) { + // Expected Array was not there, wrap the value + if (i < Nod-1) + continue; + else + val = new(G) JVALUE(row); + + } else + val = ((PJOB)row)->GetKeyValue(Nodes[i].Key); + + break; + case TYPE_JAR: + arp = (PJAR)row; + + if (!Nodes[i].Key) { + if (Nodes[i].Op == OP_EQ) + val = arp->GetArrayValue(Nodes[i].Rank); + else if (Nodes[i].Op == OP_EXP) + return ExpandArray(g, arp, i); + else + return CalculateArray(g, arp, i); + + } else { + // Unexpected array, unwrap it as [0] + val = arp->GetArrayValue(0); + i--; + } // endif's + + break; + case TYPE_JVAL: + val = (PJVAL)row; + break; + default: + snprintf(g->Message, sizeof(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); + return Value; +} // end of GetColumnValue + +/***********************************************************************/ +/* ExpandArray: */ +/***********************************************************************/ +PVAL JSONCOL::ExpandArray(PGLOBAL g, PJAR arp, int n) +{ + int ars = MY_MIN(Tjp->Limit, arp->size()); + PJVAL jvp; + JVALUE jval; + + if (!ars) { + Value->Reset(); + Value->SetNull(true); + Tjp->NextSame = 0; + return Value; + } // endif ars + + if (!(jvp = arp->GetArrayValue((Nodes[n].Rx = Nodes[n].Nx)))) { + safe_strcpy(g->Message, sizeof(g->Message), "Logical error expanding array"); + throw 666; + } // endif jvp + + if (n < Nod - 1 && jvp->GetJson()) { + jval.SetValue(g, 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); + 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()); + + if (trace(1)) + htrc("CalculateArray: size=%d op=%d nextsame=%d\n", + ars, op, nextsame); + + for (i = 0; i < ars; i++) { + jvrp = arp->GetArrayValue(i); + + if (trace(1)) + htrc("i=%d nv=%d\n", i, nv); + + if (!jvrp->IsNull() || (op == OP_CNC && GetJsonNull())) do { + if (jvrp->IsNull()) { + jvrp->Strp = PlugDup(g, GetJsonNull()); + jvrp->DataType = TYPE_STRG; + jvp = jvrp; + } else if (n < Nod - 1 && jvrp->GetJson()) { + Tjp->NextSame = nextsame; + jval.SetValue(g, GetColumnValue(g, jvrp->GetJson(), n + 1)); + jvp = &jval; + } else + jvp = jvrp; + + if (trace(1)) + htrc("jvp=%s null=%d\n", + jvp->GetString(g), jvp->IsNull() ? 1 : 0); + + if (!nv++) { + SetJsonValue(g, vp, jvp); + continue; + } else + SetJsonValue(g, MulVal, jvp); + + if (!MulVal->IsNull()) { + 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(); + + if (trace(1)) { + char buf(32); + + htrc("vp='%s' err=%d\n", + vp->GetCharString(&buf), err ? 1 : 0); + + } // endif trace + + } // endif Null + + } 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 = NULL; + PJAR arp; + PJSON nwr, row = Tjp->Row; + + for (int i = 0; i < Nod && row; i++) { + if (i < Nod-1 && Nodes[i+1].Op == OP_XX) + break; + else switch (row->GetType()) { + case TYPE_JOB: + if (!Nodes[i].Key) + // Expected Array was not there, wrap the value + continue; + + val = ((PJOB)row)->GetKeyValue(Nodes[i].Key); + break; + case TYPE_JAR: + arp = (PJAR)row; + + if (!Nodes[i].Key) { + if (Nodes[i].Op == OP_EQ) + val = arp->GetArrayValue(Nodes[i].Rank); + else + val = arp->GetArrayValue(Nodes[i].Rx); + + } else { + // Unexpected array, unwrap it as [0] + val = arp->GetArrayValue(0); + i--; + } // endif Nodes + + break; + case TYPE_JVAL: + val = (PJVAL)row; + break; + default: + snprintf(g->Message, sizeof(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)->SetKeyValue(G, new(G) JVALUE(nwr), Nodes[i-1].Key); + } else if (row->GetType() == TYPE_JAR) { + ((PJAR)row)->AddArrayValue(G, new(G) JVALUE(nwr)); + ((PJAR)row)->InitArray(G); + } else { + safe_strcpy(g->Message, sizeof(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) +{ + if (Xpd && Tjp->Pretty < 2) { + safe_strcpy(g->Message, sizeof(g->Message), "Cannot write expanded column when Pretty is not 2"); + throw 666; + } // endif Xpd + + /*********************************************************************/ + /* 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); + + 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 (s && *s) { + if (!(jsp = ParseJson(G, s, strlen(s)))) { + safe_strcpy(g->Message, sizeof(g->Message), s); + throw 666; + } // endif jsp + + } else + jsp = NULL; + + if (arp) { + if (Nod > 1 && Nodes[Nod-2].Op == OP_EQ) + arp->SetArrayValue(G, new(G) JVALUE(jsp), Nodes[Nod-2].Rank); + else + arp->AddArrayValue(G, new(G) JVALUE(jsp)); + + arp->InitArray(G); + } else if (objp) { + if (Nod > 1 && Nodes[Nod-2].Key) + objp->SetKeyValue(G, new(G) JVALUE(jsp), Nodes[Nod-2].Key); + + } else if (jvp) + jvp->SetValue(jsp); + + break; + } // endif Op + + // fall through + case TYPE_DATE: + case TYPE_INT: + case TYPE_TINY: + case TYPE_SHORT: + case TYPE_BIGINT: + case TYPE_DOUBLE: + if (arp) { + if (Nodes[Nod-1].Op == OP_EQ) + arp->SetArrayValue(G, new(G) JVALUE(G, Value), Nodes[Nod-1].Rank); + else + arp->AddArrayValue(G, new(G) JVALUE(G, Value)); + + arp->InitArray(G); + } else if (objp) { + if (Nodes[Nod-1].Key) + objp->SetKeyValue(G, new(G) JVALUE(G, Value), Nodes[Nod-1].Key); + + } else if (jvp) + jvp->SetValue(g, Value); + + break; + default: // ?????????? + snprintf(g->Message, sizeof(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) +{ + Doc = NULL; + Multiple = tdp->Multiple; + Done = Changed = false; +} // end of TDBJSON standard constructor + +TDBJSON::TDBJSON(PJTDB tdbp) : TDBJSN(tdbp) +{ + Doc = tdbp->Doc; + Multiple = tdbp->Multiple; + Done = tdbp->Done; + Changed = tdbp->Changed; +} // end of TDBJSON copy constructor + +// Used for update +PTDB TDBJSON::Clone(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 Clone + +/***********************************************************************/ +/* Make the document tree from the object path. */ +/***********************************************************************/ +int TDBJSON::MakeNewDoc(PGLOBAL g) +{ + // Create a void table that will be populated + Doc = new(g) JARRAY; + + if (MakeTopTree(g, Doc)) + return RC_FX; + + Done = true; + return RC_OK; +} // end of MakeNewDoc + +/***********************************************************************/ +/* Make the document tree from a file. */ +/***********************************************************************/ +int TDBJSON::MakeDocument(PGLOBAL g) +{ + char *p, *p1, *p2, *memory, *objpath, *key = NULL; + int i = 0; + size_t len; + my_bool a; + MODE mode = Mode; + PJSON jsp; + PJOB objp = NULL; + PJAR arp = NULL; + PJVAL val = NULL; + + if (Done) + return RC_OK; + + /*********************************************************************/ + /* Create the mapping file object in mode read. */ + /*********************************************************************/ + Mode = MODE_READ; + + if (!Txfp->OpenTableFile(g)) { + PFBLOCK fp = Txfp->GetTo_Fb(); + + if (fp) { + len = fp->Length; + memory = fp->Memory; + } else { + Mode = mode; // Restore saved Mode + return MakeNewDoc(g); + } // endif fp + + } else + return RC_FX; + + /*********************************************************************/ + /* Parse the json file and allocate its tree structure. */ + /*********************************************************************/ + g->Message[0] = 0; + jsp = Top = ParseJson(g, memory, len, &Pretty); + Txfp->CloseTableFile(g, false); + Mode = mode; // Restore saved Mode + + if (!jsp && g->Message[0]) + return RC_FX; + + if ((objpath = PlugDup(g, Objname))) { + if (*objpath == '$') objpath++; + if (*objpath == '.') objpath++; + p1 = (*objpath == '[') ? objpath++ : NULL; + + /*********************************************************************/ + /* Find the table in the tree structure. */ + /*********************************************************************/ + for (p = objpath; jsp && p; p = (p2 ? p2 : NULL)) { + a = (p1 != NULL); + p1 = strchr(p, '['); + p2 = strchr(p, '.'); + + if (!p2) + p2 = p1; + else if (p1) { + if (p1 < p2) + p2 = p1; + else if (p1 == p2 + 1) + *p2++ = 0; // Old syntax .[ + else + p1 = NULL; + + } // endif p1 + + if (p2) + *p2++ = 0; + + if (!a && *p && *p != '[' && !IsNum(p)) { + // obj is a key + if (jsp->GetType() != TYPE_JOB) { + safe_strcpy(g->Message, sizeof(g->Message), "Table path does not match the json file"); + return RC_FX; + } // endif Type + + key = p; + objp = jsp->GetObject(); + arp = NULL; + val = objp->GetKeyValue(key); + + if (!val || !(jsp = val->GetJson())) { + snprintf(g->Message, sizeof(g->Message), "Cannot find object key %s", key); + return RC_FX; + } // endif val + + } else { + if (*p == '[') { + // Old style + if (p[strlen(p) - 1] != ']') { + snprintf(g->Message, sizeof(g->Message), "Invalid Table path near %s", p); + return RC_FX; + } else + p++; + + } // endif p + + if (jsp->GetType() != TYPE_JAR) { + safe_strcpy(g->Message, sizeof(g->Message), "Table path does not match the json file"); + return RC_FX; + } // endif Type + + arp = jsp->GetArray(); + objp = NULL; + i = atoi(p) - B; + val = arp->GetArrayValue(i); + + if (!val) { + snprintf(g->Message, sizeof(g->Message), "Cannot find array value %d", i); + return RC_FX; + } // endif val + + } // endif + + jsp = val->GetJson(); + } // endfor p + + } // endif 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->AddArrayValue(g, val); + Doc->InitArray(g); + } else if (jsp) { + Doc->AddArrayValue(g, new(g) JVALUE(jsp)); + Doc->InitArray(g); + } // endif val + + if (objp) + objp->SetKeyValue(g, new(g) JVALUE(Doc), key); + else if (arp) + arp->SetArrayValue(g, new(g) JVALUE(Doc), i); + else + Top = Doc; + + } // endif jsp + + Done = true; + return RC_OK; +} // end of MakeDocument + +/***********************************************************************/ +/* 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) +{ + if (pxdf) { + safe_strcpy(g->Message, sizeof(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, 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: + snprintf(g->Message, sizeof(g->Message), "Invalid Jmode %d", Jmode); + return true; + } // endswitch Jmode + + if (Xcol) + To_Filter = NULL; // Imcompatible + + Use = USE_OPEN; + return false; +} // end of OpenDB + +/***********************************************************************/ +/* ReadDB: Data Base read routine for JSON access method. */ +/***********************************************************************/ +int TDBJSON::ReadDB(PGLOBAL) +{ + int rc; + + N++; + + if (NextSame) { + SameRow = NextSame; + NextSame = false; + M++; + rc = RC_OK; + } else if (++Fpos < (signed)Doc->size()) { + Row = Doc->GetArrayValue(Fpos); + + if (Row->GetType() == TYPE_JVAL) + Row = ((PJVAL)Row)->GetJson(); + + SameRow = 0; + M = 1; + 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->AddArrayValue(g, vp); + Row = new(g) JOBJECT; + } else + Doc->SetArrayValue(g, vp, Fpos); + + } else if (Jmode == MODE_ARRAY) { + PJVAL vp = new(g) JVALUE(Row); + + if (Mode == MODE_INSERT) { + Doc->AddArrayValue(g, vp); + Row = new(g) JARRAY; + } else + Doc->SetArrayValue(g, vp, Fpos); + + } else { // if (Jmode == MODE_VALUE) + if (Mode == MODE_INSERT) { + Doc->AddArrayValue(g, (PJVAL)Row); + Row = new(g) JVALUE; + } else + Doc->SetArrayValue(g, (PJVAL)Row, Fpos); + + } // 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)) { + snprintf(g->Message, sizeof(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]; + + Doc->InitArray(g); + + // We used the file name relative to recorded datapath + PlugSetPath(filename, ((PJDEF)To_Def)->Fn, GetPath()); + + // Serialize the modified table + if (!Serialize(g, Top, filename, Pretty)) + puts(g->Message); + +} // end of CloseDB + +/* ---------------------------TDBJCL class --------------------------- */ + +/***********************************************************************/ +/* TDBJCL class constructor. */ +/***********************************************************************/ +TDBJCL::TDBJCL(PJDEF tdp) : TDBCAT(tdp) +{ + Topt = tdp->GetTopt(); + Db = tdp->Schema; + Dsn = tdp->Uri; +} // end of TDBJCL constructor + +/***********************************************************************/ +/* GetResult: Get the list the JSON file columns. */ +/***********************************************************************/ +PQRYRES TDBJCL::GetResult(PGLOBAL g) +{ + return JSONColumns(g, Db, Dsn, Topt, false); +} // end of GetResult + +/* --------------------------- End of json --------------------------- */ |