9.6 KiB
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 callingfree - 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 modifykv_store_t *const store→ can modify store, cannot change the pointer itselfkv_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:
TYPE *kv_store_entry_init(...)
// Returns pointer to newly allocated struct
// Caller must call kv_store_entry_free() when done
Freeing:
void kv_store_entry_free(TYPE *ptr)
// Takes pointer to free
// Should be NULL-safe (check for NULL before free)
Reading:
TYPE *kv_store_get_entry(...)
// Returns newly allocated COPY of entry
// Caller must free the returned pointer
Modifying:
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:
kv_store_init(capacity)- Create empty storekv_store_set_entry(store, entry)- Add or updatekv_store_get_entry(store, key)- Retrieve (returns copy)kv_store_delete_entry(store, key)- Removekv_store_free(store)- Clean up
Entry operations:
kv_store_entry_init(key, value)- Create entrykv_store_entry_copy(entry)- Deep copykv_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:
cli_print_help()- Display GNU-style helpcli_execute(argc, argv)- Parse and execute commandcli_print_result(result)- Format output
Behavior:
main.cchecks for--helpor-hfirst, before callingcli_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_twith status and message
Future commands (not yet implemented):
kvstore get <key> # Retrieve a value
kvstore set <key> <value> # Store a value
kvstore delete <key> # 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:
string_copy(src)- Allocate and copystring_compare(s1, s2)- Like strcmpstring_trim(str)- Copy with whitespace trimmedstring_search(src, search)- Find substring (returns index or -1)string_free(str)- Safe free (NULL-safe)
Use case: These are helpers for CLI parsing and kvstore string operations.
Implementation Roadmap
Week 1: Foundation
-
Implement
string.cfunctionsstring_copy()- basic malloc + strcpy wrapperstring_compare()- wrapper for strcmpstring_free()- free with NULL check- Test manually in main
-
Implement
kv_store.cbasicskv_store_entry_init()- allocate entry, copy stringskv_store_entry_free()- free both key and valuekv_store_entry_copy()- allocate and copykv_store_init()- allocate store, allocate empty entries arraykv_store_free()- free all entries, then store
-
Test with simple program in main.c
Week 2: Core CRUD
-
kv_store_get_entry()- Linear search, return copy -
kv_store_set_entry()- Find or append, copy input -
kv_store_delete_entry()- Find and remove -
Add array resizing when capacity is reached
-
Test each operation manually
Week 3: CLI Integration
- Implement
cli_print_help()- GNU-style output - Implement
cli_execute()- Command parsing - Wire commands to store operations
- Update
main.cto check for help flag - Test:
./kvstore --help,./kvstore set x y,./kvstore get x
Week 4+: Polish & Future
- Add
string_trim()andstring_search()if needed - Consider testing framework (Unity)
- File persistence (load/save)
- Performance improvements (hash table)
Common Patterns & Idioms
Safe pointer casting in free functions
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
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)
if (store->length == store->capacity) {
store->capacity *= 2;
store->entries = realloc(store->entries,
store->capacity * sizeof(kv_store_entry_t));
}
Returning allocated struct
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
- Valgrind for memory leaks:
valgrind ./bin/kvstore - GDB for stepping:
gdb ./bin/kvstore - Add printf statements to trace execution
- Check return codes - most functions signal errors via -1 or NULL
- Test edge cases: NULL inputs, empty store, duplicate keys
Common Mistakes to Avoid
- Forgetting to copy strings - Just assigning pointers will cause use-after-free
- Not NULL-checking allocations - malloc can fail
- Confusion on ownership - Who allocated this? Who must free it?
- Buffer overruns - When resizing array, check capacity is adequate
- Off-by-one in loops - Easy to miss the last entry or go past array bounds
- const correctness - If you can't modify, declare const
Testing Strategy (Future)
When you add Unity tests:
// 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 NULLstring_compare()- 0 if equal, negative/positive if differentstring_trim()- Return malloc'd trimmed copy or NULLstring_search()- Return index or -1string_free()- Free with NULL check
kv_store.c
kv_store_entry_init()- Allocate entry, copy stringskv_store_entry_copy()- Allocate copy, copy stringskv_store_entry_free()- Free key, value, then entrykv_store_init()- Allocate store and entries arraykv_store_free()- Free all entries, then storekv_store_get_entry()- Find and return copy (or NULL)kv_store_set_entry()- Find/add and copy, return 0/1/-1kv_store_delete_entry()- Find/remove, return 1/0/-1
cli.c
cli_print_help()- Print usage to stdoutcli_execute()- Parse argv, call store ops, return resultcli_print_result()- Format and print result
Notes for AI Assistants
If you're helping with this project:
- Focus on learning: Provide hints and clarifications, not complete solutions
- Respect the design: Use the patterns and conventions established
- Memory safety: Always think about allocation, ownership, and freeing
- Testing mindset: Suggest test cases and edge cases
- Documentation: Functions should have clear comments
- Error handling: Follow the 1/0/-1 pattern consistently
The goal is building understanding, not just shipping code.