00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024 #include <config.h>
00025
00026 #include "autoptr.h"
00027
00028 #include <xapian/error.h>
00029 #include <xapian/positioniterator.h>
00030 #include <xapian/postingiterator.h>
00031 #include <xapian/termiterator.h>
00032 #include <xapian/unicode.h>
00033
00034 #include "omassert.h"
00035 #include "omdebug.h"
00036 #include "../backends/multi/multi_postlist.h"
00037 #include "../backends/multi/multi_termlist.h"
00038 #include "alltermslist.h"
00039 #include "multialltermslist.h"
00040 #include "database.h"
00041 #include "editdistance.h"
00042 #include "ortermlist.h"
00043 #include "noreturn.h"
00044
00045 #include <stdlib.h>
00046
00047 #include <cstring>
00048 #include <vector>
00049
00050 using namespace std;
00051
00052 namespace Xapian {
00053
00054 Database::Database()
00055 {
00056 DEBUGAPICALL(void, "Database::Database", "");
00057 }
00058
00059 Database::Database(Database::Internal *internal_)
00060 {
00061 DEBUGAPICALL(void, "Database::Database", "Database::Internal");
00062 Xapian::Internal::RefCntPtr<Database::Internal> newi(internal_);
00063 internal.push_back(newi);
00064 }
00065
00066 Database::Database(const Database &other)
00067 {
00068 DEBUGAPICALL(void, "Database::Database", "Database");
00069 internal = other.internal;
00070 }
00071
00072 void
00073 Database::operator=(const Database &other)
00074 {
00075 DEBUGAPICALL(void, "Database::operator=", "Database");
00076 if (this == &other) {
00077 DEBUGLINE(API, "Database assigned to itself");
00078 return;
00079 }
00080
00081 internal = other.internal;
00082 }
00083
00084 Database::~Database()
00085 {
00086 DEBUGAPICALL(void, "Database::~Database", "");
00087 }
00088
00089 void
00090 Database::reopen()
00091 {
00092 DEBUGAPICALL(void, "Database::reopen", "");
00093 vector<Xapian::Internal::RefCntPtr<Database::Internal> >::iterator i;
00094 for (i = internal.begin(); i != internal.end(); ++i) {
00095 (*i)->reopen();
00096 }
00097 }
00098
00099 void
00100 Database::add_database(const Database & database)
00101 {
00102 DEBUGAPICALL(void, "Database::add_database", "Database");
00103 if (this == &database) {
00104 DEBUGLINE(API, "Database added to itself");
00105 throw Xapian::InvalidArgumentError("Can't add an Database to itself");
00106 return;
00107 }
00108 vector<Xapian::Internal::RefCntPtr<Database::Internal> >::const_iterator i;
00109 for (i = database.internal.begin(); i != database.internal.end(); ++i) {
00110 internal.push_back(*i);
00111 }
00112 }
00113
00114 PostingIterator
00115 Database::postlist_begin(const string &tname) const
00116 {
00117 DEBUGAPICALL(PostingIterator, "Database::postlist_begin", tname);
00118
00119
00120
00121
00122
00123
00124 if (internal.size() == 1)
00125 RETURN(PostingIterator(internal[0]->open_post_list(tname)));
00126
00127 vector<LeafPostList *> pls;
00128 try {
00129 vector<Xapian::Internal::RefCntPtr<Database::Internal> >::const_iterator i;
00130 for (i = internal.begin(); i != internal.end(); ++i) {
00131 pls.push_back((*i)->open_post_list(tname));
00132 pls.back()->next();
00133 }
00134 Assert(pls.begin() != pls.end());
00135 } catch (...) {
00136 vector<LeafPostList *>::iterator i;
00137 for (i = pls.begin(); i != pls.end(); ++i) {
00138 delete *i;
00139 *i = 0;
00140 }
00141 throw;
00142 }
00143
00144 RETURN(PostingIterator(new MultiPostList(pls, *this)));
00145 }
00146
00147 TermIterator
00148 Database::termlist_begin(Xapian::docid did) const
00149 {
00150 DEBUGAPICALL(TermIterator, "Database::termlist_begin", did);
00151 if (did == 0) throw InvalidArgumentError("Document ID 0 is invalid");
00152
00153 unsigned int multiplier = internal.size();
00154 TermList *tl;
00155 if (multiplier == 1) {
00156
00157
00158 tl = internal[0]->open_term_list(did);
00159 } else {
00160 Assert(multiplier != 0);
00161 Xapian::doccount n = (did - 1) % multiplier;
00162 Xapian::docid m = (did - 1) / multiplier + 1;
00163
00164 tl = new MultiTermList(internal[n]->open_term_list(m), *this, n);
00165 }
00166 RETURN(TermIterator(tl));
00167 }
00168
00169 TermIterator
00170 Database::allterms_begin() const
00171 {
00172 return allterms_begin("");
00173 }
00174
00175 TermIterator
00176 Database::allterms_begin(const std::string & prefix) const
00177 {
00178 DEBUGAPICALL(TermIterator, "Database::allterms_begin", "");
00179 if (internal.empty()) RETURN(TermIterator(NULL));
00180
00181 if (internal.size() == 1)
00182 RETURN(TermIterator(internal[0]->open_allterms(prefix)));
00183
00184 RETURN(TermIterator(new MultiAllTermsList(internal, prefix)));
00185 }
00186
00187 bool
00188 Database::has_positions() const
00189 {
00190 DEBUGAPICALL(bool, "Database::has_positions", "");
00191
00192 vector<Xapian::Internal::RefCntPtr<Database::Internal> >::const_iterator i;
00193 for (i = internal.begin(); i != internal.end(); ++i) {
00194 if ((*i)->has_positions()) RETURN(true);
00195 }
00196 RETURN(false);
00197 }
00198
00199 PositionIterator
00200 Database::positionlist_begin(Xapian::docid did, const string &tname) const
00201 {
00202 DEBUGAPICALL(PositionIterator, "Database::positionlist_begin",
00203 did << ", " << tname);
00204 if (tname.empty())
00205 throw InvalidArgumentError("Zero length terms are invalid");
00206 if (did == 0) throw InvalidArgumentError("Document ID 0 is invalid");
00207
00208 unsigned int multiplier = internal.size();
00209 Assert(multiplier != 0);
00210 Xapian::doccount n = (did - 1) % multiplier;
00211 Xapian::docid m = (did - 1) / multiplier + 1;
00212
00213 RETURN(PositionIterator(internal[n]->open_position_list(m, tname)));
00214 }
00215
00216 Xapian::doccount
00217 Database::get_doccount() const
00218 {
00219 DEBUGAPICALL(Xapian::doccount, "Database::get_doccount", "");
00220 Xapian::doccount docs = 0;
00221 vector<Xapian::Internal::RefCntPtr<Database::Internal> >::const_iterator i;
00222 for (i = internal.begin(); i != internal.end(); ++i) {
00223 docs += (*i)->get_doccount();
00224 }
00225 RETURN(docs);
00226 }
00227
00228 Xapian::docid
00229 Database::get_lastdocid() const
00230 {
00231 DEBUGAPICALL(Xapian::docid, "Database::get_lastdocid", "");
00232 Xapian::docid did = 0;
00233
00234 unsigned int multiplier = internal.size();
00235 Assert(multiplier != 0);
00236 for (Xapian::doccount i = 0; i < multiplier; ++i) {
00237 Xapian::docid did_i = internal[i]->get_lastdocid();
00238 if (did_i) did = std::max(did, (did_i - 1) * multiplier + i + 1);
00239 }
00240 RETURN(did);
00241 }
00242
00243 Xapian::doclength
00244 Database::get_avlength() const
00245 {
00246 DEBUGAPICALL(Xapian::doclength, "Database::get_avlength", "");
00247 Xapian::doccount docs = 0;
00248 Xapian::doclength totlen = 0;
00249
00250 vector<Xapian::Internal::RefCntPtr<Database::Internal> >::const_iterator i;
00251 for (i = internal.begin(); i != internal.end(); ++i) {
00252 Xapian::doccount db_doccount = (*i)->get_doccount();
00253 docs += db_doccount;
00254 totlen += (*i)->get_avlength() * db_doccount;
00255 }
00256 DEBUGLINE(UNKNOWN, "get_avlength() = " << totlen << " / " << docs <<
00257 " (from " << internal.size() << " dbs)");
00258
00259 if (docs == 0) RETURN(0.0);
00260 RETURN(totlen / docs);
00261 }
00262
00263 Xapian::doccount
00264 Database::get_termfreq(const string & tname) const
00265 {
00266 DEBUGAPICALL(Xapian::doccount, "Database::get_termfreq", tname);
00267 if (tname.empty()) RETURN(get_doccount());
00268
00269 Xapian::doccount tf = 0;
00270 vector<Xapian::Internal::RefCntPtr<Database::Internal> >::const_iterator i;
00271 for (i = internal.begin(); i != internal.end(); i++) {
00272 tf += (*i)->get_termfreq(tname);
00273 }
00274 RETURN(tf);
00275 }
00276
00277 Xapian::termcount
00278 Database::get_collection_freq(const string & tname) const
00279 {
00280 DEBUGAPICALL(Xapian::termcount, "Database::get_collection_freq", tname);
00281 if (tname.empty()) RETURN(get_doccount());
00282
00283 Xapian::termcount cf = 0;
00284 vector<Xapian::Internal::RefCntPtr<Database::Internal> >::const_iterator i;
00285 for (i = internal.begin(); i != internal.end(); i++) {
00286 cf += (*i)->get_collection_freq(tname);
00287 }
00288 RETURN(cf);
00289 }
00290
00291 Xapian::doclength
00292 Database::get_doclength(Xapian::docid did) const
00293 {
00294 DEBUGAPICALL(Xapian::doclength, "Database::get_doclength", did);
00295 if (did == 0) throw InvalidArgumentError("Document ID 0 is invalid");
00296
00297 unsigned int multiplier = internal.size();
00298 Assert(multiplier != 0);
00299 Xapian::doccount n = (did - 1) % multiplier;
00300 Xapian::docid m = (did - 1) / multiplier + 1;
00301 RETURN(internal[n]->get_doclength(m));
00302 }
00303
00304 Document
00305 Database::get_document(Xapian::docid did) const
00306 {
00307 DEBUGAPICALL(Document, "Database::get_document", did);
00308 if (did == 0) throw InvalidArgumentError("Document ID 0 is invalid");
00309
00310 unsigned int multiplier = internal.size();
00311 Assert(multiplier != 0);
00312 Xapian::doccount n = (did - 1) % multiplier;
00313 Xapian::docid m = (did - 1) / multiplier + 1;
00314
00315 RETURN(Document(internal[n]->open_document(m)));
00316 }
00317
00318 bool
00319 Database::term_exists(const string & tname) const
00320 {
00321 DEBUGAPICALL(bool, "Database::term_exists", tname);
00322 if (tname.empty()) {
00323 RETURN(get_doccount() != 0);
00324 }
00325 vector<Xapian::Internal::RefCntPtr<Database::Internal> >::const_iterator i;
00326 for (i = internal.begin(); i != internal.end(); ++i) {
00327 if ((*i)->term_exists(tname)) RETURN(true);
00328 }
00329 RETURN(false);
00330 }
00331
00332 void
00333 Database::keep_alive()
00334 {
00335 DEBUGAPICALL(void, "Database::keep_alive", "");
00336 vector<Xapian::Internal::RefCntPtr<Database::Internal> >::const_iterator i;
00337 for (i = internal.begin(); i != internal.end(); ++i) {
00338 (*i)->keep_alive();
00339 }
00340 }
00341
00342 string
00343 Database::get_description() const
00344 {
00346 return "Database()";
00347 }
00348
00349
00350
00351
00352
00353
00354
00355
00356
00357
00358
00359 #define VEC_SIZE 64
00360
00361 static int
00362 freq_edit_lower_bound(const vector<unsigned> & a, const vector<unsigned> & b)
00363 {
00364 int vec[VEC_SIZE];
00365 memset(vec, 0, sizeof(vec));
00366 vector<unsigned>::const_iterator i;
00367 for (i = a.begin(); i != a.end(); ++i) {
00368 ++vec[(*i) % VEC_SIZE];
00369 }
00370 for (i = b.begin(); i != b.end(); ++i) {
00371 --vec[(*i) % VEC_SIZE];
00372 }
00373 unsigned int total = 0;
00374 for (size_t j = 0; j < VEC_SIZE; ++j) {
00375 total += abs(vec[j]);
00376 }
00377
00378
00379
00380
00381 return (total + 1) / 2;
00382 }
00383
00384
00385
00386 #define TRIGRAM_SCORE_THRESHOLD 2
00387
00388 string
00389 Database::get_spelling_suggestion(const string &word,
00390 unsigned max_edit_distance) const
00391 {
00392 DEBUGAPICALL(string, "Database::get_spelling_suggestion",
00393 word << ", " << max_edit_distance);
00394 AutoPtr<TermList> merger;
00395 for (size_t i = 0; i < internal.size(); ++i) {
00396 TermList * tl = internal[i]->open_spelling_termlist(word);
00397 DEBUGLINE(SPELLING, "Sub db " << i << " tl = " << (void*)tl);
00398 if (tl) {
00399 if (merger.get()) {
00400 merger = new OrTermList(merger.release(), tl);
00401 } else {
00402 merger = tl;
00403 }
00404 }
00405 }
00406 if (!merger.get()) RETURN(string());
00407
00408
00409 vector<unsigned> utf32_word;
00410 utf32_word.assign(Utf8Iterator(word), Utf8Iterator());
00411
00412 vector<unsigned> utf32_term;
00413
00414 Xapian::termcount best = 1;
00415 string result;
00416 int edist_best = max_edit_distance;
00417 Xapian::doccount freq_best = 0;
00418 while (true) {
00419 TermList *ret = merger->next();
00420 if (ret) merger = ret;
00421
00422 if (merger->at_end()) break;
00423
00424 string term = merger->get_termname();
00425 Xapian::termcount score = merger->get_wdf();
00426
00427 DEBUGLINE(SPELLING, "Term \"" << term << "\" ngram score " << score);
00428 if (score + TRIGRAM_SCORE_THRESHOLD >= best) {
00429 if (score > best) best = score;
00430
00431
00432
00433
00434
00435
00436
00437 if (abs((long)term.size() - (long)word.size()) > edist_best * 4) {
00438 DEBUGLINE(SPELLING, "Lengths much too different");
00439 continue;
00440 }
00441
00442
00443
00444 utf32_term.assign(Utf8Iterator(term), Utf8Iterator());
00445
00446 if (abs((long)utf32_term.size() - (long)utf32_word.size())
00447 > edist_best) {
00448 DEBUGLINE(SPELLING, "Lengths too different");
00449 continue;
00450 }
00451
00452 if (freq_edit_lower_bound(utf32_term, utf32_word) > edist_best) {
00453 DEBUGLINE(SPELLING, "Rejected by character frequency test");
00454 continue;
00455 }
00456
00457 int edist = edit_distance_unsigned(&utf32_term[0],
00458 utf32_term.size(),
00459 &utf32_word[0],
00460 utf32_word.size(),
00461 edist_best);
00462 DEBUGLINE(SPELLING, "Edit distance " << edist);
00463
00464
00465 if (edist == 0) RETURN(string());
00466
00467 if (edist <= edist_best) {
00468 Xapian::doccount freq = 0;
00469 for (size_t j = 0; j < internal.size(); ++j)
00470 freq += internal[j]->get_spelling_frequency(term);
00471
00472 DEBUGLINE(SPELLING, "Freq " << freq << " best " << freq_best);
00473 if (edist < edist_best || freq > freq_best) {
00474 DEBUGLINE(SPELLING, "Best so far: \"" << term <<
00475 "\" edist " << edist << " freq " <<
00476 freq);
00477 result = term;
00478 edist_best = edist;
00479 freq_best = freq;
00480 }
00481 }
00482 }
00483 }
00484 RETURN(result);
00485 }
00486
00487 TermIterator
00488 Database::spellings_begin() const
00489 {
00490 DEBUGAPICALL(TermIterator, "Database::spellings_begin", "");
00491 AutoPtr<TermList> merger;
00492 for (size_t i = 0; i < internal.size(); ++i) {
00493 TermList * tl = internal[i]->open_spelling_wordlist();
00494 if (tl) {
00495 if (merger.get()) {
00496 merger = new FreqAdderOrTermList(merger.release(), tl);
00497 } else {
00498 merger = tl;
00499 }
00500 }
00501 }
00502 RETURN(TermIterator(merger.release()));
00503 }
00504
00505 TermIterator
00506 Database::synonyms_begin(const std::string &term) const
00507 {
00508 DEBUGAPICALL(TermIterator, "Database::synonyms_begin", term);
00509 AutoPtr<TermList> merger;
00510 for (size_t i = 0; i < internal.size(); ++i) {
00511 TermList * tl = internal[i]->open_synonym_termlist(term);
00512 if (tl) {
00513 if (merger.get()) {
00514 merger = new OrTermList(merger.release(), tl);
00515 } else {
00516 merger = tl;
00517 }
00518 }
00519 }
00520 RETURN(TermIterator(merger.release()));
00521 }
00522
00523 TermIterator
00524 Database::synonym_keys_begin(const std::string &prefix) const
00525 {
00526 DEBUGAPICALL(TermIterator, "Database::synonyms_keys_begin", prefix);
00527 AutoPtr<TermList> merger;
00528 for (size_t i = 0; i < internal.size(); ++i) {
00529 TermList * tl = internal[i]->open_synonym_keylist(prefix);
00530 if (tl) {
00531 if (merger.get()) {
00532 merger = new OrTermList(merger.release(), tl);
00533 } else {
00534 merger = tl;
00535 }
00536 }
00537 }
00538 RETURN(TermIterator(merger.release()));
00539 }
00540
00541 string
00542 Database::get_metadata(const string & key) const
00543 {
00544 DEBUGAPICALL(string, "Database::get_metadata", key);
00545 if (key.empty())
00546 throw InvalidArgumentError("Empty metadata keys are invalid");
00547 RETURN(internal[0]->get_metadata(key));
00548 }
00549
00550 Xapian::TermIterator
00551 Database::metadata_keys_begin(const std::string &prefix) const
00552 {
00553 DEBUGAPICALL(string, "Database::metadata_keys_begin", "");
00554 RETURN(TermIterator(internal[0]->open_metadata_keylist(prefix)));
00555 }
00556
00558
00559 WritableDatabase::WritableDatabase() : Database()
00560 {
00561 DEBUGAPICALL(void, "WritableDatabase::WritableDatabase", "");
00562 }
00563
00564 WritableDatabase::WritableDatabase(Database::Internal *internal_)
00565 : Database(internal_)
00566 {
00567 DEBUGAPICALL(void, "WritableDatabase::WritableDatabase",
00568 "Database::Internal");
00569 }
00570
00571 WritableDatabase::WritableDatabase(const WritableDatabase &other)
00572 : Database(other)
00573 {
00574 DEBUGAPICALL(void, "WritableDatabase::WritableDatabase", "WritableDatabase");
00575 }
00576
00577 void
00578 WritableDatabase::operator=(const WritableDatabase &other)
00579 {
00580 DEBUGAPICALL(void, "WritableDatabase::operator=", "WritableDatabase");
00581 Database::operator=(other);
00582 }
00583
00584 WritableDatabase::~WritableDatabase()
00585 {
00586 DEBUGAPICALL(void, "WritableDatabase::~WritableDatabase", "");
00587 }
00588
00589 XAPIAN_NORETURN(static void only_one_subdatabase_allowed());
00590 static void only_one_subdatabase_allowed()
00591 {
00592 throw Xapian::InvalidOperationError("WritableDatabase needs exactly one subdatabase");
00593 }
00594
00595 void
00596 WritableDatabase::flush()
00597 {
00598 DEBUGAPICALL(void, "WritableDatabase::flush", "");
00599 if (internal.size() != 1) only_one_subdatabase_allowed();
00600 internal[0]->flush();
00601 }
00602
00603 void
00604 WritableDatabase::begin_transaction(bool flushed)
00605 {
00606 DEBUGAPICALL(void, "WritableDatabase::begin_transaction", "");
00607 if (internal.size() != 1) only_one_subdatabase_allowed();
00608 internal[0]->begin_transaction(flushed);
00609 }
00610
00611 void
00612 WritableDatabase::commit_transaction()
00613 {
00614 DEBUGAPICALL(void, "WritableDatabase::commit_transaction", "");
00615 if (internal.size() != 1) only_one_subdatabase_allowed();
00616 internal[0]->commit_transaction();
00617 }
00618
00619 void
00620 WritableDatabase::cancel_transaction()
00621 {
00622 DEBUGAPICALL(void, "WritableDatabase::cancel_transaction", "");
00623 if (internal.size() != 1) only_one_subdatabase_allowed();
00624 internal[0]->cancel_transaction();
00625 }
00626
00627
00628 Xapian::docid
00629 WritableDatabase::add_document(const Document & document)
00630 {
00631 DEBUGAPICALL(Xapian::docid, "WritableDatabase::add_document", document);
00632 if (internal.size() != 1) only_one_subdatabase_allowed();
00633 RETURN(internal[0]->add_document(document));
00634 }
00635
00636 void
00637 WritableDatabase::delete_document(Xapian::docid did)
00638 {
00639 DEBUGAPICALL(void, "WritableDatabase::delete_document", did);
00640 if (internal.size() != 1) only_one_subdatabase_allowed();
00641 if (did == 0) throw InvalidArgumentError("Document ID 0 is invalid");
00642 internal[0]->delete_document(did);
00643 }
00644
00645 void
00646 WritableDatabase::delete_document(const std::string & unique_term)
00647 {
00648 DEBUGAPICALL(void, "WritableDatabase::delete_document", unique_term);
00649 if (internal.size() != 1) only_one_subdatabase_allowed();
00650 if (unique_term.empty())
00651 throw InvalidArgumentError("Empty termnames are invalid");
00652 internal[0]->delete_document(unique_term);
00653 }
00654
00655 void
00656 WritableDatabase::replace_document(Xapian::docid did, const Document & document)
00657 {
00658 DEBUGAPICALL(void, "WritableDatabase::replace_document",
00659 did << ", " << document);
00660 if (internal.size() != 1) only_one_subdatabase_allowed();
00661 if (did == 0) throw Xapian::InvalidArgumentError("Document ID 0 is invalid");
00662 internal[0]->replace_document(did, document);
00663 }
00664
00665 Xapian::docid
00666 WritableDatabase::replace_document(const std::string & unique_term,
00667 const Document & document)
00668 {
00669 DEBUGAPICALL(Xapian::docid, "WritableDatabase::replace_document",
00670 unique_term << ", " << document);
00671 if (internal.size() != 1) only_one_subdatabase_allowed();
00672 if (unique_term.empty())
00673 throw InvalidArgumentError("Empty termnames are invalid");
00674 RETURN(internal[0]->replace_document(unique_term, document));
00675 }
00676
00677 void
00678 WritableDatabase::add_spelling(const std::string & word,
00679 Xapian::termcount freqinc) const
00680 {
00681 DEBUGAPICALL(void, "WritableDatabase::add_spelling",
00682 word << ", " << freqinc);
00683 if (internal.size() != 1) only_one_subdatabase_allowed();
00684 internal[0]->add_spelling(word, freqinc);
00685 }
00686
00687 void
00688 WritableDatabase::remove_spelling(const std::string & word,
00689 Xapian::termcount freqdec) const
00690 {
00691 DEBUGAPICALL(void, "WritableDatabase::remove_spelling",
00692 word << ", " << freqdec);
00693 if (internal.size() != 1) only_one_subdatabase_allowed();
00694 internal[0]->remove_spelling(word, freqdec);
00695 }
00696
00697 void
00698 WritableDatabase::add_synonym(const std::string & term,
00699 const std::string & synonym) const
00700 {
00701 DEBUGAPICALL(void, "WritableDatabase::add_synonym",
00702 term << ", " << synonym);
00703 if (internal.size() != 1) only_one_subdatabase_allowed();
00704 internal[0]->add_synonym(term, synonym);
00705 }
00706
00707 void
00708 WritableDatabase::remove_synonym(const std::string & term,
00709 const std::string & synonym) const
00710 {
00711 DEBUGAPICALL(void, "WritableDatabase::remove_synonym",
00712 term << ", " << synonym);
00713 if (internal.size() != 1) only_one_subdatabase_allowed();
00714 internal[0]->remove_synonym(term, synonym);
00715 }
00716
00717 void
00718 WritableDatabase::clear_synonyms(const std::string & term) const
00719 {
00720 DEBUGAPICALL(void, "WritableDatabase::clear_synonyms", term);
00721 if (internal.size() != 1) only_one_subdatabase_allowed();
00722 internal[0]->clear_synonyms(term);
00723 }
00724
00725 void
00726 WritableDatabase::set_metadata(const string & key, const string & value)
00727 {
00728 DEBUGAPICALL(void, "WritableDatabase::set_metadata", key << ", " << value);
00729 if (internal.size() != 1) only_one_subdatabase_allowed();
00730 if (key.empty())
00731 throw InvalidArgumentError("Empty metadata keys are invalid");
00732 internal[0]->set_metadata(key, value);
00733 }
00734
00735 string
00736 WritableDatabase::get_description() const
00737 {
00739 return "WritableDatabase()";
00740 }
00741
00742 }