* fcl-db: dbase:

- Visual Foxpro support for reading/writing backlink (to .dbc database container file)
- No support yet for the .dbc format itself, e.g. larger field/table names and referential integrity
- Renamed header parts to properly reflect status (i.e. at end of header rather than after header)
- Updated comments
- code layout

git-svn-id: trunk@28017 -
This commit is contained in:
reiniero 2014-06-21 08:12:25 +00:00
parent 2df1d5ec58
commit 08efada1df
3 changed files with 138 additions and 63 deletions

View File

@ -169,7 +169,8 @@ type
FEditingRecNo: Integer;
{$ifdef SUPPORT_VARIANTS}
FLocateRecNo: Integer;
{$endif}
{$endif}
FBackLink: String;
FLanguageID: Byte;
FTableLevel: Integer;
FExclusive: Boolean;
@ -203,6 +204,7 @@ type
function GetKeySize: Integer;
function GetMasterFields: string;
function FieldDefsStored: Boolean;
procedure SetBackLink(NewBackLink: String);
procedure SetIndexName(AIndexName: string);
procedure SetDbfIndexDefs(const Value: TDbfIndexDefs);
@ -392,6 +394,10 @@ type
property AbsolutePath: string read FAbsolutePath;
property DbfFieldDefs: TDbfFieldDefs read GetDbfFieldDefs;
property PhysicalRecNo: Integer read GetPhysicalRecNo write SetPhysicalRecNo;
// Visual Foxpro: relative path to .dbc database file containing
// long field names and other metadata
// Empty if this is a "free table", not linked to a .dbc file
property BackLink: String read FBackLink write SetBackLink;
property LanguageID: Byte read FLanguageID write SetLanguageID;
property LanguageStr: String read GetLanguageStr;
property CodePage: Cardinal read GetCodePage;
@ -1267,6 +1273,7 @@ begin
xFoxPro: FTableLevel := TDBF_TABLELEVEL_FOXPRO;
xVisualFoxPro: FTableLevel := TDBF_TABLELEVEL_VISUALFOXPRO;
end;
FBackLink := FDbfFile.BackLink;
FLanguageID := FDbfFile.LanguageID;
// build VCL fielddef list from native DBF FieldDefs
@ -1549,6 +1556,7 @@ begin
InitDbfFile(pfExclusiveCreate);
FDbfFile.CopyDateTimeAsString := FInCopyFrom and FCopyDateTimeAsString;
FDbfFile.DbfVersion := TableLevelToDbfVersion(FTableLevel);
FDbfFile.BackLink := FBackLink;
FDbfFile.FileLangID := FLanguageID;
FDbfFile.Open;
// Default memo blocklength for FoxPro/VisualFoxpro is 64 (not 512 as specs say)
@ -2203,6 +2211,13 @@ begin
Result := StoreDefs and (FieldDefs.Count > 0);
end;
procedure TDbf.SetBackLink(NewBackLink: String);
begin
CheckInactive;
FBackLink := NewBackLink;
end;
procedure TDbf.SetBookmarkFlag(Buffer: TRecordBuffer; Value: TBookmarkFlag); {override virtual abstract from TDataset}
begin
pDbfRecord(Buffer)^.BookmarkFlag := Value;

View File

@ -45,6 +45,8 @@ type
TDbfFile = class(TPagedFile)
protected
FBackLink: string;
FBackLinkOffset: integer; //position of VFP backlink within header
FMdxFile: TIndexFile;
FMemoFile: TMemoFile;
FMemoStream: TStream;
@ -153,6 +155,10 @@ type
property FileCodePage: Cardinal read FFileCodePage;
property UseCodePage: Cardinal read FUseCodePage write FUseCodePage;
property FileLangId: Byte read FFileLangId write FFileLangId;
// Visual Foxpro: relative path to .dbc database file containing
// long field names and other metadata
// Empty if this is a "free table", not linked to a .dbc file
property BackLink: string read FBackLink write FBackLink;
// Dbase (clone) version that this format emulates. Related to tablelevel.
property DbfVersion: TXBaseVersion read FDbfVersion write FDbfVersion;
property PrevBuffer: TRecordBuffer read FPrevBuffer;
@ -325,6 +331,8 @@ end;
constructor TDbfFile.Create;
begin
// init variables first
FBackLink := '';
FBackLinkOffset := 0;
FFieldDefs := TDbfFieldDefs.Create(nil);
FIndexNames := TStringList.Create;
FIndexFiles := TList.Create;
@ -366,7 +374,7 @@ var
var
version: byte;
begin
// OH 2000-11-15 dBase7 support. I build dBase Tables with different
// OH 2000-11-15 dBase7 support. I built dBase Tables with different
// BDE dBase Level (1. without Memo, 2. with Memo)
// Header Byte ($1d hex) (29 dec) -> Language driver ID.
// $03,$83 xBaseIII Header Byte $1d=$00, Float -> N($13.$04) DateTime C($1E)
@ -390,7 +398,7 @@ var
if FDbfVersion = xUnknown then
case (version and $07) of
$03: //dbf with/without memo. Could be Foxpro, too
if not(version in [$03,$8B]) {dbase IV, even with cleared language ID} and
if not(version in [$03,$8B]) {e.g. dbase IV < v2.0 with 0 language ID} and
(LanguageID = 0) then
FDbfVersion := xBaseIII
else
@ -417,7 +425,7 @@ var
xBaseVII:
begin
// cache language str
LangStr := @PAfterHdrVII(PChar(Header) + SizeOf(rDbfHdr))^.LanguageDriverName;
LangStr := @PEndFixedHdrVII(PChar(Header) + SizeOf(rDbfHdr))^.LanguageDriverName;
// VdBase 7 Language strings
// 'DBWIN...' -> Charset 1252 (ansi)
// 'DB999...' -> Code page 999, 9 any digit
@ -462,6 +470,16 @@ var
FUseCodePage := DbfGlobals.DefaultOpenCodePage;
end;
procedure GetBackLink;
// Gets backlink info - only supported in Visual Foxpro
begin
FBackLink:='';
if FDBFVersion=xVisualFoxPro then //only format that supports it
begin
FBackLink:= StrPas(@PEndHdrVFP(PChar(Header) + FBackLinkOffset)^.Backlink);
end;
end;
begin
// check if not already opened
if not Active then
@ -497,6 +515,8 @@ begin
GetCodePage;
// get list of fields
ConstructFieldDefs;
GetBackLink;
// open blob file if present
lMemoFileName := ChangeFileExt(FileName, GetMemoExt);
if HasBlob then
@ -525,12 +545,13 @@ begin
PDbfHdr(Header)^.VerDBF := PDbfHdr(Header)^.VerDBF or $80;
lModified := true;
end;
end else
end else // no HasBlob
if not(FDbfVersion in [xFoxPro,xVisualFoxPro]) then
begin
PDbfHdr(Header)^.VerDBF := PDbfHdr(Header)^.VerDBF and $7F;
lModified := true;
end;
// check if mdx flagged
if not(FDbfVersion in [xFoxPro,xVisualFoxPro]) and (PDbfHdr(Header)^.MDXFlag <> 0) then
begin
@ -637,6 +658,7 @@ end;
procedure TDbfFile.FinishCreate(AFieldDefs: TDbfFieldDefs; MemoSize: Integer);
var
lEndHdrVFP: rEndHdrVFP; //Contains Visual FoxPro backlink
lFieldDescIII: rFieldDescIII;
lFieldDescVII: rFieldDescVII;
lFieldDescPtr: Pointer;
@ -646,7 +668,6 @@ var
lHasBlob: Boolean;
lLocaleID: LCID;
lNullVarFlagCount:integer; //(VFP only) Keeps track of number null/varlength flags needed for _NULLFLAGS size calculation
begin
try
// first reset file
@ -670,31 +691,35 @@ begin
lLocaleID := LangId_To_Locale[FFileLangId];
FUseCodePage := FFileCodePage;
// prepare header size
// Prepare header size. This size may be changed later depending on number
// of fields etc - we start out with the first, fixed part of the header,
// write out the variable parts (field descriptor arrays etc) and then
// correct the header length in the header.
if FDbfVersion = xBaseVII then
begin
// version xBaseVII without memo
HeaderSize := SizeOf(rDbfHdr) + SizeOf(rAfterHdrVII);
HeaderSize := SizeOf(rDbfHdr) + SizeOf(rEndFixedHdrVII);
RecordSize := SizeOf(rFieldDescVII);
FillChar(Header^, HeaderSize, #0);
PDbfHdr(Header)^.VerDBF := $04;
// write language string. FPC needs an explicit cast to pchar to avoid calling widestring version of StrPLCopy
StrPLCopy(
PChar(@PAfterHdrVII(PChar(Header)+SizeOf(rDbfHdr))^.LanguageDriverName[32]),
PChar(@PEndFixedHdrVII(PChar(Header)+SizeOf(rDbfHdr))^.LanguageDriverName[32]),
PChar(ConstructLangName(FFileCodePage, lLocaleID, false)),
63-32);
lFieldDescPtr := @lFieldDescVII;
end else begin
// DBase III..V, (Visual) FoxPro without memo
HeaderSize := SizeOf(rDbfHdr) + SizeOf(rAfterHdrIII);
// rEndHdrVFP is covered at the end as it is placed after the variable
// length part of the header.
HeaderSize := SizeOf(rDbfHdr);
RecordSize := SizeOf(rFieldDescIII);
FillChar(Header^, HeaderSize, #0);
// Note: VerDBF may be changed later on depending on what features/fields are used
// (autoincrement etc)
case FDbfVersion of
xFoxPro: PDbfHdr(Header)^.VerDBF := $03; {FoxBASE+/FoxPro/dBASE III PLUS/dBASE IV, no memo
alternative $02 FoxBASE is not readable by current Visual FoxPro drivers.
alternative $02 FoxBASE is not readable by current MS Visual FoxPro drivers.
}
xVisualFoxPro: PDbfHdr(Header)^.VerDBF := $30; {Visual FoxPro no autoincrement,no varchar}
else PDbfHdr(Header)^.VerDBF := $03; {FoxBASE+/FoxPro/dBASE III PLUS/dBASE IV, no memo}
@ -709,7 +734,9 @@ begin
// init field ptr
lFieldDescPtr := @lFieldDescIII;
end;
// begin writing field definitions
// Begin variable part of the header
// Writing field definitions
FFieldDefs.Clear;
// deleted mark takes 1 byte, so skip over that
lFieldOffset := 1;
@ -730,13 +757,13 @@ begin
lFieldDef.FieldName := AnsiUpperCase(lFieldDef.FieldName);
lFieldDef.Offset := lFieldOffset;
lHasBlob := lHasBlob or lFieldDef.IsBlob;
// Check for foxpro, too, as it can get auto-upgraded to vfp:
// Check for Foxpro, too, as it can get auto-upgraded to vfp:
if (FDbfVersion in [xFoxPro,xVisualFoxPro]) then
begin
if (lFieldDef.NativeFieldType='Q') or (lFieldDef.NativeFieldType='V') then
begin
begin
lNullVarFlagCount:=lNullVarFlagCount+1;
end;
end;
if (lFieldDef.NullPosition>=0) then
lNullVarFlagCount:=lNullVarFlagCount+1;
end;
@ -756,7 +783,7 @@ begin
lSize := lSize and $FF;
end;
// update temp field props
// update temp field properties
if FDbfVersion = xBaseVII then
begin
FillChar(lFieldDescVII, SizeOf(lFieldDescVII), #0);
@ -782,12 +809,11 @@ begin
begin
// VerDBF=$03 also includes dbase formats, so we perform an extra check
if (PDbfHdr(Header)^.VerDBF in [$02,$03]) and
((lFieldDef.NativeFieldType in ['0', 'Y', 'T', 'O', '+', 'Q', 'V']) or (lNullVarFlagCount>0))
then
begin
PDbfHdr(Header)^.VerDBF := $30; {Visual FoxPro}
FDBFVersion:=xVisualFoxPro; //needed to write the backlink info
end;
((lFieldDef.NativeFieldType in ['0', 'Y', 'T', 'O', '+', 'Q', 'V']) or (lNullVarFlagCount>0)) then
begin
PDbfHdr(Header)^.VerDBF := $30; {Visual FoxPro}
FDBFVersion:=xVisualFoxPro; //needed to write the backlink info
end;
//AutoInc only support in Visual Foxpro; another upgrade
//Note: .AutoIncrementNext is really a cardinal (see the definition)
lFieldDescIII.AutoIncrementNext:=SwapIntLE(lFieldDef.AutoInc);
@ -816,7 +842,7 @@ begin
AutoInc := 0;
end;
// save field props
// save field properties
WriteRecord(I, lFieldDescPtr);
Inc(lFieldOffset, lFieldDef.Size);
end;
@ -831,17 +857,26 @@ begin
lFieldDescIII.FieldPrecision := 0;
lFieldDescIII.FieldOffset := SwapIntLE(lFieldOffset);
lFieldDescIII.VisualFoxProFlags:=$01+$04 ; //System column (hidden)+Column can store null values (which is a bit of a paradox)
// save field props
// Save field properties
WriteRecord(AFieldDefs.Count+1, @lFieldDescIII);
Inc(lFieldOffset, lFieldDescIII.FieldSize);
end;
// end of field descriptor; ussually end of header -
// End of field descriptor; usually end of header as well.
// Visual Foxpro backlink info is part of the header but comes after the
// terminator
WriteChar(FIELD_DESCRIPTOR_ARRAY_TERMINATOR);
// write memo bit
{ For Visual FoxPro, add back-link info }
if (FDbfVersion = xVisualFoxPro) then
begin
FBackLinkOffset := Stream.Position;
// Backlink is defined as all $0 bytes if empty
lEndHdrVFP.Backlink:=FBackLink+StringOfChar(#0, SizeOf(lEndHdrVFP.BackLink));
WriteBlock(@lEndHdrVFP,SizeOf(lEndHdrVFP),Stream.Position);
end;
// Write memo bit to begin of header
if lHasBlob then
begin
case FDbfVersion of
@ -854,19 +889,14 @@ begin
end;
end;
// update header
// Update header to correct sizes
PDbfHdr(Header)^.RecordSize := lFieldOffset;
if lNullVarFlagCount>0 then
PDbfHdr(Header)^.FullHdrSize := HeaderSize + RecordSize * (AFieldDefs.Count+1) + 1
PDbfHdr(Header)^.FullHdrSize := HeaderSize + RecordSize * (AFieldDefs.Count + 1) + 1
else
PDbfHdr(Header)^.FullHdrSize := HeaderSize + RecordSize * AFieldDefs.Count + 1;
{ For Visual FoxPro only, add empty "back-link" info:
A 263-byte range that contains the backlink, which is the relative path of
an associated database (.dbc) file, information. If the first byte is 0x00,
the file is not associated with a database. Therefore, database files always
contain 0x00. }
if (FDbfVersion = xVisualFoxPro) then
Inc(PDbfHdr(Header)^.FullHdrSize, 263);
if DbfVersion=xVisualFoxPro then
PDbfHdr(Header)^.FullHdrSize := PDbfHdr(Header)^.FullHdrSize + SizeOf(rEndHdrVFP);
// write dbf header to disk
inherited WriteHeader;
@ -957,7 +987,12 @@ end;
procedure TDbfFile.ConstructFieldDefs;
var
{lColumnCount,}lHeaderSize,lFieldSize: Integer;
// The size of the fixed part of the header
// excluding the field descriptor array
// also excluding everything that comes after the field descriptor array
// like VFP backlink records
lFakeHeaderSize: Integer;
lFieldSize: Integer;
lPropHdrOffset, lFieldOffset: Integer;
lFieldDescIII: rFieldDescIII;
lFieldDescVII: rFieldDescVII;
@ -976,22 +1011,36 @@ var
lCurrentNullPosition: integer;
begin
FFieldDefs.Clear;
if DbfVersion = xBaseVII then
begin
lHeaderSize := SizeOf(rAfterHdrVII) + SizeOf(rDbfHdr);
lFieldSize := SizeOf(rFieldDescVII);
end else begin
// DBase III..V, (Visual) FoxPro
lHeaderSize := SizeOf(rAfterHdrIII) + SizeOf(rDbfHdr);
lFieldSize := SizeOf(rFieldDescIII);
case DbfVersion of
xBaseVII:
begin
lFakeHeaderSize := SizeOf(rDbfHdr) + SizeOf(rEndFixedHdrVII);
lFieldSize := SizeOf(rFieldDescVII);
end;
else
begin
// DBase III..V, (Visual) FoxPro
if DBfVersion = xVisualFoxPro then
lFakeHeaderSize := SizeOf(rDbfHdr)
else
lFakeHeaderSize := SizeOf(rDbfHdr);
lFieldSize := SizeOf(rFieldDescIII);
end;
end;
HeaderSize := lHeaderSize;
RecordSize := lFieldSize;
// This is of course not true but it shrinks the perceived header to just
// before the records with field info:
HeaderSize := lFakeHeaderSize;
RecordSize := lFieldSize;
if FDbfVersion=xVisualFoxPro then
lColumnCount := (PDbfHdr(Header)^.FullHdrSize - lFakeHeaderSize - SizeOf(rEndHdrVFP)) div lFieldSize
else
lColumnCount := (PDbfHdr(Header)^.FullHdrSize - lFakeHeaderSize) div lFieldSize;
FBackLinkOffset := 0;
FLockField := nil;
FNullField := nil;
FAutoIncPresent := false;
lColumnCount := (PDbfHdr(Header)^.FullHdrSize - lHeaderSize) div lFieldSize;
lFieldOffset := 1;
lAutoInc := 0;
I := 1;
@ -1107,6 +1156,9 @@ begin
// or end of header reached
until (I > lColumnCount) or (ReadChar = FIELD_DESCRIPTOR_ARRAY_TERMINATOR);
if FDbfVersion=xVisualFoxPro then
FBackLinkOffset:=Stream.Position+SizeOf(FIELD_DESCRIPTOR_ARRAY_TERMINATOR); //after FIELD_DESCRIPTION_ARRAY_TERMINATOR
// test if not too many fields
if FFieldDefs.Count >= 4096 then
raise EDbfError.CreateFmt(STRING_INVALID_FIELD_COUNT, [FFieldDefs.Count]);
@ -1119,7 +1171,7 @@ begin
// dBase 7 -> read field properties, test if enough space, maybe no header
if (FDbfVersion = xBaseVII) and (lPropHdrOffset + Sizeof(lFieldPropsHdr) <
PDbfHdr(Header)^.FullHdrSize) then
PDbfHdr(Header)^.FullHdrSize) then
begin
// read in field properties header
ReadBlock(@lFieldPropsHdr, SizeOf(lFieldPropsHdr), lPropHdrOffset);
@ -1164,10 +1216,11 @@ begin
ReadBlock(dataPtr, lStdProp.DataSize, lPropHdrOffset + lStdProp.DataOffset);
end;
end;
// todo: read custom properties...not implemented
// todo: read RI/referential integrity properties...not implemented
// todo: read dbase7 custom properties...not implemented
// todo: read dbase7 RI/referential integrity properties...not implemented
end;
finally
// Restore proper sizes so normal records after the header can be read
HeaderSize := PDbfHdr(Header)^.FullHdrSize;
RecordSize := PDbfHdr(Header)^.RecordSize;
end;
@ -1181,7 +1234,7 @@ end;
function TDbfFile.GetLanguageStr: string;
begin
if FDbfVersion >= xBaseVII then
Result := PAfterHdrVII(PChar(Header) + SizeOf(rDbfHdr))^.LanguageDriverName;
Result := PEndFixedHdrVII(PChar(Header) + SizeOf(rDbfHdr))^.LanguageDriverName;
end;
function TDbfFile.IsNullFlagSet(const Src: Pointer; var AFieldDef: TDbfFieldDef; WhichField: TNullFieldFlag): boolean;
@ -2327,7 +2380,7 @@ begin
(TempFieldDef.NativeFieldType = '+') then
begin
// read current auto inc, from header or field, depending on sharing
lAutoIncOffset := sizeof(rDbfHdr) + sizeof(rAfterHdrVII) +
lAutoIncOffset := sizeof(rDbfHdr) + sizeof(rEndFixedHdrVII) +
FieldDescVII_AutoIncOffset + I * sizeof(rFieldDescVII);
if NeedLocks then
begin
@ -2343,12 +2396,12 @@ begin
// write new value to header buffer
PCardinal(FHeader+lAutoIncOffset)^ := SwapIntLE(NextVal);
end
else
else //No DBaseVII
if (DbfVersion=xVisualFoxPro) and
(TempFieldDef.AutoIncStep<>0) then
begin
// read current auto inc from field header
NextVal:=TempFieldDef.AutoInc; //todo: is this correc
NextVal:=TempFieldDef.AutoInc; //todo: is this correct
PCardinal(DestBuf+TempFieldDef.Offset)^ := SwapIntBE(NextVal); //todo: is swapintbe correct?
// Increase with step size
NextVal:=NextVal+TempFieldDef.AutoIncStep;

View File

@ -36,18 +36,25 @@ type
// $04: (Visual FoxPro): is this a dbc/database container
MDXFlag : Byte; // 28 Flags:
Language : Byte; // 29 code page mark
Dummy3 : Word; // 30-31
Dummy3 : Word; // 30-31 reserved
end;
//====================================================================
PAfterHdrIII = ^rAfterHdrIII;
rAfterHdrIII = packed record // Empty
end;
//====================================================================
PAfterHdrVII = ^rAfterHdrVII;
rAfterHdrVII = packed record
LanguageDriverName : array[32..63] of Char;
// Data at end of the fixed part of the header for DBaseVII -
// before the variable length part (e.g. the field descriptor array)
PEndFixedHdrVII = ^rEndFixedHdrVII;
rEndFixedHdrVII = packed record
LanguageDriverName : array[32..63] of Char; //starting position 32 of header
Dummy : array[64..67] of Byte;
end;
//====================================================================
// Data at end of header for Visual Foxpro, after the variable length part
PEndHdrVFP = ^rEndHdrVFP;
rEndHdrVFP = packed record
{ Relative path of an associated database (.dbc) file or filled with $00.
If the first byte is $00, the file is a "free table", not associated with
a database file. Therefore, database files always contain $00. }
Backlink : array[0..262] of Char; //263 bytees
end;
//====================================================================
// DBase III,IV,FoxPro,VisualFoxPro field description
PFieldDescIII = ^rFieldDescIII;