PageRenderTime 59ms CodeModel.GetById 17ms RepoModel.GetById 15ms app.codeStats 0ms

/lib/query.cc

https://github.com/felipec/notmuch
C++ | 459 lines | 336 code | 90 blank | 33 comment | 35 complexity | 8e9ac1f47d058674c4202911caf282c9 MD5 | raw file
Possible License(s): GPL-3.0, LGPL-2.1
  1. /* query.cc - Support for searching a notmuch database
  2. *
  3. * Copyright © 2009 Carl Worth
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  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 General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see http://www.gnu.org/licenses/ .
  17. *
  18. * Author: Carl Worth <cworth@cworth.org>
  19. */
  20. #include "notmuch-private.h"
  21. #include "database-private.h"
  22. #include <glib.h> /* GHashTable, GPtrArray */
  23. struct _notmuch_query {
  24. notmuch_database_t *notmuch;
  25. const char *query_string;
  26. notmuch_sort_t sort;
  27. };
  28. typedef struct _notmuch_mset_messages {
  29. notmuch_messages_t base;
  30. notmuch_database_t *notmuch;
  31. Xapian::MSetIterator iterator;
  32. Xapian::MSetIterator iterator_end;
  33. } notmuch_mset_messages_t;
  34. struct _notmuch_doc_id_set {
  35. unsigned int *bitmap;
  36. unsigned int bound;
  37. };
  38. #define DOCIDSET_WORD(bit) ((bit) / sizeof (unsigned int))
  39. #define DOCIDSET_BIT(bit) ((bit) % sizeof (unsigned int))
  40. struct _notmuch_threads {
  41. notmuch_query_t *query;
  42. /* The ordered list of doc ids matched by the query. */
  43. GArray *doc_ids;
  44. /* Our iterator's current position in doc_ids. */
  45. unsigned int doc_id_pos;
  46. /* The set of matched docid's that have not been assigned to a
  47. * thread. Initially, this contains every docid in doc_ids. */
  48. notmuch_doc_id_set_t match_set;
  49. };
  50. notmuch_query_t *
  51. notmuch_query_create (notmuch_database_t *notmuch,
  52. const char *query_string)
  53. {
  54. notmuch_query_t *query;
  55. #ifdef DEBUG_QUERY
  56. fprintf (stderr, "Query string is:\n%s\n", query_string);
  57. #endif
  58. query = talloc (NULL, notmuch_query_t);
  59. if (unlikely (query == NULL))
  60. return NULL;
  61. query->notmuch = notmuch;
  62. query->query_string = talloc_strdup (query, query_string);
  63. query->sort = NOTMUCH_SORT_NEWEST_FIRST;
  64. return query;
  65. }
  66. const char *
  67. notmuch_query_get_query_string (notmuch_query_t *query)
  68. {
  69. return query->query_string;
  70. }
  71. void
  72. notmuch_query_set_sort (notmuch_query_t *query, notmuch_sort_t sort)
  73. {
  74. query->sort = sort;
  75. }
  76. notmuch_sort_t
  77. notmuch_query_get_sort (notmuch_query_t *query)
  78. {
  79. return query->sort;
  80. }
  81. /* We end up having to call the destructors explicitly because we had
  82. * to use "placement new" in order to initialize C++ objects within a
  83. * block that we allocated with talloc. So C++ is making talloc
  84. * slightly less simple to use, (we wouldn't need
  85. * talloc_set_destructor at all otherwise).
  86. */
  87. static int
  88. _notmuch_messages_destructor (notmuch_mset_messages_t *messages)
  89. {
  90. messages->iterator.~MSetIterator ();
  91. messages->iterator_end.~MSetIterator ();
  92. return 0;
  93. }
  94. notmuch_messages_t *
  95. notmuch_query_search_messages (notmuch_query_t *query)
  96. {
  97. notmuch_database_t *notmuch = query->notmuch;
  98. const char *query_string = query->query_string;
  99. notmuch_mset_messages_t *messages;
  100. messages = talloc (query, notmuch_mset_messages_t);
  101. if (unlikely (messages == NULL))
  102. return NULL;
  103. try {
  104. messages->base.is_of_list_type = FALSE;
  105. messages->base.iterator = NULL;
  106. messages->notmuch = notmuch;
  107. new (&messages->iterator) Xapian::MSetIterator ();
  108. new (&messages->iterator_end) Xapian::MSetIterator ();
  109. talloc_set_destructor (messages, _notmuch_messages_destructor);
  110. Xapian::Enquire enquire (*notmuch->xapian_db);
  111. Xapian::Query mail_query (talloc_asprintf (query, "%s%s",
  112. _find_prefix ("type"),
  113. "mail"));
  114. Xapian::Query string_query, final_query;
  115. Xapian::MSet mset;
  116. unsigned int flags = (Xapian::QueryParser::FLAG_BOOLEAN |
  117. Xapian::QueryParser::FLAG_PHRASE |
  118. Xapian::QueryParser::FLAG_LOVEHATE |
  119. Xapian::QueryParser::FLAG_BOOLEAN_ANY_CASE |
  120. Xapian::QueryParser::FLAG_WILDCARD |
  121. Xapian::QueryParser::FLAG_PURE_NOT);
  122. if (strcmp (query_string, "") == 0 ||
  123. strcmp (query_string, "*") == 0)
  124. {
  125. final_query = mail_query;
  126. } else {
  127. string_query = notmuch->query_parser->
  128. parse_query (query_string, flags);
  129. final_query = Xapian::Query (Xapian::Query::OP_AND,
  130. mail_query, string_query);
  131. }
  132. enquire.set_weighting_scheme (Xapian::BoolWeight());
  133. switch (query->sort) {
  134. case NOTMUCH_SORT_OLDEST_FIRST:
  135. enquire.set_sort_by_value (NOTMUCH_VALUE_TIMESTAMP, FALSE);
  136. break;
  137. case NOTMUCH_SORT_NEWEST_FIRST:
  138. enquire.set_sort_by_value (NOTMUCH_VALUE_TIMESTAMP, TRUE);
  139. break;
  140. case NOTMUCH_SORT_MESSAGE_ID:
  141. enquire.set_sort_by_value (NOTMUCH_VALUE_MESSAGE_ID, FALSE);
  142. break;
  143. case NOTMUCH_SORT_UNSORTED:
  144. break;
  145. }
  146. #if DEBUG_QUERY
  147. fprintf (stderr, "Final query is:\n%s\n", final_query.get_description().c_str());
  148. #endif
  149. enquire.set_query (final_query);
  150. mset = enquire.get_mset (0, notmuch->xapian_db->get_doccount ());
  151. messages->iterator = mset.begin ();
  152. messages->iterator_end = mset.end ();
  153. return &messages->base;
  154. } catch (const Xapian::Error &error) {
  155. fprintf (stderr, "A Xapian exception occurred performing query: %s\n",
  156. error.get_msg().c_str());
  157. fprintf (stderr, "Query string was: %s\n", query->query_string);
  158. notmuch->exception_reported = TRUE;
  159. talloc_free (messages);
  160. return NULL;
  161. }
  162. }
  163. notmuch_bool_t
  164. _notmuch_mset_messages_valid (notmuch_messages_t *messages)
  165. {
  166. notmuch_mset_messages_t *mset_messages;
  167. mset_messages = (notmuch_mset_messages_t *) messages;
  168. return (mset_messages->iterator != mset_messages->iterator_end);
  169. }
  170. static Xapian::docid
  171. _notmuch_mset_messages_get_doc_id (notmuch_messages_t *messages)
  172. {
  173. notmuch_mset_messages_t *mset_messages;
  174. mset_messages = (notmuch_mset_messages_t *) messages;
  175. if (! _notmuch_mset_messages_valid (&mset_messages->base))
  176. return 0;
  177. return *mset_messages->iterator;
  178. }
  179. notmuch_message_t *
  180. _notmuch_mset_messages_get (notmuch_messages_t *messages)
  181. {
  182. notmuch_message_t *message;
  183. Xapian::docid doc_id;
  184. notmuch_private_status_t status;
  185. notmuch_mset_messages_t *mset_messages;
  186. mset_messages = (notmuch_mset_messages_t *) messages;
  187. if (! _notmuch_mset_messages_valid (&mset_messages->base))
  188. return NULL;
  189. doc_id = *mset_messages->iterator;
  190. message = _notmuch_message_create (mset_messages,
  191. mset_messages->notmuch, doc_id,
  192. &status);
  193. if (message == NULL &&
  194. status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND)
  195. {
  196. INTERNAL_ERROR ("a messages iterator contains a non-existent document ID.\n");
  197. }
  198. return message;
  199. }
  200. void
  201. _notmuch_mset_messages_move_to_next (notmuch_messages_t *messages)
  202. {
  203. notmuch_mset_messages_t *mset_messages;
  204. mset_messages = (notmuch_mset_messages_t *) messages;
  205. mset_messages->iterator++;
  206. }
  207. static notmuch_bool_t
  208. _notmuch_doc_id_set_init (void *ctx,
  209. notmuch_doc_id_set_t *doc_ids,
  210. GArray *arr)
  211. {
  212. unsigned int max = 0;
  213. unsigned int *bitmap;
  214. for (unsigned int i = 0; i < arr->len; i++)
  215. max = MAX(max, g_array_index (arr, unsigned int, i));
  216. bitmap = talloc_zero_array (ctx, unsigned int, 1 + max / sizeof (*bitmap));
  217. if (bitmap == NULL)
  218. return FALSE;
  219. doc_ids->bitmap = bitmap;
  220. doc_ids->bound = max + 1;
  221. for (unsigned int i = 0; i < arr->len; i++) {
  222. unsigned int doc_id = g_array_index (arr, unsigned int, i);
  223. bitmap[DOCIDSET_WORD(doc_id)] |= 1 << DOCIDSET_BIT(doc_id);
  224. }
  225. return TRUE;
  226. }
  227. notmuch_bool_t
  228. _notmuch_doc_id_set_contains (notmuch_doc_id_set_t *doc_ids,
  229. unsigned int doc_id)
  230. {
  231. if (doc_id >= doc_ids->bound)
  232. return FALSE;
  233. return doc_ids->bitmap[DOCIDSET_WORD(doc_id)] & (1 << DOCIDSET_BIT(doc_id));
  234. }
  235. void
  236. _notmuch_doc_id_set_remove (notmuch_doc_id_set_t *doc_ids,
  237. unsigned int doc_id)
  238. {
  239. if (doc_id < doc_ids->bound)
  240. doc_ids->bitmap[DOCIDSET_WORD(doc_id)] &= ~(1 << DOCIDSET_BIT(doc_id));
  241. }
  242. /* Glib objects force use to use a talloc destructor as well, (but not
  243. * nearly as ugly as the for messages due to C++ objects). At
  244. * this point, I'd really like to have some talloc-friendly
  245. * equivalents for the few pieces of glib that I'm using. */
  246. static int
  247. _notmuch_threads_destructor (notmuch_threads_t *threads)
  248. {
  249. if (threads->doc_ids)
  250. g_array_unref (threads->doc_ids);
  251. return 0;
  252. }
  253. notmuch_threads_t *
  254. notmuch_query_search_threads (notmuch_query_t *query)
  255. {
  256. notmuch_threads_t *threads;
  257. notmuch_messages_t *messages;
  258. threads = talloc (query, notmuch_threads_t);
  259. if (threads == NULL)
  260. return NULL;
  261. threads->doc_ids = NULL;
  262. talloc_set_destructor (threads, _notmuch_threads_destructor);
  263. threads->query = query;
  264. messages = notmuch_query_search_messages (query);
  265. if (messages == NULL) {
  266. talloc_free (threads);
  267. return NULL;
  268. }
  269. threads->doc_ids = g_array_new (FALSE, FALSE, sizeof (unsigned int));
  270. while (notmuch_messages_valid (messages)) {
  271. unsigned int doc_id = _notmuch_mset_messages_get_doc_id (messages);
  272. g_array_append_val (threads->doc_ids, doc_id);
  273. notmuch_messages_move_to_next (messages);
  274. }
  275. threads->doc_id_pos = 0;
  276. talloc_free (messages);
  277. if (! _notmuch_doc_id_set_init (threads, &threads->match_set,
  278. threads->doc_ids)) {
  279. talloc_free (threads);
  280. return NULL;
  281. }
  282. return threads;
  283. }
  284. void
  285. notmuch_query_destroy (notmuch_query_t *query)
  286. {
  287. talloc_free (query);
  288. }
  289. notmuch_bool_t
  290. notmuch_threads_valid (notmuch_threads_t *threads)
  291. {
  292. unsigned int doc_id;
  293. while (threads->doc_id_pos < threads->doc_ids->len) {
  294. doc_id = g_array_index (threads->doc_ids, unsigned int,
  295. threads->doc_id_pos);
  296. if (_notmuch_doc_id_set_contains (&threads->match_set, doc_id))
  297. break;
  298. threads->doc_id_pos++;
  299. }
  300. return threads->doc_id_pos < threads->doc_ids->len;
  301. }
  302. notmuch_thread_t *
  303. notmuch_threads_get (notmuch_threads_t *threads)
  304. {
  305. unsigned int doc_id;
  306. if (! notmuch_threads_valid (threads))
  307. return NULL;
  308. doc_id = g_array_index (threads->doc_ids, unsigned int,
  309. threads->doc_id_pos);
  310. return _notmuch_thread_create (threads->query,
  311. threads->query->notmuch,
  312. doc_id,
  313. &threads->match_set,
  314. threads->query->sort);
  315. }
  316. void
  317. notmuch_threads_move_to_next (notmuch_threads_t *threads)
  318. {
  319. threads->doc_id_pos++;
  320. }
  321. void
  322. notmuch_threads_destroy (notmuch_threads_t *threads)
  323. {
  324. talloc_free (threads);
  325. }
  326. unsigned
  327. notmuch_query_count_messages (notmuch_query_t *query)
  328. {
  329. notmuch_database_t *notmuch = query->notmuch;
  330. const char *query_string = query->query_string;
  331. Xapian::doccount count = 0;
  332. try {
  333. Xapian::Enquire enquire (*notmuch->xapian_db);
  334. Xapian::Query mail_query (talloc_asprintf (query, "%s%s",
  335. _find_prefix ("type"),
  336. "mail"));
  337. Xapian::Query string_query, final_query;
  338. Xapian::MSet mset;
  339. unsigned int flags = (Xapian::QueryParser::FLAG_BOOLEAN |
  340. Xapian::QueryParser::FLAG_PHRASE |
  341. Xapian::QueryParser::FLAG_LOVEHATE |
  342. Xapian::QueryParser::FLAG_BOOLEAN_ANY_CASE |
  343. Xapian::QueryParser::FLAG_WILDCARD |
  344. Xapian::QueryParser::FLAG_PURE_NOT);
  345. if (strcmp (query_string, "") == 0 ||
  346. strcmp (query_string, "*") == 0)
  347. {
  348. final_query = mail_query;
  349. } else {
  350. string_query = notmuch->query_parser->
  351. parse_query (query_string, flags);
  352. final_query = Xapian::Query (Xapian::Query::OP_AND,
  353. mail_query, string_query);
  354. }
  355. enquire.set_weighting_scheme(Xapian::BoolWeight());
  356. enquire.set_docid_order(Xapian::Enquire::ASCENDING);
  357. #if DEBUG_QUERY
  358. fprintf (stderr, "Final query is:\n%s\n", final_query.get_description().c_str());
  359. #endif
  360. enquire.set_query (final_query);
  361. mset = enquire.get_mset (0, notmuch->xapian_db->get_doccount ());
  362. count = mset.get_matches_estimated();
  363. } catch (const Xapian::Error &error) {
  364. fprintf (stderr, "A Xapian exception occurred: %s\n",
  365. error.get_msg().c_str());
  366. fprintf (stderr, "Query string was: %s\n", query->query_string);
  367. }
  368. return count;
  369. }