...
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
module radixtree
|
||||
|
||||
fn test_basic_operations() ! {
|
||||
mut rt := new(path:'/tmp/radixtree_test',reset:true)!
|
||||
mut rt := new(path: '/tmp/radixtree_test', reset: true)!
|
||||
|
||||
// Test insert and search
|
||||
rt.insert('test', 'value1'.bytes())!
|
||||
@@ -20,15 +20,15 @@ fn test_basic_operations() ! {
|
||||
|
||||
// Test delete
|
||||
rt.delete('test')!
|
||||
mut ok:=false
|
||||
mut ok := false
|
||||
if _ := rt.search('test') {
|
||||
ok=true
|
||||
ok = true
|
||||
}
|
||||
assert ok
|
||||
}
|
||||
|
||||
fn test_prefix_matching() ! {
|
||||
mut rt := new(path:'/tmp/radixtree_test_prefix')!
|
||||
mut rt := new(path: '/tmp/radixtree_test_prefix')!
|
||||
|
||||
// Insert keys with common prefixes
|
||||
rt.insert('team', 'value1'.bytes())!
|
||||
@@ -47,7 +47,7 @@ fn test_prefix_matching() ! {
|
||||
|
||||
// Delete middle key and verify others still work
|
||||
rt.delete('test')!
|
||||
|
||||
|
||||
if _ := rt.search('test') {
|
||||
assert false, 'Expected error after deletion'
|
||||
}
|
||||
@@ -60,7 +60,7 @@ fn test_prefix_matching() ! {
|
||||
}
|
||||
|
||||
fn test_edge_cases() ! {
|
||||
mut rt := new(path:'/tmp/radixtree_test_edge')!
|
||||
mut rt := new(path: '/tmp/radixtree_test_edge')!
|
||||
|
||||
// Test empty key
|
||||
rt.insert('', 'empty'.bytes())!
|
||||
@@ -89,7 +89,7 @@ fn test_edge_cases() ! {
|
||||
}
|
||||
|
||||
fn test_multiple_operations() ! {
|
||||
mut rt := new(path:'/tmp/radixtree_test_multiple')!
|
||||
mut rt := new(path: '/tmp/radixtree_test_multiple')!
|
||||
|
||||
// Insert multiple keys
|
||||
keys := ['abc', 'abcd', 'abcde', 'bcd', 'bcde']
|
||||
@@ -110,7 +110,7 @@ fn test_multiple_operations() ! {
|
||||
// Verify remaining keys
|
||||
remaining := ['abc', 'abcde', 'bcd']
|
||||
expected := ['value1', 'value3', 'value4']
|
||||
|
||||
|
||||
for i, key in remaining {
|
||||
value := rt.search(key)!
|
||||
assert value.bytestr() == expected[i]
|
||||
|
||||
@@ -6,39 +6,38 @@ import freeflowuniverse.herolib.data.ourdb
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
// Reference to a node in the database
|
||||
struct NodeRef {
|
||||
mut:
|
||||
key_part string // The key segment for this child
|
||||
node_id u32 // Database ID of the node
|
||||
node_id u32 // Database ID of the node
|
||||
}
|
||||
|
||||
// RadixTree represents a radix tree data structure
|
||||
pub struct RadixTree {
|
||||
mut:
|
||||
db &ourdb.OurDB // Database for persistent storage
|
||||
root_id u32 // Database ID of the root node
|
||||
root_id u32 // Database ID of the root node
|
||||
}
|
||||
|
||||
|
||||
pub struct NewArgs {
|
||||
pub mut:
|
||||
path string
|
||||
path string
|
||||
reset bool
|
||||
}
|
||||
|
||||
// Creates a new radix tree with the specified database path
|
||||
pub fn new(args NewArgs) !&RadixTree {
|
||||
mut db := ourdb.new(
|
||||
path: args.path
|
||||
record_size_max: 1024 * 4 // 4KB max record size
|
||||
path: args.path
|
||||
record_size_max: 1024 * 4 // 4KB max record size
|
||||
incremental_mode: true
|
||||
reset:args.reset
|
||||
reset: args.reset
|
||||
)!
|
||||
|
||||
mut root_id := u32(0)
|
||||
@@ -47,11 +46,11 @@ pub fn new(args NewArgs) !&RadixTree {
|
||||
println('Debug: Creating new root node')
|
||||
root := Node{
|
||||
key_segment: ''
|
||||
value: []u8{}
|
||||
children: []NodeRef{}
|
||||
is_leaf: false
|
||||
value: []u8{}
|
||||
children: []NodeRef{}
|
||||
is_leaf: false
|
||||
}
|
||||
root_id = db.set(data: serialize_node(root))!
|
||||
root_id = db.set(data: serialize_node(root))!
|
||||
println('Debug: Created root node with ID ${root_id}')
|
||||
assert root_id == 0
|
||||
} else {
|
||||
@@ -62,7 +61,7 @@ pub fn new(args NewArgs) !&RadixTree {
|
||||
}
|
||||
|
||||
return &RadixTree{
|
||||
db: &db
|
||||
db: &db
|
||||
root_id: root_id
|
||||
}
|
||||
}
|
||||
@@ -83,7 +82,7 @@ pub fn (mut rt RadixTree) insert(key string, value []u8) ! {
|
||||
|
||||
for offset < key.len {
|
||||
mut node := deserialize_node(rt.db.get(current_id)!)!
|
||||
|
||||
|
||||
// Find matching child
|
||||
mut matched_child := -1
|
||||
for i, child in node.children {
|
||||
@@ -98,33 +97,33 @@ pub fn (mut rt RadixTree) insert(key string, value []u8) ! {
|
||||
key_part := key[offset..]
|
||||
new_node := Node{
|
||||
key_segment: key_part
|
||||
value: value
|
||||
children: []NodeRef{}
|
||||
is_leaf: true
|
||||
value: value
|
||||
children: []NodeRef{}
|
||||
is_leaf: true
|
||||
}
|
||||
println('Debug: Creating new leaf node with key_part "${key_part}"')
|
||||
new_id := rt.db.set(data: serialize_node(new_node))!
|
||||
println('Debug: Created node ID ${new_id}')
|
||||
|
||||
|
||||
// Create new child reference and update parent node
|
||||
println('Debug: Updating parent node ${current_id} to add child reference')
|
||||
|
||||
|
||||
// Get fresh copy of parent node
|
||||
mut parent_node := deserialize_node(rt.db.get(current_id)!)!
|
||||
println('Debug: Parent node initially has ${parent_node.children.len} children')
|
||||
|
||||
|
||||
// Add new child reference
|
||||
parent_node.children << NodeRef{
|
||||
key_part: key_part
|
||||
node_id: new_id
|
||||
node_id: new_id
|
||||
}
|
||||
println('Debug: Added child reference, now has ${parent_node.children.len} children')
|
||||
|
||||
|
||||
// Update parent node in DB
|
||||
println('Debug: Serializing parent node with ${parent_node.children.len} children')
|
||||
parent_data := serialize_node(parent_node)
|
||||
println('Debug: Parent data size: ${parent_data.len} bytes')
|
||||
|
||||
|
||||
// First verify we can deserialize the data correctly
|
||||
println('Debug: Verifying serialization...')
|
||||
if test_node := deserialize_node(parent_data) {
|
||||
@@ -133,17 +132,17 @@ pub fn (mut rt RadixTree) insert(key string, value []u8) ! {
|
||||
println('Debug: ERROR - Failed to deserialize test data')
|
||||
return error('Serialization verification failed')
|
||||
}
|
||||
|
||||
|
||||
// Set with explicit ID to update existing node
|
||||
println('Debug: Writing to DB...')
|
||||
rt.db.set(id: current_id, data: parent_data)!
|
||||
|
||||
|
||||
// Verify by reading back and comparing
|
||||
println('Debug: Reading back for verification...')
|
||||
verify_data := rt.db.get(current_id)!
|
||||
verify_node := deserialize_node(verify_data)!
|
||||
println('Debug: Verification - node has ${verify_node.children.len} children')
|
||||
|
||||
|
||||
if verify_node.children.len == 0 {
|
||||
println('Debug: ERROR - Node update verification failed!')
|
||||
println('Debug: Original node children: ${node.children.len}')
|
||||
@@ -159,24 +158,24 @@ pub fn (mut rt RadixTree) insert(key string, value []u8) ! {
|
||||
|
||||
child := node.children[matched_child]
|
||||
common_prefix := get_common_prefix(key[offset..], child.key_part)
|
||||
|
||||
|
||||
if common_prefix.len < child.key_part.len {
|
||||
// Split existing node
|
||||
mut child_node := deserialize_node(rt.db.get(child.node_id)!)!
|
||||
|
||||
|
||||
// Create new intermediate node
|
||||
mut new_node := Node{
|
||||
key_segment: child.key_part[common_prefix.len..]
|
||||
value: child_node.value
|
||||
children: child_node.children
|
||||
is_leaf: child_node.is_leaf
|
||||
value: child_node.value
|
||||
children: child_node.children
|
||||
is_leaf: child_node.is_leaf
|
||||
}
|
||||
new_id := rt.db.set(data: serialize_node(new_node))!
|
||||
|
||||
// Update current node
|
||||
node.children[matched_child] = NodeRef{
|
||||
key_part: common_prefix
|
||||
node_id: new_id
|
||||
node_id: new_id
|
||||
}
|
||||
rt.db.set(id: current_id, data: serialize_node(node))!
|
||||
}
|
||||
@@ -211,7 +210,7 @@ pub fn (mut rt RadixTree) search(key string) ![]u8 {
|
||||
|
||||
for offset < key.len {
|
||||
node := deserialize_node(rt.db.get(current_id)!)!
|
||||
|
||||
|
||||
mut found := false
|
||||
for child in node.children {
|
||||
if key[offset..].starts_with(child.key_part) {
|
||||
@@ -245,7 +244,7 @@ pub fn (mut rt RadixTree) delete(key string) ! {
|
||||
// Find the node to delete
|
||||
for offset < key.len {
|
||||
node := deserialize_node(rt.db.get(current_id)!)!
|
||||
|
||||
|
||||
mut found := false
|
||||
for child in node.children {
|
||||
if key[offset..].starts_with(child.key_part) {
|
||||
@@ -279,7 +278,7 @@ pub fn (mut rt RadixTree) delete(key string) ! {
|
||||
mut last_node := deserialize_node(rt.db.get(path.last().node_id)!)!
|
||||
last_node.is_leaf = false
|
||||
last_node.value = []u8{}
|
||||
|
||||
|
||||
// If node has no children, remove it from parent
|
||||
if last_node.children.len == 0 {
|
||||
if path.len > 1 {
|
||||
|
||||
@@ -50,7 +50,7 @@ pub fn (mut rt RadixTree) debug_db() ! {
|
||||
// Prints the tree structure starting from a given node ID
|
||||
pub fn (mut rt RadixTree) print_tree_from_node(node_id u32, indent string) ! {
|
||||
node := rt.get_node_by_id(node_id)!
|
||||
|
||||
|
||||
mut node_info := '${indent}Node(id: ${node_id})'
|
||||
node_info += '\n${indent}├── key_segment: "${node.key_segment}"'
|
||||
node_info += '\n${indent}├── is_leaf: ${node.is_leaf}'
|
||||
@@ -61,7 +61,9 @@ pub fn (mut rt RadixTree) print_tree_from_node(node_id u32, indent string) ! {
|
||||
if node.children.len > 0 {
|
||||
node_info += ' ['
|
||||
for i, child in node.children {
|
||||
if i > 0 { node_info += ', ' }
|
||||
if i > 0 {
|
||||
node_info += ', '
|
||||
}
|
||||
node_info += '${child.node_id}:${child.key_part}'
|
||||
}
|
||||
node_info += ']'
|
||||
@@ -90,7 +92,7 @@ pub fn (mut rt RadixTree) print_tree() ! {
|
||||
// Gets detailed information about a specific node
|
||||
pub fn (mut rt RadixTree) get_node_info(id u32) !string {
|
||||
node := rt.get_node_by_id(id)!
|
||||
|
||||
|
||||
mut info := 'Node Details:\n'
|
||||
info += '=============\n'
|
||||
info += 'ID: ${id}\n'
|
||||
@@ -106,6 +108,6 @@ pub fn (mut rt RadixTree) get_node_info(id u32) !string {
|
||||
info += '- ID: ${child.node_id}, Key Part: "${child.key_part}"\n'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
@@ -2,57 +2,55 @@ module radixtree
|
||||
|
||||
import freeflowuniverse.herolib.data.encoder
|
||||
|
||||
const (
|
||||
version = u8(1) // Current binary format version
|
||||
)
|
||||
const version = u8(1) // Current binary format version
|
||||
|
||||
// Serializes a node to bytes for storage
|
||||
fn serialize_node(node Node) []u8 {
|
||||
mut e := encoder.new()
|
||||
|
||||
|
||||
// Add version byte
|
||||
e.add_u8(version)
|
||||
|
||||
|
||||
// Add key segment
|
||||
e.add_string(node.key_segment)
|
||||
|
||||
|
||||
// Add value as []u8
|
||||
e.add_u16(u16(node.value.len))
|
||||
e.data << node.value
|
||||
|
||||
|
||||
// Add children
|
||||
e.add_u16(u16(node.children.len))
|
||||
for child in node.children {
|
||||
e.add_string(child.key_part)
|
||||
e.add_u32(child.node_id)
|
||||
}
|
||||
|
||||
|
||||
// Add leaf flag
|
||||
e.add_u8(if node.is_leaf { u8(1) } else { u8(0) })
|
||||
|
||||
|
||||
return e.data
|
||||
}
|
||||
|
||||
// Deserializes bytes to a node
|
||||
fn deserialize_node(data []u8) !Node {
|
||||
mut d := encoder.decoder_new(data)
|
||||
|
||||
|
||||
// Read and verify version
|
||||
version_byte := d.get_u8()
|
||||
if version_byte != version {
|
||||
return error('Invalid version byte: expected ${version}, got ${version_byte}')
|
||||
}
|
||||
|
||||
|
||||
// Read key segment
|
||||
key_segment := d.get_string()
|
||||
|
||||
|
||||
// Read value as []u8
|
||||
value_len := d.get_u16()
|
||||
mut value := []u8{len: int(value_len)}
|
||||
for i in 0..int(value_len) {
|
||||
for i in 0 .. int(value_len) {
|
||||
value[i] = d.get_u8()
|
||||
}
|
||||
|
||||
|
||||
// Read children
|
||||
children_len := d.get_u16()
|
||||
mut children := []NodeRef{cap: int(children_len)}
|
||||
@@ -61,17 +59,17 @@ fn deserialize_node(data []u8) !Node {
|
||||
node_id := d.get_u32()
|
||||
children << NodeRef{
|
||||
key_part: key_part
|
||||
node_id: node_id
|
||||
node_id: node_id
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Read leaf flag
|
||||
is_leaf := d.get_u8() == 1
|
||||
|
||||
|
||||
return Node{
|
||||
key_segment: key_segment
|
||||
value: value
|
||||
children: children
|
||||
is_leaf: is_leaf
|
||||
value: value
|
||||
children: children
|
||||
is_leaf: is_leaf
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,35 +4,35 @@ fn test_serialize_deserialize() {
|
||||
// Create a test node with children
|
||||
node := Node{
|
||||
key_segment: 'test'
|
||||
value: 'hello world'.bytes()
|
||||
children: [
|
||||
value: 'hello world'.bytes()
|
||||
children: [
|
||||
NodeRef{
|
||||
key_part: 'child1'
|
||||
node_id: 1
|
||||
node_id: 1
|
||||
},
|
||||
NodeRef{
|
||||
key_part: 'child2'
|
||||
node_id: 2
|
||||
}
|
||||
node_id: 2
|
||||
},
|
||||
]
|
||||
is_leaf: true
|
||||
is_leaf: true
|
||||
}
|
||||
|
||||
// Serialize
|
||||
data := serialize_node(node)
|
||||
|
||||
|
||||
// Verify version byte
|
||||
assert data[0] == version
|
||||
|
||||
|
||||
// Deserialize
|
||||
decoded := deserialize_node(data)!
|
||||
|
||||
|
||||
// Verify all fields match
|
||||
assert decoded.key_segment == node.key_segment
|
||||
assert decoded.value == node.value
|
||||
assert decoded.is_leaf == node.is_leaf
|
||||
assert decoded.children.len == node.children.len
|
||||
|
||||
|
||||
// Verify children
|
||||
assert decoded.children[0].key_part == node.children[0].key_part
|
||||
assert decoded.children[0].node_id == node.children[0].node_id
|
||||
@@ -44,14 +44,14 @@ fn test_empty_node() {
|
||||
// Test node with empty values
|
||||
node := Node{
|
||||
key_segment: ''
|
||||
value: []u8{}
|
||||
children: []NodeRef{}
|
||||
is_leaf: false
|
||||
value: []u8{}
|
||||
children: []NodeRef{}
|
||||
is_leaf: false
|
||||
}
|
||||
|
||||
|
||||
data := serialize_node(node)
|
||||
decoded := deserialize_node(data)!
|
||||
|
||||
|
||||
assert decoded.key_segment == node.key_segment
|
||||
assert decoded.value == node.value
|
||||
assert decoded.children == node.children
|
||||
@@ -62,27 +62,27 @@ fn test_large_values() {
|
||||
// Create large test data
|
||||
mut large_value := []u8{len: 1000, init: u8(index & 0xFF)}
|
||||
mut children := []NodeRef{cap: 100}
|
||||
for i in 0..100 {
|
||||
for i in 0 .. 100 {
|
||||
children << NodeRef{
|
||||
key_part: 'child${i}'
|
||||
node_id: u32(i)
|
||||
node_id: u32(i)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
node := Node{
|
||||
key_segment: 'large_test'
|
||||
value: large_value
|
||||
children: children
|
||||
is_leaf: true
|
||||
value: large_value
|
||||
children: children
|
||||
is_leaf: true
|
||||
}
|
||||
|
||||
|
||||
data := serialize_node(node)
|
||||
decoded := deserialize_node(data)!
|
||||
|
||||
|
||||
assert decoded.key_segment == node.key_segment
|
||||
assert decoded.value == node.value
|
||||
assert decoded.children.len == node.children.len
|
||||
|
||||
|
||||
// Verify some random children
|
||||
assert decoded.children[0] == node.children[0]
|
||||
assert decoded.children[50] == node.children[50]
|
||||
@@ -92,15 +92,15 @@ fn test_large_values() {
|
||||
fn test_invalid_version() {
|
||||
node := Node{
|
||||
key_segment: 'test'
|
||||
value: []u8{}
|
||||
children: []NodeRef{}
|
||||
is_leaf: false
|
||||
value: []u8{}
|
||||
children: []NodeRef{}
|
||||
is_leaf: false
|
||||
}
|
||||
|
||||
|
||||
mut data := serialize_node(node)
|
||||
// Corrupt version byte
|
||||
data[0] = 255
|
||||
|
||||
|
||||
// Should return error for version mismatch
|
||||
if result := deserialize_node(data) {
|
||||
assert false, 'Expected error for invalid version byte'
|
||||
|
||||
Reference in New Issue
Block a user