IndexMetaPage.go 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891
  1. package core
  2. import (
  3. "TsDB2/DB/core/index"
  4. "TsDB2/DB/core/page"
  5. "TsDB2/DB/core/types"
  6. "TsDB2/DB/core/util"
  7. "encoding/binary"
  8. "fmt"
  9. "unsafe"
  10. )
  11. // 🔥 最终对接版:只存索引定义 + 指向你的 IndexPage
  12. type IndexMeta struct {
  13. IndexName [IndexNameSize]byte
  14. IndexType page.IndexType
  15. FieldCnt byte // 列的数量
  16. FieldIDs [16]byte // 列
  17. EntryPage uint32 // ✅ 你的 IndexPage 首页ID
  18. PrevMetaID uint32 // 上一个
  19. NextMetaID uint32 // 下一个
  20. Flag byte // 标志位 (见 IndexFlag* 常量)
  21. }
  22. // ===== IndexMeta.Flag 位布局 =====
  23. //
  24. // bit7 | bit0
  25. // 已退位(软删除) | 唯一约束
  26. //
  27. // 位 7 的语义由 util.IsHighBitSet 统一判定 (Deleted); 位 0 是本轮新增的唯一约束标记。
  28. const (
  29. // IndexFlagUnique 该索引是唯一索引: 同一索引键只允许存在一行。
  30. //
  31. // 【NULL 免除】复合唯一索引只要有一个分量是 NULL, extractIndexKey 就返回 nil,
  32. // 该行根本不进这个索引 —— 索引里没有条目, 唯一性自然无从违反。因此
  33. // "唯一但可空"的列可以存任意多行 NULL, 与 SQL 的 UNIQUE 语义一致。
  34. IndexFlagUnique byte = 1 << 0
  35. )
  36. // 编译期断言: 钉死 IndexMeta 的盘上大小与字段偏移。
  37. // analyzer/main.go 里那套硬编码偏移 (META_SZ / im[...]) 必须与之一致,
  38. // 因此改布局时这里会先编译失败, 而不是让分析工具静默读错。
  39. //
  40. // 布局 (IndexNameSize=64) = 100 字节:
  41. //
  42. // IndexName[64] + IndexType(1) + FieldCnt(1) + FieldIDs[16]
  43. // + pad(2) + EntryPage(4) + PrevMetaID(4) + NextMetaID(4) + Flag(1) + pad(3)
  44. var (
  45. _ [unsafe.Sizeof(IndexMeta{}) - 100]struct{}
  46. _ [100 - unsafe.Sizeof(IndexMeta{})]struct{}
  47. _ [64]byte = [unsafe.Offsetof(IndexMeta{}.IndexType)]byte{}
  48. _ [65]byte = [unsafe.Offsetof(IndexMeta{}.FieldCnt)]byte{}
  49. _ [66]byte = [unsafe.Offsetof(IndexMeta{}.FieldIDs)]byte{}
  50. _ [84]byte = [unsafe.Offsetof(IndexMeta{}.EntryPage)]byte{}
  51. _ [96]byte = [unsafe.Offsetof(IndexMeta{}.Flag)]byte{}
  52. )
  53. type IndexMetaPage struct {
  54. hdr page.PageHdr
  55. metas []IndexMeta
  56. handle page.HandleDb
  57. hdrSize uint32
  58. metaSize uint32
  59. lastPageID uint32
  60. rowCache []index.DPRange // 行存储索引回表定位缓存 (由处理器复用, 变更时失效)
  61. // active 活跃索引缓存 (元信息 + 已查表的处理器), 惰性构建。
  62. // 热路径 (InsertRow/DeleteByKey/UpdateByKey/ResolveByPK) 每行都要遍历索引,
  63. // 缓存后省去每行每索引的 Flag 判断与 handler 查表; 索引增删/重载时失效。
  64. active []resolvedIndex
  65. activeResolved bool
  66. rowIdx int // active 中行存储索引的下标, -1 表示无
  67. }
  68. // resolvedIndex 已解析的活跃索引: 元信息 + 已查表的处理器
  69. type resolvedIndex struct {
  70. meta *IndexMeta
  71. handler index.IndexHandler
  72. }
  73. // 初始化
  74. func (im *IndexMetaPage) Init(pageID uint32) {
  75. im.hdrSize = uint32(unsafe.Sizeof(page.PageHdr{}))
  76. im.metaSize = uint32(unsafe.Sizeof(IndexMeta{}))
  77. im.hdr.PageID = pageID
  78. im.hdr.Type = page.IndexMetaIndex
  79. im.hdr.NextPage = 0
  80. im.lastPageID = pageID
  81. im.metas = im.loadMetas(pageID)
  82. im.invalidateIndexCache()
  83. }
  84. // InitEmpty 把本页初始化成"空的索引元数据页", 专供「刚 NewPage 出来的新页」使用。
  85. //
  86. // 为什么不能直接用 Init —— 调用顺序错了就是 DROP TABLE 假死 52s 的根源:
  87. // - Init 是"打开一张已有页": 它用 loadMetas 从盘上回读本页内容来重建 metas。
  88. // 而新页是从空闲链摘出来的, 回收链 (WAL 回收 / Drop 回收) 都只做 O(1) 拼接、
  89. // 不擦除页内容, 盘上还留着它上一世的东西 —— 典型是 WAL 页 (Type=7、
  90. // UseSize=16384、NextPage 还指向旧 WAL 链)。此时 Init 会把整页旧内容当作
  91. // IndexMeta 数组解析, 还顺着旧链多解析好几页: 实测建 G 表时解出 91 条垃圾元数据
  92. // (IndexType=66/71/76…, EntryPage=16777216)。随后即便用 InitHeader 覆盖成干净页,
  93. // 内存里的垃圾 meta 也不会消失, 最终被 DropTable 拿去枚举页 → FreePage 落到从未
  94. // 分配过的页号上 → 写到几十 GB 偏移, DROP 假死。
  95. // - InitEmpty 不读盘: 先写一张只含页头的干净页覆盖旧内容, 再把内存状态置空。
  96. func (im *IndexMetaPage) InitEmpty(pageID uint32) {
  97. im.hdrSize = uint32(unsafe.Sizeof(page.PageHdr{}))
  98. im.metaSize = uint32(unsafe.Sizeof(IndexMeta{}))
  99. im.hdr.Type = page.IndexMetaIndex
  100. im.hdr.PageID = pageID
  101. im.hdr.NextPage = 0
  102. im.hdr.UseSize = im.hdrSize
  103. im.lastPageID = pageID
  104. im.InitHeader(pageID) // 干净页覆盖回收页里的旧内容
  105. im.metas = nil
  106. im.invalidateIndexCache()
  107. }
  108. // loadMetas 解析索引元数据链 (Init / Reload 共用的读取部分)
  109. func (im *IndexMetaPage) loadMetas(pageID uint32) []IndexMeta {
  110. metas := make([]IndexMeta, 0)
  111. for pageID != 0 {
  112. im.lastPageID = pageID
  113. buf := make([]byte, PageSize)
  114. if err := im.handle.ReadPage(pageID, buf); err != nil {
  115. break
  116. }
  117. hdr := page.HdrOf(buf)
  118. // 页类型 / 自述页号校验: 读索引元数据链绝不能"盘上有多少就解析多少"。
  119. // 新页可能刚从空闲链摘出来, 盘上留着上一世的内容 (典型: WAL 页 Type=7、
  120. // UseSize=16384、NextPage 指向旧 WAL 链)。不校验就会把整页旧内容按 IndexMeta
  121. // 数组解析, 还顺着旧链一路解析下去 —— 实测建表时解出 91 条垃圾元数据
  122. // (EntryPage=16777216), 最终让 DropTable 去 FreePage 从未分配过的页号:
  123. // 写到几十 GB 偏移、DROP 假死 52s、稀疏文件暴涨到 40GB。
  124. if hdr.Type != page.IndexMetaIndex || hdr.PageID != pageID {
  125. break
  126. }
  127. offset := im.hdrSize
  128. for offset+im.metaSize <= hdr.UseSize {
  129. m := (*IndexMeta)(unsafe.Pointer(&buf[offset]))
  130. if m.Flag&0x80 == 0 {
  131. metas = append(metas, *m)
  132. }
  133. offset += im.metaSize
  134. }
  135. pageID = hdr.NextPage
  136. }
  137. return metas
  138. }
  139. // Reload 从磁盘重新读回索引元数据, 原地覆盖 im.metas —— 供事务 Rollback 使用。
  140. //
  141. // metas 是 Init 时从页里**拷贝**到 Go 堆的独立副本, 丢弃页缓存并不会刷新它。
  142. // 不重载的话 EntryPage 仍停留在事务中的值 (指向事务里新建的 B+ 树根, 删空时还会被
  143. // 写成 0), 而那个根页已随缓存一起被丢弃 → 回滚后所有索引查找都走空。
  144. //
  145. // firstPageID 必须由调用方从"刚从磁盘读回的 TableInfo.FirstIndexID"传入:
  146. // im.hdr.PageID 同样是内存副本, 事务内首次建索引时可能已被改写。
  147. func (im *IndexMetaPage) Reload(firstPageID uint32) {
  148. im.hdr.PageID = firstPageID
  149. im.hdr.NextPage = 0
  150. im.lastPageID = firstPageID
  151. im.rowCache = nil // 回表定位缓存指向事务中的 DataPage, 必须失效
  152. metas := im.loadMetas(firstPageID)
  153. if len(metas) == len(im.metas) {
  154. copy(im.metas, metas) // 长度一致时原地覆盖, 外部持有的 &metas[i] 继续有效
  155. } else {
  156. im.metas = metas
  157. }
  158. im.invalidateIndexCache() // 元信息已变, 缓存里的 &metas[i] 与 handler 必须重算
  159. }
  160. // PageID 返回索引元数据页 ID (调试用)
  161. func (im *IndexMetaPage) PageID() uint32 { return im.hdr.PageID }
  162. // HdrSize 返回页头大小 (调试用)
  163. func (im *IndexMetaPage) HdrSize() uint32 { return im.hdrSize }
  164. // 创建索引(纯元数据,不碰IndexPage)
  165. //
  166. // unique 是变参 (只取第一个值): 这样 `CreateIndex(name, typ, fields)` 的既有调用
  167. // 一处都不用改, 需要唯一约束的调用方写成 `CreateIndex(name, typ, fields, true)` 即可。
  168. func (im *IndexMetaPage) CreateIndex(indexName string, itype page.IndexType, fieldIDs []byte, unique ...bool) (*IndexMeta, error) {
  169. uniqueIdx := len(unique) > 0 && unique[0]
  170. // 名字超长必须显式报错。下面用 copy 写入定长数组, 超长会被静默截断: 存进盘里的
  171. // 名字与调用方用来查询的名字不一致, 索引建了却永远查不到 —— 必须拦在建的这一步。
  172. // (自动命名 pk_<表名> / idx_<表名>_<列名> 会额外多出几个字节, 所以名字接近上限时
  173. // 这里就会报错, 而不是默默建出一个查不到的索引。
  174. // 上限取 IndexNameSize 而非 TableNameSize —— 索引名与表名是两套独立额度。)
  175. if len(indexName) > IndexNameSize {
  176. return nil, fmt.Errorf("%w: 索引名 %q 有 %d 字节, 上限 %d",
  177. ErrIndexNameTooLong, indexName, len(indexName), IndexNameSize)
  178. }
  179. // 分量个数闸门。先按 int 判再转 byte: 直接 `byte(len(fieldIDs))` 会在超过 255 时回绕
  180. // (例如 257 个份量截成 1), 于是越界的调用反而"通过"了, 之后按错误的分量数建条目。
  181. if len(fieldIDs) == 0 || len(fieldIDs) > MaxIndexFields {
  182. return nil, fmt.Errorf("%w (本次 %d 个分量, 索引 %q)",
  183. ErrIndexFieldCount, len(fieldIDs), indexName)
  184. }
  185. fc := byte(len(fieldIDs))
  186. // 复合键要求各分量互不相同。重复列会让 extractIndexKey 永远取不齐分量
  187. // (某一分量无人填充), 索引静默变成空索引 —— 写入成功却查不到, 因此直接拒绝。
  188. var seen [256]bool
  189. for _, fid := range fieldIDs {
  190. if seen[fid] {
  191. return nil, ErrIndexFieldDuplicated
  192. }
  193. seen[fid] = true
  194. }
  195. // 行存储索引 (主键 B+ 树 / 时序) 的键同时就是"行在哪里"的定位键,
  196. // 复合键会让回表语义失效 (ResolveByPK 用 pkID, 与键是两回事), 明确拒绝。
  197. if fc > 1 {
  198. if h, ok := index.Compose.GetHandler(itype); ok && h.IsRowStore() {
  199. return nil, ErrIndexCompositeRowStore
  200. }
  201. }
  202. // 行存储索引不支持唯一约束, 明确拒绝而不是静默忽略:
  203. // - 主键索引本就唯一 (InsertRowWithID 用 ErrDuplicatePK 兜着), 唯一标记是废话;
  204. // - 时序索引的多行同刻是【刻意允许】的 (同一时刻的多个测点), 给它加唯一约束
  205. // 等于把时序表写死 —— 而"忽略这个标记"会让调用方以为约束生效了,
  206. // 之后插入同刻第二行成功时才发觉不对, 那时数据已经写进去了。
  207. if uniqueIdx {
  208. if h, ok := index.Compose.GetHandler(itype); ok && h.IsRowStore() {
  209. return nil, fmt.Errorf("%w (索引 %q)", ErrUniqueRowStore, indexName)
  210. }
  211. }
  212. // 一张表只能有一个行存储索引。行数据 ([pkID + rowData]) 只可能落在其中一个里, 而
  213. // resolveIndexes 取的是"第一个 IsRowStore 的索引"; 再建一个不会立刻出错, 只会得到一个
  214. // "元数据说自己是行存储、却一行都没有"的索引 —— 之后所有按行存储解读它的路径
  215. // (回表、删除、重建) 都会错乱, 而且错得没有来路。所以拦在建的这一步。
  216. //
  217. // 主键 (B+ 树) 与时序都属于行存储, 因此"建了主键表再补时序索引"同样被拒 —— 这不是
  218. // 额外限制, 而是引擎的真实约束: 一份行数据不可能同时按 pkID 和按时间分段存放。
  219. if h, ok := index.Compose.GetHandler(itype); ok && h.IsRowStore() {
  220. for i := range im.metas {
  221. m := &im.metas[i]
  222. if m.Flag&0x80 != 0 { // 已退位的元数据不占位
  223. continue
  224. }
  225. if indexStoresRows(m) {
  226. return nil, fmt.Errorf("%w: 表上已有行存储索引 %q (类型 %d), 不能再建 %q (类型 %d)",
  227. ErrRowStoreExists, util.BytesToString(m.IndexName[:]), m.IndexType, indexName, itype)
  228. }
  229. }
  230. }
  231. for _, m := range im.metas {
  232. if util.BytesToString(m.IndexName[:]) == indexName && m.Flag&0x80 == 0 {
  233. return nil, ErrIndexExists
  234. }
  235. }
  236. var m IndexMeta
  237. copy(m.IndexName[:], indexName)
  238. m.IndexType = itype
  239. m.FieldCnt = fc
  240. copy(m.FieldIDs[:fc], fieldIDs)
  241. m.Flag = 0
  242. m.SetUnique(uniqueIdx) // bit7 是软删除 (Deleted), 新索引一律不带; 唯一位按参数置
  243. m.EntryPage = 0
  244. var buf []byte
  245. var hdr *page.PageHdr
  246. if im.hdr.PageID == 0 {
  247. pid := im.handle.NewPage()
  248. buf, hdr = im.InitHeader(pid)
  249. im.hdr.PageID = pid
  250. im.lastPageID = pid
  251. } else {
  252. buf = make([]byte, PageSize)
  253. im.handle.ReadPage(im.lastPageID, buf)
  254. hdr = page.HdrOf(buf)
  255. }
  256. offset := hdr.UseSize
  257. if offset+im.metaSize > PageSize {
  258. newID := im.handle.NewPage()
  259. newBuf, newHdr := im.InitHeader(newID)
  260. hdr.NextPage = newID
  261. im.handle.SavePage(im.lastPageID, buf) // 保存下当前页 不然就被新开的页替换了
  262. im.lastPageID = newID
  263. buf = newBuf
  264. hdr = newHdr
  265. offset = im.hdrSize
  266. }
  267. dst := (*IndexMeta)(unsafe.Pointer(&buf[offset]))
  268. *dst = m
  269. hdr.UseSize = offset + im.metaSize
  270. im.handle.SavePage(im.lastPageID, buf)
  271. im.metas = append(im.metas, m)
  272. im.invalidateIndexCache() // metas 扩容后旧缓存里的 &metas[i] 会失效
  273. // 必须返回 im.metas 里的那一份, 而不是局部变量 m 的地址:
  274. // 调用方 (Table.CreateIndex → backfillIndex) 会在建完树后回写 EntryPage。
  275. // 若返回 &m, 回写只落在局部副本 + 磁盘页上, 而查询走的是 im.metas ——
  276. // 于是"回填了存量行却查不到", 直到关库重开 (metas 从盘上重载) 才恢复正常。
  277. // 回填过程中不会 append 新 meta, 故该指针在本次调用内保持有效。
  278. return &im.metas[len(im.metas)-1], nil
  279. }
  280. // 查找单个索引
  281. func (im *IndexMetaPage) FindIndex(idxName string) (*IndexMeta, bool) {
  282. for i := range im.metas {
  283. m := &im.metas[i]
  284. if util.BytesToString(m.IndexName[:]) == idxName && m.Flag&0x80 == 0 {
  285. return m, true
  286. }
  287. }
  288. return nil, false
  289. }
  290. // 删除索引
  291. func (im *IndexMetaPage) DropIndex(idxName string) error {
  292. cur := im.hdr.PageID
  293. for cur != 0 {
  294. buf := make([]byte, PageSize)
  295. im.handle.ReadPage(cur, buf)
  296. hdr := page.HdrOf(buf)
  297. // 同 loadMetas: 页类型/自述页号对不上就不是索引元数据页, 不能在这上面按名字改 Flag
  298. if hdr.Type != page.IndexMetaIndex || hdr.PageID != cur {
  299. break
  300. }
  301. offset := im.hdrSize
  302. for offset+im.metaSize <= hdr.UseSize {
  303. m := (*IndexMeta)(unsafe.Pointer(&buf[offset]))
  304. if util.BytesToString(m.IndexName[:]) == idxName && m.Flag&0x80 == 0 {
  305. m.Flag |= 0x80
  306. im.handle.SavePage(cur, buf)
  307. for i := range im.metas {
  308. if util.BytesToString(im.metas[i].IndexName[:]) == idxName {
  309. im.metas = append(im.metas[:i], im.metas[i+1:]...)
  310. im.invalidateIndexCache() // metas 已删除元素, 旧缓存 (含下标) 失效
  311. break
  312. }
  313. }
  314. return nil
  315. }
  316. offset += im.metaSize
  317. }
  318. cur = hdr.NextPage
  319. }
  320. return ErrIndexNotFound
  321. }
  322. func (im *IndexMetaPage) InitHeader(pageID uint32) ([]byte, *page.PageHdr) {
  323. buf := make([]byte, PageSize)
  324. hdr := page.HdrOf(buf)
  325. hdr.Type = page.IndexMetaIndex
  326. hdr.PageID = pageID
  327. hdr.UseSize = uint32(unsafe.Sizeof(*hdr))
  328. im.handle.SavePage(pageID, buf)
  329. return buf, hdr
  330. }
  331. // ======================== 高层索引操作 ========================
  332. // rowStoreMeta 返回承载行数据的一级索引 (主键/时序)。
  333. // 本层不认识索引类型枚举: 谁是"行存储"由处理器自己通过 IsRowStore 回答。
  334. func (im *IndexMetaPage) rowStoreMeta() (*IndexMeta, index.IndexHandler, bool) {
  335. im.resolveIndexes()
  336. if im.rowIdx < 0 {
  337. return nil, nil, false
  338. }
  339. ri := im.active[im.rowIdx]
  340. return ri.meta, ri.handler, true
  341. }
  342. // Name 索引名 (去掉定长补零)。
  343. func (m *IndexMeta) Name() string {
  344. if m == nil {
  345. return ""
  346. }
  347. return util.BytesToString(m.IndexName[:])
  348. }
  349. // Fields 索引分量在表 master registry 中的字段下标 (按建索引时给的顺序)。
  350. // 返回拷贝: 调用方不该拿到会随元数据增删而失效的内存。
  351. func (m *IndexMeta) Fields() []byte {
  352. if m == nil || m.FieldCnt == 0 {
  353. return nil
  354. }
  355. n := int(m.FieldCnt)
  356. if n > len(m.FieldIDs) {
  357. n = len(m.FieldIDs)
  358. }
  359. out := make([]byte, n)
  360. copy(out, m.FieldIDs[:n])
  361. return out
  362. }
  363. // Deleted 索引是否已被软删除 (Flag 最高位)。
  364. func (m *IndexMeta) Deleted() bool {
  365. return m != nil && util.IsHighBitSet(m.Flag)
  366. }
  367. // Unique 该索引是否为唯一索引 (同一索引键只允许一行; NULL 分量不进索引, 不参与约束)。
  368. //
  369. // 这是唯一约束的【权威标记】: 列上的 types.FlagUnique 只是声明, 约束由索引在
  370. // 写入路径上执行 (插入/更新前先按键探一次), 所以判定必须以索引元信息为准。
  371. func (m *IndexMeta) Unique() bool {
  372. return m != nil && m.Flag&IndexFlagUnique != 0
  373. }
  374. // SetUnique 就地设置/取消唯一标记 (仅改内存元信息, 落盘由 saveMeta 负责)。
  375. func (m *IndexMeta) SetUnique(v bool) {
  376. if m == nil {
  377. return
  378. }
  379. if v {
  380. m.Flag |= IndexFlagUnique
  381. } else {
  382. m.Flag &^= IndexFlagUnique
  383. }
  384. }
  385. // GetIndexes 返回本表全部可用索引的元数据拷贝 (按创建顺序, 已删除的跳过)。
  386. //
  387. // 元数据是迁移/导出类工具的入口: 在此之前 core 包外没有任何办法枚举一张表的索引
  388. // (metas 未导出, 只能逐名 FindIndex —— 那要求调用方事先知道所有索引名)。
  389. // 返回拷贝而不是内部指针: metas 会随 CreateIndex/DropIndex 重新分配,
  390. // 外部持有 &metas[i] 只会在下一次建/删索引时变成野指针。
  391. func (im *IndexMetaPage) GetIndexes() []IndexMeta {
  392. im.resolveIndexes()
  393. out := make([]IndexMeta, 0, len(im.active))
  394. for _, ri := range im.active {
  395. out = append(out, *ri.meta)
  396. }
  397. return out
  398. }
  399. // GetRowStoreIndex 返回承载行数据的一级索引 (VRTX 的主键索引 / 时序索引) 的元数据拷贝。
  400. // 第二个返回值为 false 表示这张表没有行存储索引 —— 此时行数据无处安放,
  401. // 调用方应当报错而不是"照常建表".
  402. func (im *IndexMetaPage) GetRowStoreIndex() (IndexMeta, bool) {
  403. m, _, ok := im.rowStoreMeta()
  404. if !ok {
  405. return IndexMeta{}, false
  406. }
  407. return *m, true
  408. }
  409. // resolveIndexes 惰性构建并返回活跃索引缓存 (跳过已删除/无处理器的索引)。
  410. //
  411. // 缓存的 &metas[i] 指针在 metas 发生增删或整体替换后会失效, 因此 CreateIndex /
  412. // DropIndex / Reload / Init 都必须调用 invalidateIndexCache。
  413. func (im *IndexMetaPage) resolveIndexes() []resolvedIndex {
  414. if im.activeResolved {
  415. return im.active
  416. }
  417. im.active = im.active[:0]
  418. im.rowIdx = -1
  419. for i := range im.metas {
  420. m := &im.metas[i]
  421. if m.Flag&0x80 != 0 {
  422. continue
  423. }
  424. h, ok := index.Compose.GetHandler(m.IndexType)
  425. if !ok {
  426. continue
  427. }
  428. if im.rowIdx < 0 && h.IsRowStore() {
  429. im.rowIdx = len(im.active)
  430. }
  431. im.active = append(im.active, resolvedIndex{meta: m, handler: h})
  432. }
  433. im.activeResolved = true
  434. return im.active
  435. }
  436. // invalidateIndexCache 使活跃索引缓存失效 (元信息增删/重载后必须调用)。
  437. func (im *IndexMetaPage) invalidateIndexCache() {
  438. im.activeResolved = false
  439. im.active = im.active[:0]
  440. im.rowIdx = -1
  441. }
  442. // indexStoresRows 询问索引处理器: 该索引是否承载行数据。
  443. // 调用方据此判断"行存在哪里", 不需要认识具体索引类型 (主键/时序 由处理器自报)。
  444. func indexStoresRows(m *IndexMeta) bool {
  445. if m == nil {
  446. return false
  447. }
  448. h, ok := index.Compose.GetHandler(m.IndexType)
  449. return ok && h.IsRowStore()
  450. }
  451. // resolver 提供给索引处理器的回表函数: 二级索引内部据此"去找一级索引"取整行,
  452. // 上层无需参与, 也无需知道哪个索引才是主键。
  453. func (im *IndexMetaPage) resolver() index.RowResolver {
  454. return func(pkID uint64) ([]byte, bool) { return im.ResolveByPK(pkID) }
  455. }
  456. // findIndexMetaByName 按索引名查找活跃索引的元信息 (跳过已删除)。
  457. func (im *IndexMetaPage) findIndexMetaByName(name string) *IndexMeta {
  458. for i := range im.metas {
  459. if util.BytesToString(im.metas[i].IndexName[:]) == name && im.metas[i].Flag&0x80 == 0 {
  460. return &im.metas[i]
  461. }
  462. }
  463. return nil
  464. }
  465. func (im *IndexMetaPage) saveMeta(m *IndexMeta) {
  466. cur := im.hdr.PageID
  467. for cur != 0 {
  468. buf := make([]byte, PageSize)
  469. im.handle.ReadPage(cur, buf)
  470. hdr := page.HdrOf(buf)
  471. // 同 loadMetas: 页类型/自述页号对不上就不是索引元数据页, 不能在这上面按名字覆盖元数据
  472. if hdr.Type != page.IndexMetaIndex || hdr.PageID != cur {
  473. break
  474. }
  475. offset := im.hdrSize
  476. for offset+im.metaSize <= hdr.UseSize {
  477. dst := (*IndexMeta)(unsafe.Pointer(&buf[offset]))
  478. if util.BytesToString(dst.IndexName[:]) == util.BytesToString(m.IndexName[:]) {
  479. *dst = *m
  480. im.handle.SavePage(cur, buf)
  481. return
  482. }
  483. offset += im.metaSize
  484. }
  485. cur = hdr.NextPage
  486. }
  487. }
  488. // extractIndexKey 从行数据中提取索引键 (版本感知)
  489. // rowData 格式: [schemaVer(1B)] [field_data...] [NULL 位图]
  490. //
  491. // 复合索引的键 = 按 FieldIDs 顺序, 把各分量字段的原始字节定长拼接。
  492. // - 顺序取 FieldIDs 而非行内物理顺序 —— 查询侧据此才能做"列前缀"范围推导:
  493. // (a, b) 上的索引既能服务 a=?, 也能服务 a=? AND b>?;
  494. // - 每个分量长度 = FieldSize(字段类型, 列级参数), 与行内编码完全一致, 因此拼接键是定长的,
  495. // 字节序比较等价于"先比 a 再比 b"的字典序。宽度必须带列级参数 (字符串列的宽度码):
  496. // 按类型默认档切键会把宽字符串列切成半截, 后面那个分量整体读错位;
  497. // - 任一分量在该行版本里取不到 (列被 Drop / schema 版本不匹配) → 返回 nil,
  498. // 该索引跳过这一行, 与单列索引"取不到键就跳过"的既有语义一致。
  499. func extractIndexKey(rowData []byte, meta *IndexMeta, fields []types.FieldInfo, activeVer byte, versions []SchemaVersion) []byte {
  500. if meta == nil || meta.FieldCnt == 0 || len(rowData) < 1 {
  501. return nil
  502. }
  503. n := int(meta.FieldCnt)
  504. if n > len(meta.FieldIDs) {
  505. return nil
  506. }
  507. // 从 rowData 读取该行的 schema 版本
  508. ver := rowData[0]
  509. var sv *SchemaVersion
  510. for i := range versions {
  511. if versions[i].Version == ver {
  512. sv = &versions[i]
  513. break
  514. }
  515. }
  516. if sv == nil {
  517. return nil
  518. }
  519. // 沿该版本的字段顺序走一遍行数据, 顺手取齐各分量。
  520. // seg[k] 指向 rowData 的子切片, 不做拷贝; 全部取齐后再一次性拼出键。
  521. // NULL 位图在行尾, 长度按本行自己的版本算 (n = len(sv.FieldIndices)),
  522. // 位置 = 该版本行总长 - ⌈n/8⌉ (见 Table.versionedNullBitmap 的说明)。
  523. bitmapLen := types.NullBitmapLen(len(sv.FieldIndices))
  524. rowSize := 1 + bitmapLen
  525. for _, idx := range sv.FieldIndices {
  526. if int(idx) >= len(fields) {
  527. return nil
  528. }
  529. sz := fields[idx].Width()
  530. if sz == 0 { // 未知类型/非法列参数: 长度算不出来, 位图定位会错, 直接放弃该行
  531. return nil
  532. }
  533. rowSize += int(sz)
  534. }
  535. if rowSize > len(rowData) {
  536. return nil
  537. }
  538. bitmap := rowData[rowSize-bitmapLen : rowSize]
  539. var (
  540. seg [16][]byte
  541. off = 1
  542. need = n
  543. )
  544. for i, idx := range sv.FieldIndices {
  545. fi := int(idx)
  546. if fi >= len(fields) {
  547. return nil
  548. }
  549. size := int(fields[fi].Width())
  550. for k := 0; k < n; k++ {
  551. if int(meta.FieldIDs[k]) != fi {
  552. continue
  553. }
  554. // 分量是 NULL → 该行不进本索引 (键里放不下"缺失"这一档, 上层按 nil 键跳过)。
  555. if types.NullBitSet(bitmap, i) {
  556. return nil
  557. }
  558. if off+size > len(rowData) {
  559. return nil
  560. }
  561. seg[k] = rowData[off : off+size]
  562. need--
  563. break
  564. }
  565. off += size
  566. if need == 0 {
  567. break // 分量已取齐, 后面的字段与本索引无关
  568. }
  569. }
  570. if need != 0 {
  571. return nil // 有分量不在该版本中 (如该列已被 Drop), 无法构成完整键
  572. }
  573. total := 0
  574. for k := 0; k < n; k++ {
  575. total += len(seg[k])
  576. }
  577. key := make([]byte, 0, total)
  578. for k := 0; k < n; k++ {
  579. key = append(key, seg[k]...)
  580. }
  581. _ = activeVer
  582. return key
  583. }
  584. // InsertRow 插入行到所有索引
  585. func (im *IndexMetaPage) InsertRow(fields []types.FieldInfo, pkID uint64, rowData []byte, activeVer byte, versions []SchemaVersion) error {
  586. im.rowCache = nil // 插入可能创建新 DataPage, 失效缓存
  587. indexes := im.resolveIndexes()
  588. // 唯一约束预检必须先于任何写入: 逐个索引边查边写的话, 撞车时前面几个索引
  589. // (含承载行数据的行存储索引) 已经写进去了, 只剩一个错误返回 —— 库里多出一行
  590. // 半成品, 而且它的行还在、二级条目不全, 之后按任何二级索引都查不到它。
  591. if err := im.checkUniqueForKey(indexes, rowData, pkID, fields, activeVer, versions); err != nil {
  592. return err
  593. }
  594. for _, ri := range indexes {
  595. m := ri.meta
  596. indexKey := extractIndexKey(rowData, m, fields, activeVer, versions)
  597. if indexKey == nil {
  598. continue
  599. }
  600. entryPage := m.EntryPage
  601. _, err := ri.handler.Insert(im.handle, &entryPage, indexKey, pkID, rowData, len(indexKey))
  602. if err != nil {
  603. return err
  604. }
  605. if entryPage != m.EntryPage {
  606. m.EntryPage = entryPage
  607. im.saveMeta(m)
  608. }
  609. }
  610. return nil
  611. }
  612. // CheckUniqueForKey 校验行数据 rowData 是否会撞上某个唯一索引里【别的行】已有的键。
  613. // 冲突返回 ErrDuplicatedKey; 无冲突 (含"该行压根不进这些索引") 返回 nil。
  614. //
  615. // 抽成独立方法是因为它有两个必须分开的调用时机:
  616. // - InsertRow / UpdateByKey 内部各调一次 —— 保证任何入口在写入前都拦得住;
  617. // - Table 的更新路径还要在 serializeRow 之前【再】调一次: serializeRow 会就地
  618. // 覆写旧行的动态堆块, 冲突必须在它动手之前发现 (见 serializeRowForCheck)。
  619. //
  620. // 因此这里只读不写、可重复调用。
  621. func (im *IndexMetaPage) CheckUniqueForKey(rowData []byte, pkID uint64, fields []types.FieldInfo, activeVer byte, versions []SchemaVersion) error {
  622. return im.checkUniqueForKey(im.resolveIndexes(), rowData, pkID, fields, activeVer, versions)
  623. }
  624. // checkUniqueForKey 是 CheckUniqueForKey 的内核, 收【已解析】的索引集。
  625. // InsertRow / UpdateByKey 手里本来就有一份 resolveIndexes 的结果, 再解析一遍等于
  626. // 每次写入都把索引元信息链多走一趟 —— 插入/更新是热路径, 这趟省掉。
  627. func (im *IndexMetaPage) checkUniqueForKey(indexes []resolvedIndex, rowData []byte, pkID uint64, fields []types.FieldInfo, activeVer byte, versions []SchemaVersion) error {
  628. for _, ri := range indexes {
  629. if !ri.meta.Unique() {
  630. continue
  631. }
  632. indexKey := extractIndexKey(rowData, ri.meta, fields, activeVer, versions)
  633. if indexKey == nil {
  634. continue // 任一分量为 NULL → 该行不进本索引, 唯一性对 NULL 不设限
  635. }
  636. if im.uniqueConflict(ri, indexKey, pkID) {
  637. return fmt.Errorf("%w: 索引 %q, pkID=%d",
  638. ErrDuplicatedKey, util.BytesToString(ri.meta.IndexName[:]), pkID)
  639. }
  640. }
  641. return nil
  642. }
  643. // uniqueConflict 判断唯一索引里是否已有"别的行"占着这个键。
  644. //
  645. // 用 resolve=nil 直接拿条目里的 8B pkID: 唯一性只关心"是不是同一行", 不需要回表取整行
  646. // (回表会顺着行存储索引再查一次, 每次插入都多一遍 O(页数) 的定位)。返回的 pkID 与
  647. // 当前行相同说明命中的是本行自己的条目 (更新场景), 不算冲突。
  648. func (im *IndexMetaPage) uniqueConflict(ri resolvedIndex, indexKey []byte, pkID uint64) bool {
  649. got, found := ri.handler.Search(im.handle, ri.meta.EntryPage, indexKey, len(indexKey), nil)
  650. if !found || len(got) < 8 {
  651. return false
  652. }
  653. return binary.BigEndian.Uint64(got[:8]) != pkID
  654. }
  655. // searchAt 在已定位的索引元信息上查一行 (统一契约: 返回整行, 带 8B pkID 前缀)。
  656. //
  657. // 本层不判断索引类型: 二级索引所缺的行数据由处理器内部用回表函数补齐。
  658. // 独立抽出是为了让"先定位、再复用"的调用方 (Table.SearchByIndex/DeleteRow/UpdateRow)
  659. // 只做一次树查找, 不必重复「findIndexMetaByName + handler.Search」。
  660. func (im *IndexMetaPage) searchAt(meta *IndexMeta, indexKey []byte) ([]byte, bool) {
  661. if meta == nil {
  662. return nil, false
  663. }
  664. handler, ok := index.Compose.GetHandler(meta.IndexType)
  665. if !ok {
  666. return nil, false
  667. }
  668. return handler.Search(im.handle, meta.EntryPage, indexKey, len(indexKey), im.resolver())
  669. }
  670. // SearchByIndex 通过索引搜索 (统一契约: 返回整行, 带 8B pkID 前缀)
  671. func (im *IndexMetaPage) SearchByIndex(idxName string, indexKey []byte) ([]byte, bool) {
  672. return im.searchAt(im.findIndexMetaByName(idxName), indexKey)
  673. }
  674. // ResolveByPK 按 pkID 查找整行数据 (带 8B pkID 前缀)。
  675. //
  676. // 本层不知道行数据"存在哪种索引里": 由声明 IsRowStore 的处理器自己实现读取
  677. // (主键 B+ 树走 DataPage 二分缓存, 时序索引走条目链)。
  678. func (im *IndexMetaPage) ResolveByPK(pkID uint64) ([]byte, bool) {
  679. m, h, ok := im.rowStoreMeta()
  680. if !ok {
  681. return nil, false
  682. }
  683. return h.ResolveRow(im.handle, m.EntryPage, pkID, &im.rowCache)
  684. }
  685. // InvalidateRowCache 使回表定位缓存失效 (外部直接改动行存储索引后调用)
  686. func (im *IndexMetaPage) InvalidateRowCache() { im.rowCache = nil }
  687. // rowStoreHasPK 判断行存储里是否已存在该 pkID —— 不建立/复用回表定位缓存。
  688. //
  689. // 插入查重专用: ResolveByPK 会按需构建 im.rowCache (O(页数)), 而任何一次插入
  690. // 都会让它失效; 若每行插入都走 ResolveByPK, 逐行插入就退化成 O(N²)
  691. // (千万级写入从分钟级变成不可完成)。这里传 cache=nil 走处理器自己的定位路径。
  692. //
  693. // 语义与 ResolveByPK 一致 (仅看行是否存在, 且过滤墓碑页), 只是不做缓存。
  694. func (im *IndexMetaPage) rowStoreHasPK(pkID uint64) bool {
  695. m, h, ok := im.rowStoreMeta()
  696. if !ok || m == nil {
  697. return false
  698. }
  699. _, found := h.ResolveRow(im.handle, m.EntryPage, pkID, nil)
  700. return found
  701. }
  702. // DeleteByKey 通过索引删除一行 —— 统一入口, 不区分索引类型。
  703. //
  704. // storeRow 由调用方 SearchByIndex 预先定位后传入 (头 8 字节为 pkID), 本方法不再
  705. // 重复树查找 —— 否则删除路径会对同一行做两次 handler.Search。
  706. //
  707. // 步骤:
  708. // 1. 复用调用方已定位的整行 (二级索引所缺的行数据已由处理器内部回表补齐)。
  709. // 2. 遍历全部索引, 让每个处理器各自删掉该行在本索引中的条目,
  710. // pkID 一并传入以便二级索引在重复 key 下精确定位。
  711. // 主键 <-> 二级索引的级联因此天然成立, 无需在本层判断索引类型。
  712. func (im *IndexMetaPage) DeleteByKey(idxName string, indexKey []byte, storeRow []byte, fields []types.FieldInfo, activeVer byte, versions []SchemaVersion) error {
  713. im.rowCache = nil // 删除可能释放 DataPage, 失效缓存
  714. meta := im.findIndexMetaByName(idxName)
  715. if meta == nil || len(storeRow) < 8 {
  716. return page.ErrNotFound
  717. }
  718. pkID := binary.BigEndian.Uint64(storeRow[:8])
  719. rowData := storeRow[8:]
  720. // 每个索引各自删除该行在本索引中的条目
  721. var firstErr error
  722. for _, ri := range im.resolveIndexes() {
  723. m := ri.meta
  724. key := extractIndexKey(rowData, m, fields, activeVer, versions)
  725. if len(key) == 0 {
  726. if m != meta {
  727. continue // 取不到行数据, 无法推导该索引键, 跳过
  728. }
  729. key = indexKey // 目标索引: 调用方给的键就是它在索引里的键
  730. }
  731. entryPage := m.EntryPage
  732. if err := ri.handler.Delete(im.handle, &entryPage, key, pkID, len(key)); err != nil {
  733. if err != page.ErrNotFound && firstErr == nil {
  734. firstErr = err
  735. }
  736. }
  737. if entryPage != m.EntryPage {
  738. m.EntryPage = entryPage
  739. im.saveMeta(m)
  740. }
  741. }
  742. return firstErr
  743. }
  744. // UpdateByKey 通过索引更新一行 —— 统一入口, 不区分索引类型。
  745. //
  746. // oldStoreRow 由调用方 SearchByIndex 预先定位后传入 (头 8 字节为 pkID), 本方法不再
  747. // 重复回表 —— 否则更新路径会先 ResolveByPK, 失败还会再 SearchByIndex。
  748. //
  749. // 每个索引各自处理自己条目的更新:
  750. // - 行存储索引(主键/时序): 就地改写行数据;
  751. // - 二级索引: 只有 [key + pkID], 键未变则无需动作, 键变了则按 (旧键, pkID) 删旧插新。
  752. //
  753. // 本层只负责"把新行数据 + 新旧键交给处理器", 不判断索引类型。
  754. func (im *IndexMetaPage) UpdateByKey(indexKey []byte, newRowData []byte, pkID uint64, oldStoreRow []byte, fields []types.FieldInfo, activeVer byte, versions []SchemaVersion) error {
  755. // 取旧行数据: 二级索引需要用旧行提取"旧键", 才能精确删掉旧条目
  756. var oldRowData []byte
  757. if len(oldStoreRow) > 8 {
  758. oldRowData = oldStoreRow[8:]
  759. }
  760. indexes := im.resolveIndexes()
  761. // 唯一约束预检: 新键若已被"别的行"占着, 必须在任何写入之前拒绝。
  762. // 放在循环里逐个索引边查边更新是不行的 —— 撞车时前面几个索引已经改好了,
  763. // 旧键已删、新键已插, 而函数返回错误, 调用方以为没更新, 数据却变了一半。
  764. // (Table 的更新路径还会在 serializeRow 之前先调一次, 以免旧行的动态堆块被
  765. // 试改 —— 这里这次是给"直接调 UpdateByKey 的调用方"兜底。)
  766. if err := im.checkUniqueForKey(indexes, newRowData, pkID, fields, activeVer, versions); err != nil {
  767. return err
  768. }
  769. // 逐个索引更新本行的条目
  770. var firstErr error
  771. for _, ri := range indexes {
  772. m := ri.meta
  773. // 定位键: 能用旧行提取就用旧键; 提取不到时退回调用方给的查找键
  774. // (主键/时序索引的查找键即其在索引中的键, 与既有行为一致)
  775. oldKey := indexKey
  776. oldKeyFromRow := false
  777. if oldRowData != nil {
  778. if k := extractIndexKey(oldRowData, m, fields, activeVer, versions); len(k) > 0 {
  779. oldKey, oldKeyFromRow = k, true
  780. }
  781. }
  782. newKey := extractIndexKey(newRowData, m, fields, activeVer, versions)
  783. entryPage := m.EntryPage
  784. var err error
  785. switch {
  786. case len(newKey) > 0:
  787. err = ri.handler.Update(im.handle, &entryPage, oldKey, newKey, newRowData, pkID, len(newKey))
  788. case oldRowData == nil:
  789. // 行存储索引 (主键/时序): 键取不到也必须改写行数据, 用旧键原地更新
  790. err = ri.handler.Update(im.handle, &entryPage, oldKey, oldKey, newRowData, pkID, len(oldKey))
  791. case oldKeyFromRow:
  792. // 二级索引且旧行有键、新行取不到键: 该列已变为 NULL (或其版本不再有此列),
  793. // 这一行已退出本索引 —— 必须删掉旧条目。留下旧条目会让该索引继续命中一个
  794. // 现在为 NULL 的行, 直接破坏"NULL 不匹配任何比较"的三值语义。
  795. err = ri.handler.Delete(im.handle, &entryPage, oldKey, pkID, len(oldKey))
  796. default:
  797. // 二级索引且新旧都取不到键: 该行本来就不在本索引里, 无需动作。
  798. }
  799. if err != nil && err != page.ErrNotFound {
  800. // 过去这里丢弃了返回值, 导致 schema 演进后更新旧行"静默失败":
  801. // 对外返回 nil 但数据实际没变。必须向上抛出。
  802. firstErr = fmt.Errorf("更新索引 %q (pkID=%d) 失败: %w",
  803. util.BytesToString(m.IndexName[:]), pkID, err)
  804. }
  805. if entryPage != m.EntryPage {
  806. m.EntryPage = entryPage
  807. im.saveMeta(m)
  808. }
  809. if firstErr != nil {
  810. break
  811. }
  812. }
  813. // 回表定位缓存 (rowCache) 只在"行可能换页"时才失效。
  814. // PrimaryHandler.Update 的三级策略里, 只有行变长且页内装不下才会换页;
  815. // 行长不变 / 变短时行仍留在原 DataPage, 缓存依旧有效。
  816. // 若在这里无条件失效, 逐行更新会把 O(页数) 的缓存全量重建摊到每一行上,
  817. // 退化成 O(N²) —— 50 万行的批量更新会卡死。
  818. if oldRowData == nil || len(newRowData) > len(oldRowData) {
  819. im.rowCache = nil
  820. }
  821. return firstErr
  822. }