Warning, file /pim/akonadi-search/xapian/xapiansearchstore.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002  * This file is part of the KDE Akonadi Search Project
0003  * SPDX-FileCopyrightText: 2013-2014 Vishesh Handa <me@vhanda.in>
0004  *
0005  * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0006  *
0007  */
0008 
0009 #include "xapiansearchstore.h"
0010 #include "query.h"
0011 #include "xapianqueryparser.h"
0012 
0013 #include "akonadi_search_xapian_debug.h"
0014 #include <QList>
0015 
0016 #include <algorithm>
0017 
0018 using namespace Akonadi::Search;
0019 
0020 XapianSearchStore::XapianSearchStore(QObject *parent)
0021     : SearchStore(parent)
0022     , m_mutex()
0023 {
0024 }
0025 
0026 XapianSearchStore::~XapianSearchStore()
0027 {
0028     delete m_db;
0029 }
0030 
0031 void XapianSearchStore::setDbPath(const QString &path)
0032 {
0033     m_dbPath = path;
0034 
0035     delete m_db;
0036     m_db = nullptr;
0037 
0038     try {
0039         m_db = new Xapian::Database(m_dbPath.toStdString());
0040     } catch (const Xapian::DatabaseOpeningError &) {
0041         qCWarning(AKONADI_SEARCH_XAPIAN_LOG) << "Xapian Database does not exist at " << m_dbPath;
0042     } catch (const Xapian::DatabaseCorruptError &) {
0043         qCWarning(AKONADI_SEARCH_XAPIAN_LOG) << "Xapian Database corrupted at " << m_dbPath;
0044     } catch (...) {
0045         qCWarning(AKONADI_SEARCH_XAPIAN_LOG) << "Random exception, but we do not want to crash";
0046     }
0047 }
0048 
0049 QString XapianSearchStore::dbPath()
0050 {
0051     return m_dbPath;
0052 }
0053 
0054 Xapian::Query XapianSearchStore::toXapianQuery(Xapian::Query::op op, const QList<Term> &terms)
0055 {
0056     Q_ASSERT_X(op == Xapian::Query::OP_AND || op == Xapian::Query::OP_OR, "XapianSearchStore::toXapianQuery", "The op must be AND / OR");
0057 
0058     QList<Xapian::Query> queries;
0059     queries.reserve(terms.size());
0060 
0061     for (const Term &term : terms) {
0062         const Xapian::Query q = toXapianQuery(term);
0063         queries << q;
0064     }
0065 
0066     return {op, queries.begin(), queries.end()};
0067 }
0068 
0069 static Xapian::Query negate(bool shouldNegate, const Xapian::Query &query)
0070 {
0071     if (shouldNegate) {
0072         return Xapian::Query(Xapian::Query::OP_AND_NOT, Xapian::Query::MatchAll, query);
0073     }
0074     return query;
0075 }
0076 
0077 Xapian::Query XapianSearchStore::toXapianQuery(const Term &term)
0078 {
0079     if (term.operation() == Term::And) {
0080         return negate(term.isNegated(), toXapianQuery(Xapian::Query::OP_AND, term.subTerms()));
0081     }
0082     if (term.operation() == Term::Or) {
0083         return negate(term.isNegated(), toXapianQuery(Xapian::Query::OP_OR, term.subTerms()));
0084     }
0085 
0086     return negate(term.isNegated(), constructQuery(term.property(), term.value(), term.comparator()));
0087 }
0088 
0089 Xapian::Query XapianSearchStore::andQuery(const Xapian::Query &a, const Xapian::Query &b)
0090 {
0091     if (a.empty() && !b.empty()) {
0092         return b;
0093     }
0094     if (!a.empty() && b.empty()) {
0095         return a;
0096     }
0097     if (a.empty() && b.empty()) {
0098         return {};
0099     } else {
0100         return Xapian::Query(Xapian::Query::OP_AND, a, b);
0101     }
0102 }
0103 
0104 Xapian::Query XapianSearchStore::constructSearchQuery(const QString &str)
0105 {
0106     XapianQueryParser parser;
0107     parser.setDatabase(m_db);
0108     return parser.parseQuery(str);
0109 }
0110 
0111 int XapianSearchStore::exec(const Query &query)
0112 {
0113     if (!m_db) {
0114         return 0;
0115     }
0116 
0117     while (true) {
0118         try {
0119             QMutexLocker lock(&m_mutex);
0120             try {
0121                 m_db->reopen();
0122             } catch (Xapian::DatabaseError &e) {
0123                 qCDebug(AKONADI_SEARCH_XAPIAN_LOG) << "Failed to reopen database" << dbPath() << ":" << QString::fromStdString(e.get_msg());
0124                 return 0;
0125             }
0126 
0127             Xapian::Query xapQ = toXapianQuery(query.term());
0128             // The term was not properly converted. Lets abort. The properties
0129             // must not exist
0130             if (!query.term().empty() && xapQ.empty()) {
0131                 qCDebug(AKONADI_SEARCH_XAPIAN_LOG) << query.term() << "could not be processed. Aborting";
0132                 return 0;
0133             }
0134             if (!query.searchString().isEmpty()) {
0135                 QString str = query.searchString();
0136 
0137                 Xapian::Query q = constructSearchQuery(str);
0138                 xapQ = andQuery(xapQ, q);
0139             }
0140             xapQ = andQuery(xapQ, convertTypes(query.types()));
0141             xapQ = andQuery(xapQ, constructFilterQuery(query.yearFilter(), query.monthFilter(), query.dayFilter()));
0142             xapQ = applyCustomOptions(xapQ, query.customOptions());
0143             xapQ = finalizeQuery(xapQ);
0144 
0145             if (xapQ.empty()) {
0146                 // Return all the results
0147                 xapQ = Xapian::Query(std::string());
0148             }
0149             Xapian::Enquire enquire(*m_db);
0150             enquire.set_query(xapQ);
0151 
0152             if (query.sortingOption() == Query::SortNone) {
0153                 enquire.set_weighting_scheme(Xapian::BoolWeight());
0154             }
0155 
0156             Result &res = m_queryMap[m_nextId++];
0157             res.mset = enquire.get_mset(query.offset(), query.limit());
0158             res.it = res.mset.begin();
0159 
0160             return m_nextId - 1;
0161         } catch (const Xapian::DatabaseModifiedError &) {
0162             continue;
0163         } catch (const Xapian::Error &) {
0164             return 0;
0165         }
0166     }
0167 
0168     return 0;
0169 }
0170 
0171 void XapianSearchStore::close(int queryId)
0172 {
0173     QMutexLocker lock(&m_mutex);
0174     m_queryMap.remove(queryId);
0175 }
0176 
0177 QByteArray XapianSearchStore::id(int queryId)
0178 {
0179     QMutexLocker lock(&m_mutex);
0180     Q_ASSERT_X(m_queryMap.contains(queryId), "FileSearchStore::id", "Passed a queryId which does not exist");
0181 
0182     const Result res = m_queryMap.value(queryId);
0183     if (!res.lastId) {
0184         return {};
0185     }
0186 
0187     return serialize(idPrefix(), res.lastId);
0188 }
0189 
0190 QUrl XapianSearchStore::url(int queryId)
0191 {
0192     QMutexLocker lock(&m_mutex);
0193     Result &res = m_queryMap[queryId];
0194 
0195     if (!res.lastId) {
0196         return {};
0197     }
0198 
0199     if (!res.lastUrl.isEmpty()) {
0200         return res.lastUrl;
0201     }
0202 
0203     res.lastUrl = constructUrl(res.lastId);
0204     return res.lastUrl;
0205 }
0206 
0207 bool XapianSearchStore::next(int queryId)
0208 {
0209     if (!m_db) {
0210         return false;
0211     }
0212 
0213     QMutexLocker lock(&m_mutex);
0214     Result &res = m_queryMap[queryId];
0215 
0216     bool atEnd = (res.it == res.mset.end());
0217     if (atEnd) {
0218         res.lastId = 0;
0219         res.lastUrl.clear();
0220     } else {
0221         res.lastId = *res.it;
0222         res.lastUrl.clear();
0223         ++res.it;
0224     }
0225 
0226     return !atEnd;
0227 }
0228 
0229 Xapian::Document XapianSearchStore::docForQuery(int queryId)
0230 {
0231     if (!m_db) {
0232         return {};
0233     }
0234 
0235     QMutexLocker lock(&m_mutex);
0236 
0237     try {
0238         const Result &res = m_queryMap.value(queryId);
0239         if (!res.lastId) {
0240             return {};
0241         }
0242 
0243         return m_db->get_document(res.lastId);
0244     } catch (const Xapian::DocNotFoundError &) {
0245         return {};
0246     } catch (const Xapian::DatabaseModifiedError &) {
0247         m_db->reopen();
0248         return docForQuery(queryId);
0249     } catch (const Xapian::Error &) {
0250         return {};
0251     }
0252 }
0253 
0254 Xapian::Database *XapianSearchStore::xapianDb()
0255 {
0256     return m_db;
0257 }
0258 
0259 Xapian::Query XapianSearchStore::constructFilterQuery(int year, int month, int day)
0260 {
0261     Q_UNUSED(year)
0262     Q_UNUSED(month)
0263     Q_UNUSED(day)
0264     return {};
0265 }
0266 
0267 Xapian::Query XapianSearchStore::finalizeQuery(const Xapian::Query &query)
0268 {
0269     return query;
0270 }
0271 
0272 Xapian::Query XapianSearchStore::applyCustomOptions(const Xapian::Query &q, const QVariantMap &options)
0273 {
0274     Q_UNUSED(options)
0275     return q;
0276 }
0277 
0278 #include "moc_xapiansearchstore.cpp"