/************ Jdbconn C++ Functions Source Code File (.CPP) ************/
/*  Name: JDBCONN.CPP  Version 1.2                                     */
/*                                                                     */
/*  (C) Copyright to the author Olivier BERTRAND          2016-2018    */
/*                                                                     */
/*  This file contains the JDBC connection classes functions.          */
/***********************************************************************/

#if defined(_WIN32)
// This is needed for RegGetValue
#define _WINVER 0x0601
#undef  _WIN32_WINNT
#define _WIN32_WINNT 0x0601
#endif   // _WIN32

/***********************************************************************/
/*  Include relevant MariaDB header file.                              */
/***********************************************************************/
#include <my_global.h>
#include <m_string.h>
#if defined(_WIN32)
//nclude <io.h>
//nclude <fcntl.h>
#include <direct.h>                      // for getcwd
#if defined(__BORLANDC__)
#define __MFC_COMPAT__                   // To define min/max as macro
#endif   // __BORLANDC__
//#include <windows.h>
#else   // !_WIN32
#if defined(UNIX)
#include <errno.h>
#else   // !UNIX
//nclude <io.h>
#endif  // !UNIX
#include <stdio.h>
#include <stdlib.h>                      // for getenv
//nclude <fcntl.h>
#define NODW
#endif  // !_WIN32

/***********************************************************************/
/*  Required objects includes.                                         */
/***********************************************************************/
#include "global.h"
#include "plgdbsem.h"
#include "xobject.h"
#include "xtable.h"
#include "tabext.h"
#include "tabjdbc.h"
//#include "jdbconn.h"
#include "resource.h"
#include "valblk.h"
#include "osutil.h"


//#if defined(_WIN32)
//extern "C" HINSTANCE s_hModule;           // Saved module handle
//#endif   // _WIN32
#define nullptr 0

TYPCONV GetTypeConv();
int GetConvSize();
//extern char *JvmPath;   // The connect_jvm_path global variable value
//extern char *ClassPath; // The connect_class_path global variable value

//char *GetJavaWrapper(void);		// The connect_java_wrapper variable value

/***********************************************************************/
/*  Some macro's (should be defined elsewhere to be more accessible)   */
/***********************************************************************/
//#if defined(_DEBUG)
//#define ASSERT(f)          assert(f)
//#define DEBUG_ONLY(f)      (f)
//#else   // !_DEBUG
//#define ASSERT(f)          ((void)0)
//#define DEBUG_ONLY(f)      ((void)0)
//#endif  // !_DEBUG

// To avoid gcc warning
int TranslateJDBCType(int stp, char *tn, int prec, int& len, char& v);

/***********************************************************************/
/*  GetJDBCType: returns the SQL_TYPE corresponding to a PLG type.      */
/***********************************************************************/
static short GetJDBCType(int type)
{
	short tp = 0;																  // NULL

	switch (type) {
	case TYPE_STRING:    tp = 12; break;					// VARCHAR
	case TYPE_SHORT:     tp = 5;  break;					// SMALLINT
	case TYPE_INT:       tp = 4;  break;					// INTEGER
	case TYPE_DATE:      tp = 93; break;					// DATE
//case TYPE_TIME:      tp = 92; break;					// TIME
//case TYPE_TIMESTAMP: tp = 93; break;					// TIMESTAMP
	case TYPE_BIGINT:    tp = -5; break;          // BIGINT
	case TYPE_DOUBLE:    tp = 8;  break;					// DOUBLE
	case TYPE_TINY:      tp = -6; break;					// TINYINT
	case TYPE_DECIM:     tp = 3;  break;					// DECIMAL
	} // endswitch type

	return tp;
} // end of GetJDBCType

/***********************************************************************/
/*  TranslateJDBCType: translate a JDBC Type to a PLG type.            */
/***********************************************************************/
int TranslateJDBCType(int stp, char *tn, int prec, int& len, char& v)
{
	int type;

	switch (stp) {
	case -1:   // LONGVARCHAR, TEXT
	case -16:  // LONGNVARCHAR, NTEXT	(unicode)
		if (GetTypeConv() != TPC_YES)
			return TYPE_ERROR;
		else
		  len = MY_MIN(abs(len), GetConvSize());

                /* fall through */
	case 12:   // VARCHAR
		if (tn && !stricmp(tn, "TEXT"))
			// Postgresql returns 12 for TEXT
			if (GetTypeConv() == TPC_NO)
				return TYPE_ERROR;

		// Postgresql can return this 
		if (len == 0x7FFFFFFF)
			len = GetConvSize();

                /* fall through */
	case -9:   // NVARCHAR	(unicode)
		// Postgresql can return this when size is unknown 
		if (len == 0x7FFFFFFF)
			len = GetConvSize();

		v = 'V';
                /* fall through */
	case 1:    // CHAR
	case -15:  // NCHAR	 (unicode)
	case -8:   // ROWID
		type = TYPE_STRING;
		break;
	case 2:    // NUMERIC
	case 3:    // DECIMAL
	case -3:   // VARBINARY
		type = TYPE_DECIM;
		break;
	case 4:    // INTEGER
		type = TYPE_INT;
		break;
	case 5:    // SMALLINT
		type = TYPE_SHORT;
		break;
	case -6:   // TINYINT
	case -7:   // BIT
	case 16:   // BOOLEAN
		type = TYPE_TINY;
		break;
	case 6:    // FLOAT
	case 7:    // REAL
	case 8:    // DOUBLE
		type = TYPE_DOUBLE;
		break;
	case 93:   // TIMESTAMP, DATETIME
		type = TYPE_DATE;
		len = 19 + ((prec) ? (prec+1) : 0);
		v = (tn && toupper(tn[0]) == 'T') ? 'S' : 'E';
		break;
	case 91:   // DATE, YEAR
		type = TYPE_DATE;

			if (!tn || toupper(tn[0]) != 'Y') {
				len = 10;
				v = 'D';
			} else {
				len = 4;
				v = 'Y';
			}	// endif len

			break;
		case 92:   // TIME
			type = TYPE_DATE;
			len = 8 + ((prec) ? (prec + 1) : 0);
			v = 'T';
			break;
		case -5:   // BIGINT
			type = TYPE_BIGINT;
			break;
		case 1111: // UNKNOWN or UUID
			if (!tn || !stricmp(tn, "UUID")) {
				type = TYPE_STRING;
				len = 36;
				break;
			}	// endif tn

                        /* fall through */
		case 0:    // NULL
		case -2:   // BINARY
		case -4:   // LONGVARBINARY
		case 70:   // DATALINK
		case 2000: // JAVA_OBJECT
		case 2001: // DISTINCT
		case 2002: // STRUCT
		case 2003: // ARRAY
		case 2004: // BLOB
		case 2005: // CLOB
		case 2006: // REF
		case 2009: // SQLXML
		case 2011: // NCLOB
		default:
			type = TYPE_ERROR;
		len = 0;
	} // endswitch type

	return type;
} // end of TranslateJDBCType

	/***********************************************************************/
	/*  A helper class to split an optionally qualified table name into    */
	/*  components.                                                        */
	/*  These formats are understood:                                      */
	/*    "CatalogName.SchemaName.TableName"                               */
	/*    "SchemaName.TableName"                                           */
	/*    "TableName"                                                      */
	/***********************************************************************/
class SQLQualifiedName {
	static const uint max_parts = 3;          // Catalog.Schema.Table
	MYSQL_LEX_STRING m_part[max_parts];
	char m_buf[512];

	void lex_string_set(MYSQL_LEX_STRING *S, char *str, size_t length)
	{
		S->str = str;
		S->length = length;
	} // end of lex_string_set

	void lex_string_shorten_down(MYSQL_LEX_STRING *S, size_t offs)
	{
		DBUG_ASSERT(offs <= S->length);
		S->str += offs;
		S->length -= offs;
	} // end of lex_string_shorten_down

		/*********************************************************************/
		/*  Find the rightmost '.' delimiter and return the length           */
		/*  of the qualifier, including the rightmost '.' delimier.          */
		/*  For example, for the string {"a.b.c",5} it will return 4,        */
		/*  which is the length of the qualifier "a.b."                      */
		/*********************************************************************/
	size_t lex_string_find_qualifier(MYSQL_LEX_STRING *S)
	{
		size_t i;
		for (i = S->length; i > 0; i--)
		{
			if (S->str[i - 1] == '.')
			{
				S->str[i - 1] = '\0';
				return i;
			}
		}
		return 0;
	} // end of lex_string_find_qualifier

public:
	/*********************************************************************/
	/*  Initialize to the given optionally qualified name.               */
	/*  NULL pointer in "name" is supported.                             */
	/*  name qualifier has precedence over schema.                       */
	/*********************************************************************/
	SQLQualifiedName(JCATPARM *cap)
	{
		const char *name = (const char *)cap->Tab;
		char       *db = (char *)cap->DB;
		size_t      len, i;

		// Initialize the parts
		for (i = 0; i < max_parts; i++)
			lex_string_set(&m_part[i], NULL, 0);

		if (name) {
			// Initialize the first (rightmost) part
			lex_string_set(&m_part[0], m_buf,
				strmake(m_buf, name, sizeof(m_buf) - 1) - m_buf);

			// Initialize the other parts, if exist. 
			for (i = 1; i < max_parts; i++) {
				if (!(len = lex_string_find_qualifier(&m_part[i - 1])))
					break;

				lex_string_set(&m_part[i], m_part[i - 1].str, len - 1);
				lex_string_shorten_down(&m_part[i - 1], len);
			} // endfor i

		} // endif name

		// If it was not specified, set schema as the passed db name
		if (db && !m_part[1].length)
			lex_string_set(&m_part[1], db, strlen(db));

	} // end of SQLQualifiedName

	char *ptr(uint i)
	{
		DBUG_ASSERT(i < max_parts);
		return (char *)(m_part[i].length ? m_part[i].str : NULL);
	} // end of ptr

	size_t length(uint i)
	{
		DBUG_ASSERT(i < max_parts);
		return m_part[i].length;
	} // end of length

}; // end of class SQLQualifiedName

/***********************************************************************/
/*  Allocate the structure used to refer to the result set.            */
/***********************************************************************/
static JCATPARM *AllocCatInfo(PGLOBAL g, JCATINFO fid, PCSZ db,
	                            PCSZ tab, PQRYRES qrp)
{
	JCATPARM *cap;

	if ((cap = (JCATPARM *)PlgDBSubAlloc(g, NULL, sizeof(JCATPARM)))) {
		memset(cap, 0, sizeof(JCATPARM));
		cap->Id = fid;
		cap->Qrp = qrp;
		cap->DB = db;
		cap->Tab = tab;
	} // endif cap

	return cap;
} // end of AllocCatInfo

/***********************************************************************/
/*  JDBCColumns: constructs the result blocks containing all columns   */
/*  of a JDBC table that will be retrieved by GetData commands.        */
/***********************************************************************/
PQRYRES JDBCColumns(PGLOBAL g, PCSZ db, PCSZ table, PCSZ colpat,
	                             int maxres, bool info, PJPARM sjp)
{
	int  buftyp[] = {TYPE_STRING, TYPE_STRING, TYPE_STRING, TYPE_STRING,
									 TYPE_SHORT,  TYPE_STRING, TYPE_INT,    TYPE_INT,
									 TYPE_SHORT,  TYPE_SHORT,  TYPE_SHORT,  TYPE_STRING};
	XFLD fldtyp[] = {FLD_CAT,   FLD_SCHEM,    FLD_TABNAME, FLD_NAME,
								   FLD_TYPE,  FLD_TYPENAME, FLD_PREC,    FLD_LENGTH,
								   FLD_SCALE, FLD_RADIX,    FLD_NULL,    FLD_REM};
	unsigned int length[] = {0, 0, 0, 0, 6, 0, 10, 10, 6, 6, 6, 0};
	bool     b[] = {true, true, false, false, false, false, false, false, true, true, false, true};
	int       i, n, ncol = 12;
	PCOLRES   crp;
	PQRYRES   qrp;
	JCATPARM *cap;
	JDBConn  *jcp = NULL;

	/************************************************************************/
	/*  Do an evaluation of the result size.                                */
	/************************************************************************/
	if (!info) {
		jcp = new(g)JDBConn(g, NULL);

		if (jcp->Connect(sjp))  // openReadOnly + noJDBCdialog
			return NULL;

		if (table && !strchr(table, '%')) {
			// We fix a MySQL limit because some data sources return 32767
			n = jcp->GetMaxValue(1);  // MAX_COLUMNS_IN_TABLE)
			maxres = (n > 0) ? MY_MIN(n, 4096) : 4096;
		} else if (!maxres)
			maxres = 20000;

		//  n = jcp->GetMaxValue(2);   MAX_CATALOG_NAME_LEN
		//  length[0] = (n) ? (n + 1) : 0;
		//  n = jcp->GetMaxValue(3);   MAX_SCHEMA_NAME_LEN
		//  length[1] = (n) ? (n + 1) : 0;
		//  n = jcp->GetMaxValue(4);   MAX_TABLE_NAME_LEN
		//  length[2] = (n) ? (n + 1) : 0;
		n = jcp->GetMaxValue(5);    // MAX_COLUMN_NAME_LEN
		length[3] = (n > 0) ? (n + 1) : 128;
	} else {                 // Info table
		maxres = 0;
		length[0] = 128;
		length[1] = 128;
		length[2] = 128;
		length[3] = 128;
		length[5] = 30;
		length[11] = 255;
	} // endif jcp

	if (trace(1))
		htrc("JDBCColumns: max=%d len=%d,%d,%d,%d\n",
		maxres, length[0], length[1], length[2], length[3]);

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

	for (i = 0, crp = qrp->Colresp; crp; i++, crp = crp->Next)
		if (b[i])
			crp->Kdata->SetNullable(true);

	if (info || !qrp)                      // Info table
		return qrp;

	if (trace(1))
		htrc("Getting col results ncol=%d\n", qrp->Nbcol);

	if (!(cap = AllocCatInfo(g, JCAT_COL, db, table, qrp)))
		return NULL;

	// Colpat cannot be null or empty for some drivers
	cap->Pat = (colpat && *colpat) ? colpat : PlugDup(g, "%");

	/************************************************************************/
	/*  Now get the results into blocks.                                    */
	/************************************************************************/
	if ((n = jcp->GetCatInfo(cap)) >= 0) {
		qrp->Nblin = n;
		//  ResetNullValues(cap);

		if (trace(1))
			htrc("Columns: NBCOL=%d NBLIN=%d\n", qrp->Nbcol, qrp->Nblin);

	} else
		qrp = NULL;

	/* Cleanup */
	jcp->Close();

	/************************************************************************/
	/*  Return the result pointer for use by GetData routines.              */
	/************************************************************************/
	return qrp;
} // end of JDBCColumns

/**************************************************************************/
/*  JDBCSrcCols: constructs the result blocks containing the              */
/*  description of all the columns of a Srcdef option.                    */
/**************************************************************************/
PQRYRES JDBCSrcCols(PGLOBAL g, PCSZ src, PJPARM sjp)
{
	char    *sqry;
	PQRYRES  qrp;
	JDBConn *jcp = new(g)JDBConn(g, NULL);

	if (jcp->Connect(sjp))
		return NULL;

	if (strstr(src, "%s")) {
		// Place holder for an eventual where clause
		size_t sqry_size = strlen(src) + 2;
		sqry = (char*)PlugSubAlloc(g, NULL, sqry_size);
		// Function PlugSubAlloc(...) recalculate string size
		// while allocate memory - it rounds size up size to multiple of 8
		// we need to know the real allocated size 
		// to use it in sprintf(...)
		const int sqry_real_allocated_size = ROUNDUP_TO_8(sqry_size);
		snprintf(sqry, sqry_real_allocated_size, src, "1=1");			 // dummy where clause
	} else
		sqry = (char*)src;

	qrp = jcp->GetMetaData(g, sqry);
	jcp->Close();
	return qrp;
} // end of JDBCSrcCols

/**************************************************************************/
/*  JDBCTables: constructs the result blocks containing all tables in     */
/*  an JDBC database that will be retrieved by GetData commands.          */
/**************************************************************************/
PQRYRES JDBCTables(PGLOBAL g, PCSZ db, PCSZ tabpat, PCSZ tabtyp,
	                            int maxres, bool info, PJPARM sjp)
{
	int      buftyp[] = {TYPE_STRING, TYPE_STRING, TYPE_STRING,
		                   TYPE_STRING, TYPE_STRING};
	XFLD     fldtyp[] = {FLD_CAT, FLD_SCHEM, FLD_NAME, FLD_TYPE, FLD_REM};
	unsigned int length[] = {0, 0, 0, 16, 0};
	bool     b[] = {true, true, false, false, true};
	int      i, n, ncol = 5;
	PCOLRES  crp;
	PQRYRES  qrp;
	JCATPARM *cap;
	JDBConn *jcp = NULL;

	/************************************************************************/
	/*  Do an evaluation of the result size.                                */
	/************************************************************************/
	if (!info) {
		/**********************************************************************/
		/*  Open the connection with the JDBC data source.                    */
		/**********************************************************************/
		jcp = new(g)JDBConn(g, NULL);

		if (jcp->Connect(sjp))
			return NULL;

		if (!maxres)
			maxres = 10000;                 // This is completely arbitrary

		n = jcp->GetMaxValue(2);					// Max catalog name length

//	if (n < 0)
//		return NULL;

		length[0] = (n > 0) ? (n + 1) : 0;
		n = jcp->GetMaxValue(3);					// Max schema name length
		length[1] = (n > 0) ? (n + 1) : 0;
		n = jcp->GetMaxValue(4);					// Max table name length
		length[2] = (n > 0) ? (n + 1) : 128;
	} else {
		maxres = 0;
		length[0] = 128;
		length[1] = 128;
		length[2] = 128;
		length[4] = 255;
	} // endif info

	if (trace(1))
		htrc("JDBCTables: max=%d len=%d,%d\n", maxres, length[0], length[1]);

	/************************************************************************/
	/*  Allocate the structures used to refer to the result set.            */
	/************************************************************************/
	qrp = PlgAllocResult(g, ncol, maxres, IDS_TABLES, buftyp,
		fldtyp, length, false, true);

	for (i = 0, crp = qrp->Colresp; crp; i++, crp = crp->Next)
		if (b[i])
			crp->Kdata->SetNullable(true);

	if (info || !qrp)
		return qrp;

	// Tabpat cannot be null or empty for some drivers
	if (!(cap = AllocCatInfo(g, JCAT_TAB, db, 
	               (tabpat && *tabpat) ? tabpat : PlugDup(g, "%"), qrp)))
		return NULL;

	cap->Pat = tabtyp;

	if (trace(1))
		htrc("Getting table results ncol=%d\n", cap->Qrp->Nbcol);

	/************************************************************************/
	/*  Now get the results into blocks.                                    */
	/************************************************************************/
	if ((n = jcp->GetCatInfo(cap)) >= 0) {
		qrp->Nblin = n;
		//  ResetNullValues(cap);

		if (trace(1))
			htrc("Tables: NBCOL=%d NBLIN=%d\n", qrp->Nbcol, qrp->Nblin);

	} else
		qrp = NULL;

	/************************************************************************/
	/*  Close any local connection.                                         */
	/************************************************************************/
	jcp->Close();

	/************************************************************************/
	/*  Return the result pointer for use by GetData routines.              */
	/************************************************************************/
	return qrp;
} // end of JDBCTables

/*************************************************************************/
/*  JDBCDrivers: constructs the result blocks containing all JDBC        */
/*  drivers available on the local host.                                 */
/*  Called with info=true to have result column names.                   */
/*************************************************************************/
PQRYRES JDBCDrivers(PGLOBAL g, int maxres, bool info)
{
	int      buftyp[] ={TYPE_STRING, TYPE_STRING, TYPE_STRING, TYPE_STRING};
	XFLD     fldtyp[] ={FLD_NAME, FLD_EXTRA, FLD_DEFAULT, FLD_REM };
	unsigned int length[] ={ 128, 32, 4, 256 };
	bool     b[] ={ false, false, false, true };
	int      i, ncol = 4;
	PCOLRES  crp;
	PQRYRES  qrp;
	JDBConn *jcp = NULL;

	/************************************************************************/
	/*  Do an evaluation of the result size.                                */
	/************************************************************************/
	if (!info) {
		jcp = new(g) JDBConn(g, NULL);

		if (jcp->Open(g) != RC_OK)
			return NULL;

		if (!maxres)
			maxres = 256;         // Estimated max number of drivers

	} else
		maxres = 0;

	if (trace(1))
		htrc("JDBCDrivers: max=%d len=%d\n", maxres, length[0]);

	/************************************************************************/
	/*  Allocate the structures used to refer to the result set.            */
	/************************************************************************/
	qrp = PlgAllocResult(g, ncol, maxres, 0, buftyp, fldtyp, length, false, true);

	for (i = 0, crp = qrp->Colresp; crp; i++, crp = crp->Next) {
		if (b[i])
			crp->Kdata->SetNullable(true);

		switch (i) {
		case 0: crp->Name = "Name";        break;
		case 1: crp->Name = "Version";     break;
		case 2: crp->Name = "Compliant";   break;
		case 3: crp->Name = "Description"; break;
		}	// endswitch

	} // endfor i

	/************************************************************************/
	/*  Now get the results into blocks.                                    */
	/************************************************************************/
	if (!info && qrp && jcp->GetDrivers(qrp))
		qrp = NULL;

	if (!info)
		jcp->Close();

	/************************************************************************/
	/*  Return the result pointer for use by GetData routines.              */
	/************************************************************************/
	return qrp;
} // end of JDBCDrivers

/***********************************************************************/
/*  JDBConn construction/destruction.                                  */
/***********************************************************************/
JDBConn::JDBConn(PGLOBAL g, PCSZ wrapper) : JAVAConn(g, wrapper)
{
	xqid = xuid = xid = grs = readid = fetchid = typid = errid = nullptr;
	prepid = xpid = pcid = nullptr;
	chrfldid = intfldid = dblfldid = fltfldid = bigfldid = nullptr;
	objfldid = datfldid = timfldid = tspfldid = uidfldid = nullptr;
	DiscFunc = "JdbcDisconnect";
	m_Ncol = 0;
	m_Aff = 0;
	//m_Rows = 0;
	m_Fetch = 0;
	m_RowsetSize = 0;
	m_Updatable = true;
	m_Transact = false;
	m_Scrollable = false;
	m_Full = false;
	m_Opened = false;
	m_IDQuoteChar[0] = '"';
	m_IDQuoteChar[1] = 0;
} // end of JDBConn

/***********************************************************************/
/*  Search for UUID columns.                                           */
/***********************************************************************/
bool JDBConn::SetUUID(PGLOBAL g, PTDBJDBC tjp)
{
	int          ncol, ctyp;
	bool         brc = true;
	PCSZ         fnc = "GetColumns";
	PCOL		     colp;
	JCATPARM    *cap;
	//jint         jtyp;
	jboolean     rc = false;
	jobjectArray parms;
	jmethodID    catid = nullptr;

	if (gmID(g, catid, fnc, "([Ljava/lang/String;)I"))
		return true;
	else if (gmID(g, intfldid, "IntField", "(ILjava/lang/String;)I"))
		return true;
	else if (gmID(g, readid, "ReadNext", "()I"))
		return true;

	cap = AllocCatInfo(g, JCAT_COL, tjp->Schema, tjp->TableName, NULL);
	SQLQualifiedName name(cap);

	// Build the java string array
	parms = env->NewObjectArray(4, env->FindClass("java/lang/String"), NULL);
	env->SetObjectArrayElement(parms, 0, env->NewStringUTF(name.ptr(2)));
	env->SetObjectArrayElement(parms, 1, env->NewStringUTF(name.ptr(1)));
	env->SetObjectArrayElement(parms, 2, env->NewStringUTF(name.ptr(0)));

	for (colp = tjp->GetColumns(); colp; colp = colp->GetNext()) {
		env->SetObjectArrayElement(parms, 3, env->NewStringUTF(colp->GetName()));
		ncol = env->CallIntMethod(job, catid, parms);

		if (Check(ncol)) {
			snprintf(g->Message, sizeof(g->Message), "%s: %s", fnc, Msg);
			goto err;
		}	// endif Check

		rc = env->CallBooleanMethod(job, readid);

		if (Check(rc)) {
			snprintf(g->Message, sizeof(g->Message), "ReadNext: %s", Msg);
			goto err;
		} else if (rc == 0) {
			snprintf(g->Message, sizeof(g->Message), "table %s does not exist", tjp->TableName);
			goto err;
		}	// endif rc

		// Should return 666 is case of error	(not done yet)
		ctyp = (int)env->CallIntMethod(job, intfldid, 5, nullptr);

		//if (Check((ctyp == 666) ? -1 : 1)) {
		//	snprintf(g->Message, sizeof(g->Message), "Getting ctyp: %s", Msg);
		//	goto err;
		//} // endif ctyp

		if (ctyp == 1111)
			((PJDBCCOL)colp)->uuid = true;

	} // endfor colp

	// All is Ok
	brc = false;

 err:
	// Not used anymore
	env->DeleteLocalRef(parms);
	return brc;
} // end of SetUUID

/***********************************************************************/
/*  Utility routine.                                                   */
/***********************************************************************/
int JDBConn::GetMaxValue(int n)
{
	jint      m;
	jmethodID maxid = nullptr;

	if (gmID(m_G, maxid, "GetMaxValue", "(I)I"))
		return -1;

	// call method
	if (Check(m = env->CallIntMethod(job, maxid, n)))
		htrc("GetMaxValue: %s", Msg);

	return (int)m;
} // end of GetMaxValue

/***********************************************************************/
/*  AddJars: add some jar file to the Class path.                      */
/***********************************************************************/
void JDBConn::AddJars(PSTRG jpop, char sep)
{
#if defined(DEVELOPMENT)
	jpop->Append(
		";C:/Jconnectors/postgresql-9.4.1208.jar"
		";C:/Oracle/ojdbc7.jar"
		";C:/Apache/commons-dbcp2-2.1.1/commons-dbcp2-2.1.1.jar"
		";C:/Apache/commons-pool2-2.4.2/commons-pool2-2.4.2.jar"
		";C:/Apache/commons-logging-1.2/commons-logging-1.2.jar"
		";C:/Jconnectors/mysql-connector-java-6.0.2-bin.jar"
		";C:/Jconnectors/mariadb-java-client-2.0.1.jar"
		";C:/Jconnectors/sqljdbc42.jar");
#endif   // DEVELOPMENT
} // end of AddJars

/***********************************************************************/
/*  Connect: connect to a data source.                                 */
/***********************************************************************/
bool JDBConn::Connect(PJPARM sop)
{
	bool		 err = false;
	jint     rc;
	PGLOBAL& g = m_G;

	/*******************************************************************/
	/*  Create or attach a JVM. 																			 */
	/*******************************************************************/
	if (Open(g))
		return true;

	if (!sop)						 // DRIVER catalog table
		return false;

	jmethodID cid = nullptr;

	if (gmID(g, cid, "JdbcConnect", "([Ljava/lang/String;IZ)I"))
		return true;

	// Build the java string array
	jobjectArray parms = env->NewObjectArray(4,    // constructs java array of 4
		env->FindClass("java/lang/String"), NULL);   // Strings

	m_Scrollable = sop->Scrollable;
	m_RowsetSize = sop->Fsize;
	//m_LoginTimeout = sop->Cto;
	//m_QueryTimeout = sop->Qto;
	//m_UseCnc = sop->UseCnc;

	// change some elements
	if (sop->Driver)
		env->SetObjectArrayElement(parms, 0, env->NewStringUTF(sop->Driver));

	if (sop->Url)
		env->SetObjectArrayElement(parms, 1, env->NewStringUTF(sop->Url));

	if (sop->User)
		env->SetObjectArrayElement(parms, 2, env->NewStringUTF(sop->User));

	if (sop->Pwd)
		env->SetObjectArrayElement(parms, 3, env->NewStringUTF(sop->Pwd));

	// call method
	rc = env->CallIntMethod(job, cid, parms, m_RowsetSize, m_Scrollable);
	err = Check(rc);
	env->DeleteLocalRef(parms);				 	// Not used anymore

	if (err) {
		snprintf(g->Message, sizeof(g->Message), "Connecting: %s rc=%d", Msg, (int)rc);
		return true;
	}	// endif Msg

	jmethodID qcid = nullptr;

	if (!gmID(g, qcid, "GetQuoteString", "()Ljava/lang/String;")) {
		jstring s = (jstring)env->CallObjectMethod(job, qcid);

		if (s != nullptr) {
			char *qch = GetUTFString(s);
			m_IDQuoteChar[0] = *qch;
		} else {
			s = (jstring)env->CallObjectMethod(job, errid);
			Msg = GetUTFString(s);
		}	// endif s

	}	// endif qcid

	if (gmID(g, typid, "ColumnType", "(ILjava/lang/String;)I"))
		return true;
	else
		m_Connected = true;

	return false;
} // end of Connect


/***********************************************************************/
/*  Execute an SQL command.                                            */
/***********************************************************************/
int JDBConn::ExecuteCommand(PCSZ sql)
{
	int      rc;
	jint     n;
	jstring  qry;
	PGLOBAL& g = m_G;

	// Get the methods used to execute a query and get the result
	if (gmID(g, xid, "Execute", "(Ljava/lang/String;)I") ||
			gmID(g, grs, "GetResult", "()I"))
		return RC_FX;

	qry = env->NewStringUTF(sql);
	n = env->CallIntMethod(job, xid, qry);
	env->DeleteLocalRef(qry);

	if (Check(n)) {
		snprintf(g->Message, sizeof(g->Message), "Execute: %s", Msg);
		return RC_FX;
	} // endif n

	m_Ncol = env->CallIntMethod(job, grs);

	if (Check(m_Ncol)) {
		snprintf(g->Message, sizeof(g->Message), "GetResult: %s", Msg);
		rc = RC_FX;
	} else if (m_Ncol) {
		snprintf(g->Message, sizeof(g->Message), "Result set column number");
		rc = RC_OK;						// A result set was returned
	} else {
		m_Aff = (int)n;			  // Affected rows
		snprintf(g->Message, sizeof(g->Message), "Affected rows");
		rc = RC_NF;
	} // endif ncol

	return rc;
} // end of ExecuteCommand

/***********************************************************************/
/*  Fetch next row.                                                    */
/***********************************************************************/
int JDBConn::Fetch(int pos)
{
	jint     rc = JNI_ERR;
	PGLOBAL& g = m_G;

	if (m_Full)						// Result set has one row
		return 1;

	if (pos) {
		if (!m_Scrollable) {
			snprintf(g->Message, sizeof(g->Message), "Cannot fetch(pos) if FORWARD ONLY");
		  return rc;
		} else if (gmID(m_G, fetchid, "Fetch", "(I)Z"))
			return rc;

		if (env->CallBooleanMethod(job, fetchid, pos))
			rc = m_Rows;

	} else {
		if (gmID(g, readid, "ReadNext", "()I"))
			return rc;

		rc = env->CallBooleanMethod(job, readid);

		if (!Check(rc)) {
			if (rc == 0)
				m_Full = (m_Fetch == 1);
			else
				m_Fetch++;

			m_Rows += (int)rc;
		} else
			snprintf(g->Message, sizeof(g->Message), "Fetch: %s", Msg);

	} // endif pos

	return (int)rc;
} // end of Fetch

/***********************************************************************/
/*  Restart from beginning of result set                               */
/***********************************************************************/
int JDBConn::Rewind(PCSZ sql)
{
	int rbuf = -1;

	if (m_Full)
		rbuf = m_Rows;           // No need to "rewind"
	else if (m_Scrollable) {
		if (gmID(m_G, fetchid, "Fetch", "(I)Z"))
			return -1;

		(void) env->CallBooleanMethod(job, fetchid, 0);

		rbuf = m_Rows;
	} else if (ExecuteCommand(sql) != RC_FX)
		rbuf = 0;

	return rbuf;
} // end of Rewind

/***********************************************************************/
/*  Retrieve and set the column value from the result set.             */
/***********************************************************************/
void JDBConn::SetColumnValue(int rank, PSZ name, PVAL val)
{
	const char *field;
	PGLOBAL& g = m_G;
	jint     ctyp;
	jstring  cn, jn = nullptr;
	jobject  jb = nullptr;

	if (rank == 0)
		if (!name || (jn = env->NewStringUTF(name)) == nullptr) {
			snprintf(g->Message, sizeof(g->Message), "Fail to allocate jstring %s", SVP(name));
			throw (int)TYPE_AM_JDBC;
		}	// endif name

	// Returns 666 is case of error
	ctyp = env->CallIntMethod(job, typid, rank, jn);

	if (Check((ctyp == 666) ? -1 : 1)) {
		snprintf(g->Message, sizeof(g->Message), "Getting ctyp: %s", Msg);
		throw (int)TYPE_AM_JDBC;
	} // endif Check

	if (val->GetNullable())
		if (!gmID(g, objfldid, "ObjectField", "(ILjava/lang/String;)Ljava/lang/Object;")) {
			jb = env->CallObjectMethod(job, objfldid, (jint)rank, jn);

			if (Check(0)) {
				snprintf(g->Message, sizeof(g->Message), "Getting jp: %s", Msg);
				throw (int)TYPE_AM_JDBC;
			} // endif Check

			if (jb == nullptr) {
				val->Reset();
				val->SetNull(true);
				goto chk;
			}	// endif job

		}	// endif objfldid

	switch (ctyp) {
	case 12:          // VARCHAR
	case -9:          // NVARCHAR
	case -1:          // LONGVARCHAR, TEXT
	case 1:           // CHAR
	case -15:         // NCHAR
	case -16:         // LONGNVARCHAR, NTEXT
	case 3:           // DECIMAL
	case -8:          // ROWID
		if (jb && ctyp != 3)
			cn = (jstring)jb;
		else if (!gmID(g, chrfldid, "StringField", "(ILjava/lang/String;)Ljava/lang/String;"))
			cn = (jstring)env->CallObjectMethod(job, chrfldid, (jint)rank, jn);
		else
			cn = nullptr;

		if (cn) {
			field = GetUTFString(cn);
			val->SetValue_psz((PSZ)field);
		} else
			val->Reset();

		break;
	case 4:           // INTEGER
	case 5:           // SMALLINT
	case -6:          // TINYINT
	case 16:          // BOOLEAN
	case -7:          // BIT
		if (!gmID(g, intfldid, "IntField", "(ILjava/lang/String;)I"))
			val->SetValue((int)env->CallIntMethod(job, intfldid, rank, jn));
		else
			val->Reset();

		break;
	case 8:           // DOUBLE
	case 2:           // NUMERIC
//case 3:           // DECIMAL
		if (!gmID(g, dblfldid, "DoubleField", "(ILjava/lang/String;)D"))
			val->SetValue((double)env->CallDoubleMethod(job, dblfldid, rank, jn));
		else
			val->Reset();

		break;
	case 7:           // REAL
	case 6:           // FLOAT
		if (!gmID(g, fltfldid, "FloatField", "(ILjava/lang/String;)F"))
			val->SetValue((float)env->CallFloatMethod(job, fltfldid, rank, jn));
		else
			val->Reset();

		break;
	case 91:          // DATE
		if (!gmID(g, datfldid, "DateField", "(ILjava/lang/String;)I")) {
			val->SetValue((int)env->CallIntMethod(job, datfldid, (jint)rank, jn));
		} else
			val->Reset();

		break;
	case 92:          // TIME
		if (!gmID(g, timfldid, "TimeField", "(ILjava/lang/String;)I")) {
			val->SetValue((int)env->CallIntMethod(job, timfldid, (jint)rank, jn));
		} else
			val->Reset();

		break;
	case 93:          // TIMESTAMP
		if (!gmID(g, tspfldid, "TimestampField", "(ILjava/lang/String;)I")) {
			val->SetValue((int)env->CallIntMethod(job, tspfldid, (jint)rank, jn));
		} else
			val->Reset();

		break;
	case -5:          // BIGINT
		if (!gmID(g, bigfldid, "BigintField", "(ILjava/lang/String;)J"))
			val->SetValue((long long)env->CallLongMethod(job, bigfldid, (jint)rank, jn));
		else
			val->Reset();

		break;
		/*			case java.sql.Types.SMALLINT:
		System.out.print(jdi.IntField(i));
		break;
		case java.sql.Types.BOOLEAN:
		System.out.print(jdi.BooleanField(i)); */
	case 1111:				// UUID
		if (!gmID(g, uidfldid, "UuidField", "(ILjava/lang/String;)Ljava/lang/String;"))
			cn = (jstring)env->CallObjectMethod(job, uidfldid, (jint)rank, jn);
		else
			cn = nullptr;

		if (cn) {
			val->SetValue_psz((PSZ)GetUTFString(cn));
		} else
			val->Reset();

		break;
	case 0:						// NULL
		val->SetNull(true);
                /* fall through */
	default:
		val->Reset();
	} // endswitch Type

 chk:
	if (Check()) {
		if (rank == 0)
			env->DeleteLocalRef(jn);

		snprintf(g->Message, sizeof(g->Message), "SetColumnValue: %s rank=%d ctyp=%d", Msg, rank, (int)ctyp);
		throw (int)TYPE_AM_JDBC;
	} // endif Check

	if (rank == 0)
		env->DeleteLocalRef(jn);

} // end of SetColumnValue

/***********************************************************************/
/*  Prepare an SQL statement for insert.                               */
/***********************************************************************/
bool JDBConn::PrepareSQL(PCSZ sql)
{
	bool		 b = true;
	PGLOBAL& g = m_G;

	if (!gmID(g, prepid, "CreatePrepStmt", "(Ljava/lang/String;)I")) {
		// Create the prepared statement
		jstring qry = env->NewStringUTF(sql);

		if (Check(env->CallBooleanMethod(job, prepid, qry)))
			snprintf(g->Message, sizeof(g->Message), "CreatePrepStmt: %s", Msg);
		else
			b = false;

		env->DeleteLocalRef(qry);
	} // endif prepid

	return b;
} // end of PrepareSQL

/***********************************************************************/
/*  Execute an SQL query that returns a result set.                    */
/***********************************************************************/
int JDBConn::ExecuteQuery(PCSZ sql)
{
	int			 rc = RC_FX;
	jint     ncol;
	jstring  qry;
	PGLOBAL& g = m_G;

	// Get the methods used to execute a query and get the result
	if (!gmID(g, xqid, "ExecuteQuery", "(Ljava/lang/String;)I")) {
		qry = env->NewStringUTF(sql);
		ncol = env->CallIntMethod(job, xqid, qry);

		if (!Check(ncol)) {
			m_Ncol = (int)ncol;
			m_Aff = 0;			  // Affected rows
			rc = RC_OK;
		} else
			snprintf(g->Message, sizeof(g->Message), "ExecuteQuery: %s", Msg);

		env->DeleteLocalRef(qry);
	} // endif xqid

	return rc;
} // end of ExecuteQuery

/***********************************************************************/
/*  Execute an SQL query and get the affected rows.                    */
/***********************************************************************/
int JDBConn::ExecuteUpdate(PCSZ sql)
{
	int      rc = RC_FX;
	jint     n;
	jstring  qry;
	PGLOBAL& g = m_G;

	// Get the methods used to execute a query and get the affected rows
	if (!gmID(g, xuid, "ExecuteUpdate", "(Ljava/lang/String;)I")) {
		qry = env->NewStringUTF(sql);
		n = env->CallIntMethod(job, xuid, qry);

		if (!Check(n)) {
			m_Ncol = 0;
			m_Aff = (int)n;			  // Affected rows
			rc = RC_OK;
		} else
			snprintf(g->Message, sizeof(g->Message), "ExecuteUpdate: %s n=%d", Msg, n);

		env->DeleteLocalRef(qry);
	} // endif xuid

	return rc;
} // end of ExecuteUpdate

/***********************************************************************/
/*  Get the number of lines of the result set.                         */
/***********************************************************************/
int JDBConn::GetResultSize(PCSZ sql, PCOL colp)
{
	int rc;

	if ((rc = ExecuteQuery(sql)) != RC_OK)
		return -1;

	if ((rc = Fetch()) > 0) {
		try {
			SetColumnValue(1, NULL, colp->GetValue());
		}	catch (...) {
			return -4;
		} // end catch

	} else
		return -2;

	if ((rc = Fetch()) != 0)
		return -3;
 
	m_Full = false;
	return colp->GetIntValue();
} // end of GetResultSize

/***********************************************************************/
/*  Execute a prepared statement.                                      */
/***********************************************************************/
int JDBConn::ExecuteSQL(void)
{
	int rc = RC_FX;
	PGLOBAL& g = m_G;

	// Get the methods used to execute a prepared statement
	if (!gmID(g, xpid, "ExecutePrep", "()I")) {
		jint n = env->CallIntMethod(job, xpid);

		if (n == -3)
			snprintf(g->Message, sizeof(g->Message), "SQL statement is not prepared");
		else if (Check(n))
			snprintf(g->Message, sizeof(g->Message), "ExecutePrep: %s", Msg);
		else {
			m_Aff = (int)n;
			rc = RC_OK;
		} // endswitch n

	} // endif xpid

	return rc;
} // end of ExecuteSQL

/***********************************************************************/
/*  Set a parameter for inserting.                                     */
/***********************************************************************/
bool JDBConn::SetParam(JDBCCOL *colp)
{
	PGLOBAL&   g = m_G;
	bool       rc = false;
	PVAL       val = colp->GetValue();
	jint       n, jrc = 0, i = (jint)colp->GetRank();
	jshort     s;
	jlong      lg;
//jfloat     f;
	jdouble    d;
	jclass     dat;
	jobject    datobj;
	jstring    jst = nullptr;
	jmethodID  dtc, setid = nullptr;

	if (val->GetNullable() && val->IsNull()) {
		if (gmID(g, setid, "SetNullParm", "(II)I"))
			return true;

		jrc = env->CallIntMethod(job, setid, i, 
			(colp->uuid ? 1111 : (jint)GetJDBCType(val->GetType())));
	} else if (colp->uuid) {
		if (gmID(g, setid, "SetUuidParm", "(ILjava/lang/String;)V"))
			return true;

		jst = env->NewStringUTF(val->GetCharValue());
		env->CallVoidMethod(job, setid, i, jst);
	} else switch (val->GetType()) {
		case TYPE_STRING:
			if (gmID(g, setid, "SetStringParm", "(ILjava/lang/String;)V"))
				return true;

			jst = env->NewStringUTF(val->GetCharValue());
			env->CallVoidMethod(job, setid, i, jst);
			break;
		case TYPE_INT:
			if (gmID(g, setid, "SetIntParm", "(II)V"))
				return true;

			n = (jint)val->GetIntValue();
			env->CallVoidMethod(job, setid, i, n);
			break;
		case TYPE_TINY:
		case TYPE_SHORT:
			if (gmID(g, setid, "SetShortParm", "(IS)V"))
				return true;

			s = (jshort)val->GetShortValue();
			env->CallVoidMethod(job, setid, i, s);
			break;
		case TYPE_BIGINT:
			if (gmID(g, setid, "SetBigintParm", "(IJ)V"))
				return true;

			lg = (jlong)val->GetBigintValue();
			env->CallVoidMethod(job, setid, i, lg);
			break;
		case TYPE_DOUBLE:
		case TYPE_DECIM:
			if (gmID(g, setid, "SetDoubleParm", "(ID)V"))
				return true;

			d = (jdouble)val->GetFloatValue();
			env->CallVoidMethod(job, setid, i, d);
			break;
		case TYPE_DATE:
			if ((dat = env->FindClass("java/sql/Timestamp")) == nullptr) {
				snprintf(g->Message, sizeof(g->Message), "Cannot find Timestamp class");
				return true;
			} else if (!(dtc = env->GetMethodID(dat, "<init>", "(J)V"))) {
				snprintf(g->Message, sizeof(g->Message), "Cannot find Timestamp class constructor");
				return true;
			}	// endif's

			lg = (jlong)val->GetBigintValue() * 1000;

			if ((datobj = env->NewObject(dat, dtc, lg)) == nullptr) {
				snprintf(g->Message, sizeof(g->Message), "Cannot make Timestamp object");
				return true;
			} else if (gmID(g, setid, "SetTimestampParm", "(ILjava/sql/Timestamp;)V"))
				return true;

			env->CallVoidMethod(job, setid, i, datobj);
			break;
		default:
			snprintf(g->Message, sizeof(g->Message), "Parm type %d not supported", val->GetType());
			return true;
		}	// endswitch Type

	if (Check(jrc)) {
		snprintf(g->Message, sizeof(g->Message), "SetParam: col=%s msg=%s", colp->GetName(), Msg);
		rc = true;
	} // endif msg

	if (jst)
		env->DeleteLocalRef(jst);

	return rc;
	} // end of SetParam

	/***********************************************************************/
	/*  Get the list of Drivers and set it in qrp.                         */
	/***********************************************************************/
	bool JDBConn::GetDrivers(PQRYRES qrp)
	{
		PSZ       sval;
		int       i, n, size;
		PCOLRES   crp;
		jstring   js;
		jmethodID gdid = nullptr;
		
		if (gmID(m_G, gdid, "GetDrivers", "([Ljava/lang/String;I)I"))
			return true;

		// Build the java string array
		jobjectArray s = env->NewObjectArray(4 * qrp->Maxres,
			env->FindClass("java/lang/String"), NULL);

		size = env->CallIntMethod(job, gdid, s, qrp->Maxres);

		for (i = 0, n = 0; i < size; i++) {
			crp = qrp->Colresp;
			js = (jstring)env->GetObjectArrayElement(s, n++);
			sval = GetUTFString(js);
			crp->Kdata->SetValue(sval, i);
			crp = crp->Next;
			js = (jstring)env->GetObjectArrayElement(s, n++);
			sval = GetUTFString(js);
			crp->Kdata->SetValue(sval, i);
			crp = crp->Next;
			js = (jstring)env->GetObjectArrayElement(s, n++);
			sval = GetUTFString(js);
			crp->Kdata->SetValue(sval, i);
			crp = crp->Next;
			js = (jstring)env->GetObjectArrayElement(s, n++);
			sval = GetUTFString(js);
			crp->Kdata->SetValue(sval, i);
		}	// endfor i

		// Not used anymore
		env->DeleteLocalRef(s);

		qrp->Nblin = size;
		return false;
	} // end of GetDrivers

	/**************************************************************************/
	/*  GetMetaData: constructs the result blocks containing the              */
	/*  description of all the columns of an SQL command.                     */
	/**************************************************************************/
	PQRYRES JDBConn::GetMetaData(PGLOBAL g, PCSZ src)
	{
		static int  buftyp[] = {TYPE_STRING, TYPE_INT, TYPE_INT,
			                      TYPE_INT,    TYPE_INT};
		static XFLD fldtyp[] = {FLD_NAME,  FLD_TYPE, FLD_PREC,
			                      FLD_SCALE, FLD_NULL };
		static unsigned int length[] = {0, 6, 10, 6, 6};
		const char *name;
		int     len, qcol = 5;
		PQRYRES qrp = NULL;
		PCOLRES crp;
		ushort  i;
		jint   *n = nullptr;
		jstring label;
		jmethodID colid = nullptr;
		int     rc = ExecuteCommand(src);

		if (rc == RC_NF) {
			snprintf(g->Message, sizeof(g->Message), "Srcdef is not returning a result set");
			return NULL;
		} else if ((rc) == RC_FX) {
			return NULL;
		} else if (m_Ncol == 0) {
			snprintf(g->Message, sizeof(g->Message), "Invalid Srcdef");
			return NULL;
		} // endif's

		if (gmID(g, colid, "ColumnDesc", "(I[I)Ljava/lang/String;"))
			return NULL;

		// Get max column name length
		len = GetMaxValue(5);
		length[0] = (len > 0) ? len + 1 : 128;

		/************************************************************************/
		/*  Allocate the structures used to refer to the result set.            */
		/************************************************************************/
		if (!(qrp = PlgAllocResult(g, qcol, m_Ncol, IDS_COLUMNS + 3,
			buftyp, fldtyp, length, false, true)))
			return NULL;

		// Some columns must be renamed
		for (i = 0, crp = qrp->Colresp; crp; crp = crp->Next)
			switch (++i) {
			case 3: crp->Name = "Precision"; break;
			case 4: crp->Name = "Scale";     break;
			case 5: crp->Name = "Nullable";  break;
		} // endswitch i

		// Build the java int array
		jintArray val = env->NewIntArray(4);

		if (val == nullptr) {
			strcpy(m_G->Message, "Cannot allocate jint array");
			return NULL;
		} // endif colid

		/************************************************************************/
		/*  Now get the results into blocks.                                    */
		/************************************************************************/
		for (i = 0; i < m_Ncol; i++) {
			if (!(label = (jstring)env->CallObjectMethod(job, colid, i + 1, val))) {
				if (Check())
					snprintf(g->Message, sizeof(g->Message), "ColumnDesc: %s", Msg);
				else
					snprintf(g->Message, sizeof(g->Message), "No result metadata");

				env->ReleaseIntArrayElements(val, n, 0);
				return NULL;
			} // endif label

			name = GetUTFString(label);
			crp = qrp->Colresp;                    // Column_Name
			crp->Kdata->SetValue((char*)name, i);
			n = env->GetIntArrayElements(val, 0);
			crp = crp->Next;                       // Data_Type
			crp->Kdata->SetValue((int)n[0], i);
			crp = crp->Next;                       // Precision (length)
			crp->Kdata->SetValue((int)n[1], i);
			crp = crp->Next;                       // Scale
			crp->Kdata->SetValue((int)n[2], i);
			crp = crp->Next;                       // Nullable
			crp->Kdata->SetValue((int)n[3], i);
			qrp->Nblin++;
		} // endfor i

		/* Cleanup */
		env->ReleaseIntArrayElements(val, n, 0);

		/************************************************************************/
		/*  Return the result pointer for use by GetData routines.              */
		/************************************************************************/
		return qrp;
	} // end of GetMetaData

	/***********************************************************************/
	/*  Allocate recset and call SQLTables, SQLColumns or SQLPrimaryKeys.  */
	/***********************************************************************/
	int JDBConn::GetCatInfo(JCATPARM *cap)
	{
		PGLOBAL& g = m_G;
//	void    *buffer;
		int      i, ncol;
		PCSZ     fnc = "Unknown";
		uint     n;
		short    len, tp;
		PQRYRES  qrp = cap->Qrp;
		PCOLRES  crp;
		jboolean rc = false;
//	HSTMT    hstmt = NULL;
//	SQLLEN  *vl, *vlen = NULL;
		PVAL    *pval = NULL;
		char*   *pbuf = NULL;
		jobjectArray parms;
		jmethodID    catid = nullptr;

		if (qrp->Maxres <= 0)
			return 0;				 			// 0-sized result"

		SQLQualifiedName name(cap);

		// Build the java string array
		parms = env->NewObjectArray(4, env->FindClass("java/lang/String"), NULL);
		env->SetObjectArrayElement(parms, 0, env->NewStringUTF(name.ptr(2)));
		env->SetObjectArrayElement(parms, 1, env->NewStringUTF(name.ptr(1)));
		env->SetObjectArrayElement(parms, 2, env->NewStringUTF(name.ptr(0)));
		env->SetObjectArrayElement(parms, 3, env->NewStringUTF((const char*)cap->Pat));

		// Now do call the proper JDBC API
		switch (cap->Id) {
		case JCAT_COL:
			fnc = "GetColumns";
			break;
		case JCAT_TAB:
			fnc = "GetTables";
			break;
#if 0
		case JCAT_KEY:
			fnc = "SQLPrimaryKeys";
			rc = SQLPrimaryKeys(hstmt, name.ptr(2), name.length(2),
				name.ptr(1), name.length(1),
				name.ptr(0), name.length(0));
			break;
#endif // 0
		default:
			snprintf(g->Message, sizeof(g->Message), "Invalid SQL function id");
			return -1;
		} // endswitch infotype

		if (gmID(g, catid, fnc, "([Ljava/lang/String;)I"))
			return -1;

		// call method
		ncol = env->CallIntMethod(job, catid, parms);

		if (Check(ncol)) {
			snprintf(g->Message, sizeof(g->Message), "%s: %s", fnc, Msg);
			env->DeleteLocalRef(parms);
			return -1;
		}	// endif Check

		// Not used anymore
		env->DeleteLocalRef(parms);

    if (trace(1))
      htrc("Method %s returned %d columns\n", fnc, ncol);

		// n because we no more ignore the first column
		if ((n = qrp->Nbcol) > (uint)ncol) {
			snprintf(g->Message, sizeof(g->Message), MSG(COL_NUM_MISM));
			return -1;
		} // endif n

		// Unconditional to handle STRBLK's
		pval = (PVAL *)PlugSubAlloc(g, NULL, n * sizeof(PVAL));
//	vlen = (SQLLEN *)PlugSubAlloc(g, NULL, n * sizeof(SQLLEN));
		pbuf = (char**)PlugSubAlloc(g, NULL, n * sizeof(char*));

		// Prepare retrieving column values
		for (n = 0, crp = qrp->Colresp; crp; crp = crp->Next) {
			if (!(tp = GetJDBCType(crp->Type))) {
				snprintf(g->Message, sizeof(g->Message), MSG(INV_COLUMN_TYPE), crp->Type, crp->Name);
				return -1;
			} // endif tp

			if (!(len = GetTypeSize(crp->Type, crp->Length))) {
				len = 255;           // for STRBLK's
				((STRBLK*)crp->Kdata)->SetSorted(true);
			} // endif len

			pval[n] = AllocateValue(g, crp->Type, len);
			pval[n]->SetNullable(true);

			if (crp->Type == TYPE_STRING) {
				pbuf[n] = (char*)PlugSubAlloc(g, NULL, len);
//			buffer = pbuf[n];
      } // endif Type
//		} else
//			buffer = pval[n]->GetTo_Val();

			n++;
		} // endfor n

		// Now fetch the result
		for (i = 0; i < qrp->Maxres; i++) {
			if (Check(rc = Fetch(0))) {
				snprintf(g->Message, sizeof(g->Message), "Fetch: %s", Msg);
				return -1;
			} if (rc == 0) {
        if (trace(1))
          htrc("End of fetches i=%d\n", i);

				break;
			} // endif rc

			for (n = 0, crp = qrp->Colresp; crp; n++, crp = crp->Next) {
				SetColumnValue(n + 1, nullptr, pval[n]);
				crp->Kdata->SetValue(pval[n], i);
			}	// endfor n

		} // endfor i

		if (rc > 0)
			qrp->Truncated = true;

		return i;
	} // end of GetCatInfo

	/***********************************************************************/
	/*  Allocate a CONNECT result structure from the JDBC result.          */
	/***********************************************************************/
	PQRYRES JDBConn::AllocateResult(PGLOBAL g, PTDB tdbp)
	{
		bool         uns;
		PCOL         colp;
		PCOLRES     *pcrp, crp;
		PQRYRES      qrp;

		if (!m_Rows) {
			snprintf(g->Message, sizeof(g->Message), "Void result");
			return NULL;
		} // endif m_Rows

		/*********************************************************************/
		/*  Allocate the result storage for future retrieval.                */
		/*********************************************************************/
		qrp = (PQRYRES)PlugSubAlloc(g, NULL, sizeof(QRYRES));
		pcrp = &qrp->Colresp;
		qrp->Continued = FALSE;
		qrp->Truncated = FALSE;
		qrp->Info = FALSE;
		qrp->Suball = TRUE;
		qrp->BadLines = 0;
		qrp->Maxsize = m_Rows;
		qrp->Maxres = m_Rows;
		qrp->Nbcol = 0;
		qrp->Nblin = 0;
		qrp->Cursor = 0;

		for (colp = tdbp->GetColumns(); colp; colp = colp->GetNext())
			if (!colp->IsSpecial()) {
				*pcrp = (PCOLRES)PlugSubAlloc(g, NULL, sizeof(COLRES));
				crp = *pcrp;
				pcrp = &crp->Next;
				memset(crp, 0, sizeof(COLRES));
				crp->Ncol = ++qrp->Nbcol;
				crp->Name = colp->GetName();
				crp->Type = colp->GetResultType();
				crp->Prec = colp->GetScale();
				crp->Length = colp->GetLength();
				crp->Clen = colp->GetValue()->GetClen();
				uns = colp->IsUnsigned();

				if (!(crp->Kdata = AllocValBlock(g, NULL, crp->Type, m_Rows,
					crp->Clen, 0, FALSE, TRUE, uns))) {
					snprintf(g->Message, sizeof(g->Message), MSG(INV_RESULT_TYPE),
						GetFormatType(crp->Type));
					return NULL;
				} // endif Kdata

				if (!colp->IsNullable())
					crp->Nulls = NULL;
				else {
					crp->Nulls = (char*)PlugSubAlloc(g, NULL, m_Rows);
					memset(crp->Nulls, ' ', m_Rows);
				} // endelse Nullable

				((EXTCOL*)colp)->SetCrp(crp);
			} // endif colp

		*pcrp = NULL;
		return qrp;
	} // end of AllocateResult
