table_schema.go 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890
  1. package core
  2. import (
  3. "fmt"
  4. "sync"
  5. "unsafe"
  6. "git.ycl.pub/yl32940/TsDB2/DB/core/types"
  7. "git.ycl.pub/yl32940/TsDB2/DB/core/util"
  8. )
  9. // validateFieldsForSchema 校验一组列定义能否构成合法 schema:
  10. // 列名长度可落盘 + 列级参数自洽 + 行宽不超建列闸门。
  11. //
  12. // 校验落在 core 而不是只留在 ORM / migrate: schema 也能从 SQL 或低层 API 直接建出来,
  13. // 只有拦在真正写 schema 的地方才拦得住。
  14. func validateFieldsForSchema(fields []types.FieldInfo) error {
  15. for _, f := range fields {
  16. // 列名长度: schema 里是 [nameLen(1B)][name...] (见 serializeSchema / readSchema)。
  17. // 长度为 0 或 >255 都不能落盘 —— 0 会让解析侧把下一个字段的类型字节当名字,
  18. // >255 会让 nameLen 截断并让其后所有字段整体错位。都不是"名字有瑕疵", 是整表错乱。
  19. if len(f.FieldName) == 0 {
  20. return fmt.Errorf("%w: 列名长度为 0 (schema 里列名长度是 1 字节, 空名字无法落盘)",
  21. types.ErrValueRange)
  22. }
  23. if len(f.FieldName) > FieldNameSize {
  24. return fmt.Errorf("%w: 列名 %q 有 %d 字节, 上限 %d 字节 "+
  25. "(schema 里列名长度是 1 字节, 超长会截断并让后续列整体错位)",
  26. types.ErrValueRange, f.FieldName, len(f.FieldName), FieldNameSize)
  27. }
  28. if err := types.ValidateColumnParam(f.FieldType, f.Param()); err != nil {
  29. return fmt.Errorf("列 %q: %w", f.FieldName, err)
  30. }
  31. }
  32. return types.CheckRowSize(fields)
  33. }
  34. // nextSchemaVersion 返回下一个可用的 schema 版本号。
  35. //
  36. // schemaVer 是每行行首的 1 字节 (getVersionedOffset / rowDataSizeForVersion 都按它取版本),
  37. // 版本条数也是 1 字节落盘 (serializeSchema 的 verCount)。到 0xFF 再自增就会回绕成 0:
  38. // 0 不是任何已知版本, getVersion 返回 nil → 该行按"未知版本"解析, NULL 位图取不到,
  39. // 所有列静默读成 0 / NULL。宁可在演进这一步拒绝, 让用户重建表。
  40. //
  41. // 每次 AddField / DropField 都会新增一版, 因此这道闸门也限定了"一张表的 schema
  42. // 最多能演进多少次"。
  43. func (table *Table) nextSchemaVersion() (byte, error) {
  44. if table.activeVersion >= 0xFF || len(table.versions) >= 0xFF {
  45. return 0, fmt.Errorf("schema 版本数已达上限 %d (activeVer=%d, 已存 %d 版): "+
  46. "版本号是行首 1 字节, 再演进会回绕并让旧行解析错位; 请重建该表",
  47. 0xFF, table.activeVersion, len(table.versions))
  48. }
  49. return table.activeVersion + 1, nil
  50. }
  51. // findVersion 在版本列表中线性查找指定版本的 schema 定义。
  52. // getVersion (Table 方法) 与 extractIndexKey (自由函数, 手里没有 *Table) 共用这条扫描。
  53. func findVersion(versions []SchemaVersion, ver byte) *SchemaVersion {
  54. for i := range versions {
  55. if versions[i].Version == ver {
  56. return &versions[i]
  57. }
  58. }
  59. return nil
  60. }
  61. // getVersion 获取指定版本的 schema 定义
  62. func (table *Table) getVersion(ver byte) *SchemaVersion {
  63. return findVersion(table.versions, ver)
  64. }
  65. // svCol0WidthOf svCol0Width 的自由函数形态: fields 由调用方传入 (extractIndexKey 无
  66. // *Table 接收者, 手里只有调用点传进来的 table.Field)。
  67. func svCol0WidthOf(fields []types.FieldInfo, sv *SchemaVersion) int {
  68. if sv == nil || len(sv.FieldIndices) == 0 {
  69. return 0
  70. }
  71. idx := int(sv.FieldIndices[0])
  72. if idx < 0 || idx >= len(fields) {
  73. return 0
  74. }
  75. return int(fields[idx].Width())
  76. }
  77. // svCol0Width 某版本行的【列 0 档位宽度】W0 (由 schema 唯一确定)。
  78. //
  79. // v9 行布局把列 0 固定按档位宽度存储 (见 types/rowvar.go), 偏移表基址 = 1 + W0。
  80. // 解析端必须能静态算出 W0 —— 这正是"列 0 不参与变长存储"的原因: 若列 0 也按实际
  81. // 长度存储, 偏移表的位置就取决于内容, 解析将不得不先猜列 0 有多长 (自举问题)。
  82. func (table *Table) svCol0Width(sv *SchemaVersion) int {
  83. return svCol0WidthOf(table.Field, sv)
  84. }
  85. // rowLayout 行布局解析前导的公共产物: 版本定义 + 列 0 档位宽度 + 行内偏移表。
  86. // NULL 位图不在结构体内 —— 按调用方需要再调 types.RowNullBitmap(rowData, ends, n),
  87. // 因为并非所有调用方都要位图 (如 getVersionedRange 走 types.RowIsNull 逐列判定)。
  88. type rowLayout struct {
  89. sv *SchemaVersion
  90. col0Width int
  91. ends []uint16
  92. }
  93. // parseRowLayoutOf 行布局解析前导的唯一实现, 四步: ver → findVersion → svCol0WidthOf →
  94. // RowParseOffsets。任一步失败 ok=false, 调用方按各自的失败语义返回。
  95. // 调用方必须保证 len(rowData) ≥ 1 —— 各调用点原有的长度检查保持不动。
  96. func parseRowLayoutOf(fields []types.FieldInfo, versions []SchemaVersion, rowData []byte, ver byte) (rowLayout, bool) {
  97. sv := findVersion(versions, ver)
  98. if sv == nil {
  99. return rowLayout{}, false
  100. }
  101. n := len(sv.FieldIndices)
  102. col0Width := svCol0WidthOf(fields, sv)
  103. ends, ok := types.RowParseOffsets(rowData, n, col0Width)
  104. if !ok {
  105. return rowLayout{}, false
  106. }
  107. return rowLayout{sv: sv, col0Width: col0Width, ends: ends}, true
  108. }
  109. // parseRowLayout 从行首字节读出版本号, 解析行布局前导 (行解码 / 更新 / 删行路径的入口)。
  110. func (table *Table) parseRowLayout(rowData []byte) (rowLayout, bool) {
  111. return parseRowLayoutOf(table.Field, table.versions, rowData, rowData[0])
  112. }
  113. // parseRowLayoutVer 与 parseRowLayout 相同, 但版本号由调用方显式给出
  114. // (版本号不取自 rowData[0] 时, 如 versionedNullBitmap 的 ver 参数)。
  115. func (table *Table) parseRowLayoutVer(rowData []byte, ver byte) (rowLayout, bool) {
  116. return parseRowLayoutOf(table.Field, table.versions, rowData, ver)
  117. }
  118. // rowCol0Width 指定 schemaVer 对应行的列 0 档位宽度。
  119. func (table *Table) rowCol0Width(ver byte) int {
  120. return table.svCol0Width(table.getVersion(ver))
  121. }
  122. // getVersionedOffset 计算某行中指定 master 字段索引在行数据中的偏移。
  123. //
  124. // 行布局 v9: [schemaVer 1B][列0 W0 B][列结束偏移表 2N B][列1..N-1 内容][NULL 位图 ⌈n/8⌉ B]
  125. // 列位置一律由【行内偏移表】给出 (见 getVersionedRange), 位图长度按【行自身的
  126. // schemaVer】算 (n = 该版本 FieldIndices 长度), 不能取当前字段数。
  127. //
  128. // 时序索引按【绝对偏移 9】(== [pkID 8B][schemaVer 1B]) 读首列当时间键
  129. // (见 DB/types.go 的 modelInfo 与 core/index 的 timeRowInRange / FindRowByIndexKey)。
  130. // 这条能成立, 靠的是"列 0 恒占其档位宽度 W0 且时间键列恒为 8 字节" —— 不是靠"位图放行尾":
  131. // 位图若前插会把首列整体后移 ⌈n/8⌉ 字节, 时序键读取全错, 所以位图必须落在字段内容之后。
  132. //
  133. // 字段为 NULL 时返回 false。把 NULL 判定下沉到这里, 是因为"定位偏移"是所有读取路径
  134. // (7 个条件类 + 更新时摘旧堆块) 的唯一入口, 各调用方再各判一次必然漏:
  135. // - 条件匹配: NULL 不匹配任何比较 (SQL 三值逻辑)。若按全零字节比, `x = 0` 会命中
  136. // NULL 行、`x < 1` 也会命中 —— 结果看着"有数据"却全是错的。
  137. // - 更新路径: 旧值为 NULL 时引用槽是全零, 本就没有堆块要摘。
  138. func (table *Table) getVersionedOffset(rowData []byte, masterFieldIdx int) (uint16, bool) {
  139. start, _, ok := table.getVersionedRange(rowData, masterFieldIdx)
  140. return start, ok
  141. }
  142. // getVersionedRange 定位某行中 masterFieldIdx 列内容的 [起点, 终点)。
  143. //
  144. // 【v9 起改走行内偏移表】旧实现从偏移 1 开始"累加各列 Width"推算列位置, 那是建立在
  145. // "字段区定长、每列实付档位宽度"的前提上。v9 让字符串列按实际长度存储 (见
  146. // types/rowvar.go), 累加 Width 会越算越偏 —— 必须由行内偏移表给出精确位置。
  147. //
  148. // 终点是行内【实际】字节的结尾, 不再等于 起点+Width: 调用方若需要"档位宽度"的定长
  149. // 视角 (比较 / 索引键), 必须自己用 types.PadToWidth 补齐。
  150. //
  151. // 字段为 NULL 时返回 false。
  152. func (table *Table) getVersionedRange(rowData []byte, masterFieldIdx int) (uint16, uint16, bool) {
  153. if len(rowData) < 1 {
  154. return 0, 0, false
  155. }
  156. layout, ok := table.parseRowLayout(rowData)
  157. if !ok {
  158. return 0, 0, false
  159. }
  160. sv, n, col0Width, ends := layout.sv, len(layout.sv.FieldIndices), layout.col0Width, layout.ends
  161. for i, idx := range sv.FieldIndices {
  162. if int(idx) != masterFieldIdx {
  163. continue
  164. }
  165. if types.RowIsNull(rowData, ends, n, i) {
  166. return 0, 0, false // NULL: 不参与比较, 也没有堆块
  167. }
  168. return types.RowFieldStart(ends, i), types.RowFieldEnd(ends, i, col0Width), true
  169. }
  170. return 0, 0, false
  171. }
  172. // versionedNullBitmap 取出行尾的 NULL 位图切片 (长度按该行自身的 schemaVer 算)。
  173. //
  174. // 行布局 v9: [schemaVer 1B][列0 W0 B][列结束偏移表 2N B][列1..N-1 内容][NULL 位图 ⌈n/8⌉ B]
  175. //
  176. // 位图起点由偏移表末项给出, 不再靠"版本算出的总长" —— v9 行按内容变长, 不存在
  177. // "版本对应的总长"。而【为什么仍然不能用 len(rowData)】: 行存储的原地更新允许把新行
  178. // 写进旧行的槽位 (剩余字节清零), 按 len 取尾会取到零填充而不是真正的位图。偏移表落在
  179. // 只取决于字段数的固定位置上, 不受零填充影响, 因此始终精确。
  180. func (table *Table) versionedNullBitmap(rowData []byte, ver byte) ([]byte, bool) {
  181. layout, ok := table.parseRowLayoutVer(rowData, ver)
  182. if !ok {
  183. return nil, false
  184. }
  185. n := len(layout.sv.FieldIndices)
  186. return types.RowNullBitmap(rowData, layout.ends, n)
  187. }
  188. // getActiveFieldTypes 返回当前活跃版本的字段类型列表 (不含已删除)
  189. func (table *Table) getActiveFieldTypes() []types.FieldInfo {
  190. sv := table.getVersion(table.activeVersion)
  191. if sv == nil {
  192. return nil
  193. }
  194. fields := make([]types.FieldInfo, len(sv.FieldIndices))
  195. for i, idx := range sv.FieldIndices {
  196. fields[i] = table.Field[idx]
  197. }
  198. return fields
  199. }
  200. // calcRowDataSize 计算当前活跃版本一行的【最大】字节数。
  201. func (table *Table) calcRowDataSize() int {
  202. return table.rowDataSizeForVersion(table.activeVersion)
  203. }
  204. // rowDataSizeForVersion 计算指定 schema 版本一行的【最大】字节数。
  205. //
  206. // 【v9 起语义变化】行按内容变长, 这个值只是上界 (每列都取满档位宽度时的长度), 不再是
  207. // "该版本行的实际长度"。它只用于"按行分配缓冲"的场景 (见 maxRowDataSize): 拿它当实际
  208. // 行长去做闸门判断, 会把本来装得下的行误判为超长。
  209. //
  210. // 上界 = schemaVer(1) + Σ 各列档位宽度 + 列结束偏移表(2N) + NULL 位图(⌈N/8⌉)。
  211. // 列 0 的 8 字节本身就在 Σ 里 (它是真实列, 不是额外前缀)。
  212. func (table *Table) rowDataSizeForVersion(ver byte) int {
  213. size := 1 // schemaVer
  214. sv := table.getVersion(ver)
  215. if sv != nil {
  216. n := len(sv.FieldIndices)
  217. size += types.NullBitmapLen(n)
  218. size += types.RowOffTableLen(n)
  219. for _, idx := range sv.FieldIndices {
  220. size += int(table.Field[idx].Width())
  221. }
  222. }
  223. return size
  224. }
  225. // minRowDataSizeForVersion 该版本一行的【最小】字节数 (列 0 之外全部为 NULL 时)。
  226. //
  227. // 列 0 恒占其档位宽度 W0 (偏移表基址恒为 1+W0, 这是解析与"列 0 定长"的前提), 所以最小值
  228. // 就是表头 (schemaVer + 列 0 + 偏移表) 加位图。
  229. func (table *Table) minRowDataSizeForVersion(ver byte) int {
  230. sv := table.getVersion(ver)
  231. if sv == nil {
  232. return 1
  233. }
  234. n := len(sv.FieldIndices)
  235. return types.RowHeaderSize(n, table.svCol0Width(sv)) + types.NullBitmapLen(n)
  236. }
  237. // maxRowDataSize 返回所有已知 schema 版本中最大的行数据大小。
  238. //
  239. // 给"按行分配缓冲"的调用方使用 (如删除路径的行复用池): info.RowSize 只反映最近
  240. // 一次落盘时的 schema, AddField/DropField 之后会偏小, 沿用它会 slice 越界 panic。
  241. func (table *Table) maxRowDataSize() int {
  242. maxSize := table.rowDataSizeForVersion(table.activeVersion)
  243. for i := range table.versions {
  244. if n := table.rowDataSizeForVersion(table.versions[i].Version); n > maxSize {
  245. maxSize = n
  246. }
  247. }
  248. return maxSize
  249. }
  250. // syncRowSize 让 info.RowSize 反映所有 schema 版本中最宽的一行
  251. // (语义同 types.CalcRowSize: Σ 字段字节数, 不含 schemaVer、NULL 位图与 pkID 前缀)。
  252. // 仅在 schema 变更时调用。
  253. func (table *Table) syncRowSize() {
  254. // 前缀 = schemaVer(1B) + 该版本的 NULL 位图。位图长度按活跃版本算 —— RowSize 本就
  255. // 只是"字段宽度之和"的估计值 (删除/分页路径都明确不用它, 见 deleter.go 里的说明),
  256. // 不必为跨版本差一格去逐版本精确。
  257. prefix := 1
  258. if sv := table.getVersion(table.activeVersion); sv != nil {
  259. prefix += types.NullBitmapLen(len(sv.FieldIndices))
  260. }
  261. if size := table.maxRowDataSize() - prefix; size > 0 && size <= 0xFFFF {
  262. table.info.RowSize = uint16(size)
  263. }
  264. }
  265. // pickVersionForUpdate 决定更新后按哪一版 schema 落盘。
  266. //
  267. // 惰性演进语义:
  268. // - 本次要写的字段在旧行自身的版本里都存在 → 保持旧版本。列序与偏移表大小都不变,
  269. // 不会因为"只改了一个老字段"而无谓地把行升级并搬迁。
  270. // 注意这不保证【行长】不变: v9 字符串列按实际长度存, 换一个更长的值就会让行变长,
  271. // 由索引层负责"原地 → 页内重建 → 换页"三级兜底。
  272. // - values 触及了旧版本没有的字段 (AddField 之后新增的) → 升级到当前版本;
  273. // 此时列数变化 (偏移表也变), 由索引层负责"页内重建 / 换页"。
  274. func (table *Table) pickVersionForUpdate(oldRowData []byte, values []types.FieldValue) byte {
  275. if len(oldRowData) < 1 {
  276. return table.activeVersion
  277. }
  278. oldVer := oldRowData[0]
  279. if oldVer == table.activeVersion {
  280. return oldVer
  281. }
  282. sv := table.getVersion(oldVer)
  283. if sv == nil {
  284. // 旧版本元信息缺失, 只能按当前版本重写
  285. return table.activeVersion
  286. }
  287. for _, fv := range values {
  288. idx := table.findFieldIndex(string(fv.FieldName))
  289. if idx < 0 {
  290. return table.activeVersion
  291. }
  292. inOldVer := false
  293. for _, i := range sv.FieldIndices {
  294. if int(i) == idx {
  295. inOldVer = true
  296. break
  297. }
  298. }
  299. if !inOldVer {
  300. return table.activeVersion
  301. }
  302. }
  303. return oldVer
  304. }
  305. // serializeSchema 将当前 schema 序列化为字节流
  306. //
  307. // master field 每项: [type(1B)][flag(1B)][nameLen(1B)][name...]
  308. // flag 落盘是必须的: decimal 列的 scale 一旦丢失, 读取侧就只能信任行内的 scale,
  309. // 于是"同一列出现两种 scale"这种破坏保序的情况再也无法被发现。删除/可空两个状态位
  310. // 同样必须随 schema 落盘, 否则重开库后被 DropField 的列会悄悄"复活"。
  311. func (table *Table) serializeSchema() []byte {
  312. var data []byte
  313. data = append(data, table.activeVersion)
  314. data = append(data, byte(len(table.Field)))
  315. for _, f := range table.Field {
  316. data = append(data, byte(f.FieldType))
  317. data = append(data, f.Flag)
  318. data = append(data, byte(len(f.FieldName)))
  319. data = append(data, f.FieldName...)
  320. }
  321. data = append(data, byte(len(table.versions)))
  322. for _, sv := range table.versions {
  323. data = append(data, sv.Version)
  324. data = append(data, byte(len(sv.FieldIndices)))
  325. data = append(data, sv.FieldIndices...)
  326. }
  327. return data
  328. }
  329. // writeSchema 整条重写本表的 schema 定义
  330. //
  331. // 布局:
  332. // 1. 内联块 —— 表页内, 紧跟 TableInfo, 固定 FieldAllocateSize 字节, **无跳转槽**。
  333. // 建表时按 len(serializeSchema()) 精确分配 (上限 MaxInlineSchemaSize)。
  334. // 2. 续块 —— schema 超出内联块的部分, 写入「全库共享」的 schema 扩展页,
  335. // 每块格式 [SchemaFragHdr 8B][payload], 多块由 hdr.NextPage/NextOff 串成链。
  336. // 本表首个续块的位置记录在 TableInfo.ExtPage/ExtOff。
  337. //
  338. // 续块只追加、不单独释放: 每次都是整条重写, 旧续块立刻成为垃圾, 由
  339. // SchemaExtAllocator 在尾页放不下时做「满页压缩重排」统一回收。因此这里
  340. // 不再需要任何引用计数 —— 这也正是旧实现最容易出错的地方。
  341. func (table *Table) writeSchema() error {
  342. data := table.serializeSchema()
  343. alloc := int(table.info.FieldAllocateSize) // 内联块容量 (可能大于 schema 实际长度)
  344. inline := len(data)
  345. if inline > alloc {
  346. inline = alloc // 容错: schema 超出内联块, 余下部分走续块
  347. }
  348. rest := data[inline:]
  349. // 1. 先分配续块。可能触发压缩重排 —— 那会改写(可能同页的)其他表的 TableInfo,
  350. // 所以内联块的写入必须放到分配之后再做, 否则会拿旧页缓冲覆盖掉这些更新。
  351. var frags []schemaFrag
  352. if len(rest) > 0 {
  353. if table.schemaExt == nil {
  354. return ErrFieldSpaceNotEnough
  355. }
  356. var err error
  357. frags, err = table.schemaExt.AllocChain(len(rest))
  358. if err != nil {
  359. return err
  360. }
  361. }
  362. // 2. 写续块
  363. if len(frags) > 0 {
  364. if err := table.writeFrags(frags, rest); err != nil {
  365. return err
  366. }
  367. }
  368. // 3. 写内联块 (重新读页, 见上面第 1 步的说明)
  369. buf := make([]byte, PageSize)
  370. if err := table.handle.ReadPage(table.tablePageID, buf); err != nil {
  371. return err
  372. }
  373. start := int(table.tableStartOffset + uint32(unsafe.Sizeof(*table.info)))
  374. end := start + alloc
  375. if end > len(buf) {
  376. return ErrFieldSpaceNotEnough
  377. }
  378. // 清零整块(alloc 字节): 槽位可能大于 schema 实际长度(复用空洞/预分配),
  379. // 多出来的部分是填充, 必须清零, 防止旧表的残留字节被后续误读
  380. for i := start; i < end; i++ {
  381. buf[i] = 0
  382. }
  383. copy(buf[start:end], data[:inline])
  384. if err := table.handle.SavePage(table.tablePageID, buf); err != nil {
  385. return err
  386. }
  387. // 4. 记录本表首个续块的位置
  388. if len(frags) > 0 {
  389. table.info.ExtPage = frags[0].PageID
  390. table.info.ExtOff = uint16(frags[0].Off)
  391. } else {
  392. table.info.ExtPage = 0
  393. table.info.ExtOff = 0
  394. }
  395. return nil
  396. }
  397. // writeSchemaExt 把本表 schema 的续块部分写进指定的扩展页分配器, 并落盘 TableInfo
  398. // 专供 SchemaExtAllocator.Compact 压缩重排时调用。
  399. func (table *Table) writeSchemaExt(alloc *SchemaExtAllocator) error {
  400. data := table.serializeSchema()
  401. inline := int(table.info.FieldAllocateSize)
  402. if inline > len(data) {
  403. inline = len(data)
  404. }
  405. rest := data[inline:]
  406. if len(rest) == 0 {
  407. table.info.ExtPage = 0
  408. table.info.ExtOff = 0
  409. table.saveTableInfo()
  410. return nil
  411. }
  412. frags, err := alloc.AllocChain(len(rest))
  413. if err != nil {
  414. return err
  415. }
  416. if err := table.writeFrags(frags, rest); err != nil {
  417. return err
  418. }
  419. table.info.ExtPage = frags[0].PageID
  420. table.info.ExtOff = uint16(frags[0].Off)
  421. table.saveTableInfo()
  422. return nil
  423. }
  424. // writeFrags 把 rest 按 frags 描述的容量逐块写入扩展页, 并串好续块链
  425. func (table *Table) writeFrags(frags []schemaFrag, rest []byte) error {
  426. written := 0
  427. for i := range frags {
  428. f := frags[i]
  429. buf := make([]byte, PageSize)
  430. if err := table.handle.ReadPage(f.PageID, buf); err != nil {
  431. return err
  432. }
  433. h := SchemaFragHdr{DataLen: f.Len}
  434. if i+1 < len(frags) {
  435. h.NextPage = frags[i+1].PageID
  436. h.NextOff = uint16(frags[i+1].Off)
  437. }
  438. putSchemaFragHdr(buf, int(f.Off), h)
  439. copy(buf[int(f.Off)+schemaFragHdrSize:], rest[written:written+int(f.Len)])
  440. if err := table.handle.SavePage(f.PageID, buf); err != nil {
  441. return err
  442. }
  443. written += int(f.Len)
  444. }
  445. return nil
  446. }
  447. // checkProspectiveRowSize 预演"把 newField 加进当前活跃字段集合之后"的行宽。
  448. //
  449. // 加列只会让行变宽, 而超宽的行写不进 DataPage (见 serializeRowInternal 的写行闸门),
  450. // 所以必须在建列这一步就拒绝: 否则这张表从加列成功的那一刻起, 一条也写不进去 ——
  451. // 而且是插数据时才暴露 (而且加列本身是"成功"的, 没有任何回滚线索)。
  452. func (table *Table) checkProspectiveRowSize(newField types.FieldInfo, newMasterIdx int) error {
  453. sv := table.getVersion(table.activeVersion)
  454. ineligible := table.spillIneligibleFields()
  455. probe := make([]types.FieldInfo, 0, len(table.Field)+1)
  456. unspillable := make([]bool, 0, len(table.Field)+1)
  457. if sv != nil {
  458. for _, idx := range sv.FieldIndices {
  459. if int(idx) >= len(table.Field) {
  460. continue
  461. }
  462. probe = append(probe, table.Field[idx])
  463. unspillable = append(unspillable, ineligible[idx])
  464. }
  465. }
  466. // 新增/复活的这一列此刻通常还没有索引 (建索引是独立一步), 但复活的情形下它可能
  467. // 已被某个仍活跃的索引引用 —— 那就同样不能再溢出, 按真实集合算。
  468. probe = append(probe, newField)
  469. if newMasterIdx >= 0 && newMasterIdx < len(ineligible) {
  470. unspillable = append(unspillable, ineligible[newMasterIdx])
  471. } else {
  472. unspillable = append(unspillable, false)
  473. }
  474. if err := types.CheckRowSpillFloor(probe, unspillable); err != nil {
  475. return fmt.Errorf("新增列 %q 后行宽超限: %w", newField.FieldName, err)
  476. }
  477. return nil
  478. }
  479. // spillIneligibleFields 返回"不能值级溢出的列"集合 (按 master registry 下标)。
  480. //
  481. // 建了索引的列必须留在行内: 索引键按行内字节 (补齐到档位) 拼, 一旦该列被溢出成 6B
  482. // 堆引用, 拼出的键就是页号/偏移而不是值 —— 索引里挂着一个永远查不到的键。所以凡是
  483. // 任何活跃索引的分量列, 都不参与值级溢出。
  484. //
  485. // 列 0 不在这里标记: 它本就恒占档位宽度 (偏移表基址依赖它, 见 types.CalcSpillFloor
  486. // 的 live==0 分支), 任何情况下都不参与溢出。
  487. func (table *Table) spillIneligibleFields() []bool {
  488. n := len(table.Field)
  489. if n == 0 {
  490. return nil
  491. }
  492. out := make([]bool, n)
  493. metas := table.indexMeta.metas
  494. for i := range metas {
  495. if metas[i].Deleted() {
  496. continue
  497. }
  498. for _, fid := range metas[i].Fields() {
  499. if int(fid) < n {
  500. out[fid] = true
  501. }
  502. }
  503. }
  504. return out
  505. }
  506. // rowSpillFloorForVersion 计算某 schema 版本一行经【值级溢出】之后的最小可能长度。
  507. //
  508. // 它就是写入闸门 (serializeRowInternal 开头) 的判据: 下界超物理墙说明"把所有可溢出的
  509. // 字符串列都溢出去也装不下", 不动堆永远救不回来 —— 且这个判断发生在动堆之前 (见那里
  510. // 的说明)。下界按【不可溢出列: 建了索引的列 + 列 0 + 定长列全宽】+ 可溢出字符串 6B 计。
  511. func (table *Table) rowSpillFloorForVersion(ver byte) int {
  512. sv := table.getVersion(ver)
  513. if sv == nil || len(sv.FieldIndices) == 0 {
  514. return 1 // 只有 schemaVer 1 字节
  515. }
  516. ineligible := table.spillIneligibleFields()
  517. fields := make([]types.FieldInfo, 0, len(sv.FieldIndices))
  518. unspillable := make([]bool, 0, len(sv.FieldIndices))
  519. for _, idx := range sv.FieldIndices {
  520. if int(idx) >= len(table.Field) {
  521. continue
  522. }
  523. fields = append(fields, table.Field[idx])
  524. unspillable = append(unspillable, ineligible[idx])
  525. }
  526. return types.CalcSpillFloor(fields, unspillable)
  527. }
  528. // checkIndexSpillFloor 预演"把 fieldIDs 作为索引分量"之后, 活跃版本行的溢出下界。
  529. //
  530. // 分量列一旦建索引就不能再溢出, 下界随之抬升; 若抬过"一页两行"的配额 (MaxInlineRowData),
  531. // 这张表从建索引成功起就再也压不进两行 —— 必须在建索引这一步拒绝 (ErrIndexRowTooWide),
  532. // 而不是等某次插入悄悄把行撑到 8159..16332 之间、整页行为退化时才被发现。
  533. func (table *Table) checkIndexSpillFloor(fieldIDs []byte) error {
  534. sv := table.getVersion(table.activeVersion)
  535. if sv == nil {
  536. return nil
  537. }
  538. ineligible := table.spillIneligibleFields()
  539. for _, fid := range fieldIDs {
  540. if int(fid) < len(ineligible) {
  541. ineligible[fid] = true
  542. }
  543. }
  544. fields := make([]types.FieldInfo, 0, len(sv.FieldIndices))
  545. unspillable := make([]bool, 0, len(sv.FieldIndices))
  546. for _, idx := range sv.FieldIndices {
  547. if int(idx) >= len(table.Field) {
  548. continue
  549. }
  550. fields = append(fields, table.Field[idx])
  551. unspillable = append(unspillable, ineligible[idx])
  552. }
  553. return types.CheckRowSpillFloor(fields, unspillable)
  554. }
  555. func (table *Table) AddField(newField types.FieldInfo) error {
  556. if table.db != nil && table.db.HasActiveTransaction() {
  557. return errDDLInTransaction("AddField")
  558. }
  559. defer table.db.lockOp()()
  560. // 列级参数必须先自洽: 宽度码/scale 越界会让该列宽度算成 0, 而 0 宽度会少算字节,
  561. // 让其后每一列都从错位偏移解码 (见 types.FieldSize)。
  562. if err := validateFieldsForSchema([]types.FieldInfo{newField}); err != nil {
  563. return err
  564. }
  565. // 列数闸门与建表同一条 (见 checkFieldCount): master registry 只增不减
  566. // (DropField 是软删除, 列仍留在 registry 里), 所以按"加完之后"的列数判断。
  567. if err := checkFieldCount(len(table.Field) + 1); err != nil {
  568. return err
  569. }
  570. // 版本号闸门必须在【任何改动之前】: 两个分支都会在最后新增一版, 版本耗尽时
  571. // 若已经把 in-memory 状态改了一半再返回错误, 表就停在"改了一半"的不一致状态。
  572. nextVer, err := table.nextSchemaVersion()
  573. if err != nil {
  574. return err
  575. }
  576. existing := table.findFieldByName(newField.FieldName)
  577. if existing != nil {
  578. // 字段存在且活跃 → 报错
  579. if !existing.IsDropped() {
  580. return ErrFieldExists
  581. }
  582. // 复活必须沿用原类型与列级参数。
  583. //
  584. // master registry 的类型与列级参数是【所有历史版本共用】的行偏移依据
  585. // (见 getVersionedOffset / rowDataSizeForVersion)。旧实现是整字节覆盖
  586. // (existing.FieldType = newField.FieldType), 看着像"恢复", 实际是"改掉":
  587. // varchar(50) 的列复活成 varchar(500) 之后, 已经落盘的旧行仍按 224 字节排布,
  588. // 却会被按 2048 字节解析 —— 该列之后的每一个字段都静默错位。
  589. // 想改宽度只能换列名新建, 或者重建表 (DropField 本就只标记删除、保留原类型)。
  590. if existing.FieldType != newField.FieldType || existing.Param() != newField.Param() {
  591. return fmt.Errorf("%w: 列 %q 已被删除, 复活时不允许改变类型/宽度 "+
  592. "(原 %s/参数 %d, 新 %s/参数 %d); 请改用新列名, 或重建该表",
  593. ErrFieldExists, newField.FieldName,
  594. types.TypeNameOf(existing.FieldType), existing.Param(),
  595. types.TypeNameOf(newField.FieldType), newField.Param())
  596. }
  597. // 找到该字段在 master registry 中的索引 (复活后的 masterIdx): 行宽预演要靠它
  598. // 判断这一列是否已被某个活跃索引引用 —— 已建索引的列不能再参与值级溢出。
  599. revivedIdx := -1
  600. for i := range table.Field {
  601. if &table.Field[i] == existing {
  602. revivedIdx = i
  603. break
  604. }
  605. }
  606. // 复活同样会让行变宽, 先做行宽预演
  607. if err := table.checkProspectiveRowSize(newField, revivedIdx); err != nil {
  608. return err
  609. }
  610. // 只恢复"存活 + 可空/唯一"这些标志位, 低 5 位的列级参数保持原值
  611. existing.Flag = (existing.Flag & types.FlagScaleMask) | (newField.Flag &^ types.FlagScaleMask)
  612. table.info.ActiveFieldCount++
  613. // 构建新版本: 旧版本字段 + 复活的字段索引
  614. sv := table.getVersion(table.activeVersion)
  615. newIndices := make([]byte, 0, len(sv.FieldIndices)+1)
  616. if sv != nil {
  617. newIndices = append(newIndices, sv.FieldIndices...)
  618. }
  619. newIndices = append(newIndices, byte(revivedIdx))
  620. table.activeVersion = nextVer
  621. table.versions = append(table.versions, SchemaVersion{
  622. Version: nextVer,
  623. FieldIndices: newIndices,
  624. })
  625. if err := table.writeSchema(); err != nil {
  626. // 回滚内存态: 写盘失败不能让内存 schema 与磁盘分叉 —— 后续写入会按
  627. // 新 schemaVer 落盘, 重开后该版本不可解析 (整行静默丢失)。
  628. existing.Flag |= types.FlagDeleted
  629. table.info.ActiveFieldCount--
  630. table.activeVersion = sv.Version
  631. table.versions = table.versions[:len(table.versions)-1]
  632. return err
  633. }
  634. table.syncRowSize()
  635. table.saveTableInfo()
  636. return nil
  637. }
  638. // 全新字段: 同样先做行宽预演 (加列只会让行更宽)
  639. if err := table.checkProspectiveRowSize(newField, -1); err != nil {
  640. return err
  641. }
  642. // 追加到 master registry
  643. table.Field = append(table.Field, newField)
  644. table.info.FieldCount++
  645. table.info.ActiveFieldCount++
  646. // 构建新版本: 旧版本字段 + 新字段索引
  647. sv := table.getVersion(table.activeVersion)
  648. newIndices := make([]byte, 0, len(sv.FieldIndices)+1)
  649. if sv != nil {
  650. newIndices = append(newIndices, sv.FieldIndices...)
  651. }
  652. newIndices = append(newIndices, byte(len(table.Field)-1))
  653. table.activeVersion = nextVer
  654. table.versions = append(table.versions, SchemaVersion{
  655. Version: nextVer,
  656. FieldIndices: newIndices,
  657. })
  658. if err := table.writeSchema(); err != nil {
  659. // 回滚内存态 (理由同复活分支): Field/registry/版本全部撤回到改动前,
  660. // 否则内存 schema 与磁盘分叉, 后续写入按新 schemaVer 落盘的行重开后不可解析。
  661. table.Field = table.Field[:len(table.Field)-1]
  662. table.info.FieldCount--
  663. table.info.ActiveFieldCount--
  664. table.activeVersion = sv.Version
  665. table.versions = table.versions[:len(table.versions)-1]
  666. return err
  667. }
  668. table.syncRowSize()
  669. table.saveTableInfo()
  670. if table.db != nil {
  671. if err := table.db.ddlBoundary("AddField"); err != nil {
  672. return err
  673. }
  674. }
  675. return nil
  676. }
  677. // tableInfoBufPool 复用 saveTableInfo 的整页读缓冲。
  678. // saveTableInfo 在【每次行写入】后都会被调用来同步 TotalRows/RowSize 等元信息,
  679. // 旧实现每行 make 一个 16KB —— 灌数时它一个函数就贡献 ~5% 的总分配量。
  680. // 缓冲只在函数内使用 (ReadPage 拷入 / SavePage 拷出), 不会被任何路径持有。
  681. var tableInfoBufPool = sync.Pool{New: func() any { b := make([]byte, PageSize); return &b }}
  682. func (table *Table) saveTableInfo() {
  683. bp := tableInfoBufPool.Get().(*[]byte)
  684. buf := *bp
  685. defer tableInfoBufPool.Put(bp)
  686. if err := table.handle.ReadPage(table.tablePageID, buf); err != nil {
  687. return
  688. }
  689. info := (*TableInfo)(unsafe.Pointer(&buf[table.tableStartOffset]))
  690. info.ExtPage = table.info.ExtPage
  691. info.ExtOff = table.info.ExtOff
  692. info.FieldCount = table.info.FieldCount
  693. info.ActiveFieldCount = table.info.ActiveFieldCount
  694. info.TotalRows = table.info.TotalRows
  695. // 行宽随 schema 演进变化, 必须落盘同步 (否则删除路径按旧 RowSize 分配缓冲会越界)
  696. info.RowSize = table.info.RowSize
  697. // 动态字段堆页链 (分配新堆页时才会变化)
  698. info.FirstHeap = table.info.FirstHeap
  699. info.LastHeap = table.info.LastHeap
  700. info.HeapPageCount = table.info.HeapPageCount
  701. table.handle.SavePage(table.tablePageID, buf)
  702. }
  703. // =============================================================================
  704. // 删除字段(软删除 + 新版本)
  705. // =============================================================================
  706. func (table *Table) DropField(fieldName string) error {
  707. if table.db != nil && table.db.HasActiveTransaction() {
  708. return errDDLInTransaction("DropField")
  709. }
  710. defer table.db.lockOp()()
  711. // 在 master registry 中查找
  712. masterIdx := -1
  713. for i, f := range table.Field {
  714. if f.IsDropped() {
  715. continue // 已删除
  716. }
  717. if string(f.FieldName) == fieldName {
  718. masterIdx = i
  719. break
  720. }
  721. }
  722. if masterIdx < 0 {
  723. return ErrFieldNotFound
  724. }
  725. // 守卫: 行存储索引 (主键/时序) 的分量列不允许删除 —— 删了之后
  726. // extractIndexKey 提不出键, 新行不进任何索引 (插入"成功"但行静默丢失,
  727. // Count 虚高)。ORM 层的 syncSchema 本来就不动主键列, core 层补上同一约束。
  728. if im := table.indexMeta; im != nil {
  729. if rm, _, ok := im.rowStoreMeta(); ok && rm != nil {
  730. for _, id := range rm.Fields() {
  731. if int(id) == masterIdx {
  732. return fmt.Errorf("%w: 列 %q 是行存储索引 %q 的分量, 不能删除 (需重建表)",
  733. ErrFieldNotFound, fieldName, rm.Name())
  734. }
  735. }
  736. }
  737. }
  738. // 版本号闸门必须在改动之前 (理由同 AddField): 删列也要新增一版 schema。
  739. nextVer, err := table.nextSchemaVersion()
  740. if err != nil {
  741. return err
  742. }
  743. // 标记删除 (FieldType 本身不动: 它是"这一列是什么", 与存活状态无关)
  744. table.Field[masterIdx].Flag |= types.FlagDeleted
  745. table.info.ActiveFieldCount--
  746. // 构建新版本: 跳过被删字段
  747. sv := table.getVersion(table.activeVersion)
  748. newIndices := make([]byte, 0, len(sv.FieldIndices))
  749. if sv != nil {
  750. for _, idx := range sv.FieldIndices {
  751. if int(idx) != masterIdx {
  752. newIndices = append(newIndices, idx)
  753. }
  754. }
  755. }
  756. table.activeVersion = nextVer
  757. table.versions = append(table.versions, SchemaVersion{
  758. Version: nextVer,
  759. FieldIndices: newIndices,
  760. })
  761. if err := table.writeSchema(); err != nil {
  762. // 回滚内存态 (理由同 AddField): 撤销删除标记与版本推进。
  763. table.Field[masterIdx].Flag &^= types.FlagDeleted
  764. table.info.ActiveFieldCount++
  765. table.activeVersion = sv.Version
  766. table.versions = table.versions[:len(table.versions)-1]
  767. return err
  768. }
  769. table.syncRowSize()
  770. table.saveTableInfo()
  771. if table.db != nil {
  772. if err := table.db.ddlBoundary("DropField"); err != nil {
  773. return err
  774. }
  775. }
  776. return nil
  777. }
  778. // =============================================================================
  779. // 重命名字段 (只改 FieldInfo.FieldName 字节 + writeSchema, 不改任何列偏移/索引)
  780. // =============================================================================
  781. func (table *Table) RenameColumn(oldName, newName string) error {
  782. if table.db != nil && table.db.HasActiveTransaction() {
  783. return errDDLInTransaction("RenameColumn")
  784. }
  785. if len(newName) == 0 || len(newName) > FieldNameSize {
  786. return fmt.Errorf("列名长度 %d 非法 (1..%d)", len(newName), FieldNameSize)
  787. }
  788. defer table.db.lockOp()()
  789. // 找目标列 (只能是活跃列, 已删列不能改名)
  790. var target *types.FieldInfo
  791. for i := range table.Field {
  792. f := &table.Field[i]
  793. if f.IsDropped() {
  794. continue
  795. }
  796. if util.BytesToString(f.FieldName) == oldName {
  797. target = f
  798. break
  799. }
  800. }
  801. if target == nil {
  802. return fmt.Errorf("列 %q 不存在", oldName)
  803. }
  804. // 新名不能和别的活跃列重名
  805. for i := range table.Field {
  806. f := &table.Field[i]
  807. if f.IsDropped() {
  808. continue
  809. }
  810. if f != target && util.BytesToString(f.FieldName) == newName {
  811. return fmt.Errorf("列 %q 已存在", newName)
  812. }
  813. }
  814. // 改内存态 FieldName (新建切片, 不原地改引用外部可能持有)
  815. target.FieldName = make([]byte, len(newName))
  816. copy(target.FieldName, newName)
  817. // schema 落盘 (writeSchema 会重新 serializeSchema 把新名写进去)
  818. if err := table.writeSchema(); err != nil {
  819. return err
  820. }
  821. if table.db != nil {
  822. if err := table.db.ddlBoundary("RenameColumn"); err != nil {
  823. return err
  824. }
  825. }
  826. return nil
  827. }