From ad29dbfd521ebb991646ad817015456bf40c52ca Mon Sep 17 00:00:00 2001 From: Timur Gordon <31495328+timurgordon@users.noreply.github.com> Date: Wed, 9 Apr 2025 11:51:56 +0200 Subject: [PATCH] create architecture spec and initial docs for radixtree --- radixtree/ARCHITECTURE.md | 787 ++++++++++++++++++++++++++++++++++++++ radixtree/MIGRATION.md | 265 +++++++++++++ radixtree/README.md | 148 +++++++ 3 files changed, 1200 insertions(+) create mode 100644 radixtree/ARCHITECTURE.md create mode 100644 radixtree/MIGRATION.md create mode 100644 radixtree/README.md diff --git a/radixtree/ARCHITECTURE.md b/radixtree/ARCHITECTURE.md new file mode 100644 index 0000000..381dd59 --- /dev/null +++ b/radixtree/ARCHITECTURE.md @@ -0,0 +1,787 @@ +# RadixTree: Architecture for V to Rust Port + +## 1. Overview + +RadixTree is a space-optimized tree data structure that enables efficient string key operations with persistent storage. This document outlines the architecture for porting the RadixTree module from its original V implementation to Rust, maintaining all existing functionality while leveraging Rust's memory safety, performance, and ecosystem. + +The Rust implementation will integrate with the existing OurDB Rust implementation for persistent storage. + +```mermaid +graph TD + A[Client Code] --> B[RadixTree API] + B --> C[Node Management] + B --> D[Serialization] + B --> E[Tree Operations] + C --> F[OurDB] + D --> F + E --> C +``` + +## 2. Current Architecture (V Implementation) + +The current V implementation of RadixTree consists of the following components: + +### 2.1 Core Data Structures + +#### Node +```v +struct Node { +mut: + key_segment string // The segment of the key stored at this node + value []u8 // Value stored at this node (empty if not a leaf) + children []NodeRef // References to child nodes + is_leaf bool // Whether this node is a leaf node +} +``` + +#### NodeRef +```v +struct NodeRef { +mut: + key_part string // The key segment for this child + node_id u32 // Database ID of the node +} +``` + +#### RadixTree +```v +@[heap] +pub struct RadixTree { +mut: + db &ourdb.OurDB // Database for persistent storage + root_id u32 // Database ID of the root node +} +``` + +### 2.2 Key Operations + +1. **new()**: Creates a new radix tree with a specified database path +2. **set(key, value)**: Sets a key-value pair in the tree +3. **get(key)**: Retrieves a value by key +4. **update(prefix, new_value)**: Updates the value at a given key prefix +5. **delete(key)**: Removes a key from the tree +6. **list(prefix)**: Lists all keys with a given prefix +7. **getall(prefix)**: Gets all values for keys with a given prefix + +### 2.3 Serialization + +The V implementation uses a custom binary serialization format for nodes: +- Version byte (1 byte) +- Key segment (string) +- Value length (2 bytes) followed by value bytes +- Children count (2 bytes) followed by children +- Is leaf flag (1 byte) + +Each child is serialized as: +- Key part (string) +- Node ID (4 bytes) + +### 2.4 Integration with OurDB + +The RadixTree uses OurDB for persistent storage: +- Each node is serialized and stored as a record in OurDB +- Node references use OurDB record IDs +- The tree maintains a root node ID for traversal + +## 3. Proposed Rust Architecture + +The Rust implementation will maintain the same overall architecture while leveraging Rust's type system, ownership model, and error handling. + +### 3.1 Core Data Structures + +#### Node +```rust +pub struct Node { + key_segment: String, + value: Vec, + children: Vec, + is_leaf: bool, +} +``` + +#### NodeRef +```rust +pub struct NodeRef { + key_part: String, + node_id: u32, +} +``` + +#### RadixTree +```rust +pub struct RadixTree { + db: ourdb::OurDB, + root_id: u32, +} +``` + +### 3.2 Public API + +```rust +impl RadixTree { + /// Creates a new radix tree with the specified database path + pub fn new(path: &str, reset: bool) -> Result { + // Implementation + } + + /// Sets a key-value pair in the tree + pub fn set(&mut self, key: &str, value: Vec) -> Result<(), Error> { + // Implementation + } + + /// Gets a value by key from the tree + pub fn get(&mut self, key: &str) -> Result, Error> { + // Implementation + } + + /// Updates the value at a given key prefix + pub fn update(&mut self, prefix: &str, new_value: Vec) -> Result<(), Error> { + // Implementation + } + + /// Deletes a key from the tree + pub fn delete(&mut self, key: &str) -> Result<(), Error> { + // Implementation + } + + /// Lists all keys with a given prefix + pub fn list(&mut self, prefix: &str) -> Result, Error> { + // Implementation + } + + /// Gets all values for keys with a given prefix + pub fn getall(&mut self, prefix: &str) -> Result>, Error> { + // Implementation + } +} +``` + +### 3.3 Error Handling + +```rust +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("OurDB error: {0}")] + OurDB(#[from] ourdb::Error), + + #[error("Key not found: {0}")] + KeyNotFound(String), + + #[error("Prefix not found: {0}")] + PrefixNotFound(String), + + #[error("Serialization error: {0}")] + Serialization(String), + + #[error("Deserialization error: {0}")] + Deserialization(String), + + #[error("Invalid operation: {0}")] + InvalidOperation(String), +} +``` + +### 3.4 Serialization + +The Rust implementation will maintain the same binary serialization format for compatibility: + +```rust +const VERSION: u8 = 1; + +impl Node { + /// Serializes a node to bytes for storage + fn serialize(&self) -> Vec { + // Implementation + } + + /// Deserializes bytes to a node + fn deserialize(data: &[u8]) -> Result { + // Implementation + } +} +``` + +### 3.5 Integration with OurDB + +The Rust implementation will use the existing OurDB Rust implementation: + +```rust +impl RadixTree { + fn get_node(&mut self, node_id: u32) -> Result { + let data = self.db.get(node_id)?; + Node::deserialize(&data) + } + + fn save_node(&mut self, node_id: Option, node: &Node) -> Result { + let data = node.serialize(); + let args = ourdb::OurDBSetArgs { + id: node_id, + data: &data, + }; + Ok(self.db.set(args)?) + } +} +``` + +## 4. Implementation Strategy + +### 4.1 Phase 1: Core Data Structures and Serialization + +1. Implement the `Node` and `NodeRef` structs +2. Implement serialization and deserialization functions +3. Implement the `Error` enum for error handling + +### 4.2 Phase 2: Basic Tree Operations + +1. Implement the `RadixTree` struct with OurDB integration +2. Implement the `new()` function for creating a new tree +3. Implement the `get()` and `set()` functions for basic operations + +### 4.3 Phase 3: Advanced Tree Operations + +1. Implement the `delete()` function for removing keys +2. Implement the `update()` function for updating values +3. Implement the `list()` and `getall()` functions for prefix operations + +### 4.4 Phase 4: Testing and Optimization + +1. Port existing tests from V to Rust +2. Add new tests for Rust-specific functionality +3. Benchmark and optimize performance +4. Ensure compatibility with existing RadixTree data + +## 5. Implementation Considerations + +### 5.1 Memory Management + +Leverage Rust's ownership model for safe and efficient memory management: +- Use `String` and `Vec` for data buffers instead of raw pointers +- Use references and borrows to avoid unnecessary copying +- Implement proper RAII for resource management + +### 5.2 Error Handling + +Use Rust's `Result` type for comprehensive error handling: +- Define custom error types for RadixTree-specific errors +- Propagate errors using the `?` operator +- Provide detailed error messages +- Implement proper error conversion using the `From` trait + +### 5.3 Performance Optimizations + +Identify opportunities for performance improvements: +- Use efficient string operations for prefix matching +- Minimize database operations by caching nodes when appropriate +- Use iterators for efficient traversal +- Consider using `Cow` for string operations to avoid unnecessary cloning + +### 5.4 Compatibility + +Ensure compatibility with the V implementation: +- Maintain the same serialization format +- Ensure identical behavior for all operations +- Support reading existing RadixTree data + +## 6. Testing Strategy + +### 6.1 Unit Tests + +Write comprehensive unit tests for each component: +- Test `Node` serialization/deserialization +- Test string operations (common prefix, etc.) +- Test error handling + +### 6.2 Integration Tests + +Write integration tests for the complete system: +- Test basic CRUD operations +- Test prefix operations +- Test edge cases (empty keys, very long keys, etc.) +- Test with large datasets + +### 6.3 Compatibility Tests + +Ensure compatibility with existing RadixTree data: +- Test reading existing V-created RadixTree data +- Test writing data that can be read by the V implementation + +### 6.4 Performance Tests + +Benchmark performance against the V implementation: +- Measure throughput for set/get operations +- Measure latency for different operations +- Test with different tree sizes and key distributions + +## 7. Project Structure + +``` +radixtree/ +├── Cargo.toml +├── src/ +│ ├── lib.rs # Public API and re-exports +│ ├── node.rs # Node and NodeRef implementations +│ ├── serialize.rs # Serialization and deserialization +│ ├── error.rs # Error types +│ └── operations.rs # Tree operations implementation +├── tests/ +│ ├── basic_test.rs # Basic operations tests +│ ├── prefix_test.rs # Prefix operations tests +│ └── edge_cases.rs # Edge case tests +└── examples/ + ├── basic.rs # Basic usage example + ├── prefix.rs # Prefix operations example + └── performance.rs # Performance benchmark +``` + +## 8. Dependencies + +The Rust implementation will use the following dependencies: + +- `ourdb` for persistent storage +- `thiserror` for error handling +- `log` for logging +- `criterion` for benchmarking (dev dependency) + +## 9. Compatibility Considerations + +To ensure compatibility with the V implementation: + +1. Maintain the same serialization format for nodes +2. Ensure identical behavior for all operations +3. Support reading existing RadixTree data +4. Maintain the same performance characteristics + +## 10. Future Extensions + +Potential future extensions to consider: + +1. Async API for non-blocking operations +2. Iterator interface for efficient traversal +3. Batch operations for improved performance +4. Custom serialization formats for specific use cases +5. Compression support for values +6. Concurrency support for parallel operations + +## 11. Conclusion + +This architecture provides a roadmap for porting RadixTree from V to Rust while maintaining compatibility and leveraging Rust's strengths. The implementation will follow a phased approach, starting with core data structures and gradually building up to the complete system. + +The Rust implementation aims to be: +- **Safe**: Leveraging Rust's ownership model for memory safety +- **Fast**: Maintaining or improving performance compared to V +- **Compatible**: Working with existing RadixTree data +- **Extensible**: Providing a foundation for future enhancements +- **Well-tested**: Including comprehensive test coverage + +## 12. Implementation Files + +### 12.1 Cargo.toml + +```toml +[package] +name = "radixtree" +version = "0.1.0" +edition = "2021" +description = "A persistent radix tree implementation using OurDB for storage" +authors = ["OurWorld Team"] + +[dependencies] +ourdb = { path = "../ourdb" } +thiserror = "1.0.40" +log = "0.4.17" + +[dev-dependencies] +criterion = "0.5.1" + +[[bench]] +name = "radixtree_benchmarks" +harness = false + +[[example]] +name = "basic_usage" +path = "examples/basic_usage.rs" + +[[example]] +name = "prefix_operations" +path = "examples/prefix_operations.rs" +``` + +### 12.2 src/lib.rs + +```rust +//! RadixTree is a space-optimized tree data structure that enables efficient string key operations +//! with persistent storage using OurDB as a backend. +//! +//! This implementation provides a persistent radix tree that can be used for efficient +//! prefix-based key operations, such as auto-complete, routing tables, and more. + +mod error; +mod node; +mod operations; +mod serialize; + +pub use error::Error; +pub use node::{Node, NodeRef}; + +use ourdb::{OurDB, OurDBConfig, OurDBSetArgs}; +use std::path::PathBuf; + +/// RadixTree represents a radix tree data structure with persistent storage. +pub struct RadixTree { + db: OurDB, + root_id: u32, +} + +impl RadixTree { + /// Creates a new radix tree with the specified database path. + /// + /// # Arguments + /// + /// * `path` - The path to the database directory + /// * `reset` - Whether to reset the database if it exists + /// + /// # Returns + /// + /// A new `RadixTree` instance + /// + /// # Errors + /// + /// Returns an error if the database cannot be created or opened + pub fn new(path: &str, reset: bool) -> Result { + // Implementation will go here + unimplemented!() + } + + /// Sets a key-value pair in the tree. + /// + /// # Arguments + /// + /// * `key` - The key to set + /// * `value` - The value to set + /// + /// # Errors + /// + /// Returns an error if the operation fails + pub fn set(&mut self, key: &str, value: Vec) -> Result<(), Error> { + // Implementation will go here + unimplemented!() + } + + /// Gets a value by key from the tree. + /// + /// # Arguments + /// + /// * `key` - The key to get + /// + /// # Returns + /// + /// The value associated with the key + /// + /// # Errors + /// + /// Returns an error if the key is not found or the operation fails + pub fn get(&mut self, key: &str) -> Result, Error> { + // Implementation will go here + unimplemented!() + } + + /// Updates the value at a given key prefix. + /// + /// # Arguments + /// + /// * `prefix` - The key prefix to update + /// * `new_value` - The new value to set + /// + /// # Errors + /// + /// Returns an error if the prefix is not found or the operation fails + pub fn update(&mut self, prefix: &str, new_value: Vec) -> Result<(), Error> { + // Implementation will go here + unimplemented!() + } + + /// Deletes a key from the tree. + /// + /// # Arguments + /// + /// * `key` - The key to delete + /// + /// # Errors + /// + /// Returns an error if the key is not found or the operation fails + pub fn delete(&mut self, key: &str) -> Result<(), Error> { + // Implementation will go here + unimplemented!() + } + + /// Lists all keys with a given prefix. + /// + /// # Arguments + /// + /// * `prefix` - The prefix to search for + /// + /// # Returns + /// + /// A list of keys that start with the given prefix + /// + /// # Errors + /// + /// Returns an error if the operation fails + pub fn list(&mut self, prefix: &str) -> Result, Error> { + // Implementation will go here + unimplemented!() + } + + /// Gets all values for keys with a given prefix. + /// + /// # Arguments + /// + /// * `prefix` - The prefix to search for + /// + /// # Returns + /// + /// A list of values for keys that start with the given prefix + /// + /// # Errors + /// + /// Returns an error if the operation fails + pub fn getall(&mut self, prefix: &str) -> Result>, Error> { + // Implementation will go here + unimplemented!() + } +} +``` + +### 12.3 src/error.rs + +```rust +//! Error types for the RadixTree module. + +use thiserror::Error; + +/// Error type for RadixTree operations. +#[derive(Debug, Error)] +pub enum Error { + /// Error from OurDB operations. + #[error("OurDB error: {0}")] + OurDB(#[from] ourdb::Error), + + /// Error when a key is not found. + #[error("Key not found: {0}")] + KeyNotFound(String), + + /// Error when a prefix is not found. + #[error("Prefix not found: {0}")] + PrefixNotFound(String), + + /// Error during serialization. + #[error("Serialization error: {0}")] + Serialization(String), + + /// Error during deserialization. + #[error("Deserialization error: {0}")] + Deserialization(String), + + /// Error for invalid operations. + #[error("Invalid operation: {0}")] + InvalidOperation(String), +} +``` + +### 12.4 src/node.rs + +```rust +//! Node types for the RadixTree module. + +/// Represents a node in the radix tree. +pub struct Node { + /// The segment of the key stored at this node. + pub key_segment: String, + + /// Value stored at this node (empty if not a leaf). + pub value: Vec, + + /// References to child nodes. + pub children: Vec, + + /// Whether this node is a leaf node. + pub is_leaf: bool, +} + +/// Reference to a node in the database. +pub struct NodeRef { + /// The key segment for this child. + pub key_part: String, + + /// Database ID of the node. + pub node_id: u32, +} + +impl Node { + /// Creates a new node. + pub fn new(key_segment: String, value: Vec, is_leaf: bool) -> Self { + Self { + key_segment, + value, + children: Vec::new(), + is_leaf, + } + } + + /// Creates a new root node. + pub fn new_root() -> Self { + Self { + key_segment: String::new(), + value: Vec::new(), + children: Vec::new(), + is_leaf: false, + } + } +} + +impl NodeRef { + /// Creates a new node reference. + pub fn new(key_part: String, node_id: u32) -> Self { + Self { + key_part, + node_id, + } + } +} +``` + +### 12.5 src/serialize.rs + +```rust +//! Serialization and deserialization for RadixTree nodes. + +use crate::error::Error; +use crate::node::{Node, NodeRef}; + +/// Current binary format version. +const VERSION: u8 = 1; + +impl Node { + /// Serializes a node to bytes for storage. + pub fn serialize(&self) -> Vec { + // Implementation will go here + unimplemented!() + } + + /// Deserializes bytes to a node. + pub fn deserialize(data: &[u8]) -> Result { + // Implementation will go here + unimplemented!() + } +} +``` + +### 12.6 src/operations.rs + +```rust +//! Implementation of RadixTree operations. + +use crate::error::Error; +use crate::node::{Node, NodeRef}; +use crate::RadixTree; + +impl RadixTree { + /// Helper function to get a node from the database. + pub(crate) fn get_node(&mut self, node_id: u32) -> Result { + // Implementation will go here + unimplemented!() + } + + /// Helper function to save a node to the database. + pub(crate) fn save_node(&mut self, node_id: Option, node: &Node) -> Result { + // Implementation will go here + unimplemented!() + } + + /// Helper function to find all keys with a given prefix. + fn find_keys_with_prefix( + &mut self, + node_id: u32, + current_path: &str, + prefix: &str, + result: &mut Vec, + ) -> Result<(), Error> { + // Implementation will go here + unimplemented!() + } + + /// Helper function to recursively collect all keys under a node. + fn collect_all_keys( + &mut self, + node_id: u32, + current_path: &str, + result: &mut Vec, + ) -> Result<(), Error> { + // Implementation will go here + unimplemented!() + } + + /// Helper function to get the common prefix of two strings. + fn get_common_prefix(a: &str, b: &str) -> String { + // Implementation will go here + unimplemented!() + } +} +``` + +### 12.7 examples/basic_usage.rs + +```rust +//! Basic usage example for RadixTree. + +use radixtree::RadixTree; + +fn main() -> Result<(), radixtree::Error> { + // Create a temporary directory for the database + let db_path = std::env::temp_dir().join("radixtree_example"); + std::fs::create_dir_all(&db_path)?; + + println!("Creating radix tree at: {}", db_path.display()); + + // Create a new radix tree + let mut tree = RadixTree::new(db_path.to_str().unwrap(), true)?; + + // Store some data + tree.set("hello", b"world".to_vec())?; + tree.set("help", b"me".to_vec())?; + tree.set("helicopter", b"flying".to_vec())?; + + // Retrieve and print the data + let value = tree.get("hello")?; + println!("hello: {}", String::from_utf8_lossy(&value)); + + // List keys with prefix + let keys = tree.list("hel")?; + println!("Keys with prefix 'hel': {:?}", keys); + + // Get all values with prefix + let values = tree.getall("hel")?; + println!("Values with prefix 'hel':"); + for (i, value) in values.iter().enumerate() { + println!(" {}: {}", i, String::from_utf8_lossy(value)); + } + + // Delete a key + tree.delete("help")?; + println!("Deleted 'help'"); + + // Verify deletion + let keys_after = tree.list("hel")?; + println!("Keys with prefix 'hel' after deletion: {:?}", keys_after); + + // Clean up (optional) + if std::env::var("KEEP_DB").is_err() { + std::fs::remove_dir_all(&db_path)?; + println!("Cleaned up database directory"); + } else { + println!("Database kept at: {}", db_path.display()); + } + + Ok(()) +} +``` \ No newline at end of file diff --git a/radixtree/MIGRATION.md b/radixtree/MIGRATION.md new file mode 100644 index 0000000..43222f2 --- /dev/null +++ b/radixtree/MIGRATION.md @@ -0,0 +1,265 @@ +# Migration Guide: V to Rust RadixTree + +This document provides guidance for migrating from the V implementation of RadixTree to the Rust implementation. + +## API Changes + +The Rust implementation maintains API compatibility with the V implementation, but with some idiomatic Rust changes: + +### V API + +```v +// Create a new radix tree +mut rt := radixtree.new(path: '/tmp/radixtree_test', reset: true)! + +// Set a key-value pair +rt.set('test', 'value1'.bytes())! + +// Get a value by key +value := rt.get('test')! + +// Update a value at a prefix +rt.update('prefix', 'new_value'.bytes())! + +// Delete a key +rt.delete('test')! + +// List keys with a prefix +keys := rt.list('prefix')! + +// Get all values with a prefix +values := rt.getall('prefix')! +``` + +### Rust API + +```rust +// Create a new radix tree +let mut tree = RadixTree::new("/tmp/radixtree_test", true)?; + +// Set a key-value pair +tree.set("test", b"value1".to_vec())?; + +// Get a value by key +let value = tree.get("test")?; + +// Update a value at a prefix +tree.update("prefix", b"new_value".to_vec())?; + +// Delete a key +tree.delete("test")?; + +// List keys with a prefix +let keys = tree.list("prefix")?; + +// Get all values with a prefix +let values = tree.getall("prefix")?; +``` + +## Key Differences + +1. **Error Handling**: The Rust implementation uses Rust's `Result` type for error handling, while the V implementation uses V's `!` operator. + +2. **String Handling**: The Rust implementation uses Rust's `&str` for string parameters and `String` for string return values, while the V implementation uses V's `string` type. + +3. **Binary Data**: The Rust implementation uses Rust's `Vec` for binary data, while the V implementation uses V's `[]u8` type. + +4. **Constructor**: The Rust implementation uses a constructor function with separate parameters, while the V implementation uses a struct with named parameters. + +5. **Ownership**: The Rust implementation follows Rust's ownership model, requiring mutable references for methods that modify the tree. + +## Data Compatibility + +The Rust implementation maintains data compatibility with the V implementation: + +- The same serialization format is used for nodes +- The same OurDB storage format is used +- Existing RadixTree data created with the V implementation can be read by the Rust implementation + +## Migration Steps + +1. **Update Dependencies**: Replace the V RadixTree dependency with the Rust RadixTree dependency in your project. + +2. **Update Import Statements**: Replace V import statements with Rust use statements. + + ```v + // V + import freeflowuniverse.herolib.data.radixtree + ``` + + ```rust + // Rust + use radixtree::RadixTree; + ``` + +3. **Update Constructor Calls**: Replace V constructor calls with Rust constructor calls. + + ```v + // V + mut rt := radixtree.new(path: '/path/to/db', reset: false)! + ``` + + ```rust + // Rust + let mut tree = RadixTree::new("/path/to/db", false)?; + ``` + +4. **Update Method Calls**: Replace V method calls with Rust method calls. + + ```v + // V + rt.set('key', 'value'.bytes())! + ``` + + ```rust + // Rust + tree.set("key", b"value".to_vec())?; + ``` + +5. **Update Error Handling**: Replace V error handling with Rust error handling. + + ```v + // V + if value := rt.get('key') { + println('Found: ${value.bytestr()}') + } else { + println('Error: ${err}') + } + ``` + + ```rust + // Rust + match tree.get("key") { + Ok(value) => println!("Found: {}", String::from_utf8_lossy(&value)), + Err(e) => println!("Error: {}", e), + } + ``` + +6. **Update String Conversions**: Replace V string conversions with Rust string conversions. + + ```v + // V + value.bytestr() // Convert []u8 to string + ``` + + ```rust + // Rust + String::from_utf8_lossy(&value) // Convert Vec to string + ``` + +## Example Migration + +### V Code + +```v +module main + +import freeflowuniverse.herolib.data.radixtree + +fn main() { + mut rt := radixtree.new(path: '/tmp/radixtree_test', reset: true) or { + println('Error creating RadixTree: ${err}') + return + } + + rt.set('hello', 'world'.bytes()) or { + println('Error setting key: ${err}') + return + } + + rt.set('help', 'me'.bytes()) or { + println('Error setting key: ${err}') + return + } + + if value := rt.get('hello') { + println('hello: ${value.bytestr()}') + } else { + println('Error getting key: ${err}') + return + } + + keys := rt.list('hel') or { + println('Error listing keys: ${err}') + return + } + println('Keys with prefix "hel": ${keys}') + + values := rt.getall('hel') or { + println('Error getting all values: ${err}') + return + } + println('Values with prefix "hel":') + for i, value in values { + println(' ${i}: ${value.bytestr()}') + } + + rt.delete('help') or { + println('Error deleting key: ${err}') + return + } + println('Deleted "help"') +} +``` + +### Rust Code + +```rust +use radixtree::RadixTree; + +fn main() -> Result<(), Box> { + let mut tree = RadixTree::new("/tmp/radixtree_test", true) + .map_err(|e| format!("Error creating RadixTree: {}", e))?; + + tree.set("hello", b"world".to_vec()) + .map_err(|e| format!("Error setting key: {}", e))?; + + tree.set("help", b"me".to_vec()) + .map_err(|e| format!("Error setting key: {}", e))?; + + let value = tree.get("hello") + .map_err(|e| format!("Error getting key: {}", e))?; + println!("hello: {}", String::from_utf8_lossy(&value)); + + let keys = tree.list("hel") + .map_err(|e| format!("Error listing keys: {}", e))?; + println!("Keys with prefix \"hel\": {:?}", keys); + + let values = tree.getall("hel") + .map_err(|e| format!("Error getting all values: {}", e))?; + println!("Values with prefix \"hel\":"); + for (i, value) in values.iter().enumerate() { + println!(" {}: {}", i, String::from_utf8_lossy(value)); + } + + tree.delete("help") + .map_err(|e| format!("Error deleting key: {}", e))?; + println!("Deleted \"help\""); + + Ok(()) +} +``` + +## Performance Considerations + +The Rust implementation should provide similar or better performance compared to the V implementation. However, there are some considerations: + +1. **Memory Usage**: The Rust implementation may have different memory usage patterns due to Rust's ownership model. + +2. **Error Handling**: The Rust implementation uses Rust's `Result` type, which may have different performance characteristics compared to V's error handling. + +3. **String Handling**: The Rust implementation uses Rust's string types, which may have different performance characteristics compared to V's string types. + +## Troubleshooting + +If you encounter issues during migration, check the following: + +1. **Data Compatibility**: Ensure that the data format is compatible between the V and Rust implementations. + +2. **API Usage**: Ensure that you're using the correct API for the Rust implementation. + +3. **Error Handling**: Ensure that you're handling errors correctly in the Rust implementation. + +4. **String Encoding**: Ensure that string encoding is consistent between the V and Rust implementations. + +If you encounter any issues that are not covered in this guide, please report them to the project maintainers. \ No newline at end of file diff --git a/radixtree/README.md b/radixtree/README.md new file mode 100644 index 0000000..bfe5edb --- /dev/null +++ b/radixtree/README.md @@ -0,0 +1,148 @@ +# RadixTree + +A persistent radix tree implementation in Rust using OurDB for storage. + +## Overview + +RadixTree is a space-optimized tree data structure that enables efficient string key operations with persistent storage. This implementation provides a persistent radix tree that can be used for efficient prefix-based key operations, such as auto-complete, routing tables, and more. + +A radix tree (also known as a patricia trie or radix trie) is a space-optimized tree data structure that enables efficient string key operations. Unlike a standard trie where each node represents a single character, a radix tree compresses paths by allowing nodes to represent multiple characters (key segments). + +Key characteristics: +- Each node stores a segment of a key (not just a single character) +- Nodes can have multiple children, each representing a different branch +- Leaf nodes contain the actual values +- Optimizes storage by compressing common prefixes + +## Features + +- Efficient prefix-based key operations +- Persistent storage using OurDB backend +- Memory-efficient storage of strings with common prefixes +- Support for binary values +- Thread-safe operations through OurDB + +## Usage + +Add the dependency to your `Cargo.toml`: + +```toml +[dependencies] +radixtree = { path = "../radixtree" } +``` + +### Basic Example + +```rust +use radixtree::RadixTree; + +fn main() -> Result<(), radixtree::Error> { + // Create a new radix tree + let mut tree = RadixTree::new("/path/to/storage", false)?; + + // Set key-value pairs + tree.set("hello", b"world".to_vec())?; + tree.set("help", b"me".to_vec())?; + + // Get values by key + let value = tree.get("hello")?; + println!("hello: {}", String::from_utf8_lossy(&value)); // Prints: world + + // List keys by prefix + let keys = tree.list("hel")?; // Returns ["hello", "help"] + println!("Keys with prefix 'hel': {:?}", keys); + + // Get all values by prefix + let values = tree.getall("hel")?; // Returns [b"world", b"me"] + + // Delete keys + tree.delete("help")?; + + Ok(()) +} +``` + +## API + +### Creating a RadixTree + +```rust +// Create a new radix tree +let mut tree = RadixTree::new("/path/to/storage", false)?; + +// Create a new radix tree and reset if it exists +let mut tree = RadixTree::new("/path/to/storage", true)?; +``` + +### Setting Values + +```rust +// Set a key-value pair +tree.set("key", b"value".to_vec())?; +``` + +### Getting Values + +```rust +// Get a value by key +let value = tree.get("key")?; +``` + +### Updating Values + +```rust +// Update a value at a given prefix +tree.update("prefix", b"new_value".to_vec())?; +``` + +### Deleting Keys + +```rust +// Delete a key +tree.delete("key")?; +``` + +### Listing Keys by Prefix + +```rust +// List all keys with a given prefix +let keys = tree.list("prefix")?; +``` + +### Getting All Values by Prefix + +```rust +// Get all values for keys with a given prefix +let values = tree.getall("prefix")?; +``` + +## Performance Characteristics + +- Search: O(k) where k is the key length +- Insert: O(k) for new keys, may require node splitting +- Delete: O(k) plus potential node cleanup +- Space: O(n) where n is the total length of all keys + +## Use Cases + +RadixTree is particularly useful for: +- Prefix-based searching +- IP routing tables +- Dictionary implementations +- Auto-complete systems +- File system paths +- Any application requiring efficient string key operations with persistence + +## Implementation Details + +The RadixTree implementation uses OurDB for persistent storage: +- Each node is serialized and stored as a record in OurDB +- Node references use OurDB record IDs +- The tree maintains a root node ID for traversal +- Node serialization includes version tracking for format evolution + +For more detailed information about the implementation, see the [ARCHITECTURE.md](./ARCHITECTURE.md) file. + +## License + +This project is licensed under the same license as the HeroCode project. \ No newline at end of file