- 1
LMDB
ASDF System - 2 Links
- 3 Introduction
- 4 Design and implementation
- 5 Library versions
- 6 Environments
- 7 Transactions
- 8 Databases
- 9 Encoding and decoding data
- 10 Basic operations
- 11 Cursors
- 12 Conditions
- Version: 0.1
- Description: Bindings to
LMDB
, the Lightning Memory-mapped Database. - Licence: MIT, see COPYING.
- Author: Fernando Borretti [email protected], James Anderson [email protected], Gábor Melis [email protected]
- Maintainer: Fernando Borretti [email protected]
- Homepage: https://github.com/antimer/lmdb
- Bug tracker: https://github.com/antimer/lmdb/issues
- Source control: GIT
Here is the official repository and the HTML documentation for the latest version.
LMDB, the Lightning Memory-mapped
Database, is an ACID key-value
database with
MVCC.
It is a small C library ("C lmdb" from now on), around which LMDB
is
a Common Lisp wrapper. LMDB
covers most of C lmdb's functionality,
has a simplified API, much needed Safety checks, and
comprehensive documentation.
Compared to other key-value stores, LMDB
's distuingishing features
are:
-
Transactions span multiple keys.
-
Embedded. It has no server but can be used concurrently not only by multiple threads but by multiple OS processes, too.
-
Extremely high read performance: millions of transactions per second.
-
Very low maintenance.
Other notable things:
-
With its default - the most durable - settings, it has average write performance, which is bottlenecked by
fsync()
. -
Readers don't block readers or writers, but there is at most one writer at a time.
-
Extremely simple, crash-proof design.
-
The entire database (called environment) is backed by a single memory-mapped file, with a copy-on-write B+ tree.
-
No transaction log.
-
It is very much like Berkeley DB done right, without the fluff and much improved administration.
Do read the Caveats, though. On the
Lisp side, this library will not work with virtual threads
because LMDB
's write locking is tied to native threads.
Using LMDB
is easy:
(with-temporary-env (*env*)
(let ((db (get-db "test")))
(with-txn (:write t)
(put db "k1" #(2 3))
(print (g3t db "k1")) ; => #(2 3)
(del db "k1"))))
More typically, the environment and databases are opened once so that multiple threads and transactions can access them:
(defvar *test-db*)
(unless *env*
(setq *env* (open-env "/tmp/lmdb-test-env/" :if-does-not-exist :create))
(setq *test-db* (get-db "test" :value-encoding :utf-8)))
(with-txn (:write t)
(put *test-db* 1 "hello")
(print (g3t *test-db* 1)) ; => "hello"
(del *test-db* 1))
Note how :VALUE-ENCODING
sneaked in above. This was so to make G3T
return a string instead of an octet vector.
LMDB
treats keys and values as opaque byte arrays to be hung on a B+
tree, and only requires a comparison function to be defined over
keys. LMDB
knows how to serialize the types (UNSIGNED-BYTE 64)
and
STRING
(which are often used as keys so sorting must work as
expected). Serialization of the rest of the datatypes is left to the
client. See Encoding and decoding data for more.
The lmdb C API trusts client code to respect its rules. Being C, managing object lifetimes is the biggest burden. There are also rules that are documented, but not enforced. This Lisp wrapper tries to enforce these rules itself and manage object lifetimes in a safe way to avoid data corruption. How and what it does is described in the following.
-
OPEN-ENV
checks that the same path is not used in multiple open environments to prevent locking issues documented in Caveats. -
CLOSE-ENV
waits until all active transactions are finished before actually closing the environment. Alternatively, ifOPEN-ENV
was called with:SYNCHRONIZED
NIL
, to avoid the overhead of synchronization, the environment is closed only when garbage collected.
-
Checks are made to detect illegal operations on parent transactions (see
LMDB-ILLEGAL-ACCESS-TO-PARENT-TXN-ERROR
). -
Access to closed transactions is reliably detected.
-
C
LMDB
allows read transactions to be used in multiple threads. The synchronization cost of performing this safely (i.e. without risking access to closed and freed transaction objects) is significant so this is not supported.
-
mdb_dbi_open() is wrapped by
GET-DB
in a transaction and is protected by a mutex to comply with C lmdb's requirements:A transaction that opens a database must finish (either commit or abort) before another transaction may open it. Multiple concurrent transactions cannot open the same database.
-
mdb_dbi_close() is too dangerous to be exposed as explained in the
GET-DB
documentation. -
mdb_env_set_mapsize(), mdb_env_set_max_readers(), and mdb_env_set_maxdbs() are only available through
OPEN-ENV
because they either require that there are no write transactions or do not work on open environments.
- As even read transactions are restricted to a single thread, so
are cursors. Using a cursor from a thread other than the one in
which it was created (i.e. the thread of its transaction) raises
LMDB-CURSOR-THREAD-ERROR
. In return for this restriction, access to cursors belonging to closed transactions is reliably detected.
The C lmdb library handles system calls being interrupted (EINTR
and EAGAIN
), but unwinding the stack from interrupts in the middle
of LMDB
calls can leave the in-memory data structures such as
transactions inconsistent. If this happens, their further use risks
data corruption. For this reason, calls to LMDB
are performed with
interrupts disabled. For SBCL, this means SB-SYS:WITHOUT-INTERRUPTS
.
It is an error when compiling LMDB
if an equivalent facility is not
found in the Lisp implementation. A warning is signalled if no
substitute is found for SB-SYS:WITH-INTERRUPTS
because this makes
the body of WITH-ENV
, WITH-TXN
, WITH-CURSOR
and similar
uninterruptible.
Operations that do not modify the database (G3T
, CURSOR-FIRST
,
CURSOR-VALUE
, etc) are async unwind safe, and for performance they
are called without the above provisions.
Note that the library is not reentrant, so don't call LMDB
from
signal handlers.
The following are the most prominent deviations and omissions from the C lmdb API in addition to those listed in Safety.
-
mdb_reader_list() is not implemented.
-
mdb_env_copy() and its close kin are not yet implemented.
- Read-only
WITH-TXN
s are turned into noops when "nested" (unlessIGNORE-PARENT
).
-
mdb_set_compare() and mdb_set_dupsort() are not exposed. If they are needed, implement a foreign comparison function and call
LIBLMDB:SET-COMPARE
orLIBLMDB:SET-DUPSORT
directly or perhaps change the encoding of the data. -
Working with multiple contiguous values with
DUPFIXED
is not yet implemented. This functionality would belong inPUT
,CURSOR-PUT
,CURSOR-NEXT
andCURSOR-VALUE
. -
PUT
,CURSOR-PUT
do not support theRESERVE
flag.
-
[function] LMDB-FOREIGN-VERSION
Return the version of the C lmdb library as a string like
0.9.26
.Wraps mdb_version().
-
[function] LMDB-BINDING-VERSION
Return a string representing the version of C lmdb based on which the
CFFI
bindings were created. The version string has the same format asLMDB-FOREIGN-VERSION
.
An environment (class ENV
) is basically a single memory-mapped file
holding all the data, plus some flags determining how we interact
it. An environment can have multiple databases (class DB
), each of
which is a B+ tree within the same file. An environment is like a
database in a relational db, and the databases in it are like tables
and indices. The terminology comes from Berkeley
DB.
-
[class] ENV
An environment object through which a memory-mapped data file can be accessed. Always to be created by
OPEN-ENV
.
-
[reader] ENV-PATH ENV (:PATH)
The location of the memory-mapped file and the environment lock file.
-
[reader] ENV-MAX-DBS ENV (:MAX-DBS)
The maximum number of named databases in the environment. Currently a moderate number is cheap, but a huge number gets expensive: 7-120 words per transaction, and every
GET-DB
does a linear search of the opened database.
-
[reader] ENV-MAX-READERS ENV (:MAX-READERS)
The maximum number of threads/reader slots. See the documentation of the reader lock table for more.
-
[reader] ENV-MAP-SIZE ENV (:MAP-SIZE)
Specifies the size of the data file in bytes.
- [reader] ENV-MODE ENV (:MODE)
-
[reader] ENV-FLAGS ENV (:FLAGS)
A plist of the options as captured by
OPEN-ENV
. For example,(:FIXED-MAP NIL :SUBDIR T ...)
.
-
[variable] *ENV-CLASS* ENV
The default class
OPEN-ENV
instaniates. Must be a subclass ofENV
. This provides a way to associate application specific data withENV
objects.
-
[function] OPEN-ENV PATH &KEY (CLASS *ENV-CLASS*) (IF-DOES-NOT-EXIST :ERROR) (SYNCHRONIZED T) (MAX-DBS 1) (MAX-READERS 126) (MAP-SIZE (* 1024 1024)) (MODE 436) (SUBDIR T) (SYNC T) (META-SYNC T) READ-ONLY (TLS T) (READ-AHEAD T) (LOCK T) (MEM-INIT T) FIXED-MAP WRITE-MAP MAP-ASYNC
Create an
ENV
object through which theLMDB
environment can be accessed and open it. To prevent corruption, an error is signalled if the same data file is opened multiple times. However, the checks performed do not work on remote filesystems (seeENV-PATH
).LMDB-ERROR
is signalled if opening the environment fails for any other reason.Unless explicitly noted, none of the arguments persist (i.e. they are not saved in the data file).
PATH
is the filesystem location of the environment files (seeSUBDIR
below for more). Do not useLMDB
data files on remote filesystems, even between processes on the same host. This breaksflock()
on some OSes, possibly memory map sync, and certainly sync between programs on different hosts.IF-DOES-NOT-EXIST
determines what happens ifENV-PATH
does not exists:-
:ERROR:
An error is signalled. -
:CREATE:
A new memory-mapped file is created ensuring that all containing directories exist. -
NIL
: ReturnNIL
without doing anything.
See
CLOSE-ENV
for the description ofSYNCHRONIZED
.-
MAX-DBS
: The maximum number of named databases in the environment. Currently a moderate number is cheap, but a huge number gets expensive: 7-120 words per transaction, and everyGET-DB
does a linear search of the opened database. -
MAP-SIZE
: Specifies the size of the data file in bytes. The new size takes effect immediately for the current process, but will not be persisted to any others until a write transaction has been committed by the current process. Also, only map size increases are persisted into the environment. If the map size is increased by another process, and data has grown beyond the range of the current mapsize, starting a new transaction (seeWITH-TXN
) will signalLMDB-MAP-RESIZED-ERROR
. If zero is specified forMAP-SIZE
, then the persisted size is used from the data file. Also seeLMDB-MAP-FULL-ERROR
. -
MODE
: Unix file mode for files created. The default is#o664
. Has no effect when opening an existing environment.
The rest of the arguments correspond to
LMDB
environment flags and are available in the plistENV-FLAGS
.-
SUBDIR
: IfSUBDIR
, then the path is a directory which holds thedata.mdb
and thelock.mdb
files. IfSUBDIR
isNIL
, the path is the filename of the data file and the lock file has the same name plus a-lock
suffix. -
SYNC
: IfNIL
, don'tfsync
after commit. This optimization means a system crash can corrupt the database or lose the last transactions if buffers are not yet flushed to disk. The risk is governed by how often the system flushes dirty buffers to disk and how oftenSYNC-ENV
is called. However, if the filesystem preserves write order (very few do) and theWRITE-MAP
(currently unsupported) flag is not used, transactions exhibit ACI (atomicity, consistency, isolation) properties and only lose D (durability). I.e. database integrity is maintained, but a system crash may undo the final transactions. -
META-SYNC
: IfNIL
, flush system buffers to disk only once per transaction, but omit the metadata flush. Defer that until the system flushes files to disk, the next commit of a non-read-only transaction orSYNC-ENV
. This optimization maintains database integrity, but a system crash may undo the last committed transaction. I.e. it preserves the ACI (atomicity, consistency, isolation) but not D (durability) database property. -
READ-ONLY
: Map the data file in read-only mode. It is an error to try to modify anything in it. -
TLS
: Setting it toNIL
allows each OS thread to have multiple read-only transactions (seeWITH-TXN
'sIGNORE-PARENT
argument). It also allows and transactions not to be tied to a single thread, but that's quite dangerous, see Safety. -
READ-AHEAD
: Turn off readahead as inmadvise(MADV_RANDOM)
. Most operating systems perform read-ahead on read requests by default. This option turns it off if the OS supports it. Turning it off may help random read performance when theDB
is larger than RAM and system RAM is full. This option is not implemented on Windows. -
LOCK
: Data corruption lurks here. IfNIL
, don't do any locking. If concurrent access is anticipated, the caller must manage all concurrency itself. For proper operation the caller must enforce single-writer semantics, and must ensure that no readers are using old transactions while a writer is active. The simplest approach is to use an exclusive lock so that no readers may be active at all when a writer begins. -
MEM-INIT
: IfNIL
, don't initializemalloc
ed memory before writing to unused spaces in the data file. By default, memory for pages written to the data file is obtained usingmalloc
. While these pages may be reused in subsequent transactions, freshlymalloc
ed pages will be initialized to zeroes before use. This avoids persisting leftover data from other code (that used the heap and subsequently freed the memory) into the data file. Note that many other system libraries may allocate and free memory from the heap for arbitrary uses. E.g., stdio may use the heap for file I/O buffers. This initialization step has a modest performance cost so some applications may want to disable it using this flag. This option can be a problem for applications which handle sensitive data like passwords, and it makes memory checkers like Valgrind noisy. This flag is not needed withWRITE-MAP
, which writes directly to the mmap instead of using malloc for pages. -
FIXED-MAP
(experimental): This flag must be specified when creating the environment and is stored persistently in the data file. If successful, the memory map will always reside at the same virtual address and pointers used to reference data items in the database will be constant across multiple invocations. This option may not always work, depending on how the operating system has allocated memory to shared libraries and other uses.
Unsupported flags (an error is signalled if they are changed from their default values):
-
WRITE-MAP
: Use a writable memory map unlessREAD-ONLY
is set. This is faster and uses fewer mallocs, but loses protection from application bugs like wild pointer writes and other bad updates into the database. Incompatible with nested transactions. This may be slightly faster forDB
s that fit entirely in RAM, but is slower forDB
s larger than RAM. Do not mix processes with and withoutWRITE-MAP
on the same environment. This can defeat durability (SYNC-ENV
, etc). -
MAP-ASYNC
: When usingWRITE-MAP
, use asynchronous flushes to disk. As withSYNC
NIL
, a system crash can then corrupt the database or lose the last transactions. Calling #sync ensures on-disk database integrity until next commit.
Open environments have a finalizer attached to them that takes care of freeing foreign resources. Thus, the common idiom:
(setq *env* (open-env "some-path"))
is okay for development, too. No need to always do
WITH-ENV
, which does not mesh with threads anyway.Wraps mdb_env_create() and mdb_env_open().
-
-
[function] CLOSE-ENV ENV &KEY FORCE
Close
ENV
and free the memory. Closing an already closedENV
has no effect.Since accessing Transactions, Databases and Cursors after closing their environment would risk database curruption,
CLOSE-ENV
makes sure that they are not in use. There are two ways this can happen:-
If
ENV
was opened:SYNCHRONIZED
(seeOPEN-ENV
), thenCLOSE-ENV
waits until there are no active transactions inENV
before closing it. This requires synchronization and introduces some overhead, which might be noticable for workloads involving lots of quick read transactions. It is anLMDB-ERROR
to attempt to close an environment in aWITH-TXN
to avoid deadlocks. -
On the other hand, if
SYNCHRONIZED
wasNIL
, then - unlessFORCE
is true - callingCLOSE-ENV
signals anLMDB-ERROR
to avoid the Safety issues involved in closing the environment. Environments opened with:SYNCHRONIZED
NIL
are only closed when they are garbage collected and their finalizer is run. Still, for production it might be worth it to gain the last bit of performance.
Wraps mdb_env_close().
-
-
[variable] *ENV* NIL
The default
ENV
for macros and function that take an environment argument.
-
[macro] WITH-ENV (ENV PATH &REST OPEN-ENV-ARGS) &BODY BODY
Bind the variable
ENV
to a new enviroment returned byOPEN-ENV
called withPATH
andOPEN-ENV-ARGS
, executeBODY
, andCLOSE-ENV
. The following example binds the default environment:(with-env (*env* "/tmp/lmdb-test" :if-does-not-exist :create) ...)
-
[function] OPEN-ENV-P ENV
See if
ENV
is open, i.e.OPEN-ENV
has been called on it without a correspondingCLOSE-ENV
.
-
[function] CHECK-FOR-STALE-READERS &OPTIONAL (ENV *ENV*)
Check for stale entries in the reader lock table. See Caveats. This function is called automatically by
OPEN-ENV
. If other OS processes or threads accessingENV
abort without closing read transactions, call this function periodically to get rid off them. Alternatively, close all environments accessing the data file.Wraps mdb_reader_check().
-
[function] ENV-STATISTICS &OPTIONAL (ENV *ENV*)
Return statistics about
ENV
as a plist.-
:PAGE-SIZE:
The size of a database page in bytes. -
:DEPTH:
The height of the B-tree. -
:BRANCH-PAGES:
The number of internal (non-leaf) pages. -
:LEAF-PAGES:
The number of leaf pages. -
:OVERFLOW-PAGES:
The number of overflow pages. -
:ENTRIES:
The number of data items.
Wraps mdb_env_stat().
-
-
[function] ENV-INFO &OPTIONAL (ENV *ENV*)
Return information about
ENV
as a plist.-
:MAP-ADDRESS:
Address of memory map, if fixed (seeOPEN-ENV
'sFIXED-MAP
). -
:MAP-SIZE:
Size of the memory map in bytes. -
:LAST-PAGE-NUMBER:
Id of the last used page. -
:LAST-TXN-ID:
Id of the last committed transaction. -
:MAXIMUM-READERS:
The number of reader slots. -
:N-READERS:
The number of reader slots current used.
Wraps mdb_env_info().
-
-
[function] SYNC-ENV &OPTIONAL (ENV *ENV*)
Flush the data buffers to disk as in calling
fsync()
. WhenENV
had been opened with:SYNC
NIL
or:META-SYNC
NIL
, this may be handy to force flushing the OS buffers to disk, which avoids potential durability and integrity issues.Wraps mdb_env_sync().
-
[function] ENV-MAX-KEY-SIZE &OPTIONAL (ENV *ENV*)
Return the maximum size of keys and
DUPSORT
data in bytes. Depends on the compile-time constantMDB_MAXKEYSIZE
in the C library. The default is 511. If this limit is exceededLMDB-BAD-VALSIZE-ERROR
is signalled.Wraps mdb_env_get_maxkeysize().
-
[macro] WITH-TEMPORARY-ENV (ENV &REST OPEN-ENV-ARGS) &BODY BODY
Run
BODY
with an open temporary environment bound toENV
. In more detail, create an environment in a fresh temporary directory in an OS specific location.OPEN-ENV-ARGS
is a list of keyword arguments and values forOPEN-ENV
. This macro is intended for testing and examples.(with-temporary-env (*env*) (let ((db (get-db "test"))) (with-txn (:write t) (put db "k1" #(2 3)) (print (g3t db "k1")) ; => #(2 3) (del db "k1"))))
Since data corruption in temporary environments is not a concern, unlike
WITH-ENV
,WITH-TEMPORARY-ENV
closes the environment even if it was opened with:SYNCHRONIZED
NIL
(seeOPEN-ENV
andCLOSE-ENV
).
The LMDB
environment supports transactional reads and writes. By
default, these provide the standard ACID (atomicity, consistency,
isolation, durability) guarantees. Writes from a transaction are not
immediately visible to other transactions. When the transaction is
committed, all its writes become visible atomically for future
transactions even if Lisp crashes or there is power failure. If the
transaction is aborted, its writes are discarded.
Transactions span the entire environment (see ENV
). All the updates
made in the course of an update transaction - writing records across
all databases, creating databases, and destroying databases - are
either completed atomically or rolled back.
Write transactions can be nested. Child transactions see the uncommitted writes of their parent. The child transaction can commit or abort, at which point its writes become visible to the parent transaction or are discarded. If the parent aborts, all of the writes performed in the context of the parent, including those from committed child transactions, are discarded.
-
[macro] WITH-TXN (&KEY (ENV '*ENV*) WRITE IGNORE-PARENT (SYNC T) (META-SYNC T)) &BODY BODY
Start a transaction in
ENV
, executeBODY
. Then, if the transaction is open (seeOPEN-TXN-P
) andBODY
returned normally, attempt to commit the transaction. Next, ifBODY
performed a non-local exit or committing failed, but the transaction is still open, then abort it. It is explicitly allowed to callCOMMIT-TXN
orABORT-TXN
withinWITH-TXN
.Transactions provide ACID guarantees (with
SYNC
andMETA-SYNC
both on). They span the entire environment, they are not specific to individualDB
.-
If
WRITE
isNIL
, the transaction is read-only and no writes (e.g.PUT
) may be performed in the transaction. On the flipside, many read-only transactions can run concurrently (seeENV-MAX-READERS
), while write transactions are mutually exclusive. Furthermore, the single write transaction can also run concurrently with read transactions, just keep in mind that read transactions hold on to the state of the environment at the time of their creation and thus prevent pages since replaced from being reused. -
If
IGNORE-PARENT
is true, then in an enclosingWITH-TXN
, instead of creating a child transaction, start an independent transaction. -
If
SYNC
isNIL
, then no flushing of buffers will take place after a commit as if the environment had been opened with:SYNC
NIL
. -
Likewise,
META-SYNC
is the per-transaction equivalent of theOPEN-ENV
'sMETA-SYNC
.
Also see Nesting transactions.
Wraps mdb_txn_begin().
-
-
[glossary-term] active transaction
The active transaction in some environment and thread is the transaction of the innermost
WITH-TXN
being executed in the thread that belongs to the environment. In most cases, this is simply the enclosingWITH-TXN
, but ifWITH-TXN
s with different:ENV
arguments are nested, then it may not be:(with-temporary-env (env) (let ((db (get-db "db" :env env))) (with-temporary-env (inner-env) (with-txn (:env env :write t) (with-txn (:env inner-env) (put db #(1) #(2)))))))
In the above example,
DB
is known to belong toENV
so although the immediately enclosing transaction belongs to INNER-ENV,PUT
is executed in context of the outer, write transaction because that's the innermost inENV
.Operations that require a transaction always attempt to use the active transaction even if it is not open (see
OPEN-TXN-P
).
-
[function] OPEN-TXN-P &OPTIONAL ENV
See if there is an active transaction and it is open, i.e.
COMMIT-TXN
orABORT-TXN
have not been called on it. Also,RESET-TXN
without a correspondingRENEW-TXN
closes the transaction.
-
[function] TXN-ID
The ID of
TXN
. IDs are integers incrementing from 1. For a read-only transaction, this corresponds to the snapshot being read; concurrent readers will frequently have the same transaction ID. Only committed write transactions increment the ID. If a transaction aborts, the ID may be re-used by the next writer.
-
[function] COMMIT-TXN &OPTIONAL ENV
Commit the innermost enclosig transaction (or active transaction belonging to
ENV
ifENV
is specified) or signal an error if it is not open. IfTXN
is not nested in another transaction, committing makes updates performed visible to future transactions. IfTXN
is a child transaction, then committing makes updates visible to its parent only. For read-only transactions, committing releases the reference to a historical version environment, allowing reuse of pages replaced since.Wraps mdb_txn_commit().
-
[function] ABORT-TXN &OPTIONAL ENV
Close
TXN
by discarding all updates performed, which will then not be visible to either parent or future transactions. Aborting an already closed transaction is a noop. Always succeeds.Wraps mdb_txn_abort().
-
[function] RENEW-TXN &OPTIONAL ENV
Renew
TXN
that was reset byRESET-TXN
. This acquires a new reader lock that had been released byRESET-TXN
. After renewal, it is as ifTXN
had just been started.Wraps mdb_txn_renew().
-
[function] RESET-TXN &OPTIONAL ENV
Abort the open, read-only
TXN
, release the reference to the historical version of the environment, but make it faster to start another read-only transaction withRENEW-TXN
. This is accomplished by not deallocating some data structures, and keeping the slot in the reader table. Cursors opened within the transaction must not be used again, except if renewed (see RENEW-CURSOR). IfTXN
is an open, read-only transaction, this function always succeeds.Wraps mdb_txn_reset().
When WITH-TXN
s are nested (i.e. one is executed in the dynamic
extent of another), we speak of nested transactions. Transaction can
be nested to arbitrary levels. Child transactions may be committed
or aborted independently from their parent transaction (the
immediately enclosing WITH-TXN
). Committing a child transaction only
makes the updates made by it visible to the parent. If the parent
then aborts, the child's updates are aborted too. If the parent
commits, all child transactions that were not aborted are committed,
too.
Actually, the C lmdb library only supports nesting write
transactions. To simplify usage, the Lisp side turns read-only
WITH-TXN
s nested in another WITH-TXN
s into noops.
(with-temporary-env (*env*)
(let ((db (get-db "test" :value-encoding :uint64)))
;; Create a top-level write transaction.
(with-txn (:write t)
(put db "p" 0)
;; First child transaction
(with-txn (:write t)
;; Writes of the parent are visible in children.
(assert (= (g3t db "p") 0))
(put db "c1" 1))
;; Parent sees what the child committed (but it's not visible to
;; unrelated transactions).
(assert (= (g3t db "c1") 1))
;; Second child transaction
(with-txn (:write t)
;; Sees writes from the parent that came from the first child.
(assert (= (g3t db "c1") 1))
(put db "c1" 2)
(put db "c2" 2)
(abort-txn)))
;; Create a top-level read transaction to check what was committed.
(with-txn ()
;; Since the second child aborted, its writes are discarded.
(assert (= (g3t db "p") 0))
(assert (= (g3t db "c1") 1))
(assert (null (g3t db "c2"))))))
COMMIT-TXN
, ABORT-TXN
, and RESET-TXN
all close the
active transaction (see OPEN-TXN-P
). When the active transaction is
not open, database operations such as G3T
, PUT
, DEL
signal
LMDB-BAD-TXN-ERROR
. Furthermore, any Cursors created in the
context of the transaction will no longer be valid (but see
CURSOR-RENEW
).
An LMDB
parent transaction and its cursors must not issue operations
other than COMMIT-TXN
and ABORT-TXN
while there are active child
transactions. As the Lisp side does not expose transaction objects
directly, performing Basic operations in the parent
transaction is not possible, but it is possible with Cursors
as they are tied to the transaction in which they were created.
IGNORE-PARENT
true overrides the default nesting semantics of
WITH-TXN
and creates a new top-level transaction, which is not a
child of the enclosing WITH-TXN
.
-
Since
LMDB
is single-writer, on nesting anIGNORE-PARENT
write transaction in another write transaction,LMDB-BAD-TXN-ERROR
is signalled to avoid the deadlock. -
Nesting a read-only
WITH-TXN
withIGNORE-PARENT
in another read-onlyWITH-TXN
isLMDB-BAD-RSLOT-ERROR
error with theTLS
option because it would create two read-only transactions in the same thread.
Nesting a read transaction in another transaction would be an
LMDB-BAD-RSLOT-ERROR
according to the C lmdb library, but a
read-only WITH-TXN
with IGNORE-PARENT
NIL
nested in another WITH-TXN
is turned into a noop so this edge case is papered over.
LMDB
has a default, unnamed database backed by a B+ tree. This db
can hold normal key-value pairs and named databases. The unnamed
database can be accessed by passing NIL
as the database name to
GET-DB
. There are some restrictions on the flags of the unnamed
database, see LMDB-INCOMPATIBLE-ERROR
.
A prominent feature of LMDB
is the ability to associate multiple
sorted values with keys, which is enabled by the DUPSORT
argument of
GET-DB
. Just as a named database is a B+ tree associated with a
key (its name) in the B+ tree of the unnamed database, so do these
sorted duplicates form a B+ tree under a key in a named or the
unnamed database. Among the Basic operations, PUT
and DEL
are
equipped to deal with duplicate values, but G3T
is too limited, and
Cursors are needed to make full use of DUPSORT
.
When using this feature the limit on the maximum key size applies to
duplicate data, as well. See ENV-MAX-KEY-SIZE
.
-
[variable] *DB-CLASS* DB
The default class that
GET-DB
instantiates. Must a subclass ofDB
. This provides a way to associate application specific data withDB
objects.
-
[function] GET-DB NAME &KEY (CLASS *DB-CLASS*) (ENV *ENV*) (IF-DOES-NOT-EXIST :CREATE) KEY-ENCODING VALUE-ENCODING INTEGER-KEY REVERSE-KEY DUPSORT INTEGER-DUP REVERSE-DUP DUPFIXED
Open the database with
NAME
in the open environmentENV
, and return aDB
object. IfNAME
isNIL
, then the The unnamed database is opened.If
GET-DB
is called with the same name multiple times, the returnedDB
objects will be associated with the same database (although they may not beEQ
). The first timeGET-DB
is called with any given name and environment, it must not be from an open transaction. This is becauseGET-DB
starts a transaction itself to comply with C lmdb's requirements on mdb_dbi_open() (see Safety). Since dbi handles are cached withinENV
, subsequent calls do not involvemdb_dbi_open()
and are thus permissible within transactions.CLASS
designates the class which will instantiated. See*DB-CLASS*
.If
IF-DOES-NOT-EXIST
is:CREATE
, then a new named database is created. IfIF-DOES-NOT-EXIST
is:ERROR
, then an error is signalled if the database does not exists.KEY-ENCODING
andVALUE-ENCODING
are both one ofNIL
,:UINT64
,:OCTETS
or:UTF-8
.KEY-ENCODING
is set to:UINT64
whenINTEGER-KEY
is true.VALUE-ENCODING
is set to:UINT64
whenINTEGER-DUP
is true. Note that changing the encoding does not reencode already existing data. See Encoding and decoding data for the full semantics.GET-DB
may be called more than once with the sameNAME
andENV
, and the returnedDB
objects will have the same underlying C lmdb database, but they may have differentKEY-ENCODING
andVALUE-ENCODING
.The following flags are for database creation, they do not have any effect in subsequent calls (except for the The unnamed database).
-
INTEGER-KEY
: Keys in the database are Cunsigned
orsize_t
integers encoded in native byte order. Keys must all be eitherunsigned
orsize_t
, they cannot be mixed in a single database. -
REVERSE-KEY
: Keys are strings to be compared in reverse order, from the end of the strings to the beginning. By default, keys are treated as strings and compared from beginning to end. -
DUPSORT
: Duplicate keys may be used in the database (or, from another perspective, keys may have multiple values, stored in sorted order). By default, keys must be unique and may have only a single value. Also, seeDUPSORT
. -
INTEGER-DUP
: This option specifies that duplicate data items are binary integers, similarly toINTEGER-KEY
. Only matters ifDUPSORT
. -
REVERSE-DUP
: This option specifies that duplicate data items should be compared as strings in reverse order. Only matters ifDUPSORT
. -
DUPFIXED
: This flag may only be used in combinationDUPSORT
. When true, data items for this database must all be the same size, which allows further optimizations in storage and retrieval. Currently, the wrapper functions that could take advantage of this (e.g.PUT
,CURSOR-PUT
,CURSOR-NEXT
andCURSOR-VALUE
), do not.
No function to close a database (an equivalent to mdb_dbi_close()) is provided due to subtle races and corruption it could cause when an
MDB_dbi
(unsigned integer, similar to an fd) is assigned by a subsequent open to another named database.Wraps mdb_dbi_open().
-
-
[reader] DB-NAME DB (:NAME)
The name of the database.
-
[reader] DB-VALUE-ENCODING DB (:VALUE-ENCODING)
-
[function] DROP-DB NAME PATH &KEY OPEN-ENV-ARGS (DELETE T)
Empty the database with
NAME
in the environment denoted byPATH
. IfDELETE
, then delete the database. Since closing a database is dangerous (seeGET-DB
),DROP-DB
opens and closes the environment itself.Wraps mdb_drop().
-
[function] DB-STATISTICS DB
Return statistics about the database.
Wraps mdb_stat().
In the C lmdb library, keys and values are opaque byte vectors
only ever inspected internally to maintain the sort order (of keys
and also duplicate values if DUPSORT
). The client is given the
freedom and the responsibility to choose how to perform conversion
to and from byte vectors.
LMDB
exposes this full flexibility while at the same time providing
reasonable defaults for the common cases. In particular, with the
KEY-ENCODING
and VALUE-ENCODING
arguments of GET-DB
, the
data (meaning the key or value here) encoding can be declared
explicitly.
Even if the encoding is undeclared, it is recommended to use a
single type for keys (and duplicate values) to avoid unexpected
conflicts that could arise, for example, when the UTF-8 encoding of
a string and the :UINT64
encoding of an integer coincide. The same
consideration doubly applies to named databases, which share the key
space with normal key-value pairs in the default database (see
The unnamed database).
Together, :UINT64
and :UTF-8
cover the common cases for keys. They
trade off dynamic typing for easy sortability (using the default C
lmdb behaviour). On the other hand, when sorting is not
concern (either for keys and values), serialization may be done more
freely. For this purpose, using an encoding of :OCTETS
or NIL
with
cl-conspack is
recommended because it works with complex objects, it encodes object
types, it is fast and space-efficient, has a stable specification
and an alternative implementation in C. For example:
(with-temporary-env (*env*)
(let ((db (get-db "test")))
(with-txn (:write t)
(put db "key1" (cpk:encode (list :some "stuff" 42)))
(cpk:decode (g3t db "key1")))))
=> (:SOME "stuff" 42)
Note that multiple DB
objects with different encodings can be
associated with the same C lmdb database, which declutters the code:
(defvar *cpk-encoding*
(cons #'cpk:encode (alexandria:compose #'cpk:decode #'mdb-val-to-octets)))
(with-temporary-env (*env*)
(let ((next-id-db (get-db "test" :key-encoding *cpk-encoding*
:value-encoding :uint64))
(db (get-db "test" :key-encoding *cpk-encoding*
:value-encoding *cpk-encoding*)))
(with-txn (:write t)
(let ((id (or (g3t next-id-db :next-id) 0)))
(put next-id-db :next-id (1+ id))
(put db id (list :some "stuff" 42))
(g3t db id)))))
=> (:SOME "stuff" 42)
=> T
-
[type] ENCODING
The following values are supported:
-
:UINT64:
Data to be encoded must be of type(UNSIGNED-BYTE 64)
, which is then encoded as an 8 byte array in native byte order withUINT64-TO-OCTETS
. The reverse transformation takes place when returning values. This is the encoding used forINTEGER-KEY
andINTEGER-DUP
DB
s. -
:OCTETS:
Note the plural. Data to be encoded (e.g.KEY
argument ofG3T
) must be a 1D byte array. If its element type is(UNSIGNED-BYTE 8)
, then the data can be passed to the foreign code more efficiently, but declaring the element type is not required. For example,VECTOR
s can be used as long as the actual elements are of type(UNSIGNED-BYTE 8)
. Foreign byte arrays to be decoded (e.g. the value returned byG3T
) are returned asOCTETS
. -
:UTF-8:
Data to be encoded must be a string, which is converted to octets byTRIVIAL-UTF-8
. Null-terminated. Foreign byte arrays are decoded the same way. -
NIL
: Data is encoded using the default encoding according to its Lisp type: strings as:UTF-8
, vectors as:OCTETS
,(UNSIGNED-BYTE 64)
as:UINT64
. Decoding is always performed as:OCTETS
. -
A
CONS
: Data is encoded by the function in theCAR
of the cons and decoded by the function in theCDR
. For example,:UINT64
is equivalent to(CONS #'UINT64-TO-OCTETS #'MDB-VAL-TO-UINT64)
.
-
-
[macro] WITH-MDB-VAL-SLOTS (%BYTES SIZE MDB-VAL) &BODY BODY
Bind
%BYTES
andSIZE
locally to the corresponding slots ofMDB-VAL
.MDB-VAL
is an opaque handle for a foreignMDB_val
struct, that holds the pointer to a byte array and the number of bytes in the array. This macro is needed to access the foreign data in a function used as*KEY-DECODER*
or*VALUE-DECODER*
.MDB-VAL
is dynamic extent, so don't hold on to it. Also, the pointer to which%BYTES
is bound is valid only within the context of current top-level transaction.
-
[type] OCTETS &OPTIONAL (SIZE '*)
A 1D
SIMPLE-ARRAY
of(UNSIGNED-BYTE 8)
.
-
[function] MDB-VAL-TO-OCTETS MDB-VAL
A utility function provided for writing
*KEY-DECODER*
and*VALUE-DECODER*
functions. It returns a Lisp octet vector that holds the same bytes asMDB-VAL
.
-
[function] UINT64-TO-OCTETS N
Convert an
(UNSIGNED-BYTE 64)
toOCTETS
of length 8 taking the native byte order representation ofN
. Suitable as a*KEY-ENCODER*
or*VALUE-ENCODER*
.
-
[function] OCTETS-TO-UINT64 OCTETS
The inverse of
UINT64-TO-OCTETS
. UseMDB-VAL-TO-UINT64
as a*KEY-DECODER*
or*VALUE-DECODER*
.
-
[function] MDB-VAL-TO-UINT64 MDB-VAL
Like
OCTETS-TO-UINT64
, but suitable for*KEY-DECODER*
or*VALUE-DECODER*
that decodes unsigned 64 bit integers in native byte order. This function is called automatically when the encoding is known to require it (seeGET-DB
'sINTEGER-KEY
,:VALUE-ENCODING
, etc).
-
[function] STRING-TO-OCTETS STRING
Convert
STRING
toOCTETS
by encoding it as UTF-8 with null termination. Suitable as a*KEY-ENCODER*
or*VALUE-ENCODER*
.
-
[function] OCTETS-TO-STRING OCTETS
The inverse of
STRING-TO-OCTETS
. UseMDB-VAL-TO-STRING
as a*KEY-DECODER*
or*VALUE-DECODER*
.
-
[function] MDB-VAL-TO-STRING MDB-VAL
Like
OCTETS-TO-STRING
, but suitable as a*KEY-DECODER*
or*VALUE-DECODER*
.
Using multiple DB
objects with different encodings is the
recommended practice (see the example in Encoding and decoding data), but when
that is inconvenient, one can override the encodings with the
following variables.
-
[variable] *KEY-ENCODER* NIL
A function designator,
NIL
or anENCODING
. If non-NIL, it overrides the encoding method determined byKEY-ENCODING
(seeGET-DB
). It is called with a single argument, the key, when it is to be converted to an octet vector.
-
[variable] *KEY-DECODER* NIL
A function designator,
NIL
or anENCODING
. If non-NIL, it is called with a singleMDB-VAL
argument (seeWITH-MDB-VAL-SLOTS
), that holds a pointer to data to be decoded and its size. This function is called whenever a key is to be decoded and overrides theKEY-ENCODING
argument ofGET-DB
.For example, if we are only interested in the length of the value and want to avoid creating a lisp vector on the heap, we can do this:
(with-temporary-env (*env*) (let ((db (get-db "test"))) (with-txn (:write t) (put db "key1" "abc") (let ((*value-decoder* (lambda (mdb-val) (with-mdb-val-slots (%bytes size mdb-val) (declare (ignore %bytes)) ;; Take null termination into account. (1- size))))) (g3t db "key1"))))) => 3 => T
-
[variable] *VALUE-ENCODER* NIL
Like
*KEY-ENCODER*
, but for values.
-
[variable] *VALUE-DECODER* NIL
Like
*KEY-DECODER*
, but for values.Apart from performing actual decoding, the main purpose of
*VALUE-DECODER*
, one can also pass the foreign data on to other foreign functions such aswrite()
directly from the decoder function and returning a constant such asT
to avoid consing.
-
[function] G3T DB KEY
Return the value from
DB
associated withKEY
andT
as the second value. IfKEY
is not found inDB
, thenNIL
is returned. IfDB
supportsDUPSORT
, then the first value forKEY
will be returned. Retrieval of other values requires the use of Cursors.This function is called
G3T
instead ofGET
to avoid having to shadowCL:GET
when importing theLMDB
package. On the other hand, importing theLMDB+
package, which hasLMDB::GET
exported, requires some shadowing.The
LMDB+
package is like theLMDB
package, but it has#'LMDB:G3T
fbound toLMDB+:G3T
so it probably needs shadowing to avoid conflict withCL:GET:
(defpackage lmdb/test (:shadow #:get) (:use #:cl #:lmdb+))
Wraps mdb_get().
-
[function] PUT DB KEY VALUE &KEY (OVERWRITE T) (DUPDATA T) APPEND APPEND-DUP (KEY-EXISTS-ERROR-P T)
Add a
KEY
,VALUE
pair toDB
withinTXN
(which must support writes). ReturnsT
on success.-
OVERWRITE
: IfNIL
, signalLMDB-KEY-EXISTS-ERROR
ifKEY
already appears inDB
. -
DUPDATA
: IfNIL
, signalLMDB-KEY-EXISTS-ERROR
if theKEY
,VALUE
pair already appears inDB
. Has no effect ifDB
doesn't haveDUPSORT
. -
APPEND
: Append theKEY
,VALUE
pair to the end ofDB
instead of findingKEY
's location in the B+ tree by performing comparisons. The client effectively promises that keys are inserted in sort order, which allows for fast bulk loading. If the promise is broken, aLMDB-KEY-EXISTS-ERROR
is signalled. -
APPEND-DUP
: The client promises that duplicate values are inserted in sort order. If the promise is broken, aLMDB-KEY-EXISTS-ERROR
is signalled. -
If
KEY-EXISTS-ERROR-P
isNIL
, then instead of signallingLMDB-KEY-EXISTS-ERROR
returnNIL
.
May signal
LMDB-MAP-FULL-ERROR
,LMDB-TXN-FULL-ERROR
,LMDB-TXN-READ-ONLY-ERROR
.Wraps mdb_put().
-
-
[function] DEL DB KEY &KEY VALUE
Delete
KEY
fromDB
. ReturnsT
if data was deleted,NIL
otherwise. IfDB
supports sorted duplicates (DUPSORT
), thenVALUE
is taken into account: if it'sNIL
, then all duplicate values forKEY
are deleted, if it's notNIL
, then only the matching value. May signalLMDB-TXN-READ-ONLY-ERROR
.Wraps mdb_del().
-
[macro] WITH-CURSOR (VAR DB) &BODY BODY
Bind
VAR
to a freshCURSOR
onDB
. ExecuteBODY
, then close the cursor. Within the dynamic extent ofBODY
, this will be the default cursor. The cursor is tied to the active transaction.LMDB-CURSOR-THREAD-ERROR
is signalled if the cursor is accessed from threads other than the one in which it was created.Wraps mdb_cursor_open() and mdb_cursor_close().
-
[macro] WITH-IMPLICIT-CURSOR (DB) &BODY BODY
Like
WITH-CURSOR
but the cursor object is not accessible directly, only through the default cursor mechanism. The cursor is stack-allocated, which eliminates the consing ofWITH-CURSOR
. Note that stack allocation of cursors inWITH-CURSOR
would risk data corruption if the cursor were accessed beyond its dynamic extent.Use
WITH-IMPLICIT-CURSOR
instead ofWITH-CURSOR
if a single cursor at a time will suffice. Conversely, useWITH-CURSOR
if a second cursor is needed. That is, use(with-implicit-cursor (db) (cursor-set-key 1))
but when two cursors iterate in an interleaved manner, use
WITH-CURSOR
:(with-cursor (c1 db) (with-cursor (c2 db) (cursor-first c1) (cursor-last c2) (if (some-pred (cursor-value c1) (cursor-value c2)) (cursor-next c1) (cursor-prev c2)) ...))
Wraps mdb_cursor_open() and mdb_cursor_close().
- [class] CURSOR STRUCTURE-OBJECT
- [function] CURSOR-DB INSTANCE
-
[glossary-term] default cursor
All operations, described below, that take cursor arguments accept
NIL
instead of aCURSOR
object, in which case the cursor from the immediately enclosingWITH-CURSOR
orWITH-IMPLICIT-CURSOR
is used. This cursor is referred to as the default cursor.To reduce syntactic clutter, some operations thus make cursor arguments
&OPTIONAL
. When this is undesirable - because there are keyword arguments as well - the cursor may be a required argument as inCURSOR-PUT
. StillNIL
can be passed explicitly.
The following functions position or initialize a cursor while
returning the value (a value with DUPSORT
) associated with a key,
or both the key and the value. Initialization is successful if there
is the cursor points to a key-value pair, which is indicated by the
last return value being T
.
-
[function] CURSOR-FIRST &OPTIONAL CURSOR
Move
CURSOR
to the first key of its database. Return the key, the value andT
, orNIL
if the database is empty. IfDUPSORT
, positionCURSOR
on the first value of the first key.Wraps mdb_cursor_get() with MDB_FIRST.
-
[function] CURSOR-FIRST-DUP &OPTIONAL CURSOR
Move
CURSOR
to the first duplicate value of the current key. Return the value andT
. ReturnNIL
ifCURSOR
is not positioned.Wraps mdb_cursor_get() with MDB_FIRST_DUP.
-
[function] CURSOR-LAST &OPTIONAL CURSOR
Move
CURSOR
to the last key of its database. Return the key, the value andT
, orNIL
if the database is empty. IfDUPSORT
, positionCURSOR
on the last value of the last key.Wraps mdb_cursor_get() with MDB_LAST.
-
[function] CURSOR-LAST-DUP &OPTIONAL CURSOR
Move
CURSOR
to the last duplicate value of the current key. Return the value andT
. ReturnNIL
ifCURSOR
is not positioned.Wraps mdb_cursor_get() with MDB_LAST_DUP.
-
[function] CURSOR-NEXT &OPTIONAL CURSOR
Move
CURSOR
to the next key-value pair of its database and return the key, the value, andT
. ReturnNIL
if there is no next item. IfDUPSORT
, positionCURSOR
on the next value of the current key if exists, else the first value of next key. IfCURSOR
is uninitialized, thenCURSOR-FIRST
is called on it first.Wraps mdb_cursor_get() with MDB_NEXT.
-
[function] CURSOR-NEXT-NODUP &OPTIONAL CURSOR
Move
CURSOR
to the first value of next key pair of its database (skipping over duplicate values of the current key). Return the key, the value andT
. ReturnNIL
if there is no next item. IfCURSOR
is uninitialized, thenCURSOR-FIRST
is called on it first.Wraps mdb_cursor_get() with MDB_NEXT_NODUP.
-
[function] CURSOR-NEXT-DUP &OPTIONAL CURSOR
Move
CURSOR
to the next value of current key pair of its database. Return the value andT
. ReturnNIL
if there is no next value. IfCURSOR
is uninitialized, thenCURSOR-FIRST
is called on it first.Wraps mdb_cursor_get() with MDB_NEXT_DUP.
-
[function] CURSOR-PREV &OPTIONAL CURSOR
Move
CURSOR
to the previous key-value pair of its database. Return the key, the value andT
. ReturnNIL
if there is no previous item. IfDUPSORT
, positionCURSOR
on the previous value of the current key if exists, else the last value of previous key. IfCURSOR
is uninitialized, thenCURSOR-LAST
is called on it first.Wraps mdb_cursor_get() with MDB_PREV.
-
[function] CURSOR-PREV-NODUP &OPTIONAL CURSOR
Move
CURSOR
to the last value of previous key pair of its database (skipping over duplicate values of the current and the previous key). Return the key, the value, andT
. ReturnNIL
if there is no prev item. IfCURSOR
is uninitialized, thenCURSOR-LAST
is called on it first.Wraps mdb_cursor_get() with MDB_PREV_NODUP.
-
[function] CURSOR-PREV-DUP &OPTIONAL CURSOR
Move
CURSOR
to the previous duplicate value of current key pair of its database. Return the value andT
. ReturnNIL
if there is no prev value. IfCURSOR
is uninitialized, thenCURSOR-LAST
is called on it first.Wraps mdb_cursor_get() with MDB_PREV_DUP.
-
[function] CURSOR-SET-KEY KEY &OPTIONAL CURSOR
Move
CURSOR
toKEY
of its database. Return the corresponding value andT
. ReturnNIL
ifKEY
was not found. IfDUPSORT
, positionCURSOR
on the first value ofKEY
.Wraps mdb_cursor_get() with MDB_SET_KEY.
-
[function] CURSOR-SET-KEY-DUP KEY VALUE &OPTIONAL CURSOR
Move
CURSOR
to theKEY
,VALUE
pair of its database and returnT
on success. ReturnNIL
if the pair was not found.Wraps mdb_cursor_get() with MDB_GET_BOTH.
-
[function] CURSOR-SET-RANGE KEY &OPTIONAL CURSOR
Position
CURSOR
on the first key equal to or greater thanKEY
. Return the found key, the value andT
. ReturnNIL
ifKEY
was not found. IfDUPSORT
, positionCURSOR
on the first value of the found key.Wraps mdb_cursor_get() with MDB_SET_RANGE.
-
[function] CURSOR-SET-RANGE-DUP KEY VALUE &OPTIONAL CURSOR
Position
CURSOR
exactly atKEY
on the first value greater than or equal toVALUE
. Return the value at the position andT
on success, orNIL
if there is no such value associated withKEY
.Wraps mdb_cursor_get() with MDB_GET_BOTH_RANGE.
The following operations are similar to G3T
, PUT
, DEL
(the
Basic operations), but G3T
has three variants
(CURSOR-KEY-VALUE
, CURSOR-KEY
, and CURSOR-VALUE
). All of them
require the cursor to be positioned (see
Positioning cursors).
-
[function] CURSOR-KEY-VALUE &OPTIONAL CURSOR
Return the key and value
CURSOR
is positioned at andT
. ReturnNIL
ifCURSOR
is uninitialized.Wraps mdb_cursor_get() with MDB_GET_CURRENT.
-
[function] CURSOR-KEY &OPTIONAL CURSOR
Return the key
CURSOR
is positioned at andT
. ReturnNIL
ifCURSOR
is uninitialized.Wraps mdb_cursor_get() with MDB_GET_CURRENT.
-
[function] CURSOR-VALUE &OPTIONAL CURSOR
Return the value
CURSOR
is positioned at andT
. ReturnNIL
ifCURSOR
is uninitialized.Wraps mdb_cursor_get() with MDB_GET_CURRENT.
-
[function] CURSOR-PUT KEY VALUE CURSOR &KEY CURRENT (OVERWRITE T) (DUPDATA T) APPEND APPEND-DUP
Like
PUT
, store key-value pairs intoCURSOR
's database.CURSOR
is positioned at the new item, or on failure usually near it. ReturnVALUE
.-
CURRENT
: Replace the item at the current cursor position.KEY
must still be provided, and must match it. If using sorted duplicates (DUPSORT
),VALUE
must still sort into the same place. This is intended to be used when the new data is the same size as the old. Otherwise it will simply perform a delete of the old record followed by an insert. -
OVERWRITE
: IfNIL
, signalLMDB-KEY-EXISTS-ERROR
ifKEY
already appears inCURSOR-DB
. -
DUPDATA
: IfNIL
, signalLMDB-KEY-EXISTS-ERROR
if theKEY
,VALUE
pair already appears inDB
. Has no effect ifCURSOR-DB
doesn't haveDUPSORT
. -
APPEND
: Append theKEY
,VALUE
pair to the end ofCURSOR-DB
instead of findingKEY
's location in the B+ tree by performing comparisons. The client effectively promises that keys are inserted in sort order, which allows for fast bulk loading. If the promise is broken,LMDB-KEY-EXISTS-ERROR
is signalled. -
APPEND-DUP
: The client promises that duplicate values are inserted in sort order. If the promise is broken,LMDB-KEY-EXISTS-ERROR
is signalled.
May signal
LMDB-MAP-FULL-ERROR
,LMDB-TXN-FULL-ERROR
,LMDB-TXN-READ-ONLY-ERROR
.Wraps mdb_cursor_put().
-
-
[function] CURSOR-DEL CURSOR &KEY DELETE-DUPS
Delete the key-value pair
CURSOR
is positioned at. This does not make the cursor uninitialized, so operations such asCURSOR-NEXT
can still be used on it. BothCURSOR-NEXT
andCURSOR-KEY-VALUE
will return the same record after this operation. IfCURSOR
is not initialized,LMDB-CURSOR-UNINITIALIZED-ERROR
is signalled. Returns no values.If
DELETE-DUPS
, delete all duplicate values that belong to the current key. WithDELETE-DUPS
,CURSOR-DB
must haveDUPSORT
, elseLMDB-INCOMPATIBLE-ERROR
is signalled.May signal
LMDB-CURSOR-UNINITIALIZED-ERROR
,LMDB-TXN-READ-ONLY-ERROR
.Wraps mdb_cursor_del().
-
[function] CURSOR-RENEW &OPTIONAL CURSOR
Associate
CURSOR
with the active transaction (which must be read-only) as if it had been created with that transaction to begin with to avoid allocation overhead.CURSOR-DB
stays the same. This may be done whether the previous transaction is open or closed (seeOPEN-TXN-P
). No values are returned.Wraps mdb_cursor_renew().
-
[function] CURSOR-COUNT &OPTIONAL CURSOR
Return the number of duplicate values for the current key of
CURSOR
. IfCURSOR-DB
doesn't haveDUPSORT
,LMDB-INCOMPATIBLE-ERROR
is signalled. IfCURSOR
is not initialized,LMDB-CURSOR-UNINITIALIZED-ERROR
is signalled.Wraps mdb_cursor_count().
-
[macro] DO-CURSOR (KEY-VAR VALUE-VAR CURSOR &KEY FROM-END NODUP) &BODY BODY
Iterate over key-value pairs starting from the position of
CURSOR
. IfCURSOR
is not positioned then no key-value pairs will be seen. IfFROM-END
, then iterate withCURSOR-PREV
instead ofCURSOR-NEXT
. IfNODUP
, then make thatCURSOR-PREV-NODUP
andCURSOR-NEXT-NODUP
.If
CURSOR
isNIL
, the default cursor is used.If
NODUP
and notFROM-END
, then the first duplicate of each key will be seen. IfNODUP
andFROM-END
, then the last duplicate of each key will be seen.To iterate over all key-value pairs with keys >= 7:
(with-cursor (cursor db) (cursor-set-key 7 cursor) (do-cursor (key value cursor) (print (cons key value))))
-
[macro] DO-CURSOR-DUP (VALUE-VAR CURSOR &KEY FROM-END) &BODY BODY
Iterate over duplicate values with starting from the position of
CURSOR
. IfCURSOR
is not positioned then no values will be seen. IfFROM-END
, then iterate withCURSOR-PREV-DUP
instead ofCURSOR-NEXT-DUP
.If
CURSOR
isNIL
, the default cursor is used.To iterate over all values that not smaller than #(3 4 5), associated with the key 7:
(with-cursor (cursor db) (cursor-set-key-dup cursor 7 #(3 4 5)) (do-cursor-dup (value cursor) (print value)))
-
[macro] DO-DB (KEY-VAR VALUE-VAR DB &KEY FROM-END NODUP) &BODY BODY
Iterate over all keys and values in
DB
. IfNODUP
, then all but the first (or last ifFROM-END
) value for each key are skipped. IfFROM-END
, then iterate in reverse order.To iterate over all values in
DB
:(do-db (key value db) (print (cons key value)))
This macro establishes a default cursor.
-
[macro] DO-DB-DUP (VALUE-VAR DB KEY &KEY FROM-END) &BODY BODY
Iterate over all values associated with
KEY
inDB
. IfFROM-END
, then iteration starts at the largest value.To iterate over all values associated with the key 7:
(do-db-dup (value db 7) (print value))
This macro establishes a default cursor.
-
[function] LIST-DUPS DB KEY &KEY FROM-END
A thin wrapper around
DO-DB-DUP
, this function returns all values associated withKEY
inDB
as a list. IfFROM-END
, then the first element of the list is the largest value.
-
[condition] LMDB-SERIOUS-CONDITION SERIOUS-CONDITION
The base class of all
LMDB
conditions. Conditions that areLMDB-SERIOUS-CONDITION
s, but notLMDB-ERROR
s are corruption and internal errors, which are hard to recover from.
-
[condition] LMDB-ERROR LMDB-SERIOUS-CONDITION ERROR
Base class for normal, recoverable
LMDB
errors.
The following conditions correspond to C lmdb error codes.
-
[condition] LMDB-KEY-EXISTS-ERROR LMDB-ERROR
Key-value pair already exists. Signalled by
PUT
andCURSOR-PUT
.
-
[condition] LMDB-NOT-FOUND-ERROR LMDB-ERROR
Key-value pair does not exist. All functions (
G3T
,CURSOR-NEXT
, ...) should returnNIL
instead of signalling this error. If it is signalled, that's a bug.
-
[condition] LMDB-PAGE-NOT-FOUND-ERROR LMDB-SERIOUS-CONDITION
Requested page not found - this usually indicates corruption.
-
[condition] LMDB-CORRUPTED-ERROR LMDB-SERIOUS-CONDITION
Located page was wrong type.
-
[condition] LMDB-PANIC-ERROR LMDB-SERIOUS-CONDITION
Update of meta page failed or environment had fatal error.
-
[condition] LMDB-VERSION-MISMATCH-ERROR LMDB-ERROR
Environment version mismatch.
-
[condition] LMDB-INVALID-ERROR LMDB-SERIOUS-CONDITION
File is not a valid
LMDB
file.
-
[condition] LMDB-MAP-FULL-ERROR LMDB-ERROR
ENV-MAP-SIZE
reached. Reopen the environment with a larger:MAP-SIZE
.
-
[condition] LMDB-DBS-FULL-ERROR LMDB-ERROR
ENV-MAX-DBS
reached. Reopen the environment with a higher:MAX-DBS
.
-
[condition] LMDB-READERS-FULL-ERROR LMDB-ERROR
ENV-MAX-READERS
reached. Reopen the environment with a higher:MAX-READERS
.
-
[condition] LMDB-TXN-FULL-ERROR LMDB-ERROR
TXN
has too many dirty pages. This condition is expected to occur only when using nested read-write transactions or operations multiple items (currently not supported by this wrapper).
-
[condition] LMDB-CURSOR-FULL-ERROR LMDB-SERIOUS-CONDITION
Cursor stack too deep - internal error.
-
[condition] LMDB-PAGE-FULL-ERROR LMDB-SERIOUS-CONDITION
Page has not enough space - internal error.
-
[condition] LMDB-MAP-RESIZED-ERROR LMDB-ERROR
Data file contents grew beyond
ENV-MAP-SIZE
. This can happen if another OS process using the same environment path set a larger map size than this process did.
-
[condition] LMDB-INCOMPATIBLE-ERROR LMDB-ERROR
Operation and
DB
incompatible, orDB
type changed. This can mean:-
The operation expects a
DUPSORT
orDUPFIXED
database. -
Opening a named
DB
when the unnamedDB
hasDUPSORT
orINTEGER-KEY
. -
Accessing a data record as a database, or vice versa.
-
The database was dropped and recreated with different flags.
-
-
[condition] LMDB-BAD-RSLOT-ERROR LMDB-ERROR
Invalid reuse of reader locktable slot. May be signalled by
WITH-TXN
.
-
[condition] LMDB-BAD-TXN-ERROR LMDB-ERROR
Transaction must abort, has a child, or is invalid. Signalled, for example, when a read-only transaction is nested in a read-write transaction, or when a cursor is used whose transaction has been closed (committed, aborted, or reset).
-
[condition] LMDB-BAD-VALSIZE-ERROR LMDB-ERROR
Unsupported size of key/DB name/data, or wrong
DUPFIXED
,INTEGER-KEY
orINTEGER-DUP
. SeeENV-MAX-KEY-SIZE
.
-
[condition] LMDB-BAD-DBI-ERROR LMDB-ERROR
The specified
DBI
was changed unexpectedly.
The following conditions do not have a dedicated C lmdb error code.
-
[condition] LMDB-CURSOR-UNINITIALIZED-ERROR LMDB-ERROR
Cursor was not initialized. Position the cursor at a key-value pair with a function like
CURSOR-FIRST
orCURSOR-SET-KEY
. Signalled when some functions return the C error codeEINVAL
.
-
[condition] LMDB-CURSOR-THREAD-ERROR LMDB-ERROR
Cursor was accessed from a thread other than the one in which it was created. Since the foreign cursor object's lifetime is tied to the dynamic extent of its
WITH-CURSOR
, this might mean accessing garbage in foreign memory with unpredictable consequences.
-
[condition] LMDB-TXN-READ-ONLY-ERROR LMDB-ERROR
Attempt was made to write in a read-only transaction. Signalled when some functions return the C error code
EACCESS
.
-
[condition] LMDB-ILLEGAL-ACCESS-TO-PARENT-TXN-ERROR LMDB-ERROR
A parent transaction and its cursors may not issue any other operations than
COMMIT-TXN
andABORT-TXN
while it has active child transactions. InLMDB
, Basic operations are always executed in the active transaction, but Cursors can refer to the parent transaction:(with-temporary-env (*env*) (let ((db (get-db "db"))) (with-txn (:write t) (put db #(1) #(1)) (with-cursor (cursor db) (with-txn (:write t) (assert-error lmdb-illegal-access-to-parent-txn-error (cursor-set-key #(1) cursor)))))))