batch_insert_test.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. package DB
  2. import (
  3. "fmt"
  4. "path/filepath"
  5. "testing"
  6. )
  7. // biRow 批量插入用例的行: 二级索引 (Age) + 动态列 (Name/Dept 字符串) + 数值列,
  8. // 覆盖批量路径会碰到的全部结构 (主键树 / 二级树 / 数据页 / 动态堆 / 表信息页)。
  9. type biRow struct {
  10. BaseModel
  11. Name string `vrtx:"maxlen:32"`
  12. Age int `vrtx:"index"`
  13. Dept string `vrtx:"maxlen:16"`
  14. Score int
  15. }
  16. // biUnique 唯一索引用例的行
  17. type biUnique struct {
  18. BaseModel
  19. Email string `vrtx:"index,unique"`
  20. }
  21. func biMake(i int) *biRow {
  22. return &biRow{
  23. Name: fmt.Sprintf("name_%06d", i),
  24. Age: 18 + i%60,
  25. Dept: fmt.Sprintf("dept_%02d", i%7),
  26. Score: i % 100,
  27. }
  28. }
  29. // TestBatchInsertRoundtrip 批量 + 逐行混插后全量校验:
  30. // - 行数一致 (TotalRows)
  31. // - 每行按 _id 点查命中且字段完整
  32. // - 二级索引按键反查能取到行
  33. // - 关库重开后一切照旧 (批量写入经缓冲池/提交链路的持久化)
  34. func TestBatchInsertRoundtrip(t *testing.T) {
  35. dbPath := filepath.Join(t.TempDir(), "batch_insert.db")
  36. db, err := Open(dbPath)
  37. if err != nil {
  38. t.Fatalf("打开: %v", err)
  39. }
  40. db.SetCommitPreset("batch")
  41. db.SetAutoCommitDelay(0)
  42. tbl, err := UseTable[biRow](db, "bi")
  43. if err != nil {
  44. t.Fatalf("建表: %v", err)
  45. }
  46. const (
  47. batch1 = 2000
  48. single = 500
  49. batch2 = 1500
  50. total = batch1 + single + batch2
  51. )
  52. // 批 1
  53. b1 := make([]*biRow, batch1)
  54. for i := range b1 {
  55. b1[i] = biMake(i)
  56. }
  57. if err := tbl.BatchInsert(b1); err != nil {
  58. t.Fatalf("批 1: %v", err)
  59. }
  60. // 逐行 500 条 (混插: 批量与单行路径交替构造同一棵树)
  61. for i := batch1; i < batch1+single; i++ {
  62. if err := tbl.Insert(biMake(i)); err != nil {
  63. t.Fatalf("单行 %d: %v", i, err)
  64. }
  65. }
  66. // 批 2
  67. b2 := make([]*biRow, batch2)
  68. for j := range b2 {
  69. b2[j] = biMake(batch1 + single + j)
  70. }
  71. if err := tbl.BatchInsert(b2); err != nil {
  72. t.Fatalf("批 2: %v", err)
  73. }
  74. if err := db.Commit(); err != nil {
  75. t.Fatalf("提交: %v", err)
  76. }
  77. core, ok := db.CoreDB().FindTable("bi")
  78. if !ok {
  79. t.Fatal("core 找不到表 bi")
  80. }
  81. if got := core.TotalRows(); got != total {
  82. t.Fatalf("行数: 期望 %d, 实际 %d", total, got)
  83. }
  84. // 每行按 _id 点查
  85. checkAll := func(stage string) {
  86. for i := 0; i < total; i++ {
  87. obj, err := tbl.Find(uint64(i + 1))
  88. if err != nil {
  89. t.Fatalf("%s: Find(%d): %v", stage, i+1, err)
  90. }
  91. want := biMake(i)
  92. if obj.Name != want.Name || obj.Age != want.Age ||
  93. obj.Dept != want.Dept || obj.Score != want.Score {
  94. t.Fatalf("%s: 第 %d 行字段错: got %+v", stage, i+1, obj)
  95. }
  96. }
  97. }
  98. checkAll("提交后")
  99. // 二级索引反查: Age 索引等值 COUNT, 与期望行数比对
  100. // Age = 18 + i%60 → 键 age 对应 i ≡ (age-18) mod 60
  101. age := 18 + 123%60 // 任取一个存在的键
  102. expect := 0
  103. for i := 0; i < total; i++ {
  104. if 18+i%60 == age {
  105. expect++
  106. }
  107. }
  108. got, err := tbl.Where("Age", "=", age).Count()
  109. if err != nil {
  110. t.Fatalf("Age 索引 COUNT: %v", err)
  111. }
  112. if int(got) != expect {
  113. t.Fatalf("Age=%d 索引计数: 期望 %d, 实际 %d", age, expect, got)
  114. }
  115. // 关库重开: 持久化 + 重开后的可查性
  116. if err := db.Close(); err != nil {
  117. t.Fatalf("关库: %v", err)
  118. }
  119. db2, err := Open(dbPath)
  120. if err != nil {
  121. t.Fatalf("重开: %v", err)
  122. }
  123. defer db2.Close()
  124. tbl2, err := UseTable[biRow](db2, "bi")
  125. if err != nil {
  126. t.Fatalf("重开建表: %v", err)
  127. }
  128. _ = tbl2
  129. // 用 core 逐行点查 (Find 走 ORM, 重开后同一路径)
  130. for i := 0; i < total; i += 97 {
  131. obj, err := tbl2.Find(uint64(i + 1))
  132. if err != nil {
  133. t.Fatalf("重开: Find(%d): %v", i+1, err)
  134. }
  135. want := biMake(i)
  136. if obj.Name != want.Name || obj.Age != want.Age {
  137. t.Fatalf("重开: 第 %d 行字段错", i+1)
  138. }
  139. }
  140. }
  141. // TestBatchInsertExplicitPK 显式主键混批: 退回逐行路径, 语义不变
  142. func TestBatchInsertExplicitPK(t *testing.T) {
  143. db, err := Open(filepath.Join(t.TempDir(), "batch_explicit.db"))
  144. if err != nil {
  145. t.Fatalf("打开: %v", err)
  146. }
  147. defer db.Close()
  148. db.SetAutoCommitDelay(0)
  149. tbl, err := UseTable[biRow](db, "bix")
  150. if err != nil {
  151. t.Fatalf("建表: %v", err)
  152. }
  153. // 一半显式主键, 一半自增
  154. objs := make([]*biRow, 100)
  155. for i := range objs {
  156. objs[i] = biMake(i)
  157. if i%2 == 0 {
  158. objs[i].ID = uint64(1000 + i)
  159. }
  160. }
  161. if err := tbl.BatchInsert(objs); err != nil {
  162. t.Fatalf("混批: %v", err)
  163. }
  164. // 自增号在显式号之后继续, 不撞车
  165. next := &biRow{Name: "next", Age: 20, Dept: "d", Score: 1}
  166. if err := tbl.Insert(next); err != nil {
  167. t.Fatalf("后续单行: %v", err)
  168. }
  169. core, _ := db.CoreDB().FindTable("bix")
  170. if got := core.TotalRows(); got != 101 {
  171. t.Fatalf("行数: 期望 101, 实际 %d", got)
  172. }
  173. // 显式号 (偶数行, id=1000+i) 能查到
  174. if _, err := tbl.Find(1000); err != nil {
  175. t.Fatalf("显式主键 1000 查不到: %v", err)
  176. }
  177. if _, err := tbl.Find(1098); err != nil {
  178. t.Fatalf("显式主键 1098 查不到: %v", err)
  179. }
  180. // 自增号 (奇数行, 显式号顶起游标后交错续号, id=1000+i) 也能查到
  181. if _, err := tbl.Find(1001); err != nil {
  182. t.Fatalf("自增主键 1001 查不到: %v", err)
  183. }
  184. if _, err := tbl.Find(1099); err != nil {
  185. t.Fatalf("自增主键 1099 查不到: %v", err)
  186. }
  187. }
  188. // TestBatchInsertUniqueDup 唯一索引批内重复: 整批拒绝, 一行都不落
  189. func TestBatchInsertUniqueDup(t *testing.T) {
  190. db, err := Open(filepath.Join(t.TempDir(), "batch_unique.db"))
  191. if err != nil {
  192. t.Fatalf("打开: %v", err)
  193. }
  194. defer db.Close()
  195. db.SetAutoCommitDelay(0)
  196. tbl, err := UseTable[biUnique](db, "biu")
  197. if err != nil {
  198. t.Fatalf("建表: %v", err)
  199. }
  200. objs := make([]*biUnique, 50)
  201. for i := range objs {
  202. objs[i] = &biUnique{Email: fmt.Sprintf("u%03d@x.com", i)}
  203. }
  204. objs[30].Email = objs[7].Email // 批内重复
  205. if err := tbl.BatchInsert(objs); err == nil {
  206. t.Fatal("批内重复键应当报错")
  207. }
  208. core, _ := db.CoreDB().FindTable("biu")
  209. if got := core.TotalRows(); got != 0 {
  210. t.Fatalf("唯一冲突后行数: 期望 0, 实际 %d", got)
  211. }
  212. // 去掉重复后整批可写入
  213. objs[30].Email = "u030@x.com"
  214. if err := tbl.BatchInsert(objs); err != nil {
  215. t.Fatalf("去重后批插: %v", err)
  216. }
  217. if got := core.TotalRows(); got != 50 {
  218. t.Fatalf("去重后行数: 期望 50, 实际 %d", got)
  219. }
  220. }