/********** PlgDBUtl Fpe C++ Program Source Code File (.CPP) ***********/
/* PROGRAM NAME: PLGDBUTL                                              */
/* -------------                                                       */
/*  Version 4.1                                                        */
/*                                                                     */
/* COPYRIGHT:                                                          */
/* ----------                                                          */
/*  (C) Copyright to the author Olivier BERTRAND          1998-2020    */
/*                                                                     */
/* WHAT THIS PROGRAM DOES:                                             */
/* -----------------------                                             */
/*  Utility functions used by DB semantic routines.                    */
/*                                                                     */
/* WHAT YOU NEED TO COMPILE THIS PROGRAM:                              */
/* --------------------------------------                              */
/*                                                                     */
/*  REQUIRED FILES:                                                    */
/*  ---------------                                                    */
/*  See Readme.C for a list and description of required SYSTEM files.  */
/*                                                                     */
/*    PLGDBUTL.C     - Source code                                     */
/*    GLOBAL.H       - Global declaration file                         */
/*    PLGDBSEM.H     - DB application declaration file                 */
/*                                                                     */
/*  REQUIRED LIBRARIES:                                                */
/*  -------------------                                                */
/*    OS2.LIB        - OS2 libray                                      */
/*    LLIBCE.LIB     - Protect mode/standard combined large model C    */
/*                     library                                         */
/*                                                                     */
/*  REQUIRED PROGRAMS:                                                 */
/*  ------------------                                                 */
/*    IBM, MS, Borland or GNU C++ Compiler                             */
/*    IBM, MS, Borland or GNU Linker                                   */
/***********************************************************************/

/***********************************************************************/
/*  Include relevant MariaDB header file.                              */
/***********************************************************************/
#include "my_global.h"
#include "my_pthread.h"
#if defined(_WIN32)
#include <io.h>
#include <fcntl.h>
#include <errno.h>
#define BIGMEM         1048576            // 1 Megabyte
#else     // !_WIN32
#include <unistd.h>
#include <fcntl.h>
//#if defined(THREAD)
#include <pthread.h>
//#endif   // THREAD
#include <stdarg.h>
#define BIGMEM      2147483647            // Max int value
#endif    // !_WIN32
#include <locale.h>

/***********************************************************************/
/*  Include application header files                                   */
/***********************************************************************/
#include "global.h"    // header containing all global declarations.
#include "plgdbsem.h"  // header containing the DB applic. declarations.
#include "preparse.h"  // For DATPAR
#include "osutil.h"
#include "maputil.h"
#include "catalog.h"
#include "colblk.h"
#include "xtable.h"    // header of TBX, TDB and TDBASE classes
#include "tabcol.h"    // header of XTAB and COLUMN classes
#include "valblk.h"
#include "rcmsg.h"
#ifdef ZIP_SUPPORT
#include "filamzip.h"
#endif // ZIP_SUPPORT
#ifdef JAVA_SUPPORT
#include "javaconn.h"
#endif // JAVA_SUPPORT
#ifdef CMGO_SUPPORT
#include "cmgoconn.h"
#endif // JAVA_SUPPORT

/***********************************************************************/
/*  DB static variables.                                               */
/***********************************************************************/
bool  Initdone = false;
bool  plugin = false;  // True when called by the XDB plugin handler 

extern "C" {
extern char version[];
} // extern "C"

//#if defined(_WIN32)
//extern CRITICAL_SECTION parsec;      // Used calling the Flex parser
//#else   // !_WIN32
extern pthread_mutex_t parmut;
//#endif  // !_WIN32

// The debug trace used by the main thread
FILE *pfile = NULL;

MBLOCK Nmblk = {NULL, false, 0, false, NULL};   // Used to init MBLOCK's

/***********************************************************************/
/*  Routines called externally and internally by utility routines.     */
/***********************************************************************/
bool PlugEvalLike(PGLOBAL, LPCSTR, LPCSTR, bool);
bool EvalLikePattern(LPCSTR, LPCSTR);
void PlugConvertConstant(PGLOBAL, void* &, short&);

#ifdef DOMDOC_SUPPORT
void CloseXMLFile(PGLOBAL, PFBLOCK, bool);
#endif   // DOMDOC_SUPPORT

#ifdef LIBXML2_SUPPORT
#include "libdoc.h"
#endif   // LIBXML2_SUPPORT

#ifdef ODBC_SUPPORT
void OdbcClose(PGLOBAL g, PFBLOCK fp);
#endif   // ODBC_SUPPORT

/***********************************************************************/
/* Routines for file IO with error reporting to g->Message             */
/* Note: errno and strerror must be called before the message file     */
/* is read in the case of XMSG compile.                                */
/***********************************************************************/
static void global_open_error_msg(GLOBAL *g, int msgid, const char *path, 
                                                        const char *mode)
{
  int  len, rno= (int)errno;
  char errmsg[256]= "";

  strncat(errmsg, strerror(errno), 255);

  switch (msgid)
  {
    case MSGID_CANNOT_OPEN:
      len= snprintf(g->Message, sizeof(g->Message) - 1,
                    MSG(CANNOT_OPEN), // Cannot open %s
                    path);
      break;

    case MSGID_OPEN_MODE_ERROR:
      len= snprintf(g->Message, sizeof(g->Message) - 1,
                    MSG(OPEN_MODE_ERROR), // "Open(%s) error %d on %s"
                    mode, rno, path);
      break;

    case MSGID_OPEN_MODE_STRERROR:
      {char fmt[256];
      strcat(strcpy(fmt, MSG(OPEN_MODE_ERROR)), ": %s");
      len= snprintf(g->Message, sizeof(g->Message) - 1,
                    fmt, // Open(%s) error %d on %s: %s
                    mode, rno, path, errmsg);
      }break;

    case MSGID_OPEN_STRERROR:
      len= snprintf(g->Message, sizeof(g->Message) - 1,
                    MSG(OPEN_STRERROR), // "open error: %s"
                    errmsg);
      break;

    case MSGID_OPEN_ERROR_AND_STRERROR:
      len= snprintf(g->Message, sizeof(g->Message) - 1,
                    //OPEN_ERROR does not work, as it wants mode %d (not %s)
                    //MSG(OPEN_ERROR) "%s",// "Open error %d in mode %d on %s: %s"
                    "Open error %d in mode %s on %s: %s",
                    rno, mode, path, errmsg);
      break;

    case MSGID_OPEN_EMPTY_FILE:
      len= snprintf(g->Message, sizeof(g->Message) - 1,
                    MSG(OPEN_EMPTY_FILE), // "Opening empty file %s: %s"
                    path, errmsg);
      break;

    default:
      DBUG_ASSERT(0);
      /* Fall through*/
    case 0:
      len= 0;
  }
  g->Message[len]= '\0';
}


FILE *global_fopen(GLOBAL *g, int msgid, const char *path, const char *mode)
{
  FILE *f;
  if (!(f= fopen(path, mode)))
    global_open_error_msg(g, msgid, path, mode);
  return f;
}


int global_open(GLOBAL *g, int msgid, const char *path, int flags)
{
  int h;
  if ((h= open(path, flags)) <= 0)
    global_open_error_msg(g, msgid, path, "");
  return h;
}


int global_open(GLOBAL *g, int msgid, const char *path, int flags, int mode)
{
  int h;
  if ((h= open(path, flags, mode)) <= 0)
  {
    char modestr[64];
    snprintf(modestr, sizeof(modestr), "%d", mode);
    global_open_error_msg(g, msgid, path, modestr);
  }
  return h;
}

DllExport void SetTrc(void)
{
  // If tracing is on, debug must be initialized.
  debug = pfile;
} // end of SetTrc

/**************************************************************************/
/*  SubAllocate the result structure that will contain result data.       */
/**************************************************************************/
PQRYRES PlgAllocResult(PGLOBAL g, int ncol, int maxres, int ids,
                       int *buftyp, XFLD *fldtyp, 
                       unsigned int *length, bool blank, bool nonull)
{
  char     cname[NAM_LEN+1];
  int      i;
  PCOLRES *pcrp, crp;
  PQRYRES  qrp;

	try {
		/**********************************************************************/
		/*  Allocate the structure used to contain the result set.            */
		/**********************************************************************/
		qrp = (PQRYRES)PlugSubAlloc(g, NULL, sizeof(QRYRES));
		pcrp = &qrp->Colresp;
		qrp->Continued = false;
		qrp->Truncated = false;
		qrp->Info = false;
		qrp->Suball = true;
		qrp->Maxres = maxres;
		qrp->Maxsize = 0;
		qrp->Nblin = 0;
		qrp->Nbcol = 0;                                     // will be ncol
		qrp->Cursor = 0;
		qrp->BadLines = 0;

		for (i = 0; i < ncol; i++) {
			*pcrp = (PCOLRES)PlugSubAlloc(g, NULL, sizeof(COLRES));
			crp = *pcrp;
			pcrp = &crp->Next;
			memset(crp, 0, sizeof(COLRES));
			crp->Colp = NULL;
			crp->Ncol = ++qrp->Nbcol;
			crp->Type = buftyp[i];
			crp->Length = length[i];
			crp->Clen = GetTypeSize(crp->Type, length[i]);
			crp->Prec = 0;

			if (ids > 0) {
#if defined(XMSG)
				// Get header from message file
				strncpy(cname, PlugReadMessage(g, ids + crp->Ncol, NULL), NAM_LEN);
				cname[NAM_LEN] = 0;					// for truncated long names
#else   // !XMSG
				GetRcString(ids + crp->Ncol, cname, sizeof(cname));
#endif  // !XMSG
				crp->Name = (PSZ)PlugDup(g, cname);
			} else
				crp->Name = NULL;           // Will be set by caller

			if (fldtyp)
				crp->Fld = fldtyp[i];
			else
				crp->Fld = FLD_NO;

			// Allocate the Value Block that will contain data
			if (crp->Length || nonull)
				crp->Kdata = AllocValBlock(g, NULL, crp->Type, maxres,
					crp->Length, 0, true, blank, false);
			else
				crp->Kdata = NULL;

			if (trace(1))
				htrc("Column(%d) %s type=%d len=%d value=%p\n",
					crp->Ncol, crp->Name, crp->Type, crp->Length, crp->Kdata);

		} // endfor i

		*pcrp = NULL;

	} catch (int n) {
  	htrc("Exception %d: %s\n", n, g->Message);
	  qrp = NULL;
  } catch (const char *msg) {
  	strcpy(g->Message, msg);
	  htrc("%s\n", g->Message);
	  qrp = NULL;
  } // end catch

	return qrp;
} // end of PlgAllocResult

/***********************************************************************/
/*  Allocate and initialize the new DB User Block.                     */
/***********************************************************************/
PDBUSER PlgMakeUser(PGLOBAL g)
  {
  PDBUSER dbuserp;

  if (!(dbuserp = (PDBUSER)malloc(sizeof(DBUSERBLK)))) {
    snprintf(g->Message, sizeof(g->Message), MSG(MALLOC_ERROR), "PlgMakeUser");
    return NULL;
    } // endif dbuserp

  memset(dbuserp, 0, sizeof(DBUSERBLK));
  dbuserp->Maxbmp = MAXBMP;
//dbuserp->UseTemp = TMP_AUTO;
  dbuserp->Check = CHK_ALL;
  strcpy(dbuserp->Server, "CONNECT");
  return dbuserp;
  } // end of PlgMakeUser

/***********************************************************************/
/*  PlgGetUser: returns DBUSER block pointer.                          */
/***********************************************************************/
PDBUSER PlgGetUser(PGLOBAL g)
  {
  PDBUSER dup = (PDBUSER)((g->Activityp) ? g->Activityp->Aptr : NULL);

  if (!dup)
    strcpy(g->Message, MSG(APPL_NOT_INIT));

  return dup;
  } // end of PlgGetUser

/***********************************************************************/
/*  PlgGetCatalog: returns CATALOG class pointer.                      */
/***********************************************************************/
PCATLG PlgGetCatalog(PGLOBAL g, bool jump)
  {
  PDBUSER dbuserp = PlgGetUser(g);
  PCATLG  cat = (dbuserp) ? dbuserp->Catalog : NULL;

  if (!cat && jump) {
    // Raise exception so caller doesn't have to check return value
    strcpy(g->Message, MSG(NO_ACTIVE_DB));
		throw 1;
	} // endif cat

  return cat;
  } // end of PlgGetCatalog

#if 0
/***********************************************************************/
/*  PlgGetDataPath: returns the default data path.                     */
/***********************************************************************/
char *PlgGetDataPath(PGLOBAL g)
  {
  PCATLG cat = PlgGetCatalog(g, false);

  return (cat) ? cat->GetDataPath() : NULL;
  } // end of PlgGetDataPath
#endif // 0

/***********************************************************************/
/*  This function returns a database path.                             */
/***********************************************************************/
char *SetPath(PGLOBAL g, const char *path)
{
  char *buf= NULL;

	if (path) {
		size_t len = strlen(path) + (*path != '.' ? 4 : 1);

		if (!(buf = (char*)PlgDBSubAlloc(g, NULL, len)))
			return NULL;

		if (PlugIsAbsolutePath(path)) {
			snprintf(buf, len, "%s", path);
			return buf;
		} // endif path

		if (*path != '.') {
#if defined(_WIN32)
			const char *s = "\\";
#else   // !_WIN32
			const char *s = "/";
#endif  // !_WIN32
			snprintf(buf, len, ".%s%s%s", s, path, s);
		} else
			snprintf(buf, len, "%s", path);

	} // endif path

  return buf;
} // end of SetPath

/***********************************************************************/
/*  Extract from a path name the required component.                   */
/*  This function assumes there is enough space in the buffer.         */
/***********************************************************************/
char *ExtractFromPath(PGLOBAL g, char *pBuff, char *FileName, OPVAL op)
  {
  char *drive = NULL, *direc = NULL, *fname = NULL, *ftype = NULL;

  switch (op) {           // Determine which part to extract
#if defined(_WIN32)
    case OP_FDISK: drive = pBuff; break;
#endif   // !UNIX
    case OP_FPATH: direc = pBuff; break;
    case OP_FNAME: fname = pBuff; break;
    case OP_FTYPE: ftype = pBuff; break;
    default:
      snprintf(g->Message, sizeof(g->Message), MSG(INVALID_OPER), op, "ExtractFromPath");
      return NULL;
    } // endswitch op

  // Now do the extraction
  _splitpath(FileName, drive, direc, fname, ftype);
  return pBuff;
  } // end of PlgExtractFromPath


#ifdef NOT_USED
/***********************************************************************/
/*  Check the occurrence and matching of a pattern against a string.    */
/*  Because this function is only used for catalog name checking,      */
/*  it must be case insensitive.                                       */
/***********************************************************************/
static bool PlugCheckPattern(PGLOBAL g, LPCSTR string, LPCSTR pat)
  {
  if (pat && strlen(pat)) {
    // This leaves 2048 bytes (MAX_STR / 2) for each components
    LPSTR name = g->Message + MAX_STR / 2;

    strlwr(strcpy(name, string));
    strlwr(strcpy(g->Message, pat));         // Can be modified by Eval
    return EvalLikePattern(name, g->Message);
  } else
    return true;

  } // end of PlugCheckPattern
#endif /* NOT_USED */

/***********************************************************************/
/*  PlugEvalLike: evaluates a LIKE clause.                             */
/*  Syntaxe: M like P escape C. strg->M, pat->P, C not implemented yet */
/***********************************************************************/
bool PlugEvalLike(PGLOBAL g, LPCSTR strg, LPCSTR pat, bool ci)
  {
  char *tp, *sp;
  bool  b;

  if (trace(2))
    htrc("LIKE: strg='%s' pattern='%s'\n", strg, pat);

  if (ci) {                        /* Case insensitive test             */
    if (strlen(pat) + strlen(strg) + 1 < MAX_STR)
      tp = g->Message;
    else if (!(tp = new char[strlen(pat) + strlen(strg) + 2])) {
      strcpy(g->Message, MSG(NEW_RETURN_NULL));
			throw (int)OP_LIKE;
		} /* endif tp */
    
    sp = tp + strlen(pat) + 1;
    strlwr(strcpy(tp, pat));      /* Make a lower case copy of pat     */
    strlwr(strcpy(sp, strg));     /* Make a lower case copy of strg    */
  } else {                        /* Case sensitive test               */
    if (strlen(pat) < MAX_STR)    /* In most of the case for small pat */
      tp = g->Message;            /* Use this as temporary work space. */
    else if (!(tp = new char[strlen(pat) + 1])) {
      strcpy(g->Message, MSG(NEW_RETURN_NULL));
			throw (int)OP_LIKE;
		} /* endif tp */
    
    strcpy(tp, pat);                  /* Make a copy to be worked into */
    sp = (char*)strg;
  } /* endif ci */

  b = EvalLikePattern(sp, tp);

  if (tp != g->Message)               /* If working space was obtained */
    delete [] tp;                     /* by the use of new, delete it. */

  return (b);
  } /* end of PlugEvalLike */

/***********************************************************************/
/*  M and P are variable length character string. If M and P are zero  */
/*  length strings then the Like predicate is true.                    */
/*                                                                     */
/*  The Like predicate is true if:                                     */
/*                                                                     */
/*  1- A substring of M is a sequence of 0 or more contiguous <CR> of M*/
/*     and each <CR> of M is part of exactly one substring.            */
/*                                                                     */
/*  2- If the i-th <substring-specifier> of P is an <arbitrary-char-   */
/*     specifier>, the i-th substring of M is any single <CR>.         */
/*                                                                     */
/*  3- If the i-th <substring-specifier> of P is an <arbitrary-string- */
/*     specifier>, then the i-th substring of M is any sequence of zero*/
/*     or more <CR>.                                                   */
/*                                                                     */
/*  4- If the i-th <substring-specifier> of P is neither an <arbitrary-*/
/*     character-specifier> nor an <arbitrary-string-specifier>, then  */
/*     the i-th substring of M is equal to that <substring-specifier>  */
/*     according to the collating sequence of the <like-predicate>,    */
/*     without the appending of <space-character>, and has the same    */
/*     length as that <substring-specifier>.                           */
/*                                                                     */
/*  5- The number of substrings of M is equal to the number of         */
/*     <substring-specifiers> of P.                                    */
/*                                                                     */
/*  Otherwise M like P is false.                                       */
/***********************************************************************/
bool EvalLikePattern(LPCSTR sp, LPCSTR tp)
  {
  LPSTR p;
  char  c;
  ssize_t   n;
  bool  b, t = false;

  if (trace(2))
    htrc("Eval Like: sp=%s tp=%s\n", 
         (sp) ? sp : "Null", (tp) ? tp : "Null");

  /********************************************************************/
  /*  If pattern is void, Like is true only if string is also void.   */
  /********************************************************************/
  if (!*tp)
    return (!*sp);

  /********************************************************************/
  /*  Analyse eventual arbitrary specifications ahead of pattern.     */
  /********************************************************************/
  for (p = (LPSTR)tp; p;)
    switch (*p) {                     /*   it can contain % and/or _   */
      case '%':                       /* An % has been found           */
        t = true;                     /* Note eventual character skip  */
        p++;
        break;
      case '_':                       /* An _ has been found           */
        if (*sp) {                    /* If more character in string   */
          sp++;                       /*   skip it                     */
          p++;
        } else
          return false;               /* Like condition is not met     */

        break;
      default:
        tp = p;                       /* Point to rest of template     */
        p = NULL;                     /* To stop For loop              */
        break;
      } /* endswitch */

  if ((p = (LPSTR)strpbrk(tp, "%_"))) /* Get position of next % or _   */
    n = p - tp;
  else
    n = strlen(tp);                   /* Get length of pattern head    */

  if (trace(2))
    htrc(" testing: t=%d sp=%s tp=%s p=%p\n", t, sp, tp, p);

  if (n > (signed)strlen(sp))         /* If head is longer than strg   */
    b = false;                        /* Like condition is not met     */
  else if (n == 0)                    /* If void <substring-specifier> */
    b = (t || !*sp);                  /*   true if %  or void strg.    */
  else if (!t) {
    /*******************************************************************/
    /*  No character to skip, check occurrence of <substring-specifier>*/
    /*  at the very beginning of remaining string.                     */
    /*******************************************************************/
    if (p) {
      if ((b = !strncmp(sp, tp, n)))
        b = EvalLikePattern(sp + n, p);

    } else
      b = !strcmp(sp, tp);            /*   strg and tmp heads match    */

  } else
    if (p)
      /*****************************************************************/
      /*  Here is the case explaining why we need a recursive routine. */
      /*  The test must be done not only against the first occurrence  */
      /*  of the <substring-specifier> in the remaining string,        */
      /*  but also with all eventual succeeding ones.                  */
      /*****************************************************************/
      for (b = false, c = *p; !b && (signed)strlen(sp) >= n; sp++) {
        *p = '\0';                    /* Separate pattern header       */

        if ((sp = strstr(sp, tp))) {
          *p = c;
          b = EvalLikePattern(sp + n, p);
        } else {
          *p = c;
          b = false;
          break;
        } /* endif s */

        } /* endfor b, sp */

    else {
      sp += (strlen(sp) - n);
      b = !strcmp(sp, tp);
    } /* endif p */

  if (trace(2))
    htrc(" done: b=%d n=%d sp=%s tp=%s\n",
          b, n, (sp) ? sp : "Null", tp);

  return (b);
  } /* end of EvalLikePattern */

/***********************************************************************/
/*  MakeEscape: Escape some characters in a string.                    */
/***********************************************************************/
char *MakeEscape(PGLOBAL g, char* str, char q)
  {
  char *bufp;
  int i, k, n = 0, len = (int)strlen(str);

  for (i = 0; i < len; i++)
    if (str[i] == q || str[i] == '\\')
      n++;

  if (!n)
    return str;
  else
    bufp = (char*)PlugSubAlloc(g, NULL, len + n + 1);

  for (i = k = 0; i < len; i++) {
    if (str[i] == q || str[i] == '\\')
      bufp[k++] = '\\';

    bufp[k++] = str[i];
    } // endfor i

  bufp[k] = 0;
  return bufp;
  } /* end of MakeEscape */

/***********************************************************************/
/*  PlugConvertConstant: convert a Plug constant to an Xobject.        */
/***********************************************************************/
void PlugConvertConstant(PGLOBAL g, void* & value, short& type)
  {
  if (trace(1))
    htrc("PlugConvertConstant: value=%p type=%hd\n", value, type);

  if (type != TYPE_XOBJECT) {
    value = new(g) CONSTANT(g, value, type);
    type = TYPE_XOBJECT;
    } // endif type

  } // end of PlugConvertConstant

/***********************************************************************/
/*  Call the Flex preparser to convert a date format to a sscanf input */
/*  format and a Strftime output format. Flag if not 0 indicates that  */
/*  non quoted blanks are not included in the output format.           */
/***********************************************************************/
PDTP MakeDateFormat(PGLOBAL g, PCSZ dfmt, bool in, bool out, int flag)
{
	int  rc;
  PDTP pdp = (PDTP)PlugSubAlloc(g, NULL, sizeof(DATPAR));

  if (trace(1))
    htrc("MakeDateFormat: dfmt=%s\n", dfmt);

  memset(pdp, 0, sizeof(DATPAR));
  pdp->Format = pdp->Curp = PlugDup(g, dfmt);
  pdp->Outsize = 2 * strlen(dfmt) + 1;

  if (in)
    pdp->InFmt = (char*)PlugSubAlloc(g, NULL, pdp->Outsize);

  if (out)
    pdp->OutFmt = (char*)PlugSubAlloc(g, NULL, pdp->Outsize);

  pdp->Flag = flag;

  /*********************************************************************/
  /* Call the FLEX generated parser. In multi-threading mode the next  */
  /* instruction is protected by mutex fmdflex using static variables. */
  /*********************************************************************/
  pthread_mutex_lock(&parmut);
  rc = fmdflex(pdp);
  pthread_mutex_unlock(&parmut);

  if (trace(1))
    htrc("Done: in=%s out=%s rc=%d\n", SVP(pdp->InFmt), SVP(pdp->OutFmt), rc);

  return pdp;
} // end of MakeDateFormat

/***********************************************************************/
/* Extract the date from a formatted string according to format.       */
/***********************************************************************/
int ExtractDate(char *dts, PDTP pdp, int defy, int val[6])
  {
	PCSZ fmt;
	char c, d, e, W[8][12];
  int  i, k, m, numval;
  int  n, y = 30;
  bool b = true;           // true for null dates

  if (pdp)
    fmt = pdp->InFmt;
  else            // assume standard MySQL date format
    fmt = "%4d-%2d-%2d %2d:%2d:%2d";

  if (trace(2))
    htrc("ExtractDate: dts=%s fmt=%s defy=%d\n", dts, fmt, defy);

  // Set default values for time only use
  if (defy) {
    // This may be a default value for year
    y = defy;
    val[0] = y;
    y = (y < 100) ? y : 30;
  } else
    val[0] = 70;

  val[1] = 1;
  val[2] = 1;

  for (i = 3; i < 6; i++)
    val[i] = 0;

  numval = 0;

  // Get the date field parse it with derived input format
  m = sscanf(dts, fmt, W[0], W[1], W[2], W[3], W[4], W[5], W[6], W[7]);

  if (m > pdp->Num)
    m = pdp->Num;

  for (i = 0; i < m; i++) {
    if ((n = *(int*)W[i]))
      b = false;

    switch (k = pdp->Index[i]) {
      case 0:
        if (n < y)
          n += 100;

        val[0] = n;
        numval = MY_MAX(numval, 1);
        break;
      case 1:
      case 2:
      case 3:
      case 4:
      case 5:
        val[k] = n;
        numval = MY_MAX(numval, k + 1);
        break;
      case -1:
        c = toupper(W[i][0]);
        d = toupper(W[i][1]);
        e = toupper(W[i][2]);

        switch (c) {
          case 'J':
            n = (d == 'A') ? 1
              : (e == 'N') ? 6 : 7; break;
          case 'F': n =  2; break;
          case 'M':
            n = (e == 'R') ? 3 : 5; break;
          case 'A':
            n = (d == 'P') ? 4 : 8; break;
            break;
          case 'S': n =  9; break;
          case 'O': n = 10; break;
          case 'N': n = 11; break;
          case 'D': n = 12; break;
          } /* endswitch c */

        val[1] = n;
        numval = MY_MAX(numval, 2);
        break;
      case -6:
        c = toupper(W[i][0]);
        n = val[3] % 12;

        if (c == 'P')
          n += 12;

        val[3] = n;
        break;
      } // endswitch Plugpar

    } // endfor i

  if (trace(2))
    htrc("numval=%d val=(%d,%d,%d,%d,%d,%d)\n",
          numval, val[0], val[1], val[2], val[3], val[4], val[5]); 

  return (b) ? 0 : numval;
  } // end of ExtractDate

/***********************************************************************/
/*  Open file routine: the purpose of this routine is to make a list   */
/*  of all open file so they can be closed in SQLINIT on error jump.   */
/***********************************************************************/
FILE *PlugOpenFile(PGLOBAL g, LPCSTR fname, LPCSTR ftype)
  {
  FILE     *fop;
  PFBLOCK   fp;
  PDBUSER   dbuserp = (PDBUSER)g->Activityp->Aptr;

  if (trace(1)) {
    htrc("PlugOpenFile: fname=%s ftype=%s\n", fname, ftype);
    htrc("dbuserp=%p\n", dbuserp);
    } // endif trace

  if ((fop= global_fopen(g, MSGID_OPEN_MODE_STRERROR, fname, ftype)) != NULL) {
    if (trace(1))
      htrc(" fop=%p\n", fop);

    fp = (PFBLOCK)PlugSubAlloc(g, NULL, sizeof(FBLOCK));

    if (trace(1))
      htrc(" fp=%p\n", fp);

    // fname may be in volatile memory such as stack
    fp->Fname = PlugDup(g, fname);
    fp->Count = 1;
    fp->Type = TYPE_FB_FILE;
    fp->File = fop;
    fp->Mode = MODE_ANY;                        // ???
    fp->Next = dbuserp->Openlist;
    dbuserp->Openlist = fp;
    } /* endif fop */

  if (trace(1))
    htrc(" returning fop=%p\n", fop);

  return (fop);
  } // end of PlugOpenFile

/***********************************************************************/
/*  Close file routine: the purpose of this routine is to avoid        */
/*  double closing that freeze the system on some Unix platforms.      */
/***********************************************************************/
FILE *PlugReopenFile(PGLOBAL g, PFBLOCK fp, LPCSTR md)
  {
  FILE *fop;

  if ((fop = global_fopen(g, MSGID_OPEN_MODE_STRERROR, fp->Fname, md))) {
    fp->Count = 1;
    fp->Type = TYPE_FB_FILE;
    fp->File = fop;
    } /* endif fop */

  return (fop);
  } // end of PlugOpenFile

/***********************************************************************/
/*  Close file routine: the purpose of this routine is to avoid        */
/*  double closing that freeze the system on some Unix platforms.      */
/***********************************************************************/
int PlugCloseFile(PGLOBAL g, PFBLOCK fp, bool all)
  {
  int rc = 0;

  if (trace(1))
    htrc("PlugCloseFile: fp=%p count=%hd type=%hd\n",
          fp, ((fp) ? fp->Count : 0), ((fp) ? fp->Type : 0));

  if (!fp || !fp->Count)
    return rc;

  switch (fp->Type) {
    case TYPE_FB_FILE:
      if (fclose((FILE *)fp->File) == EOF)
        rc = errno;

      fp->File = NULL;
      fp->Mode = MODE_ANY;
      fp->Count = 0;
      break;
    case TYPE_FB_MAP:
      if ((fp->Count = (all) ? 0 : fp->Count - 1))
        break;

      if (CloseMemMap(fp->Memory, fp->Length))
        rc = (int)GetLastError();

      fp->Memory = NULL;
      fp->Mode = MODE_ANY;
      // fall through
    case TYPE_FB_HANDLE:
      if (fp->Handle && fp->Handle != INVALID_HANDLE_VALUE)
        if (CloseFileHandle(fp->Handle))
          rc = (rc) ? rc : (int)GetLastError();

      fp->Handle = INVALID_HANDLE_VALUE;
      fp->Mode = MODE_ANY;
      fp->Count = 0;
      break;
#ifdef DOMDOC_SUPPORT
    case TYPE_FB_XML:
      CloseXMLFile(g, fp, all);
      break;
#endif   // DOMDOC_SUPPORT
#ifdef LIBXML2_SUPPORT
    case TYPE_FB_XML2:
      CloseXML2File(g, fp, all);
      break;
#endif   // LIBXML2_SUPPORT
#ifdef ODBC_SUPPORT
		case TYPE_FB_ODBC:
			OdbcClose(g, fp);
			fp->Count = 0;
			fp->File = NULL;
			break;
#endif   // ODBC_SUPPORT
#ifdef ZIP_SUPPORT
		case TYPE_FB_ZIP:
			if (fp->Mode == MODE_INSERT)
				((ZIPUTIL*)fp->File)->close();
			else
				((UNZIPUTL*)fp->File)->close();

			fp->Memory = NULL;
			fp->Mode = MODE_ANY;
			fp->Count = 0;
			fp->File = NULL;
			break;
#endif   // ZIP_SUPPORT
#ifdef JAVA_SUPPORT
		case TYPE_FB_JAVA:
			((JAVAConn*)fp->File)->Close();
			fp->Count = 0;
			fp->File = NULL;
			break;
#endif   // JAVA_SUPPORT
#ifdef CMGO_SUPPORT
		case TYPE_FB_MONGO:
			((CMgoConn*)fp->File)->Close();
			fp->Count = 0;
			fp->File = NULL;
			break;
#endif   // JAVA_SUPPORT
		default:
      rc = RC_FX;
    } // endswitch Type

  return rc;
  } // end of PlugCloseFile

/***********************************************************************/
/*  PlugCleanup: Cleanup remaining items of a SQL query.               */
/***********************************************************************/
void PlugCleanup(PGLOBAL g, bool dofree)
  {
  PCATLG  cat;
  PDBUSER dbuserp = (PDBUSER)g->Activityp->Aptr;

  // The test on Catalog is to avoid a Windows bug that can make
  // LoadString in PlugGetMessage to fail in some case
  if (!dbuserp || !(cat = dbuserp->Catalog))
    return;

  /*********************************************************************/
  /*  Close eventually still open/mapped files.                        */
  /*********************************************************************/
  for (PFBLOCK fp = dbuserp->Openlist; fp; fp = fp->Next)
    PlugCloseFile(g, fp, true);

  dbuserp->Openlist = NULL;

  if (dofree) {
    /*******************************************************************/
    /*  Cleanup any non suballocated memory still not freed.           */
    /*******************************************************************/
    for (PMBLOCK mp = dbuserp->Memlist; mp; mp = mp->Next)
      PlgDBfree(*mp);

    dbuserp->Memlist = NULL;

    /*******************************************************************/
    /*  If not using permanent storage catalog, reset volatile values. */
    /*******************************************************************/
    cat->Reset();

    /*******************************************************************/
    /*  This is the place to reset the pointer on domains.             */
    /*******************************************************************/
    dbuserp->Subcor = false;
    dbuserp->Step = "New query";     // was STEP(PARSING_QUERY);
    dbuserp->ProgMax = dbuserp->ProgCur = dbuserp->ProgSav = 0;
    } // endif dofree

  } // end of PlugCleanup

#if 0
/***********************************************************************/
/*  That stupid Windows 98 does not provide this function.             */
/***********************************************************************/
bool WritePrivateProfileInt(LPCSTR sec, LPCSTR key, int n, LPCSTR ini)
  {
  char buf[12];

  snprintf(buf, sizeof(buf), "%d", n);
  return WritePrivateProfileString(sec, key, buf, ini);
  } // end of WritePrivateProfileInt

/***********************************************************************/
/*  Retrieve a size from an INI file with eventual K or M following.   */
/***********************************************************************/
int GetIniSize(char *section, char *key, char *def, char *ini)
  {
  char c, buff[32];
  int  i;
  int  n = 0;

  GetPrivateProfileString(section, key, def, buff, sizeof(buff), ini);

  if ((i = sscanf(buff, " %d %c ", &n, &c)) == 2)
    switch (toupper(c)) {
      case 'M':
        n *= 1024;
      case 'K':
        n *= 1024;
      } // endswitch c

  if (trace(1))
    htrc("GetIniSize: key=%s buff=%s i=%d n=%d\n", key, buff, i, n);

  return n;
  } // end of GetIniSize

/***********************************************************************/
/* Allocate a string retrieved from an INI file and return its address */
/***********************************************************************/
DllExport PSZ GetIniString(PGLOBAL g, void *mp, LPCSTR sec, LPCSTR key,
                                                LPCSTR def, LPCSTR ini)
  {
  char  buff[_MAX_PATH];
  PSZ   p;
  int   n, m = sizeof(buff);
  char *buf = buff;

#if defined(_DEBUG)
  assert (sec && key);
#endif

 again:
  n = GetPrivateProfileString(sec, key, def, buf, m, ini);

  if (n == m - 1) {
    // String may have been truncated, make sure to have all
    if (buf != buff)
      delete [] buf;

    m *= 2;
    buf = new char[m];
    goto again;
    } // endif n

  p = (PSZ)PlugSubAlloc(g, mp, n + 1);

  if (trace(1))
    htrc("GetIniString: sec=%s key=%s buf=%s\n", sec, key, buf);

  strcpy(p, buf);

  if (buf != buff)
    delete [] buf;

  return p;
  } // end of GetIniString
#endif // 0

/***********************************************************************/
/*  GetAmName: return the name corresponding to an AM code.            */
/***********************************************************************/
char *GetAmName(PGLOBAL g, AMT am, void *memp)
  {
  char *amn= (char*)PlugSubAlloc(g, memp, 16);

  switch (am) {
    case TYPE_AM_ERROR: strcpy(amn, "ERROR"); break;
    case TYPE_AM_ROWID: strcpy(amn, "ROWID"); break;
    case TYPE_AM_FILID: strcpy(amn, "FILID"); break;
    case TYPE_AM_VIEW:  strcpy(amn, "VIEW");  break;
    case TYPE_AM_COUNT: strcpy(amn, "COUNT"); break;
    case TYPE_AM_DCD:   strcpy(amn, "DCD");   break;
    case TYPE_AM_CMS:   strcpy(amn, "CMS");   break;
    case TYPE_AM_MAP:   strcpy(amn, "MAP");   break;
    case TYPE_AM_FMT:   strcpy(amn, "FMT");   break;
    case TYPE_AM_CSV:   strcpy(amn, "CSV");   break;
    case TYPE_AM_MCV:   strcpy(amn, "MCV");   break;
    case TYPE_AM_DOS:   strcpy(amn, "DOS");   break;
    case TYPE_AM_FIX:   strcpy(amn, "FIX");   break;
    case TYPE_AM_BIN:   strcpy(amn, "BIN");   break;
    case TYPE_AM_VCT:   strcpy(amn, "VEC");   break;
    case TYPE_AM_VMP:   strcpy(amn, "VMP");   break;
    case TYPE_AM_DBF:   strcpy(amn, "DBF");   break;
    case TYPE_AM_QRY:   strcpy(amn, "QRY");   break;
    case TYPE_AM_SQL:   strcpy(amn, "SQL");   break;
    case TYPE_AM_PLG:   strcpy(amn, "PLG");   break;
    case TYPE_AM_PLM:   strcpy(amn, "PLM");   break;
    case TYPE_AM_DOM:   strcpy(amn, "DOM");   break;
    case TYPE_AM_DIR:   strcpy(amn, "DIR");   break;
    case TYPE_AM_ODBC:  strcpy(amn, "ODBC");  break;
		case TYPE_AM_JDBC:  strcpy(amn, "JDBC");  break;
		case TYPE_AM_MAC:   strcpy(amn, "MAC");   break;
    case TYPE_AM_OEM:   strcpy(amn, "OEM");   break;
    case TYPE_AM_OUT:   strcpy(amn, "OUT");   break;
    default:           snprintf(amn, 16, "OEM(%d)", am);
    } // endswitch am

  return amn;
  } // end of GetAmName

#if defined(SE_CATCH)
/***********************************************************************/
/*  GetExceptionDesc: return the description of an exception code.     */
/***********************************************************************/
char *GetExceptionDesc(PGLOBAL g, unsigned int e)
  {
  char *p;

  switch (e) {
    case EXCEPTION_GUARD_PAGE:
      p = MSG(GUARD_PAGE);
      break;
    case EXCEPTION_DATATYPE_MISALIGNMENT:
      p = MSG(DATA_MISALIGN);
      break;
    case EXCEPTION_BREAKPOINT:
      p = MSG(BREAKPOINT);
      break;
    case EXCEPTION_SINGLE_STEP:
      p = MSG(SINGLE_STEP);
      break;
    case EXCEPTION_ACCESS_VIOLATION:
      p = MSG(ACCESS_VIOLATN);
      break;
    case EXCEPTION_IN_PAGE_ERROR:
      p = MSG(PAGE_ERROR);
      break;
    case EXCEPTION_INVALID_HANDLE:
      p = MSG(INVALID_HANDLE);
      break;
    case EXCEPTION_ILLEGAL_INSTRUCTION:
      p = MSG(ILLEGAL_INSTR);
      break;
    case EXCEPTION_NONCONTINUABLE_EXCEPTION:
      p = MSG(NONCONT_EXCEPT);
      break;
    case EXCEPTION_INVALID_DISPOSITION:
      p = MSG(INVALID_DISP);
      break;
    case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
      p = MSG(ARRAY_BNDS_EXCD);
      break;
    case EXCEPTION_FLT_DENORMAL_OPERAND:
      p = MSG(FLT_DENORMAL_OP);
      break;
    case EXCEPTION_FLT_DIVIDE_BY_ZERO:
      p = MSG(FLT_ZERO_DIVIDE);
      break;
    case EXCEPTION_FLT_INEXACT_RESULT:
      p = MSG(FLT_BAD_RESULT);
      break;
    case EXCEPTION_FLT_INVALID_OPERATION:
      p = MSG(FLT_INVALID_OP);
      break;
    case EXCEPTION_FLT_OVERFLOW:
      p = MSG(FLT_OVERFLOW);
      break;
    case EXCEPTION_FLT_STACK_CHECK:
      p = MSG(FLT_STACK_CHECK);
      break;
    case EXCEPTION_FLT_UNDERFLOW:
      p = MSG(FLT_UNDERFLOW);
      break;
    case EXCEPTION_INT_DIVIDE_BY_ZERO:
      p = MSG(INT_ZERO_DIVIDE);
      break;
    case EXCEPTION_INT_OVERFLOW:
      p = MSG(INT_OVERFLOW);
      break;
    case EXCEPTION_PRIV_INSTRUCTION:
      p = MSG(PRIV_INSTR);
      break;
    case EXCEPTION_STACK_OVERFLOW:
      p = MSG(STACK_OVERFLOW);
      break;
    case CONTROL_C_EXIT:
      p = MSG(CONTROL_C_EXIT);
      break;
    case STATUS_NO_MEMORY:
      p = MSG(NO_MEMORY);
      break;
    default:
      p = MSG(UNKNOWN_EXCPT);
      break;
    } // endswitch nSE

  return p;
  } // end of GetExceptionDesc
#endif   // SE_CATCH

/***********************************************************************/
/*  PlgDBalloc: allocates or suballocates memory conditionally.        */
/*  If mp.Sub is true at entry, this forces suballocation.             */
/*  If the memory is allocated, makes an entry in an allocation list   */
/*  so it can be freed at the normal or error query completion.        */
/***********************************************************************/
void *PlgDBalloc(PGLOBAL g, void *area, MBLOCK& mp)
{
//bool        b;
  size_t      maxsub, minsub;
  void       *arp = (area) ? area : g->Sarea;
  PPOOLHEADER pph = (PPOOLHEADER)arp;

  if (mp.Memp) {
    // This is a reallocation. If this block is not suballocated, it
    // was already placed in the chain of memory blocks and we must
    // not do it again as it can trigger a loop when freeing them.
    // Note: this works if blocks can be reallocated only once.
    // Otherwise a new boolean must be added to the block that
    // indicate that it is chained, or a test on the whole chain be
    // done to check whether the block is already there.
//  b = mp.Sub;
    mp.Sub = false;    // Restrict suballocation to one quarter
  } // endif Memp

  // Suballoc when possible if mp.Sub is initially true, but leaving
  // a minimum amount of storage for future operations such as the
  // optimize recalculation after insert; otherwise
  // suballoc only if size is smaller than one quarter of free mem.
  minsub = (pph->FreeBlk + pph->To_Free + 524248) >> 2;
  maxsub = (pph->FreeBlk < minsub) ? 0 : pph->FreeBlk - minsub;
  mp.Sub = mp.Size <= ((mp.Sub) ? maxsub : (maxsub >> 2));

	if (trace(2))
		htrc("PlgDBalloc: in %p size=%zd used=%zd free=%zd sub=%d\n",
			arp, mp.Size, pph->To_Free, pph->FreeBlk, mp.Sub);

	if (!mp.Sub) {
    // For allocations greater than one fourth of remaining storage
    // in the area, do allocate from virtual storage.
		const char*v = "malloc";
#if defined(_WIN32)
		if (mp.Size >= BIGMEM) {
			v = "VirtualAlloc";
			mp.Memp = VirtualAlloc(NULL, mp.Size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
		} else
#endif
      mp.Memp = malloc(mp.Size);

		if (trace(8))
			htrc("PlgDBalloc: %s(%zd) at %p\n", v, mp.Size, mp.Memp);

		if (!mp.Inlist && mp.Memp) {
      // New allocated block, put it in the memory block chain.
      PDBUSER dbuserp = (PDBUSER)g->Activityp->Aptr;

      mp.Next = dbuserp->Memlist;
      dbuserp->Memlist = &mp;
      mp.Inlist = true;
    } // endif mp

  } else
    // Suballocating is Ok.
    mp.Memp = PlugSubAlloc(g, area, mp.Size);

  return mp.Memp;
} // end of PlgDBalloc

/***********************************************************************/
/*  PlgDBrealloc: reallocates memory conditionally.                    */
/*  Note that this routine can fail only when block size is increased  */
/*  because otherwise we keep the old storage on failure.              */
/***********************************************************************/
void *PlgDBrealloc(PGLOBAL g, void *area, MBLOCK& mp, size_t newsize)
  {
  MBLOCK m;

#if defined(_DEBUG)
//  assert (mp.Memp != NULL);
#endif

  if (trace(2))
    htrc("PlgDBrealloc: %p size=%zd sub=%d\n", mp.Memp, mp.Size, mp.Sub);

  if (newsize == mp.Size)
    return mp.Memp;      // Nothing to do
  else
    m = mp;

  if (!mp.Sub && mp.Size < BIGMEM && newsize < BIGMEM) {
    // Allocation was done by malloc, try to use realloc but
    // suballoc if newsize is smaller than one quarter of free mem.
    size_t      maxsub;
    PPOOLHEADER pph = (PPOOLHEADER)((area) ? area : g->Sarea);

    maxsub = (pph->FreeBlk < 131072) ? 0 : pph->FreeBlk - 131072;

    if ((mp.Sub = (newsize <= (maxsub >> 2)))) {
      mp.Memp = PlugSubAlloc(g, area, newsize);
      memcpy(mp.Memp, m.Memp, MY_MIN(m.Size, newsize));
      PlgDBfree(m);    // Free the old block
    } else {
			if (!(mp.Memp = realloc(mp.Memp, newsize))) {
				mp = m;          // Possible only if newsize > Size
				return NULL;     // Failed
			} else if (trace(8))
				htrc("PlgDBrealloc: realloc(%ld) at %p\n", newsize, mp.Memp);

		} // endif's

    mp.Size = newsize;
  } else if (!mp.Sub || newsize > mp.Size) {
    // Was suballocated or Allocation was done by VirtualAlloc
    // Make a new allocation and copy the useful part
    // Note: DO NOT reset Memp and Sub so we know that this
    // is a reallocation in PlgDBalloc
    mp.Size = newsize;

    if (PlgDBalloc(g, area, mp)) {
      memcpy(mp.Memp, m.Memp, MY_MIN(m.Size, newsize));
      PlgDBfree(m);    // Free the old block
    } else {
      mp = m;          // No space to realloc, do nothing

      if (newsize > m.Size)
        return NULL;   // Failed

    } // endif PlgDBalloc

  } // endif's

  if (trace(8))
    htrc(" newsize=%zd newp=%p sub=%d\n", mp.Size, mp.Memp, mp.Sub);

  return mp.Memp;
  } // end of PlgDBrealloc

/***********************************************************************/
/*  PlgDBfree: free memory if not suballocated.                        */
/***********************************************************************/
void PlgDBfree(MBLOCK& mp)
  {
	if (!mp.Sub && mp.Memp) {
		const char*v = "free";
#if defined(_WIN32)
		if (mp.Size >= BIGMEM) {
			v = "VirtualFree";
			VirtualFree(mp.Memp, 0, MEM_RELEASE);
		} else
#endif
			free(mp.Memp);

		if (trace(8))
			htrc("PlgDBfree: %s(%p) size=%d\n", v, mp.Memp, mp.Size);

	}	// endif mp

  // Do not reset Next to avoid cutting the Mblock chain
  mp.Memp = NULL;
  mp.Sub = false;
  mp.Size = 0;
  } // end of PlgDBfree

/***********************************************************************/
/*  Program for sub-allocating one item in a storage area.             */
/*  Note: This function is equivalent to PlugSubAlloc except that in   */
/*  case of insufficient memory, it returns NULL instead of doing a    */
/*  throw. The caller must test the return value for error.            */
/***********************************************************************/
void *PlgDBSubAlloc(PGLOBAL g, void *memp, size_t size)
  {
  PPOOLHEADER pph;                           // Points on area header.

  if (!memp)
    /*******************************************************************/
    /*  Allocation is to be done in the Sarea.                         */
    /*******************************************************************/
    memp = g->Sarea;

//size = ((size + 3) / 4) * 4;       /* Round up size to multiple of 4 */
  size = ((size + 7) / 8) * 8;       /* Round up size to multiple of 8 */
  pph = (PPOOLHEADER)memp;

  if (trace(16))
    htrc("PlgDBSubAlloc: memp=%p size=%zd used=%zd free=%zd\n",
         memp, size, pph->To_Free, pph->FreeBlk);

  if (size > pph->FreeBlk) {   /* Not enough memory left in pool */
    snprintf(g->Message, sizeof(g->Message),
    "Not enough memory in Work area for request of %zd (used=%zd free=%zd)",
            size, pph->To_Free, pph->FreeBlk);

    if (trace(1))
      htrc("%s\n", g->Message);

    return NULL;
    } // endif size

  /*********************************************************************/
  /*  Do the suballocation the simplest way.                           */
  /*********************************************************************/
  memp = MakePtr(memp, pph->To_Free);   // Points to suballocated block
  pph->To_Free += size;                 // New offset of pool free block
  pph->FreeBlk -= size;                 // New size   of pool free block

  if (trace(16))
    htrc("Done memp=%p used=%zd free=%zd\n",
         memp, pph->To_Free, pph->FreeBlk);

  return (memp);
  } // end of PlgDBSubAlloc

/***********************************************************************/
/*  Program for sub-allocating and copying a string in a storage area. */
/***********************************************************************/
char *PlgDBDup(PGLOBAL g, const char *str)
  {
  if (str) {
    char *sm = (char*)PlgDBSubAlloc(g, NULL, strlen(str) + 1);

    if (sm)
      strcpy(sm, str);

    return sm;
  } else
    return NULL;

  } // end of PlgDBDup

/***********************************************************************/
/*  PUTOUT: Plug DB object typing routine.                             */
/***********************************************************************/
void PlugPutOut(PGLOBAL g, FILE *f, short t, void *v, uint n)
  {
  char  m[64];

  if (trace(1))
    htrc("PUTOUT: f=%p t=%d v=%p n=%d\n", f, t, v, n);

  if (!v)
    return;

  memset(m, ' ', n);                             /* Make margin string */
  m[n] = '\0';
  n += 2;                                        /* Increase margin    */

  switch (t) {
    case TYPE_ERROR:
      fprintf(f, "--> %s\n", (PSZ)v);
      break;

    case TYPE_STRING:
    case TYPE_PSZ:
      fprintf(f, "%s%s\n", m, (PSZ)v);
      break;

    case TYPE_DOUBLE:
      fprintf(f, "%s%lf\n", m, *(double *)v);
      break;

    case TYPE_LIST:
    case TYPE_COLIST:
    case TYPE_COL:
     {PPARM p;

      if (t == TYPE_LIST)
        fprintf(f, "%s%s\n", m, MSG(LIST));
      else
        fprintf(f, "%s%s\n", m, "Colist:");

      for (p = (PPARM)v; p; p = p->Next)
        PlugPutOut(g, f, p->Type, p->Value, n);

      } break;

    case TYPE_INT:
      fprintf(f, "%s%d\n", m, *(int *)v);
      break;

    case TYPE_SHORT:
      fprintf(f, "%s%hd\n", m, *(short *)v);
      break;

    case TYPE_TINY:
      fprintf(f, "%s%d\n", m, (int)*(char *)v);
      break;

    case TYPE_VOID:
      break;

    case TYPE_SQL:
    case TYPE_TABLE:
    case TYPE_TDB:
    case TYPE_XOBJECT:
      ((PBLOCK)v)->Printf(g, f, n-2);
      break;

    default:
      fprintf(f, "%s%s %d\n", m, MSG(ANSWER_TYPE), t);
    } /* endswitch */

  return;
  } /* end of PlugPutOut */

/***********************************************************************/
/*  NewPointer: makes a table of pointer values to be changed later.   */
/***********************************************************************/
DllExport void NewPointer(PTABS t, void *oldv, void *newv)
  {
  PTABPTR tp;

  if (!oldv)                                       /* error ?????????? */
    return;

  if (!t->P1 || t->P1->Num == 50)
  {
    if (!(tp = new TABPTR)) {
      PGLOBAL g = t->G;

      snprintf(g->Message, sizeof(g->Message), "NewPointer: %s", MSG(MEM_ALLOC_ERROR));
			throw 3;
		} else {
      tp->Next = t->P1;
      tp->Num = 0;
      t->P1 = tp;
    } /* endif tp */
  }

  t->P1->Old[t->P1->Num] = oldv;
  t->P1->New[t->P1->Num++] = newv;
  } /* end of NewPointer */

#if 0
/***********************************************************************/
/*  Compare two files and return 0 if they are identical, else 1.      */
/***********************************************************************/
int FileComp(PGLOBAL g, char *file1, char *file2)
  {
  char *fn[2], *bp[2], buff1[4096], buff2[4096];
  int   i, k, n[2], h[2] = {-1,-1};
  int  len[2], rc = -1;

  fn[0] = file1; fn[1] = file2;
  bp[0] = buff1; bp[1] = buff2;

  for (i = 0; i < 2; i++) {
#if defined(_WIN32)
    h[i]= global_open(g, MSGID_NONE, fn[i], _O_RDONLY | _O_BINARY);
#else   // !_WIN32
    h[i]= global_open(g, MSGOD_NONE, fn[i], O_RDONLY);
#endif  // !_WIN32

    if (h[i] == -1) {
//      if (errno != ENOENT) {
        snprintf(g->Message, sizeof(g->Message), MSG(OPEN_MODE_ERROR),
                "rb", (int)errno, fn[i]);
        strcat(strcat(g->Message, ": "), strerror(errno));
				throw 666;
				//      } else
//        len[i] = 0;          // File does not exist yet

    } else {
      if ((len[i] = _filelength(h[i])) < 0) {
        snprintf(g->Message, sizeof(g->Message), MSG(FILELEN_ERROR), "_filelength", fn[i]);
				throw 666;
			} // endif len

    } // endif h

    } // endfor i

  if (len[0] != len[1])
    rc = 1;

  while (rc == -1) {
    for (i = 0; i < 2; i++)
      if ((n[i] = read(h[i], bp[i], 4096)) < 0) {
        snprintf(g->Message, sizeof(g->Message), MSG(READ_ERROR), fn[i], strerror(errno));
        goto fin;
        } // endif n

    if (n[0] != n[1])
      rc = 1;
    else if (*n == 0)
      rc = 0;
    else for (k = 0; k < *n; k++)
      if (*(bp[0] + k) != *(bp[1] + k)) {
        rc = 1;
        goto fin;
        } // endif bp

    } // endwhile

 fin:
  for (i = 0; i < 2; i++)
    if (h[i] != -1)
      close(h[i]);

  return rc;
  } // end of FileComp
#endif // 0
