Table.go 86 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158
  1. package core
  2. import (
  3. "TsDB2/DB/core/page"
  4. "TsDB2/DB/core/types"
  5. "TsDB2/DB/core/util"
  6. "bytes"
  7. "encoding/binary"
  8. "fmt"
  9. "sort"
  10. "unsafe"
  11. )
  12. // SchemaFragHdr 共享扩展页里每个 schema 续块的头 (定长 8 字节, 紧随 payload)
  13. // 警告: 该结构体仅用于计算大小/表达语义, 写盘一律用 putSchemaFragHdr,
  14. // 不要做 `*(*SchemaFragHdr)(p) = v` 的结构体赋值。
  15. type SchemaFragHdr struct {
  16. NextPage uint32 // 下一个续块所在页 (0 = 本表续块链结束)
  17. NextOff uint16 // 下一个续块在页内的物理偏移
  18. DataLen uint16 // 本续块 payload 字节数
  19. }
  20. // schemaFragHdrSize schema 续块头的物理大小 (字节)
  21. const schemaFragHdrSize = 8
  22. // putSchemaFragHdr 写入续块头 (大端: NextPage 4B + NextOff 2B + DataLen 2B)
  23. func putSchemaFragHdr(buf []byte, off int, h SchemaFragHdr) {
  24. binary.BigEndian.PutUint32(buf[off:off+4], h.NextPage)
  25. binary.BigEndian.PutUint16(buf[off+4:off+6], h.NextOff)
  26. binary.BigEndian.PutUint16(buf[off+6:off+8], h.DataLen)
  27. }
  28. // readSchemaFragHdr 读取续块头
  29. func readSchemaFragHdr(buf []byte, off int) SchemaFragHdr {
  30. return SchemaFragHdr{
  31. NextPage: binary.BigEndian.Uint32(buf[off : off+4]),
  32. NextOff: binary.BigEndian.Uint16(buf[off+4 : off+6]),
  33. DataLen: binary.BigEndian.Uint16(buf[off+6 : off+8]),
  34. }
  35. }
  36. // SchemaVersion 表的一个 schema 版本定义
  37. type SchemaVersion struct {
  38. Version byte
  39. FieldIndices []byte // 每个字段在 masterFields 中的索引
  40. }
  41. type TableInfo struct {
  42. Name [TableNameSize]byte
  43. Flag byte // 表的状态是否被删除
  44. FieldCount byte // 当前字段的总数量(包含已删除,永不减少)byte=255上限
  45. ActiveFieldCount byte // 实际活跃字段数量(不包含已删除)
  46. _ byte // 1 显式对齐填充 (TotalRows 需要 8 字节对齐)
  47. // HeapPageCount 本表堆链 (FirstHeap→LastHeap) 上的页数。
  48. //
  49. // 塞进 TotalRows 前的对齐填充里 (那 4 字节本来就是编译器要插入的空洞), 不额外
  50. // 增加 TableInfo 的体积。
  51. // 用途: DropTable 把整条堆链 O(1) 拼进空闲链时, 不遍历也有链长可累加空闲页计数。
  52. HeapPageCount uint32 // 4 堆链页数
  53. TotalRows uint64 // 当前表行的总数量
  54. FirstHeap uint32 // 堆链首页
  55. LastHeap uint32 // 堆链尾页
  56. FirstData uint32 // 数据链首页
  57. LastData uint32 // 数据链尾
  58. RowSize uint16 // 单条记录大小(字节)
  59. FieldAllocateSize uint16 // 当前列总分配大小
  60. IndexCount byte // 这张表有几个索引
  61. FirstIndexID uint32 // 本表第一个索引的元数据位置
  62. LastIndexID uint32 // 本表最后一个索引的元数据位置
  63. // ExtRef schema 续块在「全库共享扩展页」中的位置
  64. // (复用原 FirstOverflow/LastOverflow 占用的 8 字节: ExtPage 落在 8 字节对齐处,
  65. // 尾部 2 字节显式填充, 使 TableInfo 整体大小为 8 的倍数)
  66. ExtPage uint32 // 4 首个续块所在扩展页 ID (0 = schema 全部内联, 无续块)
  67. ExtOff uint16 // 2 首个续块在页内的物理偏移
  68. _ uint16 // 2 显式填充, 使整体大小为 8 的倍数
  69. }
  70. // 编译期断言: 钉死 TableInfo 的盘上大小与字段布局。
  71. //
  72. // TableNameSize 变化会整体平移 Name 之后所有字段的偏移 —— analyzer 那套硬编码偏移
  73. // (analyzer/main.go 的 TI_*) 必须同步修改, 改漏了会静默读错数据。这里把大小钉死,
  74. // 让「改了布局却忘了改断言」直接编译失败。
  75. //
  76. // 布局 (TableNameSize=48) = 104 字节:
  77. //
  78. // Name[48] + Flag/FieldCount/ActiveFieldCount/pad(4) + HeapPageCount(4) + TotalRows(8)
  79. // + FirstHeap/LastHeap/FirstData/LastData(16) + RowSize/FieldAllocateSize(4)
  80. // + IndexCount/pad(4) + FirstIndexID/LastIndexID/ExtPage(12) + ExtOff/pad(4)
  81. var (
  82. _ [unsafe.Sizeof(TableInfo{}) - 104]struct{}
  83. _ [104 - unsafe.Sizeof(TableInfo{})]struct{}
  84. )
  85. // readSchema 读取一张表完整的 schema 定义
  86. // 数据来源: 表页内的固定内联块 (allocSize 字节) + 共享扩展页里的续块链
  87. // 新格式: [activeVer(1B)] [masterCount(1B)] [type+len+name]×masterCount [verCount(1B)] [verDefs...]
  88. func readSchema(handle page.HandleDb, pageID uint32, startOff uint16, allocSize uint16,
  89. extPage uint32, extOff uint16) (byte, []types.FieldInfo, []SchemaVersion) {
  90. // 1. 内联块 (表页内, 固定 allocSize 字节, 无跳转槽)
  91. var data []byte
  92. buf := make([]byte, PageSize)
  93. if err := handle.ReadPage(pageID, buf); err == nil {
  94. end := int(startOff) + int(allocSize)
  95. if end > len(buf) {
  96. end = len(buf)
  97. }
  98. if int(startOff) < end {
  99. data = append(data, buf[startOff:end]...)
  100. }
  101. }
  102. // 2. 续块链 (共享扩展页): [SchemaFragHdr 8B][payload]
  103. for pid, off := extPage, extOff; pid != 0; {
  104. fb := make([]byte, PageSize)
  105. if err := handle.ReadPage(pid, fb); err != nil {
  106. break
  107. }
  108. if int(off)+schemaFragHdrSize > len(fb) {
  109. break
  110. }
  111. frag := readSchemaFragHdr(fb, int(off))
  112. start := int(off) + schemaFragHdrSize
  113. end := start + int(frag.DataLen)
  114. if end > len(fb) {
  115. break
  116. }
  117. data = append(data, fb[start:end]...)
  118. pid, off = frag.NextPage, frag.NextOff
  119. }
  120. if len(data) < 2 {
  121. return 0, nil, nil
  122. }
  123. pos := 0
  124. activeVer := data[pos]
  125. pos++
  126. masterCount := int(data[pos])
  127. pos++
  128. // 读取 master fields: [type(1B)][flag(1B)][nameLen(1B)][name...]
  129. // flag 是列级标志 (删除/可空/唯一 + decimal 的 scale), 见 types.FieldInfo.Flag。
  130. // 语义变了但字节位置与顺序不变: 必须与 serializeSchema 严格对照。
  131. var masterFields []types.FieldInfo
  132. for i := 0; i < masterCount && pos+3 <= len(data); i++ {
  133. typ := types.FieldType(data[pos])
  134. flag := data[pos+1]
  135. nameLen := int(data[pos+2])
  136. pos += 3
  137. if pos+nameLen > len(data) {
  138. break
  139. }
  140. name := make([]byte, nameLen)
  141. copy(name, data[pos:pos+nameLen])
  142. pos += nameLen
  143. masterFields = append(masterFields, types.FieldInfo{FieldType: typ, FieldName: name, Flag: flag})
  144. }
  145. // 读取版本定义
  146. var versions []SchemaVersion
  147. if pos < len(data) {
  148. verCount := int(data[pos])
  149. pos++
  150. for i := 0; i < verCount && pos < len(data); i++ {
  151. verNo := data[pos]
  152. pos++
  153. if pos >= len(data) {
  154. break
  155. }
  156. fieldCount := int(data[pos])
  157. pos++
  158. indices := make([]byte, fieldCount)
  159. if pos+fieldCount <= len(data) {
  160. copy(indices, data[pos:pos+fieldCount])
  161. pos += fieldCount
  162. }
  163. versions = append(versions, SchemaVersion{Version: verNo, FieldIndices: indices})
  164. }
  165. }
  166. return activeVer, masterFields, versions
  167. }
  168. type Table struct {
  169. info *TableInfo
  170. Field []types.FieldInfo // master registry (所有字段,含已删除,删除标记见 types.FlagDeleted)
  171. handle page.HandleDb
  172. indexMeta *IndexMetaPage
  173. tablePageID uint32 // 表ID
  174. tableStartOffset uint32 // 表所在页的偏移
  175. // schema 续块所在的「全库共享扩展页」分配器 (由 TablePage 注入, 各表同一实例)
  176. schemaExt *SchemaExtAllocator
  177. // schema 版本管理
  178. activeVersion byte
  179. versions []SchemaVersion
  180. // 写操作通知回调 (防抖自动提交)
  181. onWrite func()
  182. // 自增主键游标 (内存态, 详见 AllocPKID/ensureNextPK):
  183. // 不能再现算 TotalRows+1 —— 删除会让行数回退, 编号随之回退并撞上存活的 pkID。
  184. nextPK uint64
  185. nextPKInit bool
  186. pendingPK uint64 // AllocPKID 刚预留、等待 InsertRowWithID 消费的号
  187. hasPendingPK bool
  188. }
  189. // CreateIndex 建二级索引。
  190. //
  191. // 这里先校验分量列能不能做键, 再交给 IndexMetaPage 建 —— 拆成两层是因为
  192. // IndexMetaPage 只拿得到索引自己的元信息, 看不到表的字段类型:
  193. // - 分量列不存在 → ErrFieldNotFound;
  194. // - 分量是动态列 → ErrIndexDynamicField。动态列在行内只存 6B 堆引用, 值 != 引用,
  195. // 按值查找永远不可能命中; 建出来既查不到也不会被查询侧选中 (buildKeyLayout 会跳过),
  196. // 只会白占空间、白写。拦在建的这一步, 而不是等用到才发现;
  197. // - 复合索引 (IndexTypeCompose) 的分量宽度不是 8 字节 → ErrIndexComposeWidth。
  198. // ComposeEntry 把每个分量写死成 8 字节 (EntryLength = 8*n+8, Compare 里
  199. // offset := i*8 后按 Uint64 读), 而拼键侧 (extractIndexKey) 是按分量的真实宽度拼的。
  200. // 两者对不上时条目长度与键长度不一致, Compare 会读到相邻分量的字节 —— 排序和查找
  201. // 会稳定地给出错误结果, 且不报错。这不是新引入的问题 (16byte 字段今天就对不上),
  202. // 但新类型会让它更容易被踩到, 所以在建立时就拒绝, 而不是让它静默错位。
  203. // 需要非 8 字节分量请用 B+ 树/哈希索引 (按真实宽度拼键, 无此限制)。
  204. //
  205. // unique 为可选 (只取第一个值), 见 IndexMetaPage.CreateIndex: 传 true 建的是唯一索引,
  206. // 同一索引键只允许一行; 任一分量为 NULL 的行不进该索引, 因此多个 NULL 天然共存。
  207. // 唯一约束在写入路径 (InsertRow / UpdateByKey) 上校验, 违反时返回 ErrDuplicatedKey。
  208. func (table *Table) CreateIndex(idxName string, typ page.IndexType, fields []byte, unique ...bool) (*IndexMeta, error) {
  209. for i, fi := range fields {
  210. if int(fi) >= len(table.Field) {
  211. return nil, fmt.Errorf("%w: 索引 %q 的第 %d 个分量", ErrFieldNotFound, idxName, i+1)
  212. }
  213. ft := table.Field[fi].FieldType
  214. if types.IsDynamic(ft) {
  215. return nil, fmt.Errorf("%w (索引 %q 的第 %d 个分量)", ErrIndexDynamicField, idxName, i+1)
  216. }
  217. if typ == page.IndexTypeCompose {
  218. if w := table.Field[fi].Width(); w != 8 {
  219. return nil, fmt.Errorf("%w (索引 %q 的第 %d 个分量是 %s, 宽度 %d)",
  220. ErrIndexComposeWidth, idxName, i+1, types.TypeNameOf(ft), w)
  221. }
  222. }
  223. }
  224. m, err := table.indexMeta.CreateIndex(idxName, typ, fields, unique...)
  225. if err != nil {
  226. return nil, err
  227. }
  228. // 回填存量行。
  229. // 旧实现只建元数据, 索引里一条记录都没有: 建索引之前就存在的行永远查不到,
  230. // 而引擎又没有公开的"重建全部二级索引"接口 —— 数据在、索引空, 是最难排查的
  231. // 一类不一致。建索引这一步必须自己把存量行灌进去。
  232. if err := table.backfillIndex(m); err != nil {
  233. // 回填失败不能留下"半个索引": 元数据还在但内容不全, 比没有索引更危险
  234. // (查询会选中它, 于是稳定地查不到行)。摘掉元数据, 让错误显式冒出来。
  235. // 走 table.DropIndex 而不是 indexMeta.DropIndex: 后者只软删元数据, 会把
  236. // 回填到一半的树页留成孤儿页。
  237. _ = table.DropIndex(idxName)
  238. return nil, err
  239. }
  240. return m, nil
  241. }
  242. // backfillIndex 把表里已有的行灌进一个刚建好的二级索引。
  243. //
  244. // 与写入侧共用 extractIndexKey + 处理器 Insert, 因此:
  245. // - 键编码与后续单条插入完全一致, 不会出现"回填进去了却查不到"的错位;
  246. // - 老行 (该列在其自身的 schema 版本里还不存在, 例如 AddField 之前插入的行)
  247. // 自动跳过, 与"单条插入时缺失列不落索引"的语义保持一致;
  248. // - 动态列在 CreateIndex 已被拒绝, 这里不必再判。
  249. //
  250. // 行存储索引 (主键/时序) 不在此处理: 它们的键就是 pkID, 行数据本就由写入路径维护。
  251. func (table *Table) backfillIndex(m *IndexMeta) error {
  252. if m == nil || indexStoresRows(m) {
  253. return nil
  254. }
  255. primMeta, primHandler, ok := table.indexMeta.rowStoreMeta()
  256. if !ok || primMeta == nil || primHandler == nil {
  257. return nil // 表里没有承载行的索引, 也就没有存量行
  258. }
  259. // 收集 → 排序 → 批量建树。
  260. // 不能逐条 handler.Insert: 那是 n 次 O(log n) 的树写放大, 千万行级别慢到
  261. // 不可接受; 索引重建本来就有 O(n) 的批量路径, 这里直接复用。
  262. var entries [][]byte
  263. for _, dpID := range primHandler.GetDataPageIDs(table.handle, primMeta.EntryPage) {
  264. for _, storeRow := range primHandler.ReadDataPageRows(table.handle, dpID) {
  265. if len(storeRow) < 8 {
  266. continue
  267. }
  268. secKey := extractIndexKey(storeRow[8:], m, table.Field, table.activeVersion, table.versions)
  269. if secKey == nil {
  270. continue // 该行没有这个键 (列不在它的 schema 版本里)
  271. }
  272. entry := make([]byte, len(secKey)+8)
  273. copy(entry, secKey)
  274. // 条目尾部是 pkID (8B BE), 与 rebuildIndexes 的二级条目同一布局
  275. binary.BigEndian.PutUint64(entry[len(secKey):], binary.BigEndian.Uint64(storeRow[:8]))
  276. entries = append(entries, entry)
  277. }
  278. }
  279. if len(entries) == 0 {
  280. // 没有任何存量行进这个索引 → 保持空树 (EntryPage==0), 后续插入照常开根
  281. return nil
  282. }
  283. sort.SliceStable(entries, func(i, j int) bool {
  284. return compareBytes(entries[i][:len(entries[i])-8], entries[j][:len(entries[j])-8]) < 0
  285. })
  286. // 唯一索引进来了要顺手验一遍存量数据: 库里本来就有重复值时, 这个约束是【建不出来】的。
  287. // 硬建下去只会得到一个"违反自身约束"的索引 (同一键挂着两行), 之后点查可能返回其中
  288. // 任意一行, 而且从索引上完全看不出它已经坏了。宁可在建索引这一步报错, 由调用方决定
  289. // 是清理数据还是改用非唯一索引。排序后重复键必然相邻, 一次线性扫描即可。
  290. if m.Unique() {
  291. for i := 1; i < len(entries); i++ {
  292. if compareBytes(entries[i-1][:len(entries[i-1])-8], entries[i][:len(entries[i])-8]) == 0 {
  293. return fmt.Errorf("%w: 索引 %q 的存量数据中已存在重复键 (pkID %d 与 %d), 无法建成唯一索引",
  294. ErrDuplicatedKey, util.BytesToString(m.IndexName[:]),
  295. binary.BigEndian.Uint64(entries[i-1][len(entries[i-1])-8:]),
  296. binary.BigEndian.Uint64(entries[i][len(entries[i])-8:]))
  297. }
  298. }
  299. }
  300. if err := buildSecondaryFromEntries(table.handle, m, entries); err != nil {
  301. return err
  302. }
  303. table.indexMeta.saveMeta(m)
  304. table.indexMeta.InvalidateRowCache()
  305. return nil
  306. }
  307. func (table *Table) DropIndex(idx string) error {
  308. // DROP INDEX 必须把整棵索引树还给空闲链。
  309. // 旧实现只把元数据打上软删除标记 (Flag|=0x80) 并从 metas 摘掉: 根指针一丢, 整棵树
  310. // (内部节点 + 叶子) 就既不可达、又不在空闲链上 —— 变成永久孤儿页, 文件只涨不缩。
  311. // 实测 20 万行的一棵二级索引约 197 页; bench_10m 里 9.9M 行的 Dept 索引是数万页,
  312. // 正是那批孤儿页的主因。
  313. // 注意: indexMeta.DropIndex 会用 append 挪动 metas 元素, 之后 &metas[i] 就失效了,
  314. // 所以必须在摘除之前把 IndexType / EntryPage 快照出来。
  315. var itype page.IndexType
  316. var rootPID uint32
  317. if m := table.indexMeta.findIndexMetaByName(idx); m != nil {
  318. itype, rootPID = m.IndexType, m.EntryPage
  319. }
  320. if err := table.indexMeta.DropIndex(idx); err != nil {
  321. return err
  322. }
  323. // 旧结构此刻已不可达 (元数据已摘), 归还它的全部页。
  324. releaseIndexPages(table, itype, rootPID)
  325. return nil
  326. }
  327. // BuildIndexKey 按索引定义, 把各分量值编码成索引键 (与写入侧 extractIndexKey 同一编码)。
  328. //
  329. // 复合索引的键是各分量定长拼接, 外部无法凭空构造 —— 因此点查/删除/更新复合索引
  330. // (SearchByIndex / DeleteRow / UpdateRow) 都要先经这里把值编成键。
  331. // values 的顺序与建索引时给的 fieldIDs 顺序一致, 个数必须相等。
  332. func (table *Table) BuildIndexKey(idxName string, values ...interface{}) ([]byte, error) {
  333. m := table.indexMeta.findIndexMetaByName(idxName)
  334. if m == nil {
  335. return nil, ErrIndexNotFound
  336. }
  337. n := int(m.FieldCnt)
  338. if n != len(values) {
  339. return nil, fmt.Errorf("索引 %q 需要 %d 个分量值, 实际给了 %d 个", idxName, n, len(values))
  340. }
  341. key := make([]byte, 0, n*8)
  342. for k := 0; k < n; k++ {
  343. fi := int(m.FieldIDs[k])
  344. if fi >= len(table.Field) {
  345. return nil, ErrFieldNotFound
  346. }
  347. ft := table.Field[fi].FieldType
  348. if table.Field[fi].IsDropped() {
  349. return nil, fmt.Errorf("索引 %q 的第 %d 个分量列已被删除", idxName, k+1)
  350. }
  351. if types.IsDynamic(ft) {
  352. return nil, fmt.Errorf("索引 %q 的第 %d 个分量是动态列, 不能作为索引键", idxName, k+1)
  353. }
  354. enc, err := encodeIndexKeySafeWithParam(values[k], ft, table.fieldParam(fi))
  355. if err != nil {
  356. return nil, fmt.Errorf("索引 %q 的第 %d 个分量: %w", idxName, k+1, err)
  357. }
  358. if want := int(table.Field[fi].Width()); len(enc) != want {
  359. return nil, fmt.Errorf("索引 %q 的第 %d 个分量: 编码 %d 字节, 列宽 %d 字节",
  360. idxName, k+1, len(enc), want)
  361. }
  362. key = append(key, enc...)
  363. }
  364. return key, nil
  365. }
  366. // TotalRows 返回表的总行数
  367. func (table *Table) TotalRows() uint64 { return table.info.TotalRows }
  368. // TablePageID 返回表页 ID (调试用)
  369. func (table *Table) TablePageID() uint32 { return table.tablePageID }
  370. // TableStartOffset 返回表信息在页中的偏移 (调试用)
  371. func (table *Table) TableStartOffset() uint32 { return table.tableStartOffset }
  372. // IndexMeta 返回索引元数据页 (调试用)
  373. func (table *Table) IndexMeta() *IndexMetaPage { return table.indexMeta }
  374. // SetOnWrite 设置写操作通知回调 (防抖自动提交)
  375. func (table *Table) SetOnWrite(fn func()) { table.onWrite = fn }
  376. // findFieldIndex 查找字段在 master registry 中的索引
  377. func (table *Table) findFieldIndex(name string) int {
  378. for i, field := range table.Field {
  379. if string(field.FieldName) == name {
  380. return i
  381. }
  382. }
  383. return -1
  384. }
  385. // fieldWidth 取某列的行内宽度 (字节) —— 引擎内所有行偏移计算的唯一入口。
  386. //
  387. // 为什么不能直接 types.GetSize(f.FieldType): 字符串列的行内宽度由【列自己声明】
  388. // (Flag 低 5 位的宽度码, 上限 types.MaxInlineColumnWidth), 只看类型会一律算成 256,
  389. // 于是该列之后的所有列都从错位偏移解码。这里统一经 FieldInfo.Width()。
  390. //
  391. // 返回 0 表示索引越界或列级参数非法 —— 调用方必须当错误处理: 0 宽度会让整行错位。
  392. func (table *Table) fieldWidth(masterIdx int) uint16 {
  393. if masterIdx < 0 || masterIdx >= len(table.Field) {
  394. return 0
  395. }
  396. return table.Field[masterIdx].Width()
  397. }
  398. // MaxPhysicalRowData 单行 rowData 的物理上限 (字节) —— 一行必须能装进一个 DataPage。
  399. //
  400. // 16384 (页) - 40 (DataPage 头) - 4 (行长度前缀) - 8 (storeRow 里的 pkID) = 16332
  401. //
  402. // 它与建列闸门 types.MaxRowDataSize (8171, 一页两行) 是两件事:
  403. // - MaxRowDataSize 是设计政策, 在 CreateTable / AddField 处执行, 保证一页至少装两行;
  404. // 它卡的是【整行占用】= 4 + 8 + 这里的 rowData, 所以比这道物理墙紧得多 (8159 / 16332);
  405. // - MaxPhysicalRowData 是物理墙, 在写入时兜底 —— 超过它的行字节流会写坏 DataPage,
  406. // 必须有第二道网拦住"建列闸门之前的旧表"与"schema 被外部改坏"这两种情况。
  407. const MaxPhysicalRowData = int(PageSize) - int(page.DataPageHdrSize) - 4 - 8
  408. // validateFieldsForSchema 校验一组列定义能否构成合法 schema:
  409. // 列名长度可落盘 + 列级参数自洽 + 行宽不超建列闸门。
  410. //
  411. // 校验落在 core 而不是只留在 ORM / migrate: schema 也能从 SQL 或低层 API 直接建出来,
  412. // 只有拦在真正写 schema 的地方才拦得住。
  413. func validateFieldsForSchema(fields []types.FieldInfo) error {
  414. for _, f := range fields {
  415. // 列名长度: schema 里是 [nameLen(1B)][name...] (见 serializeSchema / readSchema)。
  416. // 长度为 0 或 >255 都不能落盘 —— 0 会让解析侧把下一个字段的类型字节当名字,
  417. // >255 会让 nameLen 截断并让其后所有字段整体错位。都不是"名字有瑕疵", 是整表错乱。
  418. if len(f.FieldName) == 0 {
  419. return fmt.Errorf("%w: 列名长度为 0 (schema 里列名长度是 1 字节, 空名字无法落盘)",
  420. types.ErrValueRange)
  421. }
  422. if len(f.FieldName) > FieldNameSize {
  423. return fmt.Errorf("%w: 列名 %q 有 %d 字节, 上限 %d 字节 "+
  424. "(schema 里列名长度是 1 字节, 超长会截断并让后续列整体错位)",
  425. types.ErrValueRange, f.FieldName, len(f.FieldName), FieldNameSize)
  426. }
  427. if err := types.ValidateColumnParam(f.FieldType, f.Param()); err != nil {
  428. return fmt.Errorf("列 %q: %w", f.FieldName, err)
  429. }
  430. }
  431. return types.CheckRowSize(fields)
  432. }
  433. // nextSchemaVersion 返回下一个可用的 schema 版本号。
  434. //
  435. // schemaVer 是每行行首的 1 字节 (getVersionedOffset / rowDataSizeForVersion 都按它取版本),
  436. // 版本条数也是 1 字节落盘 (serializeSchema 的 verCount)。到 0xFF 再自增就会回绕成 0:
  437. // 0 不是任何已知版本, getVersion 返回 nil → 该行按"未知版本"解析, NULL 位图取不到,
  438. // 所有列静默读成 0 / NULL。宁可在演进这一步拒绝, 让用户重建表。
  439. //
  440. // 每次 AddField / DropField 都会新增一版, 因此这道闸门也限定了"一张表的 schema
  441. // 最多能演进多少次"。
  442. func (table *Table) nextSchemaVersion() (byte, error) {
  443. if table.activeVersion >= 0xFF || len(table.versions) >= 0xFF {
  444. return 0, fmt.Errorf("schema 版本数已达上限 %d (activeVer=%d, 已存 %d 版): "+
  445. "版本号是行首 1 字节, 再演进会回绕并让旧行解析错位; 请重建该表",
  446. 0xFF, table.activeVersion, len(table.versions))
  447. }
  448. return table.activeVersion + 1, nil
  449. }
  450. // getVersion 获取指定版本的 schema 定义
  451. func (table *Table) getVersion(ver byte) *SchemaVersion {
  452. for i := range table.versions {
  453. if table.versions[i].Version == ver {
  454. return &table.versions[i]
  455. }
  456. }
  457. return nil
  458. }
  459. // getVersionedOffset 计算某行中指定 master 字段索引在行数据中的偏移。
  460. //
  461. // 行布局: [schemaVer 1B] [字段区...] [NULL 位图 ⌈n/8⌉ B]
  462. // 位图长度按【行自身的 schemaVer】算 (n = 该版本 FieldIndices 长度), 不能取当前字段数。
  463. //
  464. // 位图【放在行尾】而不是 schemaVer 之后, 是为了让字段区起点恒为偏移 1:
  465. // 时序索引按固定偏移 9 (== [pkID 8B][schemaVer 1B]) 读首列当时间键
  466. // (见 DB/types.go 的 modelInfo 与 core/index 的 timeRowInRange / FindRowByIndexKey),
  467. // 位图若前插会把首列整体后移 ⌈n/8⌉ 字节, 时序键读取全错 —— "行写得进却 Load 不到"。
  468. // 放行尾则字段偏移与旧格式完全一致, 这些固定偏移读取器无需感知位图。
  469. //
  470. // 字段为 NULL 时返回 false。把 NULL 判定下沉到这里, 是因为"定位偏移"是所有读取路径
  471. // (7 个条件类 + 更新时摘旧堆块) 的唯一入口, 各调用方再各判一次必然漏:
  472. // - 条件匹配: NULL 不匹配任何比较 (SQL 三值逻辑)。若按全零字节比, `x = 0` 会命中
  473. // NULL 行、`x < 1` 也会命中 —— 结果看着"有数据"却全是错的。
  474. // - 更新路径: 旧值为 NULL 时引用槽是全零, 本就没有堆块要摘。
  475. func (table *Table) getVersionedOffset(rowData []byte, masterFieldIdx int) (uint16, bool) {
  476. if len(rowData) < 1 {
  477. return 0, false
  478. }
  479. ver := rowData[0]
  480. sv := table.getVersion(ver)
  481. if sv == nil {
  482. return 0, false
  483. }
  484. bitmap, ok := table.versionedNullBitmap(rowData, ver)
  485. if !ok {
  486. return 0, false
  487. }
  488. offset := uint16(1) // 跳过 schemaVer; 位图在行尾, 不影响字段起点
  489. for i, idx := range sv.FieldIndices {
  490. if int(idx) == masterFieldIdx {
  491. if types.NullBitSet(bitmap, i) {
  492. return 0, false // NULL: 不参与比较, 也没有堆块
  493. }
  494. return offset, true
  495. }
  496. offset += table.Field[idx].Width()
  497. }
  498. return 0, false
  499. }
  500. // versionedNullBitmap 取出行尾的 NULL 位图切片 (长度按该行自身的 schemaVer 算)。
  501. //
  502. // 行布局: [schemaVer 1B] [字段区...] [NULL 位图 ⌈n/8⌉ B]
  503. // 位图位置 = 该版本行总长 - ⌈n/8⌉。用【版本算出的总长】而不是 len(rowData) 定位:
  504. // 行存储的原地更新允许把新行写进旧行的槽位 (剩余字节清零), 万一读到带尾随零填充的
  505. // 行, 按 len(rowData) 取尾会取到零填充而不是真正的位图; 按版本总长取则始终精确。
  506. func (table *Table) versionedNullBitmap(rowData []byte, ver byte) ([]byte, bool) {
  507. sv := table.getVersion(ver)
  508. if sv == nil {
  509. return nil, false
  510. }
  511. bitmapLen := types.NullBitmapLen(len(sv.FieldIndices))
  512. rowSize := table.rowDataSizeForVersion(ver)
  513. if len(rowData) < rowSize {
  514. return nil, false
  515. }
  516. return rowData[rowSize-bitmapLen : rowSize], true
  517. }
  518. // getActiveFieldTypes 返回当前活跃版本的字段类型列表 (不含已删除)
  519. func (table *Table) getActiveFieldTypes() []types.FieldInfo {
  520. sv := table.getVersion(table.activeVersion)
  521. if sv == nil {
  522. return nil
  523. }
  524. fields := make([]types.FieldInfo, len(sv.FieldIndices))
  525. for i, idx := range sv.FieldIndices {
  526. fields[i] = table.Field[idx]
  527. }
  528. return fields
  529. }
  530. // calcRowDataSize 计算当前活跃版本的行数据大小 (含 schemaVer 1B)
  531. func (table *Table) calcRowDataSize() int {
  532. return table.rowDataSizeForVersion(table.activeVersion)
  533. }
  534. // rowDataSizeForVersion 计算指定 schema 版本的行数据大小
  535. // (含 schemaVer 1B 与该版本字段数对应的 NULL 位图; 位图在行尾)
  536. func (table *Table) rowDataSizeForVersion(ver byte) int {
  537. size := 1 // schemaVer
  538. sv := table.getVersion(ver)
  539. if sv != nil {
  540. size += types.NullBitmapLen(len(sv.FieldIndices)) // NULL 位图 (行尾)
  541. for _, idx := range sv.FieldIndices {
  542. size += int(table.Field[idx].Width())
  543. }
  544. }
  545. return size
  546. }
  547. // maxRowDataSize 返回所有已知 schema 版本中最大的行数据大小。
  548. //
  549. // 给"按行分配缓冲"的调用方使用 (如删除路径的行复用池): info.RowSize 只反映最近
  550. // 一次落盘时的 schema, AddField/DropField 之后会偏小, 沿用它会 slice 越界 panic。
  551. func (table *Table) maxRowDataSize() int {
  552. maxSize := table.rowDataSizeForVersion(table.activeVersion)
  553. for i := range table.versions {
  554. if n := table.rowDataSizeForVersion(table.versions[i].Version); n > maxSize {
  555. maxSize = n
  556. }
  557. }
  558. return maxSize
  559. }
  560. // syncRowSize 让 info.RowSize 反映所有 schema 版本中最宽的一行
  561. // (语义同 types.CalcRowSize: Σ 字段字节数, 不含 schemaVer、NULL 位图与 pkID 前缀)。
  562. // 仅在 schema 变更时调用。
  563. func (table *Table) syncRowSize() {
  564. // 前缀 = schemaVer(1B) + 该版本的 NULL 位图。位图长度按活跃版本算 —— RowSize 本就
  565. // 只是"字段宽度之和"的估计值 (删除/分页路径都明确不用它, 见 deleter.go 里的说明),
  566. // 不必为跨版本差一格去逐版本精确。
  567. prefix := 1
  568. if sv := table.getVersion(table.activeVersion); sv != nil {
  569. prefix += types.NullBitmapLen(len(sv.FieldIndices))
  570. }
  571. if size := table.maxRowDataSize() - prefix; size > 0 && size <= 0xFFFF {
  572. table.info.RowSize = uint16(size)
  573. }
  574. }
  575. // pickVersionForUpdate 决定更新后按哪一版 schema 落盘。
  576. //
  577. // 惰性演进语义:
  578. // - 本次要写的字段在旧行自身的版本里都存在 → 保持旧版本。行长不变, 可原地更新,
  579. // 不会因为"只改了一个老字段"而无谓地把行升级并搬迁。
  580. // - values 触及了旧版本没有的字段 (AddField 之后新增的) → 升级到当前版本;
  581. // 此时行长会变化, 由索引层负责"页内重建 / 换页"。
  582. func (table *Table) pickVersionForUpdate(oldRowData []byte, values []types.FieldValue) byte {
  583. if len(oldRowData) < 1 {
  584. return table.activeVersion
  585. }
  586. oldVer := oldRowData[0]
  587. if oldVer == table.activeVersion {
  588. return oldVer
  589. }
  590. sv := table.getVersion(oldVer)
  591. if sv == nil {
  592. // 旧版本元信息缺失, 只能按当前版本重写
  593. return table.activeVersion
  594. }
  595. for _, fv := range values {
  596. idx := table.findFieldIndex(string(fv.FieldName))
  597. if idx < 0 {
  598. return table.activeVersion
  599. }
  600. inOldVer := false
  601. for _, i := range sv.FieldIndices {
  602. if int(i) == idx {
  603. inOldVer = true
  604. break
  605. }
  606. }
  607. if !inOldVer {
  608. return table.activeVersion
  609. }
  610. }
  611. return oldVer
  612. }
  613. // maxCompareInlineSize 字段比较时使用的栈缓冲上界 == 最宽的行内定长列。
  614. //
  615. // 取自 types.MaxInlineColumnWidth 而不是写死 256: 字符串列可以用宽度码声明到 2048 字节,
  616. // 缓冲若还停在 256, 宽字符串列的每次比较都会走堆分配。
  617. const maxCompareInlineSize = types.MaxInlineColumnWidth
  618. // ======================== 行内字段比较 ========================
  619. //
  620. // 三个比较函数都经由 compareFieldBytes 实现, 也就是"把条件值编码成与行内同源的字节,
  621. // 再按字节比较"。这样做的理由:
  622. // - 行内字节与索引键字节本就同源 (都出自 types.EncodeValue), 于是行级过滤与索引
  623. // 范围扫描必然给出同一个结论, 不会出现"索引选出 5 行、过滤后剩 0 行"的自相矛盾;
  624. // - 新增类型不必再回来改这三个函数 (以前每个类型要在这里各写 3 遍, 漏改一处就是静默漏行)。
  625. //
  626. // 与旧的"解码成 Go 值再比"相比, 唯一的行为差异是 NaN: 按字节比时 NaN == NaN 成立,
  627. // 而 float 的 == 不成立。索引键本来就是按字节比的, 统一到字节序反而消除了
  628. // "全表扫和索引给出不同答案"的隐患。
  629. // compareFieldBytes 比较行内字段与给定值, 返回 (cmp, ok); ok=false 表示无法比较。
  630. //
  631. // ct 必须是【列的实际存储类型 + 列级参数】(调用方经 compareColTypeFor 取, 与索引路径
  632. // 编键同源), 否则编码出来的字节与行内不同源, 比较结果没有意义。
  633. // ok=false 的来源: 字段越界、动态列解引用失败、值无法按该类型编码。
  634. // 前两种按"不匹配"处理是正确的; 第三种是调用方 bug —— 索引路径会显式报错
  635. // (见 encodeIndexKeySafe), 行级过滤按不匹配处理, 至少不会污染数据。
  636. func (table *Table) compareFieldBytes(row []byte, offset uint16, ct colType, value interface{}) (int, bool) {
  637. if types.IsDynamic(ct.typ) {
  638. if int(offset)+types.DynamicRefSize > len(row) {
  639. return 0, false
  640. }
  641. pageID := binary.BigEndian.Uint32(row[offset : offset+4])
  642. offInPage := binary.BigEndian.Uint16(row[offset+4 : offset+6])
  643. stored, err := table.heapRead(pageID, offInPage)
  644. if err != nil {
  645. return 0, false
  646. }
  647. switch v := value.(type) {
  648. case []byte:
  649. return bytes.Compare(stored, v), true
  650. case string:
  651. return bytes.Compare(stored, []byte(v)), true
  652. }
  653. return 0, false
  654. }
  655. size := int(ct.width())
  656. if size == 0 || int(offset)+size > len(row) {
  657. return 0, false
  658. }
  659. stored := row[offset : int(offset)+size]
  660. // decimal 的 scale 存在行内, 条件值的 scale 可能与行内不同 (1.5 与 1.50 的系数
  661. // 分别是 15 和 150), 系数直接 memcmp 会得出错误结论 → 走数值比较。
  662. if ct.typ == types.FieldType_Decimal {
  663. return table.compareDecimalBytes(stored, value)
  664. }
  665. // 编码到栈缓冲再比较 (最宽列 2048 字节; 更宽的类型退化为堆分配, 不 panic)
  666. var stack [maxCompareInlineSize]byte
  667. var buf []byte
  668. if size <= len(stack) {
  669. buf = stack[:size]
  670. } else {
  671. buf = make([]byte, size)
  672. }
  673. // param 必须一起传: 字符串列的宽度码决定 EncodeValue 期望的 buf 长度,
  674. // 传 0 会让"按类型默认 256 编"与"按声明 2048 存"对不上 → 该列静默全不匹配。
  675. if err := types.EncodeValue(ct.typ, ct.param, value, buf); err != nil {
  676. return 0, false
  677. }
  678. return bytes.Compare(stored, buf), true
  679. }
  680. // compareDecimalBytes 比较行内 decimal 字节与给定值。
  681. //
  682. // 同 scale (同一列的常态) → 直接字节比较, 与索引键行为一致;
  683. // 不同 scale (条件值写成 1.5 而列是 DECIMAL(18,2)) → 退回数值比较, 保证语义正确。
  684. func (table *Table) compareDecimalBytes(stored []byte, value interface{}) (int, bool) {
  685. d, err := types.DecimalFromValue(value)
  686. if err != nil {
  687. return 0, false
  688. }
  689. if d.Scale() == types.DecimalScaleOfBytes(stored) {
  690. var buf [types.DecimalSize]byte
  691. if err := types.EncodeValue(types.FieldType_Decimal, byte(d.Scale()), d, buf[:]); err != nil {
  692. return 0, false
  693. }
  694. return bytes.Compare(stored, buf[:]), true
  695. }
  696. return types.DecimalFromBytes(stored).Cmp(d), true
  697. }
  698. // colType 一列"编码/比较所需的全部类型信息": 类型值 + 列级参数 (scale / 宽度码)。
  699. //
  700. // 打包成一个值传递, 而不是把 typ 与 param 拆成两个形参: 两者必须一起走 —— 只传类型
  701. // 会出现"按 256 字节编、按 2048 字节存"这类不一致, 而它表现为整列静默不匹配或整行
  702. // 错位, 都不是编译错误。打包后"忘了带参数"这件事在签名上就写不出来。
  703. type colType struct {
  704. typ types.FieldType
  705. param byte
  706. }
  707. // width 该列的行内宽度 (字节); 0 表示参数非法。
  708. func (c colType) width() uint16 { return types.FieldSize(c.typ, c.param) }
  709. // compareColTypeFor 取出行级过滤比较要用的列信息 —— 以【列的实际存储类型与参数】为准。
  710. //
  711. // 为什么不能沿用条件里声明的 Type:
  712. // - 索引路径编键用的是列定义 (见 query.go buildKeyLayout / planIndex:
  713. // q.table.Field[..].FieldType 与 fieldParam), 行级过滤若用声明类型, 两条路径就会
  714. // 分叉: 同一列带索引时"声明错类型"仍能命中 (索引键按列定义编), 不带索引时却按
  715. // 声明类型编出另一种字节 → 静默返回 0 行, 看起来像数据不存在。
  716. // - 声明类型因此降级为"值怎么从 interface{} 取出来"的提示: EncodeValue 按 Go
  717. // 值的 kind 分派, 本身并不依赖它。
  718. //
  719. // 列级参数 (字符串宽度码) 同理必须一起取: 宽度决定了"行内那几字节从哪切、切多长",
  720. // 与是否走索引无关。
  721. //
  722. // 删除标记已迁到 FieldInfo.Flag, FieldType 本身不再混入状态位, 因此这里直接返回
  723. // 即可 —— 旧实现在此清 0x80, 留着标记位会让 IsDynamic 查不到注册项: 动态列会被
  724. // 当成定长字段, 拿 6 字节堆引用去和值做字节比较。
  725. //
  726. // 拿不到字段时返回零值 (未注册类型 + 参数 0), compareFieldBytes 判 width()==0 后按
  727. // 不匹配处理 —— 调用方已先判过 fieldIdx < 0, 这里只是兜底。
  728. func (table *Table) compareColTypeFor(fieldIdx int) colType {
  729. if fieldIdx < 0 || fieldIdx >= len(table.Field) {
  730. return colType{}
  731. }
  732. return colType{table.Field[fieldIdx].FieldType, table.Field[fieldIdx].Param()}
  733. }
  734. // compareValue 比较字段值是否相等
  735. func (table *Table) compareValue(row []byte, offset uint16, ct colType, value interface{}) bool {
  736. c, ok := table.compareFieldBytes(row, offset, ct, value)
  737. return ok && c == 0
  738. }
  739. // compareGreater 比较字段值是否大于给定值
  740. func (table *Table) compareGreater(row []byte, offset uint16, ct colType, value interface{}) bool {
  741. c, ok := table.compareFieldBytes(row, offset, ct, value)
  742. return ok && c > 0
  743. }
  744. // compareLess 比较字段值是否小于给定值
  745. func (table *Table) compareLess(row []byte, offset uint16, ct colType, value interface{}) bool {
  746. c, ok := table.compareFieldBytes(row, offset, ct, value)
  747. return ok && c < 0
  748. }
  749. // compareGreaterOrEqual 比较字段值是否大于等于给定值
  750. func (table *Table) compareGreaterOrEqual(row []byte, offset uint16, ct colType, value interface{}) bool {
  751. return table.compareGreater(row, offset, ct, value) || table.compareValue(row, offset, ct, value)
  752. }
  753. // compareLessOrEqual 比较字段值是否小于等于给定值
  754. func (table *Table) compareLessOrEqual(row []byte, offset uint16, ct colType, value interface{}) bool {
  755. return table.compareLess(row, offset, ct, value) || table.compareValue(row, offset, ct, value)
  756. }
  757. func (table *Table) GetFieldCount() byte { return table.info.FieldCount }
  758. func (table *Table) GetActiveFieldCount() byte { return table.info.ActiveFieldCount }
  759. // GetActiveFieldNames 返回当前活跃版本中包含的所有字段名 (不含已删除)
  760. func (table *Table) GetActiveFieldNames() []string {
  761. sv := table.getVersion(table.activeVersion)
  762. if sv == nil {
  763. return nil
  764. }
  765. names := make([]string, 0, len(sv.FieldIndices))
  766. for _, idx := range sv.FieldIndices {
  767. if table.Field[idx].IsDropped() {
  768. continue // 跳过已删除
  769. }
  770. names = append(names, string(table.Field[idx].FieldName))
  771. }
  772. return names
  773. }
  774. // GetActiveFieldType 返回指定字段的 FieldType (基于 master registry)
  775. func (table *Table) GetActiveFieldType(name string) (types.FieldType, bool) {
  776. for _, f := range table.Field {
  777. if string(f.FieldName) == name && !f.IsDropped() {
  778. return f.FieldType, true
  779. }
  780. }
  781. return 0, false
  782. }
  783. // GetActiveFieldFlag 返回指定字段的列级标志 (含 Decimal 的 scale 与可空位)。
  784. //
  785. // 与 GetActiveFieldType 配对: 类型一致但 scale 不一致是"同列不同 scale"这种会被
  786. // 静默破坏保序前提的情况, 调用方 (ORM 重开表时) 必须能读到它来做一致性校验。
  787. func (table *Table) GetActiveFieldFlag(name string) (byte, bool) {
  788. for i := range table.Field {
  789. f := &table.Field[i]
  790. if string(f.FieldName) == name && !f.IsDropped() {
  791. return f.Flag, true
  792. }
  793. }
  794. return 0, false
  795. }
  796. func (table *Table) GetFieldAllocateSize() uint16 { return table.info.FieldAllocateSize }
  797. func (table *Table) GetFirstData() uint32 { return table.info.FirstData }
  798. // GetSchemaExtRef 返回本表首个 schema 续块的位置 (pageID, off); pageID == 0 表示无续块
  799. func (table *Table) GetSchemaExtRef() (uint32, uint16) {
  800. return table.info.ExtPage, table.info.ExtOff
  801. }
  802. func (table *Table) GetFields() []types.FieldInfo {
  803. return table.Field
  804. }
  805. func (table *Table) findFieldByName(name []byte) *types.FieldInfo {
  806. target := util.BytesToString(name)
  807. for i := range table.Field {
  808. if util.BytesToString(table.Field[i].FieldName) == target {
  809. return &table.Field[i]
  810. }
  811. }
  812. return nil
  813. }
  814. // serializeSchema 将当前 schema 序列化为字节流
  815. //
  816. // master field 每项: [type(1B)][flag(1B)][nameLen(1B)][name...]
  817. // flag 落盘是必须的: decimal 列的 scale 一旦丢失, 读取侧就只能信任行内的 scale,
  818. // 于是"同一列出现两种 scale"这种破坏保序的情况再也无法被发现。删除/可空两个状态位
  819. // 同样必须随 schema 落盘, 否则重开库后被 DropField 的列会悄悄"复活"。
  820. func (table *Table) serializeSchema() []byte {
  821. var data []byte
  822. data = append(data, table.activeVersion)
  823. data = append(data, byte(len(table.Field)))
  824. for _, f := range table.Field {
  825. data = append(data, byte(f.FieldType))
  826. data = append(data, f.Flag)
  827. data = append(data, byte(len(f.FieldName)))
  828. data = append(data, f.FieldName...)
  829. }
  830. data = append(data, byte(len(table.versions)))
  831. for _, sv := range table.versions {
  832. data = append(data, sv.Version)
  833. data = append(data, byte(len(sv.FieldIndices)))
  834. data = append(data, sv.FieldIndices...)
  835. }
  836. return data
  837. }
  838. // writeSchema 整条重写本表的 schema 定义
  839. //
  840. // 布局:
  841. // 1. 内联块 —— 表页内, 紧跟 TableInfo, 固定 FieldAllocateSize 字节, **无跳转槽**。
  842. // 建表时按 len(serializeSchema()) 精确分配 (上限 MaxInlineSchemaSize)。
  843. // 2. 续块 —— schema 超出内联块的部分, 写入「全库共享」的 schema 扩展页,
  844. // 每块格式 [SchemaFragHdr 8B][payload], 多块由 hdr.NextPage/NextOff 串成链。
  845. // 本表首个续块的位置记录在 TableInfo.ExtPage/ExtOff。
  846. //
  847. // 续块只追加、不单独释放: 每次都是整条重写, 旧续块立刻成为垃圾, 由
  848. // SchemaExtAllocator 在尾页放不下时做「满页压缩重排」统一回收。因此这里
  849. // 不再需要任何引用计数 —— 这也正是旧实现最容易出错的地方。
  850. func (table *Table) writeSchema() error {
  851. data := table.serializeSchema()
  852. alloc := int(table.info.FieldAllocateSize) // 内联块容量 (可能大于 schema 实际长度)
  853. inline := len(data)
  854. if inline > alloc {
  855. inline = alloc // 容错: schema 超出内联块, 余下部分走续块
  856. }
  857. rest := data[inline:]
  858. // 1. 先分配续块。可能触发压缩重排 —— 那会改写(可能同页的)其他表的 TableInfo,
  859. // 所以内联块的写入必须放到分配之后再做, 否则会拿旧页缓冲覆盖掉这些更新。
  860. var frags []schemaFrag
  861. if len(rest) > 0 {
  862. if table.schemaExt == nil {
  863. return ErrFieldSpaceNotEnough
  864. }
  865. var err error
  866. frags, err = table.schemaExt.AllocChain(len(rest))
  867. if err != nil {
  868. return err
  869. }
  870. }
  871. // 2. 写续块
  872. if len(frags) > 0 {
  873. if err := table.writeFrags(frags, rest); err != nil {
  874. return err
  875. }
  876. }
  877. // 3. 写内联块 (重新读页, 见上面第 1 步的说明)
  878. buf := make([]byte, PageSize)
  879. if err := table.handle.ReadPage(table.tablePageID, buf); err != nil {
  880. return err
  881. }
  882. start := int(table.tableStartOffset + uint32(unsafe.Sizeof(*table.info)))
  883. end := start + alloc
  884. if end > len(buf) {
  885. return ErrFieldSpaceNotEnough
  886. }
  887. // 清零整块(alloc 字节): 槽位可能大于 schema 实际长度(复用空洞/预分配),
  888. // 多出来的部分是填充, 必须清零, 防止旧表的残留字节被后续误读
  889. for i := start; i < end; i++ {
  890. buf[i] = 0
  891. }
  892. copy(buf[start:end], data[:inline])
  893. if err := table.handle.SavePage(table.tablePageID, buf); err != nil {
  894. return err
  895. }
  896. // 4. 记录本表首个续块的位置
  897. if len(frags) > 0 {
  898. table.info.ExtPage = frags[0].PageID
  899. table.info.ExtOff = uint16(frags[0].Off)
  900. } else {
  901. table.info.ExtPage = 0
  902. table.info.ExtOff = 0
  903. }
  904. return nil
  905. }
  906. // writeSchemaExt 把本表 schema 的续块部分写进指定的扩展页分配器, 并落盘 TableInfo
  907. // 专供 SchemaExtAllocator.Compact 压缩重排时调用。
  908. func (table *Table) writeSchemaExt(alloc *SchemaExtAllocator) error {
  909. data := table.serializeSchema()
  910. inline := int(table.info.FieldAllocateSize)
  911. if inline > len(data) {
  912. inline = len(data)
  913. }
  914. rest := data[inline:]
  915. if len(rest) == 0 {
  916. table.info.ExtPage = 0
  917. table.info.ExtOff = 0
  918. table.saveTableInfo()
  919. return nil
  920. }
  921. frags, err := alloc.AllocChain(len(rest))
  922. if err != nil {
  923. return err
  924. }
  925. if err := table.writeFrags(frags, rest); err != nil {
  926. return err
  927. }
  928. table.info.ExtPage = frags[0].PageID
  929. table.info.ExtOff = uint16(frags[0].Off)
  930. table.saveTableInfo()
  931. return nil
  932. }
  933. // writeFrags 把 rest 按 frags 描述的容量逐块写入扩展页, 并串好续块链
  934. func (table *Table) writeFrags(frags []schemaFrag, rest []byte) error {
  935. written := 0
  936. for i := range frags {
  937. f := frags[i]
  938. buf := make([]byte, PageSize)
  939. if err := table.handle.ReadPage(f.PageID, buf); err != nil {
  940. return err
  941. }
  942. h := SchemaFragHdr{DataLen: f.Len}
  943. if i+1 < len(frags) {
  944. h.NextPage = frags[i+1].PageID
  945. h.NextOff = uint16(frags[i+1].Off)
  946. }
  947. putSchemaFragHdr(buf, int(f.Off), h)
  948. copy(buf[int(f.Off)+schemaFragHdrSize:], rest[written:written+int(f.Len)])
  949. if err := table.handle.SavePage(f.PageID, buf); err != nil {
  950. return err
  951. }
  952. written += int(f.Len)
  953. }
  954. return nil
  955. }
  956. // checkProspectiveRowSize 预演"把 newField 加进当前活跃字段集合之后"的行宽。
  957. //
  958. // 加列只会让行变宽, 而超宽的行写不进 DataPage (见 serializeRowInternal 的写行闸门),
  959. // 所以必须在建列这一步就拒绝: 否则这张表从加列成功的那一刻起, 一条也写不进去 ——
  960. // 而且是插数据时才暴露 (而且加列本身是"成功"的, 没有任何回滚线索)。
  961. func (table *Table) checkProspectiveRowSize(newField types.FieldInfo) error {
  962. sv := table.getVersion(table.activeVersion)
  963. probe := make([]types.FieldInfo, 0, len(table.Field)+1)
  964. if sv != nil {
  965. for _, idx := range sv.FieldIndices {
  966. if int(idx) < len(table.Field) {
  967. probe = append(probe, table.Field[idx])
  968. }
  969. }
  970. }
  971. probe = append(probe, newField)
  972. if err := types.CheckRowSize(probe); err != nil {
  973. return fmt.Errorf("新增列 %q 后行宽超限: %w", newField.FieldName, err)
  974. }
  975. return nil
  976. }
  977. func (table *Table) AddField(newField types.FieldInfo) error {
  978. // 列级参数必须先自洽: 宽度码/scale 越界会让该列宽度算成 0, 而 0 宽度会少算字节,
  979. // 让其后每一列都从错位偏移解码 (见 types.FieldSize)。
  980. if err := validateFieldsForSchema([]types.FieldInfo{newField}); err != nil {
  981. return err
  982. }
  983. // 列数闸门与建表同一条 (见 checkFieldCount): master registry 只增不减
  984. // (DropField 是软删除, 列仍留在 registry 里), 所以按"加完之后"的列数判断。
  985. if err := checkFieldCount(len(table.Field) + 1); err != nil {
  986. return err
  987. }
  988. // 版本号闸门必须在【任何改动之前】: 两个分支都会在最后新增一版, 版本耗尽时
  989. // 若已经把 in-memory 状态改了一半再返回错误, 表就停在"改了一半"的不一致状态。
  990. nextVer, err := table.nextSchemaVersion()
  991. if err != nil {
  992. return err
  993. }
  994. existing := table.findFieldByName(newField.FieldName)
  995. if existing != nil {
  996. // 字段存在且活跃 → 报错
  997. if !existing.IsDropped() {
  998. return ErrFieldExists
  999. }
  1000. // 复活必须沿用原类型与列级参数。
  1001. //
  1002. // master registry 的类型与列级参数是【所有历史版本共用】的行偏移依据
  1003. // (见 getVersionedOffset / rowDataSizeForVersion)。旧实现是整字节覆盖
  1004. // (existing.FieldType = newField.FieldType), 看着像"恢复", 实际是"改掉":
  1005. // varchar(50) 的列复活成 varchar(500) 之后, 已经落盘的旧行仍按 224 字节排布,
  1006. // 却会被按 2048 字节解析 —— 该列之后的每一个字段都静默错位。
  1007. // 想改宽度只能换列名新建, 或者重建表 (DropField 本就只标记删除、保留原类型)。
  1008. if existing.FieldType != newField.FieldType || existing.Param() != newField.Param() {
  1009. return fmt.Errorf("%w: 列 %q 已被删除, 复活时不允许改变类型/宽度 "+
  1010. "(原 %s/参数 %d, 新 %s/参数 %d); 请改用新列名, 或重建该表",
  1011. ErrFieldExists, newField.FieldName,
  1012. types.TypeNameOf(existing.FieldType), existing.Param(),
  1013. types.TypeNameOf(newField.FieldType), newField.Param())
  1014. }
  1015. // 复活同样会让行变宽, 先做行宽预演
  1016. if err := table.checkProspectiveRowSize(newField); err != nil {
  1017. return err
  1018. }
  1019. // 只恢复"存活 + 可空/唯一"这些标志位, 低 5 位的列级参数保持原值
  1020. existing.Flag = (existing.Flag & types.FlagScaleMask) | (newField.Flag &^ types.FlagScaleMask)
  1021. table.info.ActiveFieldCount++
  1022. // 找到该字段在 master registry 中的索引
  1023. revivedIdx := -1
  1024. for i := range table.Field {
  1025. if &table.Field[i] == existing {
  1026. revivedIdx = i
  1027. break
  1028. }
  1029. }
  1030. // 构建新版本: 旧版本字段 + 复活的字段索引
  1031. sv := table.getVersion(table.activeVersion)
  1032. newIndices := make([]byte, 0, len(sv.FieldIndices)+1)
  1033. if sv != nil {
  1034. newIndices = append(newIndices, sv.FieldIndices...)
  1035. }
  1036. newIndices = append(newIndices, byte(revivedIdx))
  1037. table.activeVersion = nextVer
  1038. table.versions = append(table.versions, SchemaVersion{
  1039. Version: nextVer,
  1040. FieldIndices: newIndices,
  1041. })
  1042. if err := table.writeSchema(); err != nil {
  1043. return err
  1044. }
  1045. table.syncRowSize()
  1046. table.saveTableInfo()
  1047. return nil
  1048. }
  1049. // 全新字段: 同样先做行宽预演 (加列只会让行更宽)
  1050. if err := table.checkProspectiveRowSize(newField); err != nil {
  1051. return err
  1052. }
  1053. // 追加到 master registry
  1054. table.Field = append(table.Field, newField)
  1055. table.info.FieldCount++
  1056. table.info.ActiveFieldCount++
  1057. // 构建新版本: 旧版本字段 + 新字段索引
  1058. sv := table.getVersion(table.activeVersion)
  1059. newIndices := make([]byte, 0, len(sv.FieldIndices)+1)
  1060. if sv != nil {
  1061. newIndices = append(newIndices, sv.FieldIndices...)
  1062. }
  1063. newIndices = append(newIndices, byte(len(table.Field)-1))
  1064. table.activeVersion = nextVer
  1065. table.versions = append(table.versions, SchemaVersion{
  1066. Version: nextVer,
  1067. FieldIndices: newIndices,
  1068. })
  1069. if err := table.writeSchema(); err != nil {
  1070. return err
  1071. }
  1072. table.syncRowSize()
  1073. table.saveTableInfo()
  1074. return nil
  1075. }
  1076. func (table *Table) saveTableInfo() {
  1077. buf := make([]byte, PageSize)
  1078. if err := table.handle.ReadPage(table.tablePageID, buf); err != nil {
  1079. return
  1080. }
  1081. info := (*TableInfo)(unsafe.Pointer(&buf[table.tableStartOffset]))
  1082. info.ExtPage = table.info.ExtPage
  1083. info.ExtOff = table.info.ExtOff
  1084. info.FieldCount = table.info.FieldCount
  1085. info.ActiveFieldCount = table.info.ActiveFieldCount
  1086. info.TotalRows = table.info.TotalRows
  1087. // 行宽随 schema 演进变化, 必须落盘同步 (否则删除路径按旧 RowSize 分配缓冲会越界)
  1088. info.RowSize = table.info.RowSize
  1089. // 动态字段堆页链 (分配新堆页时才会变化)
  1090. info.FirstHeap = table.info.FirstHeap
  1091. info.LastHeap = table.info.LastHeap
  1092. info.HeapPageCount = table.info.HeapPageCount
  1093. table.handle.SavePage(table.tablePageID, buf)
  1094. }
  1095. // =============================================================================
  1096. // 删除字段(软删除 + 新版本)
  1097. // =============================================================================
  1098. func (table *Table) DropField(fieldName string) error {
  1099. // 在 master registry 中查找
  1100. masterIdx := -1
  1101. for i, f := range table.Field {
  1102. if f.IsDropped() {
  1103. continue // 已删除
  1104. }
  1105. if string(f.FieldName) == fieldName {
  1106. masterIdx = i
  1107. break
  1108. }
  1109. }
  1110. if masterIdx < 0 {
  1111. return ErrFieldNotFound
  1112. }
  1113. // 版本号闸门必须在改动之前 (理由同 AddField): 删列也要新增一版 schema。
  1114. nextVer, err := table.nextSchemaVersion()
  1115. if err != nil {
  1116. return err
  1117. }
  1118. // 标记删除 (FieldType 本身不动: 它是"这一列是什么", 与存活状态无关)
  1119. table.Field[masterIdx].Flag |= types.FlagDeleted
  1120. table.info.ActiveFieldCount--
  1121. // 构建新版本: 跳过被删字段
  1122. sv := table.getVersion(table.activeVersion)
  1123. newIndices := make([]byte, 0, len(sv.FieldIndices))
  1124. if sv != nil {
  1125. for _, idx := range sv.FieldIndices {
  1126. if int(idx) != masterIdx {
  1127. newIndices = append(newIndices, idx)
  1128. }
  1129. }
  1130. }
  1131. table.activeVersion = nextVer
  1132. table.versions = append(table.versions, SchemaVersion{
  1133. Version: nextVer,
  1134. FieldIndices: newIndices,
  1135. })
  1136. if err := table.writeSchema(); err != nil {
  1137. return err
  1138. }
  1139. table.syncRowSize()
  1140. table.saveTableInfo()
  1141. return nil
  1142. }
  1143. // ======================== 动态字段堆页存储 ========================
  1144. //
  1145. // 动态字段 (FieldType_Dynamicbyte: 声明宽度超过 MaxInlineColumnWidth(2048) 字节的
  1146. // 字符串 / []byte) 的行内只保存 6 字节引用: [PageID uint32 BE(4B)][Offset uint16 BE(2B)]
  1147. // - PageID == 0 → 空值 (NULL / 空串 / 空二进制)
  1148. // - 否则指向堆页 PageID 的物理偏移 Offset 处的数据块
  1149. // 堆页数据块格式: [Len uint32 BE][Payload...]
  1150. // 单块上限 = PageSize - PageHdrSize - dynamicBlockHdr (约 16KB)
  1151. // 堆页链: TableInfo.FirstHeap → ... → TableInfo.LastHeap
  1152. // 注意: 该链与 schema 续块所在的「共享扩展页」相互独立。
  1153. //
  1154. // 由于行内引用固定 6 字节, 动态字段变长/变短都不会改变行长,
  1155. // 因此更新走 DataPage 原地覆盖即可, 不会触发 errRowTooLarge。
  1156. const dynamicBlockHdr = 4 // 堆数据块的长度前缀大小
  1157. // MaxBlobSize 单个 FieldType_Blob 值的大小上限 (字节)。
  1158. //
  1159. // 它不是拍出来的数, 而是从存储约束直接推出: 一个堆块必须整体落进一个堆页, 可用
  1160. // 载荷 = 页大小 - 页头 - 块长度前缀 (与 heapAllocBlock 的 maxPayload 同源)。
  1161. //
  1162. // 为什么单独列出来: Blob 是"存文件/大对象"的类型, 使用者必须能在写入前就知道
  1163. // 这条线, 而不是写进去才撞上 heapAllocBlock 里那句与"值太大"看不出因果的
  1164. // ErrHeapPageTooLarge。写入侧的检查见 writeFieldValueAt。
  1165. //
  1166. // 它同时是【声明侧】的容量上限 (声明超限的字符串列也落动态列, 容量同样是这个数):
  1167. // types.MaxStringSize 就是它, 由下面那行编译期断言钉住。
  1168. const MaxBlobSize = PageSize - page.PageHdrSize - dynamicBlockHdr
  1169. // 编译期断言: types 里那组【页面布局镜像】推出的两个上限, 必须与这里的物理布局一致
  1170. // (行宽那条因为要整除, 只钉到 ±1 字节 —— "它恰好是一页能放下的最大值"这个精确性质由
  1171. // core 的 TestMaxRowDataSizeFitsTwoRows 用真实的 PageSize / DataPageHdrSize 复核)。
  1172. // 两处一旦漂移, 后果不是报错而是"承诺了做不到的事":
  1173. // - MaxStringSize 对不上 MaxBlobSize → 允许声明一个写不进堆页的容量, 建表时通过、
  1174. // 某天写入大值时才撞 ErrHeapPageTooLarge;
  1175. // - MaxRowDataSize 对不上"一页两行" → 放进来一张两行装不进一页的表, 页内顺序扫描、
  1176. // 按 pkID 的有序插入、更新时的页内重建全部退化, 正是那条政策要避免的情形。
  1177. //
  1178. // 用"下标必须为 0"表达相等: 常量下标越界直接编译失败, 且表达式不占任何空间。改页大小 /
  1179. // 页头 / 块长度前缀时这里会先报错, 提醒同步 types 里那组镜像。
  1180. var (
  1181. _ = [1]struct{}{}[int(MaxBlobSize)-types.MaxStringSize]
  1182. _ = [1]struct{}{}[int(types.MaxRowDataSize)-int((PageSize-page.DataPageHdrSize-1)/2)]
  1183. )
  1184. // maxHeapRefCount 单个堆页的引用数防御上限 (RefCount 为 uint16, 容器上限 65535)。
  1185. //
  1186. // 注意: 这是纯防御性常量, 实际永远不会触发 —— 堆页块数的物理上限只有 3272
  1187. // (数据区 16364 字节 ÷ 最小块 5 字节), 远在 65535 之前空间就先耗尽, 所以
  1188. // heapAllocBlock 里的 `RefCount < maxHeapRefCount` 恒为真, 从不决定任何事。
  1189. // 真正保证计数不溢出的是同一行的空间检查。
  1190. //
  1191. // 它唯一的价值: 若将来放开"1 引用 = 1 块, 且块不共享"的前提 (块级去重、多行共用
  1192. // 一个堆块), 上限会变成"全库行数", 这道闸才真正开始工作。
  1193. const maxHeapRefCount = 1<<16 - 1
  1194. // heapAllocBlock 在堆页链中分配 need 字节数据空间, 返回承载页与页内物理偏移
  1195. func (table *Table) heapAllocBlock(need uint32) (*HeapPage, uint16, error) {
  1196. maxPayload := uint32(MaxBlobSize)
  1197. if need > maxPayload {
  1198. return nil, 0, ErrHeapPageTooLarge
  1199. }
  1200. total := need + dynamicBlockHdr
  1201. // 1. 尾页是当前唯一可写页: 堆链只往尾部追加, 新块一律落在链尾。
  1202. //
  1203. // 为什么不再从 FirstHeap 逐个扫"第一个有空间的页": 均匀定长插入下, 前面每一
  1204. // 页都会因剩余空间不足而被扫到且都放不下, 单次插入退化成 O(堆页数), 整体
  1205. // O(n²) —— 实测插 10 万行要十几分钟且内存顶不住。尾插把单次插入降到 O(1)。
  1206. //
  1207. // 代价: 旧尾页里不足一个块的零头不再被后续小块回填, 每页最多浪费「放不下的
  1208. // 那个块大小」以内。若尾页恰好是 heapUnref 清空复用的空页, 则它必然放得下
  1209. // 任何块(空页可用空间 >= 单块上限), 不会因此多开一张页。
  1210. var tail *HeapPage
  1211. appended := false // 本次是否新开了一张堆页 —— 决定 HeapPageCount 是否 +1
  1212. if table.info.LastHeap != 0 {
  1213. hp, err := LoadHeapPage(table.handle, table.info.LastHeap)
  1214. if err != nil {
  1215. return nil, 0, err
  1216. }
  1217. // 左半边恒为真 (见 maxHeapRefCount 的注释): 空间总是先于计数耗尽。
  1218. if hp.Info.Hdr.RefCount < maxHeapRefCount && PageSize-hp.Info.Hdr.UseSize >= total {
  1219. tail = hp
  1220. } else {
  1221. // 尾页放不下 → 在它后面接一张新页作为新尾
  1222. nhp := NewHeapPage(table.handle, page.HeapIndex)
  1223. hp.Info.Hdr.NextPage = nhp.Info.Hdr.PageID
  1224. nhp.Info.Hdr.PrePage = hp.Info.Hdr.PageID
  1225. if err := hp.Save(); err != nil {
  1226. return nil, 0, err
  1227. }
  1228. tail = nhp
  1229. appended = true
  1230. }
  1231. } else {
  1232. // 链为空 (旧库或已被清空): 新建首页
  1233. tail = NewHeapPage(table.handle, page.HeapIndex)
  1234. table.info.FirstHeap = tail.Info.Hdr.PageID
  1235. appended = true
  1236. }
  1237. table.info.LastHeap = tail.Info.Hdr.PageID
  1238. if appended {
  1239. // 堆链长度自增 —— DropTable 就是靠它不遍历也拿到回收页数
  1240. table.info.HeapPageCount++
  1241. }
  1242. _, off, err := tail.Alloc(total)
  1243. if err != nil {
  1244. return nil, 0, err
  1245. }
  1246. if off > 0xFFFF {
  1247. return nil, 0, ErrHeapPageTooLarge
  1248. }
  1249. table.saveTableInfo()
  1250. return tail, uint16(off), nil
  1251. }
  1252. // heapWrite 分配堆空间并写入数据块, 返回行内引用 (pageID, offset)
  1253. func (table *Table) heapWrite(data []byte) (uint32, uint16, error) {
  1254. if len(data) == 0 {
  1255. return 0, 0, nil // 空值: PageID = 0
  1256. }
  1257. hp, off, err := table.heapAllocBlock(uint32(len(data)))
  1258. if err != nil {
  1259. return 0, 0, err
  1260. }
  1261. di := int(off) - int(page.PageHdrSize)
  1262. if di < 0 || di+dynamicBlockHdr+len(data) > len(hp.Info.Data) {
  1263. return 0, 0, ErrPageRead
  1264. }
  1265. binary.BigEndian.PutUint32(hp.Info.Data[di:], uint32(len(data)))
  1266. copy(hp.Info.Data[di+dynamicBlockHdr:], data)
  1267. if err := hp.Save(); err != nil {
  1268. return 0, 0, err
  1269. }
  1270. return hp.Info.Hdr.PageID, off, nil
  1271. }
  1272. // heapRead 读取行内引用指向的堆数据; PageID == 0 表示空值
  1273. func (table *Table) heapRead(pageID uint32, off uint16) ([]byte, error) {
  1274. if pageID == 0 {
  1275. return nil, nil
  1276. }
  1277. hp, err := LoadHeapPage(table.handle, pageID)
  1278. if err != nil {
  1279. return nil, err
  1280. }
  1281. di := int(off) - int(page.PageHdrSize)
  1282. if di < 0 || di+dynamicBlockHdr > len(hp.Info.Data) {
  1283. return nil, ErrPageRead
  1284. }
  1285. n := int(binary.BigEndian.Uint32(hp.Info.Data[di:]))
  1286. if n < 0 || di+dynamicBlockHdr+n > len(hp.Info.Data) {
  1287. return nil, ErrPageRead
  1288. }
  1289. out := make([]byte, n)
  1290. copy(out, hp.Info.Data[di+dynamicBlockHdr:di+dynamicBlockHdr+n])
  1291. return out, nil
  1292. }
  1293. // heapRewrite 尝试原地覆写已有堆块 (新数据不超过旧块容量时), 成功返回 true
  1294. // 用于更新场景: 复用旧块可避免每次变长更新都产生堆垃圾
  1295. func (table *Table) heapRewrite(pageID uint32, off uint16, data []byte) bool {
  1296. if pageID == 0 {
  1297. return false
  1298. }
  1299. hp, err := LoadHeapPage(table.handle, pageID)
  1300. if err != nil {
  1301. return false
  1302. }
  1303. di := int(off) - int(page.PageHdrSize)
  1304. if di < 0 || di+dynamicBlockHdr > len(hp.Info.Data) {
  1305. return false
  1306. }
  1307. oldLen := int(binary.BigEndian.Uint32(hp.Info.Data[di:]))
  1308. if oldLen < len(data) || di+dynamicBlockHdr+oldLen > len(hp.Info.Data) {
  1309. return false
  1310. }
  1311. binary.BigEndian.PutUint32(hp.Info.Data[di:], uint32(len(data)))
  1312. copy(hp.Info.Data[di+dynamicBlockHdr:], data)
  1313. return hp.Save() == nil
  1314. }
  1315. // heapUnref 释放一个堆页引用。计数归零后按"该页还有没有后继"分流:
  1316. //
  1317. // - 有后继 → 摘链, 整页交还空闲链表
  1318. // - 是链尾 → 作为堆尾保留, 但把页内空间清零复用
  1319. //
  1320. // 链尾为什么不能只是"原样保留": DecRef 是引用计数下降的唯一入口, 判定只在这一刻做。
  1321. // 若链尾归零后原样保留, 它的 UseSize 仍是满的; 之后新块放不下时新页会接在它后面,
  1322. // 它就变成"计数已归零的中间页" —— 而再没有任何人会来 DecRef 它, 于是 16KB
  1323. // 永久泄漏且永不复用。所以链尾归零时就把 UseSize 重置回页头之后
  1324. // (计数归零 ⟺ 页内没有活块, 这些空间全是垃圾), 让它退化成一张可用的空页。
  1325. func (table *Table) heapUnref(pageID uint32) error {
  1326. if pageID == 0 {
  1327. return nil
  1328. }
  1329. hp, err := LoadHeapPage(table.handle, pageID)
  1330. if err != nil {
  1331. return err
  1332. }
  1333. if !hp.Info.Hdr.DecRef() {
  1334. // 仍有活引用: 只把下降后的计数落盘
  1335. return hp.Save()
  1336. }
  1337. if hp.Info.Hdr.NextPage == 0 {
  1338. // 链尾: 保留作为堆尾, 但清空可用空间以便复用
  1339. hp.Info.Hdr.UseSize = page.PageHdrSize
  1340. return hp.Save()
  1341. }
  1342. return table.heapDropPage(hp)
  1343. }
  1344. // heapDropPage 把已无引用的堆页从双向链上摘除, 并登记回收
  1345. // 前置条件: 该页引用计数已归零
  1346. func (table *Table) heapDropPage(hp *HeapPage) error {
  1347. next := hp.Info.Hdr.NextPage
  1348. prev := hp.Info.Hdr.PrePage
  1349. // 上级指向下级
  1350. if prev != 0 {
  1351. p, err := LoadHeapPage(table.handle, prev)
  1352. if err != nil {
  1353. return err
  1354. }
  1355. p.Info.Hdr.NextPage = next
  1356. if err := p.Save(); err != nil {
  1357. return err
  1358. }
  1359. } else {
  1360. table.info.FirstHeap = next
  1361. }
  1362. // 下级指向上级
  1363. if next != 0 {
  1364. n, err := LoadHeapPage(table.handle, next)
  1365. if err != nil {
  1366. return err
  1367. }
  1368. n.Info.Hdr.PrePage = prev
  1369. if err := n.Save(); err != nil {
  1370. return err
  1371. }
  1372. } else {
  1373. table.info.LastHeap = prev
  1374. }
  1375. // 交还空闲链表 (页内容会被链表指针覆盖)。
  1376. // 走延迟回收: 事务中途直写盘会让"页已清零"先于 WAL 落盘, 回滚/崩溃后
  1377. // 堆链还指向这张空页 → 数据丢失。
  1378. table.deferFreePage(hp.Info.Hdr.PageID)
  1379. // 堆链长度自减 (与 heapAllocBlock 的 +1 配对, 保证 DropTable 时链长可信)
  1380. if table.info.HeapPageCount > 0 {
  1381. table.info.HeapPageCount--
  1382. }
  1383. table.saveTableInfo()
  1384. return nil
  1385. }
  1386. // deferFreePage 登记一个可回收页。有延迟回收回调时走"等提交后再还链",
  1387. // 否则退回立即回收 (例如 demo 里手搓但未提供该回调的 HandleDb)。
  1388. func (table *Table) deferFreePage(pageID uint32) {
  1389. if table.handle.DeferFreePage != nil {
  1390. table.handle.DeferFreePage(pageID)
  1391. return
  1392. }
  1393. table.handle.FreePage(pageID)
  1394. }
  1395. // releaseOldDynamic 释放旧行在 masterIdx 处持有的堆块 (若有)。
  1396. //
  1397. // 专供"更新成 NULL"这条路径: 新行不再调用 writeDynamicField, 旧块不会有人摘链,
  1398. // 不在这里释放就是每更新一次漏一个堆块。旧值本就是 NULL 时, getVersionedOffset 会因
  1399. // 位图置位直接返回 false, 不会误摘。
  1400. func (table *Table) releaseOldDynamic(oldRowData []byte, masterIdx int) {
  1401. if len(oldRowData) == 0 {
  1402. return
  1403. }
  1404. oldOff, ok := table.getVersionedOffset(oldRowData, masterIdx)
  1405. if !ok || int(oldOff)+types.DynamicRefSize > len(oldRowData) {
  1406. return
  1407. }
  1408. if pid := binary.BigEndian.Uint32(oldRowData[oldOff : oldOff+4]); pid != 0 {
  1409. _ = table.heapUnref(pid)
  1410. }
  1411. }
  1412. // heapReleaseRow 释放一行占用的全部堆页引用 (删除行时调用)
  1413. // 按行自身的 schema 版本定位各动态字段的 6 字节引用
  1414. func (table *Table) heapReleaseRow(rowData []byte) {
  1415. if len(rowData) < 1 {
  1416. return
  1417. }
  1418. sv := table.getVersion(rowData[0])
  1419. if sv == nil {
  1420. return
  1421. }
  1422. bitmap, ok := table.versionedNullBitmap(rowData, rowData[0])
  1423. if !ok {
  1424. return
  1425. }
  1426. offset := uint16(1) // 位图在行尾, 字段区从偏移 1 开始
  1427. for i, idx := range sv.FieldIndices {
  1428. f := table.Field[idx]
  1429. // NULL 字段的引用槽是全零, 下面 pageID != 0 本就能跳过; 显式查位图是为了不把
  1430. // "零引用"误当成可解引用的堆块 (将来若改了零值约定, 这里仍然安全)。
  1431. if !f.IsDropped() && types.IsDynamic(f.FieldType) && !types.NullBitSet(bitmap, i) {
  1432. if int(offset)+types.DynamicRefSize <= len(rowData) {
  1433. pageID := binary.BigEndian.Uint32(rowData[offset : offset+4])
  1434. if pageID != 0 {
  1435. _ = table.heapUnref(pageID)
  1436. }
  1437. }
  1438. }
  1439. offset += f.Width()
  1440. }
  1441. }
  1442. // writeDynamicField 将变长数据落堆, 并在行内 offset 处写入 6 字节引用
  1443. // oldRowData 为旧行 (更新场景), 用于复用已存在的堆块; 插入时传 nil
  1444. func (table *Table) writeDynamicField(rowData []byte, offset uint16, masterIdx int, data []byte, oldRowData []byte) error {
  1445. // 旧引用 (更新场景): 既用于原地复用, 也在换块后释放
  1446. var (
  1447. oldPID uint32
  1448. oldOffInPage uint16
  1449. hasOldRef bool
  1450. )
  1451. if oldRowData != nil {
  1452. if oldOff, ok := table.getVersionedOffset(oldRowData, masterIdx); ok &&
  1453. int(oldOff)+types.DynamicRefSize <= len(oldRowData) {
  1454. oldPID = binary.BigEndian.Uint32(oldRowData[oldOff : oldOff+4])
  1455. oldOffInPage = binary.BigEndian.Uint16(oldRowData[oldOff+4 : oldOff+6])
  1456. hasOldRef = oldPID != 0
  1457. }
  1458. }
  1459. if len(data) == 0 {
  1460. // 新值为空: 行内引用保持全 0, 旧块引用需要释放
  1461. if hasOldRef {
  1462. _ = table.heapUnref(oldPID)
  1463. }
  1464. return nil
  1465. }
  1466. // 1. 尽量复用旧堆块 (原地覆写), 此时引用不变
  1467. if hasOldRef && table.heapRewrite(oldPID, oldOffInPage, data) {
  1468. binary.BigEndian.PutUint32(rowData[offset:], oldPID)
  1469. binary.BigEndian.PutUint16(rowData[offset+4:], oldOffInPage)
  1470. return nil
  1471. }
  1472. // 2. 分配新块 (新引用 +1)
  1473. pageID, off, err := table.heapWrite(data)
  1474. if err != nil {
  1475. return err
  1476. }
  1477. binary.BigEndian.PutUint32(rowData[offset:], pageID)
  1478. binary.BigEndian.PutUint16(rowData[offset+4:], off)
  1479. // 3. 旧块不再被本行引用 → 释放
  1480. // 放在分配之后, 避免刚摘链回收的页立刻又被分配造成抖动
  1481. if hasOldRef && !(oldPID == pageID && oldOffInPage == off) {
  1482. _ = table.heapUnref(oldPID)
  1483. }
  1484. return nil
  1485. }
  1486. // ======================== 行数据序列化/反序列化 ========================
  1487. // lookupFieldValue 在 FieldValue 切片中按字段名取值
  1488. func lookupFieldValue(values []types.FieldValue, name []byte) (interface{}, bool) {
  1489. for _, fv := range values {
  1490. // bytes.Equal 直接比较字节, 避免 string(x)==string(y) 触发两次 string 分配
  1491. if bytes.Equal(fv.FieldName, name) {
  1492. return fv.Value, true
  1493. }
  1494. }
  1495. return nil, false
  1496. }
  1497. // writeFieldValueAt 写入单个字段; 动态字段写入行内 6B 堆引用
  1498. //
  1499. // 形参是 FieldInfo 而不是 FieldType: 宽度与列级参数都必须取自【同一个列定义】——
  1500. // 拆成两个参数传 (类型来自这里、宽度码来自那里) 就会出现"按 256 字节编、按 2048 字节存"
  1501. // 这类不一致, 而它表现为整行错位而不是报错。
  1502. //
  1503. // checkOnly 为真时动态列直接跳过 (行内引用保持全零), 见 serializeRowForCheck: 试算行
  1504. // 只用于算索引键, 绝不能有分配/覆写/释放堆块的副作用 —— 它的结果可能被整丢弃。
  1505. func (table *Table) writeFieldValueAt(rowData []byte, offset uint16, masterIdx int,
  1506. f types.FieldInfo, value interface{}, oldRowData []byte, checkOnly bool) error {
  1507. ft := f.FieldType
  1508. if types.IsDynamic(ft) {
  1509. if checkOnly {
  1510. return nil
  1511. }
  1512. // 动态列的值只认 []byte。旧写法是 `data, _ := value.([]byte)`: 一旦传进来
  1513. // 的是别的类型 (string / 数值), 断言失败 → data 为 nil → 整个值被静默写成空。
  1514. // 这与"不静默丢内容"直接冲突, 所以这里改为显式报错 (nil 仍表示空值)。
  1515. var data []byte
  1516. if value != nil {
  1517. b, ok := value.([]byte)
  1518. if !ok {
  1519. return fmt.Errorf("字段 %q (%s): 动态列只接受 []byte, 收到 %T",
  1520. table.Field[masterIdx].FieldName, types.TypeNameOf(ft), value)
  1521. }
  1522. data = b
  1523. }
  1524. // 动态列的单块尺寸闸门: 超限在这里就报出列名与上限, 而不是等 heapAllocBlock
  1525. // 抛出与"值太大"因果关系不明的 ErrHeapPageTooLarge。
  1526. //
  1527. // 必须覆盖【所有】动态列, 不能只拦 Blob: Dynamicbyte 与 Blob 的盘上布局相同
  1528. // (行内 6B 引用, 内容整块落进一个堆页), 单值上限也一样。只拦 Blob 的话,
  1529. // 往 Dynamicbyte 写一个超限值只会得到不带列名、不带字节数的
  1530. // "申请空间超过单页最大容量", 用户看不出是哪一列、超了多少。
  1531. if uint32(len(data)) > MaxBlobSize {
  1532. return fmt.Errorf("%w: 字段 %q (%s) 的值 %d 字节超过单块上限 %d 字节, "+
  1533. "更大的内容需要分块存储 (见 types.FieldType_Blob)",
  1534. types.ErrValueRange, table.Field[masterIdx].FieldName,
  1535. types.TypeNameOf(ft), len(data), MaxBlobSize)
  1536. }
  1537. return table.writeDynamicField(rowData, offset, masterIdx, data, oldRowData)
  1538. }
  1539. size := f.Width()
  1540. if size == 0 {
  1541. return fmt.Errorf("%w: 字段 %q (%s) 的列级参数 %d 非法, 宽度不可计算",
  1542. types.ErrValueRange, f.FieldName, types.TypeNameOf(ft), f.Param())
  1543. }
  1544. if int(offset)+int(size) > len(rowData) {
  1545. return fmt.Errorf("%w: 字段 %d 写入越界 (offset=%d, 列宽=%d, 行长=%d)",
  1546. types.ErrEncodeBufSize, masterIdx, offset, size, len(rowData))
  1547. }
  1548. // 编码统一走 types.EncodeValue —— 与索引键字节同源 (见 types/codec.go)。
  1549. // param 取列级类型参数: decimal 必须在这里校验"值的 scale == 列声明的 scale",
  1550. // 否则同一列出现两种 scale 会让"字节序 == 数值序"失效, 索引范围扫描静默漏行;
  1551. // 字符串列则由它决定列宽闸门 (超宽在 EncodeValue 里直接报错, 不再截断)。
  1552. if err := types.EncodeValue(ft, f.Param(), value, rowData[offset:int(offset)+int(size)]); err != nil {
  1553. return fmt.Errorf("字段 %q 写入失败: %w", table.Field[masterIdx].FieldName, err)
  1554. }
  1555. return nil
  1556. }
  1557. // fieldParam 取列级类型参数 (Flag 低 5 位): decimal 是 scale, 字符串家族是行内宽度码,
  1558. // 其余类型为 0 (由 types.ValidateColumnParam 保证)。
  1559. //
  1560. // 以前这里叫 fieldScale 且只服务 decimal; 字符串列也用上同一格之后, "取参数"与
  1561. // "算宽度"必须成对 —— 只传 scale 会漏掉宽度码, 编出来的字节宽度与行内不一致。
  1562. func (table *Table) fieldParam(masterIdx int) byte {
  1563. if masterIdx < 0 || masterIdx >= len(table.Field) {
  1564. return 0
  1565. }
  1566. return table.Field[masterIdx].Param()
  1567. }
  1568. // serializeRow 将 FieldValue 切片序列化为行数据字节 (不含 pkID 前缀)
  1569. // 行格式: [schemaVer(1B)] [field0_data] [field1_data] ... [NULL 位图]
  1570. // 动态字段的行内数据为 6 字节堆引用; oldRowData 用于更新时复用堆块。
  1571. //
  1572. // 两点关键语义 (否则 schema 演进后更新旧行会静默丢数据):
  1573. // - 版本选择走 pickVersionForUpdate: 未触及新字段时保持旧版本, 行长不变 → 可原地更新
  1574. // - values 中未提供的字段用旧行原值补齐, 而不是写零
  1575. // (否则 "只改一列" 会把该行其它列全部清空)
  1576. func (table *Table) serializeRow(values []types.FieldValue, oldRowData []byte) ([]byte, error) {
  1577. return table.serializeRowInternal(values, oldRowData, false)
  1578. }
  1579. // serializeRowForCheck 试算一遍行字节: 除动态列外与 serializeRow 逐字节一致。
  1580. //
  1581. // 动态列在这里【不碰堆】—— 不分配、不复用、不释放, 行内 6B 引用留全零。存在的理由
  1582. // 是更新前的唯一性预检必须早于 serializeRow: writeDynamicField 会就地覆写旧行的堆块
  1583. // (heapRewrite), 换块时还会 heapUnref 旧块。等 UpdateByKey 报出键冲突再返回错误,
  1584. // 旧行的动态列已被改成新值 (极端情况下堆块已释放而旧行仍指着它), 而固定列与索引条目
  1585. // 还是旧的 —— 行被改了一半, 且不留任何痕迹。多一次序列化换"要么全改要么全不动"。
  1586. //
  1587. // 索引分量一定不是动态列 (CreateIndex 已拒绝), NULL 位图也只取决于"值是否为 nil 且
  1588. // 列可空", 与堆无关 —— 所以试算行算出的索引键与真身完全一致。
  1589. func (table *Table) serializeRowForCheck(values []types.FieldValue, oldRowData []byte) ([]byte, error) {
  1590. return table.serializeRowInternal(values, oldRowData, true)
  1591. }
  1592. func (table *Table) serializeRowInternal(values []types.FieldValue, oldRowData []byte, checkOnly bool) ([]byte, error) {
  1593. ver := table.pickVersionForUpdate(oldRowData, values)
  1594. // 写行闸门 (第三处行宽校验)。
  1595. //
  1596. // 前两处在建列侧 (TablePage.Create / Table.AddField), 管的是"新表不许超宽";
  1597. // 这一处管的是"已经存在的老表": 表是在限制生效前建的 (或 schema 被外部改坏) 时,
  1598. // 行字节数会超过一个 DataPage 能装下的量。放任它走下去的后果不是"写失败"这么轻——
  1599. // 行长度会在 DataPage 里溢出到相邻区域, 把整页的行解析全部错位 (页内行是顺序排布、
  1600. // 靠每行 4B 长度前缀自描述的, 一行错位等于整页报废)。
  1601. //
  1602. // 放在这里而不是"逐字段校验"是因为行总长只有这里算得出来 (行宽由 schema 版本决定)。
  1603. // 代价是每行一次整数比较。
  1604. rowSize := table.rowDataSizeForVersion(ver)
  1605. if rowSize > MaxPhysicalRowData {
  1606. return nil, fmt.Errorf("%w: 表 %q 的 schema 版本 %d 行宽 %d 字节超过单页可容纳上限 %d 字节, 拒绝写入; "+
  1607. "请重建该表并把大列改为动态列 (FieldType_Dynamicbyte / Blob)",
  1608. types.ErrValueRange, util.BytesToString(table.info.Name[:]), ver, rowSize, MaxPhysicalRowData)
  1609. }
  1610. rowData := make([]byte, rowSize)
  1611. rowData[0] = ver
  1612. sv := table.getVersion(ver)
  1613. if sv == nil {
  1614. return rowData, nil
  1615. }
  1616. // 基底: 旧行已存在的字段值 (版本感知解析, 旧行不会凭空多出新字段)
  1617. var base []types.FieldValue
  1618. if len(oldRowData) > 0 {
  1619. base = table.deserializeRow(oldRowData)
  1620. }
  1621. // NULL 位图放在行尾 (见 getVersionedOffset 的说明): 字段区起点恒为偏移 1,
  1622. // 时序索引的固定偏移读取器因此无需感知位图。rowData 由 make 分配, 天然全零 →
  1623. // 默认"全非 NULL", 因此只有真正写 NULL 的字段需要置位。
  1624. bitmapLen := types.NullBitmapLen(len(sv.FieldIndices))
  1625. bitmap := rowData[len(rowData)-bitmapLen:]
  1626. offset := uint16(1)
  1627. for i, masterIdx := range sv.FieldIndices {
  1628. f := table.Field[masterIdx]
  1629. name := f.FieldName
  1630. // 取值优先级: 本次传入 > 旧行原值 (未提供的字段沿用旧值, 否则"只改一列"会清空其它列)
  1631. value, provided := lookupFieldValue(values, name)
  1632. if !provided {
  1633. value, provided = lookupFieldValue(base, name)
  1634. }
  1635. if provided && value == nil {
  1636. if !f.IsNullable() {
  1637. // 非空列写 NULL: 拒绝并点名列。
  1638. //
  1639. // 这里不是"收紧", 而是把已有的不一致统一掉: 定长列写 nil 本来就走不通
  1640. // (EncodeValue 对 nil 报 ErrValueType), 唯独动态列曾把 nil 静默当成
  1641. // "空内容"存下去 (行内引用全 0, NULL 位图也不置位) —— 同一句"写 nil"
  1642. // 在定长列报错、在动态列静默成功。既然声明了非空, 就该两边都拒。
  1643. //
  1644. // 注意 base (旧行原值) 不会贡献 nil: deserializeRow 只 append 非 nil 的值
  1645. // (见那里的 `if val != nil`), 所以这条只拦"本次显式传入的 NULL",
  1646. // 不会让"只改其它列"的更新半路失败。
  1647. return nil, fmt.Errorf("%w: 字段 %q (%s) 未声明可空 (nullable), 不接受 NULL; "+
  1648. "要允许写入 NULL, 请在列声明里加 `nullable` / `null`",
  1649. types.ErrValueRange, name, types.TypeNameOf(f.FieldType))
  1650. }
  1651. // NULL: 位图置位, 字段区留零 (行长固定; 动态列因此不会写入堆引用)。
  1652. // 试算模式也要置位 —— extractIndexKey 正是靠位图判定"该行不进本索引"。
  1653. types.SetNullBit(bitmap, i)
  1654. // 旧行若在该列持有堆块, 新行不再引用它 —— 不在这里释放就是每更新一次漏一块。
  1655. if !checkOnly {
  1656. table.releaseOldDynamic(oldRowData, int(masterIdx))
  1657. }
  1658. } else if provided {
  1659. if err := table.writeFieldValueAt(rowData, offset, int(masterIdx), f, value, oldRowData, checkOnly); err != nil {
  1660. return nil, err
  1661. }
  1662. }
  1663. offset += f.Width()
  1664. }
  1665. return rowData, nil
  1666. }
  1667. func writeFieldValue(buf []byte, ft types.FieldType, value interface{}) {
  1668. switch ft {
  1669. case types.FieldType_1byte:
  1670. buf[0] = value.(byte)
  1671. case types.FieldType_2byte:
  1672. binary.BigEndian.PutUint16(buf, uint16(value.(int16)))
  1673. case types.FieldType_4byte:
  1674. binary.BigEndian.PutUint32(buf, uint32(value.(int32)))
  1675. case types.FieldType_8byte:
  1676. binary.BigEndian.PutUint64(buf, value.(uint64))
  1677. case types.FieldType_Int8:
  1678. buf[0] = types.Int8ToSortKey(value.(int8))
  1679. case types.FieldType_Int16:
  1680. binary.BigEndian.PutUint16(buf, types.Int16ToSortKey(value.(int16)))
  1681. case types.FieldType_Int32:
  1682. binary.BigEndian.PutUint32(buf, types.Int32ToSortKey(value.(int32)))
  1683. case types.FieldType_Int64:
  1684. // 同 Float64: 存保序编码 (Int64ToSortKey) 而不是补码本身。补码大端对负数不保序,
  1685. // 会让跨 0 的范围扫描 / ORDER BY 静默漏行; 换成保序编码后行内字节序即数值序,
  1686. // 索引键直接复用行内字节 (extractIndexKey 原样切片) 也就自动保序。
  1687. binary.BigEndian.PutUint64(buf, types.Int64ToSortKey(value.(int64)))
  1688. case types.FieldType_Float64:
  1689. // 存保序编码 (Float64ToSortKey) 而不是原始位模式: 行内字节序即数值序,
  1690. // 索引键直接复用行内字节 (extractIndexKey 原样切片) 也就自动保序。
  1691. binary.BigEndian.PutUint64(buf, types.Float64ToSortKey(toFloat64(value)))
  1692. default:
  1693. // 所有字符串/定长类型 (动态类型不走此分支, 由 writeDynamicField 处理)
  1694. if b, ok := value.([]byte); ok {
  1695. copy(buf, b)
  1696. }
  1697. }
  1698. }
  1699. // deserializeRow 将行数据字节反序列化为 FieldValue 切片 (不含 pkID 前缀)
  1700. // 行格式: [schemaVer(1B)] [field_data...] [NULL 位图] — 按行自身的版本解析
  1701. func (table *Table) deserializeRow(rowData []byte) []types.FieldValue {
  1702. if len(rowData) < 1 {
  1703. return nil
  1704. }
  1705. ver := rowData[0]
  1706. sv := table.getVersion(ver)
  1707. if sv == nil {
  1708. return nil // 未知版本, 无法解析
  1709. }
  1710. // NULL 位图在行尾, 长度按本行自己的版本算。
  1711. bitmap, ok := table.versionedNullBitmap(rowData, ver)
  1712. if !ok {
  1713. return nil
  1714. }
  1715. // 预分配容量: 字段数即结果条数的上界 (被删除 / NULL 字段会跳过), 避免 append 反复扩容
  1716. result := make([]types.FieldValue, 0, len(sv.FieldIndices))
  1717. offset := uint16(1)
  1718. for i, idx := range sv.FieldIndices {
  1719. f := table.Field[idx]
  1720. // 跳过已删除字段 (master registry 中标记 FlagDeleted) 与 NULL 字段。
  1721. // NULL 不出现在结果里 —— 与"零值不 append"是同一约定: 读不到值就是没有值;
  1722. // 更新路径靠它区分"沿用旧值"与"显式写 NULL"(见 serializeRow 的取值优先级)。
  1723. if !f.IsDropped() && !types.NullBitSet(bitmap, i) {
  1724. size := f.Width()
  1725. if int(offset)+int(size) <= len(rowData) {
  1726. var val interface{}
  1727. if types.IsDynamic(f.FieldType) {
  1728. // 解引用堆页
  1729. pageID := binary.BigEndian.Uint32(rowData[offset : offset+4])
  1730. offInPage := binary.BigEndian.Uint16(rowData[offset+4 : offset+6])
  1731. if data, err := table.heapRead(pageID, offInPage); err == nil {
  1732. val = data
  1733. }
  1734. } else {
  1735. val = types.DecodeValue(f.FieldType, rowData[offset:offset+size])
  1736. }
  1737. if val != nil {
  1738. result = append(result, types.FieldValue{FieldName: f.FieldName, Value: val})
  1739. }
  1740. }
  1741. }
  1742. offset += table.Field[idx].Width()
  1743. }
  1744. return result
  1745. }
  1746. // readFieldValue / 行内字段解码已收敛到 types.DecodeValue (见 types/codec.go):
  1747. // 解码与编码同源, 新增类型不必再回来改这里。
  1748. // ======================== 高层 CRUD ========================
  1749. // InsertRow 插入一行数据, pkID 由引擎按自增游标分配 (只增不减, 见 AllocPKID)。
  1750. func (table *Table) InsertRow(values []types.FieldValue) (uint64, error) {
  1751. return table.InsertRowWithID(0, values)
  1752. }
  1753. // InsertRowWithID 插入一行数据, pkID == 0 表示由引擎自增分配。
  1754. //
  1755. // 允许调用方指定主键: 上层 (ORM/导入) 往往已经拿到一个明确的行标识, 旧实现
  1756. // 无条件用 TotalRows+1 覆盖, 结果是"调用方以为按 _id 写进去了, 之后按 _id 却
  1757. // 查不到" —— 行确实存在, 只是不可达 (数据静默错位的一类)。
  1758. //
  1759. // 指定的主键若已存在则报 ErrDuplicatePK, 避免同一 pkID 在主键树里出现两条记录。
  1760. // 例外: 引擎刚分配并"预留"的号 (AllocPKID) 必然唯一, 跳过这次查重 ——
  1761. // 查重走的是回表 ResolveByPK, 会重建 O(页数) 的定位缓存, 而每次插入又使其失效;
  1762. // 逐行插入将因此退化成 O(N²) (千万级写入从分钟级变成不可完成)。
  1763. func (table *Table) InsertRowWithID(pkID uint64, values []types.FieldValue) (uint64, error) {
  1764. if pkID == 0 {
  1765. pkID = table.AllocPKID()
  1766. }
  1767. reserved := table.hasPendingPK && table.pendingPK == pkID
  1768. table.hasPendingPK = false
  1769. if !reserved {
  1770. if table.indexMeta.rowStoreHasPK(pkID) {
  1771. return 0, fmt.Errorf("%w: pkID=%d", ErrDuplicatePK, pkID)
  1772. }
  1773. }
  1774. // 显式指定更大的主键时把游标顶到它之后, 避免后续自增再发一次同样的号
  1775. if pkID+1 > table.nextPK {
  1776. table.nextPK = pkID + 1
  1777. }
  1778. rowData, err := table.serializeRow(values, nil)
  1779. if err != nil {
  1780. return 0, err
  1781. }
  1782. if err := table.indexMeta.InsertRow(table.Field, pkID, rowData, table.activeVersion, table.versions); err != nil {
  1783. return 0, err
  1784. }
  1785. table.info.TotalRows++
  1786. table.saveTableInfo()
  1787. if table.onWrite != nil {
  1788. table.onWrite()
  1789. }
  1790. return pkID, nil
  1791. }
  1792. // AllocPKID 分配并预留下一个自增 pkID。
  1793. //
  1794. // 供"必须先把号写进对象、再序列化整行"的上层使用 (典型是 ORM:
  1795. // 只有先拿到 pkID 填进 _id 列, 存进行里的 _id 才与真实 pkID 一致)。
  1796. // 分配即推进游标并预留该号, 紧随其后的 InsertRowWithID 会跳过查重。
  1797. // 若最终未插入, 该号只是被跳过 —— 游标只增不减, 不会与后续插入冲突。
  1798. func (table *Table) AllocPKID() uint64 {
  1799. table.ensureNextPK()
  1800. pk := table.nextPK
  1801. table.nextPK++
  1802. table.pendingPK = pk
  1803. table.hasPendingPK = true
  1804. return pk
  1805. }
  1806. // ensureNextPK 惰性初始化自增游标: 只增不减, 从行存储里当前最大的存活 pkID 之后继续。
  1807. //
  1808. // 旧实现 (TotalRows+1) 删除后会回退编号, 与仍存活的 pkID 撞车 (探针 T4)。
  1809. // 首次使用时取一次当前最大 pkID —— 每次开表只算一次, 之后 O(1)。
  1810. func (table *Table) ensureNextPK() {
  1811. if table.nextPKInit {
  1812. return
  1813. }
  1814. table.nextPK = table.maxRowStorePK() + 1
  1815. if table.nextPK == 0 {
  1816. table.nextPK = 1
  1817. }
  1818. table.nextPKInit = true
  1819. }
  1820. // maxRowStorePK 返回行存储索引中当前最大的存活 pkID (无行或没有行存储索引时为 0)。
  1821. // DataPage 沿 B+ 树叶链按 pkID 升序排列, 故从最后一页往回找第一个非空页即可;
  1822. // 每页内取最大 pkID 以兼容页内乱序的极端情况。
  1823. func (table *Table) maxRowStorePK() uint64 {
  1824. primMeta, primHandler, ok := table.indexMeta.rowStoreMeta()
  1825. if !ok {
  1826. return 0
  1827. }
  1828. ids := primHandler.GetDataPageIDs(table.handle, primMeta.EntryPage)
  1829. for i := len(ids) - 1; i >= 0; i-- {
  1830. rows := primHandler.ReadDataPageRows(table.handle, ids[i])
  1831. var maxPK uint64
  1832. for _, r := range rows {
  1833. if len(r) < 8 {
  1834. continue
  1835. }
  1836. if pk := binary.BigEndian.Uint64(r[:8]); pk > maxPK {
  1837. maxPK = pk
  1838. }
  1839. }
  1840. if maxPK != 0 {
  1841. return maxPK
  1842. }
  1843. }
  1844. return 0
  1845. }
  1846. // SearchByIndex 通过索引搜索一行
  1847. func (table *Table) SearchByIndex(idxName string, key []byte) ([]types.FieldValue, error) {
  1848. // 元信息只查一次, 命中后复用给 searchAt, 避免重复扫描 metas
  1849. meta := table.indexMeta.findIndexMetaByName(idxName)
  1850. if meta == nil {
  1851. return nil, ErrFieldNotFound
  1852. }
  1853. storeRow, found := table.indexMeta.searchAt(meta, key)
  1854. // storeRow = [pkID(8B BE) + rowData]
  1855. if !found || len(storeRow) < 8 {
  1856. return nil, page.ErrNotFound
  1857. }
  1858. return table.deserializeRow(storeRow[8:]), nil
  1859. }
  1860. // DeleteRow 通过索引删除一行
  1861. func (table *Table) DeleteRow(idxName string, key []byte) error {
  1862. // 一次定位: 既用于释放该行的堆页引用, 也下传给索引层删除 —— 避免同一行被查两次树
  1863. storeRow, found := table.indexMeta.SearchByIndex(idxName, key)
  1864. if !found || len(storeRow) < 8 {
  1865. return page.ErrNotFound
  1866. }
  1867. if err := table.indexMeta.DeleteByKey(idxName, key, storeRow, table.Field, table.activeVersion, table.versions); err != nil {
  1868. return err
  1869. }
  1870. // 行已删除 → 释放其动态字段占用的堆块; 计数归零的堆页会被摘链回收
  1871. table.heapReleaseRow(storeRow[8:])
  1872. table.info.TotalRows--
  1873. table.saveTableInfo()
  1874. if table.onWrite != nil {
  1875. table.onWrite()
  1876. }
  1877. return nil
  1878. }
  1879. // checkUniqueUpdate 更新前的唯一性预检 —— 必须早于 serializeRow。
  1880. //
  1881. // 用 serializeRowForCheck 试算行字节 (全程不碰堆), 再交给索引层比对。冲突时旧行分毫
  1882. // 未动, 动态堆块也没有被覆写或释放。代价是更新路径多一次序列化, 但更新本来就要回表、
  1883. // 定位、重写行, 这笔开销可接受; 换来的是"要么全改, 要么全不动"。
  1884. //
  1885. // 为什么不能只靠 UpdateByKey 里那次预检: 那时 serializeRow 已经跑过了, 旧行的动态
  1886. // 列早就被改成新值, 而固定列与索引条目还是旧的 —— 行被改了一半。
  1887. func (table *Table) checkUniqueUpdate(values []types.FieldValue, oldRowData []byte, pkID uint64) error {
  1888. scratch, err := table.serializeRowForCheck(values, oldRowData)
  1889. if err != nil {
  1890. return err
  1891. }
  1892. return table.indexMeta.CheckUniqueForKey(scratch, pkID, table.Field, table.activeVersion, table.versions)
  1893. }
  1894. // UpdateRow 通过索引更新一行
  1895. func (table *Table) UpdateRow(idxName string, key []byte, values []types.FieldValue) error {
  1896. // 先取旧行: 动态字段可复用旧行已占用的堆块, 避免每次更新都产生堆垃圾。
  1897. // 该行同时下传给 UpdateByKey, 免去索引层再次回表。
  1898. storeRow, found := table.indexMeta.SearchByIndex(idxName, key)
  1899. if !found {
  1900. return ErrFieldNotFound
  1901. }
  1902. if len(storeRow) < 8 {
  1903. return ErrFieldNotFound
  1904. }
  1905. pkID := binary.BigEndian.Uint64(storeRow[:8])
  1906. // 唯一性预检放在序列化之前: 见 checkUniqueUpdate。
  1907. if err := table.checkUniqueUpdate(values, storeRow[8:], pkID); err != nil {
  1908. return err
  1909. }
  1910. newRowData, err := table.serializeRow(values, storeRow[8:])
  1911. if err != nil {
  1912. return err
  1913. }
  1914. if err := table.indexMeta.UpdateByKey(key, newRowData, pkID, storeRow, table.Field, table.activeVersion, table.versions); err != nil {
  1915. return err
  1916. }
  1917. if table.onWrite != nil {
  1918. table.onWrite()
  1919. }
  1920. return nil
  1921. }
  1922. // ======================== 按行号 (pkID) 直接读写 ========================
  1923. //
  1924. // 索引键只决定"落在哪个分段/哪一批", 行号才唯一确定"是哪一行"。
  1925. // 时序表里同一个时间戳允许存在多行 (同一时刻的多个测点/多次事件), 而上面那组
  1926. // "按索引键取第一条"的入口只能碰到第一行 —— 其余行存得进、读不到、改不了、删不掉。
  1927. // 下面这组入口按行号直达, 供 ORM (与任何需要精确定位一行的上层) 使用。
  1928. // FindByPK 按行号读取一行的字段值。
  1929. func (table *Table) FindByPK(pkID uint64) ([]types.FieldValue, error) {
  1930. storeRow, found := table.indexMeta.ResolveByPK(pkID)
  1931. if !found || len(storeRow) < 8 {
  1932. return nil, page.ErrNotFound
  1933. }
  1934. return table.deserializeRow(storeRow[8:]), nil
  1935. }
  1936. // SearchByIndexWithPK 同 SearchByIndex, 但额外把该行的真实行号一并返回。
  1937. //
  1938. // 行号是行的 8 字节前缀, 不在字段值里 —— 上层要把它带回对象, 之后才能按行号
  1939. // 精确 Update/Delete (见 ORM 的 modelCore.rowID)。
  1940. func (table *Table) SearchByIndexWithPK(idxName string, key []byte) (uint64, []types.FieldValue, error) {
  1941. meta := table.indexMeta.findIndexMetaByName(idxName)
  1942. if meta == nil {
  1943. return 0, nil, ErrFieldNotFound
  1944. }
  1945. storeRow, found := table.indexMeta.searchAt(meta, key)
  1946. // storeRow = [pkID(8B BE) + rowData]
  1947. if !found || len(storeRow) < 8 {
  1948. return 0, nil, page.ErrNotFound
  1949. }
  1950. return binary.BigEndian.Uint64(storeRow[:8]), table.deserializeRow(storeRow[8:]), nil
  1951. }
  1952. // DeleteRowByPK 按行号删除一行, 并级联清掉它在全部索引里的条目。
  1953. func (table *Table) DeleteRowByPK(pkID uint64) error {
  1954. // 一次回表: 既拿到行数据用于释放动态字段的堆块, 也下传给索引层定位 pkID。
  1955. storeRow, found := table.indexMeta.ResolveByPK(pkID)
  1956. if !found || len(storeRow) < 8 {
  1957. return page.ErrNotFound
  1958. }
  1959. name, _, ok := table.indexMeta.rowStoreMeta()
  1960. if !ok {
  1961. return ErrFieldNotFound
  1962. }
  1963. // indexKey 传 nil: 行数据在手, 每个索引都能自己从行里推出键 (时序键/主键列)。
  1964. if err := table.indexMeta.DeleteByKey(name.Name(), nil, storeRow, table.Field, table.activeVersion, table.versions); err != nil {
  1965. return err
  1966. }
  1967. table.heapReleaseRow(storeRow[8:])
  1968. table.info.TotalRows--
  1969. table.saveTableInfo()
  1970. if table.onWrite != nil {
  1971. table.onWrite()
  1972. }
  1973. return nil
  1974. }
  1975. // UpdateRowByPK 按行号更新一行, 并级联更新它在全部索引里的条目。
  1976. func (table *Table) UpdateRowByPK(pkID uint64, values []types.FieldValue) error {
  1977. // 旧行既用于复用动态字段堆块, 也用于让索引层提取"旧键" (时序键变了要搬分段)。
  1978. storeRow, found := table.indexMeta.ResolveByPK(pkID)
  1979. if !found || len(storeRow) < 8 {
  1980. return ErrFieldNotFound
  1981. }
  1982. // 唯一性预检放在序列化之前: 见 checkUniqueUpdate。
  1983. if err := table.checkUniqueUpdate(values, storeRow[8:], pkID); err != nil {
  1984. return err
  1985. }
  1986. newRowData, err := table.serializeRow(values, storeRow[8:])
  1987. if err != nil {
  1988. return err
  1989. }
  1990. if err := table.indexMeta.UpdateByKey(nil, newRowData, pkID, storeRow, table.Field, table.activeVersion, table.versions); err != nil {
  1991. return err
  1992. }
  1993. if table.onWrite != nil {
  1994. table.onWrite()
  1995. }
  1996. return nil
  1997. }