dbftoolsunit.pas 8.4 KB

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