
High-performance library for OpenLDAP's LMDB, offering type-safe API, direct native bindings, ACID transaction support, memory efficiency, and custom sorting logic for key-value storage.
A high-performance, cross-platform Kotlin library for OpenLDAP's Lightning Memory-Mapped Database (LMDB).
kotlin-lmdb provides a type-safe, idiomatic Kotlin API for LMDB, one of the fastest and most reliable key-value stores available. This library enables direct integration with LMDB across JVM and Native platforms from a single, consistent codebase.
kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation("io.github.crowded-libs:kotlin-lmdb:0.3.6")
}
}
}
}<dependency>
<groupId>io.github.crowded-libs</groupId>
<artifactId>kotlin-lmdb</artifactId>
<version>0.3.6</version>
</dependency>// Create and open an environment
val env = Env()
env.open("/path/to/database")
// Perform database operations within a transaction
env.beginTxn { txn ->
val dbi = txn.dbiOpen()
// Write data
txn.put(dbi, "key1".encodeToByteArray(), "value1".encodeToByteArray())
// Read data
val (resultCode, k, v) = txn.get(dbi, "key1".encodeToByteArray())
if (resultCode == 0) {
val value = v.toByteArray()!!.decodeToString()
println(value) // Outputs: value1
}
txn.commit()
}
// Always close the environment when done
env.close()LMDB supports storing multiple values for a single key using DupSort:
// Create environment and open database with dupsort flag
val env = Env()
env.open("/path/to/database")
env.beginTxn { txn ->
// Open database with DupSort option enabled
val dbi = txn.dbiOpen(null, DbiOption.DupSort, DbiOption.Create)
// Store multiple values for the same key
val key = "category".encodeToByteArray()
txn.put(dbi, key, "item1".encodeToByteArray())
txn.put(dbi, key, "item2".encodeToByteArray())
txn.put(dbi, key, "item3".encodeToByteArray())
// Retrieve all values for a key using a cursor
val cursor = txn.openCursor(dbi)
cursor.use {
// Position cursor at the first value for this key
val result = cursor.set(key)
if (result.first == 0) {
// Process the first value
println("Value: ${result.third.toByteArray()!!.decodeToString()}")
// Iterate through remaining values for this key
var moreValues = true
while (moreValues) {
val nextResult = cursor.nextDuplicate()
if (nextResult.first == 0) {
println("Value: ${nextResult.third.toByteArray()!!.decodeToString()}")
} else {
moreValues = false
}
}
}
}
txn.commit()
}
env.close()The library comes with several pre-built comparers that you can use without writing custom code:
val env = Env()
env.open("/path/to/database")
env.beginTxn { txn ->
// Use pre-built INTEGER_KEY comparer for numeric sorting
val config = DbiConfig(keyComparer = ValComparer.INTEGER_KEY)
val intDb = txn.dbiOpen("integer-keys", config, DbiOption.Create)
// Now keys will be sorted as integers
txn.put(intDb, byteArrayOf(10), "value-10".encodeToByteArray())
txn.put(intDb, byteArrayOf(5), "value-5".encodeToByteArray())
txn.put(intDb, byteArrayOf(20), "value-20".encodeToByteArray())
// When iterating, they will be in order: 5, 10, 20
// Open a different database with lexicographic string sorting
val stringConfig = DbiConfig(keyComparer = ValComparer.LEXICOGRAPHIC_STRING)
val stringDb = txn.dbiOpen("string-keys", stringConfig, DbiOption.Create)
txn.commit()
}
env.close()You can also define your own custom sorting logic for keys and values:
// Register a custom first-byte comparator
val firstByteComparer: ValCompare = { a, b ->
val aBytes = a.toByteArray() ?: byteArrayOf()
val bBytes = b.toByteArray() ?: byteArrayOf()
val aFirstByte = if (aBytes.isNotEmpty()) aBytes[0].toInt() else 0
val bFirstByte = if (bBytes.isNotEmpty()) bBytes[0].toInt() else 0
aFirstByte - bFirstByte
}
// Clear any existing custom comparers
clearCustomComparers()
// Register the custom comparer
registerCustomComparer(ValComparer.CUSTOM_1, firstByteComparer)
val env = Env()
env.open("/path/to/database")
env.beginTxn { txn ->
// Create database config that uses the custom comparer
val config = DbiConfig(keyComparer = ValComparer.CUSTOM_1)
val dbi = txn.dbiOpen("custom-comparer-db", config, DbiOption.Create)
// Now keys will be sorted by their first byte
// Add some test data
txn.put(dbi, "abc".encodeToByteArray(), "value1".encodeToByteArray())
txn.put(dbi, "bcd".encodeToByteArray(), "value2".encodeToByteArray())
txn.put(dbi, "zde".encodeToByteArray(), "value3".encodeToByteArray())
txn.commit()
}
env.close()The library provides these pre-built comparers through the ValComparer enum:
BITWISE - Default byte-by-byte comparison (ascending)REVERSE_BITWISE - Byte-by-byte comparison in reverse orderLEXICOGRAPHIC_STRING - Compare as UTF-8 strings (ascending)REVERSE_LEXICOGRAPHIC_STRING - Compare as UTF-8 strings (descending)INTEGER_KEY - Compare as integers (ascending)REVERSE_INTEGER_KEY - Compare as integers (descending)LENGTH - Compare by length first, then by contentREVERSE_LENGTH - Compare by length (longer first), then by contentLENGTH_ONLY - Compare only by length, ignoring contentHASH_CODE - Compare by hash code (for faster large value comparisons)The library includes pre-built native libraries for all common Android architectures:
No additional configuration is required - the native libraries are automatically included and loaded when you add the dependency.
The wasmJs target provides LMDB functionality in web browsers and Node.js environments, but has some differences from other platforms:
Implementation Details:
Behavioral Differences:
MDB_WRITEMAP flag is always enabled when opening environments, regardless of whether it's explicitly specifiedMAP_SHARED without write accessLimitations:
Usage Notes:
Required Gradle Plugin for wasmJs:
The kotlin-lmdb-wasm Gradle plugin is required when using the wasmJs target. This plugin automatically extracts the necessary WASM runtime files (lmdb-wrapper.mjs, lmdb.mjs, and lmdb.wasm) from the kotlin-lmdb dependency and places them in the correct locations for your wasmJs compilation.
Why the plugin is needed:
@WasmImport annotations that require runtime files to be available as relative importskotlin-lmdb-wasm-js artifact and copies them to both main and test WASM output directoriesSetup in your project's build.gradle.kts:
plugins {
alias(libs.plugins.kotlinMultiplatform)
// Add this plugin when using wasmJs target
id("io.github.crowded-libs.kotlin-lmdb-wasm") version "0.3.6"
}
kotlin {
wasmJs {
// your wasmJs configuration
}
sourceSets {
commonMain.dependencies {
implementation("io.github.crowded-libs:kotlin-lmdb:0.3.6")
}
}
}A high-performance, cross-platform Kotlin library for OpenLDAP's Lightning Memory-Mapped Database (LMDB).
kotlin-lmdb provides a type-safe, idiomatic Kotlin API for LMDB, one of the fastest and most reliable key-value stores available. This library enables direct integration with LMDB across JVM and Native platforms from a single, consistent codebase.
kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation("io.github.crowded-libs:kotlin-lmdb:0.3.6")
}
}
}
}<dependency>
<groupId>io.github.crowded-libs</groupId>
<artifactId>kotlin-lmdb</artifactId>
<version>0.3.6</version>
</dependency>// Create and open an environment
val env = Env()
env.open("/path/to/database")
// Perform database operations within a transaction
env.beginTxn { txn ->
val dbi = txn.dbiOpen()
// Write data
txn.put(dbi, "key1".encodeToByteArray(), "value1".encodeToByteArray())
// Read data
val (resultCode, k, v) = txn.get(dbi, "key1".encodeToByteArray())
if (resultCode == 0) {
val value = v.toByteArray()!!.decodeToString()
println(value) // Outputs: value1
}
txn.commit()
}
// Always close the environment when done
env.close()LMDB supports storing multiple values for a single key using DupSort:
// Create environment and open database with dupsort flag
val env = Env()
env.open("/path/to/database")
env.beginTxn { txn ->
// Open database with DupSort option enabled
val dbi = txn.dbiOpen(null, DbiOption.DupSort, DbiOption.Create)
// Store multiple values for the same key
val key = "category".encodeToByteArray()
txn.put(dbi, key, "item1".encodeToByteArray())
txn.put(dbi, key, "item2".encodeToByteArray())
txn.put(dbi, key, "item3".encodeToByteArray())
// Retrieve all values for a key using a cursor
val cursor = txn.openCursor(dbi)
cursor.use {
// Position cursor at the first value for this key
val result = cursor.set(key)
if (result.first == 0) {
// Process the first value
println("Value: ${result.third.toByteArray()!!.decodeToString()}")
// Iterate through remaining values for this key
var moreValues = true
while (moreValues) {
val nextResult = cursor.nextDuplicate()
if (nextResult.first == 0) {
println("Value: ${nextResult.third.toByteArray()!!.decodeToString()}")
} else {
moreValues = false
}
}
}
}
txn.commit()
}
env.close()The library comes with several pre-built comparers that you can use without writing custom code:
val env = Env()
env.open("/path/to/database")
env.beginTxn { txn ->
// Use pre-built INTEGER_KEY comparer for numeric sorting
val config = DbiConfig(keyComparer = ValComparer.INTEGER_KEY)
val intDb = txn.dbiOpen("integer-keys", config, DbiOption.Create)
// Now keys will be sorted as integers
txn.put(intDb, byteArrayOf(10), "value-10".encodeToByteArray())
txn.put(intDb, byteArrayOf(5), "value-5".encodeToByteArray())
txn.put(intDb, byteArrayOf(20), "value-20".encodeToByteArray())
// When iterating, they will be in order: 5, 10, 20
// Open a different database with lexicographic string sorting
val stringConfig = DbiConfig(keyComparer = ValComparer.LEXICOGRAPHIC_STRING)
val stringDb = txn.dbiOpen("string-keys", stringConfig, DbiOption.Create)
txn.commit()
}
env.close()You can also define your own custom sorting logic for keys and values:
// Register a custom first-byte comparator
val firstByteComparer: ValCompare = { a, b ->
val aBytes = a.toByteArray() ?: byteArrayOf()
val bBytes = b.toByteArray() ?: byteArrayOf()
val aFirstByte = if (aBytes.isNotEmpty()) aBytes[0].toInt() else 0
val bFirstByte = if (bBytes.isNotEmpty()) bBytes[0].toInt() else 0
aFirstByte - bFirstByte
}
// Clear any existing custom comparers
clearCustomComparers()
// Register the custom comparer
registerCustomComparer(ValComparer.CUSTOM_1, firstByteComparer)
val env = Env()
env.open("/path/to/database")
env.beginTxn { txn ->
// Create database config that uses the custom comparer
val config = DbiConfig(keyComparer = ValComparer.CUSTOM_1)
val dbi = txn.dbiOpen("custom-comparer-db", config, DbiOption.Create)
// Now keys will be sorted by their first byte
// Add some test data
txn.put(dbi, "abc".encodeToByteArray(), "value1".encodeToByteArray())
txn.put(dbi, "bcd".encodeToByteArray(), "value2".encodeToByteArray())
txn.put(dbi, "zde".encodeToByteArray(), "value3".encodeToByteArray())
txn.commit()
}
env.close()The library provides these pre-built comparers through the ValComparer enum:
BITWISE - Default byte-by-byte comparison (ascending)REVERSE_BITWISE - Byte-by-byte comparison in reverse orderLEXICOGRAPHIC_STRING - Compare as UTF-8 strings (ascending)REVERSE_LEXICOGRAPHIC_STRING - Compare as UTF-8 strings (descending)INTEGER_KEY - Compare as integers (ascending)REVERSE_INTEGER_KEY - Compare as integers (descending)LENGTH - Compare by length first, then by contentREVERSE_LENGTH - Compare by length (longer first), then by contentLENGTH_ONLY - Compare only by length, ignoring contentHASH_CODE - Compare by hash code (for faster large value comparisons)The library includes pre-built native libraries for all common Android architectures:
No additional configuration is required - the native libraries are automatically included and loaded when you add the dependency.
The wasmJs target provides LMDB functionality in web browsers and Node.js environments, but has some differences from other platforms:
Implementation Details:
Behavioral Differences:
MDB_WRITEMAP flag is always enabled when opening environments, regardless of whether it's explicitly specifiedMAP_SHARED without write accessLimitations:
Usage Notes:
Required Gradle Plugin for wasmJs:
The kotlin-lmdb-wasm Gradle plugin is required when using the wasmJs target. This plugin automatically extracts the necessary WASM runtime files (lmdb-wrapper.mjs, lmdb.mjs, and lmdb.wasm) from the kotlin-lmdb dependency and places them in the correct locations for your wasmJs compilation.
Why the plugin is needed:
@WasmImport annotations that require runtime files to be available as relative importskotlin-lmdb-wasm-js artifact and copies them to both main and test WASM output directoriesSetup in your project's build.gradle.kts:
plugins {
alias(libs.plugins.kotlinMultiplatform)
// Add this plugin when using wasmJs target
id("io.github.crowded-libs.kotlin-lmdb-wasm") version "0.3.6"
}
kotlin {
wasmJs {
// your wasmJs configuration
}
sourceSets {
commonMain.dependencies {
implementation("io.github.crowded-libs:kotlin-lmdb:0.3.6")
}
}
}