LeanLmdb (lean_lmdb v0.1.0)

View Source

A lean, binary-first Elixir wrapper for LMDB.

Environments are shared by canonical path within one BEAM OS process. Opaque environment and immutable named-database handles may live across calls; transactions and cursors never do. Keys and values are raw binaries and read results are copied into BEAM-owned binaries.

Example

iex> {name, version} = LeanLmdb.native_version()
iex> {name, Version.parse!(version).major}
{"lean_lmdb", 0}

Summary

Types

An ordered operation accepted by write_batch/2.

The expected state accepted by compare_exchange/4.

The replacement accepted by compare_exchange/4.

Validated options accepted by open/2.

A stable public error reason.

An option accepted by scan/2.

Functions

Checks the LMDB reader table and clears slots left by terminated processes.

Atomically compares a key's current state and applies a replacement on match.

Creates a named binary database, or reopens it when it already exists.

Deletes key if present.

Gets the binary value for key.

Returns deterministic lifecycle information for an environment or database.

Returns the tuple {"lean_lmdb", version} without accessing LMDB or the filesystem.

Opens an LMDB environment, sharing native state for its canonical path.

Opens an existing named binary database.

Stores value under key, overwriting any prior value.

Returns process-local registry counts and prunes stale weak entries.

Returns one bounded page of binary key/value pairs in ascending key order.

Applies an ordered list of puts and deletes atomically across named databases.

Types

batch_operation()

@type batch_operation() ::
  {:put, LeanLmdb.Database.t(), binary(), binary()}
  | {:delete, LeanLmdb.Database.t(), binary()}

An ordered operation accepted by write_batch/2.

compare_expected()

@type compare_expected() :: :missing | binary()

The expected state accepted by compare_exchange/4.

compare_replacement()

@type compare_replacement() :: :delete | {:put, binary()}

The replacement accepted by compare_exchange/4.

open_option()

@type open_option() ::
  {:map_size, pos_integer()}
  | {:max_dbs, pos_integer()}
  | {:max_readers, pos_integer()}
  | {:read_only, boolean()}
  | {:create, boolean()}

Validated options accepted by open/2.

reason()

@type reason() ::
  :database_create_failed
  | :database_not_found
  | :database_open_failed
  | :environment_open_failed
  | :incompatible_environment_options
  | :invalid_database
  | :invalid_database_name
  | :invalid_environment
  | :invalid_key
  | :invalid_value
  | :io_error
  | :database_error
  | :map_full
  | :map_resized
  | :readers_full
  | :transaction_full
  | :out_of_memory
  | :invalid_environment_options
  | :invalid_batch
  | :batch_too_large
  | :mixed_environments
  | :invalid_expected
  | :invalid_replacement
  | :invalid_mode
  | :invalid_limit
  | :invalid_max_bytes
  | :row_too_large
  | :invalid_continuation
  | :stale_continuation
  | :invalid_path
  | :path_not_found
  | :read_only
  | :resource_id_exhausted
  | :transaction_failed
  | {:invalid_option, atom()}

A stable public error reason.

scan_option()

@type scan_option() ::
  {:prefix, binary()}
  | {:from, binary() | nil}
  | {:to, binary() | nil}
  | {:from_inclusive, boolean()}
  | {:to_inclusive, boolean()}
  | {:limit, 1..10000}
  | {:max_bytes, 1..67_108_864}
  | {:continuation, binary() | nil}

An option accepted by scan/2.

Functions

clear_stale_readers(arg1)

@spec clear_stale_readers(LeanLmdb.Environment.t()) ::
  {:ok, non_neg_integer()} | {:error, reason()}

Checks the LMDB reader table and clears slots left by terminated processes.

Returns {:ok, cleared_count}. Active readers are never cleared. This is backed by heed's Env::clear_stale_readers and may be called while the environment is in use. Invalid handles and native failures return {:error, reason}.

compare_exchange(database, key, expected, replacement)

@spec compare_exchange(
  LeanLmdb.Database.t(),
  binary(),
  compare_expected(),
  compare_replacement()
) :: :ok | {:conflict, :missing | binary()} | {:error, reason()}

Atomically compares a key's current state and applies a replacement on match.

:missing is distinct from an expected empty binary. A mismatch returns the exact current state as {:conflict, :missing} or {:conflict, binary} and never mutates the database. Missing deletes after a successful comparison are idempotent. A match returns :ok; malformed arguments and native failures return {:error, reason}. Conflict binaries are copied before return.

create_database(environment, name)

@spec create_database(LeanLmdb.Environment.t(), binary()) ::
  {:ok, LeanLmdb.Database.t()} | {:error, reason()}

Creates a named binary database, or reopens it when it already exists.

Names must contain 1..511 bytes of valid UTF-8 and may not contain NUL. Returns {:ok, database} or {:error, reason}; read-only environments cannot create databases. The immutable database handle retains its environment.

delete(database, key)

@spec delete(LeanLmdb.Database.t(), binary()) :: :ok | {:error, reason()}

Deletes key if present.

Deletion is idempotent: both present and missing keys return :ok. Validation, read-only, and LMDB failures return {:error, reason}.

get(database, key)

@spec get(LeanLmdb.Database.t(), binary()) ::
  {:ok, binary()} | :not_found | {:error, reason()}

Gets the binary value for key.

Returns {:ok, value} when present and :not_found when missing. Empty values are therefore distinct from missing keys. Keys must be non-empty binaries no larger than LMDB's runtime maximum key size. Native read failures return {:error, reason}. The value is copied and no transaction or zero-copy view escapes the call.

info(arg1)

@spec info(LeanLmdb.Environment.t() | LeanLmdb.Database.t()) ::
  map() | {:error, :invalid_environment}

Returns deterministic lifecycle information for an environment or database.

Environment maps contain :resource_id, canonical :path, :map_size, :max_dbs, :max_readers, and :read_only; database maps additionally contain :name. Invalid handles return {:error, :invalid_environment}.

native_version()

@spec native_version() :: {String.t(), String.t()}

Returns the tuple {"lean_lmdb", version} without accessing LMDB or the filesystem.

open(path, options \\ [])

@spec open(Path.t(), [open_option()]) ::
  {:ok, LeanLmdb.Environment.t()} | {:error, reason()}

Opens an LMDB environment, sharing native state for its canonical path.

The path is canonicalized after optional creation, so relative components and symlinks resolve to the same identity. Defaults are a 64 MiB map, 16 named databases, 126 readers, writable access, and create: true.

map_size must be between 1 MiB and 1 TiB and divisible by the operating system page size; max_dbs is limited to 1..1024 and max_readers to 1..4096. A read-only environment cannot be created. Repeats must use the same effective map size, limits, and read mode; create only controls path/open existence behavior.

Returns {:ok, environment} or {:error, reason}. Invalid paths/options, missing paths, incompatible repeated opens, resource-ID exhaustion, and native open failures are reported as stable reasons. The map never grows automatically. Only LMDB's default synchronized durability flags are used.

open_database(environment, name)

@spec open_database(LeanLmdb.Environment.t(), binary()) ::
  {:ok, LeanLmdb.Database.t()} | {:error, reason()}

Opens an existing named binary database.

Names follow the same UTF-8, length, and NUL restrictions as create_database/2. Returns {:ok, database}, {:error, :database_not_found}, or another documented lifecycle error.

put(database, key, value)

@spec put(LeanLmdb.Database.t(), binary(), binary()) :: :ok | {:error, reason()}

Stores value under key, overwriting any prior value.

Keys and values are used as raw binaries; no serialization is performed. Returns :ok after commit or {:error, reason} without a partial commit.

registry_info()

@spec registry_info() :: %{
  live: non_neg_integer(),
  stale: non_neg_integer(),
  total: non_neg_integer()
}

Returns process-local registry counts and prunes stale weak entries.

This exposes counts only; it never reveals native pointers. It is intended for deterministic lifecycle diagnostics and tests. The returned map always has non-negative :live, :stale, and :total counts.

scan(database, options \\ [])

@spec scan(LeanLmdb.Database.t(), [scan_option()]) ::
  {:ok, [{binary(), binary()}], :done | binary()} | {:error, reason()}

Returns one bounded page of binary key/value pairs in ascending key order.

A scan is either a prefix scan (prefix: binary) or an explicit range scan. Range endpoints default to nil; the lower endpoint is inclusive and the upper endpoint exclusive by default. Prefix cannot be mixed with from, to, or the inclusivity options. Prefixes and endpoints are at most 511 bytes. limit defaults to 100 (maximum 10,000) and max_bytes defaults to 1 MiB (maximum 64 MiB), counting key plus value bytes.

The third result element is :done or an opaque continuation binary. Pass the continuation with the same database and bounds to resume exclusively after the last emitted key. Page limits may change while resuming. If the first eligible row exceeds max_bytes, the call returns {:error, :row_too_large} and no rows, making the output byte budget a hard bound. Invalid options/tokens and native read failures also return {:error, reason}. Pages do not share a snapshot and all row/token bytes are copied before the call returns.

write_batch(environment, operations)

@spec write_batch(LeanLmdb.Environment.t(), [batch_operation()]) ::
  :ok | {:error, reason()}

Applies an ordered list of puts and deletes atomically across named databases.

Every database must belong to exactly the supplied environment. The complete list is validated before one write transaction is opened, operations are applied in list order, and missing deletes are idempotent. Empty batches are valid no-ops. A batch is limited to 10,000 operations and 64 MiB of aggregate key and value bytes. Returns :ok only after commit; validation, mixed environments, read-only access, and LMDB failures return {:error, reason} with the transaction aborted.