1
0
mirror of https://github.com/stefanocasazza/ULib.git synced 2025-09-28 19:05:55 +08:00
ULib/include/ulib/db/rdb.h
stefanocasazza b7610d4088 fix
2017-09-16 19:44:13 +02:00

683 lines
21 KiB
C++

// ============================================================================
//
// = LIBRARY
// ULib - c++ library
//
// = FILENAME
// rdb.h - A Reliable DataBase library (Felix von Leitner)
//
// = AUTHOR
// Stefano Casazza
//
// ============================================================================
#ifndef ULIB_RDB_H
#define ULIB_RDB_H 1
#include <ulib/db/cdb.h>
#include <ulib/utility/lock.h>
#include <ulib/utility/string_ext.h>
#include <ulib/utility/data_session.h>
#ifdef _MSWINDOWS_
# include <ws2tcpip.h>
#else
# include <netinet/in.h>
#endif
/**
* @class URDB
*
* @brief URDB is a fast, Reliable, simple class for creating and reading DataBases
*
* The idea behind URDB is to take UCDB and put a journal over it.
* Then provide an abstraction layer that looks like ndbm and writes updates to the journal.
* Read operations are answered by consulting the cache (build with journal) and the cdb file.
* The result should be a reasonably small yet crash-proof read-write database
*/
class URDBServer;
class UHttpPlugIn;
class Application;
class UServer_Base;
class UDataStorage;
class URDBClient_Base;
class URDBClientImage;
// The interface is very similar to the gdbm one
# define CACHE_HASHTAB_LEN 769
# define RDB_off(prdb) ((URDB::cache_struct*)(((URDB*)prdb)->journal.map))->off
# define RDB_capacity(prdb) (uint32_t)(((URDB*)prdb)->journal.st_size - RDB_off(prdb))
# define RDB_eof(prdb) (((URDB*)prdb)->journal.map+(ptrdiff_t)((URDB*)prdb)->journal.st_size)
# define RDB_allocate(prdb) (uint32_t*)(((URDB*)prdb)->journal.map+(ptrdiff_t)RDB_off(prdb))
# define RDB_sync(prdb) ((URDB::cache_struct*)(((URDB*)prdb)->journal.map))->sync
# define RDB_nrecord(prdb) ((URDB::cache_struct*)(((URDB*)prdb)->journal.map))->nrecord
# define RDB_reference(prdb) ((URDB::cache_struct*)(((URDB*)prdb)->journal.map))->reference
# define RDB_hashtab(prdb) (((URDB::cache_struct*)(((URDB*)prdb)->journal.map))->hashtab)
# define RDB_ptr(prdb) (((URDB*)prdb)->journal.map+sizeof(URDB::cache_struct))
# define RDB_start(prdb) (RDB_ptr(prdb)-(CACHE_HASHTAB_LEN*sizeof(uint32_t)))
# define RDB_node(prdb) ((URDB::cache_node*)(((URDB*)prdb)->journal.map+prdb->node))
# define RDB_node_key_pr(prdb) u_get_unaligned32(RDB_node(prdb)->key.dptr)
# define RDB_node_key_sz(prdb) u_get_unaligned32(RDB_node(prdb)->key.dsize)
# define RDB_node_data_pr(prdb) u_get_unaligned32(RDB_node(prdb)->data.dptr)
# define RDB_node_data_sz(prdb) u_get_unaligned32(RDB_node(prdb)->data.dsize)
# define RDB_node_key(prdb) (((URDB*)prdb)->journal.map+RDB_node_key_pr(prdb))
# define RDB_node_data(prdb) (((URDB*)prdb)->journal.map+RDB_node_data_pr(prdb))
# define RDB_ptr_node(prdb,offset) ((URDB::cache_node*)(((URDB*)prdb)->journal.map+offset))
# define RDB_cache_node(node,attribute) (u_get_unaligned32(((URDB::cache_node*)node)->attribute))
class U_EXPORT URDB : public UCDB {
public:
URDB(int _ignore_case = false) : UCDB(_ignore_case)
{
U_TRACE_REGISTER_OBJECT(0, URDB, "%d", _ignore_case)
plock = U_NULLPTR;
pnode = U_NULLPTR;
node = 0;
key1.dptr = U_NULLPTR;
key1.dsize = 0;
}
URDB(const UString& pathdb, int _ignore_case) : UCDB(pathdb, _ignore_case)
{
U_TRACE_REGISTER_OBJECT(0, URDB, "%V,%d", pathdb.rep, _ignore_case)
plock = U_NULLPTR;
pnode = U_NULLPTR;
node = 0;
key1.dptr = U_NULLPTR;
key1.dsize = 0;
}
// coverity[VIRTUAL_DTOR]
#ifdef U_COVERITY_FALSE_POSITIVE
virtual
#endif
~URDB()
{
U_TRACE_UNREGISTER_OBJECT(0, URDB)
if (preclock)
{
delete[] preclock;
preclock = U_NULLPTR;
}
}
// Open a Reliable DataBase
bool open( uint32_t log_size = 1024 * 1024, bool btruncate = false, bool cdb_brdonly = true, bool breference = true, sem_t* psem = &nolock);
bool open(const UString& pathdb, uint32_t log_size = 1024 * 1024, bool btruncate = false, bool cdb_brdonly = true, bool breference = true, sem_t* psem = &nolock)
{
U_TRACE(0, "URDB::open(%V,%u,%b,%b,%b)", pathdb.rep, log_size, btruncate, cdb_brdonly, breference)
UFile::setPath(pathdb);
return URDB::open(log_size, btruncate, cdb_brdonly, breference, psem);
}
void reset();
uint32_t size() const
{
U_TRACE_NO_PARAM(0, "URDB::size()")
U_INTERNAL_DUMP("UCDB::nrecord = %u RDB_nrecord = %u", UCDB::nrecord, RDB_nrecord(this))
U_RETURN(UCDB::nrecord + RDB_nrecord(this));
}
// Close a Reliable DataBase
void close(bool breference = true);
// Combines the old cdb file and the diffs in a new cdb file.
// Close the database and deletes the obsolete journal file if everything worked out
bool closeReorganize();
// ---------------------------------------------------------------------
// Write a key/value pair to a reliable database
// ---------------------------------------------------------------------
// RETURN VALUE
// ---------------------------------------------------------------------
// 0: Everything was OK
// -1: flag was RDB_INSERT and this key already existed
// -3: there is not enough (virtual) memory available on writing journal
// ---------------------------------------------------------------------
# define RDB_INSERT 0 // Insertion of new entries only
# define RDB_REPLACE 1 // Allow replacing existing entries
# define RDB_INSERT_WITH_PADDING 2 // Allow replacing existing entries and for insertion of new entries padding data with space
int store(const UString& k, const char* d, uint32_t dlen, int _flag) { return store(U_STRING_TO_PARAM(k), d, dlen, _flag); }
int store(const char* k, uint32_t klen, const UString& d, int _flag) { return store(k, klen, U_STRING_TO_PARAM(d), _flag); }
int store(const UString& k, const UString& d, int _flag) { return store(U_STRING_TO_PARAM(k), U_STRING_TO_PARAM(d), _flag); }
int store(const char* _key, uint32_t keylen, const char* _data, uint32_t datalen, int _flag);
// ---------------------------------------------------------------------
// Mark a key/value as deleted
// ---------------------------------------------------------------------
// RETURN VALUE
// ---------------------------------------------------------------------
// 0: Everything was OK
// -1: The entry was not in the database
// -2: The entry was already marked deleted in the hash-tree
// -3: there is not enough (virtual) memory available on writing journal
// ---------------------------------------------------------------------
int remove(const UString& _key)
{
U_TRACE(0, "URDB::remove(%V)", _key.rep)
UCDB::setKey(_key);
return remove();
}
int remove(const char* _key, uint32_t keylen)
{
U_TRACE(0, "URDB::remove(%.*S,%u)", keylen, _key, keylen)
UCDB::setKey(_key, keylen);
return remove();
}
// ----------------------------------------------------------------------
// Substitute a key/value with a new key/value (remove+store)
// ----------------------------------------------------------------------
// RETURN VALUE
// ----------------------------------------------------------------------
// 0: Everything was OK
// -1: The entry was not in the database
// -2: The entry was marked deleted in the hash-tree
// -3: there is not enough (virtual) memory available on writing journal
// -4: flag was RDB_INSERT and the new key already existed
// ----------------------------------------------------------------------
int substitute(const UString& _key, const UString& new_key, const UString& _data, int _flag = RDB_INSERT)
{
U_TRACE(0, "URDB::substitute(%V,%V,%V,%d)", _key.rep, new_key.rep, _data.rep, _flag)
UCDB::setKey(_key);
UCDB::setData(_data);
UCDB::datum key2 = { (void*) new_key.data(), new_key.size() };
return substitute(&key2, _flag);
}
bool fetch();
bool find(const char* key, uint32_t keylen);
bool find(const UString& _key) { return find(U_STRING_TO_PARAM(_key)); }
UFile& getJournal() { return journal; }
uint32_t getJournalSize() { return journal.st_size; }
uint32_t getCapacity() const { return RDB_capacity(this); }
uint32_t getDataSize() const { return RDB_node_data_sz(this); }
void* getDataPointer() const { return RDB_node_data(this); }
UString at(UStringRep* _key)
{
U_TRACE(0, "URDB::at(%V)", _key)
UCDB::setKey(_key);
return at();
}
UString at(const UString& _key)
{
U_TRACE(0, "URDB::at(%V)", _key.rep)
UCDB::setKey(_key);
return at();
}
UString at(const char* _key, uint32_t keylen)
{
U_TRACE(0, "URDB::at(%.*S,%u)", keylen, _key, keylen)
UCDB::setKey(_key, keylen);
return at();
}
// operator []
UString operator[](UStringRep* _key) { return at(_key); }
UString operator[](const UString& _key) { return at(_key); }
// flushes changes made to the log file back to disk
void msync();
void fsync() { journal.fsync(); }
// LOCK
void lockRecord();
void unlockRecord()
{
U_TRACE_NO_PARAM(0, "URDB::unlockRecord()")
U_INTERNAL_ASSERT_POINTER(plock)
plock->unlock();
}
static void initRecordLock();
void lock() { if (_lock.sem) _lock.lock(); }
void unlock() { if (_lock.sem) _lock.unlock(); }
// TRANSACTION
bool beginTransaction();
void abortTransaction();
void commitTransaction();
// Call function for all entry
void getKeys(UVector<UString>& vec);
void callForAllEntry( iPFprpr function, UVector<UString>* v = U_NULLPTR);
void callForAllEntryDelete(iPFprpr function);
void callForAllEntrySorted(iPFprpr function, qcompare compare_obj = U_NULLPTR);
// PRINT
UString print();
UString printSorted();
// STREAM
#ifdef U_STDCPP_ENABLE
friend U_EXPORT ostream& operator<<(ostream& os, URDB& rdb);
// DEBUG
# ifdef DEBUG
const char* dump(bool reset) const;
# endif
#endif
protected:
ULock _lock;
ULock* plock;
UFile journal;
// ----------------------------------------------------------------------------------------------------------------
// CACHE for CDB:
// ----------------------------------------------------------------------------------------------------------------
// This code implements a chained hash table where we use binary search trees instead of linked lists as chains.
// Let's call this a hash tree. This code uses blobs, not 0-terminated strings. While these routines can be used
// as associative hash, they are meant as a cache for a larger, disk-base database or mmap'ed file. And I provide
// functions to mark a record as deleted, so that this can be used to cache deltas to a constant database like CDB
// ----------------------------------------------------------------------------------------------------------------
// NB: offsets relative to the starting address of the mapping should be employed...
// ----------------------------------------------------------------------------------------------------------------
typedef struct rdb_datum {
uint32_t dptr, dsize;
} rdb_datum;
typedef struct rdb_cache_node {
rdb_datum key, data;
uint32_t left, right; // Two cache_node 'pointer' of the binary search tree behind every entry of the hash table
} cache_node;
typedef struct rdb_cache_struct {
uint32_t off; // RDB_off
uint32_t sync; // RDB_sync
uint32_t nrecord; // RDB_nrecord
uint32_t reference; // RDB_reference
uint32_t hashtab[CACHE_HASHTAB_LEN]; // RDB_hashtab
// -----> data storage... // RDB_ptr
} cache_struct;
// Manage shared cache
UString at();
int remove();
bool _fetch();
bool isDeleted();
bool reorganize(); // Combines the old cdb file and the diffs in a new cdb file
int store(int flag);
bool compactionJournal();
int _store(int flag, bool exist);
int substitute(UCDB::datum* new_key, int flag);
void resetReference()
{
U_TRACE_NO_PARAM(0, "URDB::resetReference()")
U_CHECK_MEMORY
U_INTERNAL_DUMP("RDB_reference = %u", RDB_reference(this))
RDB_reference(this) = 1;
}
bool cdbLookup() // NB: set the value of struct UCDB::data...
{
U_TRACE_NO_PARAM(0, "URDB::cdbLookup()")
U_CHECK_MEMORY
if (UFile::st_size &&
UCDB::find())
{
U_RETURN(true);
}
U_RETURN(false);
}
// utility especially created for RDB net interface class
static char* parseLine(const char* ptr, UCDB::datum* key, UCDB::datum* data);
char* parseLine(const char* ptr) { return parseLine(ptr, &key, &data); }
static sem_t nolock;
static ULock* preclock;
private:
uint32_t* pnode;
uint32_t node; // RDB_node
UCDB::datum key1;
static uint32_t nerror;
inline void setNodeLeft() U_NO_EXPORT;
inline void setNodeRight() U_NO_EXPORT;
void copy1(URDB* prdb, uint32_t offset) U_NO_EXPORT;
void call1(UCDB* pcdb, uint32_t offset) U_NO_EXPORT;
void print1(UCDB* pcdb, uint32_t offset) U_NO_EXPORT;
void getKeys1(UCDB* pcdb, uint32_t offset) U_NO_EXPORT;
void makeAdd1(UCDB* pcdb, uint32_t offset) U_NO_EXPORT;
bool logJournal(int op) U_NO_EXPORT;
bool resizeJournal(uint32_t oversize) U_NO_EXPORT;
void call(UCDB* pcdb, vPFpvu function1, vPFpvpc function2) U_NO_EXPORT;
void callForEntryNotInCache(UCDB* pcdb, vPFpvpc function2) U_NO_EXPORT;
bool writev(const struct iovec* iov, int n, uint32_t size) U_NO_EXPORT;
static void htAlloc(URDB* prdb) U_NO_EXPORT; // Alloc one node for the hash tree
static bool htLookup(URDB* prdb) U_NO_EXPORT; // Search one key/data pair in the cache
static void htInsert(URDB* prdb) U_NO_EXPORT; // Insert one key/data pair in the cache
static void htRemoveAlloc(URDB* prdb) U_NO_EXPORT; // remove one node allocated for the hash tree
U_DISALLOW_COPY_AND_ASSIGN(URDB)
friend class UHTTP;
friend class URDBServer;
friend class UHttpPlugIn;
friend class Application;
friend class UServer_Base;
friend class URDBClient_Base;
friend class URDBClientImage;
template <class T> friend class URDBObjectHandler;
};
template <> class U_EXPORT URDBObjectHandler<UDataStorage*> : public URDB {
public:
URDBObjectHandler(const UString& pathdb, int _ignore_case, void* ptr, bool _bdirect = false);
// coverity[VIRTUAL_DTOR]
#ifdef U_COVERITY_FALSE_POSITIVE
virtual
#endif
~URDBObjectHandler()
{
U_TRACE_UNREGISTER_OBJECT(0, URDBObjectHandler<UDataStorage*>)
# ifdef DEBUG
if (bdirect == false &&
pDataStorage &&
pDataStorage->recdata)
{
uint32_t sz = pDataStorage->size();
U_INTERNAL_DUMP("pDataStorage->recdata(%u) = %p", sz, pDataStorage->recdata)
U_INTERNAL_ASSERT_MAJOR(sz, 0)
UMemoryPool::_free(pDataStorage->recdata, sz);
}
# endif
}
// SERVICES
void close();
bool getDataStorage();
bool getDataStorage(const char* s, uint32_t n);
bool getDataStorage(in_addr_t client)
{
U_TRACE(0, "URDBObjectHandler<UDataStorage*>::getDataStorage(%u)", client)
U_CHECK_MEMORY
U_cdb_no_hash(this) = true;
bool result = getDataStorage((const char*)&client, sizeof(in_addr_t));
U_cdb_no_hash(this) = false;
U_RETURN(result);
}
bool getDataStorage(const UString& _key)
{
U_TRACE(0, "URDBObjectHandler<UDataStorage*>::getDataStorage(%V)", _key.rep)
U_CHECK_MEMORY
U_INTERNAL_ASSERT_POINTER(pDataStorage)
pDataStorage->setKeyIdDataSession(_key);
return getDataStorage();
}
bool putDataStorage(const UString& _key)
{
U_TRACE(0, "URDBObjectHandler<UDataStorage*>::putDataStorage(%V)", _key.rep)
U_CHECK_MEMORY
U_INTERNAL_ASSERT_POINTER(pDataStorage)
U_INTERNAL_ASSERT_EQUALS(_key, pDataStorage->keyid)
return putDataStorage(RDB_INSERT_WITH_PADDING);
}
bool putDataStorage(int op = RDB_INSERT_WITH_PADDING);
bool insertDataStorage(int op)
{
U_TRACE(0, "URDBObjectHandler<UDataStorage*>::insertDataStorage(%d)", op)
U_CHECK_MEMORY
U_INTERNAL_ASSERT_POINTER(pDataStorage)
U_ASSERT(pDataStorage->isDataSession())
data_buffer = pDataStorage->toBuffer();
data_len = UDataStorage::buffer_len;
return _insertDataStorage(U_STRING_TO_PARAM(pDataStorage->keyid), op);
}
bool insertDataStorage(const char* s, uint32_t n, int _flag = RDB_INSERT) // SSL session cache...
{
U_TRACE(0, "URDBObjectHandler<UDataStorage*>::insertDataStorage(%.*S,%u,%d)", n, s, n, _flag)
U_CHECK_MEMORY
U_INTERNAL_ASSERT_POINTER(pDataStorage)
pDataStorage->setKeyIdDataSession(s, n);
return insertDataStorage(_flag);
}
bool insertDataStorage(const UString& _key, int _flag = RDB_INSERT_WITH_PADDING)
{
U_TRACE(0, "URDBObjectHandler<UDataStorage*>::insertDataStorage(%V,%d)", _key.rep, _flag)
return insertDataStorage(U_STRING_TO_PARAM(_key), _flag);
}
void setPointerToDataStorage()
{
U_TRACE_NO_PARAM(0, "URDBObjectHandler<UDataStorage*>::setPointerToDataStorage()")
U_INTERNAL_ASSERT(bdirect)
U_INTERNAL_ASSERT_POINTER(pDataStorage)
*(char**)pDataStorage = recval.data();
}
bool insertDataStorage(void* drec, uint32_t dlen, const char* s, uint32_t n, int op)
{
U_TRACE(0, "URDBObjectHandler<UDataStorage*>::insertDataStorage(%p,%u,%.*S,%u,%d)", drec, dlen, n, s, n, op)
data_buffer = (char*)drec;
data_len = dlen;
if (_insertDataStorage(s, n, op))
{
setPointerToDataStorage();
U_ASSERT(recval.equal(data_buffer, dlen))
U_RETURN(true);
}
U_RETURN(false);
}
bool insertDataStorage(void* drec, uint32_t dlen, in_addr_t client, int op)
{
U_TRACE(0, "URDBObjectHandler<UDataStorage*>::insertDataStorage(%p,%u,%u,%d)", drec, dlen, client, op)
U_CHECK_MEMORY
U_cdb_no_hash(this) = true;
bool result = insertDataStorage(drec, dlen, (const char*)&client, sizeof(in_addr_t), op);
U_cdb_no_hash(this) = false;
U_RETURN(result);
}
UString getKeyID() const { return pDataStorage->keyid; }
void resetKeyID() const { pDataStorage->keyid.clear(); }
bool isRecordFound() const { return brecfound; }
UString getRecordValue() const { return recval; }
UDataStorage* getPointerToDataStorage() { return pDataStorage; }
void setPointerToDataStorage(UDataStorage* ptr)
{
U_TRACE(0, "URDBObjectHandler<UDataStorage*>::setPointerToDataStorage(%p)", ptr)
pDataStorage = ptr;
# ifdef DEBUG
if (bdirect == false &&
ptr &&
ptr->recdata)
{
uint32_t sz = ptr->size();
U_INTERNAL_DUMP("pDataStorage->recdata(%u) = %p", sz, ptr->recdata)
U_INTERNAL_ASSERT_MAJOR(sz, 0)
}
# endif
}
// Call function for all entry
void callForAllEntry(iPFprpr function, vPF function_no_lock = U_NULLPTR, qcompare compare_obj = U_NULLPTR);
void callForAllEntryWithSetEntry(vPFprpr function, vPF function_no_lock = U_NULLPTR, qcompare compare_obj = U_NULLPTR)
{ bsetEntry = true; callForAllEntry((iPFprpr)function, function_no_lock, compare_obj); bsetEntry = false; }
void callForAllEntryWithVector(iPFprpr function, vPF function_no_lock = (vPF)-1, qcompare compare_obj = U_NULLPTR) { callForAllEntry(function, function_no_lock, compare_obj); }
#ifdef DEBUG
const char* dump(bool _reset) const;
#endif
protected:
UString recval;
UDataStorage* pDataStorage;
bool brecfound, bdirect;
static bool bsetEntry;
static char* data_buffer;
static uint32_t data_len;
static iPFpvpv ds_function_to_call;
static URDBObjectHandler<UDataStorage*>* pthis;
bool _insertDataStorage(const char* s, uint32_t n, int op);
void setEntry( UStringRep* key, UStringRep* data);
static int callEntryCheck(UStringRep* key, UStringRep* data);
private:
U_DISALLOW_COPY_AND_ASSIGN(URDBObjectHandler<UDataStorage*>)
friend class UServer_Base;
};
template <class T> class U_EXPORT URDBObjectHandler<T*> : public URDBObjectHandler<UDataStorage*> {
public:
URDBObjectHandler(const UString& pathdb, int _ignore_case, const T* ptr) : URDBObjectHandler<UDataStorage*>(pathdb, _ignore_case, ptr)
{
U_TRACE_REGISTER_OBJECT(0, URDBObjectHandler<T*>, "%V,%d,%p", pathdb.rep, _ignore_case, ptr)
}
~URDBObjectHandler()
{
U_TRACE_UNREGISTER_OBJECT(0, URDBObjectHandler<T*>)
}
// SERVICES
#ifdef DEBUG
const char* dump(bool _reset) const { return URDBObjectHandler<UDataStorage*>::dump(_reset); }
#endif
private:
U_DISALLOW_COPY_AND_ASSIGN(URDBObjectHandler<T*>)
};
#endif