2026-03-07 19:27:44 +08:00
|
|
|
package testing
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
2026-03-20 13:36:59 +08:00
|
|
|
"errors"
|
|
|
|
|
"strings"
|
|
|
|
|
"sync"
|
|
|
|
|
"sync/atomic"
|
2026-03-07 19:27:44 +08:00
|
|
|
"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())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
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")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-07 19:27:44 +08:00
|
|
|
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())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-07 19:27:44 +08:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-20 13:36:59 +08:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-07 19:27:44 +08:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-20 13:36:59 +08:00
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|