ientry.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. package index
  2. import (
  3. "git.ycl.pub/yl32940/TsDB2/DB/core/page"
  4. )
  5. // ======================== 页 I/O 回调 ========================
  6. // GlobalBack 页 I/O 回调 (传递给索引规则,由外部实现)
  7. type GlobalBack struct {
  8. SavePage func(pagID uint32, data []byte) error
  9. GetNewPage func(pagID uint32) uint32
  10. GetDataPage func(pagID uint32, data *[]byte) bool
  11. PoolGet func(size uint32) []byte
  12. PoolPut func([]byte)
  13. Handle page.HandleDb
  14. }
  15. // IEntry 索引条目操作接口(操作索引页内的条目字节切片)
  16. type IEntry interface {
  17. Compare(a, b []byte) int // 比较判断
  18. Serialize(e IEntry) []byte // 序列化
  19. Deserialize(buf []byte) IEntry // 反序列化
  20. EntryLength() uint16 // 固定条目长度
  21. Insert(Entries *[]byte, data []byte) error // 插入索引条目
  22. Search(Entries []byte, key []byte) (uint32, bool) // 查找索引条目
  23. DataEntry() IDateEntry // 获取数据页操作器
  24. }
  25. // IDateEntry 数据页实际操作接口(增删改查真实行数据)
  26. //
  27. // 二级索引(Str/Compose):
  28. // - Insert: 在 IndexPage 条目中写入 [key + PK_ID(8B)]
  29. // - Search: 二分查找返回 PK_ID 的 8 字节编码(作为 rowData 返回)
  30. // - Delete: 标记删除条目
  31. //
  32. // 主键索引(B+ 树)/ 时序索引:
  33. // - Insert: 在 DataPage 中写入行数据
  34. // - Search: 找到 DataPage 返回行数据
  35. // - Delete: 软删除行数据
  36. type IDateEntry interface {
  37. // Insert 插入数据
  38. // count: 当前页中已有的条目数量(由调用方从 IndexHead.Count 读取)
  39. // pkID: 主键ID(二级索引存入条目,B+树/时序忽略)
  40. // rowData: 序列化的行数据(B+树/时序写入 DataPage,二级索引忽略)
  41. Insert(handle page.HandleDb, indexEntries []byte, entryLen int, count int, key []byte, pkID uint64, rowData []byte) error
  42. // Search 查找数据
  43. // 返回值: rowData 对于二级索引是 8 字节 PK_ID 编码,对于 B+树/时序是实际行数据
  44. Search(handle page.HandleDb, indexEntries []byte, entryLen int, key []byte) ([]byte, bool)
  45. // Update 更新行数据(仅时序索引使用)
  46. Update(handle page.HandleDb, indexEntries []byte, entryLen int, key []byte, pkID uint64, rowData []byte) error
  47. // Delete 删除数据
  48. Delete(handle page.HandleDb, indexEntries []byte, entryLen int, key []byte) error
  49. // CreateDataPage 创建一个新的数据页并写入第一行数据(仅时序索引需要)
  50. //
  51. // pkID 是这一行的引擎行号, 必须原样写进 storeRow 的 8 字节前缀:
  52. // 行存储索引里 DataPage 是按 pkID 增序排列的, 前缀写错 (例如拿首列数据冒充)
  53. // 会让 FindRowByPK 的提前结束判断立刻成立 —— 整页的行都再也按行号找不到。
  54. CreateDataPage(handle page.HandleDb, key []byte, pkID uint64, rowData []byte) (uint32, error)
  55. }
  56. // RowResolver 由上层注入的回表函数: 按 pkID 读取整行数据 (带 8B pkID 前缀)。
  57. //
  58. // 二级索引本身不存行数据, 回表时必须"去找一级索引"。把这个动作做成注入函数后,
  59. // 二级索引内部即可自行完成回表, 上层 (IndexMetaPage / Table) 完全不需要知道
  60. // 哪个索引是主键、哪个是二级, 也不需要知道索引类型枚举。
  61. type RowResolver func(pkID uint64) ([]byte, bool)
  62. // DPRange 数据页的 pkID 区间, 供行存储索引回表时做二分定位缓存。
  63. type DPRange struct {
  64. PageID uint32
  65. MinPK uint64
  66. MaxPK uint64
  67. }
  68. // IndexHandler 高层索引操作接口
  69. // 每种索引类型实现此接口,封装所有存储细节(B+树路由、扁平链遍历、行存储/回表等)
  70. // Table 层只需遍历 indexMeta.metas 并调用 handler 方法,无需关心索引类型与内部实现
  71. type IndexHandler interface {
  72. // Insert 向索引插入数据
  73. // entryPage: 当前根页ID指针(B+树分裂时可能修改)
  74. // indexKey: 从行数据提取的索引键; pkID: 自增主键; rowData: 序列化行数据
  75. // keyLen: 索引键字节长度
  76. // 返回新的根页ID
  77. Insert(handle page.HandleDb, entryPage *uint32, indexKey []byte, pkID uint64, rowData []byte, keyLen int) (uint32, error)
  78. // Search 按索引键查找整行数据 (带 8B pkID 前缀)。
  79. // 行存储索引(主键/时序): 直接返回其存储的行数据;
  80. // 二级索引: 内部取出 pkID 后调用 resolve 回表, 由索引自己完成"找一级索引"。
  81. // resolve 为 nil 时二级索引退化为返回 8B BigEndian pkID。
  82. Search(handle page.HandleDb, entryPage uint32, indexKey []byte, keyLen int, resolve RowResolver) ([]byte, bool)
  83. // Delete 从索引删除条目
  84. // indexKey: 提取出的索引键; pkID: 该行的自增主键。
  85. // 二级索引允许重复 key, 必须用 (indexKey, pkID) 才能精确定位到某一行的条目;
  86. // 主键/时序索引的键本身唯一, 可忽略 pkID。各索引实现自行决定如何使用 pkID。
  87. Delete(handle page.HandleDb, entryPage *uint32, indexKey []byte, pkID uint64, keyLen int) error
  88. // Update 更新该行在本索引中的条目
  89. // oldKey: 该行在本索引中的旧键 (nil 表示未知); newKey: 新键; 二者相等表示键未变化。
  90. // 二级索引的条目只有 [key + pkID], 键未变化时是空操作; 键变化时内部按
  91. // (oldKey, pkID) 精确删旧条目再插新条目。行存储索引则就地改写行数据。
  92. Update(handle page.HandleDb, entryPage *uint32, oldKey, newKey, rowData []byte, pkID uint64, keyLen int) error
  93. // IsRowStore 该索引是否直接承载行数据 (主键/时序索引为 true, 二级索引为 false)。
  94. // 上层据此判断"行存在哪里", 而不需要认识具体的索引类型枚举。
  95. IsRowStore() bool
  96. // ResolveRow 按 pkID 读取整行 (带 8B pkID 前缀); 仅 IsRowStore 的索引有意义。
  97. // cache 由调用方持有并跨次复用, 用于避免重复构建 DataPage 边界索引。
  98. ResolveRow(handle page.HandleDb, entryPage uint32, pkID uint64, cache *[]DPRange) ([]byte, bool)
  99. // GetDataPageIDs 返回所有数据页ID (按叶节点顺序, 用于全表扫描)
  100. GetDataPageIDs(handle page.HandleDb, entryPage uint32) []uint32
  101. // ReadDataPageRows 读取数据页中所有未删除的行 (带 pkID(8B LE) 前缀)
  102. ReadDataPageRows(handle page.HandleDb, dpID uint32) [][]byte
  103. // RangeScan 范围扫描
  104. // startKey/endKey: 范围边界 (nil 表示无下界/上界)
  105. // startInclusive/endInclusive: 是否包含边界
  106. // reverse: true 时从右向左反向遍历
  107. // callback: 每行回调, 返回 false 停止
  108. // 主键/时序索引: row = [pkID(8B BE) + rowData]
  109. // 二级索引: row = 8B BigEndian pkID
  110. RangeScan(handle page.HandleDb, entryPage uint32,
  111. startKey, endKey []byte,
  112. startInclusive, endInclusive bool,
  113. reverse bool,
  114. callback func(row []byte) bool) error
  115. }
  116. // BatchRow 批量插入的单行输入 (handler 视角)。
  117. //
  118. // IndexKey 为 nil 表示该行不进本索引 (任一索引分量为 NULL), handler 自行跳过。
  119. type BatchRow struct {
  120. PKID uint64
  121. IndexKey []byte
  122. RowData []byte
  123. }
  124. // BatchRowResolver 可选的批量回表能力: 按【升序】pkID 列表一次取回多行。
  125. //
  126. // 二级索引范围扫描/IN 查询回表时, 逐个 ResolveRow 是"每行一次树根到叶下降"的
  127. // N+1 结构; 实现本接口的处理器可以用单调游标一次推进 (B+ 树: 路由一次 + 沿叶链
  128. // 双指针归并; 时序: 沿分段链一趟), 把回表从 O(N·树高) 降到 O(N + 触碰页数)。
  129. //
  130. // 契约:
  131. // - sortedPKIDs 必须升序 (调用方负责排序去重);
  132. // - 对每个【存在】的 pkID 调 sink(pkID, storeRow), storeRow 带 8B pkID 前缀,
  133. // 跨 sink 调用有效 (实现内部快照); 不存在的 pkID 直接跳过;
  134. // - sink 返回 false 时停止遍历;
  135. // - 未实现本接口的处理器由 IndexMetaPage.BatchResolveByPK 退化为逐个 ResolveRow。
  136. type BatchRowResolver interface {
  137. ResolveMany(handle page.HandleDb, entryPage uint32, sortedPKIDs []uint64,
  138. sink func(pkID uint64, storeRow []byte) bool)
  139. }
  140. // BatchInserter 可选的批量插入能力。
  141. //
  142. // 实现它的 IndexHandler 获得批量写入路径 (B+ 树叶驻内存、页落盘按页摊销);
  143. // 未实现者由 IndexMetaPage.InsertRows 退化为逐条 Insert, 行为与单行路径完全一致。
  144. // 键长不再单独传: 从首个非 nil IndexKey 推得 (同一索引内键长恒定)。
  145. type BatchInserter interface {
  146. InsertBatch(handle page.HandleDb, entryPage *uint32, rows []BatchRow) error
  147. }
  148. // PageCollector 可选的页枚举能力: 由页布局特殊的索引处理器自行实现, 汇报该索引
  149. // 当前占用的全部页 ID。
  150. //
  151. // 为什么需要它: 索引被整体"换根"后要把旧结构还进空闲链表, 而 B+ 树以外的索引
  152. // 没有统一的节点结构可供上层遍历 ——
  153. // - B+ 树: 节点格式一致, btree.CollectAllPageIDs 按内部条目下降即可枚举;
  154. // - Hash: 元数据页 + 目录页链 + 各桶的溢出链, 三种页三种头;
  155. // - Compose: 扁平条目链, 只有 NextPage 可循。
  156. // 因此枚举动作只能交回处理器, 按各自协议走。
  157. //
  158. // 实现约定: 任何一步解析不出来就**少收**, 绝不猜 —— 漏收只是页泄漏 (文件胀大),
  159. // 多收会把仍然可达的页还进空闲链表, 之后被分配给别的索引, 那是静默的数据损坏。
  160. type PageCollector interface {
  161. // CollectPageIDs 返回从 entryPage 出发可枚举到的全部页 ID (含 entryPage 自身)。
  162. CollectPageIDs(handle page.HandleDb, entryPage uint32) []uint32
  163. }
  164. // PageChain 一条已经用公共头 PageHdr.NextPage 串好的页链。
  165. // 链内每一页的 next 都在 PageHdr.NextPage 上, 与空闲链同字段, 因此这样一条链可以
  166. // 整体交给 HandleDb.FreeChain(head, tail, count) 一次拼接进空闲链。
  167. type PageChain struct {
  168. Head uint32 // 链首页
  169. Tail uint32 // 链尾页
  170. Count uint32 // 链长 (页数)
  171. }
  172. // ChainCollector 索引可选实现: 以「整链」而不是「逐页」汇报占用的页。
  173. //
  174. // 与 PageCollector 的关系: CollectPageIDs 让上层逐页 FreePage, 每页都要改写一次
  175. // 空闲链头并落盘; CollectChains 让上层对每条链只做一次 FreeChain —— 桶页的 next
  176. // 既然已经统一到 PageHdr.NextPage, 就没有理由再逐页还。
  177. //
  178. // 实现约定与 PageCollector 相同: 解析不出来就少报 (漏报只是页泄漏), 绝不能多报
  179. // (多报会把仍可达的页还进空闲链, 造成静默数据损坏)。
  180. type ChainCollector interface {
  181. // CollectChains 返回该索引占用的全部链 (含元数据页这样的单页链)。
  182. CollectChains(handle page.HandleDb, entryPage uint32) []PageChain
  183. }
  184. type ComposeType struct {
  185. IndexEntry map[page.IndexType]IEntry
  186. IndexHandlers map[page.IndexType]IndexHandler
  187. }
  188. func (com *ComposeType) register(indexType page.IndexType, entry IEntry) {
  189. com.IndexEntry[indexType] = entry
  190. }
  191. func (com *ComposeType) RegisterHandler(indexType page.IndexType, handler IndexHandler) {
  192. com.IndexHandlers[indexType] = handler
  193. }
  194. func (com *ComposeType) GetEntryHandler(t page.IndexType) (IEntry, bool) {
  195. if entry, ok := com.IndexEntry[t]; ok {
  196. return entry, true
  197. }
  198. return nil, false
  199. }
  200. func (com *ComposeType) GetHandler(t page.IndexType) (IndexHandler, bool) {
  201. if h, ok := com.IndexHandlers[t]; ok {
  202. return h, true
  203. }
  204. return nil, false
  205. }
  206. var Compose = ComposeType{
  207. IndexEntry: make(map[page.IndexType]IEntry),
  208. IndexHandlers: make(map[page.IndexType]IndexHandler),
  209. }
  210. func init() {
  211. Compose.register(page.IndexTypeTime, &IndexTimeEntry{})
  212. }