sqliteds.pas 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. unit sqliteds;
  2. {
  3. This is TSqliteDataset, 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}
  21. interface
  22. uses
  23. Classes, SysUtils, customsqliteds;
  24. type
  25. { TSqliteDataset }
  26. TSqliteDataset = class (TCustomSqliteDataset)
  27. private
  28. function SqliteExec(AHandle: Pointer; ASql:PChar):Integer;override;
  29. function GetSqliteHandle: Pointer; override;
  30. function GetSqliteEncoding: String;
  31. function GetSqliteVersion: String; override;
  32. procedure SqliteClose(AHandle: Pointer);override;
  33. procedure BuildLinkedList; override;
  34. protected
  35. procedure InternalInitFieldDefs; override;
  36. public
  37. function SqliteReturnString: String; override;
  38. function TableExists: Boolean;override;
  39. function QuickQuery(const ASql:String;const AStrList: TStrings;FillObjects:Boolean):String;override;
  40. property SqliteEncoding: String read GetSqliteEncoding;
  41. end;
  42. implementation
  43. uses
  44. sqlite,db;
  45. var
  46. DummyAutoIncFieldNo:Integer;
  47. function GetAutoIncValue(NextValue: Pointer; Columns: Integer; ColumnValues: PPChar; ColumnNames: PPChar): integer; cdecl;
  48. var
  49. CodeError, TempInt: Integer;
  50. begin
  51. TempInt:=-1;
  52. if ColumnValues[0] <> nil then
  53. begin
  54. Val(StrPas(ColumnValues[0]),TempInt,CodeError);
  55. if CodeError <> 0 then
  56. DatabaseError('SqliteDs - Error trying to get last autoinc value');
  57. end;
  58. Integer(NextValue^):=Succ(TempInt);
  59. Result:=1;
  60. end;
  61. function GetFieldDefs(TheDataset: Pointer; Columns: Integer; ColumnValues: PPChar; ColumnNames: PPChar): integer; cdecl;
  62. var
  63. FieldSize:Word;
  64. Counter:Integer;
  65. AType:TFieldType;
  66. ColumnStr:String;
  67. begin
  68. // Sqlite is typeless (allows any type in any field)
  69. // regardless of what is in Create Table, but returns
  70. // exactly what is in Create Table statement
  71. // here is a trick to get the datatype.
  72. // If the field contains another type, there will be problems
  73. for Counter:= 0 to Columns - 1 do
  74. begin
  75. ColumnStr:= UpperCase(StrPas(ColumnNames[Counter + Columns]));
  76. if (ColumnStr = 'INTEGER') then
  77. begin
  78. AType:= ftInteger;
  79. FieldSize:=SizeOf(LongInt);
  80. end else if (ColumnStr = 'VARCHAR') then
  81. begin
  82. AType:= ftString;
  83. FieldSize:=10;//??
  84. end else if (ColumnStr = 'BOOLEAN') then
  85. begin
  86. AType:= ftBoolean;
  87. FieldSize:=SizeOf(Boolean);
  88. end else if (ColumnStr = 'FLOAT') then
  89. begin
  90. AType:= ftFloat;
  91. FieldSize:=SizeOf(Double);
  92. end else if (ColumnStr = 'WORD') then
  93. begin
  94. AType:= ftWord;
  95. FieldSize:=SizeOf(Word);
  96. end else if (ColumnStr = 'DATETIME') then
  97. begin
  98. AType:= ftDateTime;
  99. FieldSize:=SizeOf(TDateTime);
  100. end else if (ColumnStr = 'DATE') then
  101. begin
  102. AType:= ftDate;
  103. FieldSize:=SizeOf(TDateTime);
  104. end else if (ColumnStr = 'TIME') then
  105. begin
  106. AType:= ftTime;
  107. FieldSize:=SizeOf(TDateTime);
  108. end else if (ColumnStr = 'LARGEINT') then
  109. begin
  110. AType:= ftLargeInt;
  111. FieldSize:=SizeOf(LargeInt);
  112. end else if (ColumnStr = 'CURRENCY') then
  113. begin
  114. AType:= ftCurrency;
  115. FieldSize:=SizeOf(Double);
  116. end else if (ColumnStr = 'MEMO') then
  117. begin
  118. AType:= ftMemo;
  119. FieldSize:=10;//??
  120. end else if (ColumnStr = 'AUTOINC') then
  121. begin
  122. AType:= ftAutoInc;
  123. FieldSize:=SizeOf(Integer);
  124. if DummyAutoIncFieldNo = -1 then
  125. DummyAutoIncFieldNo:= Counter;
  126. end else
  127. begin
  128. DatabaseError('Field type "'+ColumnStr+'" not recognized',TDataset(TheDataset));
  129. end;
  130. TDataset(TheDataset).FieldDefs.Add(StrPas(ColumnNames[Counter]), AType, FieldSize, False);
  131. end;
  132. result:=-1;
  133. end;
  134. { TSqliteDataset }
  135. function TSqliteDataset.SqliteExec(AHandle: Pointer; ASql: PChar): Integer;
  136. begin
  137. Result:=sqlite_exec(AHandle, ASql, nil, nil, nil);
  138. end;
  139. procedure TSqliteDataset.SqliteClose(AHandle: Pointer);
  140. begin
  141. sqlite_close(AHandle);
  142. end;
  143. function TSqliteDataset.GetSqliteHandle: Pointer;
  144. begin
  145. Result:=sqlite_open(PChar(FFileName),0,nil);
  146. end;
  147. procedure TSqliteDataset.InternalInitFieldDefs;
  148. begin
  149. FieldDefs.Clear;
  150. sqlite_exec(FSqliteHandle,PChar('PRAGMA empty_result_callbacks = ON;PRAGMA show_datatypes = ON;'),nil,nil,nil);
  151. DummyAutoIncFieldNo:=-1;
  152. FSqliteReturnId:=sqlite_exec(FSqliteHandle,PChar(FSql),@GetFieldDefs,Self,nil);
  153. FAutoIncFieldNo:=DummyAutoIncFieldNo;
  154. {
  155. if FSqliteReturnId <> SQLITE_ABORT then
  156. DatabaseError(SqliteReturnString,Self);
  157. }
  158. FRowBufferSize:=(SizeOf(PPChar)*FieldDefs.Count);
  159. end;
  160. procedure TSqliteDataset.BuildLinkedList;
  161. var
  162. TempItem:PDataRecord;
  163. vm:Pointer;
  164. ColumnNames,ColumnValues:PPChar;
  165. Counter:Integer;
  166. begin
  167. //Get AutoInc Field initial value
  168. if FAutoIncFieldNo <> -1 then
  169. sqlite_exec(FSqliteHandle,PChar('Select Max('+Fields[FAutoIncFieldNo].FieldName+') from ' + FTableName),
  170. @GetAutoIncValue,@FNextAutoInc,nil);
  171. FSqliteReturnId:=sqlite_compile(FSqliteHandle,Pchar(FSql),nil,@vm,nil);
  172. if FSqliteReturnId <> SQLITE_OK then
  173. case FSqliteReturnId of
  174. SQLITE_ERROR:
  175. DatabaseError('Invalid SQL',Self);
  176. else
  177. DatabaseError('Error returned by sqlite while retrieving data: '+SqliteReturnString,Self);
  178. end;
  179. FDataAllocated:=True;
  180. TempItem:=FBeginItem;
  181. FRecordCount:=0;
  182. FSqliteReturnId:=sqlite_step(vm,@FRowCount,@ColumnValues,@ColumnNames);
  183. while FSqliteReturnId = SQLITE_ROW do
  184. begin
  185. Inc(FRecordCount);
  186. New(TempItem^.Next);
  187. TempItem^.Next^.Previous:=TempItem;
  188. TempItem:=TempItem^.Next;
  189. GetMem(TempItem^.Row,FRowBufferSize);
  190. For Counter := 0 to FRowCount - 1 do
  191. TempItem^.Row[Counter]:=StrNew(ColumnValues[Counter]);
  192. FSqliteReturnId:=sqlite_step(vm,@FRowCount,@ColumnValues,@ColumnNames);
  193. end;
  194. sqlite_finalize(vm, nil);
  195. // Attach EndItem
  196. TempItem^.Next:=FEndItem;
  197. FEndItem^.Previous:=TempItem;
  198. // Alloc item used in append/insert
  199. GetMem(FCacheItem^.Row,FRowBufferSize);
  200. for Counter := 0 to FRowCount - 1 do
  201. FCacheItem^.Row[Counter]:=nil;
  202. // Fill FBeginItem.Row with nil -> necessary for avoid exceptions in empty datasets
  203. GetMem(FBeginItem^.Row,FRowBufferSize);
  204. for Counter := 0 to FRowCount - 1 do
  205. FBeginItem^.Row[Counter]:=nil;
  206. end;
  207. function TSqliteDataset.TableExists: Boolean;
  208. var
  209. AHandle,vm:Pointer;
  210. ColumnNames,ColumnValues:PPChar;
  211. AInt:Integer;
  212. begin
  213. Result:=False;
  214. if not (FTableName = '') and FileExists(FFileName) then
  215. begin
  216. if FSqliteHandle = nil then
  217. begin
  218. {$ifdef DEBUG}
  219. writeln('TableExists - FSqliteHandle=nil : Opening a file');
  220. {$endif}
  221. AHandle:=GetSqliteHandle;
  222. end
  223. else
  224. begin
  225. {$ifdef DEBUG}
  226. writeln('TableExists - FSqliteHandle<>nil : Using FSqliteHandle');
  227. {$endif}
  228. AHandle:=FSqliteHandle;
  229. end;
  230. FSqliteReturnId:=sqlite_compile(AHandle,
  231. Pchar('SELECT name FROM SQLITE_MASTER WHERE type = ''table'' AND name LIKE '''+ FTableName+ ''';'),
  232. nil,@vm,nil);
  233. {$ifdef DEBUG}
  234. WriteLn('TableExists.sqlite_compile - SqliteReturnString:',SqliteReturnString);
  235. {$endif}
  236. FSqliteReturnId:=sqlite_step(vm,@AInt,@ColumnValues,@ColumnNames);
  237. {$ifdef DEBUG}
  238. WriteLn('TableExists.sqlite_step - SqliteReturnString:',SqliteReturnString);
  239. {$endif}
  240. Result:=FSqliteReturnId = SQLITE_ROW;
  241. sqlite_finalize(vm, nil);
  242. if FSqliteHandle = nil then
  243. SqliteClose(AHandle);
  244. end;
  245. {$ifdef DEBUG}
  246. WriteLn('TableExists ('+FTableName+') Result:',Result);
  247. {$endif}
  248. end;
  249. function TSqliteDataset.SqliteReturnString: String;
  250. begin
  251. case FSqliteReturnId of
  252. SQLITE_OK : Result := 'SQLITE_OK ';
  253. SQLITE_ERROR : Result := 'SQLITE_ERROR ';
  254. SQLITE_INTERNAL : Result := 'SQLITE_INTERNAL ';
  255. SQLITE_PERM : Result := 'SQLITE_PERM ';
  256. SQLITE_ABORT : Result := 'SQLITE_ABORT ';
  257. SQLITE_BUSY : Result := 'SQLITE_BUSY ';
  258. SQLITE_LOCKED : Result := 'SQLITE_LOCKED ';
  259. SQLITE_NOMEM : Result := 'SQLITE_NOMEM ';
  260. SQLITE_READONLY : Result := 'SQLITE_READONLY ';
  261. SQLITE_INTERRUPT : Result := 'SQLITE_INTERRUPT ';
  262. SQLITE_IOERR : Result := 'SQLITE_IOERR ';
  263. SQLITE_CORRUPT : Result := 'SQLITE_CORRUPT ';
  264. SQLITE_NOTFOUND : Result := 'SQLITE_NOTFOUND ';
  265. SQLITE_FULL : Result := 'SQLITE_FULL ';
  266. SQLITE_CANTOPEN : Result := 'SQLITE_CANTOPEN ';
  267. SQLITE_PROTOCOL : Result := 'SQLITE_PROTOCOL ';
  268. SQLITE_EMPTY : Result := 'SQLITE_EMPTY ';
  269. SQLITE_SCHEMA : Result := 'SQLITE_SCHEMA ';
  270. SQLITE_TOOBIG : Result := 'SQLITE_TOOBIG ';
  271. SQLITE_CONSTRAINT : Result := 'SQLITE_CONSTRAINT ';
  272. SQLITE_MISMATCH : Result := 'SQLITE_MISMATCH ';
  273. SQLITE_MISUSE : Result := 'SQLITE_MISUSE ';
  274. SQLITE_NOLFS : Result := 'SQLITE_NOLFS ';
  275. SQLITE_AUTH : Result := 'SQLITE_AUTH ';
  276. SQLITE_FORMAT : Result := 'SQLITE_FORMAT ';
  277. SQLITE_RANGE : Result := 'SQLITE_RANGE ';
  278. SQLITE_ROW : Result := 'SQLITE_ROW ';
  279. SQLITE_DONE : Result := 'SQLITE_DONE ';
  280. else
  281. Result:='Unknow Return Value';
  282. end;
  283. end;
  284. function TSqliteDataset.GetSqliteEncoding: String;
  285. begin
  286. Result:=StrPas(sqlite_encoding);
  287. end;
  288. function TSqliteDataset.GetSqliteVersion: String;
  289. begin
  290. Result:=StrPas(sqlite_version);
  291. end;
  292. function TSqliteDataset.QuickQuery(const ASql:String;const AStrList: TStrings;FillObjects:Boolean):String;
  293. var
  294. vm,AHandle:Pointer;
  295. ColumnNames,ColumnValues:PPChar;
  296. ColCount:Integer;
  297. procedure FillStrings;
  298. begin
  299. while FSqliteReturnId = SQLITE_ROW do
  300. begin
  301. AStrList.Add(StrPas(ColumnValues[0]));
  302. FSqliteReturnId:=sqlite_step(vm,@ColCount,@ColumnValues,@ColumnNames);
  303. end;
  304. end;
  305. procedure FillStringsAndObjects;
  306. begin
  307. while FSqliteReturnId = SQLITE_ROW do
  308. begin
  309. // I know, this code is really dirty!!
  310. AStrList.AddObject(StrPas(ColumnValues[0]),TObject(PtrInt(StrToInt(StrPas(ColumnValues[1])))));
  311. FSqliteReturnId:=sqlite_step(vm,@ColCount,@ColumnValues,@ColumnNames);
  312. end;
  313. end;
  314. begin
  315. if FSqliteHandle <> nil then
  316. AHandle:=FSqliteHandle
  317. else
  318. if FileExists(FFileName) then
  319. AHandle:=GetSqliteHandle
  320. else
  321. DatabaseError('File '+FFileName+' not Exists',Self);
  322. Result:='';
  323. // It's up to the caller clear or not the list
  324. //if AStrList <> nil then
  325. // AStrList.Clear;
  326. FSqliteReturnId:=sqlite_compile(AHandle,Pchar(ASql),nil,@vm,nil);
  327. if FSqliteReturnId <> SQLITE_OK then
  328. DatabaseError('Error returned by sqlite in QuickQuery: '+SqliteReturnString,Self);
  329. FSqliteReturnId:=sqlite_step(vm,@ColCount,@ColumnValues,@ColumnNames);
  330. if (FSqliteReturnId = SQLITE_ROW) and (ColCount > 0) then
  331. begin
  332. Result:=StrPas(ColumnValues[0]);
  333. if AStrList <> nil then
  334. begin
  335. if FillObjects and (ColCount > 1) then
  336. FillStringsAndObjects
  337. else
  338. FillStrings;
  339. end;
  340. end;
  341. sqlite_finalize(vm, nil);
  342. if FSqliteHandle = nil then
  343. sqlite_close(AHandle);
  344. end;
  345. end.