table_rowcodec.go 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845
  1. package core
  2. import (
  3. "bytes"
  4. "cmp"
  5. "encoding/binary"
  6. "fmt"
  7. "io"
  8. "slices"
  9. "strings"
  10. "git.ycl.pub/yl32940/TsDB2/DB/core/types"
  11. "git.ycl.pub/yl32940/TsDB2/DB/core/util"
  12. )
  13. // ======================== 行数据序列化/反序列化 ========================
  14. // lookupFieldValue 在 FieldValue 切片中按字段名取值
  15. func lookupFieldValue(values []types.FieldValue, name []byte) (interface{}, bool) {
  16. for _, fv := range values {
  17. // bytes.Equal 直接比较字节, 避免 string(x)==string(y) 触发两次 string 分配
  18. if bytes.Equal(fv.FieldName, name) {
  19. return fv.Value, true
  20. }
  21. }
  22. return nil, false
  23. }
  24. // encodeFieldValue 把一列的值编码成行内字节, 不写缓冲: 字符串返回【实际长度】,
  25. // 定长类型返回档位宽度的字节。布局阶段统一切片拷贝 (见 serializeRowInternal)。
  26. //
  27. // 形参是 FieldInfo 而不是 FieldType: 宽度与列级参数都必须取自【同一个列定义】——
  28. // 拆成两个参数传 (类型来自这里、宽度码来自那里) 就会出现"按 256 字节编、按 2048 字节存"
  29. // 这类不一致, 而它表现为整行错位而不是报错。
  30. func (table *Table) encodeFieldValue(masterIdx int, f types.FieldInfo, value interface{}) ([]byte, error) {
  31. ft := f.FieldType
  32. // 字符串家族: 按【实际长度】编码 —— 这是 v9 变长省空间的主体。超宽仍在编码时直接
  33. // 报错 (声明宽度是硬闸门), 但不再补零到档位。
  34. if types.IsVarWidth(ft) {
  35. enc, err := types.EncodeValueVar(ft, f.Param(), value)
  36. if err != nil {
  37. return nil, fmt.Errorf("字段 %q 写入失败: %w", table.Field[masterIdx].FieldName, err)
  38. }
  39. return enc, nil
  40. }
  41. size := f.Width()
  42. if size == 0 {
  43. return nil, fmt.Errorf("%w: 字段 %q (%s) 的列级参数 %d 非法, 宽度不可计算",
  44. types.ErrValueRange, f.FieldName, types.TypeNameOf(ft), f.Param())
  45. }
  46. buf := make([]byte, size)
  47. // 编码统一走 types.EncodeValue —— 与索引键字节同源 (见 types/codec.go)。
  48. // param 取列级类型参数: decimal 必须在这里校验"值的 scale == 列声明的 scale",
  49. // 否则同一列出现两种 scale 会让"字节序 == 数值序"失效, 索引范围扫描静默漏行;
  50. // 字符串列则由它决定列宽闸门 (超宽在 EncodeValue 里直接报错, 不再截断)。
  51. if err := types.EncodeValue(ft, f.Param(), value, buf); err != nil {
  52. return nil, fmt.Errorf("字段 %q 写入失败: %w", table.Field[masterIdx].FieldName, err)
  53. }
  54. return buf, nil
  55. }
  56. // dynamicFieldData 解析动态列的值 ([]byte; nil 表示空值), 并卡单块尺寸闸门。
  57. //
  58. // 动态列的值只认 []byte。旧写法是 `data, _ := value.([]byte)`: 一旦传进来
  59. // 的是别的类型 (string / 数值), 断言失败 → data 为 nil → 整个值被静默写成空。
  60. // 这与"不静默丢内容"直接冲突, 所以这里改为显式报错 (nil 仍表示空值)。
  61. func (table *Table) dynamicFieldData(masterIdx int, f types.FieldInfo, value interface{}) ([]byte, error) {
  62. if value == nil {
  63. return nil, nil
  64. }
  65. data, ok := value.([]byte)
  66. if !ok {
  67. return nil, fmt.Errorf("字段 %q (%s): 动态列只接受 []byte, 收到 %T",
  68. f.FieldName, types.TypeNameOf(f.FieldType), value)
  69. }
  70. // 动态列的单块尺寸闸门: 超限在这里就报出列名与上限, 而不是等 heapAllocBlock
  71. // 抛出与"值太大"因果关系不明的 ErrHeapPageTooLarge。
  72. //
  73. // 只覆盖 Dynamicbyte: 它的行内是 6B 堆引用, 内容整块落进一个堆页, 装不下更大的值。
  74. // Blob 列不走这里 —— v10 起它的行内是 8B BlobRef, 内容落在可跨页的链上 (上限 ~4GB,
  75. // 见 blobCommit). 往 Dynamicbyte 写超限值若不在这一层拦, 只会得到不带列名、不带
  76. // 字节数的"申请空间超过单页最大容量", 用户看不出是哪一列、超了多少。
  77. if uint32(len(data)) > MaxBlobSize {
  78. return nil, fmt.Errorf("%w: 字段 %q (%s) 的值 %d 字节超过单块上限 %d 字节 —— "+
  79. "动态列的内容整块落进一个堆页, 更大的内容引擎目前装不下: 多页分块存储尚未实现, "+
  80. "请改小这个值, 或改用 blob 列 (链式存储, 可跨页)。",
  81. types.ErrValueRange, f.FieldName,
  82. types.TypeNameOf(f.FieldType), len(data), MaxBlobSize)
  83. }
  84. return data, nil
  85. }
  86. // blobFieldRef 把 Blob 列的载体值统一成行内 8B 的 types.BlobRef.
  87. //
  88. // Blob 列行内只放引用, 而用户写这一列时手上多半是【内容本身】。这里就是
  89. // types/blob.go 里"把 io.Reader / []byte 写进 FieldType_Blob 列, 引擎自动走链页"
  90. // 的那个落点 —— 调用方不必自己开事务、自己调 BlobCommit。
  91. //
  92. // 接受的值:
  93. //
  94. // nil -> 空引用 (NULL 已在上游 Pass 1 拦掉, 这里只兜底非空列的空值)
  95. // types.BlobRef -> 原样 (已经是引用: 用户自己 tx.BlobCommit 过, 或读回侧镜像)
  96. // types.Blob -> Source 优先 (可流式), 否则 Bytes; 已带 ref 的直接用
  97. // []byte / string -> 走链页 (空内容不分配页, 直接给空引用)
  98. // io.Reader -> 走链页 (流式, 内存峰值与内容大小无关)
  99. // 其它类型 -> 报错 (与"不静默丢内容"一致)
  100. //
  101. // checkOnly 是唯一性试算行 (serializeRowForCheck): 绝不能落盘, 返回零引用即可 ——
  102. // Blob 列不能当索引键 (见 CreateIndex), 试算行的键字节与它无关。
  103. func (table *Table) blobFieldRef(f types.FieldInfo, value interface{}, checkOnly bool) (BlobRef, bool, error) {
  104. switch v := value.(type) {
  105. case BlobRef: // == types.BlobRef (类型别名), 引用原样透传
  106. return v, false, nil
  107. case types.Blob:
  108. return table.blobRefFromValue(f, v, checkOnly)
  109. case *types.Blob:
  110. if v == nil {
  111. return BlobRef{}, false, nil
  112. }
  113. return table.blobRefFromValue(f, *v, checkOnly)
  114. }
  115. if checkOnly {
  116. return BlobRef{}, false, nil
  117. }
  118. var src io.Reader
  119. switch v := value.(type) {
  120. case []byte:
  121. if len(v) == 0 {
  122. return BlobRef{}, false, nil
  123. }
  124. src = bytes.NewReader(v)
  125. case string:
  126. if v == "" {
  127. return BlobRef{}, false, nil
  128. }
  129. src = strings.NewReader(v)
  130. case io.Reader:
  131. src = v
  132. default:
  133. return BlobRef{}, false, fmt.Errorf("字段 %q (%s): blob 列只接受 []byte / string / io.Reader / types.Blob / types.BlobRef, 收到 %T",
  134. f.FieldName, types.TypeNameOf(f.FieldType), value)
  135. }
  136. ref, err := table.db.blobCommit(src, types.CompressNone)
  137. if err != nil {
  138. return BlobRef{}, false, fmt.Errorf("字段 %q (%s): blob 落链失败: %w",
  139. f.FieldName, types.TypeNameOf(f.FieldType), err)
  140. }
  141. return ref, true, nil
  142. }
  143. // blobRefFromValue 处理 types.Blob 载体: 既有"内容"形态 (Source / Bytes),
  144. // 也有"读回镜像"形态 (FirstPageID / TotalLen 已由引擎填好).
  145. //
  146. // 返回值 owned 标注该引用是否本次新落的链: 调用方在【整行写入失败】时只能还
  147. // 自己新落的链 —— 透传引用 (调用方持有的) 还掉会把别的行/别的表的链误回收。
  148. func (table *Table) blobRefFromValue(f types.FieldInfo, b types.Blob, checkOnly bool) (BlobRef, bool, error) {
  149. hasRef := b.FirstPageID != 0 || b.TotalLen != 0
  150. hasContent := b.Source != nil || len(b.Bytes) > 0
  151. if hasRef {
  152. if hasContent {
  153. return BlobRef{}, false, fmt.Errorf("字段 %q: types.Blob 同时带了引用与内容 (Source/Bytes), 无法判定该用哪个", f.FieldName)
  154. }
  155. return b.Ref(), false, nil // 读回镜像: 原样透传
  156. }
  157. if !hasContent {
  158. return BlobRef{}, false, nil // 空 blob: 不分配链页
  159. }
  160. if checkOnly {
  161. return BlobRef{}, false, nil
  162. }
  163. if b.Source != nil && len(b.Bytes) > 0 {
  164. return BlobRef{}, false, fmt.Errorf("字段 %q: types.Blob 的 Source 与 Bytes 都非空, 引擎无法选择", f.FieldName)
  165. }
  166. if !b.Compress.IsValid() {
  167. return BlobRef{}, false, fmt.Errorf("字段 %q: types.Blob.Compress 非法", f.FieldName)
  168. }
  169. var src io.Reader
  170. if b.Source != nil {
  171. src = b.Source
  172. } else {
  173. src = bytes.NewReader(b.Bytes)
  174. }
  175. ref, err := table.db.blobCommit(src, b.Compress)
  176. if err != nil {
  177. return BlobRef{}, false, fmt.Errorf("字段 %q (%s): blob 落链失败: %w",
  178. f.FieldName, types.TypeNameOf(f.FieldType), err)
  179. }
  180. return ref, true, nil
  181. }
  182. // serializeRow 将 FieldValue 切片序列化为行数据字节 (不含 pkID 前缀)
  183. // 行格式 (v9): [schemaVer 1B][列0 W0 B][列结束偏移表 2N][列1..N-1 内容][NULL 位图]
  184. // 动态字段的行内数据为 6 字节堆引用; oldRowData 用于更新时复用堆块。
  185. //
  186. // 两点关键语义 (否则 schema 演进后更新旧行会静默丢数据):
  187. // - 版本选择走 pickVersionForUpdate: 未触及新字段时保持旧版本, 偏移表与列序都不变,
  188. // 不会因为"只改了个老字段"就无谓升版; 但 v9 的字符串列按【实际长度】存, 所以
  189. // "版本不变"不等于"行长不变", 变长仍可能发生 (由索引层的原地→页内重建→换页兜住)。
  190. // - values 中未提供的字段用旧行原值补齐, 而不是写零
  191. // (否则 "只改一列" 会把该行其它列全部清空)
  192. func (table *Table) serializeRow(values []types.FieldValue, oldRowData []byte) ([]byte, error) {
  193. return table.serializeRowInternal(values, oldRowData, false)
  194. }
  195. // serializeRowForCheck 试算一遍行字节: 除动态列外与 serializeRow 逐字节一致。
  196. //
  197. // 动态列在这里【不碰堆】—— 不分配、不复用、不释放, 行内 6B 引用留全零。存在的理由
  198. // 是更新前的唯一性预检必须早于 serializeRow: writeDynamicField 会就地覆写旧行的堆块
  199. // (heapRewrite), 换块时还会 heapUnref 旧块。等 UpdateByKey 报出键冲突再返回错误,
  200. // 旧行的动态列已被改成新值 (极端情况下堆块已释放而旧行仍指着它), 而固定列与索引条目
  201. // 还是旧的 —— 行被改了一半, 且不留任何痕迹。多一次序列化换"要么全改要么全不动"。
  202. //
  203. // 索引分量一定不是动态列 (CreateIndex 已拒绝), NULL 位图也只取决于"值是否为 nil 且
  204. // 列可空", 与堆无关 —— 所以试算行算出的索引键与真身完全一致。
  205. func (table *Table) serializeRowForCheck(values []types.FieldValue, oldRowData []byte) ([]byte, error) {
  206. return table.serializeRowInternal(values, oldRowData, true)
  207. }
  208. // nullFieldError 非空列被写入 NULL 时的统一报错 (两处循环共用, 文案必须一致)。
  209. func nullFieldError(name string, ft types.FieldType) error {
  210. return fmt.Errorf("%w: 字段 %q (%s) 未声明可空 (nullable), 不接受 NULL; "+
  211. "要允许写入 NULL, 请在列声明里加 `nullable` / `null`",
  212. types.ErrValueRange, name, types.TypeNameOf(ft))
  213. }
  214. func (table *Table) serializeRowInternal(values []types.FieldValue, oldRowData []byte, checkOnly bool) (_ []byte, err error) {
  215. // 失败回收: blob 列在 Pass 1 就落链 (行内只有 8B 引用), 一旦后续列编码失败 /
  216. // 布局自检失败, 这批新链页还不掉就成为孤儿页 (无事务写入的既有泄漏路径,
  217. // 见 blobCommit 的注释)。只还"本次新落的链" (owned) —— 透传引用
  218. // (调用方持有的 types.BlobRef / 读回镜像) 不属于本行, 绝不能碰。
  219. // 这些链此刻尚未被任何行/索引引用, 无条件归还安全; 更新路径 Pass 3 已释放的
  220. // 旧链不会出现在这里 (owned 只记新链)。
  221. var newBlobRefs []BlobRef
  222. defer func() {
  223. if err != nil && !checkOnly {
  224. for _, r := range newBlobRefs {
  225. _ = table.BlobFree(r)
  226. }
  227. }
  228. }()
  229. ver := table.pickVersionForUpdate(oldRowData, values)
  230. // 写行闸门 (第三处行宽校验)。
  231. //
  232. // 前两处在建列侧 (TablePage.Create / Table.AddField / CreateIndex), 管的是"新表 /
  233. // 新索引不许超宽"; 这一处管的是"已经存在的老表": 表是在限制生效前建的 (或 schema
  234. // 被外部改坏) 时, 行字节数会超过一个 DataPage 能装下的量。放任它走下去的后果不是
  235. // "写失败"这么轻 —— 行长度会在 DataPage 里溢出到相邻区域, 把整页的行解析全部错位
  236. // (页内行是顺序排布、靠每行 4B 长度前缀自描述的, 一行错位等于整页报废)。
  237. //
  238. // 【v9 起判据是"溢出下界"】行按内容变长, 实际长度要等内容全编完才知道, 而那时堆块
  239. // 可能已经动过 (writeHeapRefField 会覆写/释放旧块), 再报错就留下了半改的行。这里用
  240. // types.CalcSpillFloor 的下界 —— "把所有可溢出的字符串列都按 6B 引用计"之后仍超
  241. // 物理墙, 说明不动堆永远救不回来, 且这个判断发生在动堆之前。
  242. floor := table.rowSpillFloorForVersion(ver)
  243. if floor > MaxPhysicalRowData {
  244. return nil, fmt.Errorf("%w: 表 %q 的 schema 版本 %d 行宽溢出下界 %d 字节超过单页可容纳上限 %d 字节, 拒绝写入; "+
  245. "请重建该表并把大列改为动态列 (FieldType_Dynamicbyte / Blob)",
  246. types.ErrValueRange, util.BytesToString(table.info.Name[:]), ver, floor, MaxPhysicalRowData)
  247. }
  248. sv := table.getVersion(ver)
  249. if sv == nil {
  250. return []byte{ver}, nil
  251. }
  252. n := len(sv.FieldIndices)
  253. if n == 0 {
  254. return []byte{ver}, nil
  255. }
  256. // 基底: 旧行已存在的字段值 (版本感知解析, 旧行不会凭空多出新字段)
  257. var base []types.FieldValue
  258. if len(oldRowData) > 0 {
  259. base = table.deserializeRow(oldRowData)
  260. }
  261. // 旧行的 NULL 列集合: deserializeRow 不产出 NULL 列 (跳过), 部分更新时
  262. // "未提供"的列查 base 会落空、被零值兜底 —— 旧行里的 NULL 必须随位图保留,
  263. // 否则一次只改 title 的 UPDATE 会把 deleted_at 这类 NULL 列静默写成零值,
  264. // 软删除作用域 (deleted_at IS NULL) 从此查不到该行 (实测报告 #1/#3 根因)。
  265. oldNulls := map[int]bool{} // masterIdx → 旧行该列为 NULL
  266. if len(oldRowData) > 0 {
  267. if layout, ok := table.parseRowLayout(oldRowData); ok {
  268. oldN := len(layout.sv.FieldIndices)
  269. for oi, idx := range layout.sv.FieldIndices {
  270. if types.RowIsNull(oldRowData, layout.ends, oldN, oi) {
  271. oldNulls[int(idx)] = true
  272. }
  273. }
  274. }
  275. }
  276. // 行布局 (v9, 详见 types/rowvar.go):
  277. // [schemaVer 1B][列0 W0 B][列结束偏移表 2N][列1..N-1 内容][NULL 位图]
  278. //
  279. // - 列 0 恒占 [1, 1+W0) 且 W0 由其【档位宽度】决定 (不是实际内容长度): 偏移表基址
  280. // 因此可静态算出, 解析端不必先猜列 0 有多长。时序表的列 0 就是 8 字节时间键
  281. // (W0 == 8), 时间键于是落在【绝对偏移 9】, 与 index 侧写死的读取偏移一致。
  282. // 列 0 不足 W0 时补零 —— 与 v8 的定长存储逐字节相同。
  283. // - 偏移表落在"只取决于字段数与列 0 宽度"的固定位置, 因此原地更新 (新行写进旧槽位、
  284. // 尾部清零) 冲不掉它, 解析永远精确, 不依赖 len(rowData)。
  285. // - 表项 0 = 变长内容区起点 (= RowHeaderSize); 列 i (i ≥ 1) 的起点 = 表项 i-1。
  286. // - 表项的 bit15 (RowVarTag) 为真表示该位置是 6B 堆引用: 动态列恒置位, 值级溢出
  287. // 的字符串列溢出时置位 —— 读侧一律以 tag 为准, 不再看列类型。
  288. //
  289. // 序列化分三步: ① 解析并编码各列 (不写缓冲); ② 算全内联长度, 决定哪些字符串列要
  290. // 【值级溢出】(行内装不下时把未建索引的宽字符串列降级为 6B 堆引用, 见下方 Pass 2);
  291. // ③ 按最终布局一次写出行字节。分步的代价是每列多一次切片拷贝, 换来溢出决策发生在
  292. // 任何堆写入之前 —— 布局对溢出集合是闭式的, 不需要"先写再改"。
  293. col0Width := table.svCol0Width(sv)
  294. if col0Width <= 0 {
  295. return nil, fmt.Errorf("%w: 表 %q 的首列 %q 宽度不可计算 (列级参数非法); "+
  296. "行内偏移表的位置依赖首列宽度, 无法写行",
  297. types.ErrValueRange, util.BytesToString(table.info.Name[:]),
  298. util.BytesToString(table.Field[sv.FieldIndices[0]].FieldName))
  299. }
  300. // 旧行持有的堆引用 (动态列 ∪ 值级溢出列), 布局阶段按列取用: 复用 / 释放。
  301. // oldRowHeapRefs 只认"动态列或带 tag 的列", 内联内容绝不会被误当引用。
  302. oldRefs := table.oldRowHeapRefs(oldRowData)
  303. releaseOld := func(masterIdx int) {
  304. if !checkOnly {
  305. if r, ok := oldRefs[masterIdx]; ok {
  306. _ = table.heapUnref(r.pid)
  307. }
  308. }
  309. }
  310. // v10 起 blob 列同样需要在更新路径回收旧链 ——
  311. // - 改写该列 (NULL / 新 ref) 时 Pass 1 释放旧 ref;
  312. // - 同值"复用"时 Pass 3 不释放, 否则会与新 ref 重叠 (同一链页释放两次 = 误回收).
  313. // 同样按 masterIdx 索引, 解析失败/无 blob 列时为 nil, 调用安全.
  314. oldBlobRefs := table.oldBlobRefsByMaster(oldRowData)
  315. releaseOldBlob := func(masterIdx int) {
  316. if !checkOnly {
  317. if r, ok := oldBlobRefs[masterIdx]; ok {
  318. _ = table.BlobFree(r)
  319. }
  320. }
  321. }
  322. // ---- Pass 1: 解析并编码各列 ----
  323. //
  324. // colBytes[i]: 动态列 = 原始内容 (布局时落堆); 其余非 NULL 列 = 行内字节
  325. // (字符串按实际长度, 定长按档位宽度)。未提供且旧行也没有的列保持 nil:
  326. // 字符串零值就是"空" (0 字节), 定长零值就是全零 (布局时按宽度零填充)。
  327. colBytes := make([][]byte, n)
  328. colNull := make([]bool, n)
  329. bitmapLen := types.NullBitmapLen(n)
  330. bitmap := make([]byte, bitmapLen)
  331. // 列名→值映射: 列数较多时避免 Pass 1 整体 O(列数²) 的线性扫。
  332. // 小表 (≤8 列) 直接线性扫更快 —— 免一次 map 分配; base (旧行) 只在缺列时
  333. // 兜底, 常见路径 (全列提交) 不会碰到, 保持线性扫即可。
  334. var valByName map[string]interface{}
  335. if n > 8 {
  336. valByName = make(map[string]interface{}, len(values))
  337. for _, fv := range values {
  338. valByName[string(fv.FieldName)] = fv.Value
  339. }
  340. }
  341. lookupVal := func(name []byte) (interface{}, bool) {
  342. if valByName != nil {
  343. v, ok := valByName[string(name)]
  344. return v, ok
  345. }
  346. return lookupFieldValue(values, name)
  347. }
  348. for i, masterIdx := range sv.FieldIndices {
  349. f := table.Field[masterIdx]
  350. name := f.FieldName
  351. // 取值优先级: 本次传入 > 旧行原值 (未提供的字段沿用旧值, 否则"只改一列"会清空其它列)
  352. value, provided := lookupVal(name)
  353. if !provided {
  354. value, provided = lookupFieldValue(base, name)
  355. }
  356. switch {
  357. case provided && value == nil:
  358. if !f.IsNullable() {
  359. // 非空列写 NULL: 拒绝并点名列。
  360. //
  361. // 这里不是"收紧", 而是把已有的不一致统一掉: 定长列写 nil 本来就走不通
  362. // (EncodeValue 对 nil 报 ErrValueType), 唯独动态列曾把 nil 静默当成
  363. // "空内容"存下去 (行内引用全 0, NULL 位图也不置位) —— 同一句"写 nil"
  364. // 在定长列报错、在动态列静默成功。既然声明了非空, 就该两边都拒。
  365. //
  366. // 注意 base (旧行原值) 不会贡献 nil: deserializeRow 只 append 非 nil 的值
  367. // (见那里的 `if val != nil`), 所以这条只拦"本次显式传入的 NULL",
  368. // 不会让"只改其它列"的更新半路失败。
  369. return nil, nullFieldError(util.BytesToString(name), f.FieldType)
  370. }
  371. // NULL: 位图置位, 不占内容字节 (语义完全由位图承载; 动态列/溢出列因此不写堆引用)。
  372. // 试算模式也要置位 —— extractIndexKey 正是靠位图判定"该行不进本索引"。
  373. types.SetNullBit(bitmap, i)
  374. // 旧行若在该列持有堆块, 新行不再引用它 —— 不在这里释放就是每更新一次漏一块。
  375. releaseOld(int(masterIdx))
  376. // v10: blob 列同样要释放旧 ref, 走 BlobFree 归还链页.
  377. releaseOldBlob(int(masterIdx))
  378. case provided && types.IsDynamic(f.FieldType):
  379. data, err := table.dynamicFieldData(int(masterIdx), f, value)
  380. if err != nil {
  381. return nil, err
  382. }
  383. colBytes[i] = data
  384. case provided && types.IsBlob(f.FieldType):
  385. // Blob 列行内只有 8B BlobRef, 但用户手上多半是"内容本身" ([]byte / io.Reader),
  386. // 由引擎在这里把内容落进链页、换成引用 (见 blobFieldRef). 已经是引用的原样用。
  387. // 试算行 (checkOnly) 不落盘: Blob 列不能当索引键 (见 CreateIndex), 键字节与它无关。
  388. ref, owned, err := table.blobFieldRef(f, value, checkOnly)
  389. if err != nil {
  390. return nil, err
  391. }
  392. if owned {
  393. newBlobRefs = append(newBlobRefs, ref)
  394. }
  395. enc, err := table.encodeFieldValue(int(masterIdx), f, ref)
  396. if err != nil {
  397. return nil, err
  398. }
  399. colBytes[i] = enc
  400. case provided:
  401. enc, err := table.encodeFieldValue(int(masterIdx), f, value)
  402. if err != nil {
  403. return nil, err
  404. }
  405. if i == 0 && len(enc) > col0Width {
  406. return nil, fmt.Errorf("%w: 字段 %q 编码后 %d 字节超过首列档位宽度 %d",
  407. types.ErrEncodeBufSize, util.BytesToString(name), len(enc), col0Width)
  408. }
  409. colBytes[i] = enc
  410. default:
  411. // 未提供: 列 0 与定长列在布局阶段按档位宽度零填充; 字符串零值 = 空 (0 字节)。
  412. // 例外: 旧行该列本是 NULL — 位图置位、不占内容字节 (与显式 NULL 同布局;
  413. // 不做非空拒绝: 旧数据可能早于约束收紧, 保留原状比重写/报错安全)。
  414. if oldNulls[int(masterIdx)] {
  415. types.SetNullBit(bitmap, i)
  416. colNull[i] = true
  417. } else {
  418. colNull[i] = false // 占位, 语义见上
  419. }
  420. }
  421. }
  422. // ---- Pass 2: 值级溢出决策 ----
  423. //
  424. // 全内联长度超过 MaxInlineRowData ("一页两行"政策的行内配额) 时, 把【未建索引的
  425. // 宽字符串列】按"内容长度降序"逐个降级为 6B 堆引用, 直到退回限内。列 0 不能溢出
  426. // (偏移表基址依赖它的档位宽度); 建了索引的列不能溢出 (索引键按行内字节拼,
  427. // 见 CreateIndex 的溢出闸门); NULL 列与内容 ≤ 6B 的列没有可省的字节。
  428. //
  429. // 下界闸门 (开头的 rowSpillFloorForVersion) 保证了"把能溢出的全溢出去"一定装得下,
  430. // 所以这个循环必然终止于限内 (除非老表超限 —— 那已在上界处拒绝)。
  431. // checkOnly (试算行) 走同样的决策: 溢出的列不可能是索引分量, 键不受影响; 但堆引用
  432. // 只写零占位, 不碰堆。
  433. contentStart := types.RowHeaderSize(n, col0Width)
  434. inlineLen := int(contentStart) + bitmapLen
  435. for i := 1; i < n; i++ {
  436. f := table.Field[sv.FieldIndices[i]]
  437. switch {
  438. case types.IsDynamic(f.FieldType):
  439. // 动态列恒占 6B 堆引用 (即便这一行是 NULL: 布局仍写零引用参差对齐)。
  440. inlineLen += types.DynamicRefSize
  441. case types.IsBlob(f.FieldType):
  442. // blob 列同理恒占 8B 内联引用: Pass 3 对 blob 列【无条件】推进 8B
  443. // (NULL 只是不拷字节, 槽位仍在), 这里若按 colNull 记 0, 末尾那句
  444. // "行布局长度失配" 就会在"写一行带 NULL blob 列"时直接报错。
  445. inlineLen += types.BlobRefSize
  446. case colNull[i]:
  447. // NULL 列不占内容字节 (仅位图置位)。
  448. default:
  449. // 必须与 Pass 3 用同一个占用公式, 否则"预算"与"实际布局"会对不上:
  450. // 字符串列在这里也要按实际长度计, 而不是档位宽度。
  451. inlineLen += fieldInlineWidth(f, colBytes[i])
  452. }
  453. }
  454. spilled := make([]bool, n)
  455. if inlineLen > types.MaxInlineRowData {
  456. ineligible := table.spillIneligibleFields()
  457. type spillCand struct {
  458. i, gain int
  459. }
  460. cands := make([]spillCand, 0, n)
  461. for i := 1; i < n; i++ {
  462. if spilled[i] || colNull[i] {
  463. continue
  464. }
  465. masterIdx := int(sv.FieldIndices[i])
  466. f := table.Field[masterIdx]
  467. if !types.IsVarWidth(f.FieldType) || ineligible[masterIdx] {
  468. continue
  469. }
  470. if gain := len(colBytes[i]) - types.DynamicRefSize; gain > 0 {
  471. cands = append(cands, spillCand{i, gain})
  472. }
  473. }
  474. slices.SortFunc(cands, func(a, b spillCand) int { return cmp.Compare(b.gain, a.gain) })
  475. cur := inlineLen
  476. for _, c := range cands {
  477. if cur <= types.MaxInlineRowData {
  478. break
  479. }
  480. spilled[c.i] = true
  481. cur -= c.gain
  482. }
  483. }
  484. // ---- Pass 3: 布局 (一次成形, 长度精确) ----
  485. // 预算必须扣掉"值级溢出省下的字节": Pass 3 对溢出列只写 6B 堆引用, 而 inlineLen
  486. // 仍按溢出前的实际长度计那些列。少扣这一笔, 末尾的 length 断言必然失败 —— 而且
  487. // 只在真正触发溢出时才失败 (窄行永远测不出来)。
  488. finalLen := inlineLen - curAdjust(spilled, colBytes)
  489. rowData := make([]byte, finalLen)
  490. rowData[0] = ver
  491. ends := make([]uint16, n)
  492. refs := make([]bool, n) // 偏移表项要置 RowVarTag 的列 (动态列 ∪ 溢出列)
  493. ends[0] = uint16(contentStart)
  494. // 列 0: 档位宽度定长存储 —— 编码字节拷进 [1, 1+W0), 余下保持全零。
  495. switch f0 := table.Field[sv.FieldIndices[0]]; {
  496. case colNull[0]:
  497. // NULL: 位图已置位, 不占内容字节 (旧引用已在 Pass 1 释放)。
  498. case types.IsDynamic(f0.FieldType):
  499. // 动态首列 (理论情形): 行内就是 6B 堆引用, 与其它动态列走同一条路。
  500. // 绝不能把内容原样拷进 6B 槽位 —— 那会盖住后面的偏移表, 整行错位。
  501. if err := table.writeHeapRefField(rowData, 1, colBytes[0],
  502. oldRefOrNil(oldRefs, int(sv.FieldIndices[0])), checkOnly); err != nil {
  503. return nil, err
  504. }
  505. case types.IsBlob(f0.FieldType):
  506. // Blob 首列: 行内 8B BlobRef 直引链页, 与其它 blob 列一致. 不走堆.
  507. if c0 := colBytes[0]; len(c0) >= types.BlobRefSize {
  508. copy(rowData[1:1+types.BlobRefSize], c0[:types.BlobRefSize])
  509. }
  510. default:
  511. if c0 := colBytes[0]; len(c0) > 0 {
  512. copy(rowData[1:], c0)
  513. }
  514. }
  515. offset := uint16(contentStart)
  516. for i := 1; i < n; i++ {
  517. f := table.Field[sv.FieldIndices[i]]
  518. isDynamic := types.IsDynamic(f.FieldType)
  519. isBlob := types.IsBlob(f.FieldType)
  520. switch {
  521. case isDynamic || spilled[i]:
  522. refs[i] = true
  523. old := oldRefOrNil(oldRefs, int(sv.FieldIndices[i]))
  524. if err := table.writeHeapRefField(rowData, offset, colBytes[i], old, checkOnly); err != nil {
  525. return nil, err
  526. }
  527. offset += types.DynamicRefSize
  528. case isBlob:
  529. // v10 起 Blob 列行内 8B BlobRef (直引链页), 不再走堆块。
  530. // colBytes[i] = 8B [TotalLen BE 4][FirstPageID BE 4] (由 encodeFieldValue 编码);
  531. // 空 / NULL 时为 0 字节 — 不拷任何字节, 但 offset 必须推进 8B 保持布局.
  532. if !colNull[i] && len(colBytes[i]) >= types.BlobRefSize {
  533. copy(rowData[offset:offset+types.BlobRefSize], colBytes[i][:types.BlobRefSize])
  534. // 改值情形 (新 ref 与旧 ref 不同): 旧链必须归还空闲链.
  535. // 同值"复用" (FirstPageID 与 TotalLen 都一致) 不释放 ——
  536. // 释放后再写新行会立刻挂空链. NULL 情形在 Pass 1 已释放.
  537. if !checkOnly {
  538. if oldRef, ok := oldBlobRefs[int(sv.FieldIndices[i])]; ok {
  539. newRef := BlobRef{
  540. TotalLen: binary.BigEndian.Uint32(colBytes[i][:4]),
  541. FirstPageID: binary.BigEndian.Uint32(colBytes[i][4:8]),
  542. }
  543. if oldRef != newRef {
  544. _ = table.BlobFree(oldRef)
  545. }
  546. }
  547. }
  548. }
  549. offset += types.BlobRefSize
  550. // 不置 RowVarTag — BlobRef 是内联 8B 字节, 读侧按类型识别.
  551. case colNull[i]:
  552. // 不占内容字节
  553. default:
  554. if len(colBytes[i]) > 0 {
  555. copy(rowData[offset:], colBytes[i])
  556. }
  557. // 定长列未提供时按档位宽度零填充 (make 已清零, 只推进偏移)。
  558. offset += uint16(fieldInlineWidth(f, colBytes[i]))
  559. // 旧行该列若曾溢出到堆 (值级溢出), 新行收回内联 → 旧块不再被引用, 释放。
  560. releaseOld(int(sv.FieldIndices[i]))
  561. }
  562. ends[i] = offset
  563. }
  564. // NULL 位图放在偏移表末项之后。
  565. copy(rowData[offset:], bitmap)
  566. if int(offset)+bitmapLen != finalLen {
  567. return nil, fmt.Errorf("%w: 行布局长度失配 (内容末尾 %d + 位图 %d != 预算 %d)",
  568. types.ErrEncodeBufSize, offset, bitmapLen, finalLen)
  569. }
  570. types.PutRowOffsets(rowData, ends, refs, col0Width)
  571. return rowData, nil
  572. }
  573. // fieldInlineWidth 非动态、非 NULL 列的行内占用。
  574. //
  575. // - 变长 (字符串家族): 按【实际长度】占位 —— 空串 / 未提供都是 0 字节, 这才是 v9 省
  576. // 空间的本体; 若按档位宽度零填充, 一个未提供的 1024 宽串会白占 1KB。
  577. // - 定长: 有编码字节按实际长度, 未提供则必须按档位宽度零填充 —— 定长列的零值是全零
  578. // 字节, 必须占住位置, 否则后面所有列错位。
  579. func fieldInlineWidth(f types.FieldInfo, enc []byte) int {
  580. if types.IsVarWidth(f.FieldType) || len(enc) > 0 {
  581. return len(enc)
  582. }
  583. return int(f.Width())
  584. }
  585. // oldRefOrNil 从旧行引用表取某列的引用 (无则 nil)。
  586. func oldRefOrNil(refs map[int]oldHeapRefInfo, masterIdx int) *oldHeapRefInfo {
  587. if r, ok := refs[masterIdx]; ok {
  588. return &r
  589. }
  590. return nil
  591. }
  592. // curAdjust 计算溢出集合实际省下的行内字节数 (仅用于布局预算自检)。
  593. func curAdjust(spilled []bool, colBytes [][]byte) int {
  594. saved := 0
  595. for i, sp := range spilled {
  596. if sp {
  597. saved += len(colBytes[i]) - types.DynamicRefSize
  598. }
  599. }
  600. return saved
  601. }
  602. func writeFieldValue(buf []byte, ft types.FieldType, value interface{}) {
  603. switch ft {
  604. case types.FieldType_Uint8:
  605. buf[0] = value.(byte)
  606. case types.FieldType_Uint16:
  607. binary.BigEndian.PutUint16(buf, uint16(value.(int16)))
  608. case types.FieldType_Uint32:
  609. binary.BigEndian.PutUint32(buf, uint32(value.(int32)))
  610. case types.FieldType_Uint64:
  611. binary.BigEndian.PutUint64(buf, value.(uint64))
  612. case types.FieldType_Int8:
  613. buf[0] = types.Int8ToSortKey(value.(int8))
  614. case types.FieldType_Int16:
  615. binary.BigEndian.PutUint16(buf, types.Int16ToSortKey(value.(int16)))
  616. case types.FieldType_Int32:
  617. binary.BigEndian.PutUint32(buf, types.Int32ToSortKey(value.(int32)))
  618. case types.FieldType_Int64:
  619. // 同 Float64: 存保序编码 (Int64ToSortKey) 而不是补码本身。补码大端对负数不保序,
  620. // 会让跨 0 的范围扫描 / ORDER BY 静默漏行; 换成保序编码后行内字节序即数值序,
  621. // 索引键直接复用行内字节 (extractIndexKey 原样切片) 也就自动保序。
  622. binary.BigEndian.PutUint64(buf, types.Int64ToSortKey(value.(int64)))
  623. case types.FieldType_Float64:
  624. // 存保序编码 (Float64ToSortKey) 而不是原始位模式: 行内字节序即数值序,
  625. // 索引键直接复用行内字节 (extractIndexKey 原样切片) 也就自动保序。
  626. binary.BigEndian.PutUint64(buf, types.Float64ToSortKey(toFloat64(value)))
  627. default:
  628. // 所有字符串/定长类型 (动态类型不走此分支, 由 writeDynamicField 处理)
  629. if b, ok := value.([]byte); ok {
  630. copy(buf, b)
  631. }
  632. }
  633. }
  634. // deserializeRow 将行数据字节反序列化为 FieldValue 切片 (不含 pkID 前缀)。
  635. //
  636. // 行格式 (v9): [schemaVer 1B][列0 W0 B][列结束偏移表 2N][列1..N-1 内容][NULL 位图]
  637. //
  638. // 列位置一律由行内偏移表给出 —— 旧实现"从偏移 1 开始累加各列 Width"的前提是字段区
  639. // 定长, 而 v9 的字符串列按实际长度存储, 累加 Width 会越算越偏 (表现为整行错列)。
  640. func (table *Table) deserializeRow(rowData []byte) []types.FieldValue {
  641. return table.deserializeRowMode(rowData, nil, false)
  642. }
  643. // deserializeRowKeepNull 与 deserializeRow 相同, 但 NULL 列也产出
  644. // FieldValue{Value: nil} (而不是跳过)。供 VACUUM 快照使用: 重建插入时
  645. // 显式传 nil 走 NULL 位图路径 —— deserializeRow 的"跳过 NULL"会让 VACUUM
  646. // 把 NULL 物化成类型零值 (可空列语义被静默改写, 且不可逆)。
  647. func (table *Table) deserializeRowKeepNull(rowData []byte) []types.FieldValue {
  648. return table.deserializeRowMode(rowData, nil, true)
  649. }
  650. // deserializeRowSelect 只解码掩码选中的列 (掩码按 master 下标)。
  651. //
  652. // 与 deserializeRow 同一套布局解析, 未选中列跳过解码与其堆解引用 —— 宽表的投影查询
  653. // 不再为不看的列付解码/堆页 IO 成本。掩码必须包含调用方后续要读的列
  654. // (Query.deserialize 负责把 ORDER BY 列并进来)。
  655. func (table *Table) deserializeRowSelect(rowData []byte, want []bool) []types.FieldValue {
  656. return table.deserializeRowMode(rowData, want, false)
  657. }
  658. // deserializeRowMode 行解码的公共实现。want 非 nil 时是投影掩码 (master
  659. // 下标 → 是否解码); keepNull 为 true 时 NULL 列产出显式 nil 值。
  660. func (table *Table) deserializeRowMode(rowData []byte, want []bool, keepNull bool) []types.FieldValue {
  661. if len(rowData) < 1 {
  662. return nil
  663. }
  664. layout, ok := table.parseRowLayout(rowData)
  665. if !ok {
  666. return nil // 未知版本 / 偏移表不可信, 无法解析
  667. }
  668. sv, n, col0Width, ends := layout.sv, len(layout.sv.FieldIndices), layout.col0Width, layout.ends
  669. bitmap, ok := types.RowNullBitmap(rowData, ends, n)
  670. if !ok {
  671. return nil
  672. }
  673. // 预分配容量: 字段数即结果条数的上界 (被删除 / NULL 字段会跳过), 避免 append 反复扩容
  674. result := make([]types.FieldValue, 0, n)
  675. for i, idx := range sv.FieldIndices {
  676. if want != nil && int(idx) < len(want) && !want[idx] {
  677. continue // 投影模式: 未选中的列不解码、不解堆引用
  678. }
  679. f := table.Field[idx]
  680. // 跳过已删除字段 (master registry 中标记 FlagDeleted) 与 NULL 字段。
  681. // NULL 不出现在结果里 —— 与"零值不 append"是同一约定: 读不到值就是没有值;
  682. // 更新路径靠它区分"沿用旧值"与"显式写 NULL"(见 serializeRow 的取值优先级)。
  683. // keepNull 模式 (VACUUM 快照) 例外: NULL 列产出显式 nil, 重建时走 NULL 位图。
  684. if f.IsDropped() || types.NullBitSet(bitmap, i) {
  685. if keepNull && !f.IsDropped() && types.NullBitSet(bitmap, i) {
  686. result = append(result, types.FieldValue{FieldName: f.FieldName, Value: nil})
  687. }
  688. continue
  689. }
  690. start := int(types.RowFieldStart(ends, i))
  691. end := int(types.RowFieldEnd(ends, i, col0Width))
  692. if end < start || end > len(rowData) {
  693. continue
  694. }
  695. seg := rowData[start:end]
  696. var val interface{}
  697. switch {
  698. case types.IsDynamic(f.FieldType) || types.RowFieldRefTagged(rowData, i, col0Width):
  699. // 解引用堆页。NULL / 未提供的动态列内容为 0 字节或全零引用 —— 读不到就是没有值。
  700. if data, ok := table.derefHeapSeg(seg); ok {
  701. val = data
  702. }
  703. case types.IsBlob(f.FieldType):
  704. // v10 起 Blob 列行内 8B BlobRef, 不走堆页. 直接还原 BlobRef.
  705. // 空 blob (TotalLen=0, FirstPageID=0) 视为"读不到", 与其它列的零值规则一致.
  706. if len(seg) < types.BlobRefSize {
  707. continue
  708. }
  709. ref := types.BlobRef{
  710. TotalLen: binary.BigEndian.Uint32(seg[:4]),
  711. FirstPageID: binary.BigEndian.Uint32(seg[4:8]),
  712. }
  713. if ref.Empty() {
  714. continue
  715. }
  716. val = ref
  717. case types.IsVarWidth(f.FieldType):
  718. // 字符串家族: seg 就是实际字节 (无补零), DecodeValue 按 len 解码, 天然支持变长。
  719. val = types.DecodeValue(f.FieldType, seg)
  720. default:
  721. // 定长类型: 长度必须恰好等于档位宽度, 否则行已损坏 (DecodeValue 对定长类型会
  722. // 按下标取值)。这时宁可跳过该列, 也不要按错位字节解出一个"看似合理"的数值。
  723. if len(seg) != int(f.Width()) {
  724. continue
  725. }
  726. val = types.DecodeValue(f.FieldType, seg)
  727. }
  728. if val != nil {
  729. result = append(result, types.FieldValue{FieldName: f.FieldName, Value: val})
  730. }
  731. }
  732. return result
  733. }
  734. // deserializeField 解码单个字段, 不物化整行。
  735. //
  736. // 聚合 (Sum/Max/Min) 每行只需要一列; 用 deserializeRow 意味着每行都分配一个
  737. // []FieldValue 并解码全部活跃列, 100 万行的 SUM 里这占了绝对大头。
  738. // 解码逻辑与 deserializeRow 逐分支一致 (行布局解析、NULL 位图、堆解引用),
  739. // 只是跳过"其余字段的解码与组装"。
  740. // 返回 (值, true); 字段不存在、NULL、行损坏时返回 (nil, false)。
  741. func (table *Table) deserializeField(rowData []byte, fieldName string) (interface{}, bool) {
  742. if len(rowData) < 1 {
  743. return nil, false
  744. }
  745. layout, ok := table.parseRowLayout(rowData)
  746. if !ok {
  747. return nil, false
  748. }
  749. sv, n, col0Width, ends := layout.sv, len(layout.sv.FieldIndices), layout.col0Width, layout.ends
  750. // 找到字段在行内的位置 (与 deserializeRow 的循环同一编号空间)
  751. pos := -1
  752. for i, idx := range sv.FieldIndices {
  753. f := table.Field[idx]
  754. if f.IsDropped() {
  755. continue
  756. }
  757. if string(f.FieldName) == fieldName {
  758. pos = i
  759. break
  760. }
  761. }
  762. if pos < 0 {
  763. return nil, false
  764. }
  765. bitmap, ok := types.RowNullBitmap(rowData, ends, n)
  766. if !ok {
  767. return nil, false
  768. }
  769. if types.NullBitSet(bitmap, pos) {
  770. return nil, false // NULL: 聚合语义上"读不到值"
  771. }
  772. f := table.Field[sv.FieldIndices[pos]]
  773. start := int(types.RowFieldStart(ends, pos))
  774. end := int(types.RowFieldEnd(ends, pos, col0Width))
  775. if end < start || end > len(rowData) {
  776. return nil, false
  777. }
  778. seg := rowData[start:end]
  779. switch {
  780. case types.IsDynamic(f.FieldType) || types.RowFieldRefTagged(rowData, pos, col0Width):
  781. return table.derefHeapSeg(seg)
  782. case types.IsBlob(f.FieldType):
  783. if len(seg) < types.BlobRefSize {
  784. return nil, false
  785. }
  786. ref := types.BlobRef{
  787. TotalLen: binary.BigEndian.Uint32(seg[:4]),
  788. FirstPageID: binary.BigEndian.Uint32(seg[4:8]),
  789. }
  790. if ref.Empty() {
  791. return nil, false
  792. }
  793. return ref, true
  794. case types.IsVarWidth(f.FieldType):
  795. return types.DecodeValue(f.FieldType, seg), true
  796. default:
  797. if len(seg) != int(f.Width()) {
  798. return nil, false // 行损坏: 宁可当没有值, 也不解错位字节
  799. }
  800. val := types.DecodeValue(f.FieldType, seg)
  801. return val, val != nil
  802. }
  803. }
  804. // readFieldValue / 行内字段解码已收敛到 types.DecodeValue (见 types/codec.go):
  805. // 解码与编码同源, 新增类型不必再回来改这里。