Akonadi Search

xapiansearchstore.cpp
1/*
2 * This file is part of the KDE Akonadi Search Project
3 * SPDX-FileCopyrightText: 2013-2014 Vishesh Handa <me@vhanda.in>
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
18using namespace Akonadi::Search;
19
20XapianSearchStore::XapianSearchStore(QObject *parent)
21 : SearchStore(parent)
22 , m_mutex()
23{
24}
25
26XapianSearchStore::~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
49QString XapianSearchStore::dbPath()
50{
51 return m_dbPath;
52}
53
54Xapian::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
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
69static 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
77Xapian::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
89Xapian::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
104Xapian::Query XapianSearchStore::constructSearchQuery(const QString &str)
105{
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());
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
171void XapianSearchStore::close(int queryId)
172{
173 QMutexLocker lock(&m_mutex);
174 m_queryMap.remove(queryId);
175}
176
177QByteArray 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
190QUrl 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
207bool 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
229Xapian::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
254Xapian::Database *XapianSearchStore::xapianDb()
255{
256 return m_db;
257}
258
259Xapian::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
267Xapian::Query XapianSearchStore::finalizeQuery(const Xapian::Query &query)
268{
269 return query;
270}
271
272Xapian::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"
@ SortNone
The results are returned in the most efficient order.
Definition core/query.h:93
Search term.
Definition term.h:27
QString property() const
Return the property this term is targeting.
Definition term.cpp:179
virtual QUrl constructUrl(const Xapian::docid &docid)=0
Returns the url for the document with id docid.
int exec(const Query &query) override
Executes the particular query synchronously.
virtual Xapian::Query finalizeQuery(const Xapian::Query &query)
Apply any final touches to the query.
virtual Xapian::Query convertTypes(const QStringList &types)=0
Gives a list of types which have been provided with the query.
virtual QByteArray idPrefix()=0
The prefix that should be used when converting an integer id to a byte array.
virtual Xapian::Query applyCustomOptions(const Xapian::Query &q, const QVariantMap &options)
Create a query for any custom options.
Xapian::Query andQuery(const Xapian::Query &a, const Xapian::Query &b)
Convenience function to AND two Xapian queries together.
virtual void setDbPath(const QString &path)
Set the path of the xapian database.
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...
Akonadi search infrastructure.
Definition core/query.h:21
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
bool contains(const Key &key) const const
bool remove(const Key &key)
T value(const Key &key) const const
iterator begin()
bool empty() const const
iterator end()
bool isEmpty() const const
void reserve(qsizetype size)
T qobject_cast(QObject *object)
QString fromStdString(const std::string &str)
std::string toStdString() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:15:27 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.