summaryrefslogtreecommitdiff
path: root/plugins/Dbx_kyoto/src/kyotocabinet/kchashdb.h
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/Dbx_kyoto/src/kyotocabinet/kchashdb.h')
-rw-r--r--plugins/Dbx_kyoto/src/kyotocabinet/kchashdb.h3865
1 files changed, 3865 insertions, 0 deletions
diff --git a/plugins/Dbx_kyoto/src/kyotocabinet/kchashdb.h b/plugins/Dbx_kyoto/src/kyotocabinet/kchashdb.h
new file mode 100644
index 0000000000..9370e6ed35
--- /dev/null
+++ b/plugins/Dbx_kyoto/src/kyotocabinet/kchashdb.h
@@ -0,0 +1,3865 @@
+/*************************************************************************************************
+ * File hash database
+ * Copyright (C) 2009-2012 FAL Labs
+ * This file is part of Kyoto Cabinet.
+ * This program is free software: you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation, either version
+ * 3 of the License, or any later version.
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see <http://www.gnu.org/licenses/>.
+ *************************************************************************************************/
+
+
+#ifndef _KCHASHDB_H // duplication check
+#define _KCHASHDB_H
+
+#include <kccommon.h>
+#include <kcutil.h>
+#include <kcthread.h>
+#include <kcfile.h>
+#include <kccompress.h>
+#include <kccompare.h>
+#include <kcmap.h>
+#include <kcregex.h>
+#include <kcdb.h>
+#include <kcplantdb.h>
+
+#define KCHDBMAGICDATA "KC\n" ///< magic data of the file
+#define KCHDBCHKSUMSEED "__kyotocabinet__" ///< seed of the module checksum
+#define KCHDBTMPPATHEXT "tmpkch" ///< extension of the temporary file
+
+namespace kyotocabinet { // common namespace
+
+
+/**
+ * File hash database.
+ * @note This class is a concrete class to operate a hash database on a file. This class can be
+ * inherited but overwriting methods is forbidden. Before every database operation, it is
+ * necessary to call the HashDB::open method in order to open a database file and connect the
+ * database object to it. To avoid data missing or corruption, it is important to close every
+ * database file by the HashDB::close method when the database is no longer in use. It is
+ * forbidden for multible database objects in a process to open the same database at the same
+ * time. It is forbidden to share a database object with child processes.
+ */
+class HashDB : public BasicDB {
+ friend class PlantDB<HashDB, BasicDB::TYPETREE>;
+ public:
+ class Cursor;
+ private:
+ struct Record;
+ struct FreeBlock;
+ struct FreeBlockComparator;
+ class Repeater;
+ class ScopedVisitor;
+ /** An alias of set of free blocks. */
+ typedef std::set<FreeBlock> FBP;
+ /** An alias of list of cursors. */
+ typedef std::list<Cursor*> CursorList;
+ /** The offset of the library version. */
+ static const int64_t MOFFLIBVER = 4;
+ /** The offset of the library revision. */
+ static const int64_t MOFFLIBREV = 5;
+ /** The offset of the format revision. */
+ static const int64_t MOFFFMTVER = 6;
+ /** The offset of the module checksum. */
+ static const int64_t MOFFCHKSUM = 7;
+ /** The offset of the database type. */
+ static const int64_t MOFFTYPE = 8;
+ /** The offset of the alignment power. */
+ static const int64_t MOFFAPOW = 9;
+ /** The offset of the free block pool power. */
+ static const int64_t MOFFFPOW = 10;
+ /** The offset of the options. */
+ static const int64_t MOFFOPTS = 11;
+ /** The offset of the bucket number. */
+ static const int64_t MOFFBNUM = 16;
+ /** The offset of the status flags. */
+ static const int64_t MOFFFLAGS = 24;
+ /** The offset of the record number. */
+ static const int64_t MOFFCOUNT = 32;
+ /** The offset of the file size. */
+ static const int64_t MOFFSIZE = 40;
+ /** The offset of the opaque data. */
+ static const int64_t MOFFOPAQUE = 48;
+ /** The size of the header. */
+ static const int64_t HEADSIZ = 64;
+ /** The width of the free block. */
+ static const int32_t FBPWIDTH = 6;
+ /** The large width of the record address. */
+ static const int32_t WIDTHLARGE = 6;
+ /** The small width of the record address. */
+ static const int32_t WIDTHSMALL = 4;
+ /** The size of the record buffer. */
+ static const size_t RECBUFSIZ = 48;
+ /** The size of the IO buffer. */
+ static const size_t IOBUFSIZ = 1024;
+ /** The number of slots of the record lock. */
+ static const int32_t RLOCKSLOT = 1024;
+ /** The default alignment power. */
+ static const uint8_t DEFAPOW = 3;
+ /** The maximum alignment power. */
+ static const uint8_t MAXAPOW = 15;
+ /** The default free block pool power. */
+ static const uint8_t DEFFPOW = 10;
+ /** The maximum free block pool power. */
+ static const uint8_t MAXFPOW = 20;
+ /** The default bucket number. */
+ static const int64_t DEFBNUM = 1048583LL;
+ /** The default size of the memory-mapped region. */
+ static const int64_t DEFMSIZ = 64LL << 20;
+ /** The magic data for record. */
+ static const uint8_t RECMAGIC = 0xcc;
+ /** The magic data for padding. */
+ static const uint8_t PADMAGIC = 0xee;
+ /** The magic data for free block. */
+ static const uint8_t FBMAGIC = 0xdd;
+ /** The maximum unit of auto defragmentation. */
+ static const int32_t DFRGMAX = 512;
+ /** The coefficient of auto defragmentation. */
+ static const int32_t DFRGCEF = 2;
+ /** The checking width for record salvage. */
+ static const int64_t SLVGWIDTH = 1LL << 20;
+ /** The threshold of busy loop and sleep for locking. */
+ static const uint32_t LOCKBUSYLOOP = 8192;
+ public:
+ /**
+ * Cursor to indicate a record.
+ */
+ class Cursor : public BasicDB::Cursor {
+ friend class HashDB;
+ public:
+ /**
+ * Constructor.
+ * @param db the container database object.
+ */
+ explicit Cursor(HashDB* db) : db_(db), off_(0), end_(0) {
+ _assert_(db);
+ ScopedRWLock lock(&db_->mlock_, true);
+ db_->curs_.push_back(this);
+ }
+ /**
+ * Destructor.
+ */
+ virtual ~Cursor() {
+ _assert_(true);
+ if (!db_) return;
+ ScopedRWLock lock(&db_->mlock_, true);
+ db_->curs_.remove(this);
+ }
+ /**
+ * Accept a visitor to the current record.
+ * @param visitor a visitor object.
+ * @param writable true for writable operation, or false for read-only operation.
+ * @param step true to move the cursor to the next record, or false for no move.
+ * @return true on success, or false on failure.
+ * @note The operation for each record is performed atomically and other threads accessing
+ * the same record are blocked. To avoid deadlock, any explicit database operation must not
+ * be performed in this function.
+ */
+ bool accept(Visitor* visitor, bool writable = true, bool step = false) {
+ _assert_(visitor);
+ ScopedRWLock lock(&db_->mlock_, true);
+ if (db_->omode_ == 0) {
+ db_->set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ return false;
+ }
+ if (writable) {
+ if (!db_->writer_) {
+ db_->set_error(_KCCODELINE_, Error::NOPERM, "permission denied");
+ return false;
+ }
+ if (!(db_->flags_ & FOPEN) && !db_->autotran_ && !db_->tran_ &&
+ !db_->set_flag(FOPEN, true)) {
+ return false;
+ }
+ }
+ if (off_ < 1) {
+ db_->set_error(_KCCODELINE_, Error::NOREC, "no record");
+ return false;
+ }
+ Record rec;
+ char rbuf[RECBUFSIZ];
+ if (!step_impl(&rec, rbuf, 0)) return false;
+ if (!rec.vbuf && !db_->read_record_body(&rec)) {
+ delete[] rec.bbuf;
+ return false;
+ }
+ const char* vbuf = rec.vbuf;
+ size_t vsiz = rec.vsiz;
+ char* zbuf = NULL;
+ size_t zsiz = 0;
+ if (db_->comp_) {
+ zbuf = db_->comp_->decompress(vbuf, vsiz, &zsiz);
+ if (!zbuf) {
+ db_->set_error(_KCCODELINE_, Error::SYSTEM, "data decompression failed");
+ delete[] rec.bbuf;
+ return false;
+ }
+ vbuf = zbuf;
+ vsiz = zsiz;
+ }
+ vbuf = visitor->visit_full(rec.kbuf, rec.ksiz, vbuf, vsiz, &vsiz);
+ delete[] zbuf;
+ if (vbuf == Visitor::REMOVE) {
+ uint64_t hash = db_->hash_record(rec.kbuf, rec.ksiz);
+ uint32_t pivot = db_->fold_hash(hash);
+ int64_t bidx = hash % db_->bnum_;
+ Repeater repeater(Visitor::REMOVE, 0);
+ if (!db_->accept_impl(rec.kbuf, rec.ksiz, &repeater, bidx, pivot, true)) {
+ delete[] rec.bbuf;
+ return false;
+ }
+ delete[] rec.bbuf;
+ } else if (vbuf == Visitor::NOP) {
+ delete[] rec.bbuf;
+ if (step) {
+ if (step_impl(&rec, rbuf, 1)) {
+ delete[] rec.bbuf;
+ } else if (db_->error().code() != Error::NOREC) {
+ return false;
+ }
+ }
+ } else {
+ zbuf = NULL;
+ zsiz = 0;
+ if (db_->comp_) {
+ zbuf = db_->comp_->compress(vbuf, vsiz, &zsiz);
+ if (!zbuf) {
+ db_->set_error(_KCCODELINE_, Error::SYSTEM, "data compression failed");
+ delete[] rec.bbuf;
+ return false;
+ }
+ vbuf = zbuf;
+ vsiz = zsiz;
+ }
+ size_t rsiz = db_->calc_record_size(rec.ksiz, vsiz);
+ if (rsiz <= rec.rsiz) {
+ rec.psiz = rec.rsiz - rsiz;
+ rec.vsiz = vsiz;
+ rec.vbuf = vbuf;
+ if (!db_->adjust_record(&rec) || !db_->write_record(&rec, true)) {
+ delete[] zbuf;
+ delete[] rec.bbuf;
+ return false;
+ }
+ delete[] zbuf;
+ delete[] rec.bbuf;
+ if (step) {
+ if (step_impl(&rec, rbuf, 1)) {
+ delete[] rec.bbuf;
+ } else if (db_->error().code() != Error::NOREC) {
+ return false;
+ }
+ }
+ } else {
+ uint64_t hash = db_->hash_record(rec.kbuf, rec.ksiz);
+ uint32_t pivot = db_->fold_hash(hash);
+ int64_t bidx = hash % db_->bnum_;
+ Repeater repeater(vbuf, vsiz);
+ if (!db_->accept_impl(rec.kbuf, rec.ksiz, &repeater, bidx, pivot, true)) {
+ delete[] zbuf;
+ delete[] rec.bbuf;
+ return false;
+ }
+ delete[] zbuf;
+ delete[] rec.bbuf;
+ }
+ }
+ if (db_->dfunit_ > 0 && db_->frgcnt_ >= db_->dfunit_) {
+ if (!db_->defrag_impl(db_->dfunit_ * DFRGCEF)) return false;
+ db_->frgcnt_ -= db_->dfunit_;
+ }
+ return true;
+ }
+ /**
+ * Jump the cursor to the first record for forward scan.
+ * @return true on success, or false on failure.
+ */
+ bool jump() {
+ _assert_(true);
+ ScopedRWLock lock(&db_->mlock_, true);
+ if (db_->omode_ == 0) {
+ db_->set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ return false;
+ }
+ off_ = 0;
+ if (db_->lsiz_ <= db_->roff_) {
+ db_->set_error(_KCCODELINE_, Error::NOREC, "no record");
+ return false;
+ }
+ off_ = db_->roff_;
+ end_ = db_->lsiz_;
+ return true;
+ }
+ /**
+ * Jump the cursor to a record for forward scan.
+ * @param kbuf the pointer to the key region.
+ * @param ksiz the size of the key region.
+ * @return true on success, or false on failure.
+ */
+ bool jump(const char* kbuf, size_t ksiz) {
+ _assert_(kbuf && ksiz <= MEMMAXSIZ);
+ ScopedRWLock lock(&db_->mlock_, true);
+ if (db_->omode_ == 0) {
+ db_->set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ return false;
+ }
+ off_ = 0;
+ uint64_t hash = db_->hash_record(kbuf, ksiz);
+ uint32_t pivot = db_->fold_hash(hash);
+ int64_t bidx = hash % db_->bnum_;
+ int64_t off = db_->get_bucket(bidx);
+ if (off < 0) return false;
+ Record rec;
+ char rbuf[RECBUFSIZ];
+ while (off > 0) {
+ rec.off = off;
+ if (!db_->read_record(&rec, rbuf)) return false;
+ if (rec.psiz == UINT16MAX) {
+ db_->set_error(_KCCODELINE_, Error::BROKEN, "free block in the chain");
+ db_->report(_KCCODELINE_, Logger::WARN, "psiz=%lld off=%lld fsiz=%lld",
+ (long long)db_->psiz_, (long long)rec.off, (long long)db_->file_.size());
+ return false;
+ }
+ uint32_t tpivot = db_->linear_ ? pivot :
+ db_->fold_hash(db_->hash_record(rec.kbuf, rec.ksiz));
+ if (pivot > tpivot) {
+ delete[] rec.bbuf;
+ off = rec.left;
+ } else if (pivot < tpivot) {
+ delete[] rec.bbuf;
+ off = rec.right;
+ } else {
+ int32_t kcmp = db_->compare_keys(kbuf, ksiz, rec.kbuf, rec.ksiz);
+ if (db_->linear_ && kcmp != 0) kcmp = 1;
+ if (kcmp > 0) {
+ delete[] rec.bbuf;
+ off = rec.left;
+ } else if (kcmp < 0) {
+ delete[] rec.bbuf;
+ off = rec.right;
+ } else {
+ delete[] rec.bbuf;
+ off_ = off;
+ end_ = db_->lsiz_;
+ return true;
+ }
+ }
+ }
+ db_->set_error(_KCCODELINE_, Error::NOREC, "no record");
+ return false;
+ }
+ /**
+ * Jump the cursor to a record for forward scan.
+ * @note Equal to the original Cursor::jump method except that the parameter is std::string.
+ */
+ bool jump(const std::string& key) {
+ _assert_(true);
+ return jump(key.c_str(), key.size());
+ }
+ /**
+ * Jump the cursor to the last record for backward scan.
+ * @note This is a dummy implementation for compatibility.
+ */
+ bool jump_back() {
+ _assert_(true);
+ ScopedRWLock lock(&db_->mlock_, true);
+ if (db_->omode_ == 0) {
+ db_->set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ return false;
+ }
+ db_->set_error(_KCCODELINE_, Error::NOIMPL, "not implemented");
+ return false;
+ }
+ /**
+ * Jump the cursor to a record for backward scan.
+ * @note This is a dummy implementation for compatibility.
+ */
+ bool jump_back(const char* kbuf, size_t ksiz) {
+ _assert_(kbuf && ksiz <= MEMMAXSIZ);
+ ScopedRWLock lock(&db_->mlock_, true);
+ if (db_->omode_ == 0) {
+ db_->set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ return false;
+ }
+ db_->set_error(_KCCODELINE_, Error::NOIMPL, "not implemented");
+ return false;
+ }
+ /**
+ * Jump the cursor to a record for backward scan.
+ * @note This is a dummy implementation for compatibility.
+ */
+ bool jump_back(const std::string& key) {
+ _assert_(true);
+ ScopedRWLock lock(&db_->mlock_, true);
+ if (db_->omode_ == 0) {
+ db_->set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ return false;
+ }
+ db_->set_error(_KCCODELINE_, Error::NOIMPL, "not implemented");
+ return false;
+ }
+ /**
+ * Step the cursor to the next record.
+ * @return true on success, or false on failure.
+ */
+ bool step() {
+ _assert_(true);
+ ScopedRWLock lock(&db_->mlock_, true);
+ if (db_->omode_ == 0) {
+ db_->set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ return false;
+ }
+ if (off_ < 1) {
+ db_->set_error(_KCCODELINE_, Error::NOREC, "no record");
+ return false;
+ }
+ bool err = false;
+ Record rec;
+ char rbuf[RECBUFSIZ];
+ if (step_impl(&rec, rbuf, 1)) {
+ delete[] rec.bbuf;
+ } else {
+ err = true;
+ }
+ return !err;
+ }
+ /**
+ * Step the cursor to the previous record.
+ * @note This is a dummy implementation for compatibility.
+ */
+ bool step_back() {
+ _assert_(true);
+ ScopedRWLock lock(&db_->mlock_, true);
+ if (db_->omode_ == 0) {
+ db_->set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ return false;
+ }
+ db_->set_error(_KCCODELINE_, Error::NOIMPL, "not implemented");
+ return false;
+ }
+ /**
+ * Get the database object.
+ * @return the database object.
+ */
+ HashDB* db() {
+ _assert_(true);
+ return db_;
+ }
+ private:
+ /**
+ * Step the cursor to the next record.
+ * @param rec the record structure.
+ * @param rbuf the working buffer.
+ * @param skip the number of skipping blocks.
+ * @return true on success, or false on failure.
+ */
+ bool step_impl(Record* rec, char* rbuf, int64_t skip) {
+ _assert_(rec && rbuf && skip >= 0);
+ if (off_ >= end_) {
+ db_->set_error(_KCCODELINE_, Error::BROKEN, "cursor after the end");
+ db_->report(_KCCODELINE_, Logger::WARN, "psiz=%lld off=%lld fsiz=%lld",
+ (long long)db_->psiz_, (long long)rec->off, (long long)db_->file_.size());
+ return false;
+ }
+ while (off_ < end_) {
+ rec->off = off_;
+ if (!db_->read_record(rec, rbuf)) return false;
+ skip--;
+ if (rec->psiz == UINT16MAX) {
+ off_ += rec->rsiz;
+ } else {
+ if (skip < 0) return true;
+ delete[] rec->bbuf;
+ off_ += rec->rsiz;
+ }
+ }
+ db_->set_error(_KCCODELINE_, Error::NOREC, "no record");
+ off_ = 0;
+ return false;
+ }
+ /** Dummy constructor to forbid the use. */
+ Cursor(const Cursor&);
+ /** Dummy Operator to forbid the use. */
+ Cursor& operator =(const Cursor&);
+ /** The inner database. */
+ HashDB* db_;
+ /** The current offset. */
+ int64_t off_;
+ /** The end offset. */
+ int64_t end_;
+ };
+ /**
+ * Tuning options.
+ */
+ enum Option {
+ TSMALL = 1 << 0, ///< use 32-bit addressing
+ TLINEAR = 1 << 1, ///< use linear collision chaining
+ TCOMPRESS = 1 << 2 ///< compress each record
+ };
+ /**
+ * Status flags.
+ */
+ enum Flag {
+ FOPEN = 1 << 0, ///< whether opened
+ FFATAL = 1 << 1 ///< whether with fatal error
+ };
+ /**
+ * Default constructor.
+ */
+ explicit HashDB() :
+ mlock_(), rlock_(RLOCKSLOT), flock_(), atlock_(), error_(),
+ logger_(NULL), logkinds_(0), mtrigger_(NULL),
+ omode_(0), writer_(false), autotran_(false), autosync_(false),
+ reorg_(false), trim_(false),
+ file_(), fbp_(), curs_(), path_(""),
+ libver_(0), librev_(0), fmtver_(0), chksum_(0), type_(TYPEHASH),
+ apow_(DEFAPOW), fpow_(DEFFPOW), opts_(0), bnum_(DEFBNUM),
+ flags_(0), flagopen_(false), count_(0), lsiz_(0), psiz_(0), opaque_(),
+ msiz_(DEFMSIZ), dfunit_(0), embcomp_(ZLIBRAWCOMP),
+ align_(0), fbpnum_(0), width_(0), linear_(false),
+ comp_(NULL), rhsiz_(0), boff_(0), roff_(0), dfcur_(0), frgcnt_(0),
+ tran_(false), trhard_(false), trfbp_(), trcount_(0), trsize_(0) {
+ _assert_(true);
+ }
+ /**
+ * Destructor.
+ * @note If the database is not closed, it is closed implicitly.
+ */
+ virtual ~HashDB() {
+ _assert_(true);
+ if (omode_ != 0) close();
+ if (!curs_.empty()) {
+ CursorList::const_iterator cit = curs_.begin();
+ CursorList::const_iterator citend = curs_.end();
+ while (cit != citend) {
+ Cursor* cur = *cit;
+ cur->db_ = NULL;
+ ++cit;
+ }
+ }
+ }
+ /**
+ * Accept a visitor to a record.
+ * @param kbuf the pointer to the key region.
+ * @param ksiz the size of the key region.
+ * @param visitor a visitor object.
+ * @param writable true for writable operation, or false for read-only operation.
+ * @return true on success, or false on failure.
+ * @note The operation for each record is performed atomically and other threads accessing the
+ * same record are blocked. To avoid deadlock, any explicit database operation must not be
+ * performed in this function.
+ */
+ bool accept(const char* kbuf, size_t ksiz, Visitor* visitor, bool writable = true) {
+ _assert_(kbuf && ksiz <= MEMMAXSIZ && visitor);
+ mlock_.lock_reader();
+ if (omode_ == 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ mlock_.unlock();
+ return false;
+ }
+ if (writable) {
+ if (!writer_) {
+ set_error(_KCCODELINE_, Error::NOPERM, "permission denied");
+ mlock_.unlock();
+ return false;
+ }
+ if (!(flags_ & FOPEN) && !autotran_ && !tran_ && !set_flag(FOPEN, true)) {
+ mlock_.unlock();
+ return false;
+ }
+ }
+ bool err = false;
+ uint64_t hash = hash_record(kbuf, ksiz);
+ uint32_t pivot = fold_hash(hash);
+ int64_t bidx = hash % bnum_;
+ size_t lidx = bidx % RLOCKSLOT;
+ if (writable) {
+ rlock_.lock_writer(lidx);
+ } else {
+ rlock_.lock_reader(lidx);
+ }
+ if (!accept_impl(kbuf, ksiz, visitor, bidx, pivot, false)) err = true;
+ rlock_.unlock(lidx);
+ mlock_.unlock();
+ if (!err && dfunit_ > 0 && frgcnt_ >= dfunit_ && mlock_.lock_writer_try()) {
+ int64_t unit = frgcnt_;
+ if (unit >= dfunit_) {
+ if (unit > DFRGMAX) unit = DFRGMAX;
+ if (!defrag_impl(unit * DFRGCEF)) err = true;
+ frgcnt_ -= unit;
+ }
+ mlock_.unlock();
+ }
+ return !err;
+ }
+ /**
+ * Accept a visitor to multiple records at once.
+ * @param keys specifies a string vector of the keys.
+ * @param visitor a visitor object.
+ * @param writable true for writable operation, or false for read-only operation.
+ * @return true on success, or false on failure.
+ * @note The operations for specified records are performed atomically and other threads
+ * accessing the same records are blocked. To avoid deadlock, any explicit database operation
+ * must not be performed in this function.
+ */
+ bool accept_bulk(const std::vector<std::string>& keys, Visitor* visitor,
+ bool writable = true) {
+ _assert_(visitor);
+ mlock_.lock_reader();
+ if (omode_ == 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ mlock_.unlock();
+ return false;
+ }
+ if (writable) {
+ if (!writer_) {
+ set_error(_KCCODELINE_, Error::NOPERM, "permission denied");
+ mlock_.unlock();
+ return false;
+ }
+ if (!(flags_ & FOPEN) && !autotran_ && !tran_ && !set_flag(FOPEN, true)) {
+ mlock_.unlock();
+ return false;
+ }
+ }
+ visitor->visit_before();
+ size_t knum = keys.size();
+ if (knum < 1) {
+ visitor->visit_after();
+ mlock_.unlock();
+ return true;
+ }
+ bool err = false;
+ struct RecordKey {
+ const char* kbuf;
+ size_t ksiz;
+ uint32_t pivot;
+ uint64_t bidx;
+ };
+ RecordKey* rkeys = new RecordKey[knum];
+ std::set<size_t> lidxs;
+ for (size_t i = 0; i < knum; i++) {
+ const std::string& key = keys[i];
+ RecordKey* rkey = rkeys + i;
+ rkey->kbuf = key.data();
+ rkey->ksiz = key.size();
+ uint64_t hash = hash_record(rkey->kbuf, rkey->ksiz);
+ rkey->pivot = fold_hash(hash);
+ rkey->bidx = hash % bnum_;
+ lidxs.insert(rkey->bidx % RLOCKSLOT);
+ }
+ std::set<size_t>::iterator lit = lidxs.begin();
+ std::set<size_t>::iterator litend = lidxs.end();
+ while (lit != litend) {
+ if (writable) {
+ rlock_.lock_writer(*lit);
+ } else {
+ rlock_.lock_reader(*lit);
+ }
+ ++lit;
+ }
+ for (size_t i = 0; i < knum; i++) {
+ RecordKey* rkey = rkeys + i;
+ if (!accept_impl(rkey->kbuf, rkey->ksiz, visitor, rkey->bidx, rkey->pivot, false)) {
+ err = true;
+ break;
+ }
+ }
+ lit = lidxs.begin();
+ litend = lidxs.end();
+ while (lit != litend) {
+ rlock_.unlock(*lit);
+ ++lit;
+ }
+ delete[] rkeys;
+ visitor->visit_after();
+ mlock_.unlock();
+ if (!err && dfunit_ > 0 && frgcnt_ >= dfunit_ && mlock_.lock_writer_try()) {
+ int64_t unit = frgcnt_;
+ if (unit >= dfunit_) {
+ if (unit > DFRGMAX) unit = DFRGMAX;
+ if (!defrag_impl(unit * DFRGCEF)) err = true;
+ frgcnt_ -= unit;
+ }
+ mlock_.unlock();
+ }
+ return !err;
+ }
+ /**
+ * Iterate to accept a visitor for each record.
+ * @param visitor a visitor object.
+ * @param writable true for writable operation, or false for read-only operation.
+ * @param checker a progress checker object. If it is NULL, no checking is performed.
+ * @return true on success, or false on failure.
+ * @note The whole iteration is performed atomically and other threads are blocked. To avoid
+ * deadlock, any explicit database operation must not be performed in this function.
+ */
+ bool iterate(Visitor *visitor, bool writable = true, ProgressChecker* checker = NULL) {
+ _assert_(visitor);
+ ScopedRWLock lock(&mlock_, true);
+ if (omode_ == 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ return false;
+ }
+ if (writable) {
+ if (!writer_) {
+ set_error(_KCCODELINE_, Error::NOPERM, "permission denied");
+ return false;
+ }
+ if (!(flags_ & FOPEN) && !autotran_ && !tran_ && !set_flag(FOPEN, true)) {
+ mlock_.unlock();
+ return false;
+ }
+ }
+ ScopedVisitor svis(visitor);
+ bool err = false;
+ if (!iterate_impl(visitor, checker)) err = true;
+ trigger_meta(MetaTrigger::ITERATE, "iterate");
+ return !err;
+ }
+ /**
+ * Scan each record in parallel.
+ * @param visitor a visitor object.
+ * @param thnum the number of worker threads.
+ * @param checker a progress checker object. If it is NULL, no checking is performed.
+ * @return true on success, or false on failure.
+ * @note This function is for reading records and not for updating ones. The return value of
+ * the visitor is just ignored. To avoid deadlock, any explicit database operation must not
+ * be performed in this function.
+ */
+ bool scan_parallel(Visitor *visitor, size_t thnum, ProgressChecker* checker = NULL) {
+ _assert_(visitor && thnum <= MEMMAXSIZ);
+ ScopedRWLock lock(&mlock_, false);
+ if (omode_ == 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ return false;
+ }
+ if (thnum < 1) thnum = 1;
+ if (thnum > (size_t)INT8MAX) thnum = INT8MAX;
+ if ((int64_t)thnum > bnum_) thnum = bnum_;
+ ScopedVisitor svis(visitor);
+ rlock_.lock_reader_all();
+ bool err = false;
+ if (!scan_parallel_impl(visitor, thnum, checker)) err = true;
+ rlock_.unlock_all();
+ trigger_meta(MetaTrigger::ITERATE, "scan_parallel");
+ return !err;
+ }
+ /**
+ * Get the last happened error.
+ * @return the last happened error.
+ */
+ Error error() const {
+ _assert_(true);
+ return error_;
+ }
+ /**
+ * Set the error information.
+ * @param file the file name of the program source code.
+ * @param line the line number of the program source code.
+ * @param func the function name of the program source code.
+ * @param code an error code.
+ * @param message a supplement message.
+ */
+ void set_error(const char* file, int32_t line, const char* func,
+ Error::Code code, const char* message) {
+ _assert_(file && line > 0 && func && message);
+ error_->set(code, message);
+ if (code == Error::BROKEN || code == Error::SYSTEM) flags_ |= FFATAL;
+ if (logger_) {
+ Logger::Kind kind = code == Error::BROKEN || code == Error::SYSTEM ?
+ Logger::ERROR : Logger::INFO;
+ if (kind & logkinds_)
+ report(file, line, func, kind, "%d: %s: %s", code, Error::codename(code), message);
+ }
+ }
+ /**
+ * Open a database file.
+ * @param path the path of a database file.
+ * @param mode the connection mode. HashDB::OWRITER as a writer, HashDB::OREADER as a
+ * reader. The following may be added to the writer mode by bitwise-or: HashDB::OCREATE,
+ * which means it creates a new database if the file does not exist, HashDB::OTRUNCATE, which
+ * means it creates a new database regardless if the file exists, HashDB::OAUTOTRAN, which
+ * means each updating operation is performed in implicit transaction, HashDB::OAUTOSYNC,
+ * which means each updating operation is followed by implicit synchronization with the file
+ * system. The following may be added to both of the reader mode and the writer mode by
+ * bitwise-or: HashDB::ONOLOCK, which means it opens the database file without file locking,
+ * HashDB::OTRYLOCK, which means locking is performed without blocking, HashDB::ONOREPAIR,
+ * which means the database file is not repaired implicitly even if file destruction is
+ * detected.
+ * @return true on success, or false on failure.
+ * @note Every opened database must be closed by the HashDB::close method when it is no
+ * longer in use. It is not allowed for two or more database objects in the same process to
+ * keep their connections to the same database file at the same time.
+ */
+ bool open(const std::string& path, uint32_t mode = OWRITER | OCREATE) {
+ _assert_(true);
+ ScopedRWLock lock(&mlock_, true);
+ if (omode_ != 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "already opened");
+ return false;
+ }
+ report(_KCCODELINE_, Logger::DEBUG, "opening the database (path=%s)", path.c_str());
+ writer_ = false;
+ autotran_ = false;
+ autosync_ = false;
+ reorg_ = false;
+ trim_ = false;
+ uint32_t fmode = File::OREADER;
+ if (mode & OWRITER) {
+ writer_ = true;
+ fmode = File::OWRITER;
+ if (mode & OCREATE) fmode |= File::OCREATE;
+ if (mode & OTRUNCATE) fmode |= File::OTRUNCATE;
+ if (mode & OAUTOTRAN) autotran_ = true;
+ if (mode & OAUTOSYNC) autosync_ = true;
+ }
+ if (mode & ONOLOCK) fmode |= File::ONOLOCK;
+ if (mode & OTRYLOCK) fmode |= File::OTRYLOCK;
+ if (!file_.open(path, fmode, msiz_)) {
+ const char* emsg = file_.error();
+ Error::Code code = Error::SYSTEM;
+ if (std::strstr(emsg, "(permission denied)") || std::strstr(emsg, "(directory)")) {
+ code = Error::NOPERM;
+ } else if (std::strstr(emsg, "(file not found)") || std::strstr(emsg, "(invalid path)")) {
+ code = Error::NOREPOS;
+ }
+ set_error(_KCCODELINE_, code, emsg);
+ return false;
+ }
+ if (file_.recovered()) report(_KCCODELINE_, Logger::WARN, "recovered by the WAL file");
+ if ((mode & OWRITER) && file_.size() < 1) {
+ calc_meta();
+ libver_ = LIBVER;
+ librev_ = LIBREV;
+ fmtver_ = FMTVER;
+ chksum_ = calc_checksum();
+ lsiz_ = roff_;
+ if (!file_.truncate(lsiz_)) {
+ set_error(_KCCODELINE_, Error::SYSTEM, file_.error());
+ file_.close();
+ return false;
+ }
+ if (!dump_meta()) {
+ file_.close();
+ return false;
+ }
+ if (autosync_ && !File::synchronize_whole()) {
+ set_error(_KCCODELINE_, Error::SYSTEM, "synchronizing the file system failed");
+ file_.close();
+ return false;
+ }
+ }
+ if (!load_meta()) {
+ file_.close();
+ return false;
+ }
+ calc_meta();
+ uint8_t chksum = calc_checksum();
+ if (chksum != chksum_) {
+ set_error(_KCCODELINE_, Error::INVALID, "invalid module checksum");
+ report(_KCCODELINE_, Logger::WARN, "saved=%02X calculated=%02X",
+ (unsigned)chksum_, (unsigned)chksum);
+ file_.close();
+ return false;
+ }
+ if (((flags_ & FOPEN) || (flags_ & FFATAL)) && !(mode & ONOREPAIR) && !(mode & ONOLOCK)) {
+ if (!reorganize_file(path)) {
+ file_.close();
+ return false;
+ }
+ if (!file_.close()) {
+ set_error(_KCCODELINE_, Error::SYSTEM, file_.error());
+ return false;
+ }
+ if (!file_.open(path, fmode, msiz_)) {
+ set_error(_KCCODELINE_, Error::SYSTEM, file_.error());
+ return false;
+ }
+ if (!load_meta()) {
+ file_.close();
+ return false;
+ }
+ calc_meta();
+ reorg_ = true;
+ }
+ if (type_ == 0 || apow_ > MAXAPOW || fpow_ > MAXFPOW ||
+ bnum_ < 1 || count_ < 0 || lsiz_ < roff_) {
+ set_error(_KCCODELINE_, Error::BROKEN, "invalid meta data");
+ report(_KCCODELINE_, Logger::WARN, "type=0x%02X apow=%d fpow=%d bnum=%lld count=%lld"
+ " lsiz=%lld fsiz=%lld", (unsigned)type_, (int)apow_, (int)fpow_, (long long)bnum_,
+ (long long)count_, (long long)lsiz_, (long long)file_.size());
+ file_.close();
+ return false;
+ }
+ if (file_.size() < lsiz_) {
+ set_error(_KCCODELINE_, Error::BROKEN, "inconsistent file size");
+ report(_KCCODELINE_, Logger::WARN, "lsiz=%lld fsiz=%lld",
+ (long long)lsiz_, (long long)file_.size());
+ file_.close();
+ return false;
+ }
+ if (file_.size() != lsiz_ && !(mode & ONOREPAIR) && !(mode & ONOLOCK) && !trim_file(path)) {
+ file_.close();
+ return false;
+ }
+ if (mode & OWRITER) {
+ if (!(flags_ & FOPEN) && !(flags_ & FFATAL) && !load_free_blocks()) {
+ file_.close();
+ return false;
+ }
+ if (!dump_empty_free_blocks()) {
+ file_.close();
+ return false;
+ }
+ if (!autotran_ && !set_flag(FOPEN, true)) {
+ file_.close();
+ return false;
+ }
+ }
+ path_.append(path);
+ omode_ = mode;
+ trigger_meta(MetaTrigger::OPEN, "open");
+ return true;
+ }
+ /**
+ * Close the database file.
+ * @return true on success, or false on failure.
+ */
+ bool close() {
+ _assert_(true);
+ ScopedRWLock lock(&mlock_, true);
+ if (omode_ == 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ return false;
+ }
+ report(_KCCODELINE_, Logger::DEBUG, "closing the database (path=%s)", path_.c_str());
+ bool err = false;
+ if (tran_ && !abort_transaction()) err = true;
+ disable_cursors();
+ if (writer_) {
+ if (!dump_free_blocks()) err = true;
+ if (!dump_meta()) err = true;
+ }
+ if (!file_.close()) {
+ set_error(_KCCODELINE_, Error::SYSTEM, file_.error());
+ err = true;
+ }
+ fbp_.clear();
+ omode_ = 0;
+ path_.clear();
+ trigger_meta(MetaTrigger::CLOSE, "close");
+ return !err;
+ }
+ /**
+ * Synchronize updated contents with the file and the device.
+ * @param hard true for physical synchronization with the device, or false for logical
+ * synchronization with the file system.
+ * @param proc a postprocessor object. If it is NULL, no postprocessing is performed.
+ * @param checker a progress checker object. If it is NULL, no checking is performed.
+ * @return true on success, or false on failure.
+ * @note The operation of the postprocessor is performed atomically and other threads accessing
+ * the same record are blocked. To avoid deadlock, any explicit database operation must not
+ * be performed in this function.
+ */
+ bool synchronize(bool hard = false, FileProcessor* proc = NULL,
+ ProgressChecker* checker = NULL) {
+ _assert_(true);
+ ScopedRWLock lock(&mlock_, false);
+ if (omode_ == 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ return false;
+ }
+ rlock_.lock_reader_all();
+ bool err = false;
+ if (!synchronize_impl(hard, proc, checker)) err = true;
+ trigger_meta(MetaTrigger::SYNCHRONIZE, "synchronize");
+ rlock_.unlock_all();
+ return !err;
+ }
+ /**
+ * Occupy database by locking and do something meanwhile.
+ * @param writable true to use writer lock, or false to use reader lock.
+ * @param proc a processor object. If it is NULL, no processing is performed.
+ * @return true on success, or false on failure.
+ * @note The operation of the processor is performed atomically and other threads accessing
+ * the same record are blocked. To avoid deadlock, any explicit database operation must not
+ * be performed in this function.
+ */
+ bool occupy(bool writable = true, FileProcessor* proc = NULL) {
+ _assert_(true);
+ ScopedRWLock lock(&mlock_, writable);
+ bool err = false;
+ if (proc && !proc->process(path_, count_, lsiz_)) {
+ set_error(_KCCODELINE_, Error::LOGIC, "processing failed");
+ err = true;
+ }
+ trigger_meta(MetaTrigger::OCCUPY, "occupy");
+ return !err;
+ }
+ /**
+ * Begin transaction.
+ * @param hard true for physical synchronization with the device, or false for logical
+ * synchronization with the file system.
+ * @return true on success, or false on failure.
+ */
+ bool begin_transaction(bool hard = false) {
+ _assert_(true);
+ uint32_t wcnt = 0;
+ while (true) {
+ mlock_.lock_writer();
+ if (omode_ == 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ mlock_.unlock();
+ return false;
+ }
+ if (!writer_) {
+ set_error(_KCCODELINE_, Error::NOPERM, "permission denied");
+ mlock_.unlock();
+ return false;
+ }
+ if (!tran_) break;
+ mlock_.unlock();
+ if (wcnt >= LOCKBUSYLOOP) {
+ Thread::chill();
+ } else {
+ Thread::yield();
+ wcnt++;
+ }
+ }
+ trhard_ = hard;
+ if (!begin_transaction_impl()) {
+ mlock_.unlock();
+ return false;
+ }
+ tran_ = true;
+ trigger_meta(MetaTrigger::BEGINTRAN, "begin_transaction");
+ mlock_.unlock();
+ return true;
+ }
+ /**
+ * Try to begin transaction.
+ * @param hard true for physical synchronization with the device, or false for logical
+ * synchronization with the file system.
+ * @return true on success, or false on failure.
+ */
+ bool begin_transaction_try(bool hard = false) {
+ _assert_(true);
+ mlock_.lock_writer();
+ if (omode_ == 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ mlock_.unlock();
+ return false;
+ }
+ if (!writer_) {
+ set_error(_KCCODELINE_, Error::NOPERM, "permission denied");
+ mlock_.unlock();
+ return false;
+ }
+ if (tran_) {
+ set_error(_KCCODELINE_, Error::LOGIC, "competition avoided");
+ mlock_.unlock();
+ return false;
+ }
+ trhard_ = hard;
+ if (!begin_transaction_impl()) {
+ mlock_.unlock();
+ return false;
+ }
+ tran_ = true;
+ trigger_meta(MetaTrigger::BEGINTRAN, "begin_transaction_try");
+ mlock_.unlock();
+ return true;
+ }
+ /**
+ * End transaction.
+ * @param commit true to commit the transaction, or false to abort the transaction.
+ * @return true on success, or false on failure.
+ */
+ bool end_transaction(bool commit = true) {
+ _assert_(true);
+ ScopedRWLock lock(&mlock_, true);
+ if (omode_ == 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ return false;
+ }
+ if (!tran_) {
+ set_error(_KCCODELINE_, Error::INVALID, "not in transaction");
+ return false;
+ }
+ bool err = false;
+ if (commit) {
+ if (!commit_transaction()) err = true;
+ } else {
+ if (!abort_transaction()) err = true;
+ }
+ tran_ = false;
+ trigger_meta(commit ? MetaTrigger::COMMITTRAN : MetaTrigger::ABORTTRAN, "end_transaction");
+ return !err;
+ }
+ /**
+ * Remove all records.
+ * @return true on success, or false on failure.
+ */
+ bool clear() {
+ _assert_(true);
+ ScopedRWLock lock(&mlock_, true);
+ if (omode_ == 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ return false;
+ }
+ if (!writer_) {
+ set_error(_KCCODELINE_, Error::NOPERM, "permission denied");
+ return false;
+ }
+ disable_cursors();
+ if (!file_.truncate(HEADSIZ)) {
+ set_error(_KCCODELINE_, Error::SYSTEM, file_.error());
+ return false;
+ }
+ fbp_.clear();
+ bool err = false;
+ reorg_ = false;
+ trim_ = false;
+ flags_ = 0;
+ flagopen_ = false;
+ count_ = 0;
+ lsiz_ = roff_;
+ psiz_ = lsiz_;
+ dfcur_ = roff_;
+ std::memset(opaque_, 0, sizeof(opaque_));
+ if (!file_.truncate(lsiz_)) {
+ set_error(_KCCODELINE_, Error::SYSTEM, file_.error());
+ err = true;
+ }
+ if (!dump_meta()) err = true;
+ if (!autotran_ && !set_flag(FOPEN, true)) err = true;
+ trigger_meta(MetaTrigger::CLEAR, "clear");
+ return true;
+ }
+ /**
+ * Get the number of records.
+ * @return the number of records, or -1 on failure.
+ */
+ int64_t count() {
+ _assert_(true);
+ ScopedRWLock lock(&mlock_, false);
+ if (omode_ == 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ return -1;
+ }
+ return count_;
+ }
+ /**
+ * Get the size of the database file.
+ * @return the size of the database file in bytes, or -1 on failure.
+ */
+ int64_t size() {
+ _assert_(true);
+ ScopedRWLock lock(&mlock_, false);
+ if (omode_ == 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ return -1;
+ }
+ return lsiz_;
+ }
+ /**
+ * Get the path of the database file.
+ * @return the path of the database file, or an empty string on failure.
+ */
+ std::string path() {
+ _assert_(true);
+ ScopedRWLock lock(&mlock_, false);
+ if (omode_ == 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ return "";
+ }
+ return path_;
+ }
+ /**
+ * Get the miscellaneous status information.
+ * @param strmap a string map to contain the result.
+ * @return true on success, or false on failure.
+ */
+ bool status(std::map<std::string, std::string>* strmap) {
+ _assert_(strmap);
+ ScopedRWLock lock(&mlock_, true);
+ if (omode_ == 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ return false;
+ }
+ (*strmap)["type"] = strprintf("%u", (unsigned)TYPEHASH);
+ (*strmap)["realtype"] = strprintf("%u", (unsigned)type_);
+ (*strmap)["path"] = path_;
+ (*strmap)["libver"] = strprintf("%u", libver_);
+ (*strmap)["librev"] = strprintf("%u", librev_);
+ (*strmap)["fmtver"] = strprintf("%u", fmtver_);
+ (*strmap)["chksum"] = strprintf("%u", chksum_);
+ (*strmap)["flags"] = strprintf("%u", flags_);
+ (*strmap)["apow"] = strprintf("%u", apow_);
+ (*strmap)["fpow"] = strprintf("%u", fpow_);
+ (*strmap)["opts"] = strprintf("%u", opts_);
+ (*strmap)["bnum"] = strprintf("%lld", (long long)bnum_);
+ (*strmap)["msiz"] = strprintf("%lld", (long long)msiz_);
+ (*strmap)["dfunit"] = strprintf("%lld", (long long)dfunit_);
+ (*strmap)["frgcnt"] = strprintf("%lld", (long long)(frgcnt_ > 0 ? (int64_t)frgcnt_ : 0));
+ (*strmap)["realsize"] = strprintf("%lld", (long long)file_.size());
+ (*strmap)["recovered"] = strprintf("%d", file_.recovered());
+ (*strmap)["reorganized"] = strprintf("%d", reorg_);
+ (*strmap)["trimmed"] = strprintf("%d", trim_);
+ if (strmap->count("opaque") > 0)
+ (*strmap)["opaque"] = std::string(opaque_, sizeof(opaque_));
+ if (strmap->count("fbpnum_used") > 0) {
+ if (writer_) {
+ (*strmap)["fbpnum_used"] = strprintf("%lld", (long long)fbp_.size());
+ } else {
+ if (!load_free_blocks()) return false;
+ (*strmap)["fbpnum_used"] = strprintf("%lld", (long long)fbp_.size());
+ fbp_.clear();
+ }
+ }
+ if (strmap->count("bnum_used") > 0) {
+ int64_t cnt = 0;
+ for (int64_t i = 0; i < bnum_; i++) {
+ if (get_bucket(i) > 0) cnt++;
+ }
+ (*strmap)["bnum_used"] = strprintf("%lld", (long long)cnt);
+ }
+ (*strmap)["count"] = strprintf("%lld", (long long)count_);
+ (*strmap)["size"] = strprintf("%lld", (long long)lsiz_);
+ return true;
+ }
+ /**
+ * Create a cursor object.
+ * @return the return value is the created cursor object.
+ * @note Because the object of the return value is allocated by the constructor, it should be
+ * released with the delete operator when it is no longer in use.
+ */
+ Cursor* cursor() {
+ _assert_(true);
+ return new Cursor(this);
+ }
+ /**
+ * Write a log message.
+ * @param file the file name of the program source code.
+ * @param line the line number of the program source code.
+ * @param func the function name of the program source code.
+ * @param kind the kind of the event. Logger::DEBUG for debugging, Logger::INFO for normal
+ * information, Logger::WARN for warning, and Logger::ERROR for fatal error.
+ * @param message the supplement message.
+ */
+ void log(const char* file, int32_t line, const char* func, Logger::Kind kind,
+ const char* message) {
+ _assert_(file && line > 0 && func && message);
+ ScopedRWLock lock(&mlock_, false);
+ if (!logger_) return;
+ logger_->log(file, line, func, kind, message);
+ }
+ /**
+ * Set the internal logger.
+ * @param logger the logger object.
+ * @param kinds kinds of logged messages by bitwise-or: Logger::DEBUG for debugging,
+ * Logger::INFO for normal information, Logger::WARN for warning, and Logger::ERROR for fatal
+ * error.
+ * @return true on success, or false on failure.
+ */
+ bool tune_logger(Logger* logger, uint32_t kinds = Logger::WARN | Logger::ERROR) {
+ _assert_(logger);
+ ScopedRWLock lock(&mlock_, true);
+ if (omode_ != 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "already opened");
+ return false;
+ }
+ logger_ = logger;
+ logkinds_ = kinds;
+ return true;
+ }
+ /**
+ * Set the internal meta operation trigger.
+ * @param trigger the trigger object.
+ * @return true on success, or false on failure.
+ */
+ bool tune_meta_trigger(MetaTrigger* trigger) {
+ _assert_(trigger);
+ ScopedRWLock lock(&mlock_, true);
+ if (omode_ != 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "already opened");
+ return false;
+ }
+ mtrigger_ = trigger;
+ return true;
+ }
+ /**
+ * Set the power of the alignment of record size.
+ * @param apow the power of the alignment of record size.
+ * @return true on success, or false on failure.
+ */
+ bool tune_alignment(int8_t apow) {
+ _assert_(true);
+ ScopedRWLock lock(&mlock_, true);
+ if (omode_ != 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "already opened");
+ return false;
+ }
+ apow_ = apow >= 0 ? apow : DEFAPOW;
+ if (apow_ > MAXAPOW) apow_ = MAXAPOW;
+ return true;
+ }
+ /**
+ * Set the power of the capacity of the free block pool.
+ * @param fpow the power of the capacity of the free block pool.
+ * @return true on success, or false on failure.
+ */
+ bool tune_fbp(int8_t fpow) {
+ _assert_(true);
+ ScopedRWLock lock(&mlock_, true);
+ if (omode_ != 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "already opened");
+ return false;
+ }
+ fpow_ = fpow >= 0 ? fpow : DEFFPOW;
+ if (fpow_ > MAXFPOW) fpow_ = MAXFPOW;
+ return true;
+ }
+ /**
+ * Set the optional features.
+ * @param opts the optional features by bitwise-or: HashDB::TSMALL to use 32-bit addressing,
+ * HashDB::TLINEAR to use linear collision chaining, HashDB::TCOMPRESS to compress each record.
+ * @return true on success, or false on failure.
+ */
+ bool tune_options(int8_t opts) {
+ _assert_(true);
+ ScopedRWLock lock(&mlock_, true);
+ if (omode_ != 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "already opened");
+ return false;
+ }
+ opts_ = opts;
+ return true;
+ }
+ /**
+ * Set the number of buckets of the hash table.
+ * @param bnum the number of buckets of the hash table.
+ * @return true on success, or false on failure.
+ */
+ bool tune_buckets(int64_t bnum) {
+ _assert_(true);
+ ScopedRWLock lock(&mlock_, true);
+ if (omode_ != 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "already opened");
+ return false;
+ }
+ bnum_ = bnum > 0 ? bnum : DEFBNUM;
+ if (bnum_ > INT16MAX) bnum_ = nearbyprime(bnum_);
+ return true;
+ }
+ /**
+ * Set the size of the internal memory-mapped region.
+ * @param msiz the size of the internal memory-mapped region.
+ * @return true on success, or false on failure.
+ */
+ bool tune_map(int64_t msiz) {
+ _assert_(true);
+ ScopedRWLock lock(&mlock_, true);
+ if (omode_ != 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "already opened");
+ return false;
+ }
+ msiz_ = msiz >= 0 ? msiz : DEFMSIZ;
+ return true;
+ }
+ /**
+ * Set the unit step number of auto defragmentation.
+ * @param dfunit the unit step number of auto defragmentation.
+ * @return true on success, or false on failure.
+ */
+ bool tune_defrag(int64_t dfunit) {
+ _assert_(true);
+ ScopedRWLock lock(&mlock_, true);
+ if (omode_ != 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "already opened");
+ return false;
+ }
+ dfunit_ = dfunit > 0 ? dfunit : 0;
+ return true;
+ }
+ /**
+ * Set the data compressor.
+ * @param comp the data compressor object.
+ * @return true on success, or false on failure.
+ */
+ bool tune_compressor(Compressor* comp) {
+ _assert_(comp);
+ ScopedRWLock lock(&mlock_, true);
+ if (omode_ != 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "already opened");
+ return false;
+ }
+ embcomp_ = comp;
+ return true;
+ }
+ /**
+ * Get the opaque data.
+ * @return the pointer to the opaque data region, whose size is 16 bytes.
+ */
+ char* opaque() {
+ _assert_(true);
+ ScopedRWLock lock(&mlock_, false);
+ if (omode_ == 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ return NULL;
+ }
+ return opaque_;
+ }
+ /**
+ * Synchronize the opaque data.
+ * @return true on success, or false on failure.
+ */
+ bool synchronize_opaque() {
+ _assert_(true);
+ ScopedRWLock lock(&mlock_, true);
+ if (omode_ == 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ return false;
+ }
+ if (!writer_) {
+ set_error(_KCCODELINE_, Error::NOPERM, "permission denied");
+ return false;
+ }
+ bool err = false;
+ if (!dump_opaque()) err = true;
+ return !err;
+ }
+ /**
+ * Perform defragmentation of the file.
+ * @param step the number of steps. If it is not more than 0, the whole region is defraged.
+ * @return true on success, or false on failure.
+ */
+ bool defrag(int64_t step = 0) {
+ _assert_(true);
+ ScopedRWLock lock(&mlock_, true);
+ if (omode_ == 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ return false;
+ }
+ if (!writer_) {
+ set_error(_KCCODELINE_, Error::NOPERM, "permission denied");
+ return false;
+ }
+ bool err = false;
+ if (step > 0) {
+ if (!defrag_impl(step)) err = true;
+ } else {
+ dfcur_ = roff_;
+ if (!defrag_impl(INT64MAX)) err = true;
+ }
+ frgcnt_ = 0;
+ return !err;
+ }
+ /**
+ * Get the status flags.
+ * @return the status flags, or 0 on failure.
+ */
+ uint8_t flags() {
+ _assert_(true);
+ ScopedRWLock lock(&mlock_, false);
+ if (omode_ == 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ return 0;
+ }
+ return flags_;
+ }
+ protected:
+ /**
+ * Report a message for debugging.
+ * @param file the file name of the program source code.
+ * @param line the line number of the program source code.
+ * @param func the function name of the program source code.
+ * @param kind the kind of the event. Logger::DEBUG for debugging, Logger::INFO for normal
+ * information, Logger::WARN for warning, and Logger::ERROR for fatal error.
+ * @param format the printf-like format string.
+ * @param ... used according to the format string.
+ */
+ void report(const char* file, int32_t line, const char* func, Logger::Kind kind,
+ const char* format, ...) {
+ _assert_(file && line > 0 && func && format);
+ if (!logger_ || !(kind & logkinds_)) return;
+ std::string message;
+ strprintf(&message, "%s: ", path_.empty() ? "-" : path_.c_str());
+ va_list ap;
+ va_start(ap, format);
+ vstrprintf(&message, format, ap);
+ va_end(ap);
+ logger_->log(file, line, func, kind, message.c_str());
+ }
+ /**
+ * Report a message for debugging with variable number of arguments.
+ * @param file the file name of the program source code.
+ * @param line the line number of the program source code.
+ * @param func the function name of the program source code.
+ * @param kind the kind of the event. Logger::DEBUG for debugging, Logger::INFO for normal
+ * information, Logger::WARN for warning, and Logger::ERROR for fatal error.
+ * @param format the printf-like format string.
+ * @param ap used according to the format string.
+ */
+ void report_valist(const char* file, int32_t line, const char* func, Logger::Kind kind,
+ const char* format, va_list ap) {
+ _assert_(file && line > 0 && func && format);
+ if (!logger_ || !(kind & logkinds_)) return;
+ std::string message;
+ strprintf(&message, "%s: ", path_.empty() ? "-" : path_.c_str());
+ vstrprintf(&message, format, ap);
+ logger_->log(file, line, func, kind, message.c_str());
+ }
+ /**
+ * Report the content of a binary buffer for debugging.
+ * @param file the file name of the epicenter.
+ * @param line the line number of the epicenter.
+ * @param func the function name of the program source code.
+ * @param kind the kind of the event. Logger::DEBUG for debugging, Logger::INFO for normal
+ * information, Logger::WARN for warning, and Logger::ERROR for fatal error.
+ * @param name the name of the information.
+ * @param buf the binary buffer.
+ * @param size the size of the binary buffer
+ */
+ void report_binary(const char* file, int32_t line, const char* func, Logger::Kind kind,
+ const char* name, const char* buf, size_t size) {
+ _assert_(file && line > 0 && func && name && buf && size <= MEMMAXSIZ);
+ if (!logger_) return;
+ char* hex = hexencode(buf, size);
+ report(file, line, func, kind, "%s=%s", name, hex);
+ delete[] hex;
+ }
+ /**
+ * Trigger a meta database operation.
+ * @param kind the kind of the event. MetaTrigger::OPEN for opening, MetaTrigger::CLOSE for
+ * closing, MetaTrigger::CLEAR for clearing, MetaTrigger::ITERATE for iteration,
+ * MetaTrigger::SYNCHRONIZE for synchronization, MetaTrigger::BEGINTRAN for beginning
+ * transaction, MetaTrigger::COMMITTRAN for committing transaction, MetaTrigger::ABORTTRAN
+ * for aborting transaction, and MetaTrigger::MISC for miscellaneous operations.
+ * @param message the supplement message.
+ */
+ void trigger_meta(MetaTrigger::Kind kind, const char* message) {
+ _assert_(message);
+ if (mtrigger_) mtrigger_->trigger(kind, message);
+ }
+ /**
+ * Set the database type.
+ * @param type the database type.
+ * @return true on success, or false on failure.
+ */
+ bool tune_type(int8_t type) {
+ _assert_(true);
+ ScopedRWLock lock(&mlock_, true);
+ if (omode_ != 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "already opened");
+ return false;
+ }
+ type_ = type;
+ return true;
+ }
+ /**
+ * Get the library version.
+ * @return the library version, or 0 on failure.
+ */
+ uint8_t libver() {
+ _assert_(true);
+ ScopedRWLock lock(&mlock_, false);
+ if (omode_ == 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ return 0;
+ }
+ return libver_;
+ }
+ /**
+ * Get the library revision.
+ * @return the library revision, or 0 on failure.
+ */
+ uint8_t librev() {
+ _assert_(true);
+ ScopedRWLock lock(&mlock_, false);
+ if (omode_ == 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ return 0;
+ }
+ return librev_;
+ }
+ /**
+ * Get the format version.
+ * @return the format version, or 0 on failure.
+ */
+ uint8_t fmtver() {
+ _assert_(true);
+ ScopedRWLock lock(&mlock_, false);
+ if (omode_ == 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ return 0;
+ }
+ return fmtver_;
+ }
+ /**
+ * Get the module checksum.
+ * @return the module checksum, or 0 on failure.
+ */
+ uint8_t chksum() {
+ _assert_(true);
+ ScopedRWLock lock(&mlock_, false);
+ if (omode_ == 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ return 0;
+ }
+ return chksum_;
+ }
+ /**
+ * Get the database type.
+ * @return the database type, or 0 on failure.
+ */
+ uint8_t type() {
+ _assert_(true);
+ ScopedRWLock lock(&mlock_, false);
+ if (omode_ == 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ return 0;
+ }
+ return type_;
+ }
+ /**
+ * Get the alignment power.
+ * @return the alignment power, or 0 on failure.
+ */
+ uint8_t apow() {
+ _assert_(true);
+ ScopedRWLock lock(&mlock_, false);
+ if (omode_ == 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ return 0;
+ }
+ return apow_;
+ }
+ /**
+ * Get the free block pool power.
+ * @return the free block pool power, or 0 on failure.
+ */
+ uint8_t fpow() {
+ _assert_(true);
+ ScopedRWLock lock(&mlock_, false);
+ if (omode_ == 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ return 0;
+ }
+ return fpow_;
+ }
+ /**
+ * Get the options.
+ * @return the options, or 0 on failure.
+ */
+ uint8_t opts() {
+ _assert_(true);
+ ScopedRWLock lock(&mlock_, false);
+ if (omode_ == 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ return 0;
+ }
+ return opts_;
+ }
+ /**
+ * Get the bucket number.
+ * @return the bucket number, or 0 on failure.
+ */
+ int64_t bnum() {
+ _assert_(true);
+ ScopedRWLock lock(&mlock_, false);
+ if (omode_ == 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ return 0;
+ }
+ return bnum_;
+ }
+ /**
+ * Get the size of the internal memory-mapped region.
+ * @return the size of the internal memory-mapped region, or 0 on failure.
+ */
+ int64_t msiz() {
+ _assert_(true);
+ ScopedRWLock lock(&mlock_, false);
+ if (omode_ == 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ return 0;
+ }
+ return msiz_;
+ }
+ /**
+ * Get the unit step number of auto defragmentation.
+ * @return the unit step number of auto defragmentation, or 0 on failure.
+ */
+ int64_t dfunit() {
+ _assert_(true);
+ ScopedRWLock lock(&mlock_, false);
+ if (omode_ == 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ return 0;
+ }
+ return dfunit_;
+ }
+ /**
+ * Get the data compressor.
+ * @return the data compressor, or NULL on failure.
+ */
+ Compressor* comp() {
+ _assert_(true);
+ ScopedRWLock lock(&mlock_, false);
+ if (omode_ == 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ return NULL;
+ }
+ return comp_;
+ }
+ /**
+ * Check whether the database was recovered or not.
+ * @return true if recovered, or false if not.
+ */
+ bool recovered() {
+ _assert_(true);
+ ScopedRWLock lock(&mlock_, false);
+ if (omode_ == 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ return false;
+ }
+ return file_.recovered();
+ }
+ /**
+ * Check whether the database was reorganized or not.
+ * @return true if reorganized, or false if not.
+ */
+ bool reorganized() {
+ _assert_(true);
+ ScopedRWLock lock(&mlock_, false);
+ if (omode_ == 0) {
+ set_error(_KCCODELINE_, Error::INVALID, "not opened");
+ return false;
+ }
+ return reorg_;
+ }
+ private:
+ /**
+ * Record data.
+ */
+ struct Record {
+ int64_t off; ///< offset
+ size_t rsiz; ///< whole size
+ size_t psiz; ///< size of the padding
+ size_t ksiz; ///< size of the key
+ size_t vsiz; ///< size of the value
+ int64_t left; ///< address of the left child record
+ int64_t right; ///< address of the right child record
+ const char* kbuf; ///< pointer to the key
+ const char* vbuf; ///< pointer to the value
+ int64_t boff; ///< offset of the body
+ char* bbuf; ///< buffer of the body
+ };
+ /**
+ * Free block data.
+ */
+ struct FreeBlock {
+ int64_t off; ///< offset
+ size_t rsiz; ///< record size
+ /** comparing operator */
+ bool operator <(const FreeBlock& obj) const {
+ _assert_(true);
+ if (rsiz < obj.rsiz) return true;
+ if (rsiz == obj.rsiz && off > obj.off) return true;
+ return false;
+ }
+ };
+ /**
+ * Comparator for free blocks.
+ */
+ struct FreeBlockComparator {
+ /** comparing operator */
+ bool operator ()(const FreeBlock& a, const FreeBlock& b) const {
+ _assert_(true);
+ return a.off < b.off;
+ }
+ };
+ /**
+ * Repeating visitor.
+ */
+ class Repeater : public Visitor {
+ public:
+ /** constructor */
+ explicit Repeater(const char* vbuf, size_t vsiz) : vbuf_(vbuf), vsiz_(vsiz) {
+ _assert_(vbuf);
+ }
+ private:
+ /** visit a record */
+ const char* visit_full(const char* kbuf, size_t ksiz,
+ const char* vbuf, size_t vsiz, size_t* sp) {
+ _assert_(kbuf && ksiz <= MEMMAXSIZ && vbuf && vsiz <= MEMMAXSIZ && sp);
+ *sp = vsiz_;
+ return vbuf_;
+ }
+ const char* vbuf_;
+ size_t vsiz_;
+ };
+ /**
+ * Scoped visitor.
+ */
+ class ScopedVisitor {
+ public:
+ /** constructor */
+ explicit ScopedVisitor(Visitor* visitor) : visitor_(visitor) {
+ _assert_(visitor);
+ visitor_->visit_before();
+ }
+ /** destructor */
+ ~ScopedVisitor() {
+ _assert_(true);
+ visitor_->visit_after();
+ }
+ private:
+ Visitor* visitor_; ///< visitor
+ };
+ /**
+ * Accept a visitor to a record.
+ * @param kbuf the pointer to the key region.
+ * @param ksiz the size of the key region.
+ * @param visitor a visitor object.
+ * @param bidx the bucket index.
+ * @param pivot the second hash value.
+ @ @param isiter true for iterator use, or false for direct use.
+ * @return true on success, or false on failure.
+ */
+ bool accept_impl(const char* kbuf, size_t ksiz, Visitor* visitor,
+ int64_t bidx, uint32_t pivot, bool isiter) {
+ _assert_(kbuf && ksiz <= MEMMAXSIZ && visitor && bidx >= 0);
+ int64_t top = get_bucket(bidx);
+ int64_t off = top;
+ if (off < 0) return false;
+ enum { DIREMPTY, DIRLEFT, DIRRIGHT, DIRMIXED } entdir = DIREMPTY;
+ int64_t entoff = 0;
+ Record rec;
+ char rbuf[RECBUFSIZ];
+ while (off > 0) {
+ rec.off = off;
+ if (!read_record(&rec, rbuf)) return false;
+ if (rec.psiz == UINT16MAX) {
+ set_error(_KCCODELINE_, Error::BROKEN, "free block in the chain");
+ report(_KCCODELINE_, Logger::WARN, "psiz=%lld off=%lld fsiz=%lld",
+ (long long)psiz_, (long long)rec.off, (long long)file_.size());
+ return false;
+ }
+ uint32_t tpivot = linear_ ? pivot : fold_hash(hash_record(rec.kbuf, rec.ksiz));
+ if (pivot > tpivot) {
+ delete[] rec.bbuf;
+ off = rec.left;
+ switch (entdir) {
+ case DIREMPTY: entdir = DIRLEFT; break;
+ case DIRRIGHT: entdir = DIRMIXED; break;
+ default: break;
+ }
+ entoff = rec.off + sizeof(uint16_t);
+ } else if (pivot < tpivot) {
+ delete[] rec.bbuf;
+ off = rec.right;
+ switch (entdir) {
+ case DIREMPTY: entdir = DIRRIGHT; break;
+ case DIRLEFT: entdir = DIRMIXED; break;
+ default: break;
+ }
+ entoff = rec.off + sizeof(uint16_t) + width_;
+ } else {
+ int32_t kcmp = compare_keys(kbuf, ksiz, rec.kbuf, rec.ksiz);
+ if (linear_ && kcmp != 0) kcmp = 1;
+ if (kcmp > 0) {
+ delete[] rec.bbuf;
+ off = rec.left;
+ switch (entdir) {
+ case DIREMPTY: entdir = DIRLEFT; break;
+ case DIRRIGHT: entdir = DIRMIXED; break;
+ default: break;
+ }
+ entoff = rec.off + sizeof(uint16_t);
+ } else if (kcmp < 0) {
+ delete[] rec.bbuf;
+ off = rec.right;
+ switch (entdir) {
+ case DIREMPTY: entdir = DIRRIGHT; break;
+ case DIRLEFT: entdir = DIRMIXED; break;
+ default: break;
+ }
+ entoff = rec.off + sizeof(uint16_t) + width_;
+ } else {
+ if (!rec.vbuf && !read_record_body(&rec)) {
+ delete[] rec.bbuf;
+ return false;
+ }
+ const char* vbuf = rec.vbuf;
+ size_t vsiz = rec.vsiz;
+ char* zbuf = NULL;
+ size_t zsiz = 0;
+ if (comp_) {
+ zbuf = comp_->decompress(vbuf, vsiz, &zsiz);
+ if (!zbuf) {
+ set_error(_KCCODELINE_, Error::SYSTEM, "data decompression failed");
+ delete[] rec.bbuf;
+ return false;
+ }
+ vbuf = zbuf;
+ vsiz = zsiz;
+ }
+ vbuf = visitor->visit_full(kbuf, ksiz, vbuf, vsiz, &vsiz);
+ delete[] zbuf;
+ if (vbuf == Visitor::REMOVE) {
+ bool atran = false;
+ if (autotran_ && !tran_) {
+ if (!begin_auto_transaction()) {
+ delete[] rec.bbuf;
+ return false;
+ }
+ atran = true;
+ }
+ if (!write_free_block(rec.off, rec.rsiz, rbuf)) {
+ if (atran) abort_auto_transaction();
+ delete[] rec.bbuf;
+ return false;
+ }
+ insert_free_block(rec.off, rec.rsiz);
+ frgcnt_ += 1;
+ delete[] rec.bbuf;
+ if (!cut_chain(&rec, rbuf, bidx, entoff)) {
+ if (atran) abort_auto_transaction();
+ return false;
+ }
+ count_ -= 1;
+ if (atran) {
+ if (!commit_auto_transaction()) return false;
+ } else if (autosync_) {
+ if (!synchronize_meta()) return false;
+ }
+ } else if (vbuf == Visitor::NOP) {
+ delete[] rec.bbuf;
+ } else {
+ zbuf = NULL;
+ zsiz = 0;
+ if (comp_ && !isiter) {
+ zbuf = comp_->compress(vbuf, vsiz, &zsiz);
+ if (!zbuf) {
+ set_error(_KCCODELINE_, Error::SYSTEM, "data compression failed");
+ delete[] rec.bbuf;
+ return false;
+ }
+ vbuf = zbuf;
+ vsiz = zsiz;
+ }
+ bool atran = false;
+ if (autotran_ && !tran_) {
+ if (!begin_auto_transaction()) {
+ delete[] zbuf;
+ delete[] rec.bbuf;
+ return false;
+ }
+ atran = true;
+ }
+ size_t rsiz = calc_record_size(rec.ksiz, vsiz);
+ if (rsiz <= rec.rsiz) {
+ rec.psiz = rec.rsiz - rsiz;
+ rec.vsiz = vsiz;
+ rec.vbuf = vbuf;
+ if (!adjust_record(&rec) || !write_record(&rec, true)) {
+ if (atran) abort_auto_transaction();
+ delete[] zbuf;
+ delete[] rec.bbuf;
+ return false;
+ }
+ delete[] zbuf;
+ delete[] rec.bbuf;
+ } else {
+ if (!write_free_block(rec.off, rec.rsiz, rbuf)) {
+ if (atran) abort_auto_transaction();
+ delete[] zbuf;
+ delete[] rec.bbuf;
+ return false;
+ }
+ insert_free_block(rec.off, rec.rsiz);
+ frgcnt_ += 1;
+ size_t psiz = calc_record_padding(rsiz);
+ rec.rsiz = rsiz + psiz;
+ rec.psiz = psiz;
+ rec.vsiz = vsiz;
+ rec.vbuf = vbuf;
+ bool over = false;
+ FreeBlock fb;
+ if (!isiter && fetch_free_block(rec.rsiz, &fb)) {
+ rec.off = fb.off;
+ rec.rsiz = fb.rsiz;
+ rec.psiz = rec.rsiz - rsiz;
+ over = true;
+ if (!adjust_record(&rec)) {
+ if (atran) abort_auto_transaction();
+ delete[] zbuf;
+ delete[] rec.bbuf;
+ return false;
+ }
+ } else {
+ rec.off = lsiz_.add(rec.rsiz);
+ }
+ if (!write_record(&rec, over)) {
+ if (atran) abort_auto_transaction();
+ delete[] zbuf;
+ delete[] rec.bbuf;
+ return false;
+ }
+ if (!over) psiz_.secure_least(rec.off + rec.rsiz);
+ delete[] zbuf;
+ delete[] rec.bbuf;
+ if (entoff > 0) {
+ if (!set_chain(entoff, rec.off)) {
+ if (atran) abort_auto_transaction();
+ return false;
+ }
+ } else {
+ if (!set_bucket(bidx, rec.off)) {
+ if (atran) abort_auto_transaction();
+ return false;
+ }
+ }
+ }
+ if (atran) {
+ if (!commit_auto_transaction()) return false;
+ } else if (autosync_) {
+ if (!synchronize_meta()) return false;
+ }
+ }
+ return true;
+ }
+ }
+ }
+ size_t vsiz;
+ const char* vbuf = visitor->visit_empty(kbuf, ksiz, &vsiz);
+ if (vbuf != Visitor::NOP && vbuf != Visitor::REMOVE) {
+ char* zbuf = NULL;
+ size_t zsiz = 0;
+ if (comp_) {
+ zbuf = comp_->compress(vbuf, vsiz, &zsiz);
+ if (!zbuf) {
+ set_error(_KCCODELINE_, Error::SYSTEM, "data compression failed");
+ return false;
+ }
+ vbuf = zbuf;
+ vsiz = zsiz;
+ }
+ bool atran = false;
+ if (autotran_ && !tran_) {
+ if (!begin_auto_transaction()) {
+ delete[] zbuf;
+ return false;
+ }
+ atran = true;
+ }
+ size_t rsiz = calc_record_size(ksiz, vsiz);
+ size_t psiz = calc_record_padding(rsiz);
+ rec.rsiz = rsiz + psiz;
+ rec.psiz = psiz;
+ rec.ksiz = ksiz;
+ rec.vsiz = vsiz;
+ switch (entdir) {
+ default: {
+ rec.left = 0;
+ rec.right = 0;
+ break;
+ }
+ case DIRLEFT: {
+ if (linear_) {
+ rec.left = top;
+ rec.right = 0;
+ } else {
+ rec.left = 0;
+ rec.right = top;
+ }
+ break;
+ }
+ case DIRRIGHT: {
+ rec.left = top;
+ rec.right = 0;
+ break;
+ }
+ }
+ rec.kbuf = kbuf;
+ rec.vbuf = vbuf;
+ bool over = false;
+ FreeBlock fb;
+ if (fetch_free_block(rec.rsiz, &fb)) {
+ rec.off = fb.off;
+ rec.rsiz = fb.rsiz;
+ rec.psiz = rec.rsiz - rsiz;
+ over = true;
+ if (!adjust_record(&rec)) {
+ if (atran) abort_auto_transaction();
+ delete[] zbuf;
+ return false;
+ }
+ } else {
+ rec.off = lsiz_.add(rec.rsiz);
+ }
+ if (!write_record(&rec, over)) {
+ if (atran) abort_auto_transaction();
+ delete[] zbuf;
+ return false;
+ }
+ if (!over) psiz_.secure_least(rec.off + rec.rsiz);
+ delete[] zbuf;
+ if (entoff < 1 || entdir == DIRLEFT || entdir == DIRRIGHT) {
+ if (!set_bucket(bidx, rec.off)) {
+ if (atran) abort_auto_transaction();
+ return false;
+ }
+ } else {
+ if (!set_chain(entoff, rec.off)) {
+ if (atran) abort_auto_transaction();
+ return false;
+ }
+ }
+ count_ += 1;
+ if (atran) {
+ if (!commit_auto_transaction()) return false;
+ } else if (autosync_) {
+ if (!synchronize_meta()) return false;
+ }
+ }
+ return true;
+ }
+ /**
+ * Iterate to accept a visitor for each record.
+ * @param visitor a visitor object.
+ * @param checker a progress checker object.
+ * @return true on success, or false on failure.
+ */
+ bool iterate_impl(Visitor* visitor, ProgressChecker* checker) {
+ _assert_(visitor);
+ int64_t allcnt = count_;
+ if (checker && !checker->check("iterate", "beginning", 0, allcnt)) {
+ set_error(_KCCODELINE_, Error::LOGIC, "checker failed");
+ return false;
+ }
+ int64_t off = roff_;
+ int64_t end = lsiz_;
+ Record rec;
+ char rbuf[RECBUFSIZ];
+ int64_t curcnt = 0;
+ while (off > 0 && off < end) {
+ rec.off = off;
+ if (!read_record(&rec, rbuf)) return false;
+ if (rec.psiz == UINT16MAX) {
+ off += rec.rsiz;
+ } else {
+ if (!rec.vbuf && !read_record_body(&rec)) {
+ delete[] rec.bbuf;
+ return false;
+ }
+ const char* vbuf = rec.vbuf;
+ size_t vsiz = rec.vsiz;
+ char* zbuf = NULL;
+ size_t zsiz = 0;
+ if (comp_) {
+ zbuf = comp_->decompress(vbuf, vsiz, &zsiz);
+ if (!zbuf) {
+ set_error(_KCCODELINE_, Error::SYSTEM, "data decompression failed");
+ delete[] rec.bbuf;
+ return false;
+ }
+ vbuf = zbuf;
+ vsiz = zsiz;
+ }
+ vbuf = visitor->visit_full(rec.kbuf, rec.ksiz, vbuf, vsiz, &vsiz);
+ delete[] zbuf;
+ if (vbuf == Visitor::REMOVE) {
+ uint64_t hash = hash_record(rec.kbuf, rec.ksiz);
+ uint32_t pivot = fold_hash(hash);
+ int64_t bidx = hash % bnum_;
+ Repeater repeater(Visitor::REMOVE, 0);
+ if (!accept_impl(rec.kbuf, rec.ksiz, &repeater, bidx, pivot, true)) {
+ delete[] rec.bbuf;
+ return false;
+ }
+ delete[] rec.bbuf;
+ } else if (vbuf == Visitor::NOP) {
+ delete[] rec.bbuf;
+ } else {
+ zbuf = NULL;
+ zsiz = 0;
+ if (comp_) {
+ zbuf = comp_->compress(vbuf, vsiz, &zsiz);
+ if (!zbuf) {
+ set_error(_KCCODELINE_, Error::SYSTEM, "data compression failed");
+ delete[] rec.bbuf;
+ return false;
+ }
+ vbuf = zbuf;
+ vsiz = zsiz;
+ }
+ size_t rsiz = calc_record_size(rec.ksiz, vsiz);
+ if (rsiz <= rec.rsiz) {
+ rec.psiz = rec.rsiz - rsiz;
+ rec.vsiz = vsiz;
+ rec.vbuf = vbuf;
+ if (!adjust_record(&rec) || !write_record(&rec, true)) {
+ delete[] zbuf;
+ delete[] rec.bbuf;
+ return false;
+ }
+ delete[] zbuf;
+ delete[] rec.bbuf;
+ } else {
+ uint64_t hash = hash_record(rec.kbuf, rec.ksiz);
+ uint32_t pivot = fold_hash(hash);
+ int64_t bidx = hash % bnum_;
+ Repeater repeater(vbuf, vsiz);
+ if (!accept_impl(rec.kbuf, rec.ksiz, &repeater, bidx, pivot, true)) {
+ delete[] zbuf;
+ delete[] rec.bbuf;
+ return false;
+ }
+ delete[] zbuf;
+ delete[] rec.bbuf;
+ }
+ }
+ off += rec.rsiz;
+ curcnt++;
+ if (checker && !checker->check("iterate", "processing", curcnt, allcnt)) {
+ set_error(_KCCODELINE_, Error::LOGIC, "checker failed");
+ return false;
+ }
+ }
+ }
+ if (checker && !checker->check("iterate", "ending", -1, allcnt)) {
+ set_error(_KCCODELINE_, Error::LOGIC, "checker failed");
+ return false;
+ }
+ return true;
+ }
+ /**
+ * Scan each record in parallel.
+ * @param visitor a visitor object.
+ * @param thnum the number of worker threads.
+ * @param checker a progress checker object.
+ * @return true on success, or false on failure.
+ */
+ bool scan_parallel_impl(Visitor *visitor, size_t thnum, ProgressChecker* checker) {
+ _assert_(visitor && thnum <= MEMMAXSIZ);
+ int64_t allcnt = count_;
+ if (checker && !checker->check("scan_parallel", "beginning", -1, allcnt)) {
+ set_error(_KCCODELINE_, Error::LOGIC, "checker failed");
+ return false;
+ }
+ bool err = false;
+ std::vector<int64_t> offs;
+ int64_t bnum = bnum_;
+ size_t cap = (thnum + 1) * INT8MAX;
+ for (int64_t bidx = 0; bidx < bnum; bidx++) {
+ int64_t off = get_bucket(bidx);
+ if (off > 0) {
+ offs.push_back(off);
+ if (offs.size() >= cap) break;
+ }
+ }
+ if (!offs.empty()) {
+ std::sort(offs.begin(), offs.end());
+ if (thnum > offs.size()) thnum = offs.size();
+ class ThreadImpl : public Thread {
+ public:
+ explicit ThreadImpl() :
+ db_(NULL), visitor_(NULL), checker_(NULL), allcnt_(0),
+ begoff_(0), endoff_(0), error_() {}
+ void init(HashDB* db, Visitor* visitor, ProgressChecker* checker, int64_t allcnt,
+ int64_t begoff, int64_t endoff) {
+ db_ = db;
+ visitor_ = visitor;
+ checker_ = checker;
+ allcnt_ = allcnt;
+ begoff_ = begoff;
+ endoff_ = endoff;
+ }
+ const Error& error() {
+ return error_;
+ }
+ private:
+ void run() {
+ HashDB* db = db_;
+ Visitor* visitor = visitor_;
+ ProgressChecker* checker = checker_;
+ int64_t off = begoff_;
+ int64_t end = endoff_;
+ int64_t allcnt = allcnt_;
+ Compressor* comp = db->comp_;
+ Record rec;
+ char rbuf[RECBUFSIZ];
+ while (off > 0 && off < end) {
+ rec.off = off;
+ if (!db->read_record(&rec, rbuf)) {
+ error_ = db->error();
+ break;
+ }
+ if (rec.psiz == UINT16MAX) {
+ off += rec.rsiz;
+ } else {
+ if (!rec.vbuf && !db->read_record_body(&rec)) {
+ delete[] rec.bbuf;
+ error_ = db->error();
+ break;
+ }
+ const char* vbuf = rec.vbuf;
+ size_t vsiz = rec.vsiz;
+ char* zbuf = NULL;
+ size_t zsiz = 0;
+ if (comp) {
+ zbuf = comp->decompress(vbuf, vsiz, &zsiz);
+ if (!zbuf) {
+ db->set_error(_KCCODELINE_, Error::SYSTEM, "data decompression failed");
+ delete[] rec.bbuf;
+ error_ = db->error();
+ break;
+ }
+ vbuf = zbuf;
+ vsiz = zsiz;
+ }
+ visitor->visit_full(rec.kbuf, rec.ksiz, vbuf, vsiz, &vsiz);
+ delete[] zbuf;
+ delete[] rec.bbuf;
+ off += rec.rsiz;
+ if (checker && !checker->check("scan_parallel", "processing", -1, allcnt)) {
+ db->set_error(_KCCODELINE_, Error::LOGIC, "checker failed");
+ error_ = db->error();
+ break;
+ }
+ }
+ }
+ }
+ HashDB* db_;
+ Visitor* visitor_;
+ ProgressChecker* checker_;
+ int64_t allcnt_;
+ int64_t begoff_;
+ int64_t endoff_;
+ Error error_;
+ };
+ ThreadImpl* threads = new ThreadImpl[thnum];
+ double range = (double)offs.size() / thnum;
+ for (size_t i = 0; i < thnum; i++) {
+ int64_t cidx = i * range;
+ int64_t nidx = (i + 1) * range;
+ int64_t begoff = i < 1 ? roff_ : offs[cidx];
+ int64_t endoff = i < thnum - 1 ? offs[nidx] : (int64_t)lsiz_;
+ ThreadImpl* thread = threads + i;
+ thread->init(this, visitor, checker, allcnt, begoff, endoff);
+ thread->start();
+ }
+ for (size_t i = 0; i < thnum; i++) {
+ ThreadImpl* thread = threads + i;
+ thread->join();
+ if (thread->error() != Error::SUCCESS) {
+ *error_ = thread->error();
+ err = true;
+ }
+ }
+ delete[] threads;
+ }
+ if (checker && !checker->check("scan_parallel", "ending", -1, allcnt)) {
+ set_error(_KCCODELINE_, Error::LOGIC, "checker failed");
+ err = true;
+ }
+ return !err;
+ }
+ /**
+ * Synchronize updated contents with the file and the device.
+ * @param hard true for physical synchronization with the device, or false for logical
+ * synchronization with the file system.
+ * @param proc a postprocessor object.
+ * @param checker a progress checker object.
+ * @return true on success, or false on failure.
+ */
+ bool synchronize_impl(bool hard, FileProcessor* proc, ProgressChecker* checker) {
+ _assert_(true);
+ bool err = false;
+ if (writer_) {
+ if (checker && !checker->check("synchronize", "dumping the free blocks", -1, -1)) {
+ set_error(_KCCODELINE_, Error::LOGIC, "checker failed");
+ return false;
+ }
+ if (hard && !dump_free_blocks()) err = true;
+ if (checker && !checker->check("synchronize", "dumping the meta data", -1, -1)) {
+ set_error(_KCCODELINE_, Error::LOGIC, "checker failed");
+ return false;
+ }
+ if (!dump_meta()) err = true;
+ if (checker && !checker->check("synchronize", "synchronizing the file", -1, -1)) {
+ set_error(_KCCODELINE_, Error::LOGIC, "checker failed");
+ return false;
+ }
+ if (!file_.synchronize(hard)) {
+ set_error(_KCCODELINE_, Error::SYSTEM, file_.error());
+ err = true;
+ }
+ }
+ if (proc) {
+ if (checker && !checker->check("synchronize", "running the post processor", -1, -1)) {
+ set_error(_KCCODELINE_, Error::LOGIC, "checker failed");
+ return false;
+ }
+ if (!proc->process(path_, count_, lsiz_)) {
+ set_error(_KCCODELINE_, Error::LOGIC, "postprocessing failed");
+ err = true;
+ }
+ }
+ if (writer_ && !autotran_ && !set_flag(FOPEN, true)) err = true;
+ return !err;
+ }
+ /**
+ * Synchronize meta data with the file and the device.
+ * @return true on success, or false on failure.
+ */
+ bool synchronize_meta() {
+ _assert_(true);
+ ScopedMutex lock(&flock_);
+ bool err = false;
+ if (!dump_meta()) err = true;
+ if (!file_.synchronize(true)) {
+ set_error(_KCCODELINE_, Error::SYSTEM, file_.error());
+ err = true;
+ }
+ return !err;
+ }
+ /**
+ * Perform defragmentation.
+ * @param step the number of steps.
+ * @return true on success, or false on failure.
+ */
+ bool defrag_impl(int64_t step) {
+ _assert_(step >= 0);
+ int64_t end = lsiz_;
+ Record rec;
+ char rbuf[RECBUFSIZ];
+ while (true) {
+ if (dfcur_ >= end) {
+ dfcur_ = roff_;
+ return true;
+ }
+ if (step-- < 1) return true;
+ rec.off = dfcur_;
+ if (!read_record(&rec, rbuf)) return false;
+ if (rec.psiz == UINT16MAX) break;
+ delete[] rec.bbuf;
+ dfcur_ += rec.rsiz;
+ }
+ bool atran = false;
+ if (autotran_ && !tran_) {
+ if (!begin_auto_transaction()) return false;
+ atran = true;
+ }
+ int64_t base = dfcur_;
+ int64_t dest = base;
+ dfcur_ += rec.rsiz;
+ step++;
+ while (step-- > 0 && dfcur_ < end) {
+ rec.off = dfcur_;
+ if (!read_record(&rec, rbuf)) {
+ if (atran) abort_auto_transaction();
+ return false;
+ }
+ escape_cursors(rec.off, dest);
+ dfcur_ += rec.rsiz;
+ if (rec.psiz != UINT16MAX) {
+ if (!rec.vbuf && !read_record_body(&rec)) {
+ if (atran) abort_auto_transaction();
+ delete[] rec.bbuf;
+ return false;
+ }
+ if (rec.psiz >= align_) {
+ size_t diff = rec.psiz - rec.psiz % align_;
+ rec.psiz -= diff;
+ rec.rsiz -= diff;
+ }
+ if (!shift_record(&rec, dest)) {
+ if (atran) abort_auto_transaction();
+ delete[] rec.bbuf;
+ return false;
+ }
+ delete[] rec.bbuf;
+ dest += rec.rsiz;
+ }
+ }
+ trim_free_blocks(base, dfcur_);
+ if (dfcur_ >= end) {
+ lsiz_ = dest;
+ psiz_ = lsiz_;
+ if (!file_.truncate(lsiz_)) {
+ if (atran) abort_auto_transaction();
+ return false;
+ }
+ trim_cursors();
+ dfcur_ = roff_;
+ } else {
+ size_t rsiz = dfcur_ - dest;
+ if (!write_free_block(dest, rsiz, rbuf)) {
+ if (atran) abort_auto_transaction();
+ return false;
+ }
+ insert_free_block(dest, rsiz);
+ dfcur_ = dest;
+ }
+ if (atran) {
+ if (!commit_auto_transaction()) return false;
+ } else if (autosync_) {
+ if (!synchronize_meta()) return false;
+ }
+ return true;
+ }
+ /**
+ * Calculate meta data with saved ones.
+ */
+ void calc_meta() {
+ _assert_(true);
+ align_ = 1 << apow_;
+ fbpnum_ = fpow_ > 0 ? 1 << fpow_ : 0;
+ width_ = (opts_ & TSMALL) ? sizeof(uint32_t) : sizeof(uint32_t) + 2;
+ linear_ = (opts_ & TLINEAR) ? true : false;
+ comp_ = (opts_ & TCOMPRESS) ? embcomp_ : NULL;
+ rhsiz_ = sizeof(uint16_t) + sizeof(uint8_t) * 2;
+ rhsiz_ += linear_ ? width_ : width_ * 2;
+ boff_ = HEADSIZ + FBPWIDTH * fbpnum_;
+ if (fbpnum_ > 0) boff_ += width_ * 2 + sizeof(uint8_t) * 2;
+ roff_ = boff_ + width_ * bnum_;
+ int64_t rem = roff_ % align_;
+ if (rem > 0) roff_ += align_ - rem;
+ dfcur_ = roff_;
+ frgcnt_ = 0;
+ tran_ = false;
+ }
+ /**
+ * Calculate the module checksum.
+ * @return the module checksum.
+ */
+ uint8_t calc_checksum() {
+ _assert_(true);
+ const char* kbuf = KCHDBCHKSUMSEED;
+ size_t ksiz = sizeof(KCHDBCHKSUMSEED) - 1;
+ char* zbuf = NULL;
+ size_t zsiz = 0;
+ if (comp_) {
+ zbuf = comp_->compress(kbuf, ksiz, &zsiz);
+ if (!zbuf) return 0;
+ kbuf = zbuf;
+ ksiz = zsiz;
+ }
+ uint32_t hash = fold_hash(hash_record(kbuf, ksiz));
+ delete[] zbuf;
+ return (hash >> 24) ^ (hash >> 16) ^ (hash >> 8) ^ (hash >> 0);
+ }
+ /**
+ * Dump the meta data into the file.
+ * @return true on success, or false on failure.
+ */
+ bool dump_meta() {
+ _assert_(true);
+ char head[HEADSIZ];
+ std::memset(head, 0, sizeof(head));
+ std::memcpy(head, KCHDBMAGICDATA, sizeof(KCHDBMAGICDATA));
+ std::memcpy(head + MOFFLIBVER, &libver_, sizeof(libver_));
+ std::memcpy(head + MOFFLIBREV, &librev_, sizeof(librev_));
+ std::memcpy(head + MOFFFMTVER, &fmtver_, sizeof(fmtver_));
+ std::memcpy(head + MOFFCHKSUM, &chksum_, sizeof(chksum_));
+ std::memcpy(head + MOFFTYPE, &type_, sizeof(type_));
+ std::memcpy(head + MOFFAPOW, &apow_, sizeof(apow_));
+ std::memcpy(head + MOFFFPOW, &fpow_, sizeof(fpow_));
+ std::memcpy(head + MOFFOPTS, &opts_, sizeof(opts_));
+ uint64_t num = hton64(bnum_);
+ std::memcpy(head + MOFFBNUM, &num, sizeof(num));
+ if (!flagopen_) flags_ &= ~FOPEN;
+ std::memcpy(head + MOFFFLAGS, &flags_, sizeof(flags_));
+ num = hton64(count_);
+ std::memcpy(head + MOFFCOUNT, &num, sizeof(num));
+ num = hton64(lsiz_);
+ std::memcpy(head + MOFFSIZE, &num, sizeof(num));
+ std::memcpy(head + MOFFOPAQUE, opaque_, sizeof(opaque_));
+ if (!file_.write(0, head, sizeof(head))) {
+ set_error(_KCCODELINE_, Error::SYSTEM, file_.error());
+ return false;
+ }
+ trcount_ = count_;
+ trsize_ = lsiz_;
+ return true;
+ }
+ /**
+ * Dump the meta data into the file.
+ * @return true on success, or false on failure.
+ */
+ bool dump_auto_meta() {
+ _assert_(true);
+ const int64_t hsiz = MOFFOPAQUE - MOFFCOUNT;
+ char head[hsiz];
+ std::memset(head, 0, hsiz);
+ uint64_t num = hton64(count_);
+ std::memcpy(head, &num, sizeof(num));
+ num = hton64(lsiz_);
+ std::memcpy(head + MOFFSIZE - MOFFCOUNT, &num, sizeof(num));
+ if (!file_.write_fast(MOFFCOUNT, head, sizeof(head))) {
+ set_error(_KCCODELINE_, Error::SYSTEM, file_.error());
+ return false;
+ }
+ trcount_ = count_;
+ trsize_ = lsiz_;
+ return true;
+ }
+ /**
+ * Dump the opaque data into the file.
+ * @return true on success, or false on failure.
+ */
+ bool dump_opaque() {
+ _assert_(true);
+ if (!file_.write_fast(MOFFOPAQUE, opaque_, sizeof(opaque_))) {
+ set_error(_KCCODELINE_, Error::SYSTEM, file_.error());
+ return false;
+ }
+ return true;
+ }
+ /**
+ * Load the meta data from the file.
+ * @return true on success, or false on failure.
+ */
+ bool load_meta() {
+ _assert_(true);
+ char head[HEADSIZ];
+ if (file_.size() < (int64_t)sizeof(head)) {
+ set_error(_KCCODELINE_, Error::INVALID, "missing magic data of the file");
+ return false;
+ }
+ if (!file_.read(0, head, sizeof(head))) {
+ set_error(_KCCODELINE_, Error::SYSTEM, file_.error());
+ report(_KCCODELINE_, Logger::WARN, "psiz=%lld off=%lld fsiz=%lld",
+ (long long)psiz_, (long long)0, (long long)file_.size());
+ return false;
+ }
+ if (std::memcmp(head, KCHDBMAGICDATA, sizeof(KCHDBMAGICDATA))) {
+ set_error(_KCCODELINE_, Error::INVALID, "invalid magic data of the file");
+ return false;
+ }
+ std::memcpy(&libver_, head + MOFFLIBVER, sizeof(libver_));
+ std::memcpy(&librev_, head + MOFFLIBREV, sizeof(librev_));
+ std::memcpy(&fmtver_, head + MOFFFMTVER, sizeof(fmtver_));
+ std::memcpy(&chksum_, head + MOFFCHKSUM, sizeof(chksum_));
+ std::memcpy(&type_, head + MOFFTYPE, sizeof(type_));
+ std::memcpy(&apow_, head + MOFFAPOW, sizeof(apow_));
+ std::memcpy(&fpow_, head + MOFFFPOW, sizeof(fpow_));
+ std::memcpy(&opts_, head + MOFFOPTS, sizeof(opts_));
+ uint64_t num;
+ std::memcpy(&num, head + MOFFBNUM, sizeof(num));
+ bnum_ = ntoh64(num);
+ std::memcpy(&flags_, head + MOFFFLAGS, sizeof(flags_));
+ flagopen_ = flags_ & FOPEN;
+ std::memcpy(&num, head + MOFFCOUNT, sizeof(num));
+ count_ = ntoh64(num);
+ std::memcpy(&num, head + MOFFSIZE, sizeof(num));
+ lsiz_ = ntoh64(num);
+ psiz_ = lsiz_;
+ std::memcpy(opaque_, head + MOFFOPAQUE, sizeof(opaque_));
+ trcount_ = count_;
+ trsize_ = lsiz_;
+ return true;
+ }
+ /**
+ * Set a status flag.
+ * @param flag the flag kind.
+ * @param sign whether to set or unset.
+ * @return true on success, or false on failure.
+ */
+ bool set_flag(uint8_t flag, bool sign) {
+ _assert_(true);
+ uint8_t flags;
+ if (!file_.read(MOFFFLAGS, &flags, sizeof(flags))) {
+ set_error(_KCCODELINE_, Error::SYSTEM, file_.error());
+ report(_KCCODELINE_, Logger::WARN, "psiz=%lld off=%lld fsiz=%lld",
+ (long long)psiz_, (long long)MOFFFLAGS, (long long)file_.size());
+ return false;
+ }
+ if (sign) {
+ flags |= flag;
+ } else {
+ flags &= ~flag;
+ }
+ if (!file_.write(MOFFFLAGS, &flags, sizeof(flags))) {
+ set_error(_KCCODELINE_, Error::SYSTEM, file_.error());
+ return false;
+ }
+ flags_ = flags;
+ return true;
+ }
+ /**
+ * Reorganize the whole file.
+ * @param path the path of the database file.
+ * @return true on success, or false on failure.
+ */
+ bool reorganize_file(const std::string& path) {
+ _assert_(true);
+ bool err = false;
+ HashDB db;
+ db.tune_type(type_);
+ db.tune_alignment(apow_);
+ db.tune_fbp(fpow_);
+ db.tune_options(opts_);
+ db.tune_buckets(bnum_);
+ db.tune_map(msiz_);
+ if (embcomp_) db.tune_compressor(embcomp_);
+ const std::string& npath = path + File::EXTCHR + KCHDBTMPPATHEXT;
+ if (db.open(npath, OWRITER | OCREATE | OTRUNCATE)) {
+ report(_KCCODELINE_, Logger::WARN, "reorganizing the database");
+ lsiz_ = file_.size();
+ psiz_ = lsiz_;
+ if (copy_records(&db)) {
+ if (db.close()) {
+ if (!File::rename(npath, path)) {
+ set_error(_KCCODELINE_, Error::SYSTEM, "renaming the destination failed");
+ err = true;
+ }
+ } else {
+ set_error(_KCCODELINE_, db.error().code(), "closing the destination failed");
+ err = true;
+ }
+ } else {
+ set_error(_KCCODELINE_, db.error().code(), "record copying failed");
+ err = true;
+ }
+ File::remove(npath);
+ } else {
+ set_error(_KCCODELINE_, db.error().code(), "opening the destination failed");
+ err = true;
+ }
+ return !err;
+ }
+ /**
+ * Copy all records to another database.
+ * @param dest the destination database.
+ * @return true on success, or false on failure.
+ */
+ bool copy_records(HashDB* dest) {
+ _assert_(dest);
+ Logger* logger = logger_;
+ logger_ = NULL;
+ int64_t off = roff_;
+ int64_t end = psiz_;
+ Record rec, nrec;
+ char rbuf[RECBUFSIZ], nbuf[RECBUFSIZ];
+ while (off > 0 && off < end) {
+ rec.off = off;
+ if (!read_record(&rec, rbuf)) {
+ int64_t checkend = off + SLVGWIDTH;
+ if (checkend > end - (int64_t)rhsiz_) checkend = end - rhsiz_;
+ bool hit = false;
+ for (off += rhsiz_; off < checkend; off++) {
+ rec.off = off;
+ if (!read_record(&rec, rbuf)) continue;
+ if ((int64_t)rec.rsiz > SLVGWIDTH || rec.off + (int64_t)rec.rsiz >= checkend) {
+ delete[] rec.bbuf;
+ continue;
+ }
+ if (rec.psiz != UINT16MAX && !rec.vbuf && !read_record_body(&rec)) {
+ delete[] rec.bbuf;
+ continue;
+ }
+ delete[] rec.bbuf;
+ nrec.off = off + rec.rsiz;
+ if (!read_record(&nrec, nbuf)) continue;
+ if ((int64_t)nrec.rsiz > SLVGWIDTH || nrec.off + (int64_t)nrec.rsiz >= checkend) {
+ delete[] nrec.bbuf;
+ continue;
+ }
+ if (nrec.psiz != UINT16MAX && !nrec.vbuf && !read_record_body(&nrec)) {
+ delete[] nrec.bbuf;
+ continue;
+ }
+ delete[] nrec.bbuf;
+ hit = true;
+ break;
+ }
+ if (!hit || !read_record(&rec, rbuf)) break;
+ }
+ if (rec.psiz == UINT16MAX) {
+ off += rec.rsiz;
+ continue;
+ }
+ if (!rec.vbuf && !read_record_body(&rec)) {
+ delete[] rec.bbuf;
+ bool hit = false;
+ if (rec.rsiz <= MEMMAXSIZ && off + (int64_t)rec.rsiz < end) {
+ nrec.off = off + rec.rsiz;
+ if (read_record(&nrec, nbuf)) {
+ if (nrec.rsiz > MEMMAXSIZ || nrec.off + (int64_t)nrec.rsiz >= end) {
+ delete[] nrec.bbuf;
+ } else if (nrec.psiz != UINT16MAX && !nrec.vbuf && !read_record_body(&nrec)) {
+ delete[] nrec.bbuf;
+ } else {
+ delete[] nrec.bbuf;
+ hit = true;
+ }
+ }
+ }
+ if (hit) {
+ off += rec.rsiz;
+ continue;
+ } else {
+ break;
+ }
+ }
+ const char* vbuf = rec.vbuf;
+ size_t vsiz = rec.vsiz;
+ char* zbuf = NULL;
+ size_t zsiz = 0;
+ if (comp_) {
+ zbuf = comp_->decompress(vbuf, vsiz, &zsiz);
+ if (!zbuf) {
+ delete[] rec.bbuf;
+ off += rec.rsiz;
+ continue;
+ }
+ vbuf = zbuf;
+ vsiz = zsiz;
+ }
+ if (!dest->set(rec.kbuf, rec.ksiz, vbuf, vsiz)) {
+ delete[] zbuf;
+ delete[] rec.bbuf;
+ break;
+ }
+ delete[] zbuf;
+ delete[] rec.bbuf;
+ off += rec.rsiz;
+ }
+ logger_ = logger;
+ return true;
+ }
+ /**
+ * Trim the file size.
+ * @param path the path of the database file.
+ * @return true on success, or false on failure.
+ */
+ bool trim_file(const std::string& path) {
+ _assert_(true);
+ bool err = false;
+ report(_KCCODELINE_, Logger::WARN, "trimming the database");
+ File* dest = writer_ ? &file_ : new File();
+ if (dest == &file_ || dest->open(path, File::OWRITER | File::ONOLOCK, 0)) {
+ if (!dest->truncate(lsiz_)) {
+ set_error(_KCCODELINE_, Error::SYSTEM, dest->error());
+ err = true;
+ }
+ if (dest != &file_) {
+ if (!dest->close()) {
+ set_error(_KCCODELINE_, Error::SYSTEM, dest->error());
+ err = true;
+ }
+ if (!file_.refresh()) {
+ set_error(_KCCODELINE_, Error::SYSTEM, file_.error());
+ err = true;
+ }
+ }
+ trim_ = true;
+ } else {
+ set_error(_KCCODELINE_, Error::SYSTEM, dest->error());
+ err = true;
+ }
+ if (dest != &file_) delete dest;
+ return !err;
+ }
+ /**
+ * Get the hash value of a record.
+ * @param kbuf the pointer to the key region.
+ * @param ksiz the size of the key region.
+ * @return the hash value.
+ */
+ uint64_t hash_record(const char* kbuf, size_t ksiz) {
+ _assert_(kbuf && ksiz <= MEMMAXSIZ);
+ return hashmurmur(kbuf, ksiz);
+ }
+ /**
+ * Fold a hash value into a small number.
+ * @param hash the hash number.
+ * @return the result number.
+ */
+ uint32_t fold_hash(uint64_t hash) {
+ _assert_(true);
+ return (((hash & 0xffff000000000000ULL) >> 48) | ((hash & 0x0000ffff00000000ULL) >> 16)) ^
+ (((hash & 0x000000000000ffffULL) << 16) | ((hash & 0x00000000ffff0000ULL) >> 16));
+ }
+ /**
+ * Compare two keys in lexical order.
+ * @param abuf one key.
+ * @param asiz the size of the one key.
+ * @param bbuf the other key.
+ * @param bsiz the size of the other key.
+ * @return positive if the former is big, or negative if the latter is big, or 0 if both are
+ * equivalent.
+ */
+ int32_t compare_keys(const char* abuf, size_t asiz, const char* bbuf, size_t bsiz) {
+ _assert_(abuf && bbuf);
+ if (asiz != bsiz) return (int32_t)asiz - (int32_t)bsiz;
+ return std::memcmp(abuf, bbuf, asiz);
+ }
+ /**
+ * Set an address into a bucket.
+ * @param bidx the index of the bucket.
+ * @param off the address.
+ * @return true on success, or false on failure.
+ */
+ bool set_bucket(int64_t bidx, int64_t off) {
+ _assert_(bidx >= 0 && off >= 0);
+ char buf[sizeof(uint64_t)];
+ writefixnum(buf, off >> apow_, width_);
+ if (!file_.write_fast(boff_ + bidx * width_, buf, width_)) {
+ set_error(_KCCODELINE_, Error::SYSTEM, file_.error());
+ return false;
+ }
+ return true;
+ }
+ /**
+ * Get an address from a bucket.
+ * @param bidx the index of the bucket.
+ * @return the address, or -1 on failure.
+ */
+ int64_t get_bucket(int64_t bidx) {
+ _assert_(bidx >= 0);
+ char buf[sizeof(uint64_t)];
+ if (!file_.read_fast(boff_ + bidx * width_, buf, width_)) {
+ set_error(_KCCODELINE_, Error::SYSTEM, file_.error());
+ report(_KCCODELINE_, Logger::WARN, "psiz=%lld off=%lld fsiz=%lld",
+ (long long)psiz_, (long long)boff_ + bidx * width_, (long long)file_.size());
+ return -1;
+ }
+ return readfixnum(buf, width_) << apow_;
+ }
+ /**
+ * Set an address into a chain slot.
+ * @param entoff the address of the chain slot.
+ * @param off the destination address.
+ * @return true on success, or false on failure.
+ */
+ bool set_chain(int64_t entoff, int64_t off) {
+ _assert_(entoff >= 0 && off >= 0);
+ char buf[sizeof(uint64_t)];
+ writefixnum(buf, off >> apow_, width_);
+ if (!file_.write_fast(entoff, buf, width_)) {
+ set_error(_KCCODELINE_, Error::SYSTEM, file_.error());
+ return false;
+ }
+ return true;
+ }
+ /**
+ * Read a record from the file.
+ * @param rec the record structure.
+ * @param rbuf the working buffer.
+ * @return true on success, or false on failure.
+ */
+ bool read_record(Record* rec, char* rbuf) {
+ _assert_(rec && rbuf);
+ if (rec->off < roff_) {
+ set_error(_KCCODELINE_, Error::BROKEN, "invalid record offset");
+ report(_KCCODELINE_, Logger::WARN, "psiz=%lld off=%lld fsiz=%lld",
+ (long long)psiz_, (long long)rec->off, (long long)file_.size());
+ return false;
+ }
+ size_t rsiz = psiz_ - rec->off;
+ if (rsiz > RECBUFSIZ) {
+ rsiz = RECBUFSIZ;
+ } else {
+ if (rsiz < rhsiz_) {
+ set_error(_KCCODELINE_, Error::BROKEN, "too short record region");
+ report(_KCCODELINE_, Logger::WARN, "psiz=%lld off=%lld rsiz=%lld fsiz=%lld",
+ (long long)psiz_, (long long)rec->off, (long long)rsiz, (long long)file_.size());
+ return false;
+ }
+ rsiz = rhsiz_;
+ }
+ if (!file_.read_fast(rec->off, rbuf, rsiz)) {
+ set_error(_KCCODELINE_, Error::SYSTEM, file_.error());
+ report(_KCCODELINE_, Logger::WARN, "psiz=%lld off=%lld rsiz=%lld fsiz=%lld",
+ (long long)psiz_, (long long)rec->off, (long long)rsiz, (long long)file_.size());
+ return false;
+ }
+ const char* rp = rbuf;
+ uint16_t snum;
+ if (*(uint8_t*)rp == RECMAGIC) {
+ ((uint8_t*)&snum)[0] = 0;
+ ((uint8_t*)&snum)[1] = *(uint8_t*)(rp + 1);
+ } else if (*(uint8_t*)rp >= 0x80) {
+ if (*(uint8_t*)(rp++) != FBMAGIC || *(uint8_t*)(rp++) != FBMAGIC) {
+ set_error(_KCCODELINE_, Error::BROKEN, "invalid magic data of a free block");
+ report(_KCCODELINE_, Logger::WARN, "psiz=%lld off=%lld rsiz=%lld fsiz=%lld",
+ (long long)psiz_, (long long)rec->off, (long long)rsiz, (long long)file_.size());
+ report_binary(_KCCODELINE_, Logger::WARN, "rbuf", rbuf, rsiz);
+ return false;
+ }
+ rec->rsiz = readfixnum(rp, width_) << apow_;
+ rp += width_;
+ if (*(uint8_t*)(rp++) != PADMAGIC || *(uint8_t*)(rp++) != PADMAGIC) {
+ set_error(_KCCODELINE_, Error::BROKEN, "invalid magic data of a free block");
+ report(_KCCODELINE_, Logger::WARN, "psiz=%lld off=%lld rsiz=%lld fsiz=%lld",
+ (long long)psiz_, (long long)rec->off, (long long)rsiz, (long long)file_.size());
+ report_binary(_KCCODELINE_, Logger::WARN, "rbuf", rbuf, rsiz);
+ return false;
+ }
+ if (rec->rsiz < rhsiz_) {
+ set_error(_KCCODELINE_, Error::BROKEN, "invalid size of a free block");
+ report(_KCCODELINE_, Logger::WARN, "psiz=%lld off=%lld rsiz=%lld fsiz=%lld",
+ (long long)psiz_, (long long)rec->off, (long long)rsiz, (long long)file_.size());
+ report_binary(_KCCODELINE_, Logger::WARN, "rbuf", rbuf, rsiz);
+ return false;
+ }
+ rec->psiz = UINT16MAX;
+ rec->ksiz = 0;
+ rec->vsiz = 0;
+ rec->left = 0;
+ rec->right = 0;
+ rec->kbuf = NULL;
+ rec->vbuf = NULL;
+ rec->boff = 0;
+ rec->bbuf = NULL;
+ return true;
+ } else if (*rp == 0) {
+ set_error(_KCCODELINE_, Error::BROKEN, "nullified region");
+ report(_KCCODELINE_, Logger::WARN, "psiz=%lld off=%lld rsiz=%lld fsiz=%lld",
+ (long long)psiz_, (long long)rec->off, (long long)rsiz, (long long)file_.size());
+ report_binary(_KCCODELINE_, Logger::WARN, "rbuf", rbuf, rsiz);
+ return false;
+ } else {
+ std::memcpy(&snum, rp, sizeof(snum));
+ }
+ rp += sizeof(snum);
+ rsiz -= sizeof(snum);
+ rec->psiz = ntoh16(snum);
+ rec->left = readfixnum(rp, width_) << apow_;
+ rp += width_;
+ rsiz -= width_;
+ if (linear_) {
+ rec->right = 0;
+ } else {
+ rec->right = readfixnum(rp, width_) << apow_;
+ rp += width_;
+ rsiz -= width_;
+ }
+ uint64_t num;
+ size_t step = readvarnum(rp, rsiz, &num);
+ if (step < 1) {
+ set_error(_KCCODELINE_, Error::BROKEN, "invalid key length");
+ report(_KCCODELINE_, Logger::WARN, "psiz=%lld off=%lld rsiz=%lld fsiz=%lld snum=%04X",
+ (long long)psiz_, (long long)rec->off, (long long)rsiz,
+ (long long)file_.size(), snum);
+ report_binary(_KCCODELINE_, Logger::WARN, "rbuf", rbuf, rsiz);
+ return false;
+ }
+ rec->ksiz = num;
+ rp += step;
+ rsiz -= step;
+ step = readvarnum(rp, rsiz, &num);
+ if (step < 1) {
+ set_error(_KCCODELINE_, Error::BROKEN, "invalid value length");
+ report(_KCCODELINE_, Logger::WARN, "psiz=%lld off=%lld rsiz=%lld fsiz=%lld snum=%04X",
+ (long long)psiz_, (long long)rec->off, (long long)rsiz,
+ (long long)file_.size(), snum);
+ report_binary(_KCCODELINE_, Logger::WARN, "rbuf", rbuf, rsiz);
+ return false;
+ }
+ rec->vsiz = num;
+ rp += step;
+ rsiz -= step;
+ size_t hsiz = rp - rbuf;
+ rec->rsiz = hsiz + rec->ksiz + rec->vsiz + rec->psiz;
+ rec->kbuf = NULL;
+ rec->vbuf = NULL;
+ rec->boff = rec->off + hsiz;
+ rec->bbuf = NULL;
+ if (rsiz >= rec->ksiz) {
+ rec->kbuf = rp;
+ rp += rec->ksiz;
+ rsiz -= rec->ksiz;
+ if (rsiz >= rec->vsiz) {
+ rec->vbuf = rp;
+ if (rec->psiz > 0) {
+ rp += rec->vsiz;
+ rsiz -= rec->vsiz;
+ if (rsiz > 0 && *(uint8_t*)rp != PADMAGIC) {
+ set_error(_KCCODELINE_, Error::BROKEN, "invalid magic data of a record");
+ report(_KCCODELINE_, Logger::WARN, "psiz=%lld off=%lld rsiz=%lld fsiz=%lld"
+ " snum=%04X", (long long)psiz_, (long long)rec->off, (long long)rsiz,
+ (long long)file_.size(), snum);
+ report_binary(_KCCODELINE_, Logger::WARN, "rbuf", rbuf, rsiz);
+ return false;
+ }
+ }
+ }
+ } else {
+ if (rec->off + (int64_t)rec->rsiz > psiz_) {
+ set_error(_KCCODELINE_, Error::BROKEN, "invalid length of a record");
+ report(_KCCODELINE_, Logger::WARN, "psiz=%lld off=%lld rsiz=%lld fsiz=%lld"
+ " snum=%04X", (long long)psiz_, (long long)rec->off, (long long)rec->rsiz,
+ (long long)file_.size(), snum);
+ return false;
+ }
+ if (!read_record_body(rec)) return false;
+ }
+ return true;
+ }
+ /**
+ * Read the body of a record from the file.
+ * @param rec the record structure.
+ * @return true on success, or false on failure.
+ */
+ bool read_record_body(Record* rec) {
+ _assert_(rec);
+ size_t bsiz = rec->ksiz + rec->vsiz;
+ if (rec->psiz > 0) bsiz++;
+ char* bbuf = new char[bsiz];
+ if (!file_.read_fast(rec->boff, bbuf, bsiz)) {
+ set_error(_KCCODELINE_, Error::SYSTEM, file_.error());
+ report(_KCCODELINE_, Logger::WARN, "psiz=%lld off=%lld fsiz=%lld",
+ (long long)psiz_, (long long)rec->boff, (long long)file_.size());
+ delete[] bbuf;
+ return false;
+ }
+ if (rec->psiz > 0 && ((uint8_t*)bbuf)[bsiz-1] != PADMAGIC) {
+ set_error(_KCCODELINE_, Error::BROKEN, "invalid magic data of a record");
+ report_binary(_KCCODELINE_, Logger::WARN, "bbuf", bbuf, bsiz);
+ delete[] bbuf;
+ return false;
+ }
+ rec->bbuf = bbuf;
+ rec->kbuf = rec->bbuf;
+ rec->vbuf = rec->bbuf + rec->ksiz;
+ return true;
+ }
+ /**
+ * Write a record into the file.
+ * @param rec the record structure.
+ * @param over true for overwriting, or false for new record.
+ * @return true on success, or false on failure.
+ */
+ bool write_record(Record* rec, bool over) {
+ _assert_(rec);
+ char stack[IOBUFSIZ];
+ char* rbuf = rec->rsiz > sizeof(stack) ? new char[rec->rsiz] : stack;
+ char* wp = rbuf;
+ uint16_t snum = hton16(rec->psiz);
+ std::memcpy(wp, &snum, sizeof(snum));
+ if (rec->psiz < 0x100) *wp = RECMAGIC;
+ wp += sizeof(snum);
+ writefixnum(wp, rec->left >> apow_, width_);
+ wp += width_;
+ if (!linear_) {
+ writefixnum(wp, rec->right >> apow_, width_);
+ wp += width_;
+ }
+ wp += writevarnum(wp, rec->ksiz);
+ wp += writevarnum(wp, rec->vsiz);
+ std::memcpy(wp, rec->kbuf, rec->ksiz);
+ wp += rec->ksiz;
+ std::memcpy(wp, rec->vbuf, rec->vsiz);
+ wp += rec->vsiz;
+ if (rec->psiz > 0) {
+ std::memset(wp, 0, rec->psiz);
+ *wp = PADMAGIC;
+ wp += rec->psiz;
+ }
+ bool err = false;
+ if (over) {
+ if (!file_.write_fast(rec->off, rbuf, rec->rsiz)) {
+ set_error(_KCCODELINE_, Error::SYSTEM, file_.error());
+ err = true;
+ }
+ } else {
+ if (!file_.write(rec->off, rbuf, rec->rsiz)) {
+ set_error(_KCCODELINE_, Error::SYSTEM, file_.error());
+ err = true;
+ }
+ }
+ if (rbuf != stack) delete[] rbuf;
+ return !err;
+ }
+ /**
+ * Adjust the padding of a record.
+ * @param rec the record structure.
+ * @return true on success, or false on failure.
+ */
+ bool adjust_record(Record* rec) {
+ _assert_(rec);
+ if (rec->psiz > (size_t)INT16MAX || rec->psiz > rec->rsiz / 2) {
+ size_t nsiz = (rec->psiz >> apow_) << apow_;
+ if (nsiz < rhsiz_) return true;
+ rec->rsiz -= nsiz;
+ rec->psiz -= nsiz;
+ int64_t noff = rec->off + rec->rsiz;
+ char nbuf[RECBUFSIZ];
+ if (!write_free_block(noff, nsiz, nbuf)) return false;
+ insert_free_block(noff, nsiz);
+ }
+ return true;
+ }
+ /**
+ * Calculate the size of a record.
+ * @param ksiz the size of the key.
+ * @param vsiz the size of the value.
+ * @return the size of the record.
+ */
+ size_t calc_record_size(size_t ksiz, size_t vsiz) {
+ _assert_(true);
+ size_t rsiz = sizeof(uint16_t) + width_;
+ if (!linear_) rsiz += width_;
+ if (ksiz < (1ULL << 7)) {
+ rsiz += 1;
+ } else if (ksiz < (1ULL << 14)) {
+ rsiz += 2;
+ } else if (ksiz < (1ULL << 21)) {
+ rsiz += 3;
+ } else if (ksiz < (1ULL << 28)) {
+ rsiz += 4;
+ } else {
+ rsiz += 5;
+ }
+ if (vsiz < (1ULL << 7)) {
+ rsiz += 1;
+ } else if (vsiz < (1ULL << 14)) {
+ rsiz += 2;
+ } else if (vsiz < (1ULL << 21)) {
+ rsiz += 3;
+ } else if (vsiz < (1ULL << 28)) {
+ rsiz += 4;
+ } else {
+ rsiz += 5;
+ }
+ rsiz += ksiz;
+ rsiz += vsiz;
+ return rsiz;
+ }
+ /**
+ * Calculate the padding size of a record.
+ * @param rsiz the size of the record.
+ * @return the size of the padding.
+ */
+ size_t calc_record_padding(size_t rsiz) {
+ _assert_(true);
+ size_t diff = rsiz & (align_ - 1);
+ return diff > 0 ? align_ - diff : 0;
+ }
+ /**
+ * Shift a record to another place.
+ * @param orec the original record structure.
+ * @param dest the destination offset.
+ * @return true on success, or false on failure.
+ */
+ bool shift_record(Record* orec, int64_t dest) {
+ _assert_(orec && dest >= 0);
+ uint64_t hash = hash_record(orec->kbuf, orec->ksiz);
+ uint32_t pivot = fold_hash(hash);
+ int64_t bidx = hash % bnum_;
+ int64_t off = get_bucket(bidx);
+ if (off < 0) return false;
+ if (off == orec->off) {
+ orec->off = dest;
+ if (!write_record(orec, true)) return false;
+ if (!set_bucket(bidx, dest)) return false;
+ return true;
+ }
+ int64_t entoff = 0;
+ Record rec;
+ char rbuf[RECBUFSIZ];
+ while (off > 0) {
+ rec.off = off;
+ if (!read_record(&rec, rbuf)) return false;
+ if (rec.psiz == UINT16MAX) {
+ set_error(_KCCODELINE_, Error::BROKEN, "free block in the chain");
+ report(_KCCODELINE_, Logger::WARN, "psiz=%lld off=%lld fsiz=%lld",
+ (long long)psiz_, (long long)rec.off, (long long)file_.size());
+ return false;
+ }
+ uint32_t tpivot = linear_ ? pivot : fold_hash(hash_record(rec.kbuf, rec.ksiz));
+ if (pivot > tpivot) {
+ delete[] rec.bbuf;
+ off = rec.left;
+ entoff = rec.off + sizeof(uint16_t);
+ } else if (pivot < tpivot) {
+ delete[] rec.bbuf;
+ off = rec.right;
+ entoff = rec.off + sizeof(uint16_t) + width_;
+ } else {
+ int32_t kcmp = compare_keys(orec->kbuf, orec->ksiz, rec.kbuf, rec.ksiz);
+ if (linear_ && kcmp != 0) kcmp = 1;
+ if (kcmp > 0) {
+ delete[] rec.bbuf;
+ off = rec.left;
+ entoff = rec.off + sizeof(uint16_t);
+ } else if (kcmp < 0) {
+ delete[] rec.bbuf;
+ off = rec.right;
+ entoff = rec.off + sizeof(uint16_t) + width_;
+ } else {
+ delete[] rec.bbuf;
+ orec->off = dest;
+ if (!write_record(orec, true)) return false;
+ if (entoff > 0) {
+ if (!set_chain(entoff, dest)) return false;
+ } else {
+ if (!set_bucket(bidx, dest)) return false;
+ }
+ return true;
+ }
+ }
+ }
+ set_error(_KCCODELINE_, Error::BROKEN, "no record to shift");
+ report(_KCCODELINE_, Logger::WARN, "psiz=%lld fsiz=%lld",
+ (long long)psiz_, (long long)file_.size());
+ return false;
+ }
+ /**
+ * Write a free block into the file.
+ * @param off the offset of the free block.
+ * @param rsiz the size of the free block.
+ * @param rbuf the working buffer.
+ * @return true on success, or false on failure.
+ */
+ bool write_free_block(int64_t off, size_t rsiz, char* rbuf) {
+ _assert_(off >= 0 && rbuf);
+ char* wp = rbuf;
+ *(wp++) = FBMAGIC;
+ *(wp++) = FBMAGIC;
+ writefixnum(wp, rsiz >> apow_, width_);
+ wp += width_;
+ *(wp++) = PADMAGIC;
+ *(wp++) = PADMAGIC;
+ if (!file_.write_fast(off, rbuf, wp - rbuf)) {
+ set_error(_KCCODELINE_, Error::SYSTEM, file_.error());
+ return false;
+ }
+ return true;
+ }
+ /**
+ * Insert a free block to the free block pool.
+ * @param off the offset of the free block.
+ * @param rsiz the size of the free block.
+ */
+ void insert_free_block(int64_t off, size_t rsiz) {
+ _assert_(off >= 0);
+ ScopedMutex lock(&flock_);
+ escape_cursors(off, off + rsiz);
+ if (fbpnum_ < 1) return;
+ if (fbp_.size() >= (size_t)fbpnum_) {
+ FBP::const_iterator it = fbp_.begin();
+ if (rsiz <= it->rsiz) return;
+ fbp_.erase(it);
+ }
+ FreeBlock fb = { off, rsiz };
+ fbp_.insert(fb);
+ }
+ /**
+ * Fetch the free block pool from a decent sized block.
+ * @param rsiz the minimum size of the block.
+ * @param res the structure for the result.
+ * @return true on success, or false on failure.
+ */
+ bool fetch_free_block(size_t rsiz, FreeBlock* res) {
+ _assert_(res);
+ if (fbpnum_ < 1) return false;
+ ScopedMutex lock(&flock_);
+ FreeBlock fb = { INT64MAX, rsiz };
+ FBP::const_iterator it = fbp_.upper_bound(fb);
+ if (it == fbp_.end()) return false;
+ res->off = it->off;
+ res->rsiz = it->rsiz;
+ fbp_.erase(it);
+ escape_cursors(res->off, res->off + res->rsiz);
+ return true;
+ }
+ /**
+ * Trim invalid free blocks.
+ * @param begin the beginning offset.
+ * @param end the end offset.
+ */
+ void trim_free_blocks(int64_t begin, int64_t end) {
+ _assert_(begin >= 0 && end >= 0);
+ FBP::const_iterator it = fbp_.begin();
+ FBP::const_iterator itend = fbp_.end();
+ while (it != itend) {
+ if (it->off >= begin && it->off < end) {
+ fbp_.erase(it++);
+ } else {
+ ++it;
+ }
+ }
+ }
+ /**
+ * Dump all free blocks into the file.
+ * @return true on success, or false on failure.
+ */
+ bool dump_free_blocks() {
+ _assert_(true);
+ if (fbpnum_ < 1) return true;
+ size_t size = boff_ - HEADSIZ;
+ char* rbuf = new char[size];
+ char* wp = rbuf;
+ char* end = rbuf + size - width_ * 2 - sizeof(uint8_t) * 2;
+ size_t num = fbp_.size();
+ if (num > 0) {
+ FreeBlock* blocks = new FreeBlock[num];
+ size_t cnt = 0;
+ FBP::const_iterator it = fbp_.begin();
+ FBP::const_iterator itend = fbp_.end();
+ while (it != itend) {
+ blocks[cnt++] = *it;
+ ++it;
+ }
+ std::sort(blocks, blocks + num, FreeBlockComparator());
+ for (size_t i = num - 1; i > 0; i--) {
+ blocks[i].off -= blocks[i-1].off;
+ }
+ for (size_t i = 0; wp < end && i < num; i++) {
+ wp += writevarnum(wp, blocks[i].off >> apow_);
+ wp += writevarnum(wp, blocks[i].rsiz >> apow_);
+ }
+ delete[] blocks;
+ }
+ *(wp++) = 0;
+ *(wp++) = 0;
+ bool err = false;
+ if (!file_.write(HEADSIZ, rbuf, wp - rbuf)) {
+ set_error(_KCCODELINE_, Error::SYSTEM, file_.error());
+ err = true;
+ }
+ delete[] rbuf;
+ return !err;
+ }
+ /**
+ * Dump an empty set of free blocks into the file.
+ * @return true on success, or false on failure.
+ */
+ bool dump_empty_free_blocks() {
+ _assert_(true);
+ if (fbpnum_ < 1) return true;
+ char rbuf[2];
+ char* wp = rbuf;
+ *(wp++) = 0;
+ *(wp++) = 0;
+ bool err = false;
+ if (!file_.write(HEADSIZ, rbuf, wp - rbuf)) {
+ set_error(_KCCODELINE_, Error::SYSTEM, file_.error());
+ err = true;
+ }
+ return !err;
+ }
+ /**
+ * Load all free blocks from from the file.
+ * @return true on success, or false on failure.
+ */
+ bool load_free_blocks() {
+ _assert_(true);
+ if (fbpnum_ < 1) return true;
+ size_t size = boff_ - HEADSIZ;
+ char* rbuf = new char[size];
+ if (!file_.read(HEADSIZ, rbuf, size)) {
+ set_error(_KCCODELINE_, Error::SYSTEM, file_.error());
+ report(_KCCODELINE_, Logger::WARN, "psiz=%lld off=%lld fsiz=%lld",
+ (long long)psiz_, (long long)HEADSIZ, (long long)file_.size());
+ delete[] rbuf;
+ return false;
+ }
+ const char* rp = rbuf;
+ FreeBlock* blocks = new FreeBlock[fbpnum_];
+ int32_t num = 0;
+ while (num < fbpnum_ && size > 1 && *rp != '\0') {
+ uint64_t off;
+ size_t step = readvarnum(rp, size, &off);
+ if (step < 1 || off < 1) {
+ set_error(_KCCODELINE_, Error::BROKEN, "invalid free block offset");
+ report(_KCCODELINE_, Logger::WARN, "psiz=%lld off=%lld fsiz=%lld",
+ (long long)psiz_, (long long)off, (long long)file_.size());
+ delete[] rbuf;
+ delete[] blocks;
+ return false;
+ }
+ rp += step;
+ size -= step;
+ uint64_t rsiz;
+ step = readvarnum(rp, size, &rsiz);
+ if (step < 1 || rsiz < 1) {
+ set_error(_KCCODELINE_, Error::BROKEN, "invalid free block size");
+ report(_KCCODELINE_, Logger::WARN, "psiz=%lld off=%lld rsiz=%lld fsiz=%lld",
+ (long long)psiz_, (long long)off, (long long)rsiz, (long long)file_.size());
+ delete[] rbuf;
+ delete[] blocks;
+ return false;
+ }
+ rp += step;
+ size -= step;
+ blocks[num].off = off << apow_;
+ blocks[num].rsiz = rsiz << apow_;
+ num++;
+ }
+ for (int32_t i = 1; i < num; i++) {
+ blocks[i].off += blocks[i-1].off;
+ }
+ for (int32_t i = 0; i < num; i++) {
+ FreeBlock fb = { blocks[i].off, blocks[i].rsiz };
+ fbp_.insert(fb);
+ }
+ delete[] blocks;
+ delete[] rbuf;
+ return true;
+ }
+ /**
+ * Disable all cursors.
+ */
+ void disable_cursors() {
+ _assert_(true);
+ if (curs_.empty()) return;
+ CursorList::const_iterator cit = curs_.begin();
+ CursorList::const_iterator citend = curs_.end();
+ while (cit != citend) {
+ Cursor* cur = *cit;
+ cur->off_ = 0;
+ ++cit;
+ }
+ }
+ /**
+ * Escape cursors on a free block.
+ * @param off the offset of the free block.
+ * @param dest the destination offset.
+ */
+ void escape_cursors(int64_t off, int64_t dest) {
+ _assert_(off >= 0 && dest >= 0);
+ if (curs_.empty()) return;
+ CursorList::const_iterator cit = curs_.begin();
+ CursorList::const_iterator citend = curs_.end();
+ while (cit != citend) {
+ Cursor* cur = *cit;
+ if (cur->end_ == off) {
+ cur->end_ = dest;
+ if (cur->off_ >= cur->end_) cur->off_ = 0;
+ }
+ if (cur->off_ == off) {
+ cur->off_ = dest;
+ if (cur->off_ >= cur->end_) cur->off_ = 0;
+ }
+ ++cit;
+ }
+ }
+ /**
+ * Trim invalid cursors.
+ */
+ void trim_cursors() {
+ _assert_(true);
+ if (curs_.empty()) return;
+ int64_t end = lsiz_;
+ CursorList::const_iterator cit = curs_.begin();
+ CursorList::const_iterator citend = curs_.end();
+ while (cit != citend) {
+ Cursor* cur = *cit;
+ if (cur->off_ >= end) {
+ cur->off_ = 0;
+ } else if (cur->end_ > end) {
+ cur->end_ = end;
+ }
+ ++cit;
+ }
+ }
+ /**
+ * Remove a record from a bucket chain.
+ * @param rec the record structure.
+ * @param rbuf the working buffer.
+ * @param bidx the bucket index.
+ * @param entoff the offset of the entry pointer.
+ * @return true on success, or false on failure.
+ */
+ bool cut_chain(Record* rec, char* rbuf, int64_t bidx, int64_t entoff) {
+ _assert_(rec && rbuf && bidx >= 0 && entoff >= 0);
+ int64_t child;
+ if (rec->left > 0 && rec->right < 1) {
+ child = rec->left;
+ } else if (rec->left < 1 && rec->right > 0) {
+ child = rec->right;
+ } else if (rec->left < 1) {
+ child = 0;
+ } else {
+ Record prec;
+ prec.off = rec->left;
+ if (!read_record(&prec, rbuf)) return false;
+ if (prec.psiz == UINT16MAX) {
+ set_error(_KCCODELINE_, Error::BROKEN, "free block in the chain");
+ report(_KCCODELINE_, Logger::WARN, "psiz=%lld off=%lld fsiz=%lld",
+ (long long)psiz_, (long long)prec.off, (long long)file_.size());
+ report_binary(_KCCODELINE_, Logger::WARN, "rbuf", rbuf, rhsiz_);
+ return false;
+ }
+ delete[] prec.bbuf;
+ if (prec.right > 0) {
+ int64_t off = prec.right;
+ int64_t pentoff = prec.off + sizeof(uint16_t) + width_;
+ while (true) {
+ prec.off = off;
+ if (!read_record(&prec, rbuf)) return false;
+ if (prec.psiz == UINT16MAX) {
+ set_error(_KCCODELINE_, Error::BROKEN, "free block in the chain");
+ report(_KCCODELINE_, Logger::WARN, "psiz=%lld off=%lld fsiz=%lld",
+ (long long)psiz_, (long long)prec.off, (long long)file_.size());
+ report_binary(_KCCODELINE_, Logger::WARN, "rbuf", rbuf, rhsiz_);
+ return false;
+ }
+ delete[] prec.bbuf;
+ if (prec.right < 1) break;
+ off = prec.right;
+ pentoff = prec.off + sizeof(uint16_t) + width_;
+ }
+ child = off;
+ if (!set_chain(pentoff, prec.left)) return false;
+ if (!set_chain(off + sizeof(uint16_t), rec->left)) return false;
+ if (!set_chain(off + sizeof(uint16_t) + width_, rec->right)) return false;
+ } else {
+ child = prec.off;
+ if (!set_chain(prec.off + sizeof(uint16_t) + width_, rec->right)) return false;
+ }
+ }
+ if (entoff > 0) {
+ if (!set_chain(entoff, child)) return false;
+ } else {
+ if (!set_bucket(bidx, child)) return false;
+ }
+ return true;
+ }
+ /**
+ * Begin transaction.
+ * @return true on success, or false on failure.
+ */
+ bool begin_transaction_impl() {
+ _assert_(true);
+ if ((count_ != trcount_ || lsiz_ != trsize_) && !dump_meta()) return false;
+ if (!file_.begin_transaction(trhard_, boff_)) {
+ set_error(_KCCODELINE_, Error::SYSTEM, file_.error());
+ return false;
+ }
+ if (!file_.write_transaction(MOFFBNUM, HEADSIZ - MOFFBNUM)) {
+ set_error(_KCCODELINE_, Error::SYSTEM, file_.error());
+ file_.end_transaction(false);
+ return false;
+ }
+ if (fbpnum_ > 0) {
+ FBP::const_iterator it = fbp_.end();
+ FBP::const_iterator itbeg = fbp_.begin();
+ for (int32_t cnt = fpow_ * 2 + 1; cnt > 0; cnt--) {
+ if (it == itbeg) break;
+ --it;
+ trfbp_.insert(*it);
+ }
+ }
+ return true;
+ }
+ /**
+ * Begin auto transaction.
+ * @return true on success, or false on failure.
+ */
+ bool begin_auto_transaction() {
+ _assert_(true);
+ atlock_.lock();
+ if (!file_.begin_transaction(autosync_, boff_)) {
+ set_error(_KCCODELINE_, Error::SYSTEM, file_.error());
+ atlock_.unlock();
+ return false;
+ }
+ if (!file_.write_transaction(MOFFCOUNT, MOFFOPAQUE - MOFFCOUNT)) {
+ set_error(_KCCODELINE_, Error::SYSTEM, file_.error());
+ file_.end_transaction(false);
+ atlock_.unlock();
+ return false;
+ }
+ return true;
+ }
+ /**
+ * Commit transaction.
+ * @return true on success, or false on failure.
+ */
+ bool commit_transaction() {
+ _assert_(true);
+ bool err = false;
+ if ((count_ != trcount_ || lsiz_ != trsize_) && !dump_auto_meta()) err = true;
+ if (!file_.end_transaction(true)) {
+ set_error(_KCCODELINE_, Error::SYSTEM, file_.error());
+ err = true;
+ }
+ trfbp_.clear();
+ return !err;
+ }
+ /**
+ * Commit auto transaction.
+ * @return true on success, or false on failure.
+ */
+ bool commit_auto_transaction() {
+ _assert_(true);
+ bool err = false;
+ if ((count_ != trcount_ || lsiz_ != trsize_) && !dump_auto_meta()) err = true;
+ if (!file_.end_transaction(true)) {
+ set_error(_KCCODELINE_, Error::SYSTEM, file_.error());
+ err = true;
+ }
+ atlock_.unlock();
+ return !err;
+ }
+ /**
+ * Abort transaction.
+ * @return true on success, or false on failure.
+ */
+ bool abort_transaction() {
+ _assert_(true);
+ bool err = false;
+ if (!file_.end_transaction(false)) {
+ set_error(_KCCODELINE_, Error::SYSTEM, file_.error());
+ err = true;
+ }
+ bool flagopen = flagopen_;
+ if (!load_meta()) err = true;
+ flagopen_ = flagopen;
+ calc_meta();
+ disable_cursors();
+ fbp_.swap(trfbp_);
+ trfbp_.clear();
+ return !err;
+ }
+ /**
+ * Abort auto transaction.
+ * @return true on success, or false on failure.
+ */
+ bool abort_auto_transaction() {
+ _assert_(true);
+ bool err = false;
+ if (!file_.end_transaction(false)) {
+ set_error(_KCCODELINE_, Error::SYSTEM, file_.error());
+ err = true;
+ }
+ if (!load_meta()) err = true;
+ calc_meta();
+ disable_cursors();
+ fbp_.clear();
+ atlock_.unlock();
+ return !err;
+ }
+ /** Dummy constructor to forbid the use. */
+ HashDB(const HashDB&);
+ /** Dummy Operator to forbid the use. */
+ HashDB& operator =(const HashDB&);
+ /** The method lock. */
+ RWLock mlock_;
+ /** The record locks. */
+ SlottedRWLock rlock_;
+ /** The file lock. */
+ Mutex flock_;
+ /** The auto transaction lock. */
+ Mutex atlock_;
+ /** The last happened error. */
+ TSD<Error> error_;
+ /** The internal logger. */
+ Logger* logger_;
+ /** The kinds of logged messages. */
+ uint32_t logkinds_;
+ /** The internal meta operation trigger. */
+ MetaTrigger* mtrigger_;
+ /** The open mode. */
+ uint32_t omode_;
+ /** The flag for writer. */
+ bool writer_;
+ /** The flag for auto transaction. */
+ bool autotran_;
+ /** The flag for auto synchronization. */
+ bool autosync_;
+ /** The flag for reorganized. */
+ bool reorg_;
+ /** The flag for trimmed. */
+ bool trim_;
+ /** The file for data. */
+ File file_;
+ /** The free block pool. */
+ FBP fbp_;
+ /** The cursor objects. */
+ CursorList curs_;
+ /** The path of the database file. */
+ std::string path_;
+ /** The library version. */
+ uint8_t libver_;
+ /** The library revision. */
+ uint8_t librev_;
+ /** The format revision. */
+ uint8_t fmtver_;
+ /** The module checksum. */
+ uint8_t chksum_;
+ /** The database type. */
+ uint8_t type_;
+ /** The alignment power. */
+ uint8_t apow_;
+ /** The free block pool power. */
+ uint8_t fpow_;
+ /** The options. */
+ uint8_t opts_;
+ /** The bucket number. */
+ int64_t bnum_;
+ /** The status flags. */
+ uint8_t flags_;
+ /** The flag for open. */
+ bool flagopen_;
+ /** The record number. */
+ AtomicInt64 count_;
+ /** The logical size of the file. */
+ AtomicInt64 lsiz_;
+ /** The physical size of the file. */
+ AtomicInt64 psiz_;
+ /** The opaque data. */
+ char opaque_[HEADSIZ-MOFFOPAQUE];
+ /** The size of the internal memory-mapped region. */
+ int64_t msiz_;
+ /** The unit step number of auto defragmentation. */
+ int64_t dfunit_;
+ /** The embedded data compressor. */
+ Compressor* embcomp_;
+ /** The alignment of records. */
+ size_t align_;
+ /** The number of elements of the free block pool. */
+ int32_t fbpnum_;
+ /** The width of record addressing. */
+ int32_t width_;
+ /** The flag for linear collision chaining. */
+ bool linear_;
+ /** The data compressor. */
+ Compressor* comp_;
+ /** The header size of a record. */
+ size_t rhsiz_;
+ /** The offset of the buckets section. */
+ int64_t boff_;
+ /** The offset of the record section. */
+ int64_t roff_;
+ /** The defrag cursor. */
+ int64_t dfcur_;
+ /** The count of fragmentation. */
+ AtomicInt64 frgcnt_;
+ /** The flag whether in transaction. */
+ bool tran_;
+ /** The flag whether hard transaction. */
+ bool trhard_;
+ /** The escaped free block pool for transaction. */
+ FBP trfbp_;
+ /** The count history for transaction. */
+ int64_t trcount_;
+ /** The size history for transaction. */
+ int64_t trsize_;
+};
+
+
+/** An alias of the file tree database. */
+typedef PlantDB<HashDB, BasicDB::TYPETREE> TreeDB;
+
+
+} // common namespace
+
+#endif // duplication check
+
+// END OF FILE