mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-06-26 08:58:18 +02:00

* fcl-db tests: test ftWord for bufdataset, tdbf etc To do: verify why all bufdataset export tests now get an access violation git-svn-id: trunk@24545 -
338 lines
10 KiB
ObjectPascal
338 lines
10 KiB
ObjectPascal
unit DBFToolsUnit;
|
|
|
|
{ Sets up dbf datasets for testing
|
|
Tests expect Get*Dataset to return a dataset with structure and test data, but closed.
|
|
}
|
|
|
|
{$IFDEF FPC}
|
|
{$mode objfpc}{$H+}
|
|
{$ENDIF}
|
|
|
|
// If defined, save the dbf files when done and print out location to stdout:
|
|
{.$DEFINE KEEPDBFFILES}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, toolsunit,
|
|
DB, Dbf, dbf_common;
|
|
|
|
type
|
|
{ TDBFDBConnector }
|
|
|
|
TDBFDBConnector = class(TDBConnector)
|
|
protected
|
|
procedure CreateNDatasets; override;
|
|
procedure CreateFieldDataset; override;
|
|
procedure DropNDatasets; override;
|
|
procedure DropFieldDataset; override;
|
|
// InternalGetNDataset reroutes to ReallyInternalGetNDataset
|
|
function InternalGetNDataset(n: integer): TDataset; override;
|
|
function InternalGetFieldDataset: TDataSet; override;
|
|
// GetNDataset allowing trace dataset if required;
|
|
// if trace is on, use a TDbfTraceDataset instead of TDBFAutoClean
|
|
function ReallyInternalGetNDataset(n: integer; Trace: boolean): TDataset;
|
|
public
|
|
function GetTraceDataset(AChange: boolean): TDataset; override;
|
|
end;
|
|
|
|
{ TDBFAutoClean }
|
|
// DBF descendant that saves to a memory stream instead of file
|
|
TDBFAutoClean = class(TDBF)
|
|
private
|
|
FBackingStream: TMemoryStream;
|
|
FIndexBackingStream: TMemoryStream;
|
|
FMemoBackingStream: TMemoryStream;
|
|
FCreatedBy: string;
|
|
public
|
|
// Keeps track of which function created the dataset, useful for troubleshooting
|
|
property CreatedBy: string read FCreatedBy write FCreatedBy;
|
|
constructor Create;
|
|
constructor Create(AOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
function UserRequestedTableLevel: integer;
|
|
end;
|
|
|
|
{ TDbfTraceDataset }
|
|
TDbfTraceDataset = class(TdbfAutoClean)
|
|
protected
|
|
procedure SetCurrentRecord(Index: longint); override;
|
|
procedure RefreshInternalCalcFields(Buffer: PChar); override;
|
|
procedure InternalInitFieldDefs; override;
|
|
procedure CalculateFields(Buffer: PChar); override;
|
|
procedure ClearCalcFields(Buffer: PChar); override;
|
|
end;
|
|
|
|
|
|
implementation
|
|
|
|
uses
|
|
FmtBCD;
|
|
|
|
function GetNewTempDBFName: string;
|
|
// Scans temp directory for dbf names and adds
|
|
var
|
|
Res: TSearchRec;
|
|
Path, Name: string;
|
|
FileAttr: LongInt;
|
|
Attr,NextFileNo: Integer;
|
|
begin
|
|
NextFileNo:=0;
|
|
Attr := faAnyFile;
|
|
if FindFirst(IncludeTrailingPathDelimiter(GetTempDir)+'*.dbf', Attr, Res) = 0 then
|
|
begin
|
|
Path := GetTempDir;
|
|
repeat
|
|
Name := ConcatPaths([Path, Res.Name]);
|
|
FileAttr := FileGetAttr(Name);
|
|
if FileAttr and faDirectory = 0 then
|
|
begin
|
|
// Capture alphabetically latest name
|
|
try
|
|
//... only if it is numeric
|
|
if strtoint(ChangeFileExt(Res.Name,''))>NextFileNo then
|
|
NextFileNo:=strtoint(ChangeFileExt(Res.Name,''));
|
|
except
|
|
// apparently not numeric
|
|
end;
|
|
end
|
|
until FindNext(Res) <> 0;
|
|
end;
|
|
FindClose(Res);
|
|
// now we now the latest file, add 1, and paste the temp directory in front of it
|
|
NextFileNo:=NextFileNo+1;
|
|
Result:=IncludeTrailingPathDelimiter(GetTempDir)+IntToStr(NextFileNo)+'.DBF';
|
|
end;
|
|
|
|
{ TDBFAutoClean }
|
|
|
|
function TDBFAutoClean.UserRequestedTableLevel: integer;
|
|
// User can specify table level as a connector param, e.g.:
|
|
// connectorparams=4
|
|
// If none given, default to DBase IV
|
|
var
|
|
TableLevelProvided: integer;
|
|
begin
|
|
TableLevelProvided := StrToIntDef(dbconnectorparams, 4);
|
|
if not (TableLevelProvided in [3, 4, 5, 7,
|
|
TDBF_TABLELEVEL_FOXPRO, TDBF_TABLELEVEL_VISUALFOXPRO]) then
|
|
begin
|
|
Result := -1; // hope this crashes the tests so user is alerted.
|
|
//Invalid tablelevel specified in connectorparams= field. Aborting
|
|
exit;
|
|
end;
|
|
Result := TableLevelProvided;
|
|
end;
|
|
|
|
constructor TDBFAutoClean.Create;
|
|
begin
|
|
// Create storage for data:
|
|
FBackingStream:=TMemoryStream.Create;
|
|
FIndexBackingStream:=TMemoryStream.Create;
|
|
FMemoBackingStream:=TMemoryStream.Create;
|
|
// Create a unique name (within the 10 character DBIII limit):
|
|
TableName := FormatDateTime('hhnnssz',Now())+'_'+inttostr(random(99));
|
|
TableLevel := UserRequestedTableLevel;
|
|
Storage:=stoMemory;
|
|
UserStream:=FBackingStream;
|
|
UserIndexStream:=FIndexBackingStream;
|
|
UserMemoStream:=FMemoBackingStream;
|
|
CreateTable; //this will also write out the dbf header to disk/stream
|
|
end;
|
|
|
|
constructor TDBFAutoClean.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
Self.Create;
|
|
end;
|
|
|
|
destructor TDBFAutoClean.Destroy;
|
|
{$IFDEF KEEPDBFFILES}
|
|
var
|
|
FileName: string;
|
|
{$ENDIF}
|
|
begin
|
|
{$IFDEF KEEPDBFFILES}
|
|
Close;
|
|
FileName := GetNewTempDBFName;
|
|
FBackingStream.SaveToFile(FileName);
|
|
FIndexBackingStream.SaveToFile(ChangeFileExt(FileName, '.mdx'));
|
|
if Self.TableLevel in [TDBF_TABLELEVEL_FOXPRO, TDBF_TABLELEVEL_VISUALFOXPRO] then
|
|
FMemoBackingStream.SaveToFile(ChangeFileExt(FileName, '.fpt'))
|
|
else
|
|
FMemoBackingStream.SaveToFile(ChangeFileExt(FileName, '.dbt'));
|
|
writeln('TDBFAutoClean: file created by ',CreatedBy,' left file: ',FileName);
|
|
{$ENDIF}
|
|
inherited Destroy;
|
|
FBackingStream.Free;
|
|
FIndexBackingStream.Free;
|
|
end;
|
|
|
|
|
|
procedure TDBFDBConnector.CreateNDatasets;
|
|
begin
|
|
// All datasets are created in InternalGet*Dataset
|
|
end;
|
|
|
|
procedure TDBFDBConnector.CreateFieldDataset;
|
|
begin
|
|
// All datasets are created in InternalGet*Dataset
|
|
end;
|
|
|
|
procedure TDBFDBConnector.DropNDatasets;
|
|
begin
|
|
// Nothing to be done here; the dataset is cleaned up in TDBFAutoClean.Destroy
|
|
end;
|
|
|
|
procedure TDBFDBConnector.DropFieldDataset;
|
|
begin
|
|
// Nothing to be done here; the dataset is cleaned up in TDBFAutoClean.Destroy
|
|
end;
|
|
|
|
function TDBFDBConnector.InternalGetNDataset(n: integer): TDataset;
|
|
begin
|
|
result:=ReallyInternalGetNDataset(n,false);
|
|
end;
|
|
|
|
function TDBFDBConnector.InternalGetFieldDataset: TDataSet;
|
|
var
|
|
i: integer;
|
|
begin
|
|
Result := (TDbfAutoClean.Create(nil) as TDataSet);
|
|
with (Result as TDBFAutoClean) do
|
|
begin
|
|
CreatedBy:='InternalGetFieldDataset';
|
|
FieldDefs.Add('ID', ftInteger);
|
|
FieldDefs.Add('FSTRING', ftString, 10);
|
|
FieldDefs.Add('FSMALLINT', ftSmallint);
|
|
FieldDefs.Add('FINTEGER', ftInteger);
|
|
FieldDefs.Add('FWORD', ftWord);
|
|
FieldDefs.Add('FBOOLEAN', ftBoolean);
|
|
FieldDefs.Add('FFLOAT', ftFloat);
|
|
// Field types only available in (Visual) FoxPro
|
|
if (Result as TDBF).TableLevel >= TDBF_TABLELEVEL_FOXPRO then
|
|
FieldDefs.Add('FCURRENCY', ftCurrency);
|
|
if (Result as TDBF).TableLevel >= TDBF_TABLELEVEL_FOXPRO then
|
|
FieldDefs.Add('FBCD', ftBCD);
|
|
FieldDefs.Add('FDATE', ftDate);
|
|
FieldDefs.Add('FDATETIME', ftDateTime);
|
|
FieldDefs.Add('FLARGEINT', ftLargeint);
|
|
FieldDefs.Add('FMEMO', ftMemo);
|
|
CreateTable;
|
|
Open;
|
|
for i := 0 to testValuesCount - 1 do
|
|
begin
|
|
Append;
|
|
FieldByName('ID').AsInteger := i;
|
|
FieldByName('FSTRING').AsString := testStringValues[i];
|
|
FieldByName('FSMALLINT').AsInteger := testSmallIntValues[i];
|
|
FieldByName('FINTEGER').AsInteger := testIntValues[i];
|
|
FieldByName('FWORD').AsInteger := testWordValues[i];
|
|
FieldByName('FBOOLEAN').AsBoolean := testBooleanValues[i];
|
|
FieldByName('FFLOAT').AsFloat := testFloatValues[i];
|
|
if (Result as TDBF).TableLevel >= TDBF_TABLELEVEL_FOXPRO then
|
|
FieldByName('FCURRENCY').AsCurrency := testCurrencyValues[i];
|
|
// work around missing TBCDField.AsBCD:
|
|
if (Result as TDBF).TableLevel >= TDBF_TABLELEVEL_FOXPRO then
|
|
FieldByName('FBCD').AsBCD := StrToBCD(testFmtBCDValues[i],Self.FormatSettings);
|
|
FieldByName('FDATE').AsDateTime := StrToDate(testDateValues[i], 'yyyy/mm/dd', '-');
|
|
FieldByName('FDATETIME').AsDateTime := StrToDateTime(testValues[ftDateTime,i], Self.FormatSettings);
|
|
FieldByName('FLARGEINT').AsLargeInt := testLargeIntValues[i];
|
|
FieldByName('FMEMO').AsString := testStringValues[i];
|
|
Post;
|
|
end;
|
|
Close;
|
|
end;
|
|
end;
|
|
|
|
function TDBFDBConnector.ReallyInternalGetNDataset(n: integer; Trace: boolean): TDataset;
|
|
var
|
|
countID: integer;
|
|
begin
|
|
if Trace then
|
|
Result := (TDbfTraceDataset.Create(nil) as TDataSet)
|
|
else
|
|
Result := (TDBFAutoClean.Create(nil) as TDataSet);
|
|
with (Result as TDBFAutoclean) do
|
|
begin
|
|
CreatedBy:='InternalGetNDataset('+inttostr(n)+')';
|
|
FieldDefs.Add('ID', ftInteger);
|
|
FieldDefs.Add('NAME', ftString, 50);
|
|
CreateTable;
|
|
Open;
|
|
if n > 0 then
|
|
for countId := 1 to n do
|
|
begin
|
|
Append;
|
|
FieldByName('ID').AsInteger := countID;
|
|
FieldByName('NAME').AsString := 'TestName' + IntToStr(countID);
|
|
// Explicitly call .post, since there could be a bug which disturbs
|
|
// the automatic call to post. (example: when TDataset.DataEvent doesn't
|
|
// work properly)
|
|
Post;
|
|
end;
|
|
if state = dsinsert then
|
|
Post;
|
|
Close;
|
|
end;
|
|
end;
|
|
|
|
function TDBFDBConnector.GetTraceDataset(AChange: boolean): TDataset;
|
|
begin
|
|
// Mimic TDBConnector.GetNDataset
|
|
if AChange then FChangedDatasets[NForTraceDataset] := True;
|
|
Result := ReallyInternalGetNDataset(NForTraceDataset,true);
|
|
FUsedDatasets.Add(Result);
|
|
end;
|
|
|
|
{ TDbfTraceDataset }
|
|
|
|
procedure TDbfTraceDataset.SetCurrentRecord(Index: longint);
|
|
begin
|
|
DataEvents := DataEvents + 'SetCurrentRecord' + ';';
|
|
inherited SetCurrentRecord(Index);
|
|
end;
|
|
|
|
procedure TDbfTraceDataset.RefreshInternalCalcFields(Buffer: PChar);
|
|
begin
|
|
DataEvents := DataEvents + 'RefreshInternalCalcFields' + ';';
|
|
inherited RefreshInternalCalcFields(Buffer);
|
|
end;
|
|
|
|
procedure TDbfTraceDataset.InternalInitFieldDefs;
|
|
var
|
|
i: integer;
|
|
IntCalcFieldName: string;
|
|
begin
|
|
// To fake an internal calculated field, set its fielddef InternalCalcField
|
|
// property to true, before the dataset is opened.
|
|
// This procedure takes care of setting the automatically created fielddef's
|
|
// InternalCalcField property to true. (works for only one field)
|
|
IntCalcFieldName := '';
|
|
for i := 0 to FieldDefs.Count - 1 do
|
|
if fielddefs[i].InternalCalcField then
|
|
IntCalcFieldName := FieldDefs[i].Name;
|
|
inherited InternalInitFieldDefs;
|
|
if IntCalcFieldName <> '' then
|
|
with FieldDefs.find(IntCalcFieldName) do
|
|
begin
|
|
InternalCalcField := True;
|
|
end;
|
|
end;
|
|
|
|
procedure TDbfTraceDataset.CalculateFields(Buffer: PChar);
|
|
begin
|
|
DataEvents := DataEvents + 'CalculateFields' + ';';
|
|
inherited CalculateFields(Buffer);
|
|
end;
|
|
|
|
procedure TDbfTraceDataset.ClearCalcFields(Buffer: PChar);
|
|
begin
|
|
DataEvents := DataEvents + 'ClearCalcFields' + ';';
|
|
inherited ClearCalcFields(Buffer);
|
|
end;
|
|
|
|
initialization
|
|
RegisterClass(TDBFDBConnector);
|
|
end.
|