gormvrtx_test.go 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. package gormvrtx
  2. import (
  3. "errors"
  4. "fmt"
  5. "path/filepath"
  6. "testing"
  7. "gorm.io/gorm"
  8. vrtxsql "git.ycl.pub/yl32940/TsDB2/DB/driver"
  9. )
  10. // User 测试模型: 主键 + 普通字段 + 唯一索引 tag。
  11. type User struct {
  12. ID uint `gorm:"primaryKey"`
  13. Name string `gorm:"size:32;not null"`
  14. Age int
  15. Email string `gorm:"size:64;uniqueIndex"`
  16. }
  17. // UserWithAddr 在 User 基础上加一列, 表名与 User 相同 (AutoMigrate 加列路径)。
  18. type UserWithAddr struct {
  19. ID uint `gorm:"primaryKey"`
  20. Name string `gorm:"size:32;not null"`
  21. Age int
  22. Email string `gorm:"size:64;uniqueIndex"`
  23. Addr string `gorm:"size:128"`
  24. }
  25. func (UserWithAddr) TableName() string { return "users" }
  26. // openTest 打开一个位于临时目录的测试库; 结束时关连接池并释放 Dialector 持有的
  27. // 引擎引用 (归零才真正 Close 引擎、放开文件锁 —— Windows 下否则临时目录删不掉)。
  28. func openTest(t *testing.T) *gorm.DB {
  29. t.Helper()
  30. dsn := filepath.Join(t.TempDir(), "gormvrtx_test.db")
  31. db, err := gorm.Open(Open(dsn), &gorm.Config{})
  32. if err != nil {
  33. t.Fatalf("gorm.Open: %v", err)
  34. }
  35. t.Cleanup(func() {
  36. if d, ok := db.Dialector.(*Dialector); ok {
  37. // 先关连接池 (释放驱动连接的引擎引用), 再释放 Initialize 里的引用,
  38. // 归零引擎才真正 Close、放开文件锁 —— Windows 下否则临时目录删不掉。
  39. if d.sqlDB != nil {
  40. _ = d.sqlDB.Close()
  41. }
  42. _ = vrtxsql.ReleaseDB(d.DSN)
  43. }
  44. })
  45. return db
  46. }
  47. // TestAutoMigrate 建表 → 字段/索引核对 → 加列。
  48. func TestAutoMigrate(t *testing.T) {
  49. db := openTest(t)
  50. if db.Migrator().HasTable(&User{}) {
  51. t.Fatal("迁移前 users 表不应存在")
  52. }
  53. if err := db.AutoMigrate(&User{}); err != nil {
  54. t.Fatalf("AutoMigrate: %v", err)
  55. }
  56. if !db.Migrator().HasTable(&User{}) {
  57. t.Fatal("迁移后 users 表应存在")
  58. }
  59. if !db.Migrator().HasTable("users") { // 字符串表名路径
  60. t.Fatal("HasTable 字符串路径应命中")
  61. }
  62. // 字段齐全 (含 gorm NamingStrategy 的列名: ID→id, Name→name ...)
  63. for _, col := range []string{"id", "name", "age", "email"} {
  64. if !db.Migrator().HasColumn(&User{}, col) {
  65. t.Fatalf("缺少列 %q", col)
  66. }
  67. }
  68. // 主键索引 (行存储) + uniqueIndex tag 的索引
  69. if !db.Migrator().HasIndex(&User{}, "pk_users") {
  70. t.Fatal("缺少主键索引 pk_users")
  71. }
  72. if !db.Migrator().HasIndex(&User{}, "idx_users_email") {
  73. t.Fatal("缺少唯一索引 idx_users_email")
  74. }
  75. idxs, err := db.Migrator().GetIndexes(&User{})
  76. if err != nil {
  77. t.Fatalf("GetIndexes: %v", err)
  78. }
  79. t.Logf("indexes: %d", len(idxs))
  80. // 模型加一列后再迁移: AddField 路径
  81. if err := db.AutoMigrate(&UserWithAddr{}); err != nil {
  82. t.Fatalf("AutoMigrate 加列: %v", err)
  83. }
  84. if !db.Migrator().HasColumn(&UserWithAddr{}, "addr") {
  85. t.Fatal("迁移后应出现 addr 列")
  86. }
  87. // 新列完整往返 (插入 → 读回)
  88. rec := &UserWithAddr{Name: "bob", Age: 25, Email: "bob@example.com", Addr: "Beijing"}
  89. if err := db.Create(rec).Error; err != nil {
  90. t.Fatalf("Create: %v", err)
  91. }
  92. var back UserWithAddr
  93. if err := db.First(&back, rec.ID).Error; err != nil {
  94. t.Fatalf("First: %v", err)
  95. }
  96. if back.Addr != "Beijing" || back.Name != "bob" || back.Email != "bob@example.com" {
  97. t.Fatalf("读回不一致: %+v", back)
  98. }
  99. // ColumnTypes 自省
  100. cts, err := db.Migrator().ColumnTypes(&UserWithAddr{})
  101. if err != nil {
  102. t.Fatalf("ColumnTypes: %v", err)
  103. }
  104. names := map[string]bool{}
  105. for _, ct := range cts {
  106. names[ct.Name()] = true
  107. }
  108. for _, col := range []string{"id", "name", "age", "email", "addr"} {
  109. if !names[col] {
  110. t.Fatalf("ColumnTypes 缺少 %q", col)
  111. }
  112. }
  113. }
  114. // TestCRUD Create/First/Find+Where+Order+Limit/Updates/Delete/Count。
  115. func TestCRUD(t *testing.T) {
  116. db := openTest(t)
  117. if err := db.AutoMigrate(&User{}); err != nil {
  118. t.Fatalf("AutoMigrate: %v", err)
  119. }
  120. // Create: 自增主键回填
  121. u := &User{Name: "alice", Age: 30, Email: "alice@example.com"}
  122. if err := db.Create(u).Error; err != nil {
  123. t.Fatalf("Create: %v", err)
  124. }
  125. if u.ID == 0 {
  126. t.Fatal("Create 后主键 ID 应被回填")
  127. }
  128. // First: 按主键
  129. var got User
  130. if err := db.First(&got, u.ID).Error; err != nil {
  131. t.Fatalf("First: %v", err)
  132. }
  133. if got.ID != u.ID || got.Name != "alice" || got.Age != 30 || got.Email != "alice@example.com" {
  134. t.Fatalf("First 读回不一致: %+v", got)
  135. }
  136. // 造数: Find + Where + Order + Limit
  137. for i := 0; i < 4; i++ {
  138. if err := db.Create(&User{Name: fmt.Sprintf("u%d", i), Age: 20 + i,
  139. Email: fmt.Sprintf("u%d@example.com", i)}).Error; err != nil {
  140. t.Fatalf("Create #%d: %v", i, err)
  141. }
  142. }
  143. var list []User
  144. if err := db.Where("age >= ?", 21).Order("age desc").Limit(2).Find(&list).Error; err != nil {
  145. t.Fatalf("Find: %v", err)
  146. }
  147. // alice(30) u3(23) u2(22) u1(21) 满足 age>=21, desc 前 2 = 30,23
  148. if len(list) != 2 || list[0].Age != 30 || list[1].Age != 23 {
  149. t.Fatalf("Find+Where+Order+Limit 结果不对: %+v", list)
  150. }
  151. // Updates
  152. if err := db.Model(&User{}).Where("id = ?", u.ID).Updates(map[string]interface{}{"age": 31}).Error; err != nil {
  153. t.Fatalf("Updates: %v", err)
  154. }
  155. var after User
  156. if err := db.First(&after, u.ID).Error; err != nil {
  157. t.Fatalf("First after Updates: %v", err)
  158. }
  159. if after.Age != 31 {
  160. t.Fatalf("Updates 未生效: age=%d", after.Age)
  161. }
  162. // Count
  163. var n int64
  164. if err := db.Model(&User{}).Count(&n).Error; err != nil {
  165. t.Fatalf("Count: %v", err)
  166. }
  167. if n != 5 {
  168. t.Fatalf("Count=%d, 期望 5", n)
  169. }
  170. // Delete: 按条件删
  171. if err := db.Where("email = ?", "alice@example.com").Delete(&User{}).Error; err != nil {
  172. t.Fatalf("Delete: %v", err)
  173. }
  174. if err := db.Model(&User{}).Count(&n).Error; err != nil {
  175. t.Fatalf("Count after Delete: %v", err)
  176. }
  177. if n != 4 {
  178. t.Fatalf("Delete 后 Count=%d, 期望 4", n)
  179. }
  180. }
  181. // TestTransaction 事务提交与回滚。
  182. func TestTransaction(t *testing.T) {
  183. db := openTest(t)
  184. if err := db.AutoMigrate(&User{}); err != nil {
  185. t.Fatalf("AutoMigrate: %v", err)
  186. }
  187. // 回滚: 回调返回错误 → 数据不落
  188. boom := errors.New("force rollback")
  189. err := db.Transaction(func(tx *gorm.DB) error {
  190. if e := tx.Create(&User{Name: "tmp", Age: 1, Email: "tmp@example.com"}).Error; e != nil {
  191. return e
  192. }
  193. return boom
  194. })
  195. if !errors.Is(err, boom) {
  196. t.Fatalf("事务应回滚并报出回调错误, 实际 %v", err)
  197. }
  198. var n int64
  199. if err := db.Model(&User{}).Count(&n).Error; err != nil {
  200. t.Fatalf("Count: %v", err)
  201. }
  202. if n != 0 {
  203. t.Fatalf("回滚后仍见到 %d 行", n)
  204. }
  205. // 提交
  206. err = db.Transaction(func(tx *gorm.DB) error {
  207. return tx.Create(&User{Name: "committed", Age: 2, Email: "ok@example.com"}).Error
  208. })
  209. if err != nil {
  210. t.Fatalf("事务提交: %v", err)
  211. }
  212. if err := db.Model(&User{}).Count(&n).Error; err != nil {
  213. t.Fatalf("Count: %v", err)
  214. }
  215. if n != 1 {
  216. t.Fatalf("提交后应见到 1 行, 实际 %d", n)
  217. }
  218. }
  219. // Order 订单表: TestGormJoins 的右表。
  220. type Order struct {
  221. ID uint `gorm:"primaryKey"`
  222. UserID uint
  223. Amount int
  224. }
  225. func (Order) TableName() string { return "orders" }
  226. // TestGormJoins GORM Joins() 走驱动的 JOIN 支持 (INNER 等值)。
  227. func TestGormJoins(t *testing.T) {
  228. db := openTest(t)
  229. if err := db.AutoMigrate(&User{}, &Order{}); err != nil {
  230. t.Fatalf("migrate: %v", err)
  231. }
  232. u := User{Name: "alice", Age: 30, Email: "a@x.com"}
  233. if err := db.Create(&u).Error; err != nil {
  234. t.Fatal(err)
  235. }
  236. for _, amt := range []int{5, 50} {
  237. if err := db.Create(&Order{UserID: u.ID, Amount: amt}).Error; err != nil {
  238. t.Fatal(err)
  239. }
  240. }
  241. // JOIN + 右表条件: 只应命中 amount>10 的那张订单, 且只关联到 alice
  242. var users []User
  243. err := db.Joins("JOIN orders ON orders.user_id = users.id").
  244. Where("orders.amount > ?", 10).
  245. Find(&users).Error
  246. if err != nil {
  247. t.Fatalf("joins: %v", err)
  248. }
  249. if len(users) != 1 || users[0].Name != "alice" {
  250. t.Fatalf("JOIN 结果 %+v 不对", users)
  251. }
  252. }
  253. // TestBatchCreateDistinctPKs 批量创建 (切片): 每行必须分配独立主键号 ——
  254. // 回归"AllocPKID 在循环外取一次、切片所有行共用一个号被引擎拒重复主键"。
  255. func TestBatchCreateDistinctPKs(t *testing.T) {
  256. db := openTest(t)
  257. if err := db.AutoMigrate(&User{}); err != nil {
  258. t.Fatal(err)
  259. }
  260. users := []User{
  261. {Name: "b1", Age: 1, Email: "b1@x.com"},
  262. {Name: "b2", Age: 2, Email: "b2@x.com"},
  263. {Name: "b3", Age: 3, Email: "b3@x.com"},
  264. }
  265. if err := db.Create(&users).Error; err != nil {
  266. t.Fatalf("批量创建: %v", err)
  267. }
  268. seen := map[uint]bool{}
  269. for i, u := range users {
  270. if u.ID == 0 {
  271. t.Fatalf("第 %d 行主键未回填", i)
  272. }
  273. if seen[u.ID] {
  274. t.Fatalf("第 %d 行主键 %d 重复 — 共号 bug 未修", i, u.ID)
  275. }
  276. seen[u.ID] = true
  277. }
  278. var n int64
  279. if err := db.Model(&User{}).Count(&n).Error; err != nil || n != 3 {
  280. t.Fatalf("行数 %d err=%v", n, err)
  281. }
  282. }