mirror of
https://github.com/MariaDB/server.git
synced 2025-01-16 20:12:31 +01:00
7d596c9ff5
modified: storage/connect/filamdbf.cpp modified: storage/connect/filamdbf.h modified: storage/connect/reldef.cpp
1089 lines
38 KiB
C++
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 ---------------------------------- */
|