Table.go 129 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183
  1. package core
  2. import (
  3. "bytes"
  4. "encoding/binary"
  5. "fmt"
  6. "io"
  7. "sort"
  8. "strings"
  9. "sync"
  10. "unsafe"
  11. "git.ycl.pub/yl32940/TsDB2/DB/core/page"
  12. "git.ycl.pub/yl32940/TsDB2/DB/core/types"
  13. "git.ycl.pub/yl32940/TsDB2/DB/core/util"
  14. )
  15. // SchemaFragHdr 共享扩展页里每个 schema 续块的头 (定长 8 字节, 紧随 payload)
  16. // 警告: 该结构体仅用于计算大小/表达语义, 写盘一律用 putSchemaFragHdr,
  17. // 不要做 `*(*SchemaFragHdr)(p) = v` 的结构体赋值。
  18. type SchemaFragHdr struct {
  19. NextPage uint32 // 下一个续块所在页 (0 = 本表续块链结束)
  20. NextOff uint16 // 下一个续块在页内的物理偏移
  21. DataLen uint16 // 本续块 payload 字节数
  22. }
  23. // schemaFragHdrSize schema 续块头的物理大小 (字节)
  24. const schemaFragHdrSize = 8
  25. // putSchemaFragHdr 写入续块头 (大端: NextPage 4B + NextOff 2B + DataLen 2B)
  26. func putSchemaFragHdr(buf []byte, off int, h SchemaFragHdr) {
  27. binary.BigEndian.PutUint32(buf[off:off+4], h.NextPage)
  28. binary.BigEndian.PutUint16(buf[off+4:off+6], h.NextOff)
  29. binary.BigEndian.PutUint16(buf[off+6:off+8], h.DataLen)
  30. }
  31. // readSchemaFragHdr 读取续块头
  32. func readSchemaFragHdr(buf []byte, off int) SchemaFragHdr {
  33. return SchemaFragHdr{
  34. NextPage: binary.BigEndian.Uint32(buf[off : off+4]),
  35. NextOff: binary.BigEndian.Uint16(buf[off+4 : off+6]),
  36. DataLen: binary.BigEndian.Uint16(buf[off+6 : off+8]),
  37. }
  38. }
  39. // SchemaVersion 表的一个 schema 版本定义
  40. type SchemaVersion struct {
  41. Version byte
  42. FieldIndices []byte // 每个字段在 masterFields 中的索引
  43. }
  44. type TableInfo struct {
  45. Name [TableNameSize]byte
  46. Flag byte // 表的状态是否被删除
  47. FieldCount byte // 当前字段的总数量(包含已删除,永不减少)byte=255上限
  48. ActiveFieldCount byte // 实际活跃字段数量(不包含已删除)
  49. _ byte // 1 显式对齐填充 (TotalRows 需要 8 字节对齐)
  50. // HeapPageCount 本表堆链 (FirstHeap→LastHeap) 上的页数。
  51. //
  52. // 塞进 TotalRows 前的对齐填充里 (那 4 字节本来就是编译器要插入的空洞), 不额外
  53. // 增加 TableInfo 的体积。
  54. // 用途: DropTable 把整条堆链 O(1) 拼进空闲链时, 不遍历也有链长可累加空闲页计数。
  55. HeapPageCount uint32 // 4 堆链页数
  56. TotalRows uint64 // 当前表行的总数量
  57. FirstHeap uint32 // 堆链首页
  58. LastHeap uint32 // 堆链尾页
  59. FirstData uint32 // 数据链首页
  60. LastData uint32 // 数据链尾
  61. RowSize uint16 // 单条记录大小(字节)
  62. FieldAllocateSize uint16 // 当前列总分配大小
  63. IndexCount byte // 这张表有几个索引
  64. FirstIndexID uint32 // 本表第一个索引的元数据位置
  65. LastIndexID uint32 // 本表最后一个索引的元数据位置
  66. // ExtRef schema 续块在「全库共享扩展页」中的位置
  67. // (复用原 FirstOverflow/LastOverflow 占用的 8 字节: ExtPage 落在 8 字节对齐处,
  68. // 尾部 2 字节显式填充, 使 TableInfo 整体大小为 8 的倍数)
  69. ExtPage uint32 // 4 首个续块所在扩展页 ID (0 = schema 全部内联, 无续块)
  70. ExtOff uint16 // 2 首个续块在页内的物理偏移
  71. _ uint16 // 2 显式填充, 使整体大小为 8 的倍数
  72. }
  73. // 编译期断言: 钉死 TableInfo 的盘上大小与字段布局。
  74. //
  75. // TableNameSize 变化会整体平移 Name 之后所有字段的偏移 —— analyzer 那套硬编码偏移
  76. // (analyzer/main.go 的 TI_*) 必须同步修改, 改漏了会静默读错数据。这里把大小钉死,
  77. // 让「改了布局却忘了改断言」直接编译失败。
  78. //
  79. // 布局 (TableNameSize=48) = 104 字节:
  80. //
  81. // Name[48] + Flag/FieldCount/ActiveFieldCount/pad(4) + HeapPageCount(4) + TotalRows(8)
  82. // + FirstHeap/LastHeap/FirstData/LastData(16) + RowSize/FieldAllocateSize(4)
  83. // + IndexCount/pad(4) + FirstIndexID/LastIndexID/ExtPage(12) + ExtOff/pad(4)
  84. var (
  85. _ [unsafe.Sizeof(TableInfo{}) - 104]struct{}
  86. _ [104 - unsafe.Sizeof(TableInfo{})]struct{}
  87. )
  88. // readSchema 读取一张表完整的 schema 定义
  89. // 数据来源: 表页内的固定内联块 (allocSize 字节) + 共享扩展页里的续块链
  90. // 新格式: [activeVer(1B)] [masterCount(1B)] [type+len+name]×masterCount [verCount(1B)] [verDefs...]
  91. func readSchema(handle page.HandleDb, pageID uint32, startOff uint16, allocSize uint16,
  92. extPage uint32, extOff uint16) (byte, []types.FieldInfo, []SchemaVersion) {
  93. // 1. 内联块 (表页内, 固定 allocSize 字节, 无跳转槽)
  94. var data []byte
  95. buf := make([]byte, PageSize)
  96. if err := handle.ReadPage(pageID, buf); err == nil {
  97. end := int(startOff) + int(allocSize)
  98. if end > len(buf) {
  99. end = len(buf)
  100. }
  101. if int(startOff) < end {
  102. data = append(data, buf[startOff:end]...)
  103. }
  104. }
  105. // 2. 续块链 (共享扩展页): [SchemaFragHdr 8B][payload]
  106. for pid, off := extPage, extOff; pid != 0; {
  107. fb := make([]byte, PageSize)
  108. if err := handle.ReadPage(pid, fb); err != nil {
  109. break
  110. }
  111. if int(off)+schemaFragHdrSize > len(fb) {
  112. break
  113. }
  114. frag := readSchemaFragHdr(fb, int(off))
  115. start := int(off) + schemaFragHdrSize
  116. end := start + int(frag.DataLen)
  117. if end > len(fb) {
  118. break
  119. }
  120. data = append(data, fb[start:end]...)
  121. pid, off = frag.NextPage, frag.NextOff
  122. }
  123. if len(data) < 2 {
  124. return 0, nil, nil
  125. }
  126. pos := 0
  127. activeVer := data[pos]
  128. pos++
  129. masterCount := int(data[pos])
  130. pos++
  131. // 读取 master fields: [type(1B)][flag(1B)][nameLen(1B)][name...]
  132. // flag 是列级标志 (删除/可空/唯一 + decimal 的 scale), 见 types.FieldInfo.Flag。
  133. // 语义变了但字节位置与顺序不变: 必须与 serializeSchema 严格对照。
  134. var masterFields []types.FieldInfo
  135. for i := 0; i < masterCount && pos+3 <= len(data); i++ {
  136. typ := types.FieldType(data[pos])
  137. flag := data[pos+1]
  138. nameLen := int(data[pos+2])
  139. pos += 3
  140. if pos+nameLen > len(data) {
  141. break
  142. }
  143. name := make([]byte, nameLen)
  144. copy(name, data[pos:pos+nameLen])
  145. pos += nameLen
  146. masterFields = append(masterFields, types.FieldInfo{FieldType: typ, FieldName: name, Flag: flag})
  147. }
  148. // 读取版本定义
  149. var versions []SchemaVersion
  150. if pos < len(data) {
  151. verCount := int(data[pos])
  152. pos++
  153. for i := 0; i < verCount && pos < len(data); i++ {
  154. verNo := data[pos]
  155. pos++
  156. if pos >= len(data) {
  157. break
  158. }
  159. fieldCount := int(data[pos])
  160. pos++
  161. indices := make([]byte, fieldCount)
  162. if pos+fieldCount <= len(data) {
  163. copy(indices, data[pos:pos+fieldCount])
  164. pos += fieldCount
  165. }
  166. versions = append(versions, SchemaVersion{Version: verNo, FieldIndices: indices})
  167. }
  168. }
  169. return activeVer, masterFields, versions
  170. }
  171. type Table struct {
  172. info *TableInfo
  173. Field []types.FieldInfo // master registry (所有字段,含已删除,删除标记见 types.FlagDeleted)
  174. // db 所属 DB (BlobRead/BlobFree 需要它取 file / pendingFreeChain).
  175. // 创建时由 db.tables.Create 注入, 运行时不变.
  176. db *DB
  177. handle page.HandleDb
  178. indexMeta *IndexMetaPage
  179. tablePageID uint32 // 表ID
  180. tableStartOffset uint32 // 表所在页的偏移
  181. // schema 续块所在的「全库共享扩展页」分配器 (由 TablePage 注入, 各表同一实例)
  182. schemaExt *SchemaExtAllocator
  183. // schema 版本管理
  184. activeVersion byte
  185. versions []SchemaVersion
  186. // 写操作通知回调 (防抖自动提交)
  187. onWrite func()
  188. // 自增主键游标 (内存态, 详见 AllocPKID/ensureNextPK):
  189. // 不能再现算 TotalRows+1 —— 删除会让行数回退, 编号随之回退并撞上存活的 pkID。
  190. nextPK uint64
  191. nextPKInit bool
  192. pendingPK uint64 // AllocPKID 刚预留、等待 InsertRowWithID 消费的号
  193. hasPendingPK bool
  194. // fieldIdxCache: 列名 → table.Field 下标的惰性缓存。条件求值
  195. // (Eq/In/Gt... 的 Match) 每行每条件都查一次字段位置, 线性扫是 O(列数)/行;
  196. // 表字段在建模后基本不变, 缓存后 O(1)。Field 切片身份变化 (AddField 追加 /
  197. // ReloadInfos 整体换片) 时按首元素指针 + 长度自动重建, 无需手工失效。
  198. // fieldIdxMu 保护首次构建: 引擎允许并发读, 两个读 goroutine 可能同时建缓存。
  199. fieldIdxMu sync.Mutex
  200. fieldIdxCache map[string]int
  201. fieldIdxSrc []types.FieldInfo // 建缓存时的 Field 切片 (身份比对用)
  202. }
  203. // CreateIndex 建二级索引。
  204. //
  205. // 这里先校验分量列能不能做键, 再交给 IndexMetaPage 建 —— 拆成两层是因为
  206. // IndexMetaPage 只拿得到索引自己的元信息, 看不到表的字段类型:
  207. // - 分量列不存在 → ErrFieldNotFound;
  208. // - 分量是动态列 → ErrIndexDynamicField。动态列在行内只存 6B 堆引用, 值 != 引用,
  209. // 按值查找永远不可能命中; 建出来既查不到也不会被查询侧选中 (buildKeyLayout 会跳过),
  210. // 只会白占空间、白写。拦在建的这一步, 而不是等用到才发现;
  211. // - 分量是 blob 列 → ErrIndexBlobField。blob 列行内只存 8B BlobRef (页号 + 长度),
  212. // 拿它当索引键等于按页号排序/查找 —— 索引里存的不是内容, 命中与否全看内容落在哪一页;
  213. // - 复合索引 (IndexTypeCompose) 的分量列宽度不再强制 8 字节 ——
  214. // 底层已升级为 B+ 树, 按分量真实宽度拼定长键, 溢出闸门 (checkIndexSpillFloor)
  215. // 负责兜住"一页两行"的行宽上限。
  216. //
  217. // unique 为可选 (只取第一个值), 见 IndexMetaPage.CreateIndex: 传 true 建的是唯一索引,
  218. // 同一索引键只允许一行; 任一分量为 NULL 的行不进该索引, 因此多个 NULL 天然共存。
  219. // 唯一约束在写入路径 (InsertRow / UpdateByKey) 上校验, 违反时返回 ErrDuplicatedKey。
  220. func (table *Table) CreateIndex(idxName string, typ page.IndexType, fields []byte, unique ...bool) (*IndexMeta, error) {
  221. for i, fi := range fields {
  222. if int(fi) >= len(table.Field) {
  223. return nil, fmt.Errorf("%w: 索引 %q 的第 %d 个分量", ErrFieldNotFound, idxName, i+1)
  224. }
  225. ft := table.Field[fi].FieldType
  226. if types.IsDynamic(ft) {
  227. return nil, fmt.Errorf("%w (索引 %q 的第 %d 个分量)", ErrIndexDynamicField, idxName, i+1)
  228. }
  229. // v10: blob 列行内只有 8B BlobRef (页号 + 长度), 拿它当索引键等于按页号排序/查找 ——
  230. // 索引里存的不是内容, 命中与否全看内容落在哪一页. 与动态列同等拦下.
  231. if types.IsBlob(ft) {
  232. return nil, fmt.Errorf("%w (索引 %q 的第 %d 个分量)", ErrIndexBlobField, idxName, i+1)
  233. }
  234. }
  235. // 溢出闸门: 分量列一旦建索引就不能再值级溢出, 行内下界随之抬升。
  236. // 下界超过"一页两行"的配额时必须在这里拒绝 —— 否则这张表从建索引成功起就再也压不
  237. // 进两行 (单条插入仍会"成功"地落在 8159..16332 之间, 页内顺序/有序插入/页内重建
  238. // 一起退化, 而且从现象上完全看不出是索引造成的)。
  239. if err := table.checkIndexSpillFloor(fields); err != nil {
  240. return nil, fmt.Errorf("%w: 索引 %q: %v", ErrIndexRowTooWide, idxName, err)
  241. }
  242. m, err := table.indexMeta.CreateIndex(idxName, typ, fields, unique...)
  243. if err != nil {
  244. return nil, err
  245. }
  246. // 回填存量行。
  247. // 旧实现只建元数据, 索引里一条记录都没有: 建索引之前就存在的行永远查不到,
  248. // 而引擎又没有公开的"重建全部二级索引"接口 —— 数据在、索引空, 是最难排查的
  249. // 一类不一致。建索引这一步必须自己把存量行灌进去。
  250. if err := table.backfillIndex(m); err != nil {
  251. // 回填失败不能留下"半个索引": 元数据还在但内容不全, 比没有索引更危险
  252. // (查询会选中它, 于是稳定地查不到行)。摘掉元数据, 让错误显式冒出来。
  253. // 走 table.DropIndex 而不是 indexMeta.DropIndex: 后者只软删元数据, 会把
  254. // 回填到一半的树页留成孤儿页。
  255. _ = table.DropIndex(idxName)
  256. return nil, err
  257. }
  258. return m, nil
  259. }
  260. // backfillIndex 把表里已有的行灌进一个刚建好的二级索引。
  261. //
  262. // 与写入侧共用 extractIndexKey + 处理器 Insert, 因此:
  263. // - 键编码与后续单条插入完全一致, 不会出现"回填进去了却查不到"的错位;
  264. // - 老行 (该列在其自身的 schema 版本里还不存在, 例如 AddField 之前插入的行)
  265. // 自动跳过, 与"单条插入时缺失列不落索引"的语义保持一致;
  266. // - 动态列在 CreateIndex 已被拒绝, 这里不必再判。
  267. //
  268. // 行存储索引 (主键/时序) 不在此处理: 它们的键就是 pkID, 行数据本就由写入路径维护。
  269. func (table *Table) backfillIndex(m *IndexMeta) error {
  270. if m == nil || indexStoresRows(m) {
  271. return nil
  272. }
  273. primMeta, primHandler, ok := table.indexMeta.rowStoreMeta()
  274. if !ok || primMeta == nil || primHandler == nil {
  275. return nil // 表里没有承载行的索引, 也就没有存量行
  276. }
  277. // 收集 → 排序 → 批量建树。
  278. // 不能逐条 handler.Insert: 那是 n 次 O(log n) 的树写放大, 千万行级别慢到
  279. // 不可接受; 索引重建本来就有 O(n) 的批量路径, 这里直接复用。
  280. var entries [][]byte
  281. for _, dpID := range primHandler.GetDataPageIDs(table.handle, primMeta.EntryPage) {
  282. for _, storeRow := range primHandler.ReadDataPageRows(table.handle, dpID) {
  283. if len(storeRow) < 8 {
  284. continue
  285. }
  286. secKey := extractIndexKey(storeRow[8:], m, table.Field, table.activeVersion, table.versions)
  287. if secKey == nil {
  288. continue // 该行没有这个键 (列不在它的 schema 版本里)
  289. }
  290. entry := make([]byte, len(secKey)+8)
  291. copy(entry, secKey)
  292. // 条目尾部是 pkID (8B BE), 与 rebuildIndexes 的二级条目同一布局
  293. binary.BigEndian.PutUint64(entry[len(secKey):], binary.BigEndian.Uint64(storeRow[:8]))
  294. entries = append(entries, entry)
  295. }
  296. }
  297. if len(entries) == 0 {
  298. // 没有任何存量行进这个索引 → 保持空树 (EntryPage==0), 后续插入照常开根
  299. return nil
  300. }
  301. // 按复合键 (secKey, pkID) 升序排序: 二级树的复合分隔键与叶内复合二分要求
  302. // 全树按复合键全局有序, 同 key 的条目必须再按 pkID 排
  303. sort.SliceStable(entries, func(i, j int) bool {
  304. a, b := entries[i], entries[j]
  305. if c := compareBytes(a[:len(a)-8], b[:len(b)-8]); c != 0 {
  306. return c < 0
  307. }
  308. return binary.BigEndian.Uint64(a[len(a)-8:]) < binary.BigEndian.Uint64(b[len(b)-8:])
  309. })
  310. // 唯一索引进来了要顺手验一遍存量数据: 库里本来就有重复值时, 这个约束是【建不出来】的。
  311. // 硬建下去只会得到一个"违反自身约束"的索引 (同一键挂着两行), 之后点查可能返回其中
  312. // 任意一行, 而且从索引上完全看不出它已经坏了。宁可在建索引这一步报错, 由调用方决定
  313. // 是清理数据还是改用非唯一索引。排序后重复键必然相邻, 一次线性扫描即可。
  314. if m.Unique() {
  315. for i := 1; i < len(entries); i++ {
  316. if compareBytes(entries[i-1][:len(entries[i-1])-8], entries[i][:len(entries[i])-8]) == 0 {
  317. return fmt.Errorf("%w: 索引 %q 的存量数据中已存在重复键 (pkID %d 与 %d), 无法建成唯一索引",
  318. ErrDuplicatedKey, util.BytesToString(m.IndexName[:]),
  319. binary.BigEndian.Uint64(entries[i-1][len(entries[i-1])-8:]),
  320. binary.BigEndian.Uint64(entries[i][len(entries[i])-8:]))
  321. }
  322. }
  323. }
  324. if err := buildSecondaryFromEntries(table.handle, m, entries); err != nil {
  325. return err
  326. }
  327. table.indexMeta.saveMeta(m)
  328. table.indexMeta.InvalidateRowCache()
  329. return nil
  330. }
  331. func (table *Table) DropIndex(idx string) error {
  332. // DROP INDEX 必须把整棵索引树还给空闲链。
  333. // 旧实现只把元数据打上软删除标记 (Flag|=0x80) 并从 metas 摘掉: 根指针一丢, 整棵树
  334. // (内部节点 + 叶子) 就既不可达、又不在空闲链上 —— 变成永久孤儿页, 文件只涨不缩。
  335. // 实测 20 万行的一棵二级索引约 197 页; bench_10m 里 9.9M 行的 Dept 索引是数万页,
  336. // 正是那批孤儿页的主因。
  337. // 注意: indexMeta.DropIndex 会用 append 挪动 metas 元素, 之后 &metas[i] 就失效了,
  338. // 所以必须在摘除之前把 IndexType / EntryPage 快照出来。
  339. var itype page.IndexType
  340. var rootPID uint32
  341. if m := table.indexMeta.findIndexMetaByName(idx); m != nil {
  342. itype, rootPID = m.IndexType, m.EntryPage
  343. }
  344. if err := table.indexMeta.DropIndex(idx); err != nil {
  345. return err
  346. }
  347. // 旧结构此刻已不可达 (元数据已摘), 归还它的全部页。
  348. releaseIndexPages(table, itype, rootPID)
  349. return nil
  350. }
  351. // BuildIndexKey 按索引定义, 把各分量值编码成索引键 (与写入侧 extractIndexKey 同一编码)。
  352. //
  353. // 复合索引的键是各分量定长拼接, 外部无法凭空构造 —— 因此点查/删除/更新复合索引
  354. // (SearchByIndex / DeleteRow / UpdateRow) 都要先经这里把值编成键。
  355. // values 的顺序与建索引时给的 fieldIDs 顺序一致, 个数必须相等。
  356. func (table *Table) BuildIndexKey(idxName string, values ...interface{}) ([]byte, error) {
  357. m := table.indexMeta.findIndexMetaByName(idxName)
  358. if m == nil {
  359. return nil, ErrIndexNotFound
  360. }
  361. n := int(m.FieldCnt)
  362. if n != len(values) {
  363. return nil, fmt.Errorf("索引 %q 需要 %d 个分量值, 实际给了 %d 个", idxName, n, len(values))
  364. }
  365. key := make([]byte, 0, n*8)
  366. for k := 0; k < n; k++ {
  367. fi := int(m.FieldIDs[k])
  368. if fi >= len(table.Field) {
  369. return nil, ErrFieldNotFound
  370. }
  371. ft := table.Field[fi].FieldType
  372. if table.Field[fi].IsDropped() {
  373. return nil, fmt.Errorf("索引 %q 的第 %d 个分量列已被删除", idxName, k+1)
  374. }
  375. if types.IsDynamic(ft) {
  376. return nil, fmt.Errorf("索引 %q 的第 %d 个分量是动态列, 不能作为索引键", idxName, k+1)
  377. }
  378. if types.IsBlob(ft) {
  379. return nil, fmt.Errorf("索引 %q 的第 %d 个分量是 blob 列, 不能作为索引键", idxName, k+1)
  380. }
  381. enc, err := encodeIndexKeySafeWithParam(values[k], ft, table.fieldParam(fi))
  382. if err != nil {
  383. return nil, fmt.Errorf("索引 %q 的第 %d 个分量: %w", idxName, k+1, err)
  384. }
  385. if want := int(table.Field[fi].Width()); len(enc) != want {
  386. return nil, fmt.Errorf("索引 %q 的第 %d 个分量: 编码 %d 字节, 列宽 %d 字节",
  387. idxName, k+1, len(enc), want)
  388. }
  389. key = append(key, enc...)
  390. }
  391. return key, nil
  392. }
  393. // TotalRows 返回表的总行数
  394. func (table *Table) TotalRows() uint64 { return table.info.TotalRows }
  395. // TablePageID 返回表页 ID (调试用)
  396. func (table *Table) TablePageID() uint32 { return table.tablePageID }
  397. // TableStartOffset 返回表信息在页中的偏移 (调试用)
  398. func (table *Table) TableStartOffset() uint32 { return table.tableStartOffset }
  399. // IndexMeta 返回索引元数据页 (调试用)
  400. func (table *Table) IndexMeta() *IndexMetaPage { return table.indexMeta }
  401. // SetOnWrite 设置写操作通知回调 (防抖自动提交)
  402. func (table *Table) SetOnWrite(fn func()) { table.onWrite = fn }
  403. // findFieldIndex 查找字段在 master registry 中的索引
  404. //
  405. // 走 fieldIndexMap 的惰性缓存: Match 类条件是"每行每条件一次"的热路径, 线性扫
  406. // O(列数) 在宽表 + 全表扫描下会累积成可见开销。缓存按 Field 切片身份
  407. // (首元素指针 + 长度) 自动失效, AddField / ReloadInfos 后下一次调用即重建。
  408. func (table *Table) findFieldIndex(name string) int {
  409. if idx, ok := table.fieldIndexMap()[name]; ok {
  410. return idx
  411. }
  412. return -1
  413. }
  414. // fieldIndexMap 返回 (必要时重建) 列名 → Field 下标缓存。
  415. func (table *Table) fieldIndexMap() map[string]int {
  416. src := table.Field
  417. if table.fieldIdxCache != nil && len(table.fieldIdxSrc) == len(src) &&
  418. (len(src) == 0 || &table.fieldIdxSrc[0] == &src[0]) {
  419. return table.fieldIdxCache
  420. }
  421. table.fieldIdxMu.Lock()
  422. defer table.fieldIdxMu.Unlock()
  423. // 双检: 等待锁期间另一读线程可能已建好
  424. if table.fieldIdxCache != nil && len(table.fieldIdxSrc) == len(src) &&
  425. (len(src) == 0 || &table.fieldIdxSrc[0] == &src[0]) {
  426. return table.fieldIdxCache
  427. }
  428. m := make(map[string]int, len(src))
  429. for i := range src {
  430. m[string(src[i].FieldName)] = i
  431. }
  432. table.fieldIdxCache = m
  433. table.fieldIdxSrc = src
  434. return m
  435. }
  436. // fieldWidth 取某列的行内宽度 (字节) —— 引擎内所有行偏移计算的唯一入口。
  437. //
  438. // 为什么不能直接 types.GetSize(f.FieldType): 字符串列的行内宽度由【列自己声明】
  439. // (Flag 低 5 位的宽度码, 上限 types.MaxInlineColumnWidth), 只看类型会一律算成 256,
  440. // 于是该列之后的所有列都从错位偏移解码。这里统一经 FieldInfo.Width()。
  441. //
  442. // 返回 0 表示索引越界或列级参数非法 —— 调用方必须当错误处理: 0 宽度会让整行错位。
  443. func (table *Table) fieldWidth(masterIdx int) uint16 {
  444. if masterIdx < 0 || masterIdx >= len(table.Field) {
  445. return 0
  446. }
  447. return table.Field[masterIdx].Width()
  448. }
  449. // MaxPhysicalRowData 单行 rowData 的物理上限 (字节) —— 一行必须能装进一个 DataPage。
  450. //
  451. // 16384 (页) - 40 (DataPage 头) - 4 (行长度前缀) - 8 (storeRow 里的 pkID) = 16332
  452. //
  453. // 它与建列闸门 types.MaxRowDataSize (8171, 一页两行) 是两件事:
  454. // - MaxRowDataSize 是设计政策, 在 CreateTable / AddField 处执行, 保证一页至少装两行;
  455. // 它卡的是【整行占用】= 4 + 8 + 这里的 rowData, 所以比这道物理墙紧得多 (8159 / 16332);
  456. // - MaxPhysicalRowData 是物理墙, 在写入时兜底 —— 超过它的行字节流会写坏 DataPage,
  457. // 必须有第二道网拦住"建列闸门之前的旧表"与"schema 被外部改坏"这两种情况。
  458. const MaxPhysicalRowData = int(PageSize) - int(page.DataPageHdrSize) - 4 - 8
  459. // validateFieldsForSchema 校验一组列定义能否构成合法 schema:
  460. // 列名长度可落盘 + 列级参数自洽 + 行宽不超建列闸门。
  461. //
  462. // 校验落在 core 而不是只留在 ORM / migrate: schema 也能从 SQL 或低层 API 直接建出来,
  463. // 只有拦在真正写 schema 的地方才拦得住。
  464. func validateFieldsForSchema(fields []types.FieldInfo) error {
  465. for _, f := range fields {
  466. // 列名长度: schema 里是 [nameLen(1B)][name...] (见 serializeSchema / readSchema)。
  467. // 长度为 0 或 >255 都不能落盘 —— 0 会让解析侧把下一个字段的类型字节当名字,
  468. // >255 会让 nameLen 截断并让其后所有字段整体错位。都不是"名字有瑕疵", 是整表错乱。
  469. if len(f.FieldName) == 0 {
  470. return fmt.Errorf("%w: 列名长度为 0 (schema 里列名长度是 1 字节, 空名字无法落盘)",
  471. types.ErrValueRange)
  472. }
  473. if len(f.FieldName) > FieldNameSize {
  474. return fmt.Errorf("%w: 列名 %q 有 %d 字节, 上限 %d 字节 "+
  475. "(schema 里列名长度是 1 字节, 超长会截断并让后续列整体错位)",
  476. types.ErrValueRange, f.FieldName, len(f.FieldName), FieldNameSize)
  477. }
  478. if err := types.ValidateColumnParam(f.FieldType, f.Param()); err != nil {
  479. return fmt.Errorf("列 %q: %w", f.FieldName, err)
  480. }
  481. }
  482. return types.CheckRowSize(fields)
  483. }
  484. // nextSchemaVersion 返回下一个可用的 schema 版本号。
  485. //
  486. // schemaVer 是每行行首的 1 字节 (getVersionedOffset / rowDataSizeForVersion 都按它取版本),
  487. // 版本条数也是 1 字节落盘 (serializeSchema 的 verCount)。到 0xFF 再自增就会回绕成 0:
  488. // 0 不是任何已知版本, getVersion 返回 nil → 该行按"未知版本"解析, NULL 位图取不到,
  489. // 所有列静默读成 0 / NULL。宁可在演进这一步拒绝, 让用户重建表。
  490. //
  491. // 每次 AddField / DropField 都会新增一版, 因此这道闸门也限定了"一张表的 schema
  492. // 最多能演进多少次"。
  493. func (table *Table) nextSchemaVersion() (byte, error) {
  494. if table.activeVersion >= 0xFF || len(table.versions) >= 0xFF {
  495. return 0, fmt.Errorf("schema 版本数已达上限 %d (activeVer=%d, 已存 %d 版): "+
  496. "版本号是行首 1 字节, 再演进会回绕并让旧行解析错位; 请重建该表",
  497. 0xFF, table.activeVersion, len(table.versions))
  498. }
  499. return table.activeVersion + 1, nil
  500. }
  501. // getVersion 获取指定版本的 schema 定义
  502. func (table *Table) getVersion(ver byte) *SchemaVersion {
  503. for i := range table.versions {
  504. if table.versions[i].Version == ver {
  505. return &table.versions[i]
  506. }
  507. }
  508. return nil
  509. }
  510. // svCol0Width 某版本行的【列 0 档位宽度】W0 (由 schema 唯一确定)。
  511. //
  512. // v9 行布局把列 0 固定按档位宽度存储 (见 types/rowvar.go), 偏移表基址 = 1 + W0。
  513. // 解析端必须能静态算出 W0 —— 这正是"列 0 不参与变长存储"的原因: 若列 0 也按实际
  514. // 长度存储, 偏移表的位置就取决于内容, 解析将不得不先猜列 0 有多长 (自举问题)。
  515. func (table *Table) svCol0Width(sv *SchemaVersion) int {
  516. if sv == nil || len(sv.FieldIndices) == 0 {
  517. return 0
  518. }
  519. idx := int(sv.FieldIndices[0])
  520. if idx < 0 || idx >= len(table.Field) {
  521. return 0
  522. }
  523. return int(table.Field[idx].Width())
  524. }
  525. // rowCol0Width 指定 schemaVer 对应行的列 0 档位宽度。
  526. func (table *Table) rowCol0Width(ver byte) int {
  527. return table.svCol0Width(table.getVersion(ver))
  528. }
  529. // getVersionedOffset 计算某行中指定 master 字段索引在行数据中的偏移。
  530. //
  531. // 行布局 v9: [schemaVer 1B][列0 W0 B][列结束偏移表 2N B][列1..N-1 内容][NULL 位图 ⌈n/8⌉ B]
  532. // 列位置一律由【行内偏移表】给出 (见 getVersionedRange), 位图长度按【行自身的
  533. // schemaVer】算 (n = 该版本 FieldIndices 长度), 不能取当前字段数。
  534. //
  535. // 时序索引按【绝对偏移 9】(== [pkID 8B][schemaVer 1B]) 读首列当时间键
  536. // (见 DB/types.go 的 modelInfo 与 core/index 的 timeRowInRange / FindRowByIndexKey)。
  537. // 这条能成立, 靠的是"列 0 恒占其档位宽度 W0 且时间键列恒为 8 字节" —— 不是靠"位图放行尾":
  538. // 位图若前插会把首列整体后移 ⌈n/8⌉ 字节, 时序键读取全错, 所以位图必须落在字段内容之后。
  539. //
  540. // 字段为 NULL 时返回 false。把 NULL 判定下沉到这里, 是因为"定位偏移"是所有读取路径
  541. // (7 个条件类 + 更新时摘旧堆块) 的唯一入口, 各调用方再各判一次必然漏:
  542. // - 条件匹配: NULL 不匹配任何比较 (SQL 三值逻辑)。若按全零字节比, `x = 0` 会命中
  543. // NULL 行、`x < 1` 也会命中 —— 结果看着"有数据"却全是错的。
  544. // - 更新路径: 旧值为 NULL 时引用槽是全零, 本就没有堆块要摘。
  545. func (table *Table) getVersionedOffset(rowData []byte, masterFieldIdx int) (uint16, bool) {
  546. start, _, ok := table.getVersionedRange(rowData, masterFieldIdx)
  547. return start, ok
  548. }
  549. // getVersionedRange 定位某行中 masterFieldIdx 列内容的 [起点, 终点)。
  550. //
  551. // 【v9 起改走行内偏移表】旧实现从偏移 1 开始"累加各列 Width"推算列位置, 那是建立在
  552. // "字段区定长、每列实付档位宽度"的前提上。v9 让字符串列按实际长度存储 (见
  553. // types/rowvar.go), 累加 Width 会越算越偏 —— 必须由行内偏移表给出精确位置。
  554. //
  555. // 终点是行内【实际】字节的结尾, 不再等于 起点+Width: 调用方若需要"档位宽度"的定长
  556. // 视角 (比较 / 索引键), 必须自己用 types.PadToWidth 补齐。
  557. //
  558. // 字段为 NULL 时返回 false。
  559. func (table *Table) getVersionedRange(rowData []byte, masterFieldIdx int) (uint16, uint16, bool) {
  560. if len(rowData) < 1 {
  561. return 0, 0, false
  562. }
  563. ver := rowData[0]
  564. sv := table.getVersion(ver)
  565. if sv == nil {
  566. return 0, 0, false
  567. }
  568. n := len(sv.FieldIndices)
  569. col0Width := table.svCol0Width(sv)
  570. ends, ok := types.RowParseOffsets(rowData, n, col0Width)
  571. if !ok {
  572. return 0, 0, false
  573. }
  574. for i, idx := range sv.FieldIndices {
  575. if int(idx) != masterFieldIdx {
  576. continue
  577. }
  578. if types.RowIsNull(rowData, ends, n, i) {
  579. return 0, 0, false // NULL: 不参与比较, 也没有堆块
  580. }
  581. return types.RowFieldStart(ends, i), types.RowFieldEnd(ends, i, col0Width), true
  582. }
  583. return 0, 0, false
  584. }
  585. // versionedNullBitmap 取出行尾的 NULL 位图切片 (长度按该行自身的 schemaVer 算)。
  586. //
  587. // 行布局 v9: [schemaVer 1B][列0 W0 B][列结束偏移表 2N B][列1..N-1 内容][NULL 位图 ⌈n/8⌉ B]
  588. //
  589. // 位图起点由偏移表末项给出, 不再靠"版本算出的总长" —— v9 行按内容变长, 不存在
  590. // "版本对应的总长"。而【为什么仍然不能用 len(rowData)】: 行存储的原地更新允许把新行
  591. // 写进旧行的槽位 (剩余字节清零), 按 len 取尾会取到零填充而不是真正的位图。偏移表落在
  592. // 只取决于字段数的固定位置上, 不受零填充影响, 因此始终精确。
  593. func (table *Table) versionedNullBitmap(rowData []byte, ver byte) ([]byte, bool) {
  594. sv := table.getVersion(ver)
  595. if sv == nil {
  596. return nil, false
  597. }
  598. n := len(sv.FieldIndices)
  599. ends, ok := types.RowParseOffsets(rowData, n, table.svCol0Width(sv))
  600. if !ok {
  601. return nil, false
  602. }
  603. return types.RowNullBitmap(rowData, ends, n)
  604. }
  605. // getActiveFieldTypes 返回当前活跃版本的字段类型列表 (不含已删除)
  606. func (table *Table) getActiveFieldTypes() []types.FieldInfo {
  607. sv := table.getVersion(table.activeVersion)
  608. if sv == nil {
  609. return nil
  610. }
  611. fields := make([]types.FieldInfo, len(sv.FieldIndices))
  612. for i, idx := range sv.FieldIndices {
  613. fields[i] = table.Field[idx]
  614. }
  615. return fields
  616. }
  617. // calcRowDataSize 计算当前活跃版本一行的【最大】字节数。
  618. func (table *Table) calcRowDataSize() int {
  619. return table.rowDataSizeForVersion(table.activeVersion)
  620. }
  621. // rowDataSizeForVersion 计算指定 schema 版本一行的【最大】字节数。
  622. //
  623. // 【v9 起语义变化】行按内容变长, 这个值只是上界 (每列都取满档位宽度时的长度), 不再是
  624. // "该版本行的实际长度"。它只用于"按行分配缓冲"的场景 (见 maxRowDataSize): 拿它当实际
  625. // 行长去做闸门判断, 会把本来装得下的行误判为超长。
  626. //
  627. // 上界 = schemaVer(1) + Σ 各列档位宽度 + 列结束偏移表(2N) + NULL 位图(⌈N/8⌉)。
  628. // 列 0 的 8 字节本身就在 Σ 里 (它是真实列, 不是额外前缀)。
  629. func (table *Table) rowDataSizeForVersion(ver byte) int {
  630. size := 1 // schemaVer
  631. sv := table.getVersion(ver)
  632. if sv != nil {
  633. n := len(sv.FieldIndices)
  634. size += types.NullBitmapLen(n)
  635. size += types.RowOffTableLen(n)
  636. for _, idx := range sv.FieldIndices {
  637. size += int(table.Field[idx].Width())
  638. }
  639. }
  640. return size
  641. }
  642. // minRowDataSizeForVersion 该版本一行的【最小】字节数 (列 0 之外全部为 NULL 时)。
  643. //
  644. // 列 0 恒占其档位宽度 W0 (偏移表基址恒为 1+W0, 这是解析与"列 0 定长"的前提), 所以最小值
  645. // 就是表头 (schemaVer + 列 0 + 偏移表) 加位图。
  646. func (table *Table) minRowDataSizeForVersion(ver byte) int {
  647. sv := table.getVersion(ver)
  648. if sv == nil {
  649. return 1
  650. }
  651. n := len(sv.FieldIndices)
  652. return types.RowHeaderSize(n, table.svCol0Width(sv)) + types.NullBitmapLen(n)
  653. }
  654. // maxRowDataSize 返回所有已知 schema 版本中最大的行数据大小。
  655. //
  656. // 给"按行分配缓冲"的调用方使用 (如删除路径的行复用池): info.RowSize 只反映最近
  657. // 一次落盘时的 schema, AddField/DropField 之后会偏小, 沿用它会 slice 越界 panic。
  658. func (table *Table) maxRowDataSize() int {
  659. maxSize := table.rowDataSizeForVersion(table.activeVersion)
  660. for i := range table.versions {
  661. if n := table.rowDataSizeForVersion(table.versions[i].Version); n > maxSize {
  662. maxSize = n
  663. }
  664. }
  665. return maxSize
  666. }
  667. // syncRowSize 让 info.RowSize 反映所有 schema 版本中最宽的一行
  668. // (语义同 types.CalcRowSize: Σ 字段字节数, 不含 schemaVer、NULL 位图与 pkID 前缀)。
  669. // 仅在 schema 变更时调用。
  670. func (table *Table) syncRowSize() {
  671. // 前缀 = schemaVer(1B) + 该版本的 NULL 位图。位图长度按活跃版本算 —— RowSize 本就
  672. // 只是"字段宽度之和"的估计值 (删除/分页路径都明确不用它, 见 deleter.go 里的说明),
  673. // 不必为跨版本差一格去逐版本精确。
  674. prefix := 1
  675. if sv := table.getVersion(table.activeVersion); sv != nil {
  676. prefix += types.NullBitmapLen(len(sv.FieldIndices))
  677. }
  678. if size := table.maxRowDataSize() - prefix; size > 0 && size <= 0xFFFF {
  679. table.info.RowSize = uint16(size)
  680. }
  681. }
  682. // pickVersionForUpdate 决定更新后按哪一版 schema 落盘。
  683. //
  684. // 惰性演进语义:
  685. // - 本次要写的字段在旧行自身的版本里都存在 → 保持旧版本。列序与偏移表大小都不变,
  686. // 不会因为"只改了一个老字段"而无谓地把行升级并搬迁。
  687. // 注意这不保证【行长】不变: v9 字符串列按实际长度存, 换一个更长的值就会让行变长,
  688. // 由索引层负责"原地 → 页内重建 → 换页"三级兜底。
  689. // - values 触及了旧版本没有的字段 (AddField 之后新增的) → 升级到当前版本;
  690. // 此时列数变化 (偏移表也变), 由索引层负责"页内重建 / 换页"。
  691. func (table *Table) pickVersionForUpdate(oldRowData []byte, values []types.FieldValue) byte {
  692. if len(oldRowData) < 1 {
  693. return table.activeVersion
  694. }
  695. oldVer := oldRowData[0]
  696. if oldVer == table.activeVersion {
  697. return oldVer
  698. }
  699. sv := table.getVersion(oldVer)
  700. if sv == nil {
  701. // 旧版本元信息缺失, 只能按当前版本重写
  702. return table.activeVersion
  703. }
  704. for _, fv := range values {
  705. idx := table.findFieldIndex(string(fv.FieldName))
  706. if idx < 0 {
  707. return table.activeVersion
  708. }
  709. inOldVer := false
  710. for _, i := range sv.FieldIndices {
  711. if int(i) == idx {
  712. inOldVer = true
  713. break
  714. }
  715. }
  716. if !inOldVer {
  717. return table.activeVersion
  718. }
  719. }
  720. return oldVer
  721. }
  722. // maxCompareInlineSize 字段比较时使用的栈缓冲上界 == 最宽的行内定长列。
  723. //
  724. // 取自 types.MaxInlineColumnWidth 而不是写死 256: 字符串列可以用宽度码声明到 2048 字节,
  725. // 缓冲若还停在 256, 宽字符串列的每次比较都会走堆分配。
  726. const maxCompareInlineSize = types.MaxInlineColumnWidth
  727. // ======================== 行内字段比较 ========================
  728. //
  729. // 三个比较函数都经由 compareFieldBytes 实现, 也就是"把条件值编码成与行内同源的字节,
  730. // 再按字节比较"。这样做的理由:
  731. // - 行内字节与索引键字节本就同源 (都出自 types.EncodeValue), 于是行级过滤与索引
  732. // 范围扫描必然给出同一个结论, 不会出现"索引选出 5 行、过滤后剩 0 行"的自相矛盾;
  733. // - 新增类型不必再回来改这三个函数 (以前每个类型要在这里各写 3 遍, 漏改一处就是静默漏行)。
  734. //
  735. // 与旧的"解码成 Go 值再比"相比, 唯一的行为差异是 NaN: 按字节比时 NaN == NaN 成立,
  736. // 而 float 的 == 不成立。索引键本来就是按字节比的, 统一到字节序反而消除了
  737. // "全表扫和索引给出不同答案"的隐患。
  738. // compareFieldBytes 比较行内字段与给定值, 返回 (cmp, ok); ok=false 表示无法比较。
  739. //
  740. // ct 必须是【列的实际存储类型 + 列级参数】(调用方经 compareColTypeFor 取, 与索引路径
  741. // 编键同源), 否则编码出来的字节与行内不同源, 比较结果没有意义。
  742. // ok=false 的来源: 字段越界、动态列解引用失败、值无法按该类型编码。
  743. // 前两种按"不匹配"处理是正确的; 第三种是调用方 bug —— 索引路径会显式报错
  744. // (见 encodeIndexKeySafe), 行级过滤按不匹配处理, 至少不会污染数据。
  745. // rowFieldForCompare 定位列并返回【可直接比较】的字节。
  746. //
  747. // 【为什么必须补齐到档位宽度】行内在 v9 之后存的是实际长度, 而条件值与索引键走的仍是
  748. // 定长档位编码 (EncodeValue 的 buf 长度恒为 FieldSize)。两侧若不补齐就不是同一份编码:
  749. // B+ 树按定长拼键比较、行级过滤按变长比较, 同一条件会得出不同结论 (表现为"索引选出
  750. // 5 行, 过滤后剩 0 行")。补齐用零字节, 与 v8 的定长存储逐字节相同, 因此比较结果与 v8
  751. // 完全一致; 索引键那条路径也按同一规则补齐, 两条路径始终同源。
  752. //
  753. // 动态列【不补齐】: 它的内容是 6B 堆引用, 补齐毫无意义, 交由 compareFieldBytes 解引用。
  754. //
  755. // 【值级溢出同理】字符串列一旦在【这一行】被溢出, 行内就是 6B 堆引用而不是内容。
  756. // 是否溢出必须看行内 tag (RowFieldRefTagged), 不能看列类型 —— 值级溢出是逐行的,
  757. // 同一列可能"这行内联、那行溢出"。漏了这步就会把页号/偏移当成字符串内容去比,
  758. // 症状是"溢出发生后, 对未建索引的宽字符串列过滤开始静默漏行"。
  759. func (table *Table) rowFieldForCompare(row []byte, masterFieldIdx int, ct colType) ([]byte, bool) {
  760. start, end, ok := table.getVersionedRange(row, masterFieldIdx)
  761. if !ok {
  762. return nil, false
  763. }
  764. seg := row[start:end]
  765. if table.columnRefTagged(row, masterFieldIdx) {
  766. if types.IsDynamic(ct.typ) {
  767. return seg, true // 动态列: 保持既有契约, 交 compareFieldBytes 解引用
  768. }
  769. // 被值级溢出的字符串列: 解引用拿回内容, 再按档位补齐 → 与索引键同源。
  770. data, ok := table.derefHeapSeg(seg)
  771. if !ok {
  772. data = nil // pageID == 0 或读取失败 → 空内容
  773. }
  774. return types.PadToWidth(data, ct.width()), true
  775. }
  776. if types.IsDynamic(ct.typ) {
  777. return seg, true
  778. }
  779. return types.PadToWidth(seg, ct.width()), true
  780. }
  781. // columnRefTagged 判断【这一行】的 masterFieldIdx 列在行内是不是 6B 堆引用:
  782. // 动态列恒是; 字符串列看 RowFieldRefTagged 的 tag 位 —— 该实例被值级溢出了。
  783. func (table *Table) columnRefTagged(rowData []byte, masterFieldIdx int) bool {
  784. if len(rowData) < 1 {
  785. return false
  786. }
  787. sv := table.getVersion(rowData[0])
  788. if sv == nil {
  789. return false
  790. }
  791. col0Width := table.svCol0Width(sv)
  792. offTableBase := types.RowOffTableBase(col0Width)
  793. for i, idx := range sv.FieldIndices {
  794. if int(idx) != masterFieldIdx || int(idx) >= len(table.Field) {
  795. continue
  796. }
  797. if types.IsDynamic(table.Field[idx].FieldType) {
  798. return true
  799. }
  800. if i == 0 {
  801. return false // 列 0 恒内联 (偏移表基址依赖它, 不参与溢出)
  802. }
  803. if offTableBase+2*i >= len(rowData) {
  804. return false
  805. }
  806. return types.RowFieldRefTagged(rowData, i, col0Width)
  807. }
  808. return false
  809. }
  810. // derefHeapSeg 把行内 6B 堆引用 [PageID 4B][Offset 2B] 解引用成内容。
  811. // pageID == 0 (空引用) 或读取失败返回 ok=false, 调用方按"空内容"处理。
  812. func (table *Table) derefHeapSeg(seg []byte) ([]byte, bool) {
  813. if len(seg) < types.DynamicRefSize {
  814. return nil, false
  815. }
  816. pageID := binary.BigEndian.Uint32(seg[:4])
  817. if pageID == 0 {
  818. return nil, false
  819. }
  820. data, err := table.heapRead(pageID, binary.BigEndian.Uint16(seg[4:6]))
  821. if err != nil {
  822. return nil, false
  823. }
  824. return data, true
  825. }
  826. func (table *Table) compareFieldBytes(seg []byte, ct colType, value interface{}) (int, bool) {
  827. if types.IsDynamic(ct.typ) {
  828. if len(seg) < types.DynamicRefSize {
  829. return 0, false
  830. }
  831. pageID := binary.BigEndian.Uint32(seg[:4])
  832. offInPage := binary.BigEndian.Uint16(seg[4:6])
  833. stored, err := table.heapRead(pageID, offInPage)
  834. if err != nil {
  835. return 0, false
  836. }
  837. switch v := value.(type) {
  838. case []byte:
  839. return bytes.Compare(stored, v), true
  840. case string:
  841. return bytes.Compare(stored, []byte(v)), true
  842. }
  843. return 0, false
  844. }
  845. size := int(ct.width())
  846. if size == 0 || len(seg) != size {
  847. // 长度必须正好等于档位宽度: 调用方 (rowFieldForCompare) 已按档位补齐, 不齐说明列
  848. // 参数非法或行已损坏 —— 这时按"不匹配"处理, 绝不拿错位字节去比。
  849. return 0, false
  850. }
  851. stored := seg
  852. // decimal 的 scale 存在行内, 条件值的 scale 可能与行内不同 (1.5 与 1.50 的系数
  853. // 分别是 15 和 150), 系数直接 memcmp 会得出错误结论 → 走数值比较。
  854. if ct.typ == types.FieldType_Decimal {
  855. return table.compareDecimalBytes(stored, value)
  856. }
  857. // 编码到栈缓冲再比较 (最宽列 2048 字节; 更宽的类型退化为堆分配, 不 panic)
  858. var stack [maxCompareInlineSize]byte
  859. var buf []byte
  860. if size <= len(stack) {
  861. buf = stack[:size]
  862. } else {
  863. buf = make([]byte, size)
  864. }
  865. // param 必须一起传: 字符串列的宽度码决定 EncodeValue 期望的 buf 长度,
  866. // 传 0 会让"按类型默认 256 编"与"按声明 2048 存"对不上 → 该列静默全不匹配。
  867. if err := types.EncodeValue(ct.typ, ct.param, value, buf); err != nil {
  868. return 0, false
  869. }
  870. return bytes.Compare(stored, buf), true
  871. }
  872. // compareDecimalBytes 比较行内 decimal 字节与给定值。
  873. //
  874. // 同 scale (同一列的常态) → 直接字节比较, 与索引键行为一致;
  875. // 不同 scale (条件值写成 1.5 而列是 DECIMAL(18,2)) → 退回数值比较, 保证语义正确。
  876. func (table *Table) compareDecimalBytes(stored []byte, value interface{}) (int, bool) {
  877. d, err := types.DecimalFromValue(value)
  878. if err != nil {
  879. return 0, false
  880. }
  881. if d.Scale() == types.DecimalScaleOfBytes(stored) {
  882. var buf [types.DecimalSize]byte
  883. if err := types.EncodeValue(types.FieldType_Decimal, byte(d.Scale()), d, buf[:]); err != nil {
  884. return 0, false
  885. }
  886. return bytes.Compare(stored, buf[:]), true
  887. }
  888. return types.DecimalFromBytes(stored).Cmp(d), true
  889. }
  890. // colType 一列"编码/比较所需的全部类型信息": 类型值 + 列级参数 (scale / 宽度码)。
  891. //
  892. // 打包成一个值传递, 而不是把 typ 与 param 拆成两个形参: 两者必须一起走 —— 只传类型
  893. // 会出现"按 256 字节编、按 2048 字节存"这类不一致, 而它表现为整列静默不匹配或整行
  894. // 错位, 都不是编译错误。打包后"忘了带参数"这件事在签名上就写不出来。
  895. type colType struct {
  896. typ types.FieldType
  897. param byte
  898. }
  899. // width 该列的行内宽度 (字节); 0 表示参数非法。
  900. func (c colType) width() uint16 { return types.FieldSize(c.typ, c.param) }
  901. // compareColTypeFor 取出行级过滤比较要用的列信息 —— 以【列的实际存储类型与参数】为准。
  902. //
  903. // 为什么不能沿用条件里声明的 Type:
  904. // - 索引路径编键用的是列定义 (见 query.go buildKeyLayout / planIndex:
  905. // q.table.Field[..].FieldType 与 fieldParam), 行级过滤若用声明类型, 两条路径就会
  906. // 分叉: 同一列带索引时"声明错类型"仍能命中 (索引键按列定义编), 不带索引时却按
  907. // 声明类型编出另一种字节 → 静默返回 0 行, 看起来像数据不存在。
  908. // - 声明类型因此降级为"值怎么从 interface{} 取出来"的提示: EncodeValue 按 Go
  909. // 值的 kind 分派, 本身并不依赖它。
  910. //
  911. // 列级参数 (字符串宽度码) 同理必须一起取: 宽度决定了"行内那几字节从哪切、切多长",
  912. // 与是否走索引无关。
  913. //
  914. // 删除标记已迁到 FieldInfo.Flag, FieldType 本身不再混入状态位, 因此这里直接返回
  915. // 即可 —— 旧实现在此清 0x80, 留着标记位会让 IsDynamic 查不到注册项: 动态列会被
  916. // 当成定长字段, 拿 6 字节堆引用去和值做字节比较。
  917. //
  918. // 拿不到字段时返回零值 (未注册类型 + 参数 0), compareFieldBytes 判 width()==0 后按
  919. // 不匹配处理 —— 调用方已先判过 fieldIdx < 0, 这里只是兜底。
  920. func (table *Table) compareColTypeFor(fieldIdx int) colType {
  921. if fieldIdx < 0 || fieldIdx >= len(table.Field) {
  922. return colType{}
  923. }
  924. return colType{table.Field[fieldIdx].FieldType, table.Field[fieldIdx].Param()}
  925. }
  926. // compareValue 比较字段值是否相等 (seg 由 rowFieldForCompare 定位并补齐)
  927. func (table *Table) compareValue(seg []byte, ct colType, value interface{}) bool {
  928. c, ok := table.compareFieldBytes(seg, ct, value)
  929. return ok && c == 0
  930. }
  931. // compareGreater 比较字段值是否大于给定值
  932. func (table *Table) compareGreater(seg []byte, ct colType, value interface{}) bool {
  933. c, ok := table.compareFieldBytes(seg, ct, value)
  934. return ok && c > 0
  935. }
  936. // compareLess 比较字段值是否小于给定值
  937. func (table *Table) compareLess(seg []byte, ct colType, value interface{}) bool {
  938. c, ok := table.compareFieldBytes(seg, ct, value)
  939. return ok && c < 0
  940. }
  941. // compareGreaterOrEqual 比较字段值是否大于等于给定值
  942. func (table *Table) compareGreaterOrEqual(seg []byte, ct colType, value interface{}) bool {
  943. return table.compareGreater(seg, ct, value) || table.compareValue(seg, ct, value)
  944. }
  945. // compareLessOrEqual 比较字段值是否小于等于给定值
  946. func (table *Table) compareLessOrEqual(seg []byte, ct colType, value interface{}) bool {
  947. return table.compareLess(seg, ct, value) || table.compareValue(seg, ct, value)
  948. }
  949. func (table *Table) GetFieldCount() byte { return table.info.FieldCount }
  950. func (table *Table) GetActiveFieldCount() byte { return table.info.ActiveFieldCount }
  951. // GetActiveFieldNames 返回当前活跃版本中包含的所有字段名 (不含已删除)
  952. func (table *Table) GetActiveFieldNames() []string {
  953. sv := table.getVersion(table.activeVersion)
  954. if sv == nil {
  955. return nil
  956. }
  957. names := make([]string, 0, len(sv.FieldIndices))
  958. for _, idx := range sv.FieldIndices {
  959. if table.Field[idx].IsDropped() {
  960. continue // 跳过已删除
  961. }
  962. names = append(names, string(table.Field[idx].FieldName))
  963. }
  964. return names
  965. }
  966. // GetActiveFieldType 返回指定字段的 FieldType (基于 master registry)
  967. func (table *Table) GetActiveFieldType(name string) (types.FieldType, bool) {
  968. for _, f := range table.Field {
  969. if string(f.FieldName) == name && !f.IsDropped() {
  970. return f.FieldType, true
  971. }
  972. }
  973. return 0, false
  974. }
  975. // GetActiveFieldFlag 返回指定字段的列级标志 (含 Decimal 的 scale 与可空位)。
  976. //
  977. // 与 GetActiveFieldType 配对: 类型一致但 scale 不一致是"同列不同 scale"这种会被
  978. // 静默破坏保序前提的情况, 调用方 (ORM 重开表时) 必须能读到它来做一致性校验。
  979. func (table *Table) GetActiveFieldFlag(name string) (byte, bool) {
  980. for i := range table.Field {
  981. f := &table.Field[i]
  982. if string(f.FieldName) == name && !f.IsDropped() {
  983. return f.Flag, true
  984. }
  985. }
  986. return 0, false
  987. }
  988. func (table *Table) GetFieldAllocateSize() uint16 { return table.info.FieldAllocateSize }
  989. func (table *Table) GetFirstData() uint32 { return table.info.FirstData }
  990. // GetSchemaExtRef 返回本表首个 schema 续块的位置 (pageID, off); pageID == 0 表示无续块
  991. func (table *Table) GetSchemaExtRef() (uint32, uint16) {
  992. return table.info.ExtPage, table.info.ExtOff
  993. }
  994. func (table *Table) GetFields() []types.FieldInfo {
  995. return table.Field
  996. }
  997. func (table *Table) findFieldByName(name []byte) *types.FieldInfo {
  998. target := util.BytesToString(name)
  999. for i := range table.Field {
  1000. if util.BytesToString(table.Field[i].FieldName) == target {
  1001. return &table.Field[i]
  1002. }
  1003. }
  1004. return nil
  1005. }
  1006. // serializeSchema 将当前 schema 序列化为字节流
  1007. //
  1008. // master field 每项: [type(1B)][flag(1B)][nameLen(1B)][name...]
  1009. // flag 落盘是必须的: decimal 列的 scale 一旦丢失, 读取侧就只能信任行内的 scale,
  1010. // 于是"同一列出现两种 scale"这种破坏保序的情况再也无法被发现。删除/可空两个状态位
  1011. // 同样必须随 schema 落盘, 否则重开库后被 DropField 的列会悄悄"复活"。
  1012. func (table *Table) serializeSchema() []byte {
  1013. var data []byte
  1014. data = append(data, table.activeVersion)
  1015. data = append(data, byte(len(table.Field)))
  1016. for _, f := range table.Field {
  1017. data = append(data, byte(f.FieldType))
  1018. data = append(data, f.Flag)
  1019. data = append(data, byte(len(f.FieldName)))
  1020. data = append(data, f.FieldName...)
  1021. }
  1022. data = append(data, byte(len(table.versions)))
  1023. for _, sv := range table.versions {
  1024. data = append(data, sv.Version)
  1025. data = append(data, byte(len(sv.FieldIndices)))
  1026. data = append(data, sv.FieldIndices...)
  1027. }
  1028. return data
  1029. }
  1030. // writeSchema 整条重写本表的 schema 定义
  1031. //
  1032. // 布局:
  1033. // 1. 内联块 —— 表页内, 紧跟 TableInfo, 固定 FieldAllocateSize 字节, **无跳转槽**。
  1034. // 建表时按 len(serializeSchema()) 精确分配 (上限 MaxInlineSchemaSize)。
  1035. // 2. 续块 —— schema 超出内联块的部分, 写入「全库共享」的 schema 扩展页,
  1036. // 每块格式 [SchemaFragHdr 8B][payload], 多块由 hdr.NextPage/NextOff 串成链。
  1037. // 本表首个续块的位置记录在 TableInfo.ExtPage/ExtOff。
  1038. //
  1039. // 续块只追加、不单独释放: 每次都是整条重写, 旧续块立刻成为垃圾, 由
  1040. // SchemaExtAllocator 在尾页放不下时做「满页压缩重排」统一回收。因此这里
  1041. // 不再需要任何引用计数 —— 这也正是旧实现最容易出错的地方。
  1042. func (table *Table) writeSchema() error {
  1043. data := table.serializeSchema()
  1044. alloc := int(table.info.FieldAllocateSize) // 内联块容量 (可能大于 schema 实际长度)
  1045. inline := len(data)
  1046. if inline > alloc {
  1047. inline = alloc // 容错: schema 超出内联块, 余下部分走续块
  1048. }
  1049. rest := data[inline:]
  1050. // 1. 先分配续块。可能触发压缩重排 —— 那会改写(可能同页的)其他表的 TableInfo,
  1051. // 所以内联块的写入必须放到分配之后再做, 否则会拿旧页缓冲覆盖掉这些更新。
  1052. var frags []schemaFrag
  1053. if len(rest) > 0 {
  1054. if table.schemaExt == nil {
  1055. return ErrFieldSpaceNotEnough
  1056. }
  1057. var err error
  1058. frags, err = table.schemaExt.AllocChain(len(rest))
  1059. if err != nil {
  1060. return err
  1061. }
  1062. }
  1063. // 2. 写续块
  1064. if len(frags) > 0 {
  1065. if err := table.writeFrags(frags, rest); err != nil {
  1066. return err
  1067. }
  1068. }
  1069. // 3. 写内联块 (重新读页, 见上面第 1 步的说明)
  1070. buf := make([]byte, PageSize)
  1071. if err := table.handle.ReadPage(table.tablePageID, buf); err != nil {
  1072. return err
  1073. }
  1074. start := int(table.tableStartOffset + uint32(unsafe.Sizeof(*table.info)))
  1075. end := start + alloc
  1076. if end > len(buf) {
  1077. return ErrFieldSpaceNotEnough
  1078. }
  1079. // 清零整块(alloc 字节): 槽位可能大于 schema 实际长度(复用空洞/预分配),
  1080. // 多出来的部分是填充, 必须清零, 防止旧表的残留字节被后续误读
  1081. for i := start; i < end; i++ {
  1082. buf[i] = 0
  1083. }
  1084. copy(buf[start:end], data[:inline])
  1085. if err := table.handle.SavePage(table.tablePageID, buf); err != nil {
  1086. return err
  1087. }
  1088. // 4. 记录本表首个续块的位置
  1089. if len(frags) > 0 {
  1090. table.info.ExtPage = frags[0].PageID
  1091. table.info.ExtOff = uint16(frags[0].Off)
  1092. } else {
  1093. table.info.ExtPage = 0
  1094. table.info.ExtOff = 0
  1095. }
  1096. return nil
  1097. }
  1098. // writeSchemaExt 把本表 schema 的续块部分写进指定的扩展页分配器, 并落盘 TableInfo
  1099. // 专供 SchemaExtAllocator.Compact 压缩重排时调用。
  1100. func (table *Table) writeSchemaExt(alloc *SchemaExtAllocator) error {
  1101. data := table.serializeSchema()
  1102. inline := int(table.info.FieldAllocateSize)
  1103. if inline > len(data) {
  1104. inline = len(data)
  1105. }
  1106. rest := data[inline:]
  1107. if len(rest) == 0 {
  1108. table.info.ExtPage = 0
  1109. table.info.ExtOff = 0
  1110. table.saveTableInfo()
  1111. return nil
  1112. }
  1113. frags, err := alloc.AllocChain(len(rest))
  1114. if err != nil {
  1115. return err
  1116. }
  1117. if err := table.writeFrags(frags, rest); err != nil {
  1118. return err
  1119. }
  1120. table.info.ExtPage = frags[0].PageID
  1121. table.info.ExtOff = uint16(frags[0].Off)
  1122. table.saveTableInfo()
  1123. return nil
  1124. }
  1125. // writeFrags 把 rest 按 frags 描述的容量逐块写入扩展页, 并串好续块链
  1126. func (table *Table) writeFrags(frags []schemaFrag, rest []byte) error {
  1127. written := 0
  1128. for i := range frags {
  1129. f := frags[i]
  1130. buf := make([]byte, PageSize)
  1131. if err := table.handle.ReadPage(f.PageID, buf); err != nil {
  1132. return err
  1133. }
  1134. h := SchemaFragHdr{DataLen: f.Len}
  1135. if i+1 < len(frags) {
  1136. h.NextPage = frags[i+1].PageID
  1137. h.NextOff = uint16(frags[i+1].Off)
  1138. }
  1139. putSchemaFragHdr(buf, int(f.Off), h)
  1140. copy(buf[int(f.Off)+schemaFragHdrSize:], rest[written:written+int(f.Len)])
  1141. if err := table.handle.SavePage(f.PageID, buf); err != nil {
  1142. return err
  1143. }
  1144. written += int(f.Len)
  1145. }
  1146. return nil
  1147. }
  1148. // checkProspectiveRowSize 预演"把 newField 加进当前活跃字段集合之后"的行宽。
  1149. //
  1150. // 加列只会让行变宽, 而超宽的行写不进 DataPage (见 serializeRowInternal 的写行闸门),
  1151. // 所以必须在建列这一步就拒绝: 否则这张表从加列成功的那一刻起, 一条也写不进去 ——
  1152. // 而且是插数据时才暴露 (而且加列本身是"成功"的, 没有任何回滚线索)。
  1153. func (table *Table) checkProspectiveRowSize(newField types.FieldInfo, newMasterIdx int) error {
  1154. sv := table.getVersion(table.activeVersion)
  1155. ineligible := table.spillIneligibleFields()
  1156. probe := make([]types.FieldInfo, 0, len(table.Field)+1)
  1157. unspillable := make([]bool, 0, len(table.Field)+1)
  1158. if sv != nil {
  1159. for _, idx := range sv.FieldIndices {
  1160. if int(idx) >= len(table.Field) {
  1161. continue
  1162. }
  1163. probe = append(probe, table.Field[idx])
  1164. unspillable = append(unspillable, ineligible[idx])
  1165. }
  1166. }
  1167. // 新增/复活的这一列此刻通常还没有索引 (建索引是独立一步), 但复活的情形下它可能
  1168. // 已被某个仍活跃的索引引用 —— 那就同样不能再溢出, 按真实集合算。
  1169. probe = append(probe, newField)
  1170. if newMasterIdx >= 0 && newMasterIdx < len(ineligible) {
  1171. unspillable = append(unspillable, ineligible[newMasterIdx])
  1172. } else {
  1173. unspillable = append(unspillable, false)
  1174. }
  1175. if err := types.CheckRowSpillFloor(probe, unspillable); err != nil {
  1176. return fmt.Errorf("新增列 %q 后行宽超限: %w", newField.FieldName, err)
  1177. }
  1178. return nil
  1179. }
  1180. // spillIneligibleFields 返回"不能值级溢出的列"集合 (按 master registry 下标)。
  1181. //
  1182. // 建了索引的列必须留在行内: 索引键按行内字节 (补齐到档位) 拼, 一旦该列被溢出成 6B
  1183. // 堆引用, 拼出的键就是页号/偏移而不是值 —— 索引里挂着一个永远查不到的键。所以凡是
  1184. // 任何活跃索引的分量列, 都不参与值级溢出。
  1185. //
  1186. // 列 0 不在这里标记: 它本就恒占档位宽度 (偏移表基址依赖它, 见 types.CalcSpillFloor
  1187. // 的 live==0 分支), 任何情况下都不参与溢出。
  1188. func (table *Table) spillIneligibleFields() []bool {
  1189. n := len(table.Field)
  1190. if n == 0 {
  1191. return nil
  1192. }
  1193. out := make([]bool, n)
  1194. metas := table.indexMeta.metas
  1195. for i := range metas {
  1196. if metas[i].Deleted() {
  1197. continue
  1198. }
  1199. for _, fid := range metas[i].Fields() {
  1200. if int(fid) < n {
  1201. out[fid] = true
  1202. }
  1203. }
  1204. }
  1205. return out
  1206. }
  1207. // rowSpillFloorForVersion 计算某 schema 版本一行经【值级溢出】之后的最小可能长度。
  1208. //
  1209. // 它就是写入闸门 (serializeRowInternal 开头) 的判据: 下界超物理墙说明"把所有可溢出的
  1210. // 字符串列都溢出去也装不下", 不动堆永远救不回来 —— 且这个判断发生在动堆之前 (见那里
  1211. // 的说明)。下界按【不可溢出列: 建了索引的列 + 列 0 + 定长列全宽】+ 可溢出字符串 6B 计。
  1212. func (table *Table) rowSpillFloorForVersion(ver byte) int {
  1213. sv := table.getVersion(ver)
  1214. if sv == nil || len(sv.FieldIndices) == 0 {
  1215. return 1 // 只有 schemaVer 1 字节
  1216. }
  1217. ineligible := table.spillIneligibleFields()
  1218. fields := make([]types.FieldInfo, 0, len(sv.FieldIndices))
  1219. unspillable := make([]bool, 0, len(sv.FieldIndices))
  1220. for _, idx := range sv.FieldIndices {
  1221. if int(idx) >= len(table.Field) {
  1222. continue
  1223. }
  1224. fields = append(fields, table.Field[idx])
  1225. unspillable = append(unspillable, ineligible[idx])
  1226. }
  1227. return types.CalcSpillFloor(fields, unspillable)
  1228. }
  1229. // checkIndexSpillFloor 预演"把 fieldIDs 作为索引分量"之后, 活跃版本行的溢出下界。
  1230. //
  1231. // 分量列一旦建索引就不能再溢出, 下界随之抬升; 若抬过"一页两行"的配额 (MaxInlineRowData),
  1232. // 这张表从建索引成功起就再也压不进两行 —— 必须在建索引这一步拒绝 (ErrIndexRowTooWide),
  1233. // 而不是等某次插入悄悄把行撑到 8159..16332 之间、整页行为退化时才被发现。
  1234. func (table *Table) checkIndexSpillFloor(fieldIDs []byte) error {
  1235. sv := table.getVersion(table.activeVersion)
  1236. if sv == nil {
  1237. return nil
  1238. }
  1239. ineligible := table.spillIneligibleFields()
  1240. for _, fid := range fieldIDs {
  1241. if int(fid) < len(ineligible) {
  1242. ineligible[fid] = true
  1243. }
  1244. }
  1245. fields := make([]types.FieldInfo, 0, len(sv.FieldIndices))
  1246. unspillable := make([]bool, 0, len(sv.FieldIndices))
  1247. for _, idx := range sv.FieldIndices {
  1248. if int(idx) >= len(table.Field) {
  1249. continue
  1250. }
  1251. fields = append(fields, table.Field[idx])
  1252. unspillable = append(unspillable, ineligible[idx])
  1253. }
  1254. return types.CheckRowSpillFloor(fields, unspillable)
  1255. }
  1256. func (table *Table) AddField(newField types.FieldInfo) error {
  1257. // 列级参数必须先自洽: 宽度码/scale 越界会让该列宽度算成 0, 而 0 宽度会少算字节,
  1258. // 让其后每一列都从错位偏移解码 (见 types.FieldSize)。
  1259. if err := validateFieldsForSchema([]types.FieldInfo{newField}); err != nil {
  1260. return err
  1261. }
  1262. // 列数闸门与建表同一条 (见 checkFieldCount): master registry 只增不减
  1263. // (DropField 是软删除, 列仍留在 registry 里), 所以按"加完之后"的列数判断。
  1264. if err := checkFieldCount(len(table.Field) + 1); err != nil {
  1265. return err
  1266. }
  1267. // 版本号闸门必须在【任何改动之前】: 两个分支都会在最后新增一版, 版本耗尽时
  1268. // 若已经把 in-memory 状态改了一半再返回错误, 表就停在"改了一半"的不一致状态。
  1269. nextVer, err := table.nextSchemaVersion()
  1270. if err != nil {
  1271. return err
  1272. }
  1273. existing := table.findFieldByName(newField.FieldName)
  1274. if existing != nil {
  1275. // 字段存在且活跃 → 报错
  1276. if !existing.IsDropped() {
  1277. return ErrFieldExists
  1278. }
  1279. // 复活必须沿用原类型与列级参数。
  1280. //
  1281. // master registry 的类型与列级参数是【所有历史版本共用】的行偏移依据
  1282. // (见 getVersionedOffset / rowDataSizeForVersion)。旧实现是整字节覆盖
  1283. // (existing.FieldType = newField.FieldType), 看着像"恢复", 实际是"改掉":
  1284. // varchar(50) 的列复活成 varchar(500) 之后, 已经落盘的旧行仍按 224 字节排布,
  1285. // 却会被按 2048 字节解析 —— 该列之后的每一个字段都静默错位。
  1286. // 想改宽度只能换列名新建, 或者重建表 (DropField 本就只标记删除、保留原类型)。
  1287. if existing.FieldType != newField.FieldType || existing.Param() != newField.Param() {
  1288. return fmt.Errorf("%w: 列 %q 已被删除, 复活时不允许改变类型/宽度 "+
  1289. "(原 %s/参数 %d, 新 %s/参数 %d); 请改用新列名, 或重建该表",
  1290. ErrFieldExists, newField.FieldName,
  1291. types.TypeNameOf(existing.FieldType), existing.Param(),
  1292. types.TypeNameOf(newField.FieldType), newField.Param())
  1293. }
  1294. // 找到该字段在 master registry 中的索引 (复活后的 masterIdx): 行宽预演要靠它
  1295. // 判断这一列是否已被某个活跃索引引用 —— 已建索引的列不能再参与值级溢出。
  1296. revivedIdx := -1
  1297. for i := range table.Field {
  1298. if &table.Field[i] == existing {
  1299. revivedIdx = i
  1300. break
  1301. }
  1302. }
  1303. // 复活同样会让行变宽, 先做行宽预演
  1304. if err := table.checkProspectiveRowSize(newField, revivedIdx); err != nil {
  1305. return err
  1306. }
  1307. // 只恢复"存活 + 可空/唯一"这些标志位, 低 5 位的列级参数保持原值
  1308. existing.Flag = (existing.Flag & types.FlagScaleMask) | (newField.Flag &^ types.FlagScaleMask)
  1309. table.info.ActiveFieldCount++
  1310. // 构建新版本: 旧版本字段 + 复活的字段索引
  1311. sv := table.getVersion(table.activeVersion)
  1312. newIndices := make([]byte, 0, len(sv.FieldIndices)+1)
  1313. if sv != nil {
  1314. newIndices = append(newIndices, sv.FieldIndices...)
  1315. }
  1316. newIndices = append(newIndices, byte(revivedIdx))
  1317. table.activeVersion = nextVer
  1318. table.versions = append(table.versions, SchemaVersion{
  1319. Version: nextVer,
  1320. FieldIndices: newIndices,
  1321. })
  1322. if err := table.writeSchema(); err != nil {
  1323. // 回滚内存态: 写盘失败不能让内存 schema 与磁盘分叉 —— 后续写入会按
  1324. // 新 schemaVer 落盘, 重开后该版本不可解析 (整行静默丢失)。
  1325. existing.Flag |= types.FlagDeleted
  1326. table.info.ActiveFieldCount--
  1327. table.activeVersion = sv.Version
  1328. table.versions = table.versions[:len(table.versions)-1]
  1329. return err
  1330. }
  1331. table.syncRowSize()
  1332. table.saveTableInfo()
  1333. return nil
  1334. }
  1335. // 全新字段: 同样先做行宽预演 (加列只会让行更宽)
  1336. if err := table.checkProspectiveRowSize(newField, -1); err != nil {
  1337. return err
  1338. }
  1339. // 追加到 master registry
  1340. table.Field = append(table.Field, newField)
  1341. table.info.FieldCount++
  1342. table.info.ActiveFieldCount++
  1343. // 构建新版本: 旧版本字段 + 新字段索引
  1344. sv := table.getVersion(table.activeVersion)
  1345. newIndices := make([]byte, 0, len(sv.FieldIndices)+1)
  1346. if sv != nil {
  1347. newIndices = append(newIndices, sv.FieldIndices...)
  1348. }
  1349. newIndices = append(newIndices, byte(len(table.Field)-1))
  1350. table.activeVersion = nextVer
  1351. table.versions = append(table.versions, SchemaVersion{
  1352. Version: nextVer,
  1353. FieldIndices: newIndices,
  1354. })
  1355. if err := table.writeSchema(); err != nil {
  1356. // 回滚内存态 (理由同复活分支): Field/registry/版本全部撤回到改动前,
  1357. // 否则内存 schema 与磁盘分叉, 后续写入按新 schemaVer 落盘的行重开后不可解析。
  1358. table.Field = table.Field[:len(table.Field)-1]
  1359. table.info.FieldCount--
  1360. table.info.ActiveFieldCount--
  1361. table.activeVersion = sv.Version
  1362. table.versions = table.versions[:len(table.versions)-1]
  1363. return err
  1364. }
  1365. table.syncRowSize()
  1366. table.saveTableInfo()
  1367. return nil
  1368. }
  1369. // tableInfoBufPool 复用 saveTableInfo 的整页读缓冲。
  1370. // saveTableInfo 在【每次行写入】后都会被调用来同步 TotalRows/RowSize 等元信息,
  1371. // 旧实现每行 make 一个 16KB —— 灌数时它一个函数就贡献 ~5% 的总分配量。
  1372. // 缓冲只在函数内使用 (ReadPage 拷入 / SavePage 拷出), 不会被任何路径持有。
  1373. var tableInfoBufPool = sync.Pool{New: func() any { b := make([]byte, PageSize); return &b }}
  1374. func (table *Table) saveTableInfo() {
  1375. bp := tableInfoBufPool.Get().(*[]byte)
  1376. buf := *bp
  1377. defer tableInfoBufPool.Put(bp)
  1378. if err := table.handle.ReadPage(table.tablePageID, buf); err != nil {
  1379. return
  1380. }
  1381. info := (*TableInfo)(unsafe.Pointer(&buf[table.tableStartOffset]))
  1382. info.ExtPage = table.info.ExtPage
  1383. info.ExtOff = table.info.ExtOff
  1384. info.FieldCount = table.info.FieldCount
  1385. info.ActiveFieldCount = table.info.ActiveFieldCount
  1386. info.TotalRows = table.info.TotalRows
  1387. // 行宽随 schema 演进变化, 必须落盘同步 (否则删除路径按旧 RowSize 分配缓冲会越界)
  1388. info.RowSize = table.info.RowSize
  1389. // 动态字段堆页链 (分配新堆页时才会变化)
  1390. info.FirstHeap = table.info.FirstHeap
  1391. info.LastHeap = table.info.LastHeap
  1392. info.HeapPageCount = table.info.HeapPageCount
  1393. table.handle.SavePage(table.tablePageID, buf)
  1394. }
  1395. // =============================================================================
  1396. // 删除字段(软删除 + 新版本)
  1397. // =============================================================================
  1398. func (table *Table) DropField(fieldName string) error {
  1399. // 在 master registry 中查找
  1400. masterIdx := -1
  1401. for i, f := range table.Field {
  1402. if f.IsDropped() {
  1403. continue // 已删除
  1404. }
  1405. if string(f.FieldName) == fieldName {
  1406. masterIdx = i
  1407. break
  1408. }
  1409. }
  1410. if masterIdx < 0 {
  1411. return ErrFieldNotFound
  1412. }
  1413. // 守卫: 行存储索引 (主键/时序) 的分量列不允许删除 —— 删了之后
  1414. // extractIndexKey 提不出键, 新行不进任何索引 (插入"成功"但行静默丢失,
  1415. // Count 虚高)。ORM 层的 syncSchema 本来就不动主键列, core 层补上同一约束。
  1416. if im := table.indexMeta; im != nil {
  1417. if rm, _, ok := im.rowStoreMeta(); ok && rm != nil {
  1418. for _, id := range rm.Fields() {
  1419. if int(id) == masterIdx {
  1420. return fmt.Errorf("%w: 列 %q 是行存储索引 %q 的分量, 不能删除 (需重建表)",
  1421. ErrFieldNotFound, fieldName, rm.Name())
  1422. }
  1423. }
  1424. }
  1425. }
  1426. // 版本号闸门必须在改动之前 (理由同 AddField): 删列也要新增一版 schema。
  1427. nextVer, err := table.nextSchemaVersion()
  1428. if err != nil {
  1429. return err
  1430. }
  1431. // 标记删除 (FieldType 本身不动: 它是"这一列是什么", 与存活状态无关)
  1432. table.Field[masterIdx].Flag |= types.FlagDeleted
  1433. table.info.ActiveFieldCount--
  1434. // 构建新版本: 跳过被删字段
  1435. sv := table.getVersion(table.activeVersion)
  1436. newIndices := make([]byte, 0, len(sv.FieldIndices))
  1437. if sv != nil {
  1438. for _, idx := range sv.FieldIndices {
  1439. if int(idx) != masterIdx {
  1440. newIndices = append(newIndices, idx)
  1441. }
  1442. }
  1443. }
  1444. table.activeVersion = nextVer
  1445. table.versions = append(table.versions, SchemaVersion{
  1446. Version: nextVer,
  1447. FieldIndices: newIndices,
  1448. })
  1449. if err := table.writeSchema(); err != nil {
  1450. // 回滚内存态 (理由同 AddField): 撤销删除标记与版本推进。
  1451. table.Field[masterIdx].Flag &^= types.FlagDeleted
  1452. table.info.ActiveFieldCount++
  1453. table.activeVersion = sv.Version
  1454. table.versions = table.versions[:len(table.versions)-1]
  1455. return err
  1456. }
  1457. table.syncRowSize()
  1458. table.saveTableInfo()
  1459. return nil
  1460. }
  1461. // ======================== 动态字段堆页存储 ========================
  1462. //
  1463. // 动态字段 (FieldType_Dynamicbyte: 声明宽度超过 MaxInlineColumnWidth(2048) 字节的
  1464. // 字符串 / []byte) 的行内只保存 6 字节引用: [PageID uint32 BE(4B)][Offset uint16 BE(2B)]
  1465. // - PageID == 0 → 空值 (NULL / 空串 / 空二进制)
  1466. // - 否则指向堆页 PageID 的物理偏移 Offset 处的数据块
  1467. // 堆页数据块格式: [Len uint32 BE][Payload...]
  1468. // 单块上限 = PageSize - PageHdrSize - dynamicBlockHdr (约 16KB)
  1469. // 堆页链: TableInfo.FirstHeap → ... → TableInfo.LastHeap
  1470. // 注意: 该链与 schema 续块所在的「共享扩展页」相互独立。
  1471. //
  1472. // 由于行内引用固定 6 字节, 动态字段变长/变短都不会改变行长,
  1473. // 因此更新走 DataPage 原地覆盖即可, 不会触发 errRowTooLarge。
  1474. const dynamicBlockHdr = 4 // 堆数据块的长度前缀大小
  1475. // MaxBlobSize 单个【动态列】(FieldType_Dynamicbyte) 值的大小上限 (字节)。
  1476. //
  1477. // 它不是拍出来的数, 而是从存储约束直接推出: 一个堆块必须整体落进一个堆页, 可用
  1478. // 载荷 = 页大小 - 页头 - 块长度前缀 (与 heapAllocBlock 的 maxPayload 同源)。
  1479. //
  1480. // 为什么单独列出来: 动态列是"变长文本/二进制"的落点 (源库里超宽的 varchar/text
  1481. // 都落这里), 使用者必须能在写入前就知道这条线, 而不是写进去才撞上 heapAllocBlock
  1482. // 里那句与"值太大"看不出因果的 ErrHeapPageTooLarge。写入侧的检查见 writeFieldValueAt。
  1483. //
  1484. // 【v10 起不适用于 FieldType_Blob】blob 列的行内是 8B BlobRef, 内容落在可跨页的链上,
  1485. // 单值上限是 BlobChainMax (~4GB), 不受这里约束 —— 名字里的 Blob 是历史遗留
  1486. // (v10 之前 Blob 列与动态列同布局, 所以共用这一条线)。
  1487. //
  1488. // 它同时是【声明侧】的容量上限 (声明超限的字符串列也落动态列, 容量同样是这个数):
  1489. // types.MaxStringSize 就是它, 由下面那行编译期断言钉住。
  1490. const MaxBlobSize = PageSize - page.PageHdrSize - dynamicBlockHdr
  1491. // 编译期断言: types 里那组【页面布局镜像】推出的两个上限, 必须与这里的物理布局一致
  1492. // (行宽那条因为要整除, 只钉到 ±1 字节 —— "它恰好是一页能放下的最大值"这个精确性质由
  1493. // core 的 TestMaxRowDataSizeFitsTwoRows 用真实的 PageSize / DataPageHdrSize 复核)。
  1494. // 两处一旦漂移, 后果不是报错而是"承诺了做不到的事":
  1495. // - MaxStringSize 对不上 MaxBlobSize → 允许声明一个写不进堆页的容量, 建表时通过、
  1496. // 某天写入大值时才撞 ErrHeapPageTooLarge;
  1497. // - MaxRowDataSize 对不上"一页两行" → 放进来一张两行装不进一页的表, 页内顺序扫描、
  1498. // 按 pkID 的有序插入、更新时的页内重建全部退化, 正是那条政策要避免的情形。
  1499. //
  1500. // 用"下标必须为 0"表达相等: 常量下标越界直接编译失败, 且表达式不占任何空间。改页大小 /
  1501. // 页头 / 块长度前缀时这里会先报错, 提醒同步 types 里那组镜像。
  1502. var (
  1503. _ = [1]struct{}{}[int(MaxBlobSize)-types.MaxStringSize]
  1504. _ = [1]struct{}{}[int(types.MaxRowDataSize)-int((PageSize-page.DataPageHdrSize-1)/2)]
  1505. )
  1506. // maxHeapRefCount 单个堆页的引用数防御上限 (RefCount 为 uint16, 容器上限 65535)。
  1507. //
  1508. // 注意: 这是纯防御性常量, 实际永远不会触发 —— 堆页块数的物理上限只有 3272
  1509. // (数据区 16364 字节 ÷ 最小块 5 字节), 远在 65535 之前空间就先耗尽, 所以
  1510. // heapAllocBlock 里的 `RefCount < maxHeapRefCount` 恒为真, 从不决定任何事。
  1511. // 真正保证计数不溢出的是同一行的空间检查。
  1512. //
  1513. // 它唯一的价值: 若将来放开"1 引用 = 1 块, 且块不共享"的前提 (块级去重、多行共用
  1514. // 一个堆块), 上限会变成"全库行数", 这道闸才真正开始工作。
  1515. const maxHeapRefCount = 1<<16 - 1
  1516. // heapAllocBlock 在堆页链中分配 need 字节数据空间, 返回承载页与页内物理偏移
  1517. func (table *Table) heapAllocBlock(need uint32) (*HeapPage, uint16, error) {
  1518. maxPayload := uint32(MaxBlobSize)
  1519. if need > maxPayload {
  1520. return nil, 0, ErrHeapPageTooLarge
  1521. }
  1522. total := need + dynamicBlockHdr
  1523. // 1. 尾页是当前唯一可写页: 堆链只往尾部追加, 新块一律落在链尾。
  1524. //
  1525. // 为什么不再从 FirstHeap 逐个扫"第一个有空间的页": 均匀定长插入下, 前面每一
  1526. // 页都会因剩余空间不足而被扫到且都放不下, 单次插入退化成 O(堆页数), 整体
  1527. // O(n²) —— 实测插 10 万行要十几分钟且内存顶不住。尾插把单次插入降到 O(1)。
  1528. //
  1529. // 代价: 旧尾页里不足一个块的零头不再被后续小块回填, 每页最多浪费「放不下的
  1530. // 那个块大小」以内。若尾页恰好是 heapUnref 清空复用的空页, 则它必然放得下
  1531. // 任何块(空页可用空间 >= 单块上限), 不会因此多开一张页。
  1532. var tail *HeapPage
  1533. appended := false // 本次是否新开了一张堆页 —— 决定 HeapPageCount 是否 +1
  1534. if table.info.LastHeap != 0 {
  1535. hp, err := LoadHeapPage(table.handle, table.info.LastHeap)
  1536. if err != nil {
  1537. return nil, 0, err
  1538. }
  1539. // 左半边恒为真 (见 maxHeapRefCount 的注释): 空间总是先于计数耗尽。
  1540. if hp.Info.Hdr.RefCount < maxHeapRefCount && PageSize-hp.Info.Hdr.UseSize >= total {
  1541. tail = hp
  1542. } else {
  1543. // 尾页放不下 → 在它后面接一张新页作为新尾
  1544. nhp := NewHeapPage(table.handle, page.HeapIndex)
  1545. hp.Info.Hdr.NextPage = nhp.Info.Hdr.PageID
  1546. nhp.Info.Hdr.PrePage = hp.Info.Hdr.PageID
  1547. if err := hp.Save(); err != nil {
  1548. return nil, 0, err
  1549. }
  1550. tail = nhp
  1551. appended = true
  1552. }
  1553. } else {
  1554. // 链为空 (旧库或已被清空): 新建首页
  1555. tail = NewHeapPage(table.handle, page.HeapIndex)
  1556. table.info.FirstHeap = tail.Info.Hdr.PageID
  1557. appended = true
  1558. }
  1559. table.info.LastHeap = tail.Info.Hdr.PageID
  1560. if appended {
  1561. // 堆链长度自增 —— DropTable 就是靠它不遍历也拿到回收页数
  1562. table.info.HeapPageCount++
  1563. // 只有新开堆页时 FirstHeap/LastHeap/HeapPageCount 才会变 —— 尾页直接分配
  1564. // 的常见路径什么都不用落盘, 省一次"读 16KB 页 + 写 16KB 页"的往返。
  1565. // (旧实现无条件 saveTableInfo, 每行动态列都白付这两趟拷贝。)
  1566. table.saveTableInfo()
  1567. }
  1568. _, off, err := tail.Alloc(total)
  1569. if err != nil {
  1570. return nil, 0, err
  1571. }
  1572. if off > 0xFFFF {
  1573. return nil, 0, ErrHeapPageTooLarge
  1574. }
  1575. return tail, uint16(off), nil
  1576. }
  1577. // heapWrite 分配堆空间并写入数据块, 返回行内引用 (pageID, offset)
  1578. func (table *Table) heapWrite(data []byte) (uint32, uint16, error) {
  1579. if len(data) == 0 {
  1580. return 0, 0, nil // 空值: PageID = 0
  1581. }
  1582. hp, off, err := table.heapAllocBlock(uint32(len(data)))
  1583. if err != nil {
  1584. return 0, 0, err
  1585. }
  1586. di := int(off) - int(page.PageHdrSize)
  1587. if di < 0 || di+dynamicBlockHdr+len(data) > len(hp.Info.Data) {
  1588. return 0, 0, ErrPageRead
  1589. }
  1590. binary.BigEndian.PutUint32(hp.Info.Data[di:], uint32(len(data)))
  1591. copy(hp.Info.Data[di+dynamicBlockHdr:], data)
  1592. if err := hp.Save(); err != nil {
  1593. return 0, 0, err
  1594. }
  1595. return hp.Info.Hdr.PageID, off, nil
  1596. }
  1597. // heapRead 读取行内引用指向的堆数据; PageID == 0 表示空值
  1598. func (table *Table) heapRead(pageID uint32, off uint16) ([]byte, error) {
  1599. if pageID == 0 {
  1600. return nil, nil
  1601. }
  1602. hp, err := LoadHeapPage(table.handle, pageID)
  1603. if err != nil {
  1604. return nil, err
  1605. }
  1606. di := int(off) - int(page.PageHdrSize)
  1607. if di < 0 || di+dynamicBlockHdr > len(hp.Info.Data) {
  1608. return nil, ErrPageRead
  1609. }
  1610. n := int(binary.BigEndian.Uint32(hp.Info.Data[di:]))
  1611. if n < 0 || di+dynamicBlockHdr+n > len(hp.Info.Data) {
  1612. return nil, ErrPageRead
  1613. }
  1614. out := make([]byte, n)
  1615. copy(out, hp.Info.Data[di+dynamicBlockHdr:di+dynamicBlockHdr+n])
  1616. return out, nil
  1617. }
  1618. // heapRewrite 尝试原地覆写已有堆块 (新数据不超过旧块容量时), 成功返回 true
  1619. // 用于更新场景: 复用旧块可避免每次变长更新都产生堆垃圾
  1620. func (table *Table) heapRewrite(pageID uint32, off uint16, data []byte) bool {
  1621. if pageID == 0 {
  1622. return false
  1623. }
  1624. hp, err := LoadHeapPage(table.handle, pageID)
  1625. if err != nil {
  1626. return false
  1627. }
  1628. di := int(off) - int(page.PageHdrSize)
  1629. if di < 0 || di+dynamicBlockHdr > len(hp.Info.Data) {
  1630. return false
  1631. }
  1632. oldLen := int(binary.BigEndian.Uint32(hp.Info.Data[di:]))
  1633. if oldLen < len(data) || di+dynamicBlockHdr+oldLen > len(hp.Info.Data) {
  1634. return false
  1635. }
  1636. binary.BigEndian.PutUint32(hp.Info.Data[di:], uint32(len(data)))
  1637. copy(hp.Info.Data[di+dynamicBlockHdr:], data)
  1638. return hp.Save() == nil
  1639. }
  1640. // heapUnref 释放一个堆页引用。计数归零后按"该页还有没有后继"分流:
  1641. //
  1642. // - 有后继 → 摘链, 整页交还空闲链表
  1643. // - 是链尾 → 作为堆尾保留, 但把页内空间清零复用
  1644. //
  1645. // 链尾为什么不能只是"原样保留": DecRef 是引用计数下降的唯一入口, 判定只在这一刻做。
  1646. // 若链尾归零后原样保留, 它的 UseSize 仍是满的; 之后新块放不下时新页会接在它后面,
  1647. // 它就变成"计数已归零的中间页" —— 而再没有任何人会来 DecRef 它, 于是 16KB
  1648. // 永久泄漏且永不复用。所以链尾归零时就把 UseSize 重置回页头之后
  1649. // (计数归零 ⟺ 页内没有活块, 这些空间全是垃圾), 让它退化成一张可用的空页。
  1650. func (table *Table) heapUnref(pageID uint32) error {
  1651. if pageID == 0 {
  1652. return nil
  1653. }
  1654. hp, err := LoadHeapPage(table.handle, pageID)
  1655. if err != nil {
  1656. return err
  1657. }
  1658. if !hp.Info.Hdr.DecRef() {
  1659. // 仍有活引用: 只把下降后的计数落盘
  1660. return hp.Save()
  1661. }
  1662. if hp.Info.Hdr.NextPage == 0 {
  1663. // 链尾: 保留作为堆尾, 但清空可用空间以便复用
  1664. hp.Info.Hdr.UseSize = page.PageHdrSize
  1665. return hp.Save()
  1666. }
  1667. return table.heapDropPage(hp)
  1668. }
  1669. // heapDropPage 把已无引用的堆页从双向链上摘除, 并登记回收
  1670. // 前置条件: 该页引用计数已归零
  1671. func (table *Table) heapDropPage(hp *HeapPage) error {
  1672. next := hp.Info.Hdr.NextPage
  1673. prev := hp.Info.Hdr.PrePage
  1674. // 上级指向下级
  1675. if prev != 0 {
  1676. p, err := LoadHeapPage(table.handle, prev)
  1677. if err != nil {
  1678. return err
  1679. }
  1680. p.Info.Hdr.NextPage = next
  1681. if err := p.Save(); err != nil {
  1682. return err
  1683. }
  1684. } else {
  1685. table.info.FirstHeap = next
  1686. }
  1687. // 下级指向上级
  1688. if next != 0 {
  1689. n, err := LoadHeapPage(table.handle, next)
  1690. if err != nil {
  1691. return err
  1692. }
  1693. n.Info.Hdr.PrePage = prev
  1694. if err := n.Save(); err != nil {
  1695. return err
  1696. }
  1697. } else {
  1698. table.info.LastHeap = prev
  1699. }
  1700. // 交还空闲链表 (页内容会被链表指针覆盖)。
  1701. // 走延迟回收: 事务中途直写盘会让"页已清零"先于 WAL 落盘, 回滚/崩溃后
  1702. // 堆链还指向这张空页 → 数据丢失。
  1703. table.deferFreePage(hp.Info.Hdr.PageID)
  1704. // 堆链长度自减 (与 heapAllocBlock 的 +1 配对, 保证 DropTable 时链长可信)
  1705. if table.info.HeapPageCount > 0 {
  1706. table.info.HeapPageCount--
  1707. }
  1708. table.saveTableInfo()
  1709. return nil
  1710. }
  1711. // deferFreePage 登记一个可回收页。有延迟回收回调时走"等提交后再还链",
  1712. // 否则退回立即回收 (例如 demo 里手搓但未提供该回调的 HandleDb)。
  1713. func (table *Table) deferFreePage(pageID uint32) {
  1714. if table.handle.DeferFreePage != nil {
  1715. table.handle.DeferFreePage(pageID)
  1716. return
  1717. }
  1718. table.handle.FreePage(pageID)
  1719. }
  1720. // oldHeapRefInfo 旧行在某列上持有的堆引用 (动态列或值级溢出列)。
  1721. type oldHeapRefInfo struct {
  1722. pid uint32
  1723. off uint16
  1724. }
  1725. // oldRowHeapRefs 解析旧行, 收集各列持有的堆引用, 返回 masterIdx → 引用;
  1726. // 旧行为空 / 版本未知 / 偏移表不可信时返回 nil。
  1727. //
  1728. // 【只认"动态列或带 RowVarTag 的列"是安全边界】值级溢出让同一字符串列
  1729. // "这行内联、那行溢出": 旧行该列若是内联内容, 位置上那 6 字节就是字符串本身 ——
  1730. // 当引用解会拿到垃圾页号, 后续 heapRewrite / heapUnref 会读写不相干的页 (静默破坏)。
  1731. // 所以绝不能只按偏移取 6 字节就当引用, 必须先看类型或偏移表项的 tag 位。
  1732. //
  1733. // 解析一次、整行共用: 更新路径每列都要查旧引用 (复用 / 释放), 逐列各解析一遍
  1734. // 是 O(n²), n 上限 254 时每行白付几万次偏移表扫描。
  1735. func (table *Table) oldRowHeapRefs(oldRowData []byte) map[int]oldHeapRefInfo {
  1736. if len(oldRowData) < 1 {
  1737. return nil
  1738. }
  1739. sv := table.getVersion(oldRowData[0])
  1740. if sv == nil || len(sv.FieldIndices) == 0 {
  1741. return nil
  1742. }
  1743. n := len(sv.FieldIndices)
  1744. col0Width := table.svCol0Width(sv)
  1745. ends, ok := types.RowParseOffsets(oldRowData, n, col0Width)
  1746. if !ok {
  1747. return nil
  1748. }
  1749. bitmap, ok := types.RowNullBitmap(oldRowData, ends, n)
  1750. if !ok {
  1751. return nil
  1752. }
  1753. var refs map[int]oldHeapRefInfo
  1754. for i, idx := range sv.FieldIndices {
  1755. f := table.Field[idx]
  1756. // NULL 列不持有堆块; 引用槽即便残留非零也不是本行的。
  1757. if f.IsDropped() || types.NullBitSet(bitmap, i) {
  1758. continue
  1759. }
  1760. if !types.IsDynamic(f.FieldType) && !types.RowFieldRefTagged(oldRowData, i, col0Width) {
  1761. continue // 内联内容, 不是引用
  1762. }
  1763. start := int(types.RowFieldStart(ends, i))
  1764. if start+types.DynamicRefSize > len(oldRowData) {
  1765. continue
  1766. }
  1767. pid := binary.BigEndian.Uint32(oldRowData[start : start+4])
  1768. if pid == 0 {
  1769. continue // 空引用 = 空内容
  1770. }
  1771. if refs == nil {
  1772. refs = make(map[int]oldHeapRefInfo)
  1773. }
  1774. refs[int(idx)] = oldHeapRefInfo{
  1775. pid: pid,
  1776. off: binary.BigEndian.Uint16(oldRowData[start+4 : start+6]),
  1777. }
  1778. }
  1779. return refs
  1780. }
  1781. // heapReleaseRow 释放一行占用的全部堆页引用 (删除行时调用)。
  1782. // 按行自身的 schema 版本 + 行内偏移表定位各动态字段 / 值级溢出字段的 6 字节引用。
  1783. //
  1784. // v10 起: 同时回收 blob 列的内联 8B BlobRef 引用的链页. 路径与堆块不同 ——
  1785. // heap 页走 heapUnref (RefCount), blob 链页走 BlobFree (整链挂回空闲链表).
  1786. // 两者必须分别处理, 不能合并.
  1787. func (table *Table) heapReleaseRow(rowData []byte) {
  1788. // 1. 堆块 (动态列 ∪ 值级溢出列): 删除只释放, 不复用 — 直接用 oldRowHeapRefs 判定.
  1789. if refs := table.oldRowHeapRefs(rowData); refs != nil {
  1790. for _, r := range refs {
  1791. _ = table.heapUnref(r.pid)
  1792. }
  1793. }
  1794. // 2. blob 链页: 按列扫描非空 BlobRef, 一一归还空闲链表. releaseRowBlobRefs 内部
  1795. // 错误"放任不中断"语义, 让一条 blob 释放失败不会把整批后续 blob 漏掉.
  1796. _ = table.releaseRowBlobRefs(rowData)
  1797. }
  1798. // writeHeapRefField 把 data 落堆, 并在行内 offset 处写入 6 字节引用。
  1799. //
  1800. // 动态列与【值级溢出的字符串列】共用这条路径 (盘上布局相同)。old 为旧行同列的堆引用
  1801. // (来自 oldRowHeapRefs): 能原地复用就复用, 换块后释放旧块; nil 表示旧行该列没有引用。
  1802. // checkOnly 为真时只占位 (行内引用保持全零), 不碰堆 —— 试算行可能被整丢弃,
  1803. // 绝不能有分配 / 覆写 / 释放堆块的副作用 (见 serializeRowForCheck)。
  1804. func (table *Table) writeHeapRefField(rowData []byte, offset uint16, data []byte, old *oldHeapRefInfo, checkOnly bool) error {
  1805. if checkOnly {
  1806. return nil
  1807. }
  1808. if len(data) == 0 {
  1809. // 新值为空: 行内引用保持全 0, 旧块引用需要释放
  1810. if old != nil {
  1811. _ = table.heapUnref(old.pid)
  1812. }
  1813. return nil
  1814. }
  1815. // 1. 尽量复用旧堆块 (原地覆写), 此时引用不变
  1816. if old != nil && table.heapRewrite(old.pid, old.off, data) {
  1817. binary.BigEndian.PutUint32(rowData[offset:], old.pid)
  1818. binary.BigEndian.PutUint16(rowData[offset+4:], old.off)
  1819. return nil
  1820. }
  1821. // 2. 分配新块 (新引用 +1)
  1822. pageID, off, err := table.heapWrite(data)
  1823. if err != nil {
  1824. return err
  1825. }
  1826. binary.BigEndian.PutUint32(rowData[offset:], pageID)
  1827. binary.BigEndian.PutUint16(rowData[offset+4:], off)
  1828. // 3. 旧块不再被本行引用 → 释放
  1829. // 放在分配之后, 避免刚摘链回收的页立刻又被分配造成抖动
  1830. if old != nil && !(old.pid == pageID && old.off == off) {
  1831. _ = table.heapUnref(old.pid)
  1832. }
  1833. return nil
  1834. }
  1835. // ======================== 行数据序列化/反序列化 ========================
  1836. // lookupFieldValue 在 FieldValue 切片中按字段名取值
  1837. func lookupFieldValue(values []types.FieldValue, name []byte) (interface{}, bool) {
  1838. for _, fv := range values {
  1839. // bytes.Equal 直接比较字节, 避免 string(x)==string(y) 触发两次 string 分配
  1840. if bytes.Equal(fv.FieldName, name) {
  1841. return fv.Value, true
  1842. }
  1843. }
  1844. return nil, false
  1845. }
  1846. // encodeFieldValue 把一列的值编码成行内字节, 不写缓冲: 字符串返回【实际长度】,
  1847. // 定长类型返回档位宽度的字节。布局阶段统一切片拷贝 (见 serializeRowInternal)。
  1848. //
  1849. // 形参是 FieldInfo 而不是 FieldType: 宽度与列级参数都必须取自【同一个列定义】——
  1850. // 拆成两个参数传 (类型来自这里、宽度码来自那里) 就会出现"按 256 字节编、按 2048 字节存"
  1851. // 这类不一致, 而它表现为整行错位而不是报错。
  1852. func (table *Table) encodeFieldValue(masterIdx int, f types.FieldInfo, value interface{}) ([]byte, error) {
  1853. ft := f.FieldType
  1854. // 字符串家族: 按【实际长度】编码 —— 这是 v9 变长省空间的主体。超宽仍在编码时直接
  1855. // 报错 (声明宽度是硬闸门), 但不再补零到档位。
  1856. if types.IsVarWidth(ft) {
  1857. enc, err := types.EncodeValueVar(ft, f.Param(), value)
  1858. if err != nil {
  1859. return nil, fmt.Errorf("字段 %q 写入失败: %w", table.Field[masterIdx].FieldName, err)
  1860. }
  1861. return enc, nil
  1862. }
  1863. size := f.Width()
  1864. if size == 0 {
  1865. return nil, fmt.Errorf("%w: 字段 %q (%s) 的列级参数 %d 非法, 宽度不可计算",
  1866. types.ErrValueRange, f.FieldName, types.TypeNameOf(ft), f.Param())
  1867. }
  1868. buf := make([]byte, size)
  1869. // 编码统一走 types.EncodeValue —— 与索引键字节同源 (见 types/codec.go)。
  1870. // param 取列级类型参数: decimal 必须在这里校验"值的 scale == 列声明的 scale",
  1871. // 否则同一列出现两种 scale 会让"字节序 == 数值序"失效, 索引范围扫描静默漏行;
  1872. // 字符串列则由它决定列宽闸门 (超宽在 EncodeValue 里直接报错, 不再截断)。
  1873. if err := types.EncodeValue(ft, f.Param(), value, buf); err != nil {
  1874. return nil, fmt.Errorf("字段 %q 写入失败: %w", table.Field[masterIdx].FieldName, err)
  1875. }
  1876. return buf, nil
  1877. }
  1878. // dynamicFieldData 解析动态列的值 ([]byte; nil 表示空值), 并卡单块尺寸闸门。
  1879. //
  1880. // 动态列的值只认 []byte。旧写法是 `data, _ := value.([]byte)`: 一旦传进来
  1881. // 的是别的类型 (string / 数值), 断言失败 → data 为 nil → 整个值被静默写成空。
  1882. // 这与"不静默丢内容"直接冲突, 所以这里改为显式报错 (nil 仍表示空值)。
  1883. func (table *Table) dynamicFieldData(masterIdx int, f types.FieldInfo, value interface{}) ([]byte, error) {
  1884. if value == nil {
  1885. return nil, nil
  1886. }
  1887. data, ok := value.([]byte)
  1888. if !ok {
  1889. return nil, fmt.Errorf("字段 %q (%s): 动态列只接受 []byte, 收到 %T",
  1890. f.FieldName, types.TypeNameOf(f.FieldType), value)
  1891. }
  1892. // 动态列的单块尺寸闸门: 超限在这里就报出列名与上限, 而不是等 heapAllocBlock
  1893. // 抛出与"值太大"因果关系不明的 ErrHeapPageTooLarge。
  1894. //
  1895. // 只覆盖 Dynamicbyte: 它的行内是 6B 堆引用, 内容整块落进一个堆页, 装不下更大的值。
  1896. // Blob 列不走这里 —— v10 起它的行内是 8B BlobRef, 内容落在可跨页的链上 (上限 ~4GB,
  1897. // 见 blobCommit). 往 Dynamicbyte 写超限值若不在这一层拦, 只会得到不带列名、不带
  1898. // 字节数的"申请空间超过单页最大容量", 用户看不出是哪一列、超了多少。
  1899. if uint32(len(data)) > MaxBlobSize {
  1900. return nil, fmt.Errorf("%w: 字段 %q (%s) 的值 %d 字节超过单块上限 %d 字节 —— "+
  1901. "动态列的内容整块落进一个堆页, 更大的内容引擎目前装不下: 多页分块存储尚未实现, "+
  1902. "请改小这个值, 或改用 blob 列 (链式存储, 可跨页)。",
  1903. types.ErrValueRange, f.FieldName,
  1904. types.TypeNameOf(f.FieldType), len(data), MaxBlobSize)
  1905. }
  1906. return data, nil
  1907. }
  1908. // blobFieldRef 把 Blob 列的载体值统一成行内 8B 的 types.BlobRef.
  1909. //
  1910. // Blob 列行内只放引用, 而用户写这一列时手上多半是【内容本身】。这里就是
  1911. // types/blob.go 里"把 io.Reader / []byte 写进 FieldType_Blob 列, 引擎自动走链页"
  1912. // 的那个落点 —— 调用方不必自己开事务、自己调 BlobCommit。
  1913. //
  1914. // 接受的值:
  1915. //
  1916. // nil -> 空引用 (NULL 已在上游 Pass 1 拦掉, 这里只兜底非空列的空值)
  1917. // types.BlobRef -> 原样 (已经是引用: 用户自己 tx.BlobCommit 过, 或读回侧镜像)
  1918. // types.Blob -> Source 优先 (可流式), 否则 Bytes; 已带 ref 的直接用
  1919. // []byte / string -> 走链页 (空内容不分配页, 直接给空引用)
  1920. // io.Reader -> 走链页 (流式, 内存峰值与内容大小无关)
  1921. // 其它类型 -> 报错 (与"不静默丢内容"一致)
  1922. //
  1923. // checkOnly 是唯一性试算行 (serializeRowForCheck): 绝不能落盘, 返回零引用即可 ——
  1924. // Blob 列不能当索引键 (见 CreateIndex), 试算行的键字节与它无关。
  1925. func (table *Table) blobFieldRef(f types.FieldInfo, value interface{}, checkOnly bool) (BlobRef, error) {
  1926. switch v := value.(type) {
  1927. case BlobRef: // == types.BlobRef (类型别名), 引用原样透传
  1928. return v, nil
  1929. case types.Blob:
  1930. return table.blobRefFromValue(f, v, checkOnly)
  1931. case *types.Blob:
  1932. if v == nil {
  1933. return BlobRef{}, nil
  1934. }
  1935. return table.blobRefFromValue(f, *v, checkOnly)
  1936. }
  1937. if checkOnly {
  1938. return BlobRef{}, nil
  1939. }
  1940. var src io.Reader
  1941. switch v := value.(type) {
  1942. case []byte:
  1943. if len(v) == 0 {
  1944. return BlobRef{}, nil
  1945. }
  1946. src = bytes.NewReader(v)
  1947. case string:
  1948. if v == "" {
  1949. return BlobRef{}, nil
  1950. }
  1951. src = strings.NewReader(v)
  1952. case io.Reader:
  1953. src = v
  1954. default:
  1955. return BlobRef{}, fmt.Errorf("字段 %q (%s): blob 列只接受 []byte / string / io.Reader / types.Blob / types.BlobRef, 收到 %T",
  1956. f.FieldName, types.TypeNameOf(f.FieldType), value)
  1957. }
  1958. ref, err := table.db.blobCommit(src, types.CompressNone)
  1959. if err != nil {
  1960. return BlobRef{}, fmt.Errorf("字段 %q (%s): blob 落链失败: %w",
  1961. f.FieldName, types.TypeNameOf(f.FieldType), err)
  1962. }
  1963. return ref, nil
  1964. }
  1965. // blobRefFromValue 处理 types.Blob 载体: 既有"内容"形态 (Source / Bytes),
  1966. // 也有"读回镜像"形态 (FirstPageID / TotalLen 已由引擎填好).
  1967. func (table *Table) blobRefFromValue(f types.FieldInfo, b types.Blob, checkOnly bool) (BlobRef, error) {
  1968. hasRef := b.FirstPageID != 0 || b.TotalLen != 0
  1969. hasContent := b.Source != nil || len(b.Bytes) > 0
  1970. if hasRef {
  1971. if hasContent {
  1972. return BlobRef{}, fmt.Errorf("字段 %q: types.Blob 同时带了引用与内容 (Source/Bytes), 无法判定该用哪个", f.FieldName)
  1973. }
  1974. return b.Ref(), nil // 读回镜像: 原样透传
  1975. }
  1976. if !hasContent {
  1977. return BlobRef{}, nil // 空 blob: 不分配链页
  1978. }
  1979. if checkOnly {
  1980. return BlobRef{}, nil
  1981. }
  1982. if b.Source != nil && len(b.Bytes) > 0 {
  1983. return BlobRef{}, fmt.Errorf("字段 %q: types.Blob 的 Source 与 Bytes 都非空, 引擎无法选择", f.FieldName)
  1984. }
  1985. if !b.Compress.IsValid() {
  1986. return BlobRef{}, fmt.Errorf("字段 %q: types.Blob.Compress 非法", f.FieldName)
  1987. }
  1988. var src io.Reader
  1989. if b.Source != nil {
  1990. src = b.Source
  1991. } else {
  1992. src = bytes.NewReader(b.Bytes)
  1993. }
  1994. ref, err := table.db.blobCommit(src, b.Compress)
  1995. if err != nil {
  1996. return BlobRef{}, fmt.Errorf("字段 %q (%s): blob 落链失败: %w",
  1997. f.FieldName, types.TypeNameOf(f.FieldType), err)
  1998. }
  1999. return ref, nil
  2000. }
  2001. // fieldParam 取列级类型参数 (Flag 低 5 位): decimal 是 scale, 字符串家族是行内宽度码,
  2002. // 其余类型为 0 (由 types.ValidateColumnParam 保证)。
  2003. //
  2004. // 以前这里叫 fieldScale 且只服务 decimal; 字符串列也用上同一格之后, "取参数"与
  2005. // "算宽度"必须成对 —— 只传 scale 会漏掉宽度码, 编出来的字节宽度与行内不一致。
  2006. func (table *Table) fieldParam(masterIdx int) byte {
  2007. if masterIdx < 0 || masterIdx >= len(table.Field) {
  2008. return 0
  2009. }
  2010. return table.Field[masterIdx].Param()
  2011. }
  2012. // serializeRow 将 FieldValue 切片序列化为行数据字节 (不含 pkID 前缀)
  2013. // 行格式 (v9): [schemaVer 1B][列0 W0 B][列结束偏移表 2N][列1..N-1 内容][NULL 位图]
  2014. // 动态字段的行内数据为 6 字节堆引用; oldRowData 用于更新时复用堆块。
  2015. //
  2016. // 两点关键语义 (否则 schema 演进后更新旧行会静默丢数据):
  2017. // - 版本选择走 pickVersionForUpdate: 未触及新字段时保持旧版本, 偏移表与列序都不变,
  2018. // 不会因为"只改了个老字段"就无谓升版; 但 v9 的字符串列按【实际长度】存, 所以
  2019. // "版本不变"不等于"行长不变", 变长仍可能发生 (由索引层的原地→页内重建→换页兜住)。
  2020. // - values 中未提供的字段用旧行原值补齐, 而不是写零
  2021. // (否则 "只改一列" 会把该行其它列全部清空)
  2022. func (table *Table) serializeRow(values []types.FieldValue, oldRowData []byte) ([]byte, error) {
  2023. return table.serializeRowInternal(values, oldRowData, false)
  2024. }
  2025. // serializeRowForCheck 试算一遍行字节: 除动态列外与 serializeRow 逐字节一致。
  2026. //
  2027. // 动态列在这里【不碰堆】—— 不分配、不复用、不释放, 行内 6B 引用留全零。存在的理由
  2028. // 是更新前的唯一性预检必须早于 serializeRow: writeDynamicField 会就地覆写旧行的堆块
  2029. // (heapRewrite), 换块时还会 heapUnref 旧块。等 UpdateByKey 报出键冲突再返回错误,
  2030. // 旧行的动态列已被改成新值 (极端情况下堆块已释放而旧行仍指着它), 而固定列与索引条目
  2031. // 还是旧的 —— 行被改了一半, 且不留任何痕迹。多一次序列化换"要么全改要么全不动"。
  2032. //
  2033. // 索引分量一定不是动态列 (CreateIndex 已拒绝), NULL 位图也只取决于"值是否为 nil 且
  2034. // 列可空", 与堆无关 —— 所以试算行算出的索引键与真身完全一致。
  2035. func (table *Table) serializeRowForCheck(values []types.FieldValue, oldRowData []byte) ([]byte, error) {
  2036. return table.serializeRowInternal(values, oldRowData, true)
  2037. }
  2038. // nullFieldError 非空列被写入 NULL 时的统一报错 (两处循环共用, 文案必须一致)。
  2039. func nullFieldError(name string, ft types.FieldType) error {
  2040. return fmt.Errorf("%w: 字段 %q (%s) 未声明可空 (nullable), 不接受 NULL; "+
  2041. "要允许写入 NULL, 请在列声明里加 `nullable` / `null`",
  2042. types.ErrValueRange, name, types.TypeNameOf(ft))
  2043. }
  2044. func (table *Table) serializeRowInternal(values []types.FieldValue, oldRowData []byte, checkOnly bool) ([]byte, error) {
  2045. ver := table.pickVersionForUpdate(oldRowData, values)
  2046. // 写行闸门 (第三处行宽校验)。
  2047. //
  2048. // 前两处在建列侧 (TablePage.Create / Table.AddField / CreateIndex), 管的是"新表 /
  2049. // 新索引不许超宽"; 这一处管的是"已经存在的老表": 表是在限制生效前建的 (或 schema
  2050. // 被外部改坏) 时, 行字节数会超过一个 DataPage 能装下的量。放任它走下去的后果不是
  2051. // "写失败"这么轻 —— 行长度会在 DataPage 里溢出到相邻区域, 把整页的行解析全部错位
  2052. // (页内行是顺序排布、靠每行 4B 长度前缀自描述的, 一行错位等于整页报废)。
  2053. //
  2054. // 【v9 起判据是"溢出下界"】行按内容变长, 实际长度要等内容全编完才知道, 而那时堆块
  2055. // 可能已经动过 (writeHeapRefField 会覆写/释放旧块), 再报错就留下了半改的行。这里用
  2056. // types.CalcSpillFloor 的下界 —— "把所有可溢出的字符串列都按 6B 引用计"之后仍超
  2057. // 物理墙, 说明不动堆永远救不回来, 且这个判断发生在动堆之前。
  2058. floor := table.rowSpillFloorForVersion(ver)
  2059. if floor > MaxPhysicalRowData {
  2060. return nil, fmt.Errorf("%w: 表 %q 的 schema 版本 %d 行宽溢出下界 %d 字节超过单页可容纳上限 %d 字节, 拒绝写入; "+
  2061. "请重建该表并把大列改为动态列 (FieldType_Dynamicbyte / Blob)",
  2062. types.ErrValueRange, util.BytesToString(table.info.Name[:]), ver, floor, MaxPhysicalRowData)
  2063. }
  2064. sv := table.getVersion(ver)
  2065. if sv == nil {
  2066. return []byte{ver}, nil
  2067. }
  2068. n := len(sv.FieldIndices)
  2069. if n == 0 {
  2070. return []byte{ver}, nil
  2071. }
  2072. // 基底: 旧行已存在的字段值 (版本感知解析, 旧行不会凭空多出新字段)
  2073. var base []types.FieldValue
  2074. if len(oldRowData) > 0 {
  2075. base = table.deserializeRow(oldRowData)
  2076. }
  2077. // 行布局 (v9, 详见 types/rowvar.go):
  2078. // [schemaVer 1B][列0 W0 B][列结束偏移表 2N][列1..N-1 内容][NULL 位图]
  2079. //
  2080. // - 列 0 恒占 [1, 1+W0) 且 W0 由其【档位宽度】决定 (不是实际内容长度): 偏移表基址
  2081. // 因此可静态算出, 解析端不必先猜列 0 有多长。时序表的列 0 就是 8 字节时间键
  2082. // (W0 == 8), 时间键于是落在【绝对偏移 9】, 与 index 侧写死的读取偏移一致。
  2083. // 列 0 不足 W0 时补零 —— 与 v8 的定长存储逐字节相同。
  2084. // - 偏移表落在"只取决于字段数与列 0 宽度"的固定位置, 因此原地更新 (新行写进旧槽位、
  2085. // 尾部清零) 冲不掉它, 解析永远精确, 不依赖 len(rowData)。
  2086. // - 表项 0 = 变长内容区起点 (= RowHeaderSize); 列 i (i ≥ 1) 的起点 = 表项 i-1。
  2087. // - 表项的 bit15 (RowVarTag) 为真表示该位置是 6B 堆引用: 动态列恒置位, 值级溢出
  2088. // 的字符串列溢出时置位 —— 读侧一律以 tag 为准, 不再看列类型。
  2089. //
  2090. // 序列化分三步: ① 解析并编码各列 (不写缓冲); ② 算全内联长度, 决定哪些字符串列要
  2091. // 【值级溢出】(行内装不下时把未建索引的宽字符串列降级为 6B 堆引用, 见下方 Pass 2);
  2092. // ③ 按最终布局一次写出行字节。分步的代价是每列多一次切片拷贝, 换来溢出决策发生在
  2093. // 任何堆写入之前 —— 布局对溢出集合是闭式的, 不需要"先写再改"。
  2094. col0Width := table.svCol0Width(sv)
  2095. if col0Width <= 0 {
  2096. return nil, fmt.Errorf("%w: 表 %q 的首列 %q 宽度不可计算 (列级参数非法); "+
  2097. "行内偏移表的位置依赖首列宽度, 无法写行",
  2098. types.ErrValueRange, util.BytesToString(table.info.Name[:]),
  2099. util.BytesToString(table.Field[sv.FieldIndices[0]].FieldName))
  2100. }
  2101. // 旧行持有的堆引用 (动态列 ∪ 值级溢出列), 布局阶段按列取用: 复用 / 释放。
  2102. // oldRowHeapRefs 只认"动态列或带 tag 的列", 内联内容绝不会被误当引用。
  2103. oldRefs := table.oldRowHeapRefs(oldRowData)
  2104. releaseOld := func(masterIdx int) {
  2105. if !checkOnly {
  2106. if r, ok := oldRefs[masterIdx]; ok {
  2107. _ = table.heapUnref(r.pid)
  2108. }
  2109. }
  2110. }
  2111. // v10 起 blob 列同样需要在更新路径回收旧链 ——
  2112. // - 改写该列 (NULL / 新 ref) 时 Pass 1 释放旧 ref;
  2113. // - 同值"复用"时 Pass 3 不释放, 否则会与新 ref 重叠 (同一链页释放两次 = 误回收).
  2114. // 同样按 masterIdx 索引, 解析失败/无 blob 列时为 nil, 调用安全.
  2115. oldBlobRefs := table.oldBlobRefsByMaster(oldRowData)
  2116. releaseOldBlob := func(masterIdx int) {
  2117. if !checkOnly {
  2118. if r, ok := oldBlobRefs[masterIdx]; ok {
  2119. _ = table.BlobFree(r)
  2120. }
  2121. }
  2122. }
  2123. // ---- Pass 1: 解析并编码各列 ----
  2124. //
  2125. // colBytes[i]: 动态列 = 原始内容 (布局时落堆); 其余非 NULL 列 = 行内字节
  2126. // (字符串按实际长度, 定长按档位宽度)。未提供且旧行也没有的列保持 nil:
  2127. // 字符串零值就是"空" (0 字节), 定长零值就是全零 (布局时按宽度零填充)。
  2128. colBytes := make([][]byte, n)
  2129. colNull := make([]bool, n)
  2130. bitmapLen := types.NullBitmapLen(n)
  2131. bitmap := make([]byte, bitmapLen)
  2132. // 列名→值映射: 列数较多时避免 Pass 1 整体 O(列数²) 的线性扫。
  2133. // 小表 (≤8 列) 直接线性扫更快 —— 免一次 map 分配; base (旧行) 只在缺列时
  2134. // 兜底, 常见路径 (全列提交) 不会碰到, 保持线性扫即可。
  2135. var valByName map[string]interface{}
  2136. if n > 8 {
  2137. valByName = make(map[string]interface{}, len(values))
  2138. for _, fv := range values {
  2139. valByName[string(fv.FieldName)] = fv.Value
  2140. }
  2141. }
  2142. lookupVal := func(name []byte) (interface{}, bool) {
  2143. if valByName != nil {
  2144. v, ok := valByName[string(name)]
  2145. return v, ok
  2146. }
  2147. return lookupFieldValue(values, name)
  2148. }
  2149. for i, masterIdx := range sv.FieldIndices {
  2150. f := table.Field[masterIdx]
  2151. name := f.FieldName
  2152. // 取值优先级: 本次传入 > 旧行原值 (未提供的字段沿用旧值, 否则"只改一列"会清空其它列)
  2153. value, provided := lookupVal(name)
  2154. if !provided {
  2155. value, provided = lookupFieldValue(base, name)
  2156. }
  2157. switch {
  2158. case provided && value == nil:
  2159. if !f.IsNullable() {
  2160. // 非空列写 NULL: 拒绝并点名列。
  2161. //
  2162. // 这里不是"收紧", 而是把已有的不一致统一掉: 定长列写 nil 本来就走不通
  2163. // (EncodeValue 对 nil 报 ErrValueType), 唯独动态列曾把 nil 静默当成
  2164. // "空内容"存下去 (行内引用全 0, NULL 位图也不置位) —— 同一句"写 nil"
  2165. // 在定长列报错、在动态列静默成功。既然声明了非空, 就该两边都拒。
  2166. //
  2167. // 注意 base (旧行原值) 不会贡献 nil: deserializeRow 只 append 非 nil 的值
  2168. // (见那里的 `if val != nil`), 所以这条只拦"本次显式传入的 NULL",
  2169. // 不会让"只改其它列"的更新半路失败。
  2170. return nil, nullFieldError(util.BytesToString(name), f.FieldType)
  2171. }
  2172. // NULL: 位图置位, 不占内容字节 (语义完全由位图承载; 动态列/溢出列因此不写堆引用)。
  2173. // 试算模式也要置位 —— extractIndexKey 正是靠位图判定"该行不进本索引"。
  2174. types.SetNullBit(bitmap, i)
  2175. // 旧行若在该列持有堆块, 新行不再引用它 —— 不在这里释放就是每更新一次漏一块。
  2176. releaseOld(int(masterIdx))
  2177. // v10: blob 列同样要释放旧 ref, 走 BlobFree 归还链页.
  2178. releaseOldBlob(int(masterIdx))
  2179. case provided && types.IsDynamic(f.FieldType):
  2180. data, err := table.dynamicFieldData(int(masterIdx), f, value)
  2181. if err != nil {
  2182. return nil, err
  2183. }
  2184. colBytes[i] = data
  2185. case provided && types.IsBlob(f.FieldType):
  2186. // Blob 列行内只有 8B BlobRef, 但用户手上多半是"内容本身" ([]byte / io.Reader),
  2187. // 由引擎在这里把内容落进链页、换成引用 (见 blobFieldRef). 已经是引用的原样用。
  2188. // 试算行 (checkOnly) 不落盘: Blob 列不能当索引键 (见 CreateIndex), 键字节与它无关。
  2189. ref, err := table.blobFieldRef(f, value, checkOnly)
  2190. if err != nil {
  2191. return nil, err
  2192. }
  2193. enc, err := table.encodeFieldValue(int(masterIdx), f, ref)
  2194. if err != nil {
  2195. return nil, err
  2196. }
  2197. colBytes[i] = enc
  2198. case provided:
  2199. enc, err := table.encodeFieldValue(int(masterIdx), f, value)
  2200. if err != nil {
  2201. return nil, err
  2202. }
  2203. if i == 0 && len(enc) > col0Width {
  2204. return nil, fmt.Errorf("%w: 字段 %q 编码后 %d 字节超过首列档位宽度 %d",
  2205. types.ErrEncodeBufSize, util.BytesToString(name), len(enc), col0Width)
  2206. }
  2207. colBytes[i] = enc
  2208. default:
  2209. // 未提供: 列 0 与定长列在布局阶段按档位宽度零填充; 字符串零值 = 空 (0 字节)。
  2210. colNull[i] = false // 占位, 语义见上
  2211. }
  2212. }
  2213. // ---- Pass 2: 值级溢出决策 ----
  2214. //
  2215. // 全内联长度超过 MaxInlineRowData ("一页两行"政策的行内配额) 时, 把【未建索引的
  2216. // 宽字符串列】按"内容长度降序"逐个降级为 6B 堆引用, 直到退回限内。列 0 不能溢出
  2217. // (偏移表基址依赖它的档位宽度); 建了索引的列不能溢出 (索引键按行内字节拼,
  2218. // 见 CreateIndex 的溢出闸门); NULL 列与内容 ≤ 6B 的列没有可省的字节。
  2219. //
  2220. // 下界闸门 (开头的 rowSpillFloorForVersion) 保证了"把能溢出的全溢出去"一定装得下,
  2221. // 所以这个循环必然终止于限内 (除非老表超限 —— 那已在上界处拒绝)。
  2222. // checkOnly (试算行) 走同样的决策: 溢出的列不可能是索引分量, 键不受影响; 但堆引用
  2223. // 只写零占位, 不碰堆。
  2224. contentStart := types.RowHeaderSize(n, col0Width)
  2225. inlineLen := int(contentStart) + bitmapLen
  2226. for i := 1; i < n; i++ {
  2227. f := table.Field[sv.FieldIndices[i]]
  2228. switch {
  2229. case types.IsDynamic(f.FieldType):
  2230. // 动态列恒占 6B 堆引用 (即便这一行是 NULL: 布局仍写零引用参差对齐)。
  2231. inlineLen += types.DynamicRefSize
  2232. case types.IsBlob(f.FieldType):
  2233. // blob 列同理恒占 8B 内联引用: Pass 3 对 blob 列【无条件】推进 8B
  2234. // (NULL 只是不拷字节, 槽位仍在), 这里若按 colNull 记 0, 末尾那句
  2235. // "行布局长度失配" 就会在"写一行带 NULL blob 列"时直接报错。
  2236. inlineLen += types.BlobRefSize
  2237. case colNull[i]:
  2238. // NULL 列不占内容字节 (仅位图置位)。
  2239. default:
  2240. // 必须与 Pass 3 用同一个占用公式, 否则"预算"与"实际布局"会对不上:
  2241. // 字符串列在这里也要按实际长度计, 而不是档位宽度。
  2242. inlineLen += fieldInlineWidth(f, colBytes[i])
  2243. }
  2244. }
  2245. spilled := make([]bool, n)
  2246. if inlineLen > types.MaxInlineRowData {
  2247. ineligible := table.spillIneligibleFields()
  2248. type spillCand struct {
  2249. i, gain int
  2250. }
  2251. cands := make([]spillCand, 0, n)
  2252. for i := 1; i < n; i++ {
  2253. if spilled[i] || colNull[i] {
  2254. continue
  2255. }
  2256. masterIdx := int(sv.FieldIndices[i])
  2257. f := table.Field[masterIdx]
  2258. if !types.IsVarWidth(f.FieldType) || ineligible[masterIdx] {
  2259. continue
  2260. }
  2261. if gain := len(colBytes[i]) - types.DynamicRefSize; gain > 0 {
  2262. cands = append(cands, spillCand{i, gain})
  2263. }
  2264. }
  2265. sort.Slice(cands, func(a, b int) bool { return cands[a].gain > cands[b].gain })
  2266. cur := inlineLen
  2267. for _, c := range cands {
  2268. if cur <= types.MaxInlineRowData {
  2269. break
  2270. }
  2271. spilled[c.i] = true
  2272. cur -= c.gain
  2273. }
  2274. }
  2275. // ---- Pass 3: 布局 (一次成形, 长度精确) ----
  2276. // 预算必须扣掉"值级溢出省下的字节": Pass 3 对溢出列只写 6B 堆引用, 而 inlineLen
  2277. // 仍按溢出前的实际长度计那些列。少扣这一笔, 末尾的 length 断言必然失败 —— 而且
  2278. // 只在真正触发溢出时才失败 (窄行永远测不出来)。
  2279. finalLen := inlineLen - curAdjust(spilled, colBytes)
  2280. rowData := make([]byte, finalLen)
  2281. rowData[0] = ver
  2282. ends := make([]uint16, n)
  2283. refs := make([]bool, n) // 偏移表项要置 RowVarTag 的列 (动态列 ∪ 溢出列)
  2284. ends[0] = uint16(contentStart)
  2285. // 列 0: 档位宽度定长存储 —— 编码字节拷进 [1, 1+W0), 余下保持全零。
  2286. switch f0 := table.Field[sv.FieldIndices[0]]; {
  2287. case colNull[0]:
  2288. // NULL: 位图已置位, 不占内容字节 (旧引用已在 Pass 1 释放)。
  2289. case types.IsDynamic(f0.FieldType):
  2290. // 动态首列 (理论情形): 行内就是 6B 堆引用, 与其它动态列走同一条路。
  2291. // 绝不能把内容原样拷进 6B 槽位 —— 那会盖住后面的偏移表, 整行错位。
  2292. if err := table.writeHeapRefField(rowData, 1, colBytes[0],
  2293. oldRefOrNil(oldRefs, int(sv.FieldIndices[0])), checkOnly); err != nil {
  2294. return nil, err
  2295. }
  2296. case types.IsBlob(f0.FieldType):
  2297. // Blob 首列: 行内 8B BlobRef 直引链页, 与其它 blob 列一致. 不走堆.
  2298. if c0 := colBytes[0]; len(c0) >= types.BlobRefSize {
  2299. copy(rowData[1:1+types.BlobRefSize], c0[:types.BlobRefSize])
  2300. }
  2301. default:
  2302. if c0 := colBytes[0]; len(c0) > 0 {
  2303. copy(rowData[1:], c0)
  2304. }
  2305. }
  2306. offset := uint16(contentStart)
  2307. for i := 1; i < n; i++ {
  2308. f := table.Field[sv.FieldIndices[i]]
  2309. isDynamic := types.IsDynamic(f.FieldType)
  2310. isBlob := types.IsBlob(f.FieldType)
  2311. switch {
  2312. case isDynamic || spilled[i]:
  2313. refs[i] = true
  2314. old := oldRefOrNil(oldRefs, int(sv.FieldIndices[i]))
  2315. if err := table.writeHeapRefField(rowData, offset, colBytes[i], old, checkOnly); err != nil {
  2316. return nil, err
  2317. }
  2318. offset += types.DynamicRefSize
  2319. case isBlob:
  2320. // v10 起 Blob 列行内 8B BlobRef (直引链页), 不再走堆块。
  2321. // colBytes[i] = 8B [TotalLen BE 4][FirstPageID BE 4] (由 encodeFieldValue 编码);
  2322. // 空 / NULL 时为 0 字节 — 不拷任何字节, 但 offset 必须推进 8B 保持布局.
  2323. if !colNull[i] && len(colBytes[i]) >= types.BlobRefSize {
  2324. copy(rowData[offset:offset+types.BlobRefSize], colBytes[i][:types.BlobRefSize])
  2325. // 改值情形 (新 ref 与旧 ref 不同): 旧链必须归还空闲链.
  2326. // 同值"复用" (FirstPageID 与 TotalLen 都一致) 不释放 ——
  2327. // 释放后再写新行会立刻挂空链. NULL 情形在 Pass 1 已释放.
  2328. if !checkOnly {
  2329. if oldRef, ok := oldBlobRefs[int(sv.FieldIndices[i])]; ok {
  2330. newRef := BlobRef{
  2331. TotalLen: binary.BigEndian.Uint32(colBytes[i][:4]),
  2332. FirstPageID: binary.BigEndian.Uint32(colBytes[i][4:8]),
  2333. }
  2334. if oldRef != newRef {
  2335. _ = table.BlobFree(oldRef)
  2336. }
  2337. }
  2338. }
  2339. }
  2340. offset += types.BlobRefSize
  2341. // 不置 RowVarTag — BlobRef 是内联 8B 字节, 读侧按类型识别.
  2342. case colNull[i]:
  2343. // 不占内容字节
  2344. default:
  2345. if len(colBytes[i]) > 0 {
  2346. copy(rowData[offset:], colBytes[i])
  2347. }
  2348. // 定长列未提供时按档位宽度零填充 (make 已清零, 只推进偏移)。
  2349. offset += uint16(fieldInlineWidth(f, colBytes[i]))
  2350. // 旧行该列若曾溢出到堆 (值级溢出), 新行收回内联 → 旧块不再被引用, 释放。
  2351. releaseOld(int(sv.FieldIndices[i]))
  2352. }
  2353. ends[i] = offset
  2354. }
  2355. // NULL 位图放在偏移表末项之后。
  2356. copy(rowData[offset:], bitmap)
  2357. if int(offset)+bitmapLen != finalLen {
  2358. return nil, fmt.Errorf("%w: 行布局长度失配 (内容末尾 %d + 位图 %d != 预算 %d)",
  2359. types.ErrEncodeBufSize, offset, bitmapLen, finalLen)
  2360. }
  2361. types.PutRowOffsets(rowData, ends, refs, col0Width)
  2362. return rowData, nil
  2363. }
  2364. // fieldInlineWidth 非动态、非 NULL 列的行内占用。
  2365. //
  2366. // - 变长 (字符串家族): 按【实际长度】占位 —— 空串 / 未提供都是 0 字节, 这才是 v9 省
  2367. // 空间的本体; 若按档位宽度零填充, 一个未提供的 1024 宽串会白占 1KB。
  2368. // - 定长: 有编码字节按实际长度, 未提供则必须按档位宽度零填充 —— 定长列的零值是全零
  2369. // 字节, 必须占住位置, 否则后面所有列错位。
  2370. func fieldInlineWidth(f types.FieldInfo, enc []byte) int {
  2371. if types.IsVarWidth(f.FieldType) || len(enc) > 0 {
  2372. return len(enc)
  2373. }
  2374. return int(f.Width())
  2375. }
  2376. // oldRefOrNil 从旧行引用表取某列的引用 (无则 nil)。
  2377. func oldRefOrNil(refs map[int]oldHeapRefInfo, masterIdx int) *oldHeapRefInfo {
  2378. if r, ok := refs[masterIdx]; ok {
  2379. return &r
  2380. }
  2381. return nil
  2382. }
  2383. // curAdjust 计算溢出集合实际省下的行内字节数 (仅用于布局预算自检)。
  2384. func curAdjust(spilled []bool, colBytes [][]byte) int {
  2385. saved := 0
  2386. for i, sp := range spilled {
  2387. if sp {
  2388. saved += len(colBytes[i]) - types.DynamicRefSize
  2389. }
  2390. }
  2391. return saved
  2392. }
  2393. func writeFieldValue(buf []byte, ft types.FieldType, value interface{}) {
  2394. switch ft {
  2395. case types.FieldType_Uint8:
  2396. buf[0] = value.(byte)
  2397. case types.FieldType_Uint16:
  2398. binary.BigEndian.PutUint16(buf, uint16(value.(int16)))
  2399. case types.FieldType_Uint32:
  2400. binary.BigEndian.PutUint32(buf, uint32(value.(int32)))
  2401. case types.FieldType_Uint64:
  2402. binary.BigEndian.PutUint64(buf, value.(uint64))
  2403. case types.FieldType_Int8:
  2404. buf[0] = types.Int8ToSortKey(value.(int8))
  2405. case types.FieldType_Int16:
  2406. binary.BigEndian.PutUint16(buf, types.Int16ToSortKey(value.(int16)))
  2407. case types.FieldType_Int32:
  2408. binary.BigEndian.PutUint32(buf, types.Int32ToSortKey(value.(int32)))
  2409. case types.FieldType_Int64:
  2410. // 同 Float64: 存保序编码 (Int64ToSortKey) 而不是补码本身。补码大端对负数不保序,
  2411. // 会让跨 0 的范围扫描 / ORDER BY 静默漏行; 换成保序编码后行内字节序即数值序,
  2412. // 索引键直接复用行内字节 (extractIndexKey 原样切片) 也就自动保序。
  2413. binary.BigEndian.PutUint64(buf, types.Int64ToSortKey(value.(int64)))
  2414. case types.FieldType_Float64:
  2415. // 存保序编码 (Float64ToSortKey) 而不是原始位模式: 行内字节序即数值序,
  2416. // 索引键直接复用行内字节 (extractIndexKey 原样切片) 也就自动保序。
  2417. binary.BigEndian.PutUint64(buf, types.Float64ToSortKey(toFloat64(value)))
  2418. default:
  2419. // 所有字符串/定长类型 (动态类型不走此分支, 由 writeDynamicField 处理)
  2420. if b, ok := value.([]byte); ok {
  2421. copy(buf, b)
  2422. }
  2423. }
  2424. }
  2425. // deserializeRow 将行数据字节反序列化为 FieldValue 切片 (不含 pkID 前缀)。
  2426. //
  2427. // 行格式 (v9): [schemaVer 1B][列0 W0 B][列结束偏移表 2N][列1..N-1 内容][NULL 位图]
  2428. //
  2429. // 列位置一律由行内偏移表给出 —— 旧实现"从偏移 1 开始累加各列 Width"的前提是字段区
  2430. // 定长, 而 v9 的字符串列按实际长度存储, 累加 Width 会越算越偏 (表现为整行错列)。
  2431. func (table *Table) deserializeRow(rowData []byte) []types.FieldValue {
  2432. return table.deserializeRowMode(rowData, nil, false)
  2433. }
  2434. // deserializeRowKeepNull 与 deserializeRow 相同, 但 NULL 列也产出
  2435. // FieldValue{Value: nil} (而不是跳过)。供 VACUUM 快照使用: 重建插入时
  2436. // 显式传 nil 走 NULL 位图路径 —— deserializeRow 的"跳过 NULL"会让 VACUUM
  2437. // 把 NULL 物化成类型零值 (可空列语义被静默改写, 且不可逆)。
  2438. func (table *Table) deserializeRowKeepNull(rowData []byte) []types.FieldValue {
  2439. return table.deserializeRowMode(rowData, nil, true)
  2440. }
  2441. // deserializeRowSelect 只解码掩码选中的列 (掩码按 master 下标)。
  2442. //
  2443. // 与 deserializeRow 同一套布局解析, 未选中列跳过解码与其堆解引用 —— 宽表的投影查询
  2444. // 不再为不看的列付解码/堆页 IO 成本。掩码必须包含调用方后续要读的列
  2445. // (Query.deserialize 负责把 ORDER BY 列并进来)。
  2446. func (table *Table) deserializeRowSelect(rowData []byte, want []bool) []types.FieldValue {
  2447. return table.deserializeRowMode(rowData, want, false)
  2448. }
  2449. // deserializeRowMode 行解码的公共实现。want 非 nil 时是投影掩码 (master
  2450. // 下标 → 是否解码); keepNull 为 true 时 NULL 列产出显式 nil 值。
  2451. func (table *Table) deserializeRowMode(rowData []byte, want []bool, keepNull bool) []types.FieldValue {
  2452. if len(rowData) < 1 {
  2453. return nil
  2454. }
  2455. ver := rowData[0]
  2456. sv := table.getVersion(ver)
  2457. if sv == nil {
  2458. return nil // 未知版本, 无法解析
  2459. }
  2460. n := len(sv.FieldIndices)
  2461. col0Width := table.svCol0Width(sv)
  2462. ends, ok := types.RowParseOffsets(rowData, n, col0Width)
  2463. if !ok {
  2464. return nil
  2465. }
  2466. bitmap, ok := types.RowNullBitmap(rowData, ends, n)
  2467. if !ok {
  2468. return nil
  2469. }
  2470. // 预分配容量: 字段数即结果条数的上界 (被删除 / NULL 字段会跳过), 避免 append 反复扩容
  2471. result := make([]types.FieldValue, 0, n)
  2472. for i, idx := range sv.FieldIndices {
  2473. if want != nil && int(idx) < len(want) && !want[idx] {
  2474. continue // 投影模式: 未选中的列不解码、不解堆引用
  2475. }
  2476. f := table.Field[idx]
  2477. // 跳过已删除字段 (master registry 中标记 FlagDeleted) 与 NULL 字段。
  2478. // NULL 不出现在结果里 —— 与"零值不 append"是同一约定: 读不到值就是没有值;
  2479. // 更新路径靠它区分"沿用旧值"与"显式写 NULL"(见 serializeRow 的取值优先级)。
  2480. // keepNull 模式 (VACUUM 快照) 例外: NULL 列产出显式 nil, 重建时走 NULL 位图。
  2481. if f.IsDropped() || types.NullBitSet(bitmap, i) {
  2482. if keepNull && !f.IsDropped() && types.NullBitSet(bitmap, i) {
  2483. result = append(result, types.FieldValue{FieldName: f.FieldName, Value: nil})
  2484. }
  2485. continue
  2486. }
  2487. start := int(types.RowFieldStart(ends, i))
  2488. end := int(types.RowFieldEnd(ends, i, col0Width))
  2489. if end < start || end > len(rowData) {
  2490. continue
  2491. }
  2492. seg := rowData[start:end]
  2493. var val interface{}
  2494. switch {
  2495. case types.IsDynamic(f.FieldType) || types.RowFieldRefTagged(rowData, i, col0Width):
  2496. // 解引用堆页。NULL / 未提供的动态列内容为 0 字节或全零引用 —— 读不到就是没有值。
  2497. if len(seg) < types.DynamicRefSize {
  2498. continue
  2499. }
  2500. pageID := binary.BigEndian.Uint32(seg[:4])
  2501. offInPage := binary.BigEndian.Uint16(seg[4:6])
  2502. if pageID == 0 {
  2503. continue // 空引用 = 空内容, 不产出"空值"
  2504. }
  2505. if data, err := table.heapRead(pageID, offInPage); err == nil {
  2506. val = data
  2507. }
  2508. case types.IsBlob(f.FieldType):
  2509. // v10 起 Blob 列行内 8B BlobRef, 不走堆页. 直接还原 BlobRef.
  2510. // 空 blob (TotalLen=0, FirstPageID=0) 视为"读不到", 与其它列的零值规则一致.
  2511. if len(seg) < types.BlobRefSize {
  2512. continue
  2513. }
  2514. ref := types.BlobRef{
  2515. TotalLen: binary.BigEndian.Uint32(seg[:4]),
  2516. FirstPageID: binary.BigEndian.Uint32(seg[4:8]),
  2517. }
  2518. if ref.Empty() {
  2519. continue
  2520. }
  2521. val = ref
  2522. case types.IsVarWidth(f.FieldType):
  2523. // 字符串家族: seg 就是实际字节 (无补零), DecodeValue 按 len 解码, 天然支持变长。
  2524. val = types.DecodeValue(f.FieldType, seg)
  2525. default:
  2526. // 定长类型: 长度必须恰好等于档位宽度, 否则行已损坏 (DecodeValue 对定长类型会
  2527. // 按下标取值)。这时宁可跳过该列, 也不要按错位字节解出一个"看似合理"的数值。
  2528. if len(seg) != int(f.Width()) {
  2529. continue
  2530. }
  2531. val = types.DecodeValue(f.FieldType, seg)
  2532. }
  2533. if val != nil {
  2534. result = append(result, types.FieldValue{FieldName: f.FieldName, Value: val})
  2535. }
  2536. }
  2537. return result
  2538. }
  2539. // deserializeField 解码单个字段, 不物化整行。
  2540. //
  2541. // 聚合 (Sum/Max/Min) 每行只需要一列; 用 deserializeRow 意味着每行都分配一个
  2542. // []FieldValue 并解码全部活跃列, 100 万行的 SUM 里这占了绝对大头。
  2543. // 解码逻辑与 deserializeRow 逐分支一致 (行布局解析、NULL 位图、堆解引用),
  2544. // 只是跳过"其余字段的解码与组装"。
  2545. // 返回 (值, true); 字段不存在、NULL、行损坏时返回 (nil, false)。
  2546. func (table *Table) deserializeField(rowData []byte, fieldName string) (interface{}, bool) {
  2547. if len(rowData) < 1 {
  2548. return nil, false
  2549. }
  2550. ver := rowData[0]
  2551. sv := table.getVersion(ver)
  2552. if sv == nil {
  2553. return nil, false
  2554. }
  2555. n := len(sv.FieldIndices)
  2556. // 找到字段在行内的位置 (与 deserializeRow 的循环同一编号空间)
  2557. pos := -1
  2558. for i, idx := range sv.FieldIndices {
  2559. f := table.Field[idx]
  2560. if f.IsDropped() {
  2561. continue
  2562. }
  2563. if string(f.FieldName) == fieldName {
  2564. pos = i
  2565. break
  2566. }
  2567. }
  2568. if pos < 0 {
  2569. return nil, false
  2570. }
  2571. col0Width := table.svCol0Width(sv)
  2572. ends, ok := types.RowParseOffsets(rowData, n, col0Width)
  2573. if !ok {
  2574. return nil, false
  2575. }
  2576. bitmap, ok := types.RowNullBitmap(rowData, ends, n)
  2577. if !ok {
  2578. return nil, false
  2579. }
  2580. if types.NullBitSet(bitmap, pos) {
  2581. return nil, false // NULL: 聚合语义上"读不到值"
  2582. }
  2583. f := table.Field[sv.FieldIndices[pos]]
  2584. start := int(types.RowFieldStart(ends, pos))
  2585. end := int(types.RowFieldEnd(ends, pos, col0Width))
  2586. if end < start || end > len(rowData) {
  2587. return nil, false
  2588. }
  2589. seg := rowData[start:end]
  2590. switch {
  2591. case types.IsDynamic(f.FieldType) || types.RowFieldRefTagged(rowData, pos, col0Width):
  2592. if len(seg) < types.DynamicRefSize {
  2593. return nil, false
  2594. }
  2595. pageID := binary.BigEndian.Uint32(seg[:4])
  2596. offInPage := binary.BigEndian.Uint16(seg[4:6])
  2597. if pageID == 0 {
  2598. return nil, false
  2599. }
  2600. if data, err := table.heapRead(pageID, offInPage); err == nil {
  2601. return data, true
  2602. }
  2603. return nil, false
  2604. case types.IsBlob(f.FieldType):
  2605. if len(seg) < types.BlobRefSize {
  2606. return nil, false
  2607. }
  2608. ref := types.BlobRef{
  2609. TotalLen: binary.BigEndian.Uint32(seg[:4]),
  2610. FirstPageID: binary.BigEndian.Uint32(seg[4:8]),
  2611. }
  2612. if ref.Empty() {
  2613. return nil, false
  2614. }
  2615. return ref, true
  2616. case types.IsVarWidth(f.FieldType):
  2617. return types.DecodeValue(f.FieldType, seg), true
  2618. default:
  2619. if len(seg) != int(f.Width()) {
  2620. return nil, false // 行损坏: 宁可当没有值, 也不解错位字节
  2621. }
  2622. val := types.DecodeValue(f.FieldType, seg)
  2623. return val, val != nil
  2624. }
  2625. }
  2626. // readFieldValue / 行内字段解码已收敛到 types.DecodeValue (见 types/codec.go):
  2627. // 解码与编码同源, 新增类型不必再回来改这里。
  2628. // ======================== 高层 CRUD ========================
  2629. // InsertRow 插入一行数据, pkID 由引擎按自增游标分配 (只增不减, 见 AllocPKID)。
  2630. func (table *Table) InsertRow(values []types.FieldValue) (uint64, error) {
  2631. return table.InsertRowWithID(0, values)
  2632. }
  2633. // InsertRowWithID 插入一行数据, pkID == 0 表示由引擎自增分配。
  2634. //
  2635. // 允许调用方指定主键: 上层 (ORM/导入) 往往已经拿到一个明确的行标识, 旧实现
  2636. // 无条件用 TotalRows+1 覆盖, 结果是"调用方以为按 _id 写进去了, 之后按 _id 却
  2637. // 查不到" —— 行确实存在, 只是不可达 (数据静默错位的一类)。
  2638. //
  2639. // 指定的主键若已存在则报 ErrDuplicatePK, 避免同一 pkID 在主键树里出现两条记录。
  2640. // 例外: 引擎刚分配并"预留"的号 (AllocPKID) 必然唯一, 跳过这次查重 ——
  2641. // 查重走的是回表 ResolveByPK, 会重建 O(页数) 的定位缓存, 而每次插入又使其失效;
  2642. // 逐行插入将因此退化成 O(N²) (千万级写入从分钟级变成不可完成)。
  2643. func (table *Table) InsertRowWithID(pkID uint64, values []types.FieldValue) (uint64, error) {
  2644. if pkID == 0 {
  2645. pkID = table.AllocPKID()
  2646. }
  2647. reserved := table.hasPendingPK && table.pendingPK == pkID
  2648. table.hasPendingPK = false
  2649. if !reserved {
  2650. if table.indexMeta.rowStoreHasPK(pkID) {
  2651. return 0, fmt.Errorf("%w: pkID=%d", ErrDuplicatePK, pkID)
  2652. }
  2653. }
  2654. // 显式指定更大的主键时把游标顶到它之后, 避免后续自增再发一次同样的号
  2655. if pkID+1 > table.nextPK {
  2656. table.nextPK = pkID + 1
  2657. }
  2658. rowData, err := table.serializeRow(values, nil)
  2659. if err != nil {
  2660. return 0, err
  2661. }
  2662. if err := table.indexMeta.InsertRow(table.Field, pkID, rowData, table.activeVersion, table.versions); err != nil {
  2663. return 0, err
  2664. }
  2665. table.info.TotalRows++
  2666. table.saveTableInfo()
  2667. if table.onWrite != nil {
  2668. table.onWrite()
  2669. }
  2670. return pkID, nil
  2671. }
  2672. // InsertRowsWithIDs 批量插入。pkIDs[i]==0 的行由引擎自增分配 (号先拿到、写进
  2673. // _id 列后再序列化, 与 ORM 单行路径同一约定); pkIDs[i]!=0 为显式主键, 逐个走
  2674. // 查重。返回实际使用的 pkID 列表 (与入参等长)。
  2675. //
  2676. // 与逐行 InsertRowWithID 的语义差异只在"摊销", 不在"语义":
  2677. // - 自增号来自只增不减的游标, 整批天然唯一, 跳过逐行查重 (单行路径靠
  2678. // pendingPK 预留跳过同一件事; 批量下预留槽只有一格, 改为按"号段"整批豁免);
  2679. // - 显式主键照旧 rowStoreHasPK 查重, 另补批内查重 (单行路径里批内冲突会
  2680. // 在第 i+1 行的查重中被发现, 这里一次性比出来);
  2681. // - 序列化 (含动态堆写入) 仍逐行做 —— 堆页的写入粒度是页级的, 堆块追加
  2682. // 本身已经近似批量; saveTableInfo / onWrite 摊到批末一次。
  2683. //
  2684. // 失败语义: 序列化阶段报错时, 先前行写入的堆块成为无主垃圾 (泄漏而非错乱,
  2685. // 行数据并未落任何索引, 不会被读到); 索引阶段报错时部分索引已写入 —— 与
  2686. // 单行路径"写了一半报错"一致, 靠事务回滚兜底。
  2687. func (table *Table) InsertRowsWithIDs(pkIDs []uint64, values [][]types.FieldValue) ([]uint64, error) {
  2688. return table.insertRows(pkIDs, values, false)
  2689. }
  2690. // InsertRowsReserved 批量插入"引擎预留号"的行: pkIDs 全部来自 AllocPKID
  2691. // (ORM 先要号填进对象、再连同行数据一起送回来), 唯一性由只增不减的游标保证,
  2692. // 逐行查重整个跳过 —— 语义同单行路径的 pendingPK 预留豁免, 只是预留号不止
  2693. // 一格, 按号段整批豁免。批内查重仍保留 (号段自身不可能重复, 这层是防御)。
  2694. //
  2695. // 注意: 走这个入口的前提是调用方【没有】夹带任何非预留号 —— 夹带了请用
  2696. // InsertRowsWithIDs (显式号走逐行查重)。
  2697. func (table *Table) InsertRowsReserved(pkIDs []uint64, values [][]types.FieldValue) ([]uint64, error) {
  2698. return table.insertRows(pkIDs, values, true)
  2699. }
  2700. func (table *Table) insertRows(pkIDs []uint64, values [][]types.FieldValue, allReserved bool) ([]uint64, error) {
  2701. n := len(values)
  2702. if n == 0 {
  2703. return nil, nil
  2704. }
  2705. if len(pkIDs) != n {
  2706. return nil, fmt.Errorf("tsdb: 批量插入: pkIDs (%d) 与 values (%d) 数量不一致", len(pkIDs), n)
  2707. }
  2708. // 1) 补齐自增号
  2709. auto := make([]bool, n)
  2710. for i := 0; i < n; i++ {
  2711. if pkIDs[i] == 0 {
  2712. pkIDs[i] = table.AllocPKID()
  2713. auto[i] = true
  2714. }
  2715. }
  2716. table.hasPendingPK = false // 预留号已被本批整体消费
  2717. // 2) 批内主键查重 (显式 vs 显式, 以及显式撞上自增号段)
  2718. {
  2719. seen := make(map[uint64]struct{}, n)
  2720. for i := 0; i < n; i++ {
  2721. if _, dup := seen[pkIDs[i]]; dup {
  2722. return nil, fmt.Errorf("%w: pkID=%d (批内重复)", ErrDuplicatePK, pkIDs[i])
  2723. }
  2724. seen[pkIDs[i]] = struct{}{}
  2725. }
  2726. }
  2727. // 3) 显式主键对既有行查重 (自增/预留号必然唯一, 跳过)
  2728. if !allReserved {
  2729. for i := 0; i < n; i++ {
  2730. if auto[i] {
  2731. continue
  2732. }
  2733. if table.indexMeta.rowStoreHasPK(pkIDs[i]) {
  2734. return nil, fmt.Errorf("%w: pkID=%d", ErrDuplicatePK, pkIDs[i])
  2735. }
  2736. }
  2737. }
  2738. // 4) 显式大号顶起自增游标 (与单行路径一致; 预留号本就来自游标, 无需)
  2739. if !allReserved {
  2740. for i := 0; i < n; i++ {
  2741. if !auto[i] && pkIDs[i]+1 > table.nextPK {
  2742. table.nextPK = pkIDs[i] + 1
  2743. }
  2744. }
  2745. }
  2746. // 5) 逐行序列化 (动态堆写入在这一步发生)
  2747. rowDatas := make([][]byte, n)
  2748. for i := 0; i < n; i++ {
  2749. rowData, err := table.serializeRow(values[i], nil)
  2750. if err != nil {
  2751. return nil, err
  2752. }
  2753. rowDatas[i] = rowData
  2754. }
  2755. // 6) 索引层批量写入
  2756. if err := table.indexMeta.InsertRows(table.Field, pkIDs, rowDatas, table.activeVersion, table.versions); err != nil {
  2757. return nil, err
  2758. }
  2759. table.info.TotalRows += uint64(n)
  2760. table.saveTableInfo()
  2761. if table.onWrite != nil {
  2762. table.onWrite()
  2763. }
  2764. return pkIDs, nil
  2765. }
  2766. // AllocPKID 分配并预留下一个自增 pkID。
  2767. //
  2768. // 供"必须先把号写进对象、再序列化整行"的上层使用 (典型是 ORM:
  2769. // 只有先拿到 pkID 填进 _id 列, 存进行里的 _id 才与真实 pkID 一致)。
  2770. // 分配即推进游标并预留该号, 紧随其后的 InsertRowWithID 会跳过查重。
  2771. // 若最终未插入, 该号只是被跳过 —— 游标只增不减, 不会与后续插入冲突。
  2772. func (table *Table) AllocPKID() uint64 {
  2773. table.ensureNextPK()
  2774. pk := table.nextPK
  2775. table.nextPK++
  2776. table.pendingPK = pk
  2777. table.hasPendingPK = true
  2778. return pk
  2779. }
  2780. // ensureNextPK 惰性初始化自增游标: 只增不减, 从行存储里当前最大的存活 pkID 之后继续。
  2781. //
  2782. // 旧实现 (TotalRows+1) 删除后会回退编号, 与仍存活的 pkID 撞车 (探针 T4)。
  2783. // 首次使用时取一次当前最大 pkID —— 每次开表只算一次, 之后 O(1)。
  2784. func (table *Table) ensureNextPK() {
  2785. if table.nextPKInit {
  2786. return
  2787. }
  2788. table.nextPK = table.maxRowStorePK() + 1
  2789. if table.nextPK == 0 {
  2790. table.nextPK = 1
  2791. }
  2792. table.nextPKInit = true
  2793. }
  2794. // maxRowStorePK 返回行存储索引中当前最大的存活 pkID (无行或没有行存储索引时为 0)。
  2795. // DataPage 沿 B+ 树叶链按 pkID 升序排列, 故从最后一页往回找第一个非空页即可;
  2796. // 每页内取最大 pkID 以兼容页内乱序的极端情况。
  2797. func (table *Table) maxRowStorePK() uint64 {
  2798. primMeta, primHandler, ok := table.indexMeta.rowStoreMeta()
  2799. if !ok {
  2800. return 0
  2801. }
  2802. ids := primHandler.GetDataPageIDs(table.handle, primMeta.EntryPage)
  2803. for i := len(ids) - 1; i >= 0; i-- {
  2804. rows := primHandler.ReadDataPageRows(table.handle, ids[i])
  2805. var maxPK uint64
  2806. for _, r := range rows {
  2807. if len(r) < 8 {
  2808. continue
  2809. }
  2810. if pk := binary.BigEndian.Uint64(r[:8]); pk > maxPK {
  2811. maxPK = pk
  2812. }
  2813. }
  2814. if maxPK != 0 {
  2815. return maxPK
  2816. }
  2817. }
  2818. return 0
  2819. }
  2820. // SearchByIndex 通过索引搜索一行
  2821. func (table *Table) SearchByIndex(idxName string, key []byte) ([]types.FieldValue, error) {
  2822. // 元信息只查一次, 命中后复用给 searchAt, 避免重复扫描 metas
  2823. meta := table.indexMeta.findIndexMetaByName(idxName)
  2824. if meta == nil {
  2825. return nil, ErrFieldNotFound
  2826. }
  2827. storeRow, found := table.indexMeta.searchAt(meta, key)
  2828. // storeRow = [pkID(8B BE) + rowData]
  2829. if !found || len(storeRow) < 8 {
  2830. return nil, page.ErrNotFound
  2831. }
  2832. return table.deserializeRow(storeRow[8:]), nil
  2833. }
  2834. // DeleteRow 通过索引删除一行
  2835. func (table *Table) DeleteRow(idxName string, key []byte) error {
  2836. // 一次定位: 既用于释放该行的堆页引用, 也下传给索引层删除 —— 避免同一行被查两次树
  2837. storeRow, found := table.indexMeta.SearchByIndex(idxName, key)
  2838. if !found || len(storeRow) < 8 {
  2839. return page.ErrNotFound
  2840. }
  2841. if err := table.indexMeta.DeleteByKey(idxName, key, storeRow, table.Field, table.activeVersion, table.versions); err != nil {
  2842. return err
  2843. }
  2844. // 行已删除 → 释放其动态字段占用的堆块; 计数归零的堆页会被摘链回收
  2845. table.heapReleaseRow(storeRow[8:])
  2846. table.info.TotalRows--
  2847. table.saveTableInfo()
  2848. if table.onWrite != nil {
  2849. table.onWrite()
  2850. }
  2851. return nil
  2852. }
  2853. // checkUniqueUpdate 更新前的唯一性预检 —— 必须早于 serializeRow。
  2854. //
  2855. // 用 serializeRowForCheck 试算行字节 (全程不碰堆), 再交给索引层比对。冲突时旧行分毫
  2856. // 未动, 动态堆块也没有被覆写或释放。代价是更新路径多一次序列化, 但更新本来就要回表、
  2857. // 定位、重写行, 这笔开销可接受; 换来的是"要么全改, 要么全不动"。
  2858. //
  2859. // 为什么不能只靠 UpdateByKey 里那次预检: 那时 serializeRow 已经跑过了, 旧行的动态
  2860. // 列早就被改成新值, 而固定列与索引条目还是旧的 —— 行被改了一半。
  2861. func (table *Table) checkUniqueUpdate(values []types.FieldValue, oldRowData []byte, pkID uint64) error {
  2862. // 没有唯一索引的表占绝大多数: 预检唯一的目的就是拦唯一冲突, 此时整行试序列化
  2863. // (serializeRowForCheck) 纯属浪费 —— 后续真正的 serializeRow 仍会校验值宽度,
  2864. // 非法值照样在写入前被拦下, 只是少了这次纯为唯一性服务的试算。
  2865. if !table.indexMeta.HasUniqueIndex() {
  2866. return nil
  2867. }
  2868. scratch, err := table.serializeRowForCheck(values, oldRowData)
  2869. if err != nil {
  2870. return err
  2871. }
  2872. return table.indexMeta.CheckUniqueForKey(scratch, pkID, table.Field, table.activeVersion, table.versions)
  2873. }
  2874. // UpdateRow 通过索引更新一行
  2875. func (table *Table) UpdateRow(idxName string, key []byte, values []types.FieldValue) error {
  2876. // 先取旧行: 动态字段可复用旧行已占用的堆块, 避免每次更新都产生堆垃圾。
  2877. // 该行同时下传给 UpdateByKey, 免去索引层再次回表。
  2878. storeRow, found := table.indexMeta.SearchByIndex(idxName, key)
  2879. if !found {
  2880. return ErrFieldNotFound
  2881. }
  2882. if len(storeRow) < 8 {
  2883. return ErrFieldNotFound
  2884. }
  2885. pkID := binary.BigEndian.Uint64(storeRow[:8])
  2886. // 唯一性预检放在序列化之前: 见 checkUniqueUpdate。
  2887. if err := table.checkUniqueUpdate(values, storeRow[8:], pkID); err != nil {
  2888. return err
  2889. }
  2890. newRowData, err := table.serializeRow(values, storeRow[8:])
  2891. if err != nil {
  2892. return err
  2893. }
  2894. if err := table.indexMeta.UpdateByKey(key, newRowData, pkID, storeRow, table.Field, table.activeVersion, table.versions); err != nil {
  2895. return err
  2896. }
  2897. if table.onWrite != nil {
  2898. table.onWrite()
  2899. }
  2900. return nil
  2901. }
  2902. // ======================== 按行号 (pkID) 直接读写 ========================
  2903. //
  2904. // 索引键只决定"落在哪个分段/哪一批", 行号才唯一确定"是哪一行"。
  2905. // 时序表里同一个时间戳允许存在多行 (同一时刻的多个测点/多次事件), 而上面那组
  2906. // "按索引键取第一条"的入口只能碰到第一行 —— 其余行存得进、读不到、改不了、删不掉。
  2907. // 下面这组入口按行号直达, 供 ORM (与任何需要精确定位一行的上层) 使用。
  2908. // FindByPK 按行号读取一行的字段值。
  2909. func (table *Table) FindByPK(pkID uint64) ([]types.FieldValue, error) {
  2910. storeRow, found := table.indexMeta.ResolveByPK(pkID)
  2911. if !found || len(storeRow) < 8 {
  2912. return nil, page.ErrNotFound
  2913. }
  2914. return table.deserializeRow(storeRow[8:]), nil
  2915. }
  2916. // SearchByIndexWithPK 同 SearchByIndex, 但额外把该行的真实行号一并返回。
  2917. //
  2918. // 行号是行的 8 字节前缀, 不在字段值里 —— 上层要把它带回对象, 之后才能按行号
  2919. // 精确 Update/Delete (见 ORM 的 modelCore.rowID)。
  2920. func (table *Table) SearchByIndexWithPK(idxName string, key []byte) (uint64, []types.FieldValue, error) {
  2921. meta := table.indexMeta.findIndexMetaByName(idxName)
  2922. if meta == nil {
  2923. return 0, nil, ErrFieldNotFound
  2924. }
  2925. storeRow, found := table.indexMeta.searchAt(meta, key)
  2926. // storeRow = [pkID(8B BE) + rowData]
  2927. if !found || len(storeRow) < 8 {
  2928. return 0, nil, page.ErrNotFound
  2929. }
  2930. return binary.BigEndian.Uint64(storeRow[:8]), table.deserializeRow(storeRow[8:]), nil
  2931. }
  2932. // DeleteRowByPK 按行号删除一行, 并级联清掉它在全部索引里的条目。
  2933. func (table *Table) DeleteRowByPK(pkID uint64) error {
  2934. // 一次回表: 既拿到行数据用于释放动态字段的堆块, 也下传给索引层定位 pkID。
  2935. storeRow, found := table.indexMeta.ResolveByPK(pkID)
  2936. if !found || len(storeRow) < 8 {
  2937. return page.ErrNotFound
  2938. }
  2939. name, _, ok := table.indexMeta.rowStoreMeta()
  2940. if !ok {
  2941. return ErrFieldNotFound
  2942. }
  2943. // indexKey 传 nil: 行数据在手, 每个索引都能自己从行里推出键 (时序键/主键列)。
  2944. if err := table.indexMeta.DeleteByKey(name.Name(), nil, storeRow, table.Field, table.activeVersion, table.versions); err != nil {
  2945. return err
  2946. }
  2947. table.heapReleaseRow(storeRow[8:])
  2948. table.info.TotalRows--
  2949. table.saveTableInfo()
  2950. if table.onWrite != nil {
  2951. table.onWrite()
  2952. }
  2953. return nil
  2954. }
  2955. // UpdateRowByPK 按行号更新一行, 并级联更新它在全部索引里的条目。
  2956. func (table *Table) UpdateRowByPK(pkID uint64, values []types.FieldValue) error {
  2957. // 旧行既用于复用动态字段堆块, 也用于让索引层提取"旧键" (时序键变了要搬分段)。
  2958. storeRow, found := table.indexMeta.ResolveByPK(pkID)
  2959. if !found || len(storeRow) < 8 {
  2960. return ErrFieldNotFound
  2961. }
  2962. // 唯一性预检放在序列化之前: 见 checkUniqueUpdate。
  2963. if err := table.checkUniqueUpdate(values, storeRow[8:], pkID); err != nil {
  2964. return err
  2965. }
  2966. newRowData, err := table.serializeRow(values, storeRow[8:])
  2967. if err != nil {
  2968. return err
  2969. }
  2970. if err := table.indexMeta.UpdateByKey(nil, newRowData, pkID, storeRow, table.Field, table.activeVersion, table.versions); err != nil {
  2971. return err
  2972. }
  2973. if table.onWrite != nil {
  2974. table.onWrite()
  2975. }
  2976. return nil
  2977. }