Exercise 1: Rolling Back a Newly-Allocated Page — Possible Solution ==================================================================== THE TEST ------------------------------ real_heap = HeapFile(heap_path) # brand new, num_pages() == 0 txn = Transaction() thf = TransactionalHeapFile(real_heap, txn, 'x') pn = thf.allocate_page() # a page that did NOT exist before page = Page(); page.add_record(b'NEW-ROW-ON-NEW-PAGE') thf.write_page(pn, page) txn.rollback({'x': real_heap}) print(real_heap.num_pages()) blanked = real_heap.read_page(pn) table = HeapTable(real_heap, schema) print(list(table.scan())) RESULT ------------------------------ num_pages before transaction: 0 num_pages mid-transaction: 1 num_pages AFTER rollback (space not reclaimed): 1 page content after rollback is blank: True a real scan finds no rows at all: [] WHY THE FILE DOESN'T SHRINK BACK ------------------------------ Course 1's own HeapFile has no operation that removes bytes from the end of a file -- write_page() only ever writes AT a given offset, and allocate_page() only ever grows the file by appending a blank page. There simply isn't a "deallocate the last page" method to call during rollback, so num_pages() staying at 1 rather than returning to 0 isn't a bug in Transaction.rollback() -- it's a real, honest limit of what the underlying storage layer can do at all. WHY THE ROLLBACK IS STILL CORRECT DESPITE THAT ------------------------------ Transaction.record_write() captures old_bytes = None for a page that didn't exist yet (page_num >= real_heap_file.num_pages() at the time of first touch). rollback() checks for this case explicitly: if old_bytes is not None: heap_file.write_page(page_num, load_page(old_bytes)) else: heap_file.write_page(page_num, Page()) # blank it Since there's no real "before" state to restore a never-existed page to, the honest next-best thing is to overwrite it with a fresh, blank Page() -- which is exactly what allocate_page() itself would have produced if the transaction had never touched that page at all. The physical space is still there in the file (wasted, until some future page-deallocation mechanism exists to reuse it), but nothing about its CONTENT survives the rollback. table.scan() iterates every page's own num_slots, and a blank page reports num_slots = 0, so it contributes zero rows to the scan -- confirmed directly. WHY THIS WORKS AS AN ANSWER ------------------------------ Distinguishing "the page still physically exists" from "the row is still visible to the database" is the whole point of this exercise: num_pages() staying at 1 is an honest storage-layer limitation, not a correctness bug, precisely because scan() -- the actual interface anything else in this engine uses to see the table's own contents -- correctly reports nothing there.