Files
kvstore/DEVELOPMENT.md

297 lines
9.6 KiB
Markdown

# 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 <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**:
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.