[][src]Crate ledb

Lightweight embedded database


  • Processing documents which implements Serialize and Deserialize traits from serde.
  • Identifying documents using auto-incrementing integer primary keys.
  • Indexing any fields of documents using unique or duplicated keys.
  • Searching and ordering documents using indexed fields or primary key.
  • Selecting documents using complex filters with fields comparing and logical operations.
  • Updating documents using rich set of modifiers.
  • Storing documents into independent storages so called collections.
  • Flexible query! macro which helps write clear and readable queries.
  • Using LMDB as backend for document storage and indexing engine.

Usage example

use serde::{Serialize, Deserialize};
use serde_json::json;
use ledb::{Storage, Options, IndexKind, KeyType, Filter, Comp, Order, OrderKind, Identifier, Primary, Document, query, query_extr};

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Document)]
struct MyDoc {
    // primary key field
    id: Option<Primary>,
    // unique key field
    title: String,
    // key field
    tag: Vec<String>,
    timestamp: u32,

fn main() {
    let db_path = ".test_dbs/my_temp_db";
    let _ = std::fs::remove_dir_all(&db_path);

    // Open storage
    let storage = Storage::new(&db_path, Options::default()).unwrap();

    // Get collection
    let collection = storage.collection("my-docs").unwrap();

    // Ensure indexes
    query!(index for collection
        title str unique,
        tag str,
        timestamp int unique,

    // Ensure indexes using document type
    query!(index MyDoc for collection).unwrap();

    // Insert JSON document
    let first_id = query!(insert into collection {
        "title": "First title",
        "tag": ["some tag", "other tag"],
        "timestamp": 1234567890,

    // Insert typed document
    let second_id = collection.insert(&MyDoc {
        id: None,
        title: "Second title".into(),
        tag: vec![],
        timestamp: 1234567657,

    // Find documents
    let found_docs = query!(
        find MyDoc in collection
        where title == "First title"
    ).unwrap().collect::<Result<Vec<_>, _>>().unwrap();

    // Update documents
    let n_affected = query!(
        update in collection modify title = "Other title"
        where title == "First title"

    // Find documents with descending ordering
    let found_docs = query!(
        find MyDoc in collection order desc
    ).unwrap().collect::<Result<Vec<_>, _>>().unwrap();

    // Remove documents
    let n_affected = query!(
        remove from collection where title == "Other title"

Field names

Field name is a sequence of dot-separated identifiers which represents nesting of value in document. Also fully qualified field names supports wildcard pattern (*).

For example, in document below:

This example is not tested
    "a": "abc",
    "b": {
        "c": 11
    "d": [
    "s": [
      { n: 1 },
      { n: 2 }
    "h": {
      "a": { s: "a" },
      "b": { s: "b" }

You can access fields by the next ways:

This example is not tested
a == "abc"
a += "def"
b.c > 10
b.c += 3
d == "a"
d[-1..] += ["b", "c"]
s.n > 1
h.*.s == "a"


Index query example:

This example is not tested
    index for some_collection
        some_field Int unique, // unique index
        other_field.with.sub_field String,
        wildcarded.*.sub_field Int,
        // ...next fields

Index kinds

Internal TypeJSON TypeDescription
Index"index"The values can be duplicated
Unique"unique"Each value is unique

Unique index guarantee that each value can be stored once, any duplicates disalowed.

The operation will fail in two cases:

  1. When you try to insert new document which duplicate unique field
  2. When you try to ensure unique index for field which have duplicates

Unique fields is pretty fit for sorting.

TODO: Full-text index kind for searching

Key types

Internal TypeJSON TypeDescription
Int"int"64-bit signed integers
Float"float"64-bit floating point numbers
Bool"bool"boolean values
String"string"UTF-8 strings
Binary"binary"raw binary data


Comparison operations

Internal ReprJSON ReprQuery (where)Description
Eq(value){"$eq": value}field == valGeneral Equality
In(Vec){"$in": [...values]}field of [...val]One of
Lt(value){"$lt": value}field < valLess than
Le(value){"$le": value}field <= valLess than or equal
Gt(value){"$gt": value}field > valGreater than
Ge(value){"$ge": value}field >= valGreater than or equal
Bw(a, true, b, true){"$bw": [a, true, b, true]}field in a..bBetween including a b
Bw(a, false, b, false){"$bw": [a, false, b, false]}field <in> a..bBetween excluding a b
Bw(a, true, b, false){"$bw": [a, true, b, false]}field in> a..bBetween incl a excl b
Bw(a, false, b, true){"$bw": [a, false, b, true]}field <in a..bBetween excl a incl b
Has"$has"field ?Has value (not null)

NOTE: To be able to use particular field of document in filters you need create index for it first.

Some examples:

This example is not tested
query!(@filter field == 123)
query!(@filter field.subfield != "abc")
query!(@filter field > 123)
query!(@filter field <= 456)
query!(@filter field of [1, 2, 3])
query!(@filter field in 123..456)   // [123 ... 456]
query!(@filter field <in> 123..456) // (123 ... 456)
query!(@filter field <in 123..456)  // (123 ... 456]
query!(@filter field in> 123..456)  // [123 ... 456)

Logical operations

Internal ReprJSON ReprQuery (where)Description
Not(Box){"$not": filter}! filterFilter is false
And(Vec){"$and": [...filters]}filter && ...filtersAll filters is true
Or(Vec){"$or": [...filters]}filter || ...filtersAny filter is true

NOTE: Be careful with using complex ORs and global NOTs since it may slow down your queries.

Some examples:

This example is not tested
// negate filter condition
query!(@filter ! field == "abc")

// and filter conditions
query!(@filter field > 123 && field <= 456)

// or filter conditions
query!(@filter field <= 123 || field > 456)

Results ordering

Internal ReprJSON ReprQuery (where)Description
Primary(Asc)"$asc">, asc (default)Ascending ordering by primary key
Primary(Desc)"$desc"<, descDescending ordering by primary key
Field(field, Asc){"field": "$asc"}field >, field ascAscending ordering by field
Field(field, Desc){"field": "$desc"}field <, field descDescending ordering by field


This example is not tested
// ascending ordering by primary key
query!(@order >)
query!(@order asc)

// descending ordering by primary key
query!(@order <)
query!(@order desc)

// ascending ordering by field
query!(@order by field >)
query!(@order by field asc)

// descending ordering by other.field
query!(@order by other.field <)
query!(@order by other.field desc)


Internal ReprJSON ReprQuery (where)Description
Set(value){"$set": value}field = valueSet field value
Delete"$delete"field ~Delete field
Add(value){"$add": value}field += valueAdd value to field
Sub(value){"$sub": value}field -= valueSubstract value from field
Mul(value){"$mul": value}field *= valueMultiply field to value
Div(value){"$div": value}field /= valueDivide field to value
Toggle"$toggle"field !Toggle boolean field
Replace(pat, sub){"$replace": ["pat", "sub"]}field ~= "pat" "sub"Replace using regexp
Splice(from, to, Vec){"$splice": [from, to]}field[from..to] ~Remove from an array
Splice(from, to, Vec){"$splice": [from, to, ...ins]}field[from..to] = insSplice an array
Merge(object){"$merge": object}field ~= objectMerge an object

The negative range value means position from end of an array:

  • -1 the end of an array
  • -2 the last element
  • -3 the element before the last
  • ...and so on

Extended behavior of modifiers

Internal ReprJSON ReprQuery (where)Description
Add(values){"$add": [...values]}field += [..values]Add unique values to an array as a set
Sub(values){"$sub": [...values]}field -= [..values]Remove unique values to an array as a set
Add(text){"$add": "text"}field += "text"Append text to a string


This example is not tested
// set single fields
query!(@modify field = 123)
query!(@modify other.field = "abc")

// set multiple fields
    field = 1;
    other.field = "abc";

// numeric operations
query!(@modify field += 1) // add value to field
query!(@modify field -= 1) // substract value from field
query!(@modify field *= 1) // multiply field to value
query!(@modify field /= 1) // divide field to value

query!(@modify - field) // remove field
query!(@modify ! field) // toggle boolean field

query!(@modify str += "addon") // append piece to string
query!(@modify str ~= "abc" "def") // regexp replace

// modify array as list
query!(@modify list[0..0] = [1, 2, 3]) // prepend to array
query!(@modify list[-1..0] = [1, 2, 3]) // append to array
query!(@modify - list[1..2]) // remove from array
query!(@modify list[1..2] = [1, 2, 3]) // splice array

// modify array as set
query!(@modify set += [1, 2, 3]) // add elements
query!(@modify set -= [4, 5, 6]) // remove elements

// merge an object
query!(@modify obj ~= { a: true, b: "abc", c: 123 })
query!(@modify obj ~= extra)


pub use ledb_types as types;



Unified query macro



Collection of documents


Iterator across found documents


Storage info data


Indexed field definition


Indexed fields definition


Modification operator


Database options


Raw document with id representation


Storage stats data


Storage of documents




Modifier action


Comparison operator of filter


Condition operator of filter


Database error type


Filter operator


Generic string indentifier


The kind of index


The data of key


The type of key


Ordering operator


The kind ot order


The Value enum, a loosely typed way of representing any valid CBOR value.



Identified document representation


The helper for converting results with different error types into generic result



Type Definitions


Primary key (document identifier)


Database result type

Derive Macros