initial release
This commit is contained in:
parent
952eff3c9a
commit
6498e5a4e3
18 changed files with 10251 additions and 3 deletions
8
3rdLibs
Normal file
8
3rdLibs
Normal file
|
@ -0,0 +1,8 @@
|
|||
o MDB: http://highlandsun.com/hyc/mdb/
|
||||
source : from OpenLDAP git repository
|
||||
commit : 7333b6bdc90c49c332d4728c62c7904e64c95077
|
||||
license: OpenLDAP Public License
|
||||
|
||||
o UTHash: http://uthash.sourceforge.net/
|
||||
version: 1.9.6
|
||||
license: BSD revised
|
3
ChangeLog
Normal file
3
ChangeLog
Normal file
|
@ -0,0 +1,3 @@
|
|||
o Sep 29th, 2012
|
||||
initial release
|
||||
|
49
LICENSE
Normal file
49
LICENSE
Normal file
|
@ -0,0 +1,49 @@
|
|||
-------------------------------------------------------------------------
|
||||
The OpenLDAP Public License
|
||||
Version 2.8, 17 August 2003
|
||||
|
||||
Redistribution and use of this software and associated documentation
|
||||
("Software"), with or without modification, are permitted provided
|
||||
that the following conditions are met:
|
||||
|
||||
1. Redistributions in source form must retain copyright statements
|
||||
and notices,
|
||||
|
||||
2. Redistributions in binary form must reproduce applicable copyright
|
||||
statements and notices, this list of conditions, and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution, and
|
||||
|
||||
3. Redistributions must contain a verbatim copy of this document.
|
||||
|
||||
The OpenLDAP Foundation may revise this license from time to time.
|
||||
Each revision is distinguished by a version number. You may use
|
||||
this Software under terms of this license revision or under the
|
||||
terms of any subsequent revision of the license.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE OPENLDAP FOUNDATION AND ITS
|
||||
CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
||||
SHALL THE OPENLDAP FOUNDATION, ITS CONTRIBUTORS, OR THE AUTHOR(S)
|
||||
OR OWNER(S) OF THE SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
The names of the authors and copyright holders must not be used in
|
||||
advertising or otherwise to promote the sale, use or other dealing
|
||||
in this Software without specific, written prior permission. Title
|
||||
to copyright in this Software shall at all times remain with copyright
|
||||
holders.
|
||||
|
||||
OpenLDAP is a registered trademark of the OpenLDAP Foundation.
|
||||
|
||||
Copyright 1999-2003 The OpenLDAP Foundation, Redwood City,
|
||||
California, USA. All Rights Reserved. Permission to copy and
|
||||
distribute verbatim copies of this document is granted.
|
||||
-------------------------------------------------------------------------
|
24
Makefile
Normal file
24
Makefile
Normal file
|
@ -0,0 +1,24 @@
|
|||
# See LICENSE for licensing information.
|
||||
|
||||
MODULE = emdb
|
||||
|
||||
DIALYZER = dialyzer
|
||||
REBAR = rebar
|
||||
|
||||
.PHONY: build clean
|
||||
|
||||
all: ebin priv build
|
||||
|
||||
ebin:
|
||||
@mkdir -p $@
|
||||
|
||||
priv:
|
||||
@mkdir -p $@
|
||||
|
||||
build:
|
||||
@$(REBAR) compile
|
||||
|
||||
clean:
|
||||
@$(REBAR) clean
|
||||
@rm -f *~ */*~ erl_crash.dump
|
||||
@rm -rf ebin priv
|
97
README.md
97
README.md
|
@ -1,4 +1,95 @@
|
|||
emdb
|
||||
====
|
||||
EMDB
====
EMDB is a NIF library for the [Memory-Mapped Database](http://highlandsun.com/hyc/mdb/) database, aka. MDB.
The main purpose of this package is to provide a **very fast** Riak [backend](http://wiki.basho.com/Storage-Backends.html).
|
||||
|
||||
But this module could also be used as a general key-value store to replace:
|
||||
|
||||
* [DETS](http://www.erlang.org/doc/man/dets.html)
|
||||
* TokyoCabinet: [TCERL](http://code.google.com/p/tcerl/)
|
||||
* [QDBM](http://fallabs.com/qdbm/)
|
||||
* [Bitcask](https://github.com/basho/bitcask)
|
||||
* [eLevelDB](https://github.com/basho/eleveldb)
|
||||
* [BerkleyDB](http://www.oracle.com/technetwork/products/berkeleydb/overview/index.html)
|
||||
* ...
Requirements
------------
|
||||
* Erlang R14B04+
* GCC 4.2+ or MS VisualStudio 2010+
Build
-----
$ make
API
---
|
||||
The following functions were implemented:
|
||||
|
||||
* `open/1`: same as emdb:open(DBName, []).
|
||||
* `open/2`: creates a new MDB database. This call also re-open an already existing one. The second argument lets you tune the database. See [mdb.h](http://gitorious.org/mdb/mdb/blobs/master/libraries/libmdb/mdb.h)
|
||||
* `close/2`: closes the database
|
||||
* `put/2`: inserts Key with value Val into the database. Assumes that the key is not present, 'key_exit' is returned otherwise.
|
||||
* `get/1`: retrieves the value stored with Key in the database.
|
||||
* `del/1`: Removes the key-value with key Key from database.
|
||||
* `update/2`: inserts Key with value Val into the database if the key is not present, otherwise updates Key to value Val.
|
||||
* `drop/1`: deletes all key-value pairs in the database.
|
||||
|
||||
|
||||
Usage
-----
$ make
|
||||
|
||||
$ ./start.sh
|
||||
|
||||
%% create a new database
1> {ok, Handle} = emdb:open("/tmp/emdb1").
|
||||
|
||||
%% insert the key <<"a">> with value <<"1">>
2> ok = Handle:put(<<"a">>, <<"1">>).
|
||||
|
||||
%% try to re-insert the same key <<"a">>
3> key_exist = Handle:put(<<"a">>, <<"2">>).
|
||||
|
||||
%% add a new key-value pair
4> ok = Handle:put(<<"b">>, <<"2">>).
|
||||
|
||||
%% search a non-existing key <<"c">>
5> none = Handle:get(<<"c">>).
|
||||
|
||||
%% retrieve the value for key <<"b">>
6> {ok, <<"2">>} = Handle:get(<<"b">>).
|
||||
|
||||
%% retrieve the value for key <<"a">>
7> {ok, <<"1">>} = Handle:get(<<"a">>).
|
||||
|
||||
%% delete key <<"b">>
8> ok = Handle:del(<<"b">>).
|
||||
|
||||
%% search a non-existing key <<"b">>
|
||||
9> none = Handle:get(<<"b">>).
|
||||
|
||||
%% delete a non-existing key <<"z">>
10> none = Handle:del(<<"z">>).
|
||||
11> {ok, <<"1">>} = Handle:get(<<"a">>).
|
||||
%% update the value for key <<"a">>
|
||||
12> ok = Handle:update(<<"a">>, <<"7">>).
|
||||
|
||||
%% check the new value for key <<"a">>
|
||||
13> {ok, <<"7">>} = Handle:get(<<"a">>).
|
||||
|
||||
%% delete all key-value pairs in the database
14> ok = Handle:drop().
|
||||
|
||||
%% try to retrieve key <<"a">> value
15> none = Handle:get(<<"a">>).
|
||||
|
||||
%% close the database
16> ok = Handle:close().
|
||||
|
||||
17> q().
|
||||
|
||||
|
||||
Performance
-----------
For maximum speed, this library use only binaries for both keys and values.
|
||||
See the impressive [microbench](http://highlandsun.com/hyc/mdb/microbench/) against:
|
||||
|
||||
* Google's LevelDB
|
||||
* SQLite
|
||||
* Kyoto TreeDB
|
||||
* BerkeleyDB
|
||||
|
||||
MDB performs better on 64-bit arch.
|
||||
|
||||
|
||||
Supported OSes
--------------
|
||||
|
||||
Should work on 32/64-bit architectures:
|
||||
|
||||
* Linux
|
||||
* OSX
|
||||
* FreeBSD
|
||||
* Windows
|
||||
|
||||
TODO
----
|
||||
|
||||
* Unit tests
* PropEr testing
|
||||
* Bulk "writing"
|
||||
|
||||
Volunteers are always welcome!
Status
|
||||
------
|
||||
#### Work in progress. Don't use it in production!
|
||||
LICENSE
-------
|
||||
EMDB is Copyright (C) 2012 by Aleph Archives, and released under the [OpenLDAP](http://www.OpenLDAP.org/license.html) License.
|
||||
|
||||
EMDB is a NIF library for the Memory-Mapped Database database
|
469
c_src/emdb_drv.c
Normal file
469
c_src/emdb_drv.c
Normal file
|
@ -0,0 +1,469 @@
|
|||
/* -------------------------------------------------------------------------
|
||||
* This file is part of EMDB - Erlang MDB API
|
||||
*
|
||||
* Copyright (c) 2012 by Aleph Archives. All rights reserved.
|
||||
*
|
||||
* -------------------------------------------------------------------------
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
* -------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* C headers
|
||||
*/
|
||||
|
||||
#include <sys/param.h> /* for MAXPATHLEN constant */
|
||||
#include <erl_nif.h> /* for Erlang NIF interface */
|
||||
#include "uthash.h" /* for uthash */
|
||||
#include "mdb.h" /* for MDB interface */
|
||||
|
||||
|
||||
|
||||
#define FREE(p) (NULL == (p) ? 0 : (free(p), p = NULL))
|
||||
|
||||
#define FAIL_FAST(Error, Goto) \
|
||||
do{ \
|
||||
err = Error; \
|
||||
goto Goto; \
|
||||
}while(0)
|
||||
|
||||
|
||||
struct emdb_map_t {
|
||||
MDB_env * env;
|
||||
MDB_dbi dbi;
|
||||
|
||||
UT_hash_handle hh;
|
||||
};
|
||||
|
||||
|
||||
static ERL_NIF_TERM atom_ok;
|
||||
static ERL_NIF_TERM atom_none;
|
||||
|
||||
|
||||
static struct emdb_map_t * emdb_map = NULL;
|
||||
|
||||
|
||||
/* emdb ret */
|
||||
#define EMDB_RET_KEY_EXIST "key_exist"
|
||||
|
||||
/* emdb errors */
|
||||
#define EMDB_MALLOC_ERR "error_malloc"
|
||||
#define EMDB_CREATE_ERR "error_create"
|
||||
#define EMDB_MAPSIZE_ERR "error_mapsize"
|
||||
#define EMDB_OPEN_ERR "error_open"
|
||||
#define EMDB_TXN_BEGIN_ERR "error_txn_begin"
|
||||
#define EMDB_TXN_COMMIT_ERR "error_txn_commit"
|
||||
#define EMDB_OPEN_DBI_ERR "error_open_dbi"
|
||||
#define EMDB_INVALID_HANDLE_ERR "error_invalid_handle"
|
||||
#define EMDB_PUT_ERR "error_put"
|
||||
#define EMDB_UPDATE_ERR "error_update"
|
||||
#define EMDB_KEY_NOT_FOUND "error_key_not_found"
|
||||
#define EMDB_DROP_ERR "error_drop"
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Error handling callbacks
|
||||
*/
|
||||
|
||||
static void emdb_free (struct emdb_map_t * emdb_obj)
|
||||
{
|
||||
FREE(emdb_obj);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Driver callbacks
|
||||
*/
|
||||
|
||||
static ERL_NIF_TERM emdb_open_nif (ErlNifEnv * env,
|
||||
int argc, const ERL_NIF_TERM argv[])
|
||||
{
|
||||
char dirname [MAXPATHLEN];
|
||||
struct emdb_map_t * node;
|
||||
MDB_txn * txn;
|
||||
char * err;
|
||||
|
||||
if (enif_get_string(env, argv[0], dirname, MAXPATHLEN, ERL_NIF_LATIN1) <= 0)
|
||||
return enif_make_badarg(env);
|
||||
|
||||
if(! (node = calloc(1, sizeof(struct emdb_map_t))))
|
||||
FAIL_FAST(EMDB_MALLOC_ERR, err3);
|
||||
|
||||
if (mdb_env_create(& (node -> env)))
|
||||
FAIL_FAST(EMDB_CREATE_ERR, err2);
|
||||
|
||||
if (mdb_env_set_mapsize(node -> env, 10485760))
|
||||
FAIL_FAST(EMDB_MAPSIZE_ERR, err2);
|
||||
|
||||
if (mdb_env_open(node -> env, dirname, MDB_FIXEDMAP, 0664))
|
||||
FAIL_FAST(EMDB_OPEN_ERR, err2);
|
||||
|
||||
if (mdb_txn_begin(node -> env, NULL, 0, & txn))
|
||||
FAIL_FAST(EMDB_TXN_BEGIN_ERR, err2);
|
||||
|
||||
if (mdb_open(txn, NULL, 0, & (node -> dbi)))
|
||||
FAIL_FAST(EMDB_OPEN_DBI_ERR, err1);
|
||||
|
||||
if (mdb_txn_commit(txn))
|
||||
FAIL_FAST(EMDB_TXN_COMMIT_ERR, err1);
|
||||
|
||||
HASH_ADD_PTR(emdb_map, env, node);
|
||||
|
||||
return enif_make_tuple(env, 2,
|
||||
atom_ok,
|
||||
enif_make_ulong(env, (unsigned long) node -> env));
|
||||
|
||||
err1:
|
||||
mdb_txn_abort(txn);
|
||||
err2:
|
||||
mdb_env_close(node -> env);
|
||||
err3:
|
||||
emdb_free(node);
|
||||
|
||||
return enif_make_atom(env, err);
|
||||
}
|
||||
|
||||
static ERL_NIF_TERM emdb_close_nif (ErlNifEnv * env,
|
||||
int argc, const ERL_NIF_TERM argv[])
|
||||
{
|
||||
MDB_env * handle;
|
||||
struct emdb_map_t * node;
|
||||
unsigned long addr;
|
||||
|
||||
if (! enif_get_ulong(env, argv[0], & addr))
|
||||
return enif_make_badarg(env);
|
||||
|
||||
handle = (MDB_env *) addr;
|
||||
|
||||
HASH_FIND_PTR(emdb_map, & handle, node);
|
||||
if (NULL == node)
|
||||
return enif_make_atom(env, EMDB_INVALID_HANDLE_ERR);
|
||||
|
||||
HASH_DEL(emdb_map, node);
|
||||
|
||||
mdb_env_close(node -> env);
|
||||
emdb_free(node);
|
||||
|
||||
return atom_ok;
|
||||
}
|
||||
|
||||
|
||||
static ERL_NIF_TERM emdb_put_nif (ErlNifEnv * env,
|
||||
int argc, const ERL_NIF_TERM argv[])
|
||||
{
|
||||
ErlNifBinary key;
|
||||
ErlNifBinary val;
|
||||
|
||||
MDB_val mkey;
|
||||
MDB_val mdata;
|
||||
|
||||
MDB_env * handle;
|
||||
MDB_txn * txn;
|
||||
|
||||
struct emdb_map_t * node;
|
||||
unsigned long addr;
|
||||
char * err;
|
||||
int ret;
|
||||
|
||||
if (! enif_get_ulong(env, argv[0], & addr))
|
||||
return enif_make_badarg(env);
|
||||
|
||||
handle = (MDB_env *) addr;
|
||||
|
||||
HASH_FIND_PTR(emdb_map, & handle, node);
|
||||
if (NULL == node)
|
||||
return enif_make_atom(env, EMDB_INVALID_HANDLE_ERR);
|
||||
|
||||
if (! enif_inspect_iolist_as_binary(env, argv[1], &key))
|
||||
return enif_make_badarg(env);
|
||||
|
||||
if (! enif_inspect_iolist_as_binary(env, argv[2], &val))
|
||||
return enif_make_badarg(env);
|
||||
|
||||
if (mdb_txn_begin(node -> env, NULL, 0, & txn))
|
||||
FAIL_FAST(EMDB_TXN_BEGIN_ERR, err2);
|
||||
|
||||
mkey.mv_size = key.size;
|
||||
mkey.mv_data = key.data;
|
||||
mdata.mv_size = val.size;
|
||||
mdata.mv_data = val.data;
|
||||
|
||||
ret = mdb_put(txn, node -> dbi, & mkey, & mdata, MDB_NOOVERWRITE);
|
||||
if (MDB_KEYEXIST == ret)
|
||||
FAIL_FAST(EMDB_RET_KEY_EXIST, err1);
|
||||
if (ret)
|
||||
FAIL_FAST(EMDB_PUT_ERR, err1);
|
||||
|
||||
if (mdb_txn_commit(txn))
|
||||
FAIL_FAST(EMDB_TXN_COMMIT_ERR, err1);
|
||||
|
||||
return atom_ok;
|
||||
|
||||
err1:
|
||||
mdb_txn_abort(txn);
|
||||
err2:
|
||||
return enif_make_atom(env, err);
|
||||
}
|
||||
|
||||
|
||||
static ERL_NIF_TERM emdb_get_nif (ErlNifEnv * env,
|
||||
int argc, const ERL_NIF_TERM argv[])
|
||||
{
|
||||
ErlNifBinary key;
|
||||
ErlNifBinary val;
|
||||
|
||||
MDB_val mkey;
|
||||
MDB_val mdata;
|
||||
|
||||
MDB_env * handle;
|
||||
MDB_txn * txn;
|
||||
|
||||
struct emdb_map_t * node;
|
||||
char * err;
|
||||
unsigned long addr;
|
||||
|
||||
if (! enif_get_ulong(env, argv[0], & addr))
|
||||
return enif_make_badarg(env);
|
||||
|
||||
handle = (MDB_env *) addr;
|
||||
|
||||
HASH_FIND_PTR(emdb_map, & handle, node);
|
||||
if (NULL == node)
|
||||
return enif_make_atom(env, EMDB_INVALID_HANDLE_ERR);
|
||||
|
||||
if (! enif_inspect_iolist_as_binary(env, argv[1], &key))
|
||||
return enif_make_badarg(env);
|
||||
|
||||
mkey.mv_size = key.size;
|
||||
mkey.mv_data = key.data;
|
||||
|
||||
if (mdb_txn_begin(node -> env, NULL, 0, & txn))
|
||||
FAIL_FAST(EMDB_TXN_BEGIN_ERR, err1);
|
||||
|
||||
if(mdb_get(txn, node -> dbi, & mkey, & mdata))
|
||||
{
|
||||
mdb_txn_abort(txn);
|
||||
return atom_none;
|
||||
}
|
||||
|
||||
if (! enif_alloc_binary(mdata.mv_size, & val))
|
||||
FAIL_FAST(EMDB_MALLOC_ERR, err2);
|
||||
|
||||
memcpy(val.data, mdata.mv_data, mdata.mv_size);
|
||||
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
return enif_make_tuple(env, 2,
|
||||
atom_ok,
|
||||
enif_make_binary(env, & val));
|
||||
|
||||
err2:
|
||||
mdb_txn_abort(txn);
|
||||
err1:
|
||||
return enif_make_atom(env, err);
|
||||
}
|
||||
|
||||
|
||||
static ERL_NIF_TERM emdb_del_nif (ErlNifEnv * env,
|
||||
int argc, const ERL_NIF_TERM argv[])
|
||||
{
|
||||
ErlNifBinary key;
|
||||
|
||||
MDB_val mkey;
|
||||
|
||||
MDB_env * handle;
|
||||
MDB_txn * txn;
|
||||
|
||||
struct emdb_map_t * node;
|
||||
char * err;
|
||||
unsigned long addr;
|
||||
int ret;
|
||||
|
||||
if (! enif_get_ulong(env, argv[0], & addr))
|
||||
return enif_make_badarg(env);
|
||||
|
||||
handle = (MDB_env *) addr;
|
||||
|
||||
HASH_FIND_PTR(emdb_map, & handle, node);
|
||||
if (NULL == node)
|
||||
return enif_make_atom(env, EMDB_INVALID_HANDLE_ERR);
|
||||
|
||||
if (! enif_inspect_iolist_as_binary(env, argv[1], &key))
|
||||
return enif_make_badarg(env);
|
||||
|
||||
mkey.mv_size = key.size;
|
||||
mkey.mv_data = key.data;
|
||||
|
||||
if (mdb_txn_begin(node -> env, NULL, 0, & txn))
|
||||
FAIL_FAST(EMDB_TXN_BEGIN_ERR, err);
|
||||
|
||||
ret = mdb_del(txn, node -> dbi, & mkey, NULL);
|
||||
|
||||
if (mdb_txn_commit(txn))
|
||||
FAIL_FAST(EMDB_TXN_COMMIT_ERR, err);
|
||||
|
||||
if(ret)
|
||||
return atom_none;
|
||||
|
||||
return atom_ok;
|
||||
|
||||
err:
|
||||
return enif_make_atom(env, err);
|
||||
}
|
||||
|
||||
|
||||
static ERL_NIF_TERM emdb_update_nif (ErlNifEnv * env,
|
||||
int argc, const ERL_NIF_TERM argv[])
|
||||
{
|
||||
ErlNifBinary key;
|
||||
ErlNifBinary val;
|
||||
|
||||
MDB_val mkey;
|
||||
MDB_val mdata;
|
||||
|
||||
MDB_env * handle;
|
||||
MDB_txn * txn;
|
||||
|
||||
struct emdb_map_t * node;
|
||||
unsigned long addr;
|
||||
char * err;
|
||||
|
||||
if (! enif_get_ulong(env, argv[0], & addr))
|
||||
return enif_make_badarg(env);
|
||||
|
||||
handle = (MDB_env *) addr;
|
||||
|
||||
HASH_FIND_PTR(emdb_map, & handle, node);
|
||||
if (NULL == node)
|
||||
return enif_make_atom(env, EMDB_INVALID_HANDLE_ERR);
|
||||
|
||||
if (! enif_inspect_iolist_as_binary(env, argv[1], &key))
|
||||
return enif_make_badarg(env);
|
||||
|
||||
if (! enif_inspect_iolist_as_binary(env, argv[2], &val))
|
||||
return enif_make_badarg(env);
|
||||
|
||||
if (mdb_txn_begin(node -> env, NULL, 0, & txn))
|
||||
FAIL_FAST(EMDB_TXN_BEGIN_ERR, err2);
|
||||
|
||||
mkey.mv_size = key.size;
|
||||
mkey.mv_data = key.data;
|
||||
mdata.mv_size = val.size;
|
||||
mdata.mv_data = val.data;
|
||||
|
||||
if (mdb_put(txn, node -> dbi, & mkey, & mdata, 0))
|
||||
FAIL_FAST(EMDB_UPDATE_ERR, err1);
|
||||
|
||||
if (mdb_txn_commit(txn))
|
||||
FAIL_FAST(EMDB_TXN_COMMIT_ERR, err1);
|
||||
|
||||
return atom_ok;
|
||||
|
||||
err1:
|
||||
mdb_txn_abort(txn);
|
||||
err2:
|
||||
return enif_make_atom(env, err);
|
||||
}
|
||||
|
||||
|
||||
static ERL_NIF_TERM emdb_drop_nif (ErlNifEnv * env,
|
||||
int argc, const ERL_NIF_TERM argv[])
|
||||
{
|
||||
MDB_env * handle;
|
||||
MDB_txn * txn;
|
||||
struct emdb_map_t * node;
|
||||
unsigned long addr;
|
||||
char * err;
|
||||
int ret;
|
||||
|
||||
if (! enif_get_ulong(env, argv[0], & addr))
|
||||
return enif_make_badarg(env);
|
||||
|
||||
handle = (MDB_env *) addr;
|
||||
|
||||
HASH_FIND_PTR(emdb_map, & handle, node);
|
||||
if (NULL == node)
|
||||
return enif_make_atom(env, EMDB_INVALID_HANDLE_ERR);
|
||||
|
||||
if (mdb_txn_begin(node -> env, NULL, 0, & txn))
|
||||
FAIL_FAST(EMDB_TXN_BEGIN_ERR, err2);
|
||||
|
||||
ret = mdb_drop(txn, node -> dbi, 0);
|
||||
if (ret)
|
||||
FAIL_FAST(EMDB_DROP_ERR, err1);
|
||||
|
||||
if (mdb_txn_commit(txn))
|
||||
FAIL_FAST(EMDB_TXN_COMMIT_ERR, err1);
|
||||
|
||||
return atom_ok;
|
||||
|
||||
err1:
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
err2:
|
||||
return enif_make_atom(env, err);
|
||||
}
|
||||
|
||||
|
||||
static int emdb_load(ErlNifEnv* env, void** priv, ERL_NIF_TERM load_info)
|
||||
{
|
||||
atom_ok = enif_make_atom(env, "ok");
|
||||
atom_none = enif_make_atom(env, "none");
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int emdb_reload(ErlNifEnv* env, void** priv, ERL_NIF_TERM info)
|
||||
{
|
||||
return (0);
|
||||
}
|
||||
|
||||
|
||||
static int emdb_upgrade(ErlNifEnv* env, void** priv, void** old_priv, ERL_NIF_TERM load_info)
|
||||
{
|
||||
return (0);
|
||||
}
|
||||
|
||||
|
||||
static void emdb_unload(ErlNifEnv* env, void* priv)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static ErlNifFunc nif_funcs [] = {
|
||||
{"open", 1, emdb_open_nif},
|
||||
{"close", 1, emdb_close_nif},
|
||||
{"put", 3, emdb_put_nif},
|
||||
{"get", 2, emdb_get_nif},
|
||||
{"del", 2, emdb_del_nif},
|
||||
{"update", 3, emdb_update_nif},
|
||||
{"drop", 1, emdb_drop_nif}
|
||||
};
|
||||
|
||||
/* driver entry point */
|
||||
ERL_NIF_INIT(emdb_drv,
|
||||
nif_funcs,
|
||||
& emdb_load,
|
||||
& emdb_reload,
|
||||
& emdb_upgrade,
|
||||
& emdb_unload)
|
6763
c_src/mdb.c
Normal file
6763
c_src/mdb.c
Normal file
File diff suppressed because it is too large
Load diff
1065
c_src/mdb.h
Normal file
1065
c_src/mdb.h
Normal file
File diff suppressed because it is too large
Load diff
327
c_src/midl.c
Normal file
327
c_src/midl.c
Normal file
|
@ -0,0 +1,327 @@
|
|||
/** @file midl.c
|
||||
* @brief ldap bdb back-end ID List functions */
|
||||
/* $OpenLDAP$ */
|
||||
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
|
||||
*
|
||||
* Copyright 2000-2012 The OpenLDAP Foundation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
#include <assert.h>
|
||||
#include "midl.h"
|
||||
|
||||
/** @defgroup internal MDB Internals
|
||||
* @{
|
||||
*/
|
||||
/** @defgroup idls ID List Management
|
||||
* @{
|
||||
*/
|
||||
#define CMP(x,y) ( (x) < (y) ? -1 : (x) > (y) )
|
||||
|
||||
#if 0 /* superseded by append/sort */
|
||||
static unsigned mdb_midl_search( MDB_IDL ids, MDB_ID id )
|
||||
{
|
||||
/*
|
||||
* binary search of id in ids
|
||||
* if found, returns position of id
|
||||
* if not found, returns first position greater than id
|
||||
*/
|
||||
unsigned base = 0;
|
||||
unsigned cursor = 1;
|
||||
int val = 0;
|
||||
unsigned n = ids[0];
|
||||
|
||||
while( 0 < n ) {
|
||||
unsigned pivot = n >> 1;
|
||||
cursor = base + pivot + 1;
|
||||
val = CMP( ids[cursor], id );
|
||||
|
||||
if( val < 0 ) {
|
||||
n = pivot;
|
||||
|
||||
} else if ( val > 0 ) {
|
||||
base = cursor;
|
||||
n -= pivot + 1;
|
||||
|
||||
} else {
|
||||
return cursor;
|
||||
}
|
||||
}
|
||||
|
||||
if( val > 0 ) {
|
||||
++cursor;
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
|
||||
int mdb_midl_insert( MDB_IDL ids, MDB_ID id )
|
||||
{
|
||||
unsigned x, i;
|
||||
|
||||
if (MDB_IDL_IS_RANGE( ids )) {
|
||||
/* if already in range, treat as a dup */
|
||||
if (id >= MDB_IDL_RANGE_FIRST(ids) && id <= MDB_IDL_RANGE_LAST(ids))
|
||||
return -1;
|
||||
if (id < MDB_IDL_RANGE_FIRST(ids))
|
||||
ids[1] = id;
|
||||
else if (id > MDB_IDL_RANGE_LAST(ids))
|
||||
ids[2] = id;
|
||||
return 0;
|
||||
}
|
||||
|
||||
x = mdb_midl_search( ids, id );
|
||||
assert( x > 0 );
|
||||
|
||||
if( x < 1 ) {
|
||||
/* internal error */
|
||||
return -2;
|
||||
}
|
||||
|
||||
if ( x <= ids[0] && ids[x] == id ) {
|
||||
/* duplicate */
|
||||
assert(0);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ( ++ids[0] >= MDB_IDL_DB_MAX ) {
|
||||
if( id < ids[1] ) {
|
||||
ids[1] = id;
|
||||
ids[2] = ids[ids[0]-1];
|
||||
} else if ( ids[ids[0]-1] < id ) {
|
||||
ids[2] = id;
|
||||
} else {
|
||||
ids[2] = ids[ids[0]-1];
|
||||
}
|
||||
ids[0] = MDB_NOID;
|
||||
|
||||
} else {
|
||||
/* insert id */
|
||||
for (i=ids[0]; i>x; i--)
|
||||
ids[i] = ids[i-1];
|
||||
ids[x] = id;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
MDB_IDL mdb_midl_alloc()
|
||||
{
|
||||
MDB_IDL ids = malloc((MDB_IDL_UM_MAX+1) * sizeof(MDB_ID));
|
||||
*ids++ = MDB_IDL_UM_MAX;
|
||||
return ids;
|
||||
}
|
||||
|
||||
void mdb_midl_free(MDB_IDL ids)
|
||||
{
|
||||
free(ids-1);
|
||||
}
|
||||
|
||||
int mdb_midl_shrink( MDB_IDL *idp )
|
||||
{
|
||||
MDB_IDL ids = *idp;
|
||||
if (ids[-1] > MDB_IDL_UM_MAX) {
|
||||
ids = realloc(ids, (MDB_IDL_UM_MAX+1) * sizeof(MDB_ID));
|
||||
*ids++ = MDB_IDL_UM_MAX;
|
||||
*idp = ids;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mdb_midl_append( MDB_IDL *idp, MDB_ID id )
|
||||
{
|
||||
MDB_IDL ids = *idp;
|
||||
/* Too big? */
|
||||
if (ids[0] >= ids[-1]) {
|
||||
MDB_IDL idn = ids-1;
|
||||
/* grow it */
|
||||
idn = realloc(idn, (*idn + MDB_IDL_UM_MAX + 1) * sizeof(MDB_ID));
|
||||
if (!idn)
|
||||
return -1;
|
||||
*idn++ += MDB_IDL_UM_MAX;
|
||||
ids = idn;
|
||||
*idp = ids;
|
||||
}
|
||||
ids[0]++;
|
||||
ids[ids[0]] = id;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mdb_midl_append_list( MDB_IDL *idp, MDB_IDL app )
|
||||
{
|
||||
MDB_IDL ids = *idp;
|
||||
/* Too big? */
|
||||
if (ids[0] + app[0] >= ids[-1]) {
|
||||
MDB_IDL idn = ids-1;
|
||||
/* grow it */
|
||||
idn = realloc(idn, (*idn + app[-1]) * sizeof(MDB_ID));
|
||||
if (!idn)
|
||||
return -1;
|
||||
*idn++ += app[-1];
|
||||
ids = idn;
|
||||
*idp = ids;
|
||||
}
|
||||
memcpy(&ids[ids[0]+1], &app[1], app[0] * sizeof(MDB_ID));
|
||||
ids[0] += app[0];
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Quicksort + Insertion sort for small arrays */
|
||||
|
||||
#define SMALL 8
|
||||
#define SWAP(a,b) { itmp=(a); (a)=(b); (b)=itmp; }
|
||||
|
||||
void
|
||||
mdb_midl_sort( MDB_IDL ids )
|
||||
{
|
||||
/* Max possible depth of int-indexed tree * 2 items/level */
|
||||
int istack[sizeof(int)*CHAR_BIT * 2];
|
||||
int i,j,k,l,ir,jstack;
|
||||
MDB_ID a, itmp;
|
||||
|
||||
ir = ids[0];
|
||||
l = 1;
|
||||
jstack = 0;
|
||||
for(;;) {
|
||||
if (ir - l < SMALL) { /* Insertion sort */
|
||||
for (j=l+1;j<=ir;j++) {
|
||||
a = ids[j];
|
||||
for (i=j-1;i>=1;i--) {
|
||||
if (ids[i] >= a) break;
|
||||
ids[i+1] = ids[i];
|
||||
}
|
||||
ids[i+1] = a;
|
||||
}
|
||||
if (jstack == 0) break;
|
||||
ir = istack[jstack--];
|
||||
l = istack[jstack--];
|
||||
} else {
|
||||
k = (l + ir) >> 1; /* Choose median of left, center, right */
|
||||
SWAP(ids[k], ids[l+1]);
|
||||
if (ids[l] < ids[ir]) {
|
||||
SWAP(ids[l], ids[ir]);
|
||||
}
|
||||
if (ids[l+1] < ids[ir]) {
|
||||
SWAP(ids[l+1], ids[ir]);
|
||||
}
|
||||
if (ids[l] < ids[l+1]) {
|
||||
SWAP(ids[l], ids[l+1]);
|
||||
}
|
||||
i = l+1;
|
||||
j = ir;
|
||||
a = ids[l+1];
|
||||
for(;;) {
|
||||
do i++; while(ids[i] > a);
|
||||
do j--; while(ids[j] < a);
|
||||
if (j < i) break;
|
||||
SWAP(ids[i],ids[j]);
|
||||
}
|
||||
ids[l+1] = ids[j];
|
||||
ids[j] = a;
|
||||
jstack += 2;
|
||||
if (ir-i+1 >= j-1) {
|
||||
istack[jstack] = ir;
|
||||
istack[jstack-1] = i;
|
||||
ir = j-1;
|
||||
} else {
|
||||
istack[jstack] = j-1;
|
||||
istack[jstack-1] = l;
|
||||
l = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned mdb_mid2l_search( MDB_ID2L ids, MDB_ID id )
|
||||
{
|
||||
/*
|
||||
* binary search of id in ids
|
||||
* if found, returns position of id
|
||||
* if not found, returns first position greater than id
|
||||
*/
|
||||
unsigned base = 0;
|
||||
unsigned cursor = 1;
|
||||
int val = 0;
|
||||
unsigned n = ids[0].mid;
|
||||
|
||||
while( 0 < n ) {
|
||||
unsigned pivot = n >> 1;
|
||||
cursor = base + pivot + 1;
|
||||
val = CMP( id, ids[cursor].mid );
|
||||
|
||||
if( val < 0 ) {
|
||||
n = pivot;
|
||||
|
||||
} else if ( val > 0 ) {
|
||||
base = cursor;
|
||||
n -= pivot + 1;
|
||||
|
||||
} else {
|
||||
return cursor;
|
||||
}
|
||||
}
|
||||
|
||||
if( val > 0 ) {
|
||||
++cursor;
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
|
||||
int mdb_mid2l_insert( MDB_ID2L ids, MDB_ID2 *id )
|
||||
{
|
||||
unsigned x, i;
|
||||
|
||||
x = mdb_mid2l_search( ids, id->mid );
|
||||
assert( x > 0 );
|
||||
|
||||
if( x < 1 ) {
|
||||
/* internal error */
|
||||
return -2;
|
||||
}
|
||||
|
||||
if ( x <= ids[0].mid && ids[x].mid == id->mid ) {
|
||||
/* duplicate */
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ( ids[0].mid >= MDB_IDL_UM_MAX ) {
|
||||
/* too big */
|
||||
return -2;
|
||||
|
||||
} else {
|
||||
/* insert id */
|
||||
ids[0].mid++;
|
||||
for (i=ids[0].mid; i>x; i--)
|
||||
ids[i] = ids[i-1];
|
||||
ids[x] = *id;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mdb_mid2l_append( MDB_ID2L ids, MDB_ID2 *id )
|
||||
{
|
||||
/* Too big? */
|
||||
if (ids[0].mid >= MDB_IDL_UM_MAX) {
|
||||
return -2;
|
||||
}
|
||||
ids[0].mid++;
|
||||
ids[ids[0].mid] = *id;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** @} */
|
||||
/** @} */
|
195
c_src/midl.h
Normal file
195
c_src/midl.h
Normal file
|
@ -0,0 +1,195 @@
|
|||
/** @file midl.h
|
||||
* @brief mdb ID List header file.
|
||||
*
|
||||
* This file was originally part of back-bdb but has been
|
||||
* modified for use in libmdb. Most of the macros defined
|
||||
* in this file are unused, just left over from the original.
|
||||
*
|
||||
* This file is only used internally in libmdb and its definitions
|
||||
* are not exposed publicly.
|
||||
*/
|
||||
/* $OpenLDAP$ */
|
||||
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
|
||||
*
|
||||
* Copyright 2000-2012 The OpenLDAP Foundation.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
#ifndef _MDB_MIDL_H_
|
||||
#define _MDB_MIDL_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** @defgroup internal MDB Internals
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** @defgroup idls ID List Management
|
||||
* @{
|
||||
*/
|
||||
/** A generic ID number. These were entryIDs in back-bdb.
|
||||
* Preferably it should have the same size as a pointer.
|
||||
*/
|
||||
typedef size_t MDB_ID;
|
||||
|
||||
/** An IDL is an ID List, a sorted array of IDs. The first
|
||||
* element of the array is a counter for how many actual
|
||||
* IDs are in the list. In the original back-bdb code, IDLs are
|
||||
* sorted in ascending order. For libmdb IDLs are sorted in
|
||||
* descending order.
|
||||
*/
|
||||
typedef MDB_ID *MDB_IDL;
|
||||
|
||||
#define MDB_NOID (~(MDB_ID)0)
|
||||
|
||||
/* IDL sizes - likely should be even bigger
|
||||
* limiting factors: sizeof(ID), thread stack size
|
||||
*/
|
||||
#define MDB_IDL_LOGN 16 /* DB_SIZE is 2^16, UM_SIZE is 2^17 */
|
||||
#define MDB_IDL_DB_SIZE (1<<MDB_IDL_LOGN)
|
||||
#define MDB_IDL_UM_SIZE (1<<(MDB_IDL_LOGN+1))
|
||||
#define MDB_IDL_UM_SIZEOF (MDB_IDL_UM_SIZE * sizeof(MDB_ID))
|
||||
|
||||
#define MDB_IDL_DB_MAX (MDB_IDL_DB_SIZE-1)
|
||||
|
||||
#define MDB_IDL_UM_MAX (MDB_IDL_UM_SIZE-1)
|
||||
|
||||
#define MDB_IDL_IS_RANGE(ids) ((ids)[0] == MDB_NOID)
|
||||
#define MDB_IDL_RANGE_SIZE (3)
|
||||
#define MDB_IDL_RANGE_SIZEOF (MDB_IDL_RANGE_SIZE * sizeof(MDB_ID))
|
||||
#define MDB_IDL_SIZEOF(ids) ((MDB_IDL_IS_RANGE(ids) \
|
||||
? MDB_IDL_RANGE_SIZE : ((ids)[0]+1)) * sizeof(MDB_ID))
|
||||
|
||||
#define MDB_IDL_RANGE_FIRST(ids) ((ids)[1])
|
||||
#define MDB_IDL_RANGE_LAST(ids) ((ids)[2])
|
||||
|
||||
#define MDB_IDL_RANGE( ids, f, l ) \
|
||||
do { \
|
||||
(ids)[0] = MDB_NOID; \
|
||||
(ids)[1] = (f); \
|
||||
(ids)[2] = (l); \
|
||||
} while(0)
|
||||
|
||||
#define MDB_IDL_ZERO(ids) \
|
||||
do { \
|
||||
(ids)[0] = 0; \
|
||||
(ids)[1] = 0; \
|
||||
(ids)[2] = 0; \
|
||||
} while(0)
|
||||
|
||||
#define MDB_IDL_IS_ZERO(ids) ( (ids)[0] == 0 )
|
||||
#define MDB_IDL_IS_ALL( range, ids ) ( (ids)[0] == MDB_NOID \
|
||||
&& (ids)[1] <= (range)[1] && (range)[2] <= (ids)[2] )
|
||||
|
||||
#define MDB_IDL_CPY( dst, src ) (memcpy( dst, src, MDB_IDL_SIZEOF( src ) ))
|
||||
|
||||
#define MDB_IDL_ID( bdb, ids, id ) MDB_IDL_RANGE( ids, id, ((bdb)->bi_lastid) )
|
||||
#define MDB_IDL_ALL( bdb, ids ) MDB_IDL_RANGE( ids, 1, ((bdb)->bi_lastid) )
|
||||
|
||||
#define MDB_IDL_FIRST( ids ) ( (ids)[1] )
|
||||
#define MDB_IDL_LAST( ids ) ( MDB_IDL_IS_RANGE(ids) \
|
||||
? (ids)[2] : (ids)[(ids)[0]] )
|
||||
|
||||
#define MDB_IDL_N( ids ) ( MDB_IDL_IS_RANGE(ids) \
|
||||
? ((ids)[2]-(ids)[1])+1 : (ids)[0] )
|
||||
|
||||
#if 0 /* superseded by append/sort */
|
||||
/** Insert an ID into an IDL.
|
||||
* @param[in,out] ids The IDL to insert into.
|
||||
* @param[in] id The ID to insert.
|
||||
* @return 0 on success, -1 if the ID was already present in the IDL.
|
||||
*/
|
||||
int mdb_midl_insert( MDB_IDL ids, MDB_ID id );
|
||||
#endif
|
||||
|
||||
/** Allocate an IDL.
|
||||
* Allocates memory for an IDL of a default size.
|
||||
* @return IDL on success, NULL on failure.
|
||||
*/
|
||||
MDB_IDL mdb_midl_alloc();
|
||||
|
||||
/** Free an IDL.
|
||||
* @param[in] ids The IDL to free.
|
||||
*/
|
||||
void mdb_midl_free(MDB_IDL ids);
|
||||
|
||||
/** Shrink an IDL.
|
||||
* Return the IDL to the default size if it has grown larger.
|
||||
* @param[in,out] idp Address of the IDL to shrink.
|
||||
* @return 0 on no change, non-zero if shrunk.
|
||||
*/
|
||||
int mdb_midl_shrink(MDB_IDL *idp);
|
||||
|
||||
/** Append an ID onto an IDL.
|
||||
* @param[in,out] idp Address of the IDL to append to.
|
||||
* @param[in] id The ID to append.
|
||||
* @return 0 on success, -1 if the IDL is too large.
|
||||
*/
|
||||
int mdb_midl_append( MDB_IDL *idp, MDB_ID id );
|
||||
|
||||
/** Append an IDL onto an IDL.
|
||||
* @param[in,out] idp Address of the IDL to append to.
|
||||
* @param[in] app The IDL to append.
|
||||
* @return 0 on success, -1 if the IDL is too large.
|
||||
*/
|
||||
int mdb_midl_append_list( MDB_IDL *idp, MDB_IDL app );
|
||||
|
||||
/** Sort an IDL.
|
||||
* @param[in,out] ids The IDL to sort.
|
||||
*/
|
||||
void mdb_midl_sort( MDB_IDL ids );
|
||||
|
||||
/** An ID2 is an ID/pointer pair.
|
||||
*/
|
||||
typedef struct MDB_ID2 {
|
||||
MDB_ID mid; /**< The ID */
|
||||
void *mptr; /**< The pointer */
|
||||
} MDB_ID2;
|
||||
|
||||
/** An ID2L is an ID2 List, a sorted array of ID2s.
|
||||
* The first element's \b mid member is a count of how many actual
|
||||
* elements are in the array. The \b mptr member of the first element is unused.
|
||||
* The array is sorted in ascending order by \b mid.
|
||||
*/
|
||||
typedef MDB_ID2 *MDB_ID2L;
|
||||
|
||||
/** Search for an ID in an ID2L.
|
||||
* @param[in] ids The ID2L to search.
|
||||
* @param[in] id The ID to search for.
|
||||
* @return The index of the first ID2 whose \b mid member is greater than or equal to \b id.
|
||||
*/
|
||||
unsigned mdb_mid2l_search( MDB_ID2L ids, MDB_ID id );
|
||||
|
||||
|
||||
/** Insert an ID2 into a ID2L.
|
||||
* @param[in,out] ids The ID2L to insert into.
|
||||
* @param[in] id The ID2 to insert.
|
||||
* @return 0 on success, -1 if the ID was already present in the ID2L.
|
||||
*/
|
||||
int mdb_mid2l_insert( MDB_ID2L ids, MDB_ID2 *id );
|
||||
|
||||
/** Append an ID2 into a ID2L.
|
||||
* @param[in,out] ids The ID2L to append into.
|
||||
* @param[in] id The ID2 to append.
|
||||
* @return 0 on success, -2 if the ID2L is too big.
|
||||
*/
|
||||
int mdb_mid2l_append( MDB_ID2L ids, MDB_ID2 *id );
|
||||
|
||||
/** @} */
|
||||
/** @} */
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* _MDB_MIDL_H_ */
|
915
c_src/uthash.h
Normal file
915
c_src/uthash.h
Normal file
|
@ -0,0 +1,915 @@
|
|||
/*
|
||||
Copyright (c) 2003-2012, Troy D. Hanson http://uthash.sourceforge.net
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef UTHASH_H
|
||||
#define UTHASH_H
|
||||
|
||||
#include <string.h> /* memcmp,strlen */
|
||||
#include <stddef.h> /* ptrdiff_t */
|
||||
#include <stdlib.h> /* exit() */
|
||||
|
||||
/* These macros use decltype or the earlier __typeof GNU extension.
|
||||
As decltype is only available in newer compilers (VS2010 or gcc 4.3+
|
||||
when compiling c++ source) this code uses whatever method is needed
|
||||
or, for VS2008 where neither is available, uses casting workarounds. */
|
||||
#ifdef _MSC_VER /* MS compiler */
|
||||
#if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */
|
||||
#define DECLTYPE(x) (decltype(x))
|
||||
#else /* VS2008 or older (or VS2010 in C mode) */
|
||||
#define NO_DECLTYPE
|
||||
#define DECLTYPE(x)
|
||||
#endif
|
||||
#else /* GNU, Sun and other compilers */
|
||||
#define DECLTYPE(x) (__typeof(x))
|
||||
#endif
|
||||
|
||||
#ifdef NO_DECLTYPE
|
||||
#define DECLTYPE_ASSIGN(dst,src) \
|
||||
do { \
|
||||
char **_da_dst = (char**)(&(dst)); \
|
||||
*_da_dst = (char*)(src); \
|
||||
} while(0)
|
||||
#else
|
||||
#define DECLTYPE_ASSIGN(dst,src) \
|
||||
do { \
|
||||
(dst) = DECLTYPE(dst)(src); \
|
||||
} while(0)
|
||||
#endif
|
||||
|
||||
/* a number of the hash function use uint32_t which isn't defined on win32 */
|
||||
#ifdef _MSC_VER
|
||||
typedef unsigned int uint32_t;
|
||||
typedef unsigned char uint8_t;
|
||||
#else
|
||||
#include <inttypes.h> /* uint32_t */
|
||||
#endif
|
||||
|
||||
#define UTHASH_VERSION 1.9.6
|
||||
|
||||
#ifndef uthash_fatal
|
||||
#define uthash_fatal(msg) exit(-1) /* fatal error (out of memory,etc) */
|
||||
#endif
|
||||
#ifndef uthash_malloc
|
||||
#define uthash_malloc(sz) malloc(sz) /* malloc fcn */
|
||||
#endif
|
||||
#ifndef uthash_free
|
||||
#define uthash_free(ptr,sz) free(ptr) /* free fcn */
|
||||
#endif
|
||||
|
||||
#ifndef uthash_noexpand_fyi
|
||||
#define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */
|
||||
#endif
|
||||
#ifndef uthash_expand_fyi
|
||||
#define uthash_expand_fyi(tbl) /* can be defined to log expands */
|
||||
#endif
|
||||
|
||||
/* initial number of buckets */
|
||||
#define HASH_INITIAL_NUM_BUCKETS 32 /* initial number of buckets */
|
||||
#define HASH_INITIAL_NUM_BUCKETS_LOG2 5 /* lg2 of initial number of buckets */
|
||||
#define HASH_BKT_CAPACITY_THRESH 10 /* expand when bucket count reaches */
|
||||
|
||||
/* calculate the element whose hash handle address is hhe */
|
||||
#define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho)))
|
||||
|
||||
#define HASH_FIND(hh,head,keyptr,keylen,out) \
|
||||
do { \
|
||||
unsigned _hf_bkt,_hf_hashv; \
|
||||
out=NULL; \
|
||||
if (head) { \
|
||||
HASH_FCN(keyptr,keylen, (head)->hh.tbl->num_buckets, _hf_hashv, _hf_bkt); \
|
||||
if (HASH_BLOOM_TEST((head)->hh.tbl, _hf_hashv)) { \
|
||||
HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], \
|
||||
keyptr,keylen,out); \
|
||||
} \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#ifdef HASH_BLOOM
|
||||
#define HASH_BLOOM_BITLEN (1ULL << HASH_BLOOM)
|
||||
#define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8) + ((HASH_BLOOM_BITLEN%8) ? 1:0)
|
||||
#define HASH_BLOOM_MAKE(tbl) \
|
||||
do { \
|
||||
(tbl)->bloom_nbits = HASH_BLOOM; \
|
||||
(tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \
|
||||
if (!((tbl)->bloom_bv)) { uthash_fatal( "out of memory"); } \
|
||||
memset((tbl)->bloom_bv, 0, HASH_BLOOM_BYTELEN); \
|
||||
(tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \
|
||||
} while (0)
|
||||
|
||||
#define HASH_BLOOM_FREE(tbl) \
|
||||
do { \
|
||||
uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \
|
||||
} while (0)
|
||||
|
||||
#define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8] |= (1U << ((idx)%8)))
|
||||
#define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8] & (1U << ((idx)%8)))
|
||||
|
||||
#define HASH_BLOOM_ADD(tbl,hashv) \
|
||||
HASH_BLOOM_BITSET((tbl)->bloom_bv, (hashv & (uint32_t)((1ULL << (tbl)->bloom_nbits) - 1)))
|
||||
|
||||
#define HASH_BLOOM_TEST(tbl,hashv) \
|
||||
HASH_BLOOM_BITTEST((tbl)->bloom_bv, (hashv & (uint32_t)((1ULL << (tbl)->bloom_nbits) - 1)))
|
||||
|
||||
#else
|
||||
#define HASH_BLOOM_MAKE(tbl)
|
||||
#define HASH_BLOOM_FREE(tbl)
|
||||
#define HASH_BLOOM_ADD(tbl,hashv)
|
||||
#define HASH_BLOOM_TEST(tbl,hashv) (1)
|
||||
#endif
|
||||
|
||||
#define HASH_MAKE_TABLE(hh,head) \
|
||||
do { \
|
||||
(head)->hh.tbl = (UT_hash_table*)uthash_malloc( \
|
||||
sizeof(UT_hash_table)); \
|
||||
if (!((head)->hh.tbl)) { uthash_fatal( "out of memory"); } \
|
||||
memset((head)->hh.tbl, 0, sizeof(UT_hash_table)); \
|
||||
(head)->hh.tbl->tail = &((head)->hh); \
|
||||
(head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \
|
||||
(head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \
|
||||
(head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \
|
||||
(head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \
|
||||
HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \
|
||||
if (! (head)->hh.tbl->buckets) { uthash_fatal( "out of memory"); } \
|
||||
memset((head)->hh.tbl->buckets, 0, \
|
||||
HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \
|
||||
HASH_BLOOM_MAKE((head)->hh.tbl); \
|
||||
(head)->hh.tbl->signature = HASH_SIGNATURE; \
|
||||
} while(0)
|
||||
|
||||
#define HASH_ADD(hh,head,fieldname,keylen_in,add) \
|
||||
HASH_ADD_KEYPTR(hh,head,&((add)->fieldname),keylen_in,add)
|
||||
|
||||
#define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \
|
||||
do { \
|
||||
unsigned _ha_bkt; \
|
||||
(add)->hh.next = NULL; \
|
||||
(add)->hh.key = (char*)keyptr; \
|
||||
(add)->hh.keylen = (unsigned)keylen_in; \
|
||||
if (!(head)) { \
|
||||
head = (add); \
|
||||
(head)->hh.prev = NULL; \
|
||||
HASH_MAKE_TABLE(hh,head); \
|
||||
} else { \
|
||||
(head)->hh.tbl->tail->next = (add); \
|
||||
(add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \
|
||||
(head)->hh.tbl->tail = &((add)->hh); \
|
||||
} \
|
||||
(head)->hh.tbl->num_items++; \
|
||||
(add)->hh.tbl = (head)->hh.tbl; \
|
||||
HASH_FCN(keyptr,keylen_in, (head)->hh.tbl->num_buckets, \
|
||||
(add)->hh.hashv, _ha_bkt); \
|
||||
HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt],&(add)->hh); \
|
||||
HASH_BLOOM_ADD((head)->hh.tbl,(add)->hh.hashv); \
|
||||
HASH_EMIT_KEY(hh,head,keyptr,keylen_in); \
|
||||
HASH_FSCK(hh,head); \
|
||||
} while(0)
|
||||
|
||||
#define HASH_TO_BKT( hashv, num_bkts, bkt ) \
|
||||
do { \
|
||||
bkt = ((hashv) & ((num_bkts) - 1)); \
|
||||
} while(0)
|
||||
|
||||
/* delete "delptr" from the hash table.
|
||||
* "the usual" patch-up process for the app-order doubly-linked-list.
|
||||
* The use of _hd_hh_del below deserves special explanation.
|
||||
* These used to be expressed using (delptr) but that led to a bug
|
||||
* if someone used the same symbol for the head and deletee, like
|
||||
* HASH_DELETE(hh,users,users);
|
||||
* We want that to work, but by changing the head (users) below
|
||||
* we were forfeiting our ability to further refer to the deletee (users)
|
||||
* in the patch-up process. Solution: use scratch space to
|
||||
* copy the deletee pointer, then the latter references are via that
|
||||
* scratch pointer rather than through the repointed (users) symbol.
|
||||
*/
|
||||
#define HASH_DELETE(hh,head,delptr) \
|
||||
do { \
|
||||
unsigned _hd_bkt; \
|
||||
struct UT_hash_handle *_hd_hh_del; \
|
||||
if ( ((delptr)->hh.prev == NULL) && ((delptr)->hh.next == NULL) ) { \
|
||||
uthash_free((head)->hh.tbl->buckets, \
|
||||
(head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket) ); \
|
||||
HASH_BLOOM_FREE((head)->hh.tbl); \
|
||||
uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \
|
||||
head = NULL; \
|
||||
} else { \
|
||||
_hd_hh_del = &((delptr)->hh); \
|
||||
if ((delptr) == ELMT_FROM_HH((head)->hh.tbl,(head)->hh.tbl->tail)) { \
|
||||
(head)->hh.tbl->tail = \
|
||||
(UT_hash_handle*)((char*)((delptr)->hh.prev) + \
|
||||
(head)->hh.tbl->hho); \
|
||||
} \
|
||||
if ((delptr)->hh.prev) { \
|
||||
((UT_hash_handle*)((char*)((delptr)->hh.prev) + \
|
||||
(head)->hh.tbl->hho))->next = (delptr)->hh.next; \
|
||||
} else { \
|
||||
DECLTYPE_ASSIGN(head,(delptr)->hh.next); \
|
||||
} \
|
||||
if (_hd_hh_del->next) { \
|
||||
((UT_hash_handle*)((char*)_hd_hh_del->next + \
|
||||
(head)->hh.tbl->hho))->prev = \
|
||||
_hd_hh_del->prev; \
|
||||
} \
|
||||
HASH_TO_BKT( _hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \
|
||||
HASH_DEL_IN_BKT(hh,(head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \
|
||||
(head)->hh.tbl->num_items--; \
|
||||
} \
|
||||
HASH_FSCK(hh,head); \
|
||||
} while (0)
|
||||
|
||||
|
||||
/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */
|
||||
#define HASH_FIND_STR(head,findstr,out) \
|
||||
HASH_FIND(hh,head,findstr,strlen(findstr),out)
|
||||
#define HASH_ADD_STR(head,strfield,add) \
|
||||
HASH_ADD(hh,head,strfield,strlen(add->strfield),add)
|
||||
#define HASH_FIND_INT(head,findint,out) \
|
||||
HASH_FIND(hh,head,findint,sizeof(int),out)
|
||||
#define HASH_ADD_INT(head,intfield,add) \
|
||||
HASH_ADD(hh,head,intfield,sizeof(int),add)
|
||||
#define HASH_FIND_PTR(head,findptr,out) \
|
||||
HASH_FIND(hh,head,findptr,sizeof(void *),out)
|
||||
#define HASH_ADD_PTR(head,ptrfield,add) \
|
||||
HASH_ADD(hh,head,ptrfield,sizeof(void *),add)
|
||||
#define HASH_DEL(head,delptr) \
|
||||
HASH_DELETE(hh,head,delptr)
|
||||
|
||||
/* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined.
|
||||
* This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined.
|
||||
*/
|
||||
#ifdef HASH_DEBUG
|
||||
#define HASH_OOPS(...) do { fprintf(stderr,__VA_ARGS__); exit(-1); } while (0)
|
||||
#define HASH_FSCK(hh,head) \
|
||||
do { \
|
||||
unsigned _bkt_i; \
|
||||
unsigned _count, _bkt_count; \
|
||||
char *_prev; \
|
||||
struct UT_hash_handle *_thh; \
|
||||
if (head) { \
|
||||
_count = 0; \
|
||||
for( _bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; _bkt_i++) { \
|
||||
_bkt_count = 0; \
|
||||
_thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \
|
||||
_prev = NULL; \
|
||||
while (_thh) { \
|
||||
if (_prev != (char*)(_thh->hh_prev)) { \
|
||||
HASH_OOPS("invalid hh_prev %p, actual %p\n", \
|
||||
_thh->hh_prev, _prev ); \
|
||||
} \
|
||||
_bkt_count++; \
|
||||
_prev = (char*)(_thh); \
|
||||
_thh = _thh->hh_next; \
|
||||
} \
|
||||
_count += _bkt_count; \
|
||||
if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \
|
||||
HASH_OOPS("invalid bucket count %d, actual %d\n", \
|
||||
(head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \
|
||||
} \
|
||||
} \
|
||||
if (_count != (head)->hh.tbl->num_items) { \
|
||||
HASH_OOPS("invalid hh item count %d, actual %d\n", \
|
||||
(head)->hh.tbl->num_items, _count ); \
|
||||
} \
|
||||
/* traverse hh in app order; check next/prev integrity, count */ \
|
||||
_count = 0; \
|
||||
_prev = NULL; \
|
||||
_thh = &(head)->hh; \
|
||||
while (_thh) { \
|
||||
_count++; \
|
||||
if (_prev !=(char*)(_thh->prev)) { \
|
||||
HASH_OOPS("invalid prev %p, actual %p\n", \
|
||||
_thh->prev, _prev ); \
|
||||
} \
|
||||
_prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \
|
||||
_thh = ( _thh->next ? (UT_hash_handle*)((char*)(_thh->next) + \
|
||||
(head)->hh.tbl->hho) : NULL ); \
|
||||
} \
|
||||
if (_count != (head)->hh.tbl->num_items) { \
|
||||
HASH_OOPS("invalid app item count %d, actual %d\n", \
|
||||
(head)->hh.tbl->num_items, _count ); \
|
||||
} \
|
||||
} \
|
||||
} while (0)
|
||||
#else
|
||||
#define HASH_FSCK(hh,head)
|
||||
#endif
|
||||
|
||||
/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to
|
||||
* the descriptor to which this macro is defined for tuning the hash function.
|
||||
* The app can #include <unistd.h> to get the prototype for write(2). */
|
||||
#ifdef HASH_EMIT_KEYS
|
||||
#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \
|
||||
do { \
|
||||
unsigned _klen = fieldlen; \
|
||||
write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \
|
||||
write(HASH_EMIT_KEYS, keyptr, fieldlen); \
|
||||
} while (0)
|
||||
#else
|
||||
#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen)
|
||||
#endif
|
||||
|
||||
/* default to Jenkin's hash unless overridden e.g. DHASH_FUNCTION=HASH_SAX */
|
||||
#ifdef HASH_FUNCTION
|
||||
#define HASH_FCN HASH_FUNCTION
|
||||
#else
|
||||
#define HASH_FCN HASH_JEN
|
||||
#endif
|
||||
|
||||
/* The Bernstein hash function, used in Perl prior to v5.6 */
|
||||
#define HASH_BER(key,keylen,num_bkts,hashv,bkt) \
|
||||
do { \
|
||||
unsigned _hb_keylen=keylen; \
|
||||
char *_hb_key=(char*)(key); \
|
||||
(hashv) = 0; \
|
||||
while (_hb_keylen--) { (hashv) = ((hashv) * 33) + *_hb_key++; } \
|
||||
bkt = (hashv) & (num_bkts-1); \
|
||||
} while (0)
|
||||
|
||||
|
||||
/* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at
|
||||
* http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx */
|
||||
#define HASH_SAX(key,keylen,num_bkts,hashv,bkt) \
|
||||
do { \
|
||||
unsigned _sx_i; \
|
||||
char *_hs_key=(char*)(key); \
|
||||
hashv = 0; \
|
||||
for(_sx_i=0; _sx_i < keylen; _sx_i++) \
|
||||
hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \
|
||||
bkt = hashv & (num_bkts-1); \
|
||||
} while (0)
|
||||
|
||||
#define HASH_FNV(key,keylen,num_bkts,hashv,bkt) \
|
||||
do { \
|
||||
unsigned _fn_i; \
|
||||
char *_hf_key=(char*)(key); \
|
||||
hashv = 2166136261UL; \
|
||||
for(_fn_i=0; _fn_i < keylen; _fn_i++) \
|
||||
hashv = (hashv * 16777619) ^ _hf_key[_fn_i]; \
|
||||
bkt = hashv & (num_bkts-1); \
|
||||
} while(0)
|
||||
|
||||
#define HASH_OAT(key,keylen,num_bkts,hashv,bkt) \
|
||||
do { \
|
||||
unsigned _ho_i; \
|
||||
char *_ho_key=(char*)(key); \
|
||||
hashv = 0; \
|
||||
for(_ho_i=0; _ho_i < keylen; _ho_i++) { \
|
||||
hashv += _ho_key[_ho_i]; \
|
||||
hashv += (hashv << 10); \
|
||||
hashv ^= (hashv >> 6); \
|
||||
} \
|
||||
hashv += (hashv << 3); \
|
||||
hashv ^= (hashv >> 11); \
|
||||
hashv += (hashv << 15); \
|
||||
bkt = hashv & (num_bkts-1); \
|
||||
} while(0)
|
||||
|
||||
#define HASH_JEN_MIX(a,b,c) \
|
||||
do { \
|
||||
a -= b; a -= c; a ^= ( c >> 13 ); \
|
||||
b -= c; b -= a; b ^= ( a << 8 ); \
|
||||
c -= a; c -= b; c ^= ( b >> 13 ); \
|
||||
a -= b; a -= c; a ^= ( c >> 12 ); \
|
||||
b -= c; b -= a; b ^= ( a << 16 ); \
|
||||
c -= a; c -= b; c ^= ( b >> 5 ); \
|
||||
a -= b; a -= c; a ^= ( c >> 3 ); \
|
||||
b -= c; b -= a; b ^= ( a << 10 ); \
|
||||
c -= a; c -= b; c ^= ( b >> 15 ); \
|
||||
} while (0)
|
||||
|
||||
#define HASH_JEN(key,keylen,num_bkts,hashv,bkt) \
|
||||
do { \
|
||||
unsigned _hj_i,_hj_j,_hj_k; \
|
||||
char *_hj_key=(char*)(key); \
|
||||
hashv = 0xfeedbeef; \
|
||||
_hj_i = _hj_j = 0x9e3779b9; \
|
||||
_hj_k = (unsigned)keylen; \
|
||||
while (_hj_k >= 12) { \
|
||||
_hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \
|
||||
+ ( (unsigned)_hj_key[2] << 16 ) \
|
||||
+ ( (unsigned)_hj_key[3] << 24 ) ); \
|
||||
_hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \
|
||||
+ ( (unsigned)_hj_key[6] << 16 ) \
|
||||
+ ( (unsigned)_hj_key[7] << 24 ) ); \
|
||||
hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \
|
||||
+ ( (unsigned)_hj_key[10] << 16 ) \
|
||||
+ ( (unsigned)_hj_key[11] << 24 ) ); \
|
||||
\
|
||||
HASH_JEN_MIX(_hj_i, _hj_j, hashv); \
|
||||
\
|
||||
_hj_key += 12; \
|
||||
_hj_k -= 12; \
|
||||
} \
|
||||
hashv += keylen; \
|
||||
switch ( _hj_k ) { \
|
||||
case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); \
|
||||
case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); \
|
||||
case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); \
|
||||
case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); \
|
||||
case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); \
|
||||
case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); \
|
||||
case 5: _hj_j += _hj_key[4]; \
|
||||
case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); \
|
||||
case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); \
|
||||
case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); \
|
||||
case 1: _hj_i += _hj_key[0]; \
|
||||
} \
|
||||
HASH_JEN_MIX(_hj_i, _hj_j, hashv); \
|
||||
bkt = hashv & (num_bkts-1); \
|
||||
} while(0)
|
||||
|
||||
/* The Paul Hsieh hash function */
|
||||
#undef get16bits
|
||||
#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \
|
||||
|| defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__)
|
||||
#define get16bits(d) (*((const uint16_t *) (d)))
|
||||
#endif
|
||||
|
||||
#if !defined (get16bits)
|
||||
#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \
|
||||
+(uint32_t)(((const uint8_t *)(d))[0]) )
|
||||
#endif
|
||||
#define HASH_SFH(key,keylen,num_bkts,hashv,bkt) \
|
||||
do { \
|
||||
char *_sfh_key=(char*)(key); \
|
||||
uint32_t _sfh_tmp, _sfh_len = keylen; \
|
||||
\
|
||||
int _sfh_rem = _sfh_len & 3; \
|
||||
_sfh_len >>= 2; \
|
||||
hashv = 0xcafebabe; \
|
||||
\
|
||||
/* Main loop */ \
|
||||
for (;_sfh_len > 0; _sfh_len--) { \
|
||||
hashv += get16bits (_sfh_key); \
|
||||
_sfh_tmp = (get16bits (_sfh_key+2) << 11) ^ hashv; \
|
||||
hashv = (hashv << 16) ^ _sfh_tmp; \
|
||||
_sfh_key += 2*sizeof (uint16_t); \
|
||||
hashv += hashv >> 11; \
|
||||
} \
|
||||
\
|
||||
/* Handle end cases */ \
|
||||
switch (_sfh_rem) { \
|
||||
case 3: hashv += get16bits (_sfh_key); \
|
||||
hashv ^= hashv << 16; \
|
||||
hashv ^= _sfh_key[sizeof (uint16_t)] << 18; \
|
||||
hashv += hashv >> 11; \
|
||||
break; \
|
||||
case 2: hashv += get16bits (_sfh_key); \
|
||||
hashv ^= hashv << 11; \
|
||||
hashv += hashv >> 17; \
|
||||
break; \
|
||||
case 1: hashv += *_sfh_key; \
|
||||
hashv ^= hashv << 10; \
|
||||
hashv += hashv >> 1; \
|
||||
} \
|
||||
\
|
||||
/* Force "avalanching" of final 127 bits */ \
|
||||
hashv ^= hashv << 3; \
|
||||
hashv += hashv >> 5; \
|
||||
hashv ^= hashv << 4; \
|
||||
hashv += hashv >> 17; \
|
||||
hashv ^= hashv << 25; \
|
||||
hashv += hashv >> 6; \
|
||||
bkt = hashv & (num_bkts-1); \
|
||||
} while(0)
|
||||
|
||||
#ifdef HASH_USING_NO_STRICT_ALIASING
|
||||
/* The MurmurHash exploits some CPU's (x86,x86_64) tolerance for unaligned reads.
|
||||
* For other types of CPU's (e.g. Sparc) an unaligned read causes a bus error.
|
||||
* MurmurHash uses the faster approach only on CPU's where we know it's safe.
|
||||
*
|
||||
* Note the preprocessor built-in defines can be emitted using:
|
||||
*
|
||||
* gcc -m64 -dM -E - < /dev/null (on gcc)
|
||||
* cc -## a.c (where a.c is a simple test file) (Sun Studio)
|
||||
*/
|
||||
#if (defined(__i386__) || defined(__x86_64__))
|
||||
#define MUR_GETBLOCK(p,i) p[i]
|
||||
#else /* non intel */
|
||||
#define MUR_PLUS0_ALIGNED(p) (((unsigned long)p & 0x3) == 0)
|
||||
#define MUR_PLUS1_ALIGNED(p) (((unsigned long)p & 0x3) == 1)
|
||||
#define MUR_PLUS2_ALIGNED(p) (((unsigned long)p & 0x3) == 2)
|
||||
#define MUR_PLUS3_ALIGNED(p) (((unsigned long)p & 0x3) == 3)
|
||||
#define WP(p) ((uint32_t*)((unsigned long)(p) & ~3UL))
|
||||
#if (defined(__BIG_ENDIAN__) || defined(SPARC) || defined(__ppc__) || defined(__ppc64__))
|
||||
#define MUR_THREE_ONE(p) ((((*WP(p))&0x00ffffff) << 8) | (((*(WP(p)+1))&0xff000000) >> 24))
|
||||
#define MUR_TWO_TWO(p) ((((*WP(p))&0x0000ffff) <<16) | (((*(WP(p)+1))&0xffff0000) >> 16))
|
||||
#define MUR_ONE_THREE(p) ((((*WP(p))&0x000000ff) <<24) | (((*(WP(p)+1))&0xffffff00) >> 8))
|
||||
#else /* assume little endian non-intel */
|
||||
#define MUR_THREE_ONE(p) ((((*WP(p))&0xffffff00) >> 8) | (((*(WP(p)+1))&0x000000ff) << 24))
|
||||
#define MUR_TWO_TWO(p) ((((*WP(p))&0xffff0000) >>16) | (((*(WP(p)+1))&0x0000ffff) << 16))
|
||||
#define MUR_ONE_THREE(p) ((((*WP(p))&0xff000000) >>24) | (((*(WP(p)+1))&0x00ffffff) << 8))
|
||||
#endif
|
||||
#define MUR_GETBLOCK(p,i) (MUR_PLUS0_ALIGNED(p) ? ((p)[i]) : \
|
||||
(MUR_PLUS1_ALIGNED(p) ? MUR_THREE_ONE(p) : \
|
||||
(MUR_PLUS2_ALIGNED(p) ? MUR_TWO_TWO(p) : \
|
||||
MUR_ONE_THREE(p))))
|
||||
#endif
|
||||
#define MUR_ROTL32(x,r) (((x) << (r)) | ((x) >> (32 - (r))))
|
||||
#define MUR_FMIX(_h) \
|
||||
do { \
|
||||
_h ^= _h >> 16; \
|
||||
_h *= 0x85ebca6b; \
|
||||
_h ^= _h >> 13; \
|
||||
_h *= 0xc2b2ae35l; \
|
||||
_h ^= _h >> 16; \
|
||||
} while(0)
|
||||
|
||||
#define HASH_MUR(key,keylen,num_bkts,hashv,bkt) \
|
||||
do { \
|
||||
const uint8_t *_mur_data = (const uint8_t*)(key); \
|
||||
const int _mur_nblocks = (keylen) / 4; \
|
||||
uint32_t _mur_h1 = 0xf88D5353; \
|
||||
uint32_t _mur_c1 = 0xcc9e2d51; \
|
||||
uint32_t _mur_c2 = 0x1b873593; \
|
||||
const uint32_t *_mur_blocks = (const uint32_t*)(_mur_data+_mur_nblocks*4); \
|
||||
int _mur_i; \
|
||||
for(_mur_i = -_mur_nblocks; _mur_i; _mur_i++) { \
|
||||
uint32_t _mur_k1 = MUR_GETBLOCK(_mur_blocks,_mur_i); \
|
||||
_mur_k1 *= _mur_c1; \
|
||||
_mur_k1 = MUR_ROTL32(_mur_k1,15); \
|
||||
_mur_k1 *= _mur_c2; \
|
||||
\
|
||||
_mur_h1 ^= _mur_k1; \
|
||||
_mur_h1 = MUR_ROTL32(_mur_h1,13); \
|
||||
_mur_h1 = _mur_h1*5+0xe6546b64; \
|
||||
} \
|
||||
const uint8_t *_mur_tail = (const uint8_t*)(_mur_data + _mur_nblocks*4); \
|
||||
uint32_t _mur_k1=0; \
|
||||
switch((keylen) & 3) { \
|
||||
case 3: _mur_k1 ^= _mur_tail[2] << 16; \
|
||||
case 2: _mur_k1 ^= _mur_tail[1] << 8; \
|
||||
case 1: _mur_k1 ^= _mur_tail[0]; \
|
||||
_mur_k1 *= _mur_c1; \
|
||||
_mur_k1 = MUR_ROTL32(_mur_k1,15); \
|
||||
_mur_k1 *= _mur_c2; \
|
||||
_mur_h1 ^= _mur_k1; \
|
||||
} \
|
||||
_mur_h1 ^= (keylen); \
|
||||
MUR_FMIX(_mur_h1); \
|
||||
hashv = _mur_h1; \
|
||||
bkt = hashv & (num_bkts-1); \
|
||||
} while(0)
|
||||
#endif /* HASH_USING_NO_STRICT_ALIASING */
|
||||
|
||||
/* key comparison function; return 0 if keys equal */
|
||||
#define HASH_KEYCMP(a,b,len) memcmp(a,b,len)
|
||||
|
||||
/* iterate over items in a known bucket to find desired item */
|
||||
#define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,out) \
|
||||
do { \
|
||||
if (head.hh_head) DECLTYPE_ASSIGN(out,ELMT_FROM_HH(tbl,head.hh_head)); \
|
||||
else out=NULL; \
|
||||
while (out) { \
|
||||
if ((out)->hh.keylen == keylen_in) { \
|
||||
if ((HASH_KEYCMP((out)->hh.key,keyptr,keylen_in)) == 0) break; \
|
||||
} \
|
||||
if ((out)->hh.hh_next) DECLTYPE_ASSIGN(out,ELMT_FROM_HH(tbl,(out)->hh.hh_next)); \
|
||||
else out = NULL; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
/* add an item to a bucket */
|
||||
#define HASH_ADD_TO_BKT(head,addhh) \
|
||||
do { \
|
||||
head.count++; \
|
||||
(addhh)->hh_next = head.hh_head; \
|
||||
(addhh)->hh_prev = NULL; \
|
||||
if (head.hh_head) { (head).hh_head->hh_prev = (addhh); } \
|
||||
(head).hh_head=addhh; \
|
||||
if (head.count >= ((head.expand_mult+1) * HASH_BKT_CAPACITY_THRESH) \
|
||||
&& (addhh)->tbl->noexpand != 1) { \
|
||||
HASH_EXPAND_BUCKETS((addhh)->tbl); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
/* remove an item from a given bucket */
|
||||
#define HASH_DEL_IN_BKT(hh,head,hh_del) \
|
||||
(head).count--; \
|
||||
if ((head).hh_head == hh_del) { \
|
||||
(head).hh_head = hh_del->hh_next; \
|
||||
} \
|
||||
if (hh_del->hh_prev) { \
|
||||
hh_del->hh_prev->hh_next = hh_del->hh_next; \
|
||||
} \
|
||||
if (hh_del->hh_next) { \
|
||||
hh_del->hh_next->hh_prev = hh_del->hh_prev; \
|
||||
}
|
||||
|
||||
/* Bucket expansion has the effect of doubling the number of buckets
|
||||
* and redistributing the items into the new buckets. Ideally the
|
||||
* items will distribute more or less evenly into the new buckets
|
||||
* (the extent to which this is true is a measure of the quality of
|
||||
* the hash function as it applies to the key domain).
|
||||
*
|
||||
* With the items distributed into more buckets, the chain length
|
||||
* (item count) in each bucket is reduced. Thus by expanding buckets
|
||||
* the hash keeps a bound on the chain length. This bounded chain
|
||||
* length is the essence of how a hash provides constant time lookup.
|
||||
*
|
||||
* The calculation of tbl->ideal_chain_maxlen below deserves some
|
||||
* explanation. First, keep in mind that we're calculating the ideal
|
||||
* maximum chain length based on the *new* (doubled) bucket count.
|
||||
* In fractions this is just n/b (n=number of items,b=new num buckets).
|
||||
* Since the ideal chain length is an integer, we want to calculate
|
||||
* ceil(n/b). We don't depend on floating point arithmetic in this
|
||||
* hash, so to calculate ceil(n/b) with integers we could write
|
||||
*
|
||||
* ceil(n/b) = (n/b) + ((n%b)?1:0)
|
||||
*
|
||||
* and in fact a previous version of this hash did just that.
|
||||
* But now we have improved things a bit by recognizing that b is
|
||||
* always a power of two. We keep its base 2 log handy (call it lb),
|
||||
* so now we can write this with a bit shift and logical AND:
|
||||
*
|
||||
* ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0)
|
||||
*
|
||||
*/
|
||||
#define HASH_EXPAND_BUCKETS(tbl) \
|
||||
do { \
|
||||
unsigned _he_bkt; \
|
||||
unsigned _he_bkt_i; \
|
||||
struct UT_hash_handle *_he_thh, *_he_hh_nxt; \
|
||||
UT_hash_bucket *_he_new_buckets, *_he_newbkt; \
|
||||
_he_new_buckets = (UT_hash_bucket*)uthash_malloc( \
|
||||
2 * tbl->num_buckets * sizeof(struct UT_hash_bucket)); \
|
||||
if (!_he_new_buckets) { uthash_fatal( "out of memory"); } \
|
||||
memset(_he_new_buckets, 0, \
|
||||
2 * tbl->num_buckets * sizeof(struct UT_hash_bucket)); \
|
||||
tbl->ideal_chain_maxlen = \
|
||||
(tbl->num_items >> (tbl->log2_num_buckets+1)) + \
|
||||
((tbl->num_items & ((tbl->num_buckets*2)-1)) ? 1 : 0); \
|
||||
tbl->nonideal_items = 0; \
|
||||
for(_he_bkt_i = 0; _he_bkt_i < tbl->num_buckets; _he_bkt_i++) \
|
||||
{ \
|
||||
_he_thh = tbl->buckets[ _he_bkt_i ].hh_head; \
|
||||
while (_he_thh) { \
|
||||
_he_hh_nxt = _he_thh->hh_next; \
|
||||
HASH_TO_BKT( _he_thh->hashv, tbl->num_buckets*2, _he_bkt); \
|
||||
_he_newbkt = &(_he_new_buckets[ _he_bkt ]); \
|
||||
if (++(_he_newbkt->count) > tbl->ideal_chain_maxlen) { \
|
||||
tbl->nonideal_items++; \
|
||||
_he_newbkt->expand_mult = _he_newbkt->count / \
|
||||
tbl->ideal_chain_maxlen; \
|
||||
} \
|
||||
_he_thh->hh_prev = NULL; \
|
||||
_he_thh->hh_next = _he_newbkt->hh_head; \
|
||||
if (_he_newbkt->hh_head) _he_newbkt->hh_head->hh_prev = \
|
||||
_he_thh; \
|
||||
_he_newbkt->hh_head = _he_thh; \
|
||||
_he_thh = _he_hh_nxt; \
|
||||
} \
|
||||
} \
|
||||
uthash_free( tbl->buckets, tbl->num_buckets*sizeof(struct UT_hash_bucket) ); \
|
||||
tbl->num_buckets *= 2; \
|
||||
tbl->log2_num_buckets++; \
|
||||
tbl->buckets = _he_new_buckets; \
|
||||
tbl->ineff_expands = (tbl->nonideal_items > (tbl->num_items >> 1)) ? \
|
||||
(tbl->ineff_expands+1) : 0; \
|
||||
if (tbl->ineff_expands > 1) { \
|
||||
tbl->noexpand=1; \
|
||||
uthash_noexpand_fyi(tbl); \
|
||||
} \
|
||||
uthash_expand_fyi(tbl); \
|
||||
} while(0)
|
||||
|
||||
|
||||
/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */
|
||||
/* Note that HASH_SORT assumes the hash handle name to be hh.
|
||||
* HASH_SRT was added to allow the hash handle name to be passed in. */
|
||||
#define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn)
|
||||
#define HASH_SRT(hh,head,cmpfcn) \
|
||||
do { \
|
||||
unsigned _hs_i; \
|
||||
unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \
|
||||
struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \
|
||||
if (head) { \
|
||||
_hs_insize = 1; \
|
||||
_hs_looping = 1; \
|
||||
_hs_list = &((head)->hh); \
|
||||
while (_hs_looping) { \
|
||||
_hs_p = _hs_list; \
|
||||
_hs_list = NULL; \
|
||||
_hs_tail = NULL; \
|
||||
_hs_nmerges = 0; \
|
||||
while (_hs_p) { \
|
||||
_hs_nmerges++; \
|
||||
_hs_q = _hs_p; \
|
||||
_hs_psize = 0; \
|
||||
for ( _hs_i = 0; _hs_i < _hs_insize; _hs_i++ ) { \
|
||||
_hs_psize++; \
|
||||
_hs_q = (UT_hash_handle*)((_hs_q->next) ? \
|
||||
((void*)((char*)(_hs_q->next) + \
|
||||
(head)->hh.tbl->hho)) : NULL); \
|
||||
if (! (_hs_q) ) break; \
|
||||
} \
|
||||
_hs_qsize = _hs_insize; \
|
||||
while ((_hs_psize > 0) || ((_hs_qsize > 0) && _hs_q )) { \
|
||||
if (_hs_psize == 0) { \
|
||||
_hs_e = _hs_q; \
|
||||
_hs_q = (UT_hash_handle*)((_hs_q->next) ? \
|
||||
((void*)((char*)(_hs_q->next) + \
|
||||
(head)->hh.tbl->hho)) : NULL); \
|
||||
_hs_qsize--; \
|
||||
} else if ( (_hs_qsize == 0) || !(_hs_q) ) { \
|
||||
_hs_e = _hs_p; \
|
||||
_hs_p = (UT_hash_handle*)((_hs_p->next) ? \
|
||||
((void*)((char*)(_hs_p->next) + \
|
||||
(head)->hh.tbl->hho)) : NULL); \
|
||||
_hs_psize--; \
|
||||
} else if (( \
|
||||
cmpfcn(DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl,_hs_p)), \
|
||||
DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl,_hs_q))) \
|
||||
) <= 0) { \
|
||||
_hs_e = _hs_p; \
|
||||
_hs_p = (UT_hash_handle*)((_hs_p->next) ? \
|
||||
((void*)((char*)(_hs_p->next) + \
|
||||
(head)->hh.tbl->hho)) : NULL); \
|
||||
_hs_psize--; \
|
||||
} else { \
|
||||
_hs_e = _hs_q; \
|
||||
_hs_q = (UT_hash_handle*)((_hs_q->next) ? \
|
||||
((void*)((char*)(_hs_q->next) + \
|
||||
(head)->hh.tbl->hho)) : NULL); \
|
||||
_hs_qsize--; \
|
||||
} \
|
||||
if ( _hs_tail ) { \
|
||||
_hs_tail->next = ((_hs_e) ? \
|
||||
ELMT_FROM_HH((head)->hh.tbl,_hs_e) : NULL); \
|
||||
} else { \
|
||||
_hs_list = _hs_e; \
|
||||
} \
|
||||
_hs_e->prev = ((_hs_tail) ? \
|
||||
ELMT_FROM_HH((head)->hh.tbl,_hs_tail) : NULL); \
|
||||
_hs_tail = _hs_e; \
|
||||
} \
|
||||
_hs_p = _hs_q; \
|
||||
} \
|
||||
_hs_tail->next = NULL; \
|
||||
if ( _hs_nmerges <= 1 ) { \
|
||||
_hs_looping=0; \
|
||||
(head)->hh.tbl->tail = _hs_tail; \
|
||||
DECLTYPE_ASSIGN(head,ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \
|
||||
} \
|
||||
_hs_insize *= 2; \
|
||||
} \
|
||||
HASH_FSCK(hh,head); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/* This function selects items from one hash into another hash.
|
||||
* The end result is that the selected items have dual presence
|
||||
* in both hashes. There is no copy of the items made; rather
|
||||
* they are added into the new hash through a secondary hash
|
||||
* hash handle that must be present in the structure. */
|
||||
#define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \
|
||||
do { \
|
||||
unsigned _src_bkt, _dst_bkt; \
|
||||
void *_last_elt=NULL, *_elt; \
|
||||
UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \
|
||||
ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \
|
||||
if (src) { \
|
||||
for(_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \
|
||||
for(_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \
|
||||
_src_hh; \
|
||||
_src_hh = _src_hh->hh_next) { \
|
||||
_elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \
|
||||
if (cond(_elt)) { \
|
||||
_dst_hh = (UT_hash_handle*)(((char*)_elt) + _dst_hho); \
|
||||
_dst_hh->key = _src_hh->key; \
|
||||
_dst_hh->keylen = _src_hh->keylen; \
|
||||
_dst_hh->hashv = _src_hh->hashv; \
|
||||
_dst_hh->prev = _last_elt; \
|
||||
_dst_hh->next = NULL; \
|
||||
if (_last_elt_hh) { _last_elt_hh->next = _elt; } \
|
||||
if (!dst) { \
|
||||
DECLTYPE_ASSIGN(dst,_elt); \
|
||||
HASH_MAKE_TABLE(hh_dst,dst); \
|
||||
} else { \
|
||||
_dst_hh->tbl = (dst)->hh_dst.tbl; \
|
||||
} \
|
||||
HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \
|
||||
HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt],_dst_hh); \
|
||||
(dst)->hh_dst.tbl->num_items++; \
|
||||
_last_elt = _elt; \
|
||||
_last_elt_hh = _dst_hh; \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
HASH_FSCK(hh_dst,dst); \
|
||||
} while (0)
|
||||
|
||||
#define HASH_CLEAR(hh,head) \
|
||||
do { \
|
||||
if (head) { \
|
||||
uthash_free((head)->hh.tbl->buckets, \
|
||||
(head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \
|
||||
HASH_BLOOM_FREE((head)->hh.tbl); \
|
||||
uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \
|
||||
(head)=NULL; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#ifdef NO_DECLTYPE
|
||||
#define HASH_ITER(hh,head,el,tmp) \
|
||||
for((el)=(head), (*(char**)(&(tmp)))=(char*)((head)?(head)->hh.next:NULL); \
|
||||
el; (el)=(tmp),(*(char**)(&(tmp)))=(char*)((tmp)?(tmp)->hh.next:NULL))
|
||||
#else
|
||||
#define HASH_ITER(hh,head,el,tmp) \
|
||||
for((el)=(head),(tmp)=DECLTYPE(el)((head)?(head)->hh.next:NULL); \
|
||||
el; (el)=(tmp),(tmp)=DECLTYPE(el)((tmp)?(tmp)->hh.next:NULL))
|
||||
#endif
|
||||
|
||||
/* obtain a count of items in the hash */
|
||||
#define HASH_COUNT(head) HASH_CNT(hh,head)
|
||||
#define HASH_CNT(hh,head) ((head)?((head)->hh.tbl->num_items):0)
|
||||
|
||||
typedef struct UT_hash_bucket {
|
||||
struct UT_hash_handle *hh_head;
|
||||
unsigned count;
|
||||
|
||||
/* expand_mult is normally set to 0. In this situation, the max chain length
|
||||
* threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If
|
||||
* the bucket's chain exceeds this length, bucket expansion is triggered).
|
||||
* However, setting expand_mult to a non-zero value delays bucket expansion
|
||||
* (that would be triggered by additions to this particular bucket)
|
||||
* until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH.
|
||||
* (The multiplier is simply expand_mult+1). The whole idea of this
|
||||
* multiplier is to reduce bucket expansions, since they are expensive, in
|
||||
* situations where we know that a particular bucket tends to be overused.
|
||||
* It is better to let its chain length grow to a longer yet-still-bounded
|
||||
* value, than to do an O(n) bucket expansion too often.
|
||||
*/
|
||||
unsigned expand_mult;
|
||||
|
||||
} UT_hash_bucket;
|
||||
|
||||
/* random signature used only to find hash tables in external analysis */
|
||||
#define HASH_SIGNATURE 0xa0111fe1
|
||||
#define HASH_BLOOM_SIGNATURE 0xb12220f2
|
||||
|
||||
typedef struct UT_hash_table {
|
||||
UT_hash_bucket *buckets;
|
||||
unsigned num_buckets, log2_num_buckets;
|
||||
unsigned num_items;
|
||||
struct UT_hash_handle *tail; /* tail hh in app order, for fast append */
|
||||
ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */
|
||||
|
||||
/* in an ideal situation (all buckets used equally), no bucket would have
|
||||
* more than ceil(#items/#buckets) items. that's the ideal chain length. */
|
||||
unsigned ideal_chain_maxlen;
|
||||
|
||||
/* nonideal_items is the number of items in the hash whose chain position
|
||||
* exceeds the ideal chain maxlen. these items pay the penalty for an uneven
|
||||
* hash distribution; reaching them in a chain traversal takes >ideal steps */
|
||||
unsigned nonideal_items;
|
||||
|
||||
/* ineffective expands occur when a bucket doubling was performed, but
|
||||
* afterward, more than half the items in the hash had nonideal chain
|
||||
* positions. If this happens on two consecutive expansions we inhibit any
|
||||
* further expansion, as it's not helping; this happens when the hash
|
||||
* function isn't a good fit for the key domain. When expansion is inhibited
|
||||
* the hash will still work, albeit no longer in constant time. */
|
||||
unsigned ineff_expands, noexpand;
|
||||
|
||||
uint32_t signature; /* used only to find hash tables in external analysis */
|
||||
#ifdef HASH_BLOOM
|
||||
uint32_t bloom_sig; /* used only to test bloom exists in external analysis */
|
||||
uint8_t *bloom_bv;
|
||||
char bloom_nbits;
|
||||
#endif
|
||||
|
||||
} UT_hash_table;
|
||||
|
||||
typedef struct UT_hash_handle {
|
||||
struct UT_hash_table *tbl;
|
||||
void *prev; /* prev element in app order */
|
||||
void *next; /* next element in app order */
|
||||
struct UT_hash_handle *hh_prev; /* previous hh in bucket order */
|
||||
struct UT_hash_handle *hh_next; /* next hh in bucket order */
|
||||
void *key; /* ptr to enclosing struct's key */
|
||||
unsigned keylen; /* enclosing struct's key len */
|
||||
unsigned hashv; /* result of hash-fcn(key) */
|
||||
} UT_hash_handle;
|
||||
|
||||
#endif /* UTHASH_H */
|
BIN
rebar
vendored
Executable file
BIN
rebar
vendored
Executable file
Binary file not shown.
17
rebar.config
Normal file
17
rebar.config
Normal file
|
@ -0,0 +1,17 @@
|
|||
%% -*- erlang -*-
|
||||
|
||||
{erl_opts, [
|
||||
debug_info,
|
||||
warnings_as_errors,
|
||||
warn_export_all
|
||||
]}.
|
||||
|
||||
{port_specs, [
|
||||
{"unix", "priv/emdb_drv.so", ["c_src/*.c"]},
|
||||
{"darwin", "priv/emdb_drv.so", ["c_src/*.c"]},
|
||||
{"win32", "priv/emdb_drv.dll", ["c_src/*.c"]}
|
||||
]}.
|
||||
|
||||
{port_env, [
|
||||
{".*", "CFLAGS", "-O2 -Wall"}
|
||||
]}.
|
11
src/emdb.app.src
Normal file
11
src/emdb.app.src
Normal file
|
@ -0,0 +1,11 @@
|
|||
{application, emdb,
|
||||
[
|
||||
{description, "Memory-Mapped Database"},
|
||||
{vsn, "0.9.0"},
|
||||
{registered, []},
|
||||
{applications, [
|
||||
kernel,
|
||||
stdlib
|
||||
]},
|
||||
{env, []}
|
||||
]}.
|
73
src/emdb.erl
Normal file
73
src/emdb.erl
Normal file
|
@ -0,0 +1,73 @@
|
|||
%%-------------------------------------------------------------------
|
||||
%% This file is part of EMDB - Erlang MDB API
|
||||
%%
|
||||
%% Copyright (c) 2012 by Aleph Archives. All rights reserved.
|
||||
%%
|
||||
%%-------------------------------------------------------------------
|
||||
%% Redistribution and use in source and binary forms, with or without
|
||||
%% modification, are permitted only as authorized by the OpenLDAP
|
||||
%% Public License.
|
||||
%%
|
||||
%% A copy of this license is available in the file LICENSE in the
|
||||
%% top-level directory of the distribution or, alternatively, at
|
||||
%% <http://www.OpenLDAP.org/license.html>.
|
||||
%%
|
||||
%% Permission to use, copy, modify, and distribute this software for any
|
||||
%% purpose with or without fee is hereby granted, provided that the above
|
||||
%% copyright notice and this permission notice appear in all copies.
|
||||
%%
|
||||
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
%%-------------------------------------------------------------------
|
||||
|
||||
-module(emdb).
|
||||
|
||||
|
||||
%%====================================================================
|
||||
%% EXPORTS
|
||||
%%====================================================================
|
||||
-export([
|
||||
open/1
|
||||
]).
|
||||
|
||||
|
||||
%%====================================================================
|
||||
%% Types
|
||||
%%====================================================================
|
||||
-record(emdb_oop, {
|
||||
handle :: non_neg_integer()
|
||||
}).
|
||||
|
||||
|
||||
|
||||
%%====================================================================
|
||||
%% PUBLIC API
|
||||
%%====================================================================
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc Create a new MDB database
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec open(file:name()) -> #emdb_oop{}.
|
||||
open(DirName) ->
|
||||
%% ensure directory exists
|
||||
ok = filelib:ensure_dir(DirName ++ "/"),
|
||||
decorate(emdb_drv:open(DirName)).
|
||||
|
||||
|
||||
%%====================================================================
|
||||
%% PRIVATE API
|
||||
%%====================================================================
|
||||
|
||||
%% @private
|
||||
decorate({ok, Handle}) ->
|
||||
CDB = emdb_oop:new(Handle),
|
||||
{ok, CDB};
|
||||
|
||||
decorate(Error) ->
|
||||
Error.
|
137
src/emdb_drv.erl
Normal file
137
src/emdb_drv.erl
Normal file
|
@ -0,0 +1,137 @@
|
|||
%%-------------------------------------------------------------------
|
||||
%% This file is part of EMDB - Erlang MDB API
|
||||
%%
|
||||
%% Copyright (c) 2012 by Aleph Archives. All rights reserved.
|
||||
%%
|
||||
%%-------------------------------------------------------------------
|
||||
%% Redistribution and use in source and binary forms, with or without
|
||||
%% modification, are permitted only as authorized by the OpenLDAP
|
||||
%% Public License.
|
||||
%%
|
||||
%% A copy of this license is available in the file LICENSE in the
|
||||
%% top-level directory of the distribution or, alternatively, at
|
||||
%% <http://www.OpenLDAP.org/license.html>.
|
||||
%%
|
||||
%% Permission to use, copy, modify, and distribute this software for any
|
||||
%% purpose with or without fee is hereby granted, provided that the above
|
||||
%% copyright notice and this permission notice appear in all copies.
|
||||
%%
|
||||
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
%%-------------------------------------------------------------------
|
||||
|
||||
-module(emdb_drv).
|
||||
|
||||
%%====================================================================
|
||||
%% EXPORTS
|
||||
%%====================================================================
|
||||
-export([
|
||||
open/1,
|
||||
close/1,
|
||||
|
||||
put/3,
|
||||
get/2,
|
||||
del/2,
|
||||
|
||||
update/3,
|
||||
|
||||
drop/1
|
||||
]).
|
||||
|
||||
|
||||
%% internal export (ex. spawn, apply)
|
||||
-on_load(init/0).
|
||||
|
||||
%%====================================================================
|
||||
%% MACROS
|
||||
%%====================================================================
|
||||
-define(EMDB_DRIVER_NAME, "emdb_drv").
|
||||
-define(NOT_LOADED, not_loaded(?LINE)).
|
||||
|
||||
|
||||
%%====================================================================
|
||||
%% PUBLIC API
|
||||
%%====================================================================
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
open(_DirName) ->
|
||||
?NOT_LOADED.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
close(_Handle) ->
|
||||
?NOT_LOADED.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
put(_Handle, _Key, _Val) ->
|
||||
?NOT_LOADED.
|
||||
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
get(_Handle, _Key) ->
|
||||
?NOT_LOADED.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
del(_Handle, _Key) ->
|
||||
?NOT_LOADED.
|
||||
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
update(_Handle, _Key, _Val) ->
|
||||
?NOT_LOADED.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
drop(_Handle) ->
|
||||
?NOT_LOADED.
|
||||
|
||||
%%====================================================================
|
||||
%% PRIVATE API
|
||||
%%====================================================================
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
init() ->
|
||||
PrivDir = case code:priv_dir(?MODULE) of
|
||||
{error, _} ->
|
||||
EbinDir = filename:dirname(code:which(?MODULE)),
|
||||
AppPath = filename:dirname(EbinDir),
|
||||
filename:join(AppPath, "priv");
|
||||
Path ->
|
||||
Path
|
||||
end,
|
||||
erlang:load_nif(filename:join(PrivDir, ?EMDB_DRIVER_NAME), 0).
|
||||
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
not_loaded(Line) ->
|
||||
erlang:nif_error({not_loaded, [{module, ?MODULE}, {line, Line}]}).
|
97
src/emdb_oop.erl
Normal file
97
src/emdb_oop.erl
Normal file
|
@ -0,0 +1,97 @@
|
|||
%%-------------------------------------------------------------------
|
||||
%% This file is part of EMDB - Erlang MDB API
|
||||
%%
|
||||
%% Copyright (c) 2012 by Aleph Archives. All rights reserved.
|
||||
%%
|
||||
%%-------------------------------------------------------------------
|
||||
%% Redistribution and use in source and binary forms, with or without
|
||||
%% modification, are permitted only as authorized by the OpenLDAP
|
||||
%% Public License.
|
||||
%%
|
||||
%% A copy of this license is available in the file LICENSE in the
|
||||
%% top-level directory of the distribution or, alternatively, at
|
||||
%% <http://www.OpenLDAP.org/license.html>.
|
||||
%%
|
||||
%% Permission to use, copy, modify, and distribute this software for any
|
||||
%% purpose with or without fee is hereby granted, provided that the above
|
||||
%% copyright notice and this permission notice appear in all copies.
|
||||
%%
|
||||
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
%%-------------------------------------------------------------------
|
||||
|
||||
-module(emdb_oop, [Handle]).
|
||||
|
||||
|
||||
%%====================================================================
|
||||
%% EXPORTS
|
||||
%%====================================================================
|
||||
-export([
|
||||
close/0,
|
||||
|
||||
put/2,
|
||||
get/1,
|
||||
del/1,
|
||||
|
||||
update/2,
|
||||
|
||||
drop/0
|
||||
]).
|
||||
|
||||
|
||||
%%====================================================================
|
||||
%% PUBLIC API
|
||||
%%====================================================================
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
close() ->
|
||||
emdb_drv:close(Handle).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
put(Key, Val) when is_binary(Key) andalso is_binary(Val) ->
|
||||
emdb_drv:put(Handle, Key, Val).
|
||||
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
get(Key) when is_binary(Key) ->
|
||||
emdb_drv:get(Handle, Key).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
del(Key) when is_binary(Key) ->
|
||||
emdb_drv:del(Handle, Key).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
update(Key, Val) when is_binary(Key) andalso is_binary(Val) ->
|
||||
emdb_drv:update(Handle, Key, Val).
|
||||
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
drop() ->
|
||||
emdb_drv:drop(Handle).
|
||||
|
||||
%%====================================================================
|
||||
%% INTERNAL FUNCTIONS
|
||||
%%====================================================================
|
4
start.sh
Executable file
4
start.sh
Executable file
|
@ -0,0 +1,4 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
cd `dirname $0`
|
||||
exec erl -pa $PWD/ebin -pa +B -- $@
|
Loading…
Reference in a new issue