backends/database.cc

Go to the documentation of this file.
00001 /* database.cc: Database factories and base class
00002  *
00003  * Copyright 1999,2000,2001 BrightStation PLC
00004  * Copyright 2002 Ananova Ltd
00005  * Copyright 2002,2003,2004,2005,2006,2007 Olly Betts
00006  * Copyright 2008 Lemur Consulting Ltd
00007  *
00008  * This program is free software; you can redistribute it and/or
00009  * modify it under the terms of the GNU General Public License as
00010  * published by the Free Software Foundation; either version 2 of the
00011  * License, or (at your option) any later version.
00012  *
00013  * This program is distributed in the hope that it will be useful,
00014  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00015  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00016  * GNU General Public License for more details.
00017  *
00018  * You should have received a copy of the GNU General Public License
00019  * along with this program; if not, write to the Free Software
00020  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
00021  * USA
00022  */
00023 
00024 #include <config.h>
00025 
00026 // We have to use the deprecated Quartz::open() method.
00027 #define XAPIAN_DEPRECATED(D) D
00028 
00029 #include "database.h"
00030 #include "utils.h" // for om_tostring
00031 #include <xapian/dbfactory.h>
00032 #include <xapian/error.h>
00033 #include <xapian/version.h> // For XAPIAN_HAS_XXX_BACKEND
00034 
00035 #include <fstream>
00036 #include <string>
00037 
00038 using namespace std;
00039 
00040 // Include headers for all the enabled database backends
00041 #ifdef XAPIAN_HAS_INMEMORY_BACKEND
00042 #include "inmemory/inmemory_database.h"
00043 #endif
00044 #ifdef XAPIAN_HAS_QUARTZ_BACKEND
00045 #include "quartz/quartz_database.h"
00046 #endif
00047 #ifdef XAPIAN_HAS_FLINT_BACKEND
00048 #include "flint/flint_database.h"
00049 #endif
00050 
00051 namespace Xapian {
00052 
00053 #ifdef XAPIAN_HAS_QUARTZ_BACKEND
00054 Database
00055 Quartz::open(const string &dir) {
00056     DEBUGAPICALL_STATIC(Database, "Quartz::open", dir);
00057     return Database(new QuartzDatabase(dir));
00058 }
00059 
00060 WritableDatabase
00061 Quartz::open(const string &dir, int action, int block_size) {
00062     DEBUGAPICALL_STATIC(WritableDatabase, "Quartz::open", dir << ", " <<
00063                         action << ", " << block_size);
00064     return WritableDatabase(new QuartzWritableDatabase(dir, action,
00065                                                        block_size));
00066 }
00067 #endif
00068 
00069 #ifdef XAPIAN_HAS_FLINT_BACKEND
00070 Database
00071 Flint::open(const string &dir) {
00072     DEBUGAPICALL_STATIC(Database, "Flint::open", dir);
00073     return Database(new FlintDatabase(dir));
00074 }
00075 
00076 WritableDatabase
00077 Flint::open(const string &dir, int action, int block_size) {
00078     DEBUGAPICALL_STATIC(WritableDatabase, "Flint::open", dir << ", " <<
00079                         action << ", " << block_size);
00080     return WritableDatabase(new FlintWritableDatabase(dir, action, block_size));
00081 }
00082 #endif
00083 
00084 #ifdef XAPIAN_HAS_INMEMORY_BACKEND
00085 // Note: a read-only inmemory database will always be empty, and so there's
00086 // not much use in allowing one to be created.
00087 WritableDatabase
00088 InMemory::open() {
00089     DEBUGAPICALL_STATIC(Database, "InMemory::open", "");
00090     return WritableDatabase(new InMemoryDatabase());
00091 }
00092 #endif
00093 
00094 static void
00095 open_stub(Database *db, const string &file)
00096 {
00097     // A stub database is a text file with one or more lines of this format:
00098     // <dbtype> <serialised db object>
00099     ifstream stub(file.c_str());
00100     string line;
00101     int line_no = 1;
00102     bool ok = false;
00103     while (getline(stub, line)) {
00104         string::size_type space = line.find(' ');
00105         if (space != string::npos) {
00106             string type = line.substr(0, space);
00107             line.erase(0, space + 1);
00108             if (type == "auto") {
00109                 db->add_database(Database(line));
00110                 ok = true;
00111 #ifdef XAPIAN_HAS_FLINT_BACKEND
00112             } else if (type == "flint") {
00113                 db->add_database(Flint::open(line));
00114                 ok = true;
00115 #endif
00116 #ifdef XAPIAN_HAS_QUARTZ_BACKEND
00117             } else if (type == "quartz") {
00118                 db->add_database(Quartz::open(line));
00119                 ok = true;
00120 #endif
00121 #ifdef XAPIAN_HAS_REMOTE_BACKEND
00122             } else if (type == "remote") {
00123                 string::size_type colon = line.find(':');
00124                 if (colon == 0) {
00125                     // prog
00126                     // FIXME: timeouts
00127                     // FIXME: Is prog actually useful beyond testing?
00128                     // Is it a security risk?
00129                     space = line.find(' ');
00130                     string args;
00131                     if (space != string::npos) {
00132                         args = line.substr(space + 1);
00133                         line = line.substr(1, space - 1);
00134                     } else {
00135                         line.erase(0, 1);
00136                     }
00137                     db->add_database(Remote::open(line, args));
00138                     ok = true;
00139                 } else if (colon != string::npos) {
00140                     // tcp
00141                     // FIXME: timeouts
00142                     unsigned int port = atoi(line.c_str() + colon + 1);
00143                     line.erase(colon);
00144                     db->add_database(Remote::open(line, port));
00145                     ok = true;
00146                 }
00147 #endif
00148             }
00149         }
00150         if (!ok) break;
00151         ++line_no;
00152     }
00153     if (!ok) {
00154         // Don't include the line itself - that might help an attacker
00155         // by revealing part of a sensitive file's contents if they can
00156         // arrange for it to be read as a stub database via infelicities in
00157         // an application which uses Xapian.  The line number is enough
00158         // information to identify the problem line.
00159         throw DatabaseOpeningError("Bad line " + om_tostring(line_no) + " in stub database file `" + file + "'");
00160     }
00161 }
00162 
00163 Database
00164 Auto::open_stub(const string &file)
00165 {
00166     DEBUGAPICALL_STATIC(Database, "Auto::open_stub", file);
00167     Database db;
00168     open_stub(&db, file);
00169     return db;
00170 }
00171 
00172 Database::Database(const string &path)
00173 {
00174     DEBUGAPICALL(void, "Database::Database", path);
00175     // Check for path actually being a file - if so, assume it to be
00176     // a stub database.
00177     if (file_exists(path)) {
00178         open_stub(this, path);
00179         return;
00180     }
00181 
00182 #ifdef XAPIAN_HAS_FLINT_BACKEND
00183     if (file_exists(path + "/iamflint")) {
00184         internal.push_back(new FlintDatabase(path));
00185         return;
00186     }
00187 #endif
00188 #ifdef XAPIAN_HAS_QUARTZ_BACKEND
00189     if (file_exists(path + "/record_DB")) {
00190         internal.push_back(new QuartzDatabase(path));
00191         return;
00192     }
00193 #endif
00194 
00195     throw DatabaseOpeningError("Couldn't detect type of database");
00196 }
00197 
00198 WritableDatabase::WritableDatabase(const std::string &path, int action)
00199     : Database()
00200 {
00201     DEBUGAPICALL(void, "WritableDatabase::WritableDatabase",
00202                  path << ", " << action);
00203 #if defined XAPIAN_HAS_FLINT_BACKEND && defined XAPIAN_HAS_QUARTZ_BACKEND
00204     // Both Flint and Quartz are enabled.
00205     if (!file_exists(path + "/record_DB")) {
00206         internal.push_back(new FlintWritableDatabase(path, action, 8192));
00207     } else {
00208         internal.push_back(new QuartzWritableDatabase(path, action, 8192));
00209     }
00210 #elif defined XAPIAN_HAS_FLINT_BACKEND
00211     // Only Flint is enabled.
00212     internal.push_back(new FlintWritableDatabase(path, action, 8192));
00213 #elif defined XAPIAN_HAS_QUARTZ_BACKEND
00214     // Only Quartz is enabled.
00215     internal.push_back(new QuartzWritableDatabase(path, action, 8192));
00216 #else
00217     throw FeatureUnavailableError("No disk-based writable backend is enabled");
00218 #endif
00219 }
00220 
00222 
00223 Database::Internal::Internal()
00224         : transaction_state(TRANSACTION_NONE)
00225 {
00226 }
00227 
00228 Database::Internal::~Internal()
00229 {
00230 }
00231 
00232 void
00233 Database::Internal::keep_alive()
00234 {
00235     // For the normal case of local databases, nothing needs to be done.
00236 }
00237 
00238 // Discard any exceptions - we're called from the destructors of derived
00239 // classes so we can't safely throw.
00240 void
00241 Database::Internal::dtor_called()
00242 {
00243     try {
00244         if (transaction_active()) {
00245             cancel_transaction();
00246         } else if (transaction_state == TRANSACTION_NONE) {
00247             flush();
00248         }
00249     } catch (...) {
00250         // We can't safely throw exceptions from a destructor in case an
00251         // exception is already active and causing us to be destroyed.
00252     }
00253 }
00254 
00255 void
00256 Database::Internal::flush()
00257 {
00258     // Writable databases should override this method.
00259     Assert(false);
00260 }
00261 
00262 void
00263 Database::Internal::cancel()
00264 {
00265     // Writable databases should override this method.
00266     Assert(false);
00267 }
00268 
00269 void
00270 Database::Internal::begin_transaction(bool flushed)
00271 {
00272     if (transaction_state != TRANSACTION_NONE) {
00273         if (transaction_state == TRANSACTION_UNIMPLEMENTED)
00274             throw Xapian::UnimplementedError("This backend doesn't implement transactions");
00275         throw InvalidOperationError("Cannot begin transaction - transaction already in progress");
00276     }
00277     if (flushed) {
00278         // N.B. Call flush() before we set transaction_state since flush()
00279         // isn't allowing during a transaction.
00280         flush();
00281         transaction_state = TRANSACTION_FLUSHED;
00282     } else {
00283         transaction_state = TRANSACTION_UNFLUSHED;
00284     }
00285 }
00286 
00287 void
00288 Database::Internal::commit_transaction()
00289 {
00290     if (!transaction_active()) {
00291         if (transaction_state == TRANSACTION_UNIMPLEMENTED)
00292             throw Xapian::UnimplementedError("This backend doesn't implement transactions");
00293         throw InvalidOperationError("Cannot commit transaction - no transaction currently in progress");
00294     }
00295     bool flushed = (transaction_state == TRANSACTION_FLUSHED);
00296     transaction_state = TRANSACTION_NONE;
00297     // N.B. Call flush() after we clear transaction_state since flush()
00298     // isn't allowing during a transaction.
00299     if (flushed) flush();
00300 }
00301 
00302 void
00303 Database::Internal::cancel_transaction()
00304 {
00305     if (!transaction_active()) {
00306         if (transaction_state == TRANSACTION_UNIMPLEMENTED)
00307             throw Xapian::UnimplementedError("This backend doesn't implement transactions");
00308         throw InvalidOperationError("Cannot cancel transaction - no transaction currently in progress");
00309     }
00310     transaction_state = TRANSACTION_NONE;
00311     cancel();
00312 }
00313 
00314 Xapian::docid
00315 Database::Internal::add_document(const Xapian::Document &)
00316 {
00317     // Writable databases should override this method.
00318     Assert(false);
00319     return 0;
00320 }
00321 
00322 void
00323 Database::Internal::delete_document(Xapian::docid)
00324 {
00325     // Writable databases should override this method.
00326     Assert(false);
00327 }
00328 
00329 void
00330 Database::Internal::delete_document(const std::string & unique_term)
00331 {
00332     // Default implementation - overridden for remote databases
00333     Xapian::Internal::RefCntPtr<LeafPostList> pl(open_post_list(unique_term));
00334     while (pl->next(), !pl->at_end()) {
00335         delete_document(pl->get_docid());
00336     }
00337 }
00338 
00339 void
00340 Database::Internal::replace_document(Xapian::docid, const Xapian::Document &)
00341 {
00342     // Writable databases should override this method.
00343     Assert(false);
00344 }
00345 
00346 Xapian::docid
00347 Database::Internal::replace_document(const std::string & unique_term,
00348                                      const Xapian::Document & document)
00349 {
00350     // Default implementation - overridden for remote databases
00351     Xapian::Internal::RefCntPtr<LeafPostList> pl(open_post_list(unique_term));
00352     pl->next();
00353     if (pl->at_end()) {
00354         return add_document(document);
00355     }
00356     Xapian::docid did = pl->get_docid();
00357     replace_document(did, document);
00358     while (pl->next(), !pl->at_end()) {
00359         delete_document(pl->get_docid());
00360     }
00361     return did;
00362 }
00363 
00364 TermList *
00365 Database::Internal::open_spelling_termlist(const string &) const
00366 {
00367     // Only implemented for some database backends - others will just not
00368     // suggest spelling corrections (or not contribute to them in a multiple
00369     // database situation).
00370     return NULL;
00371 }
00372 
00373 TermList *
00374 Database::Internal::open_spelling_wordlist() const
00375 {
00376     // Only implemented for some database backends - others will just not
00377     // suggest spelling corrections (or not contribute to them in a multiple
00378     // database situation).
00379     return NULL;
00380 }
00381 
00382 Xapian::doccount
00383 Database::Internal::get_spelling_frequency(const string &) const
00384 {
00385     // Only implemented for some database backends - others will just not
00386     // suggest spelling corrections (or not contribute to them in a multiple
00387     // database situation).
00388     return 0;
00389 }
00390 
00391 void
00392 Database::Internal::add_spelling(const string &, Xapian::termcount) const
00393 {
00394     throw Xapian::UnimplementedError("This backend doesn't implement spelling correction");
00395 }
00396 
00397 void
00398 Database::Internal::remove_spelling(const string &, Xapian::termcount) const
00399 {
00400     throw Xapian::UnimplementedError("This backend doesn't implement spelling correction");
00401 }
00402 
00403 TermList *
00404 Database::Internal::open_synonym_termlist(const string &) const
00405 {
00406     // Only implemented for some database backends - others will just not
00407     // expand synonyms (or not contribute to them in a multiple database
00408     // situation).
00409     return NULL;
00410 }
00411 
00412 TermList *
00413 Database::Internal::open_synonym_keylist(const string &) const
00414 {
00415     // Only implemented for some database backends - others will just not
00416     // expand synonyms (or not contribute to them in a multiple database
00417     // situation).
00418     return NULL;
00419 }
00420 
00421 void
00422 Database::Internal::add_synonym(const string &, const string &) const
00423 {
00424     throw Xapian::UnimplementedError("This backend doesn't implement synonyms");
00425 }
00426 
00427 void
00428 Database::Internal::remove_synonym(const string &, const string &) const
00429 {
00430     throw Xapian::UnimplementedError("This backend doesn't implement synonyms");
00431 }
00432 
00433 void
00434 Database::Internal::clear_synonyms(const string &) const
00435 {
00436     throw Xapian::UnimplementedError("This backend doesn't implement synonyms");
00437 }
00438 
00439 string
00440 Database::Internal::get_metadata(const string &) const
00441 {
00442     return string();
00443 }
00444 
00445 TermList *
00446 Database::Internal::open_metadata_keylist(const std::string &) const
00447 {
00448     // Only implemented for some database backends - others will simply report
00449     // there being no metadata keys.
00450     return NULL;
00451 }
00452 
00453 void
00454 Database::Internal::set_metadata(const string &, const string &)
00455 {
00456     throw Xapian::UnimplementedError("This backend doesn't implement metadata");
00457 }
00458 
00459 void
00460 Database::Internal::reopen()
00461 {
00462     // Database backends which don't support simultaneous update and reading
00463     // probably don't need to do anything here.
00464 }
00465 
00466 void
00467 Database::Internal::request_document(Xapian::docid /*did*/) const
00468 {
00469 }
00470 
00471 Xapian::Document::Internal *
00472 Database::Internal::collect_document(Xapian::docid did) const
00473 {
00474     return open_document(did);
00475 }
00476 
00477 RemoteDatabase *
00478 Database::Internal::as_remotedatabase()
00479 {
00480     return NULL;
00481 }
00482 
00483 }

Documentation for Xapian (version 1.0.10).
Generated on 24 Dec 2008 by Doxygen 1.5.2.