dbftoolsunit.pas 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. unit DBFToolsUnit;
  2. { Sets up dbf datasets for testing
  3. Tests expect Get*Dataset to return a dataset with structure and test data, but closed.
  4. }
  5. {$IFDEF FPC}
  6. {$mode objfpc}{$H+}
  7. {$ENDIF}
  8. // If defined, save the dbf files when done and print out location to stdout:
  9. {.$DEFINE KEEPDBFFILES}
  10. interface
  11. uses
  12. Classes, SysUtils, toolsunit,
  13. DB, Dbf, dbf_common;
  14. type
  15. { TDBFDBConnector }
  16. TDBFDBConnector = class(TDBConnector)
  17. protected
  18. procedure CreateNDatasets; override;
  19. procedure CreateFieldDataset; override;
  20. procedure DropNDatasets; override;
  21. procedure DropFieldDataset; override;
  22. // InternalGetNDataset reroutes to ReallyInternalGetNDataset
  23. function InternalGetNDataset(n: integer): TDataset; override;
  24. function InternalGetFieldDataset: TDataSet; override;
  25. // GetNDataset allowing trace dataset if required;
  26. // if trace is on, use a TDbfTraceDataset instead of TDBFAutoClean
  27. function ReallyInternalGetNDataset(n: integer; Trace: boolean): TDataset;
  28. public
  29. function GetTraceDataset(AChange: boolean): TDataset; override;
  30. end;
  31. { TDBFAutoClean }
  32. // DBF descendant that saves to a memory stream instead of file
  33. TDBFAutoClean = class(TDBF)
  34. private
  35. FBackingStream: TMemoryStream;
  36. FIndexBackingStream: TMemoryStream;
  37. FMemoBackingStream: TMemoryStream;
  38. FCreatedBy: string;
  39. public
  40. // Keeps track of which function created the dataset, useful for troubleshooting
  41. property CreatedBy: string read FCreatedBy write FCreatedBy;
  42. constructor Create;
  43. constructor Create(AOwner: TComponent); override;
  44. destructor Destroy; override;
  45. function UserRequestedTableLevel: integer;
  46. end;
  47. { TDbfTraceDataset }
  48. TDbfTraceDataset = class(TdbfAutoClean)
  49. protected
  50. procedure SetCurrentRecord(Index: longint); override;
  51. procedure RefreshInternalCalcFields(Buffer: PChar); override;
  52. procedure InternalInitFieldDefs; override;
  53. procedure CalculateFields(Buffer: PChar); override;
  54. procedure ClearCalcFields(Buffer: PChar); override;
  55. end;
  56. implementation
  57. uses
  58. FmtBCD;
  59. function GetNewTempDBFName: string;
  60. // Scans temp directory for dbf names and adds
  61. var
  62. Res: TSearchRec;
  63. Path, Name: string;
  64. FileAttr: LongInt;
  65. Attr,NextFileNo: Integer;
  66. begin
  67. NextFileNo:=0;
  68. Attr := faAnyFile;
  69. if FindFirst(IncludeTrailingPathDelimiter(GetTempDir)+'*.dbf', Attr, Res) = 0 then
  70. begin
  71. Path := GetTempDir;
  72. repeat
  73. Name := ConcatPaths([Path, Res.Name]);
  74. FileAttr := FileGetAttr(Name);
  75. if FileAttr and faDirectory = 0 then
  76. begin
  77. // Capture alphabetically latest name
  78. try
  79. //... only if it is numeric
  80. if strtoint(ChangeFileExt(Res.Name,''))>NextFileNo then
  81. NextFileNo:=strtoint(ChangeFileExt(Res.Name,''));
  82. except
  83. // apparently not numeric
  84. end;
  85. end
  86. until FindNext(Res) <> 0;
  87. end;
  88. FindClose(Res);
  89. // now we now the latest file, add 1, and paste the temp directory in front of it
  90. NextFileNo:=NextFileNo+1;
  91. Result:=IncludeTrailingPathDelimiter(GetTempDir)+IntToStr(NextFileNo)+'.DBF';
  92. end;
  93. { TDBFAutoClean }
  94. function TDBFAutoClean.UserRequestedTableLevel: integer;
  95. // User can specify table level as a connector param, e.g.:
  96. // connectorparams=4
  97. // If none given, default to DBase IV
  98. var
  99. TableLevelProvided: integer;
  100. begin
  101. TableLevelProvided := StrToIntDef(dbconnectorparams, 4);
  102. if not (TableLevelProvided in [3, 4, 5, 7,
  103. TDBF_TABLELEVEL_FOXPRO, TDBF_TABLELEVEL_VISUALFOXPRO]) then
  104. begin
  105. Result := -1; // hope this crashes the tests so user is alerted.
  106. //Invalid tablelevel specified in connectorparams= field. Aborting
  107. exit;
  108. end;
  109. Result := TableLevelProvided;
  110. end;
  111. constructor TDBFAutoClean.Create;
  112. begin
  113. // Create storage for data:
  114. FBackingStream:=TMemoryStream.Create;
  115. FIndexBackingStream:=TMemoryStream.Create;
  116. FMemoBackingStream:=TMemoryStream.Create;
  117. // Create a unique name (within the 10 character DBIII limit):
  118. TableName := FormatDateTime('hhnnssz',Now())+'_'+inttostr(random(99));
  119. TableLevel := UserRequestedTableLevel;
  120. Storage:=stoMemory;
  121. UserStream:=FBackingStream;
  122. UserIndexStream:=FIndexBackingStream;
  123. UserMemoStream:=FMemoBackingStream;
  124. CreateTable; //this will also write out the dbf header to disk/stream
  125. end;
  126. constructor TDBFAutoClean.Create(AOwner: TComponent);
  127. begin
  128. inherited Create(AOwner);
  129. Self.Create;
  130. end;
  131. destructor TDBFAutoClean.Destroy;
  132. {$IFDEF KEEPDBFFILES}
  133. var
  134. FileName: string;
  135. {$ENDIF}
  136. begin
  137. {$IFDEF KEEPDBFFILES}
  138. Close;
  139. FileName := GetNewTempDBFName;
  140. FBackingStream.SaveToFile(FileName);
  141. FIndexBackingStream.SaveToFile(ChangeFileExt(FileName, '.mdx'));
  142. if Self.TableLevel in [TDBF_TABLELEVEL_FOXPRO, TDBF_TABLELEVEL_VISUALFOXPRO] then
  143. FMemoBackingStream.SaveToFile(ChangeFileExt(FileName, '.fpt'))
  144. else
  145. FMemoBackingStream.SaveToFile(ChangeFileExt(FileName, '.dbt'));
  146. writeln('TDBFAutoClean: file created by ',CreatedBy,' left file: ',FileName);
  147. {$ENDIF}
  148. inherited Destroy;
  149. FBackingStream.Free;
  150. FIndexBackingStream.Free;
  151. end;
  152. procedure TDBFDBConnector.CreateNDatasets;
  153. begin
  154. // All datasets are created in InternalGet*Dataset
  155. end;
  156. procedure TDBFDBConnector.CreateFieldDataset;
  157. begin
  158. // All datasets are created in InternalGet*Dataset
  159. end;
  160. procedure TDBFDBConnector.DropNDatasets;
  161. begin
  162. // Nothing to be done here; the dataset is cleaned up in TDBFAutoClean.Destroy
  163. end;
  164. procedure TDBFDBConnector.DropFieldDataset;
  165. begin
  166. // Nothing to be done here; the dataset is cleaned up in TDBFAutoClean.Destroy
  167. end;
  168. function TDBFDBConnector.InternalGetNDataset(n: integer): TDataset;
  169. begin
  170. result:=ReallyInternalGetNDataset(n,false);
  171. end;
  172. function TDBFDBConnector.InternalGetFieldDataset: TDataSet;
  173. var
  174. i: integer;
  175. begin
  176. Result := (TDbfAutoClean.Create(nil) as TDataSet);
  177. with (Result as TDBFAutoClean) do
  178. begin
  179. CreatedBy:='InternalGetFieldDataset';
  180. FieldDefs.Add('ID', ftInteger);
  181. FieldDefs.Add('FSTRING', ftString, 10);
  182. FieldDefs.Add('FSMALLINT', ftSmallint);
  183. FieldDefs.Add('FINTEGER', ftInteger);
  184. FieldDefs.Add('FWORD', ftWord);
  185. FieldDefs.Add('FBOOLEAN', ftBoolean);
  186. FieldDefs.Add('FFLOAT', ftFloat);
  187. // Field types only available in (Visual) FoxPro
  188. if (Result as TDBF).TableLevel >= TDBF_TABLELEVEL_FOXPRO then
  189. FieldDefs.Add('FCURRENCY', ftCurrency);
  190. if (Result as TDBF).TableLevel >= TDBF_TABLELEVEL_FOXPRO then
  191. FieldDefs.Add('FBCD', ftBCD);
  192. FieldDefs.Add('FDATE', ftDate);
  193. FieldDefs.Add('FDATETIME', ftDateTime);
  194. FieldDefs.Add('FLARGEINT', ftLargeint);
  195. FieldDefs.Add('FMEMO', ftMemo);
  196. CreateTable;
  197. Open;
  198. for i := 0 to testValuesCount - 1 do
  199. begin
  200. Append;
  201. FieldByName('ID').AsInteger := i;
  202. FieldByName('FSTRING').AsString := testStringValues[i];
  203. FieldByName('FSMALLINT').AsInteger := testSmallIntValues[i];
  204. FieldByName('FINTEGER').AsInteger := testIntValues[i];
  205. FieldByName('FWORD').AsInteger := testWordValues[i];
  206. FieldByName('FBOOLEAN').AsBoolean := testBooleanValues[i];
  207. FieldByName('FFLOAT').AsFloat := testFloatValues[i];
  208. if (Result as TDBF).TableLevel >= TDBF_TABLELEVEL_FOXPRO then
  209. FieldByName('FCURRENCY').AsCurrency := testCurrencyValues[i];
  210. // work around missing TBCDField.AsBCD:
  211. if (Result as TDBF).TableLevel >= TDBF_TABLELEVEL_FOXPRO then
  212. FieldByName('FBCD').AsBCD := StrToBCD(testFmtBCDValues[i],Self.FormatSettings);
  213. FieldByName('FDATE').AsDateTime := StrToDate(testDateValues[i], 'yyyy/mm/dd', '-');
  214. FieldByName('FDATETIME').AsDateTime := StrToDateTime(testValues[ftDateTime,i], Self.FormatSettings);
  215. FieldByName('FLARGEINT').AsLargeInt := testLargeIntValues[i];
  216. FieldByName('FMEMO').AsString := testStringValues[i];
  217. Post;
  218. end;
  219. Close;
  220. end;
  221. end;
  222. function TDBFDBConnector.ReallyInternalGetNDataset(n: integer; Trace: boolean): TDataset;
  223. var
  224. countID: integer;
  225. begin
  226. if Trace then
  227. Result := (TDbfTraceDataset.Create(nil) as TDataSet)
  228. else
  229. Result := (TDBFAutoClean.Create(nil) as TDataSet);
  230. with (Result as TDBFAutoclean) do
  231. begin
  232. CreatedBy:='InternalGetNDataset('+inttostr(n)+')';
  233. FieldDefs.Add('ID', ftInteger);
  234. FieldDefs.Add('NAME', ftString, 50);
  235. CreateTable;
  236. Open;
  237. if n > 0 then
  238. for countId := 1 to n do
  239. begin
  240. Append;
  241. FieldByName('ID').AsInteger := countID;
  242. FieldByName('NAME').AsString := 'TestName' + IntToStr(countID);
  243. // Explicitly call .post, since there could be a bug which disturbs
  244. // the automatic call to post. (example: when TDataset.DataEvent doesn't
  245. // work properly)
  246. Post;
  247. end;
  248. if state = dsinsert then
  249. Post;
  250. Close;
  251. end;
  252. end;
  253. function TDBFDBConnector.GetTraceDataset(AChange: boolean): TDataset;
  254. begin
  255. // Mimic TDBConnector.GetNDataset
  256. if AChange then FChangedDatasets[NForTraceDataset] := True;
  257. Result := ReallyInternalGetNDataset(NForTraceDataset,true);
  258. FUsedDatasets.Add(Result);
  259. end;
  260. { TDbfTraceDataset }
  261. procedure TDbfTraceDataset.SetCurrentRecord(Index: longint);
  262. begin
  263. DataEvents := DataEvents + 'SetCurrentRecord' + ';';
  264. inherited SetCurrentRecord(Index);
  265. end;
  266. procedure TDbfTraceDataset.RefreshInternalCalcFields(Buffer: PChar);
  267. begin
  268. DataEvents := DataEvents + 'RefreshInternalCalcFields' + ';';
  269. inherited RefreshInternalCalcFields(Buffer);
  270. end;
  271. procedure TDbfTraceDataset.InternalInitFieldDefs;
  272. var
  273. i: integer;
  274. IntCalcFieldName: string;
  275. begin
  276. // To fake an internal calculated field, set its fielddef InternalCalcField
  277. // property to true, before the dataset is opened.
  278. // This procedure takes care of setting the automatically created fielddef's
  279. // InternalCalcField property to true. (works for only one field)
  280. IntCalcFieldName := '';
  281. for i := 0 to FieldDefs.Count - 1 do
  282. if fielddefs[i].InternalCalcField then
  283. IntCalcFieldName := FieldDefs[i].Name;
  284. inherited InternalInitFieldDefs;
  285. if IntCalcFieldName <> '' then
  286. with FieldDefs.find(IntCalcFieldName) do
  287. begin
  288. InternalCalcField := True;
  289. end;
  290. end;
  291. procedure TDbfTraceDataset.CalculateFields(Buffer: PChar);
  292. begin
  293. DataEvents := DataEvents + 'CalculateFields' + ';';
  294. inherited CalculateFields(Buffer);
  295. end;
  296. procedure TDbfTraceDataset.ClearCalcFields(Buffer: PChar);
  297. begin
  298. DataEvents := DataEvents + 'ClearCalcFields' + ';';
  299. inherited ClearCalcFields(Buffer);
  300. end;
  301. initialization
  302. RegisterClass(TDBFDBConnector);
  303. end.