Skip to main content
The MemoryManager and MemoryCollection provide a simple, thread-safe in-memory database implementation. This is ideal for testing, development, and scenarios where persistence is not required.

MemoryManager

Structure

pub struct MemoryManager {
    data: RwLock<HashMap<String, Arc<DataStore>>>,
}
The MemoryManager maintains a thread-safe map of data stores, allowing multiple collections to share the same underlying storage.

Implementation

impl DatabaseManager<MemoryCollection> for MemoryManager {
    fn default() -> Self {
        Self {
            data: RwLock::new(HashMap::new()),
        }
    }

    fn create_collection(&self, _identifier: &str) -> MemoryCollection {
        let mut lock = self.data.write().unwrap();
        let db: Arc<DataStore> = match lock.get("") {
            Some(map) => map.clone(),
            None => {
                let db: Arc<DataStore> = Arc::new(DataStore::new());
                lock.insert("".to_string(), db.clone());
                db
            }
        };
        MemoryCollection { data: db }
    }
}

Constructor

new

impl MemoryManager {
    pub fn new() -> Self
}
Creates a new in-memory database manager. Example:
use taple_core::database::MemoryManager;

let manager = MemoryManager::new();

MemoryCollection

Structure

pub struct MemoryCollection {
    data: Arc<DataStore>,
}
A collection backed by an in-memory BTreeMap with thread-safe access through RwLock.

Methods

Implements all DatabaseCollection trait methods:

get

fn get(&self, key: &str) -> Result<Vec<u8>, Error>
Retrieves a value by key from the in-memory store. Example:
let value = collection.get("my-key")?;

put

fn put(&self, key: &str, data: Vec<u8>) -> Result<(), Error>
Stores a key-value pair in memory. Example:
collection.put("my-key", vec![1, 2, 3])?;

del

fn del(&self, key: &str) -> Result<(), Error>
Removes a key-value pair from memory. Example:
collection.del("my-key")?;

iter

fn iter<'a>(
    &'a self,
    reverse: bool,
    prefix: String,
) -> Box<dyn Iterator<Item = (String, Vec<u8>)> + 'a>
Iterates over entries, optionally in reverse and filtered by prefix. Example:
// Forward iteration
for (key, value) in collection.iter(false, String::new()) {
    println!("Key: {}", key);
}

// Reverse iteration with prefix
for (key, value) in collection.iter(true, "prefix_".to_string()) {
    println!("Key: {}", key);
}

Complete Usage Example

use taple_core::database::{
    DatabaseManager, DatabaseCollection, 
    MemoryManager, MemoryCollection
};
use borsh::{BorshSerialize, BorshDeserialize};

#[derive(BorshSerialize, BorshDeserialize)]
struct User {
    id: u64,
    name: String,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create manager and collection
    let manager = MemoryManager::new();
    let users: MemoryCollection = manager.create_collection("users");

    // Store data
    let user = User { 
        id: 1, 
        name: "Alice".to_string() 
    };
    let data = user.try_to_vec()?;
    users.put("user:1", data)?;

    // Retrieve data
    let retrieved = users.get("user:1")?;
    let user: User = User::try_from_slice(&retrieved)?;
    println!("User: {} - {}", user.id, user.name);

    // Iterate over all users
    for (key, value) in users.iter(false, "user:".to_string()) {
        let user: User = User::try_from_slice(&value)?;
        println!("Found: {} - {}", key, user.name);
    }

    // Delete data
    users.del("user:1")?;

    Ok(())
}

Thread Safety

The in-memory implementation uses:
  • RwLock for read-write synchronization
  • Arc for shared ownership across threads
  • BTreeMap for ordered key storage
This allows safe concurrent access:
use std::sync::Arc;
use std::thread;

let manager = Arc::new(MemoryManager::new());
let collection = manager.create_collection("shared");

// Spawn multiple threads
let handles: Vec<_> = (0..10)
    .map(|i| {
        let coll = collection.clone();
        thread::spawn(move || {
            coll.put(&format!("key{}", i), vec![i]).unwrap();
        })
    })
    .collect();

// Wait for completion
for handle in handles {
    handle.join().unwrap();
}

Internal Implementation Details

DataStore

The underlying storage structure:
struct DataStore {
    data: RwLock<BTreeMap<String, Vec<u8>>>,
}
Uses BTreeMap to maintain ordered keys, enabling efficient prefix-based iteration.

Iterator Types

Two specialized iterator types handle forward and reverse iteration:
  • MemoryIterator - Forward iteration with prefix filtering
  • RevMemoryIterator - Reverse iteration with prefix filtering
Both iterators:
  1. Acquire a read lock on the data
  2. Filter entries by prefix
  3. Strip the prefix from returned keys
  4. Yield (String, Vec<u8>) tuples

Testing

The implementation includes comprehensive tests:
use taple_core::test_database_manager_trait;

test_database_manager_trait! {
    unit_test_memory_manager: MemoryManager: MemoryCollection
}
This validates:
  • Basic CRUD operations
  • Collection behavior
  • Iterator functionality (forward and reverse)
  • Prefix filtering

Use Cases

Unit Testing

Perfect for testing TAPLE nodes without persistence overhead

Development

Quick prototyping and development without database setup

Ephemeral Nodes

Temporary nodes that don’t need to persist state

Integration Tests

Clean state for each test run

Limitations

The in-memory implementation:
  • Does not persist data to disk
  • Loses all data when the process terminates
  • Memory usage grows with stored data
  • Not suitable for production use
For production deployments, implement a persistent database backend using the DatabaseManager and DatabaseCollection traits.

Build docs developers (and LLMs) love