sqliteindexdb.pp 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. {
  2. This file is part of the Free Component Library (FCL)
  3. Copyright (c) 2012 by the Free Pascal development team
  4. SQLite-based index database
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. {$IFNDEF FPC_DOTTEDUNITS}
  12. unit SQLiteIndexDB;
  13. {$ENDIF FPC_DOTTEDUNITS}
  14. {$mode objfpc}{$H+}
  15. interface
  16. {$IFDEF FPC_DOTTEDUNITS}
  17. uses
  18. System.SysUtils, FpIndexer.Indexer, System.CTypes, Api.Sqlite3;
  19. {$ELSE FPC_DOTTEDUNITS}
  20. uses
  21. SysUtils, fpIndexer, ctypes, sqlite3;
  22. {$ENDIF FPC_DOTTEDUNITS}
  23. type
  24. TDatabaseID = record
  25. ID: int64;
  26. Name: UTF8string;
  27. end;
  28. { TSQLiteIndexDB }
  29. TSQLiteIndexDB = class(TSQLIndexDB)
  30. private
  31. db: Psqlite3;
  32. FFileName: UTF8string;
  33. Frow: integer;
  34. FSearchClass: TFPSearch;
  35. LanguageID: TDatabaseID;
  36. QueryResult: UTF8string;
  37. SearchWordID: TDatabaseID;
  38. URLID: TDatabaseID;
  39. FMatchList : TUTF8StringArray;
  40. procedure CheckSQLite(Rc: cint; pzErrMsg: PAnsiChar);
  41. protected
  42. class function AllowForeignKeyInTable: boolean; override;
  43. function GetFieldType(FieldType: TIndexField): UTF8string; override;
  44. function GetLanguageID(const ALanguage: UTF8string): int64;
  45. function GetURLID(const URL: UTF8string; ATimeStamp: TDateTime; ALanguageID: int64; DoCreate: boolean = True): int64; override;
  46. function GetWordID(const AWord: UTF8string): int64; virtual;
  47. function InsertLanguage(const ALanguage: UTF8string): int64; virtual;
  48. function InsertURL(const URL: UTF8string; ATimeStamp: TDateTime; ALanguageID: int64): int64;
  49. function InsertWord(const AWord: UTF8string): int64; virtual;
  50. procedure Execute(const sql: UTF8string; ignoreErrors: boolean = True); override;
  51. public
  52. destructor Destroy; override;
  53. procedure AddSearchData(ASearchData: TSearchWordData); override;
  54. procedure BeginTrans; override;
  55. procedure CommitTrans; override;
  56. procedure CompactDB; override;
  57. procedure Connect; override;
  58. procedure CreateDB; override;
  59. procedure DeleteWordsFromFile(URL: UTF8string); override;
  60. procedure FindSearchData(SearchWord: TWordParser; FPSearch: TFPSearch; SearchOptions: TSearchOptions); override;
  61. function GetAvailableWords(out aList : TUTF8StringArray; aContaining : UTF8String; Partial : TAvailableMatch) : integer;override;
  62. published
  63. property FileName: UTF8string read FFileName write FFileName;
  64. end;
  65. implementation
  66. function SearchCallback(_para1: pointer; plArgc: longint; argv: PPAnsiChar; argcol: PPAnsiChar): longint; cdecl;
  67. var
  68. PVal: ^PAnsiChar;
  69. SearchRes: TSearchWordData;
  70. begin
  71. PVal := argv;
  72. with SearchRes do
  73. begin
  74. Position := StrToInt64(PVal^); Inc(PVal);
  75. URL := PVal^; Inc(PVal);
  76. Context := PVal^; Inc(PVal);
  77. SearchWord := PVal^; Inc(PVal);
  78. FileDate := ISO8601ToDate(PVal^); Inc(PVal);
  79. Language := PVal^;
  80. end;
  81. with TSQLiteIndexDB(_para1) do
  82. begin
  83. FSearchClass.AddResult(FRow, SearchRes);
  84. Inc(Frow);
  85. end;
  86. Result := 0;
  87. end;
  88. function WordListCallback(_para1: pointer; plArgc: longint; argv: PPAnsiChar; argcol: PPAnsiChar): longint; cdecl;
  89. var
  90. PVal: ^PAnsiChar;
  91. S : UTF8String;
  92. begin
  93. PVal := argv;
  94. S:=PVal^;
  95. with TSQLiteIndexDB(_para1) do
  96. begin
  97. if Length(FMatchList)<=FRow then
  98. SetLength(FMatchList,Length(FMatchList)+10);
  99. FMatchList[FRow]:=S;
  100. Inc(Frow);
  101. end;
  102. Result := 0;
  103. end;
  104. function IndexCallback(_para1: pointer; plArgc: longint; argv: PPAnsiChar; argcol: PPAnsiChar): longint; cdecl;
  105. begin
  106. //store the query result
  107. TSQLiteIndexDB(_para1).QueryResult := argv^;
  108. Result := 0;
  109. end;
  110. { TSQLiteIndexDB }
  111. procedure TSQLiteIndexDB.Execute(const sql: UTF8string; ignoreErrors: boolean = True);
  112. var
  113. pzErrMsg: PAnsiChar;
  114. rc: cint;
  115. begin
  116. QueryResult := '';
  117. //Writeln('Executing : ',SQL);
  118. rc := sqlite3_exec(db, PAnsiChar(sql), @IndexCallback, self, @pzErrMsg);
  119. if not ignoreErrors then
  120. CheckSQLite(rc, pzErrMsg);
  121. end;
  122. function TSQLiteIndexDB.GetURLID(const URL: UTF8string; ATimeStamp: TDateTime; ALanguageID: int64; DoCreate: boolean): int64;
  123. var
  124. SQL: UTF8string;
  125. begin
  126. if (URL = URLID.Name) then
  127. Result := URLID.ID
  128. else
  129. begin
  130. SQL := Format(GetURLSQL(False), [QuoteString(URL)]);
  131. Execute(SQL, False);
  132. Result := StrToInT64Def(QueryResult, -1);
  133. if (Result = -1) and DoCreate then
  134. Result := InsertURL(URL, ATimeStamp, ALanguageID);
  135. URLID.ID := Result;
  136. URLID.Name := URL;
  137. end;
  138. end;
  139. function TSQLiteIndexDB.GetLanguageID(const ALanguage: UTF8string): int64;
  140. var
  141. SQL: UTF8string;
  142. begin
  143. if (ALanguage = LanguageID.Name) then
  144. Result := LanguageID.ID
  145. else
  146. begin
  147. SQL := Format(GetLanguageSQL(False), [QuoteString(Alanguage)]);
  148. Execute(SQL, False);
  149. Result := StrToInT64Def(QueryResult, -1);
  150. if (Result = -1) then
  151. Result := InsertLanguage(ALanguage);
  152. LanguageID.ID := Result;
  153. LanguageID.Name := ALanguage;
  154. end;
  155. end;
  156. function TSQLiteIndexDB.GetWordID(const AWord: UTF8string): int64;
  157. var
  158. SQL: UTF8string;
  159. begin
  160. if (AWord = SearchWordID.Name) then
  161. Result := SearchWordID.ID
  162. else
  163. begin
  164. SQL := Format(GetWordSQL(False), [QuoteString(AWord)]);
  165. Execute(SQL, False);
  166. Result := StrToInT64Def(QueryResult, -1);
  167. if (Result = -1) then
  168. Result := InsertWord(AWord);
  169. SearchWordID.ID := Result;
  170. SearchWordID.Name := AWord;
  171. end;
  172. end;
  173. function TSQLiteIndexDB.InsertWord(const AWord: UTF8string): int64;
  174. begin
  175. Execute(Format(InsertSQL(itWords, False), ['Null', QuoteString(AWord)]), False);
  176. Result := sqlite3_last_insert_rowid(db);
  177. end;
  178. function TSQLiteIndexDB.InsertURL(const URL: UTF8string; ATimeStamp: TDateTime; ALanguageID: int64): int64;
  179. begin
  180. // ifFilesID,ifFilesURL,ifFilesReindex,ifFilesUpdated,ifFilesTimeStamp,ifFilesLanguageID
  181. Execute(Format(InsertSQL(itFiles, False), ['Null', QuoteString(URL), '0', '0', QuoteString(DateToISO8601(ATimeStamp)), IntToStr(AlanguageID)]), False);
  182. Result := sqlite3_last_insert_rowid(db);
  183. end;
  184. function TSQLiteIndexDB.InsertLanguage(const ALanguage: UTF8string): int64;
  185. begin
  186. Execute(Format(InsertSQL(itLanguages, False), ['Null', QuoteString(ALanguage)]), False);
  187. Result := sqlite3_last_insert_rowid(db);
  188. end;
  189. function TSQLiteIndexDB.GetFieldType(FieldType: TIndexField): UTF8string;
  190. begin
  191. Result := inherited GetFieldType(FieldType);
  192. if (Result = PrimaryFieldType) then
  193. Result := 'INTEGER PRIMARY KEY NOT NULL';
  194. end;
  195. class function TSQLiteIndexDB.AllowForeignKeyInTable: boolean;
  196. begin
  197. Result := True;
  198. end;
  199. procedure TSQLiteIndexDB.DeleteWordsFromFile(URL: UTF8string);
  200. begin
  201. inherited DeleteWordsFromFile(URL);
  202. //reset the cached URL ID
  203. URLID.ID := -1;
  204. URLID.Name := '';
  205. end;
  206. procedure TSQLiteIndexDB.CreateDB;
  207. begin
  208. Connect;
  209. CreateIndexerTables;
  210. end;
  211. procedure TSQLiteIndexDB.Connect;
  212. var
  213. rc: cint;
  214. begin
  215. if (Filename = '') then
  216. raise EFPIndexer.Create('Error: no filename specified');
  217. rc := sqlite3_open(PAnsiChar(FFilename), @db);
  218. if rc <> SQLITE_OK then
  219. raise EFPIndexer.CreateFmt('Cannot open database: %s', [filename]);
  220. end;
  221. destructor TSQLiteIndexDB.Destroy;
  222. begin
  223. sqlite3_close(db);
  224. inherited Destroy;
  225. end;
  226. procedure TSQLiteIndexDB.BeginTrans;
  227. begin
  228. Execute('BEGIN IMMEDIATE TRANSACTION');
  229. end;
  230. procedure TSQLiteIndexDB.CommitTrans;
  231. begin
  232. Execute('COMMIT TRANSACTION');
  233. end;
  234. procedure TSQLiteIndexDB.CompactDB;
  235. begin
  236. {$note this does not work, why?}
  237. //Execute('VACUUM');
  238. end;
  239. procedure TSQLiteIndexDB.AddSearchData(ASearchData: TSearchWordData);
  240. var
  241. WID, LID, FID: int64;
  242. SQL: UTF8string;
  243. begin
  244. WID := GetWordID(ASearchData.SearchWord);
  245. LID := GetLanguageID(ASearchData.Language);
  246. FID := GetURLID(ASearchData.URL, ASearchData.FileDate, LID, True);
  247. SQL := InsertSQL(itMatches, False);
  248. // ifMatchesID,ifMatchesWordId,ifMatchesFileID,ifMatchesLanguageID,ifMatchesPosition,ifMatchesContext,
  249. SQL := Format(SQL, ['Null', IntToStr(WID), IntToStr(FID), IntToStr(LID), IntToStr(ASearchData.Position), QuoteString(ASearchData.Context)]);
  250. //add to SearchWordList
  251. Execute(SQL, False);
  252. // Result:=sqlite3_last_insert_rowid(db);
  253. end;
  254. procedure TSQLiteIndexDB.CheckSQLite(Rc: cint; pzErrMsg: PAnsiChar);
  255. var
  256. S: UTF8string;
  257. begin
  258. if (rc <> SQLITE_OK) then
  259. begin
  260. if (pzErrMsg <> nil) then
  261. S := strPas(pzErrMsg);
  262. raise EFPIndexer.CreateFmt('SQLite error: %s', [S]);
  263. end;
  264. end;
  265. procedure TSQLiteIndexDB.FindSearchData(SearchWord: TWordParser; FPSearch: TFPSearch; SearchOptions: TSearchOptions);
  266. var
  267. pzErrMsg: PAnsiChar;
  268. rc: cint;
  269. sql: UTF8string;
  270. begin
  271. FSearchClass := FPSearch;
  272. Frow := 0;
  273. sql := GetMatchSQL(SearchOptions, SearchWord, False);
  274. //sql := Format(sql, [SearchWord]);
  275. rc := sqlite3_exec(db, PAnsiChar(sql), @SearchCallback, self, @pzErrMsg);
  276. CheckSQLite(rc, pzErrMsg);
  277. end;
  278. function TSQLiteIndexDB.GetAvailableWords(out aList: TUTF8StringArray; aContaining: UTF8String; Partial: TAvailableMatch): integer;
  279. Var
  280. st,sql: UTF8string;
  281. rc: cint;
  282. pzErrMsg: PAnsiChar;
  283. begin
  284. Result:=0;
  285. FRow:=0;
  286. SetLength(FMatchList,0);
  287. aContaining:=LowerCase(aContaining);
  288. sql := AvailableWordsSQL(aContaining,Partial);
  289. aContaining:=StringReplace(aContaining,'''','''''',[rfReplaceAll]);
  290. case Partial of
  291. amExact : st:=aContaining;
  292. amContains : st:='%'+aContaining+'%';
  293. amStartsWith : st:=aContaining+'%';
  294. else
  295. ST:='';
  296. end;
  297. sql:=StringReplace(SQL,':'+SearchTermParam,''''+ST+'''',[]);
  298. rc := sqlite3_exec(db, PAnsiChar(sql), @WordListCallback, self, @pzErrMsg);
  299. CheckSQLite(rc, pzErrMsg);
  300. SetLength(FMatchList,FRow);
  301. aList:=FMatchList;
  302. FMatchList:=Nil;
  303. end;
  304. end.