/*********** File AM Txt C++ Program Source Code File (.CPP) ***********/ /* PROGRAM NAME: FILAMTXT */ /* ------------- */ /* Version 1.8 */ /* */ /* COPYRIGHT: */ /* ---------- */ /* (C) Copyright to the author Olivier BERTRAND 2005-2020 */ /* */ /* WHAT THIS PROGRAM DOES: */ /* ----------------------- */ /* This program are the Text file access method classes. */ /* */ /***********************************************************************/
/***********************************************************************/ /* Include relevant sections of the System header files. */ /***********************************************************************/ #include"my_global.h" #ifdefined(_WIN32) #include <io.h> #include <fcntl.h> #include <errno.h> #ifdefined(__BORLANDC__) #define __MFC_COMPAT__ // To define min/max as macro #endif// __BORLANDC__ //#include <windows.h> #else// !_WIN32 #ifdefined(UNIX) || defined(UNIV_LINUX) #include <errno.h> #include <unistd.h> //#if !defined(sun) // Sun has the ftruncate fnc. //#define USETEMP // Force copy mode for DELETE //#endif // !sun #else// !UNIX #include <io.h> #endif// !UNIX #include <fcntl.h> #endif// !_WIN32
#include <m_string.h>
/***********************************************************************/ /* Include application header files: */ /* global.h is header containing all global declarations. */ /* plgdbsem.h is header containing the DB application declarations. */ /* filamtxt.h is header containing the file AM classes declarations. */ /***********************************************************************/ #include"global.h" #include"plgdbsem.h" #include"filamtxt.h" #include"tabdos.h" #include"tabjson.h"
/***********************************************************************/ /* Reset: reset position values at the beginning of file. */ /***********************************************************************/ void TXTFAM::Reset(void)
{
Rows = 0;
DelRows = 0;
OldBlk = -1;
CurBlk = -1;
ReadBlks = 0;
CurNum = 0;
Rbuf = 0;
Modif = 0;
Placed = false;
} // end of Reset
/***********************************************************************/ /* TXT GetFileLength: returns file size in number of bytes. */ /***********************************************************************/ int TXTFAM::GetFileLength(PGLOBAL g)
{ char filename[_MAX_PATH]; int h; int len;
if (Eof && len)
len--; // Do not count the EOF character
close(h);
} // endif h
return len;
} // end of GetFileLength
/***********************************************************************/ /* 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). */ /* Note: This function is meant only for fixed length files but is */ /* placed here to be available to FIXFAM and MPXFAM classes. */ /***********************************************************************/ int TXTFAM::Cardinality(PGLOBAL g)
{ if (g) { int card = -1; int len = GetFileLength(g);
if (len >= 0) { if (Padded && Blksize) { if (!(len % Blksize))
card = (len / Blksize) * Nrec; else
snprintf(g->Message, sizeof(g->Message), MSG(NOT_FIXED_LEN), To_File, len, Lrecl);
if (trace(1))
htrc(" Computed max_K=%d Filen=%d lrecl=%d\n",
card, len, Lrecl);
} else
card = 0;
// Set number of blocks for later use
Block = (card > 0) ? (card + Nrec - 1) / Nrec : 0; return card;
} else return1;
} // end of Cardinality
/***********************************************************************/ /* Use BlockTest to reduce the table estimated size. */ /* Note: This function is meant only for fixed length files but is */ /* placed here to be available to FIXFAM and MPXFAM classes. */ /***********************************************************************/ int TXTFAM::MaxBlkSize(PGLOBAL g, int s)
{ int rc = RC_OK, savcur = CurBlk, blm1 = Block - 1; int size, last = s - blm1 * Nrec;
// Roughly estimate the table size as the sum of blocks // that can contain good rows for (size = 0, CurBlk = 0; CurBlk < Block; CurBlk++) if ((rc = Tdbp->TestBlock(g)) == RC_OK)
size += (CurBlk == blm1) ? last : Nrec; elseif (rc == RC_EF) break;
CurBlk = savcur; return size;
} // end of MaxBlkSize
/***********************************************************************/ /* AddListValue: Used when doing indexed update or delete. */ /***********************************************************************/ bool TXTFAM::AddListValue(PGLOBAL g, int type, void *val, PPARM *top)
{
PPARM pp = (PPARM)PlugSubAlloc(g, NULL, sizeof(PARM));
switch (type) { // case TYPE_INT: // pp->Value = PlugSubAlloc(g, NULL, sizeof(int)); // *((int*)pp->Value) = *((int*)val); // break; case TYPE_VOID:
pp->Intval = *(int*)val; break; // case TYPE_STRING: // pp->Value = PlugDup(g, (char*)val); // break; case TYPE_PCHAR:
pp->Value = val; break; default: returntrue;
} // endswitch type
pp->Type = type;
pp->Domain = 0;
pp->Next = *top;
*top = pp; returnfalse;
} // end of AddListValue
/***********************************************************************/ /* Store needed values for indexed UPDATE or DELETE. */ /***********************************************************************/ int TXTFAM::StoreValues(PGLOBAL g, bool upd)
{ int pos = GetPos(); bool rc = AddListValue(g, TYPE_VOID, &pos, &To_Pos);
return rc ? RC_FX : RC_OK;
} // end of StoreValues
/***********************************************************************/ /* UpdateSortedRows. When updating using indexing, the issue is that */ /* record are not necessarily updated in sequential order. */ /* Moving intermediate lines cannot be done while making them because */ /* this can cause extra wrong records to be included in the new file. */ /* What we do here is reorder the updated records and do all the */ /* updates ordered by record position. */ /***********************************************************************/ int TXTFAM::UpdateSortedRows(PGLOBAL g)
{ int *ix, i;
/*********************************************************************/ /* Get the stored update values and sort them. */ /*********************************************************************/ if (!(Posar = MakeValueArray(g, To_Pos))) { // strcpy(g->Message, "Position array is null"); // return RC_INFO; return RC_OK; // Nothing to do
} elseif (!(Sosar = MakeValueArray(g, To_Sos))) {
snprintf(g->Message, sizeof(g->Message), "Start position array is null"); goto err;
} elseif (!(Updar = MakeValueArray(g, To_Upd))) {
snprintf(g->Message, sizeof(g->Message), "Updated line array is null"); goto err;
} elseif (!(ix = (int*)Posar->GetSortIndex(g))) {
snprintf(g->Message, sizeof(g->Message), "Error getting array sort index"); goto err;
} // endif's
Rewind();
for (i = 0; i < Posar->GetNval(); i++) {
SetPos(g, Sosar->GetIntValue(ix[i]));
Fpos = Posar->GetIntValue(ix[i]);
strcpy(Tdbp->To_Line, Updar->GetStringValue(ix[i]));
// Now write the updated line. if (WriteBuffer(g)) goto err;
} // endfor i
return RC_OK;
err: if (trace(1))
htrc("%s\n", g->Message);
return RC_FX;
} // end of UpdateSortedRows
/***********************************************************************/ /* DeleteSortedRows. When deleting using indexing, the issue is that */ /* record are not necessarily deleted in sequential order. Moving */ /* intermediate lines cannot be done while deleting them because */ /* this can cause extra wrong records to be included in the new file. */ /* What we do here is to reorder the deleted record and delete from */ /* the file from the ordered deleted records. */ /***********************************************************************/ int TXTFAM::DeleteSortedRows(PGLOBAL g)
{ int *ix, i, irc;
/*********************************************************************/ /* Get the stored delete values and sort them. */ /*********************************************************************/ if (!(Posar = MakeValueArray(g, To_Pos))) { // strcpy(g->Message, "Position array is null"); // return RC_INFO; return RC_OK; // Nothing to do
} elseif (!(Sosar = MakeValueArray(g, To_Sos))) {
snprintf(g->Message, sizeof(g->Message), "Start position array is null"); goto err;
} elseif (!(ix = (int*)Posar->GetSortIndex(g))) {
snprintf(g->Message, sizeof(g->Message), "Error getting array sort index"); goto err;
} // endif's
Tpos = Spos = 0;
for (i = 0; i < Posar->GetNval(); i++) { if ((irc = InitDelete(g, Posar->GetIntValue(ix[i]),
Sosar->GetIntValue(ix[i]))) == RC_FX) goto err;
// Now delete the sorted rows if (DeleteRecords(g, irc)) goto err;
} // endfor i
return RC_OK;
err: if (trace(1))
htrc("%s\n", g->Message);
return RC_FX;
} // end of DeleteSortedRows
/***********************************************************************/ /* The purpose of this function is to deal with access methods that */ /* are not coherent regarding the use of SetPos and GetPos. */ /***********************************************************************/ int TXTFAM::InitDelete(PGLOBAL g, int, int)
{
snprintf(g->Message, sizeof(g->Message), "InitDelete should not be used by this table type"); return RC_FX;
} // end of InitDelete
/* --------------------------- Class DOSFAM -------------------------- */
/***********************************************************************/ /* Constructors. */ /***********************************************************************/
DOSFAM::DOSFAM(PDOSDEF tdp) : TXTFAM(tdp)
{
To_Fbt = NULL;
Stream = NULL;
T_Stream = NULL;
UseTemp = false;
Bin = false;
} // end of DOSFAM standard constructor
DOSFAM::DOSFAM(PDOSFAM tdfp) : TXTFAM(tdfp)
{
To_Fbt = tdfp->To_Fbt;
Stream = tdfp->Stream;
T_Stream = tdfp->T_Stream;
UseTemp = tdfp->UseTemp;
Bin = tdfp->Bin;
} // end of DOSFAM copy constructor
DOSFAM::DOSFAM(PBLKFAM tdfp, PDOSDEF tdp) : TXTFAM(tdp)
{
Tdbp = tdfp->Tdbp;
To_Fb = tdfp->To_Fb;
To_Fbt = tdfp->To_Fbt;
Stream = tdfp->Stream;
T_Stream = tdfp->T_Stream;
UseTemp = tdfp->UseTemp;
Bin = tdfp->Bin;
} // end of DOSFAM constructor from BLKFAM
/***********************************************************************/ /* Reset: reset position values at the beginning of file. */ /***********************************************************************/ void DOSFAM::Reset(void)
{
TXTFAM::Reset();
Bin = false;
Fpos = Tpos = Spos = 0;
} // end of Reset
/***********************************************************************/ /* DOS GetFileLength: returns file size in number of bytes. */ /***********************************************************************/ int DOSFAM::GetFileLength(PGLOBAL g)
{ int len;
if (!Stream)
len = TXTFAM::GetFileLength(g); else if ((len = _filelength(_fileno(Stream))) < 0)
snprintf(g->Message, sizeof(g->Message), MSG(FILELEN_ERROR), "_filelength", To_File);
if (trace(1))
htrc("File length=%d\n", len);
return len;
} // end of GetFileLength
/***********************************************************************/ /* 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 DOSFAM::Cardinality(PGLOBAL g)
{ return (g) ? -1 : 0;
} // end of Cardinality
/***********************************************************************/ /* Use BlockTest to reduce the table estimated size. */ /* Note: This function is not really implemented yet. */ /***********************************************************************/ int DOSFAM::MaxBlkSize(PGLOBAL, int s)
{ return s;
} // end of MaxBlkSize
/***********************************************************************/ /* OpenTableFile: Open a DOS/UNIX table file using C standard I/Os. */ /***********************************************************************/ bool DOSFAM::OpenTableFile(PGLOBAL g)
{ char opmode[4], filename[_MAX_PATH]; //int ftype = Tdbp->GetFtype();
MODE mode = Tdbp->Mode;
PDBUSER dbuserp = PlgGetUser(g);
// This is required when using Unix files under Windows and vice versa //Bin = (Blocked || Ending != CRLF);
Bin = true; // To avoid ftell problems
switch (mode) { case MODE_READ:
snprintf(opmode, sizeof(opmode), "r"); break; case MODE_DELETE: if (!Tdbp->Next) { // Store the number of deleted lines
DelRows = Cardinality(g);
if (Blocked) { // Cardinality must return 0
Block = 0;
Last = Nrec;
} // endif blocked
// This will erase the entire file
snprintf(opmode, sizeof(opmode), "w");
Tdbp->ResetSize(); break;
} // endif
// Selective delete, pass thru
Bin = true; /* fall through */ case MODE_UPDATE: if ((UseTemp = Tdbp->IsUsingTemp(g))) {
snprintf(opmode, sizeof(opmode), "r");
Bin = true;
} else
snprintf(opmode, sizeof(opmode), "r+");
if (trace(1))
htrc("File %s open Stream=%p mode=%s\n", filename, Stream, 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 line buffer. For mode Delete a bigger buffer has to */ /* be allocated because is it also used to move lines into the file. */ /***********************************************************************/ bool DOSFAM::AllocateBuffer(PGLOBAL g)
{
MODE mode = Tdbp->Mode;
// Lrecl does not include line ending
Buflen = Lrecl + Ending + ((Bin) ? 1 : 0) + 1; // Sergei
if (trace(1))
htrc("SubAllocating a buffer of %d bytes\n", Buflen);
To_Buf = (char*)PlugSubAlloc(g, NULL, Buflen);
if (UseTemp || mode == MODE_DELETE) { // Have a big buffer to move lines
Dbflen = Buflen * DOS_BUFF_LEN;
DelBuf = PlugSubAlloc(g, NULL, Dbflen);
} elseif (mode == MODE_INSERT) { /*******************************************************************/ /* Prepare the buffer so eventual gaps are filled with blanks. */ /*******************************************************************/
memset(To_Buf, ' ', Buflen);
To_Buf[Buflen - 2] = '\n';
To_Buf[Buflen - 1] = '\0';
} // endif's mode
returnfalse;
} // end of AllocateBuffer
/***********************************************************************/ /* GetRowID: return the RowID of last read record. */ /***********************************************************************/ int DOSFAM::GetRowID(void)
{ return Rows;
} // end of GetRowID
/***********************************************************************/ /* GetPos: return the position of last read record. */ /***********************************************************************/ int DOSFAM::GetPos(void)
{ return Fpos;
} // end of GetPos
/***********************************************************************/ /* GetNextPos: return the position of next record. */ /***********************************************************************/ int DOSFAM::GetNextPos(void)
{ return ftell(Stream);
} // end of GetNextPos
/***********************************************************************/ /* SetPos: Replace the table at the specified position. */ /***********************************************************************/ bool DOSFAM::SetPos(PGLOBAL g, int pos)
{
Fpos = pos;
/***********************************************************************/ /* Record file position in case of UPDATE or DELETE. */ /***********************************************************************/ bool DOSFAM::RecordPos(PGLOBAL g)
{ if ((Fpos = ftell(Stream)) < 0) {
snprintf(g->Message, sizeof(g->Message), MSG(FTELL_ERROR), 0, strerror(errno)); // strcat(g->Message, " (possible wrong ENDING option value)"); returntrue;
} // endif Fpos
returnfalse;
} // end of RecordPos
/***********************************************************************/ /* Initialize Fpos and the current position for indexed DELETE. */ /***********************************************************************/ int DOSFAM::InitDelete(PGLOBAL g, int fpos, int spos)
{
Fpos = fpos;
/***********************************************************************/ /* Skip one record in file. */ /***********************************************************************/ int DOSFAM::SkipRecord(PGLOBAL g, bool header)
{
PDBUSER dup = (PDBUSER)g->Activityp->Aptr;
// Skip this record if (!fgets(To_Buf, Buflen, Stream)) { if (feof(Stream)) return RC_EF;
// Update progress information
dup->ProgCur = GetPos();
if (header) { // For Delete
Fpos = ftell(Stream);
if (!UseTemp)
Tpos = Spos = Fpos; // No need to move header
} // endif header
#ifdefined(THREAD) return RC_NF; // To have progress info #else return RC_OK; // To loop locally #endif
} // end of SkipRecord
/***********************************************************************/ /* ReadBuffer: Read one line for a text file. */ /***********************************************************************/ int DOSFAM::ReadBuffer(PGLOBAL g)
{ char *p; int rc;
if (!Stream) return RC_EF;
if (trace(2))
htrc("ReadBuffer: Tdbp=%p To_Line=%p Placed=%d\n",
Tdbp, Tdbp->To_Line, Placed);
if (!Placed) { /*******************************************************************/ /* Record file position in case of UPDATE or DELETE. */ /*******************************************************************/
next: if (RecordPos(g)) return RC_FX;
CurBlk = (int)Rows++;
if (trace(2))
htrc("ReadBuffer: CurBlk=%d\n", CurBlk);
/********************************************************************/ /* Check whether optimization on ROWID */ /* can be done, as well as for join as for local filtering. */ /*******************************************************************/ switch (Tdbp->TestBlock(g)) { case RC_EF: return RC_EF; case RC_NF: // Skip this record if ((rc = SkipRecord(g, FALSE)) != RC_OK) return rc;
goto next;
} // endswitch rc
} else
Placed = false;
if (trace(2))
htrc(" About to read: stream=%p To_Buf=%p Buflen=%d Fpos=%d\n",
Stream, To_Buf, Buflen, Fpos);
if (fgets(To_Buf, Buflen, Stream)) {
p = To_Buf + strlen(To_Buf) - 1;
if (trace(2))
htrc(" Read: To_Buf=%p p=%c\n", To_Buf, p);
#ifdefined(_WIN32) if (Bin) { // Data file is read in binary so CRLF remains #else if (true) { // Data files can be imported from Windows (having CRLF) #endif if (*p == '\n' || *p == '\r') { // is this enough for Unix ???
*p = '\0'; // Eliminate ending CR or LF character
if (p > To_Buf) { // is this enough for Unix ???
p--;
if (*p == '\n' || *p == '\r')
*p = '\0'; // Eliminate ending CR or LF character
/***********************************************************************/ /* WriteBuffer: File write routine for DOS access method. */ /***********************************************************************/ int DOSFAM::WriteBuffer(PGLOBAL g)
{ int curpos = 0; bool moved = true;
// T_Stream is the temporary stream or the table file stream itself if (!T_Stream) { if (UseTemp && Tdbp->Mode == MODE_UPDATE) { if (OpenTempFile(g)) return RC_FX;
} else
T_Stream = Stream;
} // endif T_Stream
if (Tdbp->Mode == MODE_UPDATE) { /*******************************************************************/ /* Here we simply rewrite a record on itself. There are two cases */ /* were another method should be used, a/ when Update apply to */ /* the whole file, b/ when updating the last field of a variable */ /* length file. The method could be to rewrite a new file, then */ /* to erase the old one and rename the new updated file. */ /*******************************************************************/
curpos = ftell(Stream);
if (trace(1))
htrc("Last : %d cur: %d\n", Fpos, curpos);
if (UseTemp) { /*****************************************************************/ /* We are using a temporary file. */ /* Before writing the updated record, we must eventually copy */ /* all the intermediate records that have not been updated. */ /*****************************************************************/ if (MoveIntermediateLines(g, &moved)) return RC_FX;
Spos = curpos; // New start position
} else // Update is directly written back into the file, // with this (fast) method, record size cannot change. if (fseek(Stream, Fpos, SEEK_SET)) {
snprintf(g->Message, sizeof(g->Message), MSG(FSETPOS_ERROR), 0); return RC_FX;
} // endif
} // endif mode
/*********************************************************************/ /* Prepare the write the updated line. */ /*********************************************************************/
strcat(strcpy(To_Buf, Tdbp->To_Line), (Bin) ? CrLf : "\n");
/*********************************************************************/ /* Now start the writing process. */ /*********************************************************************/ if ((fputs(To_Buf, T_Stream)) == EOF) {
snprintf(g->Message, sizeof(g->Message), MSG(FPUTS_ERROR), strerror(errno)); return RC_FX;
} // endif EOF
if (Tdbp->Mode == MODE_UPDATE && moved) if (fseek(Stream, curpos, SEEK_SET)) {
snprintf(g->Message, sizeof(g->Message), MSG(FSEEK_ERROR), strerror(errno)); return RC_FX;
} // endif
if (trace(1))
htrc("write done\n");
return RC_OK;
} // end of WriteBuffer
/***********************************************************************/ /* Data Base delete line routine for DOS and BLK access methods. */ /***********************************************************************/ int DOSFAM::DeleteRecords(PGLOBAL g, int irc)
{ bool moved; int curpos = ftell(Stream);
/*********************************************************************/ /* There is an alternative here: */ /* 1 - use a temporary file in which are copied all not deleted */ /* lines, at the end the original file will be deleted and */ /* the temporary file renamed to the original file name. */ /* 2 - directly move the not deleted lines inside the original */ /* file, and at the end erase all trailing records. */ /* This will be experimented. */ /*********************************************************************/ if (trace(1))
htrc( "DOS DeleteDB: rc=%d UseTemp=%d curpos=%d Fpos=%d Tpos=%d Spos=%d\n",
irc, UseTemp, curpos, Fpos, Tpos, Spos);
if (irc != RC_OK) { /*******************************************************************/ /* EOF: position Fpos at the end-of-file position. */ /*******************************************************************/
fseek(Stream, 0, SEEK_END);
Fpos = ftell(Stream);
if (trace(1))
htrc("Fpos placed at file end=%d\n", Fpos);
} // endif irc
if (Tpos == Spos) { /*******************************************************************/ /* First line to delete, Open temporary file. */ /*******************************************************************/ if (UseTemp) { if (OpenTempFile(g)) return RC_FX;
} else { /*****************************************************************/ /* Move of eventual preceding lines is not required here. */ /* Set the target file as being the source file itself. */ /* Set the future Tpos, and give Spos a value to block copying. */ /*****************************************************************/
T_Stream = Stream;
Spos = Tpos = Fpos;
} // endif UseTemp
} // endif Tpos == Spos
/*********************************************************************/ /* Move any intermediate lines. */ /*********************************************************************/ if (MoveIntermediateLines(g, &moved)) return RC_FX;
if (irc == RC_OK) { /*******************************************************************/ /* Reposition the file pointer and set Spos. */ /*******************************************************************/ if (!UseTemp || moved) if (fseek(Stream, curpos, SEEK_SET)) {
snprintf(g->Message, sizeof(g->Message), MSG(FSETPOS_ERROR), 0); return RC_FX;
} // endif
Spos = GetNextPos(); // New start position
if (trace(1))
htrc("after: Tpos=%d Spos=%d\n", Tpos, Spos);
} else { /*******************************************************************/ /* Last call after EOF has been reached. */ /* The UseTemp case is treated in CloseTableFile. */ /*******************************************************************/ if (!UseTemp & !Abort) { /*****************************************************************/ /* Because the chsize functionality is only accessible with a */ /* system call we must close the file and reopen it with the */ /* open function (_fopen for MS ??) this is still to be checked */ /* for compatibility with Text files and other OS's. */ /*****************************************************************/ char filename[_MAX_PATH]; int h; // File handle, return code
if ((h= global_open(g, MSGID_OPEN_STRERROR, filename, O_WRONLY)) <= 0) return RC_FX;
/*****************************************************************/ /* Remove extra records. */ /*****************************************************************/ #ifdefined(_WIN32) if (chsize(h, Tpos)) {
snprintf(g->Message, sizeof(g->Message), MSG(CHSIZE_ERROR), strerror(errno));
close(h); return RC_FX;
} // endif #else if (ftruncate(h, (off_t)Tpos)) {
snprintf(g->Message, sizeof(g->Message), MSG(TRUNCATE_ERROR), strerror(errno));
close(h); return RC_FX;
} // endif #endif
close(h);
if (trace(1))
htrc("done, h=%d irc=%d\n", h, irc);
} // endif !UseTemp
} // endif irc
return RC_OK; // All is correct
} // end of DeleteRecords
/***********************************************************************/ /* Open a temporary file used while updating or deleting. */ /***********************************************************************/ bool DOSFAM::OpenTempFile(PGLOBAL g)
{ char tempname[_MAX_PATH]; bool rc = false;
/*********************************************************************/ /* Open the temporary file, Spos is at the beginning of file. */ /*********************************************************************/
PlugSetPath(tempname, To_File, Tdbp->GetPath());
PlugRemoveType(tempname, tempname);
safe_strcat(tempname, sizeof(tempname), ".t");
if (!(T_Stream = PlugOpenFile(g, tempname, "wb"))) { if (trace(1))
htrc("%s\n", g->Message);
/***********************************************************************/ /* Move intermediate deleted or updated lines. */ /* This works only for file open in binary mode. */ /***********************************************************************/ bool DOSFAM::MoveIntermediateLines(PGLOBAL g, bool *b)
{ int n;
size_t req, len;
for (*b = false, n = Fpos - Spos; n > 0; n -= req) { if (!UseTemp || !*b) if (fseek(Stream, Spos, SEEK_SET)) {
snprintf(g->Message, sizeof(g->Message), MSG(READ_SEEK_ERROR), strerror(errno)); returntrue;
} // endif
req = (size_t)MY_MIN(n, Dbflen);
len = fread(DelBuf, 1, req, Stream);
if (trace(1))
htrc("after read req=%d len=%d\n", req, len);
if (len != req) {
snprintf(g->Message, sizeof(g->Message), MSG(DEL_READ_ERROR), (int) req, (int) len); returntrue;
} // endif len
if (!UseTemp) if (fseek(T_Stream, Tpos, SEEK_SET)) {
snprintf(g->Message, sizeof(g->Message), MSG(WRITE_SEEK_ERR), strerror(errno)); returntrue;
} // endif
if (trace(1))
htrc("after write pos=%d\n", ftell(Stream));
Tpos += (int)req;
Spos += (int)req;
if (trace(1))
htrc("loop: Tpos=%d Spos=%d\n", Tpos, Spos);
*b = true;
} // endfor n
returnfalse;
} // end of MoveIntermediate Lines
/***********************************************************************/ /* Delete the old file and rename the new temp file. */ /* If aborting just delete the new temp file. */ /* If indexed, make the temp file from the arrays. */ /***********************************************************************/ int DOSFAM::RenameTempFile(PGLOBAL g)
{ char *tempname, filetemp[_MAX_PATH], filename[_MAX_PATH]; int rc = RC_OK;
if (To_Fbt)
tempname = (char*)To_Fbt->Fname; else return RC_INFO; // Nothing to do ???
// This loop is necessary because, in case of join, // To_File can have been open several times. for (PFBLOCK fb = PlgGetUser(g)->Openlist; fb; fb = fb->Next) if (fb == To_Fb || (fb == To_Fbt))
rc = PlugCloseFile(g, fb);
if (!Abort) {
PlugSetPath(filename, To_File, Tdbp->GetPath());
PlugRemoveType(filetemp, filename);
safe_strcat(filetemp, sizeof(filetemp), ".ttt");
remove(filetemp); // May still be there from previous error
/***********************************************************************/ /* Table file close routine for DOS access method. */ /***********************************************************************/ void DOSFAM::CloseTableFile(PGLOBAL g, bool abort)
{ int rc;
Abort = abort;
if (UseTemp && T_Stream) { if (Tdbp->Mode == MODE_UPDATE && !Abort) { // Copy eventually remaining lines bool b;
// Delete the old file and rename the new temp file.
rc = RenameTempFile(g); // Also close all files
} else {
rc = PlugCloseFile(g, To_Fb);
if (trace(1))
htrc("DOS Close: closing %s rc=%d\n", To_File, rc);
} // endif UseTemp
Stream = NULL; // So we can know whether table is open
T_Stream = NULL;
} // end of CloseTableFile
/***********************************************************************/ /* Rewind routine for DOS access method. */ /***********************************************************************/ void DOSFAM::Rewind(void)
{ if (Stream) // Can be NULL when making index on void table
rewind(Stream);
Rows = 0;
OldBlk = CurBlk = -1;
} // end of Rewind
/* --------------------------- Class BLKFAM -------------------------- */
/***********************************************************************/ /* Reset: reset position values at the beginning of file. */ /***********************************************************************/ void BLKFAM::Reset(void)
{
DOSFAM::Reset();
Closing = false;
} // end of Reset
/***********************************************************************/ /* 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 BLKFAM::Cardinality(PGLOBAL g)
{ return (g) ? ((Block > 0) ? (int)((Block - 1) * Nrec + Last) : 0) : 1;
} // end of Cardinality
/***********************************************************************/ /* Use BlockTest to reduce the table estimated size. */ /***********************************************************************/ int BLKFAM::MaxBlkSize(PGLOBAL g, int)
{ int rc = RC_OK, savcur = CurBlk; int size;
// Roughly estimate the table size as the sum of blocks // that can contain good rows for (size = 0, CurBlk = 0; CurBlk < Block; CurBlk++) if ((rc = Tdbp->TestBlock(g)) == RC_OK)
size += (CurBlk == Block - 1) ? Last : Nrec; elseif (rc == RC_EF) break;
CurBlk = savcur; return size;
} // end of MaxBlkSize
/***********************************************************************/ /* Allocate the line buffer. For mode Delete or when a temp file is */ /* used another big buffer has to be allocated because is it used */ /* to move or update the lines into the (temp) file. */ /***********************************************************************/ bool BLKFAM::AllocateBuffer(PGLOBAL g)
{ int len;
MODE mode = Tdbp->GetMode();
// For variable length files, Lrecl does not include CRLF
len = Lrecl + ((Tdbp->GetFtype()) ? 0 : Ending);
Buflen = len * Nrec;
CurLine = To_Buf = (char*)PlugSubAlloc(g, NULL, Buflen);
if (UseTemp || mode == MODE_DELETE) { if (mode == MODE_UPDATE)
OutBuf = (char*)PlugSubAlloc(g, NULL, len + 1);
Dbflen = Buflen;
DelBuf = PlugSubAlloc(g, NULL, Dbflen);
} elseif (mode == MODE_INSERT)
Rbuf = Nrec; // To be used by WriteDB
returnfalse;
} // end of AllocateBuffer
/***********************************************************************/ /* GetRowID: return the RowID of last read record. */ /***********************************************************************/ int BLKFAM::GetRowID(void)
{ return CurNum + Nrec * CurBlk + 1;
} // end of GetRowID
/***********************************************************************/ /* GetPos: return the position of last read record. */ /***********************************************************************/ int BLKFAM::GetPos(void)
{ return (CurNum + Nrec * CurBlk); // Computed file index
} // end of GetPos
/***********************************************************************/ /* GetNextPos: called by DeleteRecords. */ /***********************************************************************/ int BLKFAM::GetNextPos(void)
{ return (int)(Fpos + NxtLine - CurLine);
} // end of GetNextPos
/***********************************************************************/ /* SetPos: Replace the table at the specified position. */ /***********************************************************************/ bool BLKFAM::SetPos(PGLOBAL g, int)
{
snprintf(g->Message, sizeof(g->Message), "Blocked variable tables cannot be used indexed"); returntrue;
} // end of SetPos
/***********************************************************************/ /* Record file position in case of UPDATE or DELETE. */ /* Not used yet for blocked tables. */ /***********************************************************************/ bool BLKFAM::RecordPos(PGLOBAL)
{
Fpos = (CurNum + Nrec * CurBlk); // Computed file index returnfalse;
} // end of RecordPos
/***********************************************************************/ /* Skip one record in file. */ /***********************************************************************/ int BLKFAM::SkipRecord(PGLOBAL, bool header)
{ if (header) { // For Delete
Fpos = BlkPos[0]; // First block starts after the header
if (!UseTemp)
Tpos = Spos = Fpos; // No need to move header
} // endif header
OldBlk = -2; // To force fseek on first block return RC_OK;
} // end of SkipRecord
/***********************************************************************/ /* ReadBuffer: Read one line for a text file. */ /***********************************************************************/ int BLKFAM::ReadBuffer(PGLOBAL g)
{ int i, rc = RC_OK;
size_t n;
/*********************************************************************/ /* Sequential reading when Placed is not true. */ /*********************************************************************/ if (Placed) {
Placed = false;
} elseif (++CurNum < Rbuf) {
CurLine = NxtLine;
// Get the position of the next line in the buffer while (*NxtLine++ != '\n') ;
/*******************************************************************/ /* Before reading a new block, check whether block optimization */ /* can be done, as well as for join as for local filtering. */ /*******************************************************************/ switch (Tdbp->TestBlock(g)) { case RC_EF: return RC_EF; case RC_NF: goto next;
} // endswitch rc
} // endif's
if (OldBlk == CurBlk) goto ok; // Block is already there
// fseek is required only in non sequential reading if (CurBlk != OldBlk + 1) if (fseek(Stream, BlkPos[CurBlk], SEEK_SET)) {
snprintf(g->Message, sizeof(g->Message), MSG(FSETPOS_ERROR), BlkPos[CurBlk]); return RC_FX;
} // endif fseek
// Calculate the length of block to read
BlkLen = BlkPos[CurBlk + 1] - BlkPos[CurBlk];
if (trace(1))
htrc("File position is now %d\n", ftell(Stream));
// Read the entire next block
n = fread(To_Buf, 1, (size_t)BlkLen, Stream);
if ((size_t) n == (size_t) BlkLen) { // ReadBlks++;
num_read++;
Rbuf = (CurBlk == Block - 1) ? Last : Nrec;
ok:
rc = RC_OK;
// Get the position of the current line for (i = 0, CurLine = To_Buf; i < CurNum; i++) while (*CurLine++ != '\n') ; // What about Unix ???
// Now get the position of the next line for (NxtLine = CurLine; *NxtLine++ != '\n';) ;
OldBlk = CurBlk; // Last block actually read
IsRead = true; // Is read indeed
fin: // Store the current record file position for Delete and Update
Fpos = (int)(BlkPos[CurBlk] + CurLine - To_Buf); return rc;
} // end of ReadBuffer
/***********************************************************************/ /* WriteBuffer: File write routine for the blocked DOS access method. */ /* Update is directly written back into the file, */ /* with this (fast) method, record size cannot change. */ /***********************************************************************/ int BLKFAM::WriteBuffer(PGLOBAL g)
{ if (Tdbp->GetMode() == MODE_INSERT) { /*******************************************************************/ /* In Insert mode, blocks are added sequentially to the file end. */ /*******************************************************************/ if (!Closing) { // Add line to the write buffer
strcat(strcpy(CurLine, Tdbp->GetLine()), CrLf);
if (++CurNum != Rbuf) {
CurLine += strlen(CurLine); return RC_OK; // We write only full blocks
} // endif CurNum
} // endif Closing
// Now start the writing process.
NxtLine = CurLine + strlen(CurLine);
BlkLen = (int)(NxtLine - To_Buf);
if (fwrite(To_Buf, 1, BlkLen, Stream) != (size_t)BlkLen) {
snprintf(g->Message, sizeof(g->Message), MSG(FWRITE_ERROR), strerror(errno));
Closing = true; // To tell CloseDB about a Write error return RC_FX;
} // endif size
// T_Stream is the temporary stream or the table file stream itself if (!T_Stream)
{ if (UseTemp /*&& Tdbp->GetMode() == MODE_UPDATE*/) { if (OpenTempFile(g)) return RC_FX;
} else
T_Stream = Stream;
} if (UseTemp) { /*****************************************************************/ /* We are using a temporary file. Before writing the updated */ /* record, we must eventually copy all the intermediate records */ /* that have not been updated. */ /*****************************************************************/ if (MoveIntermediateLines(g, &moved)) return RC_FX;
Spos = GetNextPos(); // New start position
// Prepare the output buffer #ifdefined(_WIN32)
crlf = "\r\n"; #else
crlf = "\n"; #endif// _WIN32
strcat(strcpy(OutBuf, Tdbp->GetLine()), crlf);
len = strlen(OutBuf);
} else { if (fseek(Stream, Fpos, SEEK_SET)) { // Fpos is last position
snprintf(g->Message, sizeof(g->Message), MSG(FSETPOS_ERROR), 0); return RC_FX;
} // endif fseek
// Replace the line inside read buffer (length has not changed)
memcpy(CurLine, Tdbp->GetLine(), strlen(Tdbp->GetLine()));
OutBuf = CurLine;
len = (size_t)(NxtLine - CurLine);
} // endif UseTemp
// Delete the old file and rename the new temp file.
rc = RenameTempFile(g); // Also close all files
} else { // Closing is True if last Write was in error if (Tdbp->GetMode() == MODE_INSERT && CurNum && !Closing) { // Some more inserted lines remain to be written
Rbuf = CurNum--;
Closing = true;
wrc = WriteBuffer(g);
} elseif (Modif && !Closing) { // Last updated block remains to be written
Closing = true;
wrc = ReadBuffer(g);
} // endif's
Stream = NULL; // So we can know whether table is open
} // end of CloseTableFile
/***********************************************************************/ /* Rewind routine for DOS access method. */ /* Note: commenting out OldBlk = -1 has two advantages: */ /* 1 - It forces fseek on first block, thus suppressing the need to */ /* rewind the file, anyway unuseful when second pass if indexed. */ /* 2 - It permit to avoid re-reading small tables having only 1 block.*/ /***********************************************************************/ void BLKFAM::Rewind(void)
{ //rewind(Stream); will be placed by fseek
CurBlk = -1;
CurNum = Rbuf; //OldBlk = -1; commented out in case we reuse last read block //Rbuf = 0; commented out in case we reuse last read block
} // end of Rewind
/* --------------------------- Class BINFAM -------------------------- */
#if0 /***********************************************************************/ /* BIN GetFileLength: returns file size in number of bytes. */ /***********************************************************************/ int BINFAM::GetFileLength(PGLOBAL g)
{ int len;
if (!Stream)
len = TXTFAM::GetFileLength(g); else if ((len = _filelength(_fileno(Stream))) < 0)
snprintf(g->Message, sizeof(g->Message), MSG(FILELEN_ERROR), "_filelength", To_File);
xtrc(1, "File length=%d\n", len); return len;
} // end of GetFileLength
/***********************************************************************/ /* 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 BINFAM::Cardinality(PGLOBAL g)
{ return (g) ? -1 : 0;
} // end of Cardinality
/***********************************************************************/ /* OpenTableFile: Open a DOS/UNIX table file using C standard I/Os. */ /***********************************************************************/ bool BINFAM::OpenTableFile(PGLOBAL g) { char opmode[4], filename[_MAX_PATH];
MODE mode = Tdbp->GetMode();
PDBUSER dbuserp = PlgGetUser(g);
if (trace(1))
htrc("File %s open Stream=%p mode=%s\n", filename, Stream, opmode);
To_Fb = dbuserp->Openlist; // Keep track of File block
/*********************************************************************/ /* Allocate the line buffer. */ /*********************************************************************/ return AllocateBuffer(g);
} // end of OpenTableFile #endif// 0
/***********************************************************************/ /* 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. */ /***********************************************************************/ bool BINFAM::AllocateBuffer(PGLOBAL g)
{
MODE mode = Tdbp->GetMode();
// Lrecl is Ok
Buflen = Lrecl;
// Buffer will be allocated separately if (mode == MODE_ANY) {
xtrc(1, "SubAllocating a buffer of %d bytes\n", Buflen);
To_Buf = (char*)PlugSubAlloc(g, NULL, Buflen);
} elseif (UseTemp || mode == MODE_DELETE) { // Have a big buffer to move lines
Dbflen = Buflen * DOS_BUFF_LEN;
DelBuf = PlugSubAlloc(g, NULL, Dbflen);
} // endif mode
returnfalse; #if0
MODE mode = Tdbp->GetMode();
// Lrecl is Ok
Dbflen = Buflen = Lrecl;
if (trace(1))
htrc("SubAllocating a buffer of %d bytes\n", Buflen);
DelBuf = To_Buf = (char*)PlugSubAlloc(g, NULL, Buflen); returnfalse; #endif// 0
} // end of AllocateBuffer
#if0 /***********************************************************************/ /* GetRowID: return the RowID of last read record. */ /***********************************************************************/ int BINFAM::GetRowID(void) { return Rows;
} // end of GetRowID
/***********************************************************************/ /* GetPos: return the position of last read record. */ /***********************************************************************/ int BINFAM::GetPos(void) { return Fpos;
} // end of GetPos
/***********************************************************************/ /* GetNextPos: return the position of next record. */ /***********************************************************************/ int BINFAM::GetNextPos(void) { return ftell(Stream);
} // end of GetNextPos
/***********************************************************************/ /* SetPos: Replace the table at the specified position. */ /***********************************************************************/ bool BINFAM::SetPos(PGLOBAL g, int pos) {
Fpos = pos;
/***********************************************************************/ /* Record file position in case of UPDATE or DELETE. */ /***********************************************************************/ bool BINFAM::RecordPos(PGLOBAL g) { if ((Fpos = ftell(Stream)) < 0) {
snprintf(g->Message, sizeof(g->Message), MSG(FTELL_ERROR), 0, strerror(errno)); // strcat(g->Message, " (possible wrong ENDING option value)"); returntrue;
} // endif Fpos
returnfalse;
} // end of RecordPos #endif// 0
/***********************************************************************/ /* ReadBuffer: Read one line for a text file. */ /***********************************************************************/ int BINFAM::ReadBuffer(PGLOBAL g)
{ int rc;
if (!Placed) { /*******************************************************************/ /* Record file position in case of UPDATE or DELETE. */ /*******************************************************************/ if (RecordPos(g)) return RC_FX;
xtrc(2, "ReadBuffer: rc=%d\n", rc);
IsRead = true; return rc;
} // end of ReadBuffer
/***********************************************************************/ /* WriteBuffer: File write routine for BIN access method. */ /***********************************************************************/ int BINFAM::WriteBuffer(PGLOBAL g)
{ int curpos = 0; bool moved = true;
// T_Stream is the temporary stream or the table file stream itself if (!T_Stream) { if (UseTemp && Tdbp->GetMode() == MODE_UPDATE) { if (OpenTempFile(g)) return RC_FX;
} else
T_Stream = Stream;
} // endif T_Stream
if (Tdbp->GetMode() == MODE_UPDATE) { /*******************************************************************/ /* Here we simply rewrite a record on itself. There are two cases */ /* were another method should be used, a/ when Update apply to */ /* the whole file, b/ when updating the last field of a variable */ /* length file. The method could be to rewrite a new file, then */ /* to erase the old one and rename the new updated file. */ /*******************************************************************/
curpos = ftell(Stream);
if (trace(1))
htrc("Last : %d cur: %d\n", Fpos, curpos);
if (UseTemp) { /*****************************************************************/ /* We are using a temporary file. */ /* Before writing the updated record, we must eventually copy */ /* all the intermediate records that have not been updated. */ /*****************************************************************/ if (MoveIntermediateLines(g, &moved)) return RC_FX;
Spos = curpos; // New start position
} else // Update is directly written back into the file, // with this (fast) method, record size cannot change. if (fseek(Stream, Fpos, SEEK_SET)) {
snprintf(g->Message, sizeof(g->Message), MSG(FSETPOS_ERROR), 0); return RC_FX;
} // endif
if (Tdbp->GetMode() == MODE_UPDATE && moved) if (fseek(Stream, curpos, SEEK_SET)) {
snprintf(g->Message, sizeof(g->Message), MSG(FSEEK_ERROR), strerror(errno)); return RC_FX;
} // endif
xtrc(1, "Binary write done\n"); return RC_OK;
} // end of WriteBuffer
#if0 /***********************************************************************/ /* Data Base delete line routine for DOS and BLK access methods. */ /***********************************************************************/ int DOSFAM::DeleteRecords(PGLOBAL g, int irc)
{ bool moved; int curpos = ftell(Stream);
/*********************************************************************/ /* There is an alternative here: */ /* 1 - use a temporary file in which are copied all not deleted */ /* lines, at the end the original file will be deleted and */ /* the temporary file renamed to the original file name. */ /* 2 - directly move the not deleted lines inside the original */ /* file, and at the end erase all trailing records. */ /* This will be experimented. */ /*********************************************************************/ if (trace(1))
htrc( "DOS DeleteDB: rc=%d UseTemp=%d curpos=%d Fpos=%d Tpos=%d Spos=%d\n",
irc, UseTemp, curpos, Fpos, Tpos, Spos);
if (irc != RC_OK) { /*******************************************************************/ /* EOF: position Fpos at the end-of-file position. */ /*******************************************************************/
fseek(Stream, 0, SEEK_END);
Fpos = ftell(Stream);
if (trace(1))
htrc("Fpos placed at file end=%d\n", Fpos);
} // endif irc
if (Tpos == Spos) { /*******************************************************************/ /* First line to delete, Open temporary file. */ /*******************************************************************/ if (UseTemp) { if (OpenTempFile(g)) return RC_FX;
} else { /*****************************************************************/ /* Move of eventual preceding lines is not required here. */ /* Set the target file as being the source file itself. */ /* Set the future Tpos, and give Spos a value to block copying. */ /*****************************************************************/
T_Stream = Stream;
Spos = Tpos = Fpos;
} // endif UseTemp
} // endif Tpos == Spos
/*********************************************************************/ /* Move any intermediate lines. */ /*********************************************************************/ if (MoveIntermediateLines(g, &moved)) return RC_FX;
if (irc == RC_OK) { /*******************************************************************/ /* Reposition the file pointer and set Spos. */ /*******************************************************************/ if (!UseTemp || moved) if (fseek(Stream, curpos, SEEK_SET)) {
snprintf(g->Message, sizeof(g->Message), MSG(FSETPOS_ERROR), 0); return RC_FX;
} // endif
Spos = GetNextPos(); // New start position
if (trace(1))
htrc("after: Tpos=%d Spos=%d\n", Tpos, Spos);
} else { /*******************************************************************/ /* Last call after EOF has been reached. */ /* The UseTemp case is treated in CloseTableFile. */ /*******************************************************************/ if (!UseTemp & !Abort) { /*****************************************************************/ /* Because the chsize functionality is only accessible with a */ /* system call we must close the file and reopen it with the */ /* open function (_fopen for MS ??) this is still to be checked */ /* for compatibility with Text files and other OS's. */ /*****************************************************************/ char filename[_MAX_PATH]; int h; // File handle, return code
if ((h= global_open(g, MSGID_OPEN_STRERROR, filename, O_WRONLY)) <= 0) return RC_FX;
/*****************************************************************/ /* Remove extra records. */ /*****************************************************************/ #ifdefined(_WIN32) if (chsize(h, Tpos)) {
snprintf(g->Message, sizeof(g->Message), MSG(CHSIZE_ERROR), strerror(errno));
close(h); return RC_FX;
} // endif #else if (ftruncate(h, (off_t)Tpos)) {
snprintf(g->Message, sizeof(g->Message), MSG(TRUNCATE_ERROR), strerror(errno));
close(h); return RC_FX;
} // endif #endif
close(h);
if (trace(1))
htrc("done, h=%d irc=%d\n", h, irc);
} // endif !UseTemp
} // endif irc
return RC_OK; // All is correct
} // end of DeleteRecords
/***********************************************************************/ /* Table file close routine for DOS access method. */ /***********************************************************************/ void BINFAM::CloseTableFile(PGLOBAL g, bool abort)
{ int rc;
Abort = abort;
rc = PlugCloseFile(g, To_Fb);
xtrc(1, "BIN Close: closing %s rc=%d\n", To_File, rc);
Stream = NULL; // So we can know whether table is open
} // end of CloseTableFile
/***********************************************************************/ /* Rewind routine for BIN access method. */ /***********************************************************************/ void BINFAM::Rewind(void)
{ if (Stream) // Can be NULL when making index on void table
rewind(Stream);
Rows = 0;
OldBlk = CurBlk = -1;
} // end of Rewind #endif// 0
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.14 Sekunden
(vorverarbeitet am 2026-10-08)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.