# Development Guide This document captures the design decisions, architecture, and implementation strategy for the kvstore project. Use this when returning to the project or working with an AI assistant. ## Project Philosophy **Learning First**: This is an educational project prioritizing understanding over optimization. **Explicit Over Implicit**: C makes you manage memory explicitly. We lean into this—every allocation and free is intentional and documented. **Batch Processing**: The application runs, executes one command, and exits. No persistent state between invocations (yet). **String-Only Storage**: Keys and values are always strings. Simple but sufficient for the learning phase. ## Current Design State ### Data Model ``` Entry: key (char*) -> value (char*) Store: Dynamic array of entries with length and capacity tracking ``` Key insight: The store is not a hash table yet—it's a simple array. This means O(n) lookups, but it's easier to understand and implement. Future evolution: hash table or B-tree. ### Memory Ownership Rules **Rule 1: Allocation implies ownership** - Whoever calls `malloc` (directly or indirectly) is responsible for calling `free` - Functions document what they allocate and return **Rule 2: Deep copying on boundaries** - When you pass data to the store, it makes its own copy - When you retrieve data from the store, you get a copy - This prevents external code from accidentally modifying internal state **Rule 3: Pointer parameters indicate mutability** - `const kv_store_t *store` → function only reads, cannot modify - `kv_store_t *const store` → can modify store, cannot change the pointer itself - `kv_store_t *store` → can modify store (rare in read-only functions) ### Error Handling Convention All kvstore operations return: ``` 1 = success / found 0 = not found / created new / no error (but different result) -1 = error (allocation failed, etc.) ``` Never throw exceptions. Use return codes. Allocation failures return NULL pointers. Always check before dereferencing. ### Function Patterns **Creating allocations:** ```c TYPE *kv_store_entry_init(...) // Returns pointer to newly allocated struct // Caller must call kv_store_entry_free() when done ``` **Freeing:** ```c void kv_store_entry_free(TYPE *ptr) // Takes pointer to free // Should be NULL-safe (check for NULL before free) ``` **Reading:** ```c TYPE *kv_store_get_entry(...) // Returns newly allocated COPY of entry // Caller must free the returned pointer ``` **Modifying:** ```c int kv_store_set_entry(TYPE *store, const TYPE *input) // Takes pointer to store and const input // Makes its own copy of input data // Returns status code (1/0/-1) ``` ## Module Breakdown ### kv_store (src/kv_store.c / include/kv_store.h) **Current State**: Interface complete, implementation needed **Core operations**: 1. `kv_store_init(capacity)` - Create empty store 2. `kv_store_set_entry(store, entry)` - Add or update 3. `kv_store_get_entry(store, key)` - Retrieve (returns copy) 4. `kv_store_delete_entry(store, key)` - Remove 5. `kv_store_free(store)` - Clean up **Entry operations**: 1. `kv_store_entry_init(key, value)` - Create entry 2. `kv_store_entry_copy(entry)` - Deep copy 3. `kv_store_entry_free(entry)` - Cleanup **Implementation considerations**: - Entries array may need resizing (when length reaches capacity) - Need way to find entries by key (linear search for now: O(n)) - Deletion might use swap-and-pop or mark-as-deleted - All strings must be copied, not just pointer assignments ### cli (src/cli.c / include/cli.h) **Current State**: Interface complete, minimal implementation **Key functions**: 1. `cli_print_help()` - Display GNU-style help 2. `cli_execute(argc, argv)` - Parse and execute command 3. `cli_print_result(result)` - Format output **Behavior**: - `main.c` checks for `--help` or `-h` first, before calling `cli_execute` - If help found, call `cli_print_help()` and exit(0) - Otherwise, pass full argv to `cli_execute()` - `cli_execute()` parses first argv (after program name) as command - Routes to appropriate store operation - Returns `cli_result_t` with status and message **Future commands** (not yet implemented): ``` kvstore get # Retrieve a value kvstore set # Store a value kvstore delete # Remove a value kvstore list # Show all entries kvstore --help, -h # Show help ``` ### string (src/string.c / include/string.h) **Current State**: Interface complete, implementation needed **Functions**: 1. `string_copy(src)` - Allocate and copy 2. `string_compare(s1, s2)` - Like strcmp 3. `string_trim(str)` - Copy with whitespace trimmed 4. `string_search(src, search)` - Find substring (returns index or -1) 5. `string_free(str)` - Safe free (NULL-safe) **Use case**: These are helpers for CLI parsing and kvstore string operations. ## Implementation Roadmap ### Week 1: Foundation 1. Implement `string.c` functions - `string_copy()` - basic malloc + strcpy wrapper - `string_compare()` - wrapper for strcmp - `string_free()` - free with NULL check - Test manually in main 2. Implement `kv_store.c` basics - `kv_store_entry_init()` - allocate entry, copy strings - `kv_store_entry_free()` - free both key and value - `kv_store_entry_copy()` - allocate and copy - `kv_store_init()` - allocate store, allocate empty entries array - `kv_store_free()` - free all entries, then store 3. Test with simple program in main.c ### Week 2: Core CRUD 1. `kv_store_get_entry()` - Linear search, return copy 2. `kv_store_set_entry()` - Find or append, copy input 3. `kv_store_delete_entry()` - Find and remove 4. Add array resizing when capacity is reached 5. Test each operation manually ### Week 3: CLI Integration 1. Implement `cli_print_help()` - GNU-style output 2. Implement `cli_execute()` - Command parsing 3. Wire commands to store operations 4. Update `main.c` to check for help flag 5. Test: `./kvstore --help`, `./kvstore set x y`, `./kvstore get x` ### Week 4+: Polish & Future 1. Add `string_trim()` and `string_search()` if needed 2. Consider testing framework (Unity) 3. File persistence (load/save) 4. Performance improvements (hash table) ## Common Patterns & Idioms ### Safe pointer casting in free functions ```c void kv_store_entry_free(kv_store_entry_t *entry) { if (entry == NULL) return; // NULL-safe string_free(entry->key); string_free(entry->value); free(entry); } ``` ### Linear search pattern ```c for (int i = 0; i < store->length; i++) { if (string_compare(store->entries[i].key, key) == 0) { // Found at index i return i; } } // Not found return -1; ``` ### Array resizing pattern (for future) ```c if (store->length == store->capacity) { store->capacity *= 2; store->entries = realloc(store->entries, store->capacity * sizeof(kv_store_entry_t)); } ``` ### Returning allocated struct ```c kv_store_entry_t *copy = malloc(sizeof(kv_store_entry_t)); copy->key = string_copy(entry->key); copy->value = string_copy(entry->value); return copy; // Caller must free this ``` ## Debugging Tips 1. **Valgrind** for memory leaks: `valgrind ./bin/kvstore` 2. **GDB** for stepping: `gdb ./bin/kvstore` 3. **Add printf statements** to trace execution 4. **Check return codes** - most functions signal errors via -1 or NULL 5. **Test edge cases**: NULL inputs, empty store, duplicate keys ## Common Mistakes to Avoid 1. **Forgetting to copy strings** - Just assigning pointers will cause use-after-free 2. **Not NULL-checking allocations** - malloc can fail 3. **Confusion on ownership** - Who allocated this? Who must free it? 4. **Buffer overruns** - When resizing array, check capacity is adequate 5. **Off-by-one in loops** - Easy to miss the last entry or go past array bounds 6. **const correctness** - If you can't modify, declare const ## Testing Strategy (Future) When you add Unity tests: ```c // Test entry lifecycle TEST_ASSERT_NOT_NULL(entry); TEST_ASSERT_EQUAL_STRING("key", entry->key); // Test store operations TEST_ASSERT_EQUAL_INT(1, kv_store_set_entry(store, entry)); TEST_ASSERT_EQUAL_INT(1, kv_store_get_entry(store, "key", &out)); // Test edge cases TEST_ASSERT_EQUAL_INT(-1, kv_store_get_entry(NULL, "key", &out)); ``` ## Quick Reference: Function Checklist ### string.c - [ ] `string_copy()` - Return malloc'd copy or NULL - [ ] `string_compare()` - 0 if equal, negative/positive if different - [ ] `string_trim()` - Return malloc'd trimmed copy or NULL - [ ] `string_search()` - Return index or -1 - [ ] `string_free()` - Free with NULL check ### kv_store.c - [ ] `kv_store_entry_init()` - Allocate entry, copy strings - [ ] `kv_store_entry_copy()` - Allocate copy, copy strings - [ ] `kv_store_entry_free()` - Free key, value, then entry - [ ] `kv_store_init()` - Allocate store and entries array - [ ] `kv_store_free()` - Free all entries, then store - [ ] `kv_store_get_entry()` - Find and return copy (or NULL) - [ ] `kv_store_set_entry()` - Find/add and copy, return 0/1/-1 - [ ] `kv_store_delete_entry()` - Find/remove, return 1/0/-1 ### cli.c - [ ] `cli_print_help()` - Print usage to stdout - [ ] `cli_execute()` - Parse argv, call store ops, return result - [ ] `cli_print_result()` - Format and print result ## Notes for AI Assistants If you're helping with this project: 1. **Focus on learning**: Provide hints and clarifications, not complete solutions 2. **Respect the design**: Use the patterns and conventions established 3. **Memory safety**: Always think about allocation, ownership, and freeing 4. **Testing mindset**: Suggest test cases and edge cases 5. **Documentation**: Functions should have clear comments 6. **Error handling**: Follow the 1/0/-1 pattern consistently The goal is building understanding, not just shipping code.