mirror of
https://gitee.com/johng/gf
synced 2026-06-07 02:12:11 +08:00
## Summary
- Add 4 new test files for pgsql driver to align with MySQL driver test
coverage (86 test functions, ~3100 lines)
- `pgsql_z_unit_transaction_test.go`: 40 tests — TX CRUD, nested
transactions, propagation behaviors
(Required/RequiresNew/Nested/NotSupported/Mandatory/Never/Supports),
isolation levels (ReadCommitted/RepeatableRead/Serializable), savepoints
- `pgsql_z_unit_model_where_test.go`: 35 tests — Where variants
(string/slice/map/struct/gmap), comparisons (LT/LTE/GT/GTE), IN/NotIn,
Between, Like, Null, EXISTS/NOT EXISTS subqueries, WherePrefix with JOIN
- `pgsql_z_unit_feature_hook_test.go`: 6 tests —
Select/Insert/Update/Delete hooks, Count with hook, hook chaining and
error handling
- `pgsql_z_unit_feature_ctx_test.go`: 5 tests — context propagation,
trace logging (SpanId/TraceId), transaction context, timeout
cancellation
- Migrate `Test_Model_Where` from `pgsql_z_unit_model_test.go` to
dedicated where test file with expanded coverage (2 → 30+ sub-tests)
**PostgreSQL adaptations from MySQL:**
- `?` → `$N` placeholders for raw SQL
- `REPLACE INTO` → `OnConflict("id").Save()` for upsert
- `AUTO_INCREMENT` → `bigserial`
- `user` alias → `"user"` (reserved word in PgSQL)
- Skip `READ UNCOMMITTED` dirty read test (PgSQL treats as READ
COMMITTED)
## Test plan
- [ ] Run `go test -v -run "Test_TX_" -count=1` in
`contrib/drivers/pgsql`
- [ ] Run `go test -v -run "Test_Model_Where" -count=1` in
`contrib/drivers/pgsql`
- [ ] Run `go test -v -run "Test_Model_Hook" -count=1` in
`contrib/drivers/pgsql`
- [ ] Run `go test -v -run "Test_Ctx" -count=1` in
`contrib/drivers/pgsql`
- [ ] Verify `go vet ./...` passes (only unreachable code warnings
matching MySQL driver pattern)
ref #4689
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1694 lines
42 KiB
Go
1694 lines
42 KiB
Go
// 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 pgsql_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 PostgreSQL, 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 PostgreSQL, 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) {
|
|
// PgSQL 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) {
|
|
// PgSQL 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: PostgreSQL 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)
|
|
})
|
|
}
|