WIP-- Not Yet Compiling or Functional -- WIP
* No longer expose WT_SESSION into Erlang at all as WT's model is to maintain one WT_SESSION per-thread and we don't know anything about threads in Erlang. * async_nif worker threads don't pull both mutexes on every loop when processing requests, only one * async_nif provides a worker_id (int, 0 - MAX_WORKERS) within the work block scope which we use to find our per-worker WT_SESSIONs * async_nif maintained a number of globals which I'm moving into the NIF's priv_data so that on upgrade/reload we have a fighting chance to "Do the Right Thing(TM)". * NIF Upgrades/Reloads started to plumb this in. * Use a khash to manage the cache of URI->WT_CURSORs per WT_SESSION. * Added start/stop positions into truncate call to allow for truncating sub-ranges data. * Lots of other details I'm sure I've forgotten and more left undone. Search for "TODO:" or try to compile to see what's left, and then there is a need for a lot more tests given all this new complexity.
This commit is contained in:
parent
163a5073cb
commit
19268b7c77
6 changed files with 1843 additions and 800 deletions
|
@ -1,5 +1,4 @@
|
|||
/*
|
||||
*
|
||||
* async_nif: An async thread-pool layer for Erlang's NIF API
|
||||
*
|
||||
* Copyright (c) 2012 Basho Technologies, Inc. All Rights Reserved.
|
||||
|
@ -18,7 +17,6 @@
|
|||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __ASYNC_NIF_H__
|
||||
|
@ -28,49 +26,42 @@
|
|||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Redefine this in your NIF implementation before including this file to
|
||||
change the thread pool size. The maximum number of threads might be
|
||||
bounded on your OS. For instance, to allow 1,000,000 threads on a Linux
|
||||
system you must do the following before launching the process.
|
||||
echo 1000000 > /proc/sys/kernel/threads-max
|
||||
and for all UNIX systems there will be ulimit maximums. */
|
||||
#ifndef ASYNC_NIF_MAX_WORKERS
|
||||
#define ASYNC_NIF_MAX_WORKERS 16
|
||||
#endif
|
||||
|
||||
#include "queue.h"
|
||||
|
||||
#define ASYNC_NIF_MAX_WORKERS 32
|
||||
|
||||
struct async_nif_req_entry {
|
||||
ERL_NIF_TERM ref, *argv;
|
||||
ErlNifEnv *env;
|
||||
ErlNifPid pid;
|
||||
void *args;
|
||||
void *priv_data;
|
||||
void (*fn_work)(ErlNifEnv*, ERL_NIF_TERM, void *, ErlNifPid*, void *);
|
||||
void (*fn_work)(ErlNifEnv*, ERL_NIF_TERM, ErlNifPid*, unsigned int, void *);
|
||||
void (*fn_post)(void *);
|
||||
STAILQ_ENTRY(async_nif_req_entry) entries;
|
||||
};
|
||||
STAILQ_HEAD(reqs, async_nif_req_entry) async_nif_reqs = STAILQ_HEAD_INITIALIZER(async_nif_reqs);
|
||||
|
||||
struct async_nif_worker_entry {
|
||||
ErlNifTid tid;
|
||||
LIST_ENTRY(async_nif_worker_entry) entries;
|
||||
};
|
||||
LIST_HEAD(idle_workers, async_nif_worker_entry) async_nif_idle_workers = LIST_HEAD_INITIALIZER(async_nif_worker);
|
||||
|
||||
static volatile unsigned int async_nif_req_count = 0;
|
||||
static volatile unsigned int async_nif_shutdown = 0;
|
||||
static ErlNifMutex *async_nif_req_mutex = NULL;
|
||||
static ErlNifMutex *async_nif_worker_mutex = NULL;
|
||||
static ErlNifCond *async_nif_cnd = NULL;
|
||||
static struct async_nif_worker_entry async_nif_worker_entries[ASYNC_NIF_MAX_WORKERS];
|
||||
|
||||
struct async_nif_state {
|
||||
volatile unsigned int req_count;
|
||||
volatile unsigned int shutdown;
|
||||
ErlNifMutex *req_mutex;
|
||||
ErlNifMutex *worker_mutex;
|
||||
ErlNifCond *cnd;
|
||||
STAILQ_HEAD(reqs, async_nif_req_entry) reqs;
|
||||
LIST_HEAD(workers, async_nif_worker_entry) workers;
|
||||
unsigned int num_workers;
|
||||
struct async_nif_worker_entry worker_entries[ASYNC_NIF_MAX_WORKERS];
|
||||
};
|
||||
|
||||
#define ASYNC_NIF_DECL(decl, frame, pre_block, work_block, post_block) \
|
||||
struct decl ## _args frame; \
|
||||
static void fn_work_ ## decl (ErlNifEnv *env, ERL_NIF_TERM ref, void *priv_data, ErlNifPid *pid, struct decl ## _args *args) work_block \
|
||||
static void fn_work_ ## decl (ErlNifEnv *env, ERL_NIF_TERM ref, ErlNifPid *pid, unsigned int worker_id, struct decl ## _args *args) work_block \
|
||||
static void fn_post_ ## decl (struct decl ## _args *args) { \
|
||||
do post_block while(0); \
|
||||
do post_block while(0); \
|
||||
} \
|
||||
static ERL_NIF_TERM decl(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv_in[]) { \
|
||||
struct decl ## _args on_stack_args; \
|
||||
|
@ -81,7 +72,8 @@ static struct async_nif_worker_entry async_nif_worker_entries[ASYNC_NIF_MAX_WORK
|
|||
/* argv[0] is a ref used for selective recv */ \
|
||||
const ERL_NIF_TERM *argv = argv_in + 1; \
|
||||
argc--; \
|
||||
if (async_nif_shutdown) \
|
||||
async_nif = (struct async_nif_state*)enif_priv_data(env); \
|
||||
if (async_nif->shutdown) \
|
||||
return enif_make_tuple2(env, enif_make_atom(env, "error"), \
|
||||
enif_make_atom(env, "shutdown")); \
|
||||
if (!(new_env = enif_alloc_env())) { \
|
||||
|
@ -108,17 +100,14 @@ static struct async_nif_worker_entry async_nif_worker_entries[ASYNC_NIF_MAX_WORK
|
|||
req->ref = enif_make_copy(new_env, argv_in[0]); \
|
||||
enif_self(env, &req->pid); \
|
||||
req->args = (void*)copy_of_args; \
|
||||
req->priv_data = enif_priv_data(env); \
|
||||
req->fn_work = (void (*)(ErlNifEnv *, ERL_NIF_TERM, void*, ErlNifPid*, void *))fn_work_ ## decl ; \
|
||||
req->fn_work = (void (*)(ErlNifEnv *, ERL_NIF_TERM, ErlNifPid*, unsigned int, void *))fn_work_ ## decl ; \
|
||||
req->fn_post = (void (*)(void *))fn_post_ ## decl; \
|
||||
async_nif_enqueue_req(req); \
|
||||
return enif_make_tuple2(env, enif_make_atom(env, "ok"), \
|
||||
enif_make_tuple2(env, enif_make_atom(env, "enqueued"), \
|
||||
enif_make_int(env, async_nif_req_count))); \
|
||||
return async_nif_enqueue_req(async_nif, req); \
|
||||
}
|
||||
|
||||
#define ASYNC_NIF_LOAD() if (async_nif_init() != 0) return -1;
|
||||
#define ASYNC_NIF_LOAD() async_nif_load();
|
||||
#define ASYNC_NIF_UNLOAD() async_nif_unload();
|
||||
//define ASYNC_NIF_RELOAD()
|
||||
#define ASYNC_NIF_UPGRADE() async_nif_unload();
|
||||
|
||||
#define ASYNC_NIF_RETURN_BADARG() return enif_make_badarg(env);
|
||||
|
@ -130,14 +119,25 @@ static struct async_nif_worker_entry async_nif_worker_entries[ASYNC_NIF_MAX_WORK
|
|||
#define ASYNC_NIF_REPLY(msg) PULSE_SEND(NULL, pid, env, enif_make_tuple2(env, ref, msg))
|
||||
#endif
|
||||
|
||||
static void async_nif_enqueue_req(struct async_nif_req_entry *r)
|
||||
static ERL_NIF_TERM
|
||||
async_nif_enqueue_req(struct async_nif_state* async_nif, struct async_nif_req_entry *req)
|
||||
{
|
||||
/* Add the request to the work queue. */
|
||||
enif_mutex_lock(async_nif_req_mutex);
|
||||
STAILQ_INSERT_TAIL(&async_nif_reqs, r, entries);
|
||||
async_nif_req_count++;
|
||||
enif_mutex_unlock(async_nif_req_mutex);
|
||||
enif_cond_broadcast(async_nif_cnd);
|
||||
/* If we're shutting down return an error term and ignore the request. */
|
||||
if (async_nif->shutdown) {
|
||||
return enif_make_tuple2(req->env, enif_make_atom(req->env, "error"),
|
||||
enif_make_atom(req->env, "shutdown")));
|
||||
}
|
||||
|
||||
/* Otherwise, add the request to the work queue. */
|
||||
enif_mutex_lock(async_nif->req_mutex);
|
||||
STAILQ_INSERT_TAIL(&async_nif->reqs, req, entries);
|
||||
async_nif->req_count++;
|
||||
enif_mutex_unlock(async_nif->req_mutex);
|
||||
enif_cond_broadcast(async_nif->cnd);
|
||||
|
||||
return enif_make_tuple2(env, enif_make_atom(env, "ok"),
|
||||
enif_make_tuple2(env, enif_make_atom(env, "enqueued"),
|
||||
enif_make_int(env, async_nif->req_count))); \
|
||||
}
|
||||
|
||||
static void *async_nif_worker_fn(void *arg)
|
||||
|
@ -151,128 +151,164 @@ static void *async_nif_worker_fn(void *arg)
|
|||
*/
|
||||
for(;;) {
|
||||
/* Examine the request queue, are there things to be done? */
|
||||
enif_mutex_lock(async_nif_req_mutex);
|
||||
enif_mutex_lock(async_nif_worker_mutex);
|
||||
LIST_INSERT_HEAD(&async_nif_idle_workers, worker, entries);
|
||||
enif_mutex_unlock(async_nif_worker_mutex);
|
||||
enif_mutex_lock(async_nif->req_mutex);
|
||||
enif_mutex_lock(async_nif->worker_mutex);
|
||||
LIST_INSERT_HEAD(&async_nif->workers, worker, entries);
|
||||
enif_mutex_unlock(async_nif->worker_mutex);
|
||||
check_again_for_work:
|
||||
if (async_nif_shutdown) { enif_mutex_unlock(async_nif_req_mutex); break; }
|
||||
if ((req = STAILQ_FIRST(&async_nif_reqs)) == NULL) {
|
||||
if (async_nif->shutdown) { enif_mutex_unlock(async_nif->req_mutex); break; }
|
||||
if ((req = STAILQ_FIRST(&async_nif->reqs)) == NULL) {
|
||||
/* Queue is empty, join the list of idle workers and wait for work */
|
||||
enif_cond_wait(async_nif_cnd, async_nif_req_mutex);
|
||||
enif_cond_wait(async_nif->cnd, async_nif->req_mutex);
|
||||
goto check_again_for_work;
|
||||
} else {
|
||||
/* `req` is our work request and we hold the lock. */
|
||||
enif_cond_broadcast(async_nif_cnd);
|
||||
/* `req` is our work request and we hold the req_mutex lock. */
|
||||
// TODO: do we need this? enif_cond_broadcast(async_nif->cnd);
|
||||
|
||||
/* Take the request off the queue. */
|
||||
STAILQ_REMOVE(&async_nif_reqs, req, async_nif_req_entry, entries); async_nif_req_count--;
|
||||
|
||||
/* Now we need to remove this thread from the list of idle threads. */
|
||||
enif_mutex_lock(async_nif_worker_mutex);
|
||||
/* Remove this thread from the list of idle threads. */
|
||||
enif_mutex_lock(async_nif->worker_mutex);
|
||||
LIST_REMOVE(worker, entries);
|
||||
enif_mutex_unlock(async_nif->worker_mutex);
|
||||
|
||||
/* Release the locks in reverse order that we acquired them,
|
||||
so as not to self-deadlock. */
|
||||
enif_mutex_unlock(async_nif_worker_mutex);
|
||||
enif_mutex_unlock(async_nif_req_mutex);
|
||||
do {
|
||||
/* Take the request off the queue. */
|
||||
STAILQ_REMOVE(&async_nif->reqs, req, async_nif->req_entry, entries);
|
||||
async_nif->req_count--;
|
||||
enif_mutex_unlock(async_nif->req_mutex);
|
||||
|
||||
/* Finally, let's do the work! :) */
|
||||
req->fn_work(req->env, req->ref, req->priv_data, &req->pid, req->args);
|
||||
req->fn_post(req->args);
|
||||
enif_free(req->args);
|
||||
enif_free_env(req->env);
|
||||
enif_free(req);
|
||||
/* Finally, do the work. */
|
||||
unsigned int worker_id = (unsigned int)(worker - worker_entries);
|
||||
req->fn_work(req->env, req->ref, &req->pid, worker_id, req->args);
|
||||
req->fn_post(req->args);
|
||||
enif_free(req->args);
|
||||
enif_free_env(req->env);
|
||||
enif_free(req);
|
||||
|
||||
/* Finally, check the request queue for more work before switching
|
||||
into idle mode. */
|
||||
enif_mutex_lock(async_nif->req_mutex);
|
||||
if ((req = STAILQ_FIRST(&async_nif->reqs)) == NULL) {
|
||||
enif_mutex_unlock(async_nif->req_mutex);
|
||||
}
|
||||
|
||||
/* Take a second to see if we need to adjust the number of active
|
||||
worker threads up or down. */
|
||||
// TODO: if queue_depth > last_depth && num_workers < MAX, start one up
|
||||
|
||||
} while(req);
|
||||
}
|
||||
}
|
||||
enif_thread_exit(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void async_nif_unload(void)
|
||||
static void async_nif_unload(ErlNifEnv *env)
|
||||
{
|
||||
unsigned int i;
|
||||
struct_nif_state *async_nif = (struct async_nif_state*)enif_priv_data(env);
|
||||
|
||||
/* Signal the worker threads, stop what you're doing and exit. */
|
||||
enif_mutex_lock(async_nif_req_mutex);
|
||||
async_nif_shutdown = 1;
|
||||
enif_cond_broadcast(async_nif_cnd);
|
||||
enif_mutex_unlock(async_nif_req_mutex);
|
||||
enif_mutex_lock(async_nif->req_mutex);
|
||||
async_nif->shutdown = 1;
|
||||
enif_cond_broadcast(async_nif->cnd);
|
||||
enif_mutex_unlock(async_nif->req_mutex);
|
||||
|
||||
/* Join for the now exiting worker threads. */
|
||||
for (i = 0; i < ASYNC_NIF_MAX_WORKERS; ++i) {
|
||||
void *exit_value = 0; /* Ignore this. */
|
||||
enif_thread_join(async_nif_worker_entries[i].tid, &exit_value);
|
||||
enif_thread_join(async_nif->worker_entries[i].tid, &exit_value);
|
||||
}
|
||||
|
||||
/* We won't get here until all threads have exited.
|
||||
Patch things up, and carry on. */
|
||||
enif_mutex_lock(async_nif_req_mutex);
|
||||
enif_mutex_lock(async_nif->req_mutex);
|
||||
|
||||
/* Worker threads are stopped, now toss anything left in the queue. */
|
||||
struct async_nif_req_entry *req = NULL;
|
||||
STAILQ_FOREACH(req, &async_nif_reqs, entries) {
|
||||
STAILQ_REMOVE(&async_nif_reqs, STAILQ_LAST(&async_nif_reqs, async_nif_req_entry, entries),
|
||||
STAILQ_FOREACH(req, &async_nif->reqs, entries) {
|
||||
STAILQ_REMOVE(&async_nif->reqs, STAILQ_LAST(&async_nif->reqs, async_nif_req_entry, entries),
|
||||
async_nif_req_entry, entries);
|
||||
#ifdef PULSE
|
||||
PULSE_SEND(NULL, &req->pid, req->env,
|
||||
enif_make_tuple2(req->env, enif_make_atom(req->env, "error"),
|
||||
enif_make_atom(req->env, "shutdown")));
|
||||
#else
|
||||
enif_send(NULL, &req->pid, req->env,
|
||||
enif_make_tuple2(req->env, enif_make_atom(req->env, "error"),
|
||||
enif_make_atom(req->env, "shutdown")));
|
||||
#endif
|
||||
req->fn_post(req->args);
|
||||
enif_free(req->args);
|
||||
enif_free(req);
|
||||
async_nif_req_count--;
|
||||
async_nif->req_count--;
|
||||
}
|
||||
enif_mutex_unlock(async_nif_req_mutex);
|
||||
enif_mutex_unlock(async_nif->req_mutex);
|
||||
|
||||
memset(async_nif_worker_entries, sizeof(struct async_nif_worker_entry) * ASYNC_NIF_MAX_WORKERS, 0);
|
||||
enif_cond_destroy(async_nif_cnd); async_nif_cnd = NULL;
|
||||
enif_mutex_destroy(async_nif_req_mutex); async_nif_req_mutex = NULL;
|
||||
enif_mutex_destroy(async_nif_worker_mutex); async_nif_worker_mutex = NULL;
|
||||
bzero(async_nif->worker_entries, sizeof(struct async_nif_worker_entry) * ASYNC_NIF_MAX_WORKERS);
|
||||
enif_cond_destroy(async_nif->cnd); async_nif->cnd = NULL;
|
||||
enif_mutex_destroy(async_nif->req_mutex); async_nif->req_mutex = NULL;
|
||||
enif_mutex_destroy(async_nif->worker_mutex); async_nif->worker_mutex = NULL;
|
||||
bzero(async_nif, sizeof(struct async_nif_state));
|
||||
free(async_nif);
|
||||
}
|
||||
|
||||
static int async_nif_init(void)
|
||||
static void *
|
||||
async_nif_load(void)
|
||||
{
|
||||
int i;
|
||||
int i, num_schedulers;
|
||||
ErlDrvSysInfo info;
|
||||
struct async_nif_state *async_nif;
|
||||
|
||||
/* Don't init more than once. */
|
||||
if (async_nif_req_mutex) return 0;
|
||||
|
||||
async_nif_req_mutex = enif_mutex_create(NULL);
|
||||
async_nif_worker_mutex = enif_mutex_create(NULL);
|
||||
async_nif_cnd = enif_cond_create(NULL);
|
||||
/* Find out how many schedulers there are. */
|
||||
erl_drv_sys_info(&info, sizeof(ErlDrvSysInfo));
|
||||
num_schedulers = info->scheduler_threads;
|
||||
|
||||
/* Init our portion of priv_data's module-specific state. */
|
||||
async_nif = malloc(sizeof(struct async_nif_state));
|
||||
if (!async_nif)
|
||||
return NULL;
|
||||
STAILQ_INIT(async_nif->reqs);
|
||||
LIST_INIT(async_nif->workers);
|
||||
async_nif->shutdown = 0;
|
||||
|
||||
async_nif->req_mutex = enif_mutex_create(NULL);
|
||||
async_nif->worker_mutex = enif_mutex_create(NULL);
|
||||
async_nif->cnd = enif_cond_create(NULL);
|
||||
|
||||
/* Setup the requests management. */
|
||||
async_nif_req_count = 0;
|
||||
async_nif->req_count = 0;
|
||||
|
||||
/* Setup the thread pool management. */
|
||||
enif_mutex_lock(async_nif_worker_mutex);
|
||||
memset(async_nif_worker_entries, sizeof(struct async_nif_worker_entry) * ASYNC_NIF_MAX_WORKERS, 0);
|
||||
enif_mutex_lock(async_nif->worker_mutex);
|
||||
bzero(async_nif->worker_entries, sizeof(struct async_nif_worker_entry) * ASYNC_NIF_MAX_WORKERS);
|
||||
|
||||
for (i = 0; i < ASYNC_NIF_MAX_WORKERS; i++) {
|
||||
if (enif_thread_create(NULL, &async_nif_worker_entries[i].tid,
|
||||
&async_nif_worker_fn, (void*)&async_nif_worker_entries[i], NULL) != 0) {
|
||||
async_nif_shutdown = 1;
|
||||
enif_cond_broadcast(async_nif_cnd);
|
||||
enif_mutex_unlock(async_nif_worker_mutex);
|
||||
/* Start the minimum of max workers allowed or number of scheduler threads running. */
|
||||
unsigned int num_worker_threads = ASYNC_NIF_MAX_WORKERS;
|
||||
if (num_schedulers < ASYNC_NIF_MAX_WORKERS)
|
||||
num_worker_threads = num_schedulers;
|
||||
if (num_worker_threads < 1)
|
||||
num_worker_threads = 1;
|
||||
|
||||
for (i = 0; i < num_worker_threads; i++) {
|
||||
if (enif_thread_create(NULL, &async_nif->worker_entries[i].tid,
|
||||
&async_nif_worker_fn, (void*)&async_nif->worker_entries[i], NULL) != 0) {
|
||||
async_nif->shutdown = 1;
|
||||
enif_cond_broadcast(async_nif->cnd);
|
||||
enif_mutex_unlock(async_nif->worker_mutex);
|
||||
while(i-- > 0) {
|
||||
void *exit_value = 0; /* Ignore this. */
|
||||
enif_thread_join(async_nif_worker_entries[i].tid, &exit_value);
|
||||
enif_thread_join(async_nif->worker_entries[i].tid, &exit_value);
|
||||
}
|
||||
memset(async_nif_worker_entries, sizeof(struct async_nif_worker_entry) * ASYNC_NIF_MAX_WORKERS, 0);
|
||||
enif_cond_destroy(async_nif_cnd); async_nif_cnd = NULL;
|
||||
enif_mutex_destroy(async_nif_req_mutex); async_nif_req_mutex = NULL;
|
||||
enif_mutex_destroy(async_nif_worker_mutex); async_nif_worker_mutex = NULL;
|
||||
return -1;
|
||||
bzero(async_nif->worker_entries, sizeof(struct async_nif_worker_entry) * ASYNC_NIF_MAX_WORKERS);
|
||||
enif_cond_destroy(async_nif->cnd);
|
||||
async_nif->cnd = NULL;
|
||||
enif_mutex_destroy(async_nif->req_mutex);
|
||||
async_nif->req_mutex = NULL;
|
||||
enif_mutex_destroy(async_nif->worker_mutex);
|
||||
async_nif->worker_mutex = NULL;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
enif_mutex_unlock(async_nif_worker_mutex);
|
||||
return 0;
|
||||
async_nif->num_workers = num_worker_threads;
|
||||
enif_mutex_unlock(async_nif->worker_mutex);
|
||||
return async_nif;
|
||||
}
|
||||
|
||||
#if defined(__cplusplus)
|
||||
|
|
610
c_src/khash.h
Normal file
610
c_src/khash.h
Normal file
|
@ -0,0 +1,610 @@
|
|||
/* The MIT License
|
||||
|
||||
Copyright (c) 2008, 2009, 2011 by Attractive Chaos <attractor@live.co.uk>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
An example:
|
||||
|
||||
#include "khash.h"
|
||||
KHASH_MAP_INIT_INT(32, char)
|
||||
int main() {
|
||||
int ret, is_missing;
|
||||
khiter_t k;
|
||||
khash_t(32) *h = kh_init(32);
|
||||
k = kh_put(32, h, 5, &ret);
|
||||
kh_value(h, k) = 10;
|
||||
k = kh_get(32, h, 10);
|
||||
is_missing = (k == kh_end(h));
|
||||
k = kh_get(32, h, 5);
|
||||
kh_del(32, h, k);
|
||||
for (k = kh_begin(h); k != kh_end(h); ++k)
|
||||
if (kh_exist(h, k)) kh_value(h, k) = 1;
|
||||
kh_destroy(32, h);
|
||||
return 0;
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
2011-12-29 (0.2.7):
|
||||
|
||||
* Minor code clean up; no actual effect.
|
||||
|
||||
2011-09-16 (0.2.6):
|
||||
|
||||
* The capacity is a power of 2. This seems to dramatically improve the
|
||||
speed for simple keys. Thank Zilong Tan for the suggestion. Reference:
|
||||
|
||||
- http://code.google.com/p/ulib/
|
||||
- http://nothings.org/computer/judy/
|
||||
|
||||
* Allow to optionally use linear probing which usually has better
|
||||
performance for random input. Double hashing is still the default as it
|
||||
is more robust to certain non-random input.
|
||||
|
||||
* Added Wang's integer hash function (not used by default). This hash
|
||||
function is more robust to certain non-random input.
|
||||
|
||||
2011-02-14 (0.2.5):
|
||||
|
||||
* Allow to declare global functions.
|
||||
|
||||
2009-09-26 (0.2.4):
|
||||
|
||||
* Improve portability
|
||||
|
||||
2008-09-19 (0.2.3):
|
||||
|
||||
* Corrected the example
|
||||
* Improved interfaces
|
||||
|
||||
2008-09-11 (0.2.2):
|
||||
|
||||
* Improved speed a little in kh_put()
|
||||
|
||||
2008-09-10 (0.2.1):
|
||||
|
||||
* Added kh_clear()
|
||||
* Fixed a compiling error
|
||||
|
||||
2008-09-02 (0.2.0):
|
||||
|
||||
* Changed to token concatenation which increases flexibility.
|
||||
|
||||
2008-08-31 (0.1.2):
|
||||
|
||||
* Fixed a bug in kh_get(), which has not been tested previously.
|
||||
|
||||
2008-08-31 (0.1.1):
|
||||
|
||||
* Added destructor
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __AC_KHASH_H
|
||||
#define __AC_KHASH_H
|
||||
|
||||
/*!
|
||||
@header
|
||||
|
||||
Generic hash table library.
|
||||
*/
|
||||
|
||||
#define AC_VERSION_KHASH_H "0.2.6"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
|
||||
/* compiler specific configuration */
|
||||
|
||||
#if UINT_MAX == 0xffffffffu
|
||||
typedef unsigned int khint32_t;
|
||||
#elif ULONG_MAX == 0xffffffffu
|
||||
typedef unsigned long khint32_t;
|
||||
#endif
|
||||
|
||||
#if ULONG_MAX == ULLONG_MAX
|
||||
typedef unsigned long khint64_t;
|
||||
#else
|
||||
typedef unsigned long long khint64_t;
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define kh_inline __inline
|
||||
#else
|
||||
#define kh_inline inline
|
||||
#endif
|
||||
|
||||
typedef khint32_t khint_t;
|
||||
typedef khint_t khiter_t;
|
||||
|
||||
#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2)
|
||||
#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1)
|
||||
#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3)
|
||||
#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1)))
|
||||
#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1)))
|
||||
#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1)))
|
||||
#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1))
|
||||
|
||||
#ifdef KHASH_LINEAR
|
||||
#define __ac_inc(k, m) 1
|
||||
#else
|
||||
#define __ac_inc(k, m) (((k)>>3 ^ (k)<<3) | 1) & (m)
|
||||
#endif
|
||||
|
||||
#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4)
|
||||
|
||||
#ifndef kroundup32
|
||||
#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x))
|
||||
#endif
|
||||
|
||||
#ifndef kcalloc
|
||||
#define kcalloc(N,Z) calloc(N,Z)
|
||||
#endif
|
||||
#ifndef kmalloc
|
||||
#define kmalloc(Z) malloc(Z)
|
||||
#endif
|
||||
#ifndef krealloc
|
||||
#define krealloc(P,Z) realloc(P,Z)
|
||||
#endif
|
||||
#ifndef kfree
|
||||
#define kfree(P) free(P)
|
||||
#endif
|
||||
|
||||
static const double __ac_HASH_UPPER = 0.77;
|
||||
|
||||
#define __KHASH_TYPE(name, khkey_t, khval_t) \
|
||||
typedef struct { \
|
||||
khint_t n_buckets, size, n_occupied, upper_bound; \
|
||||
khint32_t *flags; \
|
||||
khkey_t *keys; \
|
||||
khval_t *vals; \
|
||||
} kh_##name##_t;
|
||||
|
||||
#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \
|
||||
extern kh_##name##_t *kh_init_##name(void); \
|
||||
extern void kh_destroy_##name(kh_##name##_t *h); \
|
||||
extern void kh_clear_##name(kh_##name##_t *h); \
|
||||
extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \
|
||||
extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \
|
||||
extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \
|
||||
extern void kh_del_##name(kh_##name##_t *h, khint_t x);
|
||||
|
||||
#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
|
||||
SCOPE kh_##name##_t *kh_init_##name(void) { \
|
||||
return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \
|
||||
} \
|
||||
SCOPE void kh_destroy_##name(kh_##name##_t *h) \
|
||||
{ \
|
||||
if (h) { \
|
||||
kfree((void *)h->keys); kfree(h->flags); \
|
||||
kfree((void *)h->vals); \
|
||||
kfree(h); \
|
||||
} \
|
||||
} \
|
||||
SCOPE void kh_clear_##name(kh_##name##_t *h) \
|
||||
{ \
|
||||
if (h && h->flags) { \
|
||||
memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \
|
||||
h->size = h->n_occupied = 0; \
|
||||
} \
|
||||
} \
|
||||
SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \
|
||||
{ \
|
||||
if (h->n_buckets) { \
|
||||
khint_t inc, k, i, last, mask; \
|
||||
mask = h->n_buckets - 1; \
|
||||
k = __hash_func(key); i = k & mask; \
|
||||
inc = __ac_inc(k, mask); last = i; /* inc==1 for linear probing */ \
|
||||
while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \
|
||||
i = (i + inc) & mask; \
|
||||
if (i == last) return h->n_buckets; \
|
||||
} \
|
||||
return __ac_iseither(h->flags, i)? h->n_buckets : i; \
|
||||
} else return 0; \
|
||||
} \
|
||||
SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \
|
||||
{ /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \
|
||||
khint32_t *new_flags = 0; \
|
||||
khint_t j = 1; \
|
||||
{ \
|
||||
kroundup32(new_n_buckets); \
|
||||
if (new_n_buckets < 4) new_n_buckets = 4; \
|
||||
if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \
|
||||
else { /* hash table size to be changed (shrink or expand); rehash */ \
|
||||
new_flags = (khint32_t*)kmalloc(__ac_fsize(new_n_buckets) * sizeof(khint32_t)); \
|
||||
if (!new_flags) return -1; \
|
||||
memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \
|
||||
if (h->n_buckets < new_n_buckets) { /* expand */ \
|
||||
khkey_t *new_keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \
|
||||
if (!new_keys) return -1; \
|
||||
h->keys = new_keys; \
|
||||
if (kh_is_map) { \
|
||||
khval_t *new_vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \
|
||||
if (!new_vals) return -1; \
|
||||
h->vals = new_vals; \
|
||||
} \
|
||||
} /* otherwise shrink */ \
|
||||
} \
|
||||
} \
|
||||
if (j) { /* rehashing is needed */ \
|
||||
for (j = 0; j != h->n_buckets; ++j) { \
|
||||
if (__ac_iseither(h->flags, j) == 0) { \
|
||||
khkey_t key = h->keys[j]; \
|
||||
khval_t val; \
|
||||
khint_t new_mask; \
|
||||
new_mask = new_n_buckets - 1; \
|
||||
if (kh_is_map) val = h->vals[j]; \
|
||||
__ac_set_isdel_true(h->flags, j); \
|
||||
while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \
|
||||
khint_t inc, k, i; \
|
||||
k = __hash_func(key); \
|
||||
i = k & new_mask; \
|
||||
inc = __ac_inc(k, new_mask); \
|
||||
while (!__ac_isempty(new_flags, i)) i = (i + inc) & new_mask; \
|
||||
__ac_set_isempty_false(new_flags, i); \
|
||||
if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \
|
||||
{ khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \
|
||||
if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \
|
||||
__ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \
|
||||
} else { /* write the element and jump out of the loop */ \
|
||||
h->keys[i] = key; \
|
||||
if (kh_is_map) h->vals[i] = val; \
|
||||
break; \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \
|
||||
h->keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \
|
||||
if (kh_is_map) h->vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \
|
||||
} \
|
||||
kfree(h->flags); /* free the working space */ \
|
||||
h->flags = new_flags; \
|
||||
h->n_buckets = new_n_buckets; \
|
||||
h->n_occupied = h->size; \
|
||||
h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \
|
||||
} \
|
||||
return 0; \
|
||||
} \
|
||||
SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \
|
||||
{ \
|
||||
khint_t x; \
|
||||
if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \
|
||||
if (h->n_buckets > (h->size<<1)) { \
|
||||
if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \
|
||||
*ret = -1; return h->n_buckets; \
|
||||
} \
|
||||
} else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \
|
||||
*ret = -1; return h->n_buckets; \
|
||||
} \
|
||||
} /* TODO: to implement automatically shrinking; resize() already support shrinking */ \
|
||||
{ \
|
||||
khint_t inc, k, i, site, last, mask = h->n_buckets - 1; \
|
||||
x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \
|
||||
if (__ac_isempty(h->flags, i)) x = i; /* for speed up */ \
|
||||
else { \
|
||||
inc = __ac_inc(k, mask); last = i; \
|
||||
while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \
|
||||
if (__ac_isdel(h->flags, i)) site = i; \
|
||||
i = (i + inc) & mask; \
|
||||
if (i == last) { x = site; break; } \
|
||||
} \
|
||||
if (x == h->n_buckets) { \
|
||||
if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \
|
||||
else x = i; \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
if (__ac_isempty(h->flags, x)) { /* not present at all */ \
|
||||
h->keys[x] = key; \
|
||||
__ac_set_isboth_false(h->flags, x); \
|
||||
++h->size; ++h->n_occupied; \
|
||||
*ret = 1; \
|
||||
} else if (__ac_isdel(h->flags, x)) { /* deleted */ \
|
||||
h->keys[x] = key; \
|
||||
__ac_set_isboth_false(h->flags, x); \
|
||||
++h->size; \
|
||||
*ret = 2; \
|
||||
} else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \
|
||||
return x; \
|
||||
} \
|
||||
SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \
|
||||
{ \
|
||||
if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \
|
||||
__ac_set_isdel_true(h->flags, x); \
|
||||
--h->size; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define KHASH_DECLARE(name, khkey_t, khval_t) \
|
||||
__KHASH_TYPE(name, khkey_t, khval_t) \
|
||||
__KHASH_PROTOTYPES(name, khkey_t, khval_t)
|
||||
|
||||
#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
|
||||
__KHASH_TYPE(name, khkey_t, khval_t) \
|
||||
__KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
|
||||
|
||||
#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
|
||||
KHASH_INIT2(name, static kh_inline, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
|
||||
|
||||
/* --- BEGIN OF HASH FUNCTIONS --- */
|
||||
|
||||
/*! @function
|
||||
@abstract Integer hash function
|
||||
@param key The integer [khint32_t]
|
||||
@return The hash value [khint_t]
|
||||
*/
|
||||
#define kh_int_hash_func(key) (khint32_t)(key)
|
||||
/*! @function
|
||||
@abstract Integer comparison function
|
||||
*/
|
||||
#define kh_int_hash_equal(a, b) ((a) == (b))
|
||||
/*! @function
|
||||
@abstract 64-bit integer hash function
|
||||
@param key The integer [khint64_t]
|
||||
@return The hash value [khint_t]
|
||||
*/
|
||||
#define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11)
|
||||
/*! @function
|
||||
@abstract 64-bit integer comparison function
|
||||
*/
|
||||
#define kh_int64_hash_equal(a, b) ((a) == (b))
|
||||
/*! @function
|
||||
@abstract const char* hash function
|
||||
@param s Pointer to a null terminated string
|
||||
@return The hash value
|
||||
*/
|
||||
static kh_inline khint_t __ac_X31_hash_string(const char *s)
|
||||
{
|
||||
khint_t h = (khint_t)*s;
|
||||
if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s;
|
||||
return h;
|
||||
}
|
||||
/*! @function
|
||||
@abstract Another interface to const char* hash function
|
||||
@param key Pointer to a null terminated string [const char*]
|
||||
@return The hash value [khint_t]
|
||||
*/
|
||||
#define kh_str_hash_func(key) __ac_X31_hash_string(key)
|
||||
/*! @function
|
||||
@abstract Const char* comparison function
|
||||
*/
|
||||
#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0)
|
||||
|
||||
static kh_inline khint_t __ac_Wang_hash(khint_t key)
|
||||
{
|
||||
key += ~(key << 15);
|
||||
key ^= (key >> 10);
|
||||
key += (key << 3);
|
||||
key ^= (key >> 6);
|
||||
key += ~(key << 11);
|
||||
key ^= (key >> 16);
|
||||
return key;
|
||||
}
|
||||
#define kh_int_hash_func2(k) __ac_Wang_hash((khint_t)key)
|
||||
|
||||
/* --- END OF HASH FUNCTIONS --- */
|
||||
|
||||
/* Other convenient macros... */
|
||||
|
||||
/*!
|
||||
@abstract Type of the hash table.
|
||||
@param name Name of the hash table [symbol]
|
||||
*/
|
||||
#define khash_t(name) kh_##name##_t
|
||||
|
||||
/*! @function
|
||||
@abstract Initiate a hash table.
|
||||
@param name Name of the hash table [symbol]
|
||||
@return Pointer to the hash table [khash_t(name)*]
|
||||
*/
|
||||
#define kh_init(name) kh_init_##name()
|
||||
|
||||
/*! @function
|
||||
@abstract Destroy a hash table.
|
||||
@param name Name of the hash table [symbol]
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
*/
|
||||
#define kh_destroy(name, h) kh_destroy_##name(h)
|
||||
|
||||
/*! @function
|
||||
@abstract Reset a hash table without deallocating memory.
|
||||
@param name Name of the hash table [symbol]
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
*/
|
||||
#define kh_clear(name, h) kh_clear_##name(h)
|
||||
|
||||
/*! @function
|
||||
@abstract Resize a hash table.
|
||||
@param name Name of the hash table [symbol]
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param s New size [khint_t]
|
||||
*/
|
||||
#define kh_resize(name, h, s) kh_resize_##name(h, s)
|
||||
|
||||
/*! @function
|
||||
@abstract Insert a key to the hash table.
|
||||
@param name Name of the hash table [symbol]
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param k Key [type of keys]
|
||||
@param r Extra return code: 0 if the key is present in the hash table;
|
||||
1 if the bucket is empty (never used); 2 if the element in
|
||||
the bucket has been deleted [int*]
|
||||
@return Iterator to the inserted element [khint_t]
|
||||
*/
|
||||
#define kh_put(name, h, k, r) kh_put_##name(h, k, r)
|
||||
|
||||
/*! @function
|
||||
@abstract Retrieve a key from the hash table.
|
||||
@param name Name of the hash table [symbol]
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param k Key [type of keys]
|
||||
@return Iterator to the found element, or kh_end(h) if the element is absent [khint_t]
|
||||
*/
|
||||
#define kh_get(name, h, k) kh_get_##name(h, k)
|
||||
|
||||
/*! @function
|
||||
@abstract Remove a key from the hash table.
|
||||
@param name Name of the hash table [symbol]
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param k Iterator to the element to be deleted [khint_t]
|
||||
*/
|
||||
#define kh_del(name, h, k) kh_del_##name(h, k)
|
||||
|
||||
/*! @function
|
||||
@abstract Test whether a bucket contains data.
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param x Iterator to the bucket [khint_t]
|
||||
@return 1 if containing data; 0 otherwise [int]
|
||||
*/
|
||||
#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x)))
|
||||
|
||||
/*! @function
|
||||
@abstract Get key given an iterator
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param x Iterator to the bucket [khint_t]
|
||||
@return Key [type of keys]
|
||||
*/
|
||||
#define kh_key(h, x) ((h)->keys[x])
|
||||
|
||||
/*! @function
|
||||
@abstract Get value given an iterator
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param x Iterator to the bucket [khint_t]
|
||||
@return Value [type of values]
|
||||
@discussion For hash sets, calling this results in segfault.
|
||||
*/
|
||||
#define kh_val(h, x) ((h)->vals[x])
|
||||
|
||||
/*! @function
|
||||
@abstract Alias of kh_val()
|
||||
*/
|
||||
#define kh_value(h, x) ((h)->vals[x])
|
||||
|
||||
/*! @function
|
||||
@abstract Get the start iterator
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@return The start iterator [khint_t]
|
||||
*/
|
||||
#define kh_begin(h) (khint_t)(0)
|
||||
|
||||
/*! @function
|
||||
@abstract Get the end iterator
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@return The end iterator [khint_t]
|
||||
*/
|
||||
#define kh_end(h) ((h)->n_buckets)
|
||||
|
||||
/*! @function
|
||||
@abstract Get the number of elements in the hash table
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@return Number of elements in the hash table [khint_t]
|
||||
*/
|
||||
#define kh_size(h) ((h)->size)
|
||||
|
||||
/*! @function
|
||||
@abstract Get the number of buckets in the hash table
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@return Number of buckets in the hash table [khint_t]
|
||||
*/
|
||||
#define kh_n_buckets(h) ((h)->n_buckets)
|
||||
|
||||
/*! @function
|
||||
@abstract Iterate over the entries in the hash table
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param kvar Variable to which key will be assigned
|
||||
@param vvar Variable to which value will be assigned
|
||||
@param code Block of code to execute
|
||||
*/
|
||||
#define kh_foreach(h, kvar, vvar, code) { khint_t __i; \
|
||||
for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \
|
||||
if (!kh_exist(h,__i)) continue; \
|
||||
(kvar) = kh_key(h,__i); \
|
||||
(vvar) = kh_val(h,__i); \
|
||||
code; \
|
||||
} }
|
||||
|
||||
/*! @function
|
||||
@abstract Iterate over the values in the hash table
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param vvar Variable to which value will be assigned
|
||||
@param code Block of code to execute
|
||||
*/
|
||||
#define kh_foreach_value(h, vvar, code) { khint_t __i; \
|
||||
for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \
|
||||
if (!kh_exist(h,__i)) continue; \
|
||||
(vvar) = kh_val(h,__i); \
|
||||
code; \
|
||||
} }
|
||||
|
||||
/* More conenient interfaces */
|
||||
|
||||
/*! @function
|
||||
@abstract Instantiate a hash set containing integer keys
|
||||
@param name Name of the hash table [symbol]
|
||||
*/
|
||||
#define KHASH_SET_INIT_INT(name) \
|
||||
KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal)
|
||||
|
||||
/*! @function
|
||||
@abstract Instantiate a hash map containing integer keys
|
||||
@param name Name of the hash table [symbol]
|
||||
@param khval_t Type of values [type]
|
||||
*/
|
||||
#define KHASH_MAP_INIT_INT(name, khval_t) \
|
||||
KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal)
|
||||
|
||||
/*! @function
|
||||
@abstract Instantiate a hash map containing 64-bit integer keys
|
||||
@param name Name of the hash table [symbol]
|
||||
*/
|
||||
#define KHASH_SET_INIT_INT64(name) \
|
||||
KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal)
|
||||
|
||||
/*! @function
|
||||
@abstract Instantiate a hash map containing 64-bit integer keys
|
||||
@param name Name of the hash table [symbol]
|
||||
@param khval_t Type of values [type]
|
||||
*/
|
||||
#define KHASH_MAP_INIT_INT64(name, khval_t) \
|
||||
KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal)
|
||||
|
||||
typedef const char *kh_cstr_t;
|
||||
/*! @function
|
||||
@abstract Instantiate a hash map containing const char* keys
|
||||
@param name Name of the hash table [symbol]
|
||||
*/
|
||||
#define KHASH_SET_INIT_STR(name) \
|
||||
KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal)
|
||||
|
||||
/*! @function
|
||||
@abstract Instantiate a hash map containing const char* keys
|
||||
@param name Name of the hash table [symbol]
|
||||
@param khval_t Type of values [type]
|
||||
*/
|
||||
#define KHASH_MAP_INIT_STR(name, khval_t) \
|
||||
KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal)
|
||||
|
||||
#endif /* __AC_KHASH_H */
|
1390
c_src/wterl.c
1390
c_src/wterl.c
File diff suppressed because it is too large
Load diff
|
@ -97,6 +97,8 @@ start(Partition, Config) ->
|
|||
end,
|
||||
case AppStart of
|
||||
ok ->
|
||||
%% TODO: on failure to open a table try to verify, and then salvage it
|
||||
%% if the cluster size > the n value
|
||||
Table = "lsm:wt" ++ integer_to_list(Partition),
|
||||
{ok, Connection} = establish_connection(Config),
|
||||
Passes = establish_passes(erlang:system_info(schedulers), Connection, Table),
|
||||
|
|
382
src/wterl.erl
382
src/wterl.erl
|
@ -28,6 +28,7 @@
|
|||
cursor_next_key/1,
|
||||
cursor_next_value/1,
|
||||
cursor_open/2,
|
||||
cursor_open/3,
|
||||
cursor_prev/1,
|
||||
cursor_prev_key/1,
|
||||
cursor_prev_value/1,
|
||||
|
@ -36,28 +37,26 @@
|
|||
cursor_search/2,
|
||||
cursor_search_near/2,
|
||||
cursor_update/3,
|
||||
session_checkpoint/1,
|
||||
session_checkpoint/2,
|
||||
session_close/1,
|
||||
session_create/2,
|
||||
session_create/3,
|
||||
session_delete/3,
|
||||
session_drop/2,
|
||||
session_drop/3,
|
||||
session_get/3,
|
||||
session_open/1,
|
||||
session_open/2,
|
||||
session_put/4,
|
||||
session_rename/3,
|
||||
session_rename/4,
|
||||
session_salvage/2,
|
||||
session_salvage/3,
|
||||
session_truncate/2,
|
||||
session_truncate/3,
|
||||
session_upgrade/2,
|
||||
session_upgrade/3,
|
||||
session_verify/2,
|
||||
session_verify/3,
|
||||
checkpoint/1,
|
||||
checkpoint/2,
|
||||
create/2,
|
||||
create/4,
|
||||
delete/3,
|
||||
drop/2,
|
||||
drop/3,
|
||||
get/3,
|
||||
put/4,
|
||||
rename/3,
|
||||
rename/4,
|
||||
salvage/2,
|
||||
salvage/3,
|
||||
truncate/2,
|
||||
truncate/3,
|
||||
truncate/5,
|
||||
upgrade/2,
|
||||
upgrade/3,
|
||||
verify/2,
|
||||
verify/3,
|
||||
config_value/3,
|
||||
config_to_bin/1,
|
||||
priv_dir/0,
|
||||
|
@ -77,12 +76,11 @@
|
|||
-type config() :: binary().
|
||||
-type config_list() :: [{atom(), any()}].
|
||||
-opaque connection() :: reference().
|
||||
-opaque session() :: reference().
|
||||
-opaque cursor() :: reference().
|
||||
-type key() :: binary().
|
||||
-type value() :: binary().
|
||||
|
||||
-export_type([connection/0, session/0, cursor/0]).
|
||||
-export_type([connection/0, cursor/0]).
|
||||
|
||||
-on_load(init/0).
|
||||
|
||||
|
@ -94,7 +92,9 @@ nif_stub_error(Line) ->
|
|||
|
||||
-spec init() -> ok | {error, any()}.
|
||||
init() ->
|
||||
erlang:load_nif(filename:join(priv_dir(), atom_to_list(?MODULE)), 0).
|
||||
erlang:load_nif(filename:join(priv_dir(), atom_to_list(?MODULE)),
|
||||
[{wterl, "163a5073cb85db2a270ebe904e788bd8d478ea1c"},
|
||||
{wiredtiger, "e9a607b1b78ffa528631519b5cb6ac944468991e"}]).
|
||||
|
||||
-spec connection_open(string(), config()) -> {ok, connection()} | {error, term()}.
|
||||
connection_open(HomeDir, Config) ->
|
||||
|
@ -127,143 +127,130 @@ connection_close(ConnRef) ->
|
|||
conn_close_nif(_AsyncRef, _ConnRef) ->
|
||||
?nif_stub.
|
||||
|
||||
-spec session_open(connection()) -> {ok, session()} | {error, term()}.
|
||||
-spec session_open(connection(), config()) -> {ok, session()} | {error, term()}.
|
||||
session_open(ConnRef) ->
|
||||
session_open(ConnRef, ?EMPTY_CONFIG).
|
||||
session_open(ConnRef, Config) ->
|
||||
?ASYNC_NIF_CALL(fun session_open_nif/3, [ConnRef, Config]).
|
||||
-spec create(connection(), string()) -> ok | {error, term()}.
|
||||
-spec create(connection(), string(), config(), config()) -> ok | {error, term()}.
|
||||
create(Ref, Name) ->
|
||||
create(Ref, Name, ?EMPTY_CONFIG, ?EMPTY_CONFIG).
|
||||
create(Ref, Name, Config, SessionConfig) ->
|
||||
?ASYNC_NIF_CALL(fun create_nif/5, [Ref, Name, Config, SessionConfig]).
|
||||
|
||||
-spec session_open_nif(reference(), connection(), config()) -> {ok, session()} | {error, term()}.
|
||||
session_open_nif(_AsyncRef, _ConnRef, _Config) ->
|
||||
-spec create_nif(reference(), connection(), string(), config(), config()) -> ok | {error, term()}.
|
||||
create_nif(_AsyncNif, _Ref, _Name, _Config, _SessionConfig) ->
|
||||
?nif_stub.
|
||||
|
||||
-spec session_close(session()) -> ok | {error, term()}.
|
||||
session_close(Ref) ->
|
||||
?ASYNC_NIF_CALL(fun session_close_nif/2, [Ref]).
|
||||
-spec drop(connection(), string()) -> ok | {error, term()}.
|
||||
-spec drop(connection(), string(), config()) -> ok | {error, term()}.
|
||||
drop(Ref, Name) ->
|
||||
drop(Ref, Name, ?EMPTY_CONFIG).
|
||||
drop(Ref, Name, Config) ->
|
||||
?ASYNC_NIF_CALL(fun drop_nif/4, [Ref, Name, Config]).
|
||||
|
||||
-spec session_close_nif(reference(), session()) -> ok | {error, term()}.
|
||||
session_close_nif(_AsyncRef, _Ref) ->
|
||||
-spec drop_nif(reference(), connection(), string(), config()) -> ok | {error, term()}.
|
||||
drop_nif(_AsyncRef, _Ref, _Name, _Config) ->
|
||||
?nif_stub.
|
||||
|
||||
-spec session_create(session(), string()) -> ok | {error, term()}.
|
||||
-spec session_create(session(), string(), config()) -> ok | {error, term()}.
|
||||
session_create(Ref, Name) ->
|
||||
session_create(Ref, Name, ?EMPTY_CONFIG).
|
||||
session_create(Ref, Name, Config) ->
|
||||
?ASYNC_NIF_CALL(fun session_create_nif/4, [Ref, Name, Config]).
|
||||
-spec delete(connection(), string(), key()) -> ok | {error, term()}.
|
||||
delete(Ref, Table, Key) ->
|
||||
?ASYNC_NIF_CALL(fun delete_nif/4, [Ref, Table, Key]).
|
||||
|
||||
-spec session_create_nif(reference(), session(), string(), config()) -> ok | {error, term()}.
|
||||
session_create_nif(_AsyncNif, _Ref, _Name, _Config) ->
|
||||
-spec delete_nif(reference(), connection(), string(), key()) -> ok | {error, term()}.
|
||||
delete_nif(_AsyncRef, _Ref, _Table, _Key) ->
|
||||
?nif_stub.
|
||||
|
||||
-spec session_drop(session(), string()) -> ok | {error, term()}.
|
||||
-spec session_drop(session(), string(), config()) -> ok | {error, term()}.
|
||||
session_drop(Ref, Name) ->
|
||||
session_drop(Ref, Name, ?EMPTY_CONFIG).
|
||||
session_drop(Ref, Name, Config) ->
|
||||
?ASYNC_NIF_CALL(fun session_drop_nif/4, [Ref, Name, Config]).
|
||||
-spec get(connection(), string(), key()) -> {ok, value()} | not_found | {error, term()}.
|
||||
get(Ref, Table, Key) ->
|
||||
?ASYNC_NIF_CALL(fun get_nif/4, [Ref, Table, Key]).
|
||||
|
||||
-spec session_drop_nif(reference(), session(), string(), config()) -> ok | {error, term()}.
|
||||
session_drop_nif(_AsyncRef, _Ref, _Name, _Config) ->
|
||||
-spec get_nif(reference(), connection(), string(), key()) -> {ok, value()} | not_found | {error, term()}.
|
||||
get_nif(_AsyncRef, _Ref, _Table, _Key) ->
|
||||
?nif_stub.
|
||||
|
||||
-spec session_delete(session(), string(), key()) -> ok | {error, term()}.
|
||||
session_delete(Ref, Table, Key) ->
|
||||
?ASYNC_NIF_CALL(fun session_delete_nif/4, [Ref, Table, Key]).
|
||||
-spec put(connection(), string(), key(), value()) -> ok | {error, term()}.
|
||||
put(Ref, Table, Key, Value) ->
|
||||
?ASYNC_NIF_CALL(fun put_nif/5, [Ref, Table, Key, Value]).
|
||||
|
||||
-spec session_delete_nif(reference(), session(), string(), key()) -> ok | {error, term()}.
|
||||
session_delete_nif(_AsyncRef, _Ref, _Table, _Key) ->
|
||||
-spec put_nif(reference(), connection(), string(), key(), value()) -> ok | {error, term()}.
|
||||
put_nif(_AsyncRef, _Ref, _Table, _Key, _Value) ->
|
||||
?nif_stub.
|
||||
|
||||
-spec session_get(session(), string(), key()) -> {ok, value()} | not_found | {error, term()}.
|
||||
session_get(Ref, Table, Key) ->
|
||||
?ASYNC_NIF_CALL(fun session_get_nif/4, [Ref, Table, Key]).
|
||||
-spec rename(connection(), string(), string()) -> ok | {error, term()}.
|
||||
-spec rename(connection(), string(), string(), config()) -> ok | {error, term()}.
|
||||
rename(Ref, OldName, NewName) ->
|
||||
rename(Ref, OldName, NewName, ?EMPTY_CONFIG).
|
||||
rename(Ref, OldName, NewName, Config) ->
|
||||
?ASYNC_NIF_CALL(fun rename_nif/5, [Ref, OldName, NewName, Config]).
|
||||
|
||||
-spec session_get_nif(reference(), session(), string(), key()) -> {ok, value()} | not_found | {error, term()}.
|
||||
session_get_nif(_AsyncRef, _Ref, _Table, _Key) ->
|
||||
-spec rename_nif(reference(), connection(), string(), string(), config()) -> ok | {error, term()}.
|
||||
rename_nif(_AsyncRef, _Ref, _OldName, _NewName, _Config) ->
|
||||
?nif_stub.
|
||||
|
||||
-spec session_put(session(), string(), key(), value()) -> ok | {error, term()}.
|
||||
session_put(Ref, Table, Key, Value) ->
|
||||
?ASYNC_NIF_CALL(fun session_put_nif/5, [Ref, Table, Key, Value]).
|
||||
-spec salvage(connection(), string()) -> ok | {error, term()}.
|
||||
-spec salvage(connection(), string(), config()) -> ok | {error, term()}.
|
||||
salvage(Ref, Name) ->
|
||||
salvage(Ref, Name, ?EMPTY_CONFIG).
|
||||
salvage(Ref, Name, Config) ->
|
||||
?ASYNC_NIF_CALL(fun salvage_nif/4, [Ref, Name, Config]).
|
||||
|
||||
-spec session_put_nif(reference(), session(), string(), key(), value()) -> ok | {error, term()}.
|
||||
session_put_nif(_AsyncRef, _Ref, _Table, _Key, _Value) ->
|
||||
-spec salvage_nif(reference(), connection(), string(), config()) -> ok | {error, term()}.
|
||||
salvage_nif(_AsyncRef, _Ref, _Name, _Config) ->
|
||||
?nif_stub.
|
||||
|
||||
-spec session_rename(session(), string(), string()) -> ok | {error, term()}.
|
||||
-spec session_rename(session(), string(), string(), config()) -> ok | {error, term()}.
|
||||
session_rename(Ref, OldName, NewName) ->
|
||||
session_rename(Ref, OldName, NewName, ?EMPTY_CONFIG).
|
||||
session_rename(Ref, OldName, NewName, Config) ->
|
||||
?ASYNC_NIF_CALL(fun session_rename_nif/5, [Ref, OldName, NewName, Config]).
|
||||
-spec checkpoint(connection()) -> ok | {error, term()}.
|
||||
-spec checkpoint(connection(), config()) -> ok | {error, term()}.
|
||||
checkpoint(_Ref) ->
|
||||
checkpoint(_Ref, ?EMPTY_CONFIG).
|
||||
checkpoint(Ref, Config) ->
|
||||
?ASYNC_NIF_CALL(fun checkpoint_nif/3, [Ref, Config]).
|
||||
|
||||
-spec session_rename_nif(reference(), session(), string(), string(), config()) -> ok | {error, term()}.
|
||||
session_rename_nif(_AsyncRef, _Ref, _OldName, _NewName, _Config) ->
|
||||
-spec checkpoint_nif(reference(), connection(), config()) -> ok | {error, term()}.
|
||||
checkpoint_nif(_AsyncRef, _Ref, _Config) ->
|
||||
?nif_stub.
|
||||
|
||||
-spec session_salvage(session(), string()) -> ok | {error, term()}.
|
||||
-spec session_salvage(session(), string(), config()) -> ok | {error, term()}.
|
||||
session_salvage(Ref, Name) ->
|
||||
session_salvage(Ref, Name, ?EMPTY_CONFIG).
|
||||
session_salvage(Ref, Name, Config) ->
|
||||
?ASYNC_NIF_CALL(fun session_salvage_nif/4, [Ref, Name, Config]).
|
||||
-spec truncate(connection(), string()) -> ok | {error, term()}.
|
||||
-spec truncate(connection(), string(), config()) -> ok | {error, term()}.
|
||||
truncate(Ref, Name, Config) ->
|
||||
truncate(Ref, Name, 0, 0, Config).
|
||||
-spec truncate(connection(), string(), cursor() | 0, cursor() | 0, config()) -> ok | {error, term()}.
|
||||
truncate(Ref, Name) ->
|
||||
truncate(Ref, Name, 0, 0, ?EMPTY_CONFIG).
|
||||
truncate(Ref, Name, Start, Stop, Config) ->
|
||||
?ASYNC_NIF_CALL(fun truncate_nif/6, [Ref, Name, Start, Stop, Config]).
|
||||
|
||||
-spec session_salvage_nif(reference(), session(), string(), config()) -> ok | {error, term()}.
|
||||
session_salvage_nif(_AsyncRef, _Ref, _Name, _Config) ->
|
||||
-spec truncate_nif(reference(), connection(), string(), cursor() | 0, cursor() | 0, config()) -> ok | {error, term()}.
|
||||
truncate_nif(_AsyncRef, _Ref, _Name, _Start, _Stop, _Config) ->
|
||||
?nif_stub.
|
||||
|
||||
-spec session_checkpoint(session()) -> ok | {error, term()}.
|
||||
-spec session_checkpoint(session(), config()) -> ok | {error, term()}.
|
||||
session_checkpoint(_Ref) ->
|
||||
session_checkpoint(_Ref, ?EMPTY_CONFIG).
|
||||
session_checkpoint(Ref, Config) ->
|
||||
?ASYNC_NIF_CALL(fun session_checkpoint_nif/3, [Ref, Config]).
|
||||
-spec upgrade(connection(), string()) -> ok | {error, term()}.
|
||||
-spec upgrade(connection(), string(), config()) -> ok | {error, term()}.
|
||||
upgrade(Ref, Name) ->
|
||||
upgrade(Ref, Name, ?EMPTY_CONFIG).
|
||||
upgrade(Ref, Name, Config) ->
|
||||
?ASYNC_NIF_CALL(fun upgrade_nif/4, [Ref, Name, Config]).
|
||||
|
||||
-spec session_checkpoint_nif(reference(), session(), config()) -> ok | {error, term()}.
|
||||
session_checkpoint_nif(_AsyncRef, _Ref, _Config) ->
|
||||
-spec upgrade_nif(reference(), connection(), string(), config()) -> ok | {error, term()}.
|
||||
upgrade_nif(_AsyncRef, _Ref, _Name, _Config) ->
|
||||
?nif_stub.
|
||||
|
||||
-spec session_truncate(session(), string()) -> ok | {error, term()}.
|
||||
-spec session_truncate(session(), string(), config()) -> ok | {error, term()}.
|
||||
session_truncate(Ref, Name) ->
|
||||
session_truncate(Ref, Name, ?EMPTY_CONFIG).
|
||||
session_truncate(Ref, Name, Config) ->
|
||||
?ASYNC_NIF_CALL(fun session_truncate_nif/4, [Ref, Name, Config]).
|
||||
-spec verify(connection(), string()) -> ok | {error, term()}.
|
||||
-spec verify(connection(), string(), config()) -> ok | {error, term()}.
|
||||
verify(Ref, Name) ->
|
||||
verify(Ref, Name, ?EMPTY_CONFIG).
|
||||
verify(Ref, Name, Config) ->
|
||||
?ASYNC_NIF_CALL(fun verify_nif/4, [Ref, Name, Config]).
|
||||
|
||||
-spec session_truncate_nif(reference(), session(), string(), config()) -> ok | {error, term()}.
|
||||
session_truncate_nif(_AsyncRef, _Ref, _Name, _Config) ->
|
||||
-spec verify_nif(reference(), connection(), string(), config()) -> ok | {error, term()}.
|
||||
verify_nif(_AsyncRef, _Ref, _Name, _Config) ->
|
||||
?nif_stub.
|
||||
|
||||
-spec session_upgrade(session(), string()) -> ok | {error, term()}.
|
||||
-spec session_upgrade(session(), string(), config()) -> ok | {error, term()}.
|
||||
session_upgrade(Ref, Name) ->
|
||||
session_upgrade(Ref, Name, ?EMPTY_CONFIG).
|
||||
session_upgrade(Ref, Name, Config) ->
|
||||
?ASYNC_NIF_CALL(fun session_upgrade_nif/4, [Ref, Name, Config]).
|
||||
|
||||
-spec session_upgrade_nif(reference(), session(), string(), config()) -> ok | {error, term()}.
|
||||
session_upgrade_nif(_AsyncRef, _Ref, _Name, _Config) ->
|
||||
?nif_stub.
|
||||
|
||||
-spec session_verify(session(), string()) -> ok | {error, term()}.
|
||||
-spec session_verify(session(), string(), config()) -> ok | {error, term()}.
|
||||
session_verify(Ref, Name) ->
|
||||
session_verify(Ref, Name, ?EMPTY_CONFIG).
|
||||
session_verify(Ref, Name, Config) ->
|
||||
?ASYNC_NIF_CALL(fun session_verify_nif/4, [Ref, Name, Config]).
|
||||
|
||||
-spec session_verify_nif(reference(), session(), string(), config()) -> ok | {error, term()}.
|
||||
session_verify_nif(_AsyncRef, _Ref, _Name, _Config) ->
|
||||
?nif_stub.
|
||||
|
||||
-spec cursor_open(session(), string()) -> {ok, cursor()} | {error, term()}.
|
||||
-spec cursor_open(connection(), string()) -> {ok, cursor()} | {error, term()}.
|
||||
-spec cursor_open(connection(), string(), config() | 0) -> {ok, cursor()} | {error, term()}.
|
||||
cursor_open(Ref, Table) ->
|
||||
?ASYNC_NIF_CALL(fun cursor_open_nif/3, [Ref, Table]).
|
||||
cursor_open(Ref, Table, 0).
|
||||
cursor_open(Ref, Table, Config) ->
|
||||
?ASYNC_NIF_CALL(fun cursor_open_nif/4, [Ref, Table, Config]).
|
||||
|
||||
-spec cursor_open_nif(reference(), session(), string()) -> {ok, cursor()} | {error, term()}.
|
||||
cursor_open_nif(_AsyncRef, _Ref, _Table) ->
|
||||
-spec cursor_open_nif(reference(), connection(), string(), config() | 0) -> {ok, cursor()} | {error, term()}.
|
||||
cursor_open_nif(_AsyncRef, _Ref, _Table, _Config) ->
|
||||
?nif_stub.
|
||||
|
||||
-spec cursor_close(cursor()) -> ok | {error, term()}.
|
||||
|
@ -518,11 +505,10 @@ open_test_conn(DataDir) ->
|
|||
{ok, ConnRef} = connection_open(DataDir, OpenConfig),
|
||||
ConnRef.
|
||||
|
||||
open_test_session(ConnRef) ->
|
||||
{ok, SRef} = session_open(ConnRef),
|
||||
?assertMatch(ok, session_drop(SRef, "table:test", config_to_bin([{force,true}]))),
|
||||
?assertMatch(ok, session_create(SRef, "table:test", config_to_bin([{block_compressor, "snappy"}]))),
|
||||
SRef.
|
||||
open_test_table(ConnRef) ->
|
||||
?assertMatch(ok, drop(ConnRef, "table:test", config_to_bin([{force,true}]))),
|
||||
?assertMatch(ok, create(ConnRef, "table:test", config_to_bin([{block_compressor, "snappy"}]))),
|
||||
ConnRef.
|
||||
|
||||
conn_test() ->
|
||||
ConnRef = open_test_conn(?TEST_DATA_DIR),
|
||||
|
@ -538,110 +524,102 @@ session_test_() ->
|
|||
end,
|
||||
fun(ConnRef) ->
|
||||
{inorder,
|
||||
[{"open/close a session",
|
||||
[{"create and drop a table",
|
||||
fun() ->
|
||||
{ok, SRef} = session_open(ConnRef),
|
||||
?assertMatch(ok, session_close(SRef))
|
||||
end},
|
||||
{"create and drop a table",
|
||||
fun() ->
|
||||
SRef = open_test_session(ConnRef),
|
||||
?assertMatch(ok, session_drop(SRef, "table:test")),
|
||||
?assertMatch(ok, session_close(SRef))
|
||||
ConnRef = open_test_table(ConnRef),
|
||||
?assertMatch(ok, drop(ConnRef, "table:test"))
|
||||
end}]}
|
||||
end}.
|
||||
|
||||
insert_delete_test() ->
|
||||
ConnRef = open_test_conn(?TEST_DATA_DIR),
|
||||
SRef = open_test_session(ConnRef),
|
||||
?assertMatch(ok, session_put(SRef, "table:test", <<"a">>, <<"apple">>)),
|
||||
?assertMatch({ok, <<"apple">>}, session_get(SRef, "table:test", <<"a">>)),
|
||||
?assertMatch(ok, session_delete(SRef, "table:test", <<"a">>)),
|
||||
?assertMatch(not_found, session_get(SRef, "table:test", <<"a">>)),
|
||||
ok = session_close(SRef),
|
||||
ConnRef = open_test_table(ConnRef),
|
||||
?assertMatch(ok, put(ConnRef, "table:test", <<"a">>, <<"apple">>)),
|
||||
?assertMatch({ok, <<"apple">>}, get(ConnRef, "table:test", <<"a">>)),
|
||||
?assertMatch(ok, delete(ConnRef, "table:test", <<"a">>)),
|
||||
?assertMatch(not_found, get(ConnRef, "table:test", <<"a">>)),
|
||||
ok = connection_close(ConnRef).
|
||||
|
||||
init_test_table() ->
|
||||
ConnRef = open_test_conn(?TEST_DATA_DIR),
|
||||
SRef = open_test_session(ConnRef),
|
||||
?assertMatch(ok, session_put(SRef, "table:test", <<"a">>, <<"apple">>)),
|
||||
?assertMatch(ok, session_put(SRef, "table:test", <<"b">>, <<"banana">>)),
|
||||
?assertMatch(ok, session_put(SRef, "table:test", <<"c">>, <<"cherry">>)),
|
||||
?assertMatch(ok, session_put(SRef, "table:test", <<"d">>, <<"date">>)),
|
||||
?assertMatch(ok, session_put(SRef, "table:test", <<"g">>, <<"gooseberry">>)),
|
||||
{ConnRef, SRef}.
|
||||
ConnRef = open_test_table(ConnRef),
|
||||
?assertMatch(ok, put(ConnRef, "table:test", <<"a">>, <<"apple">>)),
|
||||
?assertMatch(ok, put(ConnRef, "table:test", <<"b">>, <<"banana">>)),
|
||||
?assertMatch(ok, put(ConnRef, "table:test", <<"c">>, <<"cherry">>)),
|
||||
?assertMatch(ok, put(ConnRef, "table:test", <<"d">>, <<"date">>)),
|
||||
?assertMatch(ok, put(ConnRef, "table:test", <<"g">>, <<"gooseberry">>)),
|
||||
ConnRef.
|
||||
|
||||
stop_test_table({ConnRef, SRef}) ->
|
||||
?assertMatch(ok, session_close(SRef)),
|
||||
stop_test_table(ConnRef) ->
|
||||
?assertMatch(ok, connection_close(ConnRef)).
|
||||
|
||||
various_session_test_() ->
|
||||
{setup,
|
||||
fun init_test_table/0,
|
||||
fun stop_test_table/1,
|
||||
fun({_, SRef}) ->
|
||||
fun(ConnRef) ->
|
||||
{inorder,
|
||||
[{"session verify",
|
||||
fun() ->
|
||||
?assertMatch(ok, session_verify(SRef, "table:test")),
|
||||
?assertMatch(ok, verify(ConnRef, "table:test")),
|
||||
?assertMatch({ok, <<"apple">>},
|
||||
session_get(SRef, "table:test", <<"a">>))
|
||||
get(ConnRef, "table:test", <<"a">>))
|
||||
end},
|
||||
{"session checkpoint",
|
||||
fun() ->
|
||||
Cfg = wterl:config_to_bin([{target, ["\"table:test\""]}]),
|
||||
?assertMatch(ok, session_checkpoint(SRef, Cfg)),
|
||||
?assertMatch(ok, checkpoint(ConnRef, Cfg)),
|
||||
?assertMatch({ok, <<"apple">>},
|
||||
session_get(SRef, "table:test", <<"a">>))
|
||||
get(ConnRef, "table:test", <<"a">>))
|
||||
end},
|
||||
{"session salvage",
|
||||
fun() ->
|
||||
ok = session_salvage(SRef, "table:test"),
|
||||
{ok, <<"apple">>} = session_get(SRef, "table:test", <<"a">>)
|
||||
ok = salvage(ConnRef, "table:test"),
|
||||
{ok, <<"apple">>} = get(ConnRef, "table:test", <<"a">>)
|
||||
end},
|
||||
{"session upgrade",
|
||||
fun() ->
|
||||
?assertMatch(ok, session_upgrade(SRef, "table:test")),
|
||||
?assertMatch(ok, upgrade(ConnRef, "table:test")),
|
||||
?assertMatch({ok, <<"apple">>},
|
||||
session_get(SRef, "table:test", <<"a">>))
|
||||
get(ConnRef, "table:test", <<"a">>))
|
||||
end},
|
||||
{"session rename",
|
||||
fun() ->
|
||||
?assertMatch(ok,
|
||||
session_rename(SRef, "table:test", "table:new")),
|
||||
rename(ConnRef, "table:test", "table:new")),
|
||||
?assertMatch({ok, <<"apple">>},
|
||||
session_get(SRef, "table:new", <<"a">>)),
|
||||
get(ConnRef, "table:new", <<"a">>)),
|
||||
?assertMatch(ok,
|
||||
session_rename(SRef, "table:new", "table:test")),
|
||||
rename(ConnRef, "table:new", "table:test")),
|
||||
?assertMatch({ok, <<"apple">>},
|
||||
session_get(SRef, "table:test", <<"a">>))
|
||||
get(ConnRef, "table:test", <<"a">>))
|
||||
end},
|
||||
{"session truncate",
|
||||
fun() ->
|
||||
?assertMatch(ok, session_truncate(SRef, "table:test")),
|
||||
?assertMatch(not_found, session_get(SRef, "table:test", <<"a">>))
|
||||
?assertMatch(ok, truncate(ConnRef, "table:test")),
|
||||
?assertMatch(not_found, get(ConnRef, "table:test", <<"a">>))
|
||||
end}]}
|
||||
end}.
|
||||
|
||||
cursor_open_close_test() ->
|
||||
{ConnRef, SRef} = init_test_table(),
|
||||
{ok, Cursor1} = cursor_open(SRef, "table:test"),
|
||||
ConnRef = init_test_table(),
|
||||
{ok, Cursor1} = cursor_open(ConnRef, "table:test"),
|
||||
?assertMatch({ok, <<"a">>, <<"apple">>}, cursor_next(Cursor1)),
|
||||
?assertMatch(ok, cursor_close(Cursor1)),
|
||||
{ok, Cursor2} = cursor_open(SRef, "table:test"),
|
||||
{ok, Cursor2} = cursor_open(ConnRef, "table:test"),
|
||||
?assertMatch({ok, <<"g">>, <<"gooseberry">>}, cursor_prev(Cursor2)),
|
||||
?assertMatch(ok, cursor_close(Cursor2)),
|
||||
stop_test_table({ConnRef, SRef}).
|
||||
stop_test_table(ConnRef).
|
||||
|
||||
various_cursor_test_() ->
|
||||
{setup,
|
||||
fun init_test_table/0,
|
||||
fun stop_test_table/1,
|
||||
fun({_, SRef}) ->
|
||||
fun(ConnRef) ->
|
||||
{inorder,
|
||||
[{"move a cursor back and forth, getting key",
|
||||
fun() ->
|
||||
{ok, Cursor} = cursor_open(SRef, "table:test"),
|
||||
{ok, Cursor} = cursor_open(ConnRef, "table:test"),
|
||||
?assertMatch({ok, <<"a">>}, cursor_next_key(Cursor)),
|
||||
?assertMatch({ok, <<"b">>}, cursor_next_key(Cursor)),
|
||||
?assertMatch({ok, <<"c">>}, cursor_next_key(Cursor)),
|
||||
|
@ -654,7 +632,7 @@ various_cursor_test_() ->
|
|||
end},
|
||||
{"move a cursor back and forth, getting value",
|
||||
fun() ->
|
||||
{ok, Cursor} = cursor_open(SRef, "table:test"),
|
||||
{ok, Cursor} = cursor_open(ConnRef, "table:test"),
|
||||
?assertMatch({ok, <<"apple">>}, cursor_next_value(Cursor)),
|
||||
?assertMatch({ok, <<"banana">>}, cursor_next_value(Cursor)),
|
||||
?assertMatch({ok, <<"cherry">>}, cursor_next_value(Cursor)),
|
||||
|
@ -667,7 +645,7 @@ various_cursor_test_() ->
|
|||
end},
|
||||
{"move a cursor back and forth, getting key and value",
|
||||
fun() ->
|
||||
{ok, Cursor} = cursor_open(SRef, "table:test"),
|
||||
{ok, Cursor} = cursor_open(ConnRef, "table:test"),
|
||||
?assertMatch({ok, <<"a">>, <<"apple">>}, cursor_next(Cursor)),
|
||||
?assertMatch({ok, <<"b">>, <<"banana">>}, cursor_next(Cursor)),
|
||||
?assertMatch({ok, <<"c">>, <<"cherry">>}, cursor_next(Cursor)),
|
||||
|
@ -680,27 +658,27 @@ various_cursor_test_() ->
|
|||
end},
|
||||
{"fold keys",
|
||||
fun() ->
|
||||
{ok, Cursor} = cursor_open(SRef, "table:test"),
|
||||
{ok, Cursor} = cursor_open(ConnRef, "table:test"),
|
||||
?assertMatch([<<"g">>, <<"d">>, <<"c">>, <<"b">>, <<"a">>],
|
||||
fold_keys(Cursor, fun(Key, Acc) -> [Key | Acc] end, [])),
|
||||
?assertMatch(ok, cursor_close(Cursor))
|
||||
end},
|
||||
{"search for an item",
|
||||
fun() ->
|
||||
{ok, Cursor} = cursor_open(SRef, "table:test"),
|
||||
{ok, Cursor} = cursor_open(ConnRef, "table:test"),
|
||||
?assertMatch({ok, <<"banana">>}, cursor_search(Cursor, <<"b">>)),
|
||||
?assertMatch(ok, cursor_close(Cursor))
|
||||
end},
|
||||
{"range search for an item",
|
||||
fun() ->
|
||||
{ok, Cursor} = cursor_open(SRef, "table:test"),
|
||||
{ok, Cursor} = cursor_open(ConnRef, "table:test"),
|
||||
?assertMatch({ok, <<"gooseberry">>},
|
||||
cursor_search_near(Cursor, <<"z">>)),
|
||||
?assertMatch(ok, cursor_close(Cursor))
|
||||
end},
|
||||
{"check cursor reset",
|
||||
fun() ->
|
||||
{ok, Cursor} = cursor_open(SRef, "table:test"),
|
||||
{ok, Cursor} = cursor_open(ConnRef, "table:test"),
|
||||
?assertMatch({ok, <<"apple">>}, cursor_next_value(Cursor)),
|
||||
?assertMatch(ok, cursor_reset(Cursor)),
|
||||
?assertMatch({ok, <<"apple">>}, cursor_next_value(Cursor)),
|
||||
|
@ -708,7 +686,7 @@ various_cursor_test_() ->
|
|||
end},
|
||||
{"insert/overwrite an item using a cursor",
|
||||
fun() ->
|
||||
{ok, Cursor} = cursor_open(SRef, "table:test"),
|
||||
{ok, Cursor} = cursor_open(ConnRef, "table:test"),
|
||||
?assertMatch(ok,
|
||||
cursor_insert(Cursor, <<"h">>, <<"huckleberry">>)),
|
||||
?assertMatch({ok, <<"huckleberry">>},
|
||||
|
@ -719,28 +697,28 @@ various_cursor_test_() ->
|
|||
cursor_search(Cursor, <<"g">>)),
|
||||
?assertMatch(ok, cursor_close(Cursor)),
|
||||
?assertMatch({ok, <<"grapefruit">>},
|
||||
session_get(SRef, "table:test", <<"g">>)),
|
||||
get(ConnRef, "table:test", <<"g">>)),
|
||||
?assertMatch({ok, <<"huckleberry">>},
|
||||
session_get(SRef, "table:test", <<"h">>))
|
||||
get(ConnRef, "table:test", <<"h">>))
|
||||
end},
|
||||
{"update an item using a cursor",
|
||||
fun() ->
|
||||
{ok, Cursor} = cursor_open(SRef, "table:test"),
|
||||
{ok, Cursor} = cursor_open(ConnRef, "table:test"),
|
||||
?assertMatch(ok,
|
||||
cursor_update(Cursor, <<"g">>, <<"goji berries">>)),
|
||||
?assertMatch(not_found,
|
||||
cursor_update(Cursor, <<"k">>, <<"kumquat">>)),
|
||||
?assertMatch(ok, cursor_close(Cursor)),
|
||||
?assertMatch({ok, <<"goji berries">>},
|
||||
session_get(SRef, "table:test", <<"g">>))
|
||||
get(ConnRef, "table:test", <<"g">>))
|
||||
end},
|
||||
{"remove an item using a cursor",
|
||||
fun() ->
|
||||
{ok, Cursor} = cursor_open(SRef, "table:test"),
|
||||
{ok, Cursor} = cursor_open(ConnRef, "table:test"),
|
||||
?assertMatch(ok, cursor_remove(Cursor, <<"g">>)),
|
||||
?assertMatch(not_found, cursor_remove(Cursor, <<"l">>)),
|
||||
?assertMatch(ok, cursor_close(Cursor)),
|
||||
?assertMatch(not_found, session_get(SRef, "table:test", <<"g">>))
|
||||
?assertMatch(not_found, get(ConnRef, "table:test", <<"g">>))
|
||||
end}]}
|
||||
end}.
|
||||
|
||||
|
@ -758,13 +736,13 @@ values() ->
|
|||
ops(Keys, Values) ->
|
||||
{oneof([put, delete]), oneof(Keys), oneof(Values)}.
|
||||
|
||||
apply_kv_ops([], _SRef, _Tbl, Acc0) ->
|
||||
apply_kv_ops([], _ConnRef, _Tbl, Acc0) ->
|
||||
Acc0;
|
||||
apply_kv_ops([{put, K, V} | Rest], SRef, Tbl, Acc0) ->
|
||||
ok = wterl:session_put(SRef, Tbl, K, V),
|
||||
apply_kv_ops(Rest, SRef, Tbl, orddict:store(K, V, Acc0));
|
||||
apply_kv_ops([{delete, K, _} | Rest], SRef, Tbl, Acc0) ->
|
||||
ok = case wterl:session_delete(SRef, Tbl, K) of
|
||||
apply_kv_ops([{put, K, V} | Rest], ConnRef, Tbl, Acc0) ->
|
||||
ok = wterl:put(ConnRef, Tbl, K, V),
|
||||
apply_kv_ops(Rest, ConnRef, Tbl, orddict:store(K, V, Acc0));
|
||||
apply_kv_ops([{delete, K, _} | Rest], ConnRef, Tbl, Acc0) ->
|
||||
ok = case wterl:delete(ConnRef, Tbl, K) of
|
||||
ok ->
|
||||
ok;
|
||||
not_found ->
|
||||
|
@ -772,7 +750,7 @@ apply_kv_ops([{delete, K, _} | Rest], SRef, Tbl, Acc0) ->
|
|||
Else ->
|
||||
Else
|
||||
end,
|
||||
apply_kv_ops(Rest, SRef, Tbl, orddict:store(K, deleted, Acc0)).
|
||||
apply_kv_ops(Rest, ConnRef, Tbl, orddict:store(K, deleted, Acc0)).
|
||||
|
||||
prop_put_delete() ->
|
||||
?LET({Keys, Values}, {keys(), values()},
|
||||
|
@ -786,21 +764,19 @@ prop_put_delete() ->
|
|||
ok = filelib:ensure_dir(filename:join(DataDir, "x")),
|
||||
Cfg = wterl:config_to_bin([{create,true}]),
|
||||
{ok, Conn} = wterl:connection_open(DataDir, Cfg),
|
||||
{ok, SRef} = wterl:session_open(Conn),
|
||||
try
|
||||
wterl:session_create(SRef, Table),
|
||||
Model = apply_kv_ops(Ops, SRef, Table, []),
|
||||
wterl:create(ConnRef, Table),
|
||||
Model = apply_kv_ops(Ops, ConnRef, Table, []),
|
||||
|
||||
%% Validate that all deleted values return not_found
|
||||
F = fun({K, deleted}) ->
|
||||
?assertEqual(not_found, wterl:session_get(SRef, Table, K));
|
||||
?assertEqual(not_found, wterl:get(ConnRef, Table, K));
|
||||
({K, V}) ->
|
||||
?assertEqual({ok, V}, wterl:session_get(SRef, Table, K))
|
||||
?assertEqual({ok, V}, wterl:get(ConnRef, Table, K))
|
||||
end,
|
||||
lists:map(F, Model),
|
||||
true
|
||||
after
|
||||
wterl:session_close(SRef),
|
||||
wterl:connection_close(Conn)
|
||||
end
|
||||
end)).
|
||||
|
|
9
update-version.sh
Executable file
9
update-version.sh
Executable file
|
@ -0,0 +1,9 @@
|
|||
#!/bin/sh -
|
||||
|
||||
wterl=`git log -n 1 --pretty=format:"%H"`
|
||||
wiredtiger0=`(cd c_src/wiredtiger && git log -n 1 --pretty=format:"%H")`
|
||||
wiredtiger=`echo $wiredtiger0 | awk '{print $2}'`
|
||||
|
||||
echo $wterl
|
||||
echo $wiredtiger
|
||||
|
Loading…
Reference in a new issue