| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237 |
- package DB
- import (
- "fmt"
- "path/filepath"
- "testing"
- )
- // biRow 批量插入用例的行: 二级索引 (Age) + 动态列 (Name/Dept 字符串) + 数值列,
- // 覆盖批量路径会碰到的全部结构 (主键树 / 二级树 / 数据页 / 动态堆 / 表信息页)。
- type biRow struct {
- BaseModel
- Name string `vrtx:"maxlen:32"`
- Age int `vrtx:"index"`
- Dept string `vrtx:"maxlen:16"`
- Score int
- }
- // biUnique 唯一索引用例的行
- type biUnique struct {
- BaseModel
- Email string `vrtx:"index,unique"`
- }
- func biMake(i int) *biRow {
- return &biRow{
- Name: fmt.Sprintf("name_%06d", i),
- Age: 18 + i%60,
- Dept: fmt.Sprintf("dept_%02d", i%7),
- Score: i % 100,
- }
- }
- // TestBatchInsertRoundtrip 批量 + 逐行混插后全量校验:
- // - 行数一致 (TotalRows)
- // - 每行按 _id 点查命中且字段完整
- // - 二级索引按键反查能取到行
- // - 关库重开后一切照旧 (批量写入经缓冲池/提交链路的持久化)
- func TestBatchInsertRoundtrip(t *testing.T) {
- dbPath := filepath.Join(t.TempDir(), "batch_insert.db")
- db, err := Open(dbPath)
- if err != nil {
- t.Fatalf("打开: %v", err)
- }
- db.SetCommitPreset("batch")
- db.SetAutoCommitDelay(0)
- tbl, err := UseTable[biRow](db, "bi")
- if err != nil {
- t.Fatalf("建表: %v", err)
- }
- const (
- batch1 = 2000
- single = 500
- batch2 = 1500
- total = batch1 + single + batch2
- )
- // 批 1
- b1 := make([]*biRow, batch1)
- for i := range b1 {
- b1[i] = biMake(i)
- }
- if err := tbl.BatchInsert(b1); err != nil {
- t.Fatalf("批 1: %v", err)
- }
- // 逐行 500 条 (混插: 批量与单行路径交替构造同一棵树)
- for i := batch1; i < batch1+single; i++ {
- if err := tbl.Insert(biMake(i)); err != nil {
- t.Fatalf("单行 %d: %v", i, err)
- }
- }
- // 批 2
- b2 := make([]*biRow, batch2)
- for j := range b2 {
- b2[j] = biMake(batch1 + single + j)
- }
- if err := tbl.BatchInsert(b2); err != nil {
- t.Fatalf("批 2: %v", err)
- }
- if err := db.Commit(); err != nil {
- t.Fatalf("提交: %v", err)
- }
- core, ok := db.CoreDB().FindTable("bi")
- if !ok {
- t.Fatal("core 找不到表 bi")
- }
- if got := core.TotalRows(); got != total {
- t.Fatalf("行数: 期望 %d, 实际 %d", total, got)
- }
- // 每行按 _id 点查
- checkAll := func(stage string) {
- for i := 0; i < total; i++ {
- obj, err := tbl.Find(uint64(i + 1))
- if err != nil {
- t.Fatalf("%s: Find(%d): %v", stage, i+1, err)
- }
- want := biMake(i)
- if obj.Name != want.Name || obj.Age != want.Age ||
- obj.Dept != want.Dept || obj.Score != want.Score {
- t.Fatalf("%s: 第 %d 行字段错: got %+v", stage, i+1, obj)
- }
- }
- }
- checkAll("提交后")
- // 二级索引反查: Age 索引等值 COUNT, 与期望行数比对
- // Age = 18 + i%60 → 键 age 对应 i ≡ (age-18) mod 60
- age := 18 + 123%60 // 任取一个存在的键
- expect := 0
- for i := 0; i < total; i++ {
- if 18+i%60 == age {
- expect++
- }
- }
- got, err := tbl.Where("Age", "=", age).Count()
- if err != nil {
- t.Fatalf("Age 索引 COUNT: %v", err)
- }
- if int(got) != expect {
- t.Fatalf("Age=%d 索引计数: 期望 %d, 实际 %d", age, expect, got)
- }
- // 关库重开: 持久化 + 重开后的可查性
- if err := db.Close(); err != nil {
- t.Fatalf("关库: %v", err)
- }
- db2, err := Open(dbPath)
- if err != nil {
- t.Fatalf("重开: %v", err)
- }
- defer db2.Close()
- tbl2, err := UseTable[biRow](db2, "bi")
- if err != nil {
- t.Fatalf("重开建表: %v", err)
- }
- _ = tbl2
- // 用 core 逐行点查 (Find 走 ORM, 重开后同一路径)
- for i := 0; i < total; i += 97 {
- obj, err := tbl2.Find(uint64(i + 1))
- if err != nil {
- t.Fatalf("重开: Find(%d): %v", i+1, err)
- }
- want := biMake(i)
- if obj.Name != want.Name || obj.Age != want.Age {
- t.Fatalf("重开: 第 %d 行字段错", i+1)
- }
- }
- }
- // TestBatchInsertExplicitPK 显式主键混批: 退回逐行路径, 语义不变
- func TestBatchInsertExplicitPK(t *testing.T) {
- db, err := Open(filepath.Join(t.TempDir(), "batch_explicit.db"))
- if err != nil {
- t.Fatalf("打开: %v", err)
- }
- defer db.Close()
- db.SetAutoCommitDelay(0)
- tbl, err := UseTable[biRow](db, "bix")
- if err != nil {
- t.Fatalf("建表: %v", err)
- }
- // 一半显式主键, 一半自增
- objs := make([]*biRow, 100)
- for i := range objs {
- objs[i] = biMake(i)
- if i%2 == 0 {
- objs[i].ID = uint64(1000 + i)
- }
- }
- if err := tbl.BatchInsert(objs); err != nil {
- t.Fatalf("混批: %v", err)
- }
- // 自增号在显式号之后继续, 不撞车
- next := &biRow{Name: "next", Age: 20, Dept: "d", Score: 1}
- if err := tbl.Insert(next); err != nil {
- t.Fatalf("后续单行: %v", err)
- }
- core, _ := db.CoreDB().FindTable("bix")
- if got := core.TotalRows(); got != 101 {
- t.Fatalf("行数: 期望 101, 实际 %d", got)
- }
- // 显式号 (偶数行, id=1000+i) 能查到
- if _, err := tbl.Find(1000); err != nil {
- t.Fatalf("显式主键 1000 查不到: %v", err)
- }
- if _, err := tbl.Find(1098); err != nil {
- t.Fatalf("显式主键 1098 查不到: %v", err)
- }
- // 自增号 (奇数行, 显式号顶起游标后交错续号, id=1000+i) 也能查到
- if _, err := tbl.Find(1001); err != nil {
- t.Fatalf("自增主键 1001 查不到: %v", err)
- }
- if _, err := tbl.Find(1099); err != nil {
- t.Fatalf("自增主键 1099 查不到: %v", err)
- }
- }
- // TestBatchInsertUniqueDup 唯一索引批内重复: 整批拒绝, 一行都不落
- func TestBatchInsertUniqueDup(t *testing.T) {
- db, err := Open(filepath.Join(t.TempDir(), "batch_unique.db"))
- if err != nil {
- t.Fatalf("打开: %v", err)
- }
- defer db.Close()
- db.SetAutoCommitDelay(0)
- tbl, err := UseTable[biUnique](db, "biu")
- if err != nil {
- t.Fatalf("建表: %v", err)
- }
- objs := make([]*biUnique, 50)
- for i := range objs {
- objs[i] = &biUnique{Email: fmt.Sprintf("u%03d@x.com", i)}
- }
- objs[30].Email = objs[7].Email // 批内重复
- if err := tbl.BatchInsert(objs); err == nil {
- t.Fatal("批内重复键应当报错")
- }
- core, _ := db.CoreDB().FindTable("biu")
- if got := core.TotalRows(); got != 0 {
- t.Fatalf("唯一冲突后行数: 期望 0, 实际 %d", got)
- }
- // 去掉重复后整批可写入
- objs[30].Email = "u030@x.com"
- if err := tbl.BatchInsert(objs); err != nil {
- t.Fatalf("去重后批插: %v", err)
- }
- if got := core.TotalRows(); got != 50 {
- t.Fatalf("去重后行数: 期望 50, 实际 %d", got)
- }
- }
|