Ticket #295: remotepostsource.patch

File remotepostsource.patch, 31.5 kB (added by richard, 3 months ago)

Patch to make PostingSource? work with remote databases

  • xapian-core/matcher/externalpostlist.cc

     
    2929 
    3030using namespace std; 
    3131 
    32 ExternalPostList::ExternalPostList(Xapian::PostingSource *source_, 
     32ExternalPostList::ExternalPostList(const Xapian::Database & db, 
     33                                   Xapian::PostingSource *source_, 
    3334                                   double factor_) 
    34     : source(source_), current(0), factor(factor_) 
     35    : source(source_), source_is_owned(false), current(0), factor(factor_) 
    3536{ 
    3637    Assert(source); 
    37     source->reset(); 
     38    Xapian::PostingSource * newsource = source->clone(); 
     39    if (newsource != NULL) { 
     40        source = newsource; 
     41        source_is_owned = true; 
     42    } 
     43    source->reset(db); 
    3844} 
    3945 
     46ExternalPostList::~ExternalPostList() 
     47{ 
     48    if (source_is_owned) { 
     49        delete source; 
     50    } 
     51} 
     52 
    4053Xapian::doccount 
    4154ExternalPostList::get_termfreq_min() const 
    4255{ 
  • xapian-core/matcher/queryoptimiser.cc

     
    6464 
    6565        case Xapian::Query::Internal::OP_EXTERNAL_SOURCE: 
    6666            Assert(query->external_source); 
    67             RETURN(new ExternalPostList(query->external_source, factor)); 
     67            RETURN(new ExternalPostList( 
     68                Xapian::Database(const_cast<Xapian::Database::Internal *>(&db)), 
     69                query->external_source, factor)); 
    6870 
    6971        case Xapian::Query::OP_AND: 
    7072        case Xapian::Query::OP_FILTER: 
  • xapian-core/matcher/externalpostlist.h

     
    2929 
    3030class ExternalPostList : public PostList { 
    3131    Xapian::PostingSource * source; 
     32    bool source_is_owned; 
    3233 
    3334    Xapian::docid current; 
    3435 
     
    4344    PostList * update_after_advance(); 
    4445 
    4546  public: 
    46     ExternalPostList(Xapian::PostingSource *source_, double factor_); 
     47    ExternalPostList(const Xapian::Database & db, 
     48                     Xapian::PostingSource *source_, 
     49                     double factor_); 
     50    ~ExternalPostList(); 
    4751 
    4852    Xapian::doccount get_termfreq_min() const; 
    4953 
  • xapian-core/tests/api_db.cc

     
    18871887        : num_docs(db.get_doccount()), last_docid(db.get_lastdocid()), did(0) 
    18881888    { } 
    18891889 
    1890     void reset() { did = 0; } 
     1890    MyOddPostingSource(Xapian::doccount num_docs_, 
     1891                       Xapian::doccount last_docid_) 
     1892        : num_docs(num_docs_), last_docid(last_docid_), did(0) 
     1893    { } 
    18911894 
     1895    PostingSource * clone() const { return new MyOddPostingSource(num_docs, last_docid); } 
     1896 
     1897    void reset(const Xapian::Database &) { did = 0; } 
     1898 
    18921899    // These bounds could be better, but that's not important here. 
    18931900    Xapian::doccount get_termfreq_min() const { return 0; } 
    18941901 
     
    19251932    Xapian::Enquire enq(db); 
    19261933    MyOddPostingSource src(db); 
    19271934 
    1928     // Check that passing NULL is rejected as intended. 
    1929     TEST_EXCEPTION(Xapian::InvalidArgumentError, Xapian::Query bad(NULL)); 
    1930     Xapian::PostingSource * nullsrc = NULL; 
    1931     TEST_EXCEPTION(Xapian::InvalidArgumentError, Xapian::Query bad(nullsrc)); 
    1932                  
    1933     enq.set_query(Xapian::Query(&src)); 
     1935    enq.set_query(Xapian::Query(src)); 
    19341936 
    19351937    Xapian::MSet mset = enq.get_mset(0, 10); 
    19361938    mset_expect_order(mset, 1, 3, 5, 7, 9, 11, 13, 15, 17); 
    19371939 
    19381940    Xapian::Query q(Xapian::Query::OP_FILTER, 
    19391941                    Xapian::Query("leav"), 
    1940                     Xapian::Query(&src)); 
     1942                    Xapian::Query(src)); 
    19411943    enq.set_query(q); 
    19421944 
    19431945    mset = enq.get_mset(0, 10); 
     
    19531955    Xapian::Enquire enq(db); 
    19541956    MyOddPostingSource src(db); 
    19551957 
    1956     enq.set_query(Xapian::Query(&src)); 
     1958    enq.set_query(Xapian::Query(src)); 
    19571959 
    19581960    TEST_EXCEPTION(Xapian::UnimplementedError, 
    19591961                   Xapian::MSet mset = enq.get_mset(0, 10)); 
    19601962 
    19611963    Xapian::Query q(Xapian::Query::OP_FILTER, 
    19621964                    Xapian::Query("leav"), 
    1963                     Xapian::Query(&src)); 
     1965                    Xapian::Query(src)); 
    19641966    enq.set_query(q); 
    19651967 
    19661968    TEST_EXCEPTION(Xapian::UnimplementedError, 
     
    19811983        : num_docs(db.get_doccount()), last_docid(db.get_lastdocid()), did(0) 
    19821984    { } 
    19831985 
    1984     void reset() { did = 0; } 
     1986    MyOddWeightingPostingSource(Xapian::doccount num_docs_, 
     1987                                Xapian::doccount last_docid_) 
     1988        : num_docs(num_docs_), last_docid(last_docid_), did(0) 
     1989    { } 
    19851990 
     1991    PostingSource * clone() const { return new MyOddWeightingPostingSource(num_docs, last_docid); } 
     1992 
     1993    void reset(const Xapian::Database &) { did = 0; } 
     1994 
    19861995    Xapian::weight get_weight() const { 
    19871996        return (did % 2) ? 1000 : 0.001; 
    19881997    } 
     
    20282037    Xapian::Enquire enq(db); 
    20292038    MyOddWeightingPostingSource src(db); 
    20302039 
    2031     enq.set_query(Xapian::Query(&src)); 
     2040    enq.set_query(Xapian::Query(src)); 
    20322041 
    20332042    Xapian::MSet mset = enq.get_mset(0, 10); 
    20342043    mset_expect_order(mset, 1, 3, 5, 7, 9, 11, 13, 15, 17, 2); 
    20352044 
    20362045    Xapian::Query q(Xapian::Query::OP_OR, 
    20372046                    Xapian::Query("leav"), 
    2038                     Xapian::Query(&src)); 
     2047                    Xapian::Query(src)); 
    20392048    enq.set_query(q); 
    20402049 
    20412050    mset = enq.get_mset(0, 5); 
     
    20512060    tout << "max possible weight = " << mset.get_max_possible() << endl; 
    20522061    TEST(mset.get_max_possible() > 1000); 
    20532062 
    2054     enq.set_query(Xapian::Query(q.OP_SCALE_WEIGHT, Xapian::Query(&src), 0.5)); 
     2063    enq.set_query(Xapian::Query(q.OP_SCALE_WEIGHT, Xapian::Query(src), 0.5)); 
    20552064    mset = enq.get_mset(0, 10); 
    20562065    TEST(mset.empty()); 
    20572066 
    20582067    TEST_EQUAL(mset.get_max_possible(), 500); 
    20592068 
    2060     enq.set_query(Xapian::Query(q.OP_SCALE_WEIGHT, Xapian::Query(&src), 2)); 
     2069    enq.set_query(Xapian::Query(q.OP_SCALE_WEIGHT, Xapian::Query(src), 2)); 
    20612070    mset = enq.get_mset(0, 10); 
    20622071    mset_expect_order(mset, 1, 3, 5, 7, 9, 11, 13, 15, 17); 
    20632072 
     
    20782087        : num_docs(db.get_doccount()), last_docid(db.get_lastdocid()), did(0) 
    20792088    { } 
    20802089 
    2081     void reset() { did = 0; } 
     2090    MyDontAskWeightPostingSource(Xapian::doccount num_docs_, 
     2091                                 Xapian::doccount last_docid_) 
     2092        : num_docs(num_docs_), last_docid(last_docid_), did(0) 
     2093    { } 
    20822094 
     2095    PostingSource * clone() const { return new MyDontAskWeightPostingSource(num_docs, last_docid); } 
     2096 
     2097    void reset(const Xapian::Database &) { did = 0; } 
     2098 
    20832099    Xapian::weight get_weight() const { 
    20842100        FAIL_TEST("MyDontAskWeightPostingSource::get_weight() called"); 
    20852101    } 
     
    21262142    MyDontAskWeightPostingSource src(db); 
    21272143 
    21282144    tout << "OP_SCALE_WEIGHT 0" << endl; 
    2129     enq.set_query(Xapian::Query(Xapian::Query::OP_SCALE_WEIGHT, Xapian::Query(&src), 0)); 
     2145    enq.set_query(Xapian::Query(Xapian::Query::OP_SCALE_WEIGHT, Xapian::Query(src), 0)); 
    21302146 
    21312147    Xapian::MSet mset = enq.get_mset(0, 5); 
    21322148    mset_expect_order(mset, 1, 2, 3, 4, 5); 
     
    21342150    tout << "OP_FILTER" << endl; 
    21352151    Xapian::Query q(Xapian::Query::OP_FILTER, 
    21362152                    Xapian::Query("leav"), 
    2137                     Xapian::Query(&src)); 
     2153                    Xapian::Query(src)); 
    21382154    enq.set_query(q); 
    21392155 
    21402156    mset = enq.get_mset(0, 5); 
    21412157    mset_expect_order(mset, 8, 6, 4, 5, 7); 
    21422158 
    21432159    tout << "BoolWeight" << endl; 
    2144     enq.set_query(Xapian::Query(&src)); 
     2160    enq.set_query(Xapian::Query(src)); 
    21452161    enq.set_weighting_scheme(Xapian::BoolWeight()); 
    21462162 
    21472163    //mset = enq.get_mset(0, 5); 
     
    21512167} 
    21522168 
    21532169// Check that valueweightsource works correctly. 
    2154 DEFINE_TESTCASE(valueweightsource1, backend && !remote) { 
    2155     // FIXME: PostingSource doesn't currently work well with multi databases 
    2156     // but we should try to resolve that issue. 
    2157     SKIP_TEST_FOR_BACKEND("multi"); 
     2170DEFINE_TESTCASE(valueweightsource1, backend) { 
    21582171    Xapian::Database db(get_database("apitest_phrase")); 
    21592172    Xapian::Enquire enq(db); 
    2160     Xapian::ValueWeightPostingSource src(db, 11); 
     2173    Xapian::ValueWeightPostingSource src(11); 
    21612174 
    21622175    // Should be in descending order of length 
    21632176    tout << "RAW" << endl; 
    2164     enq.set_query(Xapian::Query(&src)); 
     2177    enq.set_query(Xapian::Query(src)); 
    21652178    Xapian::MSet mset = enq.get_mset(0, 5); 
    21662179    mset_expect_order(mset, 3, 1, 2, 8, 14); 
    21672180 
     
    21692182    tout << "OP_FILTER" << endl; 
    21702183    Xapian::Query q(Xapian::Query::OP_FILTER, 
    21712184                    Xapian::Query("leav"), 
    2172                     Xapian::Query(&src)); 
     2185                    Xapian::Query(src)); 
    21732186    enq.set_query(q); 
    21742187    mset = enq.get_mset(0, 5); 
    21752188    mset_expect_order(mset, 8, 6, 4, 5, 7); 
     
    21772190    // Should be in descending order of length 
    21782191    tout << "OP_FILTER other way" << endl; 
    21792192    q = Xapian::Query(Xapian::Query::OP_FILTER, 
    2180                       Xapian::Query(&src), 
     2193                      Xapian::Query(src), 
    21812194                      Xapian::Query("leav")); 
    21822195    enq.set_query(q); 
    21832196    mset = enq.get_mset(0, 5); 
     
    21892202// Check that valueweightsource gives the correct bounds for those databases 
    21902203// which support value statistics. 
    21912204DEFINE_TESTCASE(valueweightsource2, backend && valuestats) { 
    2192     // FIXME: PostingSource doesn't currently work well with multi databases 
    2193     // but we should try to resolve that issue. 
    2194     SKIP_TEST_FOR_BACKEND("multi"); 
    21952205    Xapian::Database db(get_database("apitest_phrase")); 
    2196     Xapian::ValueWeightPostingSource src(db, 11); 
     2206    Xapian::ValueWeightPostingSource src(11); 
     2207    src.reset(db); 
    21972208    TEST_EQUAL(src.get_termfreq_min(), 17); 
    21982209    TEST_EQUAL(src.get_termfreq_est(), 17); 
    21992210    TEST_EQUAL(src.get_termfreq_max(), 17); 
     
    22042215 
    22052216// Check that valueweightsource skip_to() can stay in the same position. 
    22062217DEFINE_TESTCASE(valueweightsource3, backend && valuestats) { 
    2207     // FIXME: PostingSource doesn't currently work well with multi databases 
    2208     // but we should try to resolve that issue. 
    2209     SKIP_TEST_FOR_BACKEND("multi"); 
    22102218    Xapian::Database db(get_database("apitest_phrase")); 
    2211     Xapian::ValueWeightPostingSource src(db, 11); 
     2219    Xapian::ValueWeightPostingSource src(11); 
     2220    src.reset(db); 
    22122221    TEST(!src.at_end()); 
    22132222    src.skip_to(8, 0.0); 
    22142223    TEST(!src.at_end()); 
  • xapian-core/tests/api_percentages.cc

     
    4545        : maxwt(0.0), started(false) 
    4646    {} 
    4747 
     48    MyPostingSource(const std::vector<std::pair<Xapian::docid, Xapian::weight> > &weights_, 
     49                    Xapian::weight maxwt_) 
     50        : weights(weights_), maxwt(maxwt_), started(false) 
     51    {} 
     52 
     53 
     54    PostingSource * clone() const 
     55    { 
     56        return new MyPostingSource(weights, maxwt); 
     57    } 
     58 
    4859    void append_docweight(Xapian::docid did, Xapian::weight wt) 
    4960    { 
    5061        weights.push_back(make_pair(did, wt)); 
     
    5566        if (wt > maxwt) maxwt = wt; 
    5667    } 
    5768 
    58     void reset() { started = false; } 
     69    void reset(const Xapian::Database &) { started = false; } 
    5970 
    6071    Xapian::weight get_weight() const { 
    6172        return i->second; 
     
    103114        MyPostingSource source; 
    104115        source.append_docweight(1, 100); 
    105116        source.append_docweight(2, 50 - epsilons * DBL_EPSILON); 
    106         enquire.set_query(Xapian::Query(&source)); 
     117        enquire.set_query(Xapian::Query(source)); 
    107118        Xapian::MSet mset = enquire.get_mset(0, 10); 
    108119        TEST_EQUAL(mset.size(), 2); 
    109120        if (mset[1].get_percent() != 50) break; 
     
    120131        source.append_docweight(4, 50 - epsilons * DBL_EPSILON); 
    121132        source.append_docweight(5, 25); 
    122133 
    123         enquire.set_query(Xapian::Query(&source)); 
     134        enquire.set_query(Xapian::Query(source)); 
    124135        Xapian::MSet mset1 = enquire.get_mset(0, 10); 
    125136        TEST_EQUAL(mset1.size(), 5); 
    126137        TEST_EQUAL(mset1[2].get_percent(), 50); 
  • xapian-core/include/xapian/postingsource.h

     
    137137    /// Return the current docid. 
    138138    virtual Xapian::docid get_docid() const = 0; 
    139139 
     140    /** Clone the posting source. 
     141     * 
     142     *  The clone should inherit the configuration of the parent, but need not 
     143     *  inherit the state.  ie, the clone does not need to be in the same 
     144     *  iteration position as the original: the matcher will always call 
     145     *  reset() on the clone before attempting to move the iterator, or read 
     146     *  the information about the current position of the iterator. 
     147     */ 
     148    virtual PostingSource * clone() const = 0; 
     149 
     150    /** Name of the posting source, for performing remote searches. 
     151     * 
     152     *  If the subclass is called FooPostingSource, this should return "Foo". 
     153     * 
     154     *  This should only be implemented if serialise() and unserialise() are 
     155     *  also implemented. 
     156     * 
     157     *  The default implmenentation returns an empty string, to indicate that 
     158     *  serialise() and unserialise() are not implemented. 
     159     */ 
     160    virtual std::string name() const; 
     161 
     162    /** Serialise object parameters into a string. 
     163     * 
     164     *  The serialised parameters should represent the configuration of the 
     165     *  posting source, but need not (indeed, should not) represent the current 
     166     *  iteration state. 
     167     */ 
     168    virtual std::string serialise() const; 
     169 
     170    /** Create object given string serialisation returned by serialise(). 
     171     * 
     172     *  @param s A serialised instance of this PostingSource subclass. 
     173     */ 
     174    virtual PostingSource * unserialise(const std::string &s) const; 
     175 
    140176    /** Reset this PostingSource to its freshly constructed state. 
    141177     * 
    142178     *  This is called automatically by the matcher prior to each query being 
    143179     *  processed. 
     180     * 
     181     *  FIXME - document the db parameter. 
    144182     */ 
    145     virtual void reset() = 0; 
     183    virtual void reset(const Database & db) = 0; 
    146184 
    147185    /** Return a string describing this object. 
    148186     * 
     
    196234    /// An upper bound on the value returned. 
    197235    double max_value; 
    198236 
     237    /// Upper bound on the value returned specified in constructor. 
     238    double specified_max_value; 
     239 
    199240  public: 
    200241    /** Construct a ValueWeightPostingSource. 
    201242     * 
    202243     *  @param db_ The database to read values from. 
    203244     *  @param valno_ The value slot to read values from. 
    204245     */ 
    205     ValueWeightPostingSource(Xapian::Database db_, Xapian::valueno valno_); 
     246    ValueWeightPostingSource(Xapian::valueno valno_); 
    206247 
    207248    /** Construct a ValueWeightPostingSource. 
    208249     * 
     
    214255     *  constructor need only be used if more accurate information is 
    215256     *  available. 
    216257     */ 
    217     ValueWeightPostingSource(Xapian::Database db_, Xapian::valueno valno_, 
    218                              double max_weight_); 
     258    ValueWeightPostingSource(Xapian::valueno valno_, double max_weight_); 
    219259 
    220260    Xapian::doccount get_termfreq_min() const; 
    221261    Xapian::doccount get_termfreq_est() const; 
     
    232272 
    233273    Xapian::docid get_docid() const; 
    234274 
    235     void reset(); 
     275    ValueWeightPostingSource * clone() const; 
     276    std::string name() const; 
     277    std::string serialise() const; 
     278    PostingSource * unserialise(const std::string &s) const; 
    236279 
     280    void reset(const Database & db_); 
     281 
    237282    std::string get_description() const; 
    238283}; 
    239284 
  • xapian-core/include/xapian/query.h

     
    2525#ifndef XAPIAN_INCLUDED_QUERY_H 
    2626#define XAPIAN_INCLUDED_QUERY_H 
    2727 
     28#include <map> 
    2829#include <string> 
    2930#include <vector> 
    3031 
     
    204205         */ 
    205206        Query(Query::op op_, Xapian::valueno valno, const std::string &value); 
    206207 
    207         /// Construct an external source query. 
    208         explicit Query(Xapian::PostingSource * external_source); 
     208        /** Construct an external source query. 
     209         * 
     210         *  The posting source will be cloned immediately, so the source 
     211         *  supplied may be safely deallocated after this call. 
     212         * 
     213         *  @param external_source The source to use in the query. 
     214         */ 
     215        explicit Query(const Xapian::PostingSource & external_source); 
    209216 
    210217        /** A query which matches all documents in the database. */ 
    211218        static Xapian::Query MatchAll; 
     
    405412        /** Destructor. */ 
    406413        ~Internal(); 
    407414 
    408         static Xapian::Query::Internal * unserialise(const std::string &s); 
     415        static Xapian::Query::Internal * unserialise(const std::string &s, 
     416                const std::map<std::string, Xapian::PostingSource *> &sources); 
    409417 
    410418        /** Add a subquery. 
    411419         */ 
  • xapian-core/net/remoteserver.cc

     
    114114    wtschemes[weight->name()] = weight; 
    115115    weight = new Xapian::TradWeight; 
    116116    wtschemes[weight->name()] = weight; 
     117 
     118    Xapian::PostingSource * source; 
     119    source = new Xapian::ValueWeightPostingSource(0); 
     120    postingsources[source->name()] = source; 
    117121} 
    118122 
    119123RemoteServer::~RemoteServer() 
     
    121125    delete db; 
    122126    // wdb is either NULL or equal to db, so we shouldn't delete it too! 
    123127 
    124     map<string, Xapian::Weight*>::const_iterator i; 
    125     for (i = wtschemes.begin(); i != wtschemes.end(); ++i) { 
    126         delete i->second; 
     128    { 
     129        map<string, Xapian::Weight*>::const_iterator i; 
     130        for (i = wtschemes.begin(); i != wtschemes.end(); ++i) { 
     131            delete i->second; 
     132        } 
    127133    } 
     134 
     135    { 
     136        map<string, Xapian::PostingSource *>::const_iterator i; 
     137        for (i = postingsources.begin(); i != postingsources.end(); ++i) { 
     138            delete i->second; 
     139        } 
     140    } 
    128141} 
    129142 
    130143message_type 
     
    365378 
    366379    // Unserialise the Query. 
    367380    len = decode_length(&p, p_end, true); 
    368     AutoPtr<Xapian::Query::Internal> query(Xapian::Query::Internal::unserialise(string(p, len))); 
     381    AutoPtr<Xapian::Query::Internal> query(Xapian::Query::Internal::unserialise(string(p, len), postingsources)); 
    369382    p += len; 
    370383 
    371384    // Unserialise assorted Enquire settings. 
     
    610623 
    611624    send_message(REPLY_ADDDOCUMENT, encode_length(did)); 
    612625} 
     626 
     627 
     628void 
     629RemoteServer::register_posting_source(const Xapian::PostingSource &source) 
     630{ 
     631    if (source.name().empty()) { 
     632        throw Xapian::InvalidOperationError("Unable to register posting source - name() method returns empty string."); 
     633    } 
     634    Xapian::PostingSource * sourceclone = source.clone(); 
     635    if (!sourceclone) { 
     636        throw Xapian::InvalidOperationError("Unable to register posting source - clone() method returns NULL."); 
     637    } 
     638    try { 
     639        postingsources[source.name()] = sourceclone; 
     640    } catch(...) { 
     641        delete sourceclone; 
     642        throw; 
     643    } 
     644} 
  • xapian-core/common/remoteserver.h

     
    2424 
    2525#include "xapian/database.h" 
    2626#include "xapian/enquire.h" 
     27#include "xapian/postingsource.h" 
    2728#include "xapian/visibility.h" 
    2829 
    2930#include "remoteconnection.h" 
     
    3132#include <map> 
    3233#include <string> 
    3334 
    34 // Forward declaration 
    35 namespace Xapian { class Weight; } 
    36  
    3735using namespace std; 
    3836 
    3937/** Remote backend server base class. */ 
     
    7068    /// Registered weighting schemes. 
    7169    map<string, Xapian::Weight *> wtschemes; 
    7270 
     71    /// Registered external posting sources. 
     72    map<string, Xapian::PostingSource *> postingsources; 
     73 
    7374    /// Accept a message from the client. 
    7475    message_type get_message(Xapian::timeout timeout, string & result, 
    7576                             message_type required_type = MSG_MAX); 
     
    173174    void register_weighting_scheme(const Xapian::Weight &wt) { 
    174175        wtschemes[wt.name()] = wt.clone(); 
    175176    } 
     177 
     178    /** Register a user-defined posting source class. 
     179     */ 
     180    void register_posting_source(const Xapian::PostingSource &source); 
    176181}; 
    177182 
    178183#endif // XAPIAN_INCLUDED_REMOTESERVER_H 
  • xapian-core/api/omqueryinternal.cc

     
    4242#include <cfloat> 
    4343#include <climits> 
    4444#include <cmath> 
     45#include <map> 
    4546#include <set> 
    4647#include <vector> 
    4748 
     
    142143        result += '['; 
    143144        result += encode_length(tname.length()); 
    144145        result += tname; 
    145         if (term_pos != curpos) result += '@' + om_tostring(term_pos); 
    146         if (wqf != 1) result += '#' + om_tostring(wqf); 
     146        if (term_pos != curpos) result += '@' + om_tostring(term_pos); // FIXME - should this use encode_length()? 
     147        if (wqf != 1) result += '#' + om_tostring(wqf); // FIXME - should this use encode_length()? 
    147148        ++curpos; 
    148149    } else if (op == Xapian::Query::Internal::OP_EXTERNAL_SOURCE) { 
    149         throw Xapian::UnimplementedError("Remote backend doesn't support PostingSource"); 
     150        string sourcename = external_source->name(); 
     151        if (sourcename.empty()) 
     152            throw Xapian::UnimplementedError("This PostingSource doesn't support remote use."); 
     153        result += '!'; 
     154        result += encode_length(sourcename.length()); 
     155        result += sourcename; 
     156        string sourcedata = external_source->serialise(); 
     157        result += encode_length(sourcedata.length()); 
     158        result += sourcedata; 
    150159    } else { 
    151160        result += "("; 
    152161        for (subquery_list::const_iterator i = subqs.begin(); 
     
    158167            case Xapian::Query::Internal::OP_LEAF: 
    159168                Assert(false); 
    160169                break; 
     170            case Xapian::Query::Internal::OP_EXTERNAL_SOURCE: 
     171                Assert(false); 
     172                break; 
    161173            case Xapian::Query::OP_AND: 
    162174                result += "&"; 
    163175                break; 
     
    380392    const char *p; 
    381393    const char *end; 
    382394    Xapian::termpos curpos; 
     395    const map<string, Xapian::PostingSource *> & sources; 
    383396 
    384397    Xapian::Query::Internal * readquery(); 
     398    Xapian::Query::Internal * readexternal(); 
    385399    Xapian::Query::Internal * readcompound(); 
    386400 
    387401  public: 
    388     QUnserial(const string & s) : p(s.c_str()), end(p + s.size()), curpos(1) { } 
     402    QUnserial(const string & s, 
     403              const map<string, Xapian::PostingSource *> & sources_) 
     404            : p(s.c_str()), end(p + s.size()), curpos(1), sources(sources_) { } 
    389405    Xapian::Query::Internal * decode(); 
    390406}; 
    391407 
     
    424440            ++curpos; 
    425441            return new Xapian::Query::Internal(tname, wqf, term_pos); 
    426442        } 
     443        case '!': 
     444            return readexternal(); 
    427445        case '(': 
    428446            return readcompound(); 
    429447        default: 
     
    432450    } 
    433451} 
    434452 
     453Xapian::Query::Internal * 
     454QUnserial::readexternal() 
     455{ 
     456    if (p == end) 
     457        throw Xapian::InvalidArgumentError("Bad serialised query"); 
     458 
     459    size_t length = decode_length(&p, end, true); 
     460    string sourcename(p, length); 
     461    map<string, Xapian::PostingSource *>::const_iterator i; 
     462    i = sources.find(string(p, length)); 
     463    if (i == sources.end()) { 
     464        throw Xapian::InvalidArgumentError("PostingSource " + string(p, length) + " not registered"); 
     465    } 
     466 
     467    p += length; 
     468    length = decode_length(&p, end, true); 
     469    string sourcedata(p, length); 
     470    p += length; 
     471 
     472    return new Xapian::Query::Internal(i->second->unserialise(sourcedata)); 
     473} 
     474 
    435475static Xapian::Query::Internal * 
    436476qint_from_vector(Xapian::Query::op op, 
    437477                 const vector<Xapian::Query::Internal *> & vec, 
     
    476516                    --p; 
    477517                    subqs.push_back(readquery()); 
    478518                    break; 
     519                case '!':