stardb/testing/stardb_test.go

1004 lines
24 KiB
Go

package testing
import (
"context"
"errors"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
"b612.me/stardb"
_ "modernc.org/sqlite"
)
func TestStarDB_Open(t *testing.T) {
db := &stardb.StarDB{}
err := db.Open("sqlite", ":memory:")
if err != nil {
t.Errorf("Open failed: %v", err)
}
defer db.Close()
err = db.Ping()
if err != nil {
t.Errorf("Ping failed: %v", err)
}
}
func TestStarDB_Query(t *testing.T) {
db := setupTestDB(t)
defer db.Close()
rows, err := db.Query("SELECT * FROM users WHERE age > ?", 25)
if err != nil {
t.Fatalf("Query failed: %v", err)
}
defer rows.Close()
if rows.Length() != 2 {
t.Errorf("Expected 2 rows, got %d", rows.Length())
}
if len(rows.Columns()) != 7 {
t.Errorf("Expected 7 columns, got %d", len(rows.Columns()))
}
}
func TestStarDB_QueryContext(t *testing.T) {
db := setupTestDB(t)
defer db.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
rows, err := db.QueryContext(ctx, "SELECT * FROM users WHERE name = ?", "Alice")
if err != nil {
t.Fatalf("QueryContext failed: %v", err)
}
defer rows.Close()
if rows.Length() != 1 {
t.Errorf("Expected 1 row, got %d", rows.Length())
}
}
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()
result, err := db.Exec("UPDATE users SET age = ? WHERE name = ?", 26, "Alice")
if err != nil {
t.Fatalf("Exec failed: %v", err)
}
affected, err := result.RowsAffected()
if err != nil {
t.Fatalf("RowsAffected failed: %v", err)
}
if affected != 1 {
t.Errorf("Expected 1 row affected, got %d", affected)
}
}
func TestStarDB_ExecContext(t *testing.T) {
db := setupTestDB(t)
defer db.Close()
ctx := context.Background()
result, err := db.ExecContext(ctx, "DELETE FROM users WHERE name = ?", "Charlie")
if err != nil {
t.Fatalf("ExecContext failed: %v", err)
}
affected, err := result.RowsAffected()
if err != nil {
t.Fatalf("RowsAffected failed: %v", err)
}
if affected != 1 {
t.Errorf("Expected 1 row affected, got %d", affected)
}
}
func TestStarDB_QueryStmt(t *testing.T) {
db := setupTestDB(t)
defer db.Close()
rows, err := db.QueryStmt("SELECT * FROM users WHERE age > ?", 25)
if err != nil {
t.Fatalf("QueryStmt failed: %v", err)
}
defer rows.Close()
if rows.Length() != 2 {
t.Errorf("Expected 2 rows, got %d", rows.Length())
}
}
func TestStarDB_ExecStmt(t *testing.T) {
db := setupTestDB(t)
defer db.Close()
result, err := db.ExecStmt("UPDATE users SET age = ? WHERE name = ?", 27, "Bob")
if err != nil {
t.Fatalf("ExecStmt failed: %v", err)
}
affected, err := result.RowsAffected()
if err != nil {
t.Fatalf("RowsAffected failed: %v", err)
}
if affected != 1 {
t.Errorf("Expected 1 row affected, got %d", affected)
}
}
func TestStarDB_Prepare(t *testing.T) {
db := setupTestDB(t)
defer db.Close()
stmt, err := db.Prepare("SELECT * 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.Errorf("Expected 1 row, got %d", rows.Length())
}
}
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()
tx, err := db.Begin()
if err != nil {
t.Fatalf("Begin failed: %v", err)
}
_, err = tx.Exec("UPDATE users SET age = ? WHERE name = ?", 28, "Alice")
if err != nil {
tx.Rollback()
t.Fatalf("Tx.Exec failed: %v", err)
}
err = tx.Commit()
if err != nil {
t.Fatalf("Commit failed: %v", err)
}
// Verify the change
rows, err := db.Query("SELECT age FROM users WHERE name = ?", "Alice")
if err != nil {
t.Fatalf("Query failed: %v", err)
}
defer rows.Close()
age := rows.Row(0).MustInt("age")
if age != 28 {
t.Errorf("Expected age 28, got %d", age)
}
}
func TestStarDB_TransactionRollback(t *testing.T) {
db := setupTestDB(t)
defer db.Close()
tx, err := db.Begin()
if err != nil {
t.Fatalf("Begin failed: %v", err)
}
_, err = tx.Exec("UPDATE users SET age = ? WHERE name = ?", 99, "Alice")
if err != nil {
t.Fatalf("Tx.Exec failed: %v", err)
}
err = tx.Rollback()
if err != nil {
t.Fatalf("Rollback failed: %v", err)
}
// Verify the change was rolled back
rows, err := db.Query("SELECT age FROM users WHERE name = ?", "Alice")
if err != nil {
t.Fatalf("Query failed: %v", err)
}
defer rows.Close()
age := rows.Row(0).MustInt("age")
if age == 99 {
t.Errorf("Expected age to be rolled back, but got %d", age)
}
}
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()
db.SetMaxOpenConns(10)
db.SetMaxIdleConns(5)
stats := db.Stats()
if stats.MaxOpenConnections != 10 {
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)
}
}