clang-tags
C/C++ source code indexing tool based on libclang
 All Classes Functions Variables Typedefs Groups Pages
storage.hxx
1 #pragma once
2 
3 #include "sqlite++/sqlite.hxx"
4 #include "json/json.h"
5 
6 #include <sys/stat.h>
7 #include <unistd.h>
8 #include <vector>
9 #include <sstream>
10 #include <iostream>
11 
12 class Storage {
13 public:
14  Storage ()
15  : db_ (".ct.sqlite")
16  {
17  db_.execute ("CREATE TABLE IF NOT EXISTS files ("
18  " id INTEGER PRIMARY KEY,"
19  " name TEXT,"
20  " indexed INTEGER"
21  ")");
22  db_.execute ("CREATE TABLE IF NOT EXISTS commands ("
23  " fileId INTEGER REFERENCES files(id),"
24  " directory TEXT,"
25  " args TEXT"
26  ")");
27  db_.execute ("CREATE TABLE IF NOT EXISTS includes ("
28  " sourceId INTEGER REFERENCES files(id),"
29  " includedId INTEGER REFERENCES files(id)"
30  ")");
31  db_.execute ("CREATE TABLE IF NOT EXISTS tags ("
32  " fileId INTEGER REFERENCES files(id),"
33  " usr TEXT,"
34  " kind TEXT,"
35  " spelling TEXT,"
36  " line1 INTEGER,"
37  " col1 INTEGER,"
38  " offset1 INTEGER,"
39  " line2 INTEGER,"
40  " col2 INTEGER,"
41  " offset2 INTEGER,"
42  " isDecl BOOLEAN"
43  ")");
44  db_.execute ("CREATE TABLE IF NOT EXISTS options ( "
45  " name TEXT, "
46  " value TEXT "
47  ")");
48  }
49 
50  int setCompileCommand (const std::string & fileName,
51  const std::string & directory,
52  const std::vector<std::string> & args) {
53  int fileId = addFile_ (fileName);
54  addInclude (fileId, fileId);
55 
56  db_.prepare ("DELETE FROM commands "
57  "WHERE fileId=?")
58  .bind (fileId)
59  .step();
60 
61  db_.prepare ("INSERT INTO commands VALUES (?,?,?)")
62  .bind (fileId)
63  .bind (directory)
64  .bind (serialize_ (args))
65  .step();
66 
67  return fileId;
68  }
69 
70  void getCompileCommand (const std::string & fileName,
71  std::string & directory,
72  std::vector<std::string> & args) {
73  int fileId = fileId_ (fileName);
75  = db_.prepare ("SELECT commands.directory, commands.args "
76  "FROM includes "
77  "INNER JOIN commands ON includes.sourceId = commands.fileId "
78  "WHERE includes.includedId = ?")
79  .bind (fileId);
80 
81  switch (stmt.step()) {
82  case SQLITE_DONE:
83  throw std::runtime_error ("no compilation command for file `"
84  + fileName + "'");
85  break;
86  default:
87  std::string serializedArgs;
88  stmt >> directory >> serializedArgs;
89  deserialize_ (serializedArgs, args);
90  }
91  }
92 
93  std::string nextFile () {
95  = db_.prepare ("SELECT included.name, included.indexed, source.name, "
96  " count(source.name) AS sourceCount "
97  "FROM includes "
98  "INNER JOIN files AS source ON source.id = includes.sourceId "
99  "INNER JOIN files AS included ON included.id = includes.includedId "
100  "GROUP BY included.id "
101  "ORDER BY sourceCount ");
102  while (stmt.step() == SQLITE_ROW) {
103  std::string includedName;
104  int indexed;
105  std::string sourceName;
106  stmt >> includedName >> indexed >> sourceName;
107 
108  struct stat fileStat;
109  stat (includedName.c_str(), &fileStat);
110  int modified = fileStat.st_mtime;
111 
112  if (modified > indexed) {
113  return sourceName;
114  }
115  }
116 
117  return "";
118  }
119 
120  void cleanIndex () {
121  db_.execute ("DELETE FROM tags");
122  db_.execute ("UPDATE files SET indexed = 0");
123  }
124 
125  void beginIndex () {
126  db_.execute ("BEGIN TRANSACTION");
127  }
128 
129  void endIndex () {
130  db_.execute ("END TRANSACTION");
131  }
132 
133  bool beginFile (const std::string & fileName) {
134  int fileId = addFile_ (fileName);
135 
136  int indexed;
137  {
138  Sqlite::Statement stmt
139  = db_.prepare ("SELECT indexed FROM files WHERE id = ?")
140  .bind (fileId);
141  stmt.step();
142  stmt >> indexed;
143  }
144 
145  struct stat fileStat;
146  stat (fileName.c_str(), &fileStat);
147  int modified = fileStat.st_mtime;
148 
149  if (modified > indexed) {
150  db_.prepare ("DELETE FROM tags WHERE fileId=?").bind (fileId).step();
151  db_.prepare ("DELETE FROM includes WHERE sourceId=?").bind (fileId).step();
152  db_.prepare ("UPDATE files "
153  "SET indexed=? "
154  "WHERE id=?")
155  .bind (modified)
156  .bind (fileId)
157  .step();
158  return true;
159  } else {
160  return false;
161  }
162  }
163 
164  void addInclude (const int includedId,
165  const int sourceId)
166  {
167  int res = db_.prepare ("SELECT * FROM includes "
168  "WHERE sourceId=? "
169  " AND includedId=?")
170  .bind (sourceId) .bind (includedId)
171  .step ();
172  if (res == SQLITE_DONE) { // No matching row
173  db_.prepare ("INSERT INTO includes VALUES (?,?)")
174  .bind (sourceId) . bind (includedId)
175  .step();
176  }
177  }
178 
179  void addInclude (const std::string & includedFile,
180  const std::string & sourceFile) {
181  int includedId = fileId_ (includedFile);
182  int sourceId = fileId_ (sourceFile);
183  if (includedId == -1 || sourceId == -1)
184  throw std::runtime_error ("Cannot add inclusion for unknown files `"
185  + includedFile + "' and `" + sourceFile + "'");
186  addInclude (includedId, sourceId);
187  }
188 
189  void addTag (const std::string & usr,
190  const std::string & kind,
191  const std::string & spelling,
192  const std::string & fileName,
193  const int line1, const int col1, const int offset1,
194  const int line2, const int col2, const int offset2,
195  bool isDeclaration) {
196  int fileId = fileId_ (fileName);
197  if (fileId == -1) {
198  return;
199  }
200 
201  Sqlite::Statement stmt =
202  db_.prepare ("SELECT * FROM tags "
203  "WHERE fileId=? "
204  " AND usr=?"
205  " AND offset1=?"
206  " AND offset2=?")
207  .bind (fileId).bind (usr).bind (offset1).bind (offset2);
208  if (stmt.step() == SQLITE_DONE) { // no matching row
209  db_.prepare ("INSERT INTO tags VALUES (?,?,?,?,?,?,?,?,?,?,?)")
210  .bind(fileId) .bind(usr) .bind(kind) .bind(spelling)
211  .bind(line1) .bind(col1) .bind(offset1)
212  .bind(line2) .bind(col2) .bind(offset2)
213  .bind(isDeclaration)
214  .step();
215  }
216  }
217 
218  struct Reference {
219  std::string file;
220  int line1;
221  int line2;
222  int col1;
223  int col2;
224  int offset1;
225  int offset2;
226  std::string kind;
227  std::string spelling;
228 
229  Json::Value json () const {
230  Json::Value json;
231  json["file"] = file;
232  json["line1"] = line1;
233  json["line2"] = line2;
234  json["col1"] = col1;
235  json["col2"] = col2;
236  json["offset1"] = offset1;
237  json["offset2"] = offset2;
238  json["kind"] = kind;
239  json["spelling"] = spelling;
240  return json;
241  }
242  };
243 
244  struct Definition {
245  std::string usr;
246  std::string file;
247  int line1;
248  int line2;
249  int col1;
250  int col2;
251  std::string kind;
252  std::string spelling;
253 
254  Json::Value json () const {
255  Json::Value json;
256  json["usr"] = usr;
257  json["file"] = file;
258  json["line1"] = line1;
259  json["line2"] = line2;
260  json["col1"] = col1;
261  json["col2"] = col2;
262  json["kind"] = kind;
263  json["spelling"] = spelling;
264  return json;
265  }
266  };
267 
268  struct RefDef {
269  Reference ref;
270  Definition def;
271 
272  Json::Value json () const {
273  Json::Value json;
274  json["ref"] = ref.json();
275  json["def"] = def.json();
276  return json;
277  }
278  };
279 
280  std::vector<RefDef> findDefinition (const std::string fileName,
281  int offset) {
282  int fileId = fileId_ (fileName);
283  Sqlite::Statement stmt =
284  db_.prepare ("SELECT ref.offset1, ref.offset2, ref.kind, ref.spelling,"
285  " def.usr, defFile.name,"
286  " def.line1, def.line2, def.col1, def.col2, "
287  " def.kind, def.spelling "
288  "FROM tags AS ref "
289  "INNER JOIN tags AS def ON def.usr = ref.usr "
290  "INNER JOIN files AS defFile ON def.fileId = defFile.id "
291  "WHERE def.isDecl = 1 "
292  " AND ref.fileId = ? "
293  " AND ref.offset1 <= ? "
294  " AND ref.offset2 >= ? "
295  "ORDER BY (ref.offset2 - ref.offset1)")
296  .bind (fileId)
297  .bind (offset)
298  .bind (offset);
299 
300  std::vector<RefDef> ret;
301  while (stmt.step() == SQLITE_ROW) {
302  RefDef refDef;
303  Reference & ref = refDef.ref;
304  Definition & def = refDef.def;
305 
306  stmt >> ref.offset1 >> ref.offset2 >> ref.kind >> ref.spelling
307  >> def.usr >> def.file
308  >> def.line1 >> def.line2 >> def.col1 >> def.col2
309  >> def.kind >> def.spelling;
310  ref.file = fileName;
311  ret.push_back(refDef);
312  }
313  return ret;
314  }
315 
316  std::vector<Reference> grep (const std::string usr) {
317  Sqlite::Statement stmt =
318  db_.prepare("SELECT ref.line1, ref.line2, ref.col1, ref.col2, "
319  " ref.offset1, ref.offset2, refFile.name, ref.kind "
320  "FROM tags AS ref "
321  "INNER JOIN files AS refFile ON ref.fileId = refFile.id "
322  "WHERE ref.usr = ?")
323  .bind (usr);
324 
325  std::vector<Reference> ret;
326  while (stmt.step() == SQLITE_ROW) {
327  Reference ref;
328  stmt >> ref.line1 >> ref.line2 >> ref.col1 >> ref.col2
329  >> ref.offset1 >> ref.offset2 >> ref.file >> ref.kind;
330  ret.push_back (ref);
331  }
332  return ret;
333  }
334 
335  void setOption (const std::string & name, const std::string & value) {
336  db_.prepare ("DELETE FROM options "
337  "WHERE name = ?")
338  .bind (name)
339  .step();
340 
341  db_.prepare ("INSERT INTO options "
342  "VALUES (?, ?)")
343  .bind (name)
344  .bind (value)
345  .step();
346  }
347 
348  void setOption (const std::string & name, const std::vector<std::string> & value) {
349  setOption (name, serialize_(value));
350  }
351 
352 
353  std::string getOption (const std::string & name) {
354  Sqlite::Statement stmt =
355  db_.prepare ("SELECT value FROM options "
356  "WHERE name = ?")
357  .bind (name);
358 
359  std::string ret;
360  if (stmt.step() == SQLITE_ROW) {
361  stmt >> ret;
362  } else {
363  throw std::runtime_error ("No stored value for option: `" + name + "'");
364  }
365 
366  return ret;
367  }
368 
369  struct Vector {};
370  std::vector<std::string> getOption (const std::string & name, const Vector & v) {
371  std::vector<std::string> ret;
372  deserialize_ (getOption (name), ret);
373  return ret;
374  }
375 
376 private:
377  int fileId_ (const std::string & fileName) {
378  Sqlite::Statement stmt
379  = db_.prepare ("SELECT id FROM files WHERE name=?")
380  .bind (fileName);
381 
382  int id = -1;
383  if (stmt.step() == SQLITE_ROW) {
384  stmt >> id;
385  }
386 
387  return id;
388  }
389 
390  int addFile_ (const std::string & fileName) {
391  int id = fileId_ (fileName);
392  if (id == -1) {
393  db_.prepare ("INSERT INTO files VALUES (NULL, ?, 0)")
394  .bind (fileName)
395  .step();
396 
397  id = db_.lastInsertRowId();
398  }
399  return id;
400  }
401 
402  std::string serialize_ (const std::vector<std::string> & v) {
403  Json::Value json;
404  auto it = v.begin();
405  auto end = v.end();
406  for ( ; it != end ; ++it) {
407  json.append (*it);
408  }
409 
410  Json::FastWriter writer;
411  return writer.write (json);
412  }
413 
414  void deserialize_ (const std::string & s, std::vector<std::string> & v) {
415  Json::Value json;
416  Json::Reader reader;
417  if (! reader.parse (s, json)) {
418  throw std::runtime_error (reader.getFormattedErrorMessages());
419  }
420 
421  for (int i=0 ; i<json.size() ; ++i) {
422  v.push_back (json[i].asString());
423  }
424  }
425 
426  Sqlite::Database db_;
427 };