1/*********** File AM Dbf C++ Program Source Code File (.CPP) ****************/
2/* PROGRAM NAME: FILAMDBF */
3/* ------------- */
4/* Version 1.8 */
5/* */
6/* COPYRIGHT: */
7/* ---------- */
8/* (C) Copyright to the author Olivier BERTRAND 2005-2017 */
9/* */
10/* WHAT THIS PROGRAM DOES: */
11/* ----------------------- */
12/* This program are the DBF file access method classes. */
13/* */
14/* ACKNOWLEDGEMENT: */
15/* ---------------- */
16/* Somerset Data Systems, Inc. (908) 766-5845 */
17/* Version 1.2 April 6, 1991 */
18/* Programmer: Jay Parsons */
19/****************************************************************************/
20
21/***********************************************************************/
22/* Include relevant sections of the System header files. */
23/***********************************************************************/
24#include "my_global.h"
25#if defined(__WIN__)
26#include <io.h>
27#include <fcntl.h>
28//#include <errno.h>
29//#include <windows.h>
30#else // !__WIN__
31#if defined(UNIX)
32#include <errno.h>
33#include <unistd.h>
34#else // !UNIX
35//#include <io.h>
36#endif // !UNIX
37//#include <fcntl.h>
38#endif // !__WIN__
39#include <ctype.h>
40#include <stdio.h>
41#include <string.h>
42
43/***********************************************************************/
44/* Include application header files: */
45/* global.h is header containing all global declarations. */
46/* plgdbsem.h is header containing the DB application declarations. */
47/* tabdos.h is header containing the TABDOS class declarations. */
48/***********************************************************************/
49#include "global.h"
50#include "plgdbsem.h"
51#include "filamdbf.h"
52#include "tabdos.h"
53#include "valblk.h"
54#define NO_FUNC
55#include "plgcnx.h" // For DB types
56#include "resource.h"
57
58/****************************************************************************/
59/* Definitions. */
60/****************************************************************************/
61#define HEADLEN 32 /* sizeof ( mainhead or thisfield ) */
62//efine MEMOLEN 10 /* length of memo field in .dbf */
63#define DBFTYPE 3 /* value of bits 0 and 1 if .dbf */
64#define EOH 0x0D /* end-of-header marker in .dbf file */
65
66/****************************************************************************/
67/* First 32 bytes of a .dbf file. */
68/* Note: some reserved fields are used here to store info (Fields) */
69/****************************************************************************/
70typedef struct _dbfheader {
71//uchar Dbf :2; /* both 1 for dBASE III or IV .dbf */
72//uchar :1;
73//uchar Db4dbt:1; /* 1 if a dBASE IV-type .dbt exists */
74//uchar Dbfox :4; /* FoxPro if equal to 3 */
75 uchar Version; /* Version information flags */
76 char Filedate[3]; /* date, YYMMDD, binary. YY=year-1900 */
77 private:
78 /* The following four members are stored in little-endian format on disk */
79 char m_RecordsBuf[4]; /* records in the file */
80 char m_HeadlenBuf[2]; /* bytes in the header */
81 char m_ReclenBuf[2]; /* bytes in a record */
82 char m_FieldsBuf[2]; /* Reserved but used to store fields */
83 public:
84 char Incompleteflag; /* 01 if incomplete, else 00 */
85 char Encryptflag; /* 01 if encrypted, else 00 */
86 char Reserved2[12]; /* for LAN use */
87 char Mdxflag; /* 01 if production .mdx, else 00 */
88 char Language; /* Codepage */
89 char Reserved3[2];
90
91 uint Records(void) const {return uint4korr(m_RecordsBuf);}
92 ushort Headlen(void) const {return uint2korr(m_HeadlenBuf);}
93 ushort Reclen(void) const {return uint2korr(m_ReclenBuf);}
94 ushort Fields(void) const {return uint2korr(m_FieldsBuf);}
95
96 void SetHeadlen(ushort num) {int2store(m_HeadlenBuf, num);}
97 void SetReclen(ushort num) {int2store(m_ReclenBuf, num);}
98 void SetFields(ushort num) {int2store(m_FieldsBuf, num);}
99 } DBFHEADER;
100
101/****************************************************************************/
102/* Column field descriptor of a .dbf file. */
103/****************************************************************************/
104typedef struct _descriptor {
105 char Name[11]; /* field name, in capitals, null filled*/
106 char Type; /* field type, C, D, F, L, M or N */
107 uint Offset; /* used in memvars, not in files. */
108 uchar Length; /* field length */
109 uchar Decimals; /* number of decimal places */
110 short Reserved4;
111 char Workarea; /* ??? */
112 char Reserved5[2];
113 char Setfield; /* ??? */
114 char Reserved6[7];
115 char Mdxfield; /* 01 if tag field in production .mdx */
116 } DESCRIPTOR;
117
118/****************************************************************************/
119/* dbfhead: Routine to analyze a .dbf header. */
120/* Parameters: */
121/* PGLOBAL g -- pointer to the Plug Global structure */
122/* FILE *file -- pointer to file to analyze */
123/* PSZ fn -- pathname of the file to analyze */
124/* DBFHEADER *buf -- pointer to _dbfheader structure */
125/* Returns: */
126/* RC_OK, RC_NF, RC_INFO, or RC_FX if error. */
127/* Side effects: */
128/* Moves file pointer to byte 32; fills buffer at buf with */
129/* first 32 bytes of file. */
130/****************************************************************************/
131static int dbfhead(PGLOBAL g, FILE *file, PCSZ fn, DBFHEADER *buf)
132 {
133 char endmark[2];
134 int dbc = 2, rc = RC_OK;
135
136 *g->Message = '\0';
137
138 // Read the first 32 bytes into buffer
139 if (fread(buf, HEADLEN, 1, file) != 1) {
140 strcpy(g->Message, MSG(NO_READ_32));
141 return RC_NF;
142 } // endif fread
143
144 // Check first byte to be sure of .dbf type
145 if ((buf->Version & 0x03) != DBFTYPE) {
146 strcpy(g->Message, MSG(NOT_A_DBF_FILE));
147 rc = RC_INFO;
148
149 if ((buf->Version & 0x30) == 0x30) {
150 strcpy(g->Message, MSG(FOXPRO_FILE));
151 dbc = 264; // FoxPro database container
152 } // endif Version
153
154 } else
155 strcpy(g->Message, MSG(DBASE_FILE));
156
157 // Check last byte(s) of header
158 if (fseek(file, buf->Headlen() - dbc, SEEK_SET) != 0) {
159 sprintf(g->Message, MSG(BAD_HEADER), fn);
160 return RC_FX;
161 } // endif fseek
162
163 if (fread(&endmark, 2, 1, file) != 1) {
164 strcpy(g->Message, MSG(BAD_HEAD_END));
165 return RC_FX;
166 } // endif fread
167
168 // Some files have just 1D others have 1D00 following fields
169 if (endmark[0] != EOH && endmark[1] != EOH) {
170 sprintf(g->Message, MSG(NO_0DH_HEAD), dbc);
171
172 if (rc == RC_OK)
173 return RC_FX;
174
175 } // endif endmark
176
177 // Calculate here the number of fields while we have the dbc info
178 buf->SetFields((buf->Headlen() - dbc - 1) / 32);
179 fseek(file, HEADLEN, SEEK_SET);
180 return rc;
181 } // end of dbfhead
182
183/* -------------------------- Function DBFColumns ------------------------- */
184
185/****************************************************************************/
186/* DBFColumns: constructs the result blocks containing the description */
187/* of all the columns of a DBF file that will be retrieved by #GetData. */
188/****************************************************************************/
189PQRYRES DBFColumns(PGLOBAL g, PCSZ dp, PCSZ fn, bool info)
190 {
191 int buftyp[] = {TYPE_STRING, TYPE_SHORT, TYPE_STRING,
192 TYPE_INT, TYPE_INT, TYPE_SHORT};
193 XFLD fldtyp[] = {FLD_NAME, FLD_TYPE, FLD_TYPENAME,
194 FLD_PREC, FLD_LENGTH, FLD_SCALE};
195 unsigned int length[] = {11, 6, 8, 10, 10, 6};
196 char buf[2], filename[_MAX_PATH];
197 int ncol = sizeof(buftyp) / sizeof(int);
198 int rc, type, len, field, fields;
199 bool bad;
200 DBFHEADER mainhead;
201 DESCRIPTOR thisfield;
202 FILE *infile = NULL;
203 PQRYRES qrp;
204 PCOLRES crp;
205
206 if (trace(1))
207 htrc("DBFColumns: File %s\n", SVP(fn));
208
209 if (!info) {
210 if (!fn) {
211 strcpy(g->Message, MSG(MISSING_FNAME));
212 return NULL;
213 } // endif fn
214
215 /************************************************************************/
216 /* Open the input file. */
217 /************************************************************************/
218 PlugSetPath(filename, fn, dp);
219
220 if (!(infile= global_fopen(g, MSGID_CANNOT_OPEN, filename, "rb")))
221 return NULL;
222
223 /************************************************************************/
224 /* Get the first 32 bytes of the header. */
225 /************************************************************************/
226 if ((rc = dbfhead(g, infile, filename, &mainhead)) == RC_FX) {
227 fclose(infile);
228 return NULL;
229 } // endif dbfhead
230
231 /************************************************************************/
232 /* Allocate the structures used to refer to the result set. */
233 /************************************************************************/
234 fields = mainhead.Fields();
235 } else
236 fields = 0;
237
238 qrp = PlgAllocResult(g, ncol, fields, IDS_COLUMNS + 3,
239 buftyp, fldtyp, length, true, false);
240
241 if (info || !qrp) {
242 if (infile)
243 fclose(infile);
244
245 return qrp;
246 } // endif info
247
248 if (trace(1)) {
249 htrc("Structure of %s\n", filename);
250 htrc("headlen=%hd reclen=%hd degree=%d\n",
251 mainhead.Headlen(), mainhead.Reclen(), fields);
252 htrc("flags(iem)=%d,%d,%d cp=%d\n", mainhead.Incompleteflag,
253 mainhead.Encryptflag, mainhead.Mdxflag, mainhead.Language);
254 htrc("%hd records, last changed %02d/%02d/%d\n",
255 mainhead.Records(), mainhead.Filedate[1], mainhead.Filedate[2],
256 mainhead.Filedate[0] + (mainhead.Filedate[0] <= 30) ? 2000 : 1900);
257 htrc("Field Type Offset Len Dec Set Mdx\n");
258 } // endif trace
259
260 buf[1] = '\0';
261
262 /**************************************************************************/
263 /* Do it field by field. We are at byte 32 of file. */
264 /**************************************************************************/
265 for (field = 0; field < fields; field++) {
266 bad = FALSE;
267
268 if (fread(&thisfield, HEADLEN, 1, infile) != 1) {
269 sprintf(g->Message, MSG(ERR_READING_REC), field+1, fn);
270 goto err;
271 } else
272 len = thisfield.Length;
273
274 if (trace(1))
275 htrc("%-11s %c %6ld %3d %2d %3d %3d\n",
276 thisfield.Name, thisfield.Type, thisfield.Offset, len,
277 thisfield.Decimals, thisfield.Setfield, thisfield.Mdxfield);
278
279 /************************************************************************/
280 /* Now get the results into blocks. */
281 /************************************************************************/
282 switch (thisfield.Type) {
283 case 'C': // Characters
284 case 'L': // Logical 'T' or 'F' or space
285 type = TYPE_STRING;
286 break;
287 case 'M': // Memo a .DBT block number
288 case 'B': // Binary a .DBT block number
289 case 'G': // Ole a .DBT block number
290 type = TYPE_STRING;
291 break;
292 //case 'I': // Long
293 //case '+': // Autoincrement
294 // type = TYPE_INT;
295 // break;
296 case 'N':
297 type = (thisfield.Decimals) ? TYPE_DOUBLE
298 : (len > 10) ? TYPE_BIGINT : TYPE_INT;
299 break;
300 case 'F': // Float
301 //case 'O': // Double
302 type = TYPE_DOUBLE;
303 break;
304 case 'D':
305 type = TYPE_DATE; // Is this correct ???
306 break;
307 default:
308 if (!info) {
309 sprintf(g->Message, MSG(BAD_DBF_TYPE), thisfield.Type
310 , thisfield.Name);
311 goto err;
312 } // endif info
313
314 type = TYPE_ERROR;
315 bad = TRUE;
316 } // endswitch Type
317
318 crp = qrp->Colresp; // Column Name
319 crp->Kdata->SetValue(thisfield.Name, field);
320 crp = crp->Next; // Data Type
321 crp->Kdata->SetValue((int)type, field);
322 crp = crp->Next; // Type Name
323
324 if (bad) {
325 buf[0] = thisfield.Type;
326 crp->Kdata->SetValue(buf, field);
327 } else
328 crp->Kdata->SetValue(GetTypeName(type), field);
329
330 crp = crp->Next; // Precision
331 crp->Kdata->SetValue((int)thisfield.Length, field);
332 crp = crp->Next; // Length
333 crp->Kdata->SetValue((int)thisfield.Length, field);
334 crp = crp->Next; // Scale (precision)
335 crp->Kdata->SetValue((int)thisfield.Decimals, field);
336 } // endfor field
337
338 qrp->Nblin = field;
339 fclose(infile);
340
341#if 0
342 if (info) {
343 /************************************************************************/
344 /* Prepare return message for dbfinfo command. */
345 /************************************************************************/
346 char buf[64];
347
348 sprintf(buf,
349 "Ver=%02x ncol=%hu nlin=%u lrecl=%hu headlen=%hu date=%02d/%02d/%02d",
350 mainhead.Version, fields, mainhead.Records, mainhead.Reclen,
351 mainhead.Headlen, mainhead.Filedate[0], mainhead.Filedate[1],
352 mainhead.Filedate[2]);
353
354 strcat(g->Message, buf);
355 } // endif info
356#endif // 0
357
358 /**************************************************************************/
359 /* Return the result pointer for use by GetData routines. */
360 /**************************************************************************/
361 return qrp;
362
363 err:
364 fclose(infile);
365 return NULL;
366 } // end of DBFColumns
367
368/* ---------------------------- Class DBFBASE ----------------------------- */
369
370/****************************************************************************/
371/* Constructors. */
372/****************************************************************************/
373DBFBASE::DBFBASE(PDOSDEF tdp)
374 {
375 Records = 0;
376 Nerr = 0;
377 Maxerr = tdp->Maxerr;
378 Accept = tdp->Accept;
379 ReadMode = tdp->ReadMode;
380 } // end of DBFBASE standard constructor
381
382DBFBASE::DBFBASE(DBFBASE *txfp)
383 {
384 Records = txfp->Records;
385 Nerr = txfp->Nerr;
386 Maxerr = txfp->Maxerr;
387 Accept = txfp->Accept;
388 ReadMode = txfp->ReadMode;
389 } // end of DBFBASE copy constructor
390
391/****************************************************************************/
392/* ScanHeader: scan the DBF file header for number of records, record size,*/
393/* and header length. Set Records, check that Reclen is equal to lrecl and */
394/* return the header length or 0 in case of error. */
395/****************************************************************************/
396int DBFBASE::ScanHeader(PGLOBAL g, PCSZ fn, int lrecl, int *rln, PCSZ defpath)
397 {
398 int rc;
399 char filename[_MAX_PATH];
400 DBFHEADER header;
401 FILE *infile;
402
403 /************************************************************************/
404 /* Open the input file. */
405 /************************************************************************/
406 PlugSetPath(filename, fn, defpath);
407
408 if (!(infile= global_fopen(g, MSGID_CANNOT_OPEN, filename, "rb")))
409 return 0; // Assume file does not exist
410
411 /************************************************************************/
412 /* Get the first 32 bytes of the header. */
413 /************************************************************************/
414 rc = dbfhead(g, infile, filename, &header);
415 fclose(infile);
416
417 if (rc == RC_NF) {
418 Records = 0;
419 return 0;
420 } else if (rc == RC_FX)
421 return -1;
422
423 *rln = (int)header.Reclen();
424 Records = (int)header.Records();
425 return (int)header.Headlen();
426 } // end of ScanHeader
427
428/* ---------------------------- Class DBFFAM ------------------------------ */
429
430/****************************************************************************/
431/* Cardinality: returns table cardinality in number of rows. */
432/* This function can be called with a null argument to test the */
433/* availability of Cardinality implementation (1 yes, 0 no). */
434/****************************************************************************/
435int DBFFAM::Cardinality(PGLOBAL g)
436 {
437 if (!g)
438 return 1;
439
440 if (!Headlen) {
441 int rln = 0; // Record length in the file header
442
443 Headlen = ScanHeader(g, To_File, Lrecl, &rln, Tdbp->GetPath());
444
445 if (Headlen < 0)
446 return -1; // Error in ScanHeader
447
448 if (rln && Lrecl != rln) {
449 // This happens always on some Linux platforms
450 sprintf(g->Message, MSG(BAD_LRECL), Lrecl, rln);
451
452 if (Accept) {
453 Lrecl = rln;
454 Blksize = Nrec * rln;
455 PushWarning(g, Tdbp);
456 } else
457 return -1;
458
459 } // endif rln
460
461 } // endif Headlen
462
463 // Set number of blocks for later use
464 Block = (Records > 0) ? (Records + Nrec - 1) / Nrec : 0;
465 return Records;
466 } // end of Cardinality
467
468#if 0 // Not compatible with ROWID block optimization
469/***********************************************************************/
470/* GetRowID: return the RowID of last read record. */
471/***********************************************************************/
472int DBFFAM::GetRowID(void)
473 {
474 return Rows;
475 } // end of GetRowID
476#endif
477
478/***********************************************************************/
479/* OpenTableFile: Open a DBF table file using C standard I/Os. */
480/* Binary mode cannot be used on Insert because of EOF (CTRL+Z) char. */
481/***********************************************************************/
482bool DBFFAM::OpenTableFile(PGLOBAL g)
483 {
484 char opmode[4], filename[_MAX_PATH];
485//int ftype = Tdbp->GetFtype();
486 MODE mode = Tdbp->GetMode();
487 PDBUSER dbuserp = PlgGetUser(g);
488
489 switch (mode) {
490 case MODE_READ:
491 strcpy(opmode, "rb");
492 break;
493 case MODE_DELETE:
494 if (!Tdbp->GetNext()) {
495 // Store the number of deleted lines
496 DelRows = -1; // Means all lines deleted
497// DelRows = Cardinality(g); no good because of soft deleted lines
498
499 // This will erase the entire file
500 strcpy(opmode, "w");
501 Tdbp->ResetSize();
502 Records = 0;
503 break;
504 } // endif
505
506 // Selective delete
507 /* fall through */
508 case MODE_UPDATE:
509 UseTemp = Tdbp->IsUsingTemp(g);
510 strcpy(opmode, (UseTemp) ? "rb" : "r+b");
511 break;
512 case MODE_INSERT:
513 // Must be in text mode to remove an eventual EOF character
514 strcpy(opmode, "a+");
515 break;
516 default:
517 sprintf(g->Message, MSG(BAD_OPEN_MODE), mode);
518 return true;
519 } // endswitch Mode
520
521 // Now open the file stream
522 PlugSetPath(filename, To_File, Tdbp->GetPath());
523
524 if (!(Stream = PlugOpenFile(g, filename, opmode))) {
525 if (trace(1))
526 htrc("%s\n", g->Message);
527
528 return (mode == MODE_READ && errno == ENOENT)
529 ? PushWarning(g, Tdbp) : true;
530 } // endif Stream
531
532 if (trace(1))
533 htrc("File %s is open in mode %s\n", filename, opmode);
534
535 To_Fb = dbuserp->Openlist; // Keep track of File block
536
537 /*********************************************************************/
538 /* Allocate the line buffer. For mode Delete a bigger buffer has to */
539 /* be allocated because is it also used to move lines into the file.*/
540 /*********************************************************************/
541 return AllocateBuffer(g);
542 } // end of OpenTableFile
543
544/****************************************************************************/
545/* Allocate the block buffer for the table. */
546/****************************************************************************/
547bool DBFFAM::AllocateBuffer(PGLOBAL g)
548 {
549 char c;
550 int rc;
551 MODE mode = Tdbp->GetMode();
552
553 Buflen = Blksize;
554 To_Buf = (char*)PlugSubAlloc(g, NULL, Buflen);
555
556 if (mode == MODE_INSERT) {
557#if defined(__WIN__)
558 /************************************************************************/
559 /* Now we can revert to binary mode in particular because the eventual */
560 /* writing of a new header must be done in binary mode to avoid */
561 /* translating 0A bytes (LF) into 0D0A (CRLF) by Windows in text mode. */
562 /************************************************************************/
563 if (_setmode(_fileno(Stream), _O_BINARY) == -1) {
564 sprintf(g->Message, MSG(BIN_MODE_FAIL), strerror(errno));
565 return true;
566 } // endif setmode
567#endif // __WIN__
568
569 /************************************************************************/
570 /* If this is a new file, the header must be generated. */
571 /************************************************************************/
572 int len = GetFileLength(g);
573
574 if (!len) {
575 // Make the header for this DBF table file
576 struct tm *datm;
577 int hlen, n = 0;
578 ushort reclen = 1;
579 time_t t;
580 DBFHEADER *header;
581 DESCRIPTOR *descp;
582 PCOLDEF cdp;
583 PDOSDEF tdp = (PDOSDEF)Tdbp->GetDef();
584
585 // Count the number of columns
586 for (cdp = tdp->GetCols(); cdp; cdp = cdp->GetNext())
587 if (!(cdp->Flags & U_SPECIAL)) {
588 reclen += cdp->GetLong();
589 n++;
590 } // endif Flags
591
592 if (Lrecl != reclen) {
593 sprintf(g->Message, MSG(BAD_LRECL), Lrecl, reclen);
594
595 if (Accept) {
596 Lrecl = reclen;
597 Blksize = Nrec * Lrecl;
598 PushWarning(g, Tdbp);
599 } else
600 return true;
601
602 } // endif Lrecl
603
604 hlen = HEADLEN * (n + 1) + 2;
605 header = (DBFHEADER*)PlugSubAlloc(g, NULL, hlen);
606 memset(header, 0, hlen);
607 header->Version = DBFTYPE;
608 t = time(NULL) - (time_t)DTVAL::GetShift();
609 datm = gmtime(&t);
610 header->Filedate[0] = datm->tm_year - 100;
611 header->Filedate[1] = datm->tm_mon + 1;
612 header->Filedate[2] = datm->tm_mday;
613 header->SetHeadlen((ushort)hlen);
614 header->SetReclen(reclen);
615 descp = (DESCRIPTOR*)header;
616
617 // Currently only standard Xbase types are supported
618 for (cdp = tdp->GetCols(); cdp; cdp = cdp->GetNext())
619 if (!(cdp->Flags & U_SPECIAL)) {
620 descp++;
621
622 switch ((c = *GetFormatType(cdp->GetType()))) {
623 case 'S': // Short integer
624 case 'L': // Large (big) integer
625 case 'T': // Tiny integer
626 c = 'N'; // Numeric
627 /* fall through */
628 case 'N': // Numeric (integer)
629 case 'F': // Float (double)
630 descp->Decimals = (uchar)cdp->F.Prec;
631 case 'C': // Char
632 case 'D': // Date
633 break;
634 default: // Should never happen
635 sprintf(g->Message, MSG(BAD_DBF_TYPE),
636 c, cdp->GetName());
637 return true;
638 } // endswitch c
639
640 strncpy(descp->Name, cdp->GetName(), 11);
641 descp->Type = c;
642 descp->Length = (uchar)cdp->GetLong();
643 } // endif Flags
644
645 *(char*)(++descp) = EOH;
646
647 // Now write the header
648 if (fwrite(header, 1, hlen, Stream) != (unsigned)hlen) {
649 sprintf(g->Message, MSG(FWRITE_ERROR), strerror(errno));
650 return true;
651 } // endif fwrite
652
653 Records = 0;
654 Headlen = hlen;
655 } else if (len < 0)
656 return true; // Error in GetFileLength
657
658 /************************************************************************/
659 /* For Insert the buffer must be prepared. */
660 /************************************************************************/
661 memset(To_Buf, ' ', Buflen);
662 Rbuf = Nrec; // To be used by WriteDB
663 } else if (UseTemp) {
664 // Allocate a separate buffer so block reading can be kept
665 Dbflen = Nrec;
666 DelBuf = PlugSubAlloc(g, NULL, Blksize);
667 } // endif's
668
669 if (!Headlen) {
670 /************************************************************************/
671 /* Here is a good place to process the DBF file header */
672 /************************************************************************/
673 DBFHEADER header;
674
675 if ((rc = dbfhead(g, Stream, Tdbp->GetFile(g), &header)) == RC_OK) {
676 if (Lrecl != (int)header.Reclen()) {
677 sprintf(g->Message, MSG(BAD_LRECL), Lrecl, header.Reclen());
678
679 if (Accept) {
680 Lrecl = header.Reclen();
681 Blksize = Nrec * Lrecl;
682 PushWarning(g, Tdbp);
683 } else
684 return true;
685
686 } // endif Lrecl
687
688 Records = (int)header.Records();
689 Headlen = (int)header.Headlen();
690 } else if (rc == RC_NF) {
691 Records = 0;
692 Headlen = 0;
693 } else // RC_FX
694 return true; // Error in dbfhead
695
696 } // endif Headlen
697
698 /**************************************************************************/
699 /* Position the file at the begining of the data. */
700 /**************************************************************************/
701 if (Tdbp->GetMode() == MODE_INSERT)
702 rc = fseek(Stream, 0, SEEK_END);
703 else
704 rc = fseek(Stream, Headlen, SEEK_SET);
705
706 if (rc) {
707 sprintf(g->Message, MSG(BAD_DBF_FILE), Tdbp->GetFile(g));
708 return true;
709 } // endif fseek
710
711 return false;
712 } // end of AllocateBuffer
713
714/***********************************************************************/
715/* Reset buffer access according to indexing and to mode. */
716/* >>>>>>>>>>>>>> TO BE RE-VISITED AND CHECKED <<<<<<<<<<<<<<<<<<<<<< */
717/***********************************************************************/
718void DBFFAM::ResetBuffer(PGLOBAL g)
719 {
720 /*********************************************************************/
721 /* If access is random, performances can be much better when the */
722 /* reads are done on only one row, except for small tables that can */
723 /* be entirely read in one block. */
724 /*********************************************************************/
725 if (Tdbp->GetKindex() && ReadBlks != 1) {
726 Nrec = 1; // Better for random access
727 Rbuf = 0;
728 Blksize = Lrecl;
729 OldBlk = -2; // Has no meaning anymore
730 Block = Tdbp->Cardinality(g); // Blocks are one line now
731 } // endif Mode
732
733 } // end of ResetBuffer
734
735/***********************************************************************/
736/* ReadBuffer: Read one line for a DBF file. */
737/***********************************************************************/
738int DBFFAM::ReadBuffer(PGLOBAL g)
739 {
740 if (!Placed && !Closing && GetRowID() == Records)
741 return RC_EF;
742
743 int rc = FIXFAM::ReadBuffer(g);
744
745 if (rc != RC_OK || Closing)
746 return rc;
747
748 switch (*Tdbp->GetLine()) {
749 case '*':
750 if (!ReadMode)
751 rc = RC_NF; // Deleted line
752 else
753 Rows++;
754
755 break;
756 case ' ':
757 if (ReadMode < 2)
758 Rows++; // Non deleted line
759 else
760 rc = RC_NF;
761
762 break;
763 default:
764 if (++Nerr >= Maxerr && !Accept) {
765 sprintf(g->Message, MSG(BAD_DBF_REC), Tdbp->GetFile(g), GetRowID());
766 rc = RC_FX;
767 } else
768 rc = (Accept) ? RC_OK : RC_NF;
769
770 } // endswitch To_Buf
771
772 return rc;
773 } // end of ReadBuffer
774
775/***********************************************************************/
776/* Copy the header into the temporary file. */
777/***********************************************************************/
778bool DBFFAM::CopyHeader(PGLOBAL g)
779 {
780 bool rc = true;
781
782 if (Headlen) {
783 void *hdr = PlugSubAlloc(g, NULL, Headlen);
784 size_t n, hlen = (size_t)Headlen;
785 int pos = ftell(Stream);
786
787 if (fseek(Stream, 0, SEEK_SET))
788 strcpy(g->Message, "Seek error in CopyHeader");
789 else if ((n = fread(hdr, 1, hlen, Stream)) != hlen)
790 sprintf(g->Message, MSG(BAD_READ_NUMBER), (int) n, To_File);
791 else if ((n = fwrite(hdr, 1, hlen, T_Stream)) != hlen)
792 sprintf(g->Message, MSG(WRITE_STRERROR), To_Fbt->Fname
793 , strerror(errno));
794 else if (fseek(Stream, pos, SEEK_SET))
795 strcpy(g->Message, "Seek error in CopyHeader");
796 else
797 rc = false;
798
799 } else
800 rc = false;
801
802 return rc;
803 } // end of CopyHeader
804
805#if 0 // Not useful when UseTemp is false.
806/***********************************************************************/
807/* Mark the line to delete with '*' (soft delete). */
808/* NOTE: this is not ready for UseTemp. */
809/***********************************************************************/
810int DBFFAM::InitDelete(PGLOBAL g, int fpos, int spos)
811 {
812 int rc = RC_FX;
813 size_t lrecl = (size_t)Lrecl;
814
815 if (Nrec != 1)
816 strcpy(g->Message, "Cannot delete in block mode");
817 else if (fseek(Stream, Headlen + fpos * Lrecl, SEEK_SET))
818 sprintf(g->Message, MSG(FSETPOS_ERROR), 0);
819 else if (fread(To_Buf, 1, lrecl, Stream) != lrecl)
820 sprintf(g->Message, MSG(READ_ERROR), To_File, strerror(errno));
821 else
822 *To_Buf = '*';
823
824 if (fseek(Stream, Headlen + fpos * Lrecl, SEEK_SET))
825 sprintf(g->Message, MSG(FSETPOS_ERROR), 0);
826 else if (fwrite(To_Buf, 1, lrecl, Stream) != lrecl)
827 sprintf(g->Message, MSG(FWRITE_ERROR), strerror(errno));
828 else
829 rc = RC_NF; // Ok, Nothing else to do
830
831 return rc;
832 } // end of InitDelete
833#endif // 0
834
835/***********************************************************************/
836/* Data Base delete line routine for DBF access methods. */
837/* Deleted lines are just flagged in the first buffer character. */
838/***********************************************************************/
839int DBFFAM::DeleteRecords(PGLOBAL g, int irc)
840 {
841 if (irc == RC_OK) {
842 // T_Stream is the temporary stream or the table file stream itself
843 if (!T_Stream)
844 if (UseTemp) {
845 if (OpenTempFile(g))
846 return RC_FX;
847
848 if (CopyHeader(g)) // For DBF tables
849 return RC_FX;
850
851 } else
852 T_Stream = Stream;
853
854 *Tdbp->GetLine() = '*';
855 Modif++; // Modified line in Delete mode
856 } // endif irc
857
858 return RC_OK;
859 } // end of DeleteRecords
860
861/***********************************************************************/
862/* Rewind routine for DBF access method. */
863/***********************************************************************/
864void DBFFAM::Rewind(void)
865 {
866 BLKFAM::Rewind();
867 Nerr = 0;
868 } // end of Rewind
869
870/***********************************************************************/
871/* Table file close routine for DBF access method. */
872/***********************************************************************/
873void DBFFAM::CloseTableFile(PGLOBAL g, bool abort)
874 {
875 int rc = RC_OK, wrc = RC_OK;
876 MODE mode = Tdbp->GetMode();
877
878 Abort = abort;
879
880 // Closing is True if last Write was in error
881 if (mode == MODE_INSERT && CurNum && !Closing) {
882 // Some more inserted lines remain to be written
883 Rbuf = CurNum--;
884// Closing = true;
885 wrc = WriteBuffer(g);
886 } else if (mode == MODE_UPDATE || mode == MODE_DELETE) {
887 if (Modif && !Closing) {
888 // Last updated block remains to be written
889 Closing = true;
890 wrc = WriteModifiedBlock(g);
891 } // endif Modif
892
893 if (UseTemp && T_Stream && wrc == RC_OK) {
894 if (!Abort) {
895 // Copy any remaining lines
896 bool b;
897
898 Fpos = Tdbp->Cardinality(g);
899 Abort = MoveIntermediateLines(g, &b) != RC_OK;
900 } // endif Abort
901
902 // Delete the old file and rename the new temp file.
903 RenameTempFile(g);
904 goto fin;
905 } // endif UseTemp
906
907 } // endif's mode
908
909 if (Tdbp->GetMode() == MODE_INSERT) {
910 int n = ftell(Stream) - Headlen;
911
912 rc = PlugCloseFile(g, To_Fb);
913
914 if (n >= 0 && !(n % Lrecl)) {
915 n /= Lrecl; // New number of lines
916
917 if (n > Records) {
918 // Update the number of rows in the file header
919 char filename[_MAX_PATH];
920
921 PlugSetPath(filename, To_File, Tdbp->GetPath());
922 if ((Stream= global_fopen(g, MSGID_OPEN_MODE_STRERROR, filename, "r+b")))
923 {
924 char nRecords[4];
925 int4store(nRecords, n);
926
927 fseek(Stream, 4, SEEK_SET); // Get header.Records position
928 fwrite(nRecords, sizeof(nRecords), 1, Stream);
929 fclose(Stream);
930 Stream= NULL;
931 Records= n; // Update Records value
932 }
933 } // endif n
934
935 } // endif n
936
937 } else // Finally close the file
938 rc = PlugCloseFile(g, To_Fb);
939
940 fin:
941 if (trace(1))
942 htrc("DBF CloseTableFile: closing %s mode=%d wrc=%d rc=%d\n",
943 To_File, mode, wrc, rc);
944
945 Stream = NULL; // So we can know whether table is open
946 } // end of CloseTableFile
947
948/* ---------------------------- Class DBMFAM ------------------------------ */
949
950/****************************************************************************/
951/* Cardinality: returns table cardinality in number of rows. */
952/* This function can be called with a null argument to test the */
953/* availability of Cardinality implementation (1 yes, 0 no). */
954/****************************************************************************/
955int DBMFAM::Cardinality(PGLOBAL g)
956 {
957 if (!g)
958 return 1;
959
960 if (!Headlen) {
961 int rln = 0; // Record length in the file header
962
963 Headlen = ScanHeader(g, To_File, Lrecl, &rln, Tdbp->GetPath());
964
965 if (Headlen < 0)
966 return -1; // Error in ScanHeader
967
968 if (rln && Lrecl != rln) {
969 // This happens always on some Linux platforms
970 sprintf(g->Message, MSG(BAD_LRECL), Lrecl, rln);
971
972 if (Accept) {
973 Lrecl = rln;
974 Blksize = Nrec * Lrecl;
975 PushWarning(g, Tdbp);
976 } else
977 return -1;
978
979 } // endif rln
980
981 } // endif Headlen
982
983 // Set number of blocks for later use
984 Block = (Records > 0) ? (Records + Nrec - 1) / Nrec : 0;
985 return Records;
986 } // end of Cardinality
987
988#if 0 // Not compatible with ROWID block optimization
989/***********************************************************************/
990/* GetRowID: return the RowID of last read record. */
991/***********************************************************************/
992int DBMFAM::GetRowID(void)
993 {
994 return Rows;
995 } // end of GetRowID
996#endif
997
998/***********************************************************************/
999/* Just check that on all deletion the unknown deleted line number is */
1000/* sent back because Cardinality doesn't count soft deleted lines. */
1001/***********************************************************************/
1002int DBMFAM::GetDelRows(void)
1003 {
1004 if (Tdbp->GetMode() == MODE_DELETE && !Tdbp->GetNext())
1005 return -1; // Means all lines deleted
1006 else
1007 return DelRows;
1008
1009 } // end of GetDelRows
1010
1011/****************************************************************************/
1012/* Allocate the block buffer for the table. */
1013/****************************************************************************/
1014bool DBMFAM::AllocateBuffer(PGLOBAL g)
1015 {
1016 if (!Headlen) {
1017 /************************************************************************/
1018 /* Here is a good place to process the DBF file header */
1019 /************************************************************************/
1020 DBFHEADER *hp = (DBFHEADER*)Memory;
1021
1022 if (Lrecl != (int)hp->Reclen()) {
1023 sprintf(g->Message, MSG(BAD_LRECL), Lrecl, hp->Reclen());
1024
1025 if (Accept) {
1026 Lrecl = hp->Reclen();
1027 Blksize = Nrec * Lrecl;
1028 PushWarning(g, Tdbp);
1029 } else
1030 return true;
1031
1032 } // endif Lrecl
1033
1034 Records = (int)hp->Records();
1035 Headlen = (int)hp->Headlen();
1036 } // endif Headlen
1037
1038 /**************************************************************************/
1039 /* Position the file at the begining of the data. */
1040 /**************************************************************************/
1041 Fpos = Mempos = Memory + Headlen;
1042 Top--; // Because of EOF marker
1043 return false;
1044 } // end of AllocateBuffer
1045
1046/****************************************************************************/
1047/* ReadBuffer: Read one line for a FIX file. */
1048/****************************************************************************/
1049int DBMFAM::ReadBuffer(PGLOBAL g)
1050 {
1051// if (!Placed && GetRowID() == Records)
1052// return RC_EF;
1053
1054 int rc = MPXFAM::ReadBuffer(g);
1055
1056 if (rc != RC_OK)
1057 return rc;
1058
1059 switch (*Fpos) {
1060 case '*':
1061 if (!ReadMode)
1062 rc = RC_NF; // Deleted line
1063 else
1064 Rows++;
1065
1066 break;
1067 case ' ':
1068 if (ReadMode < 2)
1069 Rows++; // Non deleted line
1070 else
1071 rc = RC_NF;
1072
1073 break;
1074 default:
1075 if (++Nerr >= Maxerr && !Accept) {
1076 sprintf(g->Message, MSG(BAD_DBF_REC), Tdbp->GetFile(g), GetRowID());
1077 rc = RC_FX;
1078 } else
1079 rc = (Accept) ? RC_OK : RC_NF;
1080 } // endswitch To_Buf
1081
1082 return rc;
1083 } // end of ReadBuffer
1084
1085/****************************************************************************/
1086/* Data Base delete line routine for DBF access methods. */
1087/* Deleted lines are just flagged in the first buffer character. */
1088/****************************************************************************/
1089int DBMFAM::DeleteRecords(PGLOBAL g, int irc)
1090 {
1091 if (irc == RC_OK)
1092 *Fpos = '*';
1093
1094 return RC_OK;
1095 } // end of DeleteRecords
1096
1097/***********************************************************************/
1098/* Rewind routine for DBF access method. */
1099/***********************************************************************/
1100void DBMFAM::Rewind(void)
1101 {
1102 MBKFAM::Rewind();
1103 Nerr = 0;
1104 } // end of Rewind
1105
1106/* --------------------------------- EOF ---------------------------------- */
1107