Provides a key/value store with MVCC concurrency: readers never block writers, and reads are zero-copy via memory-mapped B+ trees. All opaque handles are ADDRESS-typed so Modula-2 never touches LMDB's internal structs. The C bridge constructs MDB_val from (ADDRESS, LONGCARD) pairs, keeping the FFI boundary clean. Requires liblmdb at link time (-llmdb).
- macOS:
brew install lmdb - Linux:
apt install liblmdb-dev(or equivalent)
- Env (ADDRESS) -- Opaque environment handle (
MDB_env*). One per data directory. - Txn (ADDRESS) -- Opaque transaction handle (
MDB_txn*). Read-only or read-write. - Dbi (CARDINAL) -- Named database handle within an environment.
- Cursor (ADDRESS) -- Opaque cursor handle (
MDB_cursor*) for ordered iteration. - Status --
LmOk,LmKeyExist,LmNotFound,LmMapFull,LmTxnFull,LmError.
| Constant | Value | Description |
|---|---|---|
FlagCreate |
040000H | Create named database if it doesn't exist |
FlagRdOnly |
020000H | Read-only transaction |
FlagNoTls |
0200000H | Don't use thread-local reader slots |
FlagNoOverwrite |
010H | Don't overwrite existing key (returns LmKeyExist) |
FlagNoSubDir |
04000H | Path is a file, not a directory |
FlagNoMetaSync |
040000H | Skip meta-page fsync on commit |
FlagNoSync |
010000H | No fsync at all -- call EnvSync manually |
FlagWriteMap |
080000H | Use writable mmap instead of write() |
FlagMapAsync |
0100000H | Async msync with FlagWriteMap |
| Constant | Value | Description |
|---|---|---|
CurFirst |
0 | Position at first key |
CurGetCurrent |
4 | Return current key/value without moving |
CurLast |
6 | Position at last key |
CurNext |
8 | Move to next key |
CurNextDup |
9 | Move to next duplicate of current key |
CurNextNoDup |
11 | Move to first entry of next key |
CurPrev |
12 | Move to previous key |
CurPrevNoDup |
14 | Move to last entry of previous key |
CurSet |
15 | Position at exact key (returns LmNotFound if absent) |
CurSetRange |
17 | Position at key >= given key (prefix scanning) |
-
PROCEDURE EnvCreate(VAR env: Env): StatusAllocate a new environment handle. Must be configured (map size, max DBs) before opening. -
PROCEDURE EnvOpen(env: Env; path: ARRAY OF CHAR; flags: CARDINAL; mode: CARDINAL): StatusOpen the environment at the given directory path.modeis the Unix file permission (e.g. 0644). Common flags:FlagNoSyncfor high-throughput writes with manualEnvSync. -
PROCEDURE EnvClose(env: Env)Close the environment and release resources. -
PROCEDURE EnvSetMapSize(env: Env; sizeBytes: LONGCARD): StatusSet the memory map size in bytes. Must be called beforeEnvOpen. This is the maximum database size. -
PROCEDURE EnvSetMaxDbs(env: Env; count: CARDINAL): StatusSet the maximum number of named databases. Must be called beforeEnvOpen. -
PROCEDURE EnvSetMaxReaders(env: Env; count: CARDINAL): StatusSet the maximum number of concurrent reader slots. Must be called beforeEnvOpen. -
PROCEDURE EnvSync(env: Env; force: BOOLEAN): StatusFlush dirty pages to disk. Required when usingFlagNoSyncto ensure durability. Withforce = TRUE, a synchronous flush is performed.
-
PROCEDURE TxnBegin(env: Env; parent: Txn; flags: CARDINAL; VAR txn: Txn): StatusBegin a transaction. PassNILforparentfor a top-level transaction. UseFlagRdOnlyfor read-only transactions (concurrent, lock-free). Only one write transaction may be active at a time. -
PROCEDURE TxnCommit(txn: Txn): StatusCommit the transaction. Thetxnhandle is invalidated. -
PROCEDURE TxnAbort(txn: Txn)Abort the transaction. Thetxnhandle is invalidated. -
PROCEDURE TxnReset(txn: Txn)Reset a read-only transaction for reuse. Releases read locks but keeps the handle. -
PROCEDURE TxnRenew(txn: Txn): StatusRenew a read-only transaction afterTxnReset. Cheaper thanTxnBegin.
PROCEDURE DbiOpen(txn: Txn; name: ARRAY OF CHAR; flags: CARDINAL; VAR dbi: Dbi): StatusOpen a named database within the environment. UseFlagCreateto create it if it doesn't exist. The returneddbihandle is valid for the lifetime of the environment.
-
PROCEDURE Get(txn: Txn; dbi: Dbi; key: ADDRESS; keyLen: LONGCARD; VAR valPtr: ADDRESS; VAR valLen: LONGCARD): StatusRetrieve a value by key. On success,valPtrpoints directly into the memory map -- valid until the transaction ends. Do not free it. -
PROCEDURE Put(txn: Txn; dbi: Dbi; key: ADDRESS; keyLen: LONGCARD; val: ADDRESS; valLen: LONGCARD; flags: CARDINAL): StatusStore a key/value pair. WithFlagNoOverwrite, returnsLmKeyExistif the key already exists (useful for idempotent inserts). -
PROCEDURE Del(txn: Txn; dbi: Dbi; key: ADDRESS; keyLen: LONGCARD): StatusDelete a key. ReturnsLmNotFoundif the key doesn't exist.
-
PROCEDURE CursorOpen(txn: Txn; dbi: Dbi; VAR cur: Cursor): StatusOpen a cursor on a database for ordered iteration. -
PROCEDURE CursorClose(cur: Cursor)Close the cursor. -
PROCEDURE CursorGet(cur: Cursor; op: CARDINAL; VAR keyPtr: ADDRESS; VAR keyLen: LONGCARD; VAR valPtr: ADDRESS; VAR valLen: LONGCARD): StatusPosition the cursor usingopand return the key and value at the new position. ReturnsLmNotFoundwhen no more entries. -
PROCEDURE CursorSeek(cur: Cursor; op: CARDINAL; key: ADDRESS; keyLen: LONGCARD; VAR valPtr: ADDRESS; VAR valLen: LONGCARD): StatusPosition the cursor at or after the given key. Commonly used withCurSetRangefor prefix scanning. Returns the value at the positioned entry. -
PROCEDURE CursorPut(cur: Cursor; key: ADDRESS; keyLen: LONGCARD; val: ADDRESS; valLen: LONGCARD; flags: CARDINAL): StatusWrite a key/value pair via the cursor. Same flags asPut.
PROCEDURE ErrMsg(code: INTEGER; VAR buf: ARRAY OF CHAR)Write a human-readable error description intobuf.
MODULE LmdbDemo;
FROM SYSTEM IMPORT ADDRESS, ADR, TSIZE;
FROM InOut IMPORT WriteString, WriteLn;
FROM Lmdb IMPORT
Env, Txn, Dbi, Cursor, Status,
EnvCreate, EnvOpen, EnvClose, EnvSetMapSize, EnvSetMaxDbs,
TxnBegin, TxnCommit, TxnAbort,
DbiOpen, Get, Put, CursorOpen, CursorClose, CursorGet,
LmOk, LmNotFound, FlagCreate, FlagRdOnly, FlagNoOverwrite,
CurFirst, CurNext;
VAR
env: Env;
txn: Txn;
dbi: Dbi;
cur: Cursor;
s: Status;
valPtr, keyPtr: ADDRESS;
valLen, keyLen: LONGCARD;
key: ARRAY [0..31] OF CHAR;
val: ARRAY [0..63] OF CHAR;
buf: ARRAY [0..63] OF CHAR;
BEGIN
(* Create and open environment *)
s := EnvCreate(env);
s := EnvSetMapSize(env, 10485760); (* 10 MB *)
s := EnvSetMaxDbs(env, 4);
s := EnvOpen(env, "./testdb", 0, 644);
(* Write some key/value pairs *)
s := TxnBegin(env, NIL, 0, txn);
s := DbiOpen(txn, "users", FlagCreate, dbi);
key := "alice";
val := "Alice Smith";
s := Put(txn, dbi, ADR(key), 6, ADR(val), 12, 0);
key := "bob";
val := "Bob Jones";
s := Put(txn, dbi, ADR(key), 4, ADR(val), 10, 0);
s := TxnCommit(txn);
(* Read back a single key *)
s := TxnBegin(env, NIL, FlagRdOnly, txn);
key := "alice";
s := Get(txn, dbi, ADR(key), 6, valPtr, valLen);
IF s = LmOk THEN
WriteString("alice = ");
(* valPtr points into the mmap -- valid until TxnAbort *)
WriteLn
END;
(* Iterate all keys with a cursor *)
s := CursorOpen(txn, dbi, cur);
s := CursorGet(cur, CurFirst, keyPtr, keyLen, valPtr, valLen);
WHILE s = LmOk DO
WriteString("key found"); WriteLn;
s := CursorGet(cur, CurNext, keyPtr, keyLen, valPtr, valLen)
END;
CursorClose(cur);
TxnAbort(txn);
EnvClose(env)
END LmdbDemo.
LMDB uses MVCC (Multi-Version Concurrency Control):
- Multiple concurrent readers -- read-only transactions never block each other or writers. No locks, no contention.
- Single writer -- only one write transaction may be active at a time. A second
TxnBeginwithoutFlagRdOnlywill block until the first commits or aborts. - Readers don't block writers -- a write transaction can commit while read transactions are active. Readers see a consistent snapshot from when their transaction began.
This makes LMDB ideal for multi-threaded servers: worker threads acquire read-only transactions freely, while writes are serialized through a single writer (or a batching layer).
- Zero-copy reads:
GetandCursorGetreturn pointers directly into the memory-mapped file. No data is copied. The pointer is valid until the transaction ends. - Sorted keys: Keys are stored in lexicographic byte order. Use big-endian encoding for integer keys to maintain correct sort order.
- Named databases: A single environment can hold multiple named databases (up to
EnvSetMaxDbs). Each is an independent B+ tree with its own key space. FlagNoOverwrite: ReturnsLmKeyExistinstead of overwriting. This is the LMDB equivalent of SQL'sINSERT OR IGNORE-- check the return code to detect duplicates without a separate existence check.FlagNoSync: Disables fsync on commit for maximum write throughput. CallEnvSyncon a timer to control the durability/performance tradeoff.
Lmdb.def High-level Modula-2 API (types, procedures)
|
Lmdb.mod Maps Status codes, handles ARRAY OF CHAR → ADDRESS
|
LmdbBridge.def FOR "C" declarations (flat ADDRESS-based FFI)
|
lmdb_bridge.c C wrapper: constructs MDB_val structs, maps return codes
|
liblmdb System LMDB library (memory-mapped B+ tree engine)
The bridge layer exists because LMDB's MDB_val is a C struct ({size_t mv_size; void *mv_data}) that cannot be directly constructed from Modula-2. The bridge takes (ADDRESS, LONGCARD) pairs and builds the struct in C.