step 01 — pager-and-rows

Goal

Build the in-memory primary container. By the end of this step you should have:

  • a Row { v, tag, created_at, deleted_at } value type,
  • a Conn with next_txid starting at 1 and an ordered primary: i64 -> Row map,
  • exec_insert (UPSERT) bumping next_txid every call,
  • select_by_k returning live rows only.

Why "pager-and-rows" not just "rows"

Even though we are not implementing a real on-disk pager here, the discipline of treating the primary map as the single source of truth for both visible and tombstoned state mirrors what a pager gives you: a flat, ordered store you can walk in key order.

If you wanted, you could later swap the in-memory std::map for a B-tree built on top of db-11's pager and not have to change anything else in this lab.

Tasks

  1. Define Row and Conn in your chosen language.
  2. Implement exec_insert with UPSERT semantics. Make sure that inserting at the same key twice replaces the row and uses the new txid in created_at.
  3. Implement select_by_k. It must filter out tombstoned rows.
  4. Write a unit test that inserts two keys, selects them back, and asserts next_txid == 3.

Pitfalls

  • If you use a hash map (Go map, C++ unordered_map), the wire test in step 03 will fail because iteration order will not be deterministic. Use an ordered map (BTreeMap, sorted-keys iteration, std::map).
  • Use i64 for k. i32 will silently truncate when the workload in step 03 mods a u64 by keys and casts.