/************** mongo C++ Program Source Code File (.CPP) **************/
/* PROGRAM NAME: mongo     Version 1.1                                 */
/*  (C) Copyright to the author Olivier BERTRAND          2021         */
/*  These programs are the MGODEF class 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.  */
/***********************************************************************/
#include "global.h"
#include "plgdbsem.h"
#include "xtable.h"
#include "tabext.h"
#include "filter.h"
#if defined(CMGO_SUPPORT)
#include "tabcmg.h"
#endif   // CMGO_SUPPORT
#if defined(JAVA_SUPPORT)
#include "tabjmg.h"
#endif   // JAVA_SUPPORT
#include "resource.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 */

bool MakeSelector(PGLOBAL g, PFIL fp, PSTRG s);
bool IsNum(PSZ s);
int  GetDefaultDepth(void);
bool JsonAllPath(void);

/***********************************************************************/
/*  Make selector json representation for Mongo tables.                */
/***********************************************************************/
bool MakeSelector(PGLOBAL g, PFIL fp, PSTRG s)
{
	OPVAL opc = fp->GetOpc();

	s->Append('{');

	if (opc == OP_AND || opc == OP_OR) {
		if (fp->GetArgType(0) != TYPE_FILTER || fp->GetArgType(1) != TYPE_FILTER)
			return true;

		s->Append("\"$");
		s->Append(opc == OP_AND ? "and" : "or");
		s->Append("\":[");

		if (MakeSelector(g, (PFIL)fp->Arg(0), s))
			return true;

		s->Append(',');

		if (MakeSelector(g, (PFIL)fp->Arg(1), s))
			return true;

		s->Append(']');
	} else {
		if (fp->GetArgType(0) != TYPE_COLBLK)
			return true;

		s->Append('"');
		s->Append(((PCOL)fp->Arg(0))->GetJpath(g, false));
		s->Append("\":{\"$");

		switch (opc) {
			case OP_EQ:
				s->Append("eq");
				break;
			case OP_NE:
				s->Append("ne");
				break;
			case OP_GT:
				s->Append("gt");
				break;
			case OP_GE:
				s->Append("gte");
				break;
			case OP_LT:
				s->Append("lt");
				break;
			case OP_LE:
				s->Append("lte");
				break;
			case OP_NULL:
			case OP_LIKE:
			case OP_EXIST:
			default:
				return true;
		} // endswitch Opc

		s->Append("\":");

		if (fp->GetArgType(1) == TYPE_COLBLK) {
			s->Append("\"$");
			s->Append(((PEXTCOL)fp->Arg(1))->GetJpath(g, false));
			s->Append('"');
		} else {
			char buf[501];

			fp->Arg(1)->Prints(g, buf, 500);
			s->Append(buf);
		} // endif Type

		s->Append('}');
	} // endif opc

	s->Append('}');
	return false;
} // end of MakeSelector

/***********************************************************************/
/*  MGOColumns: construct the result blocks containing the description */
/*  of all the columns of a document contained inside MongoDB.         */
/***********************************************************************/
PQRYRES MGOColumns(PGLOBAL g, PCSZ db, PCSZ uri, 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};
	unsigned int length[] = {0, 6, 8, 10, 10, 6, 6, 0};
	int      ncol = sizeof(buftyp) / sizeof(int);
	int      i, n = 0;
	PCSZ     drv;
	PBCOL    bcp;
	MGODISC *cmgd = NULL;
	PQRYRES  qrp;
	PCOLRES  crp;

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

	/*********************************************************************/
	/*  Open MongoDB.                                                    */
	/*********************************************************************/
	drv = GetStringTableOption(g, topt, "Driver", NULL);

	if (drv && toupper(*drv) == 'C') {
#if defined(CMGO_SUPPORT)
		cmgd = new(g) CMGDISC(g, (int*)length);
#else
		snprintf(g->Message, sizeof(g->Message), "Mongo %s Driver not available", "C");
		goto err;
#endif
	} else if (drv && toupper(*drv) == 'J') {
#if defined(JAVA_SUPPORT)
		cmgd = new(g) JMGDISC(g, (int*)length);
#else
		snprintf(g->Message, sizeof(g->Message), "Mongo %s Driver not available", "Java");
		goto err;
#endif
	} else {						 // Driver not specified
#if defined(CMGO_SUPPORT)
		cmgd = new(g) CMGDISC(g, (int*)length);
#else
		cmgd = new(g) JMGDISC(g, (int*)length);
#endif
	}	// endif drv

	if ((n = cmgd->GetColumns(g, db, uri, topt)) < 0)
		goto err;

skipit:
	if (trace(1))
		htrc("MGOColumns: 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 = "Bpath";

	if (info || !qrp)
		return qrp;

	qrp->Nblin = n;

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

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

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

	} // endfor i

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

err:
	if (cmgd && cmgd->tmgp)
		cmgd->tmgp->CloseDB(g);

	return NULL;
} // end of MGOColumns

/***********************************************************************/
/*  Class used to get the columns of a mongo collection.               */
/***********************************************************************/
MGODISC::MGODISC(PGLOBAL g, int *lg) {
	length = lg;
	fbcp = NULL;
	pbcp = NULL;
	tmgp = NULL;
	drv = NULL;
	i = ncol = lvl = 0;
	all = false;
}	// end of MGODISC constructor

/***********************************************************************/
/*  Class used to get the columns of a mongo collection.               */
/***********************************************************************/
int MGODISC::GetColumns(PGLOBAL g, PCSZ db, PCSZ uri, PTOS topt)
{
	PMGODEF tdp;

	lvl = GetIntegerTableOption(g, topt, "Level", GetDefaultDepth());
	lvl = GetIntegerTableOption(g, topt, "Depth", lvl);
	all = GetBooleanTableOption(g, topt, "Fullarray", false);

	/*********************************************************************/
	/*  Open the MongoDB collection.                                     */
	/*********************************************************************/
	tdp = new(g) MGODEF;
	tdp->Uri = (uri && *uri) ? uri : "mongodb://localhost:27017";
	tdp->Driver = drv;
	tdp->Tabname = GetStringTableOption(g, topt, "Name", NULL);
	tdp->Tabname = GetStringTableOption(g, topt, "Tabname", tdp->Tabname);
	tdp->Tabschema = GetStringTableOption(g, topt, "Dbname", db);
	tdp->Base = GetIntegerTableOption(g, topt, "Base", 0) ? 1 : 0;
	tdp->Colist = GetStringTableOption(g, topt, "Colist", "all");
	tdp->Filter = GetStringTableOption(g, topt, "Filter", NULL);
	tdp->Pipe = GetBooleanTableOption(g, topt, "Pipeline", false);
	tdp->Version = GetIntegerTableOption(g, topt, "Version", 3);
	tdp->Wrapname = (PSZ)GetStringTableOption(g, topt, "Wrapper",
		(tdp->Version == 2) ? "Mongo2Interface" : "Mongo3Interface");

	if (trace(1))
		htrc("Uri %s coll=%s db=%s colist=%s filter=%s lvl=%d\n",
			tdp->Uri, tdp->Tabname, tdp->Tabschema, tdp->Colist, tdp->Filter, lvl);

	tmgp = tdp->GetTable(g, MODE_READ);
	tmgp->SetMode(MODE_READ);

	if (tmgp->OpenDB(g))
		return -1;

	bcol.Next = NULL;
	bcol.Name = bcol.Fmt = NULL;
	bcol.Type = TYPE_UNKNOWN;
	bcol.Len = bcol.Scale = 0;
	bcol.Found = true;
	bcol.Cbn = false;

	if (Init(g))
		return -1;

	/*********************************************************************/
	/*  Analyse the BSON tree and define columns.                        */
	/*********************************************************************/
	for (i = 1; ; i++) {
		switch (tmgp->ReadDB(g)) {
			case RC_EF:
				return ncol;
			case RC_FX:
				return -1;
			default:
				GetDoc();
		} // endswitch ReadDB

		if (Find(g))
			return -1;

		// Missing columns can be null
		for (bcp = fbcp; bcp; bcp = bcp->Next) {
			bcp->Cbn |= !bcp->Found;
			bcp->Found = false;
		} // endfor bcp

	} // endfor i

	return ncol;
} // end of GetColumns

/***********************************************************************/
/*  Add a new column in the column list.                               */
/***********************************************************************/
void MGODISC::AddColumn(PGLOBAL g, PCSZ colname, PCSZ fmt, int k)
{
	// Check whether this column was already found
	for (bcp = fbcp; bcp; bcp = bcp->Next)
		if (!strcmp(colname, bcp->Name))
			break;

	if (bcp) {
		if (bcp->Type != bcol.Type)
			bcp->Type = TYPE_STRING;

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

		bcp->Len = MY_MAX(bcp->Len, bcol.Len);
		bcp->Scale = MY_MAX(bcp->Scale, bcol.Scale);
		bcp->Cbn |= bcol.Cbn;
		bcp->Found = true;
	} else {
		// New column
		bcp = (PBCOL)PlugSubAlloc(g, NULL, sizeof(BCOL));
		*bcp = bcol;
		bcp->Cbn |= (i > 1);
		bcp->Name = PlugDup(g, colname);
		length[0] = MY_MAX(length[0], (signed)strlen(colname));

		if (k || JsonAllPath()) {
			bcp->Fmt = PlugDup(g, fmt);
			length[7] = MY_MAX(length[7], (signed)strlen(fmt));
		} else
			bcp->Fmt = NULL;

		if (pbcp) {
			bcp->Next = pbcp->Next;
			pbcp->Next = bcp;
		} else
			fbcp = bcp;

		ncol++;
	} // endif jcp

	pbcp = bcp;
} // end of AddColumn

/* -------------------------- Class MGODEF --------------------------- */

MGODEF::MGODEF(void)
{
	Driver = NULL;
	Uri = NULL;
	Colist = NULL;
	Filter = NULL;
	Base = 0;
	Version = 0;
	Pipe = false;
} // end of MGODEF constructor

/***********************************************************************/
/*  DefineAM: define specific AM block values.                         */
/***********************************************************************/
bool MGODEF::DefineAM(PGLOBAL g, LPCSTR, int poff)
{
	if (EXTDEF::DefineAM(g, "MGO", poff))
		return true;
	else if (!Tabschema)
		Tabschema = GetStringCatInfo(g, "Dbname", "*");

	Driver = GetStringCatInfo(g, "Driver", NULL);
	Uri = GetStringCatInfo(g, "Connect", "mongodb://localhost:27017");
	Colist = GetStringCatInfo(g, "Colist", NULL);
	Filter = GetStringCatInfo(g, "Filter", NULL);
	Strfy = GetStringCatInfo(g, "Stringify", NULL);
	Base = GetIntCatInfo("Base", 0) ? 1 : 0;
	Version = GetIntCatInfo("Version", 3);

	if (Version == 2)
		Wrapname = GetStringCatInfo(g, "Wrapper", "Mongo2Interface");
	else
		Wrapname = GetStringCatInfo(g, "Wrapper", "Mongo3Interface");

	Pipe = GetBoolCatInfo("Pipeline", false);
	return false;
} // end of DefineAM

/***********************************************************************/
/*  GetTable: makes a new Table Description Block.                     */
/***********************************************************************/
PTDB MGODEF::GetTable(PGLOBAL g, MODE m)
{
	if (Driver && toupper(*Driver) == 'C') {
#if defined(CMGO_SUPPORT)
		if (Catfunc == FNC_COL)
			return new(g) TDBGOL(this);
		else
			return new(g) TDBCMG(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)
		if (Catfunc == FNC_COL)
			return new(g) TDBJGL(this);
		else
			return new(g) TDBJMG(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)
		if (Catfunc == FNC_COL)
			return new(g) TDBGOL(this);
		else
			return new(g) TDBCMG(this);
#else
		if (Catfunc == FNC_COL)
			return new(g) TDBJGL(this);
		else
			return new(g) TDBJMG(this);
#endif
	} // endif Driver

} // end of GetTable
