Under MVCC (multi-version concurrency control), a database never overwrites a row in place -- it creates a new version and keeps the old one around, then decides which version each transaction should see based on a snapshot. This is how Postgres gives every transaction a consistent view of the database without taking read locks.
Each row version carries created_by (the transaction that inserted it) and
deleted_by (the transaction that deleted it, or None). A snapshot is:
{"own_xid": <this transaction's id>,
"next_xid": <smallest xid that hadn't started yet when the snapshot was taken>,
"active_xids": <set of xids that were running, not yet committed, at snapshot time>}
Implement is_row_visible(row, snapshot) (and the is_xid_visible helper it
needs): a transaction's effects are visible to a snapshot if it's the
snapshot's own transaction, or it committed strictly before the snapshot was
taken. A row version is visible if its creation is visible AND its deletion (if
any) is not visible.
To keep this scoped: assume every xid below next_xid that isn't in
active_xids committed (this exercise ignores aborted transactions).
Examples
snap = {"own_xid": 10, "next_xid": 10, "active_xids": set()}
is_row_visible({"created_by": 5, "deleted_by": None}, snap) # -> True (committed before snapshot)
is_row_visible({"created_by": 15, "deleted_by": None}, snap) # -> False (didn't exist yet)
snap2 = {"own_xid": 10, "next_xid": 10, "active_xids": {8}}
is_row_visible({"created_by": 3, "deleted_by": 8}, snap2) # -> True (deleted by an
# in-progress txn -- not visible yet)
Real-world grounding
- Postgres's actual (much more detailed) visibility check:
heapam_visibility.c
Real Postgres additionally handles subtransactions, aborted transactions (via a commit log), and several isolation levels beyond snapshot isolation -- this exercise is the core rule underneath all of that.