package core import ( "unsafe" "git.ycl.pub/yl32940/TsDB2/DB/core/page" "git.ycl.pub/yl32940/TsDB2/DB/core/types" "git.ycl.pub/yl32940/TsDB2/DB/core/util" ) // Truncate 清空一张表的全部行与索引内容, 但保留表的身份 (TableInfo 槽位)、schema // (Field / 版本) 与索引【定义】(名字 / 类型 / 分量 / 唯一标志) —— 清空后表回到 // "刚建表、零行、索引为空 (EntryPage=0, 首次插入自动开根)" 的状态。 // // 与 Drop + Create 的区别: Drop 删掉 TableInfo 与索引定义, 再 Create 得到的是另一张 // 表 (二级索引全部丢失, 需调用方逐个重建); Truncate 原地复用槽位与定义。 func (t *TablePage) Truncate(name string) error { pageID := t.hdr.PageID for pageID != 0 { buf := make([]byte, PageSize) if err := t.handle.ReadPage(pageID, buf); err != nil { return err } hdr := page.HdrOf(buf) offset := t.hdrSize + schemaExtHeadSize for offset+t.tableInfoSize <= hdr.UseSize { info := (*TableInfo)(unsafe.Pointer(&buf[offset])) recSize := t.tableInfoSize + uint32(info.FieldAllocateSize) if util.BytesToString(info.Name[:]) == name && !util.IsHighBitSet(info.Flag) { var entry *Table for _, tb := range t.tables { if util.BytesToString(tb.info.Name[:]) == name && !util.IsHighBitSet(tb.info.Flag) { entry = tb break } } if entry == nil { return ErrTableNotFound } return entry.truncateContents(info) } offset += recSize } pageID = hdr.NextPage } return ErrTableNotFound } // truncateContents 释放本表全部数据 / 索引 / 堆页, 重建空 Data / Heap 首页, 并把每个 // 索引的 EntryPage 置 0。页回收一律走 DeferFree / DeferFreeChain (提交成功后才真正还链)。 // // 三类回收互不重叠: // - releaseIndexPages 收【索引自身页】: B+ 树节点 / 哈希桶链 / 时序扁平链 + TimeDataPage; // - FirstData 链收行存储 DataPage (OLTP 主键表) 或建表时的空 DataIndex 页 (时序表); // - FirstHeap 链收堆溢出页。 func (table *Table) truncateContents(info *TableInfo) error { // 本地去重: 兜底防止任何两个枚举器碰到同一页 (重复还链会让空闲链自环)。 seen := make(map[uint32]bool) deferFree := func(pid uint32) { if pid == 0 || seen[pid] { return } seen[pid] = true table.deferFreePage(pid) } // 1. 释放行持有的 blob 链 (必须在 DataPage 回收前, 行内容仍可读)。 hasBlob := false for _, f := range table.Field { if types.IsBlob(f.FieldType) { hasBlob = true break } } if hasBlob { walked := make(map[uint32]bool) for pid := info.FirstData; pid != 0 && !walked[pid]; { walked[pid] = true dbuf := make([]byte, PageSize) if err := table.handle.ReadPage(pid, dbuf); err != nil { break } releaseBlobRefsInDataPage(table, dbuf) pid = page.HdrOf(dbuf).NextPage } } // 2. 逐索引释放内容并把 EntryPage 置 0 (空索引: 首次插入自动开根)。 // 注意 releaseIndexPages 内部走 table.deferFreePage; 它收的索引页与下面的 // Data / Heap 链不重叠 (见函数头说明)。 im := table.indexMeta for i := range im.metas { m := &im.metas[i] if m.Flag&0x80 != 0 || m.EntryPage == 0 { continue } itype, root := m.IndexType, m.EntryPage releaseIndexPages(table, itype, root) m.EntryPage = 0 im.saveMeta(m) } im.InvalidateRowCache() // 3. 释放数据页链 (无链长字段, 逐页)。 walked := make(map[uint32]bool) for pid := info.FirstData; pid != 0 && !walked[pid]; { walked[pid] = true dbuf := make([]byte, PageSize) if err := table.handle.ReadPage(pid, dbuf); err != nil { break } next := page.HdrOf(dbuf).NextPage deferFree(pid) pid = next } // 4. 释放堆链 (首尾 + 链长已知, 整链 O(1) 延迟回收; 退化时逐页)。 if info.FirstHeap != 0 && info.LastHeap != 0 && info.HeapPageCount > 0 && table.handle.DeferFreeChain != nil { table.handle.DeferFreeChain(info.FirstHeap, info.LastHeap, info.HeapPageCount) } else { hw := make(map[uint32]bool) for pid := info.FirstHeap; pid != 0 && !hw[pid]; { hw[pid] = true hbuf := make([]byte, PageSize) if err := table.handle.ReadPage(pid, hbuf); err != nil { break } next := page.HdrOf(hbuf).NextPage deferFree(pid) pid = next } } // 5. 重建空 Data / Heap 首页 (与 Create 的初始布局一致)。 baseHdr := uint32(unsafe.Sizeof(page.PageHdr{})) dataPID := table.handle.NewPage() dbuf := make([]byte, PageSize) dHdr := page.HdrOf(dbuf) dHdr.Type = page.DataIndex dHdr.PageID = dataPID dHdr.UseSize = baseHdr if err := table.handle.SavePage(dataPID, dbuf); err != nil { return err } info.FirstData = dataPID info.LastData = dataPID heapPID := table.handle.NewPage() hbuf := make([]byte, PageSize) hHdr := page.HdrOf(hbuf) hHdr.Type = page.HeapIndex hHdr.PageID = heapPID hHdr.UseSize = baseHdr if err := table.handle.SavePage(heapPID, hbuf); err != nil { return err } info.FirstHeap = heapPID info.LastHeap = heapPID info.HeapPageCount = 1 // 6. 行数清零并保存表信息。 info.TotalRows = 0 table.saveTableInfo() return nil }