Fix whitespace irregularities in code
[xapian.git] / xapian-core / net / remoteserver.cc
blob8185bc0357ab4ec8ef552cff70e1d2084a5d43a0
1 /** @file remoteserver.cc
2 * @brief Xapian remote backend server base class
3 */
4 /* Copyright (C) 2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016 Olly Betts
5 * Copyright (C) 2006,2007,2009,2010 Lemur Consulting Ltd
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22 #include <config.h>
23 #include "remoteserver.h"
25 #include "xapian/constants.h"
26 #include "xapian/database.h"
27 #include "xapian/enquire.h"
28 #include "xapian/error.h"
29 #include "xapian/matchspy.h"
30 #include "xapian/query.h"
31 #include "xapian/valueiterator.h"
33 #include "safeerrno.h"
34 #include <signal.h>
35 #include <cstdlib>
37 #include "autoptr.h"
38 #include "length.h"
39 #include "matcher/multimatch.h"
40 #include "noreturn.h"
41 #include "omassert.h"
42 #include "realtime.h"
43 #include "serialise.h"
44 #include "serialise-double.h"
45 #include "serialise-error.h"
46 #include "str.h"
47 #include "stringutils.h"
48 #include "weight/weightinternal.h"
50 XAPIAN_NORETURN(static void throw_read_only());
51 static void
52 throw_read_only()
54 throw Xapian::InvalidOperationError("Server is read-only");
57 /// Class to throw when we receive the connection closing message.
58 struct ConnectionClosed { };
60 RemoteServer::RemoteServer(const std::vector<std::string> &dbpaths,
61 int fdin_, int fdout_,
62 double active_timeout_, double idle_timeout_,
63 bool writable_)
64 : RemoteConnection(fdin_, fdout_, std::string()),
65 db(NULL), wdb(NULL), writable(writable_),
66 active_timeout(active_timeout_), idle_timeout(idle_timeout_)
68 // Catch errors opening the database and propagate them to the client.
69 try {
70 Assert(!dbpaths.empty());
71 // We always open the database read-only to start with. If we're
72 // writable, the client can ask to be upgraded to write access once
73 // connected if it wants it.
74 db = new Xapian::Database(dbpaths[0]);
75 // Build a better description than Database::get_description() gives
76 // in the variable context. FIXME: improve Database::get_description()
77 // and then just use that instead.
78 context = dbpaths[0];
80 if (!writable) {
81 vector<std::string>::const_iterator i(dbpaths.begin());
82 for (++i; i != dbpaths.end(); ++i) {
83 db->add_database(Xapian::Database(*i));
84 context += ' ';
85 context += *i;
87 } else {
88 AssertEq(dbpaths.size(), 1); // Expecting exactly one database.
90 } catch (const Xapian::Error &err) {
91 // Propagate the exception to the client.
92 send_message(REPLY_EXCEPTION, serialise_error(err));
93 // And rethrow it so our caller can log it and close the connection.
94 throw;
97 #ifndef __WIN32__
98 // It's simplest to just ignore SIGPIPE. We'll still know if the
99 // connection dies because we'll get EPIPE back from write().
100 if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
101 throw Xapian::NetworkError("Couldn't set SIGPIPE to SIG_IGN", errno);
102 #endif
104 // Send greeting message.
105 msg_update(string());
108 RemoteServer::~RemoteServer()
110 delete db;
111 // wdb is either NULL or equal to db, so we shouldn't delete it too!
114 message_type
115 RemoteServer::get_message(double timeout, string & result,
116 message_type required_type)
118 double end_time = RealTime::end_time(timeout);
119 int type = RemoteConnection::get_message(result, end_time);
121 // Handle "shutdown connection" message here. Treat EOF here for a read-only
122 // database the same way since a read-only client just closes the
123 // connection when done.
124 if (type == MSG_SHUTDOWN || (type < 0 && wdb == NULL))
125 throw ConnectionClosed();
126 if (type < 0)
127 throw Xapian::NetworkError("Connection closed unexpectedly");
128 if (type >= MSG_MAX) {
129 string errmsg("Invalid message type ");
130 errmsg += str(type);
131 throw Xapian::NetworkError(errmsg);
133 if (required_type != MSG_MAX && type != int(required_type)) {
134 string errmsg("Expecting message type ");
135 errmsg += str(int(required_type));
136 errmsg += ", got ";
137 errmsg += str(int(type));
138 throw Xapian::NetworkError(errmsg);
140 return static_cast<message_type>(type);
143 void
144 RemoteServer::send_message(reply_type type, const string &message)
146 double end_time = RealTime::end_time(active_timeout);
147 unsigned char type_as_char = static_cast<unsigned char>(type);
148 RemoteConnection::send_message(type_as_char, message, end_time);
151 typedef void (RemoteServer::* dispatch_func)(const string &);
153 void
154 RemoteServer::run()
156 while (true) {
157 try {
158 /* This list needs to be kept in the same order as the list of
159 * message types in "remoteprotocol.h". Note that messages at the
160 * end of the list in "remoteprotocol.h" can be omitted if they
161 * don't correspond to dispatch actions.
163 static const dispatch_func dispatch[] = {
164 &RemoteServer::msg_allterms,
165 &RemoteServer::msg_collfreq,
166 &RemoteServer::msg_document,
167 &RemoteServer::msg_termexists,
168 &RemoteServer::msg_termfreq,
169 &RemoteServer::msg_valuestats,
170 &RemoteServer::msg_keepalive,
171 &RemoteServer::msg_doclength,
172 &RemoteServer::msg_query,
173 &RemoteServer::msg_termlist,
174 &RemoteServer::msg_positionlist,
175 &RemoteServer::msg_postlist,
176 &RemoteServer::msg_reopen,
177 &RemoteServer::msg_update,
178 &RemoteServer::msg_adddocument,
179 &RemoteServer::msg_cancel,
180 &RemoteServer::msg_deletedocumentterm,
181 &RemoteServer::msg_commit,
182 &RemoteServer::msg_replacedocument,
183 &RemoteServer::msg_replacedocumentterm,
184 &RemoteServer::msg_deletedocument,
185 &RemoteServer::msg_writeaccess,
186 &RemoteServer::msg_getmetadata,
187 &RemoteServer::msg_setmetadata,
188 &RemoteServer::msg_addspelling,
189 &RemoteServer::msg_removespelling,
190 0, // MSG_GETMSET - used during a conversation.
191 0, // MSG_SHUTDOWN - handled by get_message().
192 &RemoteServer::msg_openmetadatakeylist,
193 &RemoteServer::msg_freqs,
194 &RemoteServer::msg_uniqueterms,
197 string message;
198 size_t type = get_message(idle_timeout, message);
199 if (type >= sizeof(dispatch) / sizeof(dispatch[0]) || !dispatch[type]) {
200 string errmsg("Unexpected message type ");
201 errmsg += str(type);
202 throw Xapian::InvalidArgumentError(errmsg);
204 (this->*(dispatch[type]))(message);
205 } catch (const Xapian::NetworkTimeoutError & e) {
206 try {
207 // We've had a timeout, so the client may not be listening, so
208 // set the end_time to 1 and if we can't send the message right
209 // away, just exit and the client will cope.
210 send_message(REPLY_EXCEPTION, serialise_error(e), 1.0);
211 } catch (...) {
213 // And rethrow it so our caller can log it and close the
214 // connection.
215 throw;
216 } catch (const Xapian::NetworkError &) {
217 // All other network errors mean we are fatally confused and are
218 // unlikely to be able to communicate further across this
219 // connection. So we don't try to propagate the error to the
220 // client, but instead just rethrow the exception so our caller can
221 // log it and close the connection.
222 throw;
223 } catch (const Xapian::Error &e) {
224 // Propagate the exception to the client, then return to the main
225 // message handling loop.
226 send_message(REPLY_EXCEPTION, serialise_error(e));
227 } catch (ConnectionClosed &) {
228 return;
229 } catch (...) {
230 // Propagate an unknown exception to the client.
231 send_message(REPLY_EXCEPTION, string());
232 // And rethrow it so our caller can log it and close the
233 // connection.
234 throw;
239 void
240 RemoteServer::msg_allterms(const string &message)
242 string prev = message;
243 string reply;
245 const string & prefix = message;
246 const Xapian::TermIterator end = db->allterms_end(prefix);
247 for (Xapian::TermIterator t = db->allterms_begin(prefix); t != end; ++t) {
248 if (rare(prev.size() > 255))
249 prev.resize(255);
250 const string & v = *t;
251 size_t reuse = common_prefix_length(prev, v);
252 reply = encode_length(t.get_termfreq());
253 reply.append(1, char(reuse));
254 reply.append(v, reuse, string::npos);
255 send_message(REPLY_ALLTERMS, reply);
256 prev = v;
259 send_message(REPLY_DONE, string());
262 void
263 RemoteServer::msg_termlist(const string &message)
265 const char *p = message.data();
266 const char *p_end = p + message.size();
267 Xapian::docid did;
268 decode_length(&p, p_end, did);
270 send_message(REPLY_DOCLENGTH, encode_length(db->get_doclength(did)));
271 string prev;
272 const Xapian::TermIterator end = db->termlist_end(did);
273 for (Xapian::TermIterator t = db->termlist_begin(did); t != end; ++t) {
274 if (rare(prev.size() > 255))
275 prev.resize(255);
276 const string & v = *t;
277 size_t reuse = common_prefix_length(prev, v);
278 string reply = encode_length(t.get_wdf());
279 reply += encode_length(t.get_termfreq());
280 reply.append(1, char(reuse));
281 reply.append(v, reuse, string::npos);
282 send_message(REPLY_TERMLIST, reply);
283 prev = v;
286 send_message(REPLY_DONE, string());
289 void
290 RemoteServer::msg_positionlist(const string &message)
292 const char *p = message.data();
293 const char *p_end = p + message.size();
294 Xapian::docid did;
295 decode_length(&p, p_end, did);
296 string term(p, p_end - p);
298 Xapian::termpos lastpos = static_cast<Xapian::termpos>(-1);
299 const Xapian::PositionIterator end = db->positionlist_end(did, term);
300 for (Xapian::PositionIterator i = db->positionlist_begin(did, term);
301 i != end; ++i) {
302 Xapian::termpos pos = *i;
303 send_message(REPLY_POSITIONLIST, encode_length(pos - lastpos - 1));
304 lastpos = pos;
307 send_message(REPLY_DONE, string());
310 void
311 RemoteServer::msg_postlist(const string &message)
313 const string & term = message;
315 Xapian::doccount termfreq = db->get_termfreq(term);
316 Xapian::termcount collfreq = db->get_collection_freq(term);
317 send_message(REPLY_POSTLISTSTART, encode_length(termfreq) + encode_length(collfreq));
319 Xapian::docid lastdocid = 0;
320 const Xapian::PostingIterator end = db->postlist_end(term);
321 for (Xapian::PostingIterator i = db->postlist_begin(term);
322 i != end; ++i) {
324 Xapian::docid newdocid = *i;
325 string reply = encode_length(newdocid - lastdocid - 1);
326 reply += encode_length(i.get_wdf());
328 send_message(REPLY_POSTLISTITEM, reply);
329 lastdocid = newdocid;
332 send_message(REPLY_DONE, string());
335 void
336 RemoteServer::msg_writeaccess(const string & msg)
338 if (!writable)
339 throw_read_only();
341 int flags = Xapian::DB_OPEN;
342 const char *p = msg.c_str();
343 const char *p_end = p + msg.size();
344 if (p != p_end) {
345 unsigned flag_bits;
346 decode_length(&p, p_end, flag_bits);
347 flags |= flag_bits &~ Xapian::DB_ACTION_MASK_;
348 if (p != p_end) {
349 throw Xapian::NetworkError("Junk at end of MSG_WRITEACCESS");
353 wdb = new Xapian::WritableDatabase(context, flags);
354 delete db;
355 db = wdb;
356 msg_update(msg);
359 void
360 RemoteServer::msg_reopen(const string & msg)
362 if (!db->reopen()) {
363 send_message(REPLY_DONE, string());
364 return;
366 msg_update(msg);
369 void
370 RemoteServer::msg_update(const string &)
372 static const char protocol[2] = {
373 char(XAPIAN_REMOTE_PROTOCOL_MAJOR_VERSION),
374 char(XAPIAN_REMOTE_PROTOCOL_MINOR_VERSION)
376 string message(protocol, 2);
377 Xapian::doccount num_docs = db->get_doccount();
378 message += encode_length(num_docs);
379 message += encode_length(db->get_lastdocid() - num_docs);
380 Xapian::termcount doclen_lb = db->get_doclength_lower_bound();
381 message += encode_length(doclen_lb);
382 message += encode_length(db->get_doclength_upper_bound() - doclen_lb);
383 message += (db->has_positions() ? '1' : '0');
384 // FIXME: clumsy to reverse calculate total_len like this:
385 totlen_t total_len = totlen_t(db->get_avlength() * db->get_doccount() + .5);
386 message += encode_length(total_len);
387 //message += encode_length(db->get_total_length());
388 string uuid = db->get_uuid();
389 message += uuid;
390 send_message(REPLY_UPDATE, message);
393 void
394 RemoteServer::msg_query(const string &message_in)
396 const char *p = message_in.c_str();
397 const char *p_end = p + message_in.size();
399 // Unserialise the Query.
400 size_t len;
401 decode_length_and_check(&p, p_end, len);
402 Xapian::Query query(Xapian::Query::unserialise(string(p, len), reg));
403 p += len;
405 // Unserialise assorted Enquire settings.
406 Xapian::termcount qlen;
407 decode_length(&p, p_end, qlen);
409 Xapian::valueno collapse_max;
410 decode_length(&p, p_end, collapse_max);
412 Xapian::valueno collapse_key = Xapian::BAD_VALUENO;
413 if (collapse_max)
414 decode_length(&p, p_end, collapse_key);
416 if (p_end - p < 4 || *p < '0' || *p > '2') {
417 throw Xapian::NetworkError("bad message (docid_order)");
419 Xapian::Enquire::docid_order order;
420 order = static_cast<Xapian::Enquire::docid_order>(*p++ - '0');
422 Xapian::valueno sort_key;
423 decode_length(&p, p_end, sort_key);
425 if (*p < '0' || *p > '3') {
426 throw Xapian::NetworkError("bad message (sort_by)");
428 Xapian::Enquire::Internal::sort_setting sort_by;
429 sort_by = static_cast<Xapian::Enquire::Internal::sort_setting>(*p++ - '0');
431 if (*p < '0' || *p > '1') {
432 throw Xapian::NetworkError("bad message (sort_value_forward)");
434 bool sort_value_forward(*p++ != '0');
436 double time_limit = unserialise_double(&p, p_end);
438 int percent_cutoff = *p++;
439 if (percent_cutoff < 0 || percent_cutoff > 100) {
440 throw Xapian::NetworkError("bad message (percent_cutoff)");
443 double weight_cutoff = unserialise_double(&p, p_end);
444 if (weight_cutoff < 0) {
445 throw Xapian::NetworkError("bad message (weight_cutoff)");
448 // Unserialise the Weight object.
449 decode_length_and_check(&p, p_end, len);
450 string wtname(p, len);
451 p += len;
453 const Xapian::Weight * wttype = reg.get_weighting_scheme(wtname);
454 if (wttype == NULL) {
455 // Note: user weighting schemes should be registered by adding them to
456 // a Registry, and setting the context using
457 // RemoteServer::set_registry().
458 throw Xapian::InvalidArgumentError("Weighting scheme " +
459 wtname + " not registered");
462 decode_length_and_check(&p, p_end, len);
463 AutoPtr<Xapian::Weight> wt(wttype->unserialise(string(p, len)));
464 p += len;
466 // Unserialise the RSet object.
467 decode_length_and_check(&p, p_end, len);
468 Xapian::RSet rset = unserialise_rset(string(p, len));
469 p += len;
471 // Unserialise any MatchSpy objects.
472 vector<Xapian::Internal::opt_intrusive_ptr<Xapian::MatchSpy>> matchspies;
473 while (p != p_end) {
474 decode_length_and_check(&p, p_end, len);
475 string spytype(p, len);
476 const Xapian::MatchSpy * spyclass = reg.get_match_spy(spytype);
477 if (spyclass == NULL) {
478 throw Xapian::InvalidArgumentError("Match spy " + spytype +
479 " not registered");
481 p += len;
483 decode_length_and_check(&p, p_end, len);
484 matchspies.push_back(spyclass->unserialise(string(p, len), reg)->release());
485 p += len;
488 Xapian::Weight::Internal local_stats;
489 MultiMatch match(*db, query, qlen, &rset, collapse_max, collapse_key,
490 percent_cutoff, weight_cutoff, order,
491 sort_key, sort_by, sort_value_forward, time_limit,
492 local_stats, wt.get(), matchspies, false, false);
494 send_message(REPLY_STATS, serialise_stats(local_stats));
496 string message;
497 get_message(active_timeout, message, MSG_GETMSET);
498 p = message.c_str();
499 p_end = p + message.size();
501 Xapian::termcount first;
502 decode_length(&p, p_end, first);
503 Xapian::termcount maxitems;
504 decode_length(&p, p_end, maxitems);
506 Xapian::termcount check_at_least;
507 decode_length(&p, p_end, check_at_least);
509 message.erase(0, message.size() - (p_end - p));
510 AutoPtr<Xapian::Weight::Internal> total_stats(new Xapian::Weight::Internal);
511 unserialise_stats(message, *(total_stats.get()));
512 total_stats->set_bounds_from_db(*db);
514 Xapian::MSet mset;
515 match.get_mset(first, maxitems, check_at_least, mset, *(total_stats.get()), 0, 0);
516 mset.internal->stats = total_stats.release();
518 message.resize(0);
519 for (auto i : matchspies) {
520 string spy_results = i->serialise_results();
521 message += encode_length(spy_results.size());
522 message += spy_results;
524 message += serialise_mset(mset);
525 send_message(REPLY_RESULTS, message);
528 void
529 RemoteServer::msg_document(const string &message)
531 const char *p = message.data();
532 const char *p_end = p + message.size();
533 Xapian::docid did;
534 decode_length(&p, p_end, did);
536 Xapian::Document doc = db->get_document(did);
538 send_message(REPLY_DOCDATA, doc.get_data());
540 Xapian::ValueIterator i;
541 for (i = doc.values_begin(); i != doc.values_end(); ++i) {
542 string item = encode_length(i.get_valueno());
543 item += *i;
544 send_message(REPLY_VALUE, item);
546 send_message(REPLY_DONE, string());
549 void
550 RemoteServer::msg_keepalive(const string &)
552 // Ensure *our* database stays alive, as it may contain remote databases!
553 db->keep_alive();
554 send_message(REPLY_DONE, string());
557 void
558 RemoteServer::msg_termexists(const string &term)
560 send_message((db->term_exists(term) ? REPLY_TERMEXISTS : REPLY_TERMDOESNTEXIST), string());
563 void
564 RemoteServer::msg_collfreq(const string &term)
566 send_message(REPLY_COLLFREQ, encode_length(db->get_collection_freq(term)));
569 void
570 RemoteServer::msg_termfreq(const string &term)
572 send_message(REPLY_TERMFREQ, encode_length(db->get_termfreq(term)));
575 void
576 RemoteServer::msg_freqs(const string &term)
578 string msg = encode_length(db->get_termfreq(term));
579 msg += encode_length(db->get_collection_freq(term));
580 send_message(REPLY_FREQS, msg);
583 void
584 RemoteServer::msg_valuestats(const string & message)
586 const char *p = message.data();
587 const char *p_end = p + message.size();
588 while (p != p_end) {
589 Xapian::valueno slot;
590 decode_length(&p, p_end, slot);
591 string message_out;
592 message_out += encode_length(db->get_value_freq(slot));
593 string bound = db->get_value_lower_bound(slot);
594 message_out += encode_length(bound.size());
595 message_out += bound;
596 bound = db->get_value_upper_bound(slot);
597 message_out += encode_length(bound.size());
598 message_out += bound;
600 send_message(REPLY_VALUESTATS, message_out);
604 void
605 RemoteServer::msg_doclength(const string &message)
607 const char *p = message.data();
608 const char *p_end = p + message.size();
609 Xapian::docid did;
610 decode_length(&p, p_end, did);
611 send_message(REPLY_DOCLENGTH, encode_length(db->get_doclength(did)));
614 void
615 RemoteServer::msg_uniqueterms(const string &message)
617 const char *p = message.data();
618 const char *p_end = p + message.size();
619 Xapian::docid did;
620 decode_length(&p, p_end, did);
621 send_message(REPLY_UNIQUETERMS, encode_length(db->get_unique_terms(did)));
624 void
625 RemoteServer::msg_commit(const string &)
627 if (!wdb)
628 throw_read_only();
630 wdb->commit();
632 send_message(REPLY_DONE, string());
635 void
636 RemoteServer::msg_cancel(const string &)
638 if (!wdb)
639 throw_read_only();
641 // We can't call cancel since that's an internal method, but this
642 // has the same effect with minimal additional overhead.
643 wdb->begin_transaction(false);
644 wdb->cancel_transaction();
647 void
648 RemoteServer::msg_adddocument(const string & message)
650 if (!wdb)
651 throw_read_only();
653 Xapian::docid did = wdb->add_document(unserialise_document(message));
655 send_message(REPLY_ADDDOCUMENT, encode_length(did));
658 void
659 RemoteServer::msg_deletedocument(const string & message)
661 if (!wdb)
662 throw_read_only();
664 const char *p = message.data();
665 const char *p_end = p + message.size();
666 Xapian::docid did;
667 decode_length(&p, p_end, did);
669 wdb->delete_document(did);
671 send_message(REPLY_DONE, string());
674 void
675 RemoteServer::msg_deletedocumentterm(const string & message)
677 if (!wdb)
678 throw_read_only();
680 wdb->delete_document(message);
683 void
684 RemoteServer::msg_replacedocument(const string & message)
686 if (!wdb)
687 throw_read_only();
689 const char *p = message.data();
690 const char *p_end = p + message.size();
691 Xapian::docid did;
692 decode_length(&p, p_end, did);
694 wdb->replace_document(did, unserialise_document(string(p, p_end)));
697 void
698 RemoteServer::msg_replacedocumentterm(const string & message)
700 if (!wdb)
701 throw_read_only();
703 const char *p = message.data();
704 const char *p_end = p + message.size();
705 size_t len;
706 decode_length_and_check(&p, p_end, len);
707 string unique_term(p, len);
708 p += len;
710 Xapian::docid did = wdb->replace_document(unique_term, unserialise_document(string(p, p_end)));
712 send_message(REPLY_ADDDOCUMENT, encode_length(did));
715 void
716 RemoteServer::msg_getmetadata(const string & message)
718 send_message(REPLY_METADATA, db->get_metadata(message));
721 void
722 RemoteServer::msg_openmetadatakeylist(const string & message)
724 string prev = message;
725 string reply;
727 const string & prefix = message;
728 const Xapian::TermIterator end = db->metadata_keys_end(prefix);
729 Xapian::TermIterator t = db->metadata_keys_begin(prefix);
730 for (; t != end; ++t) {
731 if (rare(prev.size() > 255))
732 prev.resize(255);
733 const string & v = *t;
734 size_t reuse = common_prefix_length(prev, v);
735 reply.assign(1, char(reuse));
736 reply.append(v, reuse, string::npos);
737 send_message(REPLY_METADATAKEYLIST, reply);
738 prev = v;
740 send_message(REPLY_DONE, string());
743 void
744 RemoteServer::msg_setmetadata(const string & message)
746 if (!wdb)
747 throw_read_only();
748 const char *p = message.data();
749 const char *p_end = p + message.size();
750 size_t keylen;
751 decode_length_and_check(&p, p_end, keylen);
752 string key(p, keylen);
753 p += keylen;
754 string val(p, p_end - p);
755 wdb->set_metadata(key, val);
758 void
759 RemoteServer::msg_addspelling(const string & message)
761 if (!wdb)
762 throw_read_only();
763 const char *p = message.data();
764 const char *p_end = p + message.size();
765 Xapian::termcount freqinc;
766 decode_length(&p, p_end, freqinc);
767 wdb->add_spelling(string(p, p_end - p), freqinc);
770 void
771 RemoteServer::msg_removespelling(const string & message)
773 if (!wdb)
774 throw_read_only();
775 const char *p = message.data();
776 const char *p_end = p + message.size();
777 Xapian::termcount freqdec;
778 decode_length(&p, p_end, freqdec);
779 wdb->remove_spelling(string(p, p_end - p), freqdec);