Ticket #100: patch
File patch, 45.4 KB (added by , 18 years ago) |
---|
-
xapian-maintainer-tools/win32msvc/win32_matcher.mak
32 32 $(INTDIR)\multimatch.obj \ 33 33 $(INTDIR)\expand.obj \ 34 34 $(INTDIR)\stats.obj \ 35 $(INTDIR)\matchcmp.obj \ 35 36 $(INTDIR)\mergepostlist.obj \ 36 37 $(INTDIR)\msetpostlist.obj \ 37 38 $(INTDIR)\msetcmp.obj \ … … 166 167 $(CPP_PROJ) $** 167 168 << 168 169 170 "$(INTDIR)\matchcmp.obj" : ".\matchcmp.cc" 171 $(CPP) @<< 172 $(CPP_PROJ) $** 173 << 169 174 170 175 "$(INTDIR)\mergepostlist.obj" : ".\mergepostlist.cc" 171 176 $(CPP) @<< -
xapian-core/matcher/Makefile.mk
43 43 matcher/expandweight.cc\ 44 44 matcher/filterpostlist.cc\ 45 45 matcher/localmatch.cc\ 46 matcher/matchcmp.cc\ 46 47 matcher/mergepostlist.cc\ 47 48 matcher/msetcmp.cc\ 48 49 matcher/msetpostlist.cc\ -
xapian-core/matcher/multimatch.cc
86 86 bool sort_value_forward_, 87 87 Xapian::ErrorHandler * errorhandler_, 88 88 StatsGatherer * gatherer_, 89 const Xapian::Weight * weight_) 89 const Xapian::Weight * weight_, 90 const Xapian::MatchCmp * match_cmp_) 90 91 : gatherer(gatherer_), db(db_), query(query_), 91 92 collapse_key(collapse_key_), percent_cutoff(percent_cutoff_), 92 93 weight_cutoff(weight_cutoff_), order(order_), 93 94 sort_key(sort_key_), sort_by(sort_by_), 94 95 sort_value_forward(sort_value_forward_), 95 96 errorhandler(errorhandler_), weight(weight_), 97 match_cmp(match_cmp_), 96 98 is_remote(db.internal.size()) 97 99 { 98 100 DEBUGCALL(MATCH, void, "MultiMatch", db_ << ", " << query_ << ", " << … … 139 141 is_remote[i] = true; 140 142 rem_db->set_query(query, qlen, collapse_key, order, sort_key, 141 143 sort_by, sort_value_forward, percent_cutoff, 142 weight_cutoff, weight, subrsets[i]); 144 weight_cutoff, weight, match_cmp, 145 subrsets[i]); 143 146 bool decreasing_relevance = 144 147 (sort_by == REL || sort_by == REL_VAL); 145 148 smatch = new RemoteSubMatch(rem_db, gatherer.get(), … … 193 196 } 194 197 195 198 string 196 MultiMatch::get_collapse_key(PostList *pl, Xapian::docid did, 197 Xapian::valueno keyno, Xapian::Internal::RefCntPtr<Xapian::Document::Internal> &doc) 199 MultiMatch::get_collapse_key(PostList *pl, 200 Xapian::valueno keyno, 201 const Xapian::Internal::MSetItem & item) 198 202 { 199 DEBUGCALL(MATCH, string, "MultiMatch::get_collapse_key", pl << ", " << did << ", " << keyno << ", [doc]"); 203 DEBUGCALL(MATCH, string, "MultiMatch::get_collapse_key", pl << ", " << item.did << ", " << keyno << ", [doc]"); 204 205 // FIXME - this gets the collapse key from the postlist. This only 206 // actually happens for Mset postlists (or mergepostlists containing an 207 // mset postlist), since this is the only case where the collapse key has 208 // been read already. Instead, we should use the document object stored in 209 // the mset item. 210 200 211 const string *key = pl->get_collapse_key(); 201 212 if (key) RETURN(*key); 202 if (doc.get() == 0) { 203 unsigned int multiplier = db.internal.size(); 204 Assert(multiplier != 0); 205 Xapian::doccount n = (did - 1) % multiplier; // which actual database 206 Xapian::docid m = (did - 1) / multiplier + 1; // real docid in that database 207 208 Xapian::Internal::RefCntPtr<Xapian::Document::Internal> temp(db.internal[n]->open_document(m, true)); 209 doc = temp; 210 } 211 RETURN(doc->get_value(keyno)); 213 RETURN(item.get_document()->get_value(keyno)); 212 214 } 213 215 214 216 Xapian::weight … … 231 233 void 232 234 MultiMatch::get_mset(Xapian::doccount first, Xapian::doccount maxitems, 233 235 Xapian::doccount check_at_least, 234 Xapian::MSet & mset, const Xapian::MatchDecider *mdecider) 236 Xapian::MSet & mset, 237 const Xapian::MatchDecider *mdecider) 235 238 { 236 239 DEBUGCALL(MATCH, void, "MultiMatch::get_mset", first << ", " << maxitems 237 240 << ", " << check_at_least << ", ..."); … … 362 365 363 366 /// Comparison functor for sorting MSet 364 367 bool sort_forward = (order != Xapian::Enquire::DESCENDING); 365 MSetCmp mcmp(get_msetcmp_function(sort_by, sort_forward, sort_value_forward)); 368 mset_cmp cmpfn = get_msetcmp_function(sort_by, sort_forward, sort_value_forward); 369 MSetCmp mcmp(cmpfn, sort_key, match_cmp); 366 370 371 /// Number of databases making up the database we're using. 372 const unsigned int db_internal_size = db.internal.size(); 373 Assert(db_internal_size != 0); 374 367 375 // Perform query 368 376 369 377 // We form the mset in two stages. In the first we fill up our working … … 410 418 if (min_item.wt > 0.0) wt = pl->get_weight(); 411 419 412 420 DEBUGLINE(MATCH, "Candidate document id " << did << " wt " << wt); 413 Xapian::Internal::MSetItem new_item(wt, did); 414 if (sort_by != REL) { 415 const unsigned int multiplier = db.internal.size(); 416 Assert(multiplier != 0); 417 Xapian::doccount n = (new_item.did - 1) % multiplier; // which actual database 418 Xapian::docid m = (new_item.did - 1) / multiplier + 1; // real docid in that database 419 Xapian::Internal::RefCntPtr<Xapian::Document::Internal> doc(db.internal[n]->open_document(m, true)); 420 new_item.sort_key = doc->get_value(sort_key); 421 } 421 Xapian::Internal::RefCntPtr<Xapian::Database::Internal> internal_db = db.internal[((did - 1) % db_internal_size)]; // database holding document 422 Xapian::docid internal_docid = (did - 1) / db_internal_size + 1; // docid in internal_db 423 Xapian::Internal::MSetItem new_item(wt, did, internal_db, internal_docid); 422 424 423 425 // Test if item has high enough weight (or sort key) to get into 424 426 // proto-mset. 425 427 if (sort_by != REL || min_item.wt > 0.0) 426 428 if (!mcmp(new_item, min_item)) continue; 427 429 428 Xapian::Internal::RefCntPtr<Xapian::Document::Internal> doc;429 430 430 // Use the decision functor if any. 431 431 if (mdecider != NULL) { 432 432 const unsigned int multiplier = db.internal.size(); … … 435 435 // If the results are from a remote database, then the functor will 436 436 // already have been applied there so we can skip this step. 437 437 if (!is_remote[n]) { 438 if (doc.get() == 0) { 439 Xapian::docid m = (did - 1) / multiplier + 1; // real docid in that database 440 441 Xapian::Internal::RefCntPtr<Xapian::Document::Internal> temp(db.internal[n]->open_document(m, true)); 442 doc = temp; 443 } 444 Xapian::Document mydoc(doc.get()); 438 Xapian::Document mydoc(new_item.get_document().get()); 445 439 if (!mdecider->operator()(mydoc)) continue; 446 440 } 447 441 } … … 457 451 458 452 // Perform collapsing on key if requested. 459 453 if (collapse_key != Xapian::BAD_VALUENO) { 460 new_item.collapse_key = get_collapse_key(pl, did, collapse_key, 461 doc); 454 new_item.collapse_key = get_collapse_key(pl, collapse_key, new_item); 462 455 463 456 // Don't collapse on null key 464 457 if (!new_item.collapse_key.empty()) { -
xapian-core/matcher/msetcmp.h
20 20 21 21 #include "omenquireinternal.h" 22 22 23 class MSetCmp; 24 23 25 // typedef for MSetItem comparison function. 24 26 typedef bool (* mset_cmp)(const Xapian::Internal::MSetItem &, 25 const Xapian::Internal::MSetItem &); 27 const Xapian::Internal::MSetItem &, 28 const MSetCmp *); 26 29 27 30 /// Select the appropriate msetcmp function. 28 31 mset_cmp get_msetcmp_function(Xapian::Enquire::Internal::sort_setting sort_by, bool sort_forward, bool sort_value_forward); … … 31 34 class MSetCmp { 32 35 mset_cmp fn; 33 36 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 35 46 /// Return true if MSetItem a should be ranked above MSetItem b. 36 47 bool operator()(const Xapian::Internal::MSetItem &a, 37 38 return fn(a, b );48 const Xapian::Internal::MSetItem &b) const { 49 return fn(a, b, this); 39 50 } 40 51 }; 52 -
xapian-core/matcher/msetcmp.cc
20 20 21 21 #include <config.h> 22 22 #include "msetcmp.h" 23 #include <string> 23 24 24 25 /* We use templates to generate the 14 different comparison functions 25 26 * which we need. This avoids having to write them all out by hand. … … 46 47 // Order by relevance, then docid. 47 48 template<bool FORWARD_DID> bool 48 49 msetcmp_by_relevance(const Xapian::Internal::MSetItem &a, 49 const Xapian::Internal::MSetItem &b) 50 const Xapian::Internal::MSetItem &b, 51 const MSetCmp *) 50 52 { 51 53 if (a.wt > b.wt) return true; 52 54 if (a.wt < b.wt) return false; … … 56 58 // Order by value, then docid. 57 59 template<bool FORWARD_VALUE, bool FORWARD_DID> bool 58 60 msetcmp_by_value(const Xapian::Internal::MSetItem &a, 59 const Xapian::Internal::MSetItem &b) 61 const Xapian::Internal::MSetItem &b, 62 const MSetCmp * cmp) 60 63 { 61 64 if (!FORWARD_VALUE) { 62 65 // We want dummy did 0 to compare worse than any other. 63 66 if (a.did == 0) return false; 64 67 if (b.did == 0) return true; 65 68 } 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; 68 73 return msetcmp_by_did<FORWARD_DID, FORWARD_VALUE>(a, b); 69 74 } 70 75 71 76 // Order by value, then relevance, then docid. 72 77 template<bool FORWARD_VALUE, bool FORWARD_DID> bool 73 78 msetcmp_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) 75 81 { 76 82 if (!FORWARD_VALUE) { 77 83 // two special cases to make min_item compares work when did == 0 78 84 if (a.did == 0) return false; 79 85 if (b.did == 0) return true; 80 86 } 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; 83 91 if (a.wt > b.wt) return true; 84 92 if (a.wt < b.wt) return false; 85 93 return msetcmp_by_did<FORWARD_DID, FORWARD_VALUE>(a, b); … … 88 96 // Order by relevance, then value, then docid. 89 97 template<bool FORWARD_VALUE, bool FORWARD_DID> bool 90 98 msetcmp_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) 92 101 { 93 102 if (!FORWARD_VALUE) { 94 103 // two special cases to make min_item compares work when did == 0 … … 97 106 } 98 107 if (a.wt > b.wt) return true; 99 108 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; 102 113 return msetcmp_by_did<FORWARD_DID, FORWARD_VALUE>(a, b); 103 114 } 104 115 116 template<bool FORWARD_DID> bool 117 msetcmp_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 105 140 static mset_cmp mset_cmp_table[] = { 106 141 // Xapian::Enquire::Internal::REL 107 142 msetcmp_by_relevance<false>, … … 122 157 msetcmp_by_relevance_then_value<true, true>, 123 158 msetcmp_by_relevance_then_value<false, true>, 124 159 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> 126 166 }; 127 167 128 168 mset_cmp get_msetcmp_function(Xapian::Enquire::Internal::sort_setting sort_by, bool sort_forward, bool sort_value_forward) { -
xapian-core/tests/internaltest.cc
385 385 255.5, 386 386 256.125, 387 387 257.03125, 388 0.12268031290495594321l, 389 0.12268031290495592933l, 388 390 }; 389 391 390 392 check_double_serialisation(0.0); -
xapian-core/tests/api_db.cc
1264 1264 // set_sort_by_value 1265 1265 // set_sort_by_value_then_relevance 1266 1266 // set_sort_by_relevance_then_value 1267 // User comparison functions 1267 1268 static bool test_sortrel1() 1268 1269 { 1269 1270 Xapian::Enquire enquire(get_database("apitest_sortrel")); … … 1279 1280 const Xapian::docid order7[] = { 7,9,8,6,5,4,2,1,3 }; 1280 1281 const Xapian::docid order8[] = { 7,6,2,9,5,1,8,4,3 }; 1281 1282 const Xapian::docid order9[] = { 2,6,7,1,5,9,3,4,8 }; 1283 const Xapian::docid order10[] = { 7,9,4,5,6,1,2,3,8 }; 1284 const Xapian::docid order11[] = { 8,1,2,3,4,5,6,7,9 }; 1285 const Xapian::docid order12[] = { 9,7,6,5,4,3,2,1,8 }; 1286 const Xapian::docid order13[] = { 8,3,2,1,6,5,4,9,7 }; 1282 1287 1283 1288 Xapian::MSet mset; 1284 1289 size_t i; … … 1289 1294 TEST_EQUAL(*mset[i], order1[i]); 1290 1295 } 1291 1296 1297 enquire.set_sort_by_cmpfn(Xapian::IntegerMatchCmp(100)); 1298 1299 mset = enquire.get_mset(0, 10); 1300 TEST_EQUAL(mset.size(), sizeof(order10) / sizeof(Xapian::docid)); 1301 for (i = 0; i < sizeof(order10) / sizeof(Xapian::docid); ++i) { 1302 TEST_EQUAL(*mset[i], order10[i]); 1303 } 1304 1305 enquire.set_sort_by_cmpfn(Xapian::IntegerMatchCmp(100, true)); 1306 1307 mset = enquire.get_mset(0, 10); 1308 TEST_EQUAL(mset.size(), sizeof(order10) / sizeof(Xapian::docid)); 1309 for (i = 0; i < sizeof(order10) / sizeof(Xapian::docid); ++i) { 1310 TEST_EQUAL(*mset[i], order10[i]); 1311 } 1312 1313 enquire.set_sort_by_cmpfn(Xapian::IntegerMatchCmp(100, false)); 1314 1315 mset = enquire.get_mset(0, 10); 1316 TEST_EQUAL(mset.size(), sizeof(order11) / sizeof(Xapian::docid)); 1317 for (i = 0; i < sizeof(order11) / sizeof(Xapian::docid); ++i) { 1318 TEST_EQUAL(*mset[i], order11[i]); 1319 } 1320 1292 1321 enquire.set_sort_by_value_then_relevance(1); 1293 1322 1294 1323 mset = enquire.get_mset(0, 10); … … 1305 1334 TEST_EQUAL(*mset[i], order1[i]); 1306 1335 } 1307 1336 1337 enquire.set_sort_by_cmpfn(Xapian::IntegerMatchCmp(100)); 1338 enquire.set_docid_order(Xapian::Enquire::DESCENDING); 1339 1340 mset = enquire.get_mset(0, 10); 1341 TEST_EQUAL(mset.size(), sizeof(order12) / sizeof(Xapian::docid)); 1342 for (i = 0; i < sizeof(order12) / sizeof(Xapian::docid); ++i) { 1343 TEST_EQUAL(*mset[i], order12[i]); 1344 } 1345 1346 enquire.set_sort_by_cmpfn(Xapian::IntegerMatchCmp(100, true)); 1347 enquire.set_docid_order(Xapian::Enquire::DESCENDING); 1348 1349 mset = enquire.get_mset(0, 10); 1350 TEST_EQUAL(mset.size(), sizeof(order12) / sizeof(Xapian::docid)); 1351 for (i = 0; i < sizeof(order12) / sizeof(Xapian::docid); ++i) { 1352 TEST_EQUAL(*mset[i], order12[i]); 1353 } 1354 1355 enquire.set_sort_by_cmpfn(Xapian::IntegerMatchCmp(100, false)); 1356 enquire.set_docid_order(Xapian::Enquire::DESCENDING); 1357 1358 mset = enquire.get_mset(0, 10); 1359 TEST_EQUAL(mset.size(), sizeof(order13) / sizeof(Xapian::docid)); 1360 for (i = 0; i < sizeof(order13) / sizeof(Xapian::docid); ++i) { 1361 TEST_EQUAL(*mset[i], order13[i]); 1362 } 1363 1308 1364 enquire.set_sort_by_value_then_relevance(1); 1309 1365 enquire.set_docid_order(Xapian::Enquire::DESCENDING); 1310 1366 -
xapian-core/tests/harness/index_utils.cc
78 78 for (Xapian::valueno i = min(para.length(), size_t(10)); i >= 1; --i) { 79 79 doc.add_value(i, para.substr(i, 1)); 80 80 } 81 if (para.length() > 1 && para[0] == 'V') { 82 doc.add_value(100, Xapian::IntegerMatchCmp::int_to_value(atoi(para.c_str() + 1))); 83 } 81 84 82 85 Xapian::termcount pos = 0; 83 86 string::const_iterator end = para.begin(); -
xapian-core/tests/testdata/apitest_sortrel.txt
12 12 13 13 V1 woman 14 14 15 V1 man woman fish15 V10 man woman fish 16 16 17 17 V1 man woman -
xapian-core/include/xapian/enquire.h
31 31 #include <xapian/types.h> 32 32 #include <xapian/termiterator.h> 33 33 #include <xapian/visibility.h> 34 #include <xapian/document.h> 34 35 35 36 namespace Xapian { 36 37 … … 40 41 class MSetIterator; 41 42 class Query; 42 43 class Weight; 44 class MatchCmp; 43 45 46 namespace Internal { 47 class MSetItem; 48 } 49 44 50 /** A match set (MSet). 45 51 * This class represents (a portion of) the results of a query. 46 52 */ … … 812 818 void set_sort_by_relevance_then_value(Xapian::valueno sort_key, 813 819 bool ascending = true); 814 820 821 /** Set a comparison functor to be used when comparing matches. 822 * 823 * This overrides the sort options set by set_sort_by_relevance(), 824 * set_sort_by_value(), set_sort_by_relevance_then_value() and 825 * set_sort_by_value_then_relevance(). For matches which the 826 * comparison functor considers equal (ie, for which the functor 827 * returns 0 when comparing them), the matches will be compared in 828 * document ID order, as set by set_docid_order(); 829 * 830 * The comparison function will be called many times in the process of 831 * performing the search. Therefore, it is important that it is 832 * implemented as efficiently as possible. 833 * 834 * @param cmpfn_ The comparion function to use to compare matches. 835 */ 836 void set_sort_by_cmpfn(const MatchCmp &cmpfn_); 837 815 838 /** Get (a portion of) the match set for the current query. 816 839 * 817 840 * @param first the first item in the result set to return. … … 1220 1243 bool get_sumpart_needs_doclength() const; 1221 1244 }; 1222 1245 1246 /// An item matching a search. 1247 class XAPIAN_VISIBILITY_DEFAULT MatchItem { 1248 private: 1249 const Xapian::Internal::MSetItem & item; 1250 1251 public: 1252 MatchItem(const Xapian::Internal::MSetItem & item_) 1253 : item(item_) {} 1254 1255 /** Get the weight calculated. */ 1256 Xapian::weight get_wt() const; 1257 1258 /** Get the document id. */ 1259 Xapian::docid get_docid() const; 1260 1261 /** Get the document object, for access to document data, values, 1262 * and terms. */ 1263 Xapian::Document get_document() const; 1264 }; 1265 1266 /// Abstract base class for match comparison functors. 1267 class XAPIAN_VISIBILITY_DEFAULT MatchCmp { 1268 friend class Enquire; // So Enquire can clone us 1269 friend class ::RemoteServer; // So RemoteServer can clone us - FIXME 1270 protected: 1271 MatchCmp(const MatchCmp &); 1272 private: 1273 void operator=(MatchCmp &); 1274 1275 /** Return a new MatchCmp object of this type. 1276 * 1277 * A subclass called FooMatchCmp taking parameters param1 and param2 1278 * should implement this as: 1279 * 1280 * virtual FooMatchCmp * clone() const { 1281 * return new FooMatchCmp(param1, param2); 1282 * } 1283 */ 1284 virtual MatchCmp * clone() const = 0; 1285 1286 public: 1287 MatchCmp() { } 1288 virtual ~MatchCmp() { } 1289 1290 /** Name of the comparison functor. 1291 * 1292 * If the subclass is called FooMatchCmp, this should return "Foo". 1293 */ 1294 virtual std::string name() const = 0; 1295 1296 /// Serialise object parameters into a string. 1297 virtual std::string serialise() const = 0; 1298 1299 /** Create object given string serialisation returned by serialise(). 1300 * 1301 * If the string is not a valid serialised form, this should raise a 1302 * Xapian::NetworkError; 1303 */ 1304 virtual MatchCmp * unserialise(const std::string &s) const = 0; 1305 1306 /** Compare a potential Mset entry a to an entry b. 1307 * 1308 * This function should return an integer less than 0 if MatchItem a 1309 * should be ranked below MatchItem b, and greater than 0 if MatchItem 1310 * a should be ranked above MatchItem b. The function may return 0 if 1311 * it considers the two match items to be equal. 1312 * 1313 * The function will never be called with items which have identical 1314 * document IDs. 1315 * 1316 * The comparison function must be well behaved, such that: 1317 * 1318 * - it must produce the same result if called a second time on a 1319 * particular pair of documents 1320 * - if cmp(x,y) is less than 0 and cmp(y,z) is less than 0, cmp(x,z) 1321 * must be less than 0. 1322 * - if cmp(x,y) is less than 0, cmp(y,x) must be greater than 0. 1323 * 1324 * Bear in mind that the comparison will be performed very frequently 1325 * when performing a search, so must be as fast as possible. In 1326 * particular, although the MatchItem provides access to the document 1327 * object, it is recommended that only the value items stored in the 1328 * document object are accessed. 1329 */ 1330 virtual int cmp(const MatchItem & a, 1331 const MatchItem & b) const = 0; 1332 }; 1333 1334 /** Comparison function for comparing by value as integer. 1335 */ 1336 class XAPIAN_VISIBILITY_DEFAULT IntegerMatchCmp : public MatchCmp { 1337 Xapian::valueno sort_key; 1338 int direction; 1339 public: 1340 /** Return a new IntegerMatchCmp with the same parameters as this one. 1341 */ 1342 IntegerMatchCmp * clone() const { 1343 return new IntegerMatchCmp(sort_key, direction == 1); 1344 } 1345 IntegerMatchCmp(Xapian::valueno sort_key_, 1346 bool ascending = true) 1347 : sort_key(sort_key_), 1348 direction(ascending ? 1 : -1) { } 1349 IntegerMatchCmp() 1350 : sort_key(0), 1351 direction(1) { } 1352 ~IntegerMatchCmp() { } 1353 std::string name() const { return "Integer"; } 1354 1355 std::string serialise() const; 1356 IntegerMatchCmp * unserialise(const std::string &s) const; 1357 1358 int cmp(const MatchItem & a, 1359 const MatchItem & b) const; 1360 1361 /** Convert an integer to a string, suitable for comparing with this 1362 * comparison functor. 1363 */ 1364 static std::string int_to_value(int num); 1365 1366 /** Convert a string suitable for comparison with this comparison 1367 * functor to an integer. 1368 */ 1369 static int value_to_int(const std::string &s); 1370 }; 1371 1372 /** Comparison function for comparing by value as a double. 1373 */ 1374 class XAPIAN_VISIBILITY_DEFAULT DoubleMatchCmp : public MatchCmp { 1375 Xapian::valueno sort_key; 1376 int direction; 1377 public: 1378 /** Return a new DoubleMatchCmp with the same parameters as this one. 1379 */ 1380 DoubleMatchCmp * clone() const { 1381 return new DoubleMatchCmp(sort_key, direction == 1); 1382 } 1383 DoubleMatchCmp(Xapian::valueno sort_key_, 1384 bool ascending = true) 1385 : sort_key(sort_key_), 1386 direction(ascending ? 1 : -1) { } 1387 DoubleMatchCmp() 1388 : sort_key(0), 1389 direction(1) { } 1390 ~DoubleMatchCmp() { } 1391 std::string name() const { return "Double"; } 1392 1393 std::string serialise() const; 1394 DoubleMatchCmp * unserialise(const std::string &s) const; 1395 1396 int cmp(const MatchItem & a, 1397 const MatchItem & b) const; 1398 1399 /** Convert a double to a string, suitable for comparing with this 1400 * comparison functor. 1401 */ 1402 static std::string double_to_value(double num); 1403 1404 /** Convert a string suitable for comparison with this comparison 1405 * functor to a double. 1406 */ 1407 static double value_to_double(const std::string &s); 1408 }; 1409 1223 1410 } 1224 1411 1225 1412 #endif /* XAPIAN_INCLUDED_ENQUIRE_H */ -
xapian-core/net/serialise.cc
223 223 } 224 224 225 225 Xapian::MSet 226 unserialise_mset(const string &s) 226 unserialise_mset(const string &s, 227 Xapian::Internal::RefCntPtr<Xapian::Database::Internal> internal_db) 227 228 { 228 229 const char * p = s.data(); 229 230 const char * p_end = p + s.size(); … … 242 243 size_t len = decode_length(&p, p_end, true); 243 244 string key(p, len); 244 245 p += len; 245 items.push_back(Xapian::Internal::MSetItem(wt, did, key, 246 items.push_back(Xapian::Internal::MSetItem(wt, did, 247 internal_db, 248 did, 249 key, 246 250 decode_length(&p, p_end, false))); 247 251 } 248 252 -
xapian-core/net/remoteserver.cc
90 90 wtschemes[weight->name()] = weight; 91 91 weight = new Xapian::TradWeight(); 92 92 wtschemes[weight->name()] = weight; 93 94 // Register match comparison functions. 95 Xapian::MatchCmp * match_cmp; 96 match_cmp = new Xapian::IntegerMatchCmp(); 97 match_cmps[match_cmp->name()] = match_cmp; 98 match_cmp = new Xapian::DoubleMatchCmp(); 99 match_cmps[match_cmp->name()] = match_cmp; 93 100 } 94 101 95 102 RemoteServer::~RemoteServer() … … 98 105 for (i = wtschemes.begin(); i != wtschemes.end(); ++i) { 99 106 delete i->second; 100 107 } 108 map<string, Xapian::MatchCmp*>::const_iterator j; 109 for (j = match_cmps.begin(); j != match_cmps.end(); ++j) { 110 delete j->second; 111 } 101 112 } 102 113 103 114 message_type … … 322 333 323 334 Xapian::valueno sort_key = decode_length(&p, p_end, false); 324 335 325 if (*p < '0' || *p > ' 3') {336 if (*p < '0' || *p > '4') { 326 337 throw Xapian::NetworkError("bad message (sort_by)"); 327 338 } 328 339 Xapian::Enquire::Internal::sort_setting sort_by; … … 356 367 AutoPtr<Xapian::Weight> wt(i->second->unserialise(string(p, len))); 357 368 p += len; 358 369 370 // Unserialise the MatchCmp object 371 len = decode_length(&p, p_end, true); 372 AutoPtr<Xapian::MatchCmp> mcmp; 373 if (len > 0) { 374 map<string, Xapian::MatchCmp *>::const_iterator j; 375 j = match_cmps.find(string(p, len)); 376 if (j == match_cmps.end()) { 377 throw Xapian::InvalidArgumentError("MatchCmp type " + string(p, len) + " not registered"); 378 } 379 p += len; 380 381 len = decode_length(&p, p_end, true); 382 mcmp = j->second->unserialise(string(p, len)); 383 p += len; 384 } 385 359 386 // Unserialise the RSet object. 360 387 Xapian::RSet rset = unserialise_rset(string(p, p_end - p)); 361 388 … … 364 391 MultiMatch match(*db, query.get(), qlen, rset, collapse_key, 365 392 percent_cutoff, weight_cutoff, order, 366 393 sort_key, sort_by, sort_value_forward, 367 NULL, gatherer, wt.get() );394 NULL, gatherer, wt.get(), mcmp.get()); 368 395 369 396 send_message(REPLY_STATS, serialise_stats(gatherer->get_local_stats())); 370 397 -
xapian-core/common/remote-database.h
124 124 * @param percent_cutoff Percentage cutoff. 125 125 * @param weight_cutoff Weight cutoff. 126 126 * @param wtscheme Weighting scheme. 127 * @param sort_cmpfn Comparison functor for sorting. 127 128 * @param omrset The rset. 128 129 */ 129 130 void set_query(const Xapian::Query::Internal *query, … … 135 136 bool sort_value_forward, 136 137 int percent_cutoff, Xapian::weight weight_cutoff, 137 138 const Xapian::Weight *wtscheme, 139 const Xapian::MatchCmp *sort_cmpfn, 138 140 const Xapian::RSet &omrset); 139 141 140 142 /** Get the Stats from the remote server. -
xapian-core/common/omenquireinternal.h
31 31 #include <math.h> 32 32 #include <map> 33 33 #include <set> 34 #include "document.h" 34 35 35 36 using namespace std; 36 37 … … 64 65 }; 65 66 66 67 /** An item resulting from a query. 68 * 67 69 * This item contains the document id, and the weight calculated for 68 70 * the document. 71 * 72 * It also provides access to the underlying document, 73 * and stores information needed to support collapse operations. 69 74 */ 70 75 class MSetItem { 71 76 public: 72 77 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) {} 74 82 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_) {} 77 92 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_, 79 98 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_) {} 82 105 83 106 /** Weight calculated. */ 84 107 Xapian::weight wt; 85 108 86 /** Document id . */109 /** Document id (as publically displayed). */ 87 110 Xapian::docid did; 88 111 89 112 /** Value which was used to collapse upon. … … 96 119 * for this item, the value will be a null string. Only one instance 97 120 * of each key value (apart from the null string) will be present in 98 121 * the items in the returned Xapian::MSet. 122 * 123 * FIXME - just use doc. 99 124 */ 100 125 string collapse_key; 101 126 102 /** Count of collapses done on collapse_key so far 127 /** Count of collapses done on collapse_key so far. 103 128 * 104 * This is normally 0, and goes up for each collapse done105 * It is not neccessarily an indication of how many collapses106 * might be done if an exhaustive match was done129 * 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 107 132 */ 108 133 Xapian::doccount collapse_count; 109 134 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 } 113 157 114 158 /** Returns a string representing the mset item. 115 159 * Introspection method. 116 160 */ 117 161 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; 118 173 }; 119 174 120 175 } … … 140 195 void operator=(const Internal &); 141 196 142 197 public: 143 typedef enum { REL, VAL, VAL_REL, REL_VAL } sort_setting;198 typedef enum { REL, VAL, VAL_REL, REL_VAL, USER } sort_setting; 144 199 145 200 Xapian::valueno collapse_key; 146 201 … … 153 208 Xapian::valueno sort_key; 154 209 sort_setting sort_by; 155 210 bool sort_value_forward; 211 MatchCmp * sort_cmpfn; 156 212 157 213 /** The error handler, if set. (0 if not set). 158 214 */ -
xapian-core/common/multimatch.h
64 64 /// Weighting scheme 65 65 const Xapian::Weight * weight; 66 66 67 /// User supplied match comparison function 68 const Xapian::MatchCmp * match_cmp; 69 67 70 /** Internal flag to note that w_max needs to be recalculated 68 71 * while query is running. 69 72 */ … … 74 77 75 78 /// get the collapse key 76 79 string get_collapse_key(PostList *pl, 77 Xapian:: docid did, Xapian::valueno keyno,78 Xapian::Internal::RefCntPtr<Xapian::Document::Internal> &doc);80 Xapian::valueno keyno, 81 const Xapian::Internal::MSetItem & item); 79 82 80 83 /** get the maxweight that the postlist pl may return, calling 81 84 * recalc_maxweight if recalculate_w_max is set, and unsetting it. … … 115 118 bool sort_value_forward_, 116 119 Xapian::ErrorHandler * errorhandler, 117 120 StatsGatherer * gatherer_, 118 const Xapian::Weight *wtscheme); 121 const Xapian::Weight *wtscheme, 122 const Xapian::MatchCmp * match_cmp_); 119 123 120 124 void get_mset(Xapian::doccount first, 121 125 Xapian::doccount maxitems, -
xapian-core/common/remoteprotocol.h
30 30 // 25: Support for delete_document and replace_document with unique term 31 31 // 26: Tweak delete_document with unique term; delta encode rset and termpos 32 32 // 27: Support for postlists (always passes the whole list across) 33 #define XAPIAN_REMOTE_PROTOCOL_VERSION 27 33 // 28: MatchCmp serialisation 34 #define XAPIAN_REMOTE_PROTOCOL_VERSION 28 34 35 35 36 /// Message types (client -> server). 36 37 enum message_type { -
xapian-core/common/remoteserver.h
68 68 /// Registered weighting schemes. 69 69 map<string, Xapian::Weight *> wtschemes; 70 70 71 /// Registered match cmpfns. 72 map<string, Xapian::MatchCmp *> match_cmps; 73 71 74 /// Initialisation code needed by both ctors. 72 75 void initialise(); 73 76 … … 186 189 void register_weighting_scheme(const Xapian::Weight &wt) { 187 190 wtschemes[wt.name()] = wt.clone(); 188 191 } 192 193 /// Register a user-defined match comparision class. 194 void register_match_cmp(const Xapian::MatchCmp &match_cmp) { 195 match_cmps[match_cmp.name()] = match_cmp.clone(); 196 } 189 197 }; 190 198 191 199 #endif // XAPIAN_INCLUDED_REMOTESERVER_H -
xapian-core/common/serialise.h
24 24 #include <xapian/visibility.h> 25 25 26 26 #include <string> 27 #include <xapian/database.h> 27 28 28 29 // Forward class declarations: 29 30 … … 115 116 * 116 117 * @return The unserialised Xapian::MSet object. 117 118 */ 118 Xapian::MSet unserialise_mset(const std::string &s); 119 Xapian::MSet unserialise_mset(const std::string &s, 120 Xapian::Internal::RefCntPtr<Xapian::Database::Internal> internal_db); 119 121 120 122 /** Serialise a Xapian::RSet object. 121 123 * -
xapian-core/api/omenquire.cc
644 644 : db(db_), query(), collapse_key(Xapian::BAD_VALUENO), 645 645 order(Enquire::ASCENDING), percent_cutoff(0), weight_cutoff(0), 646 646 sort_key(Xapian::BAD_VALUENO), sort_by(REL), sort_value_forward(true), 647 errorhandler(errorhandler_), weight(0)647 sort_cmpfn(0), errorhandler(errorhandler_), weight(0) 648 648 { 649 649 } 650 650 651 651 Enquire::Internal::~Internal() 652 652 { 653 653 delete weight; 654 delete sort_cmpfn; 654 655 weight = 0; 655 656 } 656 657 … … 685 686 ::MultiMatch match(db, query.internal.get(), qlen, RSet(), collapse_key, 686 687 percent_cutoff, weight_cutoff, 687 688 order, sort_key, sort_by, sort_value_forward, 688 errorhandler, new LocalStatsGatherer(), weight );689 errorhandler, new LocalStatsGatherer(), weight, sort_cmpfn); 689 690 // Run query and put results into supplied Xapian::MSet object. 690 691 match.get_mset(first, maxitems, check_at_least, retval, mdecider); 691 692 } else { 692 693 ::MultiMatch match(db, query.internal.get(), qlen, *rset, collapse_key, 693 694 percent_cutoff, weight_cutoff, 694 695 order, sort_key, sort_by, sort_value_forward, 695 errorhandler, new LocalStatsGatherer(), weight );696 errorhandler, new LocalStatsGatherer(), weight, sort_cmpfn); 696 697 // Run query and put results into supplied Xapian::MSet object. 697 698 match.get_mset(first, maxitems, check_at_least, retval, mdecider); 698 699 } … … 909 910 { 910 911 DEBUGAPICALL(void, "Xapian::Enquire::set_weighting_scheme", "[Weight]"); 911 912 delete internal->weight; 913 internal->weight = NULL; // Set to NULL before cloning, to avoid double free if clone() throws an exception. 912 914 internal->weight = weight_.clone(); 913 915 } 914 916 … … 935 937 Enquire::set_sort_by_relevance() 936 938 { 937 939 internal->sort_by = Internal::REL; 940 delete internal->sort_cmpfn; 941 internal->sort_cmpfn = NULL; 938 942 } 939 943 940 944 void … … 943 947 internal->sort_key = sort_key; 944 948 internal->sort_by = Internal::VAL; 945 949 internal->sort_value_forward = ascending; 950 delete internal->sort_cmpfn; 951 internal->sort_cmpfn = NULL; 946 952 } 947 953 948 954 void … … 952 958 internal->sort_key = sort_key; 953 959 internal->sort_by = Internal::VAL_REL; 954 960 internal->sort_value_forward = ascending; 961 delete internal->sort_cmpfn; 962 internal->sort_cmpfn = NULL; 955 963 } 956 964 957 965 void … … 961 969 internal->sort_key = sort_key; 962 970 internal->sort_by = Internal::REL_VAL; 963 971 internal->sort_value_forward = ascending; 972 delete internal->sort_cmpfn; 973 internal->sort_cmpfn = NULL; 964 974 } 965 975 976 void 977 Enquire::set_sort_by_cmpfn(const MatchCmp &cmpfn_) 978 { 979 DEBUGAPICALL(void, "Xapian::Enquire::set_sort_by_cmpfn", "[cmpfn:" << cmpfn_.name() << "(" << cmpfn_.serialise() << ")]"); 980 delete internal->sort_cmpfn; 981 internal->sort_by = Internal::USER; 982 internal->sort_cmpfn = NULL; // Set to NULL before cloning, to avoid double free if clone() throws an exception. 983 internal->sort_cmpfn = cmpfn_.clone(); 984 } 985 966 986 MSet 967 987 Enquire::get_mset(Xapian::doccount first, Xapian::doccount maxitems, 968 988 Xapian::doccount check_at_least, const RSet *rset, -
xapian-core/backends/remote/remote-database.cc
409 409 bool sort_value_forward, 410 410 int percent_cutoff, Xapian::weight weight_cutoff, 411 411 const Xapian::Weight *wtscheme, 412 const Xapian::MatchCmp *sort_cmpfn, 412 413 const Xapian::RSet &omrset) 413 414 { 414 415 string tmp = query->serialise(); … … 425 426 message += char(percent_cutoff); 426 427 message += serialise_double(weight_cutoff); 427 428 429 // Serialise the weight scheme 428 430 tmp = wtscheme->name(); 429 431 message += encode_length(tmp.size()); 430 432 message += tmp; 431 432 433 tmp = wtscheme->serialise(); 433 434 message += encode_length(tmp.size()); 434 435 message += tmp; 435 436 437 // Serialise the match cmp object 438 if (sort_cmpfn == NULL) { 439 message += encode_length(0); 440 } else { 441 tmp = sort_cmpfn->name(); 442 message += encode_length(tmp.size()); 443 message += tmp; 444 tmp = sort_cmpfn->serialise(); 445 message += encode_length(tmp.size()); 446 message += tmp; 447 } 448 436 449 message += serialise_rset(omrset); 437 450 438 451 send_message(MSG_QUERY, message); … … 466 479 { 467 480 string message; 468 481 get_message(message, REPLY_RESULTS); 469 mset = unserialise_mset(message );482 mset = unserialise_mset(message, this); 470 483 } 471 484 472 485 void -
xapian-bindings/csharp/Makefile.am
23 23 ExpandDecider.cs \ 24 24 Flint.cs \ 25 25 InMemory.cs \ 26 MatchCmp.cs \ 27 MatchItem.cs \ 26 28 MatchDecider.cs \ 27 29 MSet.cs \ 28 30 MSetIterator.cs \ … … 35 37 RSet.cs \ 36 38 SWIGTYPE_p_std__vectorTstd__string_t.cs \ 37 39 SWIGTYPE_p_std__vectorTXapian__Query_t.cs \ 40 SWIGTYPE_p_Xapian__Internal__MSetItem.cs \ 38 41 SimpleStopper.cs \ 39 42 Stem.cs \ 40 43 Stopper.cs \ -
xapian-bindings/xapian.i
156 156 int xapian_revision(); 157 157 158 158 class Weight; 159 class MatchCmp; 159 160 class Stopper; 160 161 161 162 // from xapian/positioniterator.h … … 482 483 bool ascending = true); 483 484 void set_sort_by_relevance_then_value(Xapian::valueno sort_key, 484 485 bool ascending = true); 486 void set_sort_by_cmpfn(const MatchCmp& cmpfn); 485 487 486 488 #ifdef XAPIAN_SWIG_DIRECTORS 487 489 MSet get_mset(doccount first, … … 635 637 bool get_sumpart_needs_doclength() const; 636 638 }; 637 639 640 class MatchItem { 641 public: 642 MatchItem(const Xapian::Internal::MSetItem & item_); 643 Xapian::weight get_wt() const; 644 Xapian::docid get_docid() const; 645 Xapian::Document get_document() const; 646 }; 647 648 class MatchCmp { 649 /* SWIG doesn't handle this: 650 private: 651 virtual MatchCmp * clone() const = 0; */ 652 public: 653 virtual ~MatchCmp(); 654 655 virtual std::string name() const = 0; 656 virtual std::string serialise() const = 0; 657 virtual MatchCmp * unserialise(const std::string &s) const = 0; 658 659 virtual int cmp(const MatchItem & a, 660 const MatchItem & b) const = 0; 661 }; 662 663 %warnfilter(842) IntegerMatchCmp::unserialise; 664 class IntegerMatchCmp : public MatchCmp { 665 public: 666 IntegerMatchCmp * clone() const; 667 IntegerMatchCmp(Xapian::valueno sort_key_, 668 bool ascending = true); 669 IntegerMatchCmp(); 670 ~IntegerMatchCmp(); 671 672 std::string name() const; 673 std::string serialise() const; 674 IntegerMatchCmp * unserialise(const std::string &s) const; 675 676 int cmp(const MatchItem & a, 677 const MatchItem & b) const; 678 679 static std::string int_to_value(int num); 680 static int value_to_int(const std::string &s); 681 }; 682 683 %warnfilter(842) DoubleMatchCmp::unserialise; 684 class DoubleMatchCmp : public MatchCmp { 685 public: 686 DoubleMatchCmp * clone() const; 687 DoubleMatchCmp(Xapian::valueno sort_key_, 688 bool ascending = true); 689 DoubleMatchCmp(); 690 ~DoubleMatchCmp(); 691 692 std::string name() const; 693 std::string serialise() const; 694 DoubleMatchCmp * unserialise(const std::string &s) const; 695 696 int cmp(const MatchItem & a, 697 const MatchItem & b) const; 698 699 static std::string double_to_value(double num); 700 static double value_to_double(const std::string &s); 701 }; 702 703 638 704 // xapian/database.h 639 705 640 706 class Database {