mirror of https://github.com/djcb/mu.git
lib/query,parser: update to use mu-message-fields
This commit is contained in:
parent
7c185590e4
commit
0df7a6959a
|
@ -24,6 +24,7 @@
|
|||
#include <iostream>
|
||||
#include <regex>
|
||||
|
||||
#include <mu-message.hh>
|
||||
#include <utils/mu-utils.hh>
|
||||
|
||||
namespace Mu {
|
||||
|
@ -34,13 +35,14 @@ struct Data {
|
|||
enum class Type { Value, Range };
|
||||
virtual ~Data() = default;
|
||||
|
||||
Type type; /**< type of data */
|
||||
std::string field; /**< full name of the field */
|
||||
std::string prefix; /**< Xapian prefix for thef field */
|
||||
unsigned id; /**< Xapian value no for the field */
|
||||
Type type; /**< type of data */
|
||||
std::string field; /**< full name of the field */
|
||||
std::string prefix; /**< Xapian prefix for thef field */
|
||||
Message::Field::Id id; /**< Xapian value no for the field */
|
||||
|
||||
protected:
|
||||
Data(Type _type, const std::string& _field, const std::string& _prefix, unsigned _id)
|
||||
Data(Type _type, const std::string& _field, const std::string& _prefix,
|
||||
Message::Field::Id _id)
|
||||
: type(_type), field(_field), prefix(_prefix), id(_id)
|
||||
{
|
||||
}
|
||||
|
@ -80,7 +82,7 @@ struct Range : public Data {
|
|||
*/
|
||||
Range(const std::string& _field,
|
||||
const std::string& _prefix,
|
||||
unsigned _id,
|
||||
Message::Field::Id _id,
|
||||
const std::string& _lower,
|
||||
const std::string& _upper)
|
||||
:
|
||||
|
@ -103,12 +105,12 @@ struct Value : public Data {
|
|||
*
|
||||
* @param _field the field
|
||||
* @param _prefix the xapian prefix
|
||||
* @param _id xapian value number
|
||||
* @param _id field id
|
||||
* @param _value the value
|
||||
*/
|
||||
Value(const std::string& _field,
|
||||
const std::string& _prefix,
|
||||
unsigned _id,
|
||||
Message::Field::Id _id,
|
||||
const std::string& _value,
|
||||
bool _phrase = false)
|
||||
: Data(Value::Type::Value, _field, _prefix, _id), value(_value), phrase(_phrase)
|
||||
|
|
230
lib/mu-parser.cc
230
lib/mu-parser.cc
|
@ -17,12 +17,18 @@
|
|||
** 02110-1301, USA.
|
||||
*/
|
||||
#include "mu-parser.hh"
|
||||
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
|
||||
|
||||
#include "mu-tokenizer.hh"
|
||||
#include "utils/mu-utils.hh"
|
||||
#include "utils/mu-error.hh"
|
||||
#include <algorithm>
|
||||
#include "mu-message.hh"
|
||||
|
||||
using namespace Mu;
|
||||
using namespace Mu::Message;
|
||||
|
||||
// 3 precedence levels: units (NOT,()) > factors (OR) > terms (AND)
|
||||
|
||||
|
@ -52,17 +58,14 @@ struct FieldInfo {
|
|||
const std::string field;
|
||||
const std::string prefix;
|
||||
bool supports_phrase;
|
||||
unsigned id;
|
||||
Field::Id id;
|
||||
};
|
||||
using FieldInfoVec = std::vector<FieldInfo>;
|
||||
|
||||
using Flags = Parser::Flags;
|
||||
|
||||
struct Parser::Private {
|
||||
Private(const Store& store, Flags flags) : store_{store}, flags_{flags} {}
|
||||
Private(const Store& store, Parser::Flags flags) : store_{store}, flags_{flags} {}
|
||||
|
||||
std::vector<std::string> process_regex(const std::string& field,
|
||||
const std::regex& rx) const;
|
||||
const std::regex& rx) const;
|
||||
|
||||
Mu::Tree term_1(Mu::Tokens& tokens, WarningVec& warnings) const;
|
||||
Mu::Tree term_2(Mu::Tokens& tokens, Node::Type& op, WarningVec& warnings) const;
|
||||
|
@ -71,118 +74,92 @@ struct Parser::Private {
|
|||
Mu::Tree unit(Mu::Tokens& tokens, WarningVec& warnings) const;
|
||||
Mu::Tree data(Mu::Tokens& tokens, WarningVec& warnings) const;
|
||||
Mu::Tree range(const FieldInfoVec& fields,
|
||||
const std::string& lower,
|
||||
const std::string& upper,
|
||||
size_t pos,
|
||||
WarningVec& warnings) const;
|
||||
const std::string& lower,
|
||||
const std::string& upper,
|
||||
size_t pos,
|
||||
WarningVec& warnings) const;
|
||||
Mu::Tree regex(const FieldInfoVec& fields,
|
||||
const std::string& v,
|
||||
size_t pos,
|
||||
WarningVec& warnings) const;
|
||||
const std::string& v,
|
||||
size_t pos,
|
||||
WarningVec& warnings) const;
|
||||
Mu::Tree value(const FieldInfoVec& fields,
|
||||
const std::string& v,
|
||||
size_t pos,
|
||||
WarningVec& warnings) const;
|
||||
const std::string& v,
|
||||
size_t pos,
|
||||
WarningVec& warnings) const;
|
||||
|
||||
private:
|
||||
const Store& store_;
|
||||
const Flags flags_;
|
||||
const Parser::Flags flags_;
|
||||
};
|
||||
|
||||
static MuMsgFieldId
|
||||
field_id(const std::string& field)
|
||||
{
|
||||
if (field.empty())
|
||||
return MU_MSG_FIELD_ID_NONE;
|
||||
|
||||
MuMsgFieldId id = mu_msg_field_id_from_name(field.c_str(), FALSE);
|
||||
if (id != MU_MSG_FIELD_ID_NONE)
|
||||
return id;
|
||||
else if (field.length() == 1)
|
||||
return mu_msg_field_id_from_shortcut(field[0], FALSE);
|
||||
else
|
||||
return MU_MSG_FIELD_ID_NONE;
|
||||
}
|
||||
|
||||
static std::string
|
||||
process_value(const std::string& field, const std::string& value)
|
||||
{
|
||||
const auto id = field_id(field);
|
||||
if (id == MU_MSG_FIELD_ID_NONE)
|
||||
return value;
|
||||
switch (id) {
|
||||
case MU_MSG_FIELD_ID_PRIO: {
|
||||
if (!value.empty())
|
||||
return std::string(1, value[0]);
|
||||
} break;
|
||||
|
||||
case MU_MSG_FIELD_ID_FLAGS:
|
||||
if (const auto info{message_flag_info(value)}; info)
|
||||
return std::string(1, info->shortcut_lower());
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
const auto id_opt{message_field_id(field)};
|
||||
if (id_opt) {
|
||||
switch (*id_opt) {
|
||||
case Field::Id::Priority: {
|
||||
if (!value.empty())
|
||||
return std::string(1, value[0]);
|
||||
} break;
|
||||
case Field::Id::Flags:
|
||||
if (const auto info{message_flag_info(value)}; info)
|
||||
return std::string(1, info->shortcut_lower());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return value; // XXX prio/flags, etc. alias
|
||||
}
|
||||
|
||||
static void
|
||||
add_field(std::vector<FieldInfo>& fields, MuMsgFieldId id)
|
||||
add_field(std::vector<FieldInfo>& fields, Field::Id field_id)
|
||||
{
|
||||
const auto shortcut = mu_msg_field_shortcut(id);
|
||||
if (!shortcut)
|
||||
const auto field{message_field(field_id)};
|
||||
if (!field.shortcut)
|
||||
return; // can't be searched
|
||||
|
||||
const auto name = mu_msg_field_name(id);
|
||||
const auto pfx = mu_msg_field_xapian_prefix(id);
|
||||
|
||||
if (!name || !pfx)
|
||||
return;
|
||||
|
||||
fields.push_back({{name}, {pfx}, (bool)mu_msg_field_xapian_index(id), id});
|
||||
fields.emplace_back(FieldInfo{std::string{field.name}, field.xapian_term(),
|
||||
field.is_full_text(), field_id});
|
||||
}
|
||||
|
||||
static std::vector<FieldInfo>
|
||||
process_field(const std::string& field, Flags flags)
|
||||
process_field(const std::string& field_str, Parser::Flags flags)
|
||||
{
|
||||
std::vector<FieldInfo> fields;
|
||||
if (any_of(flags & Flags::UnitTest)) {
|
||||
add_field(fields, MU_MSG_FIELD_ID_MSGID);
|
||||
if (any_of(flags & Parser::Flags::UnitTest)) {
|
||||
add_field(fields, Field::Id::MessageId);
|
||||
return fields;
|
||||
}
|
||||
|
||||
if (field == "contact" || field == "recip") { // multi fields
|
||||
add_field(fields, MU_MSG_FIELD_ID_TO);
|
||||
add_field(fields, MU_MSG_FIELD_ID_CC);
|
||||
add_field(fields, MU_MSG_FIELD_ID_BCC);
|
||||
if (field == "contact")
|
||||
add_field(fields, MU_MSG_FIELD_ID_FROM);
|
||||
} else if (field == "") {
|
||||
add_field(fields, MU_MSG_FIELD_ID_TO);
|
||||
add_field(fields, MU_MSG_FIELD_ID_CC);
|
||||
add_field(fields, MU_MSG_FIELD_ID_BCC);
|
||||
add_field(fields, MU_MSG_FIELD_ID_FROM);
|
||||
add_field(fields, MU_MSG_FIELD_ID_SUBJECT);
|
||||
add_field(fields, MU_MSG_FIELD_ID_BODY_TEXT);
|
||||
} else {
|
||||
const auto id = field_id(field);
|
||||
if (id != MU_MSG_FIELD_ID_NONE)
|
||||
add_field(fields, id);
|
||||
}
|
||||
if (field_str == "contact" || field_str == "recip") { // multi fields
|
||||
add_field(fields, Field::Id::To);
|
||||
add_field(fields, Field::Id::Cc);
|
||||
add_field(fields, Field::Id::Bcc);
|
||||
if (field_str == "contact")
|
||||
add_field(fields, Field::Id::From);
|
||||
} else if (field_str.empty()) {
|
||||
add_field(fields, Field::Id::To);
|
||||
add_field(fields, Field::Id::Cc);
|
||||
add_field(fields, Field::Id::Bcc);
|
||||
add_field(fields, Field::Id::From);
|
||||
add_field(fields, Field::Id::Subject);
|
||||
add_field(fields, Field::Id::BodyText);
|
||||
} else if (const auto id_opt{message_field_id(field_str)}; id_opt)
|
||||
add_field(fields, *id_opt);
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
static bool
|
||||
is_range_field(const std::string& field)
|
||||
is_range_field(const std::string& field_str)
|
||||
{
|
||||
const auto id = field_id(field);
|
||||
if (id == MU_MSG_FIELD_ID_NONE)
|
||||
if (const auto field_id_opt{message_field_id(field_str)}; !field_id_opt)
|
||||
return false;
|
||||
else
|
||||
return mu_msg_field_is_range_field(id);
|
||||
return message_field(*field_id_opt).is_range();
|
||||
}
|
||||
|
||||
struct MyRange {
|
||||
|
@ -191,19 +168,20 @@ struct MyRange {
|
|||
};
|
||||
|
||||
static MyRange
|
||||
process_range(const std::string& field, const std::string& lower, const std::string& upper)
|
||||
process_range(const std::string& field_str,
|
||||
const std::string& lower, const std::string& upper)
|
||||
{
|
||||
const auto id = field_id(field);
|
||||
if (id == MU_MSG_FIELD_ID_NONE)
|
||||
const auto id_opt{message_field_id(field_str)};
|
||||
if (!id_opt)
|
||||
return {lower, upper};
|
||||
|
||||
std::string l2 = lower;
|
||||
std::string u2 = upper;
|
||||
|
||||
if (id == MU_MSG_FIELD_ID_DATE) {
|
||||
if (*id_opt == Field::Id::Date) {
|
||||
l2 = Mu::date_to_time_t_string(lower, true);
|
||||
u2 = Mu::date_to_time_t_string(upper, false);
|
||||
} else if (id == MU_MSG_FIELD_ID_SIZE) {
|
||||
} else if (*id_opt == Field::Id::Size) {
|
||||
l2 = Mu::size_to_string(lower, true);
|
||||
u2 = Mu::size_to_string(upper, false);
|
||||
}
|
||||
|
@ -212,16 +190,17 @@ process_range(const std::string& field, const std::string& lower, const std::str
|
|||
}
|
||||
|
||||
std::vector<std::string>
|
||||
Parser::Private::process_regex(const std::string& field, const std::regex& rx) const
|
||||
Parser::Private::process_regex(const std::string& field_str,
|
||||
const std::regex& rx) const
|
||||
{
|
||||
const auto id = field_id(field);
|
||||
if (id == MU_MSG_FIELD_ID_NONE)
|
||||
const auto id_opt{message_field_id(field_str)};
|
||||
if (!id_opt)
|
||||
return {};
|
||||
|
||||
char pfx[] = {mu_msg_field_shortcut(id), '\0'};
|
||||
|
||||
const auto field{message_field(*id_opt)};
|
||||
const auto prefix{field.xapian_term()};
|
||||
std::vector<std::string> terms;
|
||||
store_.for_each_term(pfx, [&](auto&& str) {
|
||||
store_.for_each_term(prefix, [&](auto&& str) {
|
||||
if (std::regex_search(str.c_str() + 1, rx)) // avoid copy
|
||||
terms.emplace_back(str);
|
||||
return true;
|
||||
|
@ -244,9 +223,9 @@ empty()
|
|||
|
||||
Mu::Tree
|
||||
Parser::Private::value(const FieldInfoVec& fields,
|
||||
const std::string& v,
|
||||
size_t pos,
|
||||
WarningVec& warnings) const
|
||||
const std::string& v,
|
||||
size_t pos,
|
||||
WarningVec& warnings) const
|
||||
{
|
||||
auto val = utf8_flatten(v);
|
||||
|
||||
|
@ -256,30 +235,30 @@ Parser::Private::value(const FieldInfoVec& fields,
|
|||
if (fields.size() == 1) {
|
||||
const auto item = fields.front();
|
||||
return Tree({Node::Type::Value,
|
||||
std::make_unique<Value>(item.field,
|
||||
item.prefix,
|
||||
item.id,
|
||||
process_value(item.field, val),
|
||||
item.supports_phrase)});
|
||||
std::make_unique<Value>(item.field,
|
||||
item.prefix,
|
||||
item.id,
|
||||
process_value(item.field, val),
|
||||
item.supports_phrase)});
|
||||
}
|
||||
|
||||
// a 'multi-field' such as "recip:"
|
||||
Tree tree(Node{Node::Type::OpOr});
|
||||
for (const auto& item : fields)
|
||||
tree.add_child(Tree({Node::Type::Value,
|
||||
std::make_unique<Value>(item.field,
|
||||
item.prefix,
|
||||
item.id,
|
||||
process_value(item.field, val),
|
||||
item.supports_phrase)}));
|
||||
std::make_unique<Value>(item.field,
|
||||
item.prefix,
|
||||
item.id,
|
||||
process_value(item.field, val),
|
||||
item.supports_phrase)}));
|
||||
return tree;
|
||||
}
|
||||
|
||||
Mu::Tree
|
||||
Parser::Private::regex(const FieldInfoVec& fields,
|
||||
const std::string& v,
|
||||
size_t pos,
|
||||
WarningVec& warnings) const
|
||||
const std::string& v,
|
||||
size_t pos,
|
||||
WarningVec& warnings) const
|
||||
{
|
||||
if (v.length() < 2)
|
||||
throw BUG("expected regexp, got '%s'", v.c_str());
|
||||
|
@ -312,10 +291,10 @@ Parser::Private::regex(const FieldInfoVec& fields,
|
|||
|
||||
Mu::Tree
|
||||
Parser::Private::range(const FieldInfoVec& fields,
|
||||
const std::string& lower,
|
||||
const std::string& upper,
|
||||
size_t pos,
|
||||
WarningVec& warnings) const
|
||||
const std::string& lower,
|
||||
const std::string& upper,
|
||||
size_t pos,
|
||||
WarningVec& warnings) const
|
||||
{
|
||||
if (fields.empty())
|
||||
throw BUG("expected field");
|
||||
|
@ -329,11 +308,11 @@ Parser::Private::range(const FieldInfoVec& fields,
|
|||
prange = process_range(field.field, upper, lower);
|
||||
|
||||
return Tree({Node::Type::Range,
|
||||
std::make_unique<Range>(field.field,
|
||||
field.prefix,
|
||||
field.id,
|
||||
prange.lower,
|
||||
prange.upper)});
|
||||
std::make_unique<Range>(field.field,
|
||||
field.prefix,
|
||||
field.id,
|
||||
prange.lower,
|
||||
prange.upper)});
|
||||
}
|
||||
|
||||
Mu::Tree
|
||||
|
@ -370,10 +349,10 @@ Parser::Private::data(Mu::Tokens& tokens, WarningVec& warnings) const
|
|||
const auto dotdot = val.find("..");
|
||||
if (dotdot != std::string::npos)
|
||||
return range(fields,
|
||||
val.substr(0, dotdot),
|
||||
val.substr(dotdot + 2),
|
||||
token.pos,
|
||||
warnings);
|
||||
val.substr(0, dotdot),
|
||||
val.substr(dotdot + 2),
|
||||
token.pos,
|
||||
warnings);
|
||||
else if (is_range_field(fields.front().field)) {
|
||||
// range field without a range - treat as field:val..val
|
||||
return range(fields, val, val, token.pos, warnings);
|
||||
|
@ -511,7 +490,8 @@ Parser::Private::term_1(Mu::Tokens& tokens, WarningVec& warnings) const
|
|||
}
|
||||
}
|
||||
|
||||
Mu::Parser::Parser(const Store& store, Flags flags) : priv_{std::make_unique<Private>(store, flags)}
|
||||
Mu::Parser::Parser(const Store& store, Parser::Flags flags) :
|
||||
priv_{std::make_unique<Private>(store, flags)}
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include "utils/mu-option.hh"
|
||||
|
||||
using namespace Mu;
|
||||
using namespace Mu::Message;
|
||||
|
||||
// We use a MatchDecider to gather information about the matches, and decide
|
||||
// whether to include them in the results.
|
||||
|
@ -47,12 +48,12 @@ struct MatchDecider : public Xapian::MatchDecider {
|
|||
{
|
||||
QueryMatch qm{};
|
||||
|
||||
auto msgid{opt_string(doc, MU_MSG_FIELD_ID_MSGID)
|
||||
.value_or(*opt_string(doc, MU_MSG_FIELD_ID_PATH))};
|
||||
auto msgid{opt_string(doc, Field::Id::MessageId)
|
||||
.value_or(*opt_string(doc, Field::Id::Path))};
|
||||
if (!decider_info_.message_ids.emplace(std::move(msgid)).second)
|
||||
qm.flags |= QueryMatch::Flags::Duplicate;
|
||||
|
||||
const auto path{opt_string(doc, MU_MSG_FIELD_ID_PATH)};
|
||||
const auto path{opt_string(doc, Field::Id::Path)};
|
||||
if (!path || ::access(path->c_str(), R_OK) != 0)
|
||||
qm.flags |= QueryMatch::Flags::Unreadable;
|
||||
|
||||
|
@ -86,7 +87,7 @@ struct MatchDecider : public Xapian::MatchDecider {
|
|||
*/
|
||||
void gather_thread_ids(const Xapian::Document& doc) const
|
||||
{
|
||||
auto thread_id{opt_string(doc, MU_MSG_FIELD_ID_THREAD_ID)};
|
||||
auto thread_id{opt_string(doc, Field::Id::ThreadId)};
|
||||
if (thread_id)
|
||||
decider_info_.thread_ids.emplace(std::move(*thread_id));
|
||||
}
|
||||
|
@ -95,10 +96,10 @@ struct MatchDecider : public Xapian::MatchDecider {
|
|||
const QueryFlags qflags_;
|
||||
DeciderInfo& decider_info_;
|
||||
|
||||
private:
|
||||
Option<std::string> opt_string(const Xapian::Document& doc, MuMsgFieldId id) const noexcept
|
||||
{
|
||||
std::string val = xapian_try([&] { return doc.get_value(id); }, std::string{""});
|
||||
private:
|
||||
Option<std::string> opt_string(const Xapian::Document& doc, Field::Id id) const noexcept {
|
||||
const auto value_no{message_field(id).value_no()};
|
||||
std::string val = xapian_try([&] { return doc.get_value(value_no); }, std::string{""});
|
||||
if (val.empty())
|
||||
return Nothing;
|
||||
else
|
||||
|
|
|
@ -73,7 +73,7 @@ class QueryResultsIterator {
|
|||
|
||||
QueryResultsIterator(Xapian::MSetIterator it,
|
||||
size_t max_num,
|
||||
MuMsgFieldId sort_field,
|
||||
Message::Field::Id sort_field,
|
||||
MuMsgIterFlags flags,
|
||||
MatchInfo& minfo)
|
||||
: it_{it}, match_info_{minfo}
|
||||
|
@ -124,7 +124,7 @@ class QueryResultsIterator {
|
|||
std::string message_id() const
|
||||
{
|
||||
g_return_val_if_fail(it_ != Xapian::MSetIterator::end(), "");
|
||||
return document().get_value(MU_MSG_FIELD_ID_MSGID);
|
||||
return document().get_value(Field::Id::MessageId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -136,7 +136,7 @@ class QueryResultsIterator {
|
|||
std::string path() const
|
||||
{
|
||||
g_return_val_if_fail(it_ != Xapian::MSetIterator::end(), "");
|
||||
return document().get_value(MU_MSG_FIELD_ID_PATH);
|
||||
return document().get_value(Field::Id::Path);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -149,7 +149,7 @@ class QueryResultsIterator {
|
|||
std::vector<std::string> references() const
|
||||
{
|
||||
g_return_val_if_fail(it_ != Xapian::MSetIterator::end(), {});
|
||||
return split(document().get_value(MU_MSG_FIELD_ID_REFS), ",");
|
||||
return split(document().get_value(Field::Id::References), ",");
|
||||
}
|
||||
|
||||
private:
|
||||
|
|
|
@ -59,7 +59,7 @@ enum struct QueryFlags {
|
|||
Threading = 1 << 4, /**< calculate threading info */
|
||||
// internal
|
||||
Leader = 1 << 5, /**< This is the leader query (for internal use
|
||||
* only)*/
|
||||
* only)*/
|
||||
};
|
||||
MU_ENABLE_BITOPS(QueryFlags);
|
||||
|
||||
|
@ -219,7 +219,7 @@ public:
|
|||
*/
|
||||
Option<std::string> message_id() const noexcept
|
||||
{
|
||||
return opt_string(MU_MSG_FIELD_ID_MSGID);
|
||||
return opt_string(Message::Field::Id::MessageId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -230,7 +230,7 @@ public:
|
|||
*/
|
||||
Option<std::string> thread_id() const noexcept
|
||||
{
|
||||
return opt_string(MU_MSG_FIELD_ID_THREAD_ID);
|
||||
return opt_string(Message::Field::Id::ThreadId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -239,7 +239,7 @@ public:
|
|||
*
|
||||
* @return a filesystem path
|
||||
*/
|
||||
Option<std::string> path() const noexcept { return opt_string(MU_MSG_FIELD_ID_PATH); }
|
||||
Option<std::string> path() const noexcept { return opt_string(Message::Field::Id::Path); }
|
||||
|
||||
/**
|
||||
* Get the date for the document (message) the iterator is pointing at.
|
||||
|
@ -247,7 +247,7 @@ public:
|
|||
*
|
||||
* @return a filesystem path
|
||||
*/
|
||||
Option<std::string> date() const noexcept { return opt_string(MU_MSG_FIELD_ID_DATE); }
|
||||
Option<std::string> date() const noexcept { return opt_string(Message::Field::Id::Date); }
|
||||
|
||||
/**
|
||||
* Get the file-system path for the document (message) this iterator is
|
||||
|
@ -255,7 +255,7 @@ public:
|
|||
*
|
||||
* @return the subject
|
||||
*/
|
||||
Option<std::string> subject() const noexcept { return opt_string(MU_MSG_FIELD_ID_SUBJECT); }
|
||||
Option<std::string> subject() const noexcept { return opt_string(Message::Field::Id::Subject); }
|
||||
|
||||
/**
|
||||
* Get the references for the document (messages) this is iterator is
|
||||
|
@ -266,7 +266,7 @@ public:
|
|||
*/
|
||||
std::vector<std::string> references() const noexcept
|
||||
{
|
||||
return split(document().get_value(MU_MSG_FIELD_ID_REFS), ",");
|
||||
return split(opt_string(Message::Field::Id::References).value_or(""), ",");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -276,10 +276,11 @@ public:
|
|||
*
|
||||
* @return the value
|
||||
*/
|
||||
Option<std::string> opt_string(MuMsgFieldId id) const noexcept
|
||||
Option<std::string> opt_string(Message::Field::Id id) const noexcept
|
||||
{
|
||||
std::string empty;
|
||||
std::string val = xapian_try([&] { return document().get_value(id); }, empty);
|
||||
const auto value_no{message_field(id).value_no()};
|
||||
std::string val = xapian_try([&] {return document().get_value(value_no);}, empty);
|
||||
if (val.empty())
|
||||
return Nothing;
|
||||
else
|
||||
|
@ -314,14 +315,14 @@ public:
|
|||
return xapian_try(
|
||||
[&] {
|
||||
auto docp{reinterpret_cast<XapianDocument*>(
|
||||
new Xapian::Document(document()))};
|
||||
new Xapian::Document(document()))};
|
||||
GError* err{};
|
||||
g_clear_pointer(&msg_, mu_msg_unref);
|
||||
if (!(msg_ = mu_msg_new_from_doc(docp, &err))) {
|
||||
delete docp;
|
||||
g_warning("failed to crate message for %s: %s",
|
||||
path().value_or("<none>").c_str(),
|
||||
err ? err->message : "somethng went wrong");
|
||||
path().value_or("<none>").c_str(),
|
||||
err ? err->message : "somethng went wrong");
|
||||
g_clear_error(&err);
|
||||
}
|
||||
return msg_;
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
|
||||
#include "mu-query-threads.hh"
|
||||
#include "mu-msg-fields.h"
|
||||
#include "mu-message-fields.hh"
|
||||
|
||||
#include <set>
|
||||
#include <unordered_set>
|
||||
|
|
168
lib/mu-query.cc
168
lib/mu-query.cc
|
@ -29,72 +29,64 @@
|
|||
#include <xapian.h>
|
||||
#include <glib/gstdio.h>
|
||||
|
||||
#include "mu-msg-fields.h"
|
||||
#include "mu-query-results.hh"
|
||||
#include "mu-query-match-deciders.hh"
|
||||
#include "mu-query-threads.hh"
|
||||
#include <mu-xapian.hh>
|
||||
|
||||
using namespace Mu;
|
||||
using namespace Mu::Message;
|
||||
|
||||
struct Query::Private {
|
||||
Private(const Store& store)
|
||||
: store_{store}, parser_{store_} {}
|
||||
Private(const Store& store) : store_{store}, parser_{store_} {}
|
||||
// New
|
||||
// bool calculate_threads (Xapian::Enquire& enq, size maxnum);
|
||||
|
||||
Xapian::Enquire
|
||||
make_enquire(const std::string& expr, MuMsgFieldId sortfieldid, QueryFlags qflags) const;
|
||||
Xapian::Enquire make_related_enquire(const StringSet& thread_ids,
|
||||
MuMsgFieldId sortfieldid,
|
||||
QueryFlags qflags) const;
|
||||
Xapian::Enquire make_enquire(const std::string& expr,
|
||||
std::optional<Field::Id> sortfield_id,
|
||||
QueryFlags qflags) const;
|
||||
Xapian::Enquire make_related_enquire(const StringSet& thread_ids,
|
||||
std::optional<Field::Id> sortfield_id,
|
||||
QueryFlags qflags) const;
|
||||
|
||||
Option<QueryResults> run_threaded(QueryResults&& qres,
|
||||
Xapian::Enquire& enq,
|
||||
QueryFlags qflags,
|
||||
size_t max_size) const;
|
||||
Option<QueryResults> run_singular(const std::string& expr,
|
||||
MuMsgFieldId sortfieldid,
|
||||
QueryFlags qflags,
|
||||
size_t maxnum) const;
|
||||
Option<QueryResults> run_related(const std::string& expr,
|
||||
MuMsgFieldId sortfieldid,
|
||||
QueryFlags qflags,
|
||||
size_t maxnum) const;
|
||||
Option<QueryResults> run(const std::string& expr,
|
||||
MuMsgFieldId sortfieldid,
|
||||
QueryFlags qflags,
|
||||
size_t maxnum) const;
|
||||
Option<QueryResults> run_threaded(QueryResults&& qres, Xapian::Enquire& enq,
|
||||
QueryFlags qflags, size_t max_size) const;
|
||||
Option<QueryResults> run_singular(const std::string& expr,
|
||||
std::optional<Field::Id> sortfield_id,
|
||||
QueryFlags qflags, size_t maxnum) const;
|
||||
Option<QueryResults> run_related(const std::string& expr,
|
||||
std::optional<Field::Id> sortfield_id,
|
||||
QueryFlags qflags, size_t maxnum) const;
|
||||
|
||||
size_t store_size() const
|
||||
{
|
||||
return store_.database().get_doccount();
|
||||
}
|
||||
Option<QueryResults> run(const std::string& expr,
|
||||
std::optional<Field::Id> sortfield_id, QueryFlags qflags,
|
||||
size_t maxnum) const;
|
||||
|
||||
size_t store_size() const { return store_.database().get_doccount(); }
|
||||
|
||||
const Store& store_;
|
||||
const Parser parser_;
|
||||
};
|
||||
|
||||
Query::Query(const Store& store)
|
||||
: priv_{std::make_unique<Private>(store)} {}
|
||||
Query::Query(const Store& store) : priv_{std::make_unique<Private>(store)} {}
|
||||
|
||||
Query::Query(Query&& other) = default;
|
||||
|
||||
Query::~Query() = default;
|
||||
|
||||
static Xapian::Enquire&
|
||||
maybe_sort(Xapian::Enquire& enq, MuMsgFieldId sortfieldid, QueryFlags qflags)
|
||||
sort_enquire(Xapian::Enquire& enq, Field::Id sortfield_id, QueryFlags qflags)
|
||||
{
|
||||
if (sortfieldid != MU_MSG_FIELD_ID_NONE)
|
||||
enq.set_sort_by_value(static_cast<Xapian::valueno>(sortfieldid),
|
||||
any_of(qflags & QueryFlags::Descending));
|
||||
const auto value_no{message_field(sortfield_id).value_no()};
|
||||
enq.set_sort_by_value(value_no, any_of(qflags & QueryFlags::Descending));
|
||||
|
||||
return enq;
|
||||
}
|
||||
|
||||
Xapian::Enquire
|
||||
Query::Private::make_enquire(const std::string& expr,
|
||||
MuMsgFieldId sortfieldid,
|
||||
QueryFlags qflags) const
|
||||
Query::Private::make_enquire(const std::string& expr,
|
||||
std::optional<Field::Id> sortfield_id,
|
||||
QueryFlags qflags) const
|
||||
{
|
||||
Xapian::Enquire enq{store_.database()};
|
||||
|
||||
|
@ -109,29 +101,33 @@ Query::Private::make_enquire(const std::string& expr,
|
|||
g_debug("qtree: %s", to_string(tree).c_str());
|
||||
}
|
||||
|
||||
return maybe_sort(enq, sortfieldid, qflags);
|
||||
if (sortfield_id)
|
||||
sort_enquire(enq, *sortfield_id, qflags);
|
||||
|
||||
return enq;
|
||||
}
|
||||
|
||||
Xapian::Enquire
|
||||
Query::Private::make_related_enquire(const StringSet& thread_ids,
|
||||
MuMsgFieldId sortfieldid,
|
||||
QueryFlags qflags) const
|
||||
std::optional<Field::Id> sortfield_id,
|
||||
QueryFlags qflags) const
|
||||
{
|
||||
Xapian::Enquire enq{store_.database()};
|
||||
static std::string pfx(1, mu_msg_field_xapian_prefix(MU_MSG_FIELD_ID_THREAD_ID));
|
||||
|
||||
Xapian::Enquire enq{store_.database()};
|
||||
std::vector<Xapian::Query> qvec;
|
||||
for (auto&& t : thread_ids)
|
||||
qvec.emplace_back(pfx + t);
|
||||
qvec.emplace_back(message_field(Field::Id::ThreadId).xapian_term(t));
|
||||
|
||||
Xapian::Query qr{Xapian::Query::OP_OR, qvec.begin(), qvec.end()};
|
||||
enq.set_query(qr);
|
||||
|
||||
return maybe_sort(enq, sortfieldid, qflags);
|
||||
if (sortfield_id)
|
||||
sort_enquire(enq, *sortfield_id, qflags);
|
||||
|
||||
return enq;
|
||||
}
|
||||
|
||||
struct ThreadKeyMaker : public Xapian::KeyMaker {
|
||||
ThreadKeyMaker(const QueryMatches& matches)
|
||||
: match_info_(matches) {}
|
||||
ThreadKeyMaker(const QueryMatches& matches) : match_info_(matches) {}
|
||||
std::string operator()(const Xapian::Document& doc) const override
|
||||
{
|
||||
const auto it{match_info_.find(doc.get_docid())};
|
||||
|
@ -141,10 +137,8 @@ struct ThreadKeyMaker : public Xapian::KeyMaker {
|
|||
};
|
||||
|
||||
Option<QueryResults>
|
||||
Query::Private::run_threaded(QueryResults&& qres,
|
||||
Xapian::Enquire& enq,
|
||||
QueryFlags qflags,
|
||||
size_t maxnum) const
|
||||
Query::Private::run_threaded(QueryResults&& qres, Xapian::Enquire& enq, QueryFlags qflags,
|
||||
size_t maxnum) const
|
||||
{
|
||||
const auto descending{any_of(qflags & QueryFlags::Descending)};
|
||||
|
||||
|
@ -163,9 +157,8 @@ Query::Private::run_threaded(QueryResults&& qres,
|
|||
|
||||
Option<QueryResults>
|
||||
Query::Private::run_singular(const std::string& expr,
|
||||
MuMsgFieldId sortfieldid,
|
||||
QueryFlags qflags,
|
||||
size_t maxnum) const
|
||||
std::optional<Field::Id> sortfield_id,
|
||||
QueryFlags qflags, size_t maxnum) const
|
||||
{
|
||||
// i.e. a query _without_ related messages, but still possibly
|
||||
// with threading.
|
||||
|
@ -179,10 +172,11 @@ Query::Private::run_singular(const std::string& expr,
|
|||
DeciderInfo minfo{};
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wextra"
|
||||
auto enq{make_enquire(expr, threading ? MU_MSG_FIELD_ID_DATE : sortfieldid, qflags)};
|
||||
auto enq{make_enquire(expr, threading ? Field::Id::Date : sortfield_id, qflags)};
|
||||
#pragma GCC diagnostic ignored "-Wswitch-default"
|
||||
#pragma GCC diagnostic pop
|
||||
auto mset{enq.get_mset(0, maxnum, {}, make_leader_decider(singular_qflags, minfo).get())};
|
||||
auto mset{enq.get_mset(0, maxnum, {},
|
||||
make_leader_decider(singular_qflags, minfo).get())};
|
||||
mset.fetch();
|
||||
|
||||
auto qres{QueryResults{mset, std::move(minfo.matches)}};
|
||||
|
@ -191,9 +185,11 @@ Query::Private::run_singular(const std::string& expr,
|
|||
}
|
||||
|
||||
static Option<std::string>
|
||||
opt_string(const Xapian::Document& doc, MuMsgFieldId id) noexcept
|
||||
opt_string(const Xapian::Document& doc, Field::Id id) noexcept
|
||||
{
|
||||
std::string val = xapian_try([&] { return doc.get_value(id); }, std::string{""});
|
||||
const auto value_no{message_field(id).value_no()};
|
||||
std::string val =
|
||||
xapian_try([&] { return doc.get_value(value_no); }, std::string{""});
|
||||
if (val.empty())
|
||||
return Nothing;
|
||||
else
|
||||
|
@ -202,9 +198,8 @@ opt_string(const Xapian::Document& doc, MuMsgFieldId id) noexcept
|
|||
|
||||
Option<QueryResults>
|
||||
Query::Private::run_related(const std::string& expr,
|
||||
MuMsgFieldId sortfieldid,
|
||||
QueryFlags qflags,
|
||||
size_t maxnum) const
|
||||
std::optional<Field::Id> sortfield_id,
|
||||
QueryFlags qflags, size_t maxnum) const
|
||||
{
|
||||
// i.e. a query _with_ related messages and possibly with threading.
|
||||
//
|
||||
|
@ -218,14 +213,14 @@ Query::Private::run_related(const std::string& expr,
|
|||
|
||||
// Run our first, "leader" query
|
||||
DeciderInfo minfo{};
|
||||
auto enq{make_enquire(expr, MU_MSG_FIELD_ID_DATE, leader_qflags)};
|
||||
auto enq{make_enquire(expr, Field::Id::Date, leader_qflags)};
|
||||
const auto mset{
|
||||
enq.get_mset(0, maxnum, {}, make_leader_decider(leader_qflags, minfo).get())};
|
||||
enq.get_mset(0, maxnum, {}, make_leader_decider(leader_qflags, minfo).get())};
|
||||
|
||||
// Gather the thread-ids we found
|
||||
mset.fetch();
|
||||
for (auto it = mset.begin(); it != mset.end(); ++it) {
|
||||
auto thread_id{opt_string(it.get_document(), MU_MSG_FIELD_ID_THREAD_ID)};
|
||||
auto thread_id{opt_string(it.get_document(), Field::Id::ThreadId)};
|
||||
if (thread_id)
|
||||
minfo.thread_ids.emplace(std::move(*thread_id));
|
||||
}
|
||||
|
@ -235,28 +230,28 @@ Query::Private::run_related(const std::string& expr,
|
|||
// In the threaded-case, we search among _all_ messages, since complete
|
||||
// threads are preferred; no need to sort in that case since the search
|
||||
// is unlimited and the sorting happens during threading.
|
||||
auto r_enq{make_related_enquire(minfo.thread_ids,
|
||||
threading ? MU_MSG_FIELD_ID_NONE : sortfieldid,
|
||||
qflags)};
|
||||
const auto r_mset{r_enq.get_mset(0,
|
||||
threading ? store_size() : maxnum,
|
||||
{},
|
||||
make_related_decider(qflags, minfo).get())};
|
||||
auto r_enq = std::invoke([&]{
|
||||
if (threading)
|
||||
return make_related_enquire(minfo.thread_ids, std::nullopt, qflags);
|
||||
else
|
||||
return make_related_enquire(minfo.thread_ids, sortfield_id, qflags);
|
||||
});
|
||||
|
||||
const auto r_mset{r_enq.get_mset(0, threading ? store_size() : maxnum, {},
|
||||
make_related_decider(qflags, minfo).get())};
|
||||
auto qres{QueryResults{r_mset, std::move(minfo.matches)}};
|
||||
return threading ? run_threaded(std::move(qres), r_enq, qflags, maxnum) : qres;
|
||||
}
|
||||
|
||||
Option<QueryResults>
|
||||
Query::Private::run(const std::string& expr,
|
||||
MuMsgFieldId sortfieldid,
|
||||
QueryFlags qflags,
|
||||
size_t maxnum) const
|
||||
Query::Private::run(const std::string& expr,
|
||||
std::optional<Message::Field::Id> sortfield_id, QueryFlags qflags,
|
||||
size_t maxnum) const
|
||||
{
|
||||
const auto eff_maxnum{maxnum == 0 ? store_size() : maxnum};
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wextra"
|
||||
const auto eff_sortfield{sortfieldid == MU_MSG_FIELD_ID_NONE ? MU_MSG_FIELD_ID_DATE
|
||||
: sortfieldid};
|
||||
const auto eff_sortfield{sortfield_id.value_or(Field::Id::Date)};
|
||||
#pragma GCC diagnostic pop
|
||||
if (any_of(qflags & QueryFlags::IncludeRelated))
|
||||
return run_related(expr, eff_sortfield, qflags, eff_maxnum);
|
||||
|
@ -265,21 +260,18 @@ Query::Private::run(const std::string& expr,
|
|||
}
|
||||
|
||||
Option<QueryResults>
|
||||
Query::run(const std::string& expr,
|
||||
MuMsgFieldId sortfieldid,
|
||||
QueryFlags qflags,
|
||||
size_t maxnum) const
|
||||
Query::run(const std::string& expr, std::optional<Message::Field::Id> sortfield_id,
|
||||
QueryFlags qflags, size_t maxnum) const
|
||||
try {
|
||||
// some flags are for internal use only.
|
||||
g_return_val_if_fail(none_of(qflags & QueryFlags::Leader), Nothing);
|
||||
|
||||
StopWatch sw{format("ran query '%s'; related: %s; threads: %s; max-size: %zu",
|
||||
expr.c_str(),
|
||||
any_of(qflags & QueryFlags::IncludeRelated) ? "yes" : "no",
|
||||
any_of(qflags & QueryFlags::Threading) ? "yes" : "no",
|
||||
maxnum)};
|
||||
StopWatch sw{format(
|
||||
"ran query '%s'; related: %s; threads: %s; max-size: %zu", expr.c_str(),
|
||||
any_of(qflags & QueryFlags::IncludeRelated) ? "yes" : "no",
|
||||
any_of(qflags & QueryFlags::Threading) ? "yes" : "no", maxnum)};
|
||||
|
||||
return priv_->run(expr, sortfieldid, qflags, maxnum);
|
||||
return priv_->run(expr, sortfield_id, qflags, maxnum);
|
||||
|
||||
} catch (...) {
|
||||
return Nothing;
|
||||
|
@ -290,7 +282,7 @@ Query::count(const std::string& expr) const
|
|||
{
|
||||
return xapian_try(
|
||||
[&] {
|
||||
const auto enq{priv_->make_enquire(expr, MU_MSG_FIELD_ID_NONE, {})};
|
||||
const auto enq{priv_->make_enquire(expr, {}, {})};
|
||||
auto mset{enq.get_mset(0, priv_->store_size())};
|
||||
mset.fetch();
|
||||
return mset.size();
|
||||
|
|
|
@ -21,11 +21,13 @@
|
|||
#define __MU_QUERY_HH__
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
#include <glib.h>
|
||||
#include <mu-store.hh>
|
||||
#include <mu-query-results.hh>
|
||||
#include <utils/mu-utils.hh>
|
||||
#include <mu-message.hh>
|
||||
|
||||
namespace Mu {
|
||||
|
||||
|
@ -41,10 +43,11 @@ public:
|
|||
*
|
||||
* @return the query-results, or Nothing in case of error.
|
||||
*/
|
||||
Option<QueryResults> run(const std::string& expr = "",
|
||||
MuMsgFieldId sortfieldid = MU_MSG_FIELD_ID_NONE,
|
||||
QueryFlags flags = QueryFlags::None,
|
||||
size_t maxnum = 0) const;
|
||||
|
||||
Option<QueryResults> run(const std::string& expr = "",
|
||||
std::optional<Message::Field::Id> sortfield_id = {},
|
||||
QueryFlags flags = QueryFlags::None,
|
||||
size_t maxnum = 0) const;
|
||||
|
||||
/**
|
||||
* run a Xapian query to count the number of matches; for the syntax, please
|
||||
|
|
Loading…
Reference in New Issue