mariadb/storage/connect/tabfmt.cpp
2015-06-02 10:34:51 +02:00

1484 lines
46 KiB
C++

/************* TabFmt C++ Program Source Code File (.CPP) **************/
/* PROGRAM NAME: TABFMT */
/* ------------- */
/* Version 3.9 */
/* */
/* COPYRIGHT: */
/* ---------- */
/* (C) Copyright to the author Olivier BERTRAND 2001 - 2015 */
/* */
/* 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(__WIN__)
#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(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"
/***********************************************************************/
/* This should be an option. */
/***********************************************************************/
#define MAXCOL 200 /* Default max column nb in result */
#define TYPE_UNKNOWN 10 /* 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, char *dp, const char *fn, char sep,
char q, int hdr, int mxr, 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};
char *p, *colname[MAXCOL], dechar, filename[_MAX_PATH], buf[4096];
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];
FILE *infile;
PQRYRES qrp;
PCOLRES crp;
if (info) {
imax = hmax = 0;
length[0] = 128;
goto skipit;
} // endif info
// num_max = atoi(p+1); // Max num of record to test
#if defined(__WIN__)
if (sep == ',' || strnicmp(setlocale(LC_NUMERIC, NULL), "French", 6))
dechar = '.';
else
dechar = ',';
#else // !__WIN__
dechar = '.';
#endif // !__WIN__
if (trace)
htrc("File %s sep=%c q=%c hdr=%d mxr=%d\n",
SVP(fn), sep, q, hdr, mxr);
if (!fn) {
strcpy(g->Message, MSG(MISSING_FNAME));
return NULL;
} // endif fn
imax = hmax = nerr = 0;
mxr = MY_MAX(0, mxr);
for (i = 0; i < MAXCOL; i++) {
colname[i] = NULL;
len[i] = 0;
typ[i] = TYPE_UNKNOWN;
prc[i] = 0;
} // endfor i
/*********************************************************************/
/* Open the input file. */
/*********************************************************************/
PlugSetPath(filename, fn, dp);
if (!(infile= global_fopen(g, MSGID_CANNOT_OPEN, filename, "r")))
return NULL;
if (hdr) {
/*******************************************************************/
/* Make the column names from the first line. */
/*******************************************************************/
phase = 0;
if (fgets(buf, sizeof(buf), infile)) {
n = strlen(buf) + 1;
buf[n - 2] = '\0';
#if !defined(__WIN__)
// The file can be imported from Windows
if (buf[n - 3] == '\r')
buf[n - 3] = 0;
#endif // UNIX
p = (char*)PlugSubAlloc(g, NULL, n);
memcpy(p, buf, n);
//skip leading blanks
for (; *p == ' '; p++) ;
if (q && *p == q) {
// Header is quoted
p++;
phase = 1;
} // endif q
colname[0] = p;
} else {
sprintf(g->Message, MSG(FILE_IS_EMPTY), fn);
goto err;
} // endif's
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]));
} // endif hdr
for (num_read++; num_read <= num_max; num_read++) {
/*******************************************************************/
/* Now start the reading process. Read one line. */
/*******************************************************************/
if (fgets(buf, sizeof(buf), infile)) {
n = strlen(buf);
buf[n - 1] = '\0';
#if !defined(__WIN__)
// The file can be imported from Windows
if (buf[n - 2] == '\r')
buf[n - 2] = 0;
#endif // UNIX
} else if (feof(infile)) {
sprintf(g->Message, MSG(EOF_AFTER_LINE), num_read -1);
break;
} else {
sprintf(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 = buf; *p; p++)
if (*p == sep) {
if (phase != 1) {
if (i == MAXCOL - 1) {
sprintf(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) {
sprintf(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) {
// sprintf(g->Message, MSG(QUOTE_IN_QUOTE), num_read);
// goto err;
// } else
// goto skip;
p++;
n++;
} else
phase = 2;
} else if (++nerr > mxr) { // phase == 2
sprintf(g->Message, MSG(MISPLACED_QUOTE), num_read);
goto err;
} else
goto skip;
} else {
if (phase == 2)
if (++nerr > mxr) {
sprintf(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) {
sprintf(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) {
htrc("imax=%d Lengths:", imax);
for (i = 0; i < imax; i++)
htrc(" %d", len[i]);
htrc("\n");
} // endif trace
fclose(infile);
skipit:
if (trace)
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:
fclose(infile);
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()) {
strcpy(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;
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 (map) {
// Should be now compatible with UNIX
txfp = new(g) MAPFAM(this);
} else if (Compressed) {
#if defined(ZIP_SUPPORT)
if (Compressed == 1)
txfp = new(g) ZIPFAM(this);
else
txfp = new(g) ZLBFAM(this);
#else // !ZIP_SUPPORT
strcpy(g->Message, "Compress not supported");
return NULL;
#endif // !ZIP_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(ZIP_SUPPORT)
if (Compressed == 1)
txfp = new(g) ZBKFAM(this);
else {
txfp->SetBlkPos(To_Pos);
((PZLBFAM)txfp)->SetOptimized(To_Pos != NULL);
} // endelse
#else
sprintf(g->Message, MSG(NO_FEAT_SUPPORT), "ZIP");
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::CopyOne(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 CopyOne
/***********************************************************************/
/* 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)
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;
// 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) {
sprintf(g->Message, MSG(LRECL_TOO_SMALL), hlen);
return true;
} // endif hlen
// File is empty, write a header record
memset(To_Line, 0, Lrecl);
// 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)
To_Line[strlen(To_Line)] = Qot;
strcat(To_Line, cdp->GetName());
if (q)
To_Line[strlen(To_Line)] = Qot;
if (i < n)
To_Line[strlen(To_Line)] = Sep;
} // endif Offset
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;
int i, n, len, rc = Txfp->ReadBuffer(g);
bool bad = false;
if (trace > 1)
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
if (p) {
len = p++ - p2;
// if (Sep != ' ')
// for (; *p == ' '; p++) ; // Skip blanks
if (*p != Sep && i != Fields - 1) { // Should be the separator
if (CheckErr()) {
sprintf(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 double of internal quotes
for (j = k = 0; j < len; j++, k++) {
if (p2[j] == Qot)
j++; // skip first one
p2[k] = p2[j];
} // endfor i, j
len -= n;
} // endif n
} else if (CheckErr()) {
sprintf(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 = p - p2;
else if (i == Fields - 1)
len = strlen(p2);
else if (Accept && Maxerr == 0) {
len = strlen(p2);
bad = true;
} else if (CheckErr()) {
sprintf(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] = p2 - To_Line;
if (Mode != MODE_UPDATE)
Fldlen[i] = len;
else if (len > Fldlen[i]) {
sprintf(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 > 1)
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)
strcat(To_Line, sep);
if (Field[i])
if (!strlen(Field[i])) {
// Generally null fields are not quoted
if (Quoted > 2)
// Except if explicitely required
strcat(strcat(To_Line, qot), 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
strcat(strcat(strcat(To_Line, qot), Field[i]), qot);
else
strcat(To_Line, 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 > 1)
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 > 1)
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) {
sprintf(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) {
strcpy(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::CopyOne(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 CopyOne
/***********************************************************************/
/* 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) {
sprintf(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())) {
sprintf(g->Message, MSG(NO_FLD_FORMAT), i + 1, Name);
return true;
} // endif pfm
// Roughly check the Fmt format
if ((n = strlen(pfm) - 2) < 4) {
sprintf(g->Message, MSG(BAD_FLD_FORMAT), i + 1, Name);
return true;
} // endif n
FldFormat[i] = (PSZ)PlugSubAlloc(g, NULL, n + 5);
strcpy(FldFormat[i], 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.
strcat(FldFormat[i], "%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 > 1)
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()) {
sprintf(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]) {
// sprintf(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)
{
sprintf(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)
sprintf(g->Message, MSG(INV_DEF_READ), rc);
longjmp(g->jumper[g->jump_level], 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 > 1)
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
sprintf(g->Message, MSG(FLD_TOO_LNG_FOR),
Fldnum + 1, Name, To_Tdb->RowNumber(g), tdbp->GetFile(g));
longjmp(g->jumper[g->jump_level], 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, buf[64];
int flen;
PTDBCSV tdbp = (PTDBCSV)To_Tdb;
if (trace > 1)
htrc("CSV WriteColumn: col %s R%d coluse=%.4X status=%.4X\n",
Name, tdbp->GetTdb_No(), ColUse, Status);
flen = GetLength();
if (trace > 1)
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->ShowValue(buf);
if (trace > 1)
htrc("new length(%p)=%d\n", p, strlen(p));
if ((signed)strlen(p) > flen) {
sprintf(g->Message, MSG(BAD_FLD_LENGTH), Name, p, flen,
tdbp->RowNumber(g), tdbp->GetFile(g));
longjmp(g->jumper[g->jump_level], 34);
} else if (Dsp)
for (int i = 0; p[i]; i++)
if (p[i] == '.')
p[i] = Dsp;
if (trace > 1)
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
sprintf(g->Message, MSG(BAD_FIELD_RANK), Fldnum + 1, Name);
longjmp(g->jumper[g->jump_level], 34);
} else
strncpy(tdbp->Field[Fldnum], p, flen);
if (trace > 1)
htrc(" col written: '%s'\n", p);
} // end of WriteColumn
/* ---------------------------TDBCCL class --------------------------- */
/***********************************************************************/
/* TDBCCL class constructor. */
/***********************************************************************/
TDBCCL::TDBCCL(PCSVDEF tdp) : TDBCAT(tdp)
{
Fn = tdp->GetFn();
Hdr = tdp->Header;
Mxr = tdp->Maxerr;
Qtd = tdp->Quoted;
Sep = tdp->Sep;
} // end of TDBCCL constructor
/***********************************************************************/
/* GetResult: Get the list the CSV file columns. */
/***********************************************************************/
PQRYRES TDBCCL::GetResult(PGLOBAL g)
{
return CSVColumns(g, ((PTABDEF)To_Def)->GetPath(),
Fn, Sep, Qtd, Hdr, Mxr, false);
} // end of GetResult
/* ------------------------ End of TabFmt ---------------------------- */