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