mindb 0.1.2

Lightweight embedded key–value store with write-ahead log and zstd compression.
Documentation
use std::error::Error;
use std::sync::Arc;

use mindb::db::{Database, DatabaseOptions, SecondaryIndexConfig};
use mindb::query::FilterRequest;
use serde_json::{Value, json};

#[test]
fn database_facade_query_helpers() -> Result<(), Box<dyn Error>> {
    let tempdir = tempfile::tempdir()?;

    let mut options = DatabaseOptions::new(tempdir.path());
    options.wal_direct_io = false;
    options.secondary_indexes = SecondaryIndexConfig {
        slug: Some(Arc::new(|_, value: &[u8]| {
            serde_json::from_slice::<Value>(value).ok().and_then(|doc| {
                doc.get("slug")
                    .and_then(|slug| slug.as_str().map(|s| s.to_string()))
            })
        })),
        timestamp: Some(Arc::new(|_, value: &[u8]| {
            serde_json::from_slice::<Value>(value)
                .ok()
                .and_then(|doc| doc.get("timestamp").and_then(|ts| ts.as_i64()))
        })),
        roaring_tags: Some(Arc::new(|_, value: &[u8]| {
            serde_json::from_slice::<Value>(value)
                .ok()
                .and_then(|doc| {
                    doc.get("tags").and_then(|tags| {
                        tags.as_array().map(|entries| {
                            entries
                                .iter()
                                .filter_map(|entry| entry.as_str().map(|s| s.to_string()))
                                .collect::<Vec<_>>()
                        })
                    })
                })
                .unwrap_or_default()
        })),
        json_paths: Some(Arc::new(|_, value: &[u8]| {
            let mut fields = Vec::new();
            if let Ok(doc) = serde_json::from_slice::<Value>(value) {
                if let Some(kind) = doc.get("kind") {
                    fields.push(("kind".to_string(), kind.clone()));
                }
                if let Some(active) = doc.get("active") {
                    fields.push(("active".to_string(), active.clone()));
                }
            }
            fields
        })),
        full_text: Some(Arc::new(|_, value: &[u8]| {
            serde_json::from_slice::<Value>(value).ok().and_then(|doc| {
                doc.get("content")
                    .and_then(|text| text.as_str().map(|s| s.to_string()))
            })
        })),
    };

    let db = Database::open(options)?;

    let doc_one = json!({
        "slug": "doc-1",
        "timestamp": 10,
        "tags": ["red", "shared"],
        "kind": "alpha",
        "active": true,
        "content": "alpha document body",
    });
    let doc_two = json!({
        "slug": "doc-2",
        "timestamp": 20,
        "tags": ["blue"],
        "kind": "beta",
        "active": false,
        "content": "beta entry",
    });

    let doc_one_bytes = serde_json::to_vec(&doc_one)?;
    let doc_two_bytes = serde_json::to_vec(&doc_two)?;

    db.put(b"doc-1".to_vec(), doc_one_bytes.clone())?;
    db.put(b"doc-2".to_vec(), doc_two_bytes.clone())?;

    // Slug lookup should return the associated value.
    let slug_lookup = db.get_by_slug("doc-2")?;
    assert_eq!(slug_lookup.as_deref(), Some(doc_two_bytes.as_slice()));

    // Prefix scan should include both documents inserted above.
    let mut prefix_results = db.scan_prefix(b"doc-")?.collect(&db)?;
    prefix_results.sort_by(|a, b| a.0.cmp(&b.0));
    assert_eq!(
        prefix_results,
        vec![
            (b"doc-1".to_vec(), doc_one_bytes.clone()),
            (b"doc-2".to_vec(), doc_two_bytes.clone()),
        ]
    );

    // Time fence scan should return the document inside the requested window.
    let time_results = db.scan_time(15, 25)?.collect(&db)?;
    assert_eq!(
        time_results,
        vec![(b"doc-2".to_vec(), doc_two_bytes.clone())]
    );

    // Composite filters should intersect bitmap and JSON predicates.
    let required_tags = vec!["shared".to_string()];
    let kind_alpha = json!("alpha");
    let filter_request = FilterRequest {
        bitmap_tags: &required_tags,
        json_equals: &[("kind", &kind_alpha)],
    };
    let filter_results = db.filter(&filter_request)?.collect(&db)?;
    assert_eq!(
        filter_results,
        vec![(b"doc-1".to_vec(), doc_one_bytes.clone())]
    );

    // Full-text search should find the matching document.
    let search_results = db.search("beta")?.collect(&db)?;
    assert_eq!(
        search_results,
        vec![(b"doc-2".to_vec(), doc_two_bytes.clone())]
    );

    Ok(())
}