customsqliteds.pas 33 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139
  1. unit customsqliteds;
  2. {
  3. This is TCustomSqliteDataset, a TDataset descendant class for use with fpc compiler
  4. Copyright (C) 2004 Luiz Américo Pereira Câmara
  5. Email: [email protected]
  6. This program is free software; you can redistribute it and/or modify
  7. it under the terms of the GNU Lesser General Public License as published by
  8. the Free Software Foundation; either version 2.1 of the License, or
  9. (at your option) any later version.
  10. This program is distributed in the hope that it will be useful,
  11. but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. GNU Lesser General Public License for more details.
  14. You should have received a copy of the GNU Lesser General Public License
  15. along with this program; if not, write to the Free Software
  16. Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  17. }
  18. {$Mode ObjFpc}
  19. {$H+}
  20. { $Define USE_SQLITEDS_INTERNALS}
  21. { $Define DEBUG}
  22. { $Define DEBUGACTIVEBUFFER}
  23. interface
  24. uses
  25. Classes, SysUtils, Db;
  26. type
  27. PDataRecord = ^DataRecord;
  28. PPDataRecord = ^PDataRecord;
  29. DataRecord = record
  30. Row: PPchar;
  31. BookmarkData: Pointer;
  32. BookmarkFlag: TBookmarkFlag;
  33. Next: PDataRecord;
  34. Previous: PDataRecord;
  35. end;
  36. TDSStream = class(TStream)
  37. private
  38. FActiveItem:PDataRecord;
  39. FFieldRow:PChar;
  40. FFieldIndex:Integer;
  41. FRowSize: Integer;
  42. FPosition: Longint;
  43. public
  44. constructor Create(const ActiveItem: PDataRecord; FieldIndex:Integer);
  45. function Write(const Buffer; Count: Longint): Longint; override;
  46. function Read(var Buffer; Count: Longint): Longint; override;
  47. function Seek(Offset: Longint; Origin: Word): Longint; override;
  48. end;
  49. TSqliteCallback = function (UserData:Pointer; Columns:longint; Values:PPchar; ColumnNames:PPchar):longint;cdecl;
  50. { TCustomSqliteDataset }
  51. TCustomSqliteDataset = class(TDataSet)
  52. private
  53. FPrimaryKey: String;
  54. FPrimaryKeyNo: Integer;
  55. {$ifdef DEBUGACTIVEBUFFER}
  56. FFCurrentItem: PDataRecord;
  57. {$else}
  58. FCurrentItem: PDataRecord;
  59. {$endif}
  60. FBufferSize: Integer;
  61. FExpectedAppends: Integer;
  62. FExpectedDeletes: Integer;
  63. FExpectedUpdates: Integer;
  64. FSaveOnClose: Boolean;
  65. FSaveOnRefetch: Boolean;
  66. FComplexSql: Boolean;
  67. FUpdatedItems: TList;
  68. FAddedItems: TList;
  69. FDeletedItems: TList;
  70. FOrphanItems: TList;
  71. FMasterLink: TMasterDataLink;
  72. FIndexFieldNames: String;
  73. FIndexFieldList: TList;
  74. function GetIndexFields(Value: Integer): TField;
  75. procedure UpdateIndexFields;
  76. protected
  77. FFileName: String;
  78. FSql: String;
  79. FTableName: String;
  80. FAutoIncFieldNo: Integer;
  81. FNextAutoInc:Integer;
  82. FSqliteReturnId: Integer;
  83. FSqliteHandle: Pointer;
  84. FDataAllocated: Boolean;
  85. FRowBufferSize: Integer;
  86. FRowCount: Integer;
  87. FRecordCount: Integer;
  88. FBeginItem: PDataRecord;
  89. FEndItem: PDataRecord;
  90. FCacheItem: PDataRecord;
  91. function SqliteExec(AHandle: Pointer; Sql:PChar):Integer;virtual; abstract;
  92. procedure SqliteClose(AHandle: Pointer);virtual;abstract;
  93. function GetSqliteHandle: Pointer; virtual; abstract;
  94. procedure BuildLinkedList; virtual; abstract;
  95. function SqliteReturnString: String; virtual; abstract;
  96. function TableExists: Boolean;virtual;abstract;
  97. procedure DisposeLinkedList;
  98. procedure MasterChanged(Sender: TObject);
  99. procedure MasterDisabled(Sender: TObject);
  100. procedure SetMasterFields(Value:String);
  101. function GetMasterFields:String;
  102. procedure SetMasterSource(Value: TDataSource);
  103. function GetMasterSource:TDataSource;
  104. //TDataSet overrides
  105. function AllocRecordBuffer: PChar; override;
  106. function CreateBlobStream(Field: TField; Mode: TBlobStreamMode): TStream; override;
  107. procedure FreeRecordBuffer(var Buffer: PChar); override;
  108. procedure GetBookmarkData(Buffer: PChar; Data: Pointer); override;
  109. function GetBookmarkFlag(Buffer: PChar): TBookmarkFlag; override;
  110. function GetRecord(Buffer: PChar; GetMode: TGetMode; DoCheck: Boolean): TGetResult; override;
  111. function GetRecordCount: Integer; override;
  112. function GetRecNo: Integer; override;
  113. function GetRecordSize: Word; override;
  114. procedure InternalAddRecord(Buffer: Pointer; DoAppend: Boolean); override;
  115. procedure InternalClose; override;
  116. procedure InternalDelete; override;
  117. procedure InternalFirst; override;
  118. procedure InternalGotoBookmark(ABookmark: Pointer); override;
  119. procedure InternalHandleException; override;
  120. procedure InternalInitRecord(Buffer: PChar); override;
  121. procedure InternalLast; override;
  122. procedure InternalOpen; override;
  123. procedure InternalPost; override;
  124. procedure InternalSetToRecord(Buffer: PChar); override;
  125. function IsCursorOpen: Boolean; override;
  126. procedure SetBookmarkData(Buffer: PChar; Data: Pointer); override;
  127. procedure SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag); override;
  128. procedure SetExpectedAppends(AValue:Integer);
  129. procedure SetExpectedUpdates(AValue:Integer);
  130. procedure SetExpectedDeletes(AValue:Integer);
  131. procedure SetFieldData(Field: TField; Buffer: Pointer); override;
  132. procedure SetRecNo(Value: Integer); override;
  133. public
  134. constructor Create(AOwner: TComponent); override;
  135. destructor Destroy; override;
  136. function GetFieldData(Field: TField; Buffer: Pointer): Boolean; override;
  137. // Additional procedures
  138. function ApplyUpdates: Boolean; virtual;
  139. function CreateTable: Boolean; virtual;
  140. function ExecSQL:Integer;
  141. function ExecSQL(const ASql:String):Integer;
  142. procedure RefetchData;
  143. function UpdatesPending: Boolean;
  144. {$ifdef DEBUGACTIVEBUFFER}
  145. procedure SetCurrentItem(Value:PDataRecord);
  146. property FCurrentItem: PDataRecord read FFCurrentItem write SetCurrentItem;
  147. {$endif}
  148. {$ifdef USE_SQLITEDS_INTERNALS}
  149. property BeginItem: PDataRecord read FBeginItem;
  150. property EndItem: PDataRecord read FEndItem;
  151. property UpdatedItems: TList read FUpdatedItems;
  152. property AddedItems: TList read FAddedItems;
  153. property DeletedItems: TList read FDeletedItems;
  154. {$endif}
  155. property ComplexSql: Boolean read FComplexSql write FComplexSql;
  156. property ExpectedAppends: Integer read FExpectedAppends write SetExpectedAppends;
  157. property ExpectedUpdates: Integer read FExpectedUpdates write SetExpectedUpdates;
  158. property ExpectedDeletes: Integer read FExpectedDeletes write SetExpectedDeletes;
  159. property IndexFields[Value: Integer]: TField read GetIndexFields;
  160. property SqliteReturnId: Integer read FSqliteReturnId;
  161. published
  162. property IndexFieldNames: string read FIndexFieldNames write FIndexFieldNames;
  163. property FileName: String read FFileName write FFileName;
  164. property PrimaryKey: String read FPrimaryKey write FPrimaryKey;
  165. property SaveOnClose: Boolean read FSaveOnClose write FSaveOnClose;
  166. property SaveOnRefetch: Boolean read FSaveOnRefetch write FSaveOnRefetch;
  167. property SQL: String read FSql write FSql;
  168. property TableName: String read FTableName write FTableName;
  169. property MasterSource: TDataSource read GetMasterSource write SetMasterSource;
  170. property MasterFields: string read GetMasterFields write SetMasterFields;
  171. property Active;
  172. property FieldDefs;
  173. //Events
  174. property BeforeOpen;
  175. property AfterOpen;
  176. property BeforeClose;
  177. property AfterClose;
  178. property BeforeInsert;
  179. property AfterInsert;
  180. property BeforeEdit;
  181. property AfterEdit;
  182. property BeforePost;
  183. property AfterPost;
  184. property BeforeCancel;
  185. property AfterCancel;
  186. property BeforeDelete;
  187. property AfterDelete;
  188. property BeforeScroll;
  189. property AfterScroll;
  190. property OnDeleteError;
  191. property OnEditError;
  192. end;
  193. implementation
  194. uses
  195. strutils;
  196. const
  197. SQLITE_OK = 0;//sqlite2.x.x and sqlite3.x.x defines this equal
  198. // TDSStream
  199. constructor TDSStream.Create(const ActiveItem: PDataRecord; FieldIndex:Integer);
  200. begin
  201. inherited Create;
  202. FPosition:=0;
  203. FActiveItem:=ActiveItem;
  204. FFieldIndex:=FieldIndex;
  205. FFieldRow:=ActiveItem^.Row[FieldIndex];
  206. if FFieldRow <> nil then
  207. FRowSize:=StrLen(FFieldRow)
  208. else
  209. FRowSize:=0;
  210. end;
  211. function TDSStream.Seek(Offset: Longint; Origin: Word): Longint;
  212. begin
  213. Case Origin of
  214. soFromBeginning : FPosition:=Offset;
  215. soFromEnd : FPosition:=FRowSize+Offset;
  216. soFromCurrent : FPosition:=FPosition+Offset;
  217. end;
  218. Result:=FPosition;
  219. end;
  220. function TDSStream.Write(const Buffer; Count: Longint): Longint;
  221. var
  222. NewRow:PChar;
  223. begin
  224. Result:=Count;
  225. if Count = 0 then
  226. Exit;
  227. //Todo: see how TDbMemo read/write to field and choose best if order
  228. if FPosition = 0 then
  229. begin
  230. NewRow:=StrAlloc(Count+1);
  231. (NewRow+Count)^:=#0;
  232. Move(Buffer,NewRow^,Count);
  233. end
  234. else
  235. begin
  236. NewRow:=StrAlloc(FRowSize+Count+1);
  237. (NewRow+Count+FRowSize)^:=#0;
  238. Move(FFieldRow^,NewRow^,FRowSize);
  239. Move(Buffer,(NewRow+FRowSize)^,Count);
  240. end;
  241. FActiveItem^.Row[FFieldIndex]:=NewRow;
  242. StrDispose(FFieldRow);
  243. FFieldRow:=NewRow;
  244. FRowSize:=StrLen(NewRow);
  245. Inc(FPosition,Count);
  246. {$ifdef DEBUG}
  247. WriteLn('Writing a BlobStream');
  248. WriteLn('Stream.Size: ',StrLen(NewRow));
  249. WriteLn('Stream Value: ',NewRow);
  250. WriteLn('FPosition:',FPosition);
  251. {$endif}
  252. end;
  253. function TDSStream.Read(var Buffer; Count: Longint): Longint;
  254. var
  255. BytesToMove:Integer;
  256. begin
  257. if (FRowSize - FPosition) >= Count then
  258. BytesToMove:=Count
  259. else
  260. BytesToMove:=FRowSize - FPosition;
  261. Move((FFieldRow+FPosition)^,Buffer,BytesToMove);
  262. Inc(FPosition,BytesToMove);
  263. Result:=BytesToMove;
  264. {$ifdef DEBUG}
  265. WriteLn('Reading a BlobStream');
  266. WriteLn('Bytes requested: ',Count);
  267. WriteLn('Bytes Moved: ',BytesToMove);
  268. WriteLn('Stream.Size: ',FRowSize);
  269. WriteLn('Stream Value: ',FFieldRow);
  270. {$endif}
  271. end;
  272. // TCustomSqliteDataset override methods
  273. function TCustomSqliteDataset.AllocRecordBuffer: PChar;
  274. var
  275. APointer:Pointer;
  276. begin
  277. APointer := AllocMem(FBufferSize);
  278. PDataRecord(APointer^):=FBeginItem;
  279. Result:=APointer;
  280. end;
  281. constructor TCustomSqliteDataset.Create(AOwner: TComponent);
  282. begin
  283. // setup special items
  284. New(FBeginItem);
  285. New(FCacheItem);
  286. New(FEndItem);
  287. FBeginItem^.Previous:=nil;
  288. FEndItem^.Next:=nil;
  289. FBeginItem^.BookMarkFlag:=bfBOF;
  290. FCacheItem^.BookMarkFlag:=bfEOF;
  291. FEndItem^.BookMarkFlag:=bfEOF;
  292. FMasterLink:=TMasterDataLink.Create(Self);
  293. FMasterLink.OnMasterChange:=@MasterChanged;
  294. FMasterLink.OnMasterDisable:=@MasterDisabled;
  295. FIndexFieldList:=TList.Create;
  296. BookmarkSize := SizeOf(Pointer);
  297. FBufferSize := SizeOf(PPDataRecord);
  298. FUpdatedItems:= TList.Create;
  299. FUpdatedItems.Capacity:=20;
  300. FAddedItems:= TList.Create;
  301. FAddedItems.Capacity:=20;
  302. FOrphanItems:= TList.Create;
  303. FOrphanItems.Capacity:=20;
  304. FDeletedItems:= TList.Create;
  305. FDeletedItems.Capacity:=20;
  306. inherited Create(AOwner);
  307. end;
  308. function TCustomSqliteDataset.CreateBlobStream(Field: TField; Mode: TBlobStreamMode): TStream;
  309. begin
  310. Result:= TDSStream.Create(PPDataRecord(ActiveBuffer)^,Field.FieldNo - 1);
  311. end;
  312. destructor TCustomSqliteDataset.Destroy;
  313. begin
  314. inherited Destroy;
  315. FUpdatedItems.Destroy;
  316. FAddedItems.Destroy;
  317. FDeletedItems.Destroy;
  318. FOrphanItems.Destroy;
  319. FMasterLink.Destroy;
  320. FIndexFieldList.Destroy;
  321. // dispose special items
  322. Dispose(FBeginItem);
  323. Dispose(FCacheItem);
  324. Dispose(FEndItem);
  325. end;
  326. function TCustomSqliteDataset.GetIndexFields(Value: Integer): TField;
  327. begin
  328. if (Value < 0) or (Value > FIndexFieldList.Count - 1) then
  329. DatabaseError('Error acessing IndexFields: Index out of bonds');
  330. Result:= TField(FIndexFieldList[Value]);
  331. end;
  332. procedure TCustomSqliteDataset.DisposeLinkedList;
  333. var
  334. TempItem:PDataRecord;
  335. Counter,I:Integer;
  336. begin
  337. //Todo: insert debug info
  338. FDataAllocated:=False;
  339. TempItem:=FBeginItem^.Next;
  340. if TempItem <> nil then
  341. while TempItem^.Next <> nil do
  342. begin
  343. for Counter:= 0 to FRowCount - 1 do
  344. StrDispose(TempItem^.Row[Counter]);
  345. FreeMem(TempItem^.Row,FRowBufferSize);
  346. TempItem:=TempItem^.Next;
  347. Dispose(TempItem^.Previous);
  348. end;
  349. //Dispose FBeginItem.Row
  350. FreeMem(FBeginItem^.Row,FRowBufferSize);
  351. //Dispose cache item
  352. for Counter:= 0 to FRowCount - 1 do
  353. StrDispose(FCacheItem^.Row[Counter]);
  354. FreeMem(FCacheItem^.Row,FRowBufferSize);
  355. //Dispose OrphanItems
  356. for Counter:= 0 to FOrphanItems.Count - 1 do
  357. begin
  358. TempItem:=PDataRecord(FOrphanItems[Counter]);
  359. for I:= 0 to FRowCount - 1 do
  360. StrDispose(TempItem^.Row[I]);
  361. FreeMem(TempItem^.Row,FRowBufferSize);
  362. Dispose(TempItem);
  363. end;
  364. end;
  365. procedure TCustomSqliteDataset.FreeRecordBuffer(var Buffer: PChar);
  366. begin
  367. FreeMem(Buffer);
  368. end;
  369. procedure TCustomSqliteDataset.GetBookmarkData(Buffer: PChar; Data: Pointer);
  370. begin
  371. Pointer(Data^) := PPDataRecord(Buffer)^^.BookmarkData;
  372. end;
  373. function TCustomSqliteDataset.GetBookmarkFlag(Buffer: PChar): TBookmarkFlag;
  374. begin
  375. Result := PPDataRecord(Buffer)^^.BookmarkFlag;
  376. end;
  377. function TCustomSqliteDataset.GetFieldData(Field: TField; Buffer: Pointer): Boolean;
  378. var
  379. ValError:Word;
  380. FieldRow:PChar;
  381. begin
  382. FieldRow:=PPDataRecord(ActiveBuffer)^^.Row[Field.FieldNo - 1];
  383. Result := FieldRow <> nil;
  384. if Result and (Buffer <> nil) then //supports GetIsNull
  385. begin
  386. case Field.Datatype of
  387. ftString:
  388. begin
  389. Move(FieldRow^,PChar(Buffer)^,StrLen(FieldRow)+1);
  390. end;
  391. ftInteger,ftBoolean,ftWord,ftAutoInc:
  392. begin
  393. Val(StrPas(FieldRow),LongInt(Buffer^),ValError);
  394. Result:= ValError = 0;
  395. end;
  396. ftFloat,ftDateTime,ftTime,ftDate:
  397. begin
  398. Val(StrPas(FieldRow),Double(Buffer^),ValError);
  399. Result:= ValError = 0;
  400. end;
  401. end;
  402. end;
  403. end;
  404. function TCustomSqliteDataset.GetRecord(Buffer: PChar; GetMode: TGetMode; DoCheck: Boolean): TGetResult;
  405. begin
  406. Result := grOk;
  407. case GetMode of
  408. gmPrior:
  409. if (FCurrentItem^.Previous = FBeginItem) or (FCurrentItem = FBeginItem) then
  410. begin
  411. Result := grBOF;
  412. FCurrentItem := FBeginItem;
  413. end
  414. else
  415. FCurrentItem:=FCurrentItem^.Previous;
  416. gmCurrent:
  417. if (FCurrentItem = FBeginItem) or (FCurrentItem = FEndItem) then
  418. Result := grError;
  419. gmNext:
  420. if (FCurrentItem = FEndItem) or (FCurrentItem^.Next = FEndItem) then
  421. Result := grEOF
  422. else
  423. FCurrentItem:=FCurrentItem^.Next;
  424. end; //case
  425. if Result = grOk then
  426. begin
  427. PDataRecord(Pointer(Buffer)^):=FCurrentItem;
  428. with FCurrentItem^ do
  429. begin
  430. BookmarkData := FCurrentItem;
  431. BookmarkFlag := bfCurrent;
  432. end;
  433. end
  434. else if (Result = grError) and DoCheck then
  435. DatabaseError('SqliteDs - No records',Self);
  436. end;
  437. function TCustomSqliteDataset.GetRecordCount: Integer;
  438. begin
  439. Result := FRecordCount;
  440. end;
  441. function TCustomSqliteDataset.GetRecNo: Integer;
  442. var
  443. TempItem,TempActive:PDataRecord;
  444. begin
  445. Result:= -1;
  446. if FRecordCount = 0 then
  447. Exit;
  448. TempItem:=FBeginItem;
  449. TempActive:=PPDataRecord(ActiveBuffer)^;
  450. if TempActive = FCacheItem then // Record not posted yet
  451. Result:=FRecordCount
  452. else
  453. while TempActive <> TempItem do
  454. begin
  455. if TempItem^.Next <> nil then
  456. begin
  457. inc(Result);
  458. TempItem:=TempItem^.Next;
  459. end
  460. else
  461. begin
  462. Result:=-1;
  463. DatabaseError('Sqliteds.GetRecNo - ActiveItem Not Found',Self);
  464. break;
  465. end;
  466. end;
  467. end;
  468. function TCustomSqliteDataset.GetRecordSize: Word;
  469. begin
  470. Result := FBufferSize; //??
  471. end;
  472. procedure TCustomSqliteDataset.InternalAddRecord(Buffer: Pointer; DoAppend: Boolean);
  473. var
  474. NewItem: PDataRecord;
  475. Counter:Integer;
  476. begin
  477. {$ifdef DEBUG}
  478. if PPDataRecord(Buffer)^ <> FCacheItem then
  479. DatabaseError('PPDataRecord(Buffer) <> FCacheItem - Problem',Self);
  480. {$endif}
  481. New(NewItem);
  482. GetMem(NewItem^.Row,FRowBufferSize);
  483. for Counter := 0 to FRowCount - 1 do
  484. NewItem^.Row[Counter]:=StrNew(FCacheItem^.Row[Counter]);
  485. FEndItem^.Previous^.Next:=NewItem;
  486. NewItem^.Previous:=FEndItem^.Previous;
  487. NewItem^.Next:=FEndItem;
  488. FEndItem^.Previous:=NewItem;
  489. Inc(FRecordCount);
  490. if FAutoIncFieldNo <> - 1 then
  491. Inc(FNextAutoInc);
  492. FAddedItems.Add(NewItem);
  493. end;
  494. procedure TCustomSqliteDataset.InternalClose;
  495. begin
  496. if FSaveOnClose then
  497. ApplyUpdates;
  498. //BindFields(False);
  499. if DefaultFields then
  500. DestroyFields;
  501. if FDataAllocated then
  502. DisposeLinkedList;
  503. if FSqliteHandle <> nil then
  504. begin
  505. SqliteClose(FSqliteHandle);
  506. FSqliteHandle := nil;
  507. end;
  508. FAddedItems.Clear;
  509. FUpdatedItems.Clear;
  510. FDeletedItems.Clear;
  511. FOrphanItems.Clear;
  512. FRecordCount:=0;
  513. end;
  514. procedure TCustomSqliteDataset.InternalDelete;
  515. var
  516. TempItem:PDataRecord;
  517. ValError,TempInteger:Integer;
  518. begin
  519. If FRecordCount = 0 then
  520. Exit;
  521. Dec(FRecordCount);
  522. TempItem:=PPDataRecord(ActiveBuffer)^;
  523. // Remove from changed list
  524. FUpdatedItems.Remove(TempItem);
  525. if FAddedItems.Remove(TempItem) = -1 then
  526. FDeletedItems.Add(TempItem);
  527. FOrphanItems.Add(TempItem);
  528. TempItem^.Next^.Previous:=TempItem^.Previous;
  529. TempItem^.Previous^.Next:=TempItem^.Next;
  530. if FCurrentItem = TempItem then
  531. begin
  532. if FCurrentItem^.Previous <> FBeginItem then
  533. FCurrentItem:= FCurrentItem^.Previous
  534. else
  535. FCurrentItem:= FCurrentItem^.Next;
  536. end;
  537. // Dec FNextAutoInc (only if deleted item is the last record)
  538. if FAutoIncFieldNo <> -1 then
  539. begin
  540. Val(StrPas(TempItem^.Row[FAutoIncFieldNo]),TempInteger,ValError);
  541. if (ValError = 0) and (TempInteger = (FNextAutoInc - 1)) then
  542. Dec(FNextAutoInc);
  543. end;
  544. end;
  545. procedure TCustomSqliteDataset.InternalFirst;
  546. begin
  547. FCurrentItem := FBeginItem;
  548. end;
  549. procedure TCustomSqliteDataset.InternalGotoBookmark(ABookmark: Pointer);
  550. begin
  551. FCurrentItem := PDataRecord(ABookmark^);
  552. end;
  553. procedure TCustomSqliteDataset.InternalHandleException;
  554. begin
  555. //??
  556. end;
  557. procedure TCustomSqliteDataset.InternalInitRecord(Buffer: PChar);
  558. var
  559. Counter:Integer;
  560. TempStr:String;
  561. begin
  562. for Counter:= 0 to FRowCount - 1 do
  563. begin
  564. StrDispose(FCacheItem^.Row[Counter]);
  565. FCacheItem^.Row[Counter]:=nil;
  566. end;
  567. if FAutoIncFieldNo <> - 1 then
  568. begin
  569. Str(FNextAutoInc,TempStr);
  570. FCacheItem^.Row[FAutoIncFieldNo]:=StrAlloc(Length(TempStr)+1);
  571. StrPCopy(FCacheItem^.Row[FAutoIncFieldNo],TempStr);
  572. end;
  573. PPDataRecord(Buffer)^:=FCacheItem;
  574. end;
  575. procedure TCustomSqliteDataset.InternalLast;
  576. begin
  577. FCurrentItem := FEndItem;
  578. end;
  579. procedure TCustomSqliteDataset.InternalOpen;
  580. begin
  581. FAutoIncFieldNo:=-1;
  582. if not FileExists(FFileName) then
  583. DatabaseError('TCustomSqliteDataset - File '+FFileName+' not found');
  584. if (FTablename = '') and not (FComplexSql) then
  585. DatabaseError('TCustomSqliteDataset - Tablename not set');
  586. if MasterSource <> nil then
  587. begin
  588. FSql := 'Select * from '+FTableName+';'; // forced to obtain all fields
  589. FMasterLink.FieldNames:=MasterFields; //this should fill MasterLinks.Fields
  590. //todo: ignore if Fields.Count = 0 (OnMasterChanged will not be called) or
  591. // raise a error?
  592. //if (FMasterLink.Fields.Count = 0) and (MasterSource.DataSet.Active) then
  593. // DatabaseError('Master Fields are not defined correctly');
  594. end;
  595. FSqliteHandle:=GetSqliteHandle;
  596. if FSql = '' then
  597. FSql := 'Select * from '+FTableName+';';
  598. InternalInitFieldDefs;
  599. if DefaultFields then
  600. CreateFields;
  601. BindFields(True);
  602. UpdateIndexFields;
  603. if FMasterLink.Active and (FIndexFieldList.Count <> FMasterLink.Fields.Count) then
  604. DatabaseError('MasterFields count doesnt match IndexFields count');
  605. // Get PrimaryKeyNo if available
  606. if Fields.FindField(FPrimaryKey) <> nil then
  607. FPrimaryKeyNo:=Fields.FindField(FPrimaryKey).FieldNo - 1
  608. else
  609. FPrimaryKeyNo:=FAutoIncFieldNo; // -1 if there's no AutoIncField
  610. BuildLinkedList;
  611. FCurrentItem:=FBeginItem;
  612. end;
  613. procedure TCustomSqliteDataset.InternalPost;
  614. begin
  615. if (State<>dsEdit) then
  616. InternalAddRecord(ActiveBuffer,True);
  617. end;
  618. procedure TCustomSqliteDataset.InternalSetToRecord(Buffer: PChar);
  619. begin
  620. FCurrentItem:=PPDataRecord(Buffer)^;
  621. end;
  622. function TCustomSqliteDataset.IsCursorOpen: Boolean;
  623. begin
  624. Result := FDataAllocated;
  625. end;
  626. procedure TCustomSqliteDataset.SetBookmarkData(Buffer: PChar; Data: Pointer);
  627. begin
  628. PPDataRecord(Buffer)^^.BookmarkData := Pointer(Data^);
  629. end;
  630. procedure TCustomSqliteDataset.SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag);
  631. begin
  632. PPDataRecord(Buffer)^^.BookmarkFlag := Value;
  633. end;
  634. procedure TCustomSqliteDataset.SetExpectedAppends(AValue:Integer);
  635. begin
  636. if Assigned(FAddedItems) then
  637. FAddedItems.Capacity:=AValue;
  638. end;
  639. procedure TCustomSqliteDataset.SetExpectedUpdates(AValue:Integer);
  640. begin
  641. if Assigned(FUpdatedItems) then
  642. FUpdatedItems.Capacity:=AValue;
  643. end;
  644. procedure TCustomSqliteDataset.SetExpectedDeletes(AValue:Integer);
  645. begin
  646. if Assigned(FDeletedItems) then
  647. FDeletedItems.Capacity:=AValue;
  648. end;
  649. procedure TCustomSqliteDataset.SetFieldData(Field: TField; Buffer: Pointer);
  650. var
  651. TempStr:String;
  652. ActiveItem:PDataRecord;
  653. begin
  654. ActiveItem:=PPDataRecord(ActiveBuffer)^;
  655. if (ActiveItem <> FCacheItem) and (FUpdatedItems.IndexOf(ActiveItem) = -1) and (FAddedItems.IndexOf(ActiveItem) = -1) then
  656. FUpdatedItems.Add(ActiveItem);
  657. StrDispose(ActiveItem^.Row[Pred(Field.FieldNo)]);
  658. if Buffer <> nil then
  659. begin
  660. case Field.Datatype of
  661. ftString:
  662. begin
  663. ActiveItem^.Row[Pred(Field.FieldNo)]:=StrNew(PChar(Buffer));
  664. end;
  665. ftInteger,ftBoolean,ftWord:
  666. begin
  667. Str(LongInt(Buffer^),TempStr);
  668. ActiveItem^.Row[Pred(Field.FieldNo)]:=StrAlloc(Length(TempStr)+1);
  669. StrPCopy(ActiveItem^.Row[Pred(Field.FieldNo)],TempStr);
  670. end;
  671. ftFloat,ftDateTime,ftDate,ftTime:
  672. begin
  673. Str(Double(Buffer^),TempStr);
  674. ActiveItem^.Row[Pred(Field.FieldNo)]:=StrAlloc(Length(TempStr)+1);
  675. StrPCopy(ActiveItem^.Row[Pred(Field.FieldNo)],TempStr);
  676. end;
  677. end;// case
  678. end//if
  679. else
  680. ActiveItem^.Row[Pred(Field.FieldNo)]:=nil;
  681. end;
  682. procedure TCustomSqliteDataset.SetRecNo(Value: Integer);
  683. var
  684. Counter:Integer;
  685. TempItem:PDataRecord;
  686. begin
  687. if (Value >= FRecordCount) or (Value < 0) then
  688. DatabaseError('SqliteDs - Record Number Out Of Range');
  689. TempItem:=FBeginItem;
  690. for Counter := 0 to Value do
  691. TempItem:=TempItem^.Next;
  692. PPDataRecord(ActiveBuffer)^:=TempItem;
  693. end;
  694. // Specific functions
  695. procedure TCustomSqliteDataset.MasterChanged(Sender: TObject);
  696. function GetSqlStr(AField:TField):String;
  697. begin
  698. case AField.DataType of
  699. ftString,ftMemo: Result:='"'+AField.AsString+'"';//todo: handle " caracter properly
  700. ftDateTime,ftDate,ftTime:Str(AField.AsDateTime,Result);
  701. else
  702. Result:=AField.AsString;
  703. end;//case
  704. end;//function
  705. var
  706. AFilter:String;
  707. i:Integer;
  708. begin
  709. AFilter:=' where ';
  710. for i:= 0 to FMasterLink.Fields.Count - 1 do
  711. begin
  712. AFilter:=AFilter + IndexFields[i].FieldName +' = '+ GetSqlStr(TField(FMasterLink.Fields[i]));
  713. if i <> FMasterLink.Fields.Count - 1 then
  714. AFilter:= AFilter + ' and ';
  715. end;
  716. FSql:='Select * from '+FTableName+AFilter;
  717. {$ifdef DEBUG}
  718. writeln('Sql used to filter detail dataset:');
  719. writeln(FSql);
  720. {$endif}
  721. RefetchData;
  722. end;
  723. procedure TCustomSqliteDataset.MasterDisabled(Sender: TObject);
  724. begin
  725. FSql:='Select * from '+FTableName+';';
  726. RefetchData;
  727. end;
  728. procedure TCustomSqliteDataset.SetMasterFields(Value: String);
  729. begin
  730. if Active then
  731. DatabaseError('It''s not allowed to set MasterFields property in a open dataset');
  732. FMasterLink.FieldNames:=Value;
  733. end;
  734. function TCustomSqliteDataset.GetMasterFields: String;
  735. begin
  736. Result:=FMasterLink.FieldNames;
  737. end;
  738. procedure TCustomSqliteDataset.UpdateIndexFields;
  739. begin
  740. if FIndexFieldNames <> '' then
  741. begin
  742. FIndexFieldList.Clear;
  743. try
  744. GetFieldList(FIndexFieldList, FIndexFieldNames);
  745. except
  746. FIndexFieldList.Clear;
  747. raise;
  748. end;
  749. end;
  750. end;
  751. function TCustomSqliteDataset.GetMasterSource: TDataSource;
  752. begin
  753. Result := FMasterLink.DataSource;
  754. end;
  755. procedure TCustomSqliteDataset.SetMasterSource(Value: TDataSource);
  756. begin
  757. FMasterLink.DataSource := Value;
  758. end;
  759. function TCustomSqliteDataset.ExecSQL(const ASql:String):Integer;
  760. var
  761. AHandle: Pointer;
  762. begin
  763. Result:=0;
  764. //Todo check if Filename exists
  765. if FSqliteHandle <> nil then
  766. AHandle:=FSqliteHandle
  767. else
  768. if FFileName <> '' then
  769. AHandle := GetSqliteHandle
  770. else
  771. DatabaseError ('ExecSql - FileName not set');
  772. FSqliteReturnId:= SqliteExec(AHandle,PChar(ASql));
  773. //todo: add a way to get the num of changes
  774. //Result:=sqlite_changes(AHandle);
  775. if AHandle <> FSqliteHandle then
  776. SqliteClose(AHandle);
  777. end;
  778. function TCustomSqliteDataset.ExecSQL:Integer;
  779. begin
  780. Result:=ExecSQL(FSql);
  781. end;
  782. function TCustomSqliteDataset.ApplyUpdates:Boolean;
  783. var
  784. CounterFields,CounterItems,StatementsCounter:Integer;
  785. SqlTemp,KeyName,ASqlLine,TemplateStr:String;
  786. begin
  787. Result:=False;
  788. if (FPrimaryKeyNo <> -1) and not FComplexSql then
  789. begin
  790. StatementsCounter:=0;
  791. KeyName:=Fields[FPrimaryKeyNo].FieldName;
  792. {$ifdef DEBUG}
  793. WriteLn('ApplyUpdates called');
  794. if FPrimaryKeyNo = FAutoIncFieldNo then
  795. WriteLn('Using an AutoInc field as primary key');
  796. WriteLn('PrimaryKey: ',KeyName);
  797. WriteLn('PrimaryKeyNo: ',FPrimaryKeyNo);
  798. {$endif}
  799. SqlTemp:='BEGIN TRANSACTION;';
  800. // In some situations (LCL apps) FBeginItems is inserted in FUpdatedItems
  801. FUpdatedItems.Remove(FBeginItem);
  802. // Update changed records
  803. if FUpdatedItems.Count > 0 then
  804. TemplateStr:='UPDATE '+FTableName+' SET ';
  805. for CounterItems:= 0 to FUpdatedItems.Count - 1 do
  806. begin
  807. ASqlLine:=TemplateStr;
  808. for CounterFields:= 0 to Fields.Count - 1 do
  809. begin
  810. if PDataRecord(FUpdatedItems[CounterItems])^.Row[CounterFields] <> nil then
  811. begin
  812. ASqlLine:=ASqlLine + Fields[CounterFields].FieldName +' = ';
  813. if not (Fields[CounterFields].DataType in [ftString,ftMemo]) then
  814. ASqlLine:=ASqlLine+StrPas(PDataRecord(FUpdatedItems[CounterItems])^.Row[CounterFields])+ ','
  815. else
  816. ASqlLine:=ASqlLine+''''+
  817. AnsiReplaceStr(StrPas(PDataRecord(FUpdatedItems[CounterItems])^.Row[CounterFields]),'''','''''')+''',';
  818. end
  819. else
  820. ASqlLine:=ASqlLine + Fields[CounterFields].FieldName +' = NULL,';
  821. end;
  822. //Todo: see if system.delete trunks AnsiString
  823. system.delete(ASqlLine,Length(ASqlLine),1);
  824. SqlTemp:=SqlTemp + ASqlLine+' WHERE '+KeyName+' = '+StrPas(PDataRecord(FUpdatedItems[CounterItems])^.Row[FPrimaryKeyNo])+';';
  825. inc(StatementsCounter);
  826. //ApplyUpdates each 400 statements
  827. if StatementsCounter = 400 then
  828. begin
  829. SqlTemp:=SqlTemp+'END TRANSACTION;';
  830. FSqliteReturnId:=SqliteExec(FSqliteHandle,PChar(SqlTemp));
  831. StatementsCounter:=0;
  832. SqlTemp:='BEGIN TRANSACTION;';
  833. end;
  834. end;
  835. // Add new records
  836. // Build TemplateStr
  837. if FAddedItems.Count > 0 then
  838. begin
  839. TemplateStr:='INSERT INTO '+FTableName+ ' (';
  840. for CounterFields:= 0 to Fields.Count - 1 do
  841. begin
  842. TemplateStr:=TemplateStr + Fields[CounterFields].FieldName;
  843. if CounterFields <> Fields.Count - 1 then
  844. TemplateStr:=TemplateStr+',';
  845. end;
  846. TemplateStr:=TemplateStr+') VALUES (';
  847. end;
  848. for CounterItems:= 0 to FAddedItems.Count - 1 do
  849. begin
  850. ASqlLine:=TemplateStr;
  851. for CounterFields:= 0 to Fields.Count - 1 do
  852. begin
  853. if PDataRecord(FAddedItems[CounterItems])^.Row[CounterFields] <> nil then
  854. begin
  855. if not (Fields[CounterFields].DataType in [ftString,ftMemo]) then
  856. ASqlLine:=ASqlLine+StrPas(PDataRecord(FAddedItems[CounterItems])^.Row[CounterFields])
  857. else
  858. ASqlLine:=ASqlLine+''''+
  859. AnsiReplaceStr(StrPas(PDataRecord(FAddedItems[CounterItems])^.Row[CounterFields]),'''','''''')+'''';
  860. end
  861. else
  862. ASqlLine:=ASqlLine + 'NULL';
  863. //Todo: see if delete ASqline is faster
  864. if CounterFields <> Fields.Count - 1 then
  865. ASqlLine:=ASqlLine+',';
  866. end;
  867. SqlTemp:=SqlTemp+ASqlLine+');';
  868. inc(StatementsCounter);
  869. //ApplyUpdates each 400 statements
  870. if StatementsCounter = 400 then
  871. begin
  872. SqlTemp:=SqlTemp+'END TRANSACTION;';
  873. FSqliteReturnId:=SqliteExec(FSqliteHandle,PChar(SqlTemp));
  874. StatementsCounter:=0;
  875. SqlTemp:='BEGIN TRANSACTION;';
  876. end;
  877. end;
  878. // Delete Items
  879. if FDeletedItems.Count > 0 then
  880. TemplateStr:='DELETE FROM '+FTableName+ ' WHERE '+KeyName+' = ';
  881. for CounterItems:= 0 to FDeletedItems.Count - 1 do
  882. begin
  883. SqlTemp:=SqlTemp+TemplateStr+
  884. StrPas(PDataRecord(FDeletedItems[CounterItems])^.Row[FPrimaryKeyNo])+';';
  885. inc(StatementsCounter);
  886. //ApplyUpdates each 400 statements
  887. if StatementsCounter = 400 then
  888. begin
  889. SqlTemp:=SqlTemp+'END TRANSACTION;';
  890. FSqliteReturnId:=SqliteExec(FSqliteHandle,PChar(SqlTemp));
  891. StatementsCounter:=0;
  892. SqlTemp:='BEGIN TRANSACTION;';
  893. end;
  894. end;
  895. SqlTemp:=SqlTemp+'END TRANSACTION;';
  896. {$ifdef DEBUG}
  897. writeln('ApplyUpdates Sql: ',SqlTemp);
  898. {$endif}
  899. FAddedItems.Clear;
  900. FUpdatedItems.Clear;
  901. FDeletedItems.Clear;
  902. FSqliteReturnId:=SqliteExec(FSqliteHandle,PChar(SqlTemp));
  903. Result:= FSqliteReturnId = SQLITE_OK;
  904. end;
  905. {$ifdef DEBUG}
  906. writeln('ApplyUpdates Result: ',Result);
  907. {$endif}
  908. end;
  909. function TCustomSqliteDataset.CreateTable: Boolean;
  910. var
  911. SqlTemp:String;
  912. Counter:Integer;
  913. begin
  914. {$ifdef DEBUG}
  915. if FTableName = '' then
  916. WriteLn('CreateTable : TableName Not Set');
  917. if FieldDefs.Count = 0 then
  918. WriteLn('CreateTable : FieldDefs Not Initialized');
  919. {$endif}
  920. if (FTableName <> '') and (FieldDefs.Count > 0) then
  921. begin
  922. FSqliteHandle:= GetSqliteHandle;
  923. SqlTemp:='CREATE TABLE '+FTableName+' (';
  924. for Counter := 0 to FieldDefs.Count-1 do
  925. begin
  926. SqlTemp:=SqlTemp + FieldDefs[Counter].Name;
  927. case FieldDefs[Counter].DataType of
  928. ftInteger:
  929. SqlTemp:=SqlTemp + ' INTEGER';
  930. ftString:
  931. SqlTemp:=SqlTemp + ' VARCHAR';
  932. ftBoolean:
  933. SqlTemp:=SqlTemp + ' BOOLEAN';
  934. ftFloat:
  935. SqlTemp:=SqlTemp + ' FLOAT';
  936. ftWord:
  937. SqlTemp:=SqlTemp + ' WORD';
  938. ftDateTime:
  939. SqlTemp:=SqlTemp + ' DATETIME';
  940. ftDate:
  941. SqlTemp:=SqlTemp + ' DATE';
  942. ftTime:
  943. SqlTemp:=SqlTemp + ' TIME';
  944. ftAutoInc:
  945. SqlTemp:=SqlTemp + ' AUTOINC';
  946. ftMemo:
  947. SqlTemp:=SqlTemp + ' MEMO';
  948. else
  949. SqlTemp:=SqlTemp + ' VARCHAR';
  950. end;
  951. if Counter <> FieldDefs.Count - 1 then
  952. SqlTemp:=SqlTemp+ ' , ';
  953. end;
  954. SqlTemp:=SqlTemp+');';
  955. {$ifdef DEBUG}
  956. writeln('CreateTable Sql: ',SqlTemp);
  957. {$endif}
  958. FSqliteReturnId:=SqliteExec(FSqliteHandle,PChar(SqlTemp));
  959. Result:= FSqliteReturnId = SQLITE_OK;
  960. SqliteClose(FSqliteHandle);
  961. FSqliteHandle:=nil;
  962. end
  963. else
  964. Result:=False;
  965. end;
  966. procedure TCustomSqliteDataset.RefetchData;
  967. var
  968. i:Integer;
  969. begin
  970. //Close
  971. if FSaveOnRefetch then
  972. ApplyUpdates;
  973. if FDataAllocated then
  974. DisposeLinkedList;
  975. FAddedItems.Clear;
  976. FUpdatedItems.Clear;
  977. FDeletedItems.Clear;
  978. FOrphanItems.Clear;
  979. //Reopen
  980. BuildLinkedList;
  981. FCurrentItem:=FBeginItem;
  982. for i := 0 to BufferCount - 1 do
  983. PPDataRecord(Buffers[i])^:=FBeginItem;
  984. Resync([]);
  985. end;
  986. function TCustomSqliteDataset.UpdatesPending: Boolean;
  987. begin
  988. Result:= (FDeletedItems.Count > 0) or
  989. (FAddedItems.Count > 0) or (FUpdatedItems.Count > 0);
  990. end;
  991. {$ifdef DEBUGACTIVEBUFFER}
  992. procedure TCustomSqliteDataset.SetCurrentItem(Value:PDataRecord);
  993. var
  994. ANo:Integer;
  995. function GetItemPos:Integer;
  996. var
  997. TempItem:PDataRecord;
  998. begin
  999. Result:= -1;
  1000. TempItem:=FBeginItem;
  1001. if Value = FCacheItem then
  1002. Result:=-2
  1003. else
  1004. while Value <> TempItem do
  1005. begin
  1006. if TempItem^.Next <> nil then
  1007. begin
  1008. inc(Result);
  1009. TempItem:=TempItem^.Next;
  1010. end
  1011. else
  1012. begin
  1013. Result:=-1;
  1014. break;
  1015. end;
  1016. end;
  1017. end;
  1018. begin
  1019. if Value = FBeginItem then
  1020. begin
  1021. writeln('FCurrentItem set to FBeginItem: ',IntToHex(Integer(Value),0));
  1022. FFCurrentItem:=Value;
  1023. end
  1024. else
  1025. if Value = FEndItem then
  1026. begin
  1027. writeln('FCurrentItem set to FEndItem: ',IntToHex(Integer(Value),0));
  1028. FFCurrentItem:=Value;
  1029. end
  1030. else
  1031. if Value = FCacheItem then
  1032. begin
  1033. writeln('FCurrentItem set to FCacheItem: ',IntToHex(Integer(Value),0));
  1034. FFCurrentItem:=Value;
  1035. end
  1036. else
  1037. begin
  1038. writeln('FCurrentItem set from ',IntToHex(Integer(FFCurrentItem),0),' to ',IntToHex(Integer(Value),0));
  1039. Ano:=GetItemPos;
  1040. writeln('Item position is ',ANo);
  1041. FFCurrentItem:=Value;
  1042. end;
  1043. end;
  1044. {$endif}
  1045. end.