diff --git a/lib/core/jobs/model/README.md b/lib/core/jobs/model/core/README.md similarity index 100% rename from lib/core/jobs/model/README.md rename to lib/core/jobs/model/core/README.md diff --git a/lib/core/jobs/model/agent.v b/lib/core/jobs/model/core/agent.v similarity index 100% rename from lib/core/jobs/model/agent.v rename to lib/core/jobs/model/core/agent.v diff --git a/lib/core/jobs/model/agent_manager.v b/lib/core/jobs/model/core/agent_manager.v similarity index 100% rename from lib/core/jobs/model/agent_manager.v rename to lib/core/jobs/model/core/agent_manager.v diff --git a/lib/core/jobs/model/agent_manager_test.v b/lib/core/jobs/model/core/agent_manager_test.v similarity index 100% rename from lib/core/jobs/model/agent_manager_test.v rename to lib/core/jobs/model/core/agent_manager_test.v diff --git a/lib/core/jobs/model/agent_test.v b/lib/core/jobs/model/core/agent_test.v similarity index 100% rename from lib/core/jobs/model/agent_test.v rename to lib/core/jobs/model/core/agent_test.v diff --git a/lib/core/jobs/model/circle.v b/lib/core/jobs/model/core/circle.v similarity index 100% rename from lib/core/jobs/model/circle.v rename to lib/core/jobs/model/core/circle.v diff --git a/lib/core/jobs/model/circle_manager.v b/lib/core/jobs/model/core/circle_manager.v similarity index 100% rename from lib/core/jobs/model/circle_manager.v rename to lib/core/jobs/model/core/circle_manager.v diff --git a/lib/core/jobs/model/circle_manager_test.v b/lib/core/jobs/model/core/circle_manager_test.v similarity index 100% rename from lib/core/jobs/model/circle_manager_test.v rename to lib/core/jobs/model/core/circle_manager_test.v diff --git a/lib/core/jobs/model/circle_test.v b/lib/core/jobs/model/core/circle_test.v similarity index 100% rename from lib/core/jobs/model/circle_test.v rename to lib/core/jobs/model/core/circle_test.v diff --git a/lib/core/jobs/model/factory.v b/lib/core/jobs/model/core/factory.v similarity index 100% rename from lib/core/jobs/model/factory.v rename to lib/core/jobs/model/core/factory.v diff --git a/lib/core/jobs/model/manager_generic.v b/lib/core/jobs/model/core/manager_generic.v similarity index 100% rename from lib/core/jobs/model/manager_generic.v rename to lib/core/jobs/model/core/manager_generic.v diff --git a/lib/core/jobs/model/name.v b/lib/core/jobs/model/core/name.v similarity index 100% rename from lib/core/jobs/model/name.v rename to lib/core/jobs/model/core/name.v diff --git a/lib/core/jobs/model/name_manager.v b/lib/core/jobs/model/core/name_manager.v similarity index 100% rename from lib/core/jobs/model/name_manager.v rename to lib/core/jobs/model/core/name_manager.v diff --git a/lib/core/jobs/model/name_manager_test.v b/lib/core/jobs/model/core/name_manager_test.v similarity index 100% rename from lib/core/jobs/model/name_manager_test.v rename to lib/core/jobs/model/core/name_manager_test.v diff --git a/lib/core/jobs/model/name_test.v b/lib/core/jobs/model/core/name_test.v similarity index 100% rename from lib/core/jobs/model/name_test.v rename to lib/core/jobs/model/core/name_test.v diff --git a/lib/data/radixtree/README.md b/lib/data/radixtree/README.md index 17b2c3a9..68dbfbc2 100644 --- a/lib/data/radixtree/README.md +++ b/lib/data/radixtree/README.md @@ -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')! ``` diff --git a/lib/data/radixtree/factory_test.v b/lib/data/radixtree/factory_test.v index e75b53e2..8c9786eb 100644 --- a/lib/data/radixtree/factory_test.v +++ b/lib/data/radixtree/factory_test.v @@ -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}' } } diff --git a/lib/data/radixtree/getall_test.v b/lib/data/radixtree/getall_test.v new file mode 100644 index 00000000..89cff0b3 --- /dev/null +++ b/lib/data/radixtree/getall_test.v @@ -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') +} diff --git a/lib/data/radixtree/prefix_test.v b/lib/data/radixtree/prefix_test.v new file mode 100644 index 00000000..58ea472c --- /dev/null +++ b/lib/data/radixtree/prefix_test.v @@ -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') +} diff --git a/lib/data/radixtree/radixtree.v b/lib/data/radixtree/radixtree.v index 124d5b8a..54e53020 100644 --- a/lib/data/radixtree/radixtree.v +++ b/lib/data/radixtree/radixtree.v @@ -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,28 +317,127 @@ 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)!)! - last_node.is_leaf = false - last_node.value = []u8{} + + // 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 { - if child.node_id == path.last().node_id { - parent_node.children.delete(i) - break - } + 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 { + if child.node_id == path.last().node_id { + parent_node.children.delete(i) + break } - rt.db.set(id: path[path.len - 2].node_id, data: serialize_node(parent_node))! } + 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 diff --git a/lib/data/radixtree/radixtree_debug.v b/lib/data/radixtree/radixtree_debug.v index c00f1ad2..4f3970ed 100644 --- a/lib/data/radixtree/radixtree_debug.v +++ b/lib/data/radixtree/radixtree_debug.v @@ -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, '')! }