mariadb/storage/connect/filamdbf.cpp
Olivier Bertrand 7d596c9ff5 - Working on MDEV-10525. Lrecl mismatch on DBF files
modified:   storage/connect/filamdbf.cpp
  modified:   storage/connect/filamdbf.h
  modified:   storage/connect/reldef.cpp
2016-09-16 22:14:14 +02:00

1089 lines
38 KiB
C++

/*********** File AM Dbf C++ Program Source Code File (.CPP) ****************/
/* PROGRAM NAME: FILAMDBF */
/* ------------- */
/* Version 1.8 */
/* */
/* COPYRIGHT: */
/* ---------- */
/* (C) Copyright to the author Olivier BERTRAND 2005-2015 */
/* */
/* WHAT THIS PROGRAM DOES: */
/* ----------------------- */
/* This program are the DBF file access method classes. */
/* */
/* ACKNOWLEDGEMENT: */
/* ---------------- */
/* Somerset Data Systems, Inc. (908) 766-5845 */
/* Version 1.2 April 6, 1991 */
/* Programmer: Jay Parsons */
/****************************************************************************/
/***********************************************************************/
/* Include relevant sections of the System header files. */
/***********************************************************************/
#include "my_global.h"
#if defined(__WIN__)
#include <io.h>
#include <fcntl.h>
//#include <errno.h>
//#include <windows.h>
#else // !__WIN__
#if defined(UNIX)
#include <errno.h>
#include <unistd.h>
#else // !UNIX
//#include <io.h>
#endif // !UNIX
//#include <fcntl.h>
#endif // !__WIN__
#include <ctype.h>
#include <stdio.h>
#include <string.h>
/***********************************************************************/
/* 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 "filamdbf.h"
#include "tabdos.h"
#include "valblk.h"
#define NO_FUNC
#include "plgcnx.h" // For DB types
#include "resource.h"
/****************************************************************************/
/* Definitions. */
/****************************************************************************/
#define HEADLEN 32 /* sizeof ( mainhead or thisfield ) */
//efine MEMOLEN 10 /* length of memo field in .dbf */
#define DBFTYPE 3 /* value of bits 0 and 1 if .dbf */
#define EOH 0x0D /* end-of-header marker in .dbf file */
/****************************************************************************/
/* First 32 bytes of a .dbf file. */
/* Note: some reserved fields are used here to store info (Fields) */
/****************************************************************************/
typedef struct _dbfheader {
//uchar Dbf :2; /* both 1 for dBASE III or IV .dbf */
//uchar :1;
//uchar Db4dbt:1; /* 1 if a dBASE IV-type .dbt exists */
//uchar Dbfox :4; /* FoxPro if equal to 3 */
uchar Version; /* Version information flags */
char Filedate[3]; /* date, YYMMDD, binary. YY=year-1900 */
private:
/* The following four members are stored in little-endian format on disk */
char m_RecordsBuf[4]; /* records in the file */
char m_HeadlenBuf[2]; /* bytes in the header */
char m_ReclenBuf[2]; /* bytes in a record */
char m_FieldsBuf[2]; /* Reserved but used to store fields */
public:
char Incompleteflag; /* 01 if incomplete, else 00 */
char Encryptflag; /* 01 if encrypted, else 00 */
char Reserved2[12]; /* for LAN use */
char Mdxflag; /* 01 if production .mdx, else 00 */
char Language; /* Codepage */
char Reserved3[2];
uint Records(void) const {return uint4korr(m_RecordsBuf);}
ushort Headlen(void) const {return uint2korr(m_HeadlenBuf);}
ushort Reclen(void) const {return uint2korr(m_ReclenBuf);}
ushort Fields(void) const {return uint2korr(m_FieldsBuf);}
void SetHeadlen(ushort num) {int2store(m_HeadlenBuf, num);}
void SetReclen(ushort num) {int2store(m_ReclenBuf, num);}
void SetFields(ushort num) {int2store(m_FieldsBuf, num);}
} DBFHEADER;
/****************************************************************************/
/* Column field descriptor of a .dbf file. */
/****************************************************************************/
typedef struct _descriptor {
char Name[11]; /* field name, in capitals, null filled*/
char Type; /* field type, C, D, F, L, M or N */
uint Offset; /* used in memvars, not in files. */
uchar Length; /* field length */
uchar Decimals; /* number of decimal places */
short Reserved4;
char Workarea; /* ??? */
char Reserved5[2];
char Setfield; /* ??? */
char Reserved6[7];
char Mdxfield; /* 01 if tag field in production .mdx */
} DESCRIPTOR;
/****************************************************************************/
/* dbfhead: Routine to analyze a .dbf header. */
/* Parameters: */
/* PGLOBAL g -- pointer to the Plug Global structure */
/* FILE *file -- pointer to file to analyze */
/* PSZ fn -- pathname of the file to analyze */
/* DBFHEADER *buf -- pointer to _dbfheader structure */
/* Returns: */
/* RC_OK, RC_NF, RC_INFO, or RC_FX if error. */
/* Side effects: */
/* Moves file pointer to byte 32; fills buffer at buf with */
/* first 32 bytes of file. */
/****************************************************************************/
static int dbfhead(PGLOBAL g, FILE *file, PSZ fn, DBFHEADER *buf)
{
char endmark[2];
int dbc = 2, rc = RC_OK;
*g->Message = '\0';
// Read the first 32 bytes into buffer
if (fread(buf, HEADLEN, 1, file) != 1) {
strcpy(g->Message, MSG(NO_READ_32));
return RC_NF;
} // endif fread
// Check first byte to be sure of .dbf type
if ((buf->Version & 0x03) != DBFTYPE) {
strcpy(g->Message, MSG(NOT_A_DBF_FILE));
rc = RC_INFO;
if ((buf->Version & 0x30) == 0x30) {
strcpy(g->Message, MSG(FOXPRO_FILE));
dbc = 264; // FoxPro database container
} // endif Version
} else
strcpy(g->Message, MSG(DBASE_FILE));
// Check last byte(s) of header
if (fseek(file, buf->Headlen() - dbc, SEEK_SET) != 0) {
sprintf(g->Message, MSG(BAD_HEADER), fn);
return RC_FX;
} // endif fseek
if (fread(&endmark, 2, 1, file) != 1) {
strcpy(g->Message, MSG(BAD_HEAD_END));
return RC_FX;
} // endif fread
// Some files have just 1D others have 1D00 following fields
if (endmark[0] != EOH && endmark[1] != EOH) {
sprintf(g->Message, MSG(NO_0DH_HEAD), dbc);
if (rc == RC_OK)
return RC_FX;
} // endif endmark
// Calculate here the number of fields while we have the dbc info
buf->SetFields((buf->Headlen() - dbc - 1) / 32);
fseek(file, HEADLEN, SEEK_SET);
return rc;
} // end of dbfhead
/* -------------------------- Function DBFColumns ------------------------- */
/****************************************************************************/
/* DBFColumns: constructs the result blocks containing the description */
/* of all the columns of a DBF file that will be retrieved by #GetData. */
/****************************************************************************/
PQRYRES DBFColumns(PGLOBAL g, char *dp, const char *fn, bool info)
{
int buftyp[] = {TYPE_STRING, TYPE_SHORT, TYPE_STRING,
TYPE_INT, TYPE_INT, TYPE_SHORT};
XFLD fldtyp[] = {FLD_NAME, FLD_TYPE, FLD_TYPENAME,
FLD_PREC, FLD_LENGTH, FLD_SCALE};
unsigned int length[] = {11, 6, 8, 10, 10, 6};
char buf[2], filename[_MAX_PATH];
int ncol = sizeof(buftyp) / sizeof(int);
int rc, type, len, field, fields;
bool bad;
DBFHEADER mainhead;
DESCRIPTOR thisfield;
FILE *infile = NULL;
PQRYRES qrp;
PCOLRES crp;
if (trace)
htrc("DBFColumns: File %s\n", SVP(fn));
if (!info) {
if (!fn) {
strcpy(g->Message, MSG(MISSING_FNAME));
return NULL;
} // endif fn
/************************************************************************/
/* Open the input file. */
/************************************************************************/
PlugSetPath(filename, fn, dp);
if (!(infile= global_fopen(g, MSGID_CANNOT_OPEN, filename, "rb")))
return NULL;
/************************************************************************/
/* Get the first 32 bytes of the header. */
/************************************************************************/
if ((rc = dbfhead(g, infile, filename, &mainhead)) == RC_FX) {
fclose(infile);
return NULL;
} // endif dbfhead
/************************************************************************/
/* Allocate the structures used to refer to the result set. */
/************************************************************************/
fields = mainhead.Fields();
} else
fields = 0;
qrp = PlgAllocResult(g, ncol, fields, IDS_COLUMNS + 3,
buftyp, fldtyp, length, true, false);
if (info || !qrp) {
if (infile)
fclose(infile);
return qrp;
} // endif info
if (trace) {
htrc("Structure of %s\n", filename);
htrc("headlen=%hd reclen=%hd degree=%d\n",
mainhead.Headlen(), mainhead.Reclen(), fields);
htrc("flags(iem)=%d,%d,%d cp=%d\n", mainhead.Incompleteflag,
mainhead.Encryptflag, mainhead.Mdxflag, mainhead.Language);
htrc("%hd records, last changed %02d/%02d/%d\n",
mainhead.Records(), mainhead.Filedate[1], mainhead.Filedate[2],
mainhead.Filedate[0] + (mainhead.Filedate[0] <= 30) ? 2000 : 1900);
htrc("Field Type Offset Len Dec Set Mdx\n");
} // endif trace
buf[1] = '\0';
/**************************************************************************/
/* Do it field by field. We are at byte 32 of file. */
/**************************************************************************/
for (field = 0; field < fields; field++) {
bad = FALSE;
if (fread(&thisfield, HEADLEN, 1, infile) != 1) {
sprintf(g->Message, MSG(ERR_READING_REC), field+1, fn);
goto err;
} else
len = thisfield.Length;
if (trace)
htrc("%-11s %c %6ld %3d %2d %3d %3d\n",
thisfield.Name, thisfield.Type, thisfield.Offset, len,
thisfield.Decimals, thisfield.Setfield, thisfield.Mdxfield);
/************************************************************************/
/* Now get the results into blocks. */
/************************************************************************/
switch (thisfield.Type) {
case 'C': // Characters
case 'L': // Logical 'T' or 'F'
type = TYPE_STRING;
break;
case 'N':
type = (thisfield.Decimals) ? TYPE_DOUBLE
: (len > 10) ? TYPE_BIGINT : TYPE_INT;
break;
case 'F':
type = TYPE_DOUBLE;
break;
case 'D':
type = TYPE_DATE; // Is this correct ???
break;
default:
if (!info) {
sprintf(g->Message, MSG(BAD_DBF_TYPE), thisfield.Type
, thisfield.Name);
goto err;
} // endif info
type = TYPE_ERROR;
bad = TRUE;
} // endswitch Type
crp = qrp->Colresp; // Column Name
crp->Kdata->SetValue(thisfield.Name, field);
crp = crp->Next; // Data Type
crp->Kdata->SetValue((int)type, field);
crp = crp->Next; // Type Name
if (bad) {
buf[0] = thisfield.Type;
crp->Kdata->SetValue(buf, field);
} else
crp->Kdata->SetValue(GetTypeName(type), field);
crp = crp->Next; // Precision
crp->Kdata->SetValue((int)thisfield.Length, field);
crp = crp->Next; // Length
crp->Kdata->SetValue((int)thisfield.Length, field);
crp = crp->Next; // Scale (precision)
crp->Kdata->SetValue((int)thisfield.Decimals, field);
} // endfor field
qrp->Nblin = field;
fclose(infile);
#if 0
if (info) {
/************************************************************************/
/* Prepare return message for dbfinfo command. */
/************************************************************************/
char buf[64];
sprintf(buf,
"Ver=%02x ncol=%hu nlin=%u lrecl=%hu headlen=%hu date=%02d/%02d/%02d",
mainhead.Version, fields, mainhead.Records, mainhead.Reclen,
mainhead.Headlen, mainhead.Filedate[0], mainhead.Filedate[1],
mainhead.Filedate[2]);
strcat(g->Message, buf);
} // endif info
#endif // 0
/**************************************************************************/
/* Return the result pointer for use by GetData routines. */
/**************************************************************************/
return qrp;
err:
fclose(infile);
return NULL;
} // end of DBFColumns
/* ---------------------------- Class DBFBASE ----------------------------- */
/****************************************************************************/
/* Constructors. */
/****************************************************************************/
DBFBASE::DBFBASE(PDOSDEF tdp)
{
Records = 0;
Nerr = 0;
Maxerr = tdp->Maxerr;
Accept = tdp->Accept;
ReadMode = tdp->ReadMode;
} // end of DBFBASE standard constructor
DBFBASE::DBFBASE(DBFBASE *txfp)
{
Records = txfp->Records;
Nerr = txfp->Nerr;
Maxerr = txfp->Maxerr;
Accept = txfp->Accept;
ReadMode = txfp->ReadMode;
} // end of DBFBASE copy constructor
/****************************************************************************/
/* ScanHeader: scan the DBF file header for number of records, record size,*/
/* and header length. Set Records, check that Reclen is equal to lrecl and */
/* return the header length or 0 in case of error. */
/****************************************************************************/
int DBFBASE::ScanHeader(PGLOBAL g, PSZ fn, int lrecl, int *rln, char *defpath)
{
int rc;
char filename[_MAX_PATH];
DBFHEADER header;
FILE *infile;
/************************************************************************/
/* Open the input file. */
/************************************************************************/
PlugSetPath(filename, fn, defpath);
if (!(infile= global_fopen(g, MSGID_CANNOT_OPEN, filename, "rb")))
return 0; // Assume file does not exist
/************************************************************************/
/* Get the first 32 bytes of the header. */
/************************************************************************/
rc = dbfhead(g, infile, filename, &header);
fclose(infile);
if (rc == RC_NF) {
Records = 0;
return 0;
} else if (rc == RC_FX)
return -1;
*rln = (int)header.Reclen();
Records = (int)header.Records();
return (int)header.Headlen();
} // end of ScanHeader
/* ---------------------------- Class DBFFAM ------------------------------ */
/****************************************************************************/
/* Cardinality: returns table cardinality in number of rows. */
/* This function can be called with a null argument to test the */
/* availability of Cardinality implementation (1 yes, 0 no). */
/****************************************************************************/
int DBFFAM::Cardinality(PGLOBAL g)
{
if (!g)
return 1;
if (!Headlen) {
int rln = 0; // Record length in the file header
Headlen = ScanHeader(g, To_File, Lrecl, &rln, Tdbp->GetPath());
if (Headlen < 0)
return -1; // Error in ScanHeader
if (rln && Lrecl != rln) {
// This happens always on some Linux platforms
sprintf(g->Message, MSG(BAD_LRECL), Lrecl, rln);
if (Accept) {
Lrecl = rln;
PushWarning(g, Tdbp);
} else
return -1;
} // endif rln
} // endif Headlen
// Set number of blocks for later use
Block = (Records > 0) ? (Records + Nrec - 1) / Nrec : 0;
return Records;
} // end of Cardinality
#if 0 // Not compatible with ROWID block optimization
/***********************************************************************/
/* GetRowID: return the RowID of last read record. */
/***********************************************************************/
int DBFFAM::GetRowID(void)
{
return Rows;
} // end of GetRowID
#endif
/***********************************************************************/
/* OpenTableFile: Open a DBF table file using C standard I/Os. */
/* Binary mode cannot be used on Insert because of EOF (CTRL+Z) char. */
/***********************************************************************/
bool DBFFAM::OpenTableFile(PGLOBAL g)
{
char opmode[4], filename[_MAX_PATH];
//int ftype = Tdbp->GetFtype();
MODE mode = Tdbp->GetMode();
PDBUSER dbuserp = PlgGetUser(g);
switch (mode) {
case MODE_READ:
strcpy(opmode, "rb");
break;
case MODE_DELETE:
if (!Tdbp->GetNext()) {
// Store the number of deleted lines
DelRows = -1; // Means all lines deleted
// DelRows = Cardinality(g); no good because of soft deleted lines
// This will erase the entire file
strcpy(opmode, "w");
Tdbp->ResetSize();
Records = 0;
break;
} // endif
// Selective delete, pass thru
case MODE_UPDATE:
UseTemp = Tdbp->IsUsingTemp(g);
strcpy(opmode, (UseTemp) ? "rb" : "r+b");
break;
case MODE_INSERT:
// Must be in text mode to remove an eventual EOF character
strcpy(opmode, "a+");
break;
default:
sprintf(g->Message, MSG(BAD_OPEN_MODE), mode);
return true;
} // endswitch Mode
// Now open the file stream
PlugSetPath(filename, To_File, Tdbp->GetPath());
if (!(Stream = PlugOpenFile(g, filename, opmode))) {
if (trace)
htrc("%s\n", g->Message);
return (mode == MODE_READ && errno == ENOENT)
? PushWarning(g, Tdbp) : true;
} // endif Stream
if (trace)
htrc("File %s is open in mode %s\n", filename, opmode);
To_Fb = dbuserp->Openlist; // Keep track of File block
/*********************************************************************/
/* Allocate the line buffer. For mode Delete a bigger buffer has to */
/* be allocated because is it also used to move lines into the file.*/
/*********************************************************************/
return AllocateBuffer(g);
} // end of OpenTableFile
/****************************************************************************/
/* Allocate the block buffer for the table. */
/****************************************************************************/
bool DBFFAM::AllocateBuffer(PGLOBAL g)
{
char c;
int rc;
MODE mode = Tdbp->GetMode();
Buflen = Blksize;
To_Buf = (char*)PlugSubAlloc(g, NULL, Buflen);
if (mode == MODE_INSERT) {
#if defined(__WIN__)
/************************************************************************/
/* Now we can revert to binary mode in particular because the eventual */
/* writing of a new header must be done in binary mode to avoid */
/* translating 0A bytes (LF) into 0D0A (CRLF) by Windows in text mode. */
/************************************************************************/
if (_setmode(_fileno(Stream), _O_BINARY) == -1) {
sprintf(g->Message, MSG(BIN_MODE_FAIL), strerror(errno));
return true;
} // endif setmode
#endif // __WIN__
/************************************************************************/
/* If this is a new file, the header must be generated. */
/************************************************************************/
int len = GetFileLength(g);
if (!len) {
// Make the header for this DBF table file
struct tm *datm;
int hlen, n = 0;
ushort reclen = 1;
time_t t;
DBFHEADER *header;
DESCRIPTOR *descp;
PCOLDEF cdp;
PDOSDEF tdp = (PDOSDEF)Tdbp->GetDef();
// Count the number of columns
for (cdp = tdp->GetCols(); cdp; cdp = cdp->GetNext())
if (!(cdp->Flags & U_SPECIAL)) {
reclen += cdp->GetLong();
n++;
} // endif Flags
if (Lrecl != reclen) {
sprintf(g->Message, MSG(BAD_LRECL), Lrecl, reclen);
if (Accept) {
Lrecl = reclen;
PushWarning(g, Tdbp);
} else
return true;
} // endif Lrecl
hlen = HEADLEN * (n + 1) + 2;
header = (DBFHEADER*)PlugSubAlloc(g, NULL, hlen);
memset(header, 0, hlen);
header->Version = DBFTYPE;
t = time(NULL) - (time_t)DTVAL::GetShift();
datm = gmtime(&t);
header->Filedate[0] = datm->tm_year - 100;
header->Filedate[1] = datm->tm_mon + 1;
header->Filedate[2] = datm->tm_mday;
header->SetHeadlen((ushort)hlen);
header->SetReclen((ushort)reclen);
descp = (DESCRIPTOR*)header;
// Currently only standard Xbase types are supported
for (cdp = tdp->GetCols(); cdp; cdp = cdp->GetNext())
if (!(cdp->Flags & U_SPECIAL)) {
descp++;
switch ((c = *GetFormatType(cdp->GetType()))) {
case 'S': // Short integer
case 'L': // Large (big) integer
case 'T': // Tiny integer
c = 'N'; // Numeric
case 'N': // Numeric (integer)
case 'F': // Float (double)
descp->Decimals = (uchar)cdp->F.Prec;
case 'C': // Char
case 'D': // Date
break;
default: // Should never happen
sprintf(g->Message, MSG(BAD_DBF_TYPE),
c, cdp->GetName());
return true;
} // endswitch c
strncpy(descp->Name, cdp->GetName(), 11);
descp->Type = c;
descp->Length = (uchar)cdp->GetLong();
} // endif Flags
*(char*)(++descp) = EOH;
// Now write the header
if (fwrite(header, 1, hlen, Stream) != (unsigned)hlen) {
sprintf(g->Message, MSG(FWRITE_ERROR), strerror(errno));
return true;
} // endif fwrite
Records = 0;
Headlen = hlen;
} else if (len < 0)
return true; // Error in GetFileLength
/************************************************************************/
/* For Insert the buffer must be prepared. */
/************************************************************************/
memset(To_Buf, ' ', Buflen);
Rbuf = Nrec; // To be used by WriteDB
} else if (UseTemp) {
// Allocate a separate buffer so block reading can be kept
Dbflen = Nrec;
DelBuf = PlugSubAlloc(g, NULL, Blksize);
} // endif's
if (!Headlen) {
/************************************************************************/
/* Here is a good place to process the DBF file header */
/************************************************************************/
DBFHEADER header;
if ((rc = dbfhead(g, Stream, Tdbp->GetFile(g), &header)) == RC_OK) {
if (Lrecl != (int)header.Reclen()) {
sprintf(g->Message, MSG(BAD_LRECL), Lrecl, header.Reclen());
if (Accept) {
Lrecl = header.Reclen();
PushWarning(g, Tdbp);
} else
return true;
} // endif Lrecl
Records = (int)header.Records();
Headlen = (int)header.Headlen();
} else if (rc == RC_NF) {
Records = 0;
Headlen = 0;
} else // RC_FX
return true; // Error in dbfhead
} // endif Headlen
/**************************************************************************/
/* Position the file at the begining of the data. */
/**************************************************************************/
if (Tdbp->GetMode() == MODE_INSERT)
rc = fseek(Stream, 0, SEEK_END);
else
rc = fseek(Stream, Headlen, SEEK_SET);
if (rc) {
sprintf(g->Message, MSG(BAD_DBF_FILE), Tdbp->GetFile(g));
return true;
} // endif fseek
return false;
} // end of AllocateBuffer
/***********************************************************************/
/* Reset buffer access according to indexing and to mode. */
/* >>>>>>>>>>>>>> TO BE RE-VISITED AND CHECKED <<<<<<<<<<<<<<<<<<<<<< */
/***********************************************************************/
void DBFFAM::ResetBuffer(PGLOBAL g)
{
/*********************************************************************/
/* If access is random, performances can be much better when the */
/* reads are done on only one row, except for small tables that can */
/* be entirely read in one block. */
/*********************************************************************/
if (Tdbp->GetKindex() && ReadBlks != 1) {
Nrec = 1; // Better for random access
Rbuf = 0;
Blksize = Lrecl;
OldBlk = -2; // Has no meaning anymore
Block = Tdbp->Cardinality(g); // Blocks are one line now
} // endif Mode
} // end of ResetBuffer
/***********************************************************************/
/* ReadBuffer: Read one line for a DBF file. */
/***********************************************************************/
int DBFFAM::ReadBuffer(PGLOBAL g)
{
if (!Placed && !Closing && GetRowID() == Records)
return RC_EF;
int rc = FIXFAM::ReadBuffer(g);
if (rc != RC_OK || Closing)
return rc;
switch (*Tdbp->GetLine()) {
case '*':
if (!ReadMode)
rc = RC_NF; // Deleted line
else
Rows++;
break;
case ' ':
if (ReadMode < 2)
Rows++; // Non deleted line
else
rc = RC_NF;
break;
default:
if (++Nerr >= Maxerr && !Accept) {
sprintf(g->Message, MSG(BAD_DBF_REC), Tdbp->GetFile(g), GetRowID());
rc = RC_FX;
} else
rc = (Accept) ? RC_OK : RC_NF;
} // endswitch To_Buf
return rc;
} // end of ReadBuffer
/***********************************************************************/
/* Copy the header into the temporary file. */
/***********************************************************************/
bool DBFFAM::CopyHeader(PGLOBAL g)
{
bool rc = true;
if (Headlen) {
void *hdr = PlugSubAlloc(g, NULL, Headlen);
size_t n, hlen = (size_t)Headlen;
int pos = ftell(Stream);
if (fseek(Stream, 0, SEEK_SET))
strcpy(g->Message, "Seek error in CopyHeader");
else if ((n = fread(hdr, 1, hlen, Stream)) != hlen)
sprintf(g->Message, MSG(BAD_READ_NUMBER), (int) n, To_File);
else if ((n = fwrite(hdr, 1, hlen, T_Stream)) != hlen)
sprintf(g->Message, MSG(WRITE_STRERROR), To_Fbt->Fname
, strerror(errno));
else if (fseek(Stream, pos, SEEK_SET))
strcpy(g->Message, "Seek error in CopyHeader");
else
rc = false;
} else
rc = false;
return rc;
} // end of CopyHeader
#if 0 // Not useful when UseTemp is false.
/***********************************************************************/
/* Mark the line to delete with '*' (soft delete). */
/* NOTE: this is not ready for UseTemp. */
/***********************************************************************/
int DBFFAM::InitDelete(PGLOBAL g, int fpos, int spos)
{
int rc = RC_FX;
size_t lrecl = (size_t)Lrecl;
if (Nrec != 1)
strcpy(g->Message, "Cannot delete in block mode");
else if (fseek(Stream, Headlen + fpos * Lrecl, SEEK_SET))
sprintf(g->Message, MSG(FSETPOS_ERROR), 0);
else if (fread(To_Buf, 1, lrecl, Stream) != lrecl)
sprintf(g->Message, MSG(READ_ERROR), To_File, strerror(errno));
else
*To_Buf = '*';
if (fseek(Stream, Headlen + fpos * Lrecl, SEEK_SET))
sprintf(g->Message, MSG(FSETPOS_ERROR), 0);
else if (fwrite(To_Buf, 1, lrecl, Stream) != lrecl)
sprintf(g->Message, MSG(FWRITE_ERROR), strerror(errno));
else
rc = RC_NF; // Ok, Nothing else to do
return rc;
} // end of InitDelete
#endif // 0
/***********************************************************************/
/* Data Base delete line routine for DBF access methods. */
/* Deleted lines are just flagged in the first buffer character. */
/***********************************************************************/
int DBFFAM::DeleteRecords(PGLOBAL g, int irc)
{
if (irc == RC_OK) {
// T_Stream is the temporary stream or the table file stream itself
if (!T_Stream)
if (UseTemp) {
if (OpenTempFile(g))
return RC_FX;
if (CopyHeader(g)) // For DBF tables
return RC_FX;
} else
T_Stream = Stream;
*Tdbp->GetLine() = '*';
Modif++; // Modified line in Delete mode
} // endif irc
return RC_OK;
} // end of DeleteRecords
/***********************************************************************/
/* Rewind routine for DBF access method. */
/***********************************************************************/
void DBFFAM::Rewind(void)
{
BLKFAM::Rewind();
Nerr = 0;
} // end of Rewind
/***********************************************************************/
/* Table file close routine for DBF access method. */
/***********************************************************************/
void DBFFAM::CloseTableFile(PGLOBAL g, bool abort)
{
int rc = RC_OK, wrc = RC_OK;
MODE mode = Tdbp->GetMode();
Abort = abort;
// Closing is True if last Write was in error
if (mode == MODE_INSERT && CurNum && !Closing) {
// Some more inserted lines remain to be written
Rbuf = CurNum--;
// Closing = true;
wrc = WriteBuffer(g);
} else if (mode == MODE_UPDATE || mode == MODE_DELETE) {
if (Modif && !Closing) {
// Last updated block remains to be written
Closing = true;
wrc = WriteModifiedBlock(g);
} // endif Modif
if (UseTemp && T_Stream && wrc == RC_OK) {
if (!Abort) {
// Copy any remaining lines
bool b;
Fpos = Tdbp->Cardinality(g);
Abort = MoveIntermediateLines(g, &b) != RC_OK;
} // endif Abort
// Delete the old file and rename the new temp file.
RenameTempFile(g);
goto fin;
} // endif UseTemp
} // endif's mode
if (Tdbp->GetMode() == MODE_INSERT) {
int n = ftell(Stream) - Headlen;
rc = PlugCloseFile(g, To_Fb);
if (n >= 0 && !(n % Lrecl)) {
n /= Lrecl; // New number of lines
if (n > Records) {
// Update the number of rows in the file header
char filename[_MAX_PATH];
PlugSetPath(filename, To_File, Tdbp->GetPath());
if ((Stream= global_fopen(g, MSGID_OPEN_MODE_STRERROR, filename, "r+b")))
{
char nRecords[4];
int4store(nRecords, n);
fseek(Stream, 4, SEEK_SET); // Get header.Records position
fwrite(nRecords, sizeof(nRecords), 1, Stream);
fclose(Stream);
Stream= NULL;
Records= n; // Update Records value
}
} // endif n
} // endif n
} else // Finally close the file
rc = PlugCloseFile(g, To_Fb);
fin:
if (trace)
htrc("DBF CloseTableFile: closing %s mode=%d wrc=%d rc=%d\n",
To_File, mode, wrc, rc);
Stream = NULL; // So we can know whether table is open
} // end of CloseTableFile
/* ---------------------------- Class DBMFAM ------------------------------ */
/****************************************************************************/
/* Cardinality: returns table cardinality in number of rows. */
/* This function can be called with a null argument to test the */
/* availability of Cardinality implementation (1 yes, 0 no). */
/****************************************************************************/
int DBMFAM::Cardinality(PGLOBAL g)
{
if (!g)
return 1;
if (!Headlen) {
int rln = 0; // Record length in the file header
Headlen = ScanHeader(g, To_File, Lrecl, &rln, Tdbp->GetPath());
if (Headlen < 0)
return -1; // Error in ScanHeader
if (rln && Lrecl != rln) {
// This happens always on some Linux platforms
sprintf(g->Message, MSG(BAD_LRECL), Lrecl, rln);
if (Accept) {
Lrecl = rln;
PushWarning(g, Tdbp);
} else
return -1;
} // endif rln
} // endif Headlen
// Set number of blocks for later use
Block = (Records > 0) ? (Records + Nrec - 1) / Nrec : 0;
return Records;
} // end of Cardinality
#if 0 // Not compatible with ROWID block optimization
/***********************************************************************/
/* GetRowID: return the RowID of last read record. */
/***********************************************************************/
int DBMFAM::GetRowID(void)
{
return Rows;
} // end of GetRowID
#endif
/***********************************************************************/
/* Just check that on all deletion the unknown deleted line number is */
/* sent back because Cardinality doesn't count soft deleted lines. */
/***********************************************************************/
int DBMFAM::GetDelRows(void)
{
if (Tdbp->GetMode() == MODE_DELETE && !Tdbp->GetNext())
return -1; // Means all lines deleted
else
return DelRows;
} // end of GetDelRows
/****************************************************************************/
/* Allocate the block buffer for the table. */
/****************************************************************************/
bool DBMFAM::AllocateBuffer(PGLOBAL g)
{
if (!Headlen) {
/************************************************************************/
/* Here is a good place to process the DBF file header */
/************************************************************************/
DBFHEADER *hp = (DBFHEADER*)Memory;
if (Lrecl != (int)hp->Reclen()) {
sprintf(g->Message, MSG(BAD_LRECL), Lrecl, hp->Reclen());
if (Accept) {
Lrecl = hp->Reclen();
PushWarning(g, Tdbp);
} else
return true;
} // endif Lrecl
Records = (int)hp->Records();
Headlen = (int)hp->Headlen();
} // endif Headlen
/**************************************************************************/
/* Position the file at the begining of the data. */
/**************************************************************************/
Fpos = Mempos = Memory + Headlen;
Top--; // Because of EOF marker
return false;
} // end of AllocateBuffer
/****************************************************************************/
/* ReadBuffer: Read one line for a FIX file. */
/****************************************************************************/
int DBMFAM::ReadBuffer(PGLOBAL g)
{
// if (!Placed && GetRowID() == Records)
// return RC_EF;
int rc = MPXFAM::ReadBuffer(g);
if (rc != RC_OK)
return rc;
switch (*Fpos) {
case '*':
if (!ReadMode)
rc = RC_NF; // Deleted line
else
Rows++;
break;
case ' ':
if (ReadMode < 2)
Rows++; // Non deleted line
else
rc = RC_NF;
break;
default:
if (++Nerr >= Maxerr && !Accept) {
sprintf(g->Message, MSG(BAD_DBF_REC), Tdbp->GetFile(g), GetRowID());
rc = RC_FX;
} else
rc = (Accept) ? RC_OK : RC_NF;
} // endswitch To_Buf
return rc;
} // end of ReadBuffer
/****************************************************************************/
/* Data Base delete line routine for DBF access methods. */
/* Deleted lines are just flagged in the first buffer character. */
/****************************************************************************/
int DBMFAM::DeleteRecords(PGLOBAL g, int irc)
{
if (irc == RC_OK)
*Fpos = '*';
return RC_OK;
} // end of DeleteRecords
/***********************************************************************/
/* Rewind routine for DBF access method. */
/***********************************************************************/
void DBMFAM::Rewind(void)
{
MBKFAM::Rewind();
Nerr = 0;
} // end of Rewind
/* --------------------------------- EOF ---------------------------------- */