├── .gitignore ├── .travis.yml ├── AUTHORS ├── LICENSE ├── README.md ├── c_src ├── Makefile ├── elmdb_nif.c ├── lmdb.h ├── mdb.c ├── midl.c ├── midl.h └── queue.h ├── rebar.config ├── src ├── elmdb.app.src └── elmdb.erl └── test └── elmdb_tests.erl /.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | priv 3 | .eunit 4 | rebar.lock 5 | c_src/*.o 6 | c_src/*.d 7 | *~ 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: erlang 2 | otp_release: 3 | - 19.0 4 | - 18.3 5 | - 18.1 6 | - 17.4 7 | - 17.1 8 | sudo: false -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Vincent Siliakus 2 | Gregory Burd 3 | Aleph Archives -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------- 2 | The OpenLDAP Public License 3 | Version 2.8, 17 August 2003 4 | 5 | Redistribution and use of this software and associated documentation 6 | ("Software"), with or without modification, are permitted provided 7 | that the following conditions are met: 8 | 9 | 1. Redistributions in source form must retain copyright statements 10 | and notices, 11 | 12 | 2. Redistributions in binary form must reproduce applicable copyright 13 | statements and notices, this list of conditions, and the following 14 | disclaimer in the documentation and/or other materials provided 15 | with the distribution, and 16 | 17 | 3. Redistributions must contain a verbatim copy of this document. 18 | 19 | The OpenLDAP Foundation may revise this license from time to time. 20 | Each revision is distinguished by a version number. You may use 21 | this Software under terms of this license revision or under the 22 | terms of any subsequent revision of the license. 23 | 24 | THIS SOFTWARE IS PROVIDED BY THE OPENLDAP FOUNDATION AND ITS 25 | CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, 26 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 27 | AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 28 | SHALL THE OPENLDAP FOUNDATION, ITS CONTRIBUTORS, OR THE AUTHOR(S) 29 | OR OWNER(S) OF THE SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 30 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 31 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 32 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 33 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 34 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 35 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 36 | POSSIBILITY OF SUCH DAMAGE. 37 | 38 | The names of the authors and copyright holders must not be used in 39 | advertising or otherwise to promote the sale, use or other dealing 40 | in this Software without specific, written prior permission. Title 41 | to copyright in this Software shall at all times remain with copyright 42 | holders. 43 | 44 | OpenLDAP is a registered trademark of the OpenLDAP Foundation. 45 | 46 | Copyright 1999-2003 The OpenLDAP Foundation, Redwood City, 47 | California, USA. All Rights Reserved. Permission to copy and 48 | distribute verbatim copies of this document is granted. 49 | ------------------------------------------------------------------------- 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Elmdb, an Erlang NIF for LMDB 2 | ====================== 3 | 4 | [![Build Status](https://api.travis-ci.org/zambal/elmdb.svg?branch=master)](https://travis-ci.org/zambal/elmdb) 5 | 6 | This is an Erlang NIF for OpenLDAP's Lightning Memory-Mapped Database (LMDB) database library. LMDB is an fast, compact key-value data store developed by Symas for the OpenLDAP Project. It uses memory-mapped files, so it has the read performance of a pure in-memory database while still offering the persistence of standard disk-based databases, and is only limited to the size of the virtual address space, (it is not limited to the size of physical RAM). LMDB was originally called MDB, but was renamed to avoid confusion with other software associated with the name MDB. See the [LMDB docs](http://lmdb.tech/doc/) for more information about LMDB itself. 7 | 8 | As far as I know, two other Erlang NIF libraries exist for LMDB: [alepharchives/emdb](https://github.com/alepharchives/emdb) and [gburd/lmdb](https://github.com/gburd/lmdb). Reasons for developing another wrapper are that both libraries only offer a small subset of LMDB's functionality and their architecture prevents them from offering more of LMDB's functionality. The reason for this is that all read and writes in LMDB are wrapped in transactions and that all operations in a write transaction need to be performed on the same thread. 9 | 10 | [alepharchives/emdb](https://github.com/alepharchives/emdb) performs all operations on the calling thread and since Erlang's scheduler might move a process to a different scheduler/thread at any time, it can not be guaranteed that all operations in a transaction will be performed on the same thread. 11 | 12 | [gburd/lmdb](https://github.com/gburd/lmdb) uses an asynchronous thread pool, developed by Gregory Burd at Basho Technologies. Having a thread pool means that every operation might be scheduled to a different thread from the pool, which starts to be a problem for LMDB when executing transactions with multiple operations. 13 | 14 | Elmdb solves this by creating one background thread per LMDB environment and serializing all write transactions to this thread. This means that Erlang applications can safely perform write transactions concurrently, with the only caveat that as long as a write transaction is active, it will block all other write transactions. However, LMDB itself enforces the same limit by serializing write transactions internally too, so the performance degredation of serializing all transaction operations to a single thread is fairly small and since the calls are asynchronous, the Erlang scheduler isn't blocked. 15 | 16 | Quick Overview 17 | -------------- 18 | 19 | Elmdb strives to provide a robust and eventually complete wrapper for LMDB. Elmdb currently supports: 20 | 21 | * Opening multiple databases within an environment (do not forget to set the max_dbs option in elmdb:env_open/2) 22 | * Safe use of read/write transactions, by serializing all operations within a transaction to a dedicated thread 23 | * Read-only transactions, which are always performed on the calling thread. This results in great performance, since multiple read-only transactions may be executed concurrently and do not block each other 24 | * Basic support for cursors (all operations for databases opened with MDB_DUPSORT still need to be implemented) 25 | * Convenience functions for all basic operations like `put`, `get`, `delete`, `drop` 26 | * Build against LMDB 0.9.18 27 | 28 | Requirements 29 | ------------ 30 | Minimal requirement to be determined. Currently only build and tested on: 31 | 32 | * Ubuntu 16.04 / GCC 5.3.1 / Erlang OTP-18, Erlang OTP-19 33 | * FreeBSD 10.2 / GCC 4.8.5, Clang 3.4.1 / Erlang OTP-19 34 | * Travis CI / Erlang OTP-17.4, OTP-18.3 and OTP-19.0 35 | 36 | Should be able to run on Windows, MacOS and older/different Linux distributions too, but I haven't tried yet. 37 | 38 | 39 | Build 40 | ----- 41 | 42 | ``` 43 | $ rebar3 compile 44 | ``` 45 | 46 | If you want to use `elmdb` as a dependency, there is also a package available at [hex.pm](https://hex.pm/packages/elmdb) 47 | 48 | Usage 49 | ----- 50 | 51 | ``` 52 | $ rebar3 compile 53 | $ rebar3 shell 54 | %% create a new environment 55 | 1> {ok, Env} = elmdb:env_open("/tmp/lmdb1", []). 56 | 57 | %% create the default database of the environment 58 | 2> {ok, Dbi} = elmdb:db_open(Env, [create]). 59 | 60 | %% insert the key <<"a">> with value <<"1">> 61 | 3> ok = elmdb:put(Dbi, <<"a">>, <<"1">>). 62 | 63 | %% try to re-insert the same key <<"a">> 64 | 4> exists = elmdb:put_new(Dbi, <<"a">>, <<"2">>). 65 | 66 | %% re-insert the same key <<"a">> 67 | 5> ok = elmdb:put(Dbi, <<"a">>, <<"2">>). 68 | 69 | %% add new key-value pairs 70 | 6> ok = elmdb:put_new(Dbi, <<"b">>, <<"2">>). 71 | 7> ok = elmdb:put_new(Dbi, <<"d">>, <<"4">>). 72 | 73 | %% search a non-existing key <<"c">> 74 | 8> not_found = elmdb:get(Dbi, <<"c">>). 75 | 76 | %% retrieve the value for key <<"b">> 77 | 9> {ok, <<"2">>} = elmdb:get(Dbi, <<"b">>). 78 | 79 | %% delete key <<"b">> 80 | 10> ok = elmdb:delete(Dbi, <<"b">>). 81 | 82 | %% search a non-existing key <<"b">> 83 | 11> not_found = elmdb:get(Dbi, <<"b">>). 84 | 85 | %% delete a non-existing key <<"z">> 86 | 12> not_found = elmdb:delete(Dbi, <<"z">>). 87 | 88 | %% begin a read-only transaction 89 | 13> {ok, Txn} = elmdb:ro_txn_begin(Env). 90 | 91 | %% open a cursor 92 | 14> {ok, Cur} = elmdb:ro_txn_cursor_open(Txn, Dbi). 93 | 94 | %% get the first key/value pair 95 | 15> {ok, <<"a">>, <<"2">>} = elmdb:ro_txn_cursor_get(Cur, first). 96 | 97 | %% get the next key/value pair 98 | 16> {ok, <<"d">>, <<"4">>} = elmdb:ro_txn_cursor_get(Cur, next). 99 | 100 | %% try to get the next key/value pair 101 | 17> not_found = elmdb:ro_txn_cursor_get(Cur, next). 102 | 103 | %% get the last key/value pair 104 | 18> {ok, <<"d">>, <<"4">>} = elmdb:ro_txn_cursor_get(Cur, last). 105 | 106 | %% find the first key/value pair with a key equal or greater than the specified key. 107 | 19> {ok, <<"d">>, <<"4">>} = elmdb:ro_txn_cursor_get(Cur, {set_range, <<"c">>}). 108 | 109 | %% close cursor 110 | 20> ok = elmdb:ro_txn_cursor_close(Cur). 111 | 112 | %% commit transaction 113 | 21> ok = elmdb:ro_txn_commit(Txn). 114 | 115 | %% swap two values atomically with a read/write transaction 116 | 22> {ok, Txn2} = elmdb:txn_begin(Env). 117 | 23> {ok, A} = elmdb:txn_get(Txn2, Dbi, <<"a">>). 118 | 24> {ok, D} = elmdb:txn_get(Txn2, Dbi, <<"d">>). 119 | 25> ok = elmdb:txn_put(Txn2, Dbi, <<"a">>, D). 120 | 26> ok = elmdb:txn_put(Txn2, Dbi, <<"d">>, A). 121 | 27> ok = elmdb:txn_commit(Txn2). 122 | 123 | %% delete all key-value pairs in the database 124 | 28> ok = elmdb:drop(Env). 125 | 126 | %% try to retrieve key <<"a">> value 127 | 29> not_found = elmdb:get(Env, <<"a">>). 128 | 129 | %% close the environment 130 | 30> ok = elmdb:env_close(Env). 131 | ``` 132 | 133 | 134 | Design and implementation notes 135 | ------------------------------- 136 | 137 | When an environment is opened, `elmdb` will create a background thread for handling read/write transactions. All elmdb functions starting with `txn_`, `async_` and `update_` make use of this thread. The functions push their operation to a FIFO queue and the background thread will pop operations from this queue when it can. The background thread sends a message back to the calling process with the results of the operations. This design is inspired by Gregory Burd's `async_nif.h`, used in [gburd/lmdb](https://github.com/gburd/lmdb). 138 | 139 | All objects/handles from LMDB are allocated as NIF resources, meaning that they are reference counted and garbage collected by the Erlang run-time. If a resource has a reference to another resource, the ref counter is increased. When for example an environment resource goes out of scope, but a db from that environment is still in scope, the environment won't be closed and garbage collected as long as the db resource is still available. Here's an overview of the lifetime behaviour of resources: 140 | 141 | * Environments - Will be closed and its background thread ended by the resource destructor before collection if it's still open. Will stay alive as long as its resource is in scope or any other resource opened within (Db, transaction, or cursor) 142 | * Databases - Databases are never closed expicitly, or by its resource destructor, but only implicitly when their environment closes. Contains a reference to its environment. 143 | * Transactions - Both read/write and read-only transactions will be aborted by the resource destructor before collection if still open. Contains a reference to the environment it was opened in. 144 | * Cursors - read-only cursors will be closed by the resource destructor before collection. Read/write cursors are implicitly closed by LMDB when its transaction ends. Contains a reference to the transaction it was opened in. 145 | 146 | 147 | TODO 148 | ---- 149 | 150 | * Documentation 151 | * Complete cursor operations for MDB_DUPSORT databases 152 | * More unit tests 153 | * Bulk writing 154 | * Implement wrapper for mnesia_ex plugin 155 | * Provide more utility functions for stats and (re)configuration 156 | 157 | 158 | Status 159 | ------ 160 | 161 | Work in progress, but the code should be pretty stable, as it is already about to be used as a test case in production for a non critical project. 162 | 163 | 164 | Acknowledgements 165 | ---------------- 166 | 167 | This project started as a fork of [gburd/lmdb](https://github.com/gburd/lmdb), but ended up as an almost complete rewrite. However, there are still parts in `elmdb` that originated from that project, like async nif design, utility functions, Makefile, rebar configuration, structure of this readme, etc. 168 | 169 | 170 | LICENSE 171 | ------- 172 | 173 | Elmdb is Copyright (C) 2012 by Aleph Archives, (C) 2013 by Basho Technologies, (C) 2016 by Vincent Siliakus and released under the [OpenLDAP](http://www.OpenLDAP.org/license.html) License. 174 | -------------------------------------------------------------------------------- /c_src/Makefile: -------------------------------------------------------------------------------- 1 | # Based on c_src.mk from erlang.mk by Loic Hoguin 2 | 3 | CURDIR := $(shell pwd) 4 | BASEDIR := $(abspath $(CURDIR)/..) 5 | 6 | PROJECT ?= $(notdir $(BASEDIR)) 7 | PROJECT := $(strip $(PROJECT)) 8 | 9 | ERTS_INCLUDE_DIR ?= $(shell erl -noshell -s init stop -eval "io:format(\"~s/erts-~s/include/\", [code:root_dir(), erlang:system_info(version)]).") 10 | ERL_INTERFACE_INCLUDE_DIR ?= $(shell erl -noshell -s init stop -eval "io:format(\"~s\", [code:lib_dir(erl_interface, include)]).") 11 | ERL_INTERFACE_LIB_DIR ?= $(shell erl -noshell -s init stop -eval "io:format(\"~s\", [code:lib_dir(erl_interface, lib)]).") 12 | 13 | C_SRC_DIR = $(CURDIR) 14 | C_SRC_OUTPUT ?= $(CURDIR)/../priv/$(PROJECT).so 15 | 16 | # System type and C compiler/flags. 17 | 18 | UNAME_SYS := $(shell uname -s) 19 | ifeq ($(UNAME_SYS), Darwin) 20 | CC ?= cc 21 | CFLAGS ?= -O3 -std=c99 -arch x86_64 -finline-functions -Wall -Wmissing-prototypes 22 | CXXFLAGS ?= -O3 -arch x86_64 -finline-functions -Wall 23 | LDFLAGS ?= -arch x86_64 -flat_namespace -undefined suppress 24 | else ifeq ($(UNAME_SYS), FreeBSD) 25 | CC ?= cc 26 | CFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes 27 | CXXFLAGS ?= -O3 -finline-functions -Wall 28 | else ifeq ($(UNAME_SYS), Linux) 29 | CC ?= gcc 30 | CFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes 31 | CXXFLAGS ?= -O3 -finline-functions -Wall 32 | endif 33 | 34 | CFLAGS += -fPIC -I $(ERTS_INCLUDE_DIR) -I $(ERL_INTERFACE_INCLUDE_DIR) 35 | CXXFLAGS += -fPIC -I $(ERTS_INCLUDE_DIR) -I $(ERL_INTERFACE_INCLUDE_DIR) 36 | 37 | LDLIBS += -L $(ERL_INTERFACE_LIB_DIR) -lerl_interface -lei 38 | LDFLAGS += -shared 39 | 40 | # Verbosity. 41 | 42 | c_verbose_0 = @echo " C " $(?F); 43 | c_verbose = $(c_verbose_$(V)) 44 | 45 | cpp_verbose_0 = @echo " CPP " $(?F); 46 | cpp_verbose = $(cpp_verbose_$(V)) 47 | 48 | link_verbose_0 = @echo " LD " $(@F); 49 | link_verbose = $(link_verbose_$(V)) 50 | 51 | SOURCES := $(shell find $(C_SRC_DIR) -type f \( -name "*.c" -o -name "*.C" -o -name "*.cc" -o -name "*.cpp" \)) 52 | OBJECTS = $(addsuffix .o, $(basename $(SOURCES))) 53 | 54 | COMPILE_C = $(c_verbose) $(CC) $(CFLAGS) $(CPPFLAGS) -c 55 | COMPILE_CPP = $(cpp_verbose) $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c 56 | 57 | $(C_SRC_OUTPUT): $(OBJECTS) 58 | @mkdir -p $(BASEDIR)/priv/ 59 | $(link_verbose) $(CC) $(OBJECTS) $(LDFLAGS) $(LDLIBS) -o $(C_SRC_OUTPUT) 60 | 61 | %.o: %.c 62 | $(COMPILE_C) $(OUTPUT_OPTION) $< 63 | 64 | %.o: %.cc 65 | $(COMPILE_CPP) $(OUTPUT_OPTION) $< 66 | 67 | %.o: %.C 68 | $(COMPILE_CPP) $(OUTPUT_OPTION) $< 69 | 70 | %.o: %.cpp 71 | $(COMPILE_CPP) $(OUTPUT_OPTION) $< 72 | 73 | clean: 74 | @rm -f $(C_SRC_OUTPUT) $(OBJECTS) 75 | -------------------------------------------------------------------------------- /c_src/lmdb.h: -------------------------------------------------------------------------------- 1 | /** @file lmdb.h 2 | * @brief Lightning memory-mapped database library 3 | * 4 | * @mainpage Lightning Memory-Mapped Database Manager (LMDB) 5 | * 6 | * @section intro_sec Introduction 7 | * LMDB is a Btree-based database management library modeled loosely on the 8 | * BerkeleyDB API, but much simplified. The entire database is exposed 9 | * in a memory map, and all data fetches return data directly 10 | * from the mapped memory, so no malloc's or memcpy's occur during 11 | * data fetches. As such, the library is extremely simple because it 12 | * requires no page caching layer of its own, and it is extremely high 13 | * performance and memory-efficient. It is also fully transactional with 14 | * full ACID semantics, and when the memory map is read-only, the 15 | * database integrity cannot be corrupted by stray pointer writes from 16 | * application code. 17 | * 18 | * The library is fully thread-aware and supports concurrent read/write 19 | * access from multiple processes and threads. Data pages use a copy-on- 20 | * write strategy so no active data pages are ever overwritten, which 21 | * also provides resistance to corruption and eliminates the need of any 22 | * special recovery procedures after a system crash. Writes are fully 23 | * serialized; only one write transaction may be active at a time, which 24 | * guarantees that writers can never deadlock. The database structure is 25 | * multi-versioned so readers run with no locks; writers cannot block 26 | * readers, and readers don't block writers. 27 | * 28 | * Unlike other well-known database mechanisms which use either write-ahead 29 | * transaction logs or append-only data writes, LMDB requires no maintenance 30 | * during operation. Both write-ahead loggers and append-only databases 31 | * require periodic checkpointing and/or compaction of their log or database 32 | * files otherwise they grow without bound. LMDB tracks free pages within 33 | * the database and re-uses them for new write operations, so the database 34 | * size does not grow without bound in normal use. 35 | * 36 | * The memory map can be used as a read-only or read-write map. It is 37 | * read-only by default as this provides total immunity to corruption. 38 | * Using read-write mode offers much higher write performance, but adds 39 | * the possibility for stray application writes thru pointers to silently 40 | * corrupt the database. Of course if your application code is known to 41 | * be bug-free (...) then this is not an issue. 42 | * 43 | * If this is your first time using a transactional embedded key/value 44 | * store, you may find the \ref starting page to be helpful. 45 | * 46 | * @section caveats_sec Caveats 47 | * Troubleshooting the lock file, plus semaphores on BSD systems: 48 | * 49 | * - A broken lockfile can cause sync issues. 50 | * Stale reader transactions left behind by an aborted program 51 | * cause further writes to grow the database quickly, and 52 | * stale locks can block further operation. 53 | * 54 | * Fix: Check for stale readers periodically, using the 55 | * #mdb_reader_check function or the \ref mdb_stat_1 "mdb_stat" tool. 56 | * Stale writers will be cleared automatically on some systems: 57 | * - Windows - automatic 58 | * - Linux, systems using POSIX mutexes with Robust option - automatic 59 | * - not on BSD, systems using POSIX semaphores. 60 | * Otherwise just make all programs using the database close it; 61 | * the lockfile is always reset on first open of the environment. 62 | * 63 | * - On BSD systems or others configured with MDB_USE_POSIX_SEM, 64 | * startup can fail due to semaphores owned by another userid. 65 | * 66 | * Fix: Open and close the database as the user which owns the 67 | * semaphores (likely last user) or as root, while no other 68 | * process is using the database. 69 | * 70 | * Restrictions/caveats (in addition to those listed for some functions): 71 | * 72 | * - Only the database owner should normally use the database on 73 | * BSD systems or when otherwise configured with MDB_USE_POSIX_SEM. 74 | * Multiple users can cause startup to fail later, as noted above. 75 | * 76 | * - There is normally no pure read-only mode, since readers need write 77 | * access to locks and lock file. Exceptions: On read-only filesystems 78 | * or with the #MDB_NOLOCK flag described under #mdb_env_open(). 79 | * 80 | * - By default, in versions before 0.9.10, unused portions of the data 81 | * file might receive garbage data from memory freed by other code. 82 | * (This does not happen when using the #MDB_WRITEMAP flag.) As of 83 | * 0.9.10 the default behavior is to initialize such memory before 84 | * writing to the data file. Since there may be a slight performance 85 | * cost due to this initialization, applications may disable it using 86 | * the #MDB_NOMEMINIT flag. Applications handling sensitive data 87 | * which must not be written should not use this flag. This flag is 88 | * irrelevant when using #MDB_WRITEMAP. 89 | * 90 | * - A thread can only use one transaction at a time, plus any child 91 | * transactions. Each transaction belongs to one thread. See below. 92 | * The #MDB_NOTLS flag changes this for read-only transactions. 93 | * 94 | * - Use an MDB_env* in the process which opened it, without fork()ing. 95 | * 96 | * - Do not have open an LMDB database twice in the same process at 97 | * the same time. Not even from a plain open() call - close()ing it 98 | * breaks flock() advisory locking. 99 | * 100 | * - Avoid long-lived transactions. Read transactions prevent 101 | * reuse of pages freed by newer write transactions, thus the 102 | * database can grow quickly. Write transactions prevent 103 | * other write transactions, since writes are serialized. 104 | * 105 | * - Avoid suspending a process with active transactions. These 106 | * would then be "long-lived" as above. Also read transactions 107 | * suspended when writers commit could sometimes see wrong data. 108 | * 109 | * ...when several processes can use a database concurrently: 110 | * 111 | * - Avoid aborting a process with an active transaction. 112 | * The transaction becomes "long-lived" as above until a check 113 | * for stale readers is performed or the lockfile is reset, 114 | * since the process may not remove it from the lockfile. 115 | * 116 | * This does not apply to write transactions if the system clears 117 | * stale writers, see above. 118 | * 119 | * - If you do that anyway, do a periodic check for stale readers. Or 120 | * close the environment once in a while, so the lockfile can get reset. 121 | * 122 | * - Do not use LMDB databases on remote filesystems, even between 123 | * processes on the same host. This breaks flock() on some OSes, 124 | * possibly memory map sync, and certainly sync between programs 125 | * on different hosts. 126 | * 127 | * - Opening a database can fail if another process is opening or 128 | * closing it at exactly the same time. 129 | * 130 | * @author Howard Chu, Symas Corporation. 131 | * 132 | * @copyright Copyright 2011-2016 Howard Chu, Symas Corp. All rights reserved. 133 | * 134 | * Redistribution and use in source and binary forms, with or without 135 | * modification, are permitted only as authorized by the OpenLDAP 136 | * Public License. 137 | * 138 | * A copy of this license is available in the file LICENSE in the 139 | * top-level directory of the distribution or, alternatively, at 140 | * . 141 | * 142 | * @par Derived From: 143 | * This code is derived from btree.c written by Martin Hedenfalk. 144 | * 145 | * Copyright (c) 2009, 2010 Martin Hedenfalk 146 | * 147 | * Permission to use, copy, modify, and distribute this software for any 148 | * purpose with or without fee is hereby granted, provided that the above 149 | * copyright notice and this permission notice appear in all copies. 150 | * 151 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 152 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 153 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 154 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 155 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 156 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 157 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 158 | */ 159 | #ifndef _LMDB_H_ 160 | #define _LMDB_H_ 161 | 162 | #include 163 | 164 | #ifdef __cplusplus 165 | extern "C" { 166 | #endif 167 | 168 | /** Unix permissions for creating files, or dummy definition for Windows */ 169 | #ifdef _MSC_VER 170 | typedef int mdb_mode_t; 171 | #else 172 | typedef mode_t mdb_mode_t; 173 | #endif 174 | 175 | /** An abstraction for a file handle. 176 | * On POSIX systems file handles are small integers. On Windows 177 | * they're opaque pointers. 178 | */ 179 | #ifdef _WIN32 180 | typedef void *mdb_filehandle_t; 181 | #else 182 | typedef int mdb_filehandle_t; 183 | #endif 184 | 185 | /** @defgroup mdb LMDB API 186 | * @{ 187 | * @brief OpenLDAP Lightning Memory-Mapped Database Manager 188 | */ 189 | /** @defgroup Version Version Macros 190 | * @{ 191 | */ 192 | /** Library major version */ 193 | #define MDB_VERSION_MAJOR 0 194 | /** Library minor version */ 195 | #define MDB_VERSION_MINOR 9 196 | /** Library patch version */ 197 | #define MDB_VERSION_PATCH 18 198 | 199 | /** Combine args a,b,c into a single integer for easy version comparisons */ 200 | #define MDB_VERINT(a,b,c) (((a) << 24) | ((b) << 16) | (c)) 201 | 202 | /** The full library version as a single integer */ 203 | #define MDB_VERSION_FULL \ 204 | MDB_VERINT(MDB_VERSION_MAJOR,MDB_VERSION_MINOR,MDB_VERSION_PATCH) 205 | 206 | /** The release date of this library version */ 207 | #define MDB_VERSION_DATE "February 5, 2016" 208 | 209 | /** A stringifier for the version info */ 210 | #define MDB_VERSTR(a,b,c,d) "LMDB " #a "." #b "." #c ": (" d ")" 211 | 212 | /** A helper for the stringifier macro */ 213 | #define MDB_VERFOO(a,b,c,d) MDB_VERSTR(a,b,c,d) 214 | 215 | /** The full library version as a C string */ 216 | #define MDB_VERSION_STRING \ 217 | MDB_VERFOO(MDB_VERSION_MAJOR,MDB_VERSION_MINOR,MDB_VERSION_PATCH,MDB_VERSION_DATE) 218 | /** @} */ 219 | 220 | /** @brief Opaque structure for a database environment. 221 | * 222 | * A DB environment supports multiple databases, all residing in the same 223 | * shared-memory map. 224 | */ 225 | typedef struct MDB_env MDB_env; 226 | 227 | /** @brief Opaque structure for a transaction handle. 228 | * 229 | * All database operations require a transaction handle. Transactions may be 230 | * read-only or read-write. 231 | */ 232 | typedef struct MDB_txn MDB_txn; 233 | 234 | /** @brief A handle for an individual database in the DB environment. */ 235 | typedef unsigned int MDB_dbi; 236 | 237 | /** @brief Opaque structure for navigating through a database */ 238 | typedef struct MDB_cursor MDB_cursor; 239 | 240 | /** @brief Generic structure used for passing keys and data in and out 241 | * of the database. 242 | * 243 | * Values returned from the database are valid only until a subsequent 244 | * update operation, or the end of the transaction. Do not modify or 245 | * free them, they commonly point into the database itself. 246 | * 247 | * Key sizes must be between 1 and #mdb_env_get_maxkeysize() inclusive. 248 | * The same applies to data sizes in databases with the #MDB_DUPSORT flag. 249 | * Other data items can in theory be from 0 to 0xffffffff bytes long. 250 | */ 251 | typedef struct MDB_val { 252 | size_t mv_size; /**< size of the data item */ 253 | void *mv_data; /**< address of the data item */ 254 | } MDB_val; 255 | 256 | /** @brief A callback function used to compare two keys in a database */ 257 | typedef int (MDB_cmp_func)(const MDB_val *a, const MDB_val *b); 258 | 259 | /** @brief A callback function used to relocate a position-dependent data item 260 | * in a fixed-address database. 261 | * 262 | * The \b newptr gives the item's desired address in 263 | * the memory map, and \b oldptr gives its previous address. The item's actual 264 | * data resides at the address in \b item. This callback is expected to walk 265 | * through the fields of the record in \b item and modify any 266 | * values based at the \b oldptr address to be relative to the \b newptr address. 267 | * @param[in,out] item The item that is to be relocated. 268 | * @param[in] oldptr The previous address. 269 | * @param[in] newptr The new address to relocate to. 270 | * @param[in] relctx An application-provided context, set by #mdb_set_relctx(). 271 | * @todo This feature is currently unimplemented. 272 | */ 273 | typedef void (MDB_rel_func)(MDB_val *item, void *oldptr, void *newptr, void *relctx); 274 | 275 | /** @defgroup mdb_env Environment Flags 276 | * @{ 277 | */ 278 | /** mmap at a fixed address (experimental) */ 279 | #define MDB_FIXEDMAP 0x01 280 | /** no environment directory */ 281 | #define MDB_NOSUBDIR 0x4000 282 | /** don't fsync after commit */ 283 | #define MDB_NOSYNC 0x10000 284 | /** read only */ 285 | #define MDB_RDONLY 0x20000 286 | /** don't fsync metapage after commit */ 287 | #define MDB_NOMETASYNC 0x40000 288 | /** use writable mmap */ 289 | #define MDB_WRITEMAP 0x80000 290 | /** use asynchronous msync when #MDB_WRITEMAP is used */ 291 | #define MDB_MAPASYNC 0x100000 292 | /** tie reader locktable slots to #MDB_txn objects instead of to threads */ 293 | #define MDB_NOTLS 0x200000 294 | /** don't do any locking, caller must manage their own locks */ 295 | #define MDB_NOLOCK 0x400000 296 | /** don't do readahead (no effect on Windows) */ 297 | #define MDB_NORDAHEAD 0x800000 298 | /** don't initialize malloc'd memory before writing to datafile */ 299 | #define MDB_NOMEMINIT 0x1000000 300 | /** @} */ 301 | 302 | /** @defgroup mdb_dbi_open Database Flags 303 | * @{ 304 | */ 305 | /** use reverse string keys */ 306 | #define MDB_REVERSEKEY 0x02 307 | /** use sorted duplicates */ 308 | #define MDB_DUPSORT 0x04 309 | /** numeric keys in native byte order: either unsigned int or size_t. 310 | * The keys must all be of the same size. */ 311 | #define MDB_INTEGERKEY 0x08 312 | /** with #MDB_DUPSORT, sorted dup items have fixed size */ 313 | #define MDB_DUPFIXED 0x10 314 | /** with #MDB_DUPSORT, dups are #MDB_INTEGERKEY-style integers */ 315 | #define MDB_INTEGERDUP 0x20 316 | /** with #MDB_DUPSORT, use reverse string dups */ 317 | #define MDB_REVERSEDUP 0x40 318 | /** create DB if not already existing */ 319 | #define MDB_CREATE 0x40000 320 | /** @} */ 321 | 322 | /** @defgroup mdb_put Write Flags 323 | * @{ 324 | */ 325 | /** For put: Don't write if the key already exists. */ 326 | #define MDB_NOOVERWRITE 0x10 327 | /** Only for #MDB_DUPSORT
328 | * For put: don't write if the key and data pair already exist.
329 | * For mdb_cursor_del: remove all duplicate data items. 330 | */ 331 | #define MDB_NODUPDATA 0x20 332 | /** For mdb_cursor_put: overwrite the current key/data pair */ 333 | #define MDB_CURRENT 0x40 334 | /** For put: Just reserve space for data, don't copy it. Return a 335 | * pointer to the reserved space. 336 | */ 337 | #define MDB_RESERVE 0x10000 338 | /** Data is being appended, don't split full pages. */ 339 | #define MDB_APPEND 0x20000 340 | /** Duplicate data is being appended, don't split full pages. */ 341 | #define MDB_APPENDDUP 0x40000 342 | /** Store multiple data items in one call. Only for #MDB_DUPFIXED. */ 343 | #define MDB_MULTIPLE 0x80000 344 | /* @} */ 345 | 346 | /** @defgroup mdb_copy Copy Flags 347 | * @{ 348 | */ 349 | /** Compacting copy: Omit free space from copy, and renumber all 350 | * pages sequentially. 351 | */ 352 | #define MDB_CP_COMPACT 0x01 353 | /* @} */ 354 | 355 | /** @brief Cursor Get operations. 356 | * 357 | * This is the set of all operations for retrieving data 358 | * using a cursor. 359 | */ 360 | typedef enum MDB_cursor_op { 361 | MDB_FIRST, /**< Position at first key/data item */ 362 | MDB_FIRST_DUP, /**< Position at first data item of current key. 363 | Only for #MDB_DUPSORT */ 364 | MDB_GET_BOTH, /**< Position at key/data pair. Only for #MDB_DUPSORT */ 365 | MDB_GET_BOTH_RANGE, /**< position at key, nearest data. Only for #MDB_DUPSORT */ 366 | MDB_GET_CURRENT, /**< Return key/data at current cursor position */ 367 | MDB_GET_MULTIPLE, /**< Return key and up to a page of duplicate data items 368 | from current cursor position. Move cursor to prepare 369 | for #MDB_NEXT_MULTIPLE. Only for #MDB_DUPFIXED */ 370 | MDB_LAST, /**< Position at last key/data item */ 371 | MDB_LAST_DUP, /**< Position at last data item of current key. 372 | Only for #MDB_DUPSORT */ 373 | MDB_NEXT, /**< Position at next data item */ 374 | MDB_NEXT_DUP, /**< Position at next data item of current key. 375 | Only for #MDB_DUPSORT */ 376 | MDB_NEXT_MULTIPLE, /**< Return key and up to a page of duplicate data items 377 | from next cursor position. Move cursor to prepare 378 | for #MDB_NEXT_MULTIPLE. Only for #MDB_DUPFIXED */ 379 | MDB_NEXT_NODUP, /**< Position at first data item of next key */ 380 | MDB_PREV, /**< Position at previous data item */ 381 | MDB_PREV_DUP, /**< Position at previous data item of current key. 382 | Only for #MDB_DUPSORT */ 383 | MDB_PREV_NODUP, /**< Position at last data item of previous key */ 384 | MDB_SET, /**< Position at specified key */ 385 | MDB_SET_KEY, /**< Position at specified key, return key + data */ 386 | MDB_SET_RANGE /**< Position at first key greater than or equal to specified key. */ 387 | } MDB_cursor_op; 388 | 389 | /** @defgroup errors Return Codes 390 | * 391 | * BerkeleyDB uses -30800 to -30999, we'll go under them 392 | * @{ 393 | */ 394 | /** Successful result */ 395 | #define MDB_SUCCESS 0 396 | /** key/data pair already exists */ 397 | #define MDB_KEYEXIST (-30799) 398 | /** key/data pair not found (EOF) */ 399 | #define MDB_NOTFOUND (-30798) 400 | /** Requested page not found - this usually indicates corruption */ 401 | #define MDB_PAGE_NOTFOUND (-30797) 402 | /** Located page was wrong type */ 403 | #define MDB_CORRUPTED (-30796) 404 | /** Update of meta page failed or environment had fatal error */ 405 | #define MDB_PANIC (-30795) 406 | /** Environment version mismatch */ 407 | #define MDB_VERSION_MISMATCH (-30794) 408 | /** File is not a valid LMDB file */ 409 | #define MDB_INVALID (-30793) 410 | /** Environment mapsize reached */ 411 | #define MDB_MAP_FULL (-30792) 412 | /** Environment maxdbs reached */ 413 | #define MDB_DBS_FULL (-30791) 414 | /** Environment maxreaders reached */ 415 | #define MDB_READERS_FULL (-30790) 416 | /** Too many TLS keys in use - Windows only */ 417 | #define MDB_TLS_FULL (-30789) 418 | /** Txn has too many dirty pages */ 419 | #define MDB_TXN_FULL (-30788) 420 | /** Cursor stack too deep - internal error */ 421 | #define MDB_CURSOR_FULL (-30787) 422 | /** Page has not enough space - internal error */ 423 | #define MDB_PAGE_FULL (-30786) 424 | /** Database contents grew beyond environment mapsize */ 425 | #define MDB_MAP_RESIZED (-30785) 426 | /** Operation and DB incompatible, or DB type changed. This can mean: 427 | *
    428 | *
  • The operation expects an #MDB_DUPSORT / #MDB_DUPFIXED database. 429 | *
  • Opening a named DB when the unnamed DB has #MDB_DUPSORT / #MDB_INTEGERKEY. 430 | *
  • Accessing a data record as a database, or vice versa. 431 | *
  • The database was dropped and recreated with different flags. 432 | *
433 | */ 434 | #define MDB_INCOMPATIBLE (-30784) 435 | /** Invalid reuse of reader locktable slot */ 436 | #define MDB_BAD_RSLOT (-30783) 437 | /** Transaction must abort, has a child, or is invalid */ 438 | #define MDB_BAD_TXN (-30782) 439 | /** Unsupported size of key/DB name/data, or wrong DUPFIXED size */ 440 | #define MDB_BAD_VALSIZE (-30781) 441 | /** The specified DBI was changed unexpectedly */ 442 | #define MDB_BAD_DBI (-30780) 443 | /** The last defined error code */ 444 | #define MDB_LAST_ERRCODE MDB_BAD_DBI 445 | /** @} */ 446 | 447 | /** @brief Statistics for a database in the environment */ 448 | typedef struct MDB_stat { 449 | unsigned int ms_psize; /**< Size of a database page. 450 | This is currently the same for all databases. */ 451 | unsigned int ms_depth; /**< Depth (height) of the B-tree */ 452 | size_t ms_branch_pages; /**< Number of internal (non-leaf) pages */ 453 | size_t ms_leaf_pages; /**< Number of leaf pages */ 454 | size_t ms_overflow_pages; /**< Number of overflow pages */ 455 | size_t ms_entries; /**< Number of data items */ 456 | } MDB_stat; 457 | 458 | /** @brief Information about the environment */ 459 | typedef struct MDB_envinfo { 460 | void *me_mapaddr; /**< Address of map, if fixed */ 461 | size_t me_mapsize; /**< Size of the data memory map */ 462 | size_t me_last_pgno; /**< ID of the last used page */ 463 | size_t me_last_txnid; /**< ID of the last committed transaction */ 464 | unsigned int me_maxreaders; /**< max reader slots in the environment */ 465 | unsigned int me_numreaders; /**< max reader slots used in the environment */ 466 | } MDB_envinfo; 467 | 468 | /** @brief Return the LMDB library version information. 469 | * 470 | * @param[out] major if non-NULL, the library major version number is copied here 471 | * @param[out] minor if non-NULL, the library minor version number is copied here 472 | * @param[out] patch if non-NULL, the library patch version number is copied here 473 | * @retval "version string" The library version as a string 474 | */ 475 | char *mdb_version(int *major, int *minor, int *patch); 476 | 477 | /** @brief Return a string describing a given error code. 478 | * 479 | * This function is a superset of the ANSI C X3.159-1989 (ANSI C) strerror(3) 480 | * function. If the error code is greater than or equal to 0, then the string 481 | * returned by the system function strerror(3) is returned. If the error code 482 | * is less than 0, an error string corresponding to the LMDB library error is 483 | * returned. See @ref errors for a list of LMDB-specific error codes. 484 | * @param[in] err The error code 485 | * @retval "error message" The description of the error 486 | */ 487 | char *mdb_strerror(int err); 488 | 489 | /** @brief Create an LMDB environment handle. 490 | * 491 | * This function allocates memory for a #MDB_env structure. To release 492 | * the allocated memory and discard the handle, call #mdb_env_close(). 493 | * Before the handle may be used, it must be opened using #mdb_env_open(). 494 | * Various other options may also need to be set before opening the handle, 495 | * e.g. #mdb_env_set_mapsize(), #mdb_env_set_maxreaders(), #mdb_env_set_maxdbs(), 496 | * depending on usage requirements. 497 | * @param[out] env The address where the new handle will be stored 498 | * @return A non-zero error value on failure and 0 on success. 499 | */ 500 | int mdb_env_create(MDB_env **env); 501 | 502 | /** @brief Open an environment handle. 503 | * 504 | * If this function fails, #mdb_env_close() must be called to discard the #MDB_env handle. 505 | * @param[in] env An environment handle returned by #mdb_env_create() 506 | * @param[in] path The directory in which the database files reside. This 507 | * directory must already exist and be writable. 508 | * @param[in] flags Special options for this environment. This parameter 509 | * must be set to 0 or by bitwise OR'ing together one or more of the 510 | * values described here. 511 | * Flags set by mdb_env_set_flags() are also used. 512 | *
    513 | *
  • #MDB_FIXEDMAP 514 | * use a fixed address for the mmap region. This flag must be specified 515 | * when creating the environment, and is stored persistently in the environment. 516 | * If successful, the memory map will always reside at the same virtual address 517 | * and pointers used to reference data items in the database will be constant 518 | * across multiple invocations. This option may not always work, depending on 519 | * how the operating system has allocated memory to shared libraries and other uses. 520 | * The feature is highly experimental. 521 | *
  • #MDB_NOSUBDIR 522 | * By default, LMDB creates its environment in a directory whose 523 | * pathname is given in \b path, and creates its data and lock files 524 | * under that directory. With this option, \b path is used as-is for 525 | * the database main data file. The database lock file is the \b path 526 | * with "-lock" appended. 527 | *
  • #MDB_RDONLY 528 | * Open the environment in read-only mode. No write operations will be 529 | * allowed. LMDB will still modify the lock file - except on read-only 530 | * filesystems, where LMDB does not use locks. 531 | *
  • #MDB_WRITEMAP 532 | * Use a writeable memory map unless MDB_RDONLY is set. This uses 533 | * fewer mallocs but loses protection from application bugs 534 | * like wild pointer writes and other bad updates into the database. 535 | * This may be slightly faster for DBs that fit entirely in RAM, but 536 | * is slower for DBs larger than RAM. 537 | * Incompatible with nested transactions. 538 | * Do not mix processes with and without MDB_WRITEMAP on the same 539 | * environment. This can defeat durability (#mdb_env_sync etc). 540 | *
  • #MDB_NOMETASYNC 541 | * Flush system buffers to disk only once per transaction, omit the 542 | * metadata flush. Defer that until the system flushes files to disk, 543 | * or next non-MDB_RDONLY commit or #mdb_env_sync(). This optimization 544 | * maintains database integrity, but a system crash may undo the last 545 | * committed transaction. I.e. it preserves the ACI (atomicity, 546 | * consistency, isolation) but not D (durability) database property. 547 | * This flag may be changed at any time using #mdb_env_set_flags(). 548 | *
  • #MDB_NOSYNC 549 | * Don't flush system buffers to disk when committing a transaction. 550 | * This optimization means a system crash can corrupt the database or 551 | * lose the last transactions if buffers are not yet flushed to disk. 552 | * The risk is governed by how often the system flushes dirty buffers 553 | * to disk and how often #mdb_env_sync() is called. However, if the 554 | * filesystem preserves write order and the #MDB_WRITEMAP flag is not 555 | * used, transactions exhibit ACI (atomicity, consistency, isolation) 556 | * properties and only lose D (durability). I.e. database integrity 557 | * is maintained, but a system crash may undo the final transactions. 558 | * Note that (#MDB_NOSYNC | #MDB_WRITEMAP) leaves the system with no 559 | * hint for when to write transactions to disk, unless #mdb_env_sync() 560 | * is called. (#MDB_MAPASYNC | #MDB_WRITEMAP) may be preferable. 561 | * This flag may be changed at any time using #mdb_env_set_flags(). 562 | *
  • #MDB_MAPASYNC 563 | * When using #MDB_WRITEMAP, use asynchronous flushes to disk. 564 | * As with #MDB_NOSYNC, a system crash can then corrupt the 565 | * database or lose the last transactions. Calling #mdb_env_sync() 566 | * ensures on-disk database integrity until next commit. 567 | * This flag may be changed at any time using #mdb_env_set_flags(). 568 | *
  • #MDB_NOTLS 569 | * Don't use Thread-Local Storage. Tie reader locktable slots to 570 | * #MDB_txn objects instead of to threads. I.e. #mdb_txn_reset() keeps 571 | * the slot reseved for the #MDB_txn object. A thread may use parallel 572 | * read-only transactions. A read-only transaction may span threads if 573 | * the user synchronizes its use. Applications that multiplex many 574 | * user threads over individual OS threads need this option. Such an 575 | * application must also serialize the write transactions in an OS 576 | * thread, since LMDB's write locking is unaware of the user threads. 577 | *
  • #MDB_NOLOCK 578 | * Don't do any locking. If concurrent access is anticipated, the 579 | * caller must manage all concurrency itself. For proper operation 580 | * the caller must enforce single-writer semantics, and must ensure 581 | * that no readers are using old transactions while a writer is 582 | * active. The simplest approach is to use an exclusive lock so that 583 | * no readers may be active at all when a writer begins. 584 | *
  • #MDB_NORDAHEAD 585 | * Turn off readahead. Most operating systems perform readahead on 586 | * read requests by default. This option turns it off if the OS 587 | * supports it. Turning it off may help random read performance 588 | * when the DB is larger than RAM and system RAM is full. 589 | * The option is not implemented on Windows. 590 | *
  • #MDB_NOMEMINIT 591 | * Don't initialize malloc'd memory before writing to unused spaces 592 | * in the data file. By default, memory for pages written to the data 593 | * file is obtained using malloc. While these pages may be reused in 594 | * subsequent transactions, freshly malloc'd pages will be initialized 595 | * to zeroes before use. This avoids persisting leftover data from other 596 | * code (that used the heap and subsequently freed the memory) into the 597 | * data file. Note that many other system libraries may allocate 598 | * and free memory from the heap for arbitrary uses. E.g., stdio may 599 | * use the heap for file I/O buffers. This initialization step has a 600 | * modest performance cost so some applications may want to disable 601 | * it using this flag. This option can be a problem for applications 602 | * which handle sensitive data like passwords, and it makes memory 603 | * checkers like Valgrind noisy. This flag is not needed with #MDB_WRITEMAP, 604 | * which writes directly to the mmap instead of using malloc for pages. The 605 | * initialization is also skipped if #MDB_RESERVE is used; the 606 | * caller is expected to overwrite all of the memory that was 607 | * reserved in that case. 608 | * This flag may be changed at any time using #mdb_env_set_flags(). 609 | *
610 | * @param[in] mode The UNIX permissions to set on created files and semaphores. 611 | * This parameter is ignored on Windows. 612 | * @return A non-zero error value on failure and 0 on success. Some possible 613 | * errors are: 614 | *
    615 | *
  • #MDB_VERSION_MISMATCH - the version of the LMDB library doesn't match the 616 | * version that created the database environment. 617 | *
  • #MDB_INVALID - the environment file headers are corrupted. 618 | *
  • ENOENT - the directory specified by the path parameter doesn't exist. 619 | *
  • EACCES - the user didn't have permission to access the environment files. 620 | *
  • EAGAIN - the environment was locked by another process. 621 | *
622 | */ 623 | int mdb_env_open(MDB_env *env, const char *path, unsigned int flags, mdb_mode_t mode); 624 | 625 | /** @brief Copy an LMDB environment to the specified path. 626 | * 627 | * This function may be used to make a backup of an existing environment. 628 | * No lockfile is created, since it gets recreated at need. 629 | * @note This call can trigger significant file size growth if run in 630 | * parallel with write transactions, because it employs a read-only 631 | * transaction. See long-lived transactions under @ref caveats_sec. 632 | * @param[in] env An environment handle returned by #mdb_env_create(). It 633 | * must have already been opened successfully. 634 | * @param[in] path The directory in which the copy will reside. This 635 | * directory must already exist and be writable but must otherwise be 636 | * empty. 637 | * @return A non-zero error value on failure and 0 on success. 638 | */ 639 | int mdb_env_copy(MDB_env *env, const char *path); 640 | 641 | /** @brief Copy an LMDB environment to the specified file descriptor. 642 | * 643 | * This function may be used to make a backup of an existing environment. 644 | * No lockfile is created, since it gets recreated at need. 645 | * @note This call can trigger significant file size growth if run in 646 | * parallel with write transactions, because it employs a read-only 647 | * transaction. See long-lived transactions under @ref caveats_sec. 648 | * @param[in] env An environment handle returned by #mdb_env_create(). It 649 | * must have already been opened successfully. 650 | * @param[in] fd The filedescriptor to write the copy to. It must 651 | * have already been opened for Write access. 652 | * @return A non-zero error value on failure and 0 on success. 653 | */ 654 | int mdb_env_copyfd(MDB_env *env, mdb_filehandle_t fd); 655 | 656 | /** @brief Copy an LMDB environment to the specified path, with options. 657 | * 658 | * This function may be used to make a backup of an existing environment. 659 | * No lockfile is created, since it gets recreated at need. 660 | * @note This call can trigger significant file size growth if run in 661 | * parallel with write transactions, because it employs a read-only 662 | * transaction. See long-lived transactions under @ref caveats_sec. 663 | * @param[in] env An environment handle returned by #mdb_env_create(). It 664 | * must have already been opened successfully. 665 | * @param[in] path The directory in which the copy will reside. This 666 | * directory must already exist and be writable but must otherwise be 667 | * empty. 668 | * @param[in] flags Special options for this operation. This parameter 669 | * must be set to 0 or by bitwise OR'ing together one or more of the 670 | * values described here. 671 | *
    672 | *
  • #MDB_CP_COMPACT - Perform compaction while copying: omit free 673 | * pages and sequentially renumber all pages in output. This option 674 | * consumes more CPU and runs more slowly than the default. 675 | *
676 | * @return A non-zero error value on failure and 0 on success. 677 | */ 678 | int mdb_env_copy2(MDB_env *env, const char *path, unsigned int flags); 679 | 680 | /** @brief Copy an LMDB environment to the specified file descriptor, 681 | * with options. 682 | * 683 | * This function may be used to make a backup of an existing environment. 684 | * No lockfile is created, since it gets recreated at need. See 685 | * #mdb_env_copy2() for further details. 686 | * @note This call can trigger significant file size growth if run in 687 | * parallel with write transactions, because it employs a read-only 688 | * transaction. See long-lived transactions under @ref caveats_sec. 689 | * @param[in] env An environment handle returned by #mdb_env_create(). It 690 | * must have already been opened successfully. 691 | * @param[in] fd The filedescriptor to write the copy to. It must 692 | * have already been opened for Write access. 693 | * @param[in] flags Special options for this operation. 694 | * See #mdb_env_copy2() for options. 695 | * @return A non-zero error value on failure and 0 on success. 696 | */ 697 | int mdb_env_copyfd2(MDB_env *env, mdb_filehandle_t fd, unsigned int flags); 698 | 699 | /** @brief Return statistics about the LMDB environment. 700 | * 701 | * @param[in] env An environment handle returned by #mdb_env_create() 702 | * @param[out] stat The address of an #MDB_stat structure 703 | * where the statistics will be copied 704 | */ 705 | int mdb_env_stat(MDB_env *env, MDB_stat *stat); 706 | 707 | /** @brief Return information about the LMDB environment. 708 | * 709 | * @param[in] env An environment handle returned by #mdb_env_create() 710 | * @param[out] stat The address of an #MDB_envinfo structure 711 | * where the information will be copied 712 | */ 713 | int mdb_env_info(MDB_env *env, MDB_envinfo *stat); 714 | 715 | /** @brief Flush the data buffers to disk. 716 | * 717 | * Data is always written to disk when #mdb_txn_commit() is called, 718 | * but the operating system may keep it buffered. LMDB always flushes 719 | * the OS buffers upon commit as well, unless the environment was 720 | * opened with #MDB_NOSYNC or in part #MDB_NOMETASYNC. This call is 721 | * not valid if the environment was opened with #MDB_RDONLY. 722 | * @param[in] env An environment handle returned by #mdb_env_create() 723 | * @param[in] force If non-zero, force a synchronous flush. Otherwise 724 | * if the environment has the #MDB_NOSYNC flag set the flushes 725 | * will be omitted, and with #MDB_MAPASYNC they will be asynchronous. 726 | * @return A non-zero error value on failure and 0 on success. Some possible 727 | * errors are: 728 | *
    729 | *
  • EACCES - the environment is read-only. 730 | *
  • EINVAL - an invalid parameter was specified. 731 | *
  • EIO - an error occurred during synchronization. 732 | *
733 | */ 734 | int mdb_env_sync(MDB_env *env, int force); 735 | 736 | /** @brief Close the environment and release the memory map. 737 | * 738 | * Only a single thread may call this function. All transactions, databases, 739 | * and cursors must already be closed before calling this function. Attempts to 740 | * use any such handles after calling this function will cause a SIGSEGV. 741 | * The environment handle will be freed and must not be used again after this call. 742 | * @param[in] env An environment handle returned by #mdb_env_create() 743 | */ 744 | void mdb_env_close(MDB_env *env); 745 | 746 | /** @brief Set environment flags. 747 | * 748 | * This may be used to set some flags in addition to those from 749 | * #mdb_env_open(), or to unset these flags. If several threads 750 | * change the flags at the same time, the result is undefined. 751 | * @param[in] env An environment handle returned by #mdb_env_create() 752 | * @param[in] flags The flags to change, bitwise OR'ed together 753 | * @param[in] onoff A non-zero value sets the flags, zero clears them. 754 | * @return A non-zero error value on failure and 0 on success. Some possible 755 | * errors are: 756 | *
    757 | *
  • EINVAL - an invalid parameter was specified. 758 | *
759 | */ 760 | int mdb_env_set_flags(MDB_env *env, unsigned int flags, int onoff); 761 | 762 | /** @brief Get environment flags. 763 | * 764 | * @param[in] env An environment handle returned by #mdb_env_create() 765 | * @param[out] flags The address of an integer to store the flags 766 | * @return A non-zero error value on failure and 0 on success. Some possible 767 | * errors are: 768 | *
    769 | *
  • EINVAL - an invalid parameter was specified. 770 | *
771 | */ 772 | int mdb_env_get_flags(MDB_env *env, unsigned int *flags); 773 | 774 | /** @brief Return the path that was used in #mdb_env_open(). 775 | * 776 | * @param[in] env An environment handle returned by #mdb_env_create() 777 | * @param[out] path Address of a string pointer to contain the path. This 778 | * is the actual string in the environment, not a copy. It should not be 779 | * altered in any way. 780 | * @return A non-zero error value on failure and 0 on success. Some possible 781 | * errors are: 782 | *
    783 | *
  • EINVAL - an invalid parameter was specified. 784 | *
785 | */ 786 | int mdb_env_get_path(MDB_env *env, const char **path); 787 | 788 | /** @brief Return the filedescriptor for the given environment. 789 | * 790 | * @param[in] env An environment handle returned by #mdb_env_create() 791 | * @param[out] fd Address of a mdb_filehandle_t to contain the descriptor. 792 | * @return A non-zero error value on failure and 0 on success. Some possible 793 | * errors are: 794 | *
    795 | *
  • EINVAL - an invalid parameter was specified. 796 | *
797 | */ 798 | int mdb_env_get_fd(MDB_env *env, mdb_filehandle_t *fd); 799 | 800 | /** @brief Set the size of the memory map to use for this environment. 801 | * 802 | * The size should be a multiple of the OS page size. The default is 803 | * 10485760 bytes. The size of the memory map is also the maximum size 804 | * of the database. The value should be chosen as large as possible, 805 | * to accommodate future growth of the database. 806 | * This function should be called after #mdb_env_create() and before #mdb_env_open(). 807 | * It may be called at later times if no transactions are active in 808 | * this process. Note that the library does not check for this condition, 809 | * the caller must ensure it explicitly. 810 | * 811 | * The new size takes effect immediately for the current process but 812 | * will not be persisted to any others until a write transaction has been 813 | * committed by the current process. Also, only mapsize increases are 814 | * persisted into the environment. 815 | * 816 | * If the mapsize is increased by another process, and data has grown 817 | * beyond the range of the current mapsize, #mdb_txn_begin() will 818 | * return #MDB_MAP_RESIZED. This function may be called with a size 819 | * of zero to adopt the new size. 820 | * 821 | * Any attempt to set a size smaller than the space already consumed 822 | * by the environment will be silently changed to the current size of the used space. 823 | * @param[in] env An environment handle returned by #mdb_env_create() 824 | * @param[in] size The size in bytes 825 | * @return A non-zero error value on failure and 0 on success. Some possible 826 | * errors are: 827 | *
    828 | *
  • EINVAL - an invalid parameter was specified, or the environment has 829 | * an active write transaction. 830 | *
831 | */ 832 | int mdb_env_set_mapsize(MDB_env *env, size_t size); 833 | 834 | /** @brief Set the maximum number of threads/reader slots for the environment. 835 | * 836 | * This defines the number of slots in the lock table that is used to track readers in the 837 | * the environment. The default is 126. 838 | * Starting a read-only transaction normally ties a lock table slot to the 839 | * current thread until the environment closes or the thread exits. If 840 | * MDB_NOTLS is in use, #mdb_txn_begin() instead ties the slot to the 841 | * MDB_txn object until it or the #MDB_env object is destroyed. 842 | * This function may only be called after #mdb_env_create() and before #mdb_env_open(). 843 | * @param[in] env An environment handle returned by #mdb_env_create() 844 | * @param[in] readers The maximum number of reader lock table slots 845 | * @return A non-zero error value on failure and 0 on success. Some possible 846 | * errors are: 847 | *
    848 | *
  • EINVAL - an invalid parameter was specified, or the environment is already open. 849 | *
850 | */ 851 | int mdb_env_set_maxreaders(MDB_env *env, unsigned int readers); 852 | 853 | /** @brief Get the maximum number of threads/reader slots for the environment. 854 | * 855 | * @param[in] env An environment handle returned by #mdb_env_create() 856 | * @param[out] readers Address of an integer to store the number of readers 857 | * @return A non-zero error value on failure and 0 on success. Some possible 858 | * errors are: 859 | *
    860 | *
  • EINVAL - an invalid parameter was specified. 861 | *
862 | */ 863 | int mdb_env_get_maxreaders(MDB_env *env, unsigned int *readers); 864 | 865 | /** @brief Set the maximum number of named databases for the environment. 866 | * 867 | * This function is only needed if multiple databases will be used in the 868 | * environment. Simpler applications that use the environment as a single 869 | * unnamed database can ignore this option. 870 | * This function may only be called after #mdb_env_create() and before #mdb_env_open(). 871 | * 872 | * Currently a moderate number of slots are cheap but a huge number gets 873 | * expensive: 7-120 words per transaction, and every #mdb_dbi_open() 874 | * does a linear search of the opened slots. 875 | * @param[in] env An environment handle returned by #mdb_env_create() 876 | * @param[in] dbs The maximum number of databases 877 | * @return A non-zero error value on failure and 0 on success. Some possible 878 | * errors are: 879 | *
    880 | *
  • EINVAL - an invalid parameter was specified, or the environment is already open. 881 | *
882 | */ 883 | int mdb_env_set_maxdbs(MDB_env *env, MDB_dbi dbs); 884 | 885 | /** @brief Get the maximum size of keys and #MDB_DUPSORT data we can write. 886 | * 887 | * Depends on the compile-time constant #MDB_MAXKEYSIZE. Default 511. 888 | * See @ref MDB_val. 889 | * @param[in] env An environment handle returned by #mdb_env_create() 890 | * @return The maximum size of a key we can write 891 | */ 892 | int mdb_env_get_maxkeysize(MDB_env *env); 893 | 894 | /** @brief Set application information associated with the #MDB_env. 895 | * 896 | * @param[in] env An environment handle returned by #mdb_env_create() 897 | * @param[in] ctx An arbitrary pointer for whatever the application needs. 898 | * @return A non-zero error value on failure and 0 on success. 899 | */ 900 | int mdb_env_set_userctx(MDB_env *env, void *ctx); 901 | 902 | /** @brief Get the application information associated with the #MDB_env. 903 | * 904 | * @param[in] env An environment handle returned by #mdb_env_create() 905 | * @return The pointer set by #mdb_env_set_userctx(). 906 | */ 907 | void *mdb_env_get_userctx(MDB_env *env); 908 | 909 | /** @brief A callback function for most LMDB assert() failures, 910 | * called before printing the message and aborting. 911 | * 912 | * @param[in] env An environment handle returned by #mdb_env_create(). 913 | * @param[in] msg The assertion message, not including newline. 914 | */ 915 | typedef void MDB_assert_func(MDB_env *env, const char *msg); 916 | 917 | /** Set or reset the assert() callback of the environment. 918 | * Disabled if liblmdb is buillt with NDEBUG. 919 | * @note This hack should become obsolete as lmdb's error handling matures. 920 | * @param[in] env An environment handle returned by #mdb_env_create(). 921 | * @param[in] func An #MDB_assert_func function, or 0. 922 | * @return A non-zero error value on failure and 0 on success. 923 | */ 924 | int mdb_env_set_assert(MDB_env *env, MDB_assert_func *func); 925 | 926 | /** @brief Create a transaction for use with the environment. 927 | * 928 | * The transaction handle may be discarded using #mdb_txn_abort() or #mdb_txn_commit(). 929 | * @note A transaction and its cursors must only be used by a single 930 | * thread, and a thread may only have a single transaction at a time. 931 | * If #MDB_NOTLS is in use, this does not apply to read-only transactions. 932 | * @note Cursors may not span transactions. 933 | * @param[in] env An environment handle returned by #mdb_env_create() 934 | * @param[in] parent If this parameter is non-NULL, the new transaction 935 | * will be a nested transaction, with the transaction indicated by \b parent 936 | * as its parent. Transactions may be nested to any level. A parent 937 | * transaction and its cursors may not issue any other operations than 938 | * mdb_txn_commit and mdb_txn_abort while it has active child transactions. 939 | * @param[in] flags Special options for this transaction. This parameter 940 | * must be set to 0 or by bitwise OR'ing together one or more of the 941 | * values described here. 942 | *
    943 | *
  • #MDB_RDONLY 944 | * This transaction will not perform any write operations. 945 | *
946 | * @param[out] txn Address where the new #MDB_txn handle will be stored 947 | * @return A non-zero error value on failure and 0 on success. Some possible 948 | * errors are: 949 | *
    950 | *
  • #MDB_PANIC - a fatal error occurred earlier and the environment 951 | * must be shut down. 952 | *
  • #MDB_MAP_RESIZED - another process wrote data beyond this MDB_env's 953 | * mapsize and this environment's map must be resized as well. 954 | * See #mdb_env_set_mapsize(). 955 | *
  • #MDB_READERS_FULL - a read-only transaction was requested and 956 | * the reader lock table is full. See #mdb_env_set_maxreaders(). 957 | *
  • ENOMEM - out of memory. 958 | *
959 | */ 960 | int mdb_txn_begin(MDB_env *env, MDB_txn *parent, unsigned int flags, MDB_txn **txn); 961 | 962 | /** @brief Returns the transaction's #MDB_env 963 | * 964 | * @param[in] txn A transaction handle returned by #mdb_txn_begin() 965 | */ 966 | MDB_env *mdb_txn_env(MDB_txn *txn); 967 | 968 | /** @brief Return the transaction's ID. 969 | * 970 | * This returns the identifier associated with this transaction. For a 971 | * read-only transaction, this corresponds to the snapshot being read; 972 | * concurrent readers will frequently have the same transaction ID. 973 | * 974 | * @param[in] txn A transaction handle returned by #mdb_txn_begin() 975 | * @return A transaction ID, valid if input is an active transaction. 976 | */ 977 | size_t mdb_txn_id(MDB_txn *txn); 978 | 979 | /** @brief Commit all the operations of a transaction into the database. 980 | * 981 | * The transaction handle is freed. It and its cursors must not be used 982 | * again after this call, except with #mdb_cursor_renew(). 983 | * @note Earlier documentation incorrectly said all cursors would be freed. 984 | * Only write-transactions free cursors. 985 | * @param[in] txn A transaction handle returned by #mdb_txn_begin() 986 | * @return A non-zero error value on failure and 0 on success. Some possible 987 | * errors are: 988 | *
    989 | *
  • EINVAL - an invalid parameter was specified. 990 | *
  • ENOSPC - no more disk space. 991 | *
  • EIO - a low-level I/O error occurred while writing. 992 | *
  • ENOMEM - out of memory. 993 | *
994 | */ 995 | int mdb_txn_commit(MDB_txn *txn); 996 | 997 | /** @brief Abandon all the operations of the transaction instead of saving them. 998 | * 999 | * The transaction handle is freed. It and its cursors must not be used 1000 | * again after this call, except with #mdb_cursor_renew(). 1001 | * @note Earlier documentation incorrectly said all cursors would be freed. 1002 | * Only write-transactions free cursors. 1003 | * @param[in] txn A transaction handle returned by #mdb_txn_begin() 1004 | */ 1005 | void mdb_txn_abort(MDB_txn *txn); 1006 | 1007 | /** @brief Reset a read-only transaction. 1008 | * 1009 | * Abort the transaction like #mdb_txn_abort(), but keep the transaction 1010 | * handle. #mdb_txn_renew() may reuse the handle. This saves allocation 1011 | * overhead if the process will start a new read-only transaction soon, 1012 | * and also locking overhead if #MDB_NOTLS is in use. The reader table 1013 | * lock is released, but the table slot stays tied to its thread or 1014 | * #MDB_txn. Use mdb_txn_abort() to discard a reset handle, and to free 1015 | * its lock table slot if MDB_NOTLS is in use. 1016 | * Cursors opened within the transaction must not be used 1017 | * again after this call, except with #mdb_cursor_renew(). 1018 | * Reader locks generally don't interfere with writers, but they keep old 1019 | * versions of database pages allocated. Thus they prevent the old pages 1020 | * from being reused when writers commit new data, and so under heavy load 1021 | * the database size may grow much more rapidly than otherwise. 1022 | * @param[in] txn A transaction handle returned by #mdb_txn_begin() 1023 | */ 1024 | void mdb_txn_reset(MDB_txn *txn); 1025 | 1026 | /** @brief Renew a read-only transaction. 1027 | * 1028 | * This acquires a new reader lock for a transaction handle that had been 1029 | * released by #mdb_txn_reset(). It must be called before a reset transaction 1030 | * may be used again. 1031 | * @param[in] txn A transaction handle returned by #mdb_txn_begin() 1032 | * @return A non-zero error value on failure and 0 on success. Some possible 1033 | * errors are: 1034 | *
    1035 | *
  • #MDB_PANIC - a fatal error occurred earlier and the environment 1036 | * must be shut down. 1037 | *
  • EINVAL - an invalid parameter was specified. 1038 | *
1039 | */ 1040 | int mdb_txn_renew(MDB_txn *txn); 1041 | 1042 | /** Compat with version <= 0.9.4, avoid clash with libmdb from MDB Tools project */ 1043 | #define mdb_open(txn,name,flags,dbi) mdb_dbi_open(txn,name,flags,dbi) 1044 | /** Compat with version <= 0.9.4, avoid clash with libmdb from MDB Tools project */ 1045 | #define mdb_close(env,dbi) mdb_dbi_close(env,dbi) 1046 | 1047 | /** @brief Open a database in the environment. 1048 | * 1049 | * A database handle denotes the name and parameters of a database, 1050 | * independently of whether such a database exists. 1051 | * The database handle may be discarded by calling #mdb_dbi_close(). 1052 | * The old database handle is returned if the database was already open. 1053 | * The handle may only be closed once. 1054 | * 1055 | * The database handle will be private to the current transaction until 1056 | * the transaction is successfully committed. If the transaction is 1057 | * aborted the handle will be closed automatically. 1058 | * After a successful commit the handle will reside in the shared 1059 | * environment, and may be used by other transactions. 1060 | * 1061 | * This function must not be called from multiple concurrent 1062 | * transactions in the same process. A transaction that uses 1063 | * this function must finish (either commit or abort) before 1064 | * any other transaction in the process may use this function. 1065 | * 1066 | * To use named databases (with name != NULL), #mdb_env_set_maxdbs() 1067 | * must be called before opening the environment. Database names are 1068 | * keys in the unnamed database, and may be read but not written. 1069 | * 1070 | * @param[in] txn A transaction handle returned by #mdb_txn_begin() 1071 | * @param[in] name The name of the database to open. If only a single 1072 | * database is needed in the environment, this value may be NULL. 1073 | * @param[in] flags Special options for this database. This parameter 1074 | * must be set to 0 or by bitwise OR'ing together one or more of the 1075 | * values described here. 1076 | *
    1077 | *
  • #MDB_REVERSEKEY 1078 | * Keys are strings to be compared in reverse order, from the end 1079 | * of the strings to the beginning. By default, Keys are treated as strings and 1080 | * compared from beginning to end. 1081 | *
  • #MDB_DUPSORT 1082 | * Duplicate keys may be used in the database. (Or, from another perspective, 1083 | * keys may have multiple data items, stored in sorted order.) By default 1084 | * keys must be unique and may have only a single data item. 1085 | *
  • #MDB_INTEGERKEY 1086 | * Keys are binary integers in native byte order, either unsigned int 1087 | * or size_t, and will be sorted as such. 1088 | * The keys must all be of the same size. 1089 | *
  • #MDB_DUPFIXED 1090 | * This flag may only be used in combination with #MDB_DUPSORT. This option 1091 | * tells the library that the data items for this database are all the same 1092 | * size, which allows further optimizations in storage and retrieval. When 1093 | * all data items are the same size, the #MDB_GET_MULTIPLE and #MDB_NEXT_MULTIPLE 1094 | * cursor operations may be used to retrieve multiple items at once. 1095 | *
  • #MDB_INTEGERDUP 1096 | * This option specifies that duplicate data items are binary integers, 1097 | * similar to #MDB_INTEGERKEY keys. 1098 | *
  • #MDB_REVERSEDUP 1099 | * This option specifies that duplicate data items should be compared as 1100 | * strings in reverse order. 1101 | *
  • #MDB_CREATE 1102 | * Create the named database if it doesn't exist. This option is not 1103 | * allowed in a read-only transaction or a read-only environment. 1104 | *
1105 | * @param[out] dbi Address where the new #MDB_dbi handle will be stored 1106 | * @return A non-zero error value on failure and 0 on success. Some possible 1107 | * errors are: 1108 | *
    1109 | *
  • #MDB_NOTFOUND - the specified database doesn't exist in the environment 1110 | * and #MDB_CREATE was not specified. 1111 | *
  • #MDB_DBS_FULL - too many databases have been opened. See #mdb_env_set_maxdbs(). 1112 | *
1113 | */ 1114 | int mdb_dbi_open(MDB_txn *txn, const char *name, unsigned int flags, MDB_dbi *dbi); 1115 | 1116 | /** @brief Retrieve statistics for a database. 1117 | * 1118 | * @param[in] txn A transaction handle returned by #mdb_txn_begin() 1119 | * @param[in] dbi A database handle returned by #mdb_dbi_open() 1120 | * @param[out] stat The address of an #MDB_stat structure 1121 | * where the statistics will be copied 1122 | * @return A non-zero error value on failure and 0 on success. Some possible 1123 | * errors are: 1124 | *
    1125 | *
  • EINVAL - an invalid parameter was specified. 1126 | *
1127 | */ 1128 | int mdb_stat(MDB_txn *txn, MDB_dbi dbi, MDB_stat *stat); 1129 | 1130 | /** @brief Retrieve the DB flags for a database handle. 1131 | * 1132 | * @param[in] txn A transaction handle returned by #mdb_txn_begin() 1133 | * @param[in] dbi A database handle returned by #mdb_dbi_open() 1134 | * @param[out] flags Address where the flags will be returned. 1135 | * @return A non-zero error value on failure and 0 on success. 1136 | */ 1137 | int mdb_dbi_flags(MDB_txn *txn, MDB_dbi dbi, unsigned int *flags); 1138 | 1139 | /** @brief Close a database handle. Normally unnecessary. Use with care: 1140 | * 1141 | * This call is not mutex protected. Handles should only be closed by 1142 | * a single thread, and only if no other threads are going to reference 1143 | * the database handle or one of its cursors any further. Do not close 1144 | * a handle if an existing transaction has modified its database. 1145 | * Doing so can cause misbehavior from database corruption to errors 1146 | * like MDB_BAD_VALSIZE (since the DB name is gone). 1147 | * 1148 | * Closing a database handle is not necessary, but lets #mdb_dbi_open() 1149 | * reuse the handle value. Usually it's better to set a bigger 1150 | * #mdb_env_set_maxdbs(), unless that value would be large. 1151 | * 1152 | * @param[in] env An environment handle returned by #mdb_env_create() 1153 | * @param[in] dbi A database handle returned by #mdb_dbi_open() 1154 | */ 1155 | void mdb_dbi_close(MDB_env *env, MDB_dbi dbi); 1156 | 1157 | /** @brief Empty or delete+close a database. 1158 | * 1159 | * See #mdb_dbi_close() for restrictions about closing the DB handle. 1160 | * @param[in] txn A transaction handle returned by #mdb_txn_begin() 1161 | * @param[in] dbi A database handle returned by #mdb_dbi_open() 1162 | * @param[in] del 0 to empty the DB, 1 to delete it from the 1163 | * environment and close the DB handle. 1164 | * @return A non-zero error value on failure and 0 on success. 1165 | */ 1166 | int mdb_drop(MDB_txn *txn, MDB_dbi dbi, int del); 1167 | 1168 | /** @brief Set a custom key comparison function for a database. 1169 | * 1170 | * The comparison function is called whenever it is necessary to compare a 1171 | * key specified by the application with a key currently stored in the database. 1172 | * If no comparison function is specified, and no special key flags were specified 1173 | * with #mdb_dbi_open(), the keys are compared lexically, with shorter keys collating 1174 | * before longer keys. 1175 | * @warning This function must be called before any data access functions are used, 1176 | * otherwise data corruption may occur. The same comparison function must be used by every 1177 | * program accessing the database, every time the database is used. 1178 | * @param[in] txn A transaction handle returned by #mdb_txn_begin() 1179 | * @param[in] dbi A database handle returned by #mdb_dbi_open() 1180 | * @param[in] cmp A #MDB_cmp_func function 1181 | * @return A non-zero error value on failure and 0 on success. Some possible 1182 | * errors are: 1183 | *
    1184 | *
  • EINVAL - an invalid parameter was specified. 1185 | *
1186 | */ 1187 | int mdb_set_compare(MDB_txn *txn, MDB_dbi dbi, MDB_cmp_func *cmp); 1188 | 1189 | /** @brief Set a custom data comparison function for a #MDB_DUPSORT database. 1190 | * 1191 | * This comparison function is called whenever it is necessary to compare a data 1192 | * item specified by the application with a data item currently stored in the database. 1193 | * This function only takes effect if the database was opened with the #MDB_DUPSORT 1194 | * flag. 1195 | * If no comparison function is specified, and no special key flags were specified 1196 | * with #mdb_dbi_open(), the data items are compared lexically, with shorter items collating 1197 | * before longer items. 1198 | * @warning This function must be called before any data access functions are used, 1199 | * otherwise data corruption may occur. The same comparison function must be used by every 1200 | * program accessing the database, every time the database is used. 1201 | * @param[in] txn A transaction handle returned by #mdb_txn_begin() 1202 | * @param[in] dbi A database handle returned by #mdb_dbi_open() 1203 | * @param[in] cmp A #MDB_cmp_func function 1204 | * @return A non-zero error value on failure and 0 on success. Some possible 1205 | * errors are: 1206 | *
    1207 | *
  • EINVAL - an invalid parameter was specified. 1208 | *
1209 | */ 1210 | int mdb_set_dupsort(MDB_txn *txn, MDB_dbi dbi, MDB_cmp_func *cmp); 1211 | 1212 | /** @brief Set a relocation function for a #MDB_FIXEDMAP database. 1213 | * 1214 | * @todo The relocation function is called whenever it is necessary to move the data 1215 | * of an item to a different position in the database (e.g. through tree 1216 | * balancing operations, shifts as a result of adds or deletes, etc.). It is 1217 | * intended to allow address/position-dependent data items to be stored in 1218 | * a database in an environment opened with the #MDB_FIXEDMAP option. 1219 | * Currently the relocation feature is unimplemented and setting 1220 | * this function has no effect. 1221 | * @param[in] txn A transaction handle returned by #mdb_txn_begin() 1222 | * @param[in] dbi A database handle returned by #mdb_dbi_open() 1223 | * @param[in] rel A #MDB_rel_func function 1224 | * @return A non-zero error value on failure and 0 on success. Some possible 1225 | * errors are: 1226 | *
    1227 | *
  • EINVAL - an invalid parameter was specified. 1228 | *
1229 | */ 1230 | int mdb_set_relfunc(MDB_txn *txn, MDB_dbi dbi, MDB_rel_func *rel); 1231 | 1232 | /** @brief Set a context pointer for a #MDB_FIXEDMAP database's relocation function. 1233 | * 1234 | * See #mdb_set_relfunc and #MDB_rel_func for more details. 1235 | * @param[in] txn A transaction handle returned by #mdb_txn_begin() 1236 | * @param[in] dbi A database handle returned by #mdb_dbi_open() 1237 | * @param[in] ctx An arbitrary pointer for whatever the application needs. 1238 | * It will be passed to the callback function set by #mdb_set_relfunc 1239 | * as its \b relctx parameter whenever the callback is invoked. 1240 | * @return A non-zero error value on failure and 0 on success. Some possible 1241 | * errors are: 1242 | *
    1243 | *
  • EINVAL - an invalid parameter was specified. 1244 | *
1245 | */ 1246 | int mdb_set_relctx(MDB_txn *txn, MDB_dbi dbi, void *ctx); 1247 | 1248 | /** @brief Get items from a database. 1249 | * 1250 | * This function retrieves key/data pairs from the database. The address 1251 | * and length of the data associated with the specified \b key are returned 1252 | * in the structure to which \b data refers. 1253 | * If the database supports duplicate keys (#MDB_DUPSORT) then the 1254 | * first data item for the key will be returned. Retrieval of other 1255 | * items requires the use of #mdb_cursor_get(). 1256 | * 1257 | * @note The memory pointed to by the returned values is owned by the 1258 | * database. The caller need not dispose of the memory, and may not 1259 | * modify it in any way. For values returned in a read-only transaction 1260 | * any modification attempts will cause a SIGSEGV. 1261 | * @note Values returned from the database are valid only until a 1262 | * subsequent update operation, or the end of the transaction. 1263 | * @param[in] txn A transaction handle returned by #mdb_txn_begin() 1264 | * @param[in] dbi A database handle returned by #mdb_dbi_open() 1265 | * @param[in] key The key to search for in the database 1266 | * @param[out] data The data corresponding to the key 1267 | * @return A non-zero error value on failure and 0 on success. Some possible 1268 | * errors are: 1269 | *
    1270 | *
  • #MDB_NOTFOUND - the key was not in the database. 1271 | *
  • EINVAL - an invalid parameter was specified. 1272 | *
1273 | */ 1274 | int mdb_get(MDB_txn *txn, MDB_dbi dbi, MDB_val *key, MDB_val *data); 1275 | 1276 | /** @brief Store items into a database. 1277 | * 1278 | * This function stores key/data pairs in the database. The default behavior 1279 | * is to enter the new key/data pair, replacing any previously existing key 1280 | * if duplicates are disallowed, or adding a duplicate data item if 1281 | * duplicates are allowed (#MDB_DUPSORT). 1282 | * @param[in] txn A transaction handle returned by #mdb_txn_begin() 1283 | * @param[in] dbi A database handle returned by #mdb_dbi_open() 1284 | * @param[in] key The key to store in the database 1285 | * @param[in,out] data The data to store 1286 | * @param[in] flags Special options for this operation. This parameter 1287 | * must be set to 0 or by bitwise OR'ing together one or more of the 1288 | * values described here. 1289 | *
    1290 | *
  • #MDB_NODUPDATA - enter the new key/data pair only if it does not 1291 | * already appear in the database. This flag may only be specified 1292 | * if the database was opened with #MDB_DUPSORT. The function will 1293 | * return #MDB_KEYEXIST if the key/data pair already appears in the 1294 | * database. 1295 | *
  • #MDB_NOOVERWRITE - enter the new key/data pair only if the key 1296 | * does not already appear in the database. The function will return 1297 | * #MDB_KEYEXIST if the key already appears in the database, even if 1298 | * the database supports duplicates (#MDB_DUPSORT). The \b data 1299 | * parameter will be set to point to the existing item. 1300 | *
  • #MDB_RESERVE - reserve space for data of the given size, but 1301 | * don't copy the given data. Instead, return a pointer to the 1302 | * reserved space, which the caller can fill in later - before 1303 | * the next update operation or the transaction ends. This saves 1304 | * an extra memcpy if the data is being generated later. 1305 | * LMDB does nothing else with this memory, the caller is expected 1306 | * to modify all of the space requested. This flag must not be 1307 | * specified if the database was opened with #MDB_DUPSORT. 1308 | *
  • #MDB_APPEND - append the given key/data pair to the end of the 1309 | * database. This option allows fast bulk loading when keys are 1310 | * already known to be in the correct order. Loading unsorted keys 1311 | * with this flag will cause a #MDB_KEYEXIST error. 1312 | *
  • #MDB_APPENDDUP - as above, but for sorted dup data. 1313 | *
1314 | * @return A non-zero error value on failure and 0 on success. Some possible 1315 | * errors are: 1316 | *
    1317 | *
  • #MDB_MAP_FULL - the database is full, see #mdb_env_set_mapsize(). 1318 | *
  • #MDB_TXN_FULL - the transaction has too many dirty pages. 1319 | *
  • EACCES - an attempt was made to write in a read-only transaction. 1320 | *
  • EINVAL - an invalid parameter was specified. 1321 | *
1322 | */ 1323 | int mdb_put(MDB_txn *txn, MDB_dbi dbi, MDB_val *key, MDB_val *data, 1324 | unsigned int flags); 1325 | 1326 | /** @brief Delete items from a database. 1327 | * 1328 | * This function removes key/data pairs from the database. 1329 | * If the database does not support sorted duplicate data items 1330 | * (#MDB_DUPSORT) the data parameter is ignored. 1331 | * If the database supports sorted duplicates and the data parameter 1332 | * is NULL, all of the duplicate data items for the key will be 1333 | * deleted. Otherwise, if the data parameter is non-NULL 1334 | * only the matching data item will be deleted. 1335 | * This function will return #MDB_NOTFOUND if the specified key/data 1336 | * pair is not in the database. 1337 | * @param[in] txn A transaction handle returned by #mdb_txn_begin() 1338 | * @param[in] dbi A database handle returned by #mdb_dbi_open() 1339 | * @param[in] key The key to delete from the database 1340 | * @param[in] data The data to delete 1341 | * @return A non-zero error value on failure and 0 on success. Some possible 1342 | * errors are: 1343 | *
    1344 | *
  • EACCES - an attempt was made to write in a read-only transaction. 1345 | *
  • EINVAL - an invalid parameter was specified. 1346 | *
1347 | */ 1348 | int mdb_del(MDB_txn *txn, MDB_dbi dbi, MDB_val *key, MDB_val *data); 1349 | 1350 | /** @brief Create a cursor handle. 1351 | * 1352 | * A cursor is associated with a specific transaction and database. 1353 | * A cursor cannot be used when its database handle is closed. Nor 1354 | * when its transaction has ended, except with #mdb_cursor_renew(). 1355 | * It can be discarded with #mdb_cursor_close(). 1356 | * A cursor in a write-transaction can be closed before its transaction 1357 | * ends, and will otherwise be closed when its transaction ends. 1358 | * A cursor in a read-only transaction must be closed explicitly, before 1359 | * or after its transaction ends. It can be reused with 1360 | * #mdb_cursor_renew() before finally closing it. 1361 | * @note Earlier documentation said that cursors in every transaction 1362 | * were closed when the transaction committed or aborted. 1363 | * @param[in] txn A transaction handle returned by #mdb_txn_begin() 1364 | * @param[in] dbi A database handle returned by #mdb_dbi_open() 1365 | * @param[out] cursor Address where the new #MDB_cursor handle will be stored 1366 | * @return A non-zero error value on failure and 0 on success. Some possible 1367 | * errors are: 1368 | *
    1369 | *
  • EINVAL - an invalid parameter was specified. 1370 | *
1371 | */ 1372 | int mdb_cursor_open(MDB_txn *txn, MDB_dbi dbi, MDB_cursor **cursor); 1373 | 1374 | /** @brief Close a cursor handle. 1375 | * 1376 | * The cursor handle will be freed and must not be used again after this call. 1377 | * Its transaction must still be live if it is a write-transaction. 1378 | * @param[in] cursor A cursor handle returned by #mdb_cursor_open() 1379 | */ 1380 | void mdb_cursor_close(MDB_cursor *cursor); 1381 | 1382 | /** @brief Renew a cursor handle. 1383 | * 1384 | * A cursor is associated with a specific transaction and database. 1385 | * Cursors that are only used in read-only 1386 | * transactions may be re-used, to avoid unnecessary malloc/free overhead. 1387 | * The cursor may be associated with a new read-only transaction, and 1388 | * referencing the same database handle as it was created with. 1389 | * This may be done whether the previous transaction is live or dead. 1390 | * @param[in] txn A transaction handle returned by #mdb_txn_begin() 1391 | * @param[in] cursor A cursor handle returned by #mdb_cursor_open() 1392 | * @return A non-zero error value on failure and 0 on success. Some possible 1393 | * errors are: 1394 | *
    1395 | *
  • EINVAL - an invalid parameter was specified. 1396 | *
1397 | */ 1398 | int mdb_cursor_renew(MDB_txn *txn, MDB_cursor *cursor); 1399 | 1400 | /** @brief Return the cursor's transaction handle. 1401 | * 1402 | * @param[in] cursor A cursor handle returned by #mdb_cursor_open() 1403 | */ 1404 | MDB_txn *mdb_cursor_txn(MDB_cursor *cursor); 1405 | 1406 | /** @brief Return the cursor's database handle. 1407 | * 1408 | * @param[in] cursor A cursor handle returned by #mdb_cursor_open() 1409 | */ 1410 | MDB_dbi mdb_cursor_dbi(MDB_cursor *cursor); 1411 | 1412 | /** @brief Retrieve by cursor. 1413 | * 1414 | * This function retrieves key/data pairs from the database. The address and length 1415 | * of the key are returned in the object to which \b key refers (except for the 1416 | * case of the #MDB_SET option, in which the \b key object is unchanged), and 1417 | * the address and length of the data are returned in the object to which \b data 1418 | * refers. 1419 | * See #mdb_get() for restrictions on using the output values. 1420 | * @param[in] cursor A cursor handle returned by #mdb_cursor_open() 1421 | * @param[in,out] key The key for a retrieved item 1422 | * @param[in,out] data The data of a retrieved item 1423 | * @param[in] op A cursor operation #MDB_cursor_op 1424 | * @return A non-zero error value on failure and 0 on success. Some possible 1425 | * errors are: 1426 | *
    1427 | *
  • #MDB_NOTFOUND - no matching key found. 1428 | *
  • EINVAL - an invalid parameter was specified. 1429 | *
1430 | */ 1431 | int mdb_cursor_get(MDB_cursor *cursor, MDB_val *key, MDB_val *data, 1432 | MDB_cursor_op op); 1433 | 1434 | /** @brief Store by cursor. 1435 | * 1436 | * This function stores key/data pairs into the database. 1437 | * The cursor is positioned at the new item, or on failure usually near it. 1438 | * @note Earlier documentation incorrectly said errors would leave the 1439 | * state of the cursor unchanged. 1440 | * @param[in] cursor A cursor handle returned by #mdb_cursor_open() 1441 | * @param[in] key The key operated on. 1442 | * @param[in] data The data operated on. 1443 | * @param[in] flags Options for this operation. This parameter 1444 | * must be set to 0 or one of the values described here. 1445 | *
    1446 | *
  • #MDB_CURRENT - replace the item at the current cursor position. 1447 | * The \b key parameter must still be provided, and must match it. 1448 | * If using sorted duplicates (#MDB_DUPSORT) the data item must still 1449 | * sort into the same place. This is intended to be used when the 1450 | * new data is the same size as the old. Otherwise it will simply 1451 | * perform a delete of the old record followed by an insert. 1452 | *
  • #MDB_NODUPDATA - enter the new key/data pair only if it does not 1453 | * already appear in the database. This flag may only be specified 1454 | * if the database was opened with #MDB_DUPSORT. The function will 1455 | * return #MDB_KEYEXIST if the key/data pair already appears in the 1456 | * database. 1457 | *
  • #MDB_NOOVERWRITE - enter the new key/data pair only if the key 1458 | * does not already appear in the database. The function will return 1459 | * #MDB_KEYEXIST if the key already appears in the database, even if 1460 | * the database supports duplicates (#MDB_DUPSORT). 1461 | *
  • #MDB_RESERVE - reserve space for data of the given size, but 1462 | * don't copy the given data. Instead, return a pointer to the 1463 | * reserved space, which the caller can fill in later - before 1464 | * the next update operation or the transaction ends. This saves 1465 | * an extra memcpy if the data is being generated later. This flag 1466 | * must not be specified if the database was opened with #MDB_DUPSORT. 1467 | *
  • #MDB_APPEND - append the given key/data pair to the end of the 1468 | * database. No key comparisons are performed. This option allows 1469 | * fast bulk loading when keys are already known to be in the 1470 | * correct order. Loading unsorted keys with this flag will cause 1471 | * a #MDB_KEYEXIST error. 1472 | *
  • #MDB_APPENDDUP - as above, but for sorted dup data. 1473 | *
  • #MDB_MULTIPLE - store multiple contiguous data elements in a 1474 | * single request. This flag may only be specified if the database 1475 | * was opened with #MDB_DUPFIXED. The \b data argument must be an 1476 | * array of two MDB_vals. The mv_size of the first MDB_val must be 1477 | * the size of a single data element. The mv_data of the first MDB_val 1478 | * must point to the beginning of the array of contiguous data elements. 1479 | * The mv_size of the second MDB_val must be the count of the number 1480 | * of data elements to store. On return this field will be set to 1481 | * the count of the number of elements actually written. The mv_data 1482 | * of the second MDB_val is unused. 1483 | *
1484 | * @return A non-zero error value on failure and 0 on success. Some possible 1485 | * errors are: 1486 | *
    1487 | *
  • #MDB_MAP_FULL - the database is full, see #mdb_env_set_mapsize(). 1488 | *
  • #MDB_TXN_FULL - the transaction has too many dirty pages. 1489 | *
  • EACCES - an attempt was made to write in a read-only transaction. 1490 | *
  • EINVAL - an invalid parameter was specified. 1491 | *
1492 | */ 1493 | int mdb_cursor_put(MDB_cursor *cursor, MDB_val *key, MDB_val *data, 1494 | unsigned int flags); 1495 | 1496 | /** @brief Delete current key/data pair 1497 | * 1498 | * This function deletes the key/data pair to which the cursor refers. 1499 | * @param[in] cursor A cursor handle returned by #mdb_cursor_open() 1500 | * @param[in] flags Options for this operation. This parameter 1501 | * must be set to 0 or one of the values described here. 1502 | *
    1503 | *
  • #MDB_NODUPDATA - delete all of the data items for the current key. 1504 | * This flag may only be specified if the database was opened with #MDB_DUPSORT. 1505 | *
1506 | * @return A non-zero error value on failure and 0 on success. Some possible 1507 | * errors are: 1508 | *
    1509 | *
  • EACCES - an attempt was made to write in a read-only transaction. 1510 | *
  • EINVAL - an invalid parameter was specified. 1511 | *
1512 | */ 1513 | int mdb_cursor_del(MDB_cursor *cursor, unsigned int flags); 1514 | 1515 | /** @brief Return count of duplicates for current key. 1516 | * 1517 | * This call is only valid on databases that support sorted duplicate 1518 | * data items #MDB_DUPSORT. 1519 | * @param[in] cursor A cursor handle returned by #mdb_cursor_open() 1520 | * @param[out] countp Address where the count will be stored 1521 | * @return A non-zero error value on failure and 0 on success. Some possible 1522 | * errors are: 1523 | *
    1524 | *
  • EINVAL - cursor is not initialized, or an invalid parameter was specified. 1525 | *
1526 | */ 1527 | int mdb_cursor_count(MDB_cursor *cursor, size_t *countp); 1528 | 1529 | /** @brief Compare two data items according to a particular database. 1530 | * 1531 | * This returns a comparison as if the two data items were keys in the 1532 | * specified database. 1533 | * @param[in] txn A transaction handle returned by #mdb_txn_begin() 1534 | * @param[in] dbi A database handle returned by #mdb_dbi_open() 1535 | * @param[in] a The first item to compare 1536 | * @param[in] b The second item to compare 1537 | * @return < 0 if a < b, 0 if a == b, > 0 if a > b 1538 | */ 1539 | int mdb_cmp(MDB_txn *txn, MDB_dbi dbi, const MDB_val *a, const MDB_val *b); 1540 | 1541 | /** @brief Compare two data items according to a particular database. 1542 | * 1543 | * This returns a comparison as if the two items were data items of 1544 | * the specified database. The database must have the #MDB_DUPSORT flag. 1545 | * @param[in] txn A transaction handle returned by #mdb_txn_begin() 1546 | * @param[in] dbi A database handle returned by #mdb_dbi_open() 1547 | * @param[in] a The first item to compare 1548 | * @param[in] b The second item to compare 1549 | * @return < 0 if a < b, 0 if a == b, > 0 if a > b 1550 | */ 1551 | int mdb_dcmp(MDB_txn *txn, MDB_dbi dbi, const MDB_val *a, const MDB_val *b); 1552 | 1553 | /** @brief A callback function used to print a message from the library. 1554 | * 1555 | * @param[in] msg The string to be printed. 1556 | * @param[in] ctx An arbitrary context pointer for the callback. 1557 | * @return < 0 on failure, >= 0 on success. 1558 | */ 1559 | typedef int (MDB_msg_func)(const char *msg, void *ctx); 1560 | 1561 | /** @brief Dump the entries in the reader lock table. 1562 | * 1563 | * @param[in] env An environment handle returned by #mdb_env_create() 1564 | * @param[in] func A #MDB_msg_func function 1565 | * @param[in] ctx Anything the message function needs 1566 | * @return < 0 on failure, >= 0 on success. 1567 | */ 1568 | int mdb_reader_list(MDB_env *env, MDB_msg_func *func, void *ctx); 1569 | 1570 | /** @brief Check for stale entries in the reader lock table. 1571 | * 1572 | * @param[in] env An environment handle returned by #mdb_env_create() 1573 | * @param[out] dead Number of stale slots that were cleared 1574 | * @return 0 on success, non-zero on failure. 1575 | */ 1576 | int mdb_reader_check(MDB_env *env, int *dead); 1577 | /** @} */ 1578 | 1579 | #ifdef __cplusplus 1580 | } 1581 | #endif 1582 | /** @page tools LMDB Command Line Tools 1583 | The following describes the command line tools that are available for LMDB. 1584 | \li \ref mdb_copy_1 1585 | \li \ref mdb_dump_1 1586 | \li \ref mdb_load_1 1587 | \li \ref mdb_stat_1 1588 | */ 1589 | 1590 | #endif /* _LMDB_H_ */ 1591 | -------------------------------------------------------------------------------- /c_src/midl.c: -------------------------------------------------------------------------------- 1 | /** @file midl.c 2 | * @brief ldap bdb back-end ID List functions */ 3 | /* $OpenLDAP$ */ 4 | /* This work is part of OpenLDAP Software . 5 | * 6 | * Copyright 2000-2015 The OpenLDAP Foundation. 7 | * All rights reserved. 8 | * 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted only as authorized by the OpenLDAP 11 | * Public License. 12 | * 13 | * A copy of this license is available in the file LICENSE in the 14 | * top-level directory of the distribution or, alternatively, at 15 | * . 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include "midl.h" 24 | 25 | /** @defgroup internal LMDB Internals 26 | * @{ 27 | */ 28 | /** @defgroup idls ID List Management 29 | * @{ 30 | */ 31 | #define CMP(x,y) ( (x) < (y) ? -1 : (x) > (y) ) 32 | 33 | unsigned mdb_midl_search( MDB_IDL ids, MDB_ID id ) 34 | { 35 | /* 36 | * binary search of id in ids 37 | * if found, returns position of id 38 | * if not found, returns first position greater than id 39 | */ 40 | unsigned base = 0; 41 | unsigned cursor = 1; 42 | int val = 0; 43 | unsigned n = ids[0]; 44 | 45 | while( 0 < n ) { 46 | unsigned pivot = n >> 1; 47 | cursor = base + pivot + 1; 48 | val = CMP( ids[cursor], id ); 49 | 50 | if( val < 0 ) { 51 | n = pivot; 52 | 53 | } else if ( val > 0 ) { 54 | base = cursor; 55 | n -= pivot + 1; 56 | 57 | } else { 58 | return cursor; 59 | } 60 | } 61 | 62 | if( val > 0 ) { 63 | ++cursor; 64 | } 65 | return cursor; 66 | } 67 | 68 | #if 0 /* superseded by append/sort */ 69 | int mdb_midl_insert( MDB_IDL ids, MDB_ID id ) 70 | { 71 | unsigned x, i; 72 | 73 | x = mdb_midl_search( ids, id ); 74 | assert( x > 0 ); 75 | 76 | if( x < 1 ) { 77 | /* internal error */ 78 | return -2; 79 | } 80 | 81 | if ( x <= ids[0] && ids[x] == id ) { 82 | /* duplicate */ 83 | assert(0); 84 | return -1; 85 | } 86 | 87 | if ( ++ids[0] >= MDB_IDL_DB_MAX ) { 88 | /* no room */ 89 | --ids[0]; 90 | return -2; 91 | 92 | } else { 93 | /* insert id */ 94 | for (i=ids[0]; i>x; i--) 95 | ids[i] = ids[i-1]; 96 | ids[x] = id; 97 | } 98 | 99 | return 0; 100 | } 101 | #endif 102 | 103 | MDB_IDL mdb_midl_alloc(int num) 104 | { 105 | MDB_IDL ids = malloc((num+2) * sizeof(MDB_ID)); 106 | if (ids) { 107 | *ids++ = num; 108 | *ids = 0; 109 | } 110 | return ids; 111 | } 112 | 113 | void mdb_midl_free(MDB_IDL ids) 114 | { 115 | if (ids) 116 | free(ids-1); 117 | } 118 | 119 | void mdb_midl_shrink( MDB_IDL *idp ) 120 | { 121 | MDB_IDL ids = *idp; 122 | if (*(--ids) > MDB_IDL_UM_MAX && 123 | (ids = realloc(ids, (MDB_IDL_UM_MAX+2) * sizeof(MDB_ID)))) 124 | { 125 | *ids++ = MDB_IDL_UM_MAX; 126 | *idp = ids; 127 | } 128 | } 129 | 130 | static int mdb_midl_grow( MDB_IDL *idp, int num ) 131 | { 132 | MDB_IDL idn = *idp-1; 133 | /* grow it */ 134 | idn = realloc(idn, (*idn + num + 2) * sizeof(MDB_ID)); 135 | if (!idn) 136 | return ENOMEM; 137 | *idn++ += num; 138 | *idp = idn; 139 | return 0; 140 | } 141 | 142 | int mdb_midl_need( MDB_IDL *idp, unsigned num ) 143 | { 144 | MDB_IDL ids = *idp; 145 | num += ids[0]; 146 | if (num > ids[-1]) { 147 | num = (num + num/4 + (256 + 2)) & -256; 148 | if (!(ids = realloc(ids-1, num * sizeof(MDB_ID)))) 149 | return ENOMEM; 150 | *ids++ = num - 2; 151 | *idp = ids; 152 | } 153 | return 0; 154 | } 155 | 156 | int mdb_midl_append( MDB_IDL *idp, MDB_ID id ) 157 | { 158 | MDB_IDL ids = *idp; 159 | /* Too big? */ 160 | if (ids[0] >= ids[-1]) { 161 | if (mdb_midl_grow(idp, MDB_IDL_UM_MAX)) 162 | return ENOMEM; 163 | ids = *idp; 164 | } 165 | ids[0]++; 166 | ids[ids[0]] = id; 167 | return 0; 168 | } 169 | 170 | int mdb_midl_append_list( MDB_IDL *idp, MDB_IDL app ) 171 | { 172 | MDB_IDL ids = *idp; 173 | /* Too big? */ 174 | if (ids[0] + app[0] >= ids[-1]) { 175 | if (mdb_midl_grow(idp, app[0])) 176 | return ENOMEM; 177 | ids = *idp; 178 | } 179 | memcpy(&ids[ids[0]+1], &app[1], app[0] * sizeof(MDB_ID)); 180 | ids[0] += app[0]; 181 | return 0; 182 | } 183 | 184 | int mdb_midl_append_range( MDB_IDL *idp, MDB_ID id, unsigned n ) 185 | { 186 | MDB_ID *ids = *idp, len = ids[0]; 187 | /* Too big? */ 188 | if (len + n > ids[-1]) { 189 | if (mdb_midl_grow(idp, n | MDB_IDL_UM_MAX)) 190 | return ENOMEM; 191 | ids = *idp; 192 | } 193 | ids[0] = len + n; 194 | ids += len; 195 | while (n) 196 | ids[n--] = id++; 197 | return 0; 198 | } 199 | 200 | void mdb_midl_xmerge( MDB_IDL idl, MDB_IDL merge ) 201 | { 202 | MDB_ID old_id, merge_id, i = merge[0], j = idl[0], k = i+j, total = k; 203 | idl[0] = (MDB_ID)-1; /* delimiter for idl scan below */ 204 | old_id = idl[j]; 205 | while (i) { 206 | merge_id = merge[i--]; 207 | for (; old_id < merge_id; old_id = idl[--j]) 208 | idl[k--] = old_id; 209 | idl[k--] = merge_id; 210 | } 211 | idl[0] = total; 212 | } 213 | 214 | /* Quicksort + Insertion sort for small arrays */ 215 | 216 | #define SMALL 8 217 | #define MIDL_SWAP(a,b) { itmp=(a); (a)=(b); (b)=itmp; } 218 | 219 | void 220 | mdb_midl_sort( MDB_IDL ids ) 221 | { 222 | /* Max possible depth of int-indexed tree * 2 items/level */ 223 | int istack[sizeof(int)*CHAR_BIT * 2]; 224 | int i,j,k,l,ir,jstack; 225 | MDB_ID a, itmp; 226 | 227 | ir = (int)ids[0]; 228 | l = 1; 229 | jstack = 0; 230 | for(;;) { 231 | if (ir - l < SMALL) { /* Insertion sort */ 232 | for (j=l+1;j<=ir;j++) { 233 | a = ids[j]; 234 | for (i=j-1;i>=1;i--) { 235 | if (ids[i] >= a) break; 236 | ids[i+1] = ids[i]; 237 | } 238 | ids[i+1] = a; 239 | } 240 | if (jstack == 0) break; 241 | ir = istack[jstack--]; 242 | l = istack[jstack--]; 243 | } else { 244 | k = (l + ir) >> 1; /* Choose median of left, center, right */ 245 | MIDL_SWAP(ids[k], ids[l+1]); 246 | if (ids[l] < ids[ir]) { 247 | MIDL_SWAP(ids[l], ids[ir]); 248 | } 249 | if (ids[l+1] < ids[ir]) { 250 | MIDL_SWAP(ids[l+1], ids[ir]); 251 | } 252 | if (ids[l] < ids[l+1]) { 253 | MIDL_SWAP(ids[l], ids[l+1]); 254 | } 255 | i = l+1; 256 | j = ir; 257 | a = ids[l+1]; 258 | for(;;) { 259 | do i++; while(ids[i] > a); 260 | do j--; while(ids[j] < a); 261 | if (j < i) break; 262 | MIDL_SWAP(ids[i],ids[j]); 263 | } 264 | ids[l+1] = ids[j]; 265 | ids[j] = a; 266 | jstack += 2; 267 | if (ir-i+1 >= j-l) { 268 | istack[jstack] = ir; 269 | istack[jstack-1] = i; 270 | ir = j-1; 271 | } else { 272 | istack[jstack] = j-1; 273 | istack[jstack-1] = l; 274 | l = i; 275 | } 276 | } 277 | } 278 | } 279 | 280 | unsigned mdb_mid2l_search( MDB_ID2L ids, MDB_ID id ) 281 | { 282 | /* 283 | * binary search of id in ids 284 | * if found, returns position of id 285 | * if not found, returns first position greater than id 286 | */ 287 | unsigned base = 0; 288 | unsigned cursor = 1; 289 | int val = 0; 290 | unsigned n = (unsigned)ids[0].mid; 291 | 292 | while( 0 < n ) { 293 | unsigned pivot = n >> 1; 294 | cursor = base + pivot + 1; 295 | val = CMP( id, ids[cursor].mid ); 296 | 297 | if( val < 0 ) { 298 | n = pivot; 299 | 300 | } else if ( val > 0 ) { 301 | base = cursor; 302 | n -= pivot + 1; 303 | 304 | } else { 305 | return cursor; 306 | } 307 | } 308 | 309 | if( val > 0 ) { 310 | ++cursor; 311 | } 312 | return cursor; 313 | } 314 | 315 | int mdb_mid2l_insert( MDB_ID2L ids, MDB_ID2 *id ) 316 | { 317 | unsigned x, i; 318 | 319 | x = mdb_mid2l_search( ids, id->mid ); 320 | 321 | if( x < 1 ) { 322 | /* internal error */ 323 | return -2; 324 | } 325 | 326 | if ( x <= ids[0].mid && ids[x].mid == id->mid ) { 327 | /* duplicate */ 328 | return -1; 329 | } 330 | 331 | if ( ids[0].mid >= MDB_IDL_UM_MAX ) { 332 | /* too big */ 333 | return -2; 334 | 335 | } else { 336 | /* insert id */ 337 | ids[0].mid++; 338 | for (i=(unsigned)ids[0].mid; i>x; i--) 339 | ids[i] = ids[i-1]; 340 | ids[x] = *id; 341 | } 342 | 343 | return 0; 344 | } 345 | 346 | int mdb_mid2l_append( MDB_ID2L ids, MDB_ID2 *id ) 347 | { 348 | /* Too big? */ 349 | if (ids[0].mid >= MDB_IDL_UM_MAX) { 350 | return -2; 351 | } 352 | ids[0].mid++; 353 | ids[ids[0].mid] = *id; 354 | return 0; 355 | } 356 | 357 | /** @} */ 358 | /** @} */ 359 | -------------------------------------------------------------------------------- /c_src/midl.h: -------------------------------------------------------------------------------- 1 | /** @file midl.h 2 | * @brief LMDB ID List header file. 3 | * 4 | * This file was originally part of back-bdb but has been 5 | * modified for use in libmdb. Most of the macros defined 6 | * in this file are unused, just left over from the original. 7 | * 8 | * This file is only used internally in libmdb and its definitions 9 | * are not exposed publicly. 10 | */ 11 | /* $OpenLDAP$ */ 12 | /* This work is part of OpenLDAP Software . 13 | * 14 | * Copyright 2000-2015 The OpenLDAP Foundation. 15 | * All rights reserved. 16 | * 17 | * Redistribution and use in source and binary forms, with or without 18 | * modification, are permitted only as authorized by the OpenLDAP 19 | * Public License. 20 | * 21 | * A copy of this license is available in the file LICENSE in the 22 | * top-level directory of the distribution or, alternatively, at 23 | * . 24 | */ 25 | 26 | #ifndef _MDB_MIDL_H_ 27 | #define _MDB_MIDL_H_ 28 | 29 | #include 30 | 31 | #ifdef __cplusplus 32 | extern "C" { 33 | #endif 34 | 35 | /** @defgroup internal LMDB Internals 36 | * @{ 37 | */ 38 | 39 | /** @defgroup idls ID List Management 40 | * @{ 41 | */ 42 | /** A generic unsigned ID number. These were entryIDs in back-bdb. 43 | * Preferably it should have the same size as a pointer. 44 | */ 45 | typedef size_t MDB_ID; 46 | 47 | /** An IDL is an ID List, a sorted array of IDs. The first 48 | * element of the array is a counter for how many actual 49 | * IDs are in the list. In the original back-bdb code, IDLs are 50 | * sorted in ascending order. For libmdb IDLs are sorted in 51 | * descending order. 52 | */ 53 | typedef MDB_ID *MDB_IDL; 54 | 55 | /* IDL sizes - likely should be even bigger 56 | * limiting factors: sizeof(ID), thread stack size 57 | */ 58 | #define MDB_IDL_LOGN 16 /* DB_SIZE is 2^16, UM_SIZE is 2^17 */ 59 | #define MDB_IDL_DB_SIZE (1<m - (char *)0 )) 39 | #endif 40 | 41 | #ifndef __containerof 42 | #define __containerof(ptr, type, member) ({ \ 43 | const typeof( ((type *)0)->member ) *__mptr = (ptr); \ 44 | (type *)( (char *)__mptr - __offsetof(type,member) );}) 45 | #endif 46 | 47 | #if defined(__cplusplus) 48 | extern "C" { 49 | #endif 50 | 51 | /* 52 | * This file defines four types of data structures: singly-linked lists, 53 | * singly-linked tail queues, lists and tail queues. 54 | * 55 | * A singly-linked list is headed by a single forward pointer. The elements 56 | * are singly linked for minimum space and pointer manipulation overhead at 57 | * the expense of O(n) removal for arbitrary elements. New elements can be 58 | * added to the list after an existing element or at the head of the list. 59 | * Elements being removed from the head of the list should use the explicit 60 | * macro for this purpose for optimum efficiency. A singly-linked list may 61 | * only be traversed in the forward direction. Singly-linked lists are ideal 62 | * for applications with large datasets and few or no removals or for 63 | * implementing a LIFO queue. 64 | * 65 | * A singly-linked tail queue is headed by a pair of pointers, one to the 66 | * head of the list and the other to the tail of the list. The elements are 67 | * singly linked for minimum space and pointer manipulation overhead at the 68 | * expense of O(n) removal for arbitrary elements. New elements can be added 69 | * to the list after an existing element, at the head of the list, or at the 70 | * end of the list. Elements being removed from the head of the tail queue 71 | * should use the explicit macro for this purpose for optimum efficiency. 72 | * A singly-linked tail queue may only be traversed in the forward direction. 73 | * Singly-linked tail queues are ideal for applications with large datasets 74 | * and few or no removals or for implementing a FIFO queue. 75 | * 76 | * A list is headed by a single forward pointer (or an array of forward 77 | * pointers for a hash table header). The elements are doubly linked 78 | * so that an arbitrary element can be removed without a need to 79 | * traverse the list. New elements can be added to the list before 80 | * or after an existing element or at the head of the list. A list 81 | * may only be traversed in the forward direction. 82 | * 83 | * A tail queue is headed by a pair of pointers, one to the head of the 84 | * list and the other to the tail of the list. The elements are doubly 85 | * linked so that an arbitrary element can be removed without a need to 86 | * traverse the list. New elements can be added to the list before or 87 | * after an existing element, at the head of the list, or at the end of 88 | * the list. A tail queue may be traversed in either direction. 89 | * 90 | * For details on the use of these macros, see the queue(3) manual page. 91 | * 92 | * 93 | * SLIST LIST STAILQ TAILQ 94 | * _HEAD + + + + 95 | * _HEAD_INITIALIZER + + + + 96 | * _ENTRY + + + + 97 | * _INIT + + + + 98 | * _EMPTY + + + + 99 | * _FIRST + + + + 100 | * _NEXT + + + + 101 | * _PREV - - - + 102 | * _LAST - - + + 103 | * _FOREACH + + + + 104 | * _FOREACH_REVERSE - - - + 105 | * _INSERT_HEAD + + + + 106 | * _INSERT_BEFORE - + - + 107 | * _INSERT_AFTER + + + + 108 | * _INSERT_TAIL - - + + 109 | * _CONCAT - - + + 110 | * _REMOVE_HEAD + - + - 111 | * _REMOVE + + + + 112 | * 113 | */ 114 | 115 | /* 116 | * XXX 117 | * We #undef all of the macros because there are incompatible versions of this 118 | * file and these macros on various systems. What makes the problem worse is 119 | * they are included and/or defined by system include files which we may have 120 | * already loaded into Berkeley DB before getting here. For example, FreeBSD's 121 | * includes its system , and VxWorks UnixLib.h defines 122 | * several of the LIST_XXX macros. Visual C.NET 7.0 also defines some of these 123 | * same macros in Vc7\PlatformSDK\Include\WinNT.h. Make sure we use ours. 124 | */ 125 | #undef LIST_EMPTY 126 | #undef LIST_ENTRY 127 | #undef LIST_FIRST 128 | #undef LIST_FOREACH 129 | #undef LIST_HEAD 130 | #undef LIST_HEAD_INITIALIZER 131 | #undef LIST_INIT 132 | #undef LIST_INSERT_AFTER 133 | #undef LIST_INSERT_BEFORE 134 | #undef LIST_INSERT_HEAD 135 | #undef LIST_NEXT 136 | #undef LIST_REMOVE 137 | #undef QMD_TRACE_ELEM 138 | #undef QMD_TRACE_HEAD 139 | #undef QUEUE_MACRO_DEBUG 140 | #undef SLIST_EMPTY 141 | #undef SLIST_ENTRY 142 | #undef SLIST_FIRST 143 | #undef SLIST_FOREACH 144 | #undef SLIST_FOREACH_PREVPTR 145 | #undef SLIST_HEAD 146 | #undef SLIST_HEAD_INITIALIZER 147 | #undef SLIST_INIT 148 | #undef SLIST_INSERT_AFTER 149 | #undef SLIST_INSERT_HEAD 150 | #undef SLIST_NEXT 151 | #undef SLIST_REMOVE 152 | #undef SLIST_REMOVE_HEAD 153 | #undef STAILQ_CONCAT 154 | #undef STAILQ_EMPTY 155 | #undef STAILQ_ENTRY 156 | #undef STAILQ_FIRST 157 | #undef STAILQ_FOREACH 158 | #undef STAILQ_HEAD 159 | #undef STAILQ_HEAD_INITIALIZER 160 | #undef STAILQ_INIT 161 | #undef STAILQ_INSERT_AFTER 162 | #undef STAILQ_INSERT_HEAD 163 | #undef STAILQ_INSERT_TAIL 164 | #undef STAILQ_LAST 165 | #undef STAILQ_NEXT 166 | #undef STAILQ_REMOVE 167 | #undef STAILQ_REMOVE_HEAD 168 | #undef STAILQ_REMOVE_HEAD_UNTIL 169 | #undef TAILQ_CONCAT 170 | #undef TAILQ_EMPTY 171 | #undef TAILQ_ENTRY 172 | #undef TAILQ_FIRST 173 | #undef TAILQ_FOREACH 174 | #undef TAILQ_FOREACH_REVERSE 175 | #undef TAILQ_HEAD 176 | #undef TAILQ_HEAD_INITIALIZER 177 | #undef TAILQ_INIT 178 | #undef TAILQ_INSERT_AFTER 179 | #undef TAILQ_INSERT_BEFORE 180 | #undef TAILQ_INSERT_HEAD 181 | #undef TAILQ_INSERT_TAIL 182 | #undef TAILQ_LAST 183 | #undef TAILQ_NEXT 184 | #undef TAILQ_PREV 185 | #undef TAILQ_REMOVE 186 | #undef TRACEBUF 187 | #undef TRASHIT 188 | 189 | #define QUEUE_MACRO_DEBUG 0 190 | #if QUEUE_MACRO_DEBUG 191 | /* Store the last 2 places the queue element or head was altered */ 192 | struct qm_trace { 193 | char * lastfile; 194 | int lastline; 195 | char * prevfile; 196 | int prevline; 197 | }; 198 | 199 | #define TRACEBUF struct qm_trace trace; 200 | #define TRASHIT(x) do {(x) = (void *)-1;} while (0) 201 | 202 | #define QMD_TRACE_HEAD(head) do { \ 203 | (head)->trace.prevline = (head)->trace.lastline; \ 204 | (head)->trace.prevfile = (head)->trace.lastfile; \ 205 | (head)->trace.lastline = __LINE__; \ 206 | (head)->trace.lastfile = __FILE__; \ 207 | } while (0) 208 | 209 | #define QMD_TRACE_ELEM(elem) do { \ 210 | (elem)->trace.prevline = (elem)->trace.lastline; \ 211 | (elem)->trace.prevfile = (elem)->trace.lastfile; \ 212 | (elem)->trace.lastline = __LINE__; \ 213 | (elem)->trace.lastfile = __FILE__; \ 214 | } while (0) 215 | 216 | #else 217 | #define QMD_TRACE_ELEM(elem) 218 | #define QMD_TRACE_HEAD(head) 219 | #define TRACEBUF 220 | #define TRASHIT(x) 221 | #endif /* QUEUE_MACRO_DEBUG */ 222 | 223 | /* 224 | * Singly-linked List declarations. 225 | */ 226 | #define SLIST_HEAD(name, type) \ 227 | struct name { \ 228 | struct type *slh_first; /* first element */ \ 229 | } 230 | 231 | #define SLIST_HEAD_INITIALIZER(head) \ 232 | { NULL } 233 | 234 | #define SLIST_ENTRY(type) \ 235 | struct { \ 236 | struct type *sle_next; /* next element */ \ 237 | } 238 | 239 | /* 240 | * Singly-linked List functions. 241 | */ 242 | #define SLIST_EMPTY(head) ((head)->slh_first == NULL) 243 | 244 | #define SLIST_FIRST(head) ((head)->slh_first) 245 | 246 | #define SLIST_FOREACH(var, head, field) \ 247 | for ((var) = SLIST_FIRST((head)); \ 248 | (var); \ 249 | (var) = SLIST_NEXT((var), field)) 250 | 251 | #define SLIST_FOREACH_PREVPTR(var, varp, head, field) \ 252 | for ((varp) = &SLIST_FIRST((head)); \ 253 | ((var) = *(varp)) != NULL; \ 254 | (varp) = &SLIST_NEXT((var), field)) 255 | 256 | #define SLIST_INIT(head) do { \ 257 | SLIST_FIRST((head)) = NULL; \ 258 | } while (0) 259 | 260 | #define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ 261 | SLIST_NEXT((elm), field) = SLIST_NEXT((slistelm), field); \ 262 | SLIST_NEXT((slistelm), field) = (elm); \ 263 | } while (0) 264 | 265 | #define SLIST_INSERT_HEAD(head, elm, field) do { \ 266 | SLIST_NEXT((elm), field) = SLIST_FIRST((head)); \ 267 | SLIST_FIRST((head)) = (elm); \ 268 | } while (0) 269 | 270 | #define SLIST_NEXT(elm, field) ((elm)->field.sle_next) 271 | 272 | #define SLIST_REMOVE(head, elm, type, field) do { \ 273 | if (SLIST_FIRST((head)) == (elm)) { \ 274 | SLIST_REMOVE_HEAD((head), field); \ 275 | } \ 276 | else { \ 277 | struct type *curelm = SLIST_FIRST((head)); \ 278 | while (SLIST_NEXT(curelm, field) != (elm)) \ 279 | curelm = SLIST_NEXT(curelm, field); \ 280 | SLIST_NEXT(curelm, field) = \ 281 | SLIST_NEXT(SLIST_NEXT(curelm, field), field); \ 282 | } \ 283 | } while (0) 284 | 285 | #define SLIST_REMOVE_HEAD(head, field) do { \ 286 | SLIST_FIRST((head)) = SLIST_NEXT(SLIST_FIRST((head)), field); \ 287 | } while (0) 288 | 289 | /* 290 | * Singly-linked Tail queue declarations. 291 | */ 292 | #define STAILQ_HEAD(name, type) \ 293 | struct name { \ 294 | struct type *stqh_first;/* first element */ \ 295 | struct type **stqh_last;/* addr of last next element */ \ 296 | } 297 | 298 | #define STAILQ_HEAD_INITIALIZER(head) \ 299 | { NULL, &(head).stqh_first } 300 | 301 | #define STAILQ_ENTRY(type) \ 302 | struct { \ 303 | struct type *stqe_next; /* next element */ \ 304 | } 305 | 306 | /* 307 | * Singly-linked Tail queue functions. 308 | */ 309 | #define STAILQ_CONCAT(head1, head2) do { \ 310 | if (!STAILQ_EMPTY((head2))) { \ 311 | *(head1)->stqh_last = (head2)->stqh_first; \ 312 | (head1)->stqh_last = (head2)->stqh_last; \ 313 | STAILQ_INIT((head2)); \ 314 | } \ 315 | } while (0) 316 | 317 | #define STAILQ_EMPTY(head) ((head)->stqh_first == NULL) 318 | 319 | #define STAILQ_FIRST(head) ((head)->stqh_first) 320 | 321 | #define STAILQ_FOREACH(var, head, field) \ 322 | for ((var) = STAILQ_FIRST((head)); \ 323 | (var); \ 324 | (var) = STAILQ_NEXT((var), field)) 325 | 326 | #define STAILQ_INIT(head) do { \ 327 | STAILQ_FIRST((head)) = NULL; \ 328 | (head)->stqh_last = &STAILQ_FIRST((head)); \ 329 | } while (0) 330 | 331 | #define STAILQ_INSERT_AFTER(head, tqelm, elm, field) do { \ 332 | if ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((tqelm), field)) == NULL)\ 333 | (head)->stqh_last = &STAILQ_NEXT((elm), field); \ 334 | STAILQ_NEXT((tqelm), field) = (elm); \ 335 | } while (0) 336 | 337 | #define STAILQ_INSERT_HEAD(head, elm, field) do { \ 338 | if ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) == NULL) \ 339 | (head)->stqh_last = &STAILQ_NEXT((elm), field); \ 340 | STAILQ_FIRST((head)) = (elm); \ 341 | } while (0) 342 | 343 | #define STAILQ_INSERT_TAIL(head, elm, field) do { \ 344 | STAILQ_NEXT((elm), field) = NULL; \ 345 | *(head)->stqh_last = (elm); \ 346 | (head)->stqh_last = &STAILQ_NEXT((elm), field); \ 347 | } while (0) 348 | 349 | #define STAILQ_LAST(head, type, field) \ 350 | (STAILQ_EMPTY((head)) ? \ 351 | NULL : \ 352 | ((struct type *) \ 353 | ((char *)((head)->stqh_last) - __offsetof(struct type, field)))) 354 | 355 | #define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next) 356 | 357 | #define STAILQ_REMOVE(head, elm, type, field) do { \ 358 | if (STAILQ_FIRST((head)) == (elm)) { \ 359 | STAILQ_REMOVE_HEAD((head), field); \ 360 | } \ 361 | else { \ 362 | struct type *curelm = STAILQ_FIRST((head)); \ 363 | while (STAILQ_NEXT(curelm, field) != (elm)) \ 364 | curelm = STAILQ_NEXT(curelm, field); \ 365 | if ((STAILQ_NEXT(curelm, field) = \ 366 | STAILQ_NEXT(STAILQ_NEXT(curelm, field), field)) == NULL)\ 367 | (head)->stqh_last = &STAILQ_NEXT((curelm), field);\ 368 | } \ 369 | } while (0) 370 | 371 | #define STAILQ_REMOVE_HEAD(head, field) do { \ 372 | if ((STAILQ_FIRST((head)) = \ 373 | STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL) \ 374 | (head)->stqh_last = &STAILQ_FIRST((head)); \ 375 | } while (0) 376 | 377 | #define STAILQ_REMOVE_HEAD_UNTIL(head, elm, field) do { \ 378 | if ((STAILQ_FIRST((head)) = STAILQ_NEXT((elm), field)) == NULL) \ 379 | (head)->stqh_last = &STAILQ_FIRST((head)); \ 380 | } while (0) 381 | 382 | /* 383 | * List declarations. 384 | */ 385 | #define LIST_HEAD(name, type) \ 386 | struct name { \ 387 | struct type *lh_first; /* first element */ \ 388 | } 389 | 390 | #define LIST_HEAD_INITIALIZER(head) \ 391 | { NULL } 392 | 393 | #define LIST_ENTRY(type) \ 394 | struct { \ 395 | struct type *le_next; /* next element */ \ 396 | struct type **le_prev; /* address of previous next element */ \ 397 | } 398 | 399 | /* 400 | * List functions. 401 | */ 402 | 403 | #define LIST_EMPTY(head) ((head)->lh_first == NULL) 404 | 405 | #define LIST_FIRST(head) ((head)->lh_first) 406 | 407 | #define LIST_FOREACH(var, head, field) \ 408 | for ((var) = LIST_FIRST((head)); \ 409 | (var); \ 410 | (var) = LIST_NEXT((var), field)) 411 | 412 | #define LIST_INIT(head) do { \ 413 | LIST_FIRST((head)) = NULL; \ 414 | } while (0) 415 | 416 | #define LIST_INSERT_AFTER(listelm, elm, field) do { \ 417 | if ((LIST_NEXT((elm), field) = LIST_NEXT((listelm), field)) != NULL)\ 418 | LIST_NEXT((listelm), field)->field.le_prev = \ 419 | &LIST_NEXT((elm), field); \ 420 | LIST_NEXT((listelm), field) = (elm); \ 421 | (elm)->field.le_prev = &LIST_NEXT((listelm), field); \ 422 | } while (0) 423 | 424 | #define LIST_INSERT_BEFORE(listelm, elm, field) do { \ 425 | (elm)->field.le_prev = (listelm)->field.le_prev; \ 426 | LIST_NEXT((elm), field) = (listelm); \ 427 | *(listelm)->field.le_prev = (elm); \ 428 | (listelm)->field.le_prev = &LIST_NEXT((elm), field); \ 429 | } while (0) 430 | 431 | #define LIST_INSERT_HEAD(head, elm, field) do { \ 432 | if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL) \ 433 | LIST_FIRST((head))->field.le_prev = &LIST_NEXT((elm), field);\ 434 | LIST_FIRST((head)) = (elm); \ 435 | (elm)->field.le_prev = &LIST_FIRST((head)); \ 436 | } while (0) 437 | 438 | #define LIST_NEXT(elm, field) ((elm)->field.le_next) 439 | 440 | #define LIST_REMOVE(elm, field) do { \ 441 | if (LIST_NEXT((elm), field) != NULL) \ 442 | LIST_NEXT((elm), field)->field.le_prev = \ 443 | (elm)->field.le_prev; \ 444 | *(elm)->field.le_prev = LIST_NEXT((elm), field); \ 445 | } while (0) 446 | 447 | /* 448 | * Tail queue declarations. 449 | */ 450 | #define TAILQ_HEAD(name, type) \ 451 | struct name { \ 452 | struct type *tqh_first; /* first element */ \ 453 | struct type **tqh_last; /* addr of last next element */ \ 454 | TRACEBUF \ 455 | } 456 | 457 | #define TAILQ_HEAD_INITIALIZER(head) \ 458 | { NULL, &(head).tqh_first } 459 | 460 | #define TAILQ_ENTRY(type) \ 461 | struct { \ 462 | struct type *tqe_next; /* next element */ \ 463 | struct type **tqe_prev; /* address of previous next element */ \ 464 | TRACEBUF \ 465 | } 466 | 467 | /* 468 | * Tail queue functions. 469 | */ 470 | #define TAILQ_CONCAT(head1, head2, field) do { \ 471 | if (!TAILQ_EMPTY(head2)) { \ 472 | *(head1)->tqh_last = (head2)->tqh_first; \ 473 | (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \ 474 | (head1)->tqh_last = (head2)->tqh_last; \ 475 | TAILQ_INIT((head2)); \ 476 | QMD_TRACE_HEAD(head); \ 477 | QMD_TRACE_HEAD(head2); \ 478 | } \ 479 | } while (0) 480 | 481 | #define TAILQ_EMPTY(head) ((head)->tqh_first == NULL) 482 | 483 | #define TAILQ_FIRST(head) ((head)->tqh_first) 484 | 485 | #define TAILQ_FOREACH(var, head, field) \ 486 | for ((var) = TAILQ_FIRST((head)); \ 487 | (var); \ 488 | (var) = TAILQ_NEXT((var), field)) 489 | 490 | #define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ 491 | for ((var) = TAILQ_LAST((head), headname); \ 492 | (var); \ 493 | (var) = TAILQ_PREV((var), headname, field)) 494 | 495 | #define TAILQ_INIT(head) do { \ 496 | TAILQ_FIRST((head)) = NULL; \ 497 | (head)->tqh_last = &TAILQ_FIRST((head)); \ 498 | QMD_TRACE_HEAD(head); \ 499 | } while (0) 500 | 501 | #define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ 502 | if ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) != NULL)\ 503 | TAILQ_NEXT((elm), field)->field.tqe_prev = \ 504 | &TAILQ_NEXT((elm), field); \ 505 | else { \ 506 | (head)->tqh_last = &TAILQ_NEXT((elm), field); \ 507 | QMD_TRACE_HEAD(head); \ 508 | } \ 509 | TAILQ_NEXT((listelm), field) = (elm); \ 510 | (elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field); \ 511 | QMD_TRACE_ELEM(&(elm)->field); \ 512 | QMD_TRACE_ELEM(&listelm->field); \ 513 | } while (0) 514 | 515 | #define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ 516 | (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ 517 | TAILQ_NEXT((elm), field) = (listelm); \ 518 | *(listelm)->field.tqe_prev = (elm); \ 519 | (listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field); \ 520 | QMD_TRACE_ELEM(&(elm)->field); \ 521 | QMD_TRACE_ELEM(&listelm->field); \ 522 | } while (0) 523 | 524 | #define TAILQ_INSERT_HEAD(head, elm, field) do { \ 525 | if ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL) \ 526 | TAILQ_FIRST((head))->field.tqe_prev = \ 527 | &TAILQ_NEXT((elm), field); \ 528 | else \ 529 | (head)->tqh_last = &TAILQ_NEXT((elm), field); \ 530 | TAILQ_FIRST((head)) = (elm); \ 531 | (elm)->field.tqe_prev = &TAILQ_FIRST((head)); \ 532 | QMD_TRACE_HEAD(head); \ 533 | QMD_TRACE_ELEM(&(elm)->field); \ 534 | } while (0) 535 | 536 | #define TAILQ_INSERT_TAIL(head, elm, field) do { \ 537 | TAILQ_NEXT((elm), field) = NULL; \ 538 | (elm)->field.tqe_prev = (head)->tqh_last; \ 539 | *(head)->tqh_last = (elm); \ 540 | (head)->tqh_last = &TAILQ_NEXT((elm), field); \ 541 | QMD_TRACE_HEAD(head); \ 542 | QMD_TRACE_ELEM(&(elm)->field); \ 543 | } while (0) 544 | 545 | #define TAILQ_LAST(head, headname) \ 546 | (*(((struct headname *)((head)->tqh_last))->tqh_last)) 547 | 548 | #define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) 549 | 550 | #define TAILQ_PREV(elm, headname, field) \ 551 | (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) 552 | 553 | #define TAILQ_REMOVE(head, elm, field) do { \ 554 | if ((TAILQ_NEXT((elm), field)) != NULL) \ 555 | TAILQ_NEXT((elm), field)->field.tqe_prev = \ 556 | (elm)->field.tqe_prev; \ 557 | else { \ 558 | (head)->tqh_last = (elm)->field.tqe_prev; \ 559 | QMD_TRACE_HEAD(head); \ 560 | } \ 561 | *(elm)->field.tqe_prev = TAILQ_NEXT((elm), field); \ 562 | TRASHIT((elm)->field.tqe_next); \ 563 | TRASHIT((elm)->field.tqe_prev); \ 564 | QMD_TRACE_ELEM(&(elm)->field); \ 565 | } while (0) 566 | 567 | /* 568 | * Circular queue definitions. 569 | */ 570 | #define CIRCLEQ_HEAD(name, type) \ 571 | struct name { \ 572 | struct type *cqh_first; /* first element */ \ 573 | struct type *cqh_last; /* last element */ \ 574 | } 575 | 576 | #define CIRCLEQ_HEAD_INITIALIZER(head) \ 577 | { (void *)&head, (void *)&head } 578 | 579 | #define CIRCLEQ_ENTRY(type) \ 580 | struct { \ 581 | struct type *cqe_next; /* next element */ \ 582 | struct type *cqe_prev; /* previous element */ \ 583 | } 584 | 585 | /* 586 | * Circular queue functions. 587 | */ 588 | #define CIRCLEQ_INIT(head) do { \ 589 | (head)->cqh_first = (void *)(head); \ 590 | (head)->cqh_last = (void *)(head); \ 591 | } while (/*CONSTCOND*/0) 592 | 593 | #define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ 594 | (elm)->field.cqe_next = (listelm)->field.cqe_next; \ 595 | (elm)->field.cqe_prev = (listelm); \ 596 | if ((listelm)->field.cqe_next == (void *)(head)) \ 597 | (head)->cqh_last = (elm); \ 598 | else \ 599 | (listelm)->field.cqe_next->field.cqe_prev = (elm); \ 600 | (listelm)->field.cqe_next = (elm); \ 601 | } while (/*CONSTCOND*/0) 602 | 603 | #define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \ 604 | (elm)->field.cqe_next = (listelm); \ 605 | (elm)->field.cqe_prev = (listelm)->field.cqe_prev; \ 606 | if ((listelm)->field.cqe_prev == (void *)(head)) \ 607 | (head)->cqh_first = (elm); \ 608 | else \ 609 | (listelm)->field.cqe_prev->field.cqe_next = (elm); \ 610 | (listelm)->field.cqe_prev = (elm); \ 611 | } while (/*CONSTCOND*/0) 612 | 613 | #define CIRCLEQ_INSERT_HEAD(head, elm, field) do { \ 614 | (elm)->field.cqe_next = (head)->cqh_first; \ 615 | (elm)->field.cqe_prev = (void *)(head); \ 616 | if ((head)->cqh_last == (void *)(head)) \ 617 | (head)->cqh_last = (elm); \ 618 | else \ 619 | (head)->cqh_first->field.cqe_prev = (elm); \ 620 | (head)->cqh_first = (elm); \ 621 | } while (/*CONSTCOND*/0) 622 | 623 | #define CIRCLEQ_INSERT_TAIL(head, elm, field) do { \ 624 | (elm)->field.cqe_next = (void *)(head); \ 625 | (elm)->field.cqe_prev = (head)->cqh_last; \ 626 | if ((head)->cqh_first == (void *)(head)) \ 627 | (head)->cqh_first = (elm); \ 628 | else \ 629 | (head)->cqh_last->field.cqe_next = (elm); \ 630 | (head)->cqh_last = (elm); \ 631 | } while (/*CONSTCOND*/0) 632 | 633 | #define CIRCLEQ_REMOVE(head, elm, field) do { \ 634 | if ((elm)->field.cqe_next == (void *)(head)) \ 635 | (head)->cqh_last = (elm)->field.cqe_prev; \ 636 | else \ 637 | (elm)->field.cqe_next->field.cqe_prev = \ 638 | (elm)->field.cqe_prev; \ 639 | if ((elm)->field.cqe_prev == (void *)(head)) \ 640 | (head)->cqh_first = (elm)->field.cqe_next; \ 641 | else \ 642 | (elm)->field.cqe_prev->field.cqe_next = \ 643 | (elm)->field.cqe_next; \ 644 | } while (/*CONSTCOND*/0) 645 | 646 | #define CIRCLEQ_FOREACH(var, head, field) \ 647 | for ((var) = ((head)->cqh_first); \ 648 | (var) != (const void *)(head); \ 649 | (var) = ((var)->field.cqe_next)) 650 | 651 | #define CIRCLEQ_FOREACH_REVERSE(var, head, field) \ 652 | for ((var) = ((head)->cqh_last); \ 653 | (var) != (const void *)(head); \ 654 | (var) = ((var)->field.cqe_prev)) 655 | 656 | /* 657 | * Circular queue access methods. 658 | */ 659 | #define CIRCLEQ_EMPTY(head) ((head)->cqh_first == (void *)(head)) 660 | #define CIRCLEQ_FIRST(head) ((head)->cqh_first) 661 | #define CIRCLEQ_LAST(head) ((head)->cqh_last) 662 | #define CIRCLEQ_NEXT(elm, field) ((elm)->field.cqe_next) 663 | #define CIRCLEQ_PREV(elm, field) ((elm)->field.cqe_prev) 664 | 665 | #define CIRCLEQ_LOOP_NEXT(head, elm, field) \ 666 | (((elm)->field.cqe_next == (void *)(head)) \ 667 | ? ((head)->cqh_first) \ 668 | : (elm->field.cqe_next)) 669 | #define CIRCLEQ_LOOP_PREV(head, elm, field) \ 670 | (((elm)->field.cqe_prev == (void *)(head)) \ 671 | ? ((head)->cqh_last) \ 672 | : (elm->field.cqe_prev)) 673 | 674 | 675 | #if defined(__cplusplus) 676 | } 677 | #endif 678 | #endif /* !_DB_QUEUE_H_ */ 679 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | %% -*- erlang -*- 2 | %% ex: ft=erlang ts=4 sw=4 et 3 | 4 | {require_otp_vsn, "R1[56]|1[789]"}. 5 | 6 | {cover_enabled, true}. 7 | 8 | {erl_opts, [%{d,'DEBUG',true}, 9 | debug_info, 10 | warn_unused_vars, 11 | warn_export_all, 12 | warn_shadow_vars, 13 | warn_unused_import, 14 | warn_unused_function, 15 | warn_bif_clash, 16 | warn_unused_record, 17 | warn_deprecated_function, 18 | warn_obsolete_guard, 19 | warn_export_vars, 20 | warn_exported_vars, 21 | warn_untyped_record 22 | %warn_missing_spec, 23 | %strict_validation, 24 | %fail_on_warning 25 | ]}. 26 | 27 | {xref_checks, [undefined_function_calls, deprecated_function_calls]}. 28 | 29 | {eunit_opts, [verbose, {report, {eunit_surefire, [{dir, "."}]}}]}. 30 | 31 | {pre_hooks, 32 | [{"(linux|darwin|solaris)", compile, "make -C c_src"}, 33 | {"(freebsd)", compile, "gmake -C c_src"}]}. 34 | {post_hooks, 35 | [{"(linux|darwin|solaris)", clean, "make -C c_src clean"}, 36 | {"(freebsd)", clean, "gmake -C c_src clean"}]}. 37 | -------------------------------------------------------------------------------- /src/elmdb.app.src: -------------------------------------------------------------------------------- 1 | {application, elmdb, 2 | [ 3 | {description, "Erlang NIF driver for LMDB. A fast, compact and transactional key-value data store."}, 4 | {vsn, "0.4.1"}, 5 | {registered, []}, 6 | {applications, [ 7 | kernel, 8 | stdlib 9 | ]}, 10 | {env, []}, 11 | {maintainers, ["Vincent Siliakus"]}, 12 | {licenses, ["OpenLDAP Public License 2.8"]}, 13 | {links, [{"Github", "https://github.com/zambal/elmdb"}]} 14 | ]}. 15 | -------------------------------------------------------------------------------- /src/elmdb.erl: -------------------------------------------------------------------------------- 1 | %%------------------------------------------------------------------- 2 | %% This file is part of Elmdb - Erlang Lightning MDB API 3 | %% 4 | %% Copyright (c) 2012 by Aleph Archives. All rights reserved. 5 | %% Copyright (c) 2013 by Basho Technologies, Inc. All rights reserved. 6 | %% Copyright (c) 2016 by Vincent Siliakus. All rights reserved. 7 | %% 8 | %%------------------------------------------------------------------- 9 | %% Redistribution and use in source and binary forms, with or without 10 | %% modification, are permitted only as authorized by the OpenLDAP 11 | %% Public License. 12 | %% 13 | %% A copy of this license is available in the file LICENSE in the 14 | %% top-level directory of the distribution or, alternatively, at 15 | %% . 16 | %% 17 | %% Permission to use, copy, modify, and distribute this software for any 18 | %% purpose with or without fee is hereby granted, provided that the above 19 | %% copyright notice and this permission notice appear in all copies. 20 | %% 21 | %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 22 | %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 23 | %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 24 | %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 25 | %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 26 | %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 27 | %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 28 | %%------------------------------------------------------------------- 29 | 30 | -module(elmdb). 31 | 32 | %%==================================================================== 33 | %% EXPORTS 34 | %%==================================================================== 35 | -export([ 36 | env_open/2, 37 | env_open/3, 38 | env_close/1, 39 | env_close_by_name/1, 40 | env_close_all/0, 41 | db_open/2, 42 | db_open/3, 43 | db_open/4, 44 | 45 | put/3, 46 | put_new/3, 47 | get/2, 48 | delete/2, 49 | drop/1, 50 | 51 | async_put/3, 52 | async_put/4, 53 | async_put_new/3, 54 | async_put_new/4, 55 | async_get/2, 56 | async_get/3, 57 | async_delete/2, 58 | async_delete/3, 59 | async_drop/1, 60 | async_drop/2, 61 | 62 | update_put/4, 63 | update_put/5, 64 | update_get/2, 65 | update_get/3, 66 | 67 | ro_txn_begin/1, 68 | ro_txn_get/3, 69 | ro_txn_commit/1, 70 | ro_txn_abort/1, 71 | 72 | ro_txn_cursor_open/2, 73 | ro_txn_cursor_close/1, 74 | ro_txn_cursor_get/2, 75 | 76 | txn_begin/1, 77 | txn_begin/2, 78 | txn_put/4, 79 | txn_put/5, 80 | txn_put_new/4, 81 | txn_put_new/5, 82 | txn_get/3, 83 | txn_get/4, 84 | txn_delete/3, 85 | txn_delete/4, 86 | txn_drop/2, 87 | txn_drop/3, 88 | txn_commit/1, 89 | txn_commit/2, 90 | txn_abort/1, 91 | txn_abort/2, 92 | txn_cursor_open/2, 93 | txn_cursor_open/3, 94 | txn_cursor_get/2, 95 | txn_cursor_get/3, 96 | txn_cursor_put/3, 97 | txn_cursor_put/4 98 | ]). 99 | 100 | 101 | %% internal export (ex. spawn, apply) 102 | -on_load(init/0). 103 | 104 | 105 | %%==================================================================== 106 | %% MACROS 107 | %%==================================================================== 108 | -define(ELMDB_DRIVER_NAME, "elmdb"). 109 | -define(NOT_LOADED, not_loaded(?LINE)). 110 | -define (TIMEOUT, 5000). 111 | 112 | %%==================================================================== 113 | %% TYPES 114 | %%==================================================================== 115 | -type env() :: binary(). 116 | -type dbi() :: binary(). 117 | -type txn() :: binary(). 118 | -type cursor() :: binary(). 119 | 120 | -type key() :: binary(). 121 | -type val() :: binary(). 122 | 123 | -type env_open_opt() :: {map_size, non_neg_integer()} | 124 | {max_dbs, non_neg_integer()} | 125 | fixed_map | no_subdir | read_only | 126 | write_map | no_meta_sync | no_sync | 127 | map_async | no_read_ahead | no_mem_init. 128 | 129 | -type env_open_opts() :: [env_open_opt()]. 130 | 131 | -type db_open_opt() :: reverse_key | dup_sort | reverse_dup | create. 132 | -type db_open_opts() :: [db_open_opt()]. 133 | 134 | -type cursor_op() :: first | first_dup | get_both | get_both_range | 135 | get_current | last | last_dup | 136 | next | next_dup | next_nodup | 137 | prev | prev_dup | prev_nodup | 138 | {set, key()} | {set_range, key()}. 139 | 140 | -type elmdb_error() :: {error, {atom(), string()}}. 141 | 142 | %%==================================================================== 143 | %% PUBLIC API 144 | %%==================================================================== 145 | 146 | %%-------------------------------------------------------------------- 147 | %% @doc Create a new, or open an existing MDB environment 148 | %% @end 149 | %%-------------------------------------------------------------------- 150 | -spec env_open(string(), env_open_opts()) -> {ok, env()} | elmdb_error(). 151 | env_open(DirName, Opts) -> 152 | env_open(DirName, Opts, ?TIMEOUT). 153 | 154 | -spec env_open(string(), env_open_opts(), non_neg_integer()) -> {ok, env()} | elmdb_error(). 155 | env_open(DirName, Opts, Timeout) 156 | when is_list(Opts) -> 157 | %% ensure directory exists 158 | DirName2 = filename:absname(DirName), 159 | ok = filelib:ensure_dir(filename:join([DirName2, "x"])), 160 | Ref = make_ref(), 161 | case nif_env_open(Ref, DirName2, Opts) of 162 | ok -> recv_async(Ref, Timeout); 163 | Error -> Error 164 | end. 165 | 166 | nif_env_open(_Ref, _DirName, _Opts) -> 167 | ?NOT_LOADED. 168 | 169 | -spec env_close(env()) -> ok. 170 | env_close(_Env) -> 171 | ?NOT_LOADED. 172 | 173 | -spec env_close_by_name(string()) -> ok | elmdb_error(). 174 | env_close_by_name(DirName) -> 175 | nif_env_close_by_name(filename:absname(DirName)). 176 | 177 | nif_env_close_by_name(_DirName) -> 178 | ?NOT_LOADED. 179 | 180 | -spec env_close_all() -> ok. 181 | env_close_all() -> 182 | ?NOT_LOADED. 183 | 184 | -spec db_open(env(), db_open_opts()) -> {ok, dbi()} | elmdb_error(). 185 | db_open(Env, Opts) -> 186 | db_open(Env, <<"">>, Opts, ?TIMEOUT). 187 | 188 | -spec db_open(env(), key(), db_open_opts()) -> {ok, dbi()} | elmdb_error(). 189 | db_open(Env, Name, Opts) -> 190 | db_open(Env, Name, Opts, ?TIMEOUT). 191 | 192 | -spec db_open(env(), key(), db_open_opts(), non_neg_integer()) -> {ok, dbi()} | elmdb_error(). 193 | db_open(Env, Name, Opts, Timeout) -> 194 | Ref = make_ref(), 195 | case nif_db_open(Ref, Env, Name, Opts) of 196 | ok -> recv_async(Ref, Timeout); 197 | Error -> Error 198 | end. 199 | 200 | nif_db_open(_Ref, _Env, _Name, _Opts) -> 201 | ?NOT_LOADED. 202 | 203 | -spec put(dbi(), key(), val()) -> ok | elmdb_error(). 204 | put(_Dbi, _Key, _Val) -> 205 | ?NOT_LOADED. 206 | 207 | -spec put_new(dbi(), key(), val()) -> ok | exists | elmdb_error(). 208 | put_new(_Dbi, _Key, _Val) -> 209 | ?NOT_LOADED. 210 | 211 | -spec get(dbi(), key()) -> {ok, val()} | not_found | elmdb_error(). 212 | get(_Dbi, _Key) -> 213 | ?NOT_LOADED. 214 | 215 | -spec delete(dbi(), key()) -> ok | not_found | elmdb_error(). 216 | delete(_Dbi, _Key) -> 217 | ?NOT_LOADED. 218 | 219 | -spec drop(dbi()) -> ok | not_found | elmdb_error(). 220 | drop(_Dbi) -> 221 | ?NOT_LOADED. 222 | 223 | -spec async_put(dbi(), key(), val()) -> ok | elmdb_error(). 224 | async_put(Dbi, Key, Val) -> 225 | async_put(Dbi, Key, Val, ?TIMEOUT). 226 | 227 | -spec async_put(dbi(), key(), val(), non_neg_integer()) -> ok | elmdb_error(). 228 | async_put(Dbi, Key, Val, Timeout) -> 229 | Ref = make_ref(), 230 | case nif_async_put(Ref, Dbi, Key, Val) of 231 | ok -> recv_async(Ref, Timeout); 232 | Error -> Error 233 | end. 234 | 235 | nif_async_put(_Ref, _Dbi, _Key, _Val) -> 236 | ?NOT_LOADED. 237 | 238 | -spec async_put_new(dbi(), key(), val()) -> ok | exists | elmdb_error(). 239 | async_put_new(Dbi, Key, Val) -> 240 | async_put_new(Dbi, Key, Val, ?TIMEOUT). 241 | 242 | -spec async_put_new(dbi(), key(), val(), non_neg_integer()) -> ok | exists | elmdb_error(). 243 | async_put_new(Dbi, Key, Val, Timeout) -> 244 | Ref = make_ref(), 245 | case nif_async_put_new(Ref, Dbi, Key, Val) of 246 | ok -> recv_async(Ref, Timeout); 247 | Error -> Error 248 | end. 249 | 250 | nif_async_put_new(_Ref, _Dbi, _Key, _Val) -> 251 | ?NOT_LOADED. 252 | 253 | -spec async_get(dbi(), key()) -> {ok, val()} | not_found | elmdb_error(). 254 | async_get(Dbi, Key) -> 255 | async_get(Dbi, Key, ?TIMEOUT). 256 | 257 | -spec async_get(dbi(), key(), non_neg_integer()) -> {ok, val()} | not_found | elmdb_error(). 258 | async_get(Dbi, Key, Timeout) -> 259 | Ref = make_ref(), 260 | case nif_async_get(Ref, Dbi, Key) of 261 | ok -> recv_async(Ref, Timeout); 262 | Error -> Error 263 | end. 264 | 265 | nif_async_get(_Ref, _Dbi, _Key) -> 266 | ?NOT_LOADED. 267 | 268 | -spec async_delete(dbi(), key()) -> ok | not_found | elmdb_error(). 269 | async_delete(Dbi, Key) -> 270 | async_delete(Dbi, Key, ?TIMEOUT). 271 | 272 | -spec async_delete(dbi(), key(), non_neg_integer()) -> ok | not_found | elmdb_error(). 273 | async_delete(Dbi, Key, Timeout) -> 274 | Ref = make_ref(), 275 | case nif_async_delete(Ref, Dbi, Key) of 276 | ok -> recv_async(Ref, Timeout); 277 | Error -> Error 278 | end. 279 | 280 | nif_async_delete(_Ref, _Dbi, _Key) -> 281 | ?NOT_LOADED. 282 | 283 | -spec async_drop(dbi()) -> ok | not_found | elmdb_error(). 284 | async_drop(Dbi) -> 285 | async_drop(Dbi, ?TIMEOUT). 286 | 287 | -spec async_drop(dbi(), non_neg_integer()) -> ok | not_found | elmdb_error(). 288 | async_drop(Dbi, Timeout) -> 289 | Ref = make_ref(), 290 | case nif_async_drop(Ref, Dbi) of 291 | ok -> recv_async(Ref, Timeout); 292 | Error -> Error 293 | end. 294 | 295 | nif_async_drop(_Ref, _Dbi) -> 296 | ?NOT_LOADED. 297 | 298 | -spec update_put(txn(), dbi(), key(), val()) -> ok | elmdb_error(). 299 | update_put(Txn, Dbi, Key, Val) -> 300 | update_put(Txn, Dbi, Key, Val, ?TIMEOUT). 301 | 302 | -spec update_put(txn(), dbi(), key(), val(), non_neg_integer()) -> ok | elmdb_error(). 303 | update_put(Txn, Dbi, Key, Val, Timeout) -> 304 | Ref = make_ref(), 305 | case nif_update_put(Ref, Txn, Dbi, Key, Val) of 306 | ok -> recv_async(Ref, Timeout); 307 | Error -> Error 308 | end. 309 | 310 | nif_update_put(_Ref, _Txn, _Dbi, _Key, _Val) -> 311 | ?NOT_LOADED. 312 | 313 | -spec update_get(dbi(), key()) -> {ok, val(), txn()} | not_found | elmdb_error(). 314 | update_get(Dbi, Key) -> 315 | update_get(Dbi, Key, ?TIMEOUT). 316 | 317 | -spec update_get(dbi(), key(), non_neg_integer()) -> {ok, val(), txn()} | not_found | elmdb_error(). 318 | update_get(Dbi, Key, Timeout) -> 319 | Ref = make_ref(), 320 | case nif_update_get(Ref, Dbi, Key) of 321 | ok -> recv_async(Ref, Timeout); 322 | Error -> Error 323 | end. 324 | 325 | nif_update_get(_Ref, _Dbi, _Key) -> 326 | ?NOT_LOADED. 327 | 328 | -spec ro_txn_begin(env()) -> {ok, txn()} | elmdb_error(). 329 | ro_txn_begin(_Env) -> 330 | ?NOT_LOADED. 331 | 332 | -spec ro_txn_get(txn(), dbi(), key()) -> {ok, val()} | elmdb_error(). 333 | ro_txn_get(_Txn, _Dbi, _Key) -> 334 | ?NOT_LOADED. 335 | 336 | -spec ro_txn_commit(txn()) -> ok | elmdb_error(). 337 | ro_txn_commit(_Txn) -> 338 | ?NOT_LOADED. 339 | 340 | -spec ro_txn_abort(txn()) -> ok | elmdb_error(). 341 | ro_txn_abort(_Txn) -> 342 | ?NOT_LOADED. 343 | 344 | -spec ro_txn_cursor_open(txn(), dbi()) -> {ok, cursor()} | elmdb_error(). 345 | ro_txn_cursor_open(_Txn, _Dbi) -> 346 | ?NOT_LOADED. 347 | 348 | -spec ro_txn_cursor_close(cursor()) -> ok. 349 | ro_txn_cursor_close(_Cur) -> 350 | ?NOT_LOADED. 351 | 352 | -spec ro_txn_cursor_get(cursor(), cursor_op()) -> {ok, key(), val()} | not_found | elmdb_error(). 353 | ro_txn_cursor_get(_Cur, _Op) -> 354 | ?NOT_LOADED. 355 | 356 | -spec txn_begin(env()) -> {ok, txn()} | elmdb_error(). 357 | txn_begin(Env) -> 358 | txn_begin(Env, ?TIMEOUT). 359 | 360 | -spec txn_begin(env(), non_neg_integer()) -> {ok, txn()} | elmdb_error(). 361 | txn_begin(Env, Timeout) -> 362 | Ref = make_ref(), 363 | case nif_txn_begin(Ref, Env) of 364 | ok -> recv_async(Ref, Timeout); 365 | Error -> Error 366 | end. 367 | 368 | nif_txn_begin(_Ref, _Env) -> 369 | ?NOT_LOADED. 370 | 371 | -spec txn_put(txn(), dbi(), key(), val()) -> ok | elmdb_error(). 372 | txn_put(Txn, Dbi, Key, Val) -> 373 | txn_put(Txn, Dbi, Key, Val, ?TIMEOUT). 374 | 375 | -spec txn_put(txn(), dbi(), key(), val(), non_neg_integer()) -> ok | elmdb_error(). 376 | txn_put(Txn, Dbi, Key, Val, Timeout) -> 377 | Ref = make_ref(), 378 | case nif_txn_put(Ref, Txn, Dbi, Key, Val) of 379 | ok -> recv_async(Ref, Timeout); 380 | Error -> Error 381 | end. 382 | 383 | nif_txn_put(_Ref, _Txn, _Dbi, _Key, _Val) -> 384 | ?NOT_LOADED. 385 | 386 | -spec txn_put_new(txn(), dbi(), key(), val()) -> ok | exists | elmdb_error(). 387 | txn_put_new(Txn, Dbi, Key, Val) -> 388 | txn_put_new(Txn, Dbi, Key, Val, ?TIMEOUT). 389 | 390 | -spec txn_put_new(txn(), dbi(), key(), val(), non_neg_integer()) -> ok | exists | elmdb_error(). 391 | txn_put_new(Txn, Dbi, Key, Val, Timeout) -> 392 | Ref = make_ref(), 393 | case nif_txn_put_new(Ref, Txn, Dbi, Key, Val) of 394 | ok -> recv_async(Ref, Timeout); 395 | Error -> Error 396 | end. 397 | 398 | nif_txn_put_new(_Ref, _Txn, _Dbi, _Key, _Val) -> 399 | ?NOT_LOADED. 400 | 401 | -spec txn_get(txn(), dbi(), key()) -> {ok, val()} | not_found | elmdb_error(). 402 | txn_get(Txn, Dbi, Key) -> 403 | txn_get(Txn, Dbi, Key, ?TIMEOUT). 404 | 405 | -spec txn_get(txn(), dbi(), key(), non_neg_integer()) -> {ok, val()} | not_found | elmdb_error(). 406 | txn_get(Txn, Dbi, Key, Timeout) -> 407 | Ref = make_ref(), 408 | case nif_txn_get(Ref, Txn, Dbi, Key) of 409 | ok -> recv_async(Ref, Timeout); 410 | Error -> Error 411 | end. 412 | 413 | nif_txn_get(_Ref, _Txn, _Dbi, _Key) -> 414 | ?NOT_LOADED. 415 | 416 | -spec txn_delete(txn(), dbi(), key()) -> ok | not_found | elmdb_error(). 417 | txn_delete(Txn, Dbi, Key) -> 418 | txn_delete(Txn, Dbi, Key, ?TIMEOUT). 419 | 420 | -spec txn_delete(txn(), dbi(), key(), non_neg_integer()) -> ok | not_found | elmdb_error(). 421 | txn_delete(Txn, Dbi, Key, Timeout) -> 422 | Ref = make_ref(), 423 | case nif_txn_delete(Ref, Txn, Dbi, Key) of 424 | ok -> recv_async(Ref, Timeout); 425 | Error -> Error 426 | end. 427 | 428 | nif_txn_delete(_Ref, _Txn, _Dbi, _Key) -> 429 | ?NOT_LOADED. 430 | 431 | -spec txn_drop(txn(), dbi()) -> ok | not_found | elmdb_error(). 432 | txn_drop(Txn, Dbi) -> 433 | txn_drop(Txn, Dbi, ?TIMEOUT). 434 | 435 | -spec txn_drop(txn(), dbi(), non_neg_integer()) -> ok | not_found | elmdb_error(). 436 | txn_drop(Txn, Dbi, Timeout) -> 437 | Ref = make_ref(), 438 | case nif_txn_drop(Ref, Txn, Dbi) of 439 | ok -> recv_async(Ref, Timeout); 440 | Error -> Error 441 | end. 442 | 443 | nif_txn_drop(_Ref, _Txn, _Dbi) -> 444 | ?NOT_LOADED. 445 | 446 | -spec txn_commit(txn()) -> ok | elmdb_error(). 447 | txn_commit(Txn) -> 448 | txn_commit(Txn, ?TIMEOUT). 449 | 450 | -spec txn_commit(txn(), non_neg_integer()) -> ok | elmdb_error(). 451 | txn_commit(Txn, Timeout) -> 452 | Ref = make_ref(), 453 | case nif_txn_commit(Ref, Txn) of 454 | ok -> recv_async(Ref, Timeout); 455 | Error -> Error 456 | end. 457 | 458 | nif_txn_commit(_Ref, _Txn) -> 459 | ?NOT_LOADED. 460 | 461 | -spec txn_abort(txn()) -> ok | elmdb_error(). 462 | txn_abort(Txn) -> 463 | txn_abort(Txn, ?TIMEOUT). 464 | 465 | -spec txn_abort(txn(), non_neg_integer()) -> ok | elmdb_error(). 466 | txn_abort(Txn, Timeout) -> 467 | Ref = make_ref(), 468 | case nif_txn_abort(Ref, Txn) of 469 | ok -> recv_async(Ref, Timeout); 470 | Error -> Error 471 | end. 472 | 473 | nif_txn_abort(_Ref, _Txn) -> 474 | ?NOT_LOADED. 475 | 476 | -spec txn_cursor_open(txn(), dbi()) -> {ok, cursor()} | elmdb_error(). 477 | txn_cursor_open(Txn, Dbi) -> 478 | txn_cursor_open(Txn, Dbi, ?TIMEOUT). 479 | 480 | -spec txn_cursor_open(txn(), dbi(), non_neg_integer()) -> {ok, cursor()} | elmdb_error(). 481 | txn_cursor_open(Txn, Dbi, Timeout) -> 482 | Ref = make_ref(), 483 | case nif_txn_cursor_open(Ref, Txn, Dbi) of 484 | ok -> recv_async(Ref, Timeout); 485 | Error -> Error 486 | end. 487 | 488 | nif_txn_cursor_open(_Ref, _Txn, _Dbi) -> 489 | ?NOT_LOADED. 490 | 491 | -spec txn_cursor_get(cursor(), cursor_op()) -> {ok, key(), val()} | not_found | elmdb_error(). 492 | txn_cursor_get(Cur, Op) -> 493 | txn_cursor_get(Cur, Op, ?TIMEOUT). 494 | 495 | -spec txn_cursor_get(cursor(), cursor_op(), non_neg_integer()) -> {ok, key(), val()} | not_found | elmdb_error(). 496 | txn_cursor_get(Cur, Op, Timeout) -> 497 | Ref = make_ref(), 498 | case nif_txn_cursor_get(Ref, Cur, Op) of 499 | ok -> recv_async(Ref, Timeout); 500 | Error -> Error 501 | end. 502 | 503 | nif_txn_cursor_get(_Ref, _Cur, _Op) -> 504 | ?NOT_LOADED. 505 | 506 | -spec txn_cursor_put(cursor(), key(), val()) -> ok | elmdb_error(). 507 | txn_cursor_put(Cur, Key, Val) -> 508 | txn_cursor_put(Cur, Key, Val, ?TIMEOUT). 509 | 510 | -spec txn_cursor_put(cursor(), key(), val(), non_neg_integer()) -> ok | elmdb_error(). 511 | txn_cursor_put(Cur, Key, Val, Timeout) -> 512 | Ref = make_ref(), 513 | case nif_txn_cursor_put(Ref, Cur, Key, Val) of 514 | ok -> recv_async(Ref, Timeout); 515 | Error -> Error 516 | end. 517 | 518 | nif_txn_cursor_put(_Ref, _Cur, _Key, _Val) -> 519 | ?NOT_LOADED. 520 | 521 | %%==================================================================== 522 | %% PRIVATE API 523 | %%==================================================================== 524 | 525 | recv_async(Ref, Timeout) -> 526 | receive 527 | {elmdb, Ref, {error, badarg}} -> 528 | error(badarg); 529 | {elmdb, Ref, Ret} -> 530 | Ret; 531 | % Flush old messages left over from previous timeouts 532 | {elmdb, OtherRef, _Ret} when is_reference(OtherRef) -> 533 | recv_async(Ref, Timeout) 534 | after 535 | Timeout -> 536 | {error, {timeout, "A timeout occured while waiting for response from the environment's async thread"}} 537 | end. 538 | 539 | init() -> 540 | PrivDir = case code:priv_dir(?MODULE) of 541 | {error, _} -> 542 | EbinDir = filename:dirname(code:which(?MODULE)), 543 | AppPath = filename:dirname(EbinDir), 544 | filename:join(AppPath, "priv"); 545 | Path -> 546 | Path 547 | end, 548 | case erlang:load_nif(filename:join(PrivDir, ?ELMDB_DRIVER_NAME), 0) of 549 | ok -> ok; 550 | {error,{reload, _}} -> ok; 551 | Error -> Error 552 | end. 553 | 554 | not_loaded(Line) -> 555 | erlang:nif_error({not_loaded, [{module, ?MODULE}, {line, Line}]}). 556 | -------------------------------------------------------------------------------- /test/elmdb_tests.erl: -------------------------------------------------------------------------------- 1 | -module(elmdb_tests). 2 | -include_lib("eunit/include/eunit.hrl"). 3 | 4 | close_env_test() -> 5 | Name = "test/testdb", 6 | {ok, Env} = elmdb:env_open(Name, []), 7 | io:format("closing env~n"), 8 | ?assertMatch(ok, elmdb:env_close(Env)), 9 | io:format("try opening db~n"), 10 | ?assertMatch({error, {env_closed, _}}, elmdb:db_open(Env, [])), 11 | io:format("delete env~n"), 12 | delete_env(Name). 13 | 14 | close_env_by_name_test() -> 15 | Name = "test/testdb", 16 | {ok, Env} = elmdb:env_open(Name, []), 17 | ?assertMatch(ok, elmdb:env_close_by_name(Name)), 18 | delete_env(Name), 19 | % Make sure Env exists until closing it. 20 | Env. 21 | 22 | close_all_env_test() -> 23 | Name1 = "test/testdb1", 24 | Name2 = "test/testdb2", 25 | {ok, Env1} = elmdb:env_open(Name1, []), 26 | {ok, Env2} = elmdb:env_open(Name2, []), 27 | ?assertMatch(ok, elmdb:env_close_all()), 28 | ?assertMatch({error, {env_closed, _}}, elmdb:db_open(Env1, [])), 29 | ?assertMatch({error, {env_closed, _}}, elmdb:db_open(Env2, [])), 30 | delete_env(Name1), 31 | delete_env(Name2), 32 | % Make sure Envs exists until closing it. 33 | {Env1, Env2}. 34 | 35 | open_env_badarg_test() -> 36 | ?assertError(badarg, elmdb:env_open("test/testdb", [123])). 37 | 38 | open_db_badarg_test() -> 39 | Name = "test/testdb", 40 | {ok, Env} = elmdb:env_open(Name, []), 41 | ?assertError(badarg, elmdb:db_open(Env, 1234)), 42 | ?assertError(badarg, elmdb:db_open(1234, [])), 43 | ?assertMatch(ok, elmdb:env_close(Env)), 44 | delete_env(Name). 45 | 46 | basic_test_() -> 47 | Tests = 48 | [ 49 | fun sync_put_get/1, 50 | fun async_put_get/1, 51 | fun put_new/1, 52 | fun async_put_new/1, 53 | fun txn_put_new/1, 54 | fun delete/1, 55 | fun async_delete/1, 56 | fun txn_delete/1, 57 | fun drop/1, 58 | fun async_drop/1, 59 | fun txn_drop/1, 60 | fun update/1, 61 | fun interfere_update/1, 62 | fun ro_txn_get/1, 63 | fun ro_txn_cursor/1, 64 | fun txn_put_get/1, 65 | fun txn_cursor/1 66 | ], 67 | {foreach, fun basic_setup/0, fun basic_teardown/1, Tests}. 68 | 69 | sync_put_get({_, Dbi, _}) -> 70 | fun() -> 71 | ?assertMatch(ok, elmdb:put(Dbi, <<"a">>, <<"1">>)), 72 | ?assertMatch(ok, elmdb:put(Dbi, <<"b">>, <<"2">>)), 73 | ?assertMatch(ok, elmdb:put(Dbi, <<"c">>, <<"3">>)), 74 | ?assertMatch({ok, <<"1">>}, elmdb:get(Dbi, <<"a">>)), 75 | ?assertMatch({ok, <<"2">>}, elmdb:get(Dbi, <<"b">>)), 76 | ?assertMatch({ok, <<"3">>}, elmdb:get(Dbi, <<"c">>)), 77 | ?assertMatch(not_found, elmdb:get(Dbi, <<"d">>)) 78 | end. 79 | 80 | async_put_get({_, Dbi, _}) -> 81 | fun() -> 82 | ?assertMatch(ok, elmdb:async_put(Dbi, <<"a">>, <<"1">>)), 83 | ?assertMatch(ok, elmdb:async_put(Dbi, <<"b">>, <<"2">>)), 84 | ?assertMatch(ok, elmdb:async_put(Dbi, <<"c">>, <<"3">>)), 85 | ?assertMatch({ok, <<"1">>}, elmdb:async_get(Dbi, <<"a">>)), 86 | ?assertMatch({ok, <<"2">>}, elmdb:async_get(Dbi, <<"b">>)), 87 | ?assertMatch({ok, <<"3">>}, elmdb:async_get(Dbi, <<"c">>)), 88 | ?assertMatch(not_found, elmdb:async_get(Dbi, <<"d">>)) 89 | end. 90 | 91 | put_new({_, Dbi, _}) -> 92 | fun() -> 93 | ?assertMatch(ok, elmdb:put_new(Dbi, <<"a">>, <<"1">>)), 94 | ?assertMatch(ok, elmdb:put_new(Dbi, <<"b">>, <<"2">>)), 95 | ?assertMatch(exists, elmdb:put_new(Dbi, <<"b">>, <<"3">>)), 96 | ?assertMatch({ok, <<"2">>}, elmdb:get(Dbi, <<"b">>)) 97 | end. 98 | 99 | async_put_new({_, Dbi, _}) -> 100 | fun() -> 101 | ?assertMatch(ok, elmdb:async_put_new(Dbi, <<"a">>, <<"1">>)), 102 | ?assertMatch(ok, elmdb:async_put_new(Dbi, <<"b">>, <<"2">>)), 103 | ?assertMatch(exists, elmdb:async_put_new(Dbi, <<"b">>, <<"3">>)), 104 | ?assertMatch({ok, <<"2">>}, elmdb:get(Dbi, <<"b">>)) 105 | end. 106 | 107 | txn_put_new({Env, Dbi, _}) -> 108 | fun() -> 109 | {ok, Txn} = elmdb:txn_begin(Env), 110 | ?assertMatch(ok, elmdb:txn_put_new(Txn, Dbi, <<"a">>, <<"1">>)), 111 | ?assertMatch(ok, elmdb:txn_put_new(Txn, Dbi, <<"b">>, <<"2">>)), 112 | ?assertMatch(exists, elmdb:txn_put_new(Txn, Dbi, <<"b">>, <<"3">>)), 113 | ?assertMatch({ok, <<"2">>}, elmdb:txn_get(Txn, Dbi, <<"b">>)), 114 | ?assertMatch(ok, elmdb:txn_commit(Txn)) 115 | end. 116 | 117 | delete({_, Dbi, _}) -> 118 | fun() -> 119 | ?assertMatch(ok, elmdb:put(Dbi, <<"a">>, <<"1">>)), 120 | ?assertMatch({ok, <<"1">>}, elmdb:get(Dbi, <<"a">>)), 121 | ?assertMatch(ok, elmdb:delete(Dbi, <<"a">>)), 122 | ?assertMatch(not_found, elmdb:delete(Dbi, <<"a">>)), 123 | ?assertMatch(not_found, elmdb:get(Dbi, <<"a">>)) 124 | end. 125 | 126 | async_delete({_, Dbi, _}) -> 127 | fun() -> 128 | ?assertMatch(ok, elmdb:async_put(Dbi, <<"a">>, <<"1">>)), 129 | ?assertMatch({ok, <<"1">>}, elmdb:async_get(Dbi, <<"a">>)), 130 | ?assertMatch(ok, elmdb:async_delete(Dbi, <<"a">>)), 131 | ?assertMatch(not_found, elmdb:async_delete(Dbi, <<"a">>)), 132 | ?assertMatch(not_found, elmdb:async_get(Dbi, <<"a">>)) 133 | end. 134 | 135 | txn_delete({Env, Dbi, _}) -> 136 | fun() -> 137 | {ok, Txn} = elmdb:txn_begin(Env), 138 | ?assertMatch(ok, elmdb:txn_put(Txn, Dbi, <<"a">>, <<"1">>)), 139 | ?assertMatch({ok, <<"1">>}, elmdb:txn_get(Txn, Dbi, <<"a">>)), 140 | ?assertMatch(ok, elmdb:txn_delete(Txn, Dbi, <<"a">>)), 141 | ?assertMatch(not_found, elmdb:txn_delete(Txn, Dbi, <<"a">>)), 142 | ?assertMatch(not_found, elmdb:txn_get(Txn, Dbi, <<"a">>)), 143 | ?assertMatch(ok, elmdb:txn_commit(Txn)) 144 | end. 145 | 146 | drop({_, Dbi, _}) -> 147 | fun() -> 148 | ?assertMatch(ok, elmdb:put(Dbi, <<"a">>, <<"1">>)), 149 | ?assertMatch(ok, elmdb:put(Dbi, <<"b">>, <<"2">>)), 150 | ?assertMatch(ok, elmdb:put(Dbi, <<"c">>, <<"3">>)), 151 | ?assertMatch(ok, elmdb:drop(Dbi)), 152 | ?assertMatch(not_found, elmdb:get(Dbi, <<"a">>)), 153 | ?assertMatch(not_found, elmdb:get(Dbi, <<"b">>)), 154 | ?assertMatch(not_found, elmdb:get(Dbi, <<"c">>)) 155 | end. 156 | 157 | async_drop({_, Dbi, _}) -> 158 | fun() -> 159 | ?assertMatch(ok, elmdb:async_put(Dbi, <<"a">>, <<"1">>)), 160 | ?assertMatch(ok, elmdb:async_put(Dbi, <<"b">>, <<"2">>)), 161 | ?assertMatch(ok, elmdb:async_put(Dbi, <<"c">>, <<"3">>)), 162 | ?assertMatch(ok, elmdb:async_drop(Dbi)), 163 | ?assertMatch(not_found, elmdb:async_get(Dbi, <<"a">>)), 164 | ?assertMatch(not_found, elmdb:async_get(Dbi, <<"b">>)), 165 | ?assertMatch(not_found, elmdb:async_get(Dbi, <<"c">>)) 166 | end. 167 | 168 | txn_drop({Env, Dbi, _}) -> 169 | fun() -> 170 | {ok, Txn} = elmdb:txn_begin(Env), 171 | ?assertMatch(ok, elmdb:txn_put(Txn, Dbi, <<"a">>, <<"1">>)), 172 | ?assertMatch(ok, elmdb:txn_put(Txn, Dbi, <<"b">>, <<"2">>)), 173 | ?assertMatch(ok, elmdb:txn_put(Txn, Dbi, <<"c">>, <<"3">>)), 174 | ?assertMatch(ok, elmdb:txn_drop(Txn, Dbi)), 175 | ?assertMatch(not_found, elmdb:txn_get(Txn, Dbi, <<"a">>)), 176 | ?assertMatch(not_found, elmdb:txn_get(Txn, Dbi, <<"b">>)), 177 | ?assertMatch(not_found, elmdb:txn_get(Txn, Dbi, <<"c">>)), 178 | ?assertMatch(ok, elmdb:txn_commit(Txn)) 179 | end. 180 | 181 | update({_, Dbi, _}) -> 182 | fun() -> 183 | ?assertMatch(ok, elmdb:put(Dbi, <<"a">>, <<"1">>)), 184 | {ok, <<"1">>, Txn} = elmdb:update_get(Dbi, <<"a">>), 185 | ?assertMatch(ok, elmdb:update_put(Txn, Dbi, <<"a">>, <<"2">>)), 186 | ?assertMatch({ok, <<"2">>}, elmdb:get(Dbi, <<"a">>)) 187 | end. 188 | 189 | interfere_update({_, Dbi, _}) -> 190 | fun() -> 191 | ?assertMatch(ok, elmdb:put(Dbi, <<"a">>, <<"1">>)), 192 | {ok, <<"1">>, Txn} = elmdb:update_get(Dbi, <<"a">>), 193 | ?assertMatch({error, {timeout, _}}, elmdb:async_put(Dbi, <<"a">>, <<"3">>, 1000)), 194 | ?assertMatch(ok, elmdb:update_put(Txn, Dbi, <<"a">>, <<"2">>)), 195 | ?assertMatch({ok, <<"3">>}, elmdb:async_get(Dbi, <<"a">>)) 196 | end. 197 | 198 | ro_txn_get({Env, Dbi, _}) -> 199 | fun() -> 200 | ?assertMatch(ok, elmdb:put(Dbi, <<"a">>, <<"1">>)), 201 | ?assertMatch(ok, elmdb:put(Dbi, <<"b">>, <<"2">>)), 202 | ?assertMatch(ok, elmdb:put(Dbi, <<"c">>, <<"3">>)), 203 | {ok, Txn} = elmdb:ro_txn_begin(Env), 204 | ?assertMatch({ok, <<"1">>}, elmdb:ro_txn_get(Txn, Dbi, <<"a">>)), 205 | ?assertMatch({ok, <<"2">>}, elmdb:ro_txn_get(Txn, Dbi, <<"b">>)), 206 | ?assertMatch(ok, elmdb:ro_txn_commit(Txn)), 207 | ?assertMatch({error, {txn_closed, _}}, elmdb:ro_txn_get(Txn, Dbi, <<"c">>)), 208 | ?assertMatch({error, {txn_closed, _}}, elmdb:ro_txn_commit(Txn)), 209 | ?assertMatch({error, {txn_closed, _}}, elmdb:ro_txn_abort(Txn)), 210 | ?assertMatch({error, {txn_closed, _}}, elmdb:ro_txn_abort(Txn)) 211 | end. 212 | 213 | ro_txn_cursor({Env, Dbi, _}) -> 214 | fun() -> 215 | ?assertMatch(ok, elmdb:put(Dbi, <<"a">>, <<"1">>)), 216 | ?assertMatch(ok, elmdb:put(Dbi, <<"b">>, <<"2">>)), 217 | ?assertMatch(ok, elmdb:put(Dbi, <<"d">>, <<"4">>)), 218 | {ok, Txn} = elmdb:ro_txn_begin(Env), 219 | {ok, Cur} = elmdb:ro_txn_cursor_open(Txn, Dbi), 220 | ?assertMatch({ok, <<"a">>, <<"1">>}, elmdb:ro_txn_cursor_get(Cur, first)), 221 | ?assertMatch({ok, <<"d">>, <<"4">>}, elmdb:ro_txn_cursor_get(Cur, last)), 222 | ?assertMatch({ok, <<"b">>, <<"2">>}, elmdb:ro_txn_cursor_get(Cur, prev)), 223 | ?assertMatch({ok, <<"a">>, <<"1">>}, elmdb:ro_txn_cursor_get(Cur, prev)), 224 | ?assertMatch(not_found, elmdb:ro_txn_cursor_get(Cur, prev)), 225 | ?assertMatch(not_found, elmdb:ro_txn_cursor_get(Cur, prev)), 226 | ?assertMatch({ok, <<"a">>, <<"1">>}, elmdb:ro_txn_cursor_get(Cur, first)), 227 | ?assertMatch({ok, <<"a">>, <<"1">>}, elmdb:ro_txn_cursor_get(Cur, get_current)), 228 | ?assertMatch({ok, <<"b">>, <<"2">>}, elmdb:ro_txn_cursor_get(Cur, next)), 229 | ?assertMatch({ok, <<"d">>, <<"4">>}, elmdb:ro_txn_cursor_get(Cur, next)), 230 | ?assertMatch(not_found, elmdb:ro_txn_cursor_get(Cur, next)), 231 | ?assertMatch(not_found, elmdb:ro_txn_cursor_get(Cur, next)), 232 | ?assertMatch({ok, <<"b">>, <<"2">>}, elmdb:ro_txn_cursor_get(Cur, {set, <<"b">>})), 233 | ?assertMatch(not_found, elmdb:ro_txn_cursor_get(Cur, {set, <<"c">>})), 234 | ?assertMatch({ok, <<"d">>, <<"4">>}, elmdb:ro_txn_cursor_get(Cur, {set_range, <<"c">>})), 235 | ?assertMatch(ok, elmdb:ro_txn_cursor_close(Cur)), 236 | ?assertMatch(ok, elmdb:ro_txn_abort(Txn)) 237 | end. 238 | 239 | txn_put_get({Env, Dbi, _}) -> 240 | fun() -> 241 | {ok, Txn1} = elmdb:txn_begin(Env), 242 | ?assertMatch(ok, elmdb:txn_put(Txn1, Dbi, <<"a">>, <<"1">>)), 243 | ?assertMatch({ok, <<"1">>}, elmdb:txn_get(Txn1, Dbi, <<"a">>)), 244 | ?assertMatch(ok, elmdb:txn_abort(Txn1)), 245 | ?assertMatch(not_found, elmdb:get(Dbi, <<"a">>)), 246 | {ok, Txn2} = elmdb:txn_begin(Env), 247 | ?assertMatch({error, {txn_closed, _}}, elmdb:txn_put(Txn1, Dbi, <<"a">>, <<"1">>)), 248 | ?assertMatch(ok, elmdb:txn_put(Txn2, Dbi, <<"a">>, <<"1">>)), 249 | ?assertMatch({ok, <<"1">>}, elmdb:txn_get(Txn2, Dbi, <<"a">>)), 250 | ?assertMatch(ok, elmdb:txn_commit(Txn2)), 251 | ?assertMatch({ok, <<"1">>}, elmdb:get(Dbi, <<"a">>)), 252 | ?assertMatch({error, {txn_closed, _}}, elmdb:txn_get(Txn2, Dbi, <<"a">>)), 253 | ?assertMatch({error, {txn_closed, _}}, elmdb:txn_commit(Txn2)), 254 | ?assertMatch({error, {txn_closed, _}}, elmdb:txn_abort(Txn2)) 255 | end. 256 | 257 | txn_cursor({Env, Dbi, _}) -> 258 | fun() -> 259 | {ok, Txn} = elmdb:txn_begin(Env), 260 | {ok, Cur} = elmdb:txn_cursor_open(Txn, Dbi), 261 | ?assertMatch(ok, elmdb:txn_cursor_put(Cur, <<"a">>, <<"1">>)), 262 | ?assertMatch(ok, elmdb:txn_cursor_put(Cur, <<"b">>, <<"2">>)), 263 | ?assertMatch(ok, elmdb:txn_cursor_put(Cur, <<"d">>, <<"4">>)), 264 | ?assertMatch({ok, <<"a">>, <<"1">>}, elmdb:txn_cursor_get(Cur, first)), 265 | ?assertMatch({ok, <<"d">>, <<"4">>}, elmdb:txn_cursor_get(Cur, last)), 266 | ?assertMatch({ok, <<"b">>, <<"2">>}, elmdb:txn_cursor_get(Cur, prev)), 267 | ?assertMatch({ok, <<"a">>, <<"1">>}, elmdb:txn_cursor_get(Cur, prev)), 268 | ?assertMatch(not_found, elmdb:txn_cursor_get(Cur, prev)), 269 | ?assertMatch(not_found, elmdb:txn_cursor_get(Cur, prev)), 270 | ?assertMatch({ok, <<"a">>, <<"1">>}, elmdb:txn_cursor_get(Cur, first)), 271 | ?assertMatch({ok, <<"a">>, <<"1">>}, elmdb:txn_cursor_get(Cur, get_current)), 272 | ?assertMatch({ok, <<"b">>, <<"2">>}, elmdb:txn_cursor_get(Cur, next)), 273 | ?assertMatch({ok, <<"d">>, <<"4">>}, elmdb:txn_cursor_get(Cur, next)), 274 | ?assertMatch(not_found, elmdb:txn_cursor_get(Cur, next)), 275 | ?assertMatch(not_found, elmdb:txn_cursor_get(Cur, next)), 276 | ?assertMatch({ok, <<"b">>, <<"2">>}, elmdb:txn_cursor_get(Cur, {set, <<"b">>})), 277 | ?assertMatch(not_found, elmdb:txn_cursor_get(Cur, {set, <<"c">>})), 278 | ?assertMatch({ok, <<"d">>, <<"4">>}, elmdb:txn_cursor_get(Cur, {set_range, <<"c">>})), 279 | ?assertMatch(ok, elmdb:txn_commit(Txn)) 280 | end. 281 | 282 | basic_setup() -> 283 | Name = "test/testdb", 284 | {ok, Env} = elmdb:env_open(Name, []), 285 | {ok, Dbi} = elmdb:db_open(Env, []), 286 | {Env, Dbi, Name}. 287 | 288 | basic_teardown({Env, _Dbi, Name}) -> 289 | ?assertMatch(ok, elmdb:env_close(Env)), 290 | delete_env(Name). 291 | 292 | delete_env(Name) -> 293 | ?assertMatch(ok, file:delete(Name ++ "/data.mdb")), 294 | ?assertMatch(ok, file:delete(Name ++ "/lock.mdb")), 295 | ?assertMatch(ok, file:del_dir(Name)). 296 | --------------------------------------------------------------------------------