Commit 8e6cf2de authored by Daniel Vrátil's avatar Daniel Vrátil 🤖

Implement support for server-side search in Akonadi

Instead of using Nepomuk and SPARQL queries, we now use SearchQuery and SearchTerm objects
to construct queries (internally represented as a JSON query language based on Baloo's
query language.

We now also support server-search, so agents that implement AgentSearchInterface can search
their storage service (like IMAP SEARCH for example) and return results back to Akonadi. This
allows us to search even items that are not indexed by a local indexing service, like Nepomuk
or Baloo.

Big thanks to Christian Mollekopf for his help

Squashed commit from akonadi/server-search branch.
parent 7b0788cc
......@@ -21,7 +21,9 @@
#include "contactgroupsearchjob.h"
#include <searchquery.h>
#include <akonadi/itemfetchscope.h>
#include <QStringList>
using namespace Akonadi;
......@@ -37,8 +39,12 @@ ContactGroupSearchJob::ContactGroupSearchJob( QObject * parent )
fetchScope().fetchFullPayload();
d->mLimit = -1;
setMimeTypes( QStringList() << KABC::ContactGroup::mimeType() );
// by default search for all contact groups
ItemSearchJob::setQuery( QLatin1String( "SELECT ?r WHERE { ?r a nco:ContactGroup }" ) );
Akonadi::SearchQuery query;
query.addTerm( ContactSearchTerm(ContactSearchTerm::All, QVariant(), SearchTerm::CondEqual) );
ItemSearchJob::setQuery( query );
}
ContactGroupSearchJob::~ContactGroupSearchJob()
......@@ -53,55 +59,26 @@ void ContactGroupSearchJob::setQuery( Criterion criterion, const QString &value
setQuery( criterion, value, ExactMatch );
}
void ContactGroupSearchJob::setQuery( Criterion criterion, const QString &value, Match match )
static Akonadi::SearchTerm::Condition matchType( ContactGroupSearchJob::Match match )
{
QString query;
if ( match == ExactMatch ) {
if ( criterion == Name ) {
query += QString::fromLatin1(
QByteArray(QByteArray("SELECT DISTINCT ?group "
"WHERE { "
" graph ?g { "
" ?group <") + akonadiItemIdUri().toEncoded() + QByteArray("> ?itemId . "
" ?group nco:contactGroupName \"%1\"^^<http://www.w3.org/2001/XMLSchema#string>."
" } "
"}"))
);
}
} else if ( match == ContainsMatch ) {
if ( criterion == Name ) {
query += QString::fromLatin1(
QByteArray(QByteArray("SELECT DISTINCT ?group "
"WHERE { "
" graph ?g { "
" ?group <") + akonadiItemIdUri().toEncoded() + QByteArray("> ?itemId . "
" ?group nco:contactGroupName ?v . "
" ?v bif:contains \"'%1'\""
" } "
"}"))
);
}
} else if ( match == StartsWithMatch ) {
if ( criterion == Name ) {
query += QString::fromLatin1(
QByteArray(QByteArray("SELECT DISTINCT ?group "
"WHERE { "
" graph ?g { "
" ?group <") + akonadiItemIdUri().toEncoded() + QByteArray("> ?itemId . "
" ?group nco:contactGroupName ?v . "
" ?v bif:contains \"'%1*'\""
" } "
"}"))
);
}
switch( match ) {
case ContactGroupSearchJob::ExactMatch:
return Akonadi::SearchTerm::CondEqual;
case ContactGroupSearchJob::StartsWithMatch:
case ContactGroupSearchJob::ContainsMatch:
return Akonadi::SearchTerm::CondContains;
}
return Akonadi::SearchTerm::SearchTerm::CondEqual;
}
if ( d->mLimit != -1 ) {
query += QString::fromLatin1( " LIMIT %1" ).arg( d->mLimit );
void ContactGroupSearchJob::setQuery( Criterion criterion, const QString &value, Match match )
{
Akonadi::SearchQuery query;
if ( criterion == Name ) {
query.addTerm(ContactSearchTerm(ContactSearchTerm::Name, value, matchType(match)));
}
query = query.arg( value );
query.setLimit( d->mLimit );
ItemSearchJob::setQuery( query );
}
......
......@@ -108,7 +108,7 @@ class AKONADI_CONTACT_EXPORT ContactGroupSearchJob : public ItemSearchJob
/**
* Sets a @p limit on how many results will be returned by this search job.
* This is useful in situation where for example only the first search result is needed anyway,
* setting a limit of 1 here will greatly reduce the resource usage of Nepomuk during the
* setting a limit of 1 here will greatly reduce the resource usage during the
* search.
* @param limit the limit to set
* @note this needs to be called before calling setQuery() to have an effect.
......
......@@ -20,6 +20,7 @@
*/
#include "contactsearchjob.h"
#include <searchquery.h>
#include <akonadi/itemfetchscope.h>
......@@ -37,8 +38,12 @@ ContactSearchJob::ContactSearchJob( QObject * parent )
fetchScope().fetchFullPayload();
d->mLimit = -1;
setMimeTypes( QStringList() << KABC::Addressee::mimeType() );
// by default search for all contacts
ItemSearchJob::setQuery( QLatin1String( "SELECT ?r WHERE { ?r a nco:Contact }" ) );
Akonadi::SearchQuery query;
query.addTerm( ContactSearchTerm(ContactSearchTerm::All, QVariant(), SearchTerm::CondEqual) );
ItemSearchJob::setQuery( query );
}
ContactSearchJob::~ContactSearchJob()
......@@ -51,224 +56,37 @@ void ContactSearchJob::setQuery( Criterion criterion, const QString &value )
setQuery( criterion, value, ExactMatch );
}
// helper method, returns the SPARQL sub-expression to be used for finding
// string either as a whole word, the start of a word, or anywhere in a word
static QString containsQueryString( bool doWholeWordSearch, bool matchWordBoundary )
static Akonadi::SearchTerm::Condition matchType( ContactSearchJob::Match match )
{
if ( doWholeWordSearch ) {
return QString::fromLatin1( "?v bif:contains \"'%1'\" . " );
} else {
if ( matchWordBoundary ) {
return QString::fromLatin1( "?v bif:contains \"'%1*'\" . " );
} else {
return QString::fromLatin1( "FILTER regex(str(?v), \"%1\", \"i\")" );
}
switch ( match ) {
case ContactSearchJob::ExactMatch:
return Akonadi::SearchTerm::CondEqual;
case ContactSearchJob::StartsWithMatch:
case ContactSearchJob::ContainsWordBoundaryMatch:
case ContactSearchJob::ContainsMatch:
return Akonadi::SearchTerm::CondContains;
}
return Akonadi::SearchTerm::SearchTerm::CondEqual;
}
void ContactSearchJob::setQuery( Criterion criterion, const QString &value, Match match )
{
if ( match == StartsWithMatch && value.size() < 4 ) {
match = ExactMatch;
}
const bool doWholeWordSearch = value.size() < 3;
const bool matchWordBoundary = match == ContainsWordBoundaryMatch;
QString query;
if ( match == ExactMatch ) {
if ( criterion == Name ) {
query += QString::fromLatin1(
QByteArray(QByteArray("SELECT DISTINCT ?r ?reqProp1 "
"WHERE { "
" "
" ?r <") + akonadiItemIdUri().toEncoded() + QByteArray("> ?reqProp1 . "
" ?r nco:fullname \"%1\"^^<http://www.w3.org/2001/XMLSchema#string>. "
" "
"} "))
);
} else if ( criterion == Email ) {
query += QString::fromLatin1(
QByteArray(QByteArray("SELECT DISTINCT ?person ?reqProp1 "
"WHERE { "
" "
" ?person <") + akonadiItemIdUri().toEncoded() + QByteArray("> ?reqProp1 . "
" ?person nco:hasEmailAddress ?email . "
" ?email nco:emailAddress \"%1\"^^<http://www.w3.org/2001/XMLSchema#string> . "
" "
"}"))
);
} else if ( criterion == NickName ) {
query += QString::fromLatin1(
QByteArray(QByteArray("SELECT DISTINCT ?r ?reqProp1 "
"WHERE { "
" "
" ?r <") + akonadiItemIdUri().toEncoded() + QByteArray("> ?reqProp1 . "
" ?r nco:nickname \"%1\"^^<http://www.w3.org/2001/XMLSchema#string> ."
" "
"}"))
);
} else if ( criterion == NameOrEmail ) {
query += QString::fromLatin1(
QByteArray(QByteArray("SELECT DISTINCT ?r ?reqProp1 "
"WHERE { "
" "
" ?r <") + akonadiItemIdUri().toEncoded() + QByteArray("> ?reqProp1 . "
" { ?r ?p \"%1\"^^<http://www.w3.org/2001/XMLSchema#string>. "
" FILTER(?p in (nco:fullname, nco:nameGiven, nco:nameFamily, nco:nameAdditional)) . }"
" UNION "
" { ?r nco:hasEmailAddress ?email . "
" ?email nco:emailAddress \"%1\"^^<http://www.w3.org/2001/XMLSchema#string> . } "
" "
"}"))
);
} else if ( criterion == ContactUid ) {
query += QString::fromLatin1(
QByteArray(QByteArray("SELECT DISTINCT ?r ?reqProp1 "
"WHERE { "
" "
" ?r <") + akonadiItemIdUri().toEncoded() + QByteArray("> ?reqProp1 . "
" ?r nco:contactUID \"%1\"^^<http://www.w3.org/2001/XMLSchema#string> ."
" "
"}"))
);
}
} else if ( match == StartsWithMatch ) {
if ( criterion == Name ) {
query += QString::fromLatin1(
QByteArray(QByteArray("SELECT DISTINCT ?r ?reqProp1 "
"WHERE { "
" "
" ?r <") + akonadiItemIdUri().toEncoded() + QByteArray("> ?reqProp1 . "
" ?r nco:fullname ?v . "
" ?v bif:contains \"'%1*'\" . "
" "
"} "))
);
} else if ( criterion == Email ) {
query += QString::fromLatin1(
QByteArray(QByteArray("SELECT DISTINCT ?person ?reqProp1 "
"WHERE { "
" "
" ?person <") + akonadiItemIdUri().toEncoded() + QByteArray("> ?reqProp1 . "
" ?person nco:hasEmailAddress ?email . "
" ?email nco:emailAddress ?v . "
" ?v bif:contains \"'%1\'\" . "
" "
"}"))
);
} else if ( criterion == NickName ) {
query += QString::fromLatin1(
QByteArray(QByteArray("SELECT DISTINCT ?r ?reqProp1 "
"WHERE { "
" "
" ?r <") + akonadiItemIdUri().toEncoded() + QByteArray("> ?reqProp1 . "
" ?r nco:nickname ?v . "
" ?v bif:contains \"'%1\'\" . "
" "
"}"))
);
} else if ( criterion == NameOrEmail ) {
query += QString::fromLatin1(
QByteArray(QByteArray("SELECT DISTINCT ?r ?reqProp1 "
"WHERE { "
" "
" ?r <") + akonadiItemIdUri().toEncoded() + QByteArray("> ?reqProp1 . "
" { ?r ?p ?v . "
" FILTER(?p in (nco:fullname, nco:nameGiven, nco:nameFamily, nco:nameAdditional)). "
" ?v bif:contains \"'%1'\" . }"
" UNION "
" { ?r nco:hasEmailAddress ?email . "
" ?email nco:emailAddress ?v . "
" ?v bif:contains \"'%1'\" . }"
" "
"}"))
);
} else if ( criterion == ContactUid ) {
query += QString::fromLatin1(
QByteArray(QByteArray("SELECT DISTINCT ?r ?reqProp1 "
"WHERE { "
" "
" ?r <") + akonadiItemIdUri().toEncoded() + QByteArray("> ?reqProp1 . "
" ?r nco:contactUID ?v . "
" ?v bif:contains \"'%1*'\" . "
" "
"}"))
);
}
} else if ( match == ContainsMatch || match == ContainsWordBoundaryMatch ) {
if ( criterion == Name ) {
query += QString::fromLatin1(
QByteArray(QByteArray("SELECT DISTINCT ?r ?reqProp1 "
"WHERE { "
" "
" ?r <") + akonadiItemIdUri().toEncoded() + QByteArray("> ?reqProp1 . "
" ?r nco:fullname ?v . "
"%1"
" "
"} "))
);
query = query.arg( containsQueryString( doWholeWordSearch, matchWordBoundary ) );
} else if ( criterion == Email ) {
query += QString::fromLatin1(
QByteArray(QByteArray("SELECT DISTINCT ?person ?reqProp1 "
"WHERE { "
" "
" ?person <") + akonadiItemIdUri().toEncoded() + QByteArray("> ?reqProp1 . "
" ?person nco:hasEmailAddress ?email . "
" ?email nco:emailAddress ?v . "
"%1"
" "
"}"))
);
query = query.arg( containsQueryString( doWholeWordSearch, matchWordBoundary ) );
} else if ( criterion == NickName ) {
query += QString::fromLatin1(
QByteArray(QByteArray("SELECT DISTINCT ?r ?reqProp1 "
"WHERE { "
" "
" ?r <") + akonadiItemIdUri().toEncoded() + QByteArray("> ?reqProp1 . "
" ?r nco:nickname ?v . "
"%1"
" "
"}"))
);
query = query.arg( containsQueryString( doWholeWordSearch, matchWordBoundary ) );
} else if ( criterion == NameOrEmail ) {
query += QString::fromLatin1(
QByteArray(QByteArray("SELECT DISTINCT ?r ?reqProp1 "
"WHERE { "
" "
" ?r<") + akonadiItemIdUri().toEncoded() + QByteArray("> ?reqProp1 . "
" { ?r ?p ?v ."
" FILTER(?p in (nco:fullname, nco:nameGiven, nco:nameFamily, nco:nameAdditional) ) ."
"%1 } UNION"
" { ?r nco:hasEmailAddress ?email . "
" ?email nco:emailAddress ?v . "
"%1 }"
" "
"}"))
);
query = query.arg( containsQueryString( doWholeWordSearch, matchWordBoundary ) );
} else if ( criterion == ContactUid ) {
query += QString::fromLatin1(
QByteArray(QByteArray("SELECT DISTINCT ?r ?reqProp1 "
"WHERE { "
" "
" ?r <") + akonadiItemIdUri().toEncoded() + QByteArray("> ?reqProp1 . "
" ?r nco:contactUID ?v . "
" ?v bif:contains \"'%1'\" . "
" "
"}"))
);
}
Akonadi::SearchQuery query( SearchTerm::RelOr) ;
if ( criterion == Name ) {
query.addTerm( ContactSearchTerm( ContactSearchTerm::Name, value, matchType( match ) ) );
} else if ( criterion == Email ) {
query.addTerm( ContactSearchTerm( ContactSearchTerm::Email, value, matchType( match ) ) );
} else if ( criterion == NickName ) {
query.addTerm( ContactSearchTerm( ContactSearchTerm::Nickname, value, matchType( match ) ) );
} else if ( criterion == NameOrEmail ) {
query.addTerm( ContactSearchTerm( ContactSearchTerm::Name, value, matchType( match ) ) );
query.addTerm( ContactSearchTerm(ContactSearchTerm::Email, value, matchType( match ) ) );
} else if ( criterion == ContactUid ) {
query.addTerm( ContactSearchTerm( ContactSearchTerm::Uid, value, matchType( match ) ) );
}
if ( d->mLimit != -1 ) {
query += QString::fromLatin1( " LIMIT %1" ).arg( d->mLimit );
}
query = query.arg( value );
query.setLimit( d->mLimit );
ItemSearchJob::setQuery( query );
}
......
......@@ -138,7 +138,7 @@ class AKONADI_CONTACT_EXPORT ContactSearchJob : public ItemSearchJob
* Sets a @p limit on how many results will be returned by this search job.
*
* This is useful in situation where for example only the first search result is needed anyway,
* setting a limit of 1 here will greatly reduce the resource usage of Nepomuk during the
* setting a limit of 1 here will greatly reduce the resource usage during the
* search.
* This needs to be called before calling setQuery() to have an effect.
* By default, the number of results is unlimited.
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment