1 unit dbf_dbffile;
2 
3 interface
4 
5 {$I dbf_common.inc}
6 
7 uses
8   Classes, SysUtils,
9 {$ifdef WINDOWS}
10   Windows,
11 {$else}
12 {$ifdef KYLIX}
13   Libc,
14 {$endif}
15   Types, dbf_wtil,
16 {$endif}
17   Db,
18   dbf_common,
19   dbf_cursor,
20   dbf_pgfile,
21   dbf_fields,
22   dbf_memo,
23   dbf_idxfile;
24 
25 //====================================================================
26 //=== Dbf support (first part)
27 //====================================================================
28 //  TxBaseVersion = (xUnknown,xClipper,xBaseIII,xBaseIV,xBaseV,xFoxPro,xVisualFoxPro);
29 //  TPagedFileMode = (pfOpen,pfCreate);
30 //  TDbfGetMode = (xFirst,xPrev,xCurrent, xNext, xLast);
31 //  TDbfGetResult = (xOK, xBOF, xEOF, xError);
32 
33 type
34 
35 //====================================================================
36   TDbfIndexMissingEvent = procedure(var DeleteLink: Boolean) of object;
37   TUpdateNullField = (unfClear, unfSet);
38   TNullFieldFlag = (nfNullFlag, nfVarlengthFlag); //the field that the nullflags bit applies to
39 
40 //====================================================================
41   TDbfGlobals = class;
42 //====================================================================
43 
44   { TDbfFile }
45 
46   TDbfFile = class(TPagedFile)
47   protected
48     FBackLink: string;
49     FBackLinkOffset: integer; //position of VFP backlink within header
50     FMdxFile: TIndexFile;
51     FMemoFile: TMemoFile;
52     FMemoStream: TStream;
53     FFieldDefs: TDbfFieldDefs;
54     FIndexNames: TStringList;
55     FIndexFiles: TList;
56     FIndexStream: TStream;
57     FDbfVersion: TXBaseVersion;
58     FPrevBuffer: TRecordBuffer;
59     FDefaultBuffer: TRecordBuffer;
60     FRecordBufferSize: Integer;
61     FLockUserLen: DWORD;
62     FFileCodePage: Cardinal;
63     FUseCodePage: Cardinal;
64     FFileLangId: Byte;
65     FCountUse: Integer;
66     FCurIndex: Integer;
67     FForceClose: Boolean;
68     FLockField: TDbfFieldDef;
69     FNullField: TDbfFieldDef;
70     FAutoIncPresent: Boolean;
71     FCopyDateTimeAsString: Boolean;
72     FDateTimeHandling: TDateTimeHandling;
73     FOnLocaleError: TDbfLocaleErrorEvent;
74     FOnIndexMissing: TDbfIndexMissingEvent;
75     // Yes if table has blob/memo type field(s) (storage in external file)
HasBlobnull76     function  HasBlob: Boolean;
77     // File extension for memo field; uppercase if FFileName is uppercase
78     // (useful for *nix case-sensitive filesystems)
GetMemoExtnull79     function  GetMemoExt: string;
80 
GetLanguageIdnull81     function GetLanguageId: Integer;
GetLanguageStrnull82     function GetLanguageStr: string;
83 
84   protected
85     // Reads the field's properties from the field header(s)
86     procedure ConstructFieldDefs;
87     procedure InitDefaultBuffer;
88     // Shows if the (null or varlength) flag for AFieldDef is set.
89     function IsNullFlagSet(const Src: Pointer; var AFieldDef: TDbfFieldDef; WhichField: TNullFieldFlag): boolean;
90     // Updates _NULLFLAGS field with null or varlength flag for field
91     procedure UpdateNullField(Buffer: Pointer; AFieldDef: TDbfFieldDef; Action: TUpdateNullField; WhichField: TNullFieldFlag);
92     procedure WriteLockInfo(Buffer: TRecordBuffer);
93 
94   public
95     constructor Create;
96     destructor Destroy; override;
97 
98     procedure Open;
99     procedure Close;
100     procedure Zap;
101 
102     // Write out field definitions to header etc.
103     procedure FinishCreate(AFieldDefs: TDbfFieldDefs; MemoSize: Integer);
104     function GetIndexByName(AIndexName: string): TIndexFile;
105     procedure SetRecordSize(NewSize: Integer); override;
106 
107     procedure TryExclusive; override;
108     procedure EndExclusive; override;
109     procedure OpenIndex(IndexName, IndexField: string; CreateIndex: Boolean; Options: TIndexOptions);
110     function  DeleteIndex(const AIndexName: string): Boolean;
111     procedure CloseIndex(AIndexName: string);
112     procedure RepageIndex(AIndexFile: string);
113     procedure CompactIndex(AIndexFile: string);
114 
115     // Inserts new record
116     function  Insert(Buffer: TRecordBuffer): integer;
117     // Write dbf header as well as EOF marker at end of file if necessary
118     procedure WriteHeader; override;
119     // Writes autoinc value to record buffer and updates autoinc value in field header
120     procedure ApplyAutoIncToBuffer(DestBuf: TRecordBuffer);
121     procedure FastPackTable;
122     procedure RestructureTable(DbfFieldDefs: TDbfFieldDefs; Pack: Boolean);
123     procedure Rename(DestFileName: string; NewIndexFileNames: TStrings; DeleteFiles: boolean);
124     function  GetFieldInfo(FieldName: string): TDbfFieldDef;
125     // Copies record buffer to field buffer
126     // Returns true if not null & data succesfully copied; false if field is null
127     function  GetFieldData(Column: Integer; DataType: TFieldType; Src,Dst: Pointer;
128       NativeFormat: boolean): Boolean;
129     // Copies record buffer to field buffer
130     // Returns true if not null & data succesfully copied; false if field is null
131     function  GetFieldDataFromDef(AFieldDef: TDbfFieldDef; DataType: TFieldType;
132       Src, Dst: Pointer; NativeFormat: boolean): Boolean;
133     // Copies field buffer to record buffer for this field
134     procedure SetFieldData(Column: Integer; DataType: TFieldType; Src,Dst: Pointer; NativeFormat: boolean);
135     // Fill DestBuf with default field data
136     procedure InitRecord(DestBuf: TRecordBuffer);
137     procedure PackIndex(lIndexFile: TIndexFile; AIndexName: string);
138     procedure RegenerateIndexes;
139     procedure LockRecord(RecNo: Integer; Buffer: TRecordBuffer);
140     procedure UnlockRecord(RecNo: Integer; Buffer: TRecordBuffer);
141     procedure RecordDeleted(RecNo: Integer; Buffer: TRecordBuffer);
142     procedure RecordRecalled(RecNo: Integer; Buffer: TRecordBuffer);
143 
144     property MemoFile: TMemoFile read FMemoFile;
145     // Backing stream for stream/memory-based memo "files"
146     property MemoStream: TStream read FMemoStream write FMemoStream;
147     property FieldDefs: TDbfFieldDefs read FFieldDefs;
148     property IndexNames: TStringList read FIndexNames;
149     property IndexFiles: TList read FIndexFiles;
150     // Backing stream for stream/memory-based index "files"
151     property IndexStream: TStream read FIndexStream write FIndexStream;
152     property MdxFile: TIndexFile read FMdxFile;
153     property LanguageId: Integer read GetLanguageId;
154     property LanguageStr: string read GetLanguageStr;
155     property FileCodePage: Cardinal read FFileCodePage;
156     property UseCodePage: Cardinal read FUseCodePage write FUseCodePage;
157     property FileLangId: Byte read FFileLangId write FFileLangId;
158     // Visual Foxpro: relative path to .dbc database file containing
159     // long field names and other metadata
160     // Empty if this is a "free table", not linked to a .dbc file
161     property BackLink: string read FBackLink write FBackLink;
162     // Dbase (clone) version that this format emulates. Related to tablelevel.
163     property DbfVersion: TXBaseVersion read FDbfVersion write FDbfVersion;
164     property PrevBuffer: TRecordBuffer read FPrevBuffer;
165     property ForceClose: Boolean read FForceClose;
166     property CopyDateTimeAsString: Boolean read FCopyDateTimeAsString write FCopyDateTimeAsString;
167     property DateTimeHandling: TDateTimeHandling read FDateTimeHandling write FDateTimeHandling;
168 
169     property OnIndexMissing: TDbfIndexMissingEvent read FOnIndexMissing write FOnIndexMissing;
170     property OnLocaleError: TDbfLocaleErrorEvent read FOnLocaleError write FOnLocaleError;
171   end;
172 
173 //====================================================================
174   TDbfCursor = class(TVirtualCursor)
175   protected
176     FPhysicalRecNo: Integer;
177   public
178     constructor Create(DbfFile: TDbfFile);
179     function Next: Boolean; override;
180     function Prev: Boolean; override;
181     procedure First; override;
182     procedure Last; override;
183 
184     function GetPhysicalRecNo: Integer; override;
185     procedure SetPhysicalRecNo(RecNo: Integer); override;
186 
187     function GetSequentialRecordCount: Integer; override;
188     function GetSequentialRecNo: Integer; override;
189     procedure SetSequentialRecNo(RecNo: Integer); override;
190   end;
191 
192 //====================================================================
193 
194   { TDbfGlobals }
195 
196   TDbfGlobals = class
197   protected
198     FCodePages: TList;
199     FCurrencyAsBCD: Boolean;
200     FDefaultOpenCodePage: Integer;
201     FDefaultCreateLangId: Byte;
202     FUserName: string;
203     FUserNameLen: DWORD;
204 
205     // Translates FDefaultCreateLangId back to codepage
206     function  GetDefaultCreateCodePage: Integer;
207     // Takes codepage and sets FDefaultCreateLangId
208     procedure SetDefaultCreateCodePage(NewCodePage: Integer);
209     procedure InitUserName;
210   public
211     constructor Create;
212     destructor Destroy; override;
213 
214     function CodePageInstalled(ACodePage: Integer): Boolean;
215 
216     property CurrencyAsBCD: Boolean read FCurrencyAsBCD write FCurrencyAsBCD;
217     property DefaultOpenCodePage: Integer read FDefaultOpenCodePage write FDefaultOpenCodePage;
218     property DefaultCreateCodePage: Integer read GetDefaultCreateCodePage write SetDefaultCreateCodePage;
219     property DefaultCreateLangId: Byte read FDefaultCreateLangId write FDefaultCreateLangId;
220     property UserName: string read FUserName;
221     property UserNameLen: DWORD read FUserNameLen;
222   end;
223 
224 var
225   DbfGlobals: TDbfGlobals;
226 
227 implementation
228 
229 uses
230 {$ifndef WINDOWS}
231  {$IFNDEF OS2}
232   {$ifndef FPC}
233   RTLConsts,
234   {$else FPC}
235   BaseUnix,
236   {$endif FPC}
237  {$ENDIF OS2}
238 {$endif WINDOWS}
239 {$ifdef SUPPORT_MATH_UNIT}
240   Math,
241 {$endif}
242   dbf_str, dbf_lang, dbf_prssupp, dbf_prsdef;
243 
244 const
245   sDBF_DEC_SEP = '.';
246   FIELD_DESCRIPTOR_ARRAY_TERMINATOR = $0D; // Marker at end of list of fields within header
247   NULLFLAGSFIELD = '_NULLFLAGS'; //Visual Foxpro system field with flags for field=null and field has varlength byte
248 
249 {$I dbf_struct.inc}
250 
251 //====================================================================
252 // International separator
253 // thanks to Bruno Depero from Italy
254 // and Andreas W�llenstein from Denmark
255 //====================================================================
256 function DbfStrToFloat(const Src: PChar; const Size: Integer): Extended;
257 var
258   iPos: PChar;
259   eValue: extended;
260   endChar: Char;
261 begin
262   // temp null-term string
263   endChar := (Src + Size)^;
264   (Src + Size)^ := #0;
265   // we only have to convert if decimal separator different
266   if DecimalSeparator <> sDBF_DEC_SEP then
267   begin
268     // search dec sep
269     iPos := StrScan(Src, sDBF_DEC_SEP);
270     // replace
271     if iPos <> nil then
272       iPos^ := DecimalSeparator;
273   end else
274     iPos := nil;
275   // convert to double
276   if TextToFloat(Src, eValue {$ifndef VER1_0}, fvExtended{$endif}) then
277     Result := eValue
278   else
279     Result := 0;
280   // restore dec sep
281   if iPos <> nil then
282     iPos^ := sDBF_DEC_SEP;
283   // restore Char of null-term
284   (Src + Size)^ := endChar;
285 end;
286 
287 procedure FloatToDbfStr(const Val: Extended; const Size, Precision: Integer; const Dest: PChar);
288 var
289   Buffer: array [0..24] of Char;
290   resLen: Integer;
291   iPos: PChar;
292 begin
293   // convert to temporary buffer
294   resLen := FloatToText(@Buffer[0], Val, {$ifndef FPC_VERSION}fvExtended,{$endif} ffFixed, Size, Precision);
295   // prevent overflow in destination buffer
296   if resLen > Size then
297     resLen := Size;
298   // null-terminate buffer
299   Buffer[resLen] := #0;
300   // we only have to convert if decimal separator different
301   if DecimalSeparator <> sDBF_DEC_SEP then
302   begin
303     iPos := StrScan(@Buffer[0], DecimalSeparator);
304     if iPos <> nil then
305       iPos^ := sDBF_DEC_SEP;
306   end;
307   // fill destination with spaces
308   FillChar(Dest^, Size, ' ');
309   // now copy right-aligned to destination
310   Move(Buffer[0], Dest[Size-resLen], resLen);
311 end;
312 
313 function GetIntFromStrLength(Src: Pointer; Size: Integer; Default: Integer): Integer;
314 var
315   endChar: Char;
316   Code: Integer;
317 begin
318   // save Char at pos term. null
319   endChar := (PChar(Src) + Size)^;
320   (PChar(Src) + Size)^ := #0;
321   // convert
322   Val(PChar(Src), Result, Code);
323   // check success
324   if Code <> 0 then
325     Result := Default;
326   // restore prev. ending Char
327   (PChar(Src) + Size)^ := endChar;
328 end;
329 
330 //====================================================================
331 // TDbfFile
332 //====================================================================
333 constructor TDbfFile.Create;
334 begin
335   // init variables first
336   FBackLink := '';
337   FBackLinkOffset := 0;
338   FFieldDefs := TDbfFieldDefs.Create(nil);
339   FIndexNames := TStringList.Create;
340   FIndexFiles := TList.Create;
341 
342   // now initialize inherited
343   inherited;
344 end;
345 
346 destructor TDbfFile.Destroy;
347 var
348   I: Integer;
349 begin
350   // close file
351   Close;
352 
353   // free files
354   for I := 0 to Pred(FIndexFiles.Count) do
355     TPagedFile(FIndexFiles.Items[I]).Free;
356 
357   // free lists
358   FreeAndNil(FIndexFiles);
359   FreeAndNil(FIndexNames);
360   FreeAndNil(FFieldDefs);
361 
362   // call ancestor
363   inherited;
364 end;
365 
366 procedure TDbfFile.Open;
367 var
368   lMemoFileName: string;
369   lMdxFileName: string;
370   MemoFileClass: TMemoFileClass;
371   I: Integer;
372   deleteLink: Boolean;
373   lModified: boolean;
374 
375   procedure GetVersion;
376   var
377     version: byte;
378   begin
379     // OH 2000-11-15 dBase7 support. I built dBase Tables with different
380     // BDE dBase Level (1. without Memo, 2. with Memo)
381     //                          Header Byte ($1d hex) (29 dec) -> Language driver ID.
382     //  $03,$83 xBaseIII        Header Byte $1d=$00, Float -> N($13.$04) DateTime C($1E)
383     //  $03,$8B xBaseIV/V       Header Byte $1d=$58, Float -> N($14.$04)
384     //  $04,$8C xBaseVII        Header Byte $1d=$00  Float -> O($08)     DateTime @($08)
385     //  $03,$F5 FoxPro Level 25 Header Byte $1d=$03, Float -> N($14.$04)
386     // Access 97
387     //  $03,$83 dBaseIII        Header Byte $1d=$00, Float -> N($13.$05) DateTime D($08)
388     //  $03,$8B dBaseIV/V       Header Byte $1d=$00, Float -> N($14.$05) DateTime D($08)
389     //  $03,$F5 FoxPro Level 25 Header Byte $1d=$09, Float -> N($14.$05) DateTime D($08)
390 
391     version := PDbfHdr(Header)^.VerDBF;
392     FDbfVersion := xUnknown;
393     // Some hardcoded versions for Visual FoxPro; see MS documentation
394     // (including the correction at the bottom):
395     // http://msdn.microsoft.com/en-US/library/st4a0s68%28v=vs.80%29.aspx
396     case version of
397       $30, $31, $32 {VFP9 with new data types}: FDbfVersion:=xVisualFoxPro;
398       $F5, $FB: FDbfVersion:=xFoxPro;
399     end;
400     if FDbfVersion = xUnknown then
401       case (version and $07) of
402         $03: //dbf with/without memo. Could be Foxpro, too
403           if not(version in [$03,$8B]) {e.g. dbase IV < v2.0 with 0 language ID} and
404             (LanguageID = 0) then
405             FDbfVersion := xBaseIII
406           else
407             FDbfVersion := xBaseIV;
408         $04:
409           FDbfVersion := xBaseVII;
410         $02 {FoxBase, not readable by current MS Visual FoxPro driver}, $05:
411           FDbfVersion := xFoxPro;
412       else
413         begin
414           // not a valid DBF file
415           raise EDbfError.Create(STRING_INVALID_DBF_FILE);
416         end;
417       end;
418     FFieldDefs.DbfVersion := FDbfVersion;
419   end;
420 
421   procedure GetCodePage;
422   var
423     LangStr: PChar;
424   begin
425     // determine codepage
426     case FDbfVersion of
427       xBaseVII:
428       begin
429         // cache language str
430         LangStr := @PEndFixedHdrVII(PChar(Header) + SizeOf(rDbfHdr))^.LanguageDriverName;
431         // VdBase 7 Language strings
432         //  'DBWIN...' -> Charset 1252 (ansi)
433         //  'DB999...' -> Code page 999, 9 any digit
434         //  'DBHEBREW' -> Code page 1255 ??
435         //  'FOX..999' -> Code page 999, 9 any digit
436         //  'FOX..WIN' -> Charset 1252 (ansi)
437         if (LangStr[0] = 'D') and (LangStr[1] = 'B') then
438         begin
439           if StrLComp(LangStr+2, 'WIN', 3) = 0 then
440             FFileCodePage := 1252
441           else
442           if StrLComp(LangStr+2, 'HEBREW', 6) = 0 then
443           begin
444             FFileCodePage := 1255;
445           end else begin
446             FFileCodePage := GetIntFromStrLength(LangStr+2, 3, 0);
447             if (Ord(LangStr[5]) >= Ord('0')) and (Ord(LangStr[5]) <= Ord('9')) then
448               FFileCodePage := FFileCodePage * 10 + Ord(LangStr[5]) - Ord('0');
449           end;
450         end else
451         if StrLComp(LangStr, 'FOX', 3) = 0 then
452         begin
453           if StrLComp(LangStr+5, 'WIN', 3) = 0 then
454             FFileCodePage := 1252
455           else
456             FFileCodePage := GetIntFromStrLength(LangStr+5, 3, 0)
457         end else begin
458           FFileCodePage := 0;
459         end;
460         FFileLangId := GetLangId_From_LangName(LanguageStr);
461       end;
462     else
463       begin
464         // DBase II..V, FoxPro, Visual FoxPro
465         FFileLangId := PDbfHdr(Header)^.Language;
466         FFileCodePage := LangId_To_CodePage[FFileLangId];
467       end;
468     end;
469     // determine used codepage, if no codepage, then use default codepage
470     FUseCodePage := FFileCodePage;
471     if FUseCodePage = 0 then
472       FUseCodePage := DbfGlobals.DefaultOpenCodePage;
473   end;
474 
475   procedure GetBackLink;
476   // Gets backlink info - only supported in Visual Foxpro
477   begin
478     FBackLink:='';
479     if FDBFVersion=xVisualFoxPro then //only format that supports it
480     begin
481       FBackLink:= StrPas(@PEndHdrVFP(PChar(Header) + FBackLinkOffset)^.Backlink);
482     end;
483   end;
484 
485 begin
486   // check if not already opened
487   if not Active then
488   begin
489     // open requested file
490     OpenFile;
491 
492     // check if we opened an already existing file
493     lModified := false;
494     if not FileCreated then
495     begin
496       HeaderSize := sizeof(rDbfHdr); // temporary, required for getting version
497       GetVersion;
498 
499       RecordSize := PDbfHdr(Header)^.RecordSize;
500       HeaderSize := PDbfHdr(Header)^.FullHdrSize;
501       if (HeaderSize = 0) or (RecordSize = 0) then
502       begin
503         HeaderSize := 0;
504         RecordSize := 0;
505         RecordCount := 0;
506         FForceClose := true;
507         exit;
508       end;
509 
510       // check if specified recordcount is right; correct if not
511       if PDbfHdr(Header)^.RecordCount <> RecordCount then
512       begin
513         PDbfHdr(Header)^.RecordCount := RecordCount;
514         lModified := true;
515       end;
516 
517       GetCodePage;
518       // get list of fields
519       ConstructFieldDefs;
520       GetBackLink;
521 
522       // open blob file if present
523       lMemoFileName := ChangeFileExt(FileName, GetMemoExt);
524       if HasBlob then
525       begin
526         // open blob file; if it doesn't exist yet create it
527         // using AutoCreate as long as we're not running read-only
528         // If needed, fake a memo file:
529         if (Mode=pfReadOnly) and (not FileExists(lMemoFileName)) then
530           MemoFileClass := TNullMemoFile
531         else if (FDbfVersion in [xFoxPro,xVisualFoxPro]) then
532           MemoFileClass := TFoxProMemoFile
533         else
534           MemoFileClass := TDbaseMemoFile; //fallback/default
535         FMemoFile := MemoFileClass.Create(Self);
536         FMemoFile.FileName := lMemoFileName;
537         if (Mode in [pfMemoryOpen,pfMemoryCreate]) then
538           FMemoFile.Stream:=FMemoStream;
539         FMemoFile.Mode := Mode;
540         FMemoFile.AutoCreate := true;
541         FMemoFile.MemoRecordSize := 0;
542         FMemoFile.DbfVersion := FDbfVersion;
543         FMemoFile.Open;
544         // set header blob flag corresponding to field list
545         if not(FDbfVersion in [xFoxPro,xVisualFoxPro]) then
546         begin
547           PDbfHdr(Header)^.VerDBF := PDbfHdr(Header)^.VerDBF or $80;
548           lModified := true;
549         end;
550       end else // no HasBlob
551         if not(FDbfVersion in [xFoxPro,xVisualFoxPro]) then
552         begin
553           PDbfHdr(Header)^.VerDBF := PDbfHdr(Header)^.VerDBF and $7F;
554           lModified := true;
555         end;
556 
557       // check if mdx flagged
558       if not(FDbfVersion in [xFoxPro,xVisualFoxPro]) and (PDbfHdr(Header)^.MDXFlag <> 0) then
559       begin
560         // open mdx file if present
561         lMdxFileName := ChangeFileExt(FileName, '.mdx');
562         // Deal with case-sensitive filesystems:
563         if (FileName<>'') and (UpperCase(FileName)=FileName) then
564           lMdxFileName := UpperCase(lMdxFileName);
565         if FileExists(lMdxFileName) or ((Mode in [pfMemoryOpen,pfMemoryCreate])) then
566         begin
567           // open file
568           FMdxFile := TIndexFile.Create(Self);
569           FMdxFile.FileName := lMdxFileName;
570           FMdxFile.Mode := Mode;
571           if (Mode in [pfMemoryOpen,pfMemoryCreate]) then
572           begin
573             FMdxFile.Stream := FIndexStream;
574             FMdxFile.AutoCreate := true;
575           end
576           else
577           begin
578             FMdxFile.AutoCreate := false;
579           end;
580           FMdxFile.OnLocaleError := FOnLocaleError;
581           FMdxFile.CodePage := UseCodePage;
582           FMdxFile.Open;
583           // is index ready for use?
584           if not FMdxFile.ForceClose then
585           begin
586             FIndexFiles.Add(FMdxFile);
587             // get index tag names known
588             FMdxFile.GetIndexNames(FIndexNames);
589           end else begin
590             // asked to close! close file
591             FreeAndNil(FMdxFile);
592           end;
593         end else begin
594           // ask user
595           deleteLink := true;
596           if Assigned(FOnIndexMissing) then
597             FOnIndexMissing(deleteLink);
598           // correct flag
599           if deleteLink then
600           begin
601             PDbfHdr(Header)^.MDXFlag := 0;
602             lModified := true;
603           end else
604             FForceClose := true;
605         end;
606       end;
607     end;
608 
609     // record changes
610     if lModified then
611       WriteHeader;
612 
613     // open indexes
614     for I := 0 to FIndexFiles.Count - 1 do
615       TIndexFile(FIndexFiles.Items[I]).Open;
616   end;
617 end;
618 
619 procedure TDbfFile.Close;
620 var
621   MdxIndex, I: Integer;
622 begin
623   if Active then
624   begin
625     // close index files first
626     MdxIndex := -1;
627     for I := 0 to FIndexFiles.Count - 1 do
628     begin
629       TIndexFile(FIndexFiles.Items[I]).Close;
630       if TIndexFile(FIndexFiles.Items[I]) = FMdxFile then
631         MdxIndex := I;
632     end;
633     // free memo file if any
634     FreeAndNil(FMemoFile);
635 
636     // now we can close physical dbf file
637     CloseFile;
638 
639     // free FMdxFile, remove it from the FIndexFiles and Names lists
640     if MdxIndex >= 0 then
641       FIndexFiles.Delete(MdxIndex);
642     I := 0;
643     while I < FIndexNames.Count do
644     begin
645       if FIndexNames.Objects[I] = FMdxFile then
646       begin
647         FIndexNames.Delete(I);
648       end else begin
649         Inc(I);
650       end;
651     end;
652     FreeAndNil(FMdxFile);
653     FreeMemAndNil(Pointer(FPrevBuffer));
654     FreeMemAndNil(Pointer(FDefaultBuffer));
655 
656     // reset variables
657     FFileLangId := 0;
658   end;
659 end;
660 
661 procedure TDbfFile.FinishCreate(AFieldDefs: TDbfFieldDefs; MemoSize: Integer);
662 var
663   lEndHdrVFP:  rEndHdrVFP; //Contains Visual FoxPro backlink
664   lFieldDescIII: rFieldDescIII;
665   lFieldDescVII: rFieldDescVII;
666   lFieldDescPtr: Pointer;
667   lFieldDef: TDbfFieldDef;
668   lMemoFileName: string;
669   I, lFieldOffset, lSize, lPrec: Integer;
670   lHasBlob: Boolean;
671   lLocaleID: LCID;
672   lNullVarFlagCount: integer; //(VFP only) Keeps track of number null/varlength flags needed for _NULLFLAGS size calculation
673 begin
674   try
675     // first reset file
676     RecordCount := 0;
677     lHasBlob := false;
678     lNullVarFlagCount := 0;
679     // determine codepage & locale
680     if FDbfVersion in [xFoxPro, xVisualFoxPro] then
681     begin
682       // Don't use DbfGlobals default language ID as it is dbase-based
683       if FFileLangId = 0 then
684         FFileLangId := ConstructLangId(LangId_To_CodePage[FFileLangId],GetUserDefaultLCID, true);
685     end
686     else
687     begin
688       // DBase
689       if FFileLangId = 0 then
690         FFileLangId := DbfGlobals.DefaultCreateLangId;
691     end;
692     FFileCodePage := LangId_To_CodePage[FFileLangId];
693     lLocaleID := LangId_To_Locale[FFileLangId];
694     FUseCodePage := FFileCodePage;
695 
696     // Prepare header size. This size may be changed later depending on number
697     // of fields etc - we start out with the first, fixed part of the header,
698     // write out the variable parts (field descriptor arrays etc) and then
699     // correct the header length in the header.
700     if FDbfVersion = xBaseVII then
701     begin
702       // version xBaseVII without memo
703       HeaderSize := SizeOf(rDbfHdr) + SizeOf(rEndFixedHdrVII);
704       RecordSize := SizeOf(rFieldDescVII);
705       FillChar(Header^, HeaderSize, #0);
706       PDbfHdr(Header)^.VerDBF := $04;
707       // write language string. FPC needs an explicit cast to pchar to avoid calling widestring version of StrPLCopy
708       StrPLCopy(
709         PChar(@PEndFixedHdrVII(PChar(Header)+SizeOf(rDbfHdr))^.LanguageDriverName[32]),
710         PChar(ConstructLangName(FFileCodePage, lLocaleID, false)),
711         63-32);
712       lFieldDescPtr := @lFieldDescVII;
713     end else begin
714       // DBase III..V, (Visual) FoxPro without memo
715       // rEndHdrVFP is covered at the end as it is placed after the variable
716       // length part of the header.
717       HeaderSize := SizeOf(rDbfHdr);
718       RecordSize := SizeOf(rFieldDescIII);
719       FillChar(Header^, HeaderSize, #0);
720       // Note: VerDBF may be changed later on depending on what features/fields are used
721       // (autoincrement etc)
722       case FDbfVersion of
723         xFoxPro: PDbfHdr(Header)^.VerDBF := $03; {FoxBASE+/FoxPro/dBASE III PLUS/dBASE IV, no memo
724         alternative $02 FoxBASE is not readable by current MS Visual FoxPro drivers.
725         }
726         xVisualFoxPro: PDbfHdr(Header)^.VerDBF := $30; {Visual FoxPro no autoincrement,no varchar}
727         else PDbfHdr(Header)^.VerDBF := $03; {FoxBASE+/FoxPro/dBASE III PLUS/dBASE IV, no memo}
728       end;
729 
730       // standard language WE/Western Europe
731       if FDbfVersion=xBaseIII then
732         PDbfHdr(Header)^.Language := 0 //no language support
733       else
734         PDbfHdr(Header)^.Language := FFileLangId;
735 
736       // init field ptr
737       lFieldDescPtr := @lFieldDescIII;
738     end;
739 
740     // Begin variable part of the header
741     // Writing field definitions
742     FFieldDefs.Clear;
743     // deleted mark takes 1 byte, so skip over that
744     lFieldOffset := 1;
745     for I := 1 to AFieldDefs.Count do
746     begin
747       lFieldDef := AFieldDefs.Items[I-1];
748 
749       // check if datetime conversion
750       if FCopyDateTimeAsString then
751         if lFieldDef.FieldType = ftDateTime then
752         begin
753           // convert to string
754           lFieldDef.FieldType := ftString;
755           lFieldDef.Size := 22;
756         end;
757 
758       // update source
759       lFieldDef.FieldName := AnsiUpperCase(lFieldDef.FieldName);
760       lFieldDef.Offset := lFieldOffset;
761       lHasBlob := lHasBlob or lFieldDef.IsBlob;
762       // Check for Foxpro, too, as it can get auto-upgraded to vfp:
763       if (FDbfVersion in [xFoxPro,xVisualFoxPro]) then
764       begin
765         if (lFieldDef.NativeFieldType='Q') or (lFieldDef.NativeFieldType='V') then
766         begin
767           lNullVarFlagCount:=lNullVarFlagCount+1;
768         end;
769         if (lFieldDef.NullPosition>=0) then
770           lNullVarFlagCount:=lNullVarFlagCount+1;
771       end;
772 
773       // apply field transformation tricks
774       lSize := lFieldDef.Size;
775       lPrec := lFieldDef.Precision;
776       if (lFieldDef.NativeFieldType = 'C')
777 {$ifndef USE_LONG_CHAR_FIELDS}
778         and (FDbfVersion in [xFoxPro,xVisualFoxPro])
779 {$endif}
780         then
781       begin
782         // Up to 32kb strings
783         // Stores high byte of size in precision, low in size
784         lPrec := lSize shr 8;
785         lSize := lSize and $FF;
786       end;
787 
788       // update temp field properties
789       if FDbfVersion = xBaseVII then
790       begin
791         FillChar(lFieldDescVII, SizeOf(lFieldDescVII), #0);
792         StrPLCopy(lFieldDescVII.FieldName, lFieldDef.FieldName, SizeOf(lFieldDescVII.FieldName)-1);
793         lFieldDescVII.FieldType := lFieldDef.NativeFieldType;
794         lFieldDescVII.FieldSize := lSize;
795         lFieldDescVII.FieldPrecision := lPrec;
796         lFieldDescVII.NextAutoInc := SwapIntLE(lFieldDef.AutoInc);
797         //lFieldDescVII.MDXFlag := ???
798       end else begin
799         FillChar(lFieldDescIII, SizeOf(lFieldDescIII), #0);
800         StrPLCopy(lFieldDescIII.FieldName, lFieldDef.FieldName, SizeOf(lFieldDescIII.FieldName)-1);
801         lFieldDescIII.FieldType := lFieldDef.NativeFieldType;
802         lFieldDescIII.FieldSize := lSize;
803         lFieldDescIII.FieldPrecision := lPrec;
804         if (FDbfVersion in [xFoxPro,xVisualFoxPro]) then
805           lFieldDescIII.FieldOffset := SwapIntLE(lFieldOffset);
806 
807         // Upgrade the version info if needed for supporting field types used.
808         // This is also what Visual FoxPro does with FoxPro tables to which you
809         // add new VFP features.
810         if (FDBFVersion in [xUnknown,xFoxPro,xVisualFoxPro]) then
811         begin
812           // VerDBF=$03 also includes dbase formats, so we perform an extra check
813           if (PDbfHdr(Header)^.VerDBF in [$02,$03]) and
814             ((lFieldDef.NativeFieldType in ['0', 'Y', 'T', 'O', '+', 'Q', 'V']) or
815             (lNullVarFlagCount>0)) then
816           begin
817             PDbfHdr(Header)^.VerDBF := $30; {Visual FoxPro}
818             FDBFVersion:=xVisualFoxPro;
819           end;
820 
821           // Upgrade if a non-empty backlink is specified - for FoxPro only
822           if (FBackLink<>'') and
823             ((FDBFVersion=xFoxPro) or (PDbfHdr(Header)^.VerDBF=$02)) then
824           begin
825             PDbfHdr(Header)^.VerDBF := $30; {Visual FoxPro}
826             FDBFVersion:=xVisualFoxPro; //needed to write the backlink info
827           end;
828 
829           //AutoInc only support in Visual Foxpro; another upgrade
830           //Note: .AutoIncrementNext is really a cardinal (see the definition)
831           lFieldDescIII.AutoIncrementNext:=SwapIntLE(lFieldDef.AutoInc);
832           lFieldDescIII.AutoIncrementStep:=lFieldDef.AutoIncStep;
833           // Set autoincrement flag using AutoIncStep as a marker
834           if (lFieldDef.AutoIncStep<>0) then
835             lFieldDescIII.VisualFoxProFlags:=(lFieldDescIII.VisualFoxProFlags or $0C);
836           if (PDbfHdr(Header)^.VerDBF = $30) and (lFieldDef.AutoIncStep<>0) then
837           begin
838             PDbfHdr(Header)^.VerDBF := $31; {Visual FoxPro, autoincrement enabled}
839             FDBFVersion:=xVisualFoxPro;
840           end;
841 
842           // Only supported in Visual FoxPro but let's not upgrade format as
843           // IsSystemField is a minor property
844           if (lFieldDef.IsSystemField) then
845             lFieldDescIII.VisualFoxProFlags:=(lFieldDescIII.VisualFoxProFlags or $01);
846         end;
847       end;
848 
849       // Update our field list
850       with FFieldDefs.AddFieldDef do
851       begin
852         Assign(lFieldDef);
853         Offset := lFieldOffset;
854         AutoInc := 0;
855       end;
856 
857       // save field properties
858       WriteRecord(I, lFieldDescPtr);
859       Inc(lFieldOffset, lFieldDef.Size);
860     end;
861 
862     // Visual Foxpro: write _NULLFLAGS field if required
863     if (FDBFVersion=xVisualFoxPro) and (lNullVarFlagCount>0) then
864     begin
865       FillChar(lFieldDescIII, SizeOf(lFieldDescIII), #0);
866       StrPLCopy(lFieldDescIII.FieldName, NULLFLAGSFIELD, 10);
867       lFieldDescIII.FieldType := '0'; //bytes
868       lFieldDescIII.FieldSize := 1+(lNullVarFlagCount-1) div 8; //Number of bytes needed for all bit flags
869       lFieldDescIII.FieldPrecision := 0;
870       lFieldDescIII.FieldOffset := SwapIntLE(lFieldOffset);
871       lFieldDescIII.VisualFoxProFlags:=$01+$04 ; //System column (hidden)+Column can store null values (which is a bit of a paradox)
872       // Save field properties
873       WriteRecord(AFieldDefs.Count+1, @lFieldDescIII);
874       Inc(lFieldOffset, lFieldDescIII.FieldSize);
875     end;
876 
877     // End of field descriptor; usually end of header as well.
878     // Visual Foxpro backlink info is part of the header but comes after the
879     // terminator
880     WriteChar(FIELD_DESCRIPTOR_ARRAY_TERMINATOR);
881 
882     { For Visual FoxPro, add back-link info }
883     if (FDbfVersion = xVisualFoxPro) then
884     begin
885       FBackLinkOffset := Stream.Position;
886       // Backlink is defined as all $0 bytes if empty
887       lEndHdrVFP.Backlink:=FBackLink+StringOfChar(#0, SizeOf(lEndHdrVFP.BackLink));
888       WriteBlock(@lEndHdrVFP,SizeOf(lEndHdrVFP),Stream.Position);
889     end;
890 
891     // Write memo bit to begin of header
892     if lHasBlob then
893     begin
894       case FDbfVersion of
895         xBaseIII: PDbfHdr(Header)^.VerDBF := PDbfHdr(Header)^.VerDBF or $80;
896         xFoxPro: if (PDbfHdr(Header)^.VerDBF in [$02,$03]) then {change from FoxBASE to...}
897           PDbfHdr(Header)^.VerDBF := $F5; {...FoxPro 2.x (or earlier) with memo}
898         xVisualFoxPro: //MSDN says field 28 or $02 to set memo flag
899           PDbfHdr(Header)^.MDXFlag := PDbfHdr(Header)^.MDXFlag or $02;
900         else PDbfHdr(Header)^.VerDBF := PDbfHdr(Header)^.VerDBF or $88;
901       end;
902     end;
903 
904     // Update header to correct sizes
905     PDbfHdr(Header)^.RecordSize := lFieldOffset;
906     if lNullVarFlagCount>0 then
907       PDbfHdr(Header)^.FullHdrSize := HeaderSize + RecordSize * (AFieldDefs.Count + 1) + 1
908     else
909       PDbfHdr(Header)^.FullHdrSize := HeaderSize + RecordSize * AFieldDefs.Count + 1;
910     if DbfVersion=xVisualFoxPro then
911       PDbfHdr(Header)^.FullHdrSize := PDbfHdr(Header)^.FullHdrSize + SizeOf(rEndHdrVFP);
912 
913     // write dbf header to disk
914     inherited WriteHeader;
915   finally
916     RecordSize := PDbfHdr(Header)^.RecordSize;
917     HeaderSize := PDbfHdr(Header)^.FullHdrSize;
918 
919     // write full header to disk (dbf+fields)
920     WriteHeader;
921   end;
922 
923   if HasBlob and (FMemoFile=nil) then
924   begin
925     lMemoFileName := ChangeFileExt(FileName, GetMemoExt);
926     if (FDbfVersion in [xFoxPro,xVisualFoxPro]) then
927       FMemoFile := TFoxProMemoFile.Create(Self)
928     else
929       FMemoFile := TDbaseMemoFile.Create(Self);
930     FMemoFile.FileName := lMemoFileName;
931     if (Mode in [pfMemoryOpen,pfMemoryCreate]) then
932       FMemoFile.Stream:=FMemoStream;
933     FMemoFile.Mode := Mode;
934     FMemoFile.AutoCreate := AutoCreate;
935     FMemoFile.MemoRecordSize := MemoSize;
936     FMemoFile.DbfVersion := FDbfVersion;
937     FMemoFile.Open;
938   end;
939 end;
940 
HasBlobnull941 function TDbfFile.HasBlob: Boolean;
942 var
943   I: Integer;
944 begin
945   Result := false;
946   for I := 0 to FFieldDefs.Count-1 do
947     if FFieldDefs.Items[I].IsBlob then
948     begin
949       Result := true;
950       break;
951     end;
952 end;
953 
GetMemoExtnull954 function TDbfFile.GetMemoExt: string;
955 begin
956   case FDbfVersion of
957     xFoxPro, xVisualFoxPro: Result := '.fpt'
958     else Result := '.dbt';
959   end;
960   if (FFileName<>'') and (FFileName=UpperCase(FFileName)) then
961     Result := UpperCase(Result);
962 end;
963 
964 procedure TDbfFile.Zap;
965 begin
966   // make recordcount zero
967   RecordCount := 0;
968   // update recordcount
969   PDbfHdr(Header)^.RecordCount := RecordCount;
970   // update disk header
971   WriteHeader;
972   // update indexes
973   RegenerateIndexes;
974 end;
975 
976 procedure TDbfFile.WriteHeader;
977 var
978   SystemTime: TSystemTime;
979   lDataHdr: PDbfHdr;
980   EofTerminator: Byte;
981 begin
982   if (HeaderSize=0) then
983     exit;
984 
985   //FillHeader(0);
986   lDataHdr := PDbfHdr(Header);
987   GetLocalTime(SystemTime);
988   lDataHdr^.Year := SystemTime.wYear - 1900;
989   lDataHdr^.Month := SystemTime.wMonth;
990   lDataHdr^.Day := SystemTime.wDay;
991 //  lDataHdr.RecordCount := RecordCount;
992   inherited WriteHeader;
993 
994   // Write terminator at the end of the file, after the records:
995   EofTerminator := $1A;
996   // We're using lDataHdr to make sure we have the latest/correct version
997   WriteBlock(@EofTerminator, 1, CalcPageOffset(lDataHdr.RecordCount+1));
998 end;
999 
1000 procedure TDbfFile.ConstructFieldDefs;
1001 var
1002   // The size of the fixed part of the header
1003   // excluding the field descriptor array
1004   // also excluding everything that comes after the field descriptor array
1005   // like VFP backlink records
1006   lFakeHeaderSize: Integer;
1007   lFieldSize: Integer;
1008   lPropHdrOffset, lFieldOffset: Integer;
1009   lFieldDescIII: rFieldDescIII;
1010   lFieldDescVII: rFieldDescVII;
1011   lFieldPropsHdr: rFieldPropsHdr;
1012   lStdProp: rStdPropEntry;
1013   TempFieldDef: TDbfFieldDef;
1014   lSize,lPrec,I, lColumnCount: Integer;
1015   lAutoInc: Cardinal;
1016   dataPtr: PChar;
1017   lNativeFieldType: Char;
1018   lFieldName: string;
1019   lCanHoldNull: boolean; //Can the field store nulls, i.e. is it nullable?
1020   lIsVFPSystemField: boolean; //Is this a Visual FoxPro system/hidden field?
1021   lIsVFPVarLength: boolean; //Is this a Visual FoxPro varbinary/varchar field,
1022   // where varlength bit is maintained in _NULLFLAGS
1023   lCurrentNullPosition: integer;
1024 begin
1025   FFieldDefs.Clear;
1026   case DbfVersion of
1027     xBaseVII:
1028       begin
1029         lFakeHeaderSize := SizeOf(rDbfHdr) + SizeOf(rEndFixedHdrVII);
1030         lFieldSize := SizeOf(rFieldDescVII);
1031       end;
1032     else
1033     begin
1034       // DBase III..V, (Visual) FoxPro
1035       if DBfVersion = xVisualFoxPro then
1036         lFakeHeaderSize := SizeOf(rDbfHdr)
1037       else
1038         lFakeHeaderSize := SizeOf(rDbfHdr);
1039       lFieldSize := SizeOf(rFieldDescIII);
1040     end;
1041   end;
1042 
1043   // This is of course not true but it shrinks the perceived header to just
1044   // before the records with field info:
1045   HeaderSize := lFakeHeaderSize;
1046   RecordSize := lFieldSize;
1047   if FDbfVersion=xVisualFoxPro then
1048     lColumnCount := (PDbfHdr(Header)^.FullHdrSize - lFakeHeaderSize - SizeOf(rEndHdrVFP)) div lFieldSize
1049   else
1050     lColumnCount := (PDbfHdr(Header)^.FullHdrSize - lFakeHeaderSize) div lFieldSize;
1051 
1052   FBackLinkOffset := 0;
1053   FLockField := nil;
1054   FNullField := nil;
1055   FAutoIncPresent := false;
1056   lFieldOffset := 1;
1057   lAutoInc := 0;
1058   I := 1;
1059   lCurrentNullPosition := 0; // Contains the next value for the _NULLFLAGS bit position
1060   lCanHoldNull := false;
1061   lIsVFPSystemField := false;
1062   lIsVFPVarLength := false;
1063   try
1064     // Specs say there has to be at least one field, so use repeat:
1065     repeat
1066       // version field info?
1067       if FDbfVersion = xBaseVII then
1068       begin
1069         ReadRecord(I, @lFieldDescVII);
1070         lFieldName := AnsiUpperCase(PChar(@lFieldDescVII.FieldName[0]));
1071         lSize := lFieldDescVII.FieldSize;
1072         lPrec := lFieldDescVII.FieldPrecision;
1073         lNativeFieldType := lFieldDescVII.FieldType;
1074         lAutoInc := SwapIntLE(lFieldDescVII.NextAutoInc);
1075         if lNativeFieldType = '+' then
1076           FAutoIncPresent := true;
1077       end else begin
1078         // DBase III..V, FoxPro, Visual FoxPro
1079         ReadRecord(I, @lFieldDescIII);
1080         lFieldName := AnsiUpperCase(PChar(@lFieldDescIII.FieldName[0]));
1081         lSize := lFieldDescIII.FieldSize;
1082         lPrec := lFieldDescIII.FieldPrecision;
1083         lNativeFieldType := lFieldDescIII.FieldType;
1084         if (FDBFVersion=xVisualFoxPro) and ((lFieldDescIII.VisualFoxProFlags and $0C)<>0) then
1085         begin
1086           // We do not test for an I field - we could implement our own N autoincrement this way...
1087           lAutoInc:=lFieldDescIII.AutoIncrementNext;
1088           FAutoIncPresent:=true;
1089         end;
1090 
1091         // Only Visual FoxPro supports null fields, if the nullable field flag is on
1092         lCanHoldNull := (FDbfVersion in [xVisualFoxPro]) and
1093           ((lFieldDescIII.VisualFoxProFlags and $2) <> 0) and
1094           (lFieldName <> NULLFLAGSFIELD {the field where null status is stored can never be null itself});
1095         // System/hidden flag (VFP only):
1096         lIsVFPSystemField := (FDbfVersion in [xVisualFoxPro]) and
1097           ((lFieldDescIII.VisualFoxProFlags and $01)=$01);
1098         // Only Visual Foxpro supports varbinary/varchar fields where a flag indicates
1099         // if the actual size is stored in the last data byte.
1100         lIsVFPVarLength := (FDbfVersion in [xVisualFoxPro]) and
1101           (lNativeFieldType in ['Q','V']) and
1102           (lFieldName <> NULLFLAGSFIELD);
1103       end;
1104 
1105       // apply field transformation tricks
1106       if (lNativeFieldType = 'C')
1107 {$ifndef USE_LONG_CHAR_FIELDS}
1108         and (FDbfVersion in [xFoxPro,xVisualFoxPro])
1109 {$endif}
1110         then
1111       begin
1112         // (V)FP uses the byte where precision is normally stored
1113         // for the high byte of the field size
1114         lSize := lSize + lPrec shl 8;
1115         lPrec := 0;
1116       end;
1117 
1118       // add field
1119       TempFieldDef := FFieldDefs.AddFieldDef;
1120       with TempFieldDef do
1121       begin
1122         FieldName := lFieldName;
1123         Offset := lFieldOffset;
1124         Size := lSize;
1125         Precision := lPrec;
1126         AutoInc := lAutoInc;
1127         NativeFieldType := lNativeFieldType;
1128         IsSystemField := lIsVFPSystemField;
1129         if lIsVFPVarLength then
1130         begin
1131           // The varlength flag uses the same _NULLFLAGS field as the null flags.
1132           // It comes before the null bit for that field, if any.
1133           VarLengthPosition := lCurrentNullPosition;
1134           inc(lCurrentNullPosition);
1135         end else
1136           VarLengthPosition := -1;
1137         if lCanHoldNull then
1138         begin
1139           NullPosition := lCurrentNullPosition;
1140           inc(lCurrentNullPosition);
1141         end else
1142           NullPosition := -1;
1143       end;
1144 
1145       // check valid field:
1146       //  1) non-empty field name
1147       //  2) known field type
1148       //  {3) no changes have to be made to precision or size}
1149       if (Length(lFieldName) = 0) or (TempFieldDef.FieldType = ftUnknown) then
1150         raise EDbfError.Create(STRING_INVALID_DBF_FILE_FIELDERROR);
1151 
1152       // determine if lock field present, if present, then store additional info
1153       if lFieldName = '_DBASELOCK' then
1154       begin
1155         FLockField := TempFieldDef;
1156         FLockUserLen := lSize - 8;
1157         if FLockUserLen > DbfGlobals.UserNameLen then
1158           FLockUserLen := DbfGlobals.UserNameLen;
1159       end else
1160       if (FDbfVersion=xVisualFoxPro) and (uppercase(lFieldName) = NULLFLAGSFIELD) then
1161         FNullField := TempFieldDef;
1162 
1163       // goto next field
1164       Inc(lFieldOffset, lSize);
1165       Inc(I);
1166 
1167       // continue until header termination character found
1168       // or end of header reached
1169     until (I > lColumnCount) or (ReadChar = FIELD_DESCRIPTOR_ARRAY_TERMINATOR);
1170 
1171     if FDbfVersion=xVisualFoxPro then
1172       FBackLinkOffset:=Stream.Position+SizeOf(FIELD_DESCRIPTOR_ARRAY_TERMINATOR); //after FIELD_DESCRIPTION_ARRAY_TERMINATOR
1173 
1174     // test if not too many fields
1175     if FFieldDefs.Count >= 4096 then
1176       raise EDbfError.CreateFmt(STRING_INVALID_FIELD_COUNT, [FFieldDefs.Count]);
1177 
1178     // do not check FieldOffset = PDbfHdr(Header).RecordSize because additional
1179     // data could be present in record
1180 
1181     // get current position
1182     lPropHdrOffset := Stream.Position;
1183 
1184     // dBase 7 -> read field properties, test if enough space, maybe no header
1185     if (FDbfVersion = xBaseVII) and (lPropHdrOffset + Sizeof(lFieldPropsHdr) <
1186       PDbfHdr(Header)^.FullHdrSize) then
1187     begin
1188       // read in field properties header
1189       ReadBlock(@lFieldPropsHdr, SizeOf(lFieldPropsHdr), lPropHdrOffset);
1190       // read in standard properties
1191       lFieldOffset := lPropHdrOffset + lFieldPropsHdr.StartStdProps;
1192       for I := 0 to lFieldPropsHdr.NumStdProps - 1 do
1193       begin
1194         // read property data
1195         ReadBlock(@lStdProp, SizeOf(lStdProp), lFieldOffset+I*SizeOf(lStdProp));
1196         // is this a constraint?
1197         if lStdProp.FieldOffset = 0 then
1198         begin
1199           // this is a constraint...not implemented
1200         end else if lStdProp.FieldOffset <= FFieldDefs.Count then begin
1201           // get fielddef for this property
1202           TempFieldDef := FFieldDefs.Items[lStdProp.FieldOffset-1];
1203           // allocate space to store data
1204           TempFieldDef.AllocBuffers;
1205           // dataPtr = nil -> no data to retrieve
1206           dataPtr := nil;
1207           // store data
1208           case lStdProp.PropType of
1209             FieldPropType_Required: TempFieldDef.Required := true;
1210             FieldPropType_Default:
1211               begin
1212                 dataPtr := TempFieldDef.DefaultBuf;
1213                 TempFieldDef.HasDefault := true;
1214               end;
1215             FieldPropType_Min:
1216               begin
1217                 dataPtr := TempFieldDef.MinBuf;
1218                 TempFieldDef.HasMin := true;
1219               end;
1220             FieldPropType_Max:
1221               begin
1222                 dataPtr := TempFieldDef.MaxBuf;
1223                 TempFieldDef.HasMax := true;
1224               end;
1225           end;
1226           // get data for this property
1227           if dataPtr <> nil then
1228             ReadBlock(dataPtr, lStdProp.DataSize, lPropHdrOffset + lStdProp.DataOffset);
1229         end;
1230       end;
1231       // todo: read dbase7 custom properties...not implemented
1232       // todo: read dbase7 RI/referential integrity properties...not implemented
1233     end;
1234   finally
1235     // Restore proper sizes so normal records after the header can be read
1236     HeaderSize := PDbfHdr(Header)^.FullHdrSize;
1237     RecordSize := PDbfHdr(Header)^.RecordSize;
1238   end;
1239 end;
1240 
GetLanguageIdnull1241 function TDbfFile.GetLanguageId: Integer;
1242 begin
1243   Result := PDbfHdr(Header)^.Language;
1244 end;
1245 
GetLanguageStrnull1246 function TDbfFile.GetLanguageStr: string;
1247 begin
1248   if FDbfVersion = xBaseVII then
1249     Result := PEndFixedHdrVII(PChar(Header) + SizeOf(rDbfHdr))^.LanguageDriverName
1250   else
1251     Result := '';  // Only supported in DbaseVII
1252 end;
1253 
IsNullFlagSetnull1254 function TDbfFile.IsNullFlagSet(const Src: Pointer; var AFieldDef: TDbfFieldDef; WhichField: TNullFieldFlag): boolean;
1255 var
1256   NullFlagByte: Pointer;
1257 begin
1258   case WhichField of
1259   nfNullFlag:
1260     begin
1261       if (AFieldDef.NullPosition<0) or (FNullField=nil) then
1262         result:=false //field is not even nullable
1263       else
1264       begin
1265         // go to _NULLFLAGS byte that has this field's null flag
1266         // Find out the byte where the null bit for the field is stored by doing
1267         // NullPosition shr3 (= NullPosition div 8)...
1268         NullFlagByte := PChar(Src) + FNullField.Offset + (AFieldDef.NullPosition shr 3);
1269         // ... get the correct bit in the byte by the equivalent of getting the bit number in that byte:
1270         // NullPosition and $7 (=mod 8)... and going to the bit value in the byte (by shl)
1271         // The result is true if the field is null.
1272         Result := (PByte(NullFlagByte)^ and (1 shl (AFieldDef.NullPosition and $7))) <> 0;
1273       end;
1274     end;
1275   nfVarlengthFlag:
1276     begin
1277       if (AFieldDef.VarLengthPosition<0) or (FNullField=nil) then
1278         result:=false //field *never* has a varlength byte
1279       else
1280       begin
1281         NullFlagByte := PChar(Src) + FNullField.Offset + (AFieldDef.VarLengthPosition shr 3);
1282         Result := (PByte(NullFlagByte)^ and (1 shl (AFieldDef.VarLengthPosition and $7))) <> 0
1283       end;
1284     end;
1285   end;
1286 end;
1287 
1288 {
1289   I fill the holes with the last records.
1290   now we can do an 'in-place' pack
1291 }
1292 procedure TDbfFile.FastPackTable;
1293 var
1294   iDel,iNormal: Integer;
1295   pDel,pNormal: PChar;
1296 
1297   function FindFirstDel: Boolean;
1298   begin
1299     while iDel<=iNormal do
1300     begin
1301       ReadRecord(iDel, pDel);
1302       if (PChar(pDel)^ <> ' ') then
1303       begin
1304         Result := true;
1305         exit;
1306       end;
1307       Inc(iDel);
1308     end;
1309     Result := false;
1310   end;
1311 
1312   function FindLastNormal: Boolean;
1313   begin
1314     while iNormal>=iDel do
1315     begin
1316       ReadRecord(iNormal, pNormal);
1317       if (PChar(pNormal)^= ' ') then
1318       begin
1319         Result := true;
1320         exit;
1321       end;
1322       dec(iNormal);
1323     end;
1324     Result := false;
1325   end;
1326 
1327 begin
1328   if RecordSize < 1 then Exit;
1329 
1330   GetMem(pNormal, RecordSize);
1331   GetMem(pDel, RecordSize);
1332   try
1333     iDel := 1;
1334     iNormal := RecordCount;
1335 
1336     while FindFirstDel do
1337     begin
1338       // iDel is definitely deleted
1339       if FindLastNormal then
1340       begin
1341         // but is not anymore
1342         WriteRecord(iDel, pNormal);
1343         PChar(pNormal)^ := '*';
1344         WriteRecord(iNormal, pNormal);
1345       end else begin
1346         // Cannot find a record after iDel so iDel must be deleted
1347         dec(iDel);
1348         break;
1349       end;
1350     end;
1351     // FindFirstDel failed means than iDel is full
1352     RecordCount := iDel;
1353     RegenerateIndexes;
1354     // Pack Memofields
1355   finally
1356     FreeMem(pNormal);
1357     FreeMem(pDel);
1358   end;
1359 end;
1360 
1361 procedure TDbfFile.Rename(DestFileName: string; NewIndexFileNames: TStrings; DeleteFiles: boolean);
1362 var
1363   lIndexFileNames: TStrings;
1364   lIndexFile: TIndexFile;
1365   NewBaseName: string;
1366   I: integer;
1367 begin
1368   // todo: verify if this works with memo files
1369   // get memory for index file list
1370   lIndexFileNames := TStringList.Create;
1371   try
1372     // save index filenames
1373     for I := 0 to FIndexFiles.Count - 1 do
1374     begin
1375       lIndexFile := TIndexFile(IndexFiles[I]);
1376       lIndexFileNames.Add(lIndexFile.FileName);
1377       // prepare changing the dbf file name, needs changes in index files
1378       lIndexFile.PrepareRename(NewIndexFileNames[I]);
1379     end;
1380 
1381     // close file
1382     Close;
1383 
1384     if DeleteFiles then
1385     begin
1386       SysUtils.DeleteFile(DestFileName);
1387       SysUtils.DeleteFile(ChangeFileExt(DestFileName, GetMemoExt));
1388     end else begin
1389       I := 0;
1390       FindNextName(DestFileName, NewBaseName, I);
1391       SysUtils.RenameFile(DestFileName, NewBaseName);
1392       SysUtils.RenameFile(ChangeFileExt(DestFileName, GetMemoExt),
1393         ChangeFileExt(NewBaseName, GetMemoExt));
1394     end;
1395     // delete old index files
1396     for I := 0 to NewIndexFileNames.Count - 1 do
1397       SysUtils.DeleteFile(NewIndexFileNames.Strings[I]);
1398     // rename the new dbf files
1399     SysUtils.RenameFile(FileName, DestFileName);
1400     SysUtils.RenameFile(ChangeFileExt(FileName, GetMemoExt),
1401       ChangeFileExt(DestFileName, GetMemoExt));
1402     // rename new index files
1403     for I := 0 to NewIndexFileNames.Count - 1 do
1404       SysUtils.RenameFile(lIndexFileNames.Strings[I], NewIndexFileNames.Strings[I]);
1405   finally
1406     lIndexFileNames.Free;
1407   end;
1408 end;
1409 
1410 type
1411   TRestructFieldInfo = record
1412     SourceOffset: Integer;
1413     DestOffset: Integer;
1414     Size: Integer;
1415   end;
1416 
1417   { assume nobody has more than 8192 fields, otherwise possibly range check error }
1418   PRestructFieldInfo = ^TRestructFieldInfoArray;
1419   TRestructFieldInfoArray = array[0..8191] of TRestructFieldInfo;
1420 
1421 procedure TDbfFile.RestructureTable(DbfFieldDefs: TDbfFieldDefs; Pack: Boolean);
1422 var
1423   DestDbfFile: TDbfFile;
1424   TempIndexDef: TDbfIndexDef;
1425   TempIndexFile: TIndexFile;
1426   DestFieldDefs: TDbfFieldDefs;
1427   TempDstDef, TempSrcDef: TDbfFieldDef;
1428   OldIndexFiles: TStrings;
1429   IndexName, NewBaseName: string;
1430   I, lRecNo, lFieldNo, lFieldSize, lBlobPageNo, lWRecNo, srcOffset, dstOffset: Integer;
1431   pBuff, pDestBuff: TRecordBuffer;
1432   RestructFieldInfo: PRestructFieldInfo;
1433   BlobStream: TMemoryStream;
1434 begin
1435   // nothing to do?
1436   if (RecordSize < 1) or ((DbfFieldDefs = nil) and not Pack) then
1437     exit;
1438 
1439   // if no exclusive access, terrible things can happen!
1440   CheckExclusiveAccess;
1441 
1442   // make up some temporary filenames
1443   lRecNo := 0;
1444   FindNextName(FileName, NewBaseName, lRecNo);
1445 
1446   // select final field definition list
1447   if DbfFieldDefs = nil then
1448   begin
1449     DestFieldDefs := FFieldDefs;
1450   end else begin
1451     DestFieldDefs := DbfFieldDefs;
1452     // copy autoinc values
1453     for I := 0 to DbfFieldDefs.Count - 1 do
1454     begin
1455       lFieldNo := DbfFieldDefs.Items[I].CopyFrom;
1456       if (lFieldNo >= 0) and (lFieldNo < FFieldDefs.Count) then
1457         DbfFieldDefs.Items[I].AutoInc := FFieldDefs.Items[lFieldNo].AutoInc;
1458     end;
1459   end;
1460 
1461   // create temporary dbf
1462   DestDbfFile := TDbfFile.Create;
1463   DestDbfFile.FileName := NewBaseName;
1464   DestDbfFile.AutoCreate := true;
1465   DestDbfFile.Mode := pfExclusiveCreate;
1466   DestDbfFile.OnIndexMissing := FOnIndexMissing;
1467   DestDbfFile.OnLocaleError := FOnLocaleError;
1468   DestDbfFile.DbfVersion := FDbfVersion;
1469   DestDbfFile.FileLangId := FileLangId;
1470   DestDbfFile.Open;
1471   // create dbf header
1472   if FMemoFile <> nil then
1473     DestDbfFile.FinishCreate(DestFieldDefs, FMemoFile.RecordSize)
1474   else
1475     if (DestDbfFile.DbfVersion in [xFoxPro,xVisualFoxPro]) then
1476       DestDbfFile.FinishCreate(DestFieldDefs, 64) {VFP default}
1477     else
1478       DestDbfFile.FinishCreate(DestFieldDefs, 512);
1479 
1480   // adjust size and offsets of fields
1481   GetMem(RestructFieldInfo, sizeof(TRestructFieldInfo)*DestFieldDefs.Count);
1482   for lFieldNo := 0 to DestFieldDefs.Count - 1 do
1483   begin
1484     TempDstDef := DestFieldDefs.Items[lFieldNo];
1485     if TempDstDef.CopyFrom >= 0 then
1486     begin
1487       TempSrcDef := FFieldDefs.Items[TempDstDef.CopyFrom];
1488       if TempDstDef.NativeFieldType in ['F', 'N'] then
1489       begin
1490         // get minimum field length
1491         lFieldSize := Min(TempSrcDef.Precision, TempDstDef.Precision) +
1492           Min(TempSrcDef.Size - TempSrcDef.Precision,
1493             TempDstDef.Size - TempDstDef.Precision);
1494         // if one has dec separator, but other not, we lose one digit
1495         if (TempDstDef.Precision > 0) xor
1496           ((TempSrcDef.NativeFieldType in ['F', 'N']) and (TempSrcDef.Precision > 0)) then
1497           Dec(lFieldSize);
1498         // should not happen, but check nevertheless (maybe corrupt data)
1499         if lFieldSize < 0 then
1500           lFieldSize := 0;
1501         srcOffset := TempSrcDef.Size - TempSrcDef.Precision -
1502           (TempDstDef.Size - TempDstDef.Precision);
1503         if srcOffset < 0 then
1504         begin
1505           dstOffset := -srcOffset;
1506           srcOffset := 0;
1507         end else begin
1508           dstOffset := 0;
1509         end;
1510       end else begin
1511         lFieldSize := Min(TempSrcDef.Size, TempDstDef.Size);
1512         srcOffset := 0;
1513         dstOffset := 0;
1514       end;
1515       with RestructFieldInfo[lFieldNo] do
1516       begin
1517         Size := lFieldSize;
1518         SourceOffset := TempSrcDef.Offset + srcOffset;
1519         DestOffset := TempDstDef.Offset + dstOffset;
1520       end;
1521     end;
1522   end;
1523 
1524   // add indexes
1525   TempIndexDef := TDbfIndexDef.Create(nil);
1526   for I := 0 to FIndexNames.Count - 1 do
1527   begin
1528     // get length of extension -> determines MDX or NDX
1529     IndexName := FIndexNames.Strings[I];
1530     TempIndexFile := TIndexFile(FIndexNames.Objects[I]);
1531     TempIndexFile.GetIndexInfo(IndexName, TempIndexDef);
1532     if Length(ExtractFileExt(IndexName)) > 0 then
1533     begin
1534       // NDX index, get unique file name
1535       lRecNo := 0;
1536       FindNextName(IndexName, IndexName, lRecNo);
1537     end;
1538     // add this index
1539     DestDbfFile.OpenIndex(IndexName, TempIndexDef.SortField, true, TempIndexDef.Options);
1540   end;
1541   TempIndexDef.Free;
1542 
1543   // get memory for record buffers
1544   GetMem(pBuff, RecordSize);
1545   BlobStream := TMemoryStream.Create;
1546   OldIndexFiles := TStringList.Create;
1547   // if restructure, we need memory for dest buffer, otherwise use source
1548   if DbfFieldDefs = nil then
1549     pDestBuff := pBuff
1550   else
1551     GetMem(pDestBuff, DestDbfFile.RecordSize);
1552 
1553   // Go through record data:
1554   try
1555 {$ifdef USE_CACHE}
1556     BufferAhead := true;
1557     DestDbfFile.BufferAhead := true;
1558 {$endif}
1559     lWRecNo := 1;
1560     for lRecNo := 1 to RecordCount do
1561     begin
1562       // read record from original dbf
1563       ReadRecord(lRecNo, pBuff);
1564       // copy record unless (deleted or user wants packing)
1565       if (ansichar(pBuff^) <> '*') or not Pack then
1566       begin
1567         // if restructure, initialize dest
1568         if DbfFieldDefs <> nil then
1569         begin
1570           DestDbfFile.InitRecord(pDestBuff);
1571           // copy deleted mark (the first byte)
1572           pDestBuff^ := pBuff^;
1573         end;
1574 
1575         if (DbfFieldDefs <> nil) or (FMemoFile <> nil) then
1576         begin
1577           // copy fields
1578           for lFieldNo := 0 to DestFieldDefs.Count-1 do
1579           begin
1580             TempDstDef := DestFieldDefs.Items[lFieldNo];
1581             // handle blob fields differently
1582             // don't try to copy new blob fields!
1583             // DbfFieldDefs = nil -> pack only
1584             // TempDstDef.CopyFrom >= 0 -> copy existing (blob) field
1585             if TempDstDef.IsBlob and ((DbfFieldDefs = nil) or (TempDstDef.CopyFrom >= 0)) then
1586             begin
1587               // get current blob blockno
1588               GetFieldData(lFieldNo, ftInteger, pBuff, @lBlobPageNo, false);
1589               // valid blockno read?
1590               if lBlobPageNo > 0 then
1591               begin
1592                 BlobStream.Clear;
1593                 FMemoFile.ReadMemo(lBlobPageNo, BlobStream);
1594                 BlobStream.Position := 0;
1595                 // always append
1596                 DestDbfFile.FMemoFile.WriteMemo(lBlobPageNo, 0, BlobStream);
1597               end;
1598               // write new blockno
1599               DestDbfFile.SetFieldData(lFieldNo, ftInteger, @lBlobPageNo, pDestBuff, false);
1600             end else if (DbfFieldDefs <> nil) and (TempDstDef.CopyFrom >= 0) then
1601             begin
1602               // copy content of field
1603               with RestructFieldInfo[lFieldNo] do
1604                 Move(pBuff[SourceOffset], pDestBuff[DestOffset], Size);
1605             end;
1606           end;
1607         end;
1608 
1609         // write record
1610         DestDbfFile.WriteRecord(lWRecNo, pDestBuff);
1611         // update indexes
1612         for I := 0 to DestDbfFile.IndexFiles.Count - 1 do
1613           TIndexFile(DestDbfFile.IndexFiles.Items[I]).Insert(lWRecNo, pDestBuff);
1614 
1615         // go to next record
1616         Inc(lWRecNo);
1617       end;
1618     end;
1619 
1620 {$ifdef USE_CACHE}
1621     BufferAhead := false;
1622     DestDbfFile.BufferAhead := false;
1623 {$endif}
1624 
1625     // save index filenames
1626     for I := 0 to FIndexFiles.Count - 1 do
1627       OldIndexFiles.Add(TIndexFile(IndexFiles[I]).FileName);
1628 
1629     // close dbf
1630     Close;
1631 
1632     // if restructure -> rename the old dbf files
1633     // if pack only -> delete the old dbf files
1634     DestDbfFile.Rename(FileName, OldIndexFiles, DbfFieldDefs = nil);
1635 
1636     // we have to reinit fielddefs if restructured
1637     Open;
1638 
1639     // crop deleted records
1640     RecordCount := lWRecNo - 1;
1641     // update date/time stamp, recordcount
1642     PDbfHdr(Header)^.RecordCount := RecordCount;
1643     WriteHeader;
1644   finally
1645     // close temporary file
1646     FreeAndNil(DestDbfFile);
1647     // free mem
1648     FreeAndNil(OldIndexFiles);
1649     FreeMem(pBuff);
1650     FreeAndNil(BlobStream);
1651     FreeMem(RestructFieldInfo);
1652     if DbfFieldDefs <> nil then
1653       FreeMem(pDestBuff);
1654   end;
1655 end;
1656 
1657 procedure TDbfFile.RegenerateIndexes;
1658 var
1659   lIndexNo: Integer;
1660 begin
1661   // recreate every index in every file
1662   for lIndexNo := 0 to FIndexFiles.Count-1 do
1663   begin
1664     PackIndex(TIndexFile(FIndexFiles.Items[lIndexNo]), EmptyStr);
1665   end;
1666 end;
1667 
TDbfFile.GetFieldInfonull1668 function TDbfFile.GetFieldInfo(FieldName: string): TDbfFieldDef;
1669 var
1670   I: Integer;
1671   lfi: TDbfFieldDef;
1672 begin
1673   FieldName := AnsiUpperCase(FieldName);
1674   for I := 0 to FFieldDefs.Count-1 do
1675   begin
1676     lfi := TDbfFieldDef(FFieldDefs.Items[I]);
1677     if lfi.fieldName = FieldName then
1678     begin
1679       Result := lfi;
1680       exit;
1681     end;
1682   end;
1683   Result := nil;
1684 end;
1685 
1686 // NOTE: Dst may be nil!
GetFieldDatanull1687 function TDbfFile.GetFieldData(Column: Integer; DataType: TFieldType;
1688   Src, Dst: Pointer; NativeFormat: boolean): Boolean;
1689 var
1690   TempFieldDef: TDbfFieldDef;
1691 begin
1692   TempFieldDef := TDbfFieldDef(FFieldDefs.Items[Column]);
1693   Result := GetFieldDataFromDef(TempFieldDef, DataType, Src, Dst, NativeFormat);
1694 end;
1695 
1696 // NOTE: Dst may be nil!
GetFieldDataFromDefnull1697 function TDbfFile.GetFieldDataFromDef(AFieldDef: TDbfFieldDef; DataType: TFieldType;
1698   Src, Dst: Pointer; NativeFormat: boolean): Boolean;
1699 var
1700   FieldOffset, FieldSize: Integer;
1701 //  s: string;
1702   ldd, ldm, ldy, lth, ltm, lts: Integer;
1703   date: TDateTime;
1704   timeStamp: TTimeStamp;
1705   asciiContents: boolean;
1706   SrcRecord: Pointer;
1707 
1708 {$ifdef SUPPORT_INT64}
GetInt64FromStrLengthnull1709   function GetInt64FromStrLength(Src: Pointer; Size: Integer; Default: Int64): Int64;
1710   var
1711     endChar: Char;
1712     Code: Integer;
1713   begin
1714     // save Char at pos term. null
1715     endChar := (PChar(Src) + Size)^;
1716     (PChar(Src) + Size)^ := #0;
1717     // convert
1718     Val(PChar(Src), Result, Code);
1719     // check success
1720     if Code <> 0 then Result := Default;
1721     // restore prev. ending Char
1722     (PChar(Src) + Size)^ := endChar;
1723   end;
1724 {$endif}
1725 
1726   procedure CorrectYear(var wYear: Integer);
1727   var wD, wM, wY, CenturyBase: Word;
1728 
1729 {$ifndef DELPHI_5}
1730   // Delphi 3 standard behavior, no change possible
1731   const TwoDigitYearCenturyWindow= 0;
1732 {$endif}
1733 
1734   begin
1735      if wYear >= 100 then
1736        Exit;
1737      DecodeDate(Date, wY, wm, wD);
1738      // use Delphi-Date-Window
1739      CenturyBase := wY{must be CurrentYear} - TwoDigitYearCenturyWindow;
1740      Inc(wYear, CenturyBase div 100 * 100);
1741      if (TwoDigitYearCenturyWindow > 0) and (wYear < CenturyBase) then
1742         Inc(wYear, 100);
1743   end;
1744 
1745   procedure SaveDateToDst;
1746   begin
1747     if not NativeFormat then
1748     begin
1749       // Delphi 5 requests a TDateTime
1750       PDateTime(Dst)^ := date;
1751     end else begin
1752       // Delphi 3 and 4 request a TDateTimeRec
1753       //  date is TTimeStamp.date
1754       //  datetime = msecs == BDE timestamp as we implemented it
1755       if DataType = ftDateTime then
1756       begin
1757         PDateTimeRec(Dst)^.DateTime := date;
1758       end else begin
1759         PLongInt(Dst)^ := DateTimeToTimeStamp(date).Date;
1760       end;
1761     end;
1762   end;
1763 
1764 begin
1765   // test if non-nil source (record buffer)
1766   if Src = nil then
1767   begin
1768     Result := false;
1769     exit;
1770   end;
1771 
1772   // check Dst = nil, called with dst = nil to check empty field
1773   if (FNullField <> nil) and (Dst = nil) and (AFieldDef.NullPosition >= 0) then
1774   begin
1775     result:= not(IsNullFlagSet(Src, AFieldDef, nfNullFlag));
1776     exit;
1777   end;
1778 
1779   FieldOffset := AFieldDef.Offset;
1780   FieldSize := AFieldDef.Size;
1781   SrcRecord := Src;
1782   Src := PChar(Src) + FieldOffset;
1783   asciiContents := false;
1784   Result := true;
1785   // field types that are binary and of which the fieldsize should not be truncated
1786   case AFieldDef.NativeFieldType of
1787     '+', 'I': //Autoincrement, integer
1788       begin
1789         if not(FDbfVersion in [xFoxPro,xVisualFoxPro]) then
1790         begin
1791           Result := Unaligned(PDWord(Src)^) <> 0;
1792           if Result and (Dst <> nil) then
1793           begin
1794             PDWord(Dst)^ := SwapIntBE(Unaligned(PDWord(Src)^));
1795             if Result then
1796               PInteger(Dst)^ := Integer(PDWord(Dst)^ xor $80000000);
1797           end;
1798         end else begin
1799           Result := true;
1800           if Dst <> nil then
1801             PInteger(Dst)^ := SwapIntLE(Unaligned(PInteger(Src)^));
1802         end;
1803       end;
1804     'O':
1805       begin
1806 {$ifdef SUPPORT_INT64}
1807         Result := Unaligned(PInt64(Src)^) <> 0;
1808         if Result and (Dst <> nil) then
1809         begin
1810           SwapInt64BE(Src, Dst);
1811           if PInt64(Dst)^ > 0 then
1812             PInt64(Dst)^ := not PInt64(Dst)^
1813           else
1814             PDouble(Dst)^ := PDouble(Dst)^ * -1;
1815         end;
1816 {$endif}
1817       end;
1818     '@':
1819       begin
1820         Result := (Unaligned(PInteger(Src)^) <> 0) and (Unaligned(PInteger(PChar(Src)+4)^) <> 0);
1821         if Result and (Dst <> nil) then
1822         begin
1823           SwapInt64BE(Src, Dst);
1824           if FDateTimeHandling = dtBDETimeStamp then
1825             date := BDETimeStampToDateTime(PDouble(Dst)^)
1826           else
1827             date := PDateTime(Dst)^;
1828           SaveDateToDst;
1829         end;
1830       end;
1831     'T':
1832       begin
1833         // all binary zeroes -> empty datetime
1834 {$ifdef SUPPORT_INT64}
1835         Result := Unaligned(PInt64(Src)^) <> 0;
1836 {$else}
1837         Result := (Unaligned(PInteger(Src)^) <> 0) or (Unaligned(PInteger(PChar(Src)+4)^) <> 0);
1838 {$endif}
1839         if Result and (Dst <> nil) then
1840         begin
1841           timeStamp.Date := SwapIntLE(Unaligned(PInteger(Src)^)) - JulianDateDelta;
1842           timeStamp.Time := SwapIntLE(Unaligned(PInteger(PChar(Src)+4)^));
1843           date := TimeStampToDateTime(timeStamp);
1844           SaveDateToDst;
1845         end;
1846       end;
1847     'Y': // currency
1848       begin
1849 {$ifdef SUPPORT_INT64}
1850         Result := true;
1851         if Dst <> nil then
1852         begin
1853           PInt64(Dst)^ := SwapIntLE(Unaligned(PInt64(Src)^));
1854           if DataType = ftCurrency then
1855             PDouble(Dst)^ := PInt64(Dst)^ / 10000.0;
1856         end;
1857 {$endif}
1858       end;
1859     'B':  // Foxpro double
1860       begin
1861         if (FDbfVersion in [xFoxPro,xVisualFoxPro]) then
1862         begin
1863         {$ifdef SUPPORT_INT64}
1864           Result := Unaligned(PInt64(Src)^) <> 0;
1865           if Result and (Dst <> nil) then
1866           begin
1867             SwapInt64LE(Src, Dst);
1868             PDouble(Dst)^ := PDouble(Dst)^;
1869           end;
1870         {$endif} end else
1871           asciiContents := true;
1872       end;
1873     'M':
1874       begin
1875         if FieldSize = 4 then
1876         begin
1877           Result := Unaligned(PInteger(Src)^) <> 0;
1878           if Dst <> nil then
1879             PInteger(Dst)^ := SwapIntLE(Unaligned(PInteger(Src)^));
1880         end else
1881           asciiContents := true;
1882       end;
1883     'Q', 'V':  // Visual Foxpro varbinary, varchar
1884       //todo: check if codepage conversion/translation for varchar is needed
1885       begin
1886         if (FDbfVersion in [xVisualFoxPro]) then
1887         begin
1888           Result := true;
1889           // The length byte is only stored if the field is not full
1890           if (Dst <> nil) then
1891           begin
1892             //clear the destination, just in case
1893             Fillchar(pbyte(Dst)^,Fieldsize,0);
1894             if IsNullFlagSet(SrcRecord, AFieldDef, nfVarlengthFlag) then
1895             // so we decrease the fieldsize and let the rest of the code handle it
1896               FieldSize:=(PByte(Src)+FieldSize-1)^;
1897             // If field is not null:
1898             if not(IsNullFlagSet(SrcRecord, AFieldDef, nfNullFlag)) then
1899               if Afielddef.FieldType=ftVarBytes then
1900               begin
1901                 PWord(Dst)^:=Fieldsize; //Store size in destination
1902                 move(Src^, pbyte(Dst+sizeof(Word))^, FieldSize)
1903               end
1904               else
1905                 move(Src^, pbyte(Dst)^, FieldSize)
1906             else
1907               result:=false;
1908           end;
1909         end;
1910       end;
1911     '0':  // Zero not letter 0: bytes
1912       begin
1913         if (Dst <> nil) then
1914         begin
1915           //clear the destination, just in case
1916           Fillchar(pbyte(Dst)^,Fieldsize,0);
1917           move(Src^, pbyte(Dst)^, FieldSize);
1918           Result := true;
1919         end;
1920       end;
1921   else
1922     asciiContents := true;
1923   end;
1924   if asciiContents then
1925   begin
1926     //    SetString(s, PChar(Src) + FieldOffset, FieldSize );
1927     //    s := {TrimStr(s)} TrimRight(s);
1928     // truncate spaces at end by shortening fieldsize
1929     while (FieldSize > 0) and ((PChar(Src) + FieldSize - 1)^ = ' ') do
1930       dec(FieldSize);
1931     // if not string field, truncate spaces at beginning too
1932     if DataType <> ftString then
1933       while (FieldSize > 0) and (PChar(Src)^ = ' ') do
1934       begin
1935         inc(PChar(Src));
1936         dec(FieldSize);
1937       end;
1938     // return if field is empty
1939     Result := FieldSize > 0;
1940     if Result and (Dst <> nil) then     // data not needed if Result= false or Dst=nil
1941       case DataType of
1942       ftBoolean:
1943         begin
1944           // in DBase- FileDescription lowercase t is allowed too
1945           // with asking for Result= true s must be longer then 0
1946           // else an AV occurs, maybe field is NULL
1947           if (PChar(Src)^ = 'T') or (PChar(Src)^ = 't') then
1948             PWord(Dst)^ := 1
1949           else
1950             PWord(Dst)^ := 0;
1951         end;
1952       ftSmallInt:
1953         PSmallInt(Dst)^ := GetIntFromStrLength(Src, FieldSize, 0);
1954 {$ifdef SUPPORT_INT64}
1955       ftLargeInt:
1956         PLargeInt(Dst)^ := GetInt64FromStrLength(Src, FieldSize, 0);
1957 {$endif}
1958       ftInteger:
1959         PInteger(Dst)^ := GetIntFromStrLength(Src, FieldSize, 0);
1960       ftFloat, ftCurrency:
1961         PDouble(Dst)^ := DbfStrToFloat(Src, FieldSize);
1962       ftDate, ftDateTime:
1963         begin
1964           // get year, month, day
1965           ldy := GetIntFromStrLength(PChar(Src) + 0, 4, 1);
1966           ldm := GetIntFromStrLength(PChar(Src) + 4, 2, 1);
1967           ldd := GetIntFromStrLength(PChar(Src) + 6, 2, 1);
1968           //if (ly<1900) or (ly>2100) then ly := 1900;
1969           //Year from 0001 to 9999 is possible
1970           //everyting else is an error, an empty string too
1971           //Do DateCorrection with Delphis possibillities for one or two digits
1972           if (ldy < 100) and (PChar(Src)[0] = #32) and (PChar(Src)[1] = #32) then
1973             CorrectYear(ldy);
1974           try
1975             date := EncodeDate(ldy, ldm, ldd);
1976           except
1977             date := 0;
1978           end;
1979 
1980           // time stored too?
1981           if (AFieldDef.FieldType = ftDateTime) and (DataType = ftDateTime) then
1982           begin
1983             // get hour, minute, second
1984             lth := GetIntFromStrLength(PChar(Src) + 8,  2, 1);
1985             ltm := GetIntFromStrLength(PChar(Src) + 10, 2, 1);
1986             lts := GetIntFromStrLength(PChar(Src) + 12, 2, 1);
1987             // encode
1988             try
1989               date := date + EncodeTime(lth, ltm, lts, 0);
1990             except
1991               date := 0;
1992             end;
1993           end;
1994 
1995           SaveDateToDst;
1996         end;
1997       ftString:
1998         StrLCopy(Dst, Src, FieldSize);
1999     end else begin
2000       case DataType of
2001       ftString:
2002         if Dst <> nil then
2003           PChar(Dst)[0] := #0;
2004       end;
2005     end;
2006   end;
2007 end;
2008 
2009 procedure TDbfFile.UpdateNullField(Buffer: Pointer; AFieldDef: TDbfFieldDef;
2010   Action: TUpdateNullField; WhichField: TNullFieldFlag);
2011 var
2012   NullDst: pbyte;
2013   Mask: byte;
2014 begin
2015   // this field has null setting capability...
2016   // ... but no Super Cow Powers.
2017   case WhichField of
2018   nfNullFlag:
2019     begin
2020       // Find out the byte where the length bit for the field is stored by doing
2021       // NullPosition shr3 (= NullPosition div 8)...
2022       NullDst := PByte(PChar(Buffer) + FNullField.Offset + (AFieldDef.NullPosition shr 3));
2023       // ... get the correct bit in the byte by the equivalent of
2024       // getting the bit number in that byte:
2025       // NullPosition and $7 (=mod 8)...
2026       // and going to the bit value in the byte (shl)
2027       Mask := 1 shl (AFieldDef.NullPosition and $7);
2028     end;
2029   nfVarlengthFlag:
2030     begin
2031       NullDst := PByte(PChar(Buffer) + FNullField.Offset + (AFieldDef.VarLengthPosition shr 3));
2032       Mask := 1 shl (AFieldDef.VarLengthPosition and $7);
2033     end;
2034   end;
2035 
2036   if Action = unfSet then
2037   begin
2038     // set flag
2039     NullDst^ := NullDst^ or Mask;
2040   end else begin //unfClear
2041     // clear flag
2042     NullDst^ := NullDst^ and not Mask;
2043   end;
2044 end;
2045 
2046 procedure TDbfFile.SetFieldData(Column: Integer; DataType: TFieldType;
2047   Src, Dst: Pointer; NativeFormat: boolean);
2048 const
2049   IsBlobFieldToPadChar: array[Boolean] of Char = (#32, '0');
2050   SrcNilToUpdateNullField: array[boolean] of TUpdateNullField = (unfClear, unfSet);
2051 var
2052   DstRecord: Pointer;
2053   FieldSize,FieldPrec: Integer;
2054   TempFieldDef: TDbfFieldDef;
2055   Len: Integer;
2056   IntValue: dword;
2057   year, month, day: Word;
2058   hour, minute, sec, msec: Word;
2059   date: TDateTime;
2060   timeStamp: TTimeStamp;
2061   asciiContents: boolean;
2062 
2063   procedure LoadDateFromSrc;
2064   begin
2065     if not NativeFormat then
2066     begin
2067       // Delphi 5, new format, passes a TDateTime
2068       date := PDateTime(Src)^;
2069     end else begin
2070       // Delphi 3 and 4, old "native" format, pass a TDateTimeRec with a time stamp
2071       //  date = integer
2072       //  datetime = msecs == BDETimeStampToDateTime as we implemented it
2073       if DataType = ftDateTime then
2074       begin
2075         date := PDouble(Src)^;
2076       end else begin
2077         timeStamp.Time := 0;
2078         timeStamp.Date := PLongInt(Src)^;
2079         date := TimeStampToDateTime(timeStamp);
2080       end;
2081     end;
2082   end;
2083 
2084 begin
2085   TempFieldDef := TDbfFieldDef(FFieldDefs.Items[Column]);
2086   FieldSize := TempFieldDef.Size;
2087   FieldPrec := TempFieldDef.Precision;
2088 
2089   DstRecord:=Dst; //beginning of record
2090   Dst := PChar(Dst) + TempFieldDef.Offset; //beginning of field
2091 
2092   // if src = nil then write empty field
2093   // symmetry with above loading code
2094 
2095   // Visual Foxpro has special _nullfield for flagging fields as `null'
2096   if (FNullField <> nil) and (TempFieldDef.NullPosition >= 0) then
2097     UpdateNullField(DstRecord, TempFieldDef, SrcNilToUpdateNullField[Src = nil],nfNullFlag);
2098 
2099   // copy field data to record buffer
2100   asciiContents := false;
2101   case TempFieldDef.NativeFieldType of
2102     '+', 'I' {autoincrement, integer}:
2103       begin
2104         if not(FDbfVersion in [xFoxPro,xVisualFoxPro]) then
2105         begin
2106           if Src = nil then
2107             IntValue := 0
2108           else
2109             IntValue := PDWord(Src)^ xor $80000000;
2110           Unaligned(PDWord(Dst)^) := SwapIntBE(IntValue);
2111         end else begin
2112           if Src = nil then
2113             Unaligned(PDWord(Dst)^) := 0
2114           else
2115             Unaligned(PDWord(Dst)^) := SwapIntLE(PDWord(Src)^);
2116         end;
2117       end;
2118     'O':
2119       begin
2120 {$ifdef SUPPORT_INT64}
2121         if Src = nil then
2122         begin
2123           Unaligned(PInt64(Dst)^) := 0;
2124         end else begin
2125           if PDouble(Src)^ < 0 then
2126             Unaligned(PInt64(Dst)^) := not PInt64(Src)^
2127           else
2128             Unaligned(PDouble(Dst)^) := (PDouble(Src)^) * -1;
2129           SwapInt64BE(Dst, Dst);
2130         end;
2131 {$endif}
2132       end;
2133     '@':
2134       begin
2135         if Src = nil then
2136         begin
2137 {$ifdef SUPPORT_INT64}
2138           Unaligned(PInt64(Dst)^) := 0;
2139 {$else}
2140           Unaligned(PInteger(Dst)^) := 0;
2141           Unaligned(PInteger(PChar(Dst)+4)^) := 0;
2142 {$endif}
2143         end else begin
2144           LoadDateFromSrc;
2145           if FDateTimeHandling = dtBDETimeStamp then
2146             date := DateTimeToBDETimeStamp(date);
2147           SwapInt64BE(@date, Dst);
2148         end;
2149       end;
2150     'T':
2151       begin
2152         // all binary zeroes -> empty datetime
2153         if Src = nil then
2154         begin
2155 {$ifdef SUPPORT_INT64}
2156           Unaligned(PInt64(Dst)^) := 0;
2157 {$else}
2158           Unaligned(PInteger(Dst)^) := 0;
2159           Unaligned(PInteger(PChar(Dst)+4)^) := 0;
2160 {$endif}
2161         end else begin
2162           LoadDateFromSrc;
2163           timeStamp := DateTimeToTimeStamp(date);
2164           Unaligned(PInteger(Dst)^) := SwapIntLE(timeStamp.Date + JulianDateDelta);
2165           Unaligned(PInteger(PChar(Dst)+4)^) := SwapIntLE(timeStamp.Time);
2166         end;
2167       end;
2168     'Y':
2169       begin
2170 {$ifdef SUPPORT_INT64}
2171         if Src = nil then
2172         begin
2173           Unaligned(PInt64(Dst)^) := 0;
2174         end else begin
2175           case DataType of
2176             ftCurrency:
2177               Unaligned(PInt64(Dst)^) := Trunc(PDouble(Src)^ * 10000);
2178             ftBCD:
2179               Unaligned(PCurrency(Dst)^) := PCurrency(Src)^;
2180           end;
2181           SwapInt64LE(Dst, Dst);
2182         end;
2183 {$endif}
2184       end;
2185     'B' {(Visual) FoxPro Double}:
2186       begin
2187         if DbfVersion in [xFoxPro,xVisualFoxPro] then
2188         begin
2189           if Src = nil then
2190             Unaligned(PDouble(Dst)^) := 0
2191           else
2192             SwapInt64LE(Src, Dst);
2193         end else
2194           asciiContents := true;
2195       end;
2196     'M':
2197       begin
2198         if FieldSize = 4 then
2199         begin
2200           if Src = nil then
2201             Unaligned(PInteger(Dst)^) := 0
2202           else
2203             Unaligned(PInteger(Dst)^) := SwapIntLE(PInteger(Src)^);
2204         end else
2205           asciiContents := true;
2206       end;
2207     'Q': //Visual FoxPro varbinary
2208       begin
2209         // copy data, and update varlength flag/varlength byte in field data
2210         if Src = nil then
2211           Len := 0
2212         else
2213         begin
2214           Len := PWord(Src)^;
2215           if Len > FieldSize then
2216             Len := FieldSize;
2217         end;
2218         if Len < FieldSize then
2219         begin
2220           // Clear flag and store actual size byte in last data byte
2221           PByte(PChar(Dst)+TempFieldDef.Size-1)^:=Len;
2222           UpdateNullField(DstRecord, TempFieldDef, unfSet, nfVarlengthFlag);
2223         end
2224         else
2225         begin
2226           UpdateNullField(DstRecord, TempFieldDef, unfClear, nfVarlengthFlag);
2227         end;
2228 
2229         Move((Src+sizeof(word))^, Dst^, Len);
2230         // fill remaining data area with spaces, keeping room for size indicator if needed
2231         if Len=FieldSize then
2232           FillChar((PChar(Dst)+Len)^, FieldSize - Len, ' ')
2233         else
2234           FillChar((PChar(Dst)+Len)^, FieldSize - Len - 1, ' ');
2235       end;
2236     'V': //Visual FoxPro varchar
2237       begin
2238         // copy data, and update varlength flag/varlength byte in field data
2239         Len := StrLen(Src);
2240         if Len > FieldSize then
2241           Len := FieldSize;
2242         if Len < FieldSize then
2243         begin
2244           // Clear flag and store actual size byte in last data byte
2245           PByte(PChar(Dst)+TempFieldDef.Size-1)^:=Len;
2246           UpdateNullField(DstRecord, TempFieldDef, unfSet, nfVarlengthFlag);
2247         end
2248         else
2249         begin
2250           UpdateNullField(DstRecord, TempFieldDef, unfClear, nfVarlengthFlag);
2251         end;
2252 
2253         Move(Src^, Dst^, Len);
2254         // fill remaining data area with spaces, keeping room for size indicator if needed
2255         if Len=FieldSize then
2256           FillChar((PChar(Dst)+Len)^, FieldSize - Len, ' ')
2257         else
2258           FillChar((PChar(Dst)+Len)^, FieldSize - Len - 1, ' ');
2259       end
2260   else
2261     asciiContents := true;
2262   end;
2263   if asciiContents then
2264   begin
2265     if Src = nil then
2266     begin
2267       FillChar(Dst^, FieldSize, ' ');
2268     end else begin
2269       case DataType of
2270         ftBoolean:
2271           begin
2272             if PWord(Src)^ <> 0 then
2273               PChar(Dst)^ := 'T'
2274             else
2275               PChar(Dst)^ := 'F';
2276           end;
2277         ftSmallInt:
2278           GetStrFromInt_Width(PSmallInt(Src)^, FieldSize, PChar(Dst), #32);
2279 {$ifdef SUPPORT_INT64}
2280         ftLargeInt:
2281           GetStrFromInt64_Width(PLargeInt(Src)^, FieldSize, PChar(Dst), #32);
2282 {$endif}
2283         ftFloat, ftCurrency:
2284           FloatToDbfStr(PDouble(Src)^, FieldSize, FieldPrec, PChar(Dst));
2285         ftInteger:
2286           GetStrFromInt_Width(PInteger(Src)^, FieldSize, PChar(Dst),
2287             IsBlobFieldToPadChar[TempFieldDef.IsBlob]);
2288         ftDate, ftDateTime:
2289           begin
2290             LoadDateFromSrc;
2291             // decode
2292             DecodeDate(date, year, month, day);
2293             // format is yyyymmdd
2294             GetStrFromInt_Width(year,  4, PChar(Dst),   '0');
2295             GetStrFromInt_Width(month, 2, PChar(Dst)+4, '0');
2296             GetStrFromInt_Width(day,   2, PChar(Dst)+6, '0');
2297             // do time too if datetime
2298             if DataType = ftDateTime then
2299             begin
2300               DecodeTime(date, hour, minute, sec, msec);
2301               // format is hhmmss
2302               GetStrFromInt_Width(hour,   2, PChar(Dst)+8,  '0');
2303               GetStrFromInt_Width(minute, 2, PChar(Dst)+10, '0');
2304               GetStrFromInt_Width(sec,    2, PChar(Dst)+12, '0');
2305             end;
2306           end;
2307         ftString:
2308           begin
2309             // copy data
2310             Len := StrLen(Src);
2311             if Len > FieldSize then
2312               Len := FieldSize;
2313             Move(Src^, Dst^, Len);
2314             // fill remaining space with spaces
2315             FillChar((PChar(Dst)+Len)^, FieldSize - Len, ' ');
2316           end;
2317       end;  // case datatype
2318     end;
2319   end;
2320 end;
2321 
2322 procedure TDbfFile.InitDefaultBuffer;
2323 var
2324   lRecordSize: integer;
2325   TempFieldDef: TDbfFieldDef;
2326   I: Integer;
2327 begin
2328   lRecordSize := PDbfHdr(Header)^.RecordSize;
2329   // clear buffer (assume all string, fix specific fields later)
2330   //   note: Self.RecordSize is used for reading fielddefs too
2331   GetMem(FDefaultBuffer, lRecordSize+1);
2332   FillChar(FDefaultBuffer^, lRecordSize, ' ');
2333 
2334   // set nullflags field so that all fields are null (and var* fields marked as full)
2335   if FNullField <> nil then
2336     FillChar(PChar(FDefaultBuffer+FNullField.Offset)^, FNullField.Size, $FF);
2337 
2338   // check binary and default fields
2339   for I := 0 to FFieldDefs.Count-1 do
2340   begin
2341     TempFieldDef := FFieldDefs.Items[I];
2342     // binary (non-text) field? (foxpro memo fields are binary, but dbase not)
2343     if (TempFieldDef.NativeFieldType in ['I', 'O', '@', '+', '0', 'W', 'Y'])
2344         or ((TempFieldDef.NativeFieldType = 'M') and (TempFieldDef.Size = 4) {Visual FoxPro?}) then
2345       FillChar(PChar(FDefaultBuffer+TempFieldDef.Offset)^, TempFieldDef.Size, 0);
2346     // copy default value?
2347     if TempFieldDef.HasDefault then
2348     begin
2349       Move(TempFieldDef.DefaultBuf[0], FDefaultBuffer[TempFieldDef.Offset], TempFieldDef.Size);
2350       // clear the null flag, this field has a value
2351       if FNullField <> nil then
2352         UpdateNullField(FDefaultBuffer, TempFieldDef, unfClear, nfNullFlag);
2353       // Check for varbinary/varchar and if default matches it, then mark field as full
2354       if (TempFieldDef.VarLengthPosition>=0) then
2355         if (strlen(FDefaultBuffer)>=TempFieldDef.Size) then
2356           UpdateNullField(FDefaultBuffer, TempFieldDef, unfClear, nfVarlengthFlag)
2357         else
2358           begin
2359             // Set flag and store actual size byte in last data byte
2360             UpdateNullField(FDefaultBuffer, TempFieldDef, unfSet, nfVarlengthFlag);
2361             //todo: verify pointer use
2362             PByte(PChar(FDefaultBuffer)+TempFieldDef.Size)^:=strlen(FDefaultBuffer);
2363           end;
2364     end;
2365   end;
2366 end;
2367 
2368 procedure TDbfFile.InitRecord(DestBuf: TRecordBuffer);
2369 begin
2370   if FDefaultBuffer = nil then
2371     InitDefaultBuffer;
2372   Move(FDefaultBuffer^, DestBuf^, RecordSize);
2373 end;
2374 
2375 procedure TDbfFile.ApplyAutoIncToBuffer(DestBuf: TRecordBuffer);
2376 var
2377   TempFieldDef: TDbfFieldDef;
2378   I, NextVal, lAutoIncOffset: {LongWord} Cardinal;    {Delphi 3 does not know LongWord?}
2379 begin
2380   if FAutoIncPresent then
2381   begin
2382     // if shared, reread header to find new autoinc values
2383     if NeedLocks then
2384     begin
2385       // lock header so nobody else can use this value
2386       LockPage(0, true);
2387     end;
2388 
2389     // find autoinc fields
2390     for I := 0 to FFieldDefs.Count-1 do
2391     begin
2392       TempFieldDef := FFieldDefs.Items[I];
2393       if (DbfVersion=xBaseVII) and
2394         (TempFieldDef.NativeFieldType = '+') then
2395       begin
2396         // read current auto inc, from header or field, depending on sharing
2397         lAutoIncOffset := sizeof(rDbfHdr) + sizeof(rEndFixedHdrVII) +
2398           FieldDescVII_AutoIncOffset + I * sizeof(rFieldDescVII);
2399         if NeedLocks then
2400         begin
2401           ReadBlock(@NextVal, 4, lAutoIncOffset);
2402           NextVal := SwapIntLE(NextVal);
2403         end else
2404           NextVal := TempFieldDef.AutoInc;
2405         // store to buffer, positive = high bit on, so flip it
2406         PCardinal(DestBuf+TempFieldDef.Offset)^ := SwapIntBE(NextVal or $80000000);
2407         // increase
2408         Inc(NextVal);
2409         TempFieldDef.AutoInc := NextVal;
2410         // write new value to header buffer
2411         PCardinal(FHeader+lAutoIncOffset)^ := SwapIntLE(NextVal);
2412       end
2413       else //No DBaseVII
2414       if (DbfVersion=xVisualFoxPro) and
2415         (TempFieldDef.AutoIncStep<>0) then
2416       begin
2417         // read current auto inc from field header
2418         NextVal:=TempFieldDef.AutoInc; //todo: is this correct
2419         PCardinal(DestBuf+TempFieldDef.Offset)^ := SwapIntBE(NextVal); //todo: is swapintbe correct?
2420         // Increase with step size
2421         NextVal:=NextVal+TempFieldDef.AutoIncStep;
2422         // write new value back
2423         TempFieldDef.AutoInc:=NextVal;
2424       end;
2425     end;
2426 
2427     // write modified header (new autoinc values) to file
2428     WriteHeader;
2429 
2430     // release lock if locked
2431     if NeedLocks then
2432       UnlockPage(0);
2433   end;
2434 end;
2435 
2436 procedure TDbfFile.TryExclusive;
2437 var
2438   I: Integer;
2439 begin
2440   inherited;
2441 
2442   // exclusive succeeded? open index & memo exclusive too
2443   if Mode in [pfMemoryCreate..pfExclusiveOpen] then
2444   begin
2445     // indexes
2446     for I := 0 to FIndexFiles.Count - 1 do
2447       TPagedFile(FIndexFiles[I]).TryExclusive;
2448     // memo
2449     if FMemoFile <> nil then
2450       FMemoFile.TryExclusive;
2451   end;
2452 end;
2453 
2454 procedure TDbfFile.EndExclusive;
2455 var
2456   I: Integer;
2457 begin
2458   // end exclusive on index & memo too
2459   for I := 0 to FIndexFiles.Count - 1 do
2460     TPagedFile(FIndexFiles[I]).EndExclusive;
2461   // memo
2462   if FMemoFile <> nil then
2463     FMemoFile.EndExclusive;
2464   // dbf file
2465   inherited;
2466 end;
2467 
2468 procedure TDbfFile.OpenIndex(IndexName, IndexField: string; CreateIndex: Boolean; Options: TIndexOptions);
2469   //
2470   // assumes IndexName is not empty
2471   //
2472 const
2473   // memcr, memop, excr, exopen, rwcr, rwopen, rdonly
2474   IndexOpenMode: array[boolean, pfMemoryCreate..pfReadOnly] of TPagedFileMode =
2475    ((pfMemoryCreate, pfMemoryCreate, pfExclusiveOpen, pfExclusiveOpen, pfReadWriteOpen, pfReadWriteOpen,
2476      pfReadOnly),
2477     (pfMemoryCreate, pfMemoryCreate, pfExclusiveCreate, pfExclusiveCreate, pfReadWriteCreate, pfReadWriteCreate,
2478      pfReadOnly));
2479 var
2480   lIndexFile: TIndexFile;
2481   lIndexFileName: string;
2482   createMdxFile: Boolean;
2483   tempExclusive: boolean;
2484   addedIndexFile: Integer;
2485   addedIndexName: Integer;
2486 begin
2487   // init
2488   addedIndexFile := -1;
2489   addedIndexName := -1;
2490   createMdxFile := false;
2491   // index already opened?
2492   lIndexFile := GetIndexByName(IndexName);
2493   if (lIndexFile <> nil) and (lIndexFile = FMdxFile) and CreateIndex then
2494   begin
2495     // index already exists in MDX file
2496     // delete it to save space, this causes a repage
2497     FMdxFile.DeleteIndex(IndexName);
2498     // index no longer exists
2499     lIndexFile := nil;
2500   end;
2501   if (lIndexFile = nil) and (IndexName <> EmptyStr) then
2502   begin
2503     // check if no extension, then create MDX index
2504     if Length(ExtractFileExt(IndexName)) = 0 then
2505     begin
2506       // check if mdx index already opened
2507       if FMdxFile <> nil then
2508       begin
2509         lIndexFileName := EmptyStr;
2510         lIndexFile := FMdxFile;
2511       end else begin
2512         lIndexFileName := ChangeFileExt(FileName, '.mdx');
2513         createMdxFile := true;
2514       end;
2515     end else begin
2516       lIndexFileName := IndexName;
2517     end;
2518     // do we need to open / create file?
2519     if lIndexFileName <> EmptyStr then
2520     begin
2521       // try to open / create the file
2522       lIndexFile := TIndexFile.Create(Self);
2523       lIndexFile.FileName := lIndexFileName;
2524       lIndexFile.Mode := IndexOpenMode[CreateIndex, Mode];
2525       lIndexFile.AutoCreate := CreateIndex or (Length(IndexField) > 0);
2526       if (Mode in [pfMemoryOpen,pfMemoryCreate]) then
2527       begin
2528         if FIndexStream = nil then
2529           FIndexStream := TMemoryStream.Create;
2530         lIndexFile.Stream := FIndexStream;
2531       end;
2532       lIndexFile.CodePage := UseCodePage;
2533       lIndexFile.OnLocaleError := FOnLocaleError;
2534       lIndexFile.Open;
2535       // index file ready for use?
2536       if not lIndexFile.ForceClose then
2537       begin
2538         // if we had to create the index, store that info
2539         CreateIndex := lIndexFile.FileCreated;
2540         // check if trying to create empty index
2541         if CreateIndex and (IndexField = EmptyStr) then
2542         begin
2543           FreeAndNil(lIndexFile);
2544           CreateIndex := false;
2545           createMdxFile := false;
2546         end else begin
2547           // add new index file to list
2548           addedIndexFile := FIndexFiles.Add(lIndexFile);
2549         end;
2550         // created accompanying mdx file?
2551         if createMdxFile then
2552           FMdxFile := lIndexFile;
2553       end else begin
2554         // asked to close! close file
2555         FreeAndNil(lIndexFile);
2556       end;
2557     end;
2558 
2559     // check if file succesfully opened
2560     if lIndexFile <> nil then
2561     begin
2562       // add index to list
2563       addedIndexName := FIndexNames.AddObject(IndexName, lIndexFile);
2564     end;
2565   end;
2566   // create it or open it?
2567   if lIndexFile <> nil then
2568   begin
2569     if not CreateIndex then
2570       if lIndexFile = FMdxFile then
2571         CreateIndex := lIndexFile.IndexOf(IndexName) < 0;
2572     if CreateIndex then
2573     begin
2574       // try get exclusive mode
2575       tempExclusive := IsSharedAccess;
2576       if tempExclusive then TryExclusive;
2577       // always uppercase index expression
2578       IndexField := AnsiUpperCase(IndexField);
2579       try
2580         try
2581           // create index if asked
2582           lIndexFile.CreateIndex(IndexField, IndexName, Options);
2583           // add all records
2584           PackIndex(lIndexFile, IndexName);
2585           // if we wanted to open index readonly, but we created it, then reopen
2586           if Mode = pfReadOnly then
2587           begin
2588             lIndexFile.CloseFile;
2589             lIndexFile.Mode := pfReadOnly;
2590             lIndexFile.OpenFile;
2591           end;
2592           // if mdx file just created, write changes to dbf header
2593           // set MDX flag to true
2594           PDbfHdr(Header)^.MDXFlag := 1;
2595           WriteHeader;
2596         except
2597           on EDbfError do
2598           begin
2599             // :-( need to undo 'damage'....
2600             // remove index from list(s) if just added
2601             if addedIndexFile >= 0 then
2602               FIndexFiles.Delete(addedIndexFile);
2603             if addedIndexName >= 0 then
2604               FIndexNames.Delete(addedIndexName);
2605             // if no file created, do not destroy!
2606             if addedIndexFile >= 0 then
2607             begin
2608               lIndexFile.Close;
2609               Sysutils.DeleteFile(lIndexFileName);
2610               if FMdxFile = lIndexFile then
2611                 FMdxFile := nil;
2612               lIndexFile.Free;
2613             end;
2614             raise;
2615           end;
2616         end;
2617       finally
2618         // return to previous mode
2619         if tempExclusive then EndExclusive;
2620       end;
2621     end;
2622   end;
2623 end;
2624 
2625 procedure TDbfFile.PackIndex(lIndexFile: TIndexFile; AIndexName: string);
2626 var
2627   prevMode: TIndexUpdateMode;
2628   prevIndex: string;
2629   cur, last: Integer;
2630 {$ifdef USE_CACHE}
2631   prevCache: Integer;
2632 {$endif}
2633 begin
2634   // save current mode in case we change it
2635   prevMode := lIndexFile.UpdateMode;
2636   prevIndex := lIndexFile.IndexName;
2637   // check if index specified
2638   if Length(AIndexName) > 0 then
2639   begin
2640     // only pack specified index, not all
2641     lIndexFile.IndexName := AIndexName;
2642     lIndexFile.ClearIndex;
2643     lIndexFile.UpdateMode := umCurrent;
2644   end else begin
2645     lIndexFile.IndexName := EmptyStr;
2646     lIndexFile.Clear;
2647     lIndexFile.UpdateMode := umAll;
2648   end;
2649   // prepare update
2650   cur := 1;
2651   last := RecordCount;
2652 {$ifdef USE_CACHE}
2653   BufferAhead := true;
2654   prevCache := lIndexFile.CacheSize;
2655   lIndexFile.CacheSize := GetFreeMemory;
2656   if lIndexFile.CacheSize < 16384 * 1024 then
2657     lIndexFile.CacheSize := 16384 * 1024;
2658 {$endif}
2659   try
2660     try
2661       while cur <= last do
2662       begin
2663         ReadRecord(cur, FPrevBuffer);
2664         lIndexFile.Insert(cur, FPrevBuffer);
2665         inc(cur);
2666       end;
2667     except
2668       on E: EDbfError do
2669       begin
2670         lIndexFile.DeleteIndex(lIndexFile.IndexName);
2671         raise;
2672       end;
2673     end;
2674   finally
2675     // restore previous mode
2676 {$ifdef USE_CACHE}
2677     BufferAhead := false;
2678     lIndexFile.BufferAhead := true;
2679 {$endif}
2680     lIndexFile.Flush;
2681 {$ifdef USE_CACHE}
2682     lIndexFile.BufferAhead := false;
2683     lIndexFile.CacheSize := prevCache;
2684 {$endif}
2685     lIndexFile.UpdateMode := prevMode;
2686     lIndexFile.IndexName := prevIndex;
2687   end;
2688 end;
2689 
2690 procedure TDbfFile.RepageIndex(AIndexFile: string);
2691 var
2692   lIndexNo: Integer;
2693 begin
2694   // DBF MDX index?
2695   if Length(AIndexFile) = 0 then
2696   begin
2697     if FMdxFile <> nil then
2698     begin
2699       // repage attached mdx
2700       FMdxFile.RepageFile;
2701     end;
2702   end else begin
2703     // search index file
2704     lIndexNo := FIndexNames.IndexOf(AIndexFile);
2705     // index found?
2706     if lIndexNo >= 0 then
2707       TIndexFile(FIndexNames.Objects[lIndexNo]).RepageFile;
2708   end;
2709 end;
2710 
2711 procedure TDbfFile.CompactIndex(AIndexFile: string);
2712 var
2713   lIndexNo: Integer;
2714 begin
2715   // DBF MDX index?
2716   if Length(AIndexFile) = 0 then
2717   begin
2718     if FMdxFile <> nil then
2719     begin
2720       // repage attached mdx
2721       FMdxFile.CompactFile;
2722     end;
2723   end else begin
2724     // search index file
2725     lIndexNo := FIndexNames.IndexOf(AIndexFile);
2726     // index found?
2727     if lIndexNo >= 0 then
2728       TIndexFile(FIndexNames.Objects[lIndexNo]).CompactFile;
2729   end;
2730 end;
2731 
2732 procedure TDbfFile.CloseIndex(AIndexName: string);
2733 var
2734   lIndexNo: Integer;
2735   lIndex: TIndexFile;
2736 begin
2737   // search index file
2738   lIndexNo := FIndexNames.IndexOf(AIndexName);
2739   // don't close mdx file
2740   if (lIndexNo >= 0) then
2741   begin
2742     // get index pointer
2743     lIndex := TIndexFile(FIndexNames.Objects[lIndexNo]);
2744     if (lIndex <> FMdxFile) then
2745     begin
2746       // close file
2747       lIndex.Free;
2748       // remove from lists
2749       FIndexFiles.Remove(lIndex);
2750       FIndexNames.Delete(lIndexNo);
2751       // was this the current index?
2752       if (FCurIndex = lIndexNo) then
2753       begin
2754         FCurIndex := -1;
2755         //FCursor := FDbfCursor;
2756       end;
2757     end;
2758   end;
2759 end;
2760 
DeleteIndexnull2761 function TDbfFile.DeleteIndex(const AIndexName: string): Boolean;
2762 var
2763   lIndexNo: Integer;
2764   lIndex: TIndexFile;
2765   lFileName: string;
2766 begin
2767   // search index file
2768   lIndexNo := FIndexNames.IndexOf(AIndexName);
2769   Result := lIndexNo >= 0;
2770   // found index?
2771   if Result then
2772   begin
2773     // can only delete indexes from MDX files
2774     lIndex := TIndexFile(FIndexNames.Objects[lIndexNo]);
2775     if lIndex = FMdxFile then
2776     begin
2777       lIndex.DeleteIndex(AIndexName);
2778       // remove it from the list
2779       FIndexNames.Delete(lIndexNo);
2780       // no more MDX indexes?
2781       lIndexNo := FIndexNames.IndexOfObject(FMdxFile);
2782       if lIndexNo = -1 then
2783       begin
2784         // no MDX indexes left
2785         lIndexNo := FIndexFiles.IndexOf(FMdxFile);
2786         if lIndexNo >= 0 then
2787           FIndexFiles.Delete(lIndexNo);
2788         lFileName := FMdxFile.FileName;
2789         FreeAndNil(FMdxFile);
2790         // erase file
2791         Sysutils.DeleteFile(lFileName);
2792         // clear mdx flag
2793         PDbfHdr(Header)^.MDXFlag := 0;
2794         WriteHeader;
2795       end;
2796     end else begin
2797       // close index first
2798       CloseIndex(AIndexName);
2799       // delete file from disk
2800       SysUtils.DeleteFile(AIndexName);
2801     end;
2802   end;
2803 end;
2804 
Insertnull2805 function TDbfFile.Insert(Buffer: TRecordBuffer): integer;
2806 type
2807   TErrorContext = (ecNone, ecInsert, ecWriteIndex, ecWriteDbf);
2808 var
2809   newRecord: Integer;
2810   lIndex: TIndexFile;
2811 
2812   procedure RollBackIndexesAndRaise(Count: Integer; ErrorContext: TErrorContext);
2813   var
2814     errorMsg: string;
2815     I: Integer;
2816   begin
2817     // rollback committed indexes
2818     for I := 0 to Count-1 do
2819     begin
2820       lIndex := TIndexFile(FIndexFiles.Items[I]);
2821       lIndex.Delete(newRecord, Buffer);
2822     end;
2823 
2824     // reset any dbf file error
2825     ResetError;
2826 
2827     // if part of indexes committed -> always index error msg
2828     // if error while rolling back index -> index error msg
2829     case ErrorContext of
2830       ecInsert: begin TIndexFile(FIndexFiles.Items[Count]).InsertError; exit; end;
2831       ecWriteIndex: errorMsg := STRING_WRITE_INDEX_ERROR;
2832       ecWriteDbf: errorMsg := STRING_WRITE_ERROR;
2833     end;
2834     raise EDbfWriteError.Create(errorMsg);
2835   end;
2836 
2837 var
2838   I: Integer;
2839   error: TErrorContext;
2840 begin
2841   // get new record index
2842   Result := 0;
2843   newRecord := RecordCount+1;
2844   // lock record so we can write data
2845   while not LockPage(newRecord, false) do
2846     Inc(newRecord);
2847   // write autoinc value
2848   ApplyAutoIncToBuffer(Buffer);
2849   error := ecNone;
2850   I := 0;
2851   while I < FIndexFiles.Count do
2852   begin
2853     lIndex := TIndexFile(FIndexFiles.Items[I]);
2854     if not lIndex.Insert(newRecord, Buffer) then
2855       error := ecInsert;
2856     if lIndex.WriteError then
2857       error := ecWriteIndex;
2858     if error <> ecNone then
2859     begin
2860       // if there's an index write error, I shouldn't
2861       // try to write the dbf header and the new record,
2862       // but raise an exception right away
2863       UnlockPage(newRecord);
2864       RollBackIndexesAndRaise(I, ecWriteIndex);
2865     end;
2866     Inc(I);
2867   end;
2868 
2869   // indexes ok -> continue inserting
2870   // update header record count
2871   LockPage(0, true);
2872   // read current header
2873   ReadHeader;
2874   // increase current record count
2875   Inc(PDbfHdr(Header)^.RecordCount);
2876   // write header to disk
2877   WriteHeader;
2878   // done with header
2879   UnlockPage(0);
2880 
2881   if WriteError then
2882   begin
2883     // couldn't write header, so I shouldn't
2884     // even try to write the record.
2885     //
2886     // At this point I should "roll back"
2887     // the already written index records.
2888     // if this fails, I'm in deep trouble!
2889     UnlockPage(newRecord);
2890     RollbackIndexesAndRaise(FIndexFiles.Count, ecWriteDbf);
2891   end;
2892 
2893   // write locking info
2894   if FLockField <> nil then
2895     WriteLockInfo(Buffer);
2896   // write buffer to disk
2897   WriteRecord(newRecord, Buffer);
2898   // done updating, unlock
2899   UnlockPage(newRecord);
2900   // error occurred while writing?
2901   if WriteError then
2902   begin
2903     // The record couldn't be written, so
2904     // the written index records and the
2905     // change to the header have to be
2906     // rolled back
2907     LockPage(0, true);
2908     ReadHeader;
2909     Dec(PDbfHdr(Header)^.RecordCount);
2910     WriteHeader;
2911     UnlockPage(0);
2912     // roll back indexes, too
2913     RollbackIndexesAndRaise(FIndexFiles.Count, ecWriteDbf);
2914   end else
2915     Result := newRecord;
2916 end;
2917 
2918 procedure TDbfFile.WriteLockInfo(Buffer: TRecordBuffer);
2919 //
2920 // *) assumes FHasLockField = true
2921 //
2922 var
2923   year, month, day, hour, minute, sec, msec: Word;
2924   lockoffset: integer;
2925 begin
2926   // increase change count
2927   lockoffset := FLockField.Offset;
2928   Inc(PWord(Buffer+lockoffset)^);
2929   // set time
2930   DecodeDate(Now(), year, month, day);
2931   DecodeTime(Now(), hour, minute, sec, msec);
2932   Buffer[lockoffset+2] := TRecordBufferBaseType(hour);
2933   Buffer[lockoffset+3] := TRecordBufferBaseType(minute);
2934   Buffer[lockoffset+4] := TRecordBufferBaseType(sec);
2935   // set date
2936   Buffer[lockoffset+5] := TRecordBufferBaseType(year - 1900);
2937   Buffer[lockoffset+6] := TRecordBufferBaseType(month);
2938   Buffer[lockoffset+7] := TRecordBufferBaseType(day);
2939   // set name
2940   FillChar(Buffer[lockoffset+8], FLockField.Size-8, ' ');
2941   Move(DbfGlobals.UserName[1], Buffer[lockoffset+8], FLockUserLen);
2942 end;
2943 
2944 procedure TDbfFile.LockRecord(RecNo: Integer; Buffer: TRecordBuffer);
2945 begin
2946   if LockPage(RecNo, false) then
2947   begin
2948     // reread data
2949     ReadRecord(RecNo, Buffer);
2950     // store previous data for updating indexes
2951     Move(Buffer^, FPrevBuffer^, RecordSize);
2952     // lock succeeded, update lock info, if field present
2953     if FLockField <> nil then
2954     begin
2955       // update buffer
2956       WriteLockInfo(Buffer);
2957       // write to disk
2958       WriteRecord(RecNo, Buffer);
2959     end;
2960   end else
2961     raise EDbfError.Create(STRING_RECORD_LOCKED);
2962 end;
2963 
2964 procedure TDbfFile.UnlockRecord(RecNo: Integer; Buffer: TRecordBuffer);
2965 var
2966   I: Integer;
2967   lIndex, lErrorIndex: TIndexFile;
2968 begin
2969   // update indexes, possible key violation
2970   I := 0;
2971   while I < FIndexFiles.Count do
2972   begin
2973     lIndex := TIndexFile(FIndexFiles.Items[I]);
2974     if not lIndex.Update(RecNo, FPrevBuffer, Buffer) then
2975     begin
2976       // error -> rollback
2977       lErrorIndex := lIndex;
2978       while I > 0 do
2979       begin
2980         Dec(I);
2981         lIndex := TIndexFile(FIndexFiles.Items[I]);
2982         lIndex.Update(RecNo, Buffer, FPrevBuffer);
2983       end;
2984       lErrorIndex.InsertError;
2985     end;
2986     Inc(I);
2987   end;
2988   // write new record buffer, all keys ok
2989   WriteRecord(RecNo, Buffer);
2990   // done updating, unlock
2991   UnlockPage(RecNo);
2992 end;
2993 
2994 procedure TDbfFile.RecordDeleted(RecNo: Integer; Buffer: TRecordBuffer);
2995 var
2996   I: Integer;
2997   lIndex: TIndexFile;
2998 begin
2999   // notify indexes: record deleted
3000   for I := 0 to FIndexFiles.Count - 1 do
3001   begin
3002     lIndex := TIndexFile(FIndexFiles.Items[I]);
3003     lIndex.RecordDeleted(RecNo, Buffer);
3004   end;
3005 end;
3006 
3007 procedure TDbfFile.RecordRecalled(RecNo: Integer; Buffer: TRecordBuffer);
3008 var
3009   I: Integer;
3010   lIndex, lErrorIndex: TIndexFile;
3011 begin
3012   // notify indexes: record recalled
3013   I := 0;
3014   while I < FIndexFiles.Count do
3015   begin
3016     lIndex := TIndexFile(FIndexFiles.Items[I]);
3017     if not lIndex.RecordRecalled(RecNo, Buffer) then
3018     begin
3019       lErrorIndex := lIndex;
3020       while I > 0 do
3021       begin
3022         Dec(I);
3023         lIndex.RecordDeleted(RecNo, Buffer);
3024       end;
3025       lErrorIndex.InsertError;
3026     end;
3027     Inc(I);
3028   end;
3029 end;
3030 
3031 procedure TDbfFile.SetRecordSize(NewSize: Integer);
3032 begin
3033   if NewSize <> RecordSize then
3034   begin
3035     if FPrevBuffer <> nil then
3036       FreeMemAndNil(Pointer(FPrevBuffer));
3037 
3038     if NewSize > 0 then
3039       GetMem(FPrevBuffer, NewSize);
3040   end;
3041   inherited;
3042 end;
3043 
TDbfFile.GetIndexByNamenull3044 function TDbfFile.GetIndexByName(AIndexName: string): TIndexFile;
3045 var
3046   I: Integer;
3047 begin
3048   I := FIndexNames.IndexOf(AIndexName);
3049   if I >= 0 then
3050     Result := TIndexFile(FIndexNames.Objects[I])
3051   else
3052     Result := nil;
3053 end;
3054 
3055 //====================================================================
3056 // TDbfCursor
3057 //====================================================================
3058 constructor TDbfCursor.Create(DbfFile: TDbfFile);
3059 begin
3060   inherited Create(DbfFile);
3061 end;
3062 
Nextnull3063 function TDbfCursor.Next: Boolean;
3064 begin
3065   if TDbfFile(PagedFile).IsRecordPresent(FPhysicalRecNo) then
3066   begin
3067     inc(FPhysicalRecNo);
3068     Result := TDbfFile(PagedFile).IsRecordPresent(FPhysicalRecNo);
3069   end else begin
3070     FPhysicalRecNo := TDbfFile(PagedFile).CachedRecordCount + 1;
3071     Result := false;
3072   end;
3073 end;
3074 
Prevnull3075 function TDbfCursor.Prev: Boolean;
3076 begin
3077   if FPhysicalRecNo > 0 then
3078     dec(FPhysicalRecNo)
3079   else
3080     FPhysicalRecNo := 0;
3081   Result := FPhysicalRecNo > 0;
3082 end;
3083 
3084 procedure TDbfCursor.First;
3085 begin
3086   FPhysicalRecNo := 0;
3087 end;
3088 
3089 procedure TDbfCursor.Last;
3090 var
3091   max: Integer;
3092 begin
3093   max := TDbfFile(PagedFile).RecordCount;
3094   if max = 0 then
3095     FPhysicalRecNo := 0
3096   else
3097     FPhysicalRecNo := max + 1;
3098 end;
3099 
TDbfCursor.GetPhysicalRecNonull3100 function TDbfCursor.GetPhysicalRecNo: Integer;
3101 begin
3102   Result := FPhysicalRecNo;
3103 end;
3104 
3105 procedure TDbfCursor.SetPhysicalRecNo(RecNo: Integer);
3106 begin
3107   FPhysicalRecNo := RecNo;
3108 end;
3109 
GetSequentialRecordCountnull3110 function TDbfCursor.GetSequentialRecordCount: Integer;
3111 begin
3112   Result := TDbfFile(PagedFile).RecordCount;
3113 end;
3114 
TDbfCursor.GetSequentialRecNonull3115 function TDbfCursor.GetSequentialRecNo: Integer;
3116 begin
3117   Result := FPhysicalRecNo;
3118 end;
3119 
3120 procedure TDbfCursor.SetSequentialRecNo(RecNo: Integer);
3121 begin
3122   FPhysicalRecNo := RecNo;
3123 end;
3124 
3125 // codepage enumeration procedure
3126 var
3127   TempCodePageList: TList;
3128 
3129 // LPTSTR = PChar ok?
3130 
CodePagesProcnull3131 function CodePagesProc(CodePageString: PChar): Cardinal; stdcall;
3132 begin
3133   // add codepage to list
3134   TempCodePageList.Add(Pointer(GetIntFromStrLength(CodePageString, StrLen(CodePageString), -1)));
3135 
3136   // continue enumeration
3137   Result := 1;
3138 end;
3139 
3140 //====================================================================
3141 // TDbfGlobals
3142 //====================================================================
3143 constructor TDbfGlobals.Create;
3144 begin
3145   FCodePages := TList.Create;
3146   FDefaultOpenCodePage := GetACP;
3147   // the following sets FDefaultCreateLangId
3148   DefaultCreateCodePage := GetACP;
3149   FCurrencyAsBCD := true;
3150   // determine which code pages are installed
3151   TempCodePageList := FCodePages;
3152   EnumSystemCodePages(@CodePagesProc, {CP_SUPPORTED} CP_INSTALLED);
3153   TempCodePageList := nil;
3154   InitUserName;
3155 end;
3156 
3157 procedure TDbfGlobals.InitUserName;
3158 {$ifdef FPC}
3159 {$ifndef WINDOWS}
3160  {$IFNDEF OS2}
3161 var
3162   TempName: UTSName;
3163  {$ENDIF OS2}
3164 {$endif}
3165 {$endif}
3166 begin
3167 {$ifdef WINDOWS}
3168 {$ifdef wince}
3169   FUserName:='cedevice';
3170   FUserNameLen:=Length(FUserName);
3171 {$else}
3172   FUserNameLen := MAX_COMPUTERNAME_LENGTH+1;
3173   SetLength(FUserName, FUserNameLen);
3174   Windows.GetComputerName(PChar(FUserName),
3175     {$ifdef DELPHI_3}Windows.DWORD({$endif}
3176       FUserNameLen
3177     {$ifdef DELPHI_3}){$endif}
3178     );
3179   SetLength(FUserName, FUserNameLen);
3180 {$endif wince}
3181 {$else}
3182 {$ifdef FPC}
3183  {$IFDEF OS2}
3184   FUserName := GetEnvironmentVariable ('HOSTNAME');
3185  {$ELSE OS2}
3186   FpUname(TempName);
3187   FUserName := TempName.machine;
3188  {$ENDIF OS2}
3189   FUserNameLen := Length(FUserName);
3190 {$endif}
3191 {$endif}
3192 end;
3193 
3194 destructor TDbfGlobals.Destroy; {override;}
3195 begin
3196   FCodePages.Free;
3197 end;
3198 
GetDefaultCreateCodePagenull3199 function TDbfGlobals.GetDefaultCreateCodePage: Integer;
3200 begin
3201   Result := LangId_To_CodePage[FDefaultCreateLangId];
3202 end;
3203 
3204 procedure TDbfGlobals.SetDefaultCreateCodePage(NewCodePage: Integer);
3205 begin
3206   FDefaultCreateLangId := ConstructLangId(NewCodePage, GetUserDefaultLCID, false);
3207 end;
3208 
TDbfGlobals.CodePageInstallednull3209 function TDbfGlobals.CodePageInstalled(ACodePage: Integer): Boolean;
3210 begin
3211   Result := FCodePages.IndexOf(Pointer(ACodePage)) >= 0;
3212 end;
3213 
3214 initialization
3215 finalization
3216   FreeAndNil(DbfGlobals);
3217 
3218 
3219 (*
3220   Not implemented yet (encrypted cdx is undocumented;
3221   unencrypted cdx could be implemented)
3222   TFoxCDXHeader         = Record
3223     PointerRootNode     : Integer;
3224     PointerFreeList     : Integer;
3225     Reserved_8_11       : Cardinal;
3226     KeyLength           : Word;
3227     IndexOption         : Byte;
3228     IndexSignature      : Byte;
3229     Reserved_Null       : TFoxReservedNull;
3230     SortOrder           : Word;
3231     TotalExpressionLen  : Word;
3232     ForExpressionLen    : Word;
3233     Reserved_506_507    : Word;
3234     KeyExpressionLen    : Word;
3235     KeyForExpression    : TKeyForExpression;
3236   End;
3237   PFoxCDXHeader         = ^TFoxCDXHeader;
3238 
3239   TFoxCDXNodeCommon     = Record
3240     NodeAttributes      : Word;
3241     NumberOfKeys        : Word;
3242     PointerLeftNode     : Integer;
3243     PointerRightNode    : Integer;
3244   End;
3245 
3246   TFoxCDXNodeNonLeaf    = Record
3247     NodeCommon          : TFoxCDXNodeCommon;
3248     TempBlock           : Array [12..511] of Byte;
3249   End;
3250   PFoxCDXNodeNonLeaf    = ^TFoxCDXNodeNonLeaf;
3251 
3252   TFoxCDXNodeLeaf       = Packed Record
3253     NodeCommon          : TFoxCDXNodeCommon;
3254     BlockFreeSpace      : Word;
3255     RecordNumberMask    : Integer;
3256     DuplicateCountMask  : Byte;
3257     TrailByteCountMask  : Byte;
3258     RecNoBytes          : Byte;
3259     DuplicateCountBytes : Byte;
3260     TrailByteCountBytes : Byte;
3261     HoldingByteCount    : Byte;
3262     DataBlock           : TDataBlock;
3263   End;
3264   PFoxCDXNodeLeaf       = ^TFoxCDXNodeLeaf;
3265 
3266 *)
3267 
3268 end.
3269 
3270