datapage.go 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232
  1. package datapage
  2. import (
  3. "git.ycl.pub/yl32940/TsDB2/DB/core/page"
  4. "encoding/binary"
  5. "errors"
  6. "sort"
  7. "sync"
  8. )
  9. // PageSize 索引页大小 (避免导入 index 包造成循环依赖)
  10. const PageSize = 16384
  11. var (
  12. // errRowTooLarge 行装不下当前页, 调用方需换页/重建。
  13. // 注: "未找到"语义统一用 page.ErrNotFound, 便于上层 errors.Is 判定, 不再各自造错误值。
  14. errRowTooLarge = errors.New("row too large, need heap overflow")
  15. )
  16. // dpBufPool 复用 DataPage 的 16KB 页缓冲。
  17. // 背景: 插入路径每行至少 Load 一次主数据页, 旧实现每次 make(16KB) ——
  18. // 灌数时贡献 ~10% 的总分配量。协议与 btree 包的 nodeBufPool 相同:
  19. // 只在调用方能证明 DataPage 已死的位置 release, 其余路径照旧分配。
  20. var dpBufPool = sync.Pool{New: func() any { b := make([]byte, PageSize); return &b }}
  21. // Release 把页缓冲归还池中。调用方必须保证此后不再访问该 DataPage
  22. // (buf 的任何视图都作废)。导出供 index 包的插入热路径使用。
  23. func (dp *DataPage) Release() {
  24. if dp == nil || len(dp.buf) != PageSize || cap(dp.buf) < PageSize {
  25. return
  26. }
  27. b := dp.buf
  28. dpBufPool.Put(&b)
  29. }
  30. // =============================================================================
  31. // 统一 DataPage — 所有需要独立数据页的索引类型共享
  32. //
  33. // 磁盘布局 (权威定义见 page.DataPageHdr):
  34. //
  35. // [PageHdr(20B) + Count(4B) + Min(8B) + Max(8B)] = 40B header
  36. // [len(4B) + rowData] ... = 行数据区 (行区密度不变)
  37. // ... 空闲区 ...
  38. // [slot 目录: slotCount × 2B, 从页尾向前生长]
  39. // [slotCount(2B, 页尾最后 2 字节)]
  40. //
  41. // len 最高位 (0x80000000) 为软删除标志 (行内墓碑)
  42. // Min/Max 存储 rowData 前 8 字节的最小/最大值 (BigEndian 比较)
  43. //
  44. // 槽位目录 (Slot):
  45. // - slot i (1 基) 存放在偏移 PageSize-2-2*i 处, 值 = 该行 4B 长度前缀的页内偏移;
  46. // 0 表示空槽 (行已被整理页物理移除, 槽位可复用)。
  47. // - 槽位号稳定: 整理页 (Compact) 只搬动行、改写偏移, 绝不改变槽位号 ——
  48. // 因此主键 B+ 树叶条目里的 (dpID, slot) 在整理后依然有效。
  49. // - 槽位从页尾向前生长, 新增槽位不会移动既有槽位, 无需 memmove。
  50. //
  51. // 为什么需要槽位: 删除/更新采用「行内墓碑 + 写新行」后, 页内会长期存在失效字节,
  52. // 必须能在不重编号的前提下整理页; 而主键索引又希望命中后 O(1) 取行而非页内扫描。
  53. //
  54. // =============================================================================
  55. // HdrSize 数据页头部大小 (直接复用 page 包的权威布局定义)
  56. const HdrSize = page.DataPageHdrSize
  57. // DeletedFlag 软删除标志 (长度字段最高位)
  58. const DeletedFlag uint32 = 0x80000000
  59. // 槽位目录参数
  60. const (
  61. slotEntrySize = 2 // 每条槽位 2B: 行起始偏移 (uint16 LE); 0 = 空槽
  62. slotCountSize = 2 // 页尾最后 2B: 槽位总数 (uint16 LE)
  63. // maxSlotCount 槽位目录理论上限 (行区至少要能放下 4B 长度前缀)
  64. maxSlotCount = (PageSize - HdrSize - slotCountSize) / (slotEntrySize + 4)
  65. )
  66. // CompactDeadRatio 整理页阈值: 页内失效 (墓碑) 字节占已用行区字节的比例。
  67. // 超过该比例时 Compact / MaybeCompact 会把活行紧凑重排并回收墓碑空间。
  68. // 这是一个可配置参数, 默认 0.4 (40%); 设为 <=0 可关闭自动整理。
  69. var CompactDeadRatio = 0.4
  70. // SetCompactDeadRatio 设置整理页阈值 (供上层配置, 例如按表 / 按库调整)。
  71. func SetCompactDeadRatio(r float64) { CompactDeadRatio = r }
  72. // DataPage 统一数据页
  73. type DataPage struct {
  74. handle page.HandleDb
  75. pageID uint32
  76. buf []byte
  77. count uint32 // 行区内物理行数 (含墓碑) == 占用中的槽位数
  78. dataEnd uint32 // 行区结束偏移 (= 空闲区起点)
  79. slotCount uint32 // 槽位目录容量 (含空槽)
  80. deadBytes uint32 // 失效字节数 (4B 前缀 + 墓碑行数据) —— 页头 Dead 的内存镜像
  81. liveCount uint32 // 未删除行数 —— 页头 LiveCnt 的内存镜像
  82. }
  83. // ======================== 页 I/O ========================
  84. // New 创建一个新的空数据页
  85. func New(handle page.HandleDb) *DataPage {
  86. pid := handle.NewPage()
  87. buf := make([]byte, PageSize)
  88. hdr := page.HdrOf(buf)
  89. hdr.Type = page.DataIndex
  90. hdr.PageID = pid
  91. hdr.UseSize = HdrSize
  92. return &DataPage{handle: handle, pageID: pid, buf: buf, count: 0, dataEnd: HdrSize}
  93. }
  94. // Load 从磁盘加载指定页
  95. func Load(handle page.HandleDb, pageID uint32) (*DataPage, error) {
  96. buf := *dpBufPool.Get().(*[]byte)
  97. if err := handle.ReadPage(pageID, buf); err != nil {
  98. dpBufPool.Put(&buf)
  99. return nil, err
  100. }
  101. hdr := page.HdrOf(buf)
  102. dh := page.DataHdrOf(buf)
  103. cnt := dh.Count
  104. dataEnd := hdr.UseSize
  105. // 槽位目录容量存在页尾; 必须校验 "目录不侵入行区", 否则一律按无槽位处理
  106. // (被行级 WAL 直接改写的页可能留下垃圾尾字节)。
  107. sc := uint32(binary.LittleEndian.Uint16(buf[PageSize-slotCountSize:]))
  108. if sc > maxSlotCount || uint32(PageSize)-slotCountSize-sc*slotEntrySize < dataEnd {
  109. sc = 0
  110. binary.LittleEndian.PutUint16(buf[PageSize-slotCountSize:], 0)
  111. }
  112. // LiveCnt/Dead 由所有写路径增量维护并随页落盘 (见 page.DataPageHdr),
  113. // 这里直接读页头 —— 不再全页走链重算 (旧行为让每次 Load 都是 O(页内行数),
  114. // 对所有查询路径收税, 无条件 COUNT(*) 更是被它拖回逐行)。
  115. dp := &DataPage{
  116. handle: handle, pageID: pageID, buf: buf,
  117. count: cnt, dataEnd: dataEnd, slotCount: sc,
  118. deadBytes: dh.Dead, liveCount: dh.LiveCnt,
  119. }
  120. return dp, nil
  121. }
  122. // Save 持久化到磁盘
  123. func (dp *DataPage) Save() {
  124. page.HdrOf(dp.buf).UseSize = dp.dataEnd
  125. dh := page.DataHdrOf(dp.buf)
  126. dh.Count = dp.count
  127. dh.LiveCnt = dp.liveCount
  128. dh.Dead = dp.deadBytes
  129. _ = dp.handle.SavePage(dp.pageID, dp.buf)
  130. }
  131. // PageID 返回当前页 ID
  132. func (dp *DataPage) PageID() uint32 { return dp.pageID }
  133. // Count 返回行区内物理行数 (含墓碑)
  134. func (dp *DataPage) Count() uint32 { return dp.count }
  135. // SlotCount 返回槽位目录容量 (含空槽)
  136. func (dp *DataPage) SlotCount() uint32 { return dp.slotCount }
  137. // DeadBytes 返回页内失效 (墓碑) 字节数
  138. func (dp *DataPage) DeadBytes() uint32 { return dp.deadBytes }
  139. // DeadRatio 返回失效字节占已用行区的比例 (0~1)
  140. func (dp *DataPage) DeadRatio() float64 {
  141. used := dp.dataEnd - HdrSize
  142. if used == 0 {
  143. return 0
  144. }
  145. return float64(dp.deadBytes) / float64(used)
  146. }
  147. // freeLimit 行区允许增长到的上限 (为"新增一个槽位"预留目录空间)
  148. func (dp *DataPage) freeLimit() uint32 {
  149. return uint32(PageSize) - slotCountSize - (dp.slotCount+1)*slotEntrySize
  150. }
  151. // canFit 判断行区再放入 need 字节 (含 4B 前缀) 是否仍不侵入槽位目录
  152. func (dp *DataPage) canFit(need uint32) bool {
  153. return dp.dataEnd+need <= dp.freeLimit()
  154. }
  155. // IsFull 判断是否有足够空间追加新行 (rowSize 约定: 不含 4B 长度前缀)
  156. func (dp *DataPage) IsFull(rowSize uint32) bool {
  157. return dp.dataEnd+rowSize+4 > dp.freeLimit()
  158. }
  159. // ======================== 槽位目录 ========================
  160. // slotPos 槽位 i (1 基) 在页内的字节偏移。
  161. func (dp *DataPage) slotPos(slotID uint32) uint32 {
  162. return uint32(PageSize) - slotCountSize - slotID*slotEntrySize
  163. }
  164. // getSlot 读取槽位存储的行起始偏移 (0 = 空槽)
  165. func (dp *DataPage) getSlot(slotID uint32) uint32 {
  166. p := dp.slotPos(slotID)
  167. return uint32(binary.LittleEndian.Uint16(dp.buf[p:]))
  168. }
  169. // putSlot 写入槽位
  170. func (dp *DataPage) putSlot(slotID, off uint32) {
  171. p := dp.slotPos(slotID)
  172. binary.LittleEndian.PutUint16(dp.buf[p:], uint16(off))
  173. }
  174. // setSlotCount 写入槽位目录容量 (页尾)
  175. func (dp *DataPage) setSlotCount(n uint32) {
  176. binary.LittleEndian.PutUint16(dp.buf[PageSize-slotCountSize:], uint16(n))
  177. }
  178. // allocSlot 为一个位于 off 的行分配槽位: 优先复用空槽, 否则扩容目录。
  179. // 返回 0 表示目录已满 (正常情况下 canFit 已挡住)。
  180. func (dp *DataPage) allocSlot(off uint32) uint32 {
  181. for i := uint32(1); i <= dp.slotCount; i++ {
  182. if dp.getSlot(i) == 0 {
  183. dp.putSlot(i, off)
  184. return i
  185. }
  186. }
  187. if dp.slotCount >= maxSlotCount {
  188. return 0
  189. }
  190. dp.slotCount++
  191. dp.setSlotCount(dp.slotCount)
  192. dp.putSlot(dp.slotCount, off)
  193. return dp.slotCount
  194. }
  195. // shiftSlots 把偏移 >= from 的槽位整体后移 delta (行区内插入行导致)。
  196. func (dp *DataPage) shiftSlots(from, delta uint32) {
  197. for i := uint32(1); i <= dp.slotCount; i++ {
  198. o := dp.getSlot(i)
  199. if o != 0 && o >= from {
  200. dp.putSlot(i, o+delta)
  201. }
  202. }
  203. }
  204. // SlotOffset 返回槽位指向的行起始偏移 (0 = 空槽)。供诊断/测试使用。
  205. func (dp *DataPage) SlotOffset(slotID uint32) uint32 {
  206. if slotID == 0 || slotID > dp.slotCount {
  207. return 0
  208. }
  209. return dp.getSlot(slotID)
  210. }
  211. // ======================== 失效字节统计 / 整理页 ========================
  212. // Compact 整理页: 按原有页内顺序紧凑重排活行, 回收墓碑占用的空间并释放其槽位。
  213. // 槽位号保持不变 (活行改写偏移, 墓碑槽位置 0), 因此 B+ 树叶条目无需改动。
  214. // 返回是否真正做了整理。
  215. func (dp *DataPage) Compact() bool {
  216. type ent struct {
  217. slot uint32
  218. off uint32
  219. }
  220. ents := make([]ent, 0, dp.slotCount)
  221. for i := uint32(1); i <= dp.slotCount; i++ {
  222. if o := dp.getSlot(i); o != 0 {
  223. ents = append(ents, ent{i, o})
  224. }
  225. }
  226. if len(ents) == 0 {
  227. dp.deadBytes = 0
  228. dp.liveCount = 0
  229. return false
  230. }
  231. // 必须按页内物理偏移排序重排, 否则会打乱 "行按 pkID/键升序" 的不变式。
  232. sort.Slice(ents, func(a, b int) bool { return ents[a].off < ents[b].off })
  233. liveRows := make([][]byte, 0, len(ents))
  234. liveSlots := make([]uint32, 0, len(ents))
  235. dead := 0
  236. for _, e := range ents {
  237. rowLen, deleted, rowStart, ok := dp.nextRow(e.off)
  238. if !ok {
  239. dp.putSlot(e.slot, 0) // 坏槽位: 释放
  240. continue
  241. }
  242. if deleted {
  243. dp.putSlot(e.slot, 0)
  244. dead++
  245. continue
  246. }
  247. row := make([]byte, rowLen)
  248. copy(row, dp.buf[rowStart:rowStart+rowLen])
  249. liveRows = append(liveRows, row)
  250. liveSlots = append(liveSlots, e.slot)
  251. }
  252. if dead == 0 {
  253. dp.deadBytes = 0
  254. return false
  255. }
  256. w := HdrSize
  257. for i, r := range liveRows {
  258. dp.putSlot(liveSlots[i], w)
  259. binary.LittleEndian.PutUint32(dp.buf[w:], uint32(len(r)))
  260. w += 4
  261. copy(dp.buf[w:], r)
  262. w += uint32(len(r))
  263. }
  264. for i := w; i < dp.dataEnd; i++ {
  265. dp.buf[i] = 0
  266. }
  267. dp.dataEnd = w
  268. dp.count = uint32(len(liveRows))
  269. dp.deadBytes = 0
  270. dp.liveCount = uint32(len(liveRows))
  271. dp.recomputeMinMax(liveRows)
  272. return true
  273. }
  274. // MaybeCompact 失效字节占比超过 CompactDeadRatio 时整理页。
  275. func (dp *DataPage) MaybeCompact() bool {
  276. if CompactDeadRatio <= 0 || dp.deadBytes == 0 {
  277. return false
  278. }
  279. if dp.DeadRatio() < CompactDeadRatio {
  280. return false
  281. }
  282. return dp.Compact()
  283. }
  284. // CompactForInsert 在"即将为本页装不下的行新开一页"之前调用: 按配置 (CompactDeadRatio)
  285. // 就地整理本页, 把墓碑占用的空间还给活行。返回是否真的整理了 (整理后本页已落盘)。
  286. //
  287. // 为什么放在这里而不是删除路径上: 删除时整理是"顺手做一件可能没用的事"(代价大、收益未知),
  288. // 而此刻的整理目的非常明确 —— 若整理后装得下, 就省下了一次开新页 (以及随之而来的
  289. // 页面碎片: 每页只放少量行、扫描要读更多页)。所以本方法要求"整理确实能把 need 塞进去",
  290. // 否则宁可让调用方开新页, 也不白搬一趟 (整理是 O(页内行数) 的排序 + 拷贝)。
  291. //
  292. // 阈值仍由 CompactDeadRatio 决定 (<=0 即关闭自动整理, 见 MaybeCompact): 配置为激进值,
  293. // 整理就更容易发生; 配置为 0, 行为完全退回"装不下就开新页"。
  294. func (dp *DataPage) CompactForInsert(need uint32) bool {
  295. // 没有墓碑可回收, 或把墓碑全还给活行也仍然装不下 → 不值得整理
  296. if dp.deadBytes == 0 || dp.dataEnd-dp.deadBytes+need > dp.freeLimit() {
  297. return false
  298. }
  299. if !dp.MaybeCompact() {
  300. return false
  301. }
  302. // Compact 只改内存缓冲; 且它保持槽位号不变, 所以先落盘绝不会让 B+ 树叶条目指错行。
  303. dp.Save()
  304. return true
  305. }
  306. // ======================== Min/Max 维护 ========================
  307. // getMin 读取当前 Min 值
  308. func (dp *DataPage) getMin() uint64 { return page.DataHdrOf(dp.buf).GetMin() }
  309. // getMax 读取当前 Max 值
  310. func (dp *DataPage) getMax() uint64 { return page.DataHdrOf(dp.buf).GetMax() }
  311. // MinPK 返回页内最小 pkID (Min 字段的公开访问)
  312. func (dp *DataPage) MinPK() uint64 { return dp.getMin() }
  313. // MaxPK 返回页内最大 pkID (Max 字段的公开访问)
  314. func (dp *DataPage) MaxPK() uint64 { return dp.getMax() }
  315. // updateMinMax 追加行后更新 Min/Max (取 rowData 前 8 字节 BigEndian 比较)
  316. func (dp *DataPage) updateMinMax(rowData []byte) {
  317. if len(rowData) < 8 {
  318. return
  319. }
  320. val := binary.BigEndian.Uint64(rowData[:8])
  321. dh := page.DataHdrOf(dp.buf)
  322. if dp.count <= 1 {
  323. // 第一行 (或刚从 0 变 1), 直接设置
  324. dh.SetMin(val)
  325. dh.SetMax(val)
  326. return
  327. }
  328. if val < dh.GetMin() {
  329. dh.SetMin(val)
  330. }
  331. if val > dh.GetMax() {
  332. dh.SetMax(val)
  333. }
  334. }
  335. // recomputeMinMax 依据活行重算 Min/Max (取每行前 8 字节 BigEndian)
  336. func (dp *DataPage) recomputeMinMax(rows [][]byte) {
  337. var minV, maxV uint64
  338. first := true
  339. for _, r := range rows {
  340. if len(r) < 8 {
  341. continue
  342. }
  343. v := binary.BigEndian.Uint64(r[:8])
  344. if first {
  345. minV, maxV, first = v, v, false
  346. continue
  347. }
  348. if v < minV {
  349. minV = v
  350. }
  351. if v > maxV {
  352. maxV = v
  353. }
  354. }
  355. if first {
  356. minV, maxV = 0, 0
  357. }
  358. dh := page.DataHdrOf(dp.buf)
  359. dh.SetMin(minV)
  360. dh.SetMax(maxV)
  361. }
  362. // ======================== 行操作 ========================
  363. // AppendRow 追加一行 (不返回槽位)。
  364. // storeRow 格式: [pkID(8B BE) + rowData] 或纯 rowData, 由调用方决定
  365. func (dp *DataPage) AppendRow(storeRow []byte) { dp.AppendRowSlot(storeRow) }
  366. // AppendRowSlot 追加一行并返回其槽位号 (0 表示装不下)。
  367. func (dp *DataPage) AppendRowSlot(storeRow []byte) uint32 {
  368. need := uint32(4 + len(storeRow))
  369. if !dp.canFit(need) {
  370. return 0
  371. }
  372. off := dp.dataEnd
  373. binary.LittleEndian.PutUint32(dp.buf[off:], uint32(len(storeRow)))
  374. copy(dp.buf[off+4:], storeRow)
  375. dp.dataEnd = off + need
  376. dp.count++
  377. dp.liveCount++
  378. dp.updateMinMax(storeRow)
  379. return dp.allocSlot(off)
  380. }
  381. // InsertRowSorted 保持"页内行按 pkID 升序"不变式, 把行插到正确位置。
  382. // 返回 false 表示页内空间不足, 调用方需换页。
  383. //
  384. // 为什么不能直接用 AppendRow: FindRowByPK 依赖行按 pkID 升序, 遇到比目标更大的行就
  385. // 提前判定"不存在"。主键由调用方显式给出时 (自定义主键 / BaseModel.ID) 插入顺序与
  386. // pkID 顺序无关, AppendRow 会把小 pkID 排到大 pkID 后面 → 行明明在页里, 却查不到
  387. // (全叶扫描兜底也走同一个 FindRowByPK, 同样漏)。
  388. func (dp *DataPage) InsertRowSorted(storeRow []byte) bool {
  389. _, ok := dp.InsertRowSortedSlot(storeRow)
  390. return ok
  391. }
  392. // InsertRowSortedSlot 同 InsertRowSorted, 额外返回新行的槽位号。
  393. func (dp *DataPage) InsertRowSortedSlot(storeRow []byte) (uint32, bool) {
  394. return dp.insertRowSortedByOffsetSlot(storeRow, 0)
  395. }
  396. // InsertRowSortedByOffset 按 storeRow 中 keyOff 处的 8 字节 BigEndian 键升序插入。
  397. //
  398. // 与 InsertRowSorted 的唯一区别只是"排序键在哪":
  399. // - 主键 / 二级索引的行, 键(或 pkID 前缀)在偏移 0;
  400. // - 时序行是 [pkID(8B)][schemaVer(1B)][字段...], 时间键(首列)在偏移 9。
  401. //
  402. // 两种布局都需要页内有序: 有序的页才能让查找遇到更大键就提前终止, 也让范围扫描
  403. // 能在页内直接二分/截断, 而不是整页逐行过滤。行长度不足以容纳该键时退化为追加。
  404. func (dp *DataPage) InsertRowSortedByOffset(storeRow []byte, keyOff int) bool {
  405. _, ok := dp.insertRowSortedByOffsetSlot(storeRow, keyOff)
  406. return ok
  407. }
  408. func (dp *DataPage) insertRowSortedByOffsetSlot(storeRow []byte, keyOff int) (uint32, bool) {
  409. need := uint32(4 + len(storeRow))
  410. if !dp.canFit(need) {
  411. return 0, false
  412. }
  413. hasKey := keyOff >= 0 && len(storeRow) >= keyOff+8
  414. var key uint64
  415. if hasKey {
  416. key = binary.BigEndian.Uint64(storeRow[keyOff : keyOff+8])
  417. }
  418. // 找到第一个排序键大于 key 的行, 插在它前面; 没有则追加到末尾
  419. ins := dp.dataEnd
  420. if hasKey {
  421. off := HdrSize
  422. for i := uint32(0); i < dp.count; i++ {
  423. rowLen, _, rowStart, ok := dp.nextRow(off)
  424. if !ok {
  425. break
  426. }
  427. if rowLen >= uint32(keyOff+8) &&
  428. binary.BigEndian.Uint64(dp.buf[rowStart+uint32(keyOff):rowStart+uint32(keyOff)+8]) > key {
  429. ins = off
  430. break
  431. }
  432. off = rowStart + rowLen
  433. }
  434. }
  435. if ins < dp.dataEnd {
  436. copy(dp.buf[ins+need:dp.dataEnd+need], dp.buf[ins:dp.dataEnd])
  437. dp.shiftSlots(ins, need) // 行整体后移: 槽位偏移同步
  438. }
  439. binary.LittleEndian.PutUint32(dp.buf[ins:], uint32(len(storeRow)))
  440. copy(dp.buf[ins+4:], storeRow)
  441. dp.dataEnd += need
  442. dp.count++
  443. dp.liveCount++
  444. dp.updateMinMax(storeRow)
  445. return dp.allocSlot(ins), true
  446. }
  447. // ======================== 按槽位读写 ========================
  448. // ReadRowBySlot 按槽位号读取行数据副本 (墓碑/空槽返回 false)。
  449. func (dp *DataPage) ReadRowBySlot(slotID uint32) ([]byte, bool) {
  450. return dp.readRowBySlot(slotID, nil)
  451. }
  452. // ReadRowBySlotPK 按槽位号读取行, 并校验行首 8B 是否等于 pkKeyBE (防御陈旧槽位)。
  453. func (dp *DataPage) ReadRowBySlotPK(slotID uint32, pkKeyBE []byte) ([]byte, bool) {
  454. if len(pkKeyBE) < 8 {
  455. return nil, false
  456. }
  457. return dp.readRowBySlot(slotID, pkKeyBE)
  458. }
  459. func (dp *DataPage) readRowBySlot(slotID uint32, pkKeyBE []byte) ([]byte, bool) {
  460. if slotID == 0 || slotID > dp.slotCount {
  461. return nil, false
  462. }
  463. off := dp.getSlot(slotID)
  464. if off == 0 {
  465. return nil, false
  466. }
  467. rowLen, deleted, rowStart, ok := dp.nextRow(off)
  468. if !ok || deleted || rowLen == 0 {
  469. return nil, false
  470. }
  471. if pkKeyBE != nil {
  472. if rowLen < 8 || binary.BigEndian.Uint64(dp.buf[rowStart:rowStart+8]) != binary.BigEndian.Uint64(pkKeyBE[:8]) {
  473. return nil, false
  474. }
  475. }
  476. result := make([]byte, rowLen)
  477. copy(result, dp.buf[rowStart:rowStart+rowLen])
  478. return result, true
  479. }
  480. // DeleteBySlot 对指定槽位上的行打墓碑 (行内直接标记删除), 返回是否成功。
  481. // 失效字节随之累加, 供整理页阈值判断。
  482. func (dp *DataPage) DeleteBySlot(slotID uint32) bool {
  483. if slotID == 0 || slotID > dp.slotCount {
  484. return false
  485. }
  486. off := dp.getSlot(slotID)
  487. if off == 0 {
  488. return false
  489. }
  490. rowLen, deleted, _, ok := dp.nextRow(off)
  491. if !ok || deleted {
  492. return false
  493. }
  494. binary.LittleEndian.PutUint32(dp.buf[off:], rowLen|DeletedFlag)
  495. dp.deadBytes += 4 + rowLen
  496. dp.liveCount--
  497. return true
  498. }
  499. // ======================== 查找 ========================
  500. // FindRowByPK 按 pkID 查找 (pkKeyBE 为 BigEndian, 匹配 storedRow[0:8])
  501. // 优化: 行按 pkID 顺序存储, 找到 pkID > target 时提前终止
  502. //
  503. // ⚠️ 仅当页内行确实按 pkID 升序时才能用: 提前终止会把"页里明明有"的行判成不存在。
  504. // 时序 DataPage 的行按时间键排序 (见 InsertRowSortedByOffset), pkID 无序, 必须走
  505. // FindRowByPKLinear。
  506. func (dp *DataPage) FindRowByPK(pkKeyBE []byte) ([]byte, bool) {
  507. _, row, ok := dp.findRowByPKSlot(pkKeyBE)
  508. return row, ok
  509. }
  510. // FindRowByPKSlot 同 FindRowByPK, 额外返回该行的槽位号 (便于后续按槽位标删)。
  511. func (dp *DataPage) FindRowByPKSlot(pkKeyBE []byte) (uint32, []byte, bool) {
  512. return dp.findRowByPKSlot(pkKeyBE)
  513. }
  514. func (dp *DataPage) findRowByPKSlot(pkKeyBE []byte) (uint32, []byte, bool) {
  515. keyPK := binary.BigEndian.Uint64(pkKeyBE[:8])
  516. for i := uint32(1); i <= dp.slotCount; i++ {
  517. off := dp.getSlot(i)
  518. if off == 0 {
  519. continue
  520. }
  521. rowLen, deleted, rowStart, ok := dp.nextRow(off)
  522. if !ok || deleted || rowLen < 8 {
  523. continue
  524. }
  525. rowPK := binary.BigEndian.Uint64(dp.buf[rowStart : rowStart+8])
  526. if rowPK == keyPK {
  527. result := make([]byte, rowLen)
  528. copy(result, dp.buf[rowStart:rowStart+rowLen])
  529. return i, result, true
  530. }
  531. }
  532. return 0, nil, false
  533. }
  534. // FindRowByPKLinear 按 pkID 全页扫描查找, 不做"遇到更大行号即提前结束"的优化。
  535. // 页内行不按 pkID 有序时 (时序 DataPage 按时间键排序) 必须用本方法, 否则会漏行。
  536. func (dp *DataPage) FindRowByPKLinear(pkKeyBE []byte) ([]byte, bool) {
  537. keyPK := binary.BigEndian.Uint64(pkKeyBE[:8])
  538. off := HdrSize
  539. for i := uint32(0); i < dp.count; i++ {
  540. rowLen, deleted, rowStart, ok := dp.nextRow(off)
  541. if !ok {
  542. return nil, false
  543. }
  544. off = rowStart + rowLen
  545. if deleted || rowLen < 8 {
  546. continue
  547. }
  548. if binary.BigEndian.Uint64(dp.buf[rowStart:rowStart+8]) == keyPK {
  549. result := make([]byte, rowLen)
  550. copy(result, dp.buf[rowStart:rowStart+rowLen])
  551. return result, true
  552. }
  553. }
  554. return nil, false
  555. }
  556. // FindRowByIndexKey 按 rowData 中指定偏移处的 8 字节匹配
  557. // indexKeyBE 为 BigEndian 编码的索引键
  558. // storedOffset 为 storedRow 中比较的起始偏移
  559. func (dp *DataPage) FindRowByIndexKey(indexKeyBE []byte, storedOffset int) ([]byte, bool) {
  560. _, row, ok := dp.findRowByIndexKeySlot(indexKeyBE, storedOffset)
  561. return row, ok
  562. }
  563. // FindRowByIndexKeySlot 同 FindRowByIndexKey, 额外返回槽位号。
  564. func (dp *DataPage) FindRowByIndexKeySlot(indexKeyBE []byte, storedOffset int) (uint32, []byte, bool) {
  565. return dp.findRowByIndexKeySlot(indexKeyBE, storedOffset)
  566. }
  567. func (dp *DataPage) findRowByIndexKeySlot(indexKeyBE []byte, storedOffset int) (uint32, []byte, bool) {
  568. keyVal := binary.BigEndian.Uint64(indexKeyBE[:8])
  569. for i := uint32(1); i <= dp.slotCount; i++ {
  570. off := dp.getSlot(i)
  571. if off == 0 {
  572. continue
  573. }
  574. rowLen, deleted, rowStart, ok := dp.nextRow(off)
  575. if !ok || deleted {
  576. continue
  577. }
  578. if int(rowLen) >= storedOffset+8 {
  579. rowVal := binary.BigEndian.Uint64(dp.buf[rowStart+uint32(storedOffset) : rowStart+uint32(storedOffset)+8])
  580. if rowVal == keyVal {
  581. result := make([]byte, rowLen)
  582. copy(result, dp.buf[rowStart:rowStart+rowLen])
  583. return i, result, true
  584. }
  585. }
  586. }
  587. return 0, nil, false
  588. }
  589. // ======================== 软删除 ========================
  590. // DeleteByPK 按 pkID 软删除 (行内直接打墓碑), 返回是否找到
  591. func (dp *DataPage) DeleteByPK(pkKeyBE []byte) bool {
  592. _, ok := dp.DeleteByPKSlot(pkKeyBE)
  593. return ok
  594. }
  595. // DeleteByPKSlot 同 DeleteByPK, 额外返回被删除行的槽位号。
  596. func (dp *DataPage) DeleteByPKSlot(pkKeyBE []byte) (uint32, bool) {
  597. keyPK := binary.BigEndian.Uint64(pkKeyBE[:8])
  598. off := HdrSize
  599. for i := uint32(0); i < dp.count; i++ {
  600. rowLen, deleted, rowStart, ok := dp.nextRow(off)
  601. if !ok {
  602. return 0, false
  603. }
  604. lenOff := off
  605. off = rowStart + rowLen
  606. if deleted {
  607. continue
  608. }
  609. if rowLen >= 8 {
  610. rowPK := binary.BigEndian.Uint64(dp.buf[rowStart : rowStart+8])
  611. if rowPK == keyPK {
  612. binary.LittleEndian.PutUint32(dp.buf[lenOff:], rowLen|DeletedFlag)
  613. dp.deadBytes += 4 + rowLen
  614. dp.liveCount--
  615. return dp.slotOfOffset(lenOff), true
  616. }
  617. }
  618. }
  619. return 0, false
  620. }
  621. // DeleteByIndexKey 按 rowData 中指定偏移处的 8 字节匹配并软删除
  622. func (dp *DataPage) DeleteByIndexKey(indexKeyBE []byte, storedOffset int) bool {
  623. _, ok := dp.DeleteByIndexKeySlot(indexKeyBE, storedOffset)
  624. return ok
  625. }
  626. // DeleteByIndexKeySlot 同 DeleteByIndexKey, 额外返回被删除行的槽位号。
  627. func (dp *DataPage) DeleteByIndexKeySlot(indexKeyBE []byte, storedOffset int) (uint32, bool) {
  628. keyVal := binary.BigEndian.Uint64(indexKeyBE[:8])
  629. off := HdrSize
  630. for i := uint32(0); i < dp.count; i++ {
  631. rowLen, deleted, rowStart, ok := dp.nextRow(off)
  632. if !ok {
  633. return 0, false
  634. }
  635. lenOff := off
  636. off = rowStart + rowLen
  637. if deleted {
  638. continue
  639. }
  640. if int(rowLen) >= storedOffset+8 {
  641. rowVal := binary.BigEndian.Uint64(dp.buf[rowStart+uint32(storedOffset) : rowStart+uint32(storedOffset)+8])
  642. if rowVal == keyVal {
  643. binary.LittleEndian.PutUint32(dp.buf[lenOff:], rowLen|DeletedFlag)
  644. dp.deadBytes += 4 + rowLen
  645. dp.liveCount--
  646. return dp.slotOfOffset(lenOff), true
  647. }
  648. }
  649. }
  650. return 0, false
  651. }
  652. // slotOfOffset 反查行前缀偏移对应的槽位号 (0 = 无)。
  653. func (dp *DataPage) slotOfOffset(off uint32) uint32 {
  654. for i := uint32(1); i <= dp.slotCount; i++ {
  655. if dp.getSlot(i) == off {
  656. return i
  657. }
  658. }
  659. return 0
  660. }
  661. // ======================== 原地更新 / 页内重建 ========================
  662. // UpdateByPK 按 pkID 原地更新行数据 (newStoreRow 格式同 AppendRow)
  663. func (dp *DataPage) UpdateByPK(pkKeyBE []byte, newStoreRow []byte) error {
  664. keyPK := binary.BigEndian.Uint64(pkKeyBE[:8])
  665. off := HdrSize
  666. for i := uint32(0); i < dp.count; i++ {
  667. rowLen, deleted, rowStart, ok := dp.nextRow(off)
  668. if !ok {
  669. break
  670. }
  671. off = rowStart + rowLen
  672. if deleted {
  673. continue
  674. }
  675. if rowLen >= 8 {
  676. rowPK := binary.BigEndian.Uint64(dp.buf[rowStart : rowStart+8])
  677. if rowPK == keyPK {
  678. if uint32(len(newStoreRow)) <= rowLen {
  679. copy(dp.buf[rowStart:], newStoreRow)
  680. for j := uint32(len(newStoreRow)); j < rowLen; j++ {
  681. dp.buf[rowStart+j] = 0
  682. }
  683. dp.Save()
  684. return nil
  685. }
  686. return errRowTooLarge
  687. }
  688. }
  689. }
  690. return page.ErrNotFound
  691. }
  692. // UpdateBySlot 按槽位号原地更新行数据 (newStoreRow 格式同 AppendRow)。
  693. //
  694. // 槽位号来自主键叶条目, 定位是 O(1); 新行长度不超过旧行时直接覆盖, 否则返回
  695. // errRowTooLarge, 由调用方转 RebuildWithSlot / 换页。
  696. // 不变式 I2: 槽位号不变, 只改行内字节。
  697. func (dp *DataPage) UpdateBySlot(slotID uint32, newStoreRow []byte) error {
  698. if slotID == 0 || slotID > dp.slotCount {
  699. return page.ErrNotFound
  700. }
  701. off := dp.getSlot(slotID)
  702. if off == 0 {
  703. return page.ErrNotFound
  704. }
  705. rowLen, deleted, rowStart, ok := dp.nextRow(off)
  706. if !ok || deleted {
  707. return page.ErrNotFound
  708. }
  709. if uint32(len(newStoreRow)) > rowLen {
  710. return errRowTooLarge
  711. }
  712. copy(dp.buf[rowStart:], newStoreRow)
  713. for j := uint32(len(newStoreRow)); j < rowLen; j++ {
  714. dp.buf[rowStart+j] = 0
  715. }
  716. dp.Save()
  717. return nil
  718. }
  719. // FindAndUpdateByIndexKey 按 rowData 偏移匹配找到行后, 用 newStoreRow 原地更新
  720. func (dp *DataPage) FindAndUpdateByIndexKey(indexKeyBE []byte, storedOffset int, newStoreRow []byte) error {
  721. keyVal := binary.BigEndian.Uint64(indexKeyBE[:8])
  722. off := HdrSize
  723. for i := uint32(0); i < dp.count; i++ {
  724. rowLen, deleted, rowStart, ok := dp.nextRow(off)
  725. if !ok {
  726. break
  727. }
  728. off = rowStart + rowLen
  729. if deleted {
  730. continue
  731. }
  732. if int(rowLen) >= storedOffset+8 {
  733. rowVal := binary.BigEndian.Uint64(dp.buf[rowStart+uint32(storedOffset) : rowStart+uint32(storedOffset)+8])
  734. if rowVal == keyVal {
  735. if uint32(len(newStoreRow)) <= rowLen {
  736. copy(dp.buf[rowStart:], newStoreRow)
  737. for j := uint32(len(newStoreRow)); j < rowLen; j++ {
  738. dp.buf[rowStart+j] = 0
  739. }
  740. dp.Save()
  741. return nil
  742. }
  743. return errRowTooLarge
  744. }
  745. }
  746. }
  747. return page.ErrNotFound
  748. }
  749. // RebuildWith 页内重建 (repack): 保持活行原有顺序紧凑重排, 并把 pkKeyBE 对应的行
  750. // 原地替换为 newStoreRow, 允许该行**变长**。
  751. //
  752. // 用途: schema 演进 (AddField) 后旧行的 schemaVer 与行长都会变化, UpdateByPK 因
  753. // "新行长 > 旧行长" 返回 errRowTooLarge 时, 用本方法在页内腾挪:
  754. // - 顺带回收墓碑 (已删除行) 占用的碎片空间
  755. // - 只要整页还装得下, 变长行无需换页
  756. //
  757. // 安全性 (不变式 I2): 活行的槽位号保持不变 (只改写其指向的偏移), 墓碑槽位被释放,
  758. // 因此页内重排不会破坏主键 B+ 树叶条目里的 (dpID, slot)。
  759. //
  760. // 返回 page.ErrNotFound 表示目标行不在本页; 返回 errRowTooLarge 表示整页装不下, 调用方需换页。
  761. func (dp *DataPage) RebuildWith(pkKeyBE []byte, newStoreRow []byte) error {
  762. return dp.RebuildWithIndexKey(pkKeyBE, 0, newStoreRow)
  763. }
  764. // RebuildWithIndexKey 同 RebuildWith, 但按 storedRow 中 storedOffset 处的 8 字节匹配
  765. // (副索引/时序行布局: 见 FindAndUpdateByIndexKey)
  766. func (dp *DataPage) RebuildWithIndexKey(indexKeyBE []byte, storedOffset int, newStoreRow []byte) error {
  767. if len(indexKeyBE) < 8 {
  768. return page.ErrNotFound
  769. }
  770. keyVal := binary.BigEndian.Uint64(indexKeyBE[:8])
  771. return dp.rebuildCore(func(_ uint32, row []byte) bool {
  772. return len(row) >= storedOffset+8 &&
  773. binary.BigEndian.Uint64(row[storedOffset:storedOffset+8]) == keyVal
  774. }, newStoreRow)
  775. }
  776. // RebuildWithSlot 按槽位号定位并走页内重建, 语义同 RebuildWith, 但**不做键匹配** ——
  777. // 槽位号来自主键叶条目, 已经是 O(1) 定位, 省掉整页扫键。
  778. //
  779. // 注意: 调用方应先确认该槽位确实是目标行 (叶条目本身就是这个保证), 否则会替换到别的行。
  780. func (dp *DataPage) RebuildWithSlot(slotID uint32, newStoreRow []byte) error {
  781. if slotID == 0 || slotID > dp.slotCount {
  782. return page.ErrNotFound
  783. }
  784. return dp.rebuildCore(func(slot uint32, _ []byte) bool { return slot == slotID }, newStoreRow)
  785. }
  786. // rebuildCore 页内重建的公共实现: 按物理顺序收集活行, 把第一个 match 命中的行替换为
  787. // newStoreRow, 释放墓碑槽位后紧凑重写。
  788. //
  789. // 不变式 (必须守住): 活行的槽位号不变 —— 主键 B+ 树叶条目存的就是 (dpID, slot)。
  790. func (dp *DataPage) rebuildCore(match func(slot uint32, row []byte) bool, newStoreRow []byte) error {
  791. // 1. 按页内物理顺序收集 (槽位, 偏移), 保证重排后仍与原来同序
  792. type ent struct {
  793. slot uint32
  794. off uint32
  795. }
  796. ents := make([]ent, 0, dp.slotCount)
  797. for i := uint32(1); i <= dp.slotCount; i++ {
  798. if o := dp.getSlot(i); o != 0 {
  799. ents = append(ents, ent{i, o})
  800. }
  801. }
  802. sort.Slice(ents, func(a, b int) bool { return ents[a].off < ents[b].off })
  803. liveRows := make([][]byte, 0, len(ents))
  804. liveSlots := make([]uint32, 0, len(ents))
  805. deadSlots := make([]uint32, 0, len(ents))
  806. replaced := false
  807. for _, e := range ents {
  808. rowLen, deleted, rowStart, ok := dp.nextRow(e.off)
  809. if !ok || deleted {
  810. deadSlots = append(deadSlots, e.slot)
  811. continue
  812. }
  813. row := make([]byte, rowLen)
  814. copy(row, dp.buf[rowStart:rowStart+rowLen])
  815. if !replaced && match(e.slot, row) {
  816. replaced = true
  817. liveRows = append(liveRows, newStoreRow)
  818. liveSlots = append(liveSlots, e.slot)
  819. continue
  820. }
  821. liveRows = append(liveRows, row)
  822. liveSlots = append(liveSlots, e.slot)
  823. }
  824. if !replaced {
  825. return page.ErrNotFound
  826. }
  827. // 2. 预计算总占用, 装不下则告知调用方换页 (目录容量不变)
  828. const rowHdrSize = uint32(4) // 每行 4B 长度前缀
  829. total := HdrSize
  830. for _, r := range liveRows {
  831. total += rowHdrSize + uint32(len(r))
  832. }
  833. if total > uint32(PageSize)-slotCountSize-dp.slotCount*slotEntrySize {
  834. return errRowTooLarge
  835. }
  836. // 3. 按原顺序紧凑重写, 同步槽位
  837. for _, s := range deadSlots {
  838. dp.putSlot(s, 0)
  839. }
  840. w := HdrSize
  841. for i, r := range liveRows {
  842. dp.putSlot(liveSlots[i], w)
  843. binary.LittleEndian.PutUint32(dp.buf[w:], uint32(len(r)))
  844. w += rowHdrSize
  845. copy(dp.buf[w:], r)
  846. w += uint32(len(r))
  847. }
  848. for i := w; i < dp.dataEnd; i++ {
  849. dp.buf[i] = 0
  850. }
  851. dp.dataEnd = w
  852. dp.count = uint32(len(liveRows))
  853. dp.deadBytes = 0 // 墓碑已全部物理清除
  854. dp.liveCount = uint32(len(liveRows))
  855. dp.recomputeMinMax(liveRows)
  856. dp.Save()
  857. return nil
  858. }
  859. // ======================== 全页视图 ========================
  860. // AllDeleted 检查是否所有行都已删除 (O(1), 读页头镜像的存活计数)
  861. func (dp *DataPage) AllDeleted() bool { return dp.liveCount == 0 }
  862. // CountLiveRows 统计本页未删除的行数 (O(1), 读页头镜像的存活计数)。
  863. //
  864. // 为无条件 COUNT(*) 服务: 计数由所有写路径增量维护并随页落盘
  865. // (page.DataPageHdr.LiveCnt), 不必逐行物化、也不必逐行走链。
  866. func (dp *DataPage) CountLiveRows() int { return int(dp.liveCount) }
  867. // GetAllRows 返回所有未删除的行
  868. func (dp *DataPage) GetAllRows() [][]byte {
  869. var result [][]byte
  870. off := HdrSize
  871. for i := uint32(0); i < dp.count; i++ {
  872. rowLen, deleted, rowStart, ok := dp.nextRow(off)
  873. if !ok {
  874. break
  875. }
  876. off = rowStart + rowLen
  877. if deleted {
  878. continue
  879. }
  880. row := make([]byte, rowLen)
  881. copy(row, dp.buf[rowStart:rowStart+rowLen])
  882. result = append(result, row)
  883. }
  884. return result
  885. }
  886. // GetAllRowsPooled 同 GetAllRows, 但从 sync.Pool 获取缓冲区
  887. // 调用方处理完后必须将每个 []byte 归还给 pool
  888. func (dp *DataPage) GetAllRowsPooled(pool *sync.Pool) [][]byte {
  889. var result [][]byte
  890. off := HdrSize
  891. for i := uint32(0); i < dp.count; i++ {
  892. rowLen, deleted, rowStart, ok := dp.nextRow(off)
  893. if !ok {
  894. break
  895. }
  896. off = rowStart + rowLen
  897. if deleted {
  898. continue
  899. }
  900. // 从池中获取缓冲区, 避免 make 分配
  901. bp := pool.Get().(*[]byte)
  902. buf := *bp
  903. copy(buf[:rowLen], dp.buf[rowStart:rowStart+rowLen])
  904. result = append(result, buf[:rowLen])
  905. }
  906. return result
  907. }
  908. // GetAllRowsReversed 返回所有未删除行,顺序反转(最后一行在前)
  909. func (dp *DataPage) GetAllRowsReversed() [][]byte {
  910. rows := dp.GetAllRows()
  911. for i, j := 0, len(rows)-1; i < j; i, j = i+1, j-1 {
  912. rows[i], rows[j] = rows[j], rows[i]
  913. }
  914. return rows
  915. }
  916. // GetAllRowsWithSlots 返回所有未删除行及其槽位号 (顺序 = 页内物理顺序)。
  917. func (dp *DataPage) GetAllRowsWithSlots() ([]uint32, [][]byte) {
  918. var slots []uint32
  919. var rows [][]byte
  920. off := HdrSize
  921. for i := uint32(0); i < dp.count; i++ {
  922. rowLen, deleted, rowStart, ok := dp.nextRow(off)
  923. if !ok {
  924. break
  925. }
  926. slotOff := off
  927. off = rowStart + rowLen
  928. if deleted {
  929. continue
  930. }
  931. row := make([]byte, rowLen)
  932. copy(row, dp.buf[rowStart:rowStart+rowLen])
  933. rows = append(rows, row)
  934. slots = append(slots, dp.slotOfOffset(slotOff))
  935. }
  936. return slots, rows
  937. }
  938. // 注: 原 RewriteRows (用给定行紧凑重写整页, 槽位目录整体重新编号) 已删除 ——
  939. // 它把第 i 行重新编到槽位 i+1, 直接违反槽位不变式 I2: 主键叶条目存的是 (dpID, slot),
  940. // 一旦重编号, 所有叶条目就指向别的行。
  941. //
  942. // 需要「紧凑重排 / 回收墓碑」时用 Compact 或 MaybeCompact (二者都保留活行的槽位号);
  943. // 需要「替换某一行且允许变长」时用 RebuildWith / RebuildWithSlot。
  944. // AppendRows 依次追加多行: 先整体校验容量, 再逐行写入。
  945. // 任一行装不下时整页保持原样并返回 false —— 不会留下"写了一半"的页。
  946. func (dp *DataPage) AppendRows(rows [][]byte) bool {
  947. total := dp.dataEnd
  948. for _, r := range rows {
  949. total += 4 + uint32(len(r))
  950. }
  951. if total > dp.freeLimit() {
  952. return false
  953. }
  954. for _, r := range rows {
  955. dp.AppendRow(r)
  956. }
  957. return true
  958. }
  959. // ======================== 内部辅助 ========================
  960. // nextRow 解析下一行: 返回 (数据长度, 是否已删除, 数据起始偏移, 是否有效)
  961. func (dp *DataPage) nextRow(off uint32) (rowLen uint32, deleted bool, rowStart uint32, ok bool) {
  962. if off+4 > dp.dataEnd {
  963. return 0, false, 0, false
  964. }
  965. rawLen := binary.LittleEndian.Uint32(dp.buf[off:])
  966. deleted = rawLen&DeletedFlag != 0
  967. rowLen = rawLen & ^DeletedFlag
  968. rowStart = off + 4
  969. if rowStart+rowLen > dp.dataEnd {
  970. return 0, false, 0, false
  971. }
  972. return rowLen, deleted, rowStart, true
  973. }
  974. // ======================== 裸缓冲辅助 (WAL 行级恢复) ========================
  975. // RawAppendRowSlot 在裸页缓冲上追加一行并分配槽位 (供 WAL 行级恢复使用)。
  976. // 语义与 DataPage.AppendRow 一致, 但不构造对象; 返回是否成功。
  977. func RawAppendRowSlot(buf []byte, storeRow []byte) bool {
  978. if len(buf) < int(HdrSize) {
  979. return false
  980. }
  981. dh := page.DataHdrOf(buf)
  982. dataEnd := dh.UseSize
  983. if dataEnd < HdrSize {
  984. dataEnd = HdrSize
  985. }
  986. sc := uint32(binary.LittleEndian.Uint16(buf[PageSize-slotCountSize:]))
  987. if sc > maxSlotCount || uint32(PageSize)-slotCountSize-sc*slotEntrySize < dataEnd {
  988. sc = 0
  989. }
  990. need := uint32(4 + len(storeRow))
  991. if dataEnd+need > uint32(PageSize)-slotCountSize-(sc+1)*slotEntrySize {
  992. return false // 放不下 (含为新增槽位预留的目录空间)
  993. }
  994. binary.LittleEndian.PutUint32(buf[dataEnd:], uint32(len(storeRow)))
  995. copy(buf[dataEnd+4:], storeRow)
  996. off := dataEnd
  997. // 分配槽位: 复用空槽, 否则扩容
  998. slot := uint32(0)
  999. for i := uint32(1); i <= sc; i++ {
  1000. p := uint32(PageSize) - slotCountSize - i*slotEntrySize
  1001. if binary.LittleEndian.Uint16(buf[p:]) == 0 {
  1002. binary.LittleEndian.PutUint16(buf[p:], uint16(off))
  1003. slot = i
  1004. break
  1005. }
  1006. }
  1007. if slot == 0 {
  1008. if sc >= maxSlotCount {
  1009. return false
  1010. }
  1011. sc++
  1012. binary.LittleEndian.PutUint16(buf[PageSize-slotCountSize:], uint16(sc))
  1013. p := uint32(PageSize) - slotCountSize - sc*slotEntrySize
  1014. binary.LittleEndian.PutUint16(buf[p:], uint16(off))
  1015. }
  1016. dh.Count++
  1017. dh.LiveCnt++
  1018. dh.UseSize = dataEnd + need
  1019. // Min/Max 维护 (与 updateMinMax 同规则)
  1020. if len(storeRow) >= 8 {
  1021. val := binary.BigEndian.Uint64(storeRow[:8])
  1022. if dh.Count <= 1 {
  1023. dh.SetMin(val)
  1024. dh.SetMax(val)
  1025. } else {
  1026. if val < dh.GetMin() {
  1027. dh.SetMin(val)
  1028. }
  1029. if val > dh.GetMax() {
  1030. dh.SetMax(val)
  1031. }
  1032. }
  1033. }
  1034. return true
  1035. }
  1036. // ======================== IDateEntry 接口实现 ========================
  1037. // 供时序索引等扁平链模式使用
  1038. // IDateEntryDataPage 包装 DataPage 使其实现 IDateEntry 接口
  1039. // FindDataPage 回调负责从 indexEntries 中找到目标 DataPageID
  1040. type IDateEntryDataPage struct {
  1041. FindDataPage func(indexEntries []byte, entryLen int, count int, key []byte) uint32
  1042. }
  1043. func (d *IDateEntryDataPage) Insert(handle page.HandleDb, indexEntries []byte, entryLen int, count int, key []byte, pkID uint64, rowData []byte) error {
  1044. dpID := d.FindDataPage(indexEntries, entryLen, count, key)
  1045. var dp *DataPage
  1046. if dpID == 0 {
  1047. dp = New(handle)
  1048. } else {
  1049. var err error
  1050. dp, err = Load(handle, dpID)
  1051. if err != nil {
  1052. return err
  1053. }
  1054. }
  1055. if dp.IsFull(uint32(8 + len(rowData))) {
  1056. dp.Save()
  1057. dp = New(handle)
  1058. }
  1059. storeRow := make([]byte, 8+len(rowData))
  1060. binary.BigEndian.PutUint64(storeRow[:8], pkID)
  1061. copy(storeRow[8:], rowData)
  1062. dp.AppendRow(storeRow)
  1063. dp.Save()
  1064. return nil
  1065. }
  1066. func (d *IDateEntryDataPage) Search(handle page.HandleDb, indexEntries []byte, entryLen int, key []byte) ([]byte, bool) {
  1067. n := len(indexEntries) / entryLen
  1068. dpID := d.FindDataPage(indexEntries, entryLen, n, key)
  1069. if dpID == 0 {
  1070. return nil, false
  1071. }
  1072. dp, err := Load(handle, dpID)
  1073. if err != nil {
  1074. return nil, false
  1075. }
  1076. // 时序行布局: [pkID(8)][schemaVer(1)][字段...], 时间戳是首列 → storedRow 偏移 9
  1077. return dp.FindRowByIndexKey(key, 9)
  1078. }
  1079. func (d *IDateEntryDataPage) Update(handle page.HandleDb, indexEntries []byte, entryLen int, key []byte, pkID uint64, rowData []byte) error {
  1080. n := len(indexEntries) / entryLen
  1081. dpID := d.FindDataPage(indexEntries, entryLen, n, key)
  1082. if dpID == 0 {
  1083. return page.ErrNotFound
  1084. }
  1085. dp, err := Load(handle, dpID)
  1086. if err != nil {
  1087. return err
  1088. }
  1089. storeRow := make([]byte, 8+len(rowData))
  1090. binary.BigEndian.PutUint64(storeRow[:8], pkID)
  1091. copy(storeRow[8:], rowData)
  1092. return dp.FindAndUpdateByIndexKey(key, 9, storeRow)
  1093. }
  1094. func (d *IDateEntryDataPage) Delete(handle page.HandleDb, indexEntries []byte, entryLen int, key []byte) error {
  1095. n := len(indexEntries) / entryLen
  1096. dpID := d.FindDataPage(indexEntries, entryLen, n, key)
  1097. if dpID == 0 {
  1098. return page.ErrNotFound
  1099. }
  1100. dp, err := Load(handle, dpID)
  1101. if err != nil {
  1102. return err
  1103. }
  1104. deleted := dp.DeleteByIndexKey(key, 9)
  1105. if !deleted {
  1106. return page.ErrNotFound
  1107. }
  1108. if dp.AllDeleted() {
  1109. // 延迟还链, 理由同 TimeDataPage.Delete
  1110. handle.DeferFreeDataPage(dp.pageID)
  1111. } else {
  1112. dp.Save()
  1113. }
  1114. return nil
  1115. }
  1116. func (d *IDateEntryDataPage) CreateDataPage(handle page.HandleDb, key []byte, pkID uint64, rowData []byte) (uint32, error) {
  1117. dp := New(handle)
  1118. storeRow := make([]byte, 8+len(rowData))
  1119. binary.BigEndian.PutUint64(storeRow[:8], pkID)
  1120. copy(storeRow[8:], rowData)
  1121. dp.AppendRow(storeRow)
  1122. dp.Save()
  1123. return dp.pageID, nil
  1124. }