Akonadi Search

xapiansearchstore.cpp
1 /*
2  * This file is part of the KDE Akonadi Search Project
3  * SPDX-FileCopyrightText: 2013-2014 Vishesh Handa <[email protected]>
4  *
5  * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6  *
7  */
8 
9 #include "xapiansearchstore.h"
10 #include "query.h"
11 #include "xapianqueryparser.h"
12 
13 #include "akonadi_search_xapian_debug.h"
14 #include <QList>
15 
16 #include <algorithm>
17 
18 using namespace Akonadi::Search;
19 
20 XapianSearchStore::XapianSearchStore(QObject *parent)
21  : SearchStore(parent)
22  , m_mutex()
23 {
24 }
25 
26 XapianSearchStore::~XapianSearchStore()
27 {
28  delete m_db;
29 }
30 
32 {
33  m_dbPath = path;
34 
35  delete m_db;
36  m_db = nullptr;
37 
38  try {
39  m_db = new Xapian::Database(m_dbPath.toStdString());
40  } catch (const Xapian::DatabaseOpeningError &) {
41  qCWarning(AKONADI_SEARCH_XAPIAN_LOG) << "Xapian Database does not exist at " << m_dbPath;
42  } catch (const Xapian::DatabaseCorruptError &) {
43  qCWarning(AKONADI_SEARCH_XAPIAN_LOG) << "Xapian Database corrupted at " << m_dbPath;
44  } catch (...) {
45  qCWarning(AKONADI_SEARCH_XAPIAN_LOG) << "Random exception, but we do not want to crash";
46  }
47 }
48 
49 QString XapianSearchStore::dbPath()
50 {
51  return m_dbPath;
52 }
53 
54 Xapian::Query XapianSearchStore::toXapianQuery(Xapian::Query::op op, const QList<Term> &terms)
55 {
56  Q_ASSERT_X(op == Xapian::Query::OP_AND || op == Xapian::Query::OP_OR, "XapianSearchStore::toXapianQuery", "The op must be AND / OR");
57 
58  QList<Xapian::Query> queries;
59  queries.reserve(terms.size());
60 
61  for (const Term &term : terms) {
62  const Xapian::Query q = toXapianQuery(term);
63  queries << q;
64  }
65 
66  return {op, queries.begin(), queries.end()};
67 }
68 
69 static Xapian::Query negate(bool shouldNegate, const Xapian::Query &query)
70 {
71  if (shouldNegate) {
72  return Xapian::Query(Xapian::Query::OP_AND_NOT, Xapian::Query::MatchAll, query);
73  }
74  return query;
75 }
76 
77 Xapian::Query XapianSearchStore::toXapianQuery(const Term &term)
78 {
79  if (term.operation() == Term::And) {
80  return negate(term.isNegated(), toXapianQuery(Xapian::Query::OP_AND, term.subTerms()));
81  }
82  if (term.operation() == Term::Or) {
83  return negate(term.isNegated(), toXapianQuery(Xapian::Query::OP_OR, term.subTerms()));
84  }
85 
86  return negate(term.isNegated(), constructQuery(term.property(), term.value(), term.comparator()));
87 }
88 
89 Xapian::Query XapianSearchStore::andQuery(const Xapian::Query &a, const Xapian::Query &b)
90 {
91  if (a.empty() && !b.empty()) {
92  return b;
93  }
94  if (!a.empty() && b.empty()) {
95  return a;
96  }
97  if (a.empty() && b.empty()) {
98  return {};
99  } else {
100  return Xapian::Query(Xapian::Query::OP_AND, a, b);
101  }
102 }
103 
104 Xapian::Query XapianSearchStore::constructSearchQuery(const QString &str)
105 {
106  XapianQueryParser parser;
107  parser.setDatabase(m_db);
108  return parser.parseQuery(str);
109 }
110 
112 {
113  if (!m_db) {
114  return 0;
115  }
116 
117  while (true) {
118  try {
119  QMutexLocker lock(&m_mutex);
120  try {
121  m_db->reopen();
122  } catch (Xapian::DatabaseError &e) {
123  qCDebug(AKONADI_SEARCH_XAPIAN_LOG) << "Failed to reopen database" << dbPath() << ":" << QString::fromStdString(e.get_msg());
124  return 0;
125  }
126 
127  Xapian::Query xapQ = toXapianQuery(query.term());
128  // The term was not properly converted. Lets abort. The properties
129  // must not exist
130  if (!query.term().empty() && xapQ.empty()) {
131  qCDebug(AKONADI_SEARCH_XAPIAN_LOG) << query.term() << "could not be processed. Aborting";
132  return 0;
133  }
134  if (!query.searchString().isEmpty()) {
135  QString str = query.searchString();
136 
137  Xapian::Query q = constructSearchQuery(str);
138  xapQ = andQuery(xapQ, q);
139  }
140  xapQ = andQuery(xapQ, convertTypes(query.types()));
141  xapQ = andQuery(xapQ, constructFilterQuery(query.yearFilter(), query.monthFilter(), query.dayFilter()));
142  xapQ = applyCustomOptions(xapQ, query.customOptions());
143  xapQ = finalizeQuery(xapQ);
144 
145  if (xapQ.empty()) {
146  // Return all the results
147  xapQ = Xapian::Query(std::string());
148  }
149  Xapian::Enquire enquire(*m_db);
150  enquire.set_query(xapQ);
151 
152  if (query.sortingOption() == Query::SortNone) {
153  enquire.set_weighting_scheme(Xapian::BoolWeight());
154  }
155 
156  Result &res = m_queryMap[m_nextId++];
157  res.mset = enquire.get_mset(query.offset(), query.limit());
158  res.it = res.mset.begin();
159 
160  return m_nextId - 1;
161  } catch (const Xapian::DatabaseModifiedError &) {
162  continue;
163  } catch (const Xapian::Error &) {
164  return 0;
165  }
166  }
167 
168  return 0;
169 }
170 
171 void XapianSearchStore::close(int queryId)
172 {
173  QMutexLocker lock(&m_mutex);
174  m_queryMap.remove(queryId);
175 }
176 
177 QByteArray XapianSearchStore::id(int queryId)
178 {
179  QMutexLocker lock(&m_mutex);
180  Q_ASSERT_X(m_queryMap.contains(queryId), "FileSearchStore::id", "Passed a queryId which does not exist");
181 
182  const Result res = m_queryMap.value(queryId);
183  if (!res.lastId) {
184  return {};
185  }
186 
187  return serialize(idPrefix(), res.lastId);
188 }
189 
190 QUrl XapianSearchStore::url(int queryId)
191 {
192  QMutexLocker lock(&m_mutex);
193  Result &res = m_queryMap[queryId];
194 
195  if (!res.lastId) {
196  return {};
197  }
198 
199  if (!res.lastUrl.isEmpty()) {
200  return res.lastUrl;
201  }
202 
203  res.lastUrl = constructUrl(res.lastId);
204  return res.lastUrl;
205 }
206 
207 bool XapianSearchStore::next(int queryId)
208 {
209  if (!m_db) {
210  return false;
211  }
212 
213  QMutexLocker lock(&m_mutex);
214  Result &res = m_queryMap[queryId];
215 
216  bool atEnd = (res.it == res.mset.end());
217  if (atEnd) {
218  res.lastId = 0;
219  res.lastUrl.clear();
220  } else {
221  res.lastId = *res.it;
222  res.lastUrl.clear();
223  ++res.it;
224  }
225 
226  return !atEnd;
227 }
228 
229 Xapian::Document XapianSearchStore::docForQuery(int queryId)
230 {
231  if (!m_db) {
232  return {};
233  }
234 
235  QMutexLocker lock(&m_mutex);
236 
237  try {
238  const Result &res = m_queryMap.value(queryId);
239  if (!res.lastId) {
240  return {};
241  }
242 
243  return m_db->get_document(res.lastId);
244  } catch (const Xapian::DocNotFoundError &) {
245  return {};
246  } catch (const Xapian::DatabaseModifiedError &) {
247  m_db->reopen();
248  return docForQuery(queryId);
249  } catch (const Xapian::Error &) {
250  return {};
251  }
252 }
253 
254 Xapian::Database *XapianSearchStore::xapianDb()
255 {
256  return m_db;
257 }
258 
259 Xapian::Query XapianSearchStore::constructFilterQuery(int year, int month, int day)
260 {
261  Q_UNUSED(year)
262  Q_UNUSED(month)
263  Q_UNUSED(day)
264  return {};
265 }
266 
267 Xapian::Query XapianSearchStore::finalizeQuery(const Xapian::Query &query)
268 {
269  return query;
270 }
271 
272 Xapian::Query XapianSearchStore::applyCustomOptions(const Xapian::Query &q, const QVariantMap &options)
273 {
274  Q_UNUSED(options)
275  return q;
276 }
277 
278 #include "moc_xapiansearchstore.cpp"
const T value(const Key &key) const const
virtual QUrl constructUrl(const Xapian::docid &docid)=0
Returns the url for the document with id docid.
QHash::iterator begin()
int exec(const Query &query) override
Executes the particular query synchronously.
void reserve(int alloc)
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
bool empty() const const
int size() const const
Akonadi search infrastructure.
Definition: core/query.h:20
QString fromStdString(const std::string &str)
virtual Xapian::Query finalizeQuery(const Xapian::Query &query)
Apply any final touches to the query.
std::string toStdString() const const
bool isEmpty() const const
Xapian::Query andQuery(const Xapian::Query &a, const Xapian::Query &b)
Convenience function to AND two Xapian queries together.
virtual QByteArray idPrefix()=0
The prefix that should be used when converting an integer id to a byte array.
virtual Xapian::Query constructQuery(const QString &property, const QVariant &value, Term::Comparator com)=0
The derived class should implement the logic for constructing the appropriate Xapian::Query class fro...
Search term.
Definition: term.h:26
QString property() const
Return the property this term is targeting.
Definition: term.cpp:179
int remove(const Key &key)
virtual Xapian::Query applyCustomOptions(const Xapian::Query &q, const QVariantMap &options)
Create a query for any custom options.
QList::iterator begin()
virtual void setDbPath(const QString &path)
Set the path of the xapian database.
Search query.
Definition: core/query.h:26
virtual Xapian::Query convertTypes(const QStringList &types)=0
Gives a list of types which have been provided with the query.
bool contains(const Key &key) const const
QList::iterator end()
@ SortNone
The results are returned in the most efficient order.
Definition: core/query.h:93
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Tue Dec 5 2023 04:11:35 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.