sqliteds.pas 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  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(Integer);
  80. end else if (ColumnStr = 'BOOLEAN') then
  81. begin
  82. AType:= ftBoolean;
  83. FieldSize:=SizeOf(Boolean);
  84. end else if (ColumnStr = 'FLOAT') then
  85. begin
  86. AType:= ftFloat;
  87. FieldSize:=SizeOf(Double);
  88. end else if (ColumnStr = 'WORD') then
  89. begin
  90. AType:= ftWord;
  91. FieldSize:=SizeOf(Word);
  92. end else if (ColumnStr = 'DATETIME') then
  93. begin
  94. AType:= ftDateTime;
  95. FieldSize:=SizeOf(TDateTime);
  96. end else if (ColumnStr = 'DATE') then
  97. begin
  98. AType:= ftDate;
  99. FieldSize:=SizeOf(TDateTime);
  100. end else if (ColumnStr = 'TIME') then
  101. begin
  102. AType:= ftTime;
  103. FieldSize:=SizeOf(TDateTime);
  104. end else if (ColumnStr = 'MEMO') then
  105. begin
  106. AType:= ftMemo;
  107. FieldSize:=10;//??
  108. end else if (ColumnStr = 'AUTOINC') then
  109. begin
  110. AType:= ftAutoInc;
  111. FieldSize:=SizeOf(Integer);
  112. if DummyAutoIncFieldNo = -1 then
  113. DummyAutoIncFieldNo:= Counter;
  114. end else
  115. begin
  116. AType:= ftString;
  117. FieldSize:=10; //??
  118. end;
  119. TDataset(TheDataset).FieldDefs.Add(StrPas(ColumnNames[Counter]), AType, FieldSize, False);
  120. end;
  121. result:=-1;
  122. end;
  123. { TSqliteDataset }
  124. function TSqliteDataset.SqliteExec(AHandle: Pointer; ASql: PChar): Integer;
  125. begin
  126. Result:=sqlite_exec(AHandle, ASql, nil, nil, nil);
  127. end;
  128. procedure TSqliteDataset.SqliteClose(AHandle: Pointer);
  129. begin
  130. sqlite_close(AHandle);
  131. end;
  132. function TSqliteDataset.GetSqliteHandle: Pointer;
  133. begin
  134. Result:=sqlite_open(PChar(FFileName),0,nil);
  135. end;
  136. procedure TSqliteDataset.InternalInitFieldDefs;
  137. begin
  138. FieldDefs.Clear;
  139. sqlite_exec(FSqliteHandle,PChar('PRAGMA empty_result_callbacks = ON;PRAGMA show_datatypes = ON;'),nil,nil,nil);
  140. DummyAutoIncFieldNo:=-1;
  141. FSqliteReturnId:=sqlite_exec(FSqliteHandle,PChar(FSql),@GetFieldDefs,Self,nil);
  142. FAutoIncFieldNo:=DummyAutoIncFieldNo;
  143. {
  144. if FSqliteReturnId <> SQLITE_ABORT then
  145. DatabaseError(SqliteReturnString,Self);
  146. }
  147. FRowBufferSize:=(SizeOf(PPChar)*FieldDefs.Count);
  148. end;
  149. procedure TSqliteDataset.BuildLinkedList;
  150. var
  151. TempItem:PDataRecord;
  152. vm:Pointer;
  153. ColumnNames,ColumnValues:PPChar;
  154. Counter:Integer;
  155. begin
  156. //Get AutoInc Field initial value
  157. if FAutoIncFieldNo <> -1 then
  158. sqlite_exec(FSqliteHandle,PChar('Select Max('+Fields[FAutoIncFieldNo].FieldName+') from ' + FTableName),
  159. @GetAutoIncValue,@FNextAutoInc,nil);
  160. FSqliteReturnId:=sqlite_compile(FSqliteHandle,Pchar(FSql),nil,@vm,nil);
  161. if FSqliteReturnId <> SQLITE_OK then
  162. case FSqliteReturnId of
  163. SQLITE_ERROR:
  164. DatabaseError('Invalid SQL',Self);
  165. else
  166. DatabaseError('Error returned by sqlite while retrieving data: '+SqliteReturnString,Self);
  167. end;
  168. FDataAllocated:=True;
  169. TempItem:=FBeginItem;
  170. FRecordCount:=0;
  171. FSqliteReturnId:=sqlite_step(vm,@FRowCount,@ColumnValues,@ColumnNames);
  172. while FSqliteReturnId = SQLITE_ROW do
  173. begin
  174. Inc(FRecordCount);
  175. New(TempItem^.Next);
  176. TempItem^.Next^.Previous:=TempItem;
  177. TempItem:=TempItem^.Next;
  178. GetMem(TempItem^.Row,FRowBufferSize);
  179. For Counter := 0 to FRowCount - 1 do
  180. TempItem^.Row[Counter]:=StrNew(ColumnValues[Counter]);
  181. FSqliteReturnId:=sqlite_step(vm,@FRowCount,@ColumnValues,@ColumnNames);
  182. end;
  183. sqlite_finalize(vm, nil);
  184. // Attach EndItem
  185. TempItem^.Next:=FEndItem;
  186. FEndItem^.Previous:=TempItem;
  187. // Alloc item used in append/insert
  188. GetMem(FCacheItem^.Row,FRowBufferSize);
  189. for Counter := 0 to FRowCount - 1 do
  190. FCacheItem^.Row[Counter]:=nil;
  191. // Fill FBeginItem.Row with nil -> necessary for avoid exceptions in empty datasets
  192. GetMem(FBeginItem^.Row,FRowBufferSize);
  193. for Counter := 0 to FRowCount - 1 do
  194. FBeginItem^.Row[Counter]:=nil;
  195. end;
  196. function TSqliteDataset.TableExists: Boolean;
  197. var
  198. AHandle,vm:Pointer;
  199. ColumnNames,ColumnValues:PPChar;
  200. AInt:Integer;
  201. begin
  202. Result:=False;
  203. if not (FTableName = '') and FileExists(FFileName) then
  204. begin
  205. if FSqliteHandle = nil then
  206. begin
  207. {$ifdef DEBUG}
  208. writeln('TableExists - FSqliteHandle=nil : Opening a file');
  209. {$endif}
  210. AHandle:=GetSqliteHandle;
  211. end
  212. else
  213. begin
  214. {$ifdef DEBUG}
  215. writeln('TableExists - FSqliteHandle<>nil : Using FSqliteHandle');
  216. {$endif}
  217. AHandle:=FSqliteHandle;
  218. end;
  219. FSqliteReturnId:=sqlite_compile(AHandle,
  220. Pchar('SELECT name FROM SQLITE_MASTER WHERE type = ''table'' AND name LIKE '''+ FTableName+ ''';'),
  221. nil,@vm,nil);
  222. {$ifdef DEBUG}
  223. WriteLn('TableExists.sqlite_compile - SqliteReturnString:',SqliteReturnString);
  224. {$endif}
  225. FSqliteReturnId:=sqlite_step(vm,@AInt,@ColumnValues,@ColumnNames);
  226. {$ifdef DEBUG}
  227. WriteLn('TableExists.sqlite_step - SqliteReturnString:',SqliteReturnString);
  228. {$endif}
  229. Result:=FSqliteReturnId = SQLITE_ROW;
  230. sqlite_finalize(vm, nil);
  231. if FSqliteHandle = nil then
  232. SqliteClose(AHandle);
  233. end;
  234. {$ifdef DEBUG}
  235. WriteLn('TableExists ('+FTableName+') Result:',Result);
  236. {$endif}
  237. end;
  238. function TSqliteDataset.SqliteReturnString: String;
  239. begin
  240. case FSqliteReturnId of
  241. SQLITE_OK : Result := 'SQLITE_OK ';
  242. SQLITE_ERROR : Result := 'SQLITE_ERROR ';
  243. SQLITE_INTERNAL : Result := 'SQLITE_INTERNAL ';
  244. SQLITE_PERM : Result := 'SQLITE_PERM ';
  245. SQLITE_ABORT : Result := 'SQLITE_ABORT ';
  246. SQLITE_BUSY : Result := 'SQLITE_BUSY ';
  247. SQLITE_LOCKED : Result := 'SQLITE_LOCKED ';
  248. SQLITE_NOMEM : Result := 'SQLITE_NOMEM ';
  249. SQLITE_READONLY : Result := 'SQLITE_READONLY ';
  250. SQLITE_INTERRUPT : Result := 'SQLITE_INTERRUPT ';
  251. SQLITE_IOERR : Result := 'SQLITE_IOERR ';
  252. SQLITE_CORRUPT : Result := 'SQLITE_CORRUPT ';
  253. SQLITE_NOTFOUND : Result := 'SQLITE_NOTFOUND ';
  254. SQLITE_FULL : Result := 'SQLITE_FULL ';
  255. SQLITE_CANTOPEN : Result := 'SQLITE_CANTOPEN ';
  256. SQLITE_PROTOCOL : Result := 'SQLITE_PROTOCOL ';
  257. SQLITE_EMPTY : Result := 'SQLITE_EMPTY ';
  258. SQLITE_SCHEMA : Result := 'SQLITE_SCHEMA ';
  259. SQLITE_TOOBIG : Result := 'SQLITE_TOOBIG ';
  260. SQLITE_CONSTRAINT : Result := 'SQLITE_CONSTRAINT ';
  261. SQLITE_MISMATCH : Result := 'SQLITE_MISMATCH ';
  262. SQLITE_MISUSE : Result := 'SQLITE_MISUSE ';
  263. SQLITE_NOLFS : Result := 'SQLITE_NOLFS ';
  264. SQLITE_AUTH : Result := 'SQLITE_AUTH ';
  265. SQLITE_FORMAT : Result := 'SQLITE_FORMAT ';
  266. SQLITE_RANGE : Result := 'SQLITE_RANGE ';
  267. SQLITE_ROW : Result := 'SQLITE_ROW ';
  268. SQLITE_DONE : Result := 'SQLITE_DONE ';
  269. else
  270. Result:='Unknow Return Value';
  271. end;
  272. end;
  273. function TSqliteDataset.GetSqliteEncoding: String;
  274. begin
  275. Result:=StrPas(sqlite_encoding);
  276. end;
  277. function TSqliteDataset.GetSqliteVersion: String;
  278. begin
  279. Result:=StrPas(sqlite_version);
  280. end;
  281. function TSqliteDataset.QuickQuery(const ASql:String;const AStrList: TStrings;FillObjects:Boolean):String;
  282. var
  283. vm,AHandle:Pointer;
  284. ColumnNames,ColumnValues:PPChar;
  285. ColCount:Integer;
  286. procedure FillStrings;
  287. begin
  288. while FSqliteReturnId = SQLITE_ROW do
  289. begin
  290. AStrList.Add(StrPas(ColumnValues[0]));
  291. FSqliteReturnId:=sqlite_step(vm,@ColCount,@ColumnValues,@ColumnNames);
  292. end;
  293. end;
  294. procedure FillStringsAndObjects;
  295. begin
  296. while FSqliteReturnId = SQLITE_ROW do
  297. begin
  298. // I know, this code is really dirty!!
  299. AStrList.AddObject(StrPas(ColumnValues[0]),TObject(PtrInt(StrToInt(StrPas(ColumnValues[1])))));
  300. FSqliteReturnId:=sqlite_step(vm,@ColCount,@ColumnValues,@ColumnNames);
  301. end;
  302. end;
  303. begin
  304. if FSqliteHandle <> nil then
  305. AHandle:=FSqliteHandle
  306. else
  307. if FileExists(FFileName) then
  308. AHandle:=GetSqliteHandle
  309. else
  310. DatabaseError('File '+FFileName+' not Exists',Self);
  311. Result:='';
  312. // It's up to the caller clear or not the list
  313. //if AStrList <> nil then
  314. // AStrList.Clear;
  315. FSqliteReturnId:=sqlite_compile(AHandle,Pchar(ASql),nil,@vm,nil);
  316. if FSqliteReturnId <> SQLITE_OK then
  317. DatabaseError('Error returned by sqlite in QuickQuery: '+SqliteReturnString,Self);
  318. FSqliteReturnId:=sqlite_step(vm,@ColCount,@ColumnValues,@ColumnNames);
  319. if (FSqliteReturnId = SQLITE_ROW) and (ColCount > 0) then
  320. begin
  321. Result:=StrPas(ColumnValues[0]);
  322. if AStrList <> nil then
  323. begin
  324. if FillObjects and (ColCount > 1) then
  325. FillStringsAndObjects
  326. else
  327. FillStrings;
  328. end;
  329. end;
  330. sqlite_finalize(vm, nil);
  331. if FSqliteHandle = nil then
  332. sqlite_close(AHandle);
  333. end;
  334. end.