bug fix:修复可能的panic状态;增加更多功能
This commit is contained in:
+331
-6
@@ -2,6 +2,7 @@ package testing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -110,8 +111,53 @@ func TestStarDB_BatchInsert_Empty(t *testing.T) {
|
||||
values := [][]interface{}{}
|
||||
|
||||
_, err := db.BatchInsert("users", columns, values)
|
||||
if err == nil {
|
||||
t.Error("Expected error with empty values, got nil")
|
||||
if !errors.Is(err, stardb.ErrNoInsertValues) {
|
||||
t.Errorf("Expected ErrNoInsertValues, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_BatchInsert_EmptyColumns(t *testing.T) {
|
||||
db := setupBatchTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
values := [][]interface{}{
|
||||
{"Alice"},
|
||||
}
|
||||
|
||||
_, err := db.BatchInsert("users", nil, values)
|
||||
if !errors.Is(err, stardb.ErrNoInsertColumns) {
|
||||
t.Errorf("Expected ErrNoInsertColumns, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_BatchInsert_EmptyTableName(t *testing.T) {
|
||||
db := setupBatchTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
columns := []string{"name", "email", "age"}
|
||||
values := [][]interface{}{
|
||||
{"Alice", "alice@example.com", 25},
|
||||
}
|
||||
|
||||
_, err := db.BatchInsert("", columns, values)
|
||||
if !errors.Is(err, stardb.ErrTableNameEmpty) {
|
||||
t.Errorf("Expected ErrTableNameEmpty, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_BatchInsert_RowLengthMismatch(t *testing.T) {
|
||||
db := setupBatchTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
columns := []string{"name", "email", "age"}
|
||||
values := [][]interface{}{
|
||||
{"Alice", "alice@example.com", 25},
|
||||
{"Bob", "bob@example.com"},
|
||||
}
|
||||
|
||||
_, err := db.BatchInsert("users", columns, values)
|
||||
if !errors.Is(err, stardb.ErrBatchRowValueCountMismatch) {
|
||||
t.Errorf("Expected ErrBatchRowValueCountMismatch, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,6 +250,236 @@ func TestStarDB_BatchInsertContext_Timeout(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_BatchInsertMaxRows_Config(t *testing.T) {
|
||||
db := setupBatchTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
if got := db.BatchInsertMaxRows(); got != 0 {
|
||||
t.Fatalf("Expected default chunk size 0, got %d", got)
|
||||
}
|
||||
|
||||
db.SetBatchInsertMaxRows(3)
|
||||
if got := db.BatchInsertMaxRows(); got != 3 {
|
||||
t.Fatalf("Expected chunk size 3, got %d", got)
|
||||
}
|
||||
|
||||
db.SetBatchInsertMaxRows(-10)
|
||||
if got := db.BatchInsertMaxRows(); got != 0 {
|
||||
t.Fatalf("Expected chunk size reset to 0, got %d", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_BatchInsertMaxParams_Config(t *testing.T) {
|
||||
db := setupBatchTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
if got := db.BatchInsertMaxParams(); got != 0 {
|
||||
t.Fatalf("Expected default max params 0, got %d", got)
|
||||
}
|
||||
|
||||
db.SetBatchInsertMaxParams(100)
|
||||
if got := db.BatchInsertMaxParams(); got != 100 {
|
||||
t.Fatalf("Expected max params 100, got %d", got)
|
||||
}
|
||||
|
||||
db.SetBatchInsertMaxParams(-1)
|
||||
if got := db.BatchInsertMaxParams(); got != 0 {
|
||||
t.Fatalf("Expected max params reset to 0, got %d", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_BatchInsert_Chunked(t *testing.T) {
|
||||
db := setupBatchTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
db.SetBatchInsertMaxRows(2)
|
||||
|
||||
columns := []string{"name", "email", "age"}
|
||||
values := [][]interface{}{
|
||||
{"Alice", "alice@example.com", 25},
|
||||
{"Bob", "bob@example.com", 30},
|
||||
{"Charlie", "charlie@example.com", 35},
|
||||
{"David", "david@example.com", 40},
|
||||
{"Eva", "eva@example.com", 28},
|
||||
}
|
||||
|
||||
result, err := db.BatchInsert("users", columns, values)
|
||||
if err != nil {
|
||||
t.Fatalf("Chunked BatchInsert failed: %v", err)
|
||||
}
|
||||
|
||||
affected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
t.Fatalf("RowsAffected failed: %v", err)
|
||||
}
|
||||
if affected != int64(len(values)) {
|
||||
t.Fatalf("Expected %d affected rows, got %d", len(values), affected)
|
||||
}
|
||||
|
||||
rows, err := db.Query("SELECT COUNT(*) as count FROM users")
|
||||
if err != nil {
|
||||
t.Fatalf("Query failed: %v", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
if count := rows.Row(0).MustInt("count"); count != len(values) {
|
||||
t.Fatalf("Expected %d rows in db, got %d", len(values), count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_BatchInsert_ChunkedRollbackOnError(t *testing.T) {
|
||||
db := setupBatchTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
db.SetBatchInsertMaxRows(2)
|
||||
|
||||
columns := []string{"name", "email", "age"}
|
||||
values := [][]interface{}{
|
||||
{"Alice", "alice@example.com", 25},
|
||||
{"Bob", "bob@example.com", 30},
|
||||
{"Charlie", nil, 35}, // email NOT NULL, forces second chunk failure
|
||||
}
|
||||
|
||||
if _, err := db.BatchInsert("users", columns, values); err == nil {
|
||||
t.Fatal("Expected chunked BatchInsert to fail, got nil")
|
||||
}
|
||||
|
||||
rows, err := db.Query("SELECT COUNT(*) as count FROM users")
|
||||
if err != nil {
|
||||
t.Fatalf("Query failed: %v", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
if count := rows.Row(0).MustInt("count"); count != 0 {
|
||||
t.Fatalf("Expected rollback to keep table empty, got %d rows", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_BatchInsert_ChunkedByMaxParams(t *testing.T) {
|
||||
db := setupBatchTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
db.SetBatchInsertMaxRows(0) // disabled
|
||||
db.SetBatchInsertMaxParams(4) // 3 columns -> 1 row per chunk
|
||||
|
||||
columns := []string{"name", "email", "age"}
|
||||
values := [][]interface{}{
|
||||
{"Alice", "alice@example.com", 25},
|
||||
{"Bob", "bob@example.com", 30},
|
||||
{"Charlie", "charlie@example.com", 35},
|
||||
}
|
||||
|
||||
result, err := db.BatchInsert("users", columns, values)
|
||||
if err != nil {
|
||||
t.Fatalf("BatchInsert by max params failed: %v", err)
|
||||
}
|
||||
|
||||
affected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
t.Fatalf("RowsAffected failed: %v", err)
|
||||
}
|
||||
if affected != int64(len(values)) {
|
||||
t.Fatalf("Expected %d affected rows, got %d", len(values), affected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_BatchInsert_MaxParamsTooLow(t *testing.T) {
|
||||
db := setupBatchTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
db.SetBatchInsertMaxRows(0)
|
||||
db.SetBatchInsertMaxParams(2) // columns=3 -> invalid
|
||||
|
||||
columns := []string{"name", "email", "age"}
|
||||
values := [][]interface{}{
|
||||
{"Alice", "alice@example.com", 25},
|
||||
}
|
||||
|
||||
_, err := db.BatchInsert("users", columns, values)
|
||||
if !errors.Is(err, stardb.ErrBatchInsertMaxParamsTooLow) {
|
||||
t.Fatalf("Expected ErrBatchInsertMaxParamsTooLow, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_BatchInsert_ChunkedHookMeta(t *testing.T) {
|
||||
db := setupBatchTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
db.SetBatchInsertMaxRows(2)
|
||||
db.SetBatchInsertMaxParams(0)
|
||||
|
||||
var metas []stardb.BatchExecMeta
|
||||
db.SetSQLHooks(nil, func(ctx context.Context, query string, args []interface{}, d time.Duration, err error) {
|
||||
if meta, ok := stardb.BatchExecMetaFromContext(ctx); ok {
|
||||
metas = append(metas, meta)
|
||||
}
|
||||
})
|
||||
|
||||
columns := []string{"name", "email", "age"}
|
||||
values := [][]interface{}{
|
||||
{"Alice", "alice@example.com", 25},
|
||||
{"Bob", "bob@example.com", 30},
|
||||
{"Charlie", "charlie@example.com", 35},
|
||||
{"David", "david@example.com", 40},
|
||||
{"Eva", "eva@example.com", 28},
|
||||
}
|
||||
|
||||
_, err := db.BatchInsertContext(context.Background(), "users", columns, values)
|
||||
if err != nil {
|
||||
t.Fatalf("BatchInsertContext failed: %v", err)
|
||||
}
|
||||
|
||||
if len(metas) != 3 {
|
||||
t.Fatalf("Expected 3 chunk metas, got %d", len(metas))
|
||||
}
|
||||
|
||||
wantRows := []int{2, 2, 1}
|
||||
for i, meta := range metas {
|
||||
if meta.ChunkIndex != i+1 {
|
||||
t.Fatalf("Chunk %d: expected index %d, got %d", i, i+1, meta.ChunkIndex)
|
||||
}
|
||||
if meta.ChunkCount != 3 {
|
||||
t.Fatalf("Chunk %d: expected count 3, got %d", i, meta.ChunkCount)
|
||||
}
|
||||
if meta.ChunkRows != wantRows[i] {
|
||||
t.Fatalf("Chunk %d: expected rows %d, got %d", i, wantRows[i], meta.ChunkRows)
|
||||
}
|
||||
if meta.TotalRows != len(values) {
|
||||
t.Fatalf("Chunk %d: expected total rows %d, got %d", i, len(values), meta.TotalRows)
|
||||
}
|
||||
if meta.ColumnCount != len(columns) {
|
||||
t.Fatalf("Chunk %d: expected column count %d, got %d", i, len(columns), meta.ColumnCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_BatchInsert_HookMetaAbsentWithoutChunking(t *testing.T) {
|
||||
db := setupBatchTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
db.SetBatchInsertMaxRows(0)
|
||||
db.SetBatchInsertMaxParams(0)
|
||||
|
||||
metaFound := false
|
||||
db.SetSQLHooks(nil, func(ctx context.Context, query string, args []interface{}, d time.Duration, err error) {
|
||||
if _, ok := stardb.BatchExecMetaFromContext(ctx); ok {
|
||||
metaFound = true
|
||||
}
|
||||
})
|
||||
|
||||
columns := []string{"name", "email", "age"}
|
||||
values := [][]interface{}{
|
||||
{"Alice", "alice@example.com", 25},
|
||||
{"Bob", "bob@example.com", 30},
|
||||
}
|
||||
|
||||
_, err := db.BatchInsertContext(context.Background(), "users", columns, values)
|
||||
if err != nil {
|
||||
t.Fatalf("BatchInsertContext failed: %v", err)
|
||||
}
|
||||
if metaFound {
|
||||
t.Fatal("Expected no batch meta for non-chunked execution")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_BatchInsertStructs_Basic(t *testing.T) {
|
||||
db := setupBatchTestDB(t)
|
||||
defer db.Close()
|
||||
@@ -246,6 +522,34 @@ func TestStarDB_BatchInsertStructs_Basic(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_BatchInsertStructs_Chunked(t *testing.T) {
|
||||
db := setupBatchTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
db.SetBatchInsertMaxRows(2)
|
||||
|
||||
users := []TestUser{
|
||||
{Name: "Alice", Email: "alice@example.com", Age: 25, CreatedAt: time.Now()},
|
||||
{Name: "Bob", Email: "bob@example.com", Age: 30, CreatedAt: time.Now()},
|
||||
{Name: "Charlie", Email: "charlie@example.com", Age: 35, CreatedAt: time.Now()},
|
||||
{Name: "David", Email: "david@example.com", Age: 40, CreatedAt: time.Now()},
|
||||
{Name: "Eva", Email: "eva@example.com", Age: 28, CreatedAt: time.Now()},
|
||||
}
|
||||
|
||||
result, err := db.BatchInsertStructs("users", users, "id")
|
||||
if err != nil {
|
||||
t.Fatalf("Chunked BatchInsertStructs failed: %v", err)
|
||||
}
|
||||
|
||||
affected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
t.Fatalf("RowsAffected failed: %v", err)
|
||||
}
|
||||
if affected != int64(len(users)) {
|
||||
t.Fatalf("Expected %d affected rows, got %d", len(users), affected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_BatchInsertStructs_Single(t *testing.T) {
|
||||
db := setupBatchTestDB(t)
|
||||
defer db.Close()
|
||||
@@ -276,8 +580,8 @@ func TestStarDB_BatchInsertStructs_Empty(t *testing.T) {
|
||||
users := []TestUser{}
|
||||
|
||||
_, err := db.BatchInsertStructs("users", users, "id")
|
||||
if err == nil {
|
||||
t.Error("Expected error with empty slice, got nil")
|
||||
if !errors.Is(err, stardb.ErrNoStructsToInsert) {
|
||||
t.Errorf("Expected ErrNoStructsToInsert, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,8 +592,29 @@ func TestStarDB_BatchInsertStructs_NotSlice(t *testing.T) {
|
||||
user := TestUser{Name: "Alice", Email: "alice@example.com", Age: 25}
|
||||
|
||||
_, err := db.BatchInsertStructs("users", user, "id")
|
||||
if err == nil {
|
||||
t.Error("Expected error with non-slice, got nil")
|
||||
if !errors.Is(err, stardb.ErrStructsNotSlice) {
|
||||
t.Errorf("Expected ErrStructsNotSlice, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_BatchInsertStructs_Nil(t *testing.T) {
|
||||
db := setupBatchTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
_, err := db.BatchInsertStructs("users", nil, "id")
|
||||
if !errors.Is(err, stardb.ErrStructsNil) {
|
||||
t.Errorf("Expected ErrStructsNil, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_BatchInsertStructs_NilPointer(t *testing.T) {
|
||||
db := setupBatchTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
var users *[]TestUser
|
||||
_, err := db.BatchInsertStructs("users", users, "id")
|
||||
if !errors.Is(err, stardb.ErrStructsPointerNil) {
|
||||
t.Errorf("Expected ErrStructsPointerNil, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+244
-1
@@ -2,8 +2,11 @@ package testing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"b612.me/stardb"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
@@ -28,6 +31,12 @@ type NestedUser struct {
|
||||
Profile `db:"---"`
|
||||
}
|
||||
|
||||
type UserWithPrivateField struct {
|
||||
ID int64 `db:"id"`
|
||||
Name string `db:"name"`
|
||||
age int `db:"age"`
|
||||
}
|
||||
|
||||
func TestStarRows_Orm_Single(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
@@ -93,6 +102,47 @@ func TestStarRows_Orm_Multiple(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarRows_Orm_Array(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
rows, err := db.Query("SELECT * FROM users ORDER BY name")
|
||||
if err != nil {
|
||||
t.Fatalf("Query failed: %v", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var users [3]User
|
||||
err = rows.Orm(&users)
|
||||
if err != nil {
|
||||
t.Fatalf("Orm failed: %v", err)
|
||||
}
|
||||
|
||||
expectedNames := []string{"Alice", "Bob", "Charlie"}
|
||||
for i, user := range users {
|
||||
if user.Name != expectedNames[i] {
|
||||
t.Errorf("Expected name '%s', got '%s'", expectedNames[i], user.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarRows_Orm_ArrayTooSmall(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
rows, err := db.Query("SELECT * FROM users ORDER BY name")
|
||||
if err != nil {
|
||||
t.Fatalf("Query failed: %v", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var users [2]User
|
||||
err = rows.Orm(&users)
|
||||
if err == nil {
|
||||
t.Error("Expected error when target array is smaller than row count, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarRows_Orm_Empty(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
@@ -126,8 +176,105 @@ func TestStarRows_Orm_NotPointer(t *testing.T) {
|
||||
|
||||
var user User
|
||||
err = rows.Orm(user) // Not a pointer
|
||||
if !errors.Is(err, stardb.ErrTargetNotPointer) {
|
||||
t.Errorf("Expected ErrTargetNotPointer, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarRows_Orm_NilTarget(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
rows, err := db.Query("SELECT * FROM users WHERE name = ?", "Alice")
|
||||
if err != nil {
|
||||
t.Fatalf("Query failed: %v", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
err = rows.Orm(nil)
|
||||
if !errors.Is(err, stardb.ErrTargetNil) {
|
||||
t.Errorf("Expected ErrTargetNil, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarRows_Orm_NilPointerTarget(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
rows, err := db.Query("SELECT * FROM users WHERE name = ?", "Alice")
|
||||
if err != nil {
|
||||
t.Fatalf("Query failed: %v", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var user *User
|
||||
err = rows.Orm(user)
|
||||
if !errors.Is(err, stardb.ErrTargetPointerNil) {
|
||||
t.Errorf("Expected ErrTargetPointerNil, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarRows_Orm_MissingColumns_NonStrict(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
rows, err := db.Query("SELECT id, name FROM users WHERE name = ?", "Alice")
|
||||
if err != nil {
|
||||
t.Fatalf("Query failed: %v", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var user User
|
||||
err = rows.Orm(&user)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected non-strict ORM to ignore missing columns, got error: %v", err)
|
||||
}
|
||||
|
||||
if user.Name != "Alice" {
|
||||
t.Errorf("Expected name 'Alice', got '%s'", user.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarRows_Orm_MissingColumns_Strict(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
db.SetStrictORM(true)
|
||||
|
||||
rows, err := db.Query("SELECT id, name FROM users WHERE name = ?", "Alice")
|
||||
if err != nil {
|
||||
t.Fatalf("Query failed: %v", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var user User
|
||||
err = rows.Orm(&user)
|
||||
if err == nil {
|
||||
t.Errorf("Expected error when passing non-pointer, got nil")
|
||||
t.Fatalf("Expected strict ORM to fail on missing columns, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarRows_Orm_UnexportedTaggedField(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
rows, err := db.Query("SELECT * FROM users WHERE name = ?", "Alice")
|
||||
if err != nil {
|
||||
t.Fatalf("Query failed: %v", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var user UserWithPrivateField
|
||||
err = rows.Orm(&user)
|
||||
if err != nil {
|
||||
t.Fatalf("Orm failed: %v", err)
|
||||
}
|
||||
|
||||
if user.ID <= 0 {
|
||||
t.Errorf("Expected positive ID, got %d", user.ID)
|
||||
}
|
||||
if user.Name != "Alice" {
|
||||
t.Errorf("Expected name 'Alice', got '%s'", user.Name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,6 +502,28 @@ func TestStarDB_QueryXContext(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_QueryX_MissingField(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
user := User{Name: "Alice"}
|
||||
|
||||
_, err := db.QueryX(&user, "SELECT * FROM users WHERE name = ?", ":unknown")
|
||||
if !errors.Is(err, stardb.ErrFieldNotFound) {
|
||||
t.Errorf("Expected ErrFieldNotFound, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_QueryX_NilTarget(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
_, err := db.QueryX(nil, "SELECT * FROM users WHERE id = ?", ":id")
|
||||
if !errors.Is(err, stardb.ErrTargetNil) {
|
||||
t.Errorf("Expected ErrTargetNil, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_QueryXS(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
@@ -380,6 +549,16 @@ func TestStarDB_QueryXS(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_QueryXS_NilTargets(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
_, err := db.QueryXS(nil, "SELECT * FROM users")
|
||||
if !errors.Is(err, stardb.ErrTargetsNil) {
|
||||
t.Errorf("Expected ErrTargetsNil, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_ExecX(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
@@ -441,6 +620,28 @@ func TestStarDB_ExecXContext(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_ExecX_MissingField(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
user := User{Name: "Alice", Age: 99}
|
||||
|
||||
_, err := db.ExecX(&user, "UPDATE users SET age = ? WHERE name = ?", ":age", ":unknown")
|
||||
if !errors.Is(err, stardb.ErrFieldNotFound) {
|
||||
t.Errorf("Expected ErrFieldNotFound, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_ExecX_NilTarget(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
_, err := db.ExecX(nil, "UPDATE users SET age = ? WHERE id = ?", ":age", ":id")
|
||||
if !errors.Is(err, stardb.ErrTargetNil) {
|
||||
t.Errorf("Expected ErrTargetNil, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_ExecXS(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
@@ -470,6 +671,16 @@ func TestStarDB_ExecXS(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_ExecXS_NilTargets(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
_, err := db.ExecXS(nil, "UPDATE users SET age = age")
|
||||
if !errors.Is(err, stardb.ErrTargetsNil) {
|
||||
t.Errorf("Expected ErrTargetsNil, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarTx_Insert(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
@@ -592,6 +803,22 @@ func TestStarTx_QueryX(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarTx_QueryX_NilTarget(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Fatalf("Begin failed: %v", err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
_, err = tx.QueryX(nil, "SELECT * FROM users WHERE id = ?", ":id")
|
||||
if !errors.Is(err, stardb.ErrTargetNil) {
|
||||
t.Errorf("Expected ErrTargetNil, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarTx_ExecX(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
@@ -627,6 +854,22 @@ func TestStarTx_ExecX(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarTx_ExecX_NilTarget(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Fatalf("Begin failed: %v", err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
_, err = tx.ExecX(nil, "UPDATE users SET age = ? WHERE id = ?", ":age", ":id")
|
||||
if !errors.Is(err, stardb.ErrTargetNil) {
|
||||
t.Errorf("Expected ErrTargetNil, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarTx_Rollback(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
package testing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"b612.me/stardb"
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
func setupBenchmarkDB(b *testing.B) *stardb.StarDB {
|
||||
b.Helper()
|
||||
|
||||
db := &stardb.StarDB{}
|
||||
if err := db.Open("sqlite", ":memory:"); err != nil {
|
||||
b.Fatalf("Failed to open database: %v", err)
|
||||
}
|
||||
|
||||
_, err := db.Exec(`
|
||||
CREATE TABLE users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
email TEXT NOT NULL,
|
||||
age INTEGER,
|
||||
balance REAL,
|
||||
active BOOLEAN,
|
||||
created_at DATETIME
|
||||
)
|
||||
`)
|
||||
if err != nil {
|
||||
b.Fatalf("Failed to create table: %v", err)
|
||||
}
|
||||
|
||||
_, err = db.Exec(`
|
||||
INSERT INTO users (name, email, age, balance, active, created_at) VALUES
|
||||
('Alice', 'alice@example.com', 25, 100.50, 1, '2024-01-01 10:00:00'),
|
||||
('Bob', 'bob@example.com', 30, 200.75, 1, '2024-01-02 11:00:00'),
|
||||
('Charlie', 'charlie@example.com', 35, 300.25, 0, '2024-01-03 12:00:00')
|
||||
`)
|
||||
if err != nil {
|
||||
b.Fatalf("Failed to insert seed data: %v", err)
|
||||
}
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
func BenchmarkQueryX(b *testing.B) {
|
||||
db := setupBenchmarkDB(b)
|
||||
defer db.Close()
|
||||
|
||||
target := User{Name: "Alice"}
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
rows, err := db.QueryX(&target, "SELECT * FROM users WHERE name = ?", ":name")
|
||||
if err != nil {
|
||||
b.Fatalf("QueryX failed: %v", err)
|
||||
}
|
||||
_ = rows.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkOrm(b *testing.B) {
|
||||
db := setupBenchmarkDB(b)
|
||||
defer db.Close()
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
rows, err := db.Query("SELECT * FROM users ORDER BY name")
|
||||
if err != nil {
|
||||
b.Fatalf("Query failed: %v", err)
|
||||
}
|
||||
|
||||
var users []User
|
||||
if err := rows.Orm(&users); err != nil {
|
||||
_ = rows.Close()
|
||||
b.Fatalf("Orm failed: %v", err)
|
||||
}
|
||||
_ = rows.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkScanEach(b *testing.B) {
|
||||
db := setupBenchmarkDB(b)
|
||||
defer db.Close()
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
count := 0
|
||||
err := db.ScanEach("SELECT * FROM users ORDER BY name", func(row *stardb.StarResult) error {
|
||||
_ = row.MustString("name")
|
||||
count++
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
b.Fatalf("ScanEach failed: %v", err)
|
||||
}
|
||||
if count != 3 {
|
||||
b.Fatalf("Unexpected row count: %d", count)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBatchInsert(b *testing.B) {
|
||||
db := setupBenchmarkDB(b)
|
||||
defer db.Close()
|
||||
|
||||
columns := []string{"name", "email", "age", "balance", "active", "created_at"}
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
base := i * 2
|
||||
values := [][]interface{}{
|
||||
{fmt.Sprintf("bench_user_%d", base), fmt.Sprintf("bench_%d@example.com", base), 20 + (base % 20), 99.5, true, time.Now()},
|
||||
{fmt.Sprintf("bench_user_%d", base+1), fmt.Sprintf("bench_%d@example.com", base+1), 20 + ((base + 1) % 20), 199.5, false, time.Now()},
|
||||
}
|
||||
if _, err := db.BatchInsert("users", columns, values); err != nil {
|
||||
b.Fatalf("BatchInsert failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,6 +95,32 @@ func TestStarDB_SetPoolConfig_Zero(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_SetPoolConfig_NilConfig(t *testing.T) {
|
||||
db := stardb.NewStarDB()
|
||||
err := db.Open("sqlite", ":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to open database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
db.SetPoolConfig(nil)
|
||||
|
||||
err = db.Ping()
|
||||
if err != nil {
|
||||
t.Errorf("Ping failed after SetPoolConfig(nil): %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_SetPoolConfig_BeforeOpen(t *testing.T) {
|
||||
db := stardb.NewStarDB()
|
||||
|
||||
db.SetPoolConfig(&stardb.PoolConfig{
|
||||
MaxOpenConns: 10,
|
||||
})
|
||||
|
||||
// should not panic when called before Open
|
||||
}
|
||||
|
||||
func TestOpenWithPool_Default(t *testing.T) {
|
||||
db, err := stardb.OpenWithPool("sqlite", ":memory:", nil)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package testing
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"b612.me/stardb"
|
||||
)
|
||||
|
||||
func TestStarResult_MustString(t *testing.T) {
|
||||
@@ -229,3 +232,100 @@ func TestStarResultCol_MustBool(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarResult_GetColumnNotFoundError(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
rows, err := db.Query("SELECT * FROM users WHERE name = ?", "Alice")
|
||||
if err != nil {
|
||||
t.Fatalf("Query failed: %v", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
row := rows.Row(0)
|
||||
_, err = row.GetString("does_not_exist")
|
||||
if !errors.Is(err, stardb.ErrColumnNotFound) {
|
||||
t.Fatalf("Expected ErrColumnNotFound, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarResult_GetNullValues(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
_, err := db.Exec(
|
||||
"INSERT INTO users (name, email, age, balance, active, created_at) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
"NullUser", "null@example.com", nil, nil, nil, nil,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Insert failed: %v", err)
|
||||
}
|
||||
|
||||
rows, err := db.Query("SELECT * FROM users WHERE name = ?", "NullUser")
|
||||
if err != nil {
|
||||
t.Fatalf("Query failed: %v", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
row := rows.Row(0)
|
||||
|
||||
name, err := row.GetNullString("name")
|
||||
if err != nil {
|
||||
t.Fatalf("GetNullString failed: %v", err)
|
||||
}
|
||||
if !name.Valid || name.String != "NullUser" {
|
||||
t.Fatalf("Expected valid name NullUser, got %+v", name)
|
||||
}
|
||||
|
||||
age, err := row.GetNullInt64("age")
|
||||
if err != nil {
|
||||
t.Fatalf("GetNullInt64 failed: %v", err)
|
||||
}
|
||||
if age.Valid {
|
||||
t.Fatalf("Expected NULL age, got %+v", age)
|
||||
}
|
||||
|
||||
balance, err := row.GetNullFloat64("balance")
|
||||
if err != nil {
|
||||
t.Fatalf("GetNullFloat64 failed: %v", err)
|
||||
}
|
||||
if balance.Valid {
|
||||
t.Fatalf("Expected NULL balance, got %+v", balance)
|
||||
}
|
||||
|
||||
active, err := row.GetNullBool("active")
|
||||
if err != nil {
|
||||
t.Fatalf("GetNullBool failed: %v", err)
|
||||
}
|
||||
if active.Valid {
|
||||
t.Fatalf("Expected NULL active, got %+v", active)
|
||||
}
|
||||
|
||||
createdAt, err := row.GetNullTime("created_at")
|
||||
if err != nil {
|
||||
t.Fatalf("GetNullTime failed: %v", err)
|
||||
}
|
||||
if createdAt.Valid {
|
||||
t.Fatalf("Expected NULL created_at, got %+v", createdAt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarResult_GetNullTime_Valid(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
rows, err := db.Query("SELECT created_at FROM users WHERE name = ?", "Alice")
|
||||
if err != nil {
|
||||
t.Fatalf("Query failed: %v", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
value, err := rows.Row(0).GetNullTime("created_at")
|
||||
if err != nil {
|
||||
t.Fatalf("GetNullTime failed: %v", err)
|
||||
}
|
||||
if !value.Valid {
|
||||
t.Fatal("Expected valid created_at")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,12 @@ func TestStarRows_Row(t *testing.T) {
|
||||
if len(row.Result()) != 0 {
|
||||
t.Errorf("Expected empty result for out of bounds index")
|
||||
}
|
||||
|
||||
// Test negative index
|
||||
row = rows.Row(-1)
|
||||
if len(row.Result()) != 0 {
|
||||
t.Errorf("Expected empty result for negative index")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarRows_Col(t *testing.T) {
|
||||
|
||||
@@ -2,6 +2,10 @@ package testing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -60,6 +64,457 @@ func TestStarDB_QueryContext(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_QueryRaw(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
rows, err := db.QueryRaw("SELECT name FROM users WHERE age > ? ORDER BY name", 25)
|
||||
if err != nil {
|
||||
t.Fatalf("QueryRaw failed: %v", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var (
|
||||
name string
|
||||
count int
|
||||
)
|
||||
for rows.Next() {
|
||||
if err := rows.Scan(&name); err != nil {
|
||||
t.Fatalf("Scan failed: %v", err)
|
||||
}
|
||||
count++
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
t.Fatalf("Rows.Err failed: %v", err)
|
||||
}
|
||||
if count != 2 {
|
||||
t.Errorf("Expected 2 rows, got %d", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_QueryRawContext(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
rows, err := db.QueryRawContext(ctx, "SELECT name FROM users WHERE name = ?", "Alice")
|
||||
if err != nil {
|
||||
t.Fatalf("QueryRawContext failed: %v", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
if !rows.Next() {
|
||||
t.Fatal("Expected at least one row")
|
||||
}
|
||||
var name string
|
||||
if err := rows.Scan(&name); err != nil {
|
||||
t.Fatalf("Scan failed: %v", err)
|
||||
}
|
||||
if name != "Alice" {
|
||||
t.Errorf("Expected name Alice, got %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_QueryRaw_EmptyQuery(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
_, err := db.QueryRaw(" ")
|
||||
if !errors.Is(err, stardb.ErrQueryEmpty) {
|
||||
t.Fatalf("Expected ErrQueryEmpty, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_ScanEach(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
var names []string
|
||||
err := db.ScanEach("SELECT name FROM users ORDER BY name", func(row *stardb.StarResult) error {
|
||||
names = append(names, row.MustString("name"))
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("ScanEach failed: %v", err)
|
||||
}
|
||||
|
||||
if len(names) != 3 {
|
||||
t.Fatalf("Expected 3 rows, got %d", len(names))
|
||||
}
|
||||
if names[0] != "Alice" || names[1] != "Bob" || names[2] != "Charlie" {
|
||||
t.Fatalf("Unexpected names: %v", names)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_ScanEach_Stop(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
count := 0
|
||||
err := db.ScanEach("SELECT name FROM users ORDER BY name", func(row *stardb.StarResult) error {
|
||||
count++
|
||||
if row.MustString("name") == "Bob" {
|
||||
return stardb.ErrScanStopped
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("ScanEach stop failed: %v", err)
|
||||
}
|
||||
if count != 2 {
|
||||
t.Fatalf("Expected callback count 2, got %d", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_ScanEach_NilCallback(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
err := db.ScanEach("SELECT name FROM users", nil)
|
||||
if !errors.Is(err, stardb.ErrScanFuncNil) {
|
||||
t.Fatalf("Expected ErrScanFuncNil, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_ScanEachORM(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
var user User
|
||||
var names []string
|
||||
err := db.ScanEachORM("SELECT * FROM users ORDER BY name", &user, func(target interface{}) error {
|
||||
u := target.(*User)
|
||||
names = append(names, u.Name)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("ScanEachORM failed: %v", err)
|
||||
}
|
||||
|
||||
if len(names) != 3 {
|
||||
t.Fatalf("Expected 3 names, got %d", len(names))
|
||||
}
|
||||
if names[0] != "Alice" || names[1] != "Bob" || names[2] != "Charlie" {
|
||||
t.Fatalf("Unexpected names: %v", names)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_ScanEachORM_NilCallback(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
var user User
|
||||
err := db.ScanEachORM("SELECT * FROM users", &user, nil)
|
||||
if !errors.Is(err, stardb.ErrScanORMFuncNil) {
|
||||
t.Fatalf("Expected ErrScanORMFuncNil, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_PlaceholderDollar(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
db.SetPlaceholderStyle(stardb.PlaceholderDollar)
|
||||
|
||||
rows, err := db.Query("SELECT name FROM users WHERE name = ? AND age = ?", "Alice", 25)
|
||||
if err != nil {
|
||||
t.Fatalf("Query with dollar placeholders failed: %v", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
if rows.Length() != 1 {
|
||||
t.Fatalf("Expected 1 row, got %d", rows.Length())
|
||||
}
|
||||
if got := rows.Row(0).MustString("name"); got != "Alice" {
|
||||
t.Fatalf("Expected Alice, got %s", got)
|
||||
}
|
||||
|
||||
_, err = db.Exec("UPDATE users SET age = ? WHERE name = ?", 26, "Alice")
|
||||
if err != nil {
|
||||
t.Fatalf("Exec with dollar placeholders failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_SQLHooks(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
db.SetPlaceholderStyle(stardb.PlaceholderDollar)
|
||||
|
||||
var beforeCount int64
|
||||
var afterCount int64
|
||||
var mu sync.Mutex
|
||||
var beforeQuery string
|
||||
var afterQuery string
|
||||
var afterErr error
|
||||
|
||||
db.SetSQLHooks(
|
||||
func(ctx context.Context, query string, args []interface{}) {
|
||||
atomic.AddInt64(&beforeCount, 1)
|
||||
mu.Lock()
|
||||
beforeQuery = query
|
||||
mu.Unlock()
|
||||
},
|
||||
func(ctx context.Context, query string, args []interface{}, duration time.Duration, err error) {
|
||||
atomic.AddInt64(&afterCount, 1)
|
||||
mu.Lock()
|
||||
afterQuery = query
|
||||
afterErr = err
|
||||
mu.Unlock()
|
||||
},
|
||||
)
|
||||
|
||||
if _, err := db.Exec("UPDATE users SET age = ? WHERE name = ?", 31, "Bob"); err != nil {
|
||||
t.Fatalf("Exec failed: %v", err)
|
||||
}
|
||||
|
||||
if atomic.LoadInt64(&beforeCount) == 0 {
|
||||
t.Fatal("Expected before hook to be called")
|
||||
}
|
||||
if atomic.LoadInt64(&afterCount) == 0 {
|
||||
t.Fatal("Expected after hook to be called")
|
||||
}
|
||||
if !strings.Contains(beforeQuery, "$1") {
|
||||
t.Fatalf("Expected converted query in before hook, got %s", beforeQuery)
|
||||
}
|
||||
if !strings.Contains(afterQuery, "$1") {
|
||||
t.Fatalf("Expected converted query in after hook, got %s", afterQuery)
|
||||
}
|
||||
if afterErr != nil {
|
||||
t.Fatalf("Expected nil error in after hook, got %v", afterErr)
|
||||
}
|
||||
|
||||
_, execErr := db.Exec("UPDATE table_does_not_exist SET age = ? WHERE name = ?", 31, "Bob")
|
||||
if execErr == nil {
|
||||
t.Fatal("Expected execution error for invalid table")
|
||||
}
|
||||
if atomic.LoadInt64(&afterCount) < 2 {
|
||||
t.Fatalf("Expected after hook call count >= 2, got %d", atomic.LoadInt64(&afterCount))
|
||||
}
|
||||
if afterErr == nil {
|
||||
t.Fatal("Expected after hook to capture non-nil error for failed SQL")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_SQLHooks_SlowThreshold(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
var afterCount int64
|
||||
db.SetSQLHooks(
|
||||
nil,
|
||||
func(ctx context.Context, query string, args []interface{}, duration time.Duration, err error) {
|
||||
atomic.AddInt64(&afterCount, 1)
|
||||
},
|
||||
)
|
||||
|
||||
db.SetSQLSlowThreshold(time.Hour)
|
||||
if got := db.SQLSlowThreshold(); got != time.Hour {
|
||||
t.Fatalf("Expected threshold 1h, got %v", got)
|
||||
}
|
||||
|
||||
if _, err := db.Exec("UPDATE users SET age = ? WHERE name = ?", 41, "Alice"); err != nil {
|
||||
t.Fatalf("Exec failed: %v", err)
|
||||
}
|
||||
if atomic.LoadInt64(&afterCount) != 0 {
|
||||
t.Fatalf("Expected after hook to be skipped under threshold, got %d", afterCount)
|
||||
}
|
||||
|
||||
_, err := db.Exec("UPDATE table_does_not_exist SET age = ? WHERE name = ?", 31, "Bob")
|
||||
if err == nil {
|
||||
t.Fatal("Expected execution error for invalid table")
|
||||
}
|
||||
if atomic.LoadInt64(&afterCount) != 1 {
|
||||
t.Fatalf("Expected error path to trigger after hook, got %d", afterCount)
|
||||
}
|
||||
|
||||
db.SetSQLSlowThreshold(0)
|
||||
if _, err := db.Exec("UPDATE users SET age = ? WHERE name = ?", 42, "Alice"); err != nil {
|
||||
t.Fatalf("Exec failed: %v", err)
|
||||
}
|
||||
if atomic.LoadInt64(&afterCount) != 2 {
|
||||
t.Fatalf("Expected after hook to run after disabling threshold, got %d", afterCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_SQLHooks_FingerprintMeta(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
db.SetSQLFingerprintEnabled(true)
|
||||
|
||||
var gotMeta stardb.SQLHookMeta
|
||||
var metaFound bool
|
||||
db.SetSQLHooks(
|
||||
nil,
|
||||
func(ctx context.Context, query string, args []interface{}, duration time.Duration, err error) {
|
||||
gotMeta, metaFound = stardb.SQLHookMetaFromContext(ctx)
|
||||
},
|
||||
)
|
||||
|
||||
if _, err := db.Exec("UPDATE users SET age = ? WHERE name = ?", 31, "Bob"); err != nil {
|
||||
t.Fatalf("Exec failed: %v", err)
|
||||
}
|
||||
|
||||
if !metaFound {
|
||||
t.Fatal("Expected SQL fingerprint metadata in hook context")
|
||||
}
|
||||
if gotMeta.Fingerprint != "update users set age = ? where name = ?" {
|
||||
t.Fatalf("Unexpected fingerprint: %q", gotMeta.Fingerprint)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_SQLHooks_FingerprintMetaMaskLiterals(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
db.SetSQLFingerprintEnabled(true)
|
||||
db.SetSQLFingerprintMode(stardb.SQLFingerprintMaskLiterals)
|
||||
|
||||
var gotMeta stardb.SQLHookMeta
|
||||
var metaFound bool
|
||||
db.SetSQLHooks(
|
||||
nil,
|
||||
func(ctx context.Context, query string, args []interface{}, duration time.Duration, err error) {
|
||||
gotMeta, metaFound = stardb.SQLHookMetaFromContext(ctx)
|
||||
},
|
||||
)
|
||||
|
||||
if _, err := db.Exec("UPDATE users SET age = 42 WHERE name = 'Bob' AND age < 100"); err != nil {
|
||||
t.Fatalf("Exec failed: %v", err)
|
||||
}
|
||||
|
||||
if !metaFound {
|
||||
t.Fatal("Expected SQL fingerprint metadata in hook context")
|
||||
}
|
||||
if gotMeta.Fingerprint != "update users set age = ? where name = ? and age < ?" {
|
||||
t.Fatalf("Unexpected fingerprint for mask mode: %q", gotMeta.Fingerprint)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_SQLHooks_FingerprintMetaMaskLiterals_StripComments(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
db.SetSQLFingerprintEnabled(true)
|
||||
db.SetSQLFingerprintMode(stardb.SQLFingerprintMaskLiterals)
|
||||
db.SetSQLFingerprintKeepComments(false) // default, explicit for clarity
|
||||
|
||||
var gotMeta stardb.SQLHookMeta
|
||||
var metaFound bool
|
||||
db.SetSQLHooks(
|
||||
nil,
|
||||
func(ctx context.Context, query string, args []interface{}, duration time.Duration, err error) {
|
||||
gotMeta, metaFound = stardb.SQLHookMetaFromContext(ctx)
|
||||
},
|
||||
)
|
||||
|
||||
if _, err := db.Exec("UPDATE users SET age = 42 /* trace:abc */ WHERE name = 'Bob'"); err != nil {
|
||||
t.Fatalf("Exec failed: %v", err)
|
||||
}
|
||||
|
||||
if !metaFound {
|
||||
t.Fatal("Expected SQL fingerprint metadata in hook context")
|
||||
}
|
||||
if gotMeta.Fingerprint != "update users set age = ? where name = ?" {
|
||||
t.Fatalf("Unexpected fingerprint with stripped comments: %q", gotMeta.Fingerprint)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_SQLHooks_FingerprintMetaMaskLiterals_KeepComments(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
db.SetSQLFingerprintEnabled(true)
|
||||
db.SetSQLFingerprintMode(stardb.SQLFingerprintMaskLiterals)
|
||||
db.SetSQLFingerprintKeepComments(true)
|
||||
|
||||
var gotMeta stardb.SQLHookMeta
|
||||
var metaFound bool
|
||||
db.SetSQLHooks(
|
||||
nil,
|
||||
func(ctx context.Context, query string, args []interface{}, duration time.Duration, err error) {
|
||||
gotMeta, metaFound = stardb.SQLHookMetaFromContext(ctx)
|
||||
},
|
||||
)
|
||||
|
||||
if _, err := db.Exec("UPDATE users SET age = 42 /* trace:abc */ WHERE name = 'Bob'"); err != nil {
|
||||
t.Fatalf("Exec failed: %v", err)
|
||||
}
|
||||
|
||||
if !metaFound {
|
||||
t.Fatal("Expected SQL fingerprint metadata in hook context")
|
||||
}
|
||||
if gotMeta.Fingerprint != "update users set age = ? /* trace:abc */ where name = ?" {
|
||||
t.Fatalf("Unexpected fingerprint with kept comments: %q", gotMeta.Fingerprint)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_SQLFingerprintCounter(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
db.SetSQLFingerprintCounterEnabled(true)
|
||||
db.SetSQLFingerprintMode(stardb.SQLFingerprintMaskLiterals)
|
||||
db.SetSQLFingerprintKeepComments(false)
|
||||
|
||||
if _, err := db.Exec("UPDATE users SET age = 41 WHERE name = 'Alice'"); err != nil {
|
||||
t.Fatalf("Exec failed: %v", err)
|
||||
}
|
||||
if _, err := db.Exec("UPDATE users SET age = 42 WHERE name = 'Bob'"); err != nil {
|
||||
t.Fatalf("Exec failed: %v", err)
|
||||
}
|
||||
|
||||
counters := db.SQLFingerprintCounters()
|
||||
key := "update users set age = ? where name = ?"
|
||||
if counters[key] != 2 {
|
||||
t.Fatalf("Expected fingerprint %q count=2, got %d", key, counters[key])
|
||||
}
|
||||
|
||||
db.ResetSQLFingerprintCounters()
|
||||
if got := len(db.SQLFingerprintCounters()); got != 0 {
|
||||
t.Fatalf("Expected counters to be reset, got %d", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_SQLFingerprintCounterDisabled(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
db.SetSQLFingerprintCounterEnabled(false)
|
||||
if _, err := db.Exec("UPDATE users SET age = 31 WHERE name = ?", "Bob"); err != nil {
|
||||
t.Fatalf("Exec failed: %v", err)
|
||||
}
|
||||
|
||||
if got := len(db.SQLFingerprintCounters()); got != 0 {
|
||||
t.Fatalf("Expected no counters when disabled, got %d", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_SQLHooks_FingerprintMetaDisabled(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
db.SetSQLFingerprintEnabled(false)
|
||||
|
||||
metaFound := false
|
||||
db.SetSQLHooks(
|
||||
nil,
|
||||
func(ctx context.Context, query string, args []interface{}, duration time.Duration, err error) {
|
||||
_, metaFound = stardb.SQLHookMetaFromContext(ctx)
|
||||
},
|
||||
)
|
||||
|
||||
if _, err := db.Exec("UPDATE users SET age = ? WHERE name = ?", 31, "Bob"); err != nil {
|
||||
t.Fatalf("Exec failed: %v", err)
|
||||
}
|
||||
if metaFound {
|
||||
t.Fatal("Expected no SQL fingerprint metadata when disabled")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_Exec(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
@@ -154,6 +609,119 @@ func TestStarDB_Prepare(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarStmt_QueryRaw(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
stmt, err := db.Prepare("SELECT name FROM users WHERE name = ?")
|
||||
if err != nil {
|
||||
t.Fatalf("Prepare failed: %v", err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
rows, err := stmt.QueryRaw("Bob")
|
||||
if err != nil {
|
||||
t.Fatalf("Stmt.QueryRaw failed: %v", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
if !rows.Next() {
|
||||
t.Fatal("Expected one row for Bob")
|
||||
}
|
||||
var name string
|
||||
if err := rows.Scan(&name); err != nil {
|
||||
t.Fatalf("Scan failed: %v", err)
|
||||
}
|
||||
if name != "Bob" {
|
||||
t.Errorf("Expected Bob, got %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarStmt_ScanEach(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
stmt, err := db.Prepare("SELECT name FROM users WHERE age >= ? ORDER BY name")
|
||||
if err != nil {
|
||||
t.Fatalf("Prepare failed: %v", err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
var names []string
|
||||
err = stmt.ScanEach(func(row *stardb.StarResult) error {
|
||||
names = append(names, row.MustString("name"))
|
||||
return nil
|
||||
}, 30)
|
||||
if err != nil {
|
||||
t.Fatalf("Stmt.ScanEach failed: %v", err)
|
||||
}
|
||||
if len(names) != 2 || names[0] != "Bob" || names[1] != "Charlie" {
|
||||
t.Fatalf("Unexpected names: %v", names)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarStmt_ScanEachORM(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
stmt, err := db.Prepare("SELECT * FROM users WHERE age >= ? ORDER BY name")
|
||||
if err != nil {
|
||||
t.Fatalf("Prepare failed: %v", err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
var user User
|
||||
var names []string
|
||||
err = stmt.ScanEachORM(&user, func(target interface{}) error {
|
||||
u := target.(*User)
|
||||
names = append(names, u.Name)
|
||||
return nil
|
||||
}, 30)
|
||||
if err != nil {
|
||||
t.Fatalf("Stmt.ScanEachORM failed: %v", err)
|
||||
}
|
||||
if len(names) != 2 || names[0] != "Bob" || names[1] != "Charlie" {
|
||||
t.Fatalf("Unexpected names: %v", names)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarStmt_SQLHooks(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
var (
|
||||
beforeCount int64
|
||||
afterCount int64
|
||||
)
|
||||
db.SetSQLHooks(
|
||||
func(ctx context.Context, query string, args []interface{}) {
|
||||
atomic.AddInt64(&beforeCount, 1)
|
||||
},
|
||||
func(ctx context.Context, query string, args []interface{}, duration time.Duration, err error) {
|
||||
atomic.AddInt64(&afterCount, 1)
|
||||
},
|
||||
)
|
||||
|
||||
stmt, err := db.Prepare("SELECT name FROM users WHERE name = ?")
|
||||
if err != nil {
|
||||
t.Fatalf("Prepare failed: %v", err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
rows, err := stmt.Query("Alice")
|
||||
if err != nil {
|
||||
t.Fatalf("Stmt.Query failed: %v", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
if rows.Length() != 1 {
|
||||
t.Fatalf("Expected 1 row, got %d", rows.Length())
|
||||
}
|
||||
if atomic.LoadInt64(&beforeCount) == 0 || atomic.LoadInt64(&afterCount) == 0 {
|
||||
t.Fatalf("Expected stmt execution to trigger hooks, before=%d after=%d", beforeCount, afterCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_Transaction(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
@@ -219,6 +787,131 @@ func TestStarDB_TransactionRollback(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarTx_QueryRaw(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Fatalf("Begin failed: %v", err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
rows, err := tx.QueryRaw("SELECT name FROM users WHERE name = ?", "Charlie")
|
||||
if err != nil {
|
||||
t.Fatalf("Tx.QueryRaw failed: %v", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
if !rows.Next() {
|
||||
t.Fatal("Expected one row for Charlie")
|
||||
}
|
||||
var name string
|
||||
if err := rows.Scan(&name); err != nil {
|
||||
t.Fatalf("Scan failed: %v", err)
|
||||
}
|
||||
if name != "Charlie" {
|
||||
t.Errorf("Expected Charlie, got %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarTx_ScanEach(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Fatalf("Begin failed: %v", err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
var names []string
|
||||
err = tx.ScanEach("SELECT name FROM users WHERE age >= ? ORDER BY name", func(row *stardb.StarResult) error {
|
||||
names = append(names, row.MustString("name"))
|
||||
return nil
|
||||
}, 30)
|
||||
if err != nil {
|
||||
t.Fatalf("Tx.ScanEach failed: %v", err)
|
||||
}
|
||||
if len(names) != 2 || names[0] != "Bob" || names[1] != "Charlie" {
|
||||
t.Fatalf("Unexpected names: %v", names)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarTx_ScanEachORM(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Fatalf("Begin failed: %v", err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
var user User
|
||||
var names []string
|
||||
err = tx.ScanEachORM("SELECT * FROM users WHERE age >= ? ORDER BY name", &user, func(target interface{}) error {
|
||||
u := target.(*User)
|
||||
names = append(names, u.Name)
|
||||
return nil
|
||||
}, 30)
|
||||
if err != nil {
|
||||
t.Fatalf("Tx.ScanEachORM failed: %v", err)
|
||||
}
|
||||
if len(names) != 2 || names[0] != "Bob" || names[1] != "Charlie" {
|
||||
t.Fatalf("Unexpected names: %v", names)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_WithTx(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
err := db.WithTx(func(tx *stardb.StarTx) error {
|
||||
_, err := tx.Exec("UPDATE users SET age = ? WHERE name = ?", 41, "Alice")
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("WithTx failed: %v", err)
|
||||
}
|
||||
|
||||
rows, err := db.Query("SELECT age FROM users WHERE name = ?", "Alice")
|
||||
if err != nil {
|
||||
t.Fatalf("Query failed: %v", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
if got := rows.Row(0).MustInt("age"); got != 41 {
|
||||
t.Fatalf("Expected age 41, got %d", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_WithTx_RollbackOnError(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
expectedErr := errors.New("business error")
|
||||
err := db.WithTx(func(tx *stardb.StarTx) error {
|
||||
if _, err := tx.Exec("UPDATE users SET age = ? WHERE name = ?", 55, "Alice"); err != nil {
|
||||
return err
|
||||
}
|
||||
return expectedErr
|
||||
})
|
||||
if !errors.Is(err, expectedErr) {
|
||||
t.Fatalf("Expected %v, got %v", expectedErr, err)
|
||||
}
|
||||
|
||||
rows, err := db.Query("SELECT age FROM users WHERE name = ?", "Alice")
|
||||
if err != nil {
|
||||
t.Fatalf("Query failed: %v", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
if got := rows.Row(0).MustInt("age"); got == 55 {
|
||||
t.Fatalf("Expected rollback to keep original age, got %d", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_SetMaxConnections(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
@@ -231,3 +924,80 @@ func TestStarDB_SetMaxConnections(t *testing.T) {
|
||||
t.Errorf("Expected MaxOpenConnections 10, got %d", stats.MaxOpenConnections)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarDB_ConcurrentRuntimeAndQuery(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
db.SetMaxOpenConns(1)
|
||||
db.SetMaxIdleConns(1)
|
||||
|
||||
db.SetSQLHooks(
|
||||
func(ctx context.Context, query string, args []interface{}) {},
|
||||
func(ctx context.Context, query string, args []interface{}, duration time.Duration, err error) {},
|
||||
)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for i := 0; i < 4; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < 100; j++ {
|
||||
rows, err := db.Query("SELECT id FROM users WHERE name = ?", "Alice")
|
||||
if err != nil {
|
||||
t.Errorf("Concurrent query failed: %v", err)
|
||||
return
|
||||
}
|
||||
_ = rows.Close()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < 100; j++ {
|
||||
if j%2 == 0 {
|
||||
db.SetPlaceholderStyle(stardb.PlaceholderDollar)
|
||||
db.SetSQLSlowThreshold(time.Millisecond)
|
||||
} else {
|
||||
db.SetPlaceholderStyle(stardb.PlaceholderQuestion)
|
||||
db.SetSQLSlowThreshold(0)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestStarTx_SQLHooks(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
var beforeCount int64
|
||||
var afterCount int64
|
||||
db.SetSQLHooks(
|
||||
func(ctx context.Context, query string, args []interface{}) {
|
||||
atomic.AddInt64(&beforeCount, 1)
|
||||
},
|
||||
func(ctx context.Context, query string, args []interface{}, duration time.Duration, err error) {
|
||||
atomic.AddInt64(&afterCount, 1)
|
||||
},
|
||||
)
|
||||
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Fatalf("Begin failed: %v", err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
if _, err := tx.Exec("UPDATE users SET age = ? WHERE name = ?", 27, "Alice"); err != nil {
|
||||
t.Fatalf("Tx.Exec failed: %v", err)
|
||||
}
|
||||
|
||||
if atomic.LoadInt64(&beforeCount) == 0 || atomic.LoadInt64(&afterCount) == 0 {
|
||||
t.Fatalf("Expected tx execution to trigger hooks, before=%d after=%d", beforeCount, afterCount)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user