📄 dbf.pas
字号:
property Active;
property FieldDefs stored FieldDefsStored;
property Filter;
property Filtered;
property FilterOptions;
property BeforeOpen;
property AfterOpen;
property BeforeClose;
property AfterClose;
property BeforeInsert;
property AfterInsert;
property BeforeEdit;
property AfterEdit;
property BeforePost;
property AfterPost;
property BeforeCancel;
property AfterCancel;
property BeforeDelete;
property AfterDelete;
property BeforeRefresh;
property AfterRefresh;
property BeforeScroll;
property AfterScroll;
property OnCalcFields;
property OnDeleteError;
property OnEditError;
property OnFilterRecord;
property OnNewRecord;
property OnPostError;
end;
TDbf_GetBasePathFunction = function: string;
var
DbfBasePath: TDbf_GetBasePathFunction;
implementation
uses
SysUtils,
{$ifndef FPC}
DBConsts,
{$endif}
{$ifdef WINDOWS}
Windows,
{$else}
{$ifdef KYLIX}
Libc,
{$endif}
Types,
dbf_wtil,
{$endif}
{$ifdef SUPPORT_SEPARATE_VARIANTS_UNIT}
Variants,
{$endif}
dbf_idxcur,
dbf_memo,
dbf_str;
{$ifdef FPC}
const
// TODO: move these to DBConsts
SNotEditing = 'Dataset not in edit or insert mode';
SCircularDataLink = 'Circular datalinks are not allowed';
{$endif}
function TableLevelToDbfVersion(TableLevel: integer): TXBaseVersion;
begin
case TableLevel of
3: Result := xBaseIII;
7: Result := xBaseVII;
TDBF_TABLELEVEL_FOXPRO: Result := xFoxPro;
else
{4:} Result := xBaseIV;
end;
end;
//==========================================================
//============ TDbfBlobStream
//==========================================================
constructor TDbfBlobStream.Create(FieldVal: TField);
begin
FBlobField := FieldVal as TBlobField;
FReadSize := 0;
FMemoRecNo := 0;
FRefCount := 1;
FDirty := false;
end;
destructor TDbfBlobStream.Destroy;
begin
// only continue destroy if all references released
if FRefCount = 1 then
begin
// this is the last reference
inherited
end else begin
// fire event when dirty, and the last "user" is freeing it's reference
// tdbf always has the last reference
if FDirty and (FRefCount = 2) then
begin
// a second referer to instance has changed the data, remember modified
// TDbf(FBlobField.DataSet).SetModified(true);
// is following better? seems to provide notification for user (from VCL)
if not (FBlobField.DataSet.State in [dsCalcFields, dsFilter, dsNewValue]) then
TDbf(FBlobField.DataSet).DataEvent(deFieldChange, PtrInt(FBlobField));
end;
end;
Dec(FRefCount);
end;
procedure TDbfBlobStream.FreeInstance;
begin
// only continue freeing if all references released
if FRefCount = 0 then
inherited;
end;
procedure TDbfBlobStream.SetMode(NewMode: TBlobStreamMode);
begin
FMode := NewMode;
FDirty := FDirty or (NewMode = bmWrite) or (NewMode = bmReadWrite);
end;
procedure TDbfBlobStream.Cancel;
begin
FDirty := false;
FMemoRecNo := -1;
end;
procedure TDbfBlobStream.Commit;
var
Dbf: TDbf;
begin
if FDirty then
begin
Size := Position; // Strange but it leave tailing trash bytes if I do not write that.
Dbf := TDbf(FBlobField.DataSet);
Translate(true);
Dbf.FDbfFile.MemoFile.WriteMemo(FMemoRecNo, FReadSize, Self);
Dbf.FDbfFile.SetFieldData(FBlobField.FieldNo-1, ftInteger, @FMemoRecNo,
@pDbfRecord(TDbf(FBlobField.DataSet).ActiveBuffer)^.DeletedFlag, false);
FDirty := false;
end;
end;
function TDbfBlobStream.AddReference: TDbfBlobStream;
begin
Inc(FRefCount);
Result := Self;
end;
function TDbfBlobStream.GetTransliterate: Boolean;
begin
Result := FBlobField.Transliterate;
end;
procedure TDbfBlobStream.Translate(ToOem: Boolean);
var
bytesToDo, numBytes: Integer;
bufPos: PChar;
saveChar: Char;
begin
if (Transliterate) and (Size > 0) then
begin
// get number of bytes to be translated
bytesToDo := Size;
// make space for final null-terminator
Size := Size + 1;
bufPos := Memory;
repeat
// process blocks of 512 bytes
numBytes := bytesToDo;
if numBytes > 512 then
numBytes := 512;
// null-terminate memory
saveChar := bufPos[numBytes];
bufPos[numBytes] := #0;
// translate memory
TDbf(FBlobField.DataSet).Translate(bufPos, bufPos, ToOem);
// restore char
bufPos[numBytes] := saveChar;
// numBytes bytes translated
Dec(bytesToDo, numBytes);
Inc(bufPos, numBytes);
until bytesToDo = 0;
// cut ending null-terminator
Size := Size - 1;
end;
end;
//====================================================================
// TDbf = TDataset Descendant.
//====================================================================
constructor TDbf.Create(AOwner: TComponent); {override;}
begin
inherited;
if DbfGlobals = nil then
DbfGlobals := TDbfGlobals.Create;
BookmarkSize := sizeof(TBookmarkData);
FIndexDefs := TDbfIndexDefs.Create(Self);
FMasterLink := TDbfMasterLink.Create(Self);
FMasterLink.OnMasterChange := MasterChanged;
FMasterLink.OnMasterDisable := MasterDisabled;
FDateTimeHandling := dtBDETimeStamp;
FStorage := stoFile;
FOpenMode := omNormal;
FParser := nil;
FPosting := false;
FReadOnly := false;
FExclusive := false;
FDisableResyncOnPost := false;
FTempExclusive := false;
FCopyDateTimeAsString := false;
FInCopyFrom := false;
FFindRecordFilter := false;
FEditingRecNo := -1;
FTableLevel := 4;
FIndexName := EmptyStr;
FilePath := EmptyStr;
FTempBuffer := nil;
FFilterBuffer := nil;
FIndexFile := nil;
FOnTranslate := nil;
FOnCopyDateTimeAsString := nil;
end;
destructor TDbf.Destroy; {override;}
var
I: Integer;
begin
inherited Destroy;
if FIndexDefs <> nil then
begin
for I := FIndexDefs.Count - 1 downto 0 do
TDbfIndexDef(FIndexDefs.Items[I]).Free;
FIndexDefs.Free;
end;
FMasterLink.Free;
end;
function TDbf.AllocRecordBuffer: PChar; {override virtual abstract from TDataset}
begin
GetMem(Result, SizeOf(TDbfRecordHeader)+FDbfFile.RecordSize+CalcFieldsSize+1);
end;
procedure TDbf.FreeRecordBuffer(var Buffer: PChar); {override virtual abstract from TDataset}
begin
FreeMemAndNil(Pointer(Buffer));
end;
procedure TDbf.GetBookmarkData(Buffer: PChar; Data: Pointer); {override virtual abstract from TDataset}
begin
pBookmarkData(Data)^ := pDbfRecord(Buffer)^.BookmarkData;
end;
function TDbf.GetBookmarkFlag(Buffer: PChar): TBookmarkFlag; {override virtual abstract from TDataset}
begin
Result := pDbfRecord(Buffer)^.BookmarkFlag;
end;
function TDbf.GetCurrentBuffer: PChar;
begin
case State of
dsFilter: Result := FFilterBuffer;
dsCalcFields: Result := CalcBuffer;
// dsSetKey: Result := FKeyBuffer; // TO BE Implemented
else
if IsEmpty then
begin
Result := nil;
end else begin
Result := ActiveBuffer;
end;
end;
if Result <> nil then
Result := @PDbfRecord(Result)^.DeletedFlag;
end;
// we don't want converted data formats, we want native :-)
// it makes coding easier in TDbfFile.GetFieldData
// ftCurrency:
// Delphi 3,4: BCD array
// ftBCD:
// ftDateTime is more difficult though
function TDbf.GetFieldData(Field: TField; Buffer: Pointer): Boolean; {override virtual abstract from TDataset}
{$ifdef SUPPORT_OVERLOAD}
begin
{ calling through 'old' delphi 3 interface, use compatible/'native' format }
Result := GetFieldData(Field, Buffer, true);
end;
function TDbf.GetFieldData(Field: TField; Buffer: Pointer; NativeFormat: Boolean): Boolean; {overload; override;}
{$else}
const
{ no overload => delphi 3 => use compatible/'native' format }
NativeFormat = true;
{$endif}
var
Src: PChar;
begin
Src := GetCurrentBuffer;
if Src = nil then
begin
Result := false;
exit;
end;
if Field.FieldNo>0 then
begin
Result := FDbfFile.GetFieldData(Field.FieldNo-1, Field.DataType, Src, Buffer, NativeFormat);
end else begin { weird calculated fields voodoo (from dbtables).... }
Inc(PChar(Src), Field.Offset + GetRecordSize);
Result := Boolean(Src[0]);
if Result and (Buffer <> nil) then
Move(Src[1], Buffer^, Field.DataSize);
end;
end;
procedure TDbf.SetFieldData(Field: TField; Buffer: Pointer); {override virtual abstract from TDataset}
{$ifdef SUPPORT_OVERLOAD}
begin
{ calling through 'old' delphi 3 interface, use compatible/'native' format }
SetFieldData(Field, Buffer, true);
end;
procedure TDbf.SetFieldData(Field: TField; Buffer: Pointer; NativeFormat: Boolean); {overload; override;}
{$else}
const
{ no overload => delphi 3 => use compatible/'native' format }
NativeFormat = true;
{$endif}
var
Dst: PChar;
begin
if (Field.FieldNo >= 0) then
begin
Dst := @PDbfRecord(ActiveBuffer)^.DeletedFlag;
FDbfFile.SetFieldData(Field.FieldNo - 1, Field.DataType, Buffer, Dst, NativeFormat);
end else begin { ***** fkCalculated, fkLookup ***** }
Dst := @PDbfRecord(CalcBuffer)^.DeletedFlag;
Inc(PChar(Dst), RecordSize + Field.Offset);
Boolean(Dst[0]) := Buffer <> nil;
if Buffer <> nil then
Move(Buffer^, Dst[1], Field.DataSize)
end; { end of ***** fkCalculated, fkLookup ***** }
if not (State in [dsCalcFields, dsFilter, dsNewValue]) then begin
DataEvent(deFieldChange, PtrInt(Field));
end;
end;
procedure TDbf.DoFilterRecord(var Acceptable: Boolean);
begin
// check filtertext
if Length(Filter) > 0 then
begin
{$ifndef VER1_0}
Acceptable := Boolean((FParser.ExtractFromBuffer(GetCurrentBuffer))^);
{$else}
// strange problem
// dbf.pas(716,19) Error: Incompatible types: got "CHAR" expected "BOOLEAN"
Acceptable := not ((FParser.ExtractFromBuffer(GetCurrentBuffer))^ = #0);
{$endif}
end;
// check user filter
if Acceptable and Assigned(OnFilterRecord) then
OnFilterRecord(Self, Acceptable);
end;
function TDbf.ReadCurrentRecord(Buffer: PChar; var Acceptable: Boolean): TGetResult;
var
lPhysicalRecNo: Integer;
pRecord: pDbfRecord;
begin
lPhysicalRecNo := FCursor.PhysicalRecNo;
if (lPhysicalRecNo = 0) or not FDbfFile.IsRecordPresent(lPhysicalRecNo) then
begin
Result := grError;
Acceptable := false;
end else begin
Result := grOK;
pRecord := pDbfRecord(Buffer);
FDbfFile.ReadRecord(lPhysicalRecNo, @pRecord^.DeletedFlag);
Acceptable := (FShowDeleted or (pRecord^.DeletedFlag <> '*'))
end;
end;
function TDbf.GetRecord(Buffer: PChar; GetMode: TGetMode; DoCheck: Boolean): TGetResult; {override virtual abstract from TDataset}
var
pRecord: pDbfRecord;
acceptable: Boolean;
SaveState: TDataSetState;
// s: string;
begin
if FCursor = nil then
begin
Result := grEOF;
exit;
end;
pRecord := pDbfRecord(Buffer);
acceptable := false;
repeat
Result := grOK;
case GetMode of
gmNext :
begin
Acceptable := FCursor.Next;
if Acceptable then begin
Result := grOK;
end else begin
Result := grEOF
end;
end;
gmPrior :
begin
Acceptable := FCursor.Prev;
if Acceptable then begin
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -