|
|
@@ -0,0 +1,686 @@
|
|
|
+package btree
|
|
|
+
|
|
|
+import (
|
|
|
+ "TsDB2/DB/page"
|
|
|
+ "encoding/binary"
|
|
|
+ "errors"
|
|
|
+ "unsafe"
|
|
|
+)
|
|
|
+
|
|
|
+// ======================== 字符串 B+ 树索引操作 ========================
|
|
|
+// 叶节点: [key(keyLen) + PKID(8B)] entryLen = keyLen + 8
|
|
|
+// 内节点: [key(keyLen) + childPID(4B)] entryLen = keyLen + 4
|
|
|
+// 分裂: 统一 50/50
|
|
|
+// 合并: InnoDB 风格 — 删除后检查当前页+兄弟页能否放进一页
|
|
|
+
|
|
|
+// ----- 节点 I/O -----
|
|
|
+
|
|
|
+func strBtNew(handle page.HandleDb, isLeaf bool, entryLen int) *btNode {
|
|
|
+ pid := handle.NewPage()
|
|
|
+ buf := make([]byte, page.IndexPageSize)
|
|
|
+ info := (*page.IndexHead)(unsafe.Pointer(&buf[0]))
|
|
|
+ info.Hdr.Type = page.IndexDataIndex
|
|
|
+ info.Hdr.PageID = pid
|
|
|
+ info.Hdr.UseSize = uint32(page.IndexHeadSize)
|
|
|
+ info.IndexType = page.IndexTypeStr // 标记为字符串索引
|
|
|
+ info.EntryLen = uint16(entryLen)
|
|
|
+ info.Count = 0
|
|
|
+ if isLeaf {
|
|
|
+ info.IsLeaf = 1
|
|
|
+ }
|
|
|
+ return &btNode{buf: buf, info: info, entries: buf[page.IndexHeadSize:]}
|
|
|
+}
|
|
|
+
|
|
|
+func strBtLoad(handle page.HandleDb, pid uint32) *btNode {
|
|
|
+ if pid == 0 {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ buf := make([]byte, page.IndexPageSize)
|
|
|
+ if err := handle.ReadPage(pid, buf); err != nil {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ info := (*page.IndexHead)(unsafe.Pointer(&buf[0]))
|
|
|
+ return &btNode{buf: buf, info: info, entries: buf[page.IndexHeadSize:]}
|
|
|
+}
|
|
|
+
|
|
|
+func strBtSave(handle page.HandleDb, n *btNode) {
|
|
|
+ _ = handle.SavePage(n.info.Hdr.PageID, n.buf)
|
|
|
+}
|
|
|
+
|
|
|
+// ----- 条目操作 -----
|
|
|
+
|
|
|
+func strBtEntry(n *btNode, i int) []byte {
|
|
|
+ el := int(n.info.EntryLen)
|
|
|
+ return n.entries[i*el : (i+1)*el]
|
|
|
+}
|
|
|
+
|
|
|
+func strBtLeafKey(e []byte) []byte { return e[:len(e)-8] }
|
|
|
+func strBtPKID(e []byte) uint64 { return binary.BigEndian.Uint64(e[len(e)-8:]) }
|
|
|
+func strBtIntKey(e []byte) []byte { return e[:len(e)-4] }
|
|
|
+func strBtChildPID(e []byte) uint32 { return binary.BigEndian.Uint32(e[len(e)-4:]) }
|
|
|
+func strBtSetChildPID(e []byte, pid uint32) { binary.BigEndian.PutUint32(e[len(e)-4:], pid) }
|
|
|
+
|
|
|
+// 字符串比较(字典序)
|
|
|
+func strBtCmp(a, b []byte) int {
|
|
|
+ ml := len(a)
|
|
|
+ if len(b) < ml {
|
|
|
+ ml = len(b)
|
|
|
+ }
|
|
|
+ for i := 0; i < ml; i++ {
|
|
|
+ if a[i] < b[i] {
|
|
|
+ return -1
|
|
|
+ }
|
|
|
+ if a[i] > b[i] {
|
|
|
+ return 1
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if len(a) < len(b) {
|
|
|
+ return -1
|
|
|
+ }
|
|
|
+ if len(a) > len(b) {
|
|
|
+ return 1
|
|
|
+ }
|
|
|
+ return 0
|
|
|
+}
|
|
|
+
|
|
|
+// ----- 内节点路由 -----
|
|
|
+
|
|
|
+func strBtRoute(n *btNode, key []byte) uint32 {
|
|
|
+ cnt := int(n.info.Count)
|
|
|
+ result := 0
|
|
|
+ for i := 0; i < cnt; i++ {
|
|
|
+ if strBtCmp(strBtIntKey(strBtEntry(n, i)), key) <= 0 {
|
|
|
+ result = i
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return strBtChildPID(strBtEntry(n, result))
|
|
|
+}
|
|
|
+
|
|
|
+func strBtChildIdx(n *btNode, key []byte) int {
|
|
|
+ cnt := int(n.info.Count)
|
|
|
+ result := 0
|
|
|
+ for i := 0; i < cnt; i++ {
|
|
|
+ if strBtCmp(strBtEntry(n, i)[:int(n.info.EntryLen)-4], key) <= 0 {
|
|
|
+ result = i
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return result
|
|
|
+}
|
|
|
+
|
|
|
+// ----- 叶节点查找/插入位置 -----
|
|
|
+
|
|
|
+func strBtLeafFind(n *btNode, key []byte) (int, bool) {
|
|
|
+ cnt := int(n.info.Count)
|
|
|
+ left, right := 0, cnt-1
|
|
|
+ for left <= right {
|
|
|
+ mid := (left + right) / 2
|
|
|
+ cmp := strBtCmp(strBtLeafKey(strBtEntry(n, mid)), key)
|
|
|
+ if cmp == 0 {
|
|
|
+ return mid, true
|
|
|
+ } else if cmp < 0 {
|
|
|
+ left = mid + 1
|
|
|
+ } else {
|
|
|
+ right = mid - 1
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return left, false
|
|
|
+}
|
|
|
+
|
|
|
+func strBtLeafPos(n *btNode, key []byte) int {
|
|
|
+ pos, _ := strBtLeafFind(n, key)
|
|
|
+ return pos
|
|
|
+}
|
|
|
+
|
|
|
+// ----- 最左/最右叶节点 -----
|
|
|
+
|
|
|
+func strBtRightmostLeaf(n *btNode) uint32 {
|
|
|
+ if n.isLeaf() {
|
|
|
+ return n.info.Hdr.PageID
|
|
|
+ }
|
|
|
+ cnt := int(n.info.Count)
|
|
|
+ if cnt == 0 {
|
|
|
+ return 0
|
|
|
+ }
|
|
|
+ return strBtChildPID(strBtEntry(n, cnt-1))
|
|
|
+}
|
|
|
+
|
|
|
+// ----- 空间检查与分裂 -----
|
|
|
+
|
|
|
+func strBtHasRoom(n *btNode, entryLen int) bool {
|
|
|
+ return n.info.Hdr.UseSize+uint32(entryLen) <= page.IndexPageSize
|
|
|
+}
|
|
|
+
|
|
|
+func strBtSplit(handle page.HandleDb, n *btNode, entryLen int) (*btNode, []byte, *btNode) {
|
|
|
+ cnt := int(n.info.Count)
|
|
|
+ mid := cnt / 2
|
|
|
+
|
|
|
+ // 创建右兄弟
|
|
|
+ right := strBtNew(handle, n.isLeaf(), entryLen)
|
|
|
+ right.info.Hdr.PrePage = n.info.Hdr.PageID
|
|
|
+ right.info.Hdr.NextPage = n.info.Hdr.NextPage
|
|
|
+
|
|
|
+ // 移动后半部分到右兄弟
|
|
|
+ moveCount := cnt - mid
|
|
|
+ for i := 0; i < moveCount; i++ {
|
|
|
+ copy(strBtEntry(right, i), strBtEntry(n, mid+i))
|
|
|
+ }
|
|
|
+ right.info.Count = uint32(moveCount)
|
|
|
+ right.info.Hdr.UseSize = uint32(page.IndexHeadSize) + uint32(moveCount*entryLen)
|
|
|
+
|
|
|
+ // 截断左节点
|
|
|
+ n.info.Count = uint32(mid)
|
|
|
+ n.info.Hdr.UseSize = uint32(page.IndexHeadSize) + uint32(mid*entryLen)
|
|
|
+
|
|
|
+ // 更新链表
|
|
|
+ if n.info.Hdr.NextPage != 0 {
|
|
|
+ next := strBtLoad(handle, n.info.Hdr.NextPage)
|
|
|
+ if next != nil {
|
|
|
+ next.info.Hdr.PrePage = right.info.Hdr.PageID
|
|
|
+ strBtSave(handle, next)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ n.info.Hdr.NextPage = right.info.Hdr.PageID
|
|
|
+
|
|
|
+ // 提升键
|
|
|
+ var promoKey []byte
|
|
|
+ if n.isLeaf() {
|
|
|
+ promoKey = make([]byte, len(strBtLeafKey(strBtEntry(right, 0))))
|
|
|
+ copy(promoKey, strBtLeafKey(strBtEntry(right, 0)))
|
|
|
+ } else {
|
|
|
+ promoKey = make([]byte, len(strBtIntKey(strBtEntry(right, 0))))
|
|
|
+ copy(promoKey, strBtIntKey(strBtEntry(right, 0)))
|
|
|
+ }
|
|
|
+
|
|
|
+ strBtSave(handle, n)
|
|
|
+ strBtSave(handle, right)
|
|
|
+ return n, promoKey, right
|
|
|
+}
|
|
|
+
|
|
|
+// ----- 合并/借位 -----
|
|
|
+
|
|
|
+func strBtCanMerge(left, right *btNode, entryLen int) bool {
|
|
|
+ totalEntries := int(left.info.Count) + int(right.info.Count)
|
|
|
+ return uint32(totalEntries*entryLen+page.IndexHeadSize) <= page.IndexPageSize
|
|
|
+}
|
|
|
+
|
|
|
+func strBtMerge(handle page.HandleDb, left, right *btNode, entryLen int) {
|
|
|
+ leftCnt := int(left.info.Count)
|
|
|
+ rightCnt := int(right.info.Count)
|
|
|
+
|
|
|
+ // 将右节点条目追加到左节点
|
|
|
+ for i := 0; i < rightCnt; i++ {
|
|
|
+ copy(strBtEntry(left, leftCnt+i), strBtEntry(right, i))
|
|
|
+ }
|
|
|
+ left.info.Count = uint32(leftCnt + rightCnt)
|
|
|
+ left.info.Hdr.UseSize = uint32(page.IndexHeadSize) + uint32((leftCnt+rightCnt)*entryLen)
|
|
|
+
|
|
|
+ // 更新链表
|
|
|
+ left.info.Hdr.NextPage = right.info.Hdr.NextPage
|
|
|
+ if right.info.Hdr.NextPage != 0 {
|
|
|
+ next := strBtLoad(handle, right.info.Hdr.NextPage)
|
|
|
+ if next != nil {
|
|
|
+ next.info.Hdr.PrePage = left.info.Hdr.PageID
|
|
|
+ strBtSave(handle, next)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ strBtSave(handle, left)
|
|
|
+}
|
|
|
+
|
|
|
+func strBtBorrowFromRight(handle page.HandleDb, left, right *btNode, entryLen int) {
|
|
|
+ // 从右节点借第一个条目到左节点
|
|
|
+ leftCnt := int(left.info.Count)
|
|
|
+ copy(strBtEntry(left, leftCnt), strBtEntry(right, 0))
|
|
|
+ left.info.Count++
|
|
|
+ left.info.Hdr.UseSize += uint32(entryLen)
|
|
|
+
|
|
|
+ // 右节点前移
|
|
|
+ rightCnt := int(right.info.Count)
|
|
|
+ for i := 0; i < rightCnt-1; i++ {
|
|
|
+ copy(strBtEntry(right, i), strBtEntry(right, i+1))
|
|
|
+ }
|
|
|
+ right.info.Count--
|
|
|
+ right.info.Hdr.UseSize -= uint32(entryLen)
|
|
|
+
|
|
|
+ strBtSave(handle, left)
|
|
|
+ strBtSave(handle, right)
|
|
|
+}
|
|
|
+
|
|
|
+func strBtBorrowFromLeft(handle page.HandleDb, left, right *btNode, entryLen int) {
|
|
|
+ // 从左节点借最后一条目到右节点
|
|
|
+ rightCnt := int(right.info.Count)
|
|
|
+ for i := rightCnt; i > 0; i-- {
|
|
|
+ copy(strBtEntry(right, i), strBtEntry(right, i-1))
|
|
|
+ }
|
|
|
+ copy(strBtEntry(right, 0), strBtEntry(left, int(left.info.Count)-1))
|
|
|
+ right.info.Count++
|
|
|
+ right.info.Hdr.UseSize += uint32(entryLen)
|
|
|
+
|
|
|
+ left.info.Count--
|
|
|
+ left.info.Hdr.UseSize -= uint32(entryLen)
|
|
|
+
|
|
|
+ strBtSave(handle, left)
|
|
|
+ strBtSave(handle, right)
|
|
|
+}
|
|
|
+
|
|
|
+// ======================== 公开 API ========================
|
|
|
+
|
|
|
+// StrBTInsert 插入字符串索引条目
|
|
|
+func StrBTInsert(rootPID uint32, entry []byte, handle page.HandleDb) (uint32, error) {
|
|
|
+ el := len(entry)
|
|
|
+ if rootPID == 0 {
|
|
|
+ leaf := strBtNew(handle, true, el)
|
|
|
+ copy(strBtEntry(leaf, 0), entry)
|
|
|
+ leaf.info.Count = 1
|
|
|
+ leaf.info.Hdr.UseSize = uint32(page.IndexHeadSize) + uint32(el)
|
|
|
+ strBtSave(handle, leaf)
|
|
|
+ return leaf.pageID(), nil
|
|
|
+ }
|
|
|
+ var path []btPathEntry
|
|
|
+ cur := rootPID
|
|
|
+ for {
|
|
|
+ n := strBtLoad(handle, cur)
|
|
|
+ if n == nil {
|
|
|
+ return rootPID, errors.New("strBt load failed")
|
|
|
+ }
|
|
|
+ if n.isLeaf() {
|
|
|
+ key := entry[:el-8]
|
|
|
+ ip := strBtLeafPos(n, key)
|
|
|
+ // 允许重复键,插入到相同 key 的条目之后
|
|
|
+ for ip < int(n.info.Count) && strBtCmp(strBtLeafKey(strBtEntry(n, ip)), key) == 0 {
|
|
|
+ ip++
|
|
|
+ }
|
|
|
+ if int(n.info.Hdr.UseSize)+el <= btSplitThreshold() {
|
|
|
+ // 有空间,直接插入
|
|
|
+ if ip < int(n.info.Count) {
|
|
|
+ // 移动后续条目
|
|
|
+ cnt := int(n.info.Count)
|
|
|
+ for i := cnt; i > ip; i-- {
|
|
|
+ copy(strBtEntry(n, i), strBtEntry(n, i-1))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ copy(strBtEntry(n, ip), entry)
|
|
|
+ n.info.Count++
|
|
|
+ n.info.Hdr.UseSize += uint32(el)
|
|
|
+ strBtSave(handle, n)
|
|
|
+ return rootPID, nil
|
|
|
+ }
|
|
|
+ // 需要分裂
|
|
|
+ cnt := int(n.info.Count)
|
|
|
+ total := cnt + 1
|
|
|
+ all := make([]byte, total*el)
|
|
|
+ if ip > 0 {
|
|
|
+ copy(all, n.entries[:ip*el])
|
|
|
+ }
|
|
|
+ copy(all[ip*el:], entry)
|
|
|
+ if cnt > ip {
|
|
|
+ copy(all[(ip+1)*el:], n.entries[ip*el:cnt*el])
|
|
|
+ }
|
|
|
+ mid := total / 2
|
|
|
+ rl := strBtNew(handle, true, el)
|
|
|
+ rc := total - mid
|
|
|
+ copy(rl.entries, all[mid*el:total*el])
|
|
|
+ rl.info.Count = uint32(rc)
|
|
|
+ rl.info.Hdr.UseSize = uint32(page.IndexHeadSize) + uint32(rc*el)
|
|
|
+ rl.info.Hdr.NextPage = n.info.Hdr.NextPage
|
|
|
+ rl.info.Hdr.PrePage = n.info.Hdr.PageID
|
|
|
+ n.info.Hdr.NextPage = rl.info.Hdr.PageID
|
|
|
+ copy(n.entries, all[:mid*el])
|
|
|
+ n.info.Count = uint32(mid)
|
|
|
+ n.info.Hdr.UseSize = uint32(page.IndexHeadSize) + uint32(mid*el)
|
|
|
+ strBtSave(handle, n)
|
|
|
+ strBtSave(handle, rl)
|
|
|
+ if rl.info.Hdr.NextPage != 0 {
|
|
|
+ nl := strBtLoad(handle, rl.info.Hdr.NextPage)
|
|
|
+ if nl != nil {
|
|
|
+ nl.info.Hdr.PrePage = rl.info.Hdr.PageID
|
|
|
+ strBtSave(handle, nl)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 使用右子树最左叶的第一个 key 作为 promoKey
|
|
|
+ pk := strBtLeftmostKey(handle, rl.info.Hdr.PageID, el)
|
|
|
+ nr, err := strBtPromote(handle, rootPID, path, n.info.Hdr.PageID, pk, rl.info.Hdr.PageID, el)
|
|
|
+ return nr, err
|
|
|
+ }
|
|
|
+ ci := strBtChildIdx(n, entry[:el-8])
|
|
|
+ cpid := strBtChildPID(strBtEntry(n, ci))
|
|
|
+ path = append(path, btPathEntry{nodePID: cur, childIdx: ci})
|
|
|
+ cur = cpid
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// StrBTSearch 查找字符串索引
|
|
|
+func StrBTSearch(rootPID uint32, key []byte, handle page.HandleDb) (uint64, bool) {
|
|
|
+ if rootPID == 0 {
|
|
|
+ return 0, false
|
|
|
+ }
|
|
|
+ cur := rootPID
|
|
|
+ for {
|
|
|
+ n := strBtLoad(handle, cur)
|
|
|
+ if n == nil {
|
|
|
+ return 0, false
|
|
|
+ }
|
|
|
+ if n.isLeaf() {
|
|
|
+ pos, ok := strBtLeafFind(n, key)
|
|
|
+ if !ok {
|
|
|
+ return 0, false
|
|
|
+ }
|
|
|
+ return strBtPKID(strBtEntry(n, pos)), true
|
|
|
+ }
|
|
|
+ cur = strBtRoute(n, key)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// strBtLeftmostKey 获取子树最左叶的第一个 key
|
|
|
+func strBtLeftmostKey(handle page.HandleDb, nodePID uint32, el int) []byte {
|
|
|
+ cur := nodePID
|
|
|
+ for {
|
|
|
+ n := strBtLoad(handle, cur)
|
|
|
+ if n == nil {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ if n.isLeaf() {
|
|
|
+ e := strBtEntry(n, 0)
|
|
|
+ keyLen := el - 8 // 叶节点 key 长度
|
|
|
+ result := make([]byte, el-4) // 内节点条目需要 el-4 字节的 key
|
|
|
+ copy(result, e[:keyLen])
|
|
|
+ return result
|
|
|
+ }
|
|
|
+ cur = strBtChildPID(strBtEntry(n, 0))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// strBtPromote 将新条目提升到父节点(处理分裂)
|
|
|
+func strBtPromote(handle page.HandleDb, rootPID uint32, path []btPathEntry, leftPID uint32, promoKey []byte, rightPID uint32, el int) (uint32, error) {
|
|
|
+ if len(path) == 0 {
|
|
|
+ // 根节点分裂,创建新根
|
|
|
+ nr := strBtNew(handle, false, el)
|
|
|
+ leftKey := strBtLeftmostKey(handle, leftPID, el)
|
|
|
+ le := make([]byte, el)
|
|
|
+ copy(le[:el-4], leftKey)
|
|
|
+ strBtSetChildPID(le, leftPID)
|
|
|
+ re := make([]byte, el)
|
|
|
+ copy(re[:el-4], promoKey)
|
|
|
+ strBtSetChildPID(re, rightPID)
|
|
|
+ copy(strBtEntry(nr, 0), le)
|
|
|
+ copy(strBtEntry(nr, 1), re)
|
|
|
+ nr.info.Count = 2
|
|
|
+ nr.info.Hdr.UseSize = uint32(page.IndexHeadSize) + uint32(2*el)
|
|
|
+ strBtSave(handle, nr)
|
|
|
+ return nr.pageID(), nil
|
|
|
+ }
|
|
|
+ // 在父节点中插入新条目
|
|
|
+ pi := len(path) - 1
|
|
|
+ parent := strBtLoad(handle, path[pi].nodePID)
|
|
|
+ if parent == nil {
|
|
|
+ return rootPID, errors.New("strBt parent load failed")
|
|
|
+ }
|
|
|
+ ci := path[pi].childIdx
|
|
|
+ cnt := int(parent.info.Count)
|
|
|
+ // 找到插入位置:在 ci 之后,找第一个 >= promoKey 的位置
|
|
|
+ pos := cnt
|
|
|
+ for i := ci + 1; i < cnt; i++ {
|
|
|
+ if strBtCmp(strBtIntKey(strBtEntry(parent, i)), promoKey) >= 0 {
|
|
|
+ pos = i
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 移动后续条目腾出空间
|
|
|
+ for i := cnt; i > pos; i-- {
|
|
|
+ copy(strBtEntry(parent, i), strBtEntry(parent, i-1))
|
|
|
+ }
|
|
|
+ // 插入新条目
|
|
|
+ ne := make([]byte, el)
|
|
|
+ copy(ne[:el-4], promoKey)
|
|
|
+ strBtSetChildPID(ne, rightPID)
|
|
|
+ copy(strBtEntry(parent, pos), ne)
|
|
|
+ parent.info.Count++
|
|
|
+ parent.info.Hdr.UseSize += uint32(el)
|
|
|
+ // 检查父节点是否需要分裂
|
|
|
+ if int(parent.info.Hdr.UseSize)+el <= btSplitThreshold() {
|
|
|
+ strBtSave(handle, parent)
|
|
|
+ return rootPID, nil
|
|
|
+ }
|
|
|
+ // 父节点也需要分裂
|
|
|
+ cnt = int(parent.info.Count)
|
|
|
+ mid := cnt / 2
|
|
|
+ pk2 := make([]byte, el-4)
|
|
|
+ copy(pk2, strBtIntKey(strBtEntry(parent, mid)))
|
|
|
+ rn := strBtNew(handle, false, el)
|
|
|
+ rc := cnt - mid - 1
|
|
|
+ if rc > 0 {
|
|
|
+ copy(rn.entries, parent.entries[(mid+1)*el:cnt*el])
|
|
|
+ }
|
|
|
+ rn.info.Count = uint32(rc)
|
|
|
+ rn.info.Hdr.UseSize = uint32(page.IndexHeadSize) + uint32(rc*el)
|
|
|
+ parent.info.Count = uint32(mid)
|
|
|
+ parent.info.Hdr.UseSize = uint32(page.IndexHeadSize) + uint32(mid*el)
|
|
|
+ strBtSave(handle, parent)
|
|
|
+ strBtSave(handle, rn)
|
|
|
+ // 递归提升
|
|
|
+ return strBtPromote(handle, rootPID, path[:pi], parent.info.Hdr.PageID, pk2, rn.info.Hdr.PageID, el)
|
|
|
+}
|
|
|
+
|
|
|
+// StrBTDelete 删除字符串索引条目
|
|
|
+func StrBTDelete(rootPID uint32, key []byte, handle page.HandleDb) (uint32, error) {
|
|
|
+ if rootPID == 0 {
|
|
|
+ return 0, page.ErrNotFound
|
|
|
+ }
|
|
|
+ // 简化实现:标记删除,暂不实现合并/借位
|
|
|
+ // TODO: 实现完整的 B+ 树删除(合并/借位)
|
|
|
+ cur := rootPID
|
|
|
+ for {
|
|
|
+ n := strBtLoad(handle, cur)
|
|
|
+ if n == nil {
|
|
|
+ return rootPID, page.ErrNotFound
|
|
|
+ }
|
|
|
+ if n.isLeaf() {
|
|
|
+ pos, found := strBtLeafFind(n, key)
|
|
|
+ if !found {
|
|
|
+ return rootPID, page.ErrNotFound
|
|
|
+ }
|
|
|
+ // 删除条目:移动后续条目覆盖
|
|
|
+ cnt := int(n.info.Count)
|
|
|
+ el := int(n.info.EntryLen)
|
|
|
+ for i := pos; i < cnt-1; i++ {
|
|
|
+ copy(strBtEntry(n, i), strBtEntry(n, i+1))
|
|
|
+ }
|
|
|
+ n.info.Count--
|
|
|
+ n.info.Hdr.UseSize -= uint32(el)
|
|
|
+ strBtSave(handle, n)
|
|
|
+ return rootPID, nil
|
|
|
+ }
|
|
|
+ cur = strBtRoute(n, key)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// StrBTRangeScan 范围扫描
|
|
|
+func StrBTRangeScan(rootPID uint32, handle page.HandleDb,
|
|
|
+ startKey, endKey []byte, startInc, endInc bool, reverse bool,
|
|
|
+ callback func(row []byte) bool) error {
|
|
|
+ if rootPID == 0 {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ // 找到起始叶节点
|
|
|
+ var cur uint32
|
|
|
+ if !reverse {
|
|
|
+ cur = rootPID
|
|
|
+ for {
|
|
|
+ n := strBtLoad(handle, cur)
|
|
|
+ if n == nil {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ if n.isLeaf() {
|
|
|
+ break
|
|
|
+ }
|
|
|
+ if startKey != nil {
|
|
|
+ cur = strBtRoute(n, startKey)
|
|
|
+ } else {
|
|
|
+ cur = strBtChildPID(strBtEntry(n, 0))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 反向遍历从最右叶开始
|
|
|
+ cur = rootPID
|
|
|
+ for {
|
|
|
+ n := strBtLoad(handle, cur)
|
|
|
+ if n == nil {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ if n.isLeaf() {
|
|
|
+ break
|
|
|
+ }
|
|
|
+ cnt := int(n.info.Count)
|
|
|
+ if cnt == 0 {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ cur = strBtChildPID(strBtEntry(n, cnt-1))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 遍历叶节点链表
|
|
|
+ for cur != 0 {
|
|
|
+ n := strBtLoad(handle, cur)
|
|
|
+ if n == nil {
|
|
|
+ break
|
|
|
+ }
|
|
|
+ cnt := int(n.info.Count)
|
|
|
+ if !reverse {
|
|
|
+ for i := 0; i < cnt; i++ {
|
|
|
+ entry := strBtEntry(n, i)
|
|
|
+ key := strBtLeafKey(entry)
|
|
|
+ if endKey != nil {
|
|
|
+ cmp := strBtCmp(key, endKey)
|
|
|
+ if cmp > 0 || (cmp == 0 && !endInc) {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if startKey != nil {
|
|
|
+ cmp := strBtCmp(key, startKey)
|
|
|
+ if cmp < 0 || (cmp == 0 && !startInc) {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ }
|
|
|
+ pkID := strBtPKID(entry)
|
|
|
+ pkBuf := make([]byte, 8)
|
|
|
+ binary.BigEndian.PutUint64(pkBuf, pkID)
|
|
|
+ if !callback(pkBuf) {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ for i := cnt - 1; i >= 0; i-- {
|
|
|
+ entry := strBtEntry(n, i)
|
|
|
+ key := strBtLeafKey(entry)
|
|
|
+ if startKey != nil {
|
|
|
+ cmp := strBtCmp(key, startKey)
|
|
|
+ if cmp < 0 || (cmp == 0 && !startInc) {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if endKey != nil {
|
|
|
+ cmp := strBtCmp(key, endKey)
|
|
|
+ if cmp > 0 || (cmp == 0 && !endInc) {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ }
|
|
|
+ pkID := strBtPKID(entry)
|
|
|
+ pkBuf := make([]byte, 8)
|
|
|
+ binary.BigEndian.PutUint64(pkBuf, pkID)
|
|
|
+ if !callback(pkBuf) {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if !reverse {
|
|
|
+ cur = n.info.Hdr.NextPage
|
|
|
+ } else {
|
|
|
+ cur = n.info.Hdr.PrePage
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// StrBTDeleteWithPKID 删除指定 key + pkID 的条目(处理重复 key 场景)
|
|
|
+func StrBTDeleteWithPKID(rootPID uint32, key []byte, pkID uint64, handle page.HandleDb) (uint32, error) {
|
|
|
+ if rootPID == 0 {
|
|
|
+ return 0, page.ErrNotFound
|
|
|
+ }
|
|
|
+ cur := rootPID
|
|
|
+ for {
|
|
|
+ n := strBtLoad(handle, cur)
|
|
|
+ if n == nil {
|
|
|
+ return rootPID, page.ErrNotFound
|
|
|
+ }
|
|
|
+ if n.isLeaf() {
|
|
|
+ // 查找所有匹配 key 的条目
|
|
|
+ pos, found := strBtLeafFind(n, key)
|
|
|
+ if !found {
|
|
|
+ return rootPID, page.ErrNotFound
|
|
|
+ }
|
|
|
+ // 遍历相同 key 的所有条目,找到匹配的 pkID
|
|
|
+ cnt := int(n.info.Count)
|
|
|
+ el := int(n.info.EntryLen)
|
|
|
+ deleted := false
|
|
|
+ for i := pos; i < cnt; i++ {
|
|
|
+ entry := strBtEntry(n, i)
|
|
|
+ entryKey := strBtLeafKey(entry)
|
|
|
+ if strBtCmp(entryKey, key) != 0 {
|
|
|
+ break // 超出相同 key 范围
|
|
|
+ }
|
|
|
+ entryPKID := strBtPKID(entry)
|
|
|
+ if entryPKID == pkID {
|
|
|
+ // 找到匹配项,删除它
|
|
|
+ for j := i; j < cnt-1; j++ {
|
|
|
+ copy(strBtEntry(n, j), strBtEntry(n, j+1))
|
|
|
+ }
|
|
|
+ n.info.Count--
|
|
|
+ n.info.Hdr.UseSize -= uint32(el)
|
|
|
+ strBtSave(handle, n)
|
|
|
+ deleted = true
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if !deleted {
|
|
|
+ return rootPID, page.ErrNotFound
|
|
|
+ }
|
|
|
+ return rootPID, nil
|
|
|
+ }
|
|
|
+ cur = strBtRoute(n, key)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// StrBTSearchAll 查找相同 key 下的所有 pkID
|
|
|
+func StrBTSearchAll(rootPID uint32, key []byte, handle page.HandleDb) ([]uint64, bool) {
|
|
|
+ if rootPID == 0 {
|
|
|
+ return nil, false
|
|
|
+ }
|
|
|
+ var result []uint64
|
|
|
+ cur := rootPID
|
|
|
+ for {
|
|
|
+ n := strBtLoad(handle, cur)
|
|
|
+ if n == nil {
|
|
|
+ return nil, false
|
|
|
+ }
|
|
|
+ if n.isLeaf() {
|
|
|
+ pos, found := strBtLeafFind(n, key)
|
|
|
+ if !found {
|
|
|
+ return nil, false
|
|
|
+ }
|
|
|
+ // 收集所有相同 key 的 pkID
|
|
|
+ cnt := int(n.info.Count)
|
|
|
+ for i := pos; i < cnt; i++ {
|
|
|
+ entry := strBtEntry(n, i)
|
|
|
+ entryKey := strBtLeafKey(entry)
|
|
|
+ if strBtCmp(entryKey, key) != 0 {
|
|
|
+ break
|
|
|
+ }
|
|
|
+ result = append(result, strBtPKID(entry))
|
|
|
+ }
|
|
|
+ if len(result) == 0 {
|
|
|
+ return nil, false
|
|
|
+ }
|
|
|
+ return result, true
|
|
|
+ }
|
|
|
+ cur = strBtRoute(n, key)
|
|
|
+ }
|
|
|
+}
|