Skip to main content
The DatabaseCollection trait represents a collection of key-value pairs in a TAPLE database. It provides the core operations for storing, retrieving, and iterating over data.

Trait Definition

pub trait DatabaseCollection: Sync + Send {
    fn get(&self, key: &str) -> Result<Vec<u8>, Error>;
    fn put(&self, key: &str, data: Vec<u8>) -> Result<(), Error>;
    fn del(&self, key: &str) -> Result<(), Error>;
    fn iter<'a>(
        &'a self,
        reverse: bool,
        prefix: String,
    ) -> Box<dyn Iterator<Item = (String, Vec<u8>)> + 'a>;
}

Required Trait Bounds

Implementations must be:
  • Sync - Safe to share references between threads
  • Send - Safe to transfer ownership between threads

Methods

get

fn get(&self, key: &str) -> Result<Vec<u8>, Error>
Retrieves the value associated with the given key.
key
&str
required
The key to look up in the collection
Returns:
  • Ok(Vec<u8>) - The value associated with the key
  • Err(Error::EntryNotFound) - If the key does not exist
Example:
let value = collection.get("my-key")?;
let decoded: MyData = borsh::from_slice(&value)?;

put

fn put(&self, key: &str, data: Vec<u8>) -> Result<(), Error>
Stores a value with the given key. If the key already exists, its value is overwritten.
key
&str
required
The key to store the data under
data
Vec<u8>
required
The binary data to store
Returns:
  • Ok(()) - On successful storage
  • Err(Error) - If the operation fails
Example:
let data = MyData { id: 1, value: "example".into() };
let bytes = borsh::to_vec(&data)?;
collection.put("my-key", bytes)?;

del

fn del(&self, key: &str) -> Result<(), Error>
Removes the entry associated with the given key.
key
&str
required
The key to delete from the collection
Returns:
  • Ok(()) - On successful deletion (even if key didn’t exist)
  • Err(Error) - If the operation fails
Example:
collection.del("my-key")?;

iter

fn iter<'a>(
    &'a self,
    reverse: bool,
    prefix: String,
) -> Box<dyn Iterator<Item = (String, Vec<u8>)> + 'a>
Returns an iterator over key-value pairs in the collection, optionally filtered by prefix.
reverse
bool
required
If true, iterates in reverse order; if false, iterates forward
prefix
String
required
Only return keys that start with this prefix. Use an empty string to iterate all keys
Returns: A boxed iterator yielding (String, Vec<u8>) tuples of keys and values Example:
// Forward iteration over all keys
for (key, value) in collection.iter(false, String::new()) {
    println!("Key: {}", key);
}

// Reverse iteration with prefix filter
for (key, value) in collection.iter(true, "subject_".to_string()) {
    // Only processes keys starting with "subject_"
}

Implementation Example

use taple_core::database::{DatabaseCollection, Error};
use std::sync::RwLock;
use std::collections::BTreeMap;

pub struct MyCollection {
    data: RwLock<BTreeMap<String, Vec<u8>>>,
}

impl DatabaseCollection for MyCollection {
    fn get(&self, key: &str) -> Result<Vec<u8>, Error> {
        let lock = self.data.read().unwrap();
        lock.get(key)
            .cloned()
            .ok_or(Error::EntryNotFound)
    }

    fn put(&self, key: &str, data: Vec<u8>) -> Result<(), Error> {
        let mut lock = self.data.write().unwrap();
        lock.insert(key.to_string(), data);
        Ok(())
    }

    fn del(&self, key: &str) -> Result<(), Error> {
        let mut lock = self.data.write().unwrap();
        lock.remove(key);
        Ok(())
    }

    fn iter<'a>(
        &'a self,
        reverse: bool,
        prefix: String,
    ) -> Box<dyn Iterator<Item = (String, Vec<u8>)> + 'a> {
        // Implementation details depend on your storage backend
        // See MemoryCollection for a complete example
        unimplemented!()
    }
}

Data Serialization

TAPLE uses Borsh for serialization. When implementing custom storage:
use borsh::{BorshSerialize, BorshDeserialize};

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

// Serialize before storing
let data = MyData { id: 1, name: "example".into() };
let bytes = data.try_to_vec()
    .map_err(|_| Error::SerializeError)?;
collection.put("key", bytes)?;

// Deserialize after retrieving
let bytes = collection.get("key")?;
let data = MyData::try_from_slice(&bytes)
    .map_err(|_| Error::DeserializeError)?;

Thread Safety

All implementations must be thread-safe. Use appropriate synchronization primitives:
  • RwLock for read-heavy workloads
  • Mutex for simpler locking needs
  • Arc for shared ownership across threads

Build docs developers (and LLMs) love