Function: _delete in b-tree-storage-engine/btree.py

Date: 2026-05-28

Time: 18:29

_delete — Recursive B-Tree Key Deletion

Purpose

delete is the recursive engine behind BTree.delete(). It walks down the tree from a given node to find and remove a key from its leaf, then handles one specific structural cleanup on the way back up: removing emptied leaf nodes when they have a left sibling at depth 2. It exists to separate the tree-traversal logic from the metadata bookkeeping that the public delete() method handles (decrementing totalkeys, committing the WAL).

Contract

Preconditions:

Postconditions:

Invariants preserved:

Parameters

| Parameter | Type | Meaning |

|-----------|------|---------|

| page_num | int | Page number of the current node in the data file |

| key | str | The key to delete (must match exactly via ==) |

| depth | int | Height of the subtree rooted here. 1 = leaf, 2 = parent of leaves, etc. |

Return Value

Returns a three-valued signal to the caller:

| Value | Meaning | Caller action |

|-------|---------|---------------|

| False | Key not found | Do nothing |

| True | Key deleted, leaf still has keys (or empty leaf was already cleaned up) | Propagate success |

| 'empty' | Key deleted, leaf is now empty and was not cleaned up at this level | Parent should attempt cleanup |

The public delete() method treats any truthy return as success and decrements total_keys.

Algorithm

Leaf case (depth == 1):

1. Read and deserialize the leaf page.

2. Binary-search for the key using bisect_left.

3. If the key isn't present, return False.

4. Remove the key and value at that index.

5. Serialize and write the modified leaf via WAL.

6. Return 'empty' if the leaf has no remaining keys, otherwise True.

Internal node case (depth > 1):

1. Read and deserialize the internal node to get separator keys and child pointers.

2. Use bisect_right to find which child subtree contains the key.

3. Recurse into that child with depth - 1.

4. Cleanup path — if the child reported 'empty', depth == 2 (this node is the direct parent of leaves), and idx > 0 (the empty leaf is not the leftmost child):

5. If result == 'empty' but cleanup conditions aren't met (leftmost child, or depth > 2), return True — swallow the signal. The empty leaf stays allocated.

6. Otherwise, pass through the False or True result unchanged.

Side Effects

Error Handling

No explicit error handling. If the page data is corrupt or page_num is invalid, this will raise from struct.unpack or produce garbage. The method trusts that the tree structure on disk is consistent.

Usage Patterns

Called exclusively by BTree.delete():


def delete(self, key):
    root, height, total_keys, next_free, free_head = self._read_meta()
    found = self._delete(root, key, height)
    if found:
        self._wal_write_meta(root, height, total_keys - 1, next_free, free_head)
        self.wal.commit(self.pm)
    return bool(found)

The caller is responsible for updating the key count and committing the WAL transaction. The bool() cast collapses 'empty' and True into True for the public API.

Dependencies

Notable Assumptions and Limitations

1. No rebalancing. This is a simplified B-tree that doesn't merge or redistribute keys on underflow. An emptied leftmost leaf (idx == 0) or an empty leaf at depth > 2 is left in place with zero keys — it wastes a page and stays in the tree permanently.

2. Only cleans up at depth 2. The depth == 2 guard means cleanup only happens when the current node is the direct parent of leaves. If a deletion empties a leaf deeper in the tree, the 'empty' signal is swallowed as True at the first internal node that can't handle it.

3. freepage bypasses WAL. When a leaf is freed during cleanup, pm.freepage() writes directly to the data file without WAL logging. If the process crashes after free_page but before wal.commit(), the free list and the internal node could be inconsistent.

4. bisectright vs bisectleft. Leaves use bisectleft (find exact match position), internals use bisectright (route to the correct child). This matches B-tree convention where a key equal to a separator routes right.