| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- package index
- import (
- "git.ycl.pub/yl32940/TsDB2/DB/core/page"
- )
- // ======================== 页 I/O 回调 ========================
- // GlobalBack 页 I/O 回调 (传递给索引规则,由外部实现)
- type GlobalBack struct {
- SavePage func(pagID uint32, data []byte) error
- GetNewPage func(pagID uint32) uint32
- GetDataPage func(pagID uint32, data *[]byte) bool
- PoolGet func(size uint32) []byte
- PoolPut func([]byte)
- Handle page.HandleDb
- }
- // IEntry 索引条目操作接口(操作索引页内的条目字节切片)
- type IEntry interface {
- Compare(a, b []byte) int // 比较判断
- Serialize(e IEntry) []byte // 序列化
- Deserialize(buf []byte) IEntry // 反序列化
- EntryLength() uint16 // 固定条目长度
- Insert(Entries *[]byte, data []byte) error // 插入索引条目
- Search(Entries []byte, key []byte) (uint32, bool) // 查找索引条目
- DataEntry() IDateEntry // 获取数据页操作器
- }
- // IDateEntry 数据页实际操作接口(增删改查真实行数据)
- //
- // 二级索引(Str/Compose):
- // - Insert: 在 IndexPage 条目中写入 [key + PK_ID(8B)]
- // - Search: 二分查找返回 PK_ID 的 8 字节编码(作为 rowData 返回)
- // - Delete: 标记删除条目
- //
- // 主键索引(B+ 树)/ 时序索引:
- // - Insert: 在 DataPage 中写入行数据
- // - Search: 找到 DataPage 返回行数据
- // - Delete: 软删除行数据
- type IDateEntry interface {
- // Insert 插入数据
- // count: 当前页中已有的条目数量(由调用方从 IndexHead.Count 读取)
- // pkID: 主键ID(二级索引存入条目,B+树/时序忽略)
- // rowData: 序列化的行数据(B+树/时序写入 DataPage,二级索引忽略)
- Insert(handle page.HandleDb, indexEntries []byte, entryLen int, count int, key []byte, pkID uint64, rowData []byte) error
- // Search 查找数据
- // 返回值: rowData 对于二级索引是 8 字节 PK_ID 编码,对于 B+树/时序是实际行数据
- Search(handle page.HandleDb, indexEntries []byte, entryLen int, key []byte) ([]byte, bool)
- // Update 更新行数据(仅时序索引使用)
- Update(handle page.HandleDb, indexEntries []byte, entryLen int, key []byte, pkID uint64, rowData []byte) error
- // Delete 删除数据
- Delete(handle page.HandleDb, indexEntries []byte, entryLen int, key []byte) error
- // CreateDataPage 创建一个新的数据页并写入第一行数据(仅时序索引需要)
- //
- // pkID 是这一行的引擎行号, 必须原样写进 storeRow 的 8 字节前缀:
- // 行存储索引里 DataPage 是按 pkID 增序排列的, 前缀写错 (例如拿首列数据冒充)
- // 会让 FindRowByPK 的提前结束判断立刻成立 —— 整页的行都再也按行号找不到。
- CreateDataPage(handle page.HandleDb, key []byte, pkID uint64, rowData []byte) (uint32, error)
- }
- // RowResolver 由上层注入的回表函数: 按 pkID 读取整行数据 (带 8B pkID 前缀)。
- //
- // 二级索引本身不存行数据, 回表时必须"去找一级索引"。把这个动作做成注入函数后,
- // 二级索引内部即可自行完成回表, 上层 (IndexMetaPage / Table) 完全不需要知道
- // 哪个索引是主键、哪个是二级, 也不需要知道索引类型枚举。
- type RowResolver func(pkID uint64) ([]byte, bool)
- // DPRange 数据页的 pkID 区间, 供行存储索引回表时做二分定位缓存。
- type DPRange struct {
- PageID uint32
- MinPK uint64
- MaxPK uint64
- }
- // IndexHandler 高层索引操作接口
- // 每种索引类型实现此接口,封装所有存储细节(B+树路由、扁平链遍历、行存储/回表等)
- // Table 层只需遍历 indexMeta.metas 并调用 handler 方法,无需关心索引类型与内部实现
- type IndexHandler interface {
- // Insert 向索引插入数据
- // entryPage: 当前根页ID指针(B+树分裂时可能修改)
- // indexKey: 从行数据提取的索引键; pkID: 自增主键; rowData: 序列化行数据
- // keyLen: 索引键字节长度
- // 返回新的根页ID
- Insert(handle page.HandleDb, entryPage *uint32, indexKey []byte, pkID uint64, rowData []byte, keyLen int) (uint32, error)
- // Search 按索引键查找整行数据 (带 8B pkID 前缀)。
- // 行存储索引(主键/时序): 直接返回其存储的行数据;
- // 二级索引: 内部取出 pkID 后调用 resolve 回表, 由索引自己完成"找一级索引"。
- // resolve 为 nil 时二级索引退化为返回 8B BigEndian pkID。
- Search(handle page.HandleDb, entryPage uint32, indexKey []byte, keyLen int, resolve RowResolver) ([]byte, bool)
- // Delete 从索引删除条目
- // indexKey: 提取出的索引键; pkID: 该行的自增主键。
- // 二级索引允许重复 key, 必须用 (indexKey, pkID) 才能精确定位到某一行的条目;
- // 主键/时序索引的键本身唯一, 可忽略 pkID。各索引实现自行决定如何使用 pkID。
- Delete(handle page.HandleDb, entryPage *uint32, indexKey []byte, pkID uint64, keyLen int) error
- // Update 更新该行在本索引中的条目
- // oldKey: 该行在本索引中的旧键 (nil 表示未知); newKey: 新键; 二者相等表示键未变化。
- // 二级索引的条目只有 [key + pkID], 键未变化时是空操作; 键变化时内部按
- // (oldKey, pkID) 精确删旧条目再插新条目。行存储索引则就地改写行数据。
- Update(handle page.HandleDb, entryPage *uint32, oldKey, newKey, rowData []byte, pkID uint64, keyLen int) error
- // IsRowStore 该索引是否直接承载行数据 (主键/时序索引为 true, 二级索引为 false)。
- // 上层据此判断"行存在哪里", 而不需要认识具体的索引类型枚举。
- IsRowStore() bool
- // ResolveRow 按 pkID 读取整行 (带 8B pkID 前缀); 仅 IsRowStore 的索引有意义。
- // cache 由调用方持有并跨次复用, 用于避免重复构建 DataPage 边界索引。
- ResolveRow(handle page.HandleDb, entryPage uint32, pkID uint64, cache *[]DPRange) ([]byte, bool)
- // GetDataPageIDs 返回所有数据页ID (按叶节点顺序, 用于全表扫描)
- GetDataPageIDs(handle page.HandleDb, entryPage uint32) []uint32
- // ReadDataPageRows 读取数据页中所有未删除的行 (带 pkID(8B LE) 前缀)
- ReadDataPageRows(handle page.HandleDb, dpID uint32) [][]byte
- // RangeScan 范围扫描
- // startKey/endKey: 范围边界 (nil 表示无下界/上界)
- // startInclusive/endInclusive: 是否包含边界
- // reverse: true 时从右向左反向遍历
- // callback: 每行回调, 返回 false 停止
- // 主键/时序索引: row = [pkID(8B BE) + rowData]
- // 二级索引: row = 8B BigEndian pkID
- RangeScan(handle page.HandleDb, entryPage uint32,
- startKey, endKey []byte,
- startInclusive, endInclusive bool,
- reverse bool,
- callback func(row []byte) bool) error
- }
- // BatchRow 批量插入的单行输入 (handler 视角)。
- //
- // IndexKey 为 nil 表示该行不进本索引 (任一索引分量为 NULL), handler 自行跳过。
- type BatchRow struct {
- PKID uint64
- IndexKey []byte
- RowData []byte
- }
- // BatchRowResolver 可选的批量回表能力: 按【升序】pkID 列表一次取回多行。
- //
- // 二级索引范围扫描/IN 查询回表时, 逐个 ResolveRow 是"每行一次树根到叶下降"的
- // N+1 结构; 实现本接口的处理器可以用单调游标一次推进 (B+ 树: 路由一次 + 沿叶链
- // 双指针归并; 时序: 沿分段链一趟), 把回表从 O(N·树高) 降到 O(N + 触碰页数)。
- //
- // 契约:
- // - sortedPKIDs 必须升序 (调用方负责排序去重);
- // - 对每个【存在】的 pkID 调 sink(pkID, storeRow), storeRow 带 8B pkID 前缀,
- // 跨 sink 调用有效 (实现内部快照); 不存在的 pkID 直接跳过;
- // - sink 返回 false 时停止遍历;
- // - 未实现本接口的处理器由 IndexMetaPage.BatchResolveByPK 退化为逐个 ResolveRow。
- type BatchRowResolver interface {
- ResolveMany(handle page.HandleDb, entryPage uint32, sortedPKIDs []uint64,
- sink func(pkID uint64, storeRow []byte) bool)
- }
- // BatchInserter 可选的批量插入能力。
- //
- // 实现它的 IndexHandler 获得批量写入路径 (B+ 树叶驻内存、页落盘按页摊销);
- // 未实现者由 IndexMetaPage.InsertRows 退化为逐条 Insert, 行为与单行路径完全一致。
- // 键长不再单独传: 从首个非 nil IndexKey 推得 (同一索引内键长恒定)。
- type BatchInserter interface {
- InsertBatch(handle page.HandleDb, entryPage *uint32, rows []BatchRow) error
- }
- // PageCollector 可选的页枚举能力: 由页布局特殊的索引处理器自行实现, 汇报该索引
- // 当前占用的全部页 ID。
- //
- // 为什么需要它: 索引被整体"换根"后要把旧结构还进空闲链表, 而 B+ 树以外的索引
- // 没有统一的节点结构可供上层遍历 ——
- // - B+ 树: 节点格式一致, btree.CollectAllPageIDs 按内部条目下降即可枚举;
- // - Hash: 元数据页 + 目录页链 + 各桶的溢出链, 三种页三种头;
- // - Compose: 扁平条目链, 只有 NextPage 可循。
- // 因此枚举动作只能交回处理器, 按各自协议走。
- //
- // 实现约定: 任何一步解析不出来就**少收**, 绝不猜 —— 漏收只是页泄漏 (文件胀大),
- // 多收会把仍然可达的页还进空闲链表, 之后被分配给别的索引, 那是静默的数据损坏。
- type PageCollector interface {
- // CollectPageIDs 返回从 entryPage 出发可枚举到的全部页 ID (含 entryPage 自身)。
- CollectPageIDs(handle page.HandleDb, entryPage uint32) []uint32
- }
- // PageChain 一条已经用公共头 PageHdr.NextPage 串好的页链。
- // 链内每一页的 next 都在 PageHdr.NextPage 上, 与空闲链同字段, 因此这样一条链可以
- // 整体交给 HandleDb.FreeChain(head, tail, count) 一次拼接进空闲链。
- type PageChain struct {
- Head uint32 // 链首页
- Tail uint32 // 链尾页
- Count uint32 // 链长 (页数)
- }
- // ChainCollector 索引可选实现: 以「整链」而不是「逐页」汇报占用的页。
- //
- // 与 PageCollector 的关系: CollectPageIDs 让上层逐页 FreePage, 每页都要改写一次
- // 空闲链头并落盘; CollectChains 让上层对每条链只做一次 FreeChain —— 桶页的 next
- // 既然已经统一到 PageHdr.NextPage, 就没有理由再逐页还。
- //
- // 实现约定与 PageCollector 相同: 解析不出来就少报 (漏报只是页泄漏), 绝不能多报
- // (多报会把仍可达的页还进空闲链, 造成静默数据损坏)。
- type ChainCollector interface {
- // CollectChains 返回该索引占用的全部链 (含元数据页这样的单页链)。
- CollectChains(handle page.HandleDb, entryPage uint32) []PageChain
- }
- type ComposeType struct {
- IndexEntry map[page.IndexType]IEntry
- IndexHandlers map[page.IndexType]IndexHandler
- }
- func (com *ComposeType) register(indexType page.IndexType, entry IEntry) {
- com.IndexEntry[indexType] = entry
- }
- func (com *ComposeType) RegisterHandler(indexType page.IndexType, handler IndexHandler) {
- com.IndexHandlers[indexType] = handler
- }
- func (com *ComposeType) GetEntryHandler(t page.IndexType) (IEntry, bool) {
- if entry, ok := com.IndexEntry[t]; ok {
- return entry, true
- }
- return nil, false
- }
- func (com *ComposeType) GetHandler(t page.IndexType) (IndexHandler, bool) {
- if h, ok := com.IndexHandlers[t]; ok {
- return h, true
- }
- return nil, false
- }
- var Compose = ComposeType{
- IndexEntry: make(map[page.IndexType]IEntry),
- IndexHandlers: make(map[page.IndexType]IndexHandler),
- }
- func init() {
- Compose.register(page.IndexTypeTime, &IndexTimeEntry{})
- }
|