| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310 |
- package gormvrtx
- import (
- "errors"
- "fmt"
- "path/filepath"
- "testing"
- "gorm.io/gorm"
- vrtxsql "git.ycl.pub/yl32940/TsDB2/DB/driver"
- )
- // User 测试模型: 主键 + 普通字段 + 唯一索引 tag。
- type User struct {
- ID uint `gorm:"primaryKey"`
- Name string `gorm:"size:32;not null"`
- Age int
- Email string `gorm:"size:64;uniqueIndex"`
- }
- // UserWithAddr 在 User 基础上加一列, 表名与 User 相同 (AutoMigrate 加列路径)。
- type UserWithAddr struct {
- ID uint `gorm:"primaryKey"`
- Name string `gorm:"size:32;not null"`
- Age int
- Email string `gorm:"size:64;uniqueIndex"`
- Addr string `gorm:"size:128"`
- }
- func (UserWithAddr) TableName() string { return "users" }
- // openTest 打开一个位于临时目录的测试库; 结束时关连接池并释放 Dialector 持有的
- // 引擎引用 (归零才真正 Close 引擎、放开文件锁 —— Windows 下否则临时目录删不掉)。
- func openTest(t *testing.T) *gorm.DB {
- t.Helper()
- dsn := filepath.Join(t.TempDir(), "gormvrtx_test.db")
- db, err := gorm.Open(Open(dsn), &gorm.Config{})
- if err != nil {
- t.Fatalf("gorm.Open: %v", err)
- }
- t.Cleanup(func() {
- if d, ok := db.Dialector.(*Dialector); ok {
- // 先关连接池 (释放驱动连接的引擎引用), 再释放 Initialize 里的引用,
- // 归零引擎才真正 Close、放开文件锁 —— Windows 下否则临时目录删不掉。
- if d.sqlDB != nil {
- _ = d.sqlDB.Close()
- }
- _ = vrtxsql.ReleaseDB(d.DSN)
- }
- })
- return db
- }
- // TestAutoMigrate 建表 → 字段/索引核对 → 加列。
- func TestAutoMigrate(t *testing.T) {
- db := openTest(t)
- if db.Migrator().HasTable(&User{}) {
- t.Fatal("迁移前 users 表不应存在")
- }
- if err := db.AutoMigrate(&User{}); err != nil {
- t.Fatalf("AutoMigrate: %v", err)
- }
- if !db.Migrator().HasTable(&User{}) {
- t.Fatal("迁移后 users 表应存在")
- }
- if !db.Migrator().HasTable("users") { // 字符串表名路径
- t.Fatal("HasTable 字符串路径应命中")
- }
- // 字段齐全 (含 gorm NamingStrategy 的列名: ID→id, Name→name ...)
- for _, col := range []string{"id", "name", "age", "email"} {
- if !db.Migrator().HasColumn(&User{}, col) {
- t.Fatalf("缺少列 %q", col)
- }
- }
- // 主键索引 (行存储) + uniqueIndex tag 的索引
- if !db.Migrator().HasIndex(&User{}, "pk_users") {
- t.Fatal("缺少主键索引 pk_users")
- }
- if !db.Migrator().HasIndex(&User{}, "idx_users_email") {
- t.Fatal("缺少唯一索引 idx_users_email")
- }
- idxs, err := db.Migrator().GetIndexes(&User{})
- if err != nil {
- t.Fatalf("GetIndexes: %v", err)
- }
- t.Logf("indexes: %d", len(idxs))
- // 模型加一列后再迁移: AddField 路径
- if err := db.AutoMigrate(&UserWithAddr{}); err != nil {
- t.Fatalf("AutoMigrate 加列: %v", err)
- }
- if !db.Migrator().HasColumn(&UserWithAddr{}, "addr") {
- t.Fatal("迁移后应出现 addr 列")
- }
- // 新列完整往返 (插入 → 读回)
- rec := &UserWithAddr{Name: "bob", Age: 25, Email: "bob@example.com", Addr: "Beijing"}
- if err := db.Create(rec).Error; err != nil {
- t.Fatalf("Create: %v", err)
- }
- var back UserWithAddr
- if err := db.First(&back, rec.ID).Error; err != nil {
- t.Fatalf("First: %v", err)
- }
- if back.Addr != "Beijing" || back.Name != "bob" || back.Email != "bob@example.com" {
- t.Fatalf("读回不一致: %+v", back)
- }
- // ColumnTypes 自省
- cts, err := db.Migrator().ColumnTypes(&UserWithAddr{})
- if err != nil {
- t.Fatalf("ColumnTypes: %v", err)
- }
- names := map[string]bool{}
- for _, ct := range cts {
- names[ct.Name()] = true
- }
- for _, col := range []string{"id", "name", "age", "email", "addr"} {
- if !names[col] {
- t.Fatalf("ColumnTypes 缺少 %q", col)
- }
- }
- }
- // TestCRUD Create/First/Find+Where+Order+Limit/Updates/Delete/Count。
- func TestCRUD(t *testing.T) {
- db := openTest(t)
- if err := db.AutoMigrate(&User{}); err != nil {
- t.Fatalf("AutoMigrate: %v", err)
- }
- // Create: 自增主键回填
- u := &User{Name: "alice", Age: 30, Email: "alice@example.com"}
- if err := db.Create(u).Error; err != nil {
- t.Fatalf("Create: %v", err)
- }
- if u.ID == 0 {
- t.Fatal("Create 后主键 ID 应被回填")
- }
- // First: 按主键
- var got User
- if err := db.First(&got, u.ID).Error; err != nil {
- t.Fatalf("First: %v", err)
- }
- if got.ID != u.ID || got.Name != "alice" || got.Age != 30 || got.Email != "alice@example.com" {
- t.Fatalf("First 读回不一致: %+v", got)
- }
- // 造数: Find + Where + Order + Limit
- for i := 0; i < 4; i++ {
- if err := db.Create(&User{Name: fmt.Sprintf("u%d", i), Age: 20 + i,
- Email: fmt.Sprintf("u%d@example.com", i)}).Error; err != nil {
- t.Fatalf("Create #%d: %v", i, err)
- }
- }
- var list []User
- if err := db.Where("age >= ?", 21).Order("age desc").Limit(2).Find(&list).Error; err != nil {
- t.Fatalf("Find: %v", err)
- }
- // alice(30) u3(23) u2(22) u1(21) 满足 age>=21, desc 前 2 = 30,23
- if len(list) != 2 || list[0].Age != 30 || list[1].Age != 23 {
- t.Fatalf("Find+Where+Order+Limit 结果不对: %+v", list)
- }
- // Updates
- if err := db.Model(&User{}).Where("id = ?", u.ID).Updates(map[string]interface{}{"age": 31}).Error; err != nil {
- t.Fatalf("Updates: %v", err)
- }
- var after User
- if err := db.First(&after, u.ID).Error; err != nil {
- t.Fatalf("First after Updates: %v", err)
- }
- if after.Age != 31 {
- t.Fatalf("Updates 未生效: age=%d", after.Age)
- }
- // Count
- var n int64
- if err := db.Model(&User{}).Count(&n).Error; err != nil {
- t.Fatalf("Count: %v", err)
- }
- if n != 5 {
- t.Fatalf("Count=%d, 期望 5", n)
- }
- // Delete: 按条件删
- if err := db.Where("email = ?", "alice@example.com").Delete(&User{}).Error; err != nil {
- t.Fatalf("Delete: %v", err)
- }
- if err := db.Model(&User{}).Count(&n).Error; err != nil {
- t.Fatalf("Count after Delete: %v", err)
- }
- if n != 4 {
- t.Fatalf("Delete 后 Count=%d, 期望 4", n)
- }
- }
- // TestTransaction 事务提交与回滚。
- func TestTransaction(t *testing.T) {
- db := openTest(t)
- if err := db.AutoMigrate(&User{}); err != nil {
- t.Fatalf("AutoMigrate: %v", err)
- }
- // 回滚: 回调返回错误 → 数据不落
- boom := errors.New("force rollback")
- err := db.Transaction(func(tx *gorm.DB) error {
- if e := tx.Create(&User{Name: "tmp", Age: 1, Email: "tmp@example.com"}).Error; e != nil {
- return e
- }
- return boom
- })
- if !errors.Is(err, boom) {
- t.Fatalf("事务应回滚并报出回调错误, 实际 %v", err)
- }
- var n int64
- if err := db.Model(&User{}).Count(&n).Error; err != nil {
- t.Fatalf("Count: %v", err)
- }
- if n != 0 {
- t.Fatalf("回滚后仍见到 %d 行", n)
- }
- // 提交
- err = db.Transaction(func(tx *gorm.DB) error {
- return tx.Create(&User{Name: "committed", Age: 2, Email: "ok@example.com"}).Error
- })
- if err != nil {
- t.Fatalf("事务提交: %v", err)
- }
- if err := db.Model(&User{}).Count(&n).Error; err != nil {
- t.Fatalf("Count: %v", err)
- }
- if n != 1 {
- t.Fatalf("提交后应见到 1 行, 实际 %d", n)
- }
- }
- // Order 订单表: TestGormJoins 的右表。
- type Order struct {
- ID uint `gorm:"primaryKey"`
- UserID uint
- Amount int
- }
- func (Order) TableName() string { return "orders" }
- // TestGormJoins GORM Joins() 走驱动的 JOIN 支持 (INNER 等值)。
- func TestGormJoins(t *testing.T) {
- db := openTest(t)
- if err := db.AutoMigrate(&User{}, &Order{}); err != nil {
- t.Fatalf("migrate: %v", err)
- }
- u := User{Name: "alice", Age: 30, Email: "a@x.com"}
- if err := db.Create(&u).Error; err != nil {
- t.Fatal(err)
- }
- for _, amt := range []int{5, 50} {
- if err := db.Create(&Order{UserID: u.ID, Amount: amt}).Error; err != nil {
- t.Fatal(err)
- }
- }
- // JOIN + 右表条件: 只应命中 amount>10 的那张订单, 且只关联到 alice
- var users []User
- err := db.Joins("JOIN orders ON orders.user_id = users.id").
- Where("orders.amount > ?", 10).
- Find(&users).Error
- if err != nil {
- t.Fatalf("joins: %v", err)
- }
- if len(users) != 1 || users[0].Name != "alice" {
- t.Fatalf("JOIN 结果 %+v 不对", users)
- }
- }
- // TestBatchCreateDistinctPKs 批量创建 (切片): 每行必须分配独立主键号 ——
- // 回归"AllocPKID 在循环外取一次、切片所有行共用一个号被引擎拒重复主键"。
- func TestBatchCreateDistinctPKs(t *testing.T) {
- db := openTest(t)
- if err := db.AutoMigrate(&User{}); err != nil {
- t.Fatal(err)
- }
- users := []User{
- {Name: "b1", Age: 1, Email: "b1@x.com"},
- {Name: "b2", Age: 2, Email: "b2@x.com"},
- {Name: "b3", Age: 3, Email: "b3@x.com"},
- }
- if err := db.Create(&users).Error; err != nil {
- t.Fatalf("批量创建: %v", err)
- }
- seen := map[uint]bool{}
- for i, u := range users {
- if u.ID == 0 {
- t.Fatalf("第 %d 行主键未回填", i)
- }
- if seen[u.ID] {
- t.Fatalf("第 %d 行主键 %d 重复 — 共号 bug 未修", i, u.ID)
- }
- seen[u.ID] = true
- }
- var n int64
- if err := db.Model(&User{}).Count(&n).Error; err != nil || n != 3 {
- t.Fatalf("行数 %d err=%v", n, err)
- }
- }
|