customsqliteds.pas 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573
  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 DEBUG_SQLITEDS}
  21. {.$Define DEBUGACTIVEBUFFER}
  22. interface
  23. uses
  24. Classes, SysUtils, Db;
  25. type
  26. PDataRecord = ^DataRecord;
  27. PPDataRecord = ^PDataRecord;
  28. DataRecord = record
  29. Row: PPchar;
  30. BookmarkFlag: TBookmarkFlag;
  31. Next: PDataRecord;
  32. Previous: PDataRecord;
  33. end;
  34. TDSStream = class(TStream)
  35. private
  36. FActiveItem:PDataRecord;
  37. FFieldRow:PChar;
  38. FFieldIndex:Integer;
  39. FRowSize: Integer;
  40. FPosition: Longint;
  41. public
  42. constructor Create(const ActiveItem: PDataRecord; FieldIndex:Integer);
  43. function Write(const Buffer; Count: Longint): Longint; override;
  44. function Read(var Buffer; Count: Longint): Longint; override;
  45. function Seek(Offset: Longint; Origin: Word): Longint; override;
  46. end;
  47. //callback types
  48. TSqliteCdeclCallback = function (UserData:Pointer; Count:LongInt; Values:PPChar; Names:PPChar):LongInt;cdecl;
  49. TSqliteCallback = function (UserData:Pointer; Count:LongInt; Values:PPChar; Names:PPChar):LongInt of Object;
  50. TCallbackInfo = record
  51. Proc: TSqliteCallback;
  52. Data: Pointer;
  53. end;
  54. PCallbackInfo = ^TCallbackInfo;
  55. TRecordState = (rsAdded,rsDeleted,rsUpdated);
  56. TRecordStateSet = set of TRecordState;
  57. TQueryUpdatesCallback = procedure (UserData: Pointer; Values: PPChar; ABookmark: TBookmark; RecordState: TRecordState) of Object;
  58. TGetSqlStrFunction = function (APChar: PChar): String;
  59. { TCustomSqliteDataset }
  60. TCustomSqliteDataset = class(TDataSet)
  61. private
  62. {$ifdef DEBUGACTIVEBUFFER}
  63. FFCurrentItem: PDataRecord;
  64. {$else}
  65. FCurrentItem: PDataRecord;
  66. {$endif}
  67. FInternalActiveBuffer: PDataRecord;
  68. FInsertBookmark: PDataRecord;
  69. FExpectedAppends: Integer;
  70. FExpectedDeletes: Integer;
  71. FExpectedUpdates: Integer;
  72. FOnCallback: TSqliteCallback;
  73. FSaveOnClose: Boolean;
  74. FSaveOnRefetch: Boolean;
  75. FAutoIncrementKey: Boolean;
  76. FMasterLink: TMasterDataLink;
  77. FIndexFieldNames: String;
  78. FIndexFieldList: TList;
  79. FSqlList:TStrings;
  80. procedure CopyCacheToItem(AItem: PDataRecord);
  81. function GetIndexFields(Value: Integer): TField;
  82. procedure UpdateIndexFields;
  83. function FindRecordItem(StartItem: PDataRecord; const KeyFields: string; const KeyValues: Variant; Options: TLocateOptions; DoResync:Boolean):PDataRecord;
  84. protected
  85. FPrimaryKey: String;
  86. FPrimaryKeyNo: Integer;
  87. FFileName: String;
  88. FSql: String;
  89. FTableName: String;
  90. FSelectSqlStr: String;
  91. FAutoIncFieldNo: Integer;
  92. FNextAutoInc:Integer;
  93. FUpdatedItems: TFPList;
  94. FAddedItems: TFPList;
  95. FDeletedItems: TFPList;
  96. FReturnCode: Integer;
  97. FSqliteHandle: Pointer;
  98. FDataAllocated: Boolean;
  99. FRowBufferSize: Integer;
  100. FRowCount: Integer;
  101. FRecordCount: Integer;
  102. FBeginItem: PDataRecord;
  103. FEndItem: PDataRecord;
  104. FCacheItem: PDataRecord;
  105. FGetSqlStr: array of TGetSqlStrFunction;
  106. function SqliteExec(Sql:PChar; ACallback: TSqliteCdeclCallback; Data: Pointer):Integer;virtual; abstract;
  107. procedure InternalCloseHandle;virtual;abstract;
  108. function InternalGetHandle: Pointer; virtual; abstract;
  109. procedure GetSqliteHandle;
  110. function GetSqliteVersion: String; virtual; abstract;
  111. procedure BuildLinkedList; virtual; abstract;
  112. procedure FreeItem(AItem: PDataRecord);
  113. procedure DisposeLinkedList;
  114. procedure SetDetailFilter;
  115. procedure MasterChanged(Sender: TObject);
  116. procedure MasterDisabled(Sender: TObject);
  117. procedure SetMasterFields(Value:String);
  118. function GetMasterFields:String;
  119. procedure SetMasterSource(Value: TDataSource);
  120. function GetMasterSource:TDataSource;
  121. procedure SetFileName(Value: String);
  122. function GetRowsAffected:Integer; virtual;abstract;
  123. //TDataSet overrides
  124. function AllocRecordBuffer: PChar; override;
  125. function CreateBlobStream(Field: TField; Mode: TBlobStreamMode): TStream; override;
  126. procedure DoAfterInsert; override;
  127. procedure DoBeforeInsert; override;
  128. procedure FreeRecordBuffer(var Buffer: PChar); override;
  129. procedure GetBookmarkData(Buffer: PChar; Data: Pointer); override;
  130. function GetBookmarkFlag(Buffer: PChar): TBookmarkFlag; override;
  131. function GetRecord(Buffer: PChar; GetMode: TGetMode; DoCheck: Boolean): TGetResult; override;
  132. function GetRecordCount: Integer; override;
  133. function GetRecNo: Integer; override;
  134. function GetRecordSize: Word; override;
  135. procedure InternalAddRecord(Buffer: Pointer; DoAppend: Boolean); override;
  136. procedure InternalClose; override;
  137. procedure InternalCancel; override;
  138. procedure InternalDelete; override;
  139. procedure InternalEdit; override;
  140. procedure InternalFirst; override;
  141. procedure InternalGotoBookmark(ABookmark: Pointer); override;
  142. procedure InternalInitRecord(Buffer: PChar); override;
  143. procedure InternalLast; override;
  144. procedure InternalOpen; override;
  145. procedure InternalPost; override;
  146. procedure InternalSetToRecord(Buffer: PChar); override;
  147. function IsCursorOpen: Boolean; override;
  148. procedure SetBookmarkData(Buffer: PChar; Data: Pointer); override;
  149. procedure SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag); override;
  150. procedure SetExpectedAppends(AValue:Integer);
  151. procedure SetExpectedUpdates(AValue:Integer);
  152. procedure SetExpectedDeletes(AValue:Integer);
  153. procedure SetFieldData(Field: TField; Buffer: Pointer); override;
  154. procedure SetFieldData(Field: TField; Buffer: Pointer; NativeFormat: Boolean); override;
  155. procedure SetRecNo(Value: Integer); override;
  156. public
  157. constructor Create(AOwner: TComponent); override;
  158. destructor Destroy; override;
  159. function BookmarkValid(ABookmark: TBookmark): Boolean; override;
  160. function CompareBookmarks(Bookmark1, Bookmark2: TBookmark): Longint; override;
  161. function GetFieldData(Field: TField; Buffer: Pointer): Boolean; override;
  162. function GetFieldData(Field: TField; Buffer: Pointer; NativeFormat: Boolean): Boolean; override;
  163. function Locate(const KeyFields: string; const KeyValues: Variant; Options: TLocateOptions) : Boolean; override;
  164. function LocateNext(const KeyFields: string; const KeyValues: Variant; Options: TLocateOptions) : Boolean;
  165. function Lookup(const KeyFields: string; const KeyValues: Variant; const ResultFields: string): Variant;override;
  166. // Additional procedures
  167. function ApplyUpdates: Boolean;
  168. function CreateTable: Boolean;
  169. function CreateTable(const ATableName: String): Boolean;
  170. procedure ExecCallback(const ASql: String; UserData: Pointer = nil);
  171. procedure ExecSQL;
  172. procedure ExecSQL(const ASql:String);
  173. procedure ExecSQLList;
  174. procedure ExecuteDirect(const ASql: String);virtual;abstract;
  175. procedure QueryUpdates(RecordStates: TRecordStateSet; Callback: TQueryUpdatesCallback; UserData: Pointer=nil);
  176. function QuickQuery(const ASql:String):String;overload;
  177. function QuickQuery(const ASql:String;const AStrList: TStrings):String;overload;
  178. function QuickQuery(const ASql:String;const AStrList: TStrings;FillObjects:Boolean):String;virtual;abstract;overload;
  179. procedure RefetchData;
  180. function ReturnString: String; virtual;abstract;
  181. function TableExists: Boolean;
  182. function TableExists(const ATableName:String):Boolean;
  183. function UpdatesPending: Boolean;
  184. {$ifdef DEBUGACTIVEBUFFER}
  185. procedure SetCurrentItem(Value:PDataRecord);
  186. property FCurrentItem: PDataRecord read FFCurrentItem write SetCurrentItem;
  187. {$endif}
  188. property ExpectedAppends: Integer read FExpectedAppends write SetExpectedAppends;
  189. property ExpectedUpdates: Integer read FExpectedUpdates write SetExpectedUpdates;
  190. property ExpectedDeletes: Integer read FExpectedDeletes write SetExpectedDeletes;
  191. property IndexFields[Value: Integer]: TField read GetIndexFields;
  192. property RowsAffected: Integer read GetRowsAffected;
  193. property ReturnCode: Integer read FReturnCode;
  194. property SqliteHandle: Pointer read FSqliteHandle;
  195. property SqliteVersion: String read GetSqliteVersion;
  196. property SQLList:TStrings read FSqlList;
  197. published
  198. property AutoIncrementKey: Boolean read FAutoIncrementKey write FAutoIncrementKey;
  199. property IndexFieldNames: string read FIndexFieldNames write FIndexFieldNames;
  200. property FileName: String read FFileName write SetFileName;
  201. property OnCallback: TSqliteCallback read FOnCallback write FOnCallback;
  202. property PrimaryKey: String read FPrimaryKey write FPrimaryKey;
  203. property SaveOnClose: Boolean read FSaveOnClose write FSaveOnClose;
  204. property SaveOnRefetch: Boolean read FSaveOnRefetch write FSaveOnRefetch;
  205. property SQL: String read FSql write FSql;
  206. property TableName: String read FTableName write FTableName;
  207. property MasterSource: TDataSource read GetMasterSource write SetMasterSource;
  208. property MasterFields: string read GetMasterFields write SetMasterFields;
  209. property Active;
  210. //Events
  211. property BeforeOpen;
  212. property AfterOpen;
  213. property BeforeClose;
  214. property AfterClose;
  215. property BeforeInsert;
  216. property AfterInsert;
  217. property BeforeEdit;
  218. property AfterEdit;
  219. property BeforePost;
  220. property AfterPost;
  221. property BeforeCancel;
  222. property AfterCancel;
  223. property BeforeDelete;
  224. property AfterDelete;
  225. property BeforeScroll;
  226. property AfterScroll;
  227. property BeforeRefresh;
  228. property AfterRefresh;
  229. property OnDeleteError;
  230. property OnEditError;
  231. property OnNewRecord;
  232. property OnPostError;
  233. end;
  234. function Num2SqlStr(APChar: PChar): String;
  235. function Char2SqlStr(APChar: PChar): String;
  236. implementation
  237. uses
  238. strutils, variants, dbconst;
  239. const
  240. //sqlite2.x.x and sqlite3.x.x define these constants equally
  241. SQLITE_OK = 0;
  242. SQLITE_ROW = 100;
  243. NullString = 'NULL';
  244. function CallbackDispatcher(UserData:Pointer; Count:LongInt; Values:PPchar; Names:PPchar):LongInt;cdecl;
  245. begin
  246. with PCallbackInfo(UserData)^ do
  247. Result:= Proc(Data,Count,Values,Names);
  248. end;
  249. function Num2SqlStr(APChar: PChar): String;
  250. begin
  251. if APChar = nil then
  252. begin
  253. Result:=NullString;
  254. Exit;
  255. end;
  256. Result:=StrPas(APChar);
  257. end;
  258. function Char2SqlStr(APChar: PChar): String;
  259. begin
  260. if APChar = nil then
  261. begin
  262. Result:=NullString;
  263. Exit;
  264. end;
  265. //todo: create custom routine to directly transform PChar -> SQL str
  266. Result:=StrPas(APChar);
  267. if Pos('''',Result) > 0 then
  268. Result:=AnsiReplaceStr(Result,'''','''''');
  269. Result:=''''+Result+'''';
  270. end;
  271. // TDSStream
  272. constructor TDSStream.Create(const ActiveItem: PDataRecord; FieldIndex:Integer);
  273. begin
  274. inherited Create;
  275. FPosition:=0;
  276. FActiveItem:=ActiveItem;
  277. FFieldIndex:=FieldIndex;
  278. FFieldRow:=ActiveItem^.Row[FieldIndex];
  279. if FFieldRow <> nil then
  280. FRowSize:=StrLen(FFieldRow)
  281. else
  282. FRowSize:=0;
  283. end;
  284. function TDSStream.Seek(Offset: Longint; Origin: Word): Longint;
  285. begin
  286. Case Origin of
  287. soFromBeginning : FPosition:=Offset;
  288. soFromEnd : FPosition:=FRowSize+Offset;
  289. soFromCurrent : FPosition:=FPosition+Offset;
  290. end;
  291. Result:=FPosition;
  292. end;
  293. function TDSStream.Write(const Buffer; Count: Longint): Longint;
  294. var
  295. NewRow:PChar;
  296. begin
  297. Result:=Count;
  298. if Count = 0 then
  299. Exit;
  300. //FRowSize is always 0 when FPosition = 0,
  301. //so there's no need to check FPosition
  302. NewRow:=StrAlloc(FRowSize+Count+1);
  303. (NewRow+Count+FRowSize)^:=#0;
  304. if FRowSize > 0 then
  305. Move(FFieldRow^,NewRow^,FRowSize);
  306. Move(Buffer,(NewRow+FRowSize)^,Count);
  307. FActiveItem^.Row[FFieldIndex]:=NewRow;
  308. StrDispose(FFieldRow);
  309. {$ifdef DEBUG_SQLITEDS}
  310. WriteLn('##TDSStream.Write##');
  311. WriteLn(' FPosition(Before): ',FPosition);
  312. WriteLn(' FRowSize(Before): ',FRowSize);
  313. WriteLn(' FPosition(After): ',FPosition+Count);
  314. WriteLn(' FRowSize(After): ',StrLen(NewRow));
  315. //WriteLn(' Stream Value: ',NewRow);
  316. {$endif}
  317. FFieldRow:=NewRow;
  318. FRowSize:=StrLen(NewRow);
  319. Inc(FPosition,Count);
  320. end;
  321. function TDSStream.Read(var Buffer; Count: Longint): Longint;
  322. var
  323. BytesToMove:Integer;
  324. begin
  325. if (FRowSize - FPosition) >= Count then
  326. BytesToMove:=Count
  327. else
  328. BytesToMove:=FRowSize - FPosition;
  329. Move((FFieldRow+FPosition)^,Buffer,BytesToMove);
  330. Inc(FPosition,BytesToMove);
  331. Result:=BytesToMove;
  332. {$ifdef DEBUG_SQLITEDS}
  333. WriteLn('##TDSStream.Read##');
  334. WriteLn(' Bytes requested: ',Count);
  335. WriteLn(' Bytes moved: ',BytesToMove);
  336. WriteLn(' Stream.Size: ',FRowSize);
  337. //WriteLn(' Stream Value: ',FFieldRow);
  338. {$endif}
  339. end;
  340. // TCustomSqliteDataset override methods
  341. function TCustomSqliteDataset.AllocRecordBuffer: PChar;
  342. begin
  343. Result := AllocMem(SizeOf(PPDataRecord));
  344. PDataRecord(Pointer(Result)^):=FBeginItem;
  345. end;
  346. constructor TCustomSqliteDataset.Create(AOwner: TComponent);
  347. begin
  348. // setup special items
  349. New(FBeginItem);
  350. New(FCacheItem);
  351. New(FEndItem);
  352. FBeginItem^.Previous:=nil;
  353. FEndItem^.Next:=nil;
  354. FBeginItem^.BookMarkFlag:=bfBOF;
  355. FEndItem^.BookMarkFlag:=bfEOF;
  356. FMasterLink:=TMasterDataLink.Create(Self);
  357. FMasterLink.OnMasterChange:=@MasterChanged;
  358. FMasterLink.OnMasterDisable:=@MasterDisabled;
  359. FIndexFieldList:=TList.Create;
  360. BookmarkSize := SizeOf(Pointer);
  361. FUpdatedItems:= TFPList.Create;
  362. FAddedItems:= TFPList.Create;
  363. FDeletedItems:= TFPList.Create;
  364. FSqlList:=TStringList.Create;
  365. inherited Create(AOwner);
  366. end;
  367. function TCustomSqliteDataset.CreateBlobStream(Field: TField; Mode: TBlobStreamMode): TStream;
  368. begin
  369. if Mode = bmWrite then
  370. begin
  371. if not (State in [dsEdit, dsInsert]) then
  372. begin
  373. DatabaseErrorFmt(SNotEditing,[Name],Self);
  374. Exit;
  375. end;
  376. StrDispose(FCacheItem^.Row[Field.FieldNo - 1]);
  377. FCacheItem^.Row[Field.FieldNo - 1]:=nil;
  378. end;
  379. Result:= TDSStream.Create(PPDataRecord(ActiveBuffer)^,Field.FieldNo - 1);
  380. end;
  381. procedure TCustomSqliteDataset.DoAfterInsert;
  382. begin
  383. //an append or an insert in an empty dataset
  384. if EOF then
  385. FInsertBookmark:=FEndItem
  386. else
  387. FInsertBookmark:=FInternalActiveBuffer;
  388. inherited DoAfterInsert;
  389. end;
  390. procedure TCustomSqliteDataset.DoBeforeInsert;
  391. begin
  392. FInternalActiveBuffer:=PPDataRecord(ActiveBuffer)^;
  393. inherited DoBeforeInsert;
  394. end;
  395. destructor TCustomSqliteDataset.Destroy;
  396. begin
  397. inherited Destroy;
  398. if FSqliteHandle <> nil then
  399. InternalCloseHandle;
  400. FUpdatedItems.Destroy;
  401. FAddedItems.Destroy;
  402. FDeletedItems.Destroy;
  403. FMasterLink.Destroy;
  404. FIndexFieldList.Destroy;
  405. FSqlList.Destroy;
  406. // dispose special items
  407. Dispose(FBeginItem);
  408. Dispose(FCacheItem);
  409. Dispose(FEndItem);
  410. end;
  411. function TCustomSqliteDataset.BookmarkValid(ABookmark: TBookmark): Boolean;
  412. var
  413. TempItem: PDataRecord;
  414. begin
  415. Result:=False;
  416. TempItem:=FBeginItem^.Next;
  417. while TempItem <> FEndItem do
  418. begin
  419. if TempItem = PPDataRecord(ABookmark)^ then
  420. begin
  421. Result:=True;
  422. Exit;
  423. end;
  424. TempItem:=TempItem^.Next;
  425. end;
  426. end;
  427. function TCustomSqliteDataset.CompareBookmarks(Bookmark1, Bookmark2: TBookmark
  428. ): Longint;
  429. var
  430. TempItem: PDataRecord;
  431. begin
  432. if PPDataRecord(Bookmark1)^ = PPDataRecord(Bookmark2)^ then
  433. begin
  434. Result:=0;
  435. Exit;
  436. end;
  437. //assume Bookmark1 < Bookmark2
  438. Result:=-1;
  439. TempItem:=PPDataRecord(Bookmark1)^^.Previous;
  440. while TempItem <> FBeginItem do
  441. begin
  442. if TempItem = PPDataRecord(Bookmark2)^ then
  443. begin
  444. //Bookmark1 is greater than Bookmark2
  445. Result:=1;
  446. Exit;
  447. end;
  448. TempItem:=TempItem^.Previous;
  449. end;
  450. end;
  451. procedure TCustomSqliteDataset.CopyCacheToItem(AItem: PDataRecord);
  452. var
  453. i:Integer;
  454. begin
  455. for i := 0 to FRowCount - 1 do
  456. begin
  457. StrDispose(AItem^.Row[i]);
  458. AItem^.Row[i]:=FCacheItem^.Row[i];
  459. FCacheItem^.Row[i]:=nil;
  460. end;
  461. AItem^.BookmarkFlag:=FCacheItem^.BookmarkFlag;
  462. end;
  463. function TCustomSqliteDataset.GetIndexFields(Value: Integer): TField;
  464. begin
  465. if (Value < 0) or (Value > FIndexFieldList.Count - 1) then
  466. DatabaseError('Error acessing IndexFields: Index out of bonds',Self);
  467. Result:= TField(FIndexFieldList[Value]);
  468. end;
  469. procedure TCustomSqliteDataset.DisposeLinkedList;
  470. var
  471. TempItem:PDataRecord;
  472. i:Integer;
  473. begin
  474. //Todo: insert debug info
  475. //Todo: see if FDataAllocated is still necessary
  476. FDataAllocated:=False;
  477. TempItem:=FBeginItem^.Next;
  478. while TempItem^.Next <> nil do
  479. begin
  480. TempItem:=TempItem^.Next;
  481. FreeItem(TempItem^.Previous);
  482. end;
  483. //Dispose Deleted Items
  484. //Directly access list pointer since the index check is already done in the loop
  485. for i:= 0 to FDeletedItems.Count - 1 do
  486. FreeItem(PDataRecord(FDeletedItems.List^[i]));
  487. //Dispose FBeginItem.Row
  488. FreeMem(FBeginItem^.Row,FRowBufferSize);
  489. //Dispose cache item row
  490. for i:= 0 to FRowCount - 1 do
  491. StrDispose(FCacheItem^.Row[i]);
  492. FreeMem(FCacheItem^.Row,FRowBufferSize);
  493. end;
  494. procedure TCustomSqliteDataset.FreeRecordBuffer(var Buffer: PChar);
  495. begin
  496. FreeMem(Buffer);
  497. end;
  498. procedure TCustomSqliteDataset.GetBookmarkData(Buffer: PChar; Data: Pointer);
  499. begin
  500. Pointer(Data^) := PPDataRecord(Buffer)^;
  501. end;
  502. function TCustomSqliteDataset.GetBookmarkFlag(Buffer: PChar): TBookmarkFlag;
  503. begin
  504. Result := PPDataRecord(Buffer)^^.BookmarkFlag;
  505. end;
  506. function TCustomSqliteDataset.GetFieldData(Field: TField; Buffer: Pointer;
  507. NativeFormat: Boolean): Boolean;
  508. var
  509. ValError:Word;
  510. FieldRow:PChar;
  511. begin
  512. FieldRow:=PPDataRecord(ActiveBuffer)^^.Row[Field.FieldNo - 1];
  513. Result := FieldRow <> nil;
  514. if Result and (Buffer <> nil) then //supports GetIsNull
  515. begin
  516. case Field.Datatype of
  517. ftString:
  518. begin
  519. Move(FieldRow^,PChar(Buffer)^,StrLen(FieldRow)+1);
  520. end;
  521. ftInteger,ftAutoInc:
  522. begin
  523. Val(StrPas(FieldRow),LongInt(Buffer^),ValError);
  524. Result:= ValError = 0;
  525. end;
  526. ftBoolean,ftWord:
  527. begin
  528. Val(StrPas(FieldRow),Word(Buffer^),ValError);
  529. Result:= ValError = 0;
  530. end;
  531. ftFloat,ftDateTime,ftTime,ftDate,ftCurrency:
  532. begin
  533. Val(StrPas(FieldRow),Double(Buffer^),ValError);
  534. Result:= ValError = 0;
  535. end;
  536. ftLargeInt:
  537. begin
  538. Val(StrPas(FieldRow),Int64(Buffer^),ValError);
  539. Result:= ValError = 0;
  540. end;
  541. end;
  542. end;
  543. end;
  544. function TCustomSqliteDataset.GetFieldData(Field: TField; Buffer: Pointer): Boolean;
  545. begin
  546. Result:=GetFieldData(Field, Buffer, False);
  547. end;
  548. function TCustomSqliteDataset.GetRecord(Buffer: PChar; GetMode: TGetMode; DoCheck: Boolean): TGetResult;
  549. begin
  550. Result := grOk;
  551. case GetMode of
  552. gmPrior:
  553. if (FCurrentItem^.Previous = FBeginItem) or (FCurrentItem = FBeginItem) then
  554. begin
  555. Result := grBOF;
  556. FCurrentItem := FBeginItem;
  557. end
  558. else
  559. FCurrentItem:=FCurrentItem^.Previous;
  560. gmCurrent:
  561. if (FCurrentItem = FBeginItem) or (FCurrentItem = FEndItem) then
  562. Result := grError;
  563. gmNext:
  564. if (FCurrentItem = FEndItem) or (FCurrentItem^.Next = FEndItem) then
  565. Result := grEOF
  566. else
  567. FCurrentItem:=FCurrentItem^.Next;
  568. end; //case
  569. if Result = grOk then
  570. begin
  571. PDataRecord(Pointer(Buffer)^):=FCurrentItem;
  572. FCurrentItem^.BookmarkFlag := bfCurrent;
  573. end
  574. else if (Result = grError) and DoCheck then
  575. DatabaseError('No records found',Self);
  576. end;
  577. function TCustomSqliteDataset.GetRecordCount: Integer;
  578. begin
  579. Result := FRecordCount;
  580. end;
  581. function TCustomSqliteDataset.GetRecNo: Integer;
  582. var
  583. TempItem,TempActive:PDataRecord;
  584. begin
  585. Result:= -1;
  586. if (FRecordCount = 0) or (State = dsInsert) then
  587. Exit;
  588. TempItem:=FBeginItem;
  589. TempActive:=PPDataRecord(ActiveBuffer)^;
  590. if TempActive = FCacheItem then // Record is being edited
  591. begin
  592. TempActive:=FInternalActiveBuffer;
  593. end;
  594. //RecNo is 1 based
  595. inc(Result);
  596. while TempActive <> TempItem do
  597. begin
  598. if TempItem^.Next <> nil then
  599. begin
  600. inc(Result);
  601. TempItem:=TempItem^.Next;
  602. end
  603. else
  604. begin
  605. Result:=-1;
  606. DatabaseError('GetRecNo - ActiveItem Not Found',Self);
  607. break;
  608. end;
  609. end;
  610. end;
  611. function TCustomSqliteDataset.GetRecordSize: Word;
  612. begin
  613. Result := SizeOf(PPDataRecord); //??
  614. end;
  615. procedure TCustomSqliteDataset.InternalAddRecord(Buffer: Pointer; DoAppend: Boolean);
  616. var
  617. NewItem: PDataRecord;
  618. begin
  619. {$ifdef DEBUG_SQLITEDS}
  620. if PPDataRecord(Buffer)^ <> FCacheItem then
  621. DatabaseError('PPDataRecord(Buffer) <> FCacheItem - Problem',Self);
  622. {$endif}
  623. New(NewItem);
  624. GetMem(NewItem^.Row,FRowBufferSize);
  625. //necessary to nullify the Row before copy the cache
  626. FillChar(NewItem^.Row^,FRowBufferSize,#0);
  627. CopyCacheToItem(NewItem);
  628. //insert in the linked list
  629. FInsertBookmark^.Previous^.Next:=NewItem;
  630. NewItem^.Next:=FInsertBookmark;
  631. NewItem^.Previous:=FInsertBookmark^.Previous;
  632. FInsertBookmark^.Previous:=NewItem;
  633. //update the cursor
  634. FCurrentItem:=NewItem;
  635. Inc(FRecordCount);
  636. if FAutoIncFieldNo <> - 1 then
  637. Inc(FNextAutoInc);
  638. FAddedItems.Add(NewItem);
  639. end;
  640. procedure TCustomSqliteDataset.InternalClose;
  641. begin
  642. if FSaveOnClose then
  643. ApplyUpdates;
  644. //BindFields(False);
  645. if DefaultFields then
  646. DestroyFields;
  647. if FDataAllocated then
  648. DisposeLinkedList;
  649. FAddedItems.Clear;
  650. FUpdatedItems.Clear;
  651. FDeletedItems.Clear;
  652. FRecordCount:=0;
  653. end;
  654. procedure TCustomSqliteDataset.InternalCancel;
  655. var
  656. i: Integer;
  657. begin
  658. PPDataRecord(ActiveBuffer)^:=FInternalActiveBuffer;
  659. //free the cache
  660. for i:= 0 to FRowCount - 1 do
  661. begin
  662. StrDispose(FCacheItem^.Row[i]);
  663. FCacheItem^.Row[i]:=nil;
  664. end;
  665. end;
  666. procedure TCustomSqliteDataset.InternalDelete;
  667. var
  668. TempItem:PDataRecord;
  669. ValError,TempInteger:Integer;
  670. begin
  671. if FRecordCount = 0 then
  672. Exit;
  673. Dec(FRecordCount);
  674. TempItem:=PPDataRecord(ActiveBuffer)^;
  675. TempItem^.Next^.Previous:=TempItem^.Previous;
  676. TempItem^.Previous^.Next:=TempItem^.Next;
  677. if FCurrentItem = TempItem then
  678. begin
  679. if FCurrentItem^.Previous <> FBeginItem then
  680. FCurrentItem:= FCurrentItem^.Previous
  681. else
  682. FCurrentItem:= FCurrentItem^.Next;
  683. end;
  684. // Dec FNextAutoInc (only if deleted item is the last record)
  685. if FAutoIncFieldNo <> -1 then
  686. begin
  687. Val(StrPas(TempItem^.Row[FAutoIncFieldNo]),TempInteger,ValError);
  688. if (ValError = 0) and (TempInteger = (FNextAutoInc - 1)) then
  689. Dec(FNextAutoInc);
  690. end;
  691. // Update item lists
  692. FUpdatedItems.Remove(TempItem);
  693. if FAddedItems.Remove(TempItem) = -1 then
  694. FDeletedItems.Add(TempItem)
  695. else
  696. FreeItem(TempItem);
  697. end;
  698. procedure TCustomSqliteDataset.InternalEdit;
  699. var
  700. i: Integer;
  701. begin
  702. FInternalActiveBuffer:=PPDataRecord(ActiveBuffer)^;
  703. //copy active item to cache
  704. for i:= 0 to FRowCount - 1 do
  705. FCacheItem^.Row[i]:=StrNew(FInternalActiveBuffer^.Row[i]);
  706. FCacheItem^.BookmarkFlag:=FInternalActiveBuffer^.BookmarkFlag;
  707. //now active buffer is the cache item
  708. PPDataRecord(ActiveBuffer)^:=FCacheItem;
  709. end;
  710. procedure TCustomSqliteDataset.InternalFirst;
  711. begin
  712. FCurrentItem := FBeginItem;
  713. end;
  714. procedure TCustomSqliteDataset.InternalGotoBookmark(ABookmark: Pointer);
  715. begin
  716. FCurrentItem := PDataRecord(ABookmark^);
  717. end;
  718. procedure TCustomSqliteDataset.InternalInitRecord(Buffer: PChar);
  719. var
  720. TempStr:String;
  721. begin
  722. if FAutoIncFieldNo <> - 1 then
  723. begin
  724. Str(FNextAutoInc,TempStr);
  725. FCacheItem^.Row[FAutoIncFieldNo]:=StrAlloc(Length(TempStr)+1);
  726. StrPCopy(FCacheItem^.Row[FAutoIncFieldNo],TempStr);
  727. end;
  728. PPDataRecord(Buffer)^:=FCacheItem;
  729. FCacheItem^.BookmarkFlag:=bfInserted;
  730. end;
  731. procedure TCustomSqliteDataset.InternalLast;
  732. begin
  733. FCurrentItem := FEndItem;
  734. end;
  735. procedure TCustomSqliteDataset.InternalOpen;
  736. var
  737. i:Integer;
  738. begin
  739. if MasterSource <> nil then
  740. begin
  741. //todo: retrieve only necessary fields
  742. FSql := 'Select * from '+FTableName+';'; // forced to obtain all fields
  743. FMasterLink.FieldNames:=FMasterLink.FieldNames; //workaround to fill MasterLinks.Fields
  744. //if FMasterLink.Fields.Count = 0 MasterChanged will not be called anyway so ignore it
  745. end;
  746. if FSql = '' then
  747. begin
  748. if FTablename = '' then
  749. DatabaseError('Tablename not set',Self);
  750. FSql := 'Select * from '+FTableName+';';
  751. end;
  752. if FSqliteHandle = nil then
  753. GetSqliteHandle;
  754. InternalInitFieldDefs;
  755. //todo: move this to InitFieldDefs
  756. FSelectSqlStr:='SELECT ';
  757. for i:= 0 to FieldDefs.Count - 2 do
  758. FSelectSqlStr:=FSelectSqlStr+FieldDefs[i].Name+',';
  759. FSelectSqlStr:=FSelectSqlStr+FieldDefs[FieldDefs.Count - 1].Name+
  760. ' FROM '+FTableName;
  761. if DefaultFields then
  762. CreateFields;
  763. BindFields(True);
  764. UpdateIndexFields;
  765. if FMasterLink.Active then
  766. begin
  767. if FIndexFieldList.Count <> FMasterLink.Fields.Count then
  768. DatabaseError('MasterFields count doesn''t match IndexFields count',Self);
  769. //Set FSql considering MasterSource active record
  770. SetDetailFilter;
  771. end;
  772. // Get PrimaryKeyNo if available
  773. if Fields.FindField(FPrimaryKey) <> nil then
  774. FPrimaryKeyNo:=Fields.FindField(FPrimaryKey).FieldNo - 1
  775. else
  776. FPrimaryKeyNo:=FAutoIncFieldNo; // -1 if there's no AutoIncField
  777. BuildLinkedList;
  778. FCurrentItem:=FBeginItem;
  779. end;
  780. procedure TCustomSqliteDataset.InternalPost;
  781. begin
  782. if State <> dsEdit then
  783. InternalAddRecord(ActiveBuffer,True)
  784. else
  785. begin
  786. CopyCacheToItem(FInternalActiveBuffer);
  787. PPDataRecord(ActiveBuffer)^:=FInternalActiveBuffer;
  788. if (FUpdatedItems.IndexOf(FInternalActiveBuffer) = -1) and
  789. (FAddedItems.IndexOf(FInternalActiveBuffer) = -1) then
  790. FUpdatedItems.Add(FInternalActiveBuffer);
  791. end;
  792. end;
  793. procedure TCustomSqliteDataset.InternalSetToRecord(Buffer: PChar);
  794. begin
  795. FCurrentItem:=PPDataRecord(Buffer)^;
  796. end;
  797. function TCustomSqliteDataset.IsCursorOpen: Boolean;
  798. begin
  799. Result := FDataAllocated;
  800. end;
  801. function TCustomSqliteDataset.FindRecordItem(StartItem: PDataRecord; const KeyFields: string; const KeyValues: Variant; Options: TLocateOptions; DoResync:Boolean):PDataRecord;
  802. var
  803. ValueArray: array of string;
  804. IndexArray: array of integer;
  805. AFieldList: TList;
  806. i,AFieldCount:Integer;
  807. MatchRecord: Boolean;
  808. AValue:String;
  809. TempItem:PDataRecord;
  810. begin
  811. Result:=nil;
  812. CheckBrowseMode;
  813. // Currently ignore options
  814. AFieldList:=TList.Create;
  815. try
  816. GetFieldList(AFieldList,KeyFields);
  817. AFieldCount:=AFieldList.Count;
  818. if AFieldCount > 1 then
  819. begin
  820. if VarIsArray(KeyValues) then
  821. begin
  822. if Succ(VarArrayHighBound(KeyValues,1)) <> AFieldCount then
  823. DatabaseError('Number of fields does not correspond to number of values',Self);
  824. end
  825. else
  826. DatabaseError('Wrong number of values specified: expected an array of variants got a variant',Self);
  827. end;
  828. //set the array of values and indexes
  829. SetLength(ValueArray,AFieldCount);
  830. SetLength(IndexArray,AFieldCount);
  831. for i:= 0 to AFieldCount - 1 do
  832. with TField(AFieldList[i]) do
  833. begin
  834. //get float types in appropriate format
  835. if not (DataType in [ftFloat,ftDateTime,ftTime,ftDate]) then
  836. begin
  837. if VarIsArray(KeyValues) then
  838. ValueArray[i]:=keyvalues[i]
  839. else
  840. ValueArray[i]:=keyvalues;
  841. end
  842. else
  843. begin
  844. if VarIsArray(KeyValues) then
  845. Str(VarToDateTime(keyvalues[i]),AValue)
  846. else
  847. Str(VarToDateTime(keyvalues),AValue);
  848. ValueArray[i]:=Trim(AValue);
  849. end;
  850. IndexArray[i]:=FieldNo - 1;
  851. end;
  852. finally
  853. AFieldList.Destroy;
  854. end;
  855. {$ifdef DEBUG_SQLITEDS}
  856. writeln('##TCustomSqliteDataset.FindRecordItem##');
  857. writeln(' KeyFields: ',keyfields);
  858. writeln(' KeyValues: ',keyvalues);
  859. writeln(' AValue: ',AValue);
  860. {$endif}
  861. //Search the list
  862. TempItem:=StartItem;
  863. while TempItem <> FEndItem do
  864. begin
  865. MatchRecord:=True;
  866. for i:= 0 to AFieldCount - 1 do
  867. begin
  868. //todo: handle null values??
  869. if (TempItem^.Row[IndexArray[i]] = nil) or
  870. (StrComp(TempItem^.Row[IndexArray[i]],PChar(ValueArray[i])) <> 0) then
  871. begin
  872. MatchRecord:= False;
  873. Break;//for
  874. end;
  875. end;
  876. if MatchRecord then
  877. begin
  878. Result:=TempItem;
  879. if DoResync then
  880. begin
  881. FCurrentItem:=TempItem;
  882. Resync([]);
  883. end;
  884. Break;//while
  885. end;
  886. TempItem:=TempItem^.Next;
  887. end;
  888. end;
  889. procedure TCustomSqliteDataset.GetSqliteHandle;
  890. begin
  891. if FFileName = '' then
  892. DatabaseError ('Filename not set',Self);
  893. //todo:Handle opening non db files
  894. FSqliteHandle:=InternalGetHandle;
  895. end;
  896. procedure TCustomSqliteDataset.FreeItem(AItem: PDataRecord);
  897. var
  898. i: Integer;
  899. begin
  900. for i:= 0 to FRowCount - 1 do
  901. StrDispose(AItem^.Row[i]);
  902. FreeMem(AItem^.Row,FRowBufferSize);
  903. Dispose(AItem);
  904. end;
  905. function TCustomSqliteDataset.Locate(const KeyFields: string; const KeyValues: Variant; Options: TLocateOptions) : Boolean;
  906. begin
  907. Result:=FindRecordItem(FBeginItem^.Next,KeyFields,KeyValues,Options,True) <> nil;
  908. end;
  909. function TCustomSqliteDataset.LocateNext(const KeyFields: string; const KeyValues: Variant; Options: TLocateOptions) : Boolean;
  910. begin
  911. Result:=FindRecordItem(PPDataRecord(ActiveBuffer)^^.Next,KeyFields,KeyValues,Options,True) <> nil;
  912. end;
  913. function TCustomSqliteDataset.Lookup(const KeyFields: string; const KeyValues: Variant; const ResultFields: string): Variant;
  914. var
  915. TempItem:PDataRecord;
  916. begin
  917. TempItem:=FindRecordItem(FBeginItem^.Next,KeyFields,KeyValues,[],False);
  918. if TempItem <> nil then
  919. Result:=TempItem^.Row[FieldByName(ResultFields).FieldNo - 1]
  920. else
  921. Result:=False;
  922. end;
  923. procedure TCustomSqliteDataset.SetBookmarkData(Buffer: PChar; Data: Pointer);
  924. begin
  925. //The BookMarkData is the Buffer itself: no need to set nothing;
  926. end;
  927. procedure TCustomSqliteDataset.SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag);
  928. begin
  929. PPDataRecord(Buffer)^^.BookmarkFlag := Value;
  930. end;
  931. procedure TCustomSqliteDataset.SetExpectedAppends(AValue:Integer);
  932. begin
  933. FAddedItems.Capacity:=AValue;
  934. end;
  935. procedure TCustomSqliteDataset.SetExpectedUpdates(AValue:Integer);
  936. begin
  937. FUpdatedItems.Capacity:=AValue;
  938. end;
  939. procedure TCustomSqliteDataset.SetExpectedDeletes(AValue:Integer);
  940. begin
  941. FDeletedItems.Capacity:=AValue;
  942. end;
  943. procedure TCustomSqliteDataset.SetFieldData(Field: TField; Buffer: Pointer;
  944. NativeFormat: Boolean);
  945. var
  946. TempStr:String;
  947. begin
  948. if not (State in [dsEdit, dsInsert]) then
  949. begin
  950. DatabaseErrorFmt(SNotEditing,[Name],Self);
  951. Exit;
  952. end;
  953. StrDispose(FCacheItem^.Row[Pred(Field.FieldNo)]);
  954. if Buffer <> nil then
  955. begin
  956. case Field.Datatype of
  957. ftString:
  958. begin
  959. FCacheItem^.Row[Pred(Field.FieldNo)]:=StrNew(PChar(Buffer));
  960. end;
  961. ftInteger:
  962. begin
  963. Str(LongInt(Buffer^),TempStr);
  964. FCacheItem^.Row[Pred(Field.FieldNo)]:=StrAlloc(Length(TempStr)+1);
  965. Move(PChar(TempStr)^,(FCacheItem^.Row[Pred(Field.FieldNo)])^,Length(TempStr)+1);
  966. end;
  967. ftBoolean,ftWord:
  968. begin
  969. Str(Word(Buffer^),TempStr);
  970. FCacheItem^.Row[Pred(Field.FieldNo)]:=StrAlloc(Length(TempStr)+1);
  971. Move(PChar(TempStr)^,(FCacheItem^.Row[Pred(Field.FieldNo)])^,Length(TempStr)+1);
  972. end;
  973. ftFloat,ftDateTime,ftDate,ftTime,ftCurrency:
  974. begin
  975. Str(Double(Buffer^),TempStr);
  976. FCacheItem^.Row[Pred(Field.FieldNo)]:=StrAlloc(Length(TempStr));
  977. //Skips the first space that str returns
  978. //todo: make a custom Str?
  979. Move((PChar(TempStr)+1)^,(FCacheItem^.Row[Pred(Field.FieldNo)])^,Length(TempStr));
  980. end;
  981. ftLargeInt:
  982. begin
  983. Str(Int64(Buffer^),TempStr);
  984. FCacheItem^.Row[Pred(Field.FieldNo)]:=StrAlloc(Length(TempStr)+1);
  985. Move(PChar(TempStr)^,(FCacheItem^.Row[Pred(Field.FieldNo)])^,Length(TempStr)+1);
  986. end;
  987. end;// case
  988. end//if
  989. else
  990. FCacheItem^.Row[Pred(Field.FieldNo)]:=nil;
  991. if not (State in [dsCalcFields, dsFilter, dsNewValue]) then
  992. DataEvent(deFieldChange, Ptrint(Field));
  993. end;
  994. procedure TCustomSqliteDataset.SetFieldData(Field: TField; Buffer: Pointer);
  995. begin
  996. SetFieldData(Field, Buffer, False);
  997. end;
  998. procedure TCustomSqliteDataset.SetRecNo(Value: Integer);
  999. var
  1000. Counter:Integer;
  1001. TempItem:PDataRecord;
  1002. begin
  1003. if (Value > FRecordCount) or (Value <= 0) then
  1004. DatabaseError('Record Number Out Of Range',Self);
  1005. CheckBrowseMode;
  1006. TempItem:=FBeginItem;
  1007. for Counter := 1 to Value do
  1008. TempItem:=TempItem^.Next;
  1009. FCurrentItem:=TempItem;
  1010. Resync([]);
  1011. end;
  1012. // Specific functions
  1013. procedure TCustomSqliteDataset.SetDetailFilter;
  1014. function FieldToSqlStr(AField:TField):String;
  1015. begin
  1016. if not AField.IsNull then
  1017. begin
  1018. case AField.DataType of
  1019. ftString,ftMemo: Result:='"'+AField.AsString+'"';//todo: handle " caracter properly
  1020. ftDateTime,ftDate,ftTime:Str(AField.AsDateTime,Result);
  1021. else
  1022. Result:=AField.AsString;
  1023. end;//case
  1024. end
  1025. else
  1026. Result:=NullString;
  1027. end;//function
  1028. var
  1029. AFilter:String;
  1030. i:Integer;
  1031. begin
  1032. if FMasterLink.Dataset.RecordCount = 0 then //Retrieve all data
  1033. FSql:='Select * from '+FTableName
  1034. else
  1035. begin
  1036. AFilter:=' where ';
  1037. for i:= 0 to FMasterLink.Fields.Count - 1 do
  1038. begin
  1039. AFilter:=AFilter + IndexFields[i].FieldName +' = '+ FieldToSqlStr(TField(FMasterLink.Fields[i]));
  1040. if i <> FMasterLink.Fields.Count - 1 then
  1041. AFilter:= AFilter + ' and ';
  1042. end;
  1043. FSql:='Select * from '+FTableName+AFilter;
  1044. end;
  1045. end;
  1046. procedure TCustomSqliteDataset.MasterChanged(Sender: TObject);
  1047. begin
  1048. SetDetailFilter;
  1049. {$ifdef DEBUG_SQLITEDS}
  1050. writeln('##TCustomSqliteDataset.MasterChanged##');
  1051. writeln(' SQL used to filter detail dataset:');
  1052. writeln(' ',FSql);
  1053. {$endif}
  1054. RefetchData;
  1055. end;
  1056. procedure TCustomSqliteDataset.MasterDisabled(Sender: TObject);
  1057. begin
  1058. FSql:='Select * from '+FTableName+';';
  1059. RefetchData;
  1060. end;
  1061. procedure TCustomSqliteDataset.SetMasterFields(Value: String);
  1062. begin
  1063. FMasterLink.FieldNames:=Value;
  1064. if Active and FMasterLink.Active then
  1065. begin
  1066. UpdateIndexFields;
  1067. if (FIndexFieldList.Count <> FMasterLink.Fields.Count) then
  1068. DatabaseError('MasterFields count doesn''t match IndexFields count',Self);
  1069. end;
  1070. end;
  1071. function TCustomSqliteDataset.GetMasterFields: String;
  1072. begin
  1073. Result:=FMasterLink.FieldNames;
  1074. end;
  1075. procedure TCustomSqliteDataset.UpdateIndexFields;
  1076. begin
  1077. FIndexFieldList.Clear;
  1078. if FIndexFieldNames <> '' then
  1079. begin
  1080. try
  1081. GetFieldList(FIndexFieldList, FIndexFieldNames);
  1082. except
  1083. FIndexFieldList.Clear;
  1084. raise;
  1085. end;
  1086. end;
  1087. end;
  1088. function TCustomSqliteDataset.GetMasterSource: TDataSource;
  1089. begin
  1090. Result := FMasterLink.DataSource;
  1091. end;
  1092. procedure TCustomSqliteDataset.SetFileName(Value: String);
  1093. begin
  1094. if Value <> FFileName then
  1095. begin
  1096. if Active then
  1097. DatabaseError('It''s not allowed to change Filename in an open dataset',Self);
  1098. if FSqliteHandle <> nil then
  1099. InternalCloseHandle;
  1100. FFileName:=Value;
  1101. end;
  1102. end;
  1103. procedure TCustomSqliteDataset.SetMasterSource(Value: TDataSource);
  1104. begin
  1105. FMasterLink.DataSource := Value;
  1106. end;
  1107. procedure TCustomSqliteDataset.ExecSQL(const ASql:String);
  1108. begin
  1109. if FSqliteHandle = nil then
  1110. GetSqliteHandle;
  1111. ExecuteDirect(ASql);
  1112. end;
  1113. procedure TCustomSqliteDataset.ExecSQLList;
  1114. begin
  1115. if FSqliteHandle = nil then
  1116. GetSqliteHandle;
  1117. SqliteExec(PChar(FSqlList.Text),nil,nil);
  1118. end;
  1119. procedure TCustomSqliteDataset.ExecSQL;
  1120. begin
  1121. ExecSQL(FSql);
  1122. end;
  1123. function TCustomSqliteDataset.ApplyUpdates:Boolean;
  1124. var
  1125. iFields,iItems,StatementsCounter:Integer;
  1126. SqlTemp,WhereKeyNameEqual,ASqlLine,TemplateStr:String;
  1127. TempItem: PDataRecord;
  1128. begin
  1129. CheckBrowseMode;
  1130. if not UpdatesPending then
  1131. begin
  1132. Result:=True;
  1133. Exit;
  1134. end;
  1135. Result:=False;
  1136. if FPrimaryKeyNo <> -1 then
  1137. begin
  1138. StatementsCounter:=0;
  1139. WhereKeyNameEqual:=' WHERE '+Fields[FPrimaryKeyNo].FieldName+' = ';
  1140. {$ifdef DEBUG_SQLITEDS}
  1141. WriteLn('##TCustomSqliteDataset.ApplyUpdates##');
  1142. if FPrimaryKeyNo = FAutoIncFieldNo then
  1143. WriteLn(' Using an AutoInc field as primary key');
  1144. WriteLn(' PrimaryKey: ',WhereKeyNameEqual);
  1145. WriteLn(' PrimaryKeyNo: ',FPrimaryKeyNo);
  1146. {$endif}
  1147. SqlTemp:='BEGIN;';
  1148. // Delete Records
  1149. if FDeletedItems.Count > 0 then
  1150. TemplateStr:='DELETE FROM '+FTableName+WhereKeyNameEqual;
  1151. for iItems:= 0 to FDeletedItems.Count - 1 do
  1152. begin
  1153. TempItem:=PDataRecord(FDeletedItems.List^[iItems]);
  1154. SqlTemp:=SqlTemp+(TemplateStr+
  1155. StrPas(TempItem^.Row[FPrimaryKeyNo])+';');
  1156. FreeItem(TempItem);
  1157. inc(StatementsCounter);
  1158. //ApplyUpdates each 400 statements
  1159. if StatementsCounter = 400 then
  1160. begin
  1161. SqlTemp:=SqlTemp+'COMMIT;';
  1162. FReturnCode:=SqliteExec(PChar(SqlTemp),nil,nil);
  1163. StatementsCounter:=0;
  1164. SqlTemp:='BEGIN;';
  1165. end;
  1166. end;
  1167. // Update changed records
  1168. if FUpdatedItems.Count > 0 then
  1169. TemplateStr:='UPDATE '+FTableName+' SET ';
  1170. for iItems:= 0 to FUpdatedItems.Count - 1 do
  1171. begin
  1172. ASqlLine:=TemplateStr;
  1173. for iFields:= 0 to Fields.Count - 2 do
  1174. begin
  1175. ASqlLine:=ASqlLine + (Fields[iFields].FieldName +' = '+
  1176. FGetSqlStr[iFields](PDataRecord(FUpdatedItems[iItems])^.Row[iFields])+',');
  1177. end;
  1178. iFields:=Fields.Count - 1;
  1179. ASqlLine:=ASqlLine + (Fields[iFields].FieldName +' = '+
  1180. FGetSqlStr[iFields](PDataRecord(FUpdatedItems[iItems])^.Row[iFields])+
  1181. WhereKeyNameEqual+StrPas(PDataRecord(FUpdatedItems[iItems])^.Row[FPrimaryKeyNo])+';');
  1182. SqlTemp:=SqlTemp + ASqlLine;
  1183. inc(StatementsCounter);
  1184. //ApplyUpdates each 400 statements
  1185. if StatementsCounter = 400 then
  1186. begin
  1187. SqlTemp:=SqlTemp+'COMMIT;';
  1188. FReturnCode:=SqliteExec(PChar(SqlTemp),nil,nil);
  1189. StatementsCounter:=0;
  1190. SqlTemp:='BEGIN;';
  1191. end;
  1192. end;
  1193. // Add new records
  1194. // Build TemplateStr
  1195. if FAddedItems.Count > 0 then
  1196. begin
  1197. TemplateStr:='INSERT INTO '+FTableName+ ' (';
  1198. for iFields:= 0 to Fields.Count - 2 do
  1199. TemplateStr:=TemplateStr + Fields[iFields].FieldName+',';
  1200. TemplateStr:= TemplateStr+Fields[Fields.Count - 1].FieldName+') VALUES (';
  1201. end;
  1202. for iItems:= 0 to FAddedItems.Count - 1 do
  1203. begin
  1204. ASqlLine:=TemplateStr;
  1205. for iFields:= 0 to Fields.Count - 2 do
  1206. begin
  1207. ASqlLine:=ASqlLine + (FGetSqlStr[iFields](PDataRecord(FAddedItems[iItems])^.Row[iFields])+',');
  1208. end;
  1209. //todo: see if i can assume iFields = Fields.Count-2 safely
  1210. iFields:=Fields.Count - 1;
  1211. ASqlLine:=ASqlLine + (FGetSqlStr[iFields](PDataRecord(FAddedItems[iItems])^.Row[iFields])+');');
  1212. SqlTemp:=SqlTemp + ASqlLine;
  1213. inc(StatementsCounter);
  1214. //ApplyUpdates each 400 statements
  1215. if StatementsCounter = 400 then
  1216. begin
  1217. SqlTemp:=SqlTemp+'COMMIT;';
  1218. FReturnCode:=SqliteExec(PChar(SqlTemp),nil,nil);
  1219. StatementsCounter:=0;
  1220. SqlTemp:='BEGIN;';
  1221. end;
  1222. end;
  1223. SqlTemp:=SqlTemp+'COMMIT;';
  1224. {$ifdef DEBUG_SQLITEDS}
  1225. writeln(' SQL: ',SqlTemp);
  1226. {$endif}
  1227. FAddedItems.Clear;
  1228. FUpdatedItems.Clear;
  1229. FDeletedItems.Clear;
  1230. FReturnCode:=SqliteExec(PChar(SqlTemp),nil,nil);
  1231. Result:= FReturnCode = SQLITE_OK;
  1232. end;
  1233. {$ifdef DEBUG_SQLITEDS}
  1234. writeln(' Result: ',Result);
  1235. {$endif}
  1236. end;
  1237. function TCustomSqliteDataset.CreateTable: Boolean;
  1238. begin
  1239. Result:=CreateTable(FTableName);
  1240. end;
  1241. function TCustomSqliteDataset.CreateTable(const ATableName: String): Boolean;
  1242. var
  1243. SqlTemp:String;
  1244. i:Integer;
  1245. begin
  1246. {$ifdef DEBUG_SQLITEDS}
  1247. writeln('##TCustomSqliteDataset.CreateTable##');
  1248. if ATableName = '' then
  1249. WriteLn(' TableName Not Set');
  1250. if FieldDefs.Count = 0 then
  1251. WriteLn(' FieldDefs Not Initialized');
  1252. {$endif}
  1253. if (ATableName <> '') and (FieldDefs.Count > 0) then
  1254. begin
  1255. SqlTemp:='CREATE TABLE '+ATableName+' (';
  1256. for i := 0 to FieldDefs.Count-1 do
  1257. begin
  1258. //todo: add index to autoinc field
  1259. SqlTemp:=SqlTemp + FieldDefs[i].Name;
  1260. case FieldDefs[i].DataType of
  1261. ftInteger:
  1262. SqlTemp:=SqlTemp + ' INTEGER';
  1263. ftString:
  1264. SqlTemp:=SqlTemp + ' VARCHAR';
  1265. ftBoolean:
  1266. SqlTemp:=SqlTemp + ' BOOL_INT';
  1267. ftFloat:
  1268. SqlTemp:=SqlTemp + ' FLOAT';
  1269. ftWord:
  1270. SqlTemp:=SqlTemp + ' WORD';
  1271. ftDateTime:
  1272. SqlTemp:=SqlTemp + ' DATETIME';
  1273. ftDate:
  1274. SqlTemp:=SqlTemp + ' DATE';
  1275. ftTime:
  1276. SqlTemp:=SqlTemp + ' TIME';
  1277. ftLargeInt:
  1278. SqlTemp:=SqlTemp + ' LARGEINT';
  1279. ftCurrency:
  1280. SqlTemp:=SqlTemp + ' CURRENCY';
  1281. ftAutoInc:
  1282. SqlTemp:=SqlTemp + ' AUTOINC_INT';
  1283. ftMemo:
  1284. SqlTemp:=SqlTemp + ' TEXT';
  1285. else
  1286. DatabaseError('Field type "'+FieldTypeNames[FieldDefs[i].DataType]+'" not supported',Self);
  1287. end;
  1288. if UpperCase(FieldDefs[i].Name) = UpperCase(FPrimaryKey) then
  1289. SqlTemp:=SqlTemp + ' PRIMARY KEY';
  1290. if i <> FieldDefs.Count - 1 then
  1291. SqlTemp:=SqlTemp+ ' , ';
  1292. end;
  1293. SqlTemp:=SqlTemp+');';
  1294. {$ifdef DEBUG_SQLITEDS}
  1295. writeln(' SQL: ',SqlTemp);
  1296. {$endif}
  1297. ExecSQL(SqlTemp);
  1298. Result:= FReturnCode = SQLITE_OK;
  1299. end
  1300. else
  1301. Result:=False;
  1302. end;
  1303. procedure TCustomSqliteDataset.ExecCallback(const ASql: String; UserData: Pointer = nil);
  1304. var
  1305. CallbackInfo: TCallbackInfo;
  1306. begin
  1307. if not Assigned(FOnCallback) then
  1308. DatabaseError('OnCallback property not set',Self);
  1309. if FSqliteHandle = nil then
  1310. GetSqliteHandle;
  1311. CallbackInfo.Data:=UserData;
  1312. CallbackInfo.Proc:=FOnCallback;
  1313. SqliteExec(PChar(ASql),@CallbackDispatcher,@CallbackInfo);
  1314. end;
  1315. procedure TCustomSqliteDataset.QueryUpdates(RecordStates: TRecordStateSet; Callback: TQueryUpdatesCallback;
  1316. UserData: Pointer = nil);
  1317. var
  1318. i: Integer;
  1319. TempItem: PDataRecord;
  1320. begin
  1321. if not Assigned(Callback) then
  1322. DatabaseError('Callback parameter not set',Self);
  1323. CheckBrowseMode;
  1324. if rsDeleted in RecordStates then
  1325. with FDeletedItems do
  1326. for i:= 0 to Count - 1 do
  1327. Callback(UserData,PDataRecord(Items[i])^.Row,nil,rsDeleted);
  1328. if rsUpdated in RecordStates then
  1329. with FUpdatedItems do
  1330. for i:= 0 to Count - 1 do
  1331. begin
  1332. TempItem:=PDataRecord(Items[i]);
  1333. Callback(UserData,TempItem^.Row,TBookmark(@TempItem),rsUpdated);
  1334. end;
  1335. if rsAdded in RecordStates then
  1336. with FAddedItems do
  1337. for i:= 0 to Count - 1 do
  1338. begin
  1339. TempItem:=PDataRecord(Items[i]);
  1340. Callback(UserData,TempItem^.Row,TBookmark(@TempItem),rsAdded);
  1341. end;
  1342. end;
  1343. procedure TCustomSqliteDataset.RefetchData;
  1344. var
  1345. i:Integer;
  1346. begin
  1347. //Close
  1348. if FSaveOnRefetch then
  1349. ApplyUpdates;
  1350. if FDataAllocated then
  1351. DisposeLinkedList;
  1352. FAddedItems.Clear;
  1353. FUpdatedItems.Clear;
  1354. FDeletedItems.Clear;
  1355. //Reopen
  1356. BuildLinkedList;
  1357. FCurrentItem:=FBeginItem;
  1358. for i := 0 to BufferCount - 1 do
  1359. PPDataRecord(Buffers[i])^:=FBeginItem;
  1360. Resync([]);
  1361. DoAfterScroll;
  1362. end;
  1363. function TCustomSqliteDataset.TableExists: Boolean;
  1364. begin
  1365. Result:=TableExists(FTableName);
  1366. end;
  1367. function TCustomSqliteDataset.TableExists(const ATableName: String): Boolean;
  1368. begin
  1369. Result:=False;
  1370. if not (ATableName = '') and FileExists(FFileName) then
  1371. begin
  1372. ExecSql('SELECT name FROM SQLITE_MASTER WHERE type = ''table'' AND name LIKE '''+ ATableName+ ''';');
  1373. Result:=FReturnCode = SQLITE_ROW;
  1374. end;
  1375. end;
  1376. function TCustomSqliteDataset.UpdatesPending: Boolean;
  1377. begin
  1378. Result:= (FUpdatedItems.Count > 0) or
  1379. (FAddedItems.Count > 0) or (FDeletedItems.Count > 0);
  1380. end;
  1381. function TCustomSqliteDataset.QuickQuery(const ASql:String):String;
  1382. begin
  1383. Result:=QuickQuery(ASql,nil,False);
  1384. end;
  1385. function TCustomSqliteDataset.QuickQuery(const ASql:String;const AStrList: TStrings):String;
  1386. begin
  1387. Result:=QuickQuery(ASql,AStrList,False)
  1388. end;
  1389. {$ifdef DEBUGACTIVEBUFFER}
  1390. procedure TCustomSqliteDataset.SetCurrentItem(Value:PDataRecord);
  1391. var
  1392. ANo:Integer;
  1393. function GetItemPos:Integer;
  1394. var
  1395. TempItem:PDataRecord;
  1396. begin
  1397. Result:= -1;
  1398. TempItem:=FBeginItem;
  1399. if Value = FCacheItem then
  1400. Result:=-2
  1401. else
  1402. while Value <> TempItem do
  1403. begin
  1404. if TempItem^.Next <> nil then
  1405. begin
  1406. inc(Result);
  1407. TempItem:=TempItem^.Next;
  1408. end
  1409. else
  1410. begin
  1411. Result:=-1;
  1412. break;
  1413. end;
  1414. end;
  1415. end;
  1416. begin
  1417. if Value = FBeginItem then
  1418. begin
  1419. writeln('FCurrentItem set to FBeginItem: ',IntToHex(Integer(Value),0));
  1420. FFCurrentItem:=Value;
  1421. end
  1422. else
  1423. if Value = FEndItem then
  1424. begin
  1425. writeln('FCurrentItem set to FEndItem: ',IntToHex(Integer(Value),0));
  1426. FFCurrentItem:=Value;
  1427. end
  1428. else
  1429. if Value = FCacheItem then
  1430. begin
  1431. writeln('FCurrentItem set to FCacheItem: ',IntToHex(Integer(Value),0));
  1432. FFCurrentItem:=Value;
  1433. end
  1434. else
  1435. begin
  1436. writeln('FCurrentItem set from ',IntToHex(Integer(FFCurrentItem),0),' to ',IntToHex(Integer(Value),0));
  1437. Ano:=GetItemPos;
  1438. writeln('Item position is ',ANo);
  1439. FFCurrentItem:=Value;
  1440. end;
  1441. end;
  1442. {$endif}
  1443. end.