diff --git a/contrib/drivers/gaussdb/gaussdb_z_unit_feature_ctx_test.go b/contrib/drivers/gaussdb/gaussdb_z_unit_feature_ctx_test.go new file mode 100644 index 000000000..b3fc3e40d --- /dev/null +++ b/contrib/drivers/gaussdb/gaussdb_z_unit_feature_ctx_test.go @@ -0,0 +1,102 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gaussdb_test + +import ( + "context" + "testing" + "time" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/test/gtest" +) + +func Test_Ctx(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + db, err := gdb.Instance() + t.AssertNil(err) + + err1 := db.PingMaster() + err2 := db.PingSlave() + t.Assert(err1, nil) + t.Assert(err2, nil) + + newDb := db.Ctx(context.Background()) + t.AssertNE(newDb, nil) + }) +} + +func Test_Ctx_Query(t *testing.T) { + db.GetLogger().(*glog.Logger).SetCtxKeys("SpanId", "TraceId") + gtest.C(t, func(t *gtest.T) { + db.SetDebug(true) + defer db.SetDebug(false) + ctx := context.WithValue(context.Background(), "TraceId", "12345678") + ctx = context.WithValue(ctx, "SpanId", "0.1") + db.Query(ctx, "select 1") + }) + gtest.C(t, func(t *gtest.T) { + db.SetDebug(true) + defer db.SetDebug(false) + db.Query(ctx, "select 2") + }) +} + +func Test_Ctx_Model(t *testing.T) { + table := createInitTable() + defer dropTable(table) + db.GetLogger().(*glog.Logger).SetCtxKeys("SpanId", "TraceId") + gtest.C(t, func(t *gtest.T) { + db.SetDebug(true) + defer db.SetDebug(false) + ctx := context.WithValue(context.Background(), "TraceId", "12345678") + ctx = context.WithValue(ctx, "SpanId", "0.1") + db.Model(table).Ctx(ctx).All() + }) + gtest.C(t, func(t *gtest.T) { + db.SetDebug(true) + defer db.SetDebug(false) + db.Model(table).All() + }) +} + +func Test_Ctx_Transaction(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + db.GetLogger().(*glog.Logger).SetCtxKeys("SpanId", "TraceId") + gtest.C(t, func(t *gtest.T) { + db.SetDebug(true) + defer db.SetDebug(false) + ctx := context.WithValue(context.Background(), "TraceId", "tx_trace_123") + ctx = context.WithValue(ctx, "SpanId", "0.2") + + err := db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + _, err := tx.Model(table).Ctx(ctx).Where("id", 1).One() + return err + }) + t.AssertNil(err) + }) +} + +func Test_Ctx_Timeout(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*10) + defer cancel() + + // Wait for the context to expire + time.Sleep(time.Millisecond * 50) + + // Query with expired context should return error + _, err := db.Model(table).Ctx(ctx).All() + t.AssertNE(err, nil) + }) +} diff --git a/contrib/drivers/gaussdb/gaussdb_z_unit_feature_hook_test.go b/contrib/drivers/gaussdb/gaussdb_z_unit_feature_hook_test.go new file mode 100644 index 000000000..cca55e400 --- /dev/null +++ b/contrib/drivers/gaussdb/gaussdb_z_unit_feature_hook_test.go @@ -0,0 +1,216 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gaussdb_test + +import ( + "context" + "database/sql" + "fmt" + "testing" + + "github.com/gogf/gf/v2/container/gvar" + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/test/gtest" +) + +func Test_Model_Hook_Select(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + m := db.Model(table).Hook(gdb.HookHandler{ + Select: func(ctx context.Context, in *gdb.HookSelectInput) (result gdb.Result, err error) { + result, err = in.Next(ctx) + if err != nil { + return + } + for i, record := range result { + record["test"] = gvar.New(100 + record["id"].Int()) + result[i] = record + } + return + }, + }) + all, err := m.Where("id > ?", 6).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(all), 4) + t.Assert(all[0]["id"].Int(), 7) + t.Assert(all[0]["test"].Int(), 107) + t.Assert(all[1]["test"].Int(), 108) + t.Assert(all[2]["test"].Int(), 109) + t.Assert(all[3]["test"].Int(), 110) + }) +} + +func Test_Model_Hook_Insert(t *testing.T) { + table := createTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + m := db.Model(table).Hook(gdb.HookHandler{ + Insert: func(ctx context.Context, in *gdb.HookInsertInput) (result sql.Result, err error) { + for i, item := range in.Data { + item["passport"] = fmt.Sprintf(`test_port_%d`, item["id"]) + item["nickname"] = fmt.Sprintf(`test_name_%d`, item["id"]) + item["password"] = fmt.Sprintf(`test_pass_%d`, item["id"]) + item["create_time"] = CreateTime + in.Data[i] = item + } + return in.Next(ctx) + }, + }) + _, err := m.Insert(g.Map{ + "id": 1, + "nickname": "name_1", + }) + t.AssertNil(err) + one, err := m.One() + t.AssertNil(err) + t.Assert(one["id"].Int(), 1) + t.Assert(one["passport"], `test_port_1`) + t.Assert(one["nickname"], `test_name_1`) + }) +} + +func Test_Model_Hook_Update(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + m := db.Model(table).Hook(gdb.HookHandler{ + Update: func(ctx context.Context, in *gdb.HookUpdateInput) (result sql.Result, err error) { + switch value := in.Data.(type) { + case gdb.List: + for i, data := range value { + data["passport"] = `port` + data["nickname"] = `name` + value[i] = data + } + in.Data = value + + case gdb.Map: + value["passport"] = `port` + value["nickname"] = `name` + in.Data = value + } + return in.Next(ctx) + }, + }) + _, err := m.Data(g.Map{ + "nickname": "name_1", + }).WherePri(1).Update() + t.AssertNil(err) + + one, err := m.One() + t.AssertNil(err) + t.Assert(one["id"].Int(), 1) + t.Assert(one["passport"], `port`) + t.Assert(one["nickname"], `name`) + }) +} + +func Test_Model_Hook_Delete(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + m := db.Model(table).Hook(gdb.HookHandler{ + Delete: func(ctx context.Context, in *gdb.HookDeleteInput) (result sql.Result, err error) { + return db.Model(table).Data(g.Map{ + "nickname": `deleted`, + }).Where(in.Condition).Update() + }, + }) + _, err := m.Where("1=1").Delete() + t.AssertNil(err) + + all, err := m.All() + t.AssertNil(err) + for _, item := range all { + t.Assert(item["nickname"].String(), `deleted`) + } + }) +} + +func Test_Model_Hook_Select_Count(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + m := db.Model(table).Hook(gdb.HookHandler{ + Select: func(ctx context.Context, in *gdb.HookSelectInput) (result gdb.Result, err error) { + result, err = in.Next(ctx) + if err != nil { + return + } + // Adding extra fields should not affect Count operations + for i, record := range result { + record["extra"] = gvar.New("extra_value") + result[i] = record + } + return + }, + }) + count, err := m.Count() + t.AssertNil(err) + t.Assert(count, TableSize) + }) +} + +func Test_Model_Hook_Chain(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + // Normal chain: two hooks both modify data + gtest.C(t, func(t *gtest.T) { + m := db.Model(table).Hook(gdb.HookHandler{ + Select: func(ctx context.Context, in *gdb.HookSelectInput) (result gdb.Result, err error) { + result, err = in.Next(ctx) + if err != nil { + return + } + for i, record := range result { + record["hook1"] = gvar.New("value1") + result[i] = record + } + return + }, + }).Hook(gdb.HookHandler{ + Select: func(ctx context.Context, in *gdb.HookSelectInput) (result gdb.Result, err error) { + result, err = in.Next(ctx) + if err != nil { + return + } + for i, record := range result { + record["hook2"] = gvar.New("value2") + result[i] = record + } + return + }, + }) + all, err := m.Where("id", 1).All() + t.AssertNil(err) + t.Assert(len(all), 1) + t.Assert(all[0]["id"].Int(), 1) + // The last Hook should take effect (Hook replaces previous one) + t.Assert(all[0]["hook2"].String(), "value2") + }) + + // Error chain: hook returns error + gtest.C(t, func(t *gtest.T) { + m := db.Model(table).Hook(gdb.HookHandler{ + Select: func(ctx context.Context, in *gdb.HookSelectInput) (result gdb.Result, err error) { + return nil, gerror.New("hook error") + }, + }) + _, err := m.Where("id", 1).All() + t.AssertNE(err, nil) + t.Assert(err.Error(), "hook error") + }) +} diff --git a/contrib/drivers/gaussdb/gaussdb_z_unit_model_test.go b/contrib/drivers/gaussdb/gaussdb_z_unit_model_test.go index 62b2b8ee6..810331a23 100644 --- a/contrib/drivers/gaussdb/gaussdb_z_unit_model_test.go +++ b/contrib/drivers/gaussdb/gaussdb_z_unit_model_test.go @@ -239,37 +239,6 @@ func Test_Model_Exist(t *testing.T) { }) } -func Test_Model_Where(t *testing.T) { - table := createInitTable() - defer dropTable(table) - - // map + slice parameter - gtest.C(t, func(t *gtest.T) { - result, err := db.Model(table).Where(g.Map{ - "id": g.Slice{1, 2, 3}, - "passport": g.Slice{"user_2", "user_3"}, - }).Where("id=? and nickname=?", g.Slice{3, "name_3"}).One() - t.AssertNil(err) - t.AssertGT(len(result), 0) - t.Assert(result["id"].Int(), 3) - }) - - // struct, automatic mapping and filtering. - gtest.C(t, func(t *gtest.T) { - type User struct { - Id int - Nickname string - } - result, err := db.Model(table).Where(User{3, "name_3"}).One() - t.AssertNil(err) - t.Assert(result["id"].Int(), 3) - - result, err = db.Model(table).Where(&User{3, "name_3"}).One() - t.AssertNil(err) - t.Assert(result["id"].Int(), 3) - }) -} - func Test_Model_Save(t *testing.T) { table := createTable() defer dropTable(table) diff --git a/contrib/drivers/gaussdb/gaussdb_z_unit_model_where_test.go b/contrib/drivers/gaussdb/gaussdb_z_unit_model_where_test.go new file mode 100644 index 000000000..8e14277fa --- /dev/null +++ b/contrib/drivers/gaussdb/gaussdb_z_unit_model_where_test.go @@ -0,0 +1,1213 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gaussdb_test + +import ( + "fmt" + "testing" + + "github.com/gogf/gf/v2/container/gmap" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/test/gtest" + "github.com/gogf/gf/v2/util/gutil" +) + +func Test_Model_Where(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + // string + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Where("id=? and nickname=?", 3, "name_3").One() + t.AssertNil(err) + t.AssertGT(len(result), 0) + t.Assert(result["id"].Int(), 3) + }) + + // slice + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Where(g.Slice{"id", 3}).One() + t.AssertNil(err) + t.AssertGT(len(result), 0) + t.Assert(result["id"].Int(), 3) + }) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Where(g.Slice{"id", 3, "nickname", "name_3"}).One() + t.AssertNil(err) + t.AssertGT(len(result), 0) + t.Assert(result["id"].Int(), 3) + }) + + // slice parameter + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Where("id=? and nickname=?", g.Slice{3, "name_3"}).One() + t.AssertNil(err) + t.AssertGT(len(result), 0) + t.Assert(result["id"].Int(), 3) + }) + // map like + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Where(g.Map{ + "passport like": "user_1%", + }).Order("id asc").All() + t.AssertNil(err) + t.Assert(len(result), 2) + t.Assert(result[0].GMap().Get("id"), 1) + t.Assert(result[1].GMap().Get("id"), 10) + }) + // map + slice parameter + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Where(g.Map{ + "id": g.Slice{1, 2, 3}, + "passport": g.Slice{"user_2", "user_3"}, + }).Where("id=? and nickname=?", g.Slice{3, "name_3"}).One() + t.AssertNil(err) + t.AssertGT(len(result), 0) + t.Assert(result["id"].Int(), 3) + }) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Where(g.Map{ + "id": g.Slice{1, 2, 3}, + "passport": g.Slice{"user_2", "user_3"}, + }).WhereOr("nickname=?", g.Slice{"name_4"}).Where("id", 3).One() + t.AssertNil(err) + t.AssertGT(len(result), 0) + t.Assert(result["id"].Int(), 2) + }) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Where("id=3", g.Slice{}).One() + t.AssertNil(err) + t.AssertGT(len(result), 0) + t.Assert(result["id"].Int(), 3) + }) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Where("id=?", g.Slice{3}).One() + t.AssertNil(err) + t.AssertGT(len(result), 0) + t.Assert(result["id"].Int(), 3) + }) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Where("id", 3).One() + t.AssertNil(err) + t.AssertGT(len(result), 0) + t.Assert(result["id"].Int(), 3) + }) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Where("id", 3).Where("nickname", "name_3").One() + t.AssertNil(err) + t.Assert(result["id"].Int(), 3) + }) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Where("id", 30).WhereOr("nickname", "name_3").One() + t.AssertNil(err) + t.Assert(result["id"].Int(), 3) + }) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Where("id", 30).WhereOr("nickname", "name_3").Where("id>?", 1).One() + t.AssertNil(err) + t.Assert(result["id"].Int(), 3) + }) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Where("id", 30).WhereOr("nickname", "name_3").Where("id>", 1).One() + t.AssertNil(err) + t.Assert(result["id"].Int(), 3) + }) + // slice + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Where("id=? AND nickname=?", g.Slice{3, "name_3"}...).One() + t.AssertNil(err) + t.Assert(result["id"].Int(), 3) + }) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Where("id=? AND nickname=?", g.Slice{3, "name_3"}).One() + t.AssertNil(err) + t.Assert(result["id"].Int(), 3) + }) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Where("passport like ? and nickname like ?", g.Slice{"user_3", "name_3"}).One() + t.AssertNil(err) + t.Assert(result["id"].Int(), 3) + }) + // map + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Where(g.Map{"id": 3, "nickname": "name_3"}).One() + t.AssertNil(err) + t.Assert(result["id"].Int(), 3) + }) + // map key operator + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Where(g.Map{"id>": 1, "id<": 3}).One() + t.AssertNil(err) + t.Assert(result["id"].Int(), 2) + }) + + // gmap.Map + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Where(gmap.NewFrom(g.MapAnyAny{"id": 3, "nickname": "name_3"})).One() + t.AssertNil(err) + t.Assert(result["id"].Int(), 3) + }) + // gmap.Map key operator + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Where(gmap.NewFrom(g.MapAnyAny{"id>": 1, "id<": 3})).One() + t.AssertNil(err) + t.Assert(result["id"].Int(), 2) + }) + + // list map + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Where(gmap.NewListMapFrom(g.MapAnyAny{"id": 3, "nickname": "name_3"})).One() + t.AssertNil(err) + t.Assert(result["id"].Int(), 3) + }) + // list map key operator + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Where(gmap.NewListMapFrom(g.MapAnyAny{"id>": 1, "id<": 3})).One() + t.AssertNil(err) + t.Assert(result["id"].Int(), 2) + }) + + // tree map + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Where(gmap.NewTreeMapFrom(gutil.ComparatorString, g.MapAnyAny{"id": 3, "nickname": "name_3"})).One() + t.AssertNil(err) + t.Assert(result["id"].Int(), 3) + }) + // tree map key operator + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Where(gmap.NewTreeMapFrom(gutil.ComparatorString, g.MapAnyAny{"id>": 1, "id<": 3})).One() + t.AssertNil(err) + t.Assert(result["id"].Int(), 2) + }) + + // complicated where 1 + gtest.C(t, func(t *gtest.T) { + conditions := g.Map{ + "nickname like ?": "%name%", + "id between ? and ?": g.Slice{1, 3}, + "id > 0": nil, + "create_time IS NOT NULL": nil, + "id": g.Slice{1, 2, 3}, + } + result, err := db.Model(table).Where(conditions).Order("id asc").All() + t.AssertNil(err) + t.Assert(len(result), 3) + t.Assert(result[0]["id"].Int(), 1) + }) + // complicated where 2 + gtest.C(t, func(t *gtest.T) { + conditions := g.Map{ + "nickname like ?": "%name%", + "id between ? and ?": g.Slice{1, 3}, + "id >= ?": 1, + "create_time > ?": "1970-01-01", + "id in(?)": g.Slice{1, 2, 3}, + } + result, err := db.Model(table).Where(conditions).Order("id asc").All() + t.AssertNil(err) + t.Assert(len(result), 3) + t.Assert(result[0]["id"].Int(), 1) + }) + // struct, automatic mapping and filtering. + gtest.C(t, func(t *gtest.T) { + type User struct { + Id int + Nickname string + } + result, err := db.Model(table).Where(User{3, "name_3"}).One() + t.AssertNil(err) + t.Assert(result["id"].Int(), 3) + + result, err = db.Model(table).Where(&User{3, "name_3"}).One() + t.AssertNil(err) + t.Assert(result["id"].Int(), 3) + }) + // slice single + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Where("id IN(?)", g.Slice{1, 3}).Order("id ASC").All() + t.AssertNil(err) + t.Assert(len(result), 2) + t.Assert(result[0]["id"].Int(), 1) + t.Assert(result[1]["id"].Int(), 3) + }) + // slice + string + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Where("nickname=? AND id IN(?)", "name_3", g.Slice{1, 3}).Order("id ASC").All() + t.AssertNil(err) + t.Assert(len(result), 1) + t.Assert(result[0]["id"].Int(), 3) + }) + // slice + map + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Where(g.Map{ + "id": g.Slice{1, 3}, + "nickname": "name_3", + }).Order("id ASC").All() + t.AssertNil(err) + t.Assert(len(result), 1) + t.Assert(result[0]["id"].Int(), 3) + }) + // slice + struct + gtest.C(t, func(t *gtest.T) { + type User struct { + Ids []int `json:"id"` + Nickname string `gconv:"nickname"` + } + result, err := db.Model(table).Where(User{ + Ids: []int{1, 3}, + Nickname: "name_3", + }).Order("id ASC").All() + t.AssertNil(err) + t.Assert(len(result), 1) + t.Assert(result[0]["id"].Int(), 3) + }) +} + +func Test_Model_WherePri(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + // primary key + gtest.C(t, func(t *gtest.T) { + one, err := db.Model(table).WherePri(3).One() + t.AssertNil(err) + t.AssertNE(one, nil) + t.Assert(one["id"].Int(), 3) + }) + gtest.C(t, func(t *gtest.T) { + all, err := db.Model(table).WherePri(g.Slice{3, 9}).Order("id asc").All() + t.AssertNil(err) + t.Assert(len(all), 2) + t.Assert(all[0]["id"].Int(), 3) + t.Assert(all[1]["id"].Int(), 9) + }) + + // string + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WherePri("id=? and nickname=?", 3, "name_3").One() + t.AssertNil(err) + t.AssertGT(len(result), 0) + t.Assert(result["id"].Int(), 3) + }) + // slice parameter + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WherePri("id=? and nickname=?", g.Slice{3, "name_3"}).One() + t.AssertNil(err) + t.AssertGT(len(result), 0) + t.Assert(result["id"].Int(), 3) + }) + // map like + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WherePri(g.Map{ + "passport like": "user_1%", + }).Order("id asc").All() + t.AssertNil(err) + t.Assert(len(result), 2) + t.Assert(result[0].GMap().Get("id"), 1) + t.Assert(result[1].GMap().Get("id"), 10) + }) + // map + slice parameter + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WherePri(g.Map{ + "id": g.Slice{1, 2, 3}, + "passport": g.Slice{"user_2", "user_3"}, + }).Where("id=? and nickname=?", g.Slice{3, "name_3"}).One() + t.AssertNil(err) + t.AssertGT(len(result), 0) + t.Assert(result["id"].Int(), 3) + }) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WherePri(g.Map{ + "id": g.Slice{1, 2, 3}, + "passport": g.Slice{"user_2", "user_3"}, + }).WhereOr("nickname=?", g.Slice{"name_4"}).Where("id", 3).One() + t.AssertNil(err) + t.AssertGT(len(result), 0) + t.Assert(result["id"].Int(), 2) + }) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WherePri("id=3", g.Slice{}).One() + t.AssertNil(err) + t.AssertGT(len(result), 0) + t.Assert(result["id"].Int(), 3) + }) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WherePri("id=?", g.Slice{3}).One() + t.AssertNil(err) + t.AssertGT(len(result), 0) + t.Assert(result["id"].Int(), 3) + }) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WherePri("id", 3).One() + t.AssertNil(err) + t.AssertGT(len(result), 0) + t.Assert(result["id"].Int(), 3) + }) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WherePri("id", 3).WherePri("nickname", "name_3").One() + t.AssertNil(err) + t.Assert(result["id"].Int(), 3) + }) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WherePri("id", 3).Where("nickname", "name_3").One() + t.AssertNil(err) + t.Assert(result["id"].Int(), 3) + }) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WherePri("id", 30).WhereOr("nickname", "name_3").One() + t.AssertNil(err) + t.Assert(result["id"].Int(), 3) + }) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WherePri("id", 30).WhereOr("nickname", "name_3").Where("id>?", 1).One() + t.AssertNil(err) + t.Assert(result["id"].Int(), 3) + }) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WherePri("id", 30).WhereOr("nickname", "name_3").Where("id>", 1).One() + t.AssertNil(err) + t.Assert(result["id"].Int(), 3) + }) + // slice + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WherePri("id=? AND nickname=?", g.Slice{3, "name_3"}...).One() + t.AssertNil(err) + t.Assert(result["id"].Int(), 3) + }) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WherePri("id=? AND nickname=?", g.Slice{3, "name_3"}).One() + t.AssertNil(err) + t.Assert(result["id"].Int(), 3) + }) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WherePri("passport like ? and nickname like ?", g.Slice{"user_3", "name_3"}).One() + t.AssertNil(err) + t.Assert(result["id"].Int(), 3) + }) + // map + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WherePri(g.Map{"id": 3, "nickname": "name_3"}).One() + t.AssertNil(err) + t.Assert(result["id"].Int(), 3) + }) + // map key operator + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WherePri(g.Map{"id>": 1, "id<": 3}).One() + t.AssertNil(err) + t.Assert(result["id"].Int(), 2) + }) + + // gmap.Map + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WherePri(gmap.NewFrom(g.MapAnyAny{"id": 3, "nickname": "name_3"})).One() + t.AssertNil(err) + t.Assert(result["id"].Int(), 3) + }) + // gmap.Map key operator + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WherePri(gmap.NewFrom(g.MapAnyAny{"id>": 1, "id<": 3})).One() + t.AssertNil(err) + t.Assert(result["id"].Int(), 2) + }) + + // list map + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WherePri(gmap.NewListMapFrom(g.MapAnyAny{"id": 3, "nickname": "name_3"})).One() + t.AssertNil(err) + t.Assert(result["id"].Int(), 3) + }) + // list map key operator + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WherePri(gmap.NewListMapFrom(g.MapAnyAny{"id>": 1, "id<": 3})).One() + t.AssertNil(err) + t.Assert(result["id"].Int(), 2) + }) + + // tree map + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WherePri(gmap.NewTreeMapFrom(gutil.ComparatorString, g.MapAnyAny{"id": 3, "nickname": "name_3"})).One() + t.AssertNil(err) + t.Assert(result["id"].Int(), 3) + }) + // tree map key operator + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WherePri(gmap.NewTreeMapFrom(gutil.ComparatorString, g.MapAnyAny{"id>": 1, "id<": 3})).One() + t.AssertNil(err) + t.Assert(result["id"].Int(), 2) + }) + + // complicated where 1 + gtest.C(t, func(t *gtest.T) { + conditions := g.Map{ + "nickname like ?": "%name%", + "id between ? and ?": g.Slice{1, 3}, + "id > 0": nil, + "create_time IS NOT NULL": nil, + "id": g.Slice{1, 2, 3}, + } + result, err := db.Model(table).WherePri(conditions).Order("id asc").All() + t.AssertNil(err) + t.Assert(len(result), 3) + t.Assert(result[0]["id"].Int(), 1) + }) + // complicated where 2 + gtest.C(t, func(t *gtest.T) { + conditions := g.Map{ + "nickname like ?": "%name%", + "id between ? and ?": g.Slice{1, 3}, + "id >= ?": 1, + "create_time > ?": "1970-01-01", + "id in(?)": g.Slice{1, 2, 3}, + } + result, err := db.Model(table).WherePri(conditions).Order("id asc").All() + t.AssertNil(err) + t.Assert(len(result), 3) + t.Assert(result[0]["id"].Int(), 1) + }) + // struct + gtest.C(t, func(t *gtest.T) { + type User struct { + Id int `json:"id"` + Nickname string `gconv:"nickname"` + } + result, err := db.Model(table).WherePri(User{3, "name_3"}).One() + t.AssertNil(err) + t.Assert(result["id"].Int(), 3) + + result, err = db.Model(table).WherePri(&User{3, "name_3"}).One() + t.AssertNil(err) + t.Assert(result["id"].Int(), 3) + }) + // slice single + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WherePri("id IN(?)", g.Slice{1, 3}).Order("id ASC").All() + t.AssertNil(err) + t.Assert(len(result), 2) + t.Assert(result[0]["id"].Int(), 1) + t.Assert(result[1]["id"].Int(), 3) + }) + // slice + string + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WherePri("nickname=? AND id IN(?)", "name_3", g.Slice{1, 3}).Order("id ASC").All() + t.AssertNil(err) + t.Assert(len(result), 1) + t.Assert(result[0]["id"].Int(), 3) + }) + // slice + map + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WherePri(g.Map{ + "id": g.Slice{1, 3}, + "nickname": "name_3", + }).Order("id ASC").All() + t.AssertNil(err) + t.Assert(len(result), 1) + t.Assert(result[0]["id"].Int(), 3) + }) + // slice + struct + gtest.C(t, func(t *gtest.T) { + type User struct { + Ids []int `json:"id"` + Nickname string `gconv:"nickname"` + } + result, err := db.Model(table).WherePri(User{ + Ids: []int{1, 3}, + Nickname: "name_3", + }).Order("id ASC").All() + t.AssertNil(err) + t.Assert(len(result), 1) + t.Assert(result[0]["id"].Int(), 3) + }) +} + +func Test_Model_Where_OmitEmpty(t *testing.T) { + table := createInitTable() + defer dropTable(table) + gtest.C(t, func(t *gtest.T) { + conditions := g.Map{ + "id < 4": "", + } + result, err := db.Model(table).Where(conditions).Order("id desc").All() + t.AssertNil(err) + t.Assert(len(result), 3) + t.Assert(result[0]["id"].Int(), 3) + }) + gtest.C(t, func(t *gtest.T) { + conditions := g.Map{ + "id < 4": "", + } + result, err := db.Model(table).Where(conditions).OmitEmpty().Order("id desc").All() + t.AssertNil(err) + t.Assert(len(result), 10) + t.Assert(result[0]["id"].Int(), 10) + }) +} + +func Test_Model_WhereLT(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereLT("id", 3).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 2) + t.Assert(result[0]["id"], 1) + }) +} + +func Test_Model_WhereLTE(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereLTE("id", 3).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 3) + t.Assert(result[0]["id"], 1) + }) +} + +func Test_Model_WhereGT(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereGT("id", 8).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 2) + t.Assert(result[0]["id"], 9) + }) +} + +func Test_Model_WhereGTE(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereGTE("id", 8).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 3) + t.Assert(result[0]["id"], 8) + }) +} + +func Test_Model_WhereOrLT(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereLT("id", 3).WhereOrLT("id", 4).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 3) + t.Assert(result[0]["id"], 1) + t.Assert(result[2]["id"], 3) + }) +} + +func Test_Model_WhereOrLTE(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereLTE("id", 3).WhereOrLTE("id", 4).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 4) + t.Assert(result[0]["id"], 1) + t.Assert(result[3]["id"], 4) + }) +} + +func Test_Model_WhereOrGT(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereGT("id", 8).WhereOrGT("id", 7).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 3) + t.Assert(result[0]["id"], 8) + }) +} + +func Test_Model_WhereOrGTE(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereGTE("id", 8).WhereOrGTE("id", 7).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 4) + t.Assert(result[0]["id"], 7) + }) +} + +func Test_Model_WhereIn(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereIn("id", g.Slice{1, 2, 3, 4}).WhereIn("id", g.Slice{3, 4, 5}).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 2) + t.Assert(result[0]["id"], 3) + t.Assert(result[1]["id"], 4) + }) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereIn("id", g.Slice{}).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 0) + }) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).OmitEmptyWhere().WhereIn("id", g.Slice{}).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), TableSize) + }) +} + +func Test_Model_WhereNotIn(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereNotIn("id", g.Slice{1, 2, 3, 4}).WhereNotIn("id", g.Slice{3, 4, 5}).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 5) + t.Assert(result[0]["id"], 6) + t.Assert(result[1]["id"], 7) + }) +} + +func Test_Model_WhereOrIn(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereOrIn("id", g.Slice{1, 2, 3, 4}).WhereOrIn("id", g.Slice{3, 4, 5}).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 5) + t.Assert(result[0]["id"], 1) + t.Assert(result[4]["id"], 5) + }) +} + +func Test_Model_WhereOrNotIn(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereOrNotIn("id", g.Slice{1, 2, 3, 4}).WhereOrNotIn("id", g.Slice{3, 4, 5}).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 8) + t.Assert(result[0]["id"], 1) + t.Assert(result[4]["id"], 7) + }) +} + +func Test_Model_WhereBetween(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereBetween("id", 1, 4).WhereBetween("id", 3, 5).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 2) + t.Assert(result[0]["id"], 3) + t.Assert(result[1]["id"], 4) + }) +} + +func Test_Model_WhereNotBetween(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereNotBetween("id", 2, 8).WhereNotBetween("id", 3, 100).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 1) + t.Assert(result[0]["id"], 1) + }) +} + +func Test_Model_WhereOrBetween(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereOrBetween("id", 1, 4).WhereOrBetween("id", 3, 5).OrderDesc("id").All() + t.AssertNil(err) + t.Assert(len(result), 5) + t.Assert(result[0]["id"], 5) + t.Assert(result[4]["id"], 1) + }) +} + +func Test_Model_WhereOrNotBetween(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereOrNotBetween("id", 1, 4).WhereOrNotBetween("id", 3, 5).OrderDesc("id").All() + t.AssertNil(err) + t.Assert(len(result), 8) + t.Assert(result[0]["id"], 10) + t.Assert(result[4]["id"], 6) + }) +} + +func Test_Model_WhereLike(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereLike("nickname", "name%").OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), TableSize) + t.Assert(result[0]["id"], 1) + t.Assert(result[TableSize-1]["id"], TableSize) + }) +} + +func Test_Model_WhereNotLike(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereNotLike("nickname", "name%").OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 0) + }) +} + +func Test_Model_WhereOrLike(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereOrLike("nickname", "namexxx%").WhereOrLike("nickname", "name%").OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), TableSize) + t.Assert(result[0]["id"], 1) + t.Assert(result[TableSize-1]["id"], TableSize) + }) +} + +func Test_Model_WhereOrNotLike(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereOrNotLike("nickname", "namexxx%").WhereOrNotLike("nickname", "name%").OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), TableSize) + t.Assert(result[0]["id"], 1) + t.Assert(result[TableSize-1]["id"], TableSize) + }) +} + +func Test_Model_WhereNull(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereNull("nickname").WhereNull("passport").OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 0) + }) +} + +func Test_Model_WhereNotNull(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereNotNull("nickname").WhereNotNull("passport").OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), TableSize) + t.Assert(result[0]["id"], 1) + t.Assert(result[TableSize-1]["id"], TableSize) + }) +} + +func Test_Model_WhereOrNull(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereOrNull("nickname").WhereOrNull("passport").OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 0) + }) +} + +func Test_Model_WhereOrNotNull(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).WhereOrNotNull("nickname").WhereOrNotNull("passport").OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), TableSize) + t.Assert(result[0]["id"], 1) + t.Assert(result[TableSize-1]["id"], TableSize) + }) +} + +func Test_Model_Where_MultiSliceArguments(t *testing.T) { + table := createInitTable() + defer dropTable(table) + gtest.C(t, func(t *gtest.T) { + r, err := db.Model(table).Where(g.Map{ + "id": g.Slice{1, 2, 3, 4}, + "passport": g.Slice{"user_2", "user_3", "user_4"}, + "nickname": g.Slice{"name_2", "name_4"}, + "id >= 4": nil, + }).All() + t.AssertNil(err) + t.Assert(len(r), 1) + t.Assert(r[0]["id"], 4) + }) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Where(g.Map{ + "id": g.Slice{1, 2, 3}, + "passport": g.Slice{"user_2", "user_3"}, + }).WhereOr("nickname=?", g.Slice{"name_4"}).Where("id", 3).One() + t.AssertNil(err) + t.AssertGT(len(result), 0) + t.Assert(result["id"].Int(), 2) + }) +} + +func Test_Model_Where_ISNULL(t *testing.T) { + // Create a custom table with nullable nickname column for this test + table := fmt.Sprintf(`%s_%d`, TablePrefix+"nullable", gtime.TimestampNano()) + if _, err := db.Exec(ctx, fmt.Sprintf(` + CREATE TABLE %s ( + id bigserial NOT NULL, + passport varchar(45), + password varchar(32), + nickname varchar(45), + create_time timestamp, + PRIMARY KEY (id) + ) ;`, table, + )); err != nil { + gtest.Fatal(err) + } + defer dropTable(table) + + // Insert test data + for i := 1; i <= TableSize; i++ { + if _, err := db.Insert(ctx, table, g.Map{ + "id": i, + "passport": fmt.Sprintf(`user_%d`, i), + "password": fmt.Sprintf(`pass_%d`, i), + "nickname": fmt.Sprintf(`name_%d`, i), + "create_time": gtime.NewFromStr(CreateTime).String(), + }); err != nil { + gtest.Fatal(err) + } + } + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Data("nickname", nil).Where("id", 2).Update() + t.AssertNil(err) + n, _ := result.RowsAffected() + t.Assert(n, 1) + + one, err := db.Model(table).Where("nickname", nil).One() + t.AssertNil(err) + t.Assert(one.IsEmpty(), false) + t.Assert(one["id"], 2) + }) +} + +func Test_Model_Where_GTime(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Where("create_time>?", gtime.NewFromStr("2010-09-01")).All() + t.AssertNil(err) + t.Assert(len(result), 10) + }) + gtest.C(t, func(t *gtest.T) { + result, err := db.Model(table).Where("create_time>?", *gtime.NewFromStr("2010-09-01")).All() + t.AssertNil(err) + t.Assert(len(result), 10) + }) +} + +func Test_Model_WhereExists(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + // Create another table for exists subquery + table2 := "table2_" + gtime.TimestampNanoStr() + sqlCreate := fmt.Sprintf(` +CREATE TABLE %s ( + id bigserial NOT NULL, + uid int NOT NULL DEFAULT 0, + PRIMARY KEY (id) +);`, table2) + if _, err := db.Exec(ctx, sqlCreate); err != nil { + t.AssertNil(err) + } + defer dropTable(table2) + + // Insert test data + _, err := db.Model(table2).Insert(g.List{ + {"uid": 1}, + {"uid": 2}, + }) + t.AssertNil(err) + + // Test WhereExists with subquery + subQuery1 := db.Model(table2). + Fields("id"). + Where("uid = ?", db.Raw("\"user\".id")) + + r, err := db.Model(table + " as \"user\""). + WhereExists(subQuery1). + Order("id asc"). + All() + t.AssertNil(err) + t.Assert(len(r), 2) + t.Assert(r[0]["id"].Int(), 1) + t.Assert(r[1]["id"].Int(), 2) + + // Test WhereNotExists + r, err = db.Model(table + " as \"user\""). + WhereNotExists(subQuery1). + Order("id asc"). + All() + t.AssertNil(err) + t.Assert(len(r), 8) + t.Assert(r[0]["id"].Int(), 3) + + // Test WhereExists with empty result + subQuery2 := db.Model(table2). + Fields("id"). + Where("uid = -1") + r, err = db.Model(table). + WhereExists(subQuery2). + All() + t.AssertNil(err) + t.Assert(len(r), 0) + + // Test WhereNotExists with all results + r, err = db.Model(table). + WhereNotExists(subQuery2). + All() + t.AssertNil(err) + t.Assert(len(r), 10) + + // Test combination of Where and WhereExists + r, err = db.Model(table+" as \"user\""). + Where("id>?", 3). + WhereExists(subQuery1). + Order("id asc"). + All() + t.AssertNil(err) + t.Assert(len(r), 0) + + // Test WhereExists with complex subquery + subQuery3 := db.Model(table2). + Fields("id"). + Where("uid = ?", db.Raw("\"user\".id")). + Where("id > ?", 0) + r, err = db.Model(table + " as \"user\""). + WhereExists(subQuery3). + Order("id asc"). + All() + t.AssertNil(err) + t.Assert(len(r), 2) + t.Assert(r[0]["id"].Int(), 1) + t.Assert(r[1]["id"].Int(), 2) + + // Test WhereExists with Fields + r, err = db.Model(table + " as \"user\""). + Fields("id,passport"). + WhereExists(subQuery1). + Order("id asc"). + All() + t.AssertNil(err) + t.Assert(len(r), 2) + t.Assert(r[0]["id"].Int(), 1) + t.Assert(r[0]["passport"].String(), "user_1") + + // Test WhereExists with Group + r, err = db.Model(table + " as \"user\""). + WhereExists(subQuery1). + Group("id"). + Order("id asc"). + All() + t.AssertNil(err) + t.Assert(len(r), 2) + t.Assert(r[0]["id"].Int(), 1) + t.Assert(r[1]["id"].Int(), 2) + + // Test WhereExists with Having + r, err = db.Model(table+" as \"user\""). + WhereExists(subQuery1). + Group("id"). + Having("id > ?", 1). + Order("id asc"). + All() + t.AssertNil(err) + t.Assert(len(r), 1) + t.Assert(r[0]["id"].Int(), 2) + }) +} + +func Test_Model_WhereNotExists(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + // Create another table for exists subquery + table2 := "table2_" + gtime.TimestampNanoStr() + sqlCreate := fmt.Sprintf(` +CREATE TABLE %s ( + id bigserial NOT NULL, + uid int NOT NULL DEFAULT 0, + PRIMARY KEY (id) +);`, table2) + if _, err := db.Exec(ctx, sqlCreate); err != nil { + t.AssertNil(err) + } + defer dropTable(table2) + + // Insert test data + _, err := db.Model(table2).Insert(g.List{ + {"uid": 1}, + {"uid": 2}, + }) + t.AssertNil(err) + + // Test WhereNotExists with subquery + subQuery1 := db.Model(table2). + Fields("id"). + Where("uid = ?", db.Raw("\"user\".id")) + + r, err := db.Model(table + " as \"user\""). + WhereNotExists(subQuery1). + Order("id asc"). + All() + t.AssertNil(err) + t.Assert(len(r), 8) + t.Assert(r[0]["id"].Int(), 3) + t.Assert(r[1]["id"].Int(), 4) + + // Test WhereNotExists with empty subquery + subQuery2 := db.Model(table2). + Fields("id"). + Where("uid = -1") + r, err = db.Model(table + " as \"user\""). + WhereNotExists(subQuery2). + Order("id asc"). + All() + t.AssertNil(err) + t.Assert(len(r), 10) + + // Test WhereNotExists with complex condition + subQuery3 := db.Model(table2). + Fields("id"). + Where("uid = ?", db.Raw("\"user\".id")). + Where("id > ?", 1) + r, err = db.Model(table + " as \"user\""). + WhereNotExists(subQuery3). + Order("id asc"). + All() + t.AssertNil(err) + t.Assert(len(r), 9) + t.Assert(r[0]["id"].Int(), 1) + }) +} + +func Test_Model_WherePrefix(t *testing.T) { + var ( + table1 = "table1_" + gtime.TimestampNanoStr() + table2 = "table2_" + gtime.TimestampNanoStr() + ) + createInitTable(table1) + defer dropTable(table1) + createInitTable(table2) + defer dropTable(table2) + + gtest.C(t, func(t *gtest.T) { + r, err := db.Model(table1). + FieldsPrefix(table1, "*"). + LeftJoinOnField(table2, "id"). + WherePrefix(table2, g.Map{ + "id": g.Slice{1, 2}, + }). + Order("id asc").All() + t.AssertNil(err) + t.Assert(len(r), 2) + t.Assert(r[0]["id"], "1") + t.Assert(r[1]["id"], "2") + }) +} + +func Test_Model_WhereOrPrefix(t *testing.T) { + var ( + table1 = "table1_" + gtime.TimestampNanoStr() + table2 = "table2_" + gtime.TimestampNanoStr() + ) + createInitTable(table1) + defer dropTable(table1) + createInitTable(table2) + defer dropTable(table2) + + gtest.C(t, func(t *gtest.T) { + r, err := db.Model(table1). + FieldsPrefix(table1, "*"). + LeftJoinOnField(table2, "id"). + WhereOrPrefix(table1, g.Map{ + "id": g.Slice{1, 2}, + }). + WhereOrPrefix(table2, g.Map{ + "id": g.Slice{8, 9}, + }). + Order("id asc").All() + t.AssertNil(err) + t.Assert(len(r), 4) + t.Assert(r[0]["id"], "1") + t.Assert(r[1]["id"], "2") + t.Assert(r[2]["id"], "8") + t.Assert(r[3]["id"], "9") + }) +} + +func Test_Model_WherePrefixLike(t *testing.T) { + var ( + table1 = "table1_" + gtime.TimestampNanoStr() + table2 = "table2_" + gtime.TimestampNanoStr() + ) + createInitTable(table1) + defer dropTable(table1) + createInitTable(table2) + defer dropTable(table2) + + gtest.C(t, func(t *gtest.T) { + r, err := db.Model(table1). + FieldsPrefix(table1, "*"). + LeftJoinOnField(table2, "id"). + WherePrefix(table1, g.Map{ + "id": g.Slice{1, 2, 3}, + }). + WherePrefix(table2, g.Map{ + "id": g.Slice{3, 4, 5}, + }). + WherePrefixLike(table2, "nickname", "name%"). + Order("id asc").All() + t.AssertNil(err) + t.Assert(len(r), 1) + t.Assert(r[0]["id"], "3") + }) +} diff --git a/contrib/drivers/gaussdb/gaussdb_z_unit_transaction_test.go b/contrib/drivers/gaussdb/gaussdb_z_unit_transaction_test.go new file mode 100644 index 000000000..f18e896c3 --- /dev/null +++ b/contrib/drivers/gaussdb/gaussdb_z_unit_transaction_test.go @@ -0,0 +1,1693 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gaussdb_test + +import ( + "context" + "database/sql" + "fmt" + "testing" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/test/gtest" +) + +func Test_TX_Query(t *testing.T) { + // Test successful queries + gtest.C(t, func(t *gtest.T) { + tx, err := db.Begin(ctx) + t.AssertNil(err) + + _, err = tx.Query("SELECT $1::int", 1) + t.AssertNil(err) + + _, err = tx.Query("SELECT $1::int+$2::int", 1, 2) + t.AssertNil(err) + + _, err = tx.Query("SELECT $1::int+$2::int", g.Slice{1, 2}) + t.AssertNil(err) + + err = tx.Commit() + t.AssertNil(err) + }) + + // Test error query - in GaussDB, once a statement fails, + // the transaction is aborted and must be rolled back + gtest.C(t, func(t *gtest.T) { + tx, err := db.Begin(ctx) + t.AssertNil(err) + + _, err = tx.Query("ERROR") + t.AssertNE(err, nil) + + err = tx.Rollback() + t.AssertNil(err) + }) +} + +func Test_TX_Exec(t *testing.T) { + // Test successful exec operations + gtest.C(t, func(t *gtest.T) { + tx, err := db.Begin(ctx) + t.AssertNil(err) + + _, err = tx.Exec("SELECT $1::int", 1) + t.AssertNil(err) + + _, err = tx.Exec("SELECT $1::int+$2::int", 1, 2) + t.AssertNil(err) + + _, err = tx.Exec("SELECT $1::int+$2::int", g.Slice{1, 2}) + t.AssertNil(err) + + err = tx.Commit() + t.AssertNil(err) + }) + + // Test error exec - in GaussDB, once a statement fails, + // the transaction is aborted and must be rolled back + gtest.C(t, func(t *gtest.T) { + tx, err := db.Begin(ctx) + t.AssertNil(err) + + _, err = tx.Exec("ERROR") + t.AssertNE(err, nil) + + err = tx.Rollback() + t.AssertNil(err) + }) +} + +func Test_TX_Commit(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + tx, err := db.Begin(ctx) + t.AssertNil(err) + + err = tx.Commit() + t.AssertNil(err) + }) +} + +func Test_TX_Rollback(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + tx, err := db.Begin(ctx) + t.AssertNil(err) + + err = tx.Rollback() + t.AssertNil(err) + }) +} + +func Test_TX_Prepare(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + tx, err := db.Begin(ctx) + t.AssertNil(err) + + st, err := tx.Prepare("SELECT 100") + t.AssertNil(err) + + rows, err := st.Query() + t.AssertNil(err) + + array, err := rows.Columns() + t.AssertNil(err) + t.Assert(array[0], "?column?") + + err = rows.Close() + t.AssertNil(err) + + err = tx.Commit() + t.AssertNil(err) + }) +} + +func Test_TX_IsClosed(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + tx, err := db.Begin(ctx) + t.AssertNil(err) + t.Assert(tx.IsClosed(), false) + + err = tx.Commit() + t.AssertNil(err) + t.Assert(tx.IsClosed(), true) + }) + + gtest.C(t, func(t *gtest.T) { + tx, err := db.Begin(ctx) + t.AssertNil(err) + t.Assert(tx.IsClosed(), false) + + err = tx.Rollback() + t.AssertNil(err) + t.Assert(tx.IsClosed(), true) + }) +} + +func Test_TX_Insert(t *testing.T) { + table := createTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + tx, err := db.Begin(ctx) + t.AssertNil(err) + + user := tx.Model(table) + + _, err = user.Data(g.Map{ + "id": 1, + "passport": "t1", + "password": "25d55ad283aa400af464c76d713c07ad", + "nickname": "T1", + "create_time": gtime.Now().String(), + }).Insert() + t.AssertNil(err) + + _, err = tx.Insert(table, g.Map{ + "id": 2, + "passport": "t1", + "password": "25d55ad283aa400af464c76d713c07ad", + "nickname": "T1", + "create_time": gtime.Now().String(), + }) + t.AssertNil(err) + + n, err := tx.Model(table).Count() + t.AssertNil(err) + t.Assert(n, int64(2)) + + err = tx.Commit() + t.AssertNil(err) + }) +} + +func Test_TX_BatchInsert(t *testing.T) { + table := createTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + tx, err := db.Begin(ctx) + t.AssertNil(err) + + _, err = tx.Insert(table, g.List{ + { + "id": 2, + "passport": "t", + "password": "25d55ad283aa400af464c76d713c07ad", + "nickname": "T2", + "create_time": gtime.Now().String(), + }, + { + "id": 3, + "passport": "t3", + "password": "25d55ad283aa400af464c76d713c07ad", + "nickname": "T3", + "create_time": gtime.Now().String(), + }, + }, 10) + t.AssertNil(err) + + err = tx.Commit() + t.AssertNil(err) + + n, err := db.Model(table).Count() + t.AssertNil(err) + t.Assert(n, int64(2)) + }) +} + +func Test_TX_Update(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + tx, err := db.Begin(ctx) + t.AssertNil(err) + + result, err := tx.Update(table, "create_time='2019-10-24 10:00:00'", "id=3") + t.AssertNil(err) + + n, _ := result.RowsAffected() + t.Assert(n, 1) + + err = tx.Commit() + t.AssertNil(err) + + _, err = tx.Model(table).Fields("create_time").Where("id", 3).Value() + t.AssertNE(err, nil) + + value, err := db.Model(table).Fields("create_time").Where("id", 3).Value() + t.AssertNil(err) + t.Assert(value.String(), "2019-10-24 10:00:00") + }) +} + +func Test_TX_Delete_Commit(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + tx, err := db.Begin(ctx) + t.AssertNil(err) + + _, err = tx.Model(table).Where("id", 1).Delete() + t.AssertNil(err) + + err = tx.Commit() + t.AssertNil(err) + + n, err := db.Model(table).Count() + t.AssertNil(err) + t.Assert(n, int64(TableSize-1)) + t.Assert(tx.IsClosed(), true) + }) +} + +func Test_TX_Delete_Rollback(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + tx, err := db.Begin(ctx) + t.AssertNil(err) + + _, err = tx.Model(table).Where("id", 1).Delete() + t.AssertNil(err) + + n, err := tx.Model(table).Count() + t.AssertNil(err) + t.Assert(n, int64(TableSize-1)) + + err = tx.Rollback() + t.AssertNil(err) + + n, err = db.Model(table).Count() + t.AssertNil(err) + t.Assert(n, int64(TableSize)) + t.AssertNE(n, int64(0)) + t.Assert(tx.IsClosed(), true) + }) +} + +func Test_TX_Save(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + tx, err := db.Begin(ctx) + t.AssertNil(err) + + _, err = tx.Model(table).Data(g.Map{ + "id": 1, + "passport": "USER_1", + "password": "PASS_1", + "nickname": "NAME_1", + "create_time": gtime.Now().String(), + }).OnConflict("id").Save() + t.AssertNil(err) + + err = tx.Commit() + t.AssertNil(err) + + value, err := db.Model(table).Fields("nickname").Where("id", 1).Value() + t.AssertNil(err) + t.Assert(value.String(), "NAME_1") + }) +} + +func Test_TX_BatchSave(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + tx, err := db.Begin(ctx) + t.AssertNil(err) + + _, err = tx.Model(table).Data(g.List{ + { + "id": 4, + "passport": "USER_4", + "password": "PASS_4", + "nickname": "NAME_4", + "create_time": gtime.Now().String(), + }, + }).OnConflict("id").Save() + t.AssertNil(err) + + err = tx.Commit() + t.AssertNil(err) + + n, err := db.Model(table).Count() + t.AssertNil(err) + t.Assert(n, int64(TableSize)) + + value, err := db.Model(table).Fields("password").Where("id", 4).Value() + t.AssertNil(err) + t.Assert(value.String(), "PASS_4") + }) +} + +func Test_TX_GetAll(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + tx, err := db.Begin(ctx) + t.AssertNil(err) + + result, err := tx.GetAll(fmt.Sprintf("SELECT * FROM %s WHERE id=$1", table), 1) + t.AssertNil(err) + t.Assert(len(result), 1) + + err = tx.Commit() + t.AssertNil(err) + }) +} + +func Test_TX_GetOne(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + tx, err := db.Begin(ctx) + t.AssertNil(err) + + record, err := tx.GetOne(fmt.Sprintf("SELECT * FROM %s WHERE passport=$1", table), "user_2") + t.AssertNil(err) + t.AssertNE(record, nil) + t.Assert(record["nickname"].String(), "name_2") + + err = tx.Commit() + t.AssertNil(err) + }) +} + +func Test_TX_GetValue(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + tx, err := db.Begin(ctx) + t.AssertNil(err) + + value, err := tx.GetValue(fmt.Sprintf("SELECT id FROM %s WHERE passport=$1", table), "user_3") + t.AssertNil(err) + t.Assert(value.Int(), 3) + + err = tx.Commit() + t.AssertNil(err) + }) +} + +func Test_TX_GetCount(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + tx, err := db.Begin(ctx) + t.AssertNil(err) + + count, err := tx.GetCount("SELECT * FROM " + table) + t.AssertNil(err) + t.Assert(count, int64(TableSize)) + + err = tx.Commit() + t.AssertNil(err) + }) +} + +func Test_TX_GetStruct(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + tx, err := db.Begin(ctx) + t.AssertNil(err) + + type User struct { + Id int + Passport string + Password string + NickName string + CreateTime gtime.Time + } + user := new(User) + err = tx.GetStruct(user, fmt.Sprintf("SELECT * FROM %s WHERE id=$1", table), 3) + t.AssertNil(err) + t.Assert(user.NickName, "name_3") + t.Assert(user.CreateTime.String(), "2018-10-24 10:00:00") + + err = tx.Commit() + t.AssertNil(err) + }) + + gtest.C(t, func(t *gtest.T) { + tx, err := db.Begin(ctx) + t.AssertNil(err) + + type User struct { + Id int + Passport string + Password string + NickName string + CreateTime *gtime.Time + } + user := new(User) + err = tx.GetStruct(user, fmt.Sprintf("SELECT * FROM %s WHERE id=$1", table), 3) + t.AssertNil(err) + t.Assert(user.NickName, "name_3") + t.Assert(user.CreateTime.String(), "2018-10-24 10:00:00") + + err = tx.Commit() + t.AssertNil(err) + }) +} + +func Test_TX_GetStructs(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + tx, err := db.Begin(ctx) + t.AssertNil(err) + + type User struct { + Id int + Passport string + Password string + NickName string + CreateTime gtime.Time + } + var users []User + err = tx.GetStructs(&users, fmt.Sprintf("SELECT * FROM %s WHERE id>=$1", table), 1) + t.AssertNil(err) + t.Assert(len(users), TableSize) + t.Assert(users[0].Id, 1) + t.Assert(users[1].Id, 2) + t.Assert(users[2].Id, 3) + t.Assert(users[0].NickName, "name_1") + t.Assert(users[1].NickName, "name_2") + t.Assert(users[2].NickName, "name_3") + t.Assert(users[2].CreateTime.String(), "2018-10-24 10:00:00") + + err = tx.Commit() + t.AssertNil(err) + }) + + gtest.C(t, func(t *gtest.T) { + tx, err := db.Begin(ctx) + t.AssertNil(err) + + type User struct { + Id int + Passport string + Password string + NickName string + CreateTime *gtime.Time + } + var users []User + err = tx.GetStructs(&users, fmt.Sprintf("SELECT * FROM %s WHERE id>=$1", table), 1) + t.AssertNil(err) + t.Assert(len(users), TableSize) + t.Assert(users[0].Id, 1) + t.Assert(users[1].Id, 2) + t.Assert(users[2].Id, 3) + t.Assert(users[0].NickName, "name_1") + t.Assert(users[1].NickName, "name_2") + t.Assert(users[2].NickName, "name_3") + t.Assert(users[2].CreateTime.String(), "2018-10-24 10:00:00") + + err = tx.Commit() + t.AssertNil(err) + }) +} + +func Test_TX_GetScan(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + tx, err := db.Begin(ctx) + t.AssertNil(err) + + type User struct { + Id int + Passport string + Password string + NickName string + CreateTime gtime.Time + } + user := new(User) + err = tx.GetScan(user, fmt.Sprintf("SELECT * FROM %s WHERE id=$1", table), 3) + t.AssertNil(err) + t.Assert(user.NickName, "name_3") + t.Assert(user.CreateTime.String(), "2018-10-24 10:00:00") + + err = tx.Commit() + t.AssertNil(err) + }) + + gtest.C(t, func(t *gtest.T) { + tx, err := db.Begin(ctx) + t.AssertNil(err) + + type User struct { + Id int + Passport string + Password string + NickName string + CreateTime *gtime.Time + } + user := new(User) + err = tx.GetScan(user, fmt.Sprintf("SELECT * FROM %s WHERE id=$1", table), 3) + t.AssertNil(err) + t.Assert(user.NickName, "name_3") + t.Assert(user.CreateTime.String(), "2018-10-24 10:00:00") + + err = tx.Commit() + t.AssertNil(err) + }) + + gtest.C(t, func(t *gtest.T) { + tx, err := db.Begin(ctx) + t.AssertNil(err) + + type User struct { + Id int + Passport string + Password string + NickName string + CreateTime gtime.Time + } + var users []User + err = tx.GetScan(&users, fmt.Sprintf("SELECT * FROM %s WHERE id>=$1", table), 1) + t.AssertNil(err) + t.Assert(len(users), TableSize) + t.Assert(users[0].Id, 1) + t.Assert(users[1].Id, 2) + t.Assert(users[2].Id, 3) + t.Assert(users[0].NickName, "name_1") + t.Assert(users[1].NickName, "name_2") + t.Assert(users[2].NickName, "name_3") + t.Assert(users[2].CreateTime.String(), "2018-10-24 10:00:00") + + err = tx.Commit() + t.AssertNil(err) + }) + + gtest.C(t, func(t *gtest.T) { + tx, err := db.Begin(ctx) + t.AssertNil(err) + + type User struct { + Id int + Passport string + Password string + NickName string + CreateTime *gtime.Time + } + var users []User + err = tx.GetScan(&users, fmt.Sprintf("SELECT * FROM %s WHERE id>=$1", table), 1) + t.AssertNil(err) + t.Assert(len(users), TableSize) + t.Assert(users[0].Id, 1) + t.Assert(users[1].Id, 2) + t.Assert(users[2].Id, 3) + t.Assert(users[0].NickName, "name_1") + t.Assert(users[1].NickName, "name_2") + t.Assert(users[2].NickName, "name_3") + t.Assert(users[2].CreateTime.String(), "2018-10-24 10:00:00") + + err = tx.Commit() + t.AssertNil(err) + }) +} + +func Test_Transaction(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + ctx := context.TODO() + err := db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + _, err := tx.Ctx(ctx).Model(table).Data(g.Map{ + "id": 1, + "passport": "USER_1", + "password": "PASS_1", + "nickname": "NAME_1", + "create_time": gtime.Now().String(), + }).OnConflict("id").Save() + t.AssertNil(err) + t.Assert(tx.IsClosed(), false) + return gerror.New("error") + }) + t.AssertNE(err, nil) + + value, err := db.Model(table).Ctx(ctx).Fields("nickname").Where("id", 1).Value() + t.AssertNil(err) + t.Assert(value.String(), "name_1") + }) + + gtest.C(t, func(t *gtest.T) { + ctx := context.TODO() + err := db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + _, err := tx.Model(table).Data(g.Map{ + "id": 1, + "passport": "USER_1", + "password": "PASS_1", + "nickname": "NAME_1", + "create_time": gtime.Now().String(), + }).OnConflict("id").Save() + t.AssertNil(err) + return nil + }) + t.AssertNil(err) + + value, err := db.Model(table).Fields("nickname").Where("id", 1).Value() + t.AssertNil(err) + t.Assert(value.String(), "NAME_1") + }) +} + +func Test_Transaction_Panic(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + ctx := context.TODO() + err := db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + _, err := tx.Model(table).Data(g.Map{ + "id": 1, + "passport": "USER_1", + "password": "PASS_1", + "nickname": "NAME_1", + "create_time": gtime.Now().String(), + }).OnConflict("id").Save() + t.AssertNil(err) + panic("error") + return nil + }) + t.AssertNE(err, nil) + + value, err := db.Model(table).Fields("nickname").Where("id", 1).Value() + t.AssertNil(err) + t.Assert(value.String(), "name_1") + }) +} + +func Test_Transaction_Method(t *testing.T) { + table := createTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + var err error + err = db.Transaction(gctx.New(), func(ctx context.Context, tx gdb.TX) error { + _, err = db.Model(table).Ctx(ctx).Data(g.Map{ + "id": 1, + "passport": "t1", + "password": "25d55ad283aa400af464c76d713c07ad", + "nickname": "T1", + "create_time": gtime.Now().String(), + }).Insert() + t.AssertNil(err) + + _, err = db.Ctx(ctx).Exec(ctx, fmt.Sprintf( + "INSERT INTO %s(passport,password,nickname,create_time,id) "+ + "VALUES('t2','25d55ad283aa400af464c76d713c07ad','T2','2021-08-25 21:53:00',2) ", + table)) + t.AssertNil(err) + + return gerror.New("rollback") + }) + t.AssertNE(err, nil) + + count, err := db.Model(table).Count() + t.AssertNil(err) + t.Assert(count, int64(0)) + }) +} + +func Test_Transaction_Nested_Begin_Rollback_Commit(t *testing.T) { + table := createTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + tx, err := db.Begin(ctx) + t.AssertNil(err) + + // tx begin. + err = tx.Begin() + t.AssertNil(err) + + // tx rollback. + _, err = tx.Model(table).Data(g.Map{ + "id": 1, + "passport": "user_1", + "password": "pass_1", + "nickname": "name_1", + "create_time": gtime.Now().String(), + }).Insert() + err = tx.Rollback() + t.AssertNil(err) + + // tx commit. + _, err = tx.Model(table).Data(g.Map{ + "id": 2, + "passport": "user_2", + "password": "pass_2", + "nickname": "name_2", + "create_time": gtime.Now().String(), + }).Insert() + err = tx.Commit() + t.AssertNil(err) + + // check data. + all, err := db.Model(table).All() + t.AssertNil(err) + t.Assert(len(all), 1) + t.Assert(all[0]["id"], 2) + }) +} + +func Test_Transaction_Nested_TX_Transaction_UseTX(t *testing.T) { + table := createTable() + defer dropTable(table) + + db.SetDebug(true) + defer db.SetDebug(false) + + gtest.C(t, func(t *gtest.T) { + var ( + err error + ctx = context.TODO() + ) + err = db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + // commit + err = tx.Transaction(ctx, func(ctx context.Context, tx2 gdb.TX) error { + err = tx2.Transaction(ctx, func(ctx context.Context, tx2 gdb.TX) error { + err = tx2.Transaction(ctx, func(ctx context.Context, tx2 gdb.TX) error { + err = tx2.Transaction(ctx, func(ctx context.Context, tx2 gdb.TX) error { + err = tx2.Transaction(ctx, func(ctx context.Context, tx2 gdb.TX) error { + _, err = tx2.Model(table).Data(g.Map{ + "id": 1, + "passport": "USER_1", + "password": "PASS_1", + "nickname": "NAME_1", + "create_time": gtime.Now().String(), + }).Insert() + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + + // rollback + err = tx.Transaction(ctx, func(ctx context.Context, tx2 gdb.TX) error { + _, err = tx2.Model(table).Data(g.Map{ + "id": 2, + "passport": "USER_2", + "password": "PASS_2", + "nickname": "NAME_2", + "create_time": gtime.Now().String(), + }).Insert() + t.AssertNil(err) + panic("error") + return err + }) + t.AssertNE(err, nil) + return nil + }) + t.AssertNil(err) + + all, err := db.Ctx(ctx).Model(table).All() + t.AssertNil(err) + t.Assert(len(all), 1) + t.Assert(all[0]["id"], 1) + + // another record. + err = db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + // commit + err = tx.Transaction(ctx, func(ctx context.Context, tx2 gdb.TX) error { + err = tx2.Transaction(ctx, func(ctx context.Context, tx2 gdb.TX) error { + err = tx2.Transaction(ctx, func(ctx context.Context, tx2 gdb.TX) error { + err = tx2.Transaction(ctx, func(ctx context.Context, tx2 gdb.TX) error { + err = tx2.Transaction(ctx, func(ctx context.Context, tx2 gdb.TX) error { + _, err = tx2.Model(table).Data(g.Map{ + "id": 3, + "passport": "USER_1", + "password": "PASS_1", + "nickname": "NAME_1", + "create_time": gtime.Now().String(), + }).Insert() + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + + // rollback + err = tx.Transaction(ctx, func(ctx context.Context, tx2 gdb.TX) error { + _, err = tx2.Model(table).Data(g.Map{ + "id": 4, + "passport": "USER_2", + "password": "PASS_2", + "nickname": "NAME_2", + "create_time": gtime.Now().String(), + }).Insert() + t.AssertNil(err) + panic("error") + return err + }) + t.AssertNE(err, nil) + return nil + }) + t.AssertNil(err) + + all, err = db.Ctx(ctx).Model(table).All() + t.AssertNil(err) + t.Assert(len(all), 2) + t.Assert(all[0]["id"], 1) + t.Assert(all[1]["id"], 3) + }) +} + +func Test_Transaction_Nested_TX_Transaction_UseDB(t *testing.T) { + table := createTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + var ( + err error + ctx = context.TODO() + ) + err = db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + // commit + err = db.Transaction(ctx, func(ctx context.Context, tx2 gdb.TX) error { + err = db.Transaction(ctx, func(ctx context.Context, tx2 gdb.TX) error { + err = db.Transaction(ctx, func(ctx context.Context, tx2 gdb.TX) error { + err = db.Transaction(ctx, func(ctx context.Context, tx2 gdb.TX) error { + err = db.Transaction(ctx, func(ctx context.Context, tx2 gdb.TX) error { + _, err = db.Model(table).Ctx(ctx).Data(g.Map{ + "id": 1, + "passport": "USER_1", + "password": "PASS_1", + "nickname": "NAME_1", + "create_time": gtime.Now().String(), + }).Insert() + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + + // rollback + err = db.Transaction(ctx, func(ctx context.Context, tx2 gdb.TX) error { + _, err = tx2.Model(table).Ctx(ctx).Data(g.Map{ + "id": 2, + "passport": "USER_2", + "password": "PASS_2", + "nickname": "NAME_2", + "create_time": gtime.Now().String(), + }).Insert() + t.AssertNil(err) + // panic makes this transaction rollback. + panic("error") + return err + }) + t.AssertNE(err, nil) + return nil + }) + t.AssertNil(err) + + all, err := db.Model(table).All() + t.AssertNil(err) + t.Assert(len(all), 1) + t.Assert(all[0]["id"], 1) + + err = db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + // commit + err = db.Transaction(ctx, func(ctx context.Context, tx2 gdb.TX) error { + err = db.Transaction(ctx, func(ctx context.Context, tx2 gdb.TX) error { + err = db.Transaction(ctx, func(ctx context.Context, tx2 gdb.TX) error { + err = db.Transaction(ctx, func(ctx context.Context, tx2 gdb.TX) error { + err = db.Transaction(ctx, func(ctx context.Context, tx2 gdb.TX) error { + _, err = db.Model(table).Ctx(ctx).Data(g.Map{ + "id": 3, + "passport": "USER_1", + "password": "PASS_1", + "nickname": "NAME_1", + "create_time": gtime.Now().String(), + }).Insert() + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + return err + }) + t.AssertNil(err) + + // rollback + err = db.Transaction(ctx, func(ctx context.Context, tx2 gdb.TX) error { + _, err = tx2.Model(table).Ctx(ctx).Data(g.Map{ + "id": 4, + "passport": "USER_2", + "password": "PASS_2", + "nickname": "NAME_2", + "create_time": gtime.Now().String(), + }).Insert() + t.AssertNil(err) + // panic makes this transaction rollback. + panic("error") + return err + }) + t.AssertNE(err, nil) + return nil + }) + t.AssertNil(err) + + all, err = db.Model(table).All() + t.AssertNil(err) + t.Assert(len(all), 2) + t.Assert(all[0]["id"], 1) + t.Assert(all[1]["id"], 3) + }) +} + +func Test_Transaction_Nested_SavePoint_RollbackTo(t *testing.T) { + table := createTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + tx, err := db.Begin(ctx) + t.AssertNil(err) + + // tx save point. + _, err = tx.Model(table).Data(g.Map{ + "id": 1, + "passport": "user_1", + "password": "pass_1", + "nickname": "name_1", + "create_time": gtime.Now().String(), + }).Insert() + err = tx.SavePoint("MyPoint") + t.AssertNil(err) + + _, err = tx.Model(table).Data(g.Map{ + "id": 2, + "passport": "user_2", + "password": "pass_2", + "nickname": "name_2", + "create_time": gtime.Now().String(), + }).Insert() + // tx rollback to. + err = tx.RollbackTo("MyPoint") + t.AssertNil(err) + + // tx commit. + err = tx.Commit() + t.AssertNil(err) + + // check data. + all, err := db.Model(table).All() + t.AssertNil(err) + t.Assert(len(all), 1) + t.Assert(all[0]["id"], 1) + }) +} + +func Test_Transaction_Propagation_Required(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + table := createTable() + defer dropTable(table) + + err := db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + _, err := tx.Insert(table, g.Map{ + "id": 1, + "passport": "required", + "password": "pass_1", + "nickname": "name_1", + "create_time": gtime.Now().String(), + }) + t.AssertNil(err) + + err = tx.TransactionWithOptions(ctx, gdb.TxOptions{ + Propagation: gdb.PropagationRequired, + }, func(ctx context.Context, tx2 gdb.TX) error { + _, err := tx2.Insert(table, g.Map{ + "id": 2, + "passport": "required_nested", + "password": "pass_2", + "nickname": "name_2", + "create_time": gtime.Now().String(), + }) + return err + }) + t.AssertNil(err) + return nil + }) + t.AssertNil(err) + + count, err := db.Model(table).Count() + t.AssertNil(err) + t.Assert(count, int64(2)) + }) +} + +func Test_Transaction_Propagation_RequiresNew(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + table := createTable() + defer dropTable(table) + + err := db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + _, err := tx.Insert(table, g.Map{ + "id": 3, + "passport": "outer", + "password": "pass_3", + "nickname": "name_3", + "create_time": gtime.Now().String(), + }) + t.AssertNil(err) + + err = tx.TransactionWithOptions(ctx, gdb.TxOptions{ + Propagation: gdb.PropagationRequiresNew, + }, func(ctx context.Context, tx2 gdb.TX) error { + _, _ = tx2.Insert(table, g.Map{ + "id": 4, + "passport": "inner_new", + "password": "pass_4", + "nickname": "name_4", + "create_time": gtime.Now().String(), + }) + return gerror.New("rollback inner transaction") + }) + t.AssertNE(err, nil) + return nil + }) + t.AssertNil(err) + + count, err := db.Model(table).Where("passport", "outer").Count() + t.AssertNil(err) + t.Assert(count, int64(1)) + }) +} + +func Test_Transaction_Propagation_Nested(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + table := createTable() + defer dropTable(table) + + err := db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + _, err := tx.Insert(table, g.Map{ + "id": 5, + "passport": "nested_outer", + "password": "pass_5", + "nickname": "name_5", + "create_time": gtime.Now().String(), + }) + t.AssertNil(err) + + err = tx.TransactionWithOptions(ctx, gdb.TxOptions{ + Propagation: gdb.PropagationNested, + }, func(ctx context.Context, tx2 gdb.TX) error { + _, _ = tx2.Insert(table, g.Map{ + "id": 6, + "passport": "nested_inner", + "password": "pass_6", + "nickname": "name_6", + "create_time": gtime.Now().String(), + }) + return gerror.New("rollback to savepoint") + }) + t.AssertNE(err, nil) + + _, err = tx.Insert(table, g.Map{ + "id": 7, + "passport": "nested_after", + "password": "pass_7", + "nickname": "name_7", + "create_time": gtime.Now().String(), + }) + t.AssertNil(err) + return nil + }) + t.AssertNil(err) + + count, err := db.Model(table).Where("passport", "nested_inner").Count() + t.AssertNil(err) + t.Assert(count, int64(0)) + + count, err = db.Model(table).Where("passport IN(?,?)", + "nested_outer", "nested_after").Count() + t.AssertNil(err) + t.Assert(count, int64(2)) + }) +} + +func Test_Transaction_Propagation_NotSupported(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + table := createTable() + defer dropTable(table) + + err := db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + _, err := tx.Insert(table, g.Map{ + "id": 8, + "passport": "tx_record", + "password": "pass_8", + "nickname": "name_8", + "create_time": gtime.Now().String(), + }) + t.AssertNil(err) + + err = tx.TransactionWithOptions(ctx, gdb.TxOptions{ + Propagation: gdb.PropagationNotSupported, + }, func(ctx context.Context, tx2 gdb.TX) error { + _, err = db.Insert(ctx, table, g.Map{ + "id": 9, + "passport": "non_tx_record", + "password": "pass_9", + "nickname": "name_9", + "create_time": gtime.Now().String(), + }) + return err + }) + t.AssertNil(err) + + return gerror.New("rollback outer transaction") + }) + t.AssertNE(err, nil) + + count, err := db.Model(table).Where("passport", "tx_record").Count() + t.AssertNil(err) + t.Assert(count, int64(0)) + + count, err = db.Model(table).Where("passport", "non_tx_record").Count() + t.AssertNil(err) + t.Assert(count, int64(1)) + }) +} + +func Test_Transaction_Propagation_Mandatory(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + table := createTable() + defer dropTable(table) + + err := db.TransactionWithOptions(ctx, gdb.TxOptions{ + Propagation: gdb.PropagationMandatory, + }, func(ctx context.Context, tx gdb.TX) error { + return nil + }) + t.AssertNE(err, nil) + + err = db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + return tx.TransactionWithOptions(ctx, gdb.TxOptions{ + Propagation: gdb.PropagationMandatory, + }, func(ctx context.Context, tx2 gdb.TX) error { + _, err := tx2.Insert(table, g.Map{ + "id": 10, + "passport": "mandatory", + "password": "pass_10", + "nickname": "name_10", + "create_time": gtime.Now().String(), + }) + return err + }) + }) + t.AssertNil(err) + }) +} + +func Test_Transaction_Propagation_Never(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + table := createTable() + defer dropTable(table) + + err := db.TransactionWithOptions(ctx, gdb.TxOptions{ + Propagation: gdb.PropagationNever, + }, func(ctx context.Context, tx gdb.TX) error { + _, err := db.Insert(ctx, table, g.Map{ + "id": 11, + "passport": "never", + "password": "pass_11", + "nickname": "name_11", + "create_time": gtime.Now().String(), + }) + return err + }) + t.AssertNil(err) + + err = db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + return tx.TransactionWithOptions(ctx, gdb.TxOptions{ + Propagation: gdb.PropagationNever, + }, func(ctx context.Context, tx2 gdb.TX) error { + return nil + }) + }) + t.AssertNE(err, nil) + }) +} + +func Test_Transaction_Propagation_Supports(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + table := createTable() + defer dropTable(table) + + // scenario1: when in a transaction, use PropagationSupports to execute a transaction + err := db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + _, err := tx.Insert(table, g.Map{ + "id": 1, + "passport": "user_1", + "password": "pass_1", + "nickname": "name_1", + "create_time": gtime.Now().String(), + }) + if err != nil { + return err + } + err = tx.TransactionWithOptions(ctx, gdb.TxOptions{ + Propagation: gdb.PropagationSupports, + }, func(ctx context.Context, tx2 gdb.TX) error { + _, err = tx2.Insert(table, g.Map{ + "id": 2, + "passport": "user_2", + "password": "pass_2", + "nickname": "name_2", + "create_time": gtime.Now().String(), + }) + return gerror.New("error") + }) + return err + }) + t.AssertNE(err, nil) + + // scenario2: when not in a transaction, do not use transaction but direct db link. + err = db.TransactionWithOptions(ctx, gdb.TxOptions{ + Propagation: gdb.PropagationSupports, + }, func(ctx context.Context, tx gdb.TX) error { + _, err = tx.Insert(table, g.Map{ + "id": 3, + "passport": "user_3", + "password": "pass_3", + "nickname": "name_3", + "create_time": gtime.Now().String(), + }) + return err + }) + t.AssertNil(err) + + result, err := db.Model(table).OrderAsc("id").All() + t.AssertNil(err) + t.Assert(len(result), 1) + t.Assert(result[0]["id"], 3) + }) +} + +func Test_Transaction_Propagation_Complex(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + table1 := createTable() + table2 := createTable() + defer dropTable(table1) + defer dropTable(table2) + + err := db.Transaction(ctx, func(ctx context.Context, tx1 gdb.TX) error { + _, err := tx1.Insert(table1, g.Map{ + "id": 1, + "passport": "outer", + "password": "pass_1", + "nickname": "name_1", + "create_time": gtime.Now().String(), + }) + t.AssertNil(err) + + // First nested transaction (NESTED) + err = tx1.TransactionWithOptions(ctx, gdb.TxOptions{ + Propagation: gdb.PropagationNested, + }, func(ctx context.Context, tx2 gdb.TX) error { + _, err = tx2.Insert(table1, g.Map{ + "id": 2, + "passport": "nested1", + "password": "pass_2", + "nickname": "name_2", + "create_time": gtime.Now().String(), + }) + t.AssertNil(err) + + // Second nested transaction (REQUIRES_NEW) + err = tx2.TransactionWithOptions(ctx, gdb.TxOptions{ + Propagation: gdb.PropagationRequiresNew, + }, func(ctx context.Context, tx3 gdb.TX) error { + _, _ = tx3.Insert(table1, g.Map{ + "id": 3, + "passport": "new1", + "password": "pass_3", + "nickname": "name_3", + "create_time": gtime.Now().String(), + }) + return gerror.New("rollback new transaction") + }) + t.AssertNE(err, nil) + + // Third nested transaction (NESTED) + return tx2.TransactionWithOptions(ctx, gdb.TxOptions{ + Propagation: gdb.PropagationNested, + }, func(ctx context.Context, tx3 gdb.TX) error { + _, _ = tx3.Insert(table1, g.Map{ + "id": 4, + "passport": "nested2", + "password": "pass_4", + "nickname": "name_4", + "create_time": gtime.Now().String(), + }) + return gerror.New("rollback nested transaction") + }) + }) + t.AssertNE(err, nil) + + // Fourth transaction (NOT_SUPPORTED) + err = tx1.TransactionWithOptions(ctx, gdb.TxOptions{ + Propagation: gdb.PropagationNotSupported, + }, func(ctx context.Context, tx2 gdb.TX) error { + _, err = db.Insert(ctx, table2, g.Map{ + "id": 5, + "passport": "not_supported", + "password": "pass_5", + "nickname": "name_5", + "create_time": gtime.Now().String(), + }) + return err + }) + t.AssertNil(err) + + return nil + }) + t.AssertNil(err) + + count, err := db.Model(table1).Where("passport", "outer").Count() + t.AssertNil(err) + t.Assert(count, int64(1)) + + count, err = db.Model(table1).Where("passport", "nested1").Count() + t.AssertNil(err) + t.Assert(count, int64(0)) + + count, err = db.Model(table1).Where("passport", "new1").Count() + t.AssertNil(err) + t.Assert(count, int64(0)) + + count, err = db.Model(table1).Where("passport", "nested2").Count() + t.AssertNil(err) + t.Assert(count, int64(0)) + + count, err = db.Model(table2).Where("passport", "not_supported").Count() + t.AssertNil(err) + t.Assert(count, int64(1)) + }) + + gtest.C(t, func(t *gtest.T) { + table := createTable() + defer dropTable(table) + + err := db.Transaction(ctx, func(ctx context.Context, tx1 gdb.TX) error { + _, err := tx1.Insert(table, g.Map{ + "id": 6, + "passport": "suspend_outer", + "password": "pass6", + "nickname": "suspend_outer", + "create_time": gtime.Now().String(), + }) + t.AssertNil(err) + + // Suspend current transaction (NOT_SUPPORTED) + err = tx1.TransactionWithOptions(ctx, gdb.TxOptions{ + Propagation: gdb.PropagationNotSupported, + }, func(ctx context.Context, tx2 gdb.TX) error { + return db.Transaction(ctx, func(ctx context.Context, tx3 gdb.TX) error { + _, err := tx3.Insert(table, g.Map{ + "id": 7, + "passport": "independent", + "password": "pass7", + "nickname": "independent", + "create_time": gtime.Now().String(), + }) + return err + }) + }) + t.AssertNil(err) + + // Resume original transaction + _, err = tx1.Insert(table, g.Map{ + "id": 8, + "passport": "suspend_resume", + "password": "pass8", + "nickname": "suspend_resume", + "create_time": gtime.Now().String(), + }) + t.AssertNil(err) + + return gerror.New("rollback outer transaction") + }) + t.AssertNE(err, nil) + + count, err := db.Model(table).Where("passport IN(?,?)", + "suspend_outer", "suspend_resume").Count() + t.AssertNil(err) + t.Assert(count, int64(0)) + + count, err = db.Model(table).Where("passport", "independent").Count() + t.AssertNil(err) + t.Assert(count, int64(1)) + }) +} + +func Test_Transaction_ReadOnly(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + err := db.TransactionWithOptions(ctx, gdb.TxOptions{ + ReadOnly: true, + }, func(ctx context.Context, tx gdb.TX) error { + _, err := tx.Update(table, g.Map{"passport": "changed"}, "id=1") + return err + }) + t.AssertNE(err, nil) + + v, err := db.Model(table).Where("id=1").Value("passport") + t.AssertNil(err) + t.Assert(v.String(), "user_1") + }) +} + +func Test_Transaction_Isolation_ReadCommitted(t *testing.T) { + // GaussDB default isolation level is READ COMMITTED. + gtest.C(t, func(t *gtest.T) { + table := createInitTable() + defer dropTable(table) + err := db.TransactionWithOptions(ctx, gdb.TxOptions{ + Propagation: gdb.PropagationRequiresNew, + Isolation: sql.LevelReadCommitted, + }, func(ctx context.Context, tx1 gdb.TX) error { + // First read + v1, err := tx1.Model(table).Where("id=1").Value("passport") + t.AssertNil(err) + initialValue := v1.String() + + // Another transaction updates and commits + err = db.TransactionWithOptions(ctx, gdb.TxOptions{ + Propagation: gdb.PropagationRequiresNew, + Isolation: sql.LevelReadCommitted, + }, func(ctx context.Context, tx2 gdb.TX) error { + _, err := tx2.Update(table, g.Map{"passport": "committed_value"}, "id=1") + return err + }) + t.AssertNil(err) + + // Should see new value in READ COMMITTED + v2, err := tx1.Model(table).Where("id=1").Value("passport") + t.AssertNil(err) + t.Assert(v2.String(), "committed_value") + t.AssertNE(v2.String(), initialValue) + return nil + }) + t.AssertNil(err) + }) +} + +func Test_Transaction_Isolation_RepeatableRead(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + table := createInitTable() + defer dropTable(table) + + err := db.TransactionWithOptions(ctx, gdb.TxOptions{ + Propagation: gdb.PropagationRequiresNew, + Isolation: sql.LevelRepeatableRead, + }, func(ctx context.Context, tx1 gdb.TX) error { + // First read + v1, err := tx1.Model(table).Where("id=1").Value("passport") + t.AssertNil(err) + initialValue := v1.String() + + // Another transaction updates and commits the value + err = db.TransactionWithOptions(ctx, gdb.TxOptions{ + Propagation: gdb.PropagationRequiresNew, + }, func(ctx context.Context, tx2 gdb.TX) error { + _, err := tx2.Update(table, g.Map{ + "passport": "changed_value", + }, "id=1") + t.AssertNil(err) + return nil + }) + t.AssertNil(err) + + // Verify the change is visible outside transaction + v, err := db.Model(table).Where("id=1").Value("passport") + t.AssertNil(err) + t.Assert(v.String(), "changed_value") + + // Should still see old value in REPEATABLE READ transaction + v2, err := tx1.Model(table).Where("id=1").Value("passport") + t.AssertNil(err) + t.Assert(v2.String(), initialValue) + + // Even after multiple reads, should still see the same value + v3, err := tx1.Model(table).Where("id=1").Value("passport") + t.AssertNil(err) + t.Assert(v3.String(), initialValue) + + return nil + }) + t.AssertNil(err) + + // After transaction ends, should see the committed change + v, err := db.Model(table).Where("id=1").Value("passport") + t.AssertNil(err) + t.Assert(v.String(), "changed_value") + }) +} + +func Test_Transaction_Isolation_Serializable(t *testing.T) { + // GaussDB uses SSI (Serializable Snapshot Isolation) for SERIALIZABLE level. + // Concurrent writes to the same data may cause serialization failures. + gtest.C(t, func(t *gtest.T) { + table := createInitTable() + defer dropTable(table) + + err := db.TransactionWithOptions(ctx, gdb.TxOptions{ + Propagation: gdb.PropagationRequiresNew, + Isolation: sql.LevelSerializable, + }, func(ctx context.Context, tx1 gdb.TX) error { + // Read all records + _, err := tx1.Model(table).All() + t.AssertNil(err) + + // Try concurrent insert in another transaction + err = db.TransactionWithOptions(ctx, gdb.TxOptions{ + Propagation: gdb.PropagationRequiresNew, + Isolation: sql.LevelSerializable, + }, func(ctx context.Context, tx2 gdb.TX) error { + _, err := tx2.Insert(table, g.Map{ + "id": 1000, + "passport": "new_user", + "password": "pass_1000", + "nickname": "name_1000", + "create_time": gtime.Now().String(), + }) + return err + }) + // Note: GaussDB SSI may or may not cause serialization failure + // depending on timing and whether there's an actual conflict. + // For new rows with unique IDs, it typically succeeds. + // We only verify the outer transaction completes. + return nil + }) + t.AssertNil(err) + }) +} + +func Test_Transaction_Spread(t *testing.T) { + table := createTable() + defer dropTable(table) + + db.SetDebug(true) + defer db.SetDebug(false) + + gtest.C(t, func(t *gtest.T) { + var ( + err error + ctx = context.TODO() + ) + tx, err := db.Begin(ctx) + t.AssertNil(err) + err = db.Transaction(tx.GetCtx(), func(ctx context.Context, tx gdb.TX) error { + _, err = db.Model(table).Ctx(ctx).Data(g.Map{ + "id": 1, + "passport": "USER_1", + "password": "PASS_1", + "nickname": "NAME_1", + "create_time": gtime.Now().String(), + }).Insert() + return err + }) + t.AssertNil(err) + + all, err := tx.Model(table).All() + t.AssertNil(err) + t.Assert(len(all), 1) + t.Assert(all[0]["id"], 1) + + err = tx.Rollback() + t.AssertNil(err) + + all, err = db.Ctx(ctx).Model(table).All() + t.AssertNil(err) + t.Assert(len(all), 0) + }) +}