package btree import ( "TsDB2/DB/core/page" "fmt" ) // ValidateBTree validates all B+ tree invariants func ValidateBTree(rootPID uint32, handle page.HandleDb, isPrimary bool) error { if rootPID == 0 { return fmt.Errorf("empty tree") } validator := &btreeValidator{ handle: handle, isPrimary: isPrimary, errors: []string{}, } // 1. Validate root node root := validator.loadNode(rootPID) if root == nil { return fmt.Errorf("failed to load root page %d", rootPID) } // 2. Traverse and validate all nodes var leafKeys [][]byte validator.validateNode(root, nil, nil, &leafKeys) // 3. Validate leaf linked list validator.validateLeafList(leafKeys) if len(validator.errors) > 0 { return fmt.Errorf("B+ tree validation failed:\n%s", formatErrors(validator.errors)) } return nil } type btreeValidator struct { handle page.HandleDb isPrimary bool errors []string } func (v *btreeValidator) loadNode(pid uint32) *btNode { if v.isPrimary { return btLoad(v.handle, pid) } // For secondary/string, we need a generic loader // For now, assume primary format return btLoad(v.handle, pid) } func (v *btreeValidator) validateNode(n *btNode, minKey, maxKey []byte, leafKeys *[][]byte) { if n == nil { v.errors = append(v.errors, "nil node encountered") return } el := int(n.info.EntryLen) cnt := int(n.info.Count) if cnt == 0 { v.errors = append(v.errors, fmt.Sprintf("page %d has 0 entries", n.pageID())) return } if n.isLeaf() { v.validateLeaf(n, minKey, maxKey, el, cnt, leafKeys) } else { v.validateInternal(n, minKey, maxKey, el, cnt, leafKeys) } } func (v *btreeValidator) validateLeaf(n *btNode, minKey, maxKey []byte, el, cnt int, leafKeys *[][]byte) { pid := n.pageID() for i := 0; i < cnt; i++ { entry := btEntry(n, i) key := btKey(entry) // Check ordering within leaf if i > 0 { prevKey := btKey(btEntry(n, i-1)) if btCmp(prevKey, key) >= 0 { v.errors = append(v.errors, fmt.Sprintf("leaf page %d: entries not sorted at pos %d (%s >= %s)", pid, i, string(prevKey), string(key))) } } // Check range constraints if minKey != nil && btCmp(key, minKey) < 0 { v.errors = append(v.errors, fmt.Sprintf("leaf page %d: key %s < minKey %s at pos %d", pid, string(key), string(minKey), i)) } if maxKey != nil && btCmp(key, maxKey) >= 0 { v.errors = append(v.errors, fmt.Sprintf("leaf page %d: key %s >= maxKey %s at pos %d", pid, string(key), string(maxKey), i)) } *leafKeys = append(*leafKeys, key) } } func (v *btreeValidator) validateInternal(n *btNode, minKey, maxKey []byte, el, cnt int, leafKeys *[][]byte) { pid := n.pageID() for i := 0; i < cnt; i++ { entry := btEntry(n, i) sepKey := btKey(entry) childPID := btPID(entry) // Load child node child := v.loadNode(childPID) if child == nil { v.errors = append(v.errors, fmt.Sprintf("internal page %d: failed to load child %d at pos %d", pid, childPID, i)) continue } // Determine child's key range var childMin, childMax []byte if i == 0 { childMin = minKey } else { // Child's min should be >= previous separator prevSep := btKey(btEntry(n, i-1)) childMin = prevSep } if i == cnt-1 { childMax = maxKey } else { // Child's max should be < next separator nextSep := sepKey childMax = nextSep } // Validate separator ordering if i > 0 { prevSep := btKey(btEntry(n, i-1)) if btCmp(prevSep, sepKey) >= 0 { v.errors = append(v.errors, fmt.Sprintf("internal page %d: separators not sorted at pos %d (%s >= %s)", pid, i, string(prevSep), string(sepKey))) } } // Recursively validate child v.validateNode(child, childMin, childMax, leafKeys) } } func (v *btreeValidator) validateLeafList(allKeys [][]byte) { // Check that all keys are globally sorted for i := 1; i < len(allKeys); i++ { if btCmp(allKeys[i-1], allKeys[i]) >= 0 { v.errors = append(v.errors, fmt.Sprintf("global key order violation: %s >= %s", string(allKeys[i-1]), string(allKeys[i]))) } } } func formatErrors(errors []string) string { result := "" for i, err := range errors { if i > 0 { result += "\n" } result += fmt.Sprintf(" [%d] %s", i+1, err) } return result } // DumpBTree prints the B+ tree structure for debugging func DumpBTree(rootPID uint32, handle page.HandleDb, isPrimary bool, maxDepth int) { if rootPID == 0 { fmt.Println("Empty tree") return } dumper := &btreeDumper{ handle: handle, isPrimary: isPrimary, maxDepth: maxDepth, } fmt.Println("=== B+ Tree Structure ===") dumper.dumpNode(rootPID, 0) fmt.Println("=========================") } type btreeDumper struct { handle page.HandleDb isPrimary bool maxDepth int } func (d *btreeDumper) dumpNode(pid uint32, depth int) { if depth > d.maxDepth { fmt.Printf("%s... (max depth reached)\n", indent(depth)) return } n := btLoad(d.handle, pid) if n == nil { fmt.Printf("%sPage %d: FAILED TO LOAD\n", indent(depth), pid) return } el := int(n.info.EntryLen) cnt := int(n.info.Count) nodeType := "LEAF" if !n.isLeaf() { nodeType = "INTERNAL" } fmt.Printf("%sPage %d [%s] count=%d entryLen=%d\n", indent(depth), pid, nodeType, cnt, el) if n.isLeaf() { for i := 0; i < cnt && i < 5; i++ { // Show first 5 entries entry := btEntry(n, i) key := btKey(entry) dataPID := btPID(entry) fmt.Printf("%s [%d] key='%s' dataPage=%d\n", indent(depth+1), i, string(key), dataPID) } if cnt > 5 { fmt.Printf("%s ... and %d more entries\n", indent(depth+1), cnt-5) } } else { for i := 0; i < cnt && i < 5; i++ { // Show first 5 children entry := btEntry(n, i) sepKey := btKey(entry) childPID := btPID(entry) fmt.Printf("%s [%d] sep='%s' -> child=%d\n", indent(depth+1), i, string(sepKey), childPID) d.dumpNode(childPID, depth+1) } if cnt > 5 { fmt.Printf("%s ... and %d more children\n", indent(depth+1), cnt-5) } } } func indent(depth int) string { s := "" for i := 0; i < depth; i++ { s += " " } return s }