mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-21 10:14:19 +01:00
## Background When a divider cell is deleted from an index interior page, the following algorithm is used: 1. Find predecessor: Move to largest key in left subtree of the current page. This is always a leaf page. 2. Create replacement: Convert this predecessor leaf cell to interior cell format, using original cell's left child page pointer 3. Replace: Drop original cell from parent page, insert replacement at same position 4. Cleanup: Delete the taken predecessor cell from the leaf page <img width="845" height="266" alt="Screenshot 2025-07-16 at 10 39 18" src="https://github.com/user- attachments/assets/30517da4-a4dc-471e-a8f5-c27ba0979c86" /> ## The faulty code leading to the bug The error in our logic was that we always expected to only traverse down one level of the btree: ```rust let parent_page = self.stack.parent_page().unwrap(); let leaf_page = self.stack.top(); ``` This meant that when the deletion happened on say, level 1, and the replacement cell was taken from level 3, we actually inserted the replacement cell into level 2 instead of level 1. ## Manifestation of the bug in issue 2106 In #2106, this manifested as the following chain of pages, going from parent to children: 3 -> 111 -> 119 - Cell to be deleted was on page 3 (whose left pointer is 111) - Going to the largest key in the left subtree meant traversing from 3 to 111 and then from 111 to 119 - a replacement cell was taken from 119 - incorrectly inserted into 111 - and its left child pointer also set as 111! - now whenever page 111 wanted to go to its left child page, it would just traverse back to itself, eventually causing a crash because we have a hard limit of the number of pages on the page stack. ## The fix The fix is quite trivial: store the page we are on before we start traversing down. Closes #2106 Closes #2108