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.
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.
The key to store the data under
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.
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.
If true, iterates in reverse order; if false, iterates forward
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