546 lines
11 KiB
C
546 lines
11 KiB
C
/*
|
||
* This file implements wrappers for persistent lmdb storage for the
|
||
* shared variable arrays.
|
||
*
|
||
* See the file "license.terms" for information on usage and redistribution
|
||
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
||
* ----------------------------------------------------------------------------
|
||
*/
|
||
|
||
#ifdef HAVE_LMDB
|
||
|
||
#include "threadSvCmd.h"
|
||
#include <lmdb.h>
|
||
|
||
/*
|
||
* Structure keeping the lmdb environment context
|
||
*/
|
||
typedef struct {
|
||
MDB_env * env; // Environment
|
||
MDB_txn * txn; // Last active read transaction
|
||
MDB_cursor * cur; // Cursor used for ps_lmdb_first and ps_lmdb_next
|
||
MDB_dbi dbi; // Open database (default db)
|
||
int err; // Last error (used in ps_lmdb_geterr)
|
||
} * LmdbCtx;
|
||
|
||
/*
|
||
* Transaction and DB open mode
|
||
*/
|
||
enum LmdbOpenMode { LmdbRead, LmdbWrite };
|
||
|
||
// Initialize or renew a transaction.
|
||
static void LmdbTxnGet(LmdbCtx ctx, enum LmdbOpenMode mode);
|
||
|
||
// Commit a transaction.
|
||
static void LmdbTxnCommit(LmdbCtx ctx);
|
||
|
||
// Abort a transaction
|
||
static void LmdbTxnAbort(LmdbCtx ctx);
|
||
|
||
void LmdbTxnGet(LmdbCtx ctx, enum LmdbOpenMode mode)
|
||
{
|
||
// Read transactions are reused, if possible
|
||
if (ctx->txn && mode == LmdbRead)
|
||
{
|
||
ctx->err = mdb_txn_renew(ctx->txn);
|
||
if (ctx->err)
|
||
{
|
||
ctx->txn = NULL;
|
||
}
|
||
}
|
||
else if (ctx->txn && mode == LmdbWrite)
|
||
{
|
||
LmdbTxnAbort(ctx);
|
||
}
|
||
|
||
if (ctx->txn == NULL)
|
||
{
|
||
ctx->err = mdb_txn_begin(ctx->env, NULL, 0, &ctx->txn);
|
||
}
|
||
|
||
if (ctx->err)
|
||
{
|
||
ctx->txn = NULL;
|
||
return;
|
||
}
|
||
|
||
// Given the setup above, and the arguments given, this won't fail.
|
||
mdb_dbi_open(ctx->txn, NULL, 0, &ctx->dbi);
|
||
}
|
||
|
||
void LmdbTxnCommit(LmdbCtx ctx)
|
||
{
|
||
ctx->err = mdb_txn_commit(ctx->txn);
|
||
ctx->txn = NULL;
|
||
}
|
||
|
||
void LmdbTxnAbort(LmdbCtx ctx)
|
||
{
|
||
mdb_txn_abort(ctx->txn);
|
||
ctx->txn = NULL;
|
||
}
|
||
|
||
/*
|
||
* Functions implementing the persistent store interface
|
||
*/
|
||
|
||
static ps_open_proc ps_lmdb_open;
|
||
static ps_close_proc ps_lmdb_close;
|
||
static ps_get_proc ps_lmdb_get;
|
||
static ps_put_proc ps_lmdb_put;
|
||
static ps_first_proc ps_lmdb_first;
|
||
static ps_next_proc ps_lmdb_next;
|
||
static ps_delete_proc ps_lmdb_delete;
|
||
static ps_free_proc ps_lmdb_free;
|
||
static ps_geterr_proc ps_lmdb_geterr;
|
||
|
||
/*
|
||
* This structure collects all the various pointers
|
||
* to the functions implementing the lmdb store.
|
||
*/
|
||
|
||
const PsStore LmdbStore = {
|
||
"lmdb",
|
||
NULL,
|
||
ps_lmdb_open,
|
||
ps_lmdb_get,
|
||
ps_lmdb_put,
|
||
ps_lmdb_first,
|
||
ps_lmdb_next,
|
||
ps_lmdb_delete,
|
||
ps_lmdb_close,
|
||
ps_lmdb_free,
|
||
ps_lmdb_geterr,
|
||
NULL
|
||
};
|
||
|
||
/*
|
||
*-----------------------------------------------------------------------------
|
||
*
|
||
* Sv_RegisterLmdbStore --
|
||
*
|
||
* Register the lmdb store with shared variable implementation.
|
||
*
|
||
* Results:
|
||
* None.
|
||
*
|
||
* Side effects:
|
||
* None.
|
||
*
|
||
*-----------------------------------------------------------------------------
|
||
*/
|
||
void
|
||
Sv_RegisterLmdbStore(void)
|
||
{
|
||
Sv_RegisterPsStore(&LmdbStore);
|
||
}
|
||
|
||
/*
|
||
*-----------------------------------------------------------------------------
|
||
*
|
||
* ps_lmdb_open --
|
||
*
|
||
* Opens the lmdb-based persistent storage.
|
||
*
|
||
* Results:
|
||
* Opaque handle for LmdbCtx.
|
||
*
|
||
* Side effects:
|
||
* The lmdb file might be created if not found.
|
||
*
|
||
*-----------------------------------------------------------------------------
|
||
*/
|
||
static ClientData
|
||
ps_lmdb_open(
|
||
const char *path)
|
||
{
|
||
LmdbCtx ctx;
|
||
|
||
char *ext;
|
||
Tcl_DString toext;
|
||
|
||
ctx = (LmdbCtx)ckalloc(sizeof(*ctx));
|
||
if (ctx == NULL)
|
||
{
|
||
return NULL;
|
||
}
|
||
|
||
ctx->env = NULL;
|
||
ctx->txn = NULL;
|
||
ctx->cur = NULL;
|
||
ctx->dbi = 0;
|
||
|
||
ctx->err = mdb_env_create(&ctx->env);
|
||
if (ctx->err)
|
||
{
|
||
ckfree(ctx);
|
||
return NULL;
|
||
}
|
||
|
||
Tcl_DStringInit(&toext);
|
||
ext = Tcl_UtfToExternalDString(NULL, path, strlen(path), &toext);
|
||
ctx->err = mdb_env_open(ctx->env, ext, MDB_NOSUBDIR|MDB_NOLOCK, 0666);
|
||
Tcl_DStringFree(&toext);
|
||
|
||
if (ctx->err)
|
||
{
|
||
ckfree(ctx);
|
||
return NULL;
|
||
}
|
||
|
||
return ctx;
|
||
}
|
||
|
||
/*
|
||
*-----------------------------------------------------------------------------
|
||
*
|
||
* ps_lmdb_close --
|
||
*
|
||
* Closes the lmdb-based persistent storage.
|
||
*
|
||
* Results:
|
||
* 0 - ok
|
||
*
|
||
* Side effects:
|
||
* None.
|
||
*
|
||
*-----------------------------------------------------------------------------
|
||
*/
|
||
static int
|
||
ps_lmdb_close(
|
||
ClientData handle)
|
||
{
|
||
LmdbCtx ctx = (LmdbCtx)handle;
|
||
if (ctx->cur)
|
||
{
|
||
mdb_cursor_close(ctx->cur);
|
||
}
|
||
if (ctx->txn)
|
||
{
|
||
LmdbTxnAbort(ctx);
|
||
}
|
||
|
||
mdb_env_close(ctx->env);
|
||
ckfree(ctx);
|
||
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
*-----------------------------------------------------------------------------
|
||
*
|
||
* ps_lmdb_get --
|
||
*
|
||
* Retrieves data for the key from the lmdb storage.
|
||
*
|
||
* Results:
|
||
* 1 - no such key
|
||
* 0 - ok
|
||
*
|
||
* Side effects:
|
||
* Data returned must be copied, then psFree must be called.
|
||
*
|
||
*-----------------------------------------------------------------------------
|
||
*/
|
||
static int
|
||
ps_lmdb_get(
|
||
ClientData handle,
|
||
const char *keyptr,
|
||
char **dataptrptr,
|
||
size_t *lenptr)
|
||
{
|
||
LmdbCtx ctx = (LmdbCtx)handle;
|
||
MDB_val key, data;
|
||
|
||
LmdbTxnGet(ctx, LmdbRead);
|
||
if (ctx->err)
|
||
{
|
||
return 1;
|
||
}
|
||
|
||
key.mv_data = (void *)keyptr;
|
||
key.mv_size = strlen(keyptr) + 1;
|
||
|
||
ctx->err = mdb_get(ctx->txn, ctx->dbi, &key, &data);
|
||
if (ctx->err)
|
||
{
|
||
mdb_txn_reset(ctx->txn);
|
||
return 1;
|
||
}
|
||
|
||
*dataptrptr = (char *)data.mv_data;
|
||
*lenptr = data.mv_size;
|
||
|
||
/*
|
||
* Transaction is left open at this point, so that the caller can get ahold
|
||
* of the data and make a copy of it. Afterwards, it will call ps_lmdb_free
|
||
* to free the data, and we'll catch the chance to reset the transaction
|
||
* there.
|
||
*/
|
||
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
*-----------------------------------------------------------------------------
|
||
*
|
||
* ps_lmdb_first --
|
||
*
|
||
* Starts the iterator over the lmdb file and returns the first record.
|
||
*
|
||
* Results:
|
||
* 1 - no more records in the iterator
|
||
* 0 - ok
|
||
*
|
||
* Side effects:
|
||
* Data returned must be copied, then psFree must be called.
|
||
*
|
||
*-----------------------------------------------------------------------------
|
||
*/
|
||
static int
|
||
ps_lmdb_first(
|
||
ClientData handle,
|
||
char **keyptrptr,
|
||
char **dataptrptr,
|
||
size_t *lenptr)
|
||
{
|
||
LmdbCtx ctx = (LmdbCtx)handle;
|
||
MDB_val key, data;
|
||
|
||
LmdbTxnGet(ctx, LmdbRead);
|
||
if (ctx->err)
|
||
{
|
||
return 1;
|
||
}
|
||
|
||
ctx->err = mdb_cursor_open(ctx->txn, ctx->dbi, &ctx->cur);
|
||
if (ctx->err)
|
||
{
|
||
return 1;
|
||
}
|
||
|
||
ctx->err = mdb_cursor_get(ctx->cur, &key, &data, MDB_FIRST);
|
||
if (ctx->err)
|
||
{
|
||
mdb_txn_reset(ctx->txn);
|
||
mdb_cursor_close(ctx->cur);
|
||
ctx->cur = NULL;
|
||
return 1;
|
||
}
|
||
|
||
*dataptrptr = (char *)data.mv_data;
|
||
*lenptr = data.mv_size;
|
||
*keyptrptr = (char *)key.mv_data;
|
||
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
*-----------------------------------------------------------------------------
|
||
*
|
||
* ps_lmdb_next --
|
||
*
|
||
* Uses the iterator over the lmdb file and returns the next record.
|
||
*
|
||
* Results:
|
||
* 1 - no more records in the iterator
|
||
* 0 - ok
|
||
*
|
||
* Side effects:
|
||
* Data returned must be copied, then psFree must be called.
|
||
*
|
||
*-----------------------------------------------------------------------------
|
||
*/
|
||
static int ps_lmdb_next(
|
||
ClientData handle,
|
||
char **keyptrptr,
|
||
char **dataptrptr,
|
||
size_t *lenptr)
|
||
{
|
||
LmdbCtx ctx = (LmdbCtx)handle;
|
||
MDB_val key, data;
|
||
|
||
ctx->err = mdb_cursor_get(ctx->cur, &key, &data, MDB_NEXT);
|
||
if (ctx->err)
|
||
{
|
||
mdb_txn_reset(ctx->txn);
|
||
mdb_cursor_close(ctx->cur);
|
||
ctx->cur = NULL;
|
||
return 1;
|
||
}
|
||
|
||
*dataptrptr = (char *)data.mv_data;
|
||
*lenptr = data.mv_size;
|
||
*keyptrptr = (char *)key.mv_data;
|
||
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
*-----------------------------------------------------------------------------
|
||
*
|
||
* ps_lmdb_put --
|
||
*
|
||
* Stores used data bound to a key in lmdb storage.
|
||
*
|
||
* Results:
|
||
* 0 - ok
|
||
* -1 - error; use ps_lmdb_geterr to retrieve the error message
|
||
*
|
||
* Side effects:
|
||
* If the key is already associated with some user data, this will
|
||
* be replaced by the new data chunk.
|
||
*
|
||
*-----------------------------------------------------------------------------
|
||
*/
|
||
static int
|
||
ps_lmdb_put(
|
||
ClientData handle,
|
||
const char *keyptr,
|
||
char *dataptr,
|
||
size_t len)
|
||
{
|
||
LmdbCtx ctx = (LmdbCtx)handle;
|
||
MDB_val key, data;
|
||
|
||
LmdbTxnGet(ctx, LmdbWrite);
|
||
if (ctx->err)
|
||
{
|
||
return -1;
|
||
}
|
||
|
||
key.mv_data = (void*)keyptr;
|
||
key.mv_size = strlen(keyptr) + 1;
|
||
|
||
data.mv_data = dataptr;
|
||
data.mv_size = len;
|
||
|
||
ctx->err = mdb_put(ctx->txn, ctx->dbi, &key, &data, 0);
|
||
if (ctx->err)
|
||
{
|
||
LmdbTxnAbort(ctx);
|
||
}
|
||
else
|
||
{
|
||
LmdbTxnCommit(ctx);
|
||
}
|
||
|
||
return ctx->err ? -1 : 0;
|
||
}
|
||
|
||
/*
|
||
*-----------------------------------------------------------------------------
|
||
*
|
||
* ps_lmdb_delete --
|
||
*
|
||
* Deletes the key and associated data from the lmdb storage.
|
||
*
|
||
* Results:
|
||
* 0 - ok
|
||
* -1 - error; use ps_lmdb_geterr to retrieve the error message
|
||
*
|
||
* Side effects:
|
||
* If the key is already associated with some user data, this will
|
||
* be replaced by the new data chunk.
|
||
*
|
||
*-----------------------------------------------------------------------------
|
||
*/
|
||
static int
|
||
ps_lmdb_delete(
|
||
ClientData handle,
|
||
const char *keyptr)
|
||
{
|
||
LmdbCtx ctx = (LmdbCtx)handle;
|
||
MDB_val key;
|
||
|
||
LmdbTxnGet(ctx, LmdbWrite);
|
||
if (ctx->err)
|
||
{
|
||
return -1;
|
||
}
|
||
|
||
key.mv_data = (void*)keyptr;
|
||
key.mv_size = strlen(keyptr) + 1;
|
||
|
||
ctx->err = mdb_del(ctx->txn, ctx->dbi, &key, NULL);
|
||
if (ctx->err)
|
||
{
|
||
LmdbTxnAbort(ctx);
|
||
}
|
||
else
|
||
{
|
||
LmdbTxnCommit(ctx);
|
||
}
|
||
|
||
ctx->txn = NULL;
|
||
return ctx->err ? -1 : 0;
|
||
}
|
||
|
||
/*
|
||
*-----------------------------------------------------------------------------
|
||
*
|
||
* ps_lmdb_free --
|
||
*
|
||
* This function is called to free data returned by the persistent store
|
||
* after calls to psFirst, psNext, or psGet. Lmdb doesn't need to free any
|
||
* data, as the data returned is owned by lmdb. On the other hand, this
|
||
* method is required to reset the read transaction. This is done only
|
||
* when iteration is over (ctx->cur == NULL).
|
||
*
|
||
* Results:
|
||
* None.
|
||
*
|
||
* Side effects:
|
||
* Memory gets reclaimed.
|
||
*
|
||
*-----------------------------------------------------------------------------
|
||
*/
|
||
static void
|
||
ps_lmdb_free(
|
||
ClientData handle,
|
||
void *data)
|
||
{
|
||
LmdbCtx ctx = (LmdbCtx)handle;
|
||
(void)data;
|
||
|
||
if (ctx->cur == NULL)
|
||
{
|
||
mdb_txn_reset(ctx->txn);
|
||
}
|
||
}
|
||
|
||
/*
|
||
*-----------------------------------------------------------------------------
|
||
*
|
||
* ps_lmdb_geterr --
|
||
*
|
||
* Retrieves the textual representation of the error caused
|
||
* by the last lmdb command.
|
||
*
|
||
* Results:
|
||
* Pointer to the string message.
|
||
*
|
||
* Side effects:
|
||
* None.
|
||
*
|
||
*-----------------------------------------------------------------------------
|
||
*/
|
||
static const char*
|
||
ps_lmdb_geterr(
|
||
ClientData handle)
|
||
{
|
||
LmdbCtx ctx = (LmdbCtx)handle;
|
||
return mdb_strerror(ctx->err);
|
||
}
|
||
|
||
#endif /* HAVE_LMDB */
|
||
|
||
/* EOF $RCSfile*/
|
||
|
||
/* Emacs Setup Variables */
|
||
/* Local Variables: */
|
||
/* mode: C */
|
||
/* indent-tabs-mode: nil */
|
||
/* c-basic-offset: 4 */
|
||
/* End: */
|