radix tree has now prefix
This commit is contained in:
@@ -40,17 +40,27 @@ The radix tree uses OurDB as its persistent storage backend:
|
||||
|
||||
### Key Operations
|
||||
|
||||
#### Insertion
|
||||
#### Set (formerly Insertion)
|
||||
1. Traverse the tree following matching prefixes
|
||||
2. Split nodes when partial matches are found
|
||||
3. Create new nodes for unmatched segments
|
||||
4. Update node values and references in OurDB
|
||||
|
||||
#### Search
|
||||
#### Get (formerly Search)
|
||||
1. Start from the root node
|
||||
2. Follow child nodes whose key segments match the search key
|
||||
3. Return the value if an exact match is found at a leaf node
|
||||
|
||||
#### List (formerly Search by Prefix)
|
||||
1. Start from the root node
|
||||
2. Find all keys that start with the given prefix
|
||||
3. Return a list of matching keys
|
||||
|
||||
#### GetAll
|
||||
1. Find all keys that start with the given prefix using List
|
||||
2. Retrieve the value for each matching key
|
||||
3. Return a list of values for all matching keys
|
||||
|
||||
#### Deletion
|
||||
1. Locate the node containing the key
|
||||
2. Remove the value and leaf status
|
||||
@@ -65,14 +75,20 @@ import freeflowuniverse.herolib.data.radixtree
|
||||
// Create a new radix tree
|
||||
mut tree := radixtree.new('/path/to/storage')!
|
||||
|
||||
// Insert key-value pairs
|
||||
tree.insert('hello', 'world'.bytes())!
|
||||
tree.insert('help', 'me'.bytes())!
|
||||
// Set key-value pairs
|
||||
tree.set('hello', 'world'.bytes())!
|
||||
tree.set('help', 'me'.bytes())!
|
||||
|
||||
// Search for values
|
||||
value := tree.search('hello')! // Returns 'world' as bytes
|
||||
// Get values by key
|
||||
value := tree.get('hello')! // Returns 'world' as bytes
|
||||
println(value.bytestr()) // Prints: world
|
||||
|
||||
// List keys by prefix
|
||||
keys := tree.list('hel')! // Returns ['hello', 'help']
|
||||
|
||||
// Get all values by prefix
|
||||
values := tree.getall('hel')! // Returns ['world'.bytes(), 'me'.bytes()]
|
||||
|
||||
// Delete keys
|
||||
tree.delete('help')!
|
||||
```
|
||||
|
||||
@@ -1,61 +1,64 @@
|
||||
module radixtree
|
||||
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
|
||||
fn test_basic_operations() ! {
|
||||
mut rt := new(path: '/tmp/radixtree_test', reset: true)!
|
||||
|
||||
// Test insert and search
|
||||
rt.insert('test', 'value1'.bytes())!
|
||||
value1 := rt.search('test')!
|
||||
// Test set and get
|
||||
rt.set('test', 'value1'.bytes())!
|
||||
value1 := rt.get('test')!
|
||||
assert value1.bytestr() == 'value1'
|
||||
|
||||
// Test updating existing key
|
||||
rt.insert('test', 'value2'.bytes())!
|
||||
value2 := rt.search('test')!
|
||||
rt.set('test', 'value2'.bytes())!
|
||||
value2 := rt.get('test')!
|
||||
assert value2.bytestr() == 'value2'
|
||||
|
||||
// Test non-existent key
|
||||
if _ := rt.search('nonexistent') {
|
||||
if _ := rt.get('nonexistent') {
|
||||
assert false, 'Expected error for non-existent key'
|
||||
}
|
||||
|
||||
// Test delete
|
||||
rt.delete('test')!
|
||||
mut ok := false
|
||||
if _ := rt.search('test') {
|
||||
ok = true
|
||||
if _ := rt.get('test') {ok = true
|
||||
} else {
|
||||
ok = false
|
||||
}
|
||||
assert ok
|
||||
assert !ok, 'Expected error for deleted key'
|
||||
}
|
||||
|
||||
fn test_prefix_matching() ! {
|
||||
mut rt := new(path: '/tmp/radixtree_test_prefix')!
|
||||
|
||||
// Insert keys with common prefixes
|
||||
rt.insert('team', 'value1'.bytes())!
|
||||
rt.insert('test', 'value2'.bytes())!
|
||||
rt.insert('testing', 'value3'.bytes())!
|
||||
// Set keys with common prefixes
|
||||
rt.set('team', 'value1'.bytes())!
|
||||
rt.set('test', 'value2'.bytes())!
|
||||
rt.set('testing', 'value3'.bytes())!
|
||||
|
||||
// Verify each key has correct value
|
||||
value1 := rt.search('team')!
|
||||
value1 := rt.get('team')!
|
||||
assert value1.bytestr() == 'value1'
|
||||
|
||||
value2 := rt.search('test')!
|
||||
value2 := rt.get('test')!
|
||||
assert value2.bytestr() == 'value2'
|
||||
|
||||
value3 := rt.search('testing')!
|
||||
value3 := rt.get('testing')!
|
||||
assert value3.bytestr() == 'value3'
|
||||
|
||||
// Delete middle key and verify others still work
|
||||
rt.delete('test')!
|
||||
|
||||
if _ := rt.search('test') {
|
||||
if _ := rt.get('test') {
|
||||
assert false, 'Expected error after deletion'
|
||||
}
|
||||
|
||||
value1_after := rt.search('team')!
|
||||
value1_after := rt.get('team')!
|
||||
assert value1_after.bytestr() == 'value1'
|
||||
|
||||
value3_after := rt.search('testing')!
|
||||
value3_after := rt.get('testing')!
|
||||
assert value3_after.bytestr() == 'value3'
|
||||
}
|
||||
|
||||
@@ -63,28 +66,28 @@ fn test_edge_cases() ! {
|
||||
mut rt := new(path: '/tmp/radixtree_test_edge')!
|
||||
|
||||
// Test empty key
|
||||
rt.insert('', 'empty'.bytes())!
|
||||
empty_value := rt.search('')!
|
||||
rt.set('', 'empty'.bytes())!
|
||||
empty_value := rt.get('')!
|
||||
assert empty_value.bytestr() == 'empty'
|
||||
|
||||
// Test very long key
|
||||
long_key := 'a'.repeat(1000)
|
||||
rt.insert(long_key, 'long'.bytes())!
|
||||
long_value := rt.search(long_key)!
|
||||
rt.set(long_key, 'long'.bytes())!
|
||||
long_value := rt.get(long_key)!
|
||||
assert long_value.bytestr() == 'long'
|
||||
|
||||
// Test keys that require node splitting
|
||||
rt.insert('test', 'value1'.bytes())!
|
||||
rt.insert('testing', 'value2'.bytes())!
|
||||
rt.insert('te', 'value3'.bytes())!
|
||||
rt.set('test', 'value1'.bytes())!
|
||||
rt.set('testing', 'value2'.bytes())!
|
||||
rt.set('te', 'value3'.bytes())!
|
||||
|
||||
value1 := rt.search('test')!
|
||||
value1 := rt.get('test')!
|
||||
assert value1.bytestr() == 'value1'
|
||||
|
||||
value2 := rt.search('testing')!
|
||||
value2 := rt.get('testing')!
|
||||
assert value2.bytestr() == 'value2'
|
||||
|
||||
value3 := rt.search('te')!
|
||||
value3 := rt.get('te')!
|
||||
assert value3.bytestr() == 'value3'
|
||||
}
|
||||
|
||||
@@ -96,18 +99,18 @@ fn test_update_metadata() ! {
|
||||
initial_metadata := 'metadata_initial'.bytes()
|
||||
new_metadata := 'metadata_updated'.bytes()
|
||||
|
||||
// Insert initial entry
|
||||
rt.insert(prefix, initial_metadata)!
|
||||
// Set initial entry
|
||||
rt.set(prefix, initial_metadata)!
|
||||
|
||||
// Verify initial value
|
||||
value := rt.search(prefix)!
|
||||
value := rt.get(prefix)!
|
||||
assert value.bytestr() == 'metadata_initial'
|
||||
|
||||
// Update metadata while keeping the same prefix
|
||||
rt.update(prefix, new_metadata)!
|
||||
|
||||
// Verify updated value
|
||||
updated_value := rt.search(prefix)!
|
||||
updated_value := rt.get(prefix)!
|
||||
assert updated_value.bytestr() == 'metadata_updated'
|
||||
|
||||
// Test updating non-existent prefix
|
||||
@@ -119,15 +122,15 @@ fn test_update_metadata() ! {
|
||||
fn test_multiple_operations() ! {
|
||||
mut rt := new(path: '/tmp/radixtree_test_multiple')!
|
||||
|
||||
// Insert multiple keys
|
||||
// Set multiple keys
|
||||
keys := ['abc', 'abcd', 'abcde', 'bcd', 'bcde']
|
||||
for i, key in keys {
|
||||
rt.insert(key, 'value${i + 1}'.bytes())!
|
||||
rt.set(key, 'value${i + 1}'.bytes())!
|
||||
}
|
||||
|
||||
// Verify all keys
|
||||
for i, key in keys {
|
||||
value := rt.search(key)!
|
||||
value := rt.get(key)!
|
||||
assert value.bytestr() == 'value${i + 1}'
|
||||
}
|
||||
|
||||
@@ -140,14 +143,14 @@ fn test_multiple_operations() ! {
|
||||
expected := ['value1', 'value3', 'value4']
|
||||
|
||||
for i, key in remaining {
|
||||
value := rt.search(key)!
|
||||
value := rt.get(key)!
|
||||
assert value.bytestr() == expected[i]
|
||||
}
|
||||
|
||||
// Verify deleted keys return error
|
||||
deleted := ['abcd', 'bcde']
|
||||
for key in deleted {
|
||||
if _ := rt.search(key) {
|
||||
if _ := rt.get(key) {
|
||||
assert false, 'Expected error for deleted key: ${key}'
|
||||
}
|
||||
}
|
||||
|
||||
78
lib/data/radixtree/getall_test.v
Normal file
78
lib/data/radixtree/getall_test.v
Normal file
@@ -0,0 +1,78 @@
|
||||
module radixtree
|
||||
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
|
||||
fn test_getall() ! {
|
||||
//console.print_debug('Starting test_getall')
|
||||
mut rt := new(path: '/tmp/radixtree_getall_test', reset: true)!
|
||||
|
||||
// Set up test data with common prefixes
|
||||
test_data := {
|
||||
'user_1': 'data1',
|
||||
'user_2': 'data2',
|
||||
'user_3': 'data3',
|
||||
'admin_1': 'admin_data1',
|
||||
'admin_2': 'admin_data2',
|
||||
'guest': 'guest_data'
|
||||
}
|
||||
|
||||
// Set all test data
|
||||
for key, value in test_data {
|
||||
rt.set(key, value.bytes())!
|
||||
}
|
||||
|
||||
// Test getall with 'user_' prefix
|
||||
//console.print_debug('Testing getall with prefix "user_"')
|
||||
user_values := rt.getall('user_')!
|
||||
//console.print_debug('user_values count: ${user_values.len}')
|
||||
|
||||
// Should return 3 values
|
||||
assert user_values.len == 3
|
||||
|
||||
// Convert byte arrays to strings for easier comparison
|
||||
mut user_value_strings := []string{}
|
||||
for value in user_values {
|
||||
user_value_strings << value.bytestr()
|
||||
}
|
||||
|
||||
// Check all expected values are present
|
||||
assert 'data1' in user_value_strings
|
||||
assert 'data2' in user_value_strings
|
||||
assert 'data3' in user_value_strings
|
||||
|
||||
// Test getall with 'admin_' prefix
|
||||
//console.print_debug('Testing getall with prefix "admin_"')
|
||||
admin_values := rt.getall('admin_')!
|
||||
//console.print_debug('admin_values count: ${admin_values.len}')
|
||||
|
||||
// Should return 2 values
|
||||
assert admin_values.len == 2
|
||||
|
||||
// Convert byte arrays to strings for easier comparison
|
||||
mut admin_value_strings := []string{}
|
||||
for value in admin_values {
|
||||
admin_value_strings << value.bytestr()
|
||||
}
|
||||
|
||||
// Check all expected values are present
|
||||
assert 'admin_data1' in admin_value_strings
|
||||
assert 'admin_data2' in admin_value_strings
|
||||
|
||||
// Test getall with empty prefix (should return all values)
|
||||
//console.print_debug('Testing getall with empty prefix')
|
||||
all_values := rt.getall('')!
|
||||
//console.print_debug('all_values count: ${all_values.len}')
|
||||
|
||||
// Should return all 6 values
|
||||
assert all_values.len == test_data.len
|
||||
|
||||
// Test getall with non-existent prefix
|
||||
//console.print_debug('Testing getall with non-existent prefix "xyz"')
|
||||
non_existent_values := rt.getall('xyz')!
|
||||
//console.print_debug('non_existent_values count: ${non_existent_values.len}')
|
||||
|
||||
// Should return empty array
|
||||
assert non_existent_values.len == 0
|
||||
|
||||
//console.print_debug('test_getall completed successfully')
|
||||
}
|
||||
197
lib/data/radixtree/prefix_test.v
Normal file
197
lib/data/radixtree/prefix_test.v
Normal file
@@ -0,0 +1,197 @@
|
||||
module radixtree
|
||||
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
|
||||
fn test_list() ! {
|
||||
//console.print_debug('Starting test_list')
|
||||
mut rt := new(path: '/tmp/radixtree_prefix_test', reset: true)!
|
||||
|
||||
// Insert keys with various prefixes
|
||||
test_data := {
|
||||
'apple': 'fruit1',
|
||||
'application': 'software1',
|
||||
'apply': 'verb1',
|
||||
'banana': 'fruit2',
|
||||
'ball': 'toy1',
|
||||
'cat': 'animal1',
|
||||
'car': 'vehicle1',
|
||||
'cargo': 'shipping1'
|
||||
}
|
||||
|
||||
// Set all test data
|
||||
for key, value in test_data {
|
||||
rt.set(key, value.bytes())!
|
||||
}
|
||||
|
||||
// Test prefix 'app' - should return apple, application, apply
|
||||
//console.print_debug('Testing prefix "app"')
|
||||
app_keys := rt.list('app')!
|
||||
//console.print_debug('app_keys: ${app_keys}')
|
||||
assert app_keys.len == 3
|
||||
assert 'apple' in app_keys
|
||||
assert 'application' in app_keys
|
||||
assert 'apply' in app_keys
|
||||
|
||||
// Test prefix 'ba' - should return banana, ball
|
||||
//console.print_debug('Testing prefix "ba"')
|
||||
ba_keys := rt.list('ba')!
|
||||
//console.print_debug('ba_keys: ${ba_keys}')
|
||||
assert ba_keys.len == 2
|
||||
assert 'banana' in ba_keys
|
||||
assert 'ball' in ba_keys
|
||||
|
||||
// Test prefix 'car' - should return car, cargo
|
||||
//console.print_debug('Testing prefix "car"')
|
||||
car_keys := rt.list('car')!
|
||||
//console.print_debug('car_keys: ${car_keys}')
|
||||
assert car_keys.len == 2
|
||||
assert 'car' in car_keys
|
||||
assert 'cargo' in car_keys
|
||||
|
||||
// Test prefix 'z' - should return empty list
|
||||
//console.print_debug('Testing prefix "z"')
|
||||
z_keys := rt.list('z')!
|
||||
//console.print_debug('z_keys: ${z_keys}')
|
||||
assert z_keys.len == 0
|
||||
|
||||
// Test empty prefix - should return all keys
|
||||
//console.print_debug('Testing empty prefix')
|
||||
all_keys := rt.list('')!
|
||||
//console.print_debug('all_keys: ${all_keys}')
|
||||
assert all_keys.len == test_data.len
|
||||
for key in test_data.keys() {
|
||||
assert key in all_keys
|
||||
}
|
||||
|
||||
// Test exact key as prefix - should return just that key
|
||||
//console.print_debug('Testing exact key as prefix "apple"')
|
||||
exact_key := rt.list('apple')!
|
||||
//console.print_debug('exact_key: ${exact_key}')
|
||||
assert exact_key.len == 1
|
||||
assert exact_key[0] == 'apple'
|
||||
//console.print_debug('test_list completed successfully')
|
||||
}
|
||||
|
||||
fn test_list_with_deletion() ! {
|
||||
//console.print_debug('Starting test_list_with_deletion')
|
||||
mut rt := new(path: '/tmp/radixtree_prefix_deletion_test', reset: true)!
|
||||
|
||||
// Set keys with common prefixes
|
||||
rt.set('test1', 'value1'.bytes())!
|
||||
rt.set('test2', 'value2'.bytes())!
|
||||
rt.set('test3', 'value3'.bytes())!
|
||||
rt.set('other', 'value4'.bytes())!
|
||||
|
||||
// Initial check
|
||||
//console.print_debug('Testing initial prefix "test"')
|
||||
test_keys := rt.list('test')!
|
||||
//console.print_debug('test_keys: ${test_keys}')
|
||||
assert test_keys.len == 3
|
||||
assert 'test1' in test_keys
|
||||
assert 'test2' in test_keys
|
||||
assert 'test3' in test_keys
|
||||
|
||||
// Delete one key
|
||||
//console.print_debug('Deleting key "test2"')
|
||||
rt.delete('test2')!
|
||||
|
||||
// Check after deletion
|
||||
//console.print_debug('Testing prefix "test" after deletion')
|
||||
test_keys_after := rt.list('test')!
|
||||
//console.print_debug('test_keys_after: ${test_keys_after}')
|
||||
assert test_keys_after.len == 2
|
||||
assert 'test1' in test_keys_after
|
||||
assert 'test2' !in test_keys_after
|
||||
assert 'test3' in test_keys_after
|
||||
|
||||
// Check all keys
|
||||
//console.print_debug('Testing empty prefix')
|
||||
all_keys := rt.list('')!
|
||||
//console.print_debug('all_keys: ${all_keys}')
|
||||
assert all_keys.len == 3
|
||||
assert 'other' in all_keys
|
||||
//console.print_debug('test_list_with_deletion completed successfully')
|
||||
}
|
||||
|
||||
fn test_list_edge_cases() ! {
|
||||
//console.print_debug('Starting test_list_edge_cases')
|
||||
mut rt := new(path: '/tmp/radixtree_prefix_edge_test', reset: true)!
|
||||
|
||||
// Test with empty tree
|
||||
//console.print_debug('Testing empty tree with prefix "any"')
|
||||
empty_result := rt.list('any')!
|
||||
//console.print_debug('empty_result: ${empty_result}')
|
||||
assert empty_result.len == 0
|
||||
|
||||
// Set a single key
|
||||
//console.print_debug('Setting single key "single"')
|
||||
rt.set('single', 'value'.bytes())!
|
||||
|
||||
// Test with prefix that's longer than any key
|
||||
//console.print_debug('Testing prefix longer than any key "singlelonger"')
|
||||
long_prefix := rt.list('singlelonger')!
|
||||
//console.print_debug('long_prefix: ${long_prefix}')
|
||||
assert long_prefix.len == 0
|
||||
|
||||
// Test with partial prefix match
|
||||
//console.print_debug('Testing partial prefix match "sing"')
|
||||
partial := rt.list('sing')!
|
||||
//console.print_debug('partial: ${partial}')
|
||||
assert partial.len == 1
|
||||
assert partial[0] == 'single'
|
||||
|
||||
// Test with very long keys
|
||||
//console.print_debug('Testing with very long keys')
|
||||
long_key1 := 'a'.repeat(100) + 'key1'
|
||||
long_key2 := 'a'.repeat(100) + 'key2'
|
||||
|
||||
rt.set(long_key1, 'value1'.bytes())!
|
||||
rt.set(long_key2, 'value2'.bytes())!
|
||||
|
||||
//console.print_debug('Testing long prefix')
|
||||
long_prefix_result := rt.list('a'.repeat(100))!
|
||||
//console.print_debug('long_prefix_result: ${long_prefix_result}')
|
||||
assert long_prefix_result.len == 2
|
||||
assert long_key1 in long_prefix_result
|
||||
assert long_key2 in long_prefix_result
|
||||
//console.print_debug('test_list_edge_cases completed successfully')
|
||||
}
|
||||
|
||||
fn test_list_performance() ! {
|
||||
//console.print_debug('Starting test_list_performance')
|
||||
mut rt := new(path: '/tmp/radixtree_prefix_perf_test', reset: true)!
|
||||
|
||||
// Insert a large number of keys with different prefixes
|
||||
//console.print_debug('Setting up prefixes')
|
||||
prefixes := ['user', 'post', 'comment', 'like', 'share']
|
||||
|
||||
// Set 100 keys for each prefix (500 total)
|
||||
//console.print_debug('Setting 500 keys (100 for each prefix)')
|
||||
for prefix in prefixes {
|
||||
for i in 0..100 {
|
||||
key := '${prefix}_${i}'
|
||||
rt.set(key, 'value_${key}'.bytes())!
|
||||
}
|
||||
}
|
||||
|
||||
// Test retrieving by each prefix
|
||||
//console.print_debug('Testing retrieval by each prefix')
|
||||
for prefix in prefixes {
|
||||
//console.print_debug('Testing prefix "${prefix}"')
|
||||
keys := rt.list(prefix)!
|
||||
//console.print_debug('Found ${keys.len} keys for prefix "${prefix}"')
|
||||
assert keys.len == 100
|
||||
|
||||
// Verify all keys have the correct prefix
|
||||
for key in keys {
|
||||
assert key.starts_with(prefix)
|
||||
}
|
||||
}
|
||||
|
||||
// Test retrieving all keys
|
||||
//console.print_debug('Testing retrieval of all keys')
|
||||
all_keys := rt.list('')!
|
||||
//console.print_debug('Found ${all_keys.len} total keys')
|
||||
assert all_keys.len == 500
|
||||
//console.print_debug('test_list_performance completed successfully')
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
module radixtree
|
||||
|
||||
import freeflowuniverse.herolib.data.ourdb
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
|
||||
// Represents a node in the radix tree
|
||||
struct Node {
|
||||
@@ -42,9 +43,9 @@ pub fn new(args NewArgs) !&RadixTree {
|
||||
)!
|
||||
|
||||
mut root_id := u32(1) // First ID in ourdb is now 1 instead of 0
|
||||
println('Debug: Initializing root node')
|
||||
//console.print_debug('Debug: Initializing root node')
|
||||
if db.get_next_id()! == 1 {
|
||||
println('Debug: Creating new root node')
|
||||
//console.print_debug('Debug: Creating new root node')
|
||||
root := Node{
|
||||
key_segment: ''
|
||||
value: []u8{}
|
||||
@@ -52,13 +53,13 @@ pub fn new(args NewArgs) !&RadixTree {
|
||||
is_leaf: false
|
||||
}
|
||||
root_id = db.set(data: serialize_node(root))!
|
||||
println('Debug: Created root node with ID ${root_id}')
|
||||
//console.print_debug('Debug: Created root node with ID ${root_id}')
|
||||
assert root_id == 1 // First ID is now 1
|
||||
} else {
|
||||
println('Debug: Using existing root node')
|
||||
//console.print_debug('Debug: Using existing root node')
|
||||
root_data := db.get(1)! // Get root node with ID 1
|
||||
root_node := deserialize_node(root_data)!
|
||||
println('Debug: Root node has ${root_node.children.len} children')
|
||||
//console.print_debug('Debug: Root node has ${root_node.children.len} children')
|
||||
}
|
||||
|
||||
return &RadixTree{
|
||||
@@ -67,8 +68,8 @@ pub fn new(args NewArgs) !&RadixTree {
|
||||
}
|
||||
}
|
||||
|
||||
// Inserts a key-value pair into the tree
|
||||
pub fn (mut rt RadixTree) insert(key string, value []u8) ! {
|
||||
// Sets a key-value pair in the tree
|
||||
pub fn (mut rt RadixTree) set(key string, value []u8) ! {
|
||||
mut current_id := rt.root_id
|
||||
mut offset := 0
|
||||
|
||||
@@ -102,56 +103,56 @@ pub fn (mut rt RadixTree) insert(key string, value []u8) ! {
|
||||
children: []NodeRef{}
|
||||
is_leaf: true
|
||||
}
|
||||
println('Debug: Creating new leaf node with key_part "${key_part}"')
|
||||
//console.print_debug('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}')
|
||||
//console.print_debug('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')
|
||||
//console.print_debug('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')
|
||||
//console.print_debug('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
|
||||
}
|
||||
println('Debug: Added child reference, now has ${parent_node.children.len} children')
|
||||
//console.print_debug('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')
|
||||
//console.print_debug('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')
|
||||
//console.print_debug('Debug: Parent data size: ${parent_data.len} bytes')
|
||||
|
||||
// First verify we can deserialize the data correctly
|
||||
println('Debug: Verifying serialization...')
|
||||
//console.print_debug('Debug: Verifying serialization...')
|
||||
if test_node := deserialize_node(parent_data) {
|
||||
println('Debug: Serialization test successful - node has ${test_node.children.len} children')
|
||||
//console.print_debug('Debug: Serialization test successful - node has ${test_node.children.len} children')
|
||||
} else {
|
||||
println('Debug: ERROR - Failed to deserialize test data')
|
||||
//console.print_debug('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...')
|
||||
//console.print_debug('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...')
|
||||
//console.print_debug('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')
|
||||
//console.print_debug('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}')
|
||||
println('Debug: Parent node children: ${parent_node.children.len}')
|
||||
println('Debug: Verified node children: ${verify_node.children.len}')
|
||||
println('Debug: Original data size: ${parent_data.len}')
|
||||
println('Debug: Verified data size: ${verify_data.len}')
|
||||
println('Debug: Data equal: ${verify_data == parent_data}')
|
||||
//console.print_debug('Debug: ERROR - Node update verification failed!')
|
||||
//console.print_debug('Debug: Original node children: ${node.children.len}')
|
||||
//console.print_debug('Debug: Parent node children: ${parent_node.children.len}')
|
||||
//console.print_debug('Debug: Verified node children: ${verify_node.children.len}')
|
||||
//console.print_debug('Debug: Original data size: ${parent_data.len}')
|
||||
//console.print_debug('Debug: Verified data size: ${verify_data.len}')
|
||||
//console.print_debug('Debug: Data equal: ${verify_data == parent_data}')
|
||||
return error('Node update failed - children array is empty')
|
||||
}
|
||||
return
|
||||
@@ -195,8 +196,8 @@ pub fn (mut rt RadixTree) insert(key string, value []u8) ! {
|
||||
}
|
||||
}
|
||||
|
||||
// Searches for a key in the tree
|
||||
pub fn (mut rt RadixTree) search(key string) ![]u8 {
|
||||
// Gets a value by key from the tree
|
||||
pub fn (mut rt RadixTree) get(key string) ![]u8 {
|
||||
mut current_id := rt.root_id
|
||||
mut offset := 0
|
||||
|
||||
@@ -316,13 +317,18 @@ pub fn (mut rt RadixTree) delete(key string) ! {
|
||||
return error('Key not found')
|
||||
}
|
||||
|
||||
// Remove the leaf node
|
||||
// Get the node to delete
|
||||
mut last_node := deserialize_node(rt.db.get(path.last().node_id)!)!
|
||||
|
||||
// If the node has children, just mark it as non-leaf
|
||||
if last_node.children.len > 0 {
|
||||
last_node.is_leaf = false
|
||||
last_node.value = []u8{}
|
||||
rt.db.set(id: path.last().node_id, data: serialize_node(last_node))!
|
||||
return
|
||||
}
|
||||
|
||||
// If node has no children, remove it from parent
|
||||
if last_node.children.len == 0 {
|
||||
if path.len > 1 {
|
||||
mut parent_node := deserialize_node(rt.db.get(path[path.len - 2].node_id)!)!
|
||||
for i, child in parent_node.children {
|
||||
@@ -332,12 +338,106 @@ pub fn (mut rt RadixTree) delete(key string) ! {
|
||||
}
|
||||
}
|
||||
rt.db.set(id: path[path.len - 2].node_id, data: serialize_node(parent_node))!
|
||||
}
|
||||
|
||||
// Delete the node from the database
|
||||
rt.db.delete(path.last().node_id)!
|
||||
} else {
|
||||
// If this is a direct child of the root, just mark it as non-leaf
|
||||
last_node.is_leaf = false
|
||||
last_node.value = []u8{}
|
||||
rt.db.set(id: path.last().node_id, data: serialize_node(last_node))!
|
||||
}
|
||||
}
|
||||
|
||||
// Lists all keys with a given prefix
|
||||
pub fn (mut rt RadixTree) list(prefix string) ![]string {
|
||||
mut result := []string{}
|
||||
|
||||
// Handle empty prefix case - will return all keys
|
||||
if prefix.len == 0 {
|
||||
rt.collect_all_keys(rt.root_id, '', mut result)!
|
||||
return result
|
||||
}
|
||||
|
||||
// Start from the root and find all matching keys
|
||||
rt.find_keys_with_prefix(rt.root_id, '', prefix, mut result)!
|
||||
return result
|
||||
}
|
||||
|
||||
// Helper function to find all keys with a given prefix
|
||||
fn (mut rt RadixTree) find_keys_with_prefix(node_id u32, current_path string, prefix string, mut result []string) ! {
|
||||
node := deserialize_node(rt.db.get(node_id)!)!
|
||||
|
||||
// If the current path already matches or exceeds the prefix length
|
||||
if current_path.len >= prefix.len {
|
||||
// Check if the current path starts with the prefix
|
||||
if current_path.starts_with(prefix) {
|
||||
// If this is a leaf node, add it to the results
|
||||
if node.is_leaf {
|
||||
result << current_path
|
||||
}
|
||||
|
||||
// Collect all keys from this subtree
|
||||
for child in node.children {
|
||||
child_path := current_path + child.key_part
|
||||
rt.find_keys_with_prefix(child.node_id, child_path, prefix, mut result)!
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Current path is shorter than the prefix, continue searching
|
||||
for child in node.children {
|
||||
child_path := current_path + child.key_part
|
||||
|
||||
// Check if this child's path could potentially match the prefix
|
||||
if prefix.starts_with(current_path) {
|
||||
// The prefix starts with the current path, so we need to check if
|
||||
// the child's key_part matches the next part of the prefix
|
||||
prefix_remainder := prefix[current_path.len..]
|
||||
|
||||
// If the prefix remainder starts with the child's key_part or vice versa
|
||||
if prefix_remainder.starts_with(child.key_part) ||
|
||||
(child.key_part.starts_with(prefix_remainder) && child.key_part.len >= prefix_remainder.len) {
|
||||
rt.find_keys_with_prefix(child.node_id, child_path, prefix, mut result)!
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to recursively collect all keys under a node
|
||||
fn (mut rt RadixTree) collect_all_keys(node_id u32, current_path string, mut result []string) ! {
|
||||
node := deserialize_node(rt.db.get(node_id)!)!
|
||||
|
||||
// If this node is a leaf, add its path to the result
|
||||
if node.is_leaf {
|
||||
result << current_path
|
||||
}
|
||||
|
||||
// Recursively collect keys from all children
|
||||
for child in node.children {
|
||||
child_path := current_path + child.key_part
|
||||
rt.collect_all_keys(child.node_id, child_path, mut result)!
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to get the common prefix of two strings
|
||||
// Gets all values for keys with a given prefix
|
||||
pub fn (mut rt RadixTree) getall(prefix string) ![][]u8 {
|
||||
// Get all matching keys
|
||||
keys := rt.list(prefix)!
|
||||
|
||||
// Get values for each key
|
||||
mut values := [][]u8{}
|
||||
for key in keys {
|
||||
if value := rt.get(key) {
|
||||
values << value
|
||||
}
|
||||
}
|
||||
|
||||
return values
|
||||
}
|
||||
|
||||
// Helper function to get the common prefix of two strings
|
||||
fn get_common_prefix(a string, b string) string {
|
||||
mut i := 0
|
||||
|
||||
@@ -1,48 +1,49 @@
|
||||
module radixtree
|
||||
|
||||
import freeflowuniverse.herolib.data.ourdb
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
|
||||
// Gets a node from the database by its ID
|
||||
pub fn (mut rt RadixTree) get_node_by_id(id u32) !Node {
|
||||
node_data := rt.db.get(id)!
|
||||
node := deserialize_node(node_data)!
|
||||
println('Debug: Retrieved node ${id} with ${node.children.len} children')
|
||||
//console.print_debug('Debug: Retrieved node ${id} with ${node.children.len} children')
|
||||
return node
|
||||
}
|
||||
|
||||
// Logs the current state of a node
|
||||
pub fn (mut rt RadixTree) debug_node(id u32, msg string) ! {
|
||||
node := rt.get_node_by_id(id)!
|
||||
println('Debug: ${msg}')
|
||||
println(' Node ID: ${id}')
|
||||
println(' Key Segment: "${node.key_segment}"')
|
||||
println(' Is Leaf: ${node.is_leaf}')
|
||||
println(' Children: ${node.children.len}')
|
||||
//console.print_debug('Debug: ${msg}')
|
||||
//console.print_debug(' Node ID: ${id}')
|
||||
//console.print_debug(' Key Segment: "${node.key_segment}"')
|
||||
//console.print_debug(' Is Leaf: ${node.is_leaf}')
|
||||
//console.print_debug(' Children: ${node.children.len}')
|
||||
for child in node.children {
|
||||
println(' - Child ID: ${child.node_id}, Key Part: "${child.key_part}"')
|
||||
//console.print_debug(' - Child ID: ${child.node_id}, Key Part: "${child.key_part}"')
|
||||
}
|
||||
}
|
||||
|
||||
// Prints the current state of the database
|
||||
pub fn (mut rt RadixTree) debug_db() ! {
|
||||
println('\nDatabase State:')
|
||||
println('===============')
|
||||
//console.print_debug('\nDatabase State:')
|
||||
//console.print_debug('===============')
|
||||
mut next_id := rt.db.get_next_id()!
|
||||
for id := u32(0); id < next_id; id++ {
|
||||
if data := rt.db.get(id) {
|
||||
if node := deserialize_node(data) {
|
||||
println('ID ${id}:')
|
||||
println(' Key Segment: "${node.key_segment}"')
|
||||
println(' Is Leaf: ${node.is_leaf}')
|
||||
println(' Children: ${node.children.len}')
|
||||
//console.print_debug('ID ${id}:')
|
||||
//console.print_debug(' Key Segment: "${node.key_segment}"')
|
||||
//console.print_debug(' Is Leaf: ${node.is_leaf}')
|
||||
//console.print_debug(' Children: ${node.children.len}')
|
||||
for child in node.children {
|
||||
println(' - Child ID: ${child.node_id}, Key Part: "${child.key_part}"')
|
||||
//console.print_debug(' - Child ID: ${child.node_id}, Key Part: "${child.key_part}"')
|
||||
}
|
||||
} else {
|
||||
println('ID ${id}: Failed to deserialize node')
|
||||
//console.print_debug('ID ${id}: Failed to deserialize node')
|
||||
}
|
||||
} else {
|
||||
println('ID ${id}: No data')
|
||||
//console.print_debug('ID ${id}: No data')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,7 +69,7 @@ pub fn (mut rt RadixTree) print_tree_from_node(node_id u32, indent string) ! {
|
||||
}
|
||||
node_info += ']'
|
||||
}
|
||||
println(node_info)
|
||||
//console.print_debug(node_info)
|
||||
|
||||
// Print children recursively with increased indentation
|
||||
for i, child in node.children {
|
||||
@@ -84,8 +85,8 @@ pub fn (mut rt RadixTree) print_tree_from_node(node_id u32, indent string) ! {
|
||||
|
||||
// Prints the entire tree structure starting from root
|
||||
pub fn (mut rt RadixTree) print_tree() ! {
|
||||
println('\nRadix Tree Structure:')
|
||||
println('===================')
|
||||
//console.print_debug('\nRadix Tree Structure:')
|
||||
//console.print_debug('===================')
|
||||
rt.print_tree_from_node(rt.root_id, '')!
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user