limits_test.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. package core
  2. import (
  3. "errors"
  4. "strconv"
  5. "strings"
  6. "testing"
  7. "git.ycl.pub/yl32940/TsDB2/DB/core/page"
  8. "git.ycl.pub/yl32940/TsDB2/DB/core/types"
  9. )
  10. // 本文件集中钉死"每一处限制在触发时都必须有报错或提示"。
  11. //
  12. // 这些闸门原先有的缺口是【静默】的 —— 越界不报错, 而是把 1 字节的 FieldCount /
  13. // 字段索引 / schemaVer 写回绕掉, 读回来整表错位。所以每个用例除了"必须报错",
  14. // 还检查"报错里要有用户能据此改代码的信息"(上限与实际值 / 列名)。
  15. // TestFieldCountGate 列数上限: 建表与加列必须卡同一条。
  16. //
  17. // 只在 TablePage.Create 卡住而 AddField 不限, 就能反复加列把 master registry 推过
  18. // 255, 落盘时把 FieldCount 与字段索引 (都是 1 字节) 截断成错位 schema。
  19. func TestFieldCountGate(t *testing.T) {
  20. if err := checkFieldCount(0); err == nil || !errors.Is(err, ErrInvalidFieldCount) {
  21. t.Fatalf("0 列应报 ErrInvalidFieldCount, 得到 %v", err)
  22. }
  23. if err := checkFieldCount(MaxFieldsPerTable); err != nil {
  24. t.Fatalf("%d 列应当放行: %v", MaxFieldsPerTable, err)
  25. }
  26. err := checkFieldCount(MaxFieldsPerTable + 1)
  27. if err == nil || !errors.Is(err, ErrInvalidFieldCount) {
  28. t.Fatalf("%d 列应报 ErrInvalidFieldCount, 得到 %v", MaxFieldsPerTable+1, err)
  29. }
  30. // 只说"字段数量非法"没法用: 用户要知道上限是多少、本次是多少
  31. for _, want := range []string{strconv.Itoa(MaxFieldsPerTable), strconv.Itoa(MaxFieldsPerTable + 1)} {
  32. if !strings.Contains(err.Error(), want) {
  33. t.Errorf("列数超限的报错缺少 %q: %v", want, err)
  34. }
  35. }
  36. // 加列路径也必须走同一条闸门
  37. db := fixOpenDB(t)
  38. fields := make([]types.FieldInfo, MaxFieldsPerTable)
  39. for i := range fields {
  40. fields[i] = types.FieldInfo{
  41. FieldType: types.FieldType_Uint8,
  42. FieldName: []byte("c" + strconv.Itoa(i)),
  43. }
  44. }
  45. if _, err := db.CreateTable("FULL", fields); err != nil {
  46. t.Fatalf("建一张 %d 列的表应当成功: %v", MaxFieldsPerTable, err)
  47. }
  48. tbl, ok := db.FindTable("FULL")
  49. if !ok {
  50. t.Fatal("找不到 FULL")
  51. }
  52. err = tbl.AddField(types.FieldInfo{
  53. FieldType: types.FieldType_Uint8, FieldName: []byte("oneTooMany"),
  54. })
  55. if err == nil || !errors.Is(err, ErrInvalidFieldCount) {
  56. t.Fatalf("超过 %d 列后 AddField 应报 ErrInvalidFieldCount, 得到 %v", MaxFieldsPerTable, err)
  57. }
  58. if _, present := tbl.GetActiveFieldType("oneTooMany"); present {
  59. t.Error("被拒的列不能进 schema")
  60. }
  61. }
  62. // TestSchemaVersionGate schema 版本号是每行行首 1 字节, 版本条数也是 1 字节: 到上限再演进
  63. // 会回绕成 0, 而 0 不是任何已知版本 → getVersion 返回 nil, 该行 NULL 位图取不到、所有列
  64. // 静默读成 0/NULL。必须在演进之前拒绝, 而不是"写进去再读不出来"。
  65. func TestSchemaVersionGate(t *testing.T) {
  66. db := fixOpenDB(t)
  67. if _, err := db.CreateTable("VER", []types.FieldInfo{
  68. {FieldType: types.FieldType_Uint8, FieldName: []byte("A")},
  69. }); err != nil {
  70. t.Fatalf("create: %v", err)
  71. }
  72. tbl, ok := db.FindTable("VER")
  73. if !ok {
  74. t.Fatal("找不到 VER")
  75. }
  76. realVer := tbl.activeVersion
  77. tbl.activeVersion = 0xFF // 推到"下一版就会回绕"的状态
  78. if _, err := tbl.nextSchemaVersion(); err == nil {
  79. t.Error("activeVersion=255 时 nextSchemaVersion 应当报错")
  80. }
  81. addErr := tbl.AddField(types.FieldInfo{
  82. FieldType: types.FieldType_Uint8, FieldName: []byte("B"),
  83. })
  84. dropErr := tbl.DropField("A")
  85. if addErr == nil {
  86. t.Error("版本耗尽后 AddField 应当被拒 (否则版本号回绕成 0)")
  87. }
  88. if dropErr == nil {
  89. t.Error("版本耗尽后 DropField 应当被拒 (删列同样新增一版)")
  90. }
  91. // 被拒的调用不能改动任何 in-memory 状态, 否则表会停在"改了一半"
  92. if len(tbl.Field) != 1 {
  93. t.Errorf("被拒的 AddField 改动了 master registry: %d 列", len(tbl.Field))
  94. }
  95. if tbl.Field[0].IsDropped() {
  96. t.Error("被拒的 DropField 把列标记成了已删除")
  97. }
  98. // 恢复真实版本后, 表必须仍然可用 (闸门不能把表弄坏)
  99. tbl.activeVersion = realVer
  100. if _, err := tbl.InsertRow([]types.FieldValue{
  101. {FieldName: []byte("A"), Value: uint8(1)},
  102. }); err != nil {
  103. t.Errorf("版本闸门拒绝后表应当仍可写: %v", err)
  104. }
  105. }
  106. // TestFieldNameLengthGate 列名在 schema 里是 [nameLen(1B)][name...]:
  107. // 空名会让解析侧把下一个字段的类型字节当名字, 超过 255 字节会让 nameLen 截断并让其后
  108. // 所有字段整体错位。两者都必须在建列时报错。
  109. func TestFieldNameLengthGate(t *testing.T) {
  110. db := fixOpenDB(t)
  111. _, err := db.CreateTable("EMPTYNAME", []types.FieldInfo{
  112. {FieldType: types.FieldType_Uint8, FieldName: nil},
  113. })
  114. if err == nil {
  115. t.Error("长度为 0 的列名必须被拒")
  116. }
  117. tooLong := make([]byte, FieldNameSize+1)
  118. for i := range tooLong {
  119. tooLong[i] = 'a'
  120. }
  121. _, err = db.CreateTable("LONGNAME", []types.FieldInfo{
  122. {FieldType: types.FieldType_Uint8, FieldName: tooLong},
  123. })
  124. if err == nil {
  125. t.Fatalf("列名超过 %d 字节必须被拒", FieldNameSize)
  126. }
  127. // 报错要能直接改代码: 既要有上限, 也要有用户写下的那个长度
  128. for _, want := range []string{strconv.Itoa(FieldNameSize), strconv.Itoa(FieldNameSize + 1)} {
  129. if !strings.Contains(err.Error(), want) {
  130. t.Errorf("列名超长的报错缺少 %q: %v", want, err)
  131. }
  132. }
  133. }
  134. // TestIndexFieldCountGate 索引分量个数上限。
  135. //
  136. // 旧实现用 `byte(len(fieldIDs))` 判断, 257 个分量会回绕成 1 而"通过", 于是越界的调用
  137. // 反而建成一个只覆盖 1 列却声称覆盖全部的索引。所以先按 int 判, 再转 byte。
  138. func TestIndexFieldCountGate(t *testing.T) {
  139. db := fixOpenDB(t)
  140. if _, err := db.CreateTable("IDXCNT", []types.FieldInfo{
  141. {FieldType: types.FieldType_Uint64, FieldName: []byte("A")},
  142. }); err != nil {
  143. t.Fatalf("create: %v", err)
  144. }
  145. tbl, ok := db.FindTable("IDXCNT")
  146. if !ok {
  147. t.Fatal("找不到 IDXCNT")
  148. }
  149. if _, err := tbl.CreateIndex("pk", page.IndexTypePrimary, []byte{0}); err != nil {
  150. t.Fatalf("create pk: %v", err)
  151. }
  152. if _, err := tbl.CreateIndex("none", page.IndexTypeBTree, nil); err == nil {
  153. t.Error("0 个分量必须被拒")
  154. }
  155. if _, err := tbl.CreateIndex("many", page.IndexTypeBTree, make([]byte, MaxIndexFields+1)); err == nil {
  156. t.Errorf("%d 个分量必须被拒", MaxIndexFields+1)
  157. }
  158. // 257 个分量: 走 byte 会回绕成 1, 必须在这条闸门被挡住
  159. if _, err := tbl.CreateIndex("overflow", page.IndexTypeBTree, make([]byte, 257)); err == nil {
  160. t.Error("257 个分量 (byte 回绕) 必须被拒")
  161. }
  162. }
  163. // TestDynamicValueSizeGate Dynamicbyte 的单值上限, 以及"这条上限只管 Dynamicbyte"。
  164. //
  165. // Dynamicbyte 的内容整块落进一个堆页 (行内 6B 堆引用), 单值上限就是 MaxBlobSize。超限值
  166. // 必须在 serialize 这一层就报出列名与上限, 否则用户只会拿到底层那句不带列名、不带字节数的
  167. // ErrHeapPageTooLarge, 看不出是哪一列、超了多少。
  168. //
  169. // blob 列 (FieldType_Blob) 不在闸门内: v10 起它的行内是 8B BlobRef, 内容落在可跨页的链上
  170. // (上限约 4GB, 见 BlobChainMax), 同一个超过 MaxBlobSize 的值写进 blob 列是合法的 ——
  171. // 链上的内容能不能真的读回来由 TestBlobColumnChainRoundTrip 钉。
  172. func TestDynamicValueSizeGate(t *testing.T) {
  173. db := fixOpenDB(t)
  174. if _, err := db.CreateTable("DYN", []types.FieldInfo{
  175. {FieldType: types.FieldType_Uint64, FieldName: []byte("I")},
  176. {FieldType: types.FieldType_Dynamicbyte, FieldName: []byte("D")},
  177. }); err != nil {
  178. t.Fatalf("create: %v", err)
  179. }
  180. tbl, ok := db.FindTable("DYN")
  181. if !ok {
  182. t.Fatal("找不到 DYN")
  183. }
  184. // 主键要落在定长列 I 上: 动态列不能作为索引键
  185. if _, err := tbl.CreateIndex("pk", page.IndexTypePrimary, []byte{0}); err != nil {
  186. t.Fatalf("create pk: %v", err)
  187. }
  188. huge := make([]byte, MaxBlobSize+1)
  189. _, err := tbl.InsertRow([]types.FieldValue{
  190. {FieldName: []byte("I"), Value: uint64(1)},
  191. {FieldName: []byte("D"), Value: huge},
  192. })
  193. if err == nil {
  194. t.Fatalf("D: 超过单块上限 %d 的写入必须被拒", MaxBlobSize)
  195. }
  196. if !errors.Is(err, types.ErrValueRange) {
  197. t.Errorf("D: 超限应报 ErrValueRange, 得到 %v", err)
  198. }
  199. if !strings.Contains(err.Error(), "D") {
  200. t.Errorf("D: 超限报错应包含列名: %v", err)
  201. }
  202. if !strings.Contains(err.Error(), strconv.Itoa(int(MaxBlobSize))) {
  203. t.Errorf("D: 超限报错应包含上限 %d: %v", MaxBlobSize, err)
  204. }
  205. // 报错要指出出路: 同一个值换个列类型就装得下。
  206. if !strings.Contains(err.Error(), "blob") {
  207. t.Errorf("D: 超限报错应提示可改用 blob 列: %v", err)
  208. }
  209. }
  210. // TestNotNullGate 非空列写 NULL 必须报错, 且定长列与动态列口径一致。
  211. //
  212. // 原先只有动态列会"静默成功" (行内引用全 0 且 NULL 位图不置位, 读回来是空内容),
  213. // 而定长列写 nil 会在 EncodeValue 里报错 —— 同一句"写 nil"在两种列上行为相反。
  214. func TestNotNullGate(t *testing.T) {
  215. db := fixOpenDB(t)
  216. if _, err := db.CreateTable("NN", []types.FieldInfo{
  217. {FieldType: types.FieldType_Uint64, FieldName: []byte("I")},
  218. {FieldType: types.FieldType_Dynamicbyte, FieldName: []byte("D")},
  219. {FieldType: types.FieldType_Dynamicbyte, FieldName: []byte("N"), Flag: types.FlagNullable},
  220. }); err != nil {
  221. t.Fatalf("create: %v", err)
  222. }
  223. tbl, ok := db.FindTable("NN")
  224. if !ok {
  225. t.Fatal("找不到 NN")
  226. }
  227. if _, err := tbl.CreateIndex("pk", page.IndexTypePrimary, []byte{0}); err != nil {
  228. t.Fatalf("create pk: %v", err)
  229. }
  230. // 非空动态列写 NULL → 必须报错并点名列
  231. _, err := tbl.InsertRow([]types.FieldValue{
  232. {FieldName: []byte("I"), Value: uint64(1)},
  233. {FieldName: []byte("D"), Value: nil},
  234. })
  235. if err == nil {
  236. t.Fatal("非空动态列写 NULL 必须被拒")
  237. }
  238. if !strings.Contains(err.Error(), "D") {
  239. t.Errorf("非空列写 NULL 的报错应点名列: %v", err)
  240. }
  241. // 可空列写 NULL → 放行
  242. if _, err := tbl.InsertRow([]types.FieldValue{
  243. {FieldName: []byte("I"), Value: uint64(2)},
  244. {FieldName: []byte("N"), Value: nil},
  245. }); err != nil {
  246. t.Errorf("可空列写 NULL 应当放行: %v", err)
  247. }
  248. // 省略非空列 (本次不传) → 放行, 按零值/空内容写
  249. if _, err := tbl.InsertRow([]types.FieldValue{
  250. {FieldName: []byte("I"), Value: uint64(3)},
  251. }); err != nil {
  252. t.Errorf("省略非空列应当放行: %v", err)
  253. }
  254. }