Ticket #100: matchcmp.patch

File matchcmp.patch, 49.2 KB (added by Richard Boulton, 18 years ago)

Initial patch to implement custom sort orders

  • xapian-maintainer-tools/win32/win32_matcher.mak

     
    4242                 $(INTDIR)\multimatch.obj \
    4343                 $(INTDIR)\expand.obj \
    4444                 $(INTDIR)\stats.obj \
     45                 $(INTDIR)\matchcmp.obj \
    4546                 $(INTDIR)\mergepostlist.obj \
    4647                 $(INTDIR)\msetpostlist.obj \
    47                                 $(INTDIR)\msetcmp.obj
     48                $(INTDIR)\msetcmp.obj
    4849
    4950CLEAN :
    5051        -@erase "$(OUTDIR)\libmatcher.lib"
     
    175176<<
    176177
    177178
     179"$(INTDIR)\matchcmp.obj" : ".\matchcmp.cc"
     180       $(CPP) @<<
     181   $(CPP_PROJ) $**
     182<<
     183
    178184"$(INTDIR)\mergepostlist.obj" : ".\mergepostlist.cc"
    179185       $(CPP) @<<
    180186   $(CPP_PROJ) $**
  • xapian-core/matcher/matchcmp.cc

     
     1/* matchcmp.cc: C++ classes for comparing search results.
     2 *
     3 * Copyright 2006 Lemur Consulting Ltd
     4 *
     5 * This program is free software; you can redistribute it and/or
     6 * modify it under the terms of the GNU General Public License as
     7 * published by the Free Software Foundation; either version 2 of the
     8 * License, or (at your option) any later version.
     9 *
     10 * This program is distributed in the hope that it will be useful,
     11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13 * GNU General Public License for more details.
     14 *
     15 * You should have received a copy of the GNU General Public License
     16 * along with this program; if not, write to the Free Software
     17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
     18 * USA
     19 */
     20
     21#include <config.h>
     22#include <xapian/enquire.h>
     23#include "serialise.h"
     24#include "serialise-double.h"
     25#include "omdebug.h"
     26#include "omenquireinternal.h"
     27
     28namespace Xapian {
     29
     30weight
     31MatchItem::get_wt() const
     32{
     33    return item.wt;
     34}
     35
     36docid
     37MatchItem::get_docid() const
     38{
     39    return item.did;
     40}
     41
     42Document
     43MatchItem::get_document() const
     44{
     45    return Document(item.get_document().get());
     46}
     47
     48
     49
     50std::string IntegerMatchCmp::serialise() const {
     51    std::string result = encode_length(sort_key);
     52    result += (direction == 1) ? 'A' : 'D';
     53    return result;
     54}
     55
     56IntegerMatchCmp * IntegerMatchCmp::unserialise(const std::string & s) const {
     57    const char *p = s.data();
     58    const char *p_end = p + s.size();
     59    size_t sort_key_ = decode_length(&p, p_end);
     60    Assert(p + 1 == p_end);
     61    return new IntegerMatchCmp(sort_key_, p[0] == 'A' ? 1 : -1);
     62}
     63
     64int
     65IntegerMatchCmp::cmp(const MatchItem & a,
     66                     const MatchItem & b) const
     67{
     68    DEBUGCALL(MATCH, bool, "IntegerMatchCmp::operator()", "");
     69    std::string s = a.get_document().get_value(sort_key);
     70    const char *p = s.data();
     71    const char *p_end = p + s.size();
     72    size_t val_a = decode_length(&p, p_end);
     73
     74    s = b.get_document().get_value(sort_key);
     75    p = s.data();
     76    p_end = p + s.size();
     77    size_t val_b = decode_length(&p, p_end);
     78
     79    if (val_a > val_b)
     80        return direction;
     81    else if (val_a < val_b)
     82        return -direction;
     83    return 0;
     84}
     85
     86std::string
     87IntegerMatchCmp::int_to_value(int num)
     88{
     89    return encode_length(num);
     90}
     91
     92int
     93IntegerMatchCmp::value_to_int(const std::string &s)
     94{
     95    const char *p = s.data();
     96    const char *p_end = p + s.size();
     97    size_t result = decode_length(&p, p_end);
     98    return result;
     99}
     100
     101
     102
     103std::string DoubleMatchCmp::serialise() const {
     104    std::string result = encode_length(sort_key);
     105    result += (direction == 1) ? 'A' : 'D';
     106    return result;
     107}
     108
     109DoubleMatchCmp * DoubleMatchCmp::unserialise(const std::string & s) const {
     110    const char *p = s.data();
     111    const char *p_end = p + s.size();
     112    size_t sort_key_ = decode_length(&p, p_end);
     113    Assert(p + 1 == p_end);
     114    return new DoubleMatchCmp(sort_key_, p[0] == 'A' ? 1 : -1);
     115}
     116
     117int
     118DoubleMatchCmp::cmp(const MatchItem & a,
     119                    const MatchItem & b) const
     120{
     121    DEBUGCALL(MATCH, bool, "DoubleMatchCmp::operator()", "");
     122    std::string s = a.get_document().get_value(sort_key);
     123    const char *p = s.data();
     124    const char *p_end = p + s.size();
     125    double val_a = unserialise_double(&p, p_end);
     126
     127    s = b.get_document().get_value(sort_key);
     128    p = s.data();
     129    p_end = p + s.size();
     130    double val_b = unserialise_double(&p, p_end);
     131
     132    if (val_a > val_b)
     133        return direction;
     134    else if (val_a < val_b)
     135        return -direction;
     136    return 0;
     137}
     138
     139std::string
     140DoubleMatchCmp::double_to_value(double num)
     141{
     142    return serialise_double(num);
     143}
     144
     145double
     146DoubleMatchCmp::value_to_double(const std::string &s)
     147{
     148    const char *p = s.data();
     149    const char *p_end = p + s.size();
     150    double result = unserialise_double(&p, p_end);
     151    return result;
     152}
     153
     154}
  • xapian-core/matcher/multimatch.cc

     
    7878                       Xapian::weight bias_weight_,
    7979                       Xapian::ErrorHandler * errorhandler_,
    8080                       StatsGatherer * gatherer_,
    81                        const Xapian::Weight * weight_)
     81                       const Xapian::Weight * weight_,
     82                       const Xapian::MatchCmp * match_cmp_)
    8283        : gatherer(gatherer_), db(db_), query(query_),
    8384          collapse_key(collapse_key_), percent_cutoff(percent_cutoff_),
    8485          weight_cutoff(weight_cutoff_), order(order_),
    8586          sort_key(sort_key_), sort_by(sort_by_),
    8687          sort_value_forward(sort_value_forward_),
    8788          bias_halflife(bias_halflife_), bias_weight(bias_weight_),
    88           errorhandler(errorhandler_), weight(weight_)
     89          errorhandler(errorhandler_), weight(weight_), match_cmp(match_cmp_)
    8990{
    9091    DEBUGCALL(MATCH, void, "MultiMatch", db_ << ", " << query_ << ", " <<
    9192              qlen << ", " <<
     
    138139                }
    139140                rem_db->set_query(query, qlen, collapse_key, order, sort_key,
    140141                                  sort_by, sort_value_forward, percent_cutoff,
    141                                   weight_cutoff, weight, *subrset);
     142                                  weight_cutoff, weight, match_cmp, *subrset);
    142143                smatch = new RemoteSubMatch(rem_db, gatherer.get(),
    143144                                            sort_key != Xapian::valueno(-1));
    144145            } else {
     
    192193}
    193194
    194195string
    195 MultiMatch::get_collapse_key(PostList *pl, Xapian::docid did,
    196                              Xapian::valueno keyno, Xapian::Internal::RefCntPtr<Xapian::Document::Internal> &doc)
     196MultiMatch::get_collapse_key(PostList *pl,
     197                             Xapian::valueno keyno,
     198                             const Xapian::Internal::MSetItem & item)
    197199{
    198     DEBUGCALL(MATCH, string, "MultiMatch::get_collapse_key", pl << ", " << did << ", " << keyno << ", [doc]");
     200    DEBUGCALL(MATCH, string, "MultiMatch::get_collapse_key", pl << ", " << item.did << ", " << keyno << ", [doc]");
     201
     202    // FIXME - this gets the collapse key from the postlist.  This only
     203    // actually happens for Mset postlists (or mergepostlists containing an
     204    // mset postlist), since this is the only case where the collapse key has
     205    // been read already.  Instead, we should use the document object stored in
     206    // the mset item.
     207
    199208    const string *key = pl->get_collapse_key();
    200209    if (key) RETURN(*key);
    201     if (doc.get() == 0) {
    202         unsigned int multiplier = db.internal.size();
    203         Assert(multiplier != 0);
    204         Xapian::doccount n = (did - 1) % multiplier; // which actual database
    205         Xapian::docid m = (did - 1) / multiplier + 1; // real docid in that database
    206 
    207         Xapian::Internal::RefCntPtr<Xapian::Document::Internal> temp(db.internal[n]->open_document(m, true));
    208         doc = temp;
    209     }
    210     RETURN(doc->get_value(keyno));
     210    RETURN(item.get_document()->get_value(keyno));
    211211}
    212212
    213213Xapian::weight
     
    230230void
    231231MultiMatch::get_mset(Xapian::doccount first, Xapian::doccount maxitems,
    232232                     Xapian::doccount check_at_least,
    233                      Xapian::MSet & mset, const Xapian::MatchDecider *mdecider)
     233                     Xapian::MSet & mset,
     234                     const Xapian::MatchDecider *mdecider)
    234235{
    235236    DEBUGCALL(MATCH, void, "MultiMatch::get_mset", first << ", " << maxitems
    236237              << ", " << check_at_least << ", ...");
     
    368369
    369370    /// Comparison functor for sorting MSet
    370371    bool sort_forward = (order != Xapian::Enquire::DESCENDING);
    371     MSetCmp mcmp(get_msetcmp_function(sort_by, sort_forward, sort_value_forward));
     372    mset_cmp cmpfn = get_msetcmp_function(sort_by, sort_forward, sort_value_forward);
     373    MSetCmp mcmp(cmpfn, sort_key, match_cmp);
    372374
     375    /// Number of databases making up the database we're using.
     376    const unsigned int db_internal_size = db.internal.size();
     377    Assert(db_internal_size != 0);
     378
    373379    // Perform query
    374380
    375381    // We form the mset in two stages.  In the first we fill up our working
     
    416422        if (min_item.wt > 0.0) wt = pl->get_weight();
    417423
    418424        DEBUGLINE(MATCH, "Candidate document id " << did << " wt " << wt);
    419         Xapian::Internal::MSetItem new_item(wt, did);
    420         if (sort_key != Xapian::valueno(-1)) {
    421             const unsigned int multiplier = db.internal.size();
    422             Assert(multiplier != 0);
    423             Xapian::doccount n = (new_item.did - 1) % multiplier; // which actual database
    424             Xapian::docid m = (new_item.did - 1) / multiplier + 1; // real docid in that database
    425             Xapian::Internal::RefCntPtr<Xapian::Document::Internal> doc(db.internal[n]->open_document(m, true));
    426             new_item.sort_key = doc->get_value(sort_key);
    427         }
    428425
     426        Xapian::Internal::RefCntPtr<Xapian::Database::Internal> internal_db = db.internal[((did - 1) % db_internal_size)]; // database holding document
     427        Xapian::docid internal_docid = (did - 1) / db_internal_size + 1; // docid in internal_db
     428        Xapian::Internal::MSetItem new_item(wt, did, internal_db, internal_docid);
     429
    429430        // Test if item has high enough weight (or sort key) to get into
    430431        // proto-mset.
    431432        if (sort_key != Xapian::valueno(-1) || min_item.wt > 0.0)
    432433            if (!mcmp(new_item, min_item)) continue;
    433434
    434         Xapian::Internal::RefCntPtr<Xapian::Document::Internal> doc;
    435 
    436435        // Use the decision functor if any.
    437436        // FIXME: if results are from MSetPostList then we can omit this step
    438437        if (mdecider != NULL) {
    439             if (doc.get() == 0) {
    440                 const unsigned int multiplier = db.internal.size();
    441                 Assert(multiplier != 0);
    442                 Xapian::doccount n = (did - 1) % multiplier; // which actual database
    443                 Xapian::docid m = (did - 1) / multiplier + 1; // real docid in that database
    444 
    445                 Xapian::Internal::RefCntPtr<Xapian::Document::Internal> temp(db.internal[n]->open_document(m, true));
    446                 doc = temp;
    447             }
    448             Xapian::Document mydoc(doc.get());
     438            Xapian::Document mydoc(new_item.get_document().get());
    449439            if (!mdecider->operator()(mydoc)) continue;
    450440        }
    451441
     
    460450
    461451        // Perform collapsing on key if requested.
    462452        if (collapse_key != Xapian::valueno(-1)) {
    463             new_item.collapse_key = get_collapse_key(pl, did, collapse_key,
    464                                                      doc);
     453            new_item.collapse_key = get_collapse_key(pl, collapse_key, new_item);
    465454
    466455            // Don't collapse on null key
    467456            if (!new_item.collapse_key.empty()) {
  • xapian-core/matcher/msetcmp.h

     
    2020
    2121#include "omenquireinternal.h"
    2222
     23class MSetCmp;
     24
    2325// typedef for MSetItem comparison function.
    2426typedef bool (* mset_cmp)(const Xapian::Internal::MSetItem &,
    25                           const Xapian::Internal::MSetItem &);
     27                          const Xapian::Internal::MSetItem &,
     28                          const MSetCmp *);
    2629
    2730/// Select the appropriate msetcmp function.
    2831mset_cmp get_msetcmp_function(Xapian::Enquire::Internal::sort_setting sort_by, bool sort_forward, bool sort_value_forward);
     
    3134class MSetCmp {
    3235    mset_cmp fn;
    3336  public:
    34     MSetCmp(mset_cmp fn_) : fn(fn_) { }
     37    Xapian::valueno sort_key;
     38    const Xapian::MatchCmp * cmp;
     39
     40    /// Construct an instance of MSetCmp using a built-in comparison function.
     41    MSetCmp(mset_cmp fn_,
     42            Xapian::valueno sort_key_,
     43            const Xapian::MatchCmp * cmp_)
     44        : fn(fn_), sort_key(sort_key_), cmp(cmp_) { }
     45
    3546    /// Return true if MSetItem a should be ranked above MSetItem b.
    3647    bool operator()(const Xapian::Internal::MSetItem &a,
    37                     const Xapian::Internal::MSetItem &b) const {
    38         return fn(a, b);
     48                    const Xapian::Internal::MSetItem &b) const {
     49        return fn(a, b, this);
    3950    }
    4051};
     52
  • xapian-core/matcher/msetcmp.cc

     
    2020
    2121#include <config.h>
    2222#include "msetcmp.h"
     23#include <string>
    2324
    2425/* We use templates to generate the 14 different comparison functions
    2526 * which we need.  This avoids having to write them all out by hand.
     
    4647// Order by relevance, then docid.
    4748template<bool FORWARD_DID> bool
    4849msetcmp_by_relevance(const Xapian::Internal::MSetItem &a,
    49                      const Xapian::Internal::MSetItem &b)
     50                     const Xapian::Internal::MSetItem &b,
     51                     const MSetCmp *)
    5052{
    5153    if (a.wt > b.wt) return true;
    5254    if (a.wt < b.wt) return false;
     
    5658// Order by value, then docid.
    5759template<bool FORWARD_VALUE, bool FORWARD_DID> bool
    5860msetcmp_by_value(const Xapian::Internal::MSetItem &a,
    59                  const Xapian::Internal::MSetItem &b)
     61                 const Xapian::Internal::MSetItem &b,
     62                 const MSetCmp * cmp)
    6063{
    6164    if (!FORWARD_VALUE) {
    6265        // We want dummy did 0 to compare worse than any other.
    6366        if (a.did == 0) return false;
    6467        if (b.did == 0) return true;
    6568    }
    66     if (a.sort_key > b.sort_key) return FORWARD_VALUE;
    67     if (a.sort_key < b.sort_key) return !FORWARD_VALUE;
     69    std::string sort_key_a = a.get_document()->get_value(cmp->sort_key);
     70    std::string sort_key_b = b.get_document()->get_value(cmp->sort_key);
     71    if (sort_key_a > sort_key_b) return FORWARD_VALUE;
     72    if (sort_key_a < sort_key_b) return !FORWARD_VALUE;
    6873    return msetcmp_by_did<FORWARD_DID, FORWARD_VALUE>(a, b);
    6974}
    7075
    7176// Order by value, then relevance, then docid.
    7277template<bool FORWARD_VALUE, bool FORWARD_DID> bool
    7378msetcmp_by_value_then_relevance(const Xapian::Internal::MSetItem &a,
    74                                 const Xapian::Internal::MSetItem &b)
     79                                const Xapian::Internal::MSetItem &b,
     80                                const MSetCmp * cmp)
    7581{
    7682    if (!FORWARD_VALUE) {
    7783        // two special cases to make min_item compares work when did == 0
    7884        if (a.did == 0) return false;
    7985        if (b.did == 0) return true;
    8086    }
    81     if (a.sort_key > b.sort_key) return FORWARD_VALUE;
    82     if (a.sort_key < b.sort_key) return !FORWARD_VALUE;
     87    std::string sort_key_a = a.get_document()->get_value(cmp->sort_key);
     88    std::string sort_key_b = b.get_document()->get_value(cmp->sort_key);
     89    if (sort_key_a > sort_key_b) return FORWARD_VALUE;
     90    if (sort_key_a < sort_key_b) return !FORWARD_VALUE;
    8391    if (a.wt > b.wt) return true;
    8492    if (a.wt < b.wt) return false;
    8593    return msetcmp_by_did<FORWARD_DID, FORWARD_VALUE>(a, b);
     
    8896// Order by relevance, then value, then docid.
    8997template<bool FORWARD_VALUE, bool FORWARD_DID> bool
    9098msetcmp_by_relevance_then_value(const Xapian::Internal::MSetItem &a,
    91                                 const Xapian::Internal::MSetItem &b)
     99                                const Xapian::Internal::MSetItem &b,
     100                                const MSetCmp * cmp)
    92101{
    93102    if (!FORWARD_VALUE) {
    94103        // two special cases to make min_item compares work when did == 0
     
    97106    }
    98107    if (a.wt > b.wt) return true;
    99108    if (a.wt < b.wt) return false;
    100     if (a.sort_key > b.sort_key) return FORWARD_VALUE;
    101     if (a.sort_key < b.sort_key) return !FORWARD_VALUE;
     109    std::string sort_key_a = a.get_document()->get_value(cmp->sort_key);
     110    std::string sort_key_b = b.get_document()->get_value(cmp->sort_key);
     111    if (sort_key_a > sort_key_b) return FORWARD_VALUE;
     112    if (sort_key_a < sort_key_b) return !FORWARD_VALUE;
    102113    return msetcmp_by_did<FORWARD_DID, FORWARD_VALUE>(a, b);
    103114}
    104115
     116template<bool FORWARD_DID> bool
     117msetcmp_by_user_cmpfn(const Xapian::Internal::MSetItem &a,
     118                      const Xapian::Internal::MSetItem &b,
     119                      const MSetCmp * cmp)
     120{
     121    // We want dummy did 0 to compare worse than any other.
     122    if (a.did == 0) return false;
     123    if (b.did == 0) return true;
     124
     125    if (a.did == b.did) return false; // FIXME - can this ever happen?  If not, convert this to Assert(a.did != b.did).
     126    Xapian::MatchItem item_a(a);
     127    Xapian::MatchItem item_b(b);
     128    int c = (cmp->cmp->cmp)(item_a, item_b);
     129    if (c < 0)
     130        return true;
     131    else if (c > 0)
     132        return false;
     133    if (FORWARD_DID) {
     134        return (a.did < b.did);
     135    } else {
     136        return (a.did > b.did);
     137    }
     138}
     139
    105140static mset_cmp mset_cmp_table[] = {
    106141    // Xapian::Enquire::Internal::REL
    107142    msetcmp_by_relevance<false>,
     
    122157    msetcmp_by_relevance_then_value<true, true>,
    123158    msetcmp_by_relevance_then_value<false, true>,
    124159    msetcmp_by_relevance_then_value<true, false>,
    125     msetcmp_by_relevance_then_value<false, false>
     160    msetcmp_by_relevance_then_value<false, false>,
     161    // Xapian::Enquire::Internal::USER
     162    msetcmp_by_user_cmpfn<false>,
     163    msetcmp_by_user_cmpfn<false>,
     164    msetcmp_by_user_cmpfn<true>,
     165    msetcmp_by_user_cmpfn<true>
    126166};
    127167
    128168mset_cmp get_msetcmp_function(Xapian::Enquire::Internal::sort_setting sort_by, bool sort_forward, bool sort_value_forward) {
  • xapian-core/matcher/Makefile.am

     
    2020 andmaybepostlist.cc xorpostlist.cc phrasepostlist.cc selectpostlist.cc \
    2121 filterpostlist.cc ortermlist.cc expandweight.cc rset.cc \
    2222 bm25weight.cc tradweight.cc \
    23  localmatch.cc multimatch.cc expand.cc stats.cc \
     23 localmatch.cc matchcmp.cc multimatch.cc expand.cc stats.cc \
    2424 mergepostlist.cc msetpostlist.cc msetcmp.cc emptysubmatch.cc \
    2525 $(SOURCEFORREMOTE)
  • xapian-core/tests/internaltest.cc

     
    239239        255.5,
    240240        256.125,
    241241        257.03125,
     242        0.12268031290495594321l,
     243        0.12268031290495592933l,
    242244    };
    243245
    244246    check_double_serialisation(0.0);
  • xapian-core/tests/api_db.cc

     
    11471147// set_sort_by_value
    11481148// set_sort_by_value_then_relevance
    11491149// set_sort_by_relevance_then_value
     1150// User comparison functions
    11501151static bool test_sortrel1()
    11511152{
    11521153    Xapian::Enquire enquire(get_database("apitest_sortrel"));
     
    11621163    const Xapian::docid order7[] = { 7,9,8,6,5,4,2,1,3 };
    11631164    const Xapian::docid order8[] = { 7,6,2,9,5,1,8,4,3 };
    11641165    const Xapian::docid order9[] = { 2,6,7,1,5,9,3,4,8 };
     1166    const Xapian::docid order10[] = { 7,9,4,5,6,1,2,3,8 };
     1167    const Xapian::docid order11[] = { 8,1,2,3,4,5,6,7,9 };
     1168    const Xapian::docid order12[] = { 9,7,6,5,4,3,2,1,8 };
     1169    const Xapian::docid order13[] = { 8,3,2,1,6,5,4,9,7 };
    11651170
    11661171    Xapian::MSet mset;
    11671172    size_t i;
     
    11721177        TEST_EQUAL(*mset[i], order1[i]);
    11731178    }
    11741179
     1180    enquire.set_sort_by_cmpfn(Xapian::IntegerMatchCmp(100));
     1181
     1182    mset = enquire.get_mset(0, 10);
     1183    TEST_EQUAL(mset.size(), sizeof(order10) / sizeof(Xapian::docid));
     1184    for (i = 0; i < sizeof(order10) / sizeof(Xapian::docid); ++i) {
     1185        TEST_EQUAL(*mset[i], order10[i]);
     1186    }
     1187
     1188    enquire.set_sort_by_cmpfn(Xapian::IntegerMatchCmp(100, true));
     1189
     1190    mset = enquire.get_mset(0, 10);
     1191    TEST_EQUAL(mset.size(), sizeof(order10) / sizeof(Xapian::docid));
     1192    for (i = 0; i < sizeof(order10) / sizeof(Xapian::docid); ++i) {
     1193        TEST_EQUAL(*mset[i], order10[i]);
     1194    }
     1195
     1196    enquire.set_sort_by_cmpfn(Xapian::IntegerMatchCmp(100, false));
     1197
     1198    mset = enquire.get_mset(0, 10);
     1199    TEST_EQUAL(mset.size(), sizeof(order11) / sizeof(Xapian::docid));
     1200    for (i = 0; i < sizeof(order11) / sizeof(Xapian::docid); ++i) {
     1201        TEST_EQUAL(*mset[i], order11[i]);
     1202    }
     1203
    11751204    enquire.set_sort_by_value_then_relevance(1);
    11761205
    11771206    mset = enquire.get_mset(0, 10);
     
    11881217        TEST_EQUAL(*mset[i], order1[i]);
    11891218    }
    11901219
     1220    enquire.set_sort_by_cmpfn(Xapian::IntegerMatchCmp(100));
     1221    enquire.set_docid_order(Xapian::Enquire::DESCENDING);
     1222
     1223    mset = enquire.get_mset(0, 10);
     1224    TEST_EQUAL(mset.size(), sizeof(order12) / sizeof(Xapian::docid));
     1225    for (i = 0; i < sizeof(order12) / sizeof(Xapian::docid); ++i) {
     1226        TEST_EQUAL(*mset[i], order12[i]);
     1227    }
     1228
     1229    enquire.set_sort_by_cmpfn(Xapian::IntegerMatchCmp(100, true));
     1230    enquire.set_docid_order(Xapian::Enquire::DESCENDING);
     1231
     1232    mset = enquire.get_mset(0, 10);
     1233    TEST_EQUAL(mset.size(), sizeof(order12) / sizeof(Xapian::docid));
     1234    for (i = 0; i < sizeof(order12) / sizeof(Xapian::docid); ++i) {
     1235        TEST_EQUAL(*mset[i], order12[i]);
     1236    }
     1237
     1238    enquire.set_sort_by_cmpfn(Xapian::IntegerMatchCmp(100, false));
     1239    enquire.set_docid_order(Xapian::Enquire::DESCENDING);
     1240
     1241    mset = enquire.get_mset(0, 10);
     1242    TEST_EQUAL(mset.size(), sizeof(order13) / sizeof(Xapian::docid));
     1243    for (i = 0; i < sizeof(order13) / sizeof(Xapian::docid); ++i) {
     1244        TEST_EQUAL(*mset[i], order13[i]);
     1245    }
     1246
    11911247    enquire.set_sort_by_value_then_relevance(1);
    11921248    enquire.set_docid_order(Xapian::Enquire::DESCENDING);
    11931249
  • xapian-core/tests/testdata/apitest_sortrel.txt

     
    1212
    1313V1 woman
    1414
    15 V1 man woman fish
     15V10 man woman fish
    1616
    1717V1 man woman
  • xapian-core/include/xapian/enquire.h

     
    3131#include <xapian/error.h>
    3232#include <xapian/types.h>
    3333#include <xapian/termiterator.h>
     34#include <xapian/document.h>
    3435
    3536namespace Xapian {
    3637
     
    4041class MSetIterator;
    4142class Query;
    4243class Weight;
     44class MatchCmp;
    4345
     46namespace Internal {
     47    class MSetItem;
     48}
     49
    4450/** A match set (MSet).
    4551 *  This class represents (a portion of) the results of a query.
    4652 */
     
    839845        void set_sort_by_relevance_then_value(Xapian::valueno sort_key,
    840846                                              bool ascending = true);
    841847
     848        /** Set a comparison functor to be used when comparing matches.
     849         *
     850         *  This overrides the sort options set by set_sort_by_relevance(),
     851         *  set_sort_by_value(), set_sort_by_relevance_then_value() and
     852         *  set_sort_by_value_then_relevance().  For matches which the
     853         *  comparison functor considers equal (ie, for which the functor
     854         *  returns 0 when comparing them), the matches will be compared in
     855         *  document ID order, as set by set_docid_order();
     856         *
     857         *  The comparison function will be called many times in the process of
     858         *  performing the search.  Therefore, it is important that it is
     859         *  implemented as efficiently as possible.
     860         *
     861         *  @param cmpfn_ The comparion function to use to compare matches.
     862         */
     863        void set_sort_by_cmpfn(const MatchCmp &cmpfn_);
     864
    842865        /** Set the bias functor parameters.
    843866         *
    844867         * NB this is a temporary API for this feature.
     
    12671290        bool get_sumpart_needs_doclength() const;
    12681291};
    12691292
     1293/// An item matching a search.
     1294class MatchItem {
     1295    private:
     1296        const Xapian::Internal::MSetItem & item;
     1297
     1298    public:
     1299        MatchItem(const Xapian::Internal::MSetItem & item_)
     1300            : item(item_) {}
     1301
     1302        /** Get the weight calculated. */
     1303        Xapian::weight get_wt() const;
     1304
     1305        /** Get the document id. */
     1306        Xapian::docid get_docid() const;
     1307
     1308        /** Get the document object, for access to document data, values,
     1309         *  and terms. */
     1310        Xapian::Document get_document() const;
     1311};
     1312
     1313/// Abstract base class for match comparison functors.
     1314class MatchCmp {
     1315    friend class Enquire; // So Enquire can clone us
     1316    friend class ::RemoteServer; // So RemoteServer can clone us - FIXME
     1317    protected:
     1318        MatchCmp(const MatchCmp &);
     1319    private:
     1320        void operator=(MatchCmp &);
     1321
     1322        /** Return a new MatchCmp object of this type.
     1323         *
     1324         * A subclass called FooMatchCmp taking parameters param1 and param2
     1325         * should implement this as:
     1326         *
     1327         * virtual FooMatchCmp * clone() const {
     1328         *     return new FooMatchCmp(param1, param2);
     1329         * }
     1330         */
     1331        virtual MatchCmp * clone() const = 0;
     1332
     1333    public:
     1334        MatchCmp() { }
     1335        virtual ~MatchCmp() { }
     1336
     1337        /** Name of the comparison functor.
     1338         *
     1339         *  If the subclass is called FooMatchCmp, this should return "Foo".
     1340         */
     1341        virtual std::string name() const = 0;
     1342
     1343        /// Serialise object parameters into a string.
     1344        virtual std::string serialise() const = 0;
     1345
     1346        /** Create object given string serialisation returned by serialise().
     1347         *
     1348         *  If the string is not a valid serialised form, this should raise a
     1349         *  Xapian::NetworkError;
     1350         */
     1351        virtual MatchCmp * unserialise(const std::string &s) const = 0;
     1352
     1353        /** Compare a potential Mset entry a to an entry b.
     1354         *
     1355         *  This function should return an integer less than 0 if MatchItem a
     1356         *  should be ranked below MatchItem b, and greater than 0 if MatchItem
     1357         *  a should be ranked above MatchItem b.  The function may return 0 if
     1358         *  it considers the two match items to be equal.
     1359         *
     1360         *  The function will never be called with items which have identical
     1361         *  document IDs.
     1362         *
     1363         *  The comparison function must be well behaved, such that:
     1364         *
     1365         *   - it must produce the same result if called a second time on a
     1366         *     particular pair of documents
     1367         *   - if cmp(x,y) is less than 0 and cmp(y,z) is less than 0, cmp(x,z)
     1368         *     must be less than 0.
     1369         *   - if cmp(x,y) is less than 0, cmp(y,x) must be greater than 0.
     1370         *
     1371         *  Bear in mind that the comparison will be performed very frequently
     1372         *  when performing a search, so must be as fast as possible.  In
     1373         *  particular, although the MatchItem provides access to the document
     1374         *  object, it is recommended that only the value items stored in the
     1375         *  document object are accessed.
     1376         */
     1377        virtual int cmp(const MatchItem & a,
     1378                        const MatchItem & b) const = 0;
     1379};
     1380
     1381/** Comparison function for comparing by value as integer.
     1382 */
     1383class IntegerMatchCmp : public MatchCmp {
     1384        Xapian::valueno sort_key;
     1385        int direction;
     1386    public:
     1387        /** Return a new IntegerMatchCmp with the same parameters as this one.
     1388         */
     1389        IntegerMatchCmp * clone() const {
     1390            return new IntegerMatchCmp(sort_key, direction == 1);
     1391        }
     1392        IntegerMatchCmp(Xapian::valueno sort_key_,
     1393                        bool ascending = true)
     1394                : sort_key(sort_key_),
     1395                  direction(ascending ? 1 : -1) { }
     1396        IntegerMatchCmp()
     1397                : sort_key(0),
     1398                  direction(1) { }
     1399        ~IntegerMatchCmp() { }
     1400        std::string name() const { return "Integer"; }
     1401
     1402        std::string serialise() const;
     1403        IntegerMatchCmp * unserialise(const std::string &s) const;
     1404
     1405        int cmp(const MatchItem & a,
     1406                const MatchItem & b) const;
     1407
     1408        /** Convert an integer to a string, suitable for comparing with this
     1409         *  comparison functor.
     1410         */
     1411        static std::string int_to_value(int num);
     1412
     1413        /** Convert a string suitable for comparison with this comparison
     1414         *  functor to an integer.
     1415         */
     1416        static int value_to_int(const std::string &s);
     1417};
     1418
     1419/** Comparison function for comparing by value as a double.
     1420 */
     1421class DoubleMatchCmp : public MatchCmp {
     1422        Xapian::valueno sort_key;
     1423        int direction;
     1424    public:
     1425        /** Return a new DoubleMatchCmp with the same parameters as this one.
     1426         */
     1427        DoubleMatchCmp * clone() const {
     1428            return new DoubleMatchCmp(sort_key, direction == 1);
     1429        }
     1430        DoubleMatchCmp(Xapian::valueno sort_key_,
     1431                       bool ascending = true)
     1432                : sort_key(sort_key_),
     1433                  direction(ascending ? 1 : -1) { }
     1434        DoubleMatchCmp()
     1435                : sort_key(0),
     1436                  direction(1) { }
     1437        ~DoubleMatchCmp() { }
     1438        std::string name() const { return "Double"; }
     1439
     1440        std::string serialise() const;
     1441        DoubleMatchCmp * unserialise(const std::string &s) const;
     1442
     1443        int cmp(const MatchItem & a,
     1444                const MatchItem & b) const;
     1445
     1446        /** Convert a double to a string, suitable for comparing with this
     1447         *  comparison functor.
     1448         */
     1449        static std::string double_to_value(double num);
     1450
     1451        /** Convert a string suitable for comparison with this comparison
     1452         *  functor to a double.
     1453         */
     1454        static double value_to_double(const std::string &s);
     1455};
     1456
    12701457}
    12711458
    12721459#endif /* XAPIAN_INCLUDED_ENQUIRE_H */
  • xapian-core/net/serialise.cc

     
    220220}
    221221
    222222Xapian::MSet
    223 unserialise_mset(const string &s)
     223unserialise_mset(const string &s,
     224                 Xapian::Internal::RefCntPtr<Xapian::Database::Internal> internal_db)
    224225{
    225226    const char * p = s.data();
    226227    const char * p_end = p + s.size();
     
    239240        size_t len = decode_length(&p, p_end);
    240241        string key(p, len);
    241242        p += len;
    242         items.push_back(Xapian::Internal::MSetItem(wt, did, key,
     243        items.push_back(Xapian::Internal::MSetItem(wt, did,
     244                                                   internal_db,
     245                                                   did,
     246                                                   key,
    243247                                                   decode_length(&p, p_end)));
    244248    }
    245249
  • xapian-core/net/remoteserver.cc

     
    8787    wtschemes[weight->name()] = weight;
    8888    weight = new Xapian::TradWeight();
    8989    wtschemes[weight->name()] = weight;
     90
     91    // Register match comparison functions.
     92    Xapian::MatchCmp * match_cmp;
     93    match_cmp = new Xapian::IntegerMatchCmp();
     94    match_cmps[match_cmp->name()] = match_cmp;
     95    match_cmp = new Xapian::DoubleMatchCmp();
     96    match_cmps[match_cmp->name()] = match_cmp;
    9097}
    9198
    9299RemoteServer::~RemoteServer()
     
    95102    for (i = wtschemes.begin(); i != wtschemes.end(); ++i) {
    96103        delete i->second;
    97104    }
     105    map<string, Xapian::MatchCmp*>::const_iterator j;
     106    for (j = match_cmps.begin(); j != match_cmps.end(); ++j) {
     107        delete j->second;
     108    }
    98109}
    99110
    100111message_type
     
    274285
    275286    Xapian::valueno sort_key = decode_length(&p, p_end);
    276287
    277     if (*p < '0' || *p > '3') {
     288    if (*p < '0' || *p > '4') {
    278289        throw Xapian::NetworkError("bad message (sort_by)");
    279290    }
    280291    Xapian::Enquire::Internal::sort_setting sort_by;
     
    308319    AutoPtr<Xapian::Weight> wt(i->second->unserialise(string(p, len)));
    309320    p += len;
    310321
     322    // Unserialise the MatchCmp object
     323    len = decode_length(&p, p_end);
     324    AutoPtr<Xapian::MatchCmp> mcmp;
     325    if (len > 0) {
     326        map<string, Xapian::MatchCmp *>::const_iterator j;
     327        j = match_cmps.find(string(p, len));
     328        if (j == match_cmps.end()) {
     329            throw Xapian::InvalidArgumentError("MatchCmp type " + string(p, len) + " not registered");
     330        }
     331        p += len;
     332
     333        len = decode_length(&p, p_end);
     334        mcmp = j->second->unserialise(string(p, len));
     335        p += len;
     336    }
     337
    311338    // Unserialise the RSet object.
    312339    Xapian::RSet rset = unserialise_rset(string(p, p_end - p));
    313340
     
    316343    MultiMatch match(*db, query.get(), qlen, rset, collapse_key,
    317344                     percent_cutoff, weight_cutoff, order,
    318345                     sort_key, sort_by, sort_value_forward,
    319                      0, 0, NULL, gatherer, wt.get());
     346                     0, 0, NULL, gatherer, wt.get(), mcmp.get());
    320347
    321348    send_message(REPLY_STATS, serialise_stats(gatherer->get_local_stats()));
    322349
  • xapian-core/common/remote-database.h

     
    124124     * @param percent_cutoff            Percentage cutoff.
    125125     * @param weight_cutoff             Weight cutoff.
    126126     * @param wtscheme                  Weighting scheme.
     127     * @param sort_cmpfn                Comparison functor for sorting.
    127128     * @param omrset                    The rset.
    128129     */
    129130    void set_query(const Xapian::Query::Internal *query,
     
    135136                   bool sort_value_forward,
    136137                   int percent_cutoff, Xapian::weight weight_cutoff,
    137138                   const Xapian::Weight *wtscheme,
     139                   const Xapian::MatchCmp *sort_cmpfn,
    138140                   const Xapian::RSet &omrset);
    139141
    140142    /** Get the Stats from the remote server.
  • xapian-core/common/omenquireinternal.h

     
    3131#include <math.h>
    3232#include <map>
    3333#include <set>
     34#include "document.h"
    3435
    3536using namespace std;
    3637
     
    6465};
    6566
    6667/** An item resulting from a query.
     68 *
    6769 *  This item contains the document id, and the weight calculated for
    6870 *  the document.
     71 *
     72 *  It also provides access to the underlying document,
     73 *  and stores information needed to support collapse operations.
    6974 */
    7075class MSetItem {
    7176    public:
    7277        MSetItem(Xapian::weight wt_, Xapian::docid did_)
    73                 : wt(wt_), did(did_), collapse_count(0) {}
     78                : wt(wt_),
     79                  did(did_),
     80                  collapse_count(0),
     81                  internal_docid(0) {}
    7482
    75         MSetItem(Xapian::weight wt_, Xapian::docid did_, const string &key_)
    76                 : wt(wt_), did(did_), collapse_key(key_), collapse_count(0) {}
     83        MSetItem(Xapian::weight wt_,
     84                 Xapian::docid did_,
     85                 const Xapian::Internal::RefCntPtr<Xapian::Database::Internal> & internal_db_,
     86                 Xapian::docid internal_docid_)
     87                : wt(wt_),
     88                  did(did_),
     89                  collapse_count(0),
     90                  internal_db(internal_db_),
     91                  internal_docid(internal_docid_) {}
    7792
    78         MSetItem(Xapian::weight wt_, Xapian::docid did_, const string &key_,
     93        MSetItem(Xapian::weight wt_,
     94                 Xapian::docid did_,
     95                 const Xapian::Internal::RefCntPtr<Xapian::Database::Internal> & internal_db_,
     96                 Xapian::docid internal_docid_,
     97                 const string &collapse_key_,
    7998                 Xapian::doccount collapse_count_)
    80                 : wt(wt_), did(did_), collapse_key(key_),
    81                   collapse_count(collapse_count_) {}
     99                : wt(wt_),
     100                  did(did_),
     101                  collapse_key(collapse_key_),
     102                  collapse_count(collapse_count_),
     103                  internal_db(internal_db_),
     104                  internal_docid(internal_docid_) {}
    82105
    83106        /** Weight calculated. */
    84107        Xapian::weight wt;
    85108
    86         /** Document id. */
     109        /** Document id (as publically displayed). */
    87110        Xapian::docid did;
    88111
    89112        /** Value which was used to collapse upon.
     
    96119         *  for this item, the value will be a null string.  Only one instance
    97120         *  of each key value (apart from the null string) will be present in
    98121         *  the items in the returned Xapian::MSet.
     122         *
     123         *  FIXME - just use doc.
    99124         */
    100125        string collapse_key;
    101126
    102         /** Count of collapses done on collapse_key so far
     127        /** Count of collapses done on collapse_key so far.
    103128         *
    104          * This is normally 0, and goes up for each collapse done
    105          * It is not neccessarily an indication of how many collapses
    106          * might be done if an exhaustive match was done
     129         *  This is normally 0, and goes up for each collapse done
     130         *  It is not neccessarily an indication of how many collapses
     131         *  might be done if an exhaustive match was done
    107132         */
    108133        Xapian::doccount collapse_count;
    109134
    110         /** Used when sorting by value. */
    111         /* FIXME: why not just cache the Xapian::Document here!?! */
    112         string sort_key;
     135        /** Get the document.
     136         *
     137         *  This will cache the document from previous requests, and will
     138         *  return a pointer to NULL if the MSetItem was not initialised with
     139         *  and internal_db and internal_docid.
     140         */
     141        const Xapian::Internal::RefCntPtr<Xapian::Document::Internal> &
     142        get_document() const
     143        {
     144            if (doc.get() == NULL)
     145            {
     146                if (internal_db.get() == NULL)
     147                {
     148                    Xapian::Internal::RefCntPtr<Xapian::Document::Internal> temp(new Xapian::Document::Internal());
     149                    doc = temp;
     150                } else {
     151                    Xapian::Internal::RefCntPtr<Xapian::Document::Internal> temp(internal_db->open_document(internal_docid, true));
     152                    doc = temp;
     153                }
     154            }
     155            return doc;
     156        }
    113157
    114158        /** Returns a string representing the mset item.
    115159         *  Introspection method.
    116160         */
    117161        string get_description() const;
     162
     163    private:
     164        /** Database containing the document. */
     165        Xapian::Internal::RefCntPtr<Xapian::Database::Internal> internal_db;
     166
     167        /** Document id in database containing the document. */
     168        Xapian::docid internal_docid;
     169
     170        /** Document object.  Points to NULL before the document has
     171         *  been requested (with get_document()). */
     172        mutable Xapian::Internal::RefCntPtr<Xapian::Document::Internal> doc;
    118173};
    119174
    120175}
     
    140195        void operator=(const Internal &);
    141196
    142197    public:
    143         typedef enum { REL, VAL, VAL_REL, REL_VAL } sort_setting;
     198        typedef enum { REL, VAL, VAL_REL, REL_VAL, USER } sort_setting;
    144199
    145200        Xapian::valueno collapse_key;
    146201
     
    153208        Xapian::valueno sort_key;
    154209        sort_setting sort_by;
    155210        bool sort_value_forward;
     211        MatchCmp * sort_cmpfn;
    156212
    157213        time_t bias_halflife;
    158214        Xapian::weight bias_weight;
  • xapian-core/common/multimatch.h

     
    6868        /// Weighting scheme
    6969        const Xapian::Weight * weight;
    7070
     71        /// User supplied match comparison function
     72        const Xapian::MatchCmp * match_cmp;
     73
    7174        /** Internal flag to note that w_max needs to be recalculated
    7275         *  while query is running.
    7376         */
     
    7578
    7679        /// get the collapse key
    7780        string get_collapse_key(PostList *pl,
    78                                 Xapian::docid did, Xapian::valueno keyno,
    79                                 Xapian::Internal::RefCntPtr<Xapian::Document::Internal> &doc);
     81                                Xapian::valueno keyno,
     82                                const Xapian::Internal::MSetItem & item);
    8083
    8184        /** get the maxweight that the postlist pl may return, calling
    8285         *  recalc_maxweight if recalculate_w_max is set, and unsetting it.
     
    118121                   Xapian::weight bias_weight_,
    119122                   Xapian::ErrorHandler * errorhandler,
    120123                   StatsGatherer * gatherer_,
    121                    const Xapian::Weight *wtscheme);
     124                   const Xapian::Weight *wtscheme,
     125                   const Xapian::MatchCmp * match_cmp_);
    122126
    123127        void get_mset(Xapian::doccount first,
    124128                      Xapian::doccount maxitems,
  • xapian-core/common/remoteprotocol.h

     
    2424// Versions:
    2525// 21: Overhauled remote backend supporting WritableDatabase
    2626// 22: Lossless double serialisation
    27 #define XAPIAN_REMOTE_PROTOCOL_VERSION 22
     27// 23: MatchCmp serialisation
     28#define XAPIAN_REMOTE_PROTOCOL_VERSION 23
    2829
    2930/// Message types (client -> server).
    3031enum message_type {
  • xapian-core/common/remoteserver.h

     
    6767    /// Registered weighting schemes.
    6868    map<string, Xapian::Weight *> wtschemes;
    6969
     70    /// Registered match cmpfns.
     71    map<string, Xapian::MatchCmp *> match_cmps;
     72
    7073    /// Initialisation code needed by both ctors.
    7174    void initialise();
    7275
     
    176179    void register_weighting_scheme(const Xapian::Weight &wt) {
    177180        wtschemes[wt.name()] = wt.clone();
    178181    }
     182
     183    /// Register a user-defined match comparision class.
     184    void register_match_cmp(const Xapian::MatchCmp &match_cmp) {
     185        match_cmps[match_cmp.name()] = match_cmp.clone();
     186    }
    179187};
    180188
    181189#endif // XAPIAN_INCLUDED_REMOTESERVER_H
  • xapian-core/common/serialise.h

     
    2222#define XAPIAN_INCLUDED_SERIALISE_H
    2323
    2424#include <string>
     25#include <xapian/database.h>
    2526
    2627// Forward class declarations:
    2728
     
    108109 *
    109110 *  @return     The unserialised Xapian::MSet object.
    110111 */
    111 Xapian::MSet unserialise_mset(const std::string &s);
     112Xapian::MSet unserialise_mset(const std::string &s,
     113                              Xapian::Internal::RefCntPtr<Xapian::Database::Internal> internal_db);
    112114
    113115/** Serialise a Xapian::RSet object.
    114116 *
  • xapian-core/testsuite/index_utils.cc

     
    8080    for (Xapian::valueno i = min(para.length(), size_t(10)); i >= 1; --i) {
    8181        doc.add_value(i, para.substr(i, 1));
    8282    }
     83    if (para.length() > 1 && para[0] == 'V') {
     84        doc.add_value(100, Xapian::IntegerMatchCmp::int_to_value(atoi(para.c_str() + 1)));
     85    }
    8386
    8487    Xapian::termcount pos = 0;
    8588    string::const_iterator end = para.begin();
  • xapian-core/api/omenquire.cc

     
    640640  : db(db_), query(), collapse_key(Xapian::valueno(-1)),
    641641    order(Enquire::ASCENDING), percent_cutoff(0), weight_cutoff(0),
    642642    sort_key(Xapian::valueno(-1)), sort_by(REL), sort_value_forward(true),
     643    sort_cmpfn(0),
    643644    bias_halflife(0), bias_weight(0), errorhandler(errorhandler_), weight(0)
    644645{
    645646}
     
    647648Enquire::Internal::~Internal()
    648649{
    649650    delete weight;
     651    delete sort_cmpfn;
    650652    weight = 0;
    651653}
    652654
     
    682684                       percent_cutoff, weight_cutoff,
    683685                       order, sort_key, sort_by, sort_value_forward,
    684686                       bias_halflife, bias_weight, errorhandler,
    685                        new LocalStatsGatherer(), weight);
     687                       new LocalStatsGatherer(), weight, sort_cmpfn);
    686688        // Run query and put results into supplied Xapian::MSet object.
    687689        match.get_mset(first, maxitems, check_at_least, retval, mdecider);
    688690    } else {
     
    690692                       percent_cutoff, weight_cutoff,
    691693                       order, sort_key, sort_by, sort_value_forward,
    692694                       bias_halflife, bias_weight, errorhandler,
    693                        new LocalStatsGatherer(), weight);
     695                       new LocalStatsGatherer(), weight, sort_cmpfn);
    694696        // Run query and put results into supplied Xapian::MSet object.
    695697        match.get_mset(first, maxitems, check_at_least, retval, mdecider);
    696698    }
     
    907909{
    908910    DEBUGAPICALL(void, "Xapian::Enquire::set_weighting_scheme", "[Weight]");
    909911    delete internal->weight;
     912    internal->weight = NULL; // Set to NULL before cloning, to avoid double free if clone() throws an exception.
    910913    internal->weight = weight_.clone();
    911914}
    912915
     
    939942Enquire::set_sort_by_relevance()
    940943{
    941944    internal->sort_by = Internal::REL;
     945    delete internal->sort_cmpfn;
     946    internal->sort_cmpfn = NULL;
    942947}
    943948
    944949void
     
    947952    internal->sort_key = sort_key;
    948953    internal->sort_by = Internal::VAL;
    949954    internal->sort_value_forward = ascending;
     955    delete internal->sort_cmpfn;
     956    internal->sort_cmpfn = NULL;
    950957}
    951958
    952959void
     
    956963    internal->sort_key = sort_key;
    957964    internal->sort_by = Internal::VAL_REL;
    958965    internal->sort_value_forward = ascending;
     966    delete internal->sort_cmpfn;
     967    internal->sort_cmpfn = NULL;
    959968}
    960969
    961970void
     
    965974    internal->sort_key = sort_key;
    966975    internal->sort_by = Internal::REL_VAL;
    967976    internal->sort_value_forward = ascending;
     977    delete internal->sort_cmpfn;
     978    internal->sort_cmpfn = NULL;
    968979}
    969980
    970981void
     982Enquire::set_sort_by_cmpfn(const MatchCmp &cmpfn_)
     983{
     984    DEBUGAPICALL(void, "Xapian::Enquire::set_sort_by_cmpfn", "[cmpfn:" << cmpfn_.name() << "(" << cmpfn_.serialise() << ")]");
     985    delete internal->sort_cmpfn;
     986    internal->sort_by = Internal::USER;
     987    internal->sort_cmpfn = NULL; // Set to NULL before cloning, to avoid double free if clone() throws an exception.
     988    internal->sort_cmpfn = cmpfn_.clone();
     989}
     990
     991void
    971992Enquire::set_sorting(Xapian::valueno sort_key, int sort_bands,
    972993                     bool sort_by_relevance)
    973994{
  • xapian-core/backends/remote/remote-database.cc

     
    367367                         bool sort_value_forward,
    368368                         int percent_cutoff, Xapian::weight weight_cutoff,
    369369                         const Xapian::Weight *wtscheme,
     370                         const Xapian::MatchCmp *sort_cmpfn,
    370371                         const Xapian::RSet &omrset)
    371372{
    372373    string tmp = query->serialise();
     
    383384    message += char(percent_cutoff);
    384385    message += serialise_double(weight_cutoff);
    385386
     387    // Serialise the weight scheme
    386388    tmp = wtscheme->name();
    387389    message += encode_length(tmp.size());
    388390    message += tmp;
    389 
    390391    tmp = wtscheme->serialise();
    391392    message += encode_length(tmp.size());
    392393    message += tmp;
    393394
     395    // Serialise the match cmp object
     396    if (sort_cmpfn == NULL) {
     397        message += encode_length(0);
     398    } else {
     399        tmp = sort_cmpfn->name();
     400        message += encode_length(tmp.size());
     401        message += tmp;
     402        tmp = sort_cmpfn->serialise();
     403        message += encode_length(tmp.size());
     404        message += tmp;
     405    }
     406
    394407    message += serialise_rset(omrset);
    395408
    396409    send_message(MSG_QUERY, message);
     
    424437{
    425438    string message;
    426439    get_message(message, REPLY_RESULTS);
    427     mset = unserialise_mset(message);
     440    mset = unserialise_mset(message, this);
    428441}
    429442
    430443void
  • xapian-bindings/xapian.i

     
    251251class ExpandDecider;
    252252class MatchDecider;
    253253class Weight;
     254class MatchCmp;
    254255class Stopper;
    255256
    256257// from xapian/positioniterator.h
     
    582583                                          bool ascending = true);
    583584    void set_sort_by_relevance_then_value(Xapian::valueno sort_key,
    584585                                          bool ascending = true);
     586    void set_sort_by_cmpfn(const MatchCmp& cmpfn);
    585587
    586588    void set_bias(weight bias_weight, time_t bias_halflife);
    587589
     
    720722        bool get_sumpart_needs_doclength() const;
    721723};
    722724
     725class MatchItem {
     726    public:
     727        MatchItem(const Xapian::Internal::MSetItem & item_);
     728        Xapian::weight get_wt() const;
     729        Xapian::docid get_docid() const;
     730        Xapian::Document get_document() const;
     731};
     732
     733class MatchCmp {
     734/* SWIG doesn't handle this:
     735    private:
     736        virtual MatchCmp * clone() const = 0; */
     737    public:
     738        virtual ~MatchCmp();
     739
     740        virtual std::string name() const = 0;
     741        virtual std::string serialise() const = 0;
     742        virtual MatchCmp * unserialise(const std::string &s) const = 0;
     743
     744        virtual int cmp(const MatchItem & a,
     745                        const MatchItem & b) const = 0;
     746};
     747
     748%warnfilter(842) IntegerMatchCmp::unserialise;
     749class IntegerMatchCmp : public MatchCmp {
     750    public:
     751        IntegerMatchCmp * clone() const;
     752        IntegerMatchCmp(Xapian::valueno sort_key_,
     753                        bool ascending = true);
     754        IntegerMatchCmp();
     755        ~IntegerMatchCmp();
     756
     757        std::string name() const;
     758        std::string serialise() const;
     759        IntegerMatchCmp * unserialise(const std::string &s) const;
     760
     761        int cmp(const MatchItem & a,
     762                const MatchItem & b) const;
     763
     764        static std::string int_to_value(int num);
     765        static int value_to_int(const std::string &s);
     766};
     767
     768%warnfilter(842) DoubleMatchCmp::unserialise;
     769class DoubleMatchCmp : public MatchCmp {
     770    public:
     771        DoubleMatchCmp * clone() const;
     772        DoubleMatchCmp(Xapian::valueno sort_key_,
     773                        bool ascending = true);
     774        DoubleMatchCmp();
     775        ~DoubleMatchCmp();
     776
     777        std::string name() const;
     778        std::string serialise() const;
     779        DoubleMatchCmp * unserialise(const std::string &s) const;
     780
     781        int cmp(const MatchItem & a,
     782                const MatchItem & b) const;
     783
     784        static std::string double_to_value(double num);
     785        static double value_to_double(const std::string &s);
     786};
     787
     788
    723789// xapian/database.h
    724790
    725791class Database {