Records API
The Records API manages structured Memory objects with metadata, timestamps, and embeddings.
Memory Type
#![allow(unused)] fn main() { pub struct Memory { pub id: String, pub content: String, pub embedding: Vec<f32>, pub importance: f64, pub timestamp: i64, pub metadata: HashMap<String, String>, } }
Creating Memories
New Memory
#![allow(unused)] fn main() { use opendb::{OpenDB, Memory}; let memory = Memory::new( "mem_001".to_string(), "User asked about Rust ownership".to_string(), ); }
With Metadata
#![allow(unused)] fn main() { let memory = Memory::new("mem_002".to_string(), "Content".to_string()) .with_metadata("category", "conversation") .with_metadata("user_id", "123"); }
Custom Builder
#![allow(unused)] fn main() { use std::collections::HashMap; let mut metadata = HashMap::new(); metadata.insert("priority".to_string(), "high".to_string()); let memory = Memory { id: "mem_003".to_string(), content: "Important note".to_string(), embedding: vec![0.1, 0.2, 0.3], // 3D for demo importance: 0.95, timestamp: chrono::Utc::now().timestamp(), metadata, }; }
CRUD Operations
Insert
#![allow(unused)] fn main() { let db = OpenDB::open("./db")?; let memory = Memory::new("mem_001".to_string(), "Hello world".to_string()); db.insert_memory(&memory)?; }
Signature:
#![allow(unused)] fn main() { pub fn insert_memory(&self, memory: &Memory) -> Result<()> }
Behavior:
- Serializes with
rkyv(zero-copy) - Writes to
recordscolumn family - Updates cache
- If embedding is non-empty, stores in vector index (requires rebuild for search)
Get
#![allow(unused)] fn main() { let memory = db.get_memory("mem_001")?; match memory { Some(mem) => println!("Content: {}", mem.content), None => println!("Not found"), } }
Signature:
#![allow(unused)] fn main() { pub fn get_memory(&self, id: &str) -> Result<Option<Memory>> }
Behavior:
- Checks cache first
- Deserializes from storage on cache miss
- Returns
Noneif not found
Update
#![allow(unused)] fn main() { let mut memory = db.get_memory("mem_001")?.unwrap(); memory.content = "Updated content".to_string(); memory.importance = 0.9; memory.touch(); // Update timestamp db.insert_memory(&memory)?; // Upsert }
Note: insert_memory() acts as upsert (update if exists, insert if not).
Delete
#![allow(unused)] fn main() { db.delete_memory("mem_001")?; }
Signature:
#![allow(unused)] fn main() { pub fn delete_memory(&self, id: &str) -> Result<()> }
Behavior:
- Removes from storage
- Invalidates cache
- Does not remove from vector index (requires rebuild)
- Does not remove graph edges (handle separately)
Listing Operations
List All IDs
#![allow(unused)] fn main() { let ids = db.list_memory_ids()?; for id in ids { println!("Memory ID: {}", id); } }
Signature:
#![allow(unused)] fn main() { pub fn list_memory_ids(&self) -> Result<Vec<String>> }
List All Memories
#![allow(unused)] fn main() { let memories = db.list_memories()?; for memory in memories { println!("{}: {}", memory.id, memory.content); } }
Signature:
#![allow(unused)] fn main() { pub fn list_memories(&self) -> Result<Vec<Memory>> }
Warning: Loads all memories into memory. For large datasets, use pagination (not yet implemented) or filter by prefix.
Advanced Usage
Importance Filtering
#![allow(unused)] fn main() { let memories = db.list_memories()?; let important: Vec<_> = memories.into_iter() .filter(|m| m.importance > 0.8) .collect(); }
Metadata Queries
#![allow(unused)] fn main() { let memories = db.list_memories()?; let category_matches: Vec<_> = memories.into_iter() .filter(|m| { m.metadata.get("category") .map(|v| v == "conversation") .unwrap_or(false) }) .collect(); }
Time Range Queries
#![allow(unused)] fn main() { use chrono::{Utc, Duration}; let one_hour_ago = (Utc::now() - Duration::hours(1)).timestamp(); let recent: Vec<_> = db.list_memories()?.into_iter() .filter(|m| m.timestamp > one_hour_ago) .collect(); }
Embeddings
Setting Embeddings
Embeddings enable semantic search:
#![allow(unused)] fn main() { let embedding = generate_embedding("Hello world"); // Your embedding model let memory = Memory { id: "mem_001".to_string(), content: "Hello world".to_string(), embedding, // Vec<f32> ..Default::default() }; db.insert_memory(&memory)?; }
Dimension Requirements
All embeddings must have the same dimension (default 384):
#![allow(unused)] fn main() { use opendb::OpenDBOptions; let mut options = OpenDBOptions::default(); options.vector_dimension = 768; // For larger models let db = OpenDB::open_with_options("./db", options)?; }
Searching Embeddings
See Vector API for semantic search.
Touch Timestamp
Update access time without modifying content:
#![allow(unused)] fn main() { let mut memory = db.get_memory("mem_001")?.unwrap(); memory.touch(); // Sets timestamp to now db.insert_memory(&memory)?; }
Default Values
#![allow(unused)] fn main() { impl Default for Memory { fn default() -> Self { Self { id: String::new(), content: String::new(), embedding: Vec::new(), importance: 0.5, timestamp: chrono::Utc::now().timestamp(), metadata: HashMap::new(), } } } }
Performance Tips
- Batch Inserts: Use transactions for multiple inserts:
#![allow(unused)] fn main() { let mut txn = db.begin_transaction()?; for memory in memories { // Insert via transaction (lower-level API needed) } txn.commit()?; }
- Cache Warm-Up: Preload frequently accessed memories:
#![allow(unused)] fn main() { for id in important_ids { db.get_memory(id)?; // Populate cache } }
- Lazy Embedding Generation: Only generate embeddings when needed for search:
#![allow(unused)] fn main() { let memory = Memory::new(id, content); // Don't set embedding unless search is required db.insert_memory(&memory)?; }
Error Handling
#![allow(unused)] fn main() { use opendb::Error; match db.get_memory("mem_001") { Ok(Some(memory)) => { /* use memory */ }, Ok(None) => { /* not found */ }, Err(Error::Codec(_)) => { /* deserialization error */ }, Err(Error::Storage(_)) => { /* storage error */ }, Err(e) => { /* other error */ }, } }