Files
kvstore/DEVELOPMENT.md

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 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:

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:

  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

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

  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:

// 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.