mirror of
				https://github.com/MariaDB/server.git
				synced 2025-10-31 10:56:12 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			1583 lines
		
	
	
	
		
			50 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1583 lines
		
	
	
	
		
			50 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /************* TabFmt C++ Program Source Code File (.CPP) **************/
 | |
| /* PROGRAM NAME: TABFMT                                                */
 | |
| /* -------------                                                       */
 | |
| /*  Version 3.9.3                                                      */
 | |
| /*                                                                     */
 | |
| /* COPYRIGHT:                                                          */
 | |
| /* ----------                                                          */
 | |
| /*  (C) Copyright to the author Olivier BERTRAND          2001 - 2019  */
 | |
| /*                                                                     */
 | |
| /* WHAT THIS PROGRAM DOES:                                             */
 | |
| /* -----------------------                                             */
 | |
| /*  This program are the TABFMT classes DB execution routines.         */
 | |
| /*  The base class CSV is comma separated files.                       */
 | |
| /*  FMT (Formatted) files are those having a complex internal record   */
 | |
| /*  format described in the Format keyword of their definition.        */
 | |
| /***********************************************************************/
 | |
| 
 | |
| /***********************************************************************/
 | |
| /*  Include relevant MariaDB header file.                              */
 | |
| /***********************************************************************/
 | |
| #include "my_global.h"
 | |
| 
 | |
| #if defined(_WIN32)
 | |
| #include <io.h>
 | |
| #include <fcntl.h>
 | |
| #include <errno.h>
 | |
| #include <locale.h>
 | |
| #if defined(__BORLANDC__)
 | |
| #define __MFC_COMPAT__                   // To define min/max as macro
 | |
| #endif
 | |
| //#include <windows.h>
 | |
| #include "osutil.h"
 | |
| #else
 | |
| #if defined(UNIX)
 | |
| #include <errno.h>
 | |
| #include <unistd.h>
 | |
| #include "osutil.h"
 | |
| #else
 | |
| #include <io.h>
 | |
| #endif
 | |
| #include <fcntl.h>
 | |
| #endif
 | |
| 
 | |
| /***********************************************************************/
 | |
| /*  Include application header files:                                  */
 | |
| /*  global.h    is header containing all global declarations.          */
 | |
| /*  plgdbsem.h  is header containing the DB application declarations.  */
 | |
| /*  tabdos.h    is header containing the TABDOS class declarations.    */
 | |
| /***********************************************************************/
 | |
| #include "global.h"
 | |
| #include "plgdbsem.h"
 | |
| #include "mycat.h"
 | |
| #include "filamap.h"
 | |
| #if defined(GZ_SUPPORT)
 | |
| #include "filamgz.h"
 | |
| #endif   // GZ_SUPPORT
 | |
| #if defined(ZIP_SUPPORT)
 | |
| #include "filamzip.h"
 | |
| #endif   // ZIP_SUPPORT
 | |
| #include "tabfmt.h"
 | |
| #include "tabmul.h"
 | |
| #define  NO_FUNC
 | |
| #include "plgcnx.h"                       // For DB types
 | |
| #include "resource.h"
 | |
| #include "m_string.h"
 | |
| 
 | |
| /***********************************************************************/
 | |
| /*  This should be an option.                                          */
 | |
| /***********************************************************************/
 | |
| #define MAXCOL          200        /* Default max column nb in result  */
 | |
| #define TYPE_UNKNOWN     12        /* Must be greater than other types */
 | |
| 
 | |
| /***********************************************************************/
 | |
| /*  External function.                                                 */
 | |
| /***********************************************************************/
 | |
| USETEMP UseTemp(void);
 | |
| 
 | |
| /***********************************************************************/
 | |
| /* CSVColumns: constructs the result blocks containing the description */
 | |
| /* of all the columns of a CSV file that will be retrieved by #GetData.*/
 | |
| /* Note: the algorithm to set the type is based on the internal values */
 | |
| /* of types (TYPE_STRING < TYPE_DOUBLE < TYPE_INT) (1 < 2 < 7).        */
 | |
| /* If these values are changed, this will have to be revisited.        */
 | |
| /***********************************************************************/
 | |
| PQRYRES CSVColumns(PGLOBAL g, PCSZ dp, PTOS topt, bool info)
 | |
|   {
 | |
|   static int  buftyp[] = {TYPE_STRING, TYPE_SHORT, TYPE_STRING,
 | |
|                           TYPE_INT,   TYPE_INT, TYPE_SHORT};
 | |
|   static XFLD fldtyp[] = {FLD_NAME, FLD_TYPE,   FLD_TYPENAME,
 | |
|                           FLD_PREC, FLD_LENGTH, FLD_SCALE};
 | |
|   static unsigned int length[] = {6, 6, 8, 10, 10, 6};
 | |
| 	const char *fn;
 | |
| 	char    sep, q;
 | |
| 	int     rc, mxr;
 | |
| 	bool    hdr;
 | |
|   char   *p, *colname[MAXCOL], dechar, buf[16];
 | |
|   int     i, imax, hmax, n, nerr, phase, blank, digit, dec, type;
 | |
|   int     ncol = sizeof(buftyp) / sizeof(int);
 | |
|   int     num_read = 0, num_max = 10000000;     // Statistics
 | |
|   int     len[MAXCOL], typ[MAXCOL], prc[MAXCOL];
 | |
| 	PCSVDEF tdp;
 | |
| 	PTDBCSV tcvp;
 | |
| 	PTDBASE tdbp;
 | |
| 	PQRYRES qrp;
 | |
|   PCOLRES crp;
 | |
| 
 | |
|   if (info) {
 | |
|     imax = hmax = 0;
 | |
|     length[0] = 128;
 | |
|     goto skipit;
 | |
|     } // endif info
 | |
| 
 | |
| 	//if (GetIntegerTableOption(g, topt, "Multiple", 0)) {
 | |
| 	//	strcpy(g->Message, "Cannot find column definition for multiple table");
 | |
| 	//	return NULL;
 | |
| 	//}	// endif Multiple
 | |
| 
 | |
| //      num_max = atoi(p+1);             // Max num of record to test
 | |
|   imax = hmax = nerr = 0;
 | |
| 
 | |
|   for (i = 0; i < MAXCOL; i++) {
 | |
|     colname[i] = NULL;
 | |
|     len[i] = 0;
 | |
|     typ[i] = TYPE_UNKNOWN;
 | |
|     prc[i] = 0;
 | |
|     } // endfor i
 | |
| 
 | |
|   /*********************************************************************/
 | |
|   /*  Get the CSV table description block.                             */
 | |
|   /*********************************************************************/
 | |
| 	tdp = new(g) CSVDEF;
 | |
| 	tdp->Database = dp;
 | |
| 
 | |
| 	if ((tdp->Zipped = GetBooleanTableOption(g, topt, "Zipped", false))) {
 | |
| #if defined(ZIP_SUPPORT)
 | |
| 		tdp->Entry = GetStringTableOption(g, topt, "Entry", NULL);
 | |
| 		tdp->Mulentries = (tdp->Entry)
 | |
| 			              ? strchr(tdp->Entry, '*') || strchr(tdp->Entry, '?')
 | |
| 			              : GetBooleanTableOption(g, topt, "Mulentries", false);
 | |
| #else   // !ZIP_SUPPORT
 | |
| 		safe_strcpy(g->Message, sizeof(g->Message), "ZIP not supported by this version");
 | |
| 		return NULL;
 | |
| #endif  // !ZIP_SUPPORT
 | |
| 	} // endif // Zipped
 | |
| 
 | |
| 	fn = tdp->Fn = GetStringTableOption(g, topt, "Filename", NULL);
 | |
| 
 | |
| 	if (!tdp->Fn) {
 | |
| 		safe_strcpy(g->Message, sizeof(g->Message), MSG(MISSING_FNAME));
 | |
| 		return NULL;
 | |
| 	} // endif Fn
 | |
| 
 | |
| 	if (!(tdp->Lrecl = GetIntegerTableOption(g, topt, "Lrecl", 0)))
 | |
| 		tdp->Lrecl = 4096;
 | |
| 
 | |
| 	tdp->Multiple = GetIntegerTableOption(g, topt, "Multiple", 0);
 | |
| 	p = (char*)GetStringTableOption(g, topt, "Separator", ",");
 | |
| 	tdp->Sep = (strlen(p) == 2 && p[0] == '\\' && p[1] == 't') ? '\t' : *p;
 | |
| 
 | |
| #if defined(_WIN32)
 | |
| 	if (tdp->Sep == ',' || strnicmp(setlocale(LC_NUMERIC, NULL), "French", 6))
 | |
| 		dechar = '.';
 | |
| 	else
 | |
| 		dechar = ',';
 | |
| #else   // !_WIN32
 | |
| 	dechar = '.';
 | |
| #endif  // !_WIN32
 | |
| 
 | |
| 	sep = tdp->Sep;
 | |
| 	tdp->Quoted = GetIntegerTableOption(g, topt, "Quoted", -1);
 | |
| 	p = (char*)GetStringTableOption(g, topt, "Qchar", "");
 | |
| 	tdp->Qot = *p;
 | |
| 
 | |
| 	if (tdp->Qot && tdp->Quoted < 0)
 | |
| 		tdp->Quoted = 0;
 | |
| 	else if (!tdp->Qot && tdp->Quoted >= 0)
 | |
| 		tdp->Qot = '"';
 | |
| 
 | |
| 	q = tdp->Qot;
 | |
| 	hdr = GetBooleanTableOption(g, topt, "Header", false);
 | |
| 	tdp->Maxerr = GetIntegerTableOption(g, topt, "Maxerr", 0);
 | |
| 	tdp->Accept = GetBooleanTableOption(g, topt, "Accept", false);
 | |
| 
 | |
| 	if (tdp->Accept && tdp->Maxerr == 0)
 | |
| 		tdp->Maxerr = INT_MAX32;       // Accept all bad lines
 | |
| 
 | |
| 	mxr = MY_MAX(0, tdp->Maxerr);
 | |
| 
 | |
| 	if (trace(1))
 | |
| 		htrc("File %s Sep=%c Qot=%c Header=%d maxerr=%d\n",
 | |
| 		SVP(tdp->Fn), tdp->Sep, tdp->Qot, tdp->Header, tdp->Maxerr);
 | |
| 
 | |
| #if defined(ZIP_SUPPORT)
 | |
| 	if (tdp->Zipped)
 | |
| 		tcvp = new(g)TDBCSV(tdp, new(g)UNZFAM(tdp));
 | |
| 	else
 | |
| #endif   // ZIP_SUPPORT
 | |
| 		tcvp = new(g) TDBCSV(tdp, new(g) DOSFAM(tdp));
 | |
| 
 | |
| 	tcvp->SetMode(MODE_READ);
 | |
| 
 | |
| 	if (tdp->Multiple) {
 | |
| 		tdbp = new(g)TDBMUL(tcvp);
 | |
| 		tdbp->SetMode(MODE_READ);
 | |
| 	} else
 | |
| 	  tdbp = tcvp;
 | |
| 
 | |
| 	/*********************************************************************/
 | |
| 	/*  Open the CSV file.                                               */
 | |
| 	/*********************************************************************/
 | |
| 	if (tdbp->OpenDB(g))
 | |
| 		return NULL;
 | |
| 
 | |
|   if (hdr) {
 | |
|     /*******************************************************************/
 | |
|     /*  Make the column names from the first line.                     */
 | |
|     /*******************************************************************/
 | |
|     phase = 0;
 | |
| 
 | |
|     if ((rc = tdbp->ReadDB(g)) == RC_OK) {
 | |
| 			p = PlgDBDup(g, tcvp->To_Line);
 | |
| 
 | |
|       //skip leading blanks
 | |
|       for (; *p == ' '; p++) ;
 | |
| 
 | |
|       if (q && *p == q) {
 | |
|         // Header is quoted
 | |
|         p++;
 | |
|         phase = 1;
 | |
|         } // endif q
 | |
| 
 | |
|       colname[0] = p;
 | |
|     } else if (rc == RC_EF) {
 | |
|       snprintf(g->Message, sizeof(g->Message), MSG(FILE_IS_EMPTY), fn);
 | |
|       goto err;
 | |
| 		} else
 | |
| 			goto err;
 | |
| 
 | |
|     for (i = 1; *p; p++)
 | |
|       if (phase == 1 && *p == q) {
 | |
|         *p = '\0';
 | |
|         phase = 0;
 | |
|       } else if (*p == sep && !phase) {
 | |
|         *p = '\0';
 | |
| 
 | |
|         //skip leading blanks
 | |
|         for (; *(p+1) == ' '; p++) ;
 | |
| 
 | |
|         if (q && *(p+1) == q) {
 | |
|           // Header is quoted
 | |
|           p++;
 | |
|           phase = 1;
 | |
|           } // endif q
 | |
| 
 | |
|         colname[i++] = p + 1;
 | |
|         } // endif sep
 | |
| 
 | |
|     num_read++;
 | |
|     imax = hmax = i;
 | |
| 
 | |
|     for (i = 0; i < hmax; i++)
 | |
|       length[0] = MY_MAX(length[0], strlen(colname[i]));
 | |
| 
 | |
| 		tcvp->Header = true;			// In case of multiple table
 | |
|     } // endif hdr
 | |
| 
 | |
|   for (num_read++; num_read <= num_max; num_read++) {
 | |
|     /*******************************************************************/
 | |
|     /*  Now start the reading process. Read one line.                  */
 | |
|     /*******************************************************************/
 | |
| 		if ((rc = tdbp->ReadDB(g)) == RC_OK) {
 | |
|     } else if (rc == RC_EF) {
 | |
|       snprintf(g->Message, sizeof(g->Message), MSG(EOF_AFTER_LINE), num_read -1);
 | |
|       break;
 | |
|     } else {
 | |
|       snprintf(g->Message, sizeof(g->Message), MSG(ERR_READING_REC), num_read, fn);
 | |
|       goto err;
 | |
|     } // endif's
 | |
| 
 | |
|     /*******************************************************************/
 | |
|     /*  Make the test for field lengths.                               */
 | |
|     /*******************************************************************/
 | |
|     i = n = phase = blank = digit = dec = 0;
 | |
| 
 | |
|     for (p = tcvp->To_Line; *p; p++)
 | |
|       if (*p == sep) {
 | |
|         if (phase != 1) {
 | |
|           if (i == MAXCOL - 1) {
 | |
|             snprintf(g->Message, sizeof(g->Message), MSG(TOO_MANY_FIELDS), num_read, fn);
 | |
|             goto err;
 | |
|             } // endif i
 | |
| 
 | |
|           if (n) {
 | |
|             len[i] = MY_MAX(len[i], n);
 | |
|             type = (digit || (dec && n == 1)) ? TYPE_STRING
 | |
|                  : (dec) ? TYPE_DOUBLE : TYPE_INT;
 | |
|             typ[i] = MY_MIN(type, typ[i]);
 | |
|             prc[i] = MY_MAX((typ[i] == TYPE_DOUBLE) ? (dec - 1) : 0, prc[i]);
 | |
|             } // endif n
 | |
| 
 | |
|           i++;
 | |
|           n = phase = blank = digit = dec = 0;
 | |
|         } else          // phase == 1
 | |
|           n++;
 | |
| 
 | |
|       } else if (*p == ' ') {
 | |
|         if (phase < 2)
 | |
|           n++;
 | |
| 
 | |
|         if (blank)
 | |
|           digit = 1;
 | |
| 
 | |
|       } else if (*p == q) {
 | |
|         if (phase == 0) {
 | |
|           if (blank) {
 | |
|             if (++nerr > mxr) {
 | |
|               snprintf(g->Message, sizeof(g->Message), MSG(MISPLACED_QUOTE), num_read);
 | |
|               goto err;
 | |
|             } else
 | |
|               goto skip;
 | |
|           }
 | |
| 
 | |
|           n = 0;
 | |
|           phase = digit = 1;
 | |
|         } else if (phase == 1) {
 | |
|           if (*(p+1) == q) {
 | |
|             // This is currently not implemented for CSV tables
 | |
| //          if (++nerr > mxr) {
 | |
| //            snprintf(g->Message, sizeof(g->Message), MSG(QUOTE_IN_QUOTE), num_read);
 | |
| //            goto err;
 | |
| //          } else
 | |
| //            goto skip;
 | |
| 
 | |
|             p++;
 | |
|             n++;
 | |
|           } else
 | |
|             phase = 2;
 | |
| 
 | |
|         } else if (++nerr > mxr) {      // phase == 2
 | |
|           snprintf(g->Message, sizeof(g->Message), MSG(MISPLACED_QUOTE), num_read);
 | |
|           goto err;
 | |
|         } else
 | |
|           goto skip;
 | |
| 
 | |
|       } else {
 | |
|         if (phase == 2) {
 | |
|           if (++nerr > mxr) {
 | |
|             snprintf(g->Message, sizeof(g->Message), MSG(MISPLACED_QUOTE), num_read);
 | |
|             goto err;
 | |
|           } else
 | |
|             goto skip;
 | |
|         }
 | |
| 
 | |
|         // isdigit cannot be used here because of debug assert
 | |
|         if (!strchr("0123456789", *p)) {
 | |
|           if (!digit && *p == dechar)
 | |
|             dec = 1;                    // Decimal point found
 | |
|           else if (blank || !(*p == '-' || *p == '+'))
 | |
|             digit = 1;
 | |
| 
 | |
|         } else if (dec)
 | |
|           dec++;                        // More decimals
 | |
| 
 | |
|         n++;
 | |
|         blank = 1;
 | |
|       } // endif's *p
 | |
| 
 | |
|     if (phase == 1) {
 | |
|       if (++nerr > mxr) {
 | |
|         snprintf(g->Message, sizeof(g->Message), MSG(UNBALANCE_QUOTE), num_read);
 | |
|         goto err;
 | |
|       } else
 | |
|         goto skip;
 | |
|     }
 | |
| 
 | |
|     if (n) {
 | |
|       len[i] = MY_MAX(len[i], n);
 | |
|       type = (digit || n == 0 || (dec && n == 1)) ? TYPE_STRING
 | |
|            : (dec) ? TYPE_DOUBLE : TYPE_INT;
 | |
|       typ[i] = MY_MIN(type, typ[i]);
 | |
|       prc[i]  = MY_MAX((typ[i] == TYPE_DOUBLE) ? (dec - 1) : 0, prc[i]);
 | |
|       } // endif n
 | |
| 
 | |
|     imax = MY_MAX(imax, i+1);
 | |
|    skip: ;                  // Skip erroneous line
 | |
|     } // endfor num_read
 | |
| 
 | |
|   if (trace(1)) {
 | |
|     htrc("imax=%d Lengths:", imax);
 | |
| 
 | |
|     for (i = 0; i < imax; i++)
 | |
|       htrc(" %d", len[i]);
 | |
| 
 | |
|     htrc("\n");
 | |
|   } // endif trace
 | |
| 
 | |
| 	tdbp->CloseDB(g);
 | |
| 
 | |
|  skipit:
 | |
|   if (trace(1))
 | |
|     htrc("CSVColumns: imax=%d hmax=%d len=%d\n",
 | |
|                       imax, hmax, length[0]);
 | |
| 
 | |
|   /*********************************************************************/
 | |
|   /*  Allocate the structures used to refer to the result set.         */
 | |
|   /*********************************************************************/
 | |
|   qrp = PlgAllocResult(g, ncol, imax, IDS_COLUMNS + 3,
 | |
|                           buftyp, fldtyp, length, false, false);
 | |
|   if (info || !qrp)
 | |
|     return qrp;
 | |
| 
 | |
|   qrp->Nblin = imax;
 | |
| 
 | |
|   /*********************************************************************/
 | |
|   /*  Now get the results into blocks.                                 */
 | |
|   /*********************************************************************/
 | |
|   for (i = 0; i < imax; i++) {
 | |
|     if (i >= hmax) {
 | |
|       sprintf(buf, "COL%.3d", i+1);
 | |
|       p = buf;
 | |
|     } else
 | |
|       p = colname[i];
 | |
| 
 | |
|     if (typ[i] == TYPE_UNKNOWN)            // Void column
 | |
|       typ[i] = TYPE_STRING;
 | |
| 
 | |
|     crp = qrp->Colresp;                    // Column Name
 | |
|     crp->Kdata->SetValue(p, i);
 | |
|     crp = crp->Next;                       // Data Type
 | |
|     crp->Kdata->SetValue(typ[i], i);
 | |
|     crp = crp->Next;                       // Type Name
 | |
|     crp->Kdata->SetValue(GetTypeName(typ[i]), i);
 | |
|     crp = crp->Next;                       // Precision
 | |
|     crp->Kdata->SetValue(len[i], i);
 | |
|     crp = crp->Next;                       // Length
 | |
|     crp->Kdata->SetValue(len[i], i);
 | |
|     crp = crp->Next;                       // Scale (precision)
 | |
|     crp->Kdata->SetValue(prc[i], i);
 | |
|     } // endfor i
 | |
| 
 | |
|   /*********************************************************************/
 | |
|   /*  Return the result pointer for use by GetData routines.           */
 | |
|   /*********************************************************************/
 | |
|   return qrp;
 | |
| 
 | |
|  err:
 | |
|   tdbp->CloseDB(g);
 | |
|   return NULL;
 | |
|   } // end of CSVCColumns
 | |
| 
 | |
| /* --------------------------- Class CSVDEF -------------------------- */
 | |
| 
 | |
| /***********************************************************************/
 | |
| /*  CSVDEF constructor.                                                */
 | |
| /***********************************************************************/
 | |
| CSVDEF::CSVDEF(void)
 | |
|   {
 | |
|   Fmtd = Header = false;
 | |
| //Maxerr = 0;
 | |
|   Quoted = -1;
 | |
|   Sep = ',';
 | |
|   Qot = '\0';
 | |
|   }  // end of CSVDEF constructor
 | |
| 
 | |
| /***********************************************************************/
 | |
| /*  DefineAM: define specific AM block values from XDB file.           */
 | |
| /***********************************************************************/
 | |
| bool CSVDEF::DefineAM(PGLOBAL g, LPCSTR am, int poff)
 | |
|   {
 | |
|   char   buf[8];
 | |
| 
 | |
|   // Double check correctness of offset values
 | |
|   if (Catfunc == FNC_NO)
 | |
|     for (PCOLDEF cdp = To_Cols; cdp; cdp = cdp->GetNext())
 | |
|       if (cdp->GetOffset() < 1 && !cdp->IsSpecial()) {
 | |
|         safe_strcpy(g->Message, sizeof(g->Message), MSG(BAD_OFFSET_VAL));
 | |
|         return true;
 | |
|         } // endif Offset
 | |
| 
 | |
|   // Call DOSDEF DefineAM with am=CSV so FMT is not confused with FIX
 | |
|   if (DOSDEF::DefineAM(g, "CSV", poff))
 | |
|     return true;
 | |
| 
 | |
| 	Recfm = RECFM_CSV;
 | |
|   GetCharCatInfo("Separator", ",", buf, sizeof(buf));
 | |
|   Sep = (strlen(buf) == 2 && buf[0] == '\\' && buf[1] == 't') ? '\t' : *buf;
 | |
|   Quoted = GetIntCatInfo("Quoted", -1);
 | |
|   GetCharCatInfo("Qchar", "", buf, sizeof(buf));
 | |
|   Qot = *buf;
 | |
| 
 | |
|   if (Qot && Quoted < 0)
 | |
|     Quoted = 0;
 | |
|   else if (!Qot && Quoted >= 0)
 | |
|     Qot = '"';
 | |
| 
 | |
|   Fmtd = (!Sep || (am && (*am == 'F' || *am == 'f')));
 | |
|   Header = GetBoolCatInfo("Header", false);
 | |
|   Maxerr = GetIntCatInfo("Maxerr", 0);
 | |
|   Accept = GetBoolCatInfo("Accept", false);
 | |
| 
 | |
|   if (Accept && Maxerr == 0)
 | |
|     Maxerr = INT_MAX32;       // Accept all bad lines
 | |
| 
 | |
|   return false;
 | |
|   } // end of DefineAM
 | |
| 
 | |
| /***********************************************************************/
 | |
| /*  GetTable: makes a new Table Description Block.                     */
 | |
| /***********************************************************************/
 | |
| PTDB CSVDEF::GetTable(PGLOBAL g, MODE mode)
 | |
|   {
 | |
|   PTDBASE tdbp;
 | |
| 
 | |
|   if (Catfunc != FNC_COL) {
 | |
|     USETEMP tmp = UseTemp();
 | |
|     bool    map = Mapped && mode != MODE_INSERT &&
 | |
|                   !(tmp != TMP_NO && mode == MODE_UPDATE) &&
 | |
|                   !(tmp == TMP_FORCE &&
 | |
|                   (mode == MODE_UPDATE || mode == MODE_DELETE));
 | |
|     PTXF    txfp;
 | |
| 
 | |
|     /*******************************************************************/
 | |
|     /*  Allocate a file processing class of the proper type.           */
 | |
|     /*******************************************************************/
 | |
| 		if (Zipped) {
 | |
| #if defined(ZIP_SUPPORT)
 | |
| 			if (mode == MODE_READ || mode == MODE_ANY || mode == MODE_ALTER) {
 | |
| 				txfp = new(g) UNZFAM(this);
 | |
| 			} else if (mode == MODE_INSERT) {
 | |
| 				txfp = new(g) ZIPFAM(this);
 | |
| 			} else {
 | |
| 				safe_strcpy(g->Message, sizeof(g->Message), "UPDATE/DELETE not supported for ZIP");
 | |
| 				return NULL;
 | |
| 			}	// endif's mode
 | |
| #else   // !ZIP_SUPPORT
 | |
| 			safe_strcpy(g->Message, sizeof(g->Message), "ZIP not supported");
 | |
| 			return NULL;
 | |
| #endif  // !ZIP_SUPPORT
 | |
| 		} else if (map) {
 | |
|       // Should be now compatible with UNIX
 | |
|       txfp = new(g) MAPFAM(this);
 | |
|     } else if (Compressed) {
 | |
| #if defined(GZ_SUPPORT)
 | |
|       if (Compressed == 1)
 | |
|         txfp = new(g) GZFAM(this);
 | |
|       else
 | |
|         txfp = new(g) ZLBFAM(this);
 | |
| 
 | |
| #else   // !GZ_SUPPORT
 | |
|         safe_strcpy(g->Message, sizeof(g->Message), "Compress not supported");
 | |
|         return NULL;
 | |
| #endif  // !GZ_SUPPORT
 | |
|     } else
 | |
|       txfp = new(g) DOSFAM(this);
 | |
| 
 | |
|     /*******************************************************************/
 | |
|     /*  Allocate a TDB of the proper type.                             */
 | |
|     /*  Column blocks will be allocated only when needed.              */
 | |
|     /*******************************************************************/
 | |
|     if (!Fmtd)
 | |
|       tdbp = new(g) TDBCSV(this, txfp);
 | |
|     else
 | |
|       tdbp = new(g) TDBFMT(this, txfp);
 | |
| 
 | |
|     if (Multiple)
 | |
|       tdbp = new(g) TDBMUL(tdbp);
 | |
|     else
 | |
|       /*****************************************************************/
 | |
|       /*  For block tables, get eventually saved optimization values.  */
 | |
|       /*****************************************************************/
 | |
|       if (tdbp->GetBlockValues(g)) {
 | |
|         PushWarning(g, tdbp);
 | |
| //      return NULL;          // causes a crash when deleting index
 | |
|       } else {
 | |
|         if (IsOptimized()) {
 | |
|           if (map) {
 | |
|             txfp = new(g) MBKFAM(this);
 | |
|           } else if (Compressed) {
 | |
| #if defined(GZ_SUPPORT)
 | |
|             if (Compressed == 1)
 | |
|               txfp = new(g) ZBKFAM(this);
 | |
|             else {
 | |
|               txfp->SetBlkPos(To_Pos);
 | |
|               ((PZLBFAM)txfp)->SetOptimized(To_Pos != NULL);
 | |
|               } // endelse
 | |
| #else
 | |
|             snprintf(g->Message, sizeof(g->Message), MSG(NO_FEAT_SUPPORT), "GZ");
 | |
|             return NULL;
 | |
| #endif
 | |
|           } else
 | |
|             txfp = new(g) BLKFAM(this);
 | |
| 
 | |
|           ((PTDBDOS)tdbp)->SetTxfp(txfp);
 | |
|           } // endif Optimized
 | |
| 
 | |
|       } // endelse
 | |
| 
 | |
|   } else
 | |
|     tdbp = new(g)TDBCCL(this);
 | |
| 
 | |
|   return tdbp;
 | |
|   } // end of GetTable
 | |
| 
 | |
| /* -------------------------- Class TDBCSV --------------------------- */
 | |
| 
 | |
| /***********************************************************************/
 | |
| /*  Implementation of the TDBCSV class.                                */
 | |
| /***********************************************************************/
 | |
| TDBCSV::TDBCSV(PCSVDEF tdp, PTXF txfp) : TDBDOS(tdp, txfp)
 | |
|   {
 | |
| #if defined(_DEBUG)
 | |
|   assert (tdp);
 | |
| #endif
 | |
|   Field  = NULL;
 | |
|   Offset = NULL;
 | |
|   Fldlen = NULL;
 | |
|   Fields = 0;
 | |
|   Nerr = 0;
 | |
|   Quoted = tdp->Quoted;
 | |
|   Maxerr = tdp->Maxerr;
 | |
|   Accept = tdp->Accept;
 | |
|   Header = tdp->Header;
 | |
|   Sep = tdp->GetSep();
 | |
|   Qot = tdp->GetQot();
 | |
|   } // end of TDBCSV standard constructor
 | |
| 
 | |
| TDBCSV::TDBCSV(PGLOBAL g, PTDBCSV tdbp) : TDBDOS(g, tdbp)
 | |
|   {
 | |
|   Fields = tdbp->Fields;
 | |
| 
 | |
|   if (Fields) {
 | |
|     if (tdbp->Offset)
 | |
|       Offset = (int*)PlugSubAlloc(g, NULL, sizeof(int) * Fields);
 | |
| 
 | |
|     if (tdbp->Fldlen)
 | |
|       Fldlen = (int*)PlugSubAlloc(g, NULL, sizeof(int) * Fields);
 | |
| 
 | |
|     Field = (PSZ *)PlugSubAlloc(g, NULL, sizeof(PSZ) * Fields);
 | |
| 
 | |
|     for (int i = 0; i < Fields; i++) {
 | |
|       if (Offset)
 | |
|         Offset[i] = tdbp->Offset[i];
 | |
| 
 | |
|       if (Fldlen)
 | |
|         Fldlen[i] = tdbp->Fldlen[i];
 | |
| 
 | |
|       if (Field) {
 | |
|         assert (Fldlen);
 | |
|         Field[i] = (PSZ)PlugSubAlloc(g, NULL, Fldlen[i] + 1);
 | |
|         Field[i][Fldlen[i]] = '\0';
 | |
|         } // endif Field
 | |
| 
 | |
|       } // endfor i
 | |
| 
 | |
|   } else {
 | |
|     Field  = NULL;
 | |
|     Offset = NULL;
 | |
|     Fldlen = NULL;
 | |
|   } // endif Fields
 | |
| 
 | |
|   Nerr = tdbp->Nerr;
 | |
|   Maxerr = tdbp->Maxerr;
 | |
|   Quoted = tdbp->Quoted;
 | |
|   Accept = tdbp->Accept;
 | |
|   Header = tdbp->Header;
 | |
|   Sep = tdbp->Sep;
 | |
|   Qot = tdbp->Qot;
 | |
|   } // end of TDBCSV copy constructor
 | |
| 
 | |
| // Method
 | |
| PTDB TDBCSV::Clone(PTABS t)
 | |
|   {
 | |
|   PTDB    tp;
 | |
|   PCSVCOL cp1, cp2;
 | |
|   PGLOBAL g = t->G;        // Is this really useful ???
 | |
| 
 | |
|   tp = new(g) TDBCSV(g, this);
 | |
| 
 | |
|   for (cp1 = (PCSVCOL)Columns; cp1; cp1 = (PCSVCOL)cp1->GetNext()) {
 | |
|     cp2 = new(g) CSVCOL(cp1, tp);  // Make a copy
 | |
|     NewPointer(t, cp1, cp2);
 | |
|     } // endfor cp1
 | |
| 
 | |
|   return tp;
 | |
|   } // end of Clone
 | |
| 
 | |
| /***********************************************************************/
 | |
| /*  Allocate CSV column description block.                             */
 | |
| /***********************************************************************/
 | |
| PCOL TDBCSV::MakeCol(PGLOBAL g, PCOLDEF cdp, PCOL cprec, int n)
 | |
|   {
 | |
|   return new(g) CSVCOL(g, cdp, this, cprec, n);
 | |
|   } // end of MakeCol
 | |
| 
 | |
| /***********************************************************************/
 | |
| /*  Check whether the number of errors is greater than the maximum.    */
 | |
| /***********************************************************************/
 | |
| bool TDBCSV::CheckErr(void)
 | |
|   {
 | |
|   return (++Nerr) > Maxerr;
 | |
|   } // end of CheckErr
 | |
| 
 | |
| /***********************************************************************/
 | |
| /*  CSV EstimatedLength. Returns an estimated minimum line length.     */
 | |
| /***********************************************************************/
 | |
| int TDBCSV::EstimatedLength(void)
 | |
|   {
 | |
|   int     n = 0;
 | |
|   PCOLDEF cdp;
 | |
| 
 | |
|   if (trace(1))
 | |
|     htrc("EstimatedLength: Fields=%d Columns=%p\n", Fields, Columns);
 | |
| 
 | |
|   for (cdp = To_Def->GetCols(); cdp; cdp = cdp->GetNext())
 | |
|     if (!cdp->IsSpecial() && !cdp->IsVirtual())  // A true column
 | |
|       n++;
 | |
| 
 | |
|   return --n;   // Number of separators if all fields are null
 | |
|   } // end of Estimated Length
 | |
| 
 | |
| #if 0
 | |
| /***********************************************************************/
 | |
| /*  CSV tables needs the use temporary files for Update.               */
 | |
| /***********************************************************************/
 | |
| bool TDBCSV::IsUsingTemp(PGLOBAL g)
 | |
|   {
 | |
|   return (Use_Temp == TMP_YES || Use_Temp == TMP_FORCE ||
 | |
|          (Use_Temp == TMP_AUTO && Mode == MODE_UPDATE));
 | |
|   } // end of IsUsingTemp
 | |
| #endif // 0  (Same as TDBDOS one)
 | |
| 
 | |
| /***********************************************************************/
 | |
| /*  CSV Access Method opening routine.                                 */
 | |
| /*  First allocate the Offset and Fldlen arrays according to the       */
 | |
| /*  greatest field used in that query. Then call the DOS opening fnc.  */
 | |
| /***********************************************************************/
 | |
| bool TDBCSV::OpenDB(PGLOBAL g)
 | |
|   {
 | |
|   bool    rc = false;
 | |
|   PCOLDEF cdp;
 | |
|   PDOSDEF tdp = (PDOSDEF)To_Def;
 | |
| 
 | |
|   if (Use != USE_OPEN && (Columns || Mode == MODE_UPDATE)) {
 | |
|     // Allocate the storage used to read (or write) records
 | |
|     int     i, len;
 | |
|     PCSVCOL colp;
 | |
| 
 | |
|     if (!Fields) {            // May have been set in TABFMT::OpenDB
 | |
|       if (Mode != MODE_UPDATE && Mode != MODE_INSERT) {
 | |
|         for (colp = (PCSVCOL)Columns; colp; colp = (PCSVCOL)colp->Next)
 | |
|           if (!colp->IsSpecial() && !colp->IsVirtual())
 | |
|             Fields = MY_MAX(Fields, (int)colp->Fldnum);
 | |
| 
 | |
|         if (Columns)
 | |
|           Fields++;           // Fldnum was 0 based
 | |
| 
 | |
|       } else
 | |
|         for (cdp = tdp->GetCols(); cdp; cdp = cdp->GetNext())
 | |
|           if (!cdp->IsSpecial() && !cdp->IsVirtual())
 | |
|             Fields++;
 | |
|     }
 | |
| 
 | |
|     Offset = (int*)PlugSubAlloc(g, NULL, sizeof(int) * Fields);
 | |
|     Fldlen = (int*)PlugSubAlloc(g, NULL, sizeof(int) * Fields);
 | |
| 
 | |
|     if (Mode == MODE_INSERT || Mode == MODE_UPDATE) {
 | |
|       Field = (PSZ*)PlugSubAlloc(g, NULL, sizeof(PSZ) * Fields);
 | |
|       Fldtyp = (bool*)PlugSubAlloc(g, NULL, sizeof(bool) * Fields);
 | |
|       } // endif Mode
 | |
| 
 | |
|     for (i = 0; i < Fields; i++) {
 | |
|       Offset[i] = 0;
 | |
|       Fldlen[i] = 0;
 | |
| 
 | |
|       if (Field) {
 | |
|         Field[i] = NULL;
 | |
|         Fldtyp[i] = false;
 | |
|         } // endif Field
 | |
| 
 | |
|       } // endfor i
 | |
| 
 | |
|     if (Field) {
 | |
|       // Prepare writing fields
 | |
|       if (Mode != MODE_UPDATE) {
 | |
|         for (colp = (PCSVCOL)Columns; colp; colp = (PCSVCOL)colp->Next)
 | |
|           if (!colp->IsSpecial() && !colp->IsVirtual()) {
 | |
|             i = colp->Fldnum;
 | |
|             len = colp->GetLength();
 | |
|             Field[i] = (PSZ)PlugSubAlloc(g, NULL, len + 1);
 | |
|             Field[i][len] = '\0';
 | |
|             Fldlen[i] = len;
 | |
|             Fldtyp[i] = IsTypeNum(colp->GetResultType());
 | |
|             } // endif colp
 | |
| 
 | |
|       } else     // MODE_UPDATE
 | |
|         for (cdp = tdp->GetCols(); cdp; cdp = cdp->GetNext())
 | |
|           if (!cdp->IsSpecial() && !cdp->IsVirtual()) {
 | |
|             i = cdp->GetOffset() - 1;
 | |
|             len = cdp->GetLength();
 | |
|             Field[i] = (PSZ)PlugSubAlloc(g, NULL, len + 1);
 | |
|             Field[i][len] = '\0';
 | |
|             Fldlen[i] = len;
 | |
|             Fldtyp[i] = IsTypeNum(cdp->GetType());
 | |
|             } // endif cdp
 | |
|     }
 | |
|   } // endif Use
 | |
| 
 | |
|   if (Header) {
 | |
|     // Check that the Lrecl is at least equal to the header line length
 | |
|     int     headlen = 0;
 | |
|     PCOLDEF cdp;
 | |
|     PDOSDEF tdp = (PDOSDEF)To_Def;
 | |
| 
 | |
|     for (cdp = tdp->GetCols(); cdp; cdp = cdp->GetNext())
 | |
|       headlen += strlen(cdp->GetName()) + 3;  // 3 if names are quoted
 | |
| 
 | |
|     if (headlen > Lrecl) {
 | |
|       Lrecl = headlen;
 | |
|       Txfp->Lrecl = headlen;
 | |
|       } // endif headlen
 | |
| 
 | |
|     } // endif Header
 | |
| 
 | |
|   Nerr = 0;
 | |
|   rc = TDBDOS::OpenDB(g);
 | |
| 
 | |
|   if (!rc && Mode == MODE_UPDATE && To_Kindex)
 | |
|     // Because KINDEX::Init is executed in mode READ, we must restore
 | |
|     // the Fldlen array that was modified when reading the table file.
 | |
|     for (cdp = tdp->GetCols(); cdp; cdp = cdp->GetNext())
 | |
|       Fldlen[cdp->GetOffset() - 1] = cdp->GetLength();
 | |
| 
 | |
|   return rc;
 | |
|   } // end of OpenDB
 | |
| 
 | |
| /***********************************************************************/
 | |
| /*  SkipHeader: Physically skip first header line if applicable.       */
 | |
| /*  This is called from TDBDOS::OpenDB and must be executed before     */
 | |
| /*  Kindex construction if the file is accessed using an index.        */
 | |
| /***********************************************************************/
 | |
| bool TDBCSV::SkipHeader(PGLOBAL g)
 | |
|   {
 | |
|   int len = GetFileLength(g);
 | |
|   bool rc = false;
 | |
| 
 | |
| #if defined(_DEBUG)
 | |
|   if (len < 0)
 | |
|     return true;
 | |
| #endif   // _DEBUG
 | |
| 
 | |
|   if (Header) {
 | |
|     if (Mode == MODE_INSERT) {
 | |
|       if (!len) {
 | |
|         // New file, the header line must be constructed and written
 | |
|         int     i, n = 0;
 | |
|         int    hlen = 0;
 | |
|         bool    q = Qot && Quoted > 0;
 | |
|         PCOLDEF cdp;
 | |
|         char *pos, *end;
 | |
| 
 | |
|         // Estimate the length of the header list
 | |
|         for (cdp = To_Def->GetCols(); cdp; cdp = cdp->GetNext()) {
 | |
|           hlen += (1 + strlen(cdp->GetName()));
 | |
|           hlen += ((q) ? 2 : 0);
 | |
|           n++;            // Calculate the number of columns
 | |
|           } // endfor cdp
 | |
| 
 | |
|         if (hlen > Lrecl) {
 | |
|           snprintf(g->Message, sizeof(g->Message), MSG(LRECL_TOO_SMALL), hlen);
 | |
|           return true;
 | |
|           } // endif hlen
 | |
| 
 | |
|         // File is empty, write a header record
 | |
|         memset(To_Line, 0, Lrecl);
 | |
|         pos= To_Line;
 | |
|         end= To_Line + Lrecl-1;
 | |
| 
 | |
|         // The column order in the file is given by the offset value
 | |
|         for (i = 1; i <= n; i++)
 | |
|           for (cdp = To_Def->GetCols(); cdp; cdp = cdp->GetNext())
 | |
|             if (cdp->GetOffset() == i) {
 | |
|               if (q && pos < end)
 | |
|                 *pos++= Qot;
 | |
| 
 | |
|               pos= strnmov(pos, cdp->GetName(), (size_t) (end-pos));
 | |
| 
 | |
|               if (q && pos < end)
 | |
|                 *pos++= Qot;
 | |
| 
 | |
|               if (i < n && pos < end)
 | |
|                 *pos++= Sep;
 | |
| 
 | |
|               } // endif Offset
 | |
|         *pos= 0;
 | |
| 
 | |
|         rc = (Txfp->WriteBuffer(g) == RC_FX);
 | |
|         } // endif !FileLength
 | |
| 
 | |
|     } else if (Mode == MODE_DELETE) {
 | |
|       if (len)
 | |
|         rc = (Txfp->SkipRecord(g, true) == RC_FX);
 | |
| 
 | |
|     } else if (len) // !Insert && !Delete
 | |
|       rc = (Txfp->SkipRecord(g, false) == RC_FX || Txfp->RecordPos(g));
 | |
| 
 | |
|     } // endif Header
 | |
| 
 | |
|   return rc;
 | |
|   } // end of SkipHeader
 | |
| 
 | |
| /***********************************************************************/
 | |
| /*  ReadBuffer: Physical read routine for the CSV access method.       */
 | |
| /***********************************************************************/
 | |
| int TDBCSV::ReadBuffer(PGLOBAL g)
 | |
|   {
 | |
|   //char *p1, *p2, *p = NULL;
 | |
| 	char *p2, *p = NULL;
 | |
| 	int   i, n, len, rc = Txfp->ReadBuffer(g);
 | |
|   bool  bad = false;
 | |
| 
 | |
|   if (trace(2))
 | |
|     htrc("CSV: Row is '%s' rc=%d\n", To_Line, rc);
 | |
| 
 | |
|   if (rc != RC_OK || !Fields)
 | |
|     return rc;
 | |
|   else
 | |
|     p2 = To_Line;
 | |
| 
 | |
|   // Find the offsets and lengths of the columns for this row
 | |
|   for (i = 0; i < Fields; i++) {
 | |
|     if (!bad) {
 | |
|       if (Qot && *p2 == Qot) {                // Quoted field
 | |
|         //for (n = 0, p1 = ++p2; (p = strchr(p1, Qot)); p1 = p + 2)
 | |
|         //  if (*(p + 1) == Qot)
 | |
|         //    n++;                              // Doubled internal quotes
 | |
|         //  else
 | |
|         //    break;                            // Final quote
 | |
| 
 | |
| 				for (n = 0, p = ++p2; p; p++)
 | |
| 					if (*p == Qot || *p == '\\') {
 | |
| 						if (*(++p) == Qot)
 | |
| 							n++;														// Escaped internal quotes
 | |
| 						else if (*(p - 1) == Qot)
 | |
| 							break;													// Final quote
 | |
| 					}	// endif *p
 | |
| 
 | |
|         if (p) {
 | |
|           //len = p++ - p2;
 | |
| 					len = (int)(p - p2 - 1);
 | |
| 
 | |
| //        if (Sep != ' ')
 | |
| //          for (; *p == ' '; p++) ;          // Skip blanks
 | |
| 
 | |
|           if (*p != Sep && i != Fields - 1) { // Should be the separator
 | |
|             if (CheckErr()) {
 | |
|               snprintf(g->Message, sizeof(g->Message), MSG(MISSING_FIELD),
 | |
|                                   i+1, Name, RowNumber(g));
 | |
|               return RC_FX;
 | |
|             } else if (Accept)
 | |
|               bad = true;
 | |
|             else
 | |
|               return RC_NF;
 | |
| 
 | |
|             } // endif p
 | |
| 
 | |
|           if (n) {
 | |
|             int j, k;
 | |
| 
 | |
|             // Suppress the escape of internal quotes
 | |
|             for (j = k = 0; j < len; j++, k++) {
 | |
|               if (p2[j] == Qot || (p2[j] == '\\' && p2[j + 1] == Qot))
 | |
|                 j++;                          // skip escape char
 | |
| 							else if (p2[j] == '\\')
 | |
| 								p2[k++] = p2[j++];						// avoid \\Qot
 | |
| 
 | |
|               p2[k] = p2[j];
 | |
|               } // endfor i, j
 | |
| 
 | |
|             len -= n;
 | |
|             } // endif n
 | |
| 
 | |
|         } else if (CheckErr()) {
 | |
|           snprintf(g->Message, sizeof(g->Message), MSG(BAD_QUOTE_FIELD),
 | |
|                               Name, i+1, RowNumber(g));
 | |
|           return RC_FX;
 | |
|         } else if (Accept) {
 | |
|           len = strlen(p2);
 | |
|           bad = true;
 | |
|         } else
 | |
|           return RC_NF;
 | |
| 
 | |
|       } else if ((p = strchr(p2, Sep)))
 | |
|         len = (int)(p - p2);
 | |
|       else if (i == Fields - 1)
 | |
|         len = strlen(p2);
 | |
|       else if (Accept && Maxerr == 0) {
 | |
|         len = strlen(p2);
 | |
|         bad = true;
 | |
|       } else if (CheckErr()) {
 | |
|         snprintf(g->Message, sizeof(g->Message), MSG(MISSING_FIELD), i+1, Name, RowNumber(g));
 | |
|         return RC_FX;
 | |
|       } else if (Accept) {
 | |
|         len = strlen(p2);
 | |
|         bad = true;
 | |
|       } else
 | |
|         return RC_NF;
 | |
| 
 | |
|     } else
 | |
|       len = 0;
 | |
| 
 | |
|     Offset[i] = (int)(p2 - To_Line);
 | |
| 
 | |
|     if (Mode != MODE_UPDATE)
 | |
|       Fldlen[i] = len;
 | |
|     else if (len > Fldlen[i]) {
 | |
|       snprintf(g->Message, sizeof(g->Message), MSG(FIELD_TOO_LONG), i+1, RowNumber(g));
 | |
|       return RC_FX;
 | |
|     } else {
 | |
|       strncpy(Field[i], p2, len);
 | |
|       Field[i][len] = '\0';
 | |
|     } // endif Mode
 | |
| 
 | |
|     if (p)
 | |
|       p2 = p + 1;
 | |
| 
 | |
|     } // endfor i
 | |
| 
 | |
|   return rc;
 | |
|   } // end of ReadBuffer
 | |
| 
 | |
| /***********************************************************************/
 | |
| /*  Prepare the line to write.                                         */
 | |
| /***********************************************************************/
 | |
| bool TDBCSV::PrepareWriting(PGLOBAL g)
 | |
|   {
 | |
|   char sep[2], qot[2];
 | |
|   int  i, nlen, oldlen = strlen(To_Line);
 | |
| 
 | |
|   if (trace(2))
 | |
|     htrc("CSV WriteDB: R%d Mode=%d key=%p link=%p\n",
 | |
|           Tdb_No, Mode, To_Key_Col, To_Link);
 | |
| 
 | |
|   // Before writing the line we must check its length
 | |
|   if ((nlen = CheckWrite(g)) < 0)
 | |
|     return true;
 | |
| 
 | |
|   // Before writing the line we must make it
 | |
|   sep[0] = Sep;
 | |
|   sep[1] = '\0';
 | |
|   qot[0] = Qot;
 | |
|   qot[1] = '\0';
 | |
|   *To_Line = '\0';
 | |
| 
 | |
|   for (i = 0; i < Fields; i++) {
 | |
|     if (i)
 | |
|       safe_strcat(To_Line, Lrecl, sep);
 | |
| 
 | |
|     if (Field[i]) {
 | |
|       if (!strlen(Field[i])) {
 | |
|         // Generally null fields are not quoted
 | |
|         if (Quoted > 2) {
 | |
|           // Except if explicitly required
 | |
|           safe_strcat(To_Line, Lrecl, qot);
 | |
|           safe_strcat(To_Line, Lrecl, qot);
 | |
|         }
 | |
| 
 | |
|       } else if (Qot && (strchr(Field[i], Sep) || *Field[i] == Qot
 | |
|               || Quoted > 1 || (Quoted == 1 && !Fldtyp[i]))) {
 | |
|         if (strchr(Field[i], Qot)) {
 | |
|           // Field contains quotes that must be doubled
 | |
|           int j, k = strlen(To_Line), n = strlen(Field[i]);
 | |
| 
 | |
|           To_Line[k++] = Qot;
 | |
| 
 | |
|           for (j = 0; j < n; j++) {
 | |
|             if (Field[i][j] == Qot)
 | |
|               To_Line[k++] = Qot;
 | |
| 
 | |
|             To_Line[k++] = Field[i][j];
 | |
|             } // endfor j
 | |
| 
 | |
|           To_Line[k++] = Qot;
 | |
|           To_Line[k] = '\0';
 | |
|         } else {
 | |
|           safe_strcat(To_Line, Lrecl, qot);
 | |
|           safe_strcat(To_Line, Lrecl, Field[i]);
 | |
|           safe_strcat(To_Line, Lrecl, qot);
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       else
 | |
|         safe_strcat(To_Line, Lrecl, Field[i]);
 | |
|     }
 | |
|   } // endfor i
 | |
| 
 | |
| #if defined(_DEBUG)
 | |
|   assert ((unsigned)nlen == strlen(To_Line));
 | |
| #endif
 | |
| 
 | |
|   if (Mode == MODE_UPDATE && nlen < oldlen
 | |
|                           && !((PDOSFAM)Txfp)->GetUseTemp()) {
 | |
|     // In Update mode with no temp file, line length must not change
 | |
|     To_Line[nlen] = Sep;
 | |
| 
 | |
|     for (nlen++; nlen < oldlen; nlen++)
 | |
|       To_Line[nlen] = ' ';
 | |
| 
 | |
|     To_Line[nlen] = '\0';
 | |
|     } // endif
 | |
| 
 | |
|   if (trace(2))
 | |
|     htrc("Write: line is=%s", To_Line);
 | |
| 
 | |
|   return false;
 | |
|   } // end of PrepareWriting
 | |
| 
 | |
| /***********************************************************************/
 | |
| /*  Data Base write routine CSV file access method.                    */
 | |
| /***********************************************************************/
 | |
| int TDBCSV::WriteDB(PGLOBAL g)
 | |
|   {
 | |
|   // Before writing the line we must check and prepare it
 | |
|   if (PrepareWriting(g))
 | |
|     return RC_FX;
 | |
| 
 | |
|   /*********************************************************************/
 | |
|   /*  Now start the writing process.                                   */
 | |
|   /*********************************************************************/
 | |
|   return Txfp->WriteBuffer(g);
 | |
|   } // end of WriteDB
 | |
| 
 | |
| /***********************************************************************/
 | |
| /*  Check whether a new line fit in the file lrecl size.               */
 | |
| /***********************************************************************/
 | |
| int TDBCSV::CheckWrite(PGLOBAL g)
 | |
|   {
 | |
|   int maxlen, n, nlen = (Fields - 1);
 | |
| 
 | |
|   if (trace(2))
 | |
|     htrc("CheckWrite: R%d Mode=%d\n", Tdb_No, Mode);
 | |
| 
 | |
|   // Before writing the line we must check its length
 | |
|   maxlen = (Mode == MODE_UPDATE && !Txfp->GetUseTemp())
 | |
|          ? strlen(To_Line) : Lrecl;
 | |
| 
 | |
|   // Check whether record is too int
 | |
|   for (int i = 0; i < Fields; i++)
 | |
|   {
 | |
|     if (Field[i]) {
 | |
|       if (!(n = strlen(Field[i])))
 | |
|         n += (Quoted > 2 ? 2 : 0);
 | |
|       else if (strchr(Field[i], Sep) || (Qot && *Field[i] == Qot)
 | |
|           || Quoted > 1 || (Quoted == 1 && !Fldtyp[i]))
 | |
|       {
 | |
|         if (!Qot) {
 | |
|           snprintf(g->Message, sizeof(g->Message), MSG(SEP_IN_FIELD), i + 1);
 | |
|           return -1;
 | |
|         } else {
 | |
|           // Quotes inside a quoted field must be doubled
 | |
|           char *p1, *p2;
 | |
| 
 | |
|           for (p1 = Field[i]; (p2 = strchr(p1, Qot)); p1 = p2 + 1)
 | |
|             n++;
 | |
| 
 | |
|           n += 2;        // Outside quotes
 | |
|         } // endif
 | |
|       }
 | |
|       if ((nlen += n) > maxlen) {
 | |
|         safe_strcpy(g->Message, sizeof(g->Message), MSG(LINE_TOO_LONG));
 | |
|         return -1;
 | |
|         } // endif nlen
 | |
| 
 | |
|       } // endif Field
 | |
|   }
 | |
|   return nlen;
 | |
|   } // end of CheckWrite
 | |
| 
 | |
| /* ------------------------------------------------------------------- */
 | |
| 
 | |
| /***********************************************************************/
 | |
| /*  Implementation of the TDBFMT class.                                */
 | |
| /***********************************************************************/
 | |
| TDBFMT::TDBFMT(PGLOBAL g, PTDBFMT tdbp) : TDBCSV(g, tdbp)
 | |
|   {
 | |
|   FldFormat = tdbp->FldFormat;
 | |
|   To_Fld = tdbp->To_Fld;
 | |
|   FmtTest = tdbp->FmtTest;
 | |
|   Linenum = tdbp->Linenum;
 | |
|   } // end of TDBFMT copy constructor
 | |
| 
 | |
| // Method
 | |
| PTDB TDBFMT::Clone(PTABS t)
 | |
|   {
 | |
|   PTDB    tp;
 | |
|   PCSVCOL cp1, cp2;
 | |
| //PFMTCOL cp1, cp2;
 | |
|   PGLOBAL g = t->G;        // Is this really useful ???
 | |
| 
 | |
|   tp = new(g) TDBFMT(g, this);
 | |
| 
 | |
|   for (cp1 = (PCSVCOL)Columns; cp1; cp1 = (PCSVCOL)cp1->GetNext()) {
 | |
| //for (cp1 = (PFMTCOL)Columns; cp1; cp1 = (PFMTCOL)cp1->GetNext()) {
 | |
|     cp2 = new(g) CSVCOL(cp1, tp);  // Make a copy
 | |
| //  cp2 = new(g) FMTCOL(cp1, tp);  // Make a copy
 | |
|     NewPointer(t, cp1, cp2);
 | |
|     } // endfor cp1
 | |
| 
 | |
|   return tp;
 | |
|   } // end of Clone
 | |
| 
 | |
| /***********************************************************************/
 | |
| /*  Allocate FMT column description block.                             */
 | |
| /***********************************************************************/
 | |
| PCOL TDBFMT::MakeCol(PGLOBAL g, PCOLDEF cdp, PCOL cprec, int n)
 | |
|   {
 | |
|   return new(g) CSVCOL(g, cdp, this, cprec, n);
 | |
| //return new(g) FMTCOL(cdp, this, cprec, n);
 | |
|   } // end of MakeCol
 | |
| 
 | |
| /***********************************************************************/
 | |
| /*  FMT EstimatedLength. Returns an estimated minimum line length.     */
 | |
| /*  The big problem here is how can we astimated that minimum ?        */
 | |
| /***********************************************************************/
 | |
| int TDBFMT::EstimatedLength(void)
 | |
|   {
 | |
|   // This is rather stupid !!!
 | |
|   return ((PDOSDEF)To_Def)->GetEnding() + (int)((Lrecl / 10) + 1);
 | |
|   } // end of EstimatedLength
 | |
| 
 | |
| /***********************************************************************/
 | |
| /*  FMT Access Method opening routine.                                 */
 | |
| /***********************************************************************/
 | |
| bool TDBFMT::OpenDB(PGLOBAL g)
 | |
|   {
 | |
|   Linenum = 0;
 | |
| 
 | |
|   if (Mode == MODE_INSERT || Mode == MODE_UPDATE) {
 | |
|     snprintf(g->Message, sizeof(g->Message), MSG(FMT_WRITE_NIY), "FMT");
 | |
|     return true;                    // NIY
 | |
|     } // endif Mode
 | |
| 
 | |
|   if (Use != USE_OPEN && Columns) {
 | |
|     // Make the formats used to read records
 | |
|     PSZ     pfm;
 | |
|     int     i, n;
 | |
|     PCSVCOL colp;
 | |
|     PCOLDEF cdp;
 | |
|     PDOSDEF tdp = (PDOSDEF)To_Def;
 | |
| 
 | |
|     for (colp = (PCSVCOL)Columns; colp; colp = (PCSVCOL)colp->Next)
 | |
|       if (!colp->IsSpecial() && !colp->IsVirtual())  // a true column
 | |
|         Fields = MY_MAX(Fields, (int)colp->Fldnum);
 | |
| 
 | |
|     if (Columns)
 | |
|       Fields++;                // Fldnum was 0 based
 | |
| 
 | |
|     To_Fld = PlugSubAlloc(g, NULL, Lrecl + 1);
 | |
|     FldFormat = (PSZ*)PlugSubAlloc(g, NULL, sizeof(PSZ) * Fields);
 | |
|     memset(FldFormat, 0, sizeof(PSZ) * Fields);
 | |
|     FmtTest = (int*)PlugSubAlloc(g, NULL, sizeof(int) * Fields);
 | |
|     memset(FmtTest, 0, sizeof(int) * Fields);
 | |
| 
 | |
|     // Get the column formats
 | |
|     for (cdp = tdp->GetCols(); cdp; cdp = cdp->GetNext())
 | |
|       if (!cdp->IsSpecial() && !cdp->IsVirtual() 
 | |
|                             && (i = cdp->GetOffset() - 1) < Fields) {
 | |
|         if (!(pfm = cdp->GetFmt())) {
 | |
|           snprintf(g->Message, sizeof(g->Message), MSG(NO_FLD_FORMAT), i + 1, Name);
 | |
|           return true;
 | |
|           } // endif pfm
 | |
| 
 | |
|         // Roughly check the Fmt format
 | |
|         if ((n = strlen(pfm) - 2) < 4) {
 | |
|           snprintf(g->Message, sizeof(g->Message), MSG(BAD_FLD_FORMAT), i + 1, Name);
 | |
|           return true;
 | |
|           } // endif n
 | |
| 
 | |
|         FldFormat[i] = (PSZ)PlugSubAlloc(g, NULL, n + 5);
 | |
|         safe_strcpy(FldFormat[i], n + 5, pfm);
 | |
| 
 | |
|         if (!strcmp(pfm + n, "%m")) {
 | |
|           // This is a field that can be missing. Flag it so it can
 | |
|           // be handled with special processing.
 | |
|           FldFormat[i][n+1] = 'n';  // To have sscanf normal processing
 | |
|           FmtTest[i] = 2;
 | |
|         } else if (i+1 < Fields && strcmp(pfm + n, "%n")) {
 | |
|           // There are trailing characters after the field contents
 | |
|           // add a marker for the next field start position.
 | |
|           safe_strcat(FldFormat[i], n + 5, "%n");
 | |
|           FmtTest[i] = 1;
 | |
|         } // endif's
 | |
| 
 | |
|         } // endif i
 | |
| 
 | |
|     } // endif Use
 | |
| 
 | |
|   return TDBCSV::OpenDB(g);
 | |
|   } // end of OpenDB
 | |
| 
 | |
| /***********************************************************************/
 | |
| /*  ReadBuffer: Physical read routine for the FMT access method.       */
 | |
| /***********************************************************************/
 | |
| int TDBFMT::ReadBuffer(PGLOBAL g)
 | |
|   {
 | |
|   int  i, len, n, deb, fin, nwp, pos = 0, rc;
 | |
|   bool bad = false;
 | |
| 
 | |
|   if ((rc = Txfp->ReadBuffer(g)) != RC_OK || !Fields)
 | |
|     return rc;
 | |
|   else
 | |
|     ++Linenum;
 | |
| 
 | |
|   if (trace(2))
 | |
|     htrc("FMT: Row %d is '%s' rc=%d\n", Linenum, To_Line, rc);
 | |
| 
 | |
|   // Find the offsets and lengths of the columns for this row
 | |
|   for (i = 0; i < Fields; i++) {
 | |
|     if (!bad) {
 | |
|       deb = fin = -1;
 | |
| 
 | |
|       if (!FldFormat[i]) {
 | |
|         n = 0;
 | |
|       } else if (FmtTest[i] == 1) {
 | |
|         nwp = -1;
 | |
|         n = sscanf(To_Line + pos, FldFormat[i], &deb, To_Fld, &fin, &nwp);
 | |
|       } else {
 | |
|         n = sscanf(To_Line + pos, FldFormat[i], &deb, To_Fld, &fin);
 | |
| 
 | |
|         if (n != 1 && (deb >= 0 || i == Fields - 1) && FmtTest[i] == 2) {
 | |
|           // Missing optional field, not an error
 | |
|           n = 1;
 | |
| 
 | |
|           if (i == Fields - 1)
 | |
|             fin = deb = 0;
 | |
|           else
 | |
|             fin = deb;
 | |
| 
 | |
|           } // endif n
 | |
| 
 | |
|         nwp = fin;
 | |
|       } // endif i
 | |
| 
 | |
|       if (n != 1 || deb < 0 || fin < 0 || nwp < 0) {
 | |
|         // This is to avoid a very strange sscanf bug occuring
 | |
|         // with fields that ends with a null character.
 | |
|         // This bug causes subsequent sscanf to return in error,
 | |
|         // so next lines are not parsed correctly.
 | |
|         sscanf("a", "%*c");       // Seems to reset things Ok
 | |
| 
 | |
|         if (CheckErr()) {
 | |
|           snprintf(g->Message, sizeof(g->Message), MSG(BAD_LINEFLD_FMT), Linenum, i + 1, Name);
 | |
|           return RC_FX;
 | |
|         } else if (Accept)
 | |
|           bad = true;
 | |
|         else
 | |
|           return RC_NF;
 | |
| 
 | |
|         } // endif n...
 | |
| 
 | |
|       } // endif !bad
 | |
| 
 | |
|     if (!bad) {
 | |
|       Offset[i] = pos + deb;
 | |
|       len = fin - deb;
 | |
|     } else {
 | |
|       nwp = 0;
 | |
|       Offset[i] = pos;
 | |
|       len = 0;
 | |
|     } // endif bad
 | |
| 
 | |
| //  if (Mode != MODE_UPDATE)
 | |
|       Fldlen[i] = len;
 | |
| //  else if (len > Fldlen[i]) {
 | |
| //    snprintf(g->Message, sizeof(g->Message), MSG(FIELD_TOO_LONG), i+1, To_Tdb->RowNumber(g));
 | |
| //    return RC_FX;
 | |
| //  } else {
 | |
| //    strncpy(Field[i], To_Line + pos, len);
 | |
| //    Field[i][len] = '\0';
 | |
| //  } // endif Mode
 | |
| 
 | |
|     pos += nwp;
 | |
|     } // endfor i
 | |
| 
 | |
|   if (bad)
 | |
|     Nerr++;
 | |
|   else
 | |
|     sscanf("a", "%*c");             // Seems to reset things Ok
 | |
| 
 | |
|   return rc;
 | |
|   } // end of ReadBuffer
 | |
| 
 | |
| /***********************************************************************/
 | |
| /*  Data Base write routine FMT file access method.                    */
 | |
| /***********************************************************************/
 | |
| int TDBFMT::WriteDB(PGLOBAL g)
 | |
|   {
 | |
|   snprintf(g->Message, sizeof(g->Message), MSG(FMT_WRITE_NIY), "FMT");
 | |
|   return RC_FX;                    // NIY
 | |
|   } // end of WriteDB
 | |
| 
 | |
| // ------------------------ CSVCOL functions ----------------------------
 | |
| 
 | |
| /***********************************************************************/
 | |
| /*  CSVCOL public constructor                                          */
 | |
| /***********************************************************************/
 | |
| CSVCOL::CSVCOL(PGLOBAL g, PCOLDEF cdp, PTDB tdbp, PCOL cprec, int i)
 | |
|   : DOSCOL(g, cdp, tdbp, cprec, i, "CSV")
 | |
|   {
 | |
|   Fldnum = Deplac - 1;
 | |
|   Deplac = 0;
 | |
|   } // end of CSVCOL constructor
 | |
| 
 | |
| /***********************************************************************/
 | |
| /*  CSVCOL constructor used for copying columns.                       */
 | |
| /*  tdbp is the pointer to the new table descriptor.                   */
 | |
| /***********************************************************************/
 | |
| CSVCOL::CSVCOL(CSVCOL *col1, PTDB tdbp) : DOSCOL(col1, tdbp)
 | |
|   {
 | |
|   Fldnum = col1->Fldnum;
 | |
|   } // end of CSVCOL copy constructor
 | |
| 
 | |
| /***********************************************************************/
 | |
| /*  VarSize: This function tells UpdateDB whether or not the block     */
 | |
| /*  optimization file must be redone if this column is updated, even   */
 | |
| /*  it is not sorted or clustered. This applies to a blocked table,    */
 | |
| /*  because if it is updated using a temporary file, the block size    */
 | |
| /*  may be modified.                                                   */
 | |
| /***********************************************************************/
 | |
| bool CSVCOL::VarSize(void)
 | |
|   {
 | |
|   PTXF txfp = ((PTDBCSV)To_Tdb)->Txfp;
 | |
| 
 | |
|   if (txfp->IsBlocked() && txfp->GetUseTemp())
 | |
|     // Blocked table using a temporary file
 | |
|     return true;
 | |
|   else
 | |
|     return false;
 | |
| 
 | |
|   } // end VarSize
 | |
| 
 | |
| /***********************************************************************/
 | |
| /*  ReadColumn: call DOSCOL::ReadColumn after having set the offet     */
 | |
| /*  and length of the field to read as calculated by TDBCSV::ReadDB.   */
 | |
| /***********************************************************************/
 | |
| void CSVCOL::ReadColumn(PGLOBAL g)
 | |
|   {
 | |
|   int     rc;
 | |
|   PTDBCSV tdbp = (PTDBCSV)To_Tdb;
 | |
| 
 | |
|   /*********************************************************************/
 | |
|   /*  If physical reading of the line was deferred, do it now.         */
 | |
|   /*********************************************************************/
 | |
|   if (!tdbp->IsRead())
 | |
|     if ((rc = tdbp->ReadBuffer(g)) != RC_OK) {
 | |
|       if (rc == RC_EF)
 | |
|         snprintf(g->Message, sizeof(g->Message), MSG(INV_DEF_READ), rc);
 | |
| 
 | |
| 			throw 34;
 | |
| 		} // endif
 | |
| 
 | |
|   if (tdbp->Mode != MODE_UPDATE) {
 | |
|     int colen = Long;                    // Column length
 | |
| 
 | |
|     // Set the field offset and length for this row
 | |
|     Deplac = tdbp->Offset[Fldnum];       // Field offset
 | |
|     Long   = tdbp->Fldlen[Fldnum];       // Field length
 | |
| 
 | |
|     if (trace(2))
 | |
|       htrc("CSV ReadColumn %s Fldnum=%d offset=%d fldlen=%d\n",
 | |
|             Name, Fldnum, Deplac, Long);
 | |
| 
 | |
|     if (Long > colen && tdbp->CheckErr()) {
 | |
|       Long = colen;                       // Restore column length
 | |
|       snprintf(g->Message, sizeof(g->Message), MSG(FLD_TOO_LNG_FOR),
 | |
|               Fldnum + 1, Name, To_Tdb->RowNumber(g), tdbp->GetFile(g));
 | |
| 			throw 34;
 | |
| 		} // endif Long
 | |
| 
 | |
|     // Now do the reading
 | |
|     DOSCOL::ReadColumn(g);
 | |
| 
 | |
|     // Restore column length
 | |
|     Long = colen;
 | |
|   } else {         // Mode Update
 | |
|     // Field have been copied in TDB Field array
 | |
|     PSZ fp = tdbp->Field[Fldnum];
 | |
| 
 | |
|     if (Dsp)
 | |
|       for (int i = 0; fp[i]; i++)
 | |
|         if (fp[i] == Dsp)
 | |
|           fp[i] = '.';
 | |
| 
 | |
|     Value->SetValue_psz(fp);
 | |
| 
 | |
|     // Set null when applicable
 | |
|     if (Nullable)
 | |
|       Value->SetNull(Value->IsZero());
 | |
| 
 | |
|   } // endif Mode
 | |
| 
 | |
|   } // end of ReadColumn
 | |
| 
 | |
| /***********************************************************************/
 | |
| /*  WriteColumn: The column is written in TDBCSV matching Field.       */
 | |
| /***********************************************************************/
 | |
| void CSVCOL::WriteColumn(PGLOBAL g)
 | |
|   {
 | |
|   char   *p;
 | |
|   int     n, flen;
 | |
|   PTDBCSV tdbp = (PTDBCSV)To_Tdb;
 | |
| 
 | |
|   if (trace(2))
 | |
|     htrc("CSV WriteColumn: col %s R%d coluse=%.4X status=%.4X\n",
 | |
|           Name, tdbp->GetTdb_No(), ColUse, Status);
 | |
| 
 | |
|   flen = GetLength();
 | |
| 
 | |
|   if (trace(2))
 | |
|     htrc("Lrecl=%d Long=%d field=%d coltype=%d colval=%p\n",
 | |
|           tdbp->Lrecl, Long, flen, Buf_Type, Value);
 | |
| 
 | |
|   /*********************************************************************/
 | |
|   /*  Check whether the new value has to be converted to Buf_Type.     */
 | |
|   /*********************************************************************/
 | |
|   if (Value != To_Val)
 | |
|     Value->SetValue_pval(To_Val, false);    // Convert the updated value
 | |
| 
 | |
|   /*********************************************************************/
 | |
|   /*  Get the string representation of the column value.               */
 | |
|   /*********************************************************************/
 | |
|   p = Value->GetCharString(Buf);
 | |
| 	n = strlen(p);
 | |
| 
 | |
|   if (trace(2))
 | |
|     htrc("new length(%p)=%d\n", p, n);
 | |
| 
 | |
|   if (n > flen) {
 | |
|     snprintf(g->Message, sizeof(g->Message), MSG(BAD_FLD_LENGTH), Name, p, n,
 | |
|                         tdbp->RowNumber(g), tdbp->GetFile(g));
 | |
| 		throw 34;
 | |
| 	} else if (Dsp)
 | |
|     for (int i = 0; p[i]; i++)
 | |
|       if (p[i] == '.')
 | |
|         p[i] = Dsp; 
 | |
| 
 | |
|   if (trace(2))
 | |
|     htrc("buffer=%s\n", p);
 | |
| 
 | |
|   /*********************************************************************/
 | |
|   /*  Updating must be done also during the first pass so writing the  */
 | |
|   /*  updated record can be checked for acceptable record length.      */
 | |
|   /*********************************************************************/
 | |
|   if (Fldnum < 0) {
 | |
|     // This can happen for wrong offset value in XDB files
 | |
|     snprintf(g->Message, sizeof(g->Message), MSG(BAD_FIELD_RANK), Fldnum + 1, Name);
 | |
| 		throw 34;
 | |
| 	} else
 | |
|     strncpy(tdbp->Field[Fldnum], p, flen);
 | |
| 
 | |
|   if (trace(2))
 | |
|     htrc(" col written: '%s'\n", p);
 | |
| 
 | |
|   } // end of WriteColumn
 | |
| 
 | |
| /* ---------------------------TDBCCL class --------------------------- */
 | |
| 
 | |
| /***********************************************************************/
 | |
| /*  TDBCCL class constructor.                                          */
 | |
| /***********************************************************************/
 | |
| TDBCCL::TDBCCL(PCSVDEF tdp) : TDBCAT(tdp)
 | |
| {
 | |
| 	Topt = tdp->GetTopt();
 | |
| } // end of TDBCCL constructor
 | |
| 
 | |
| /***********************************************************************/
 | |
| /*  GetResult: Get the list the CSV file columns.                      */
 | |
| /***********************************************************************/
 | |
| PQRYRES TDBCCL::GetResult(PGLOBAL g)
 | |
|   {
 | |
|   return CSVColumns(g, ((PTABDEF)To_Def)->GetPath(), Topt, false);
 | |
|   } // end of GetResult
 | |
| 
 | |
| /* ------------------------ End of TabFmt ---------------------------- */
 | 
