Skip to content

Commit a842b42

Browse files
committed
cache the index of named parameters
The commit changes bind to cache the position of named parameters, which improves the performance of prepared queries that are re-used. ``` goos: darwin goarch: arm64 pkg: github.com/charlievieth/go-sqlite3 cpu: Apple M4 Pro │ o10.txt │ n10.txt │ │ sec/op │ sec/op vs base │ Scan10Cols-14 4.279µ ± 0% 4.264µ ± 3% ~ (p=0.579 n=10) Suite/BenchmarkExec/Params-14 715.2n ± 1% 716.3n ± 1% ~ (p=0.927 n=10) Suite/BenchmarkExec/NoParams-14 533.1n ± 2% 517.9n ± 3% -2.86% (p=0.001 n=10) Suite/BenchmarkExecContext/Params-14 1.560µ ± 4% 1.504µ ± 0% -3.56% (p=0.000 n=10) Suite/BenchmarkExecContext/NoParams-14 1.523µ ± 2% 1.518µ ± 1% ~ (p=1.000 n=10) Suite/BenchmarkExecStep-14 452.5µ ± 2% 444.6µ ± 1% -1.76% (p=0.015 n=10) Suite/BenchmarkExecContextStep-14 444.1µ ± 2% 451.7µ ± 1% +1.72% (p=0.043 n=10) Suite/BenchmarkExecTx-14 1.607µ ± 1% 1.585µ ± 1% -1.40% (p=0.000 n=10) Suite/BenchmarkQuery-14 1.757µ ± 1% 1.784µ ± 0% +1.51% (p=0.001 n=10) Suite/BenchmarkQuerySimple-14 1.008µ ± 2% 1.015µ ± 1% ~ (p=0.117 n=10) Suite/BenchmarkQueryContext/Background-14 2.380µ ± 1% 2.315µ ± 0% -2.73% (p=0.001 n=10) Suite/BenchmarkQueryContext/WithCancel-14 8.314µ ± 1% 8.236µ ± 4% ~ (p=0.225 n=10) Suite/BenchmarkParams-14 1.952µ ± 1% 2.012µ ± 1% +3.07% (p=0.000 n=10) Suite/BenchmarkStmt-14 1.373µ ± 1% 1.394µ ± 1% +1.57% (p=0.001 n=10) Suite/BenchmarkRows-14 58.61µ ± 3% 59.95µ ± 1% +2.29% (p=0.029 n=10) Suite/BenchmarkStmtRows-14 58.80µ ± 2% 59.82µ ± 2% +1.73% (p=0.050 n=10) Suite/BenchmarkStmt10Cols-14 4.409µ ± 1% 4.466µ ± 0% +1.30% (p=0.006 n=10) Suite/BenchmarkScanRawBytes-14 2.499µ ± 1% 2.470µ ± 1% -1.18% (p=0.001 n=10) Suite/BenchmarkQueryParallel-14 438.3n ± 5% 439.0n ± 4% ~ (p=0.812 n=10) Suite/BenchmarkOpen-14 13.13µ ± 1% 13.05µ ± 2% ~ (p=0.197 n=10) Suite/BenchmarkNamedParams-14 1.675µ ± 1% 1.196µ ± 2% -28.60% (p=0.000 n=10) Suite/BenchmarkParseTime-14 1.040µ ± 2% 1.019µ ± 2% -2.02% (p=0.000 n=10) geomean 4.173µ 4.102µ -1.69% │ o10.txt │ n10.txt │ │ B/op │ B/op vs base │ Scan10Cols-14 712.0 ± 0% 712.0 ± 0% ~ (p=1.000 n=10) ¹ Suite/BenchmarkExec/Params-14 200.0 ± 0% 216.0 ± 0% +8.00% (p=0.000 n=10) Suite/BenchmarkExec/NoParams-14 64.00 ± 0% 64.00 ± 0% ~ (p=1.000 n=10) ¹ Suite/BenchmarkExecContext/Params-14 360.0 ± 0% 376.0 ± 0% +4.44% (p=0.000 n=10) Suite/BenchmarkExecContext/NoParams-14 208.0 ± 0% 208.0 ± 0% ~ (p=1.000 n=10) ¹ Suite/BenchmarkExecStep-14 64.00 ± 0% 64.00 ± 0% ~ (p=1.000 n=10) ¹ Suite/BenchmarkExecContextStep-14 208.0 ± 0% 208.0 ± 0% ~ (p=1.000 n=10) Suite/BenchmarkExecTx-14 520.0 ± 0% 520.0 ± 0% ~ (p=1.000 n=10) ¹ Suite/BenchmarkQuery-14 640.0 ± 0% 656.0 ± 0% +2.50% (p=0.000 n=10) Suite/BenchmarkQuerySimple-14 440.0 ± 0% 456.0 ± 0% +3.64% (p=0.000 n=10) Suite/BenchmarkQueryContext/Background-14 396.0 ± 0% 396.0 ± 0% ~ (p=1.000 n=10) ¹ Suite/BenchmarkQueryContext/WithCancel-14 1.016Ki ± 0% 1.015Ki ± 0% ~ (p=0.121 n=10) Suite/BenchmarkParams-14 784.0 ± 0% 800.0 ± 0% +2.04% (p=0.000 n=10) Suite/BenchmarkStmt-14 784.0 ± 0% 784.0 ± 0% ~ (p=1.000 n=10) ¹ Suite/BenchmarkRows-14 6.047Ki ± 0% 6.062Ki ± 0% +0.26% (p=0.000 n=10) Suite/BenchmarkStmtRows-14 6.055Ki ± 0% 6.055Ki ± 0% ~ (p=1.000 n=10) ¹ Suite/BenchmarkStmt10Cols-14 712.0 ± 0% 712.0 ± 0% ~ (p=1.000 n=10) ¹ Suite/BenchmarkScanRawBytes-14 592.0 ± 0% 592.0 ± 0% ~ (p=1.000 n=10) ¹ Suite/BenchmarkQueryParallel-14 532.0 ± 0% 548.0 ± 0% +3.01% (p=0.000 n=10) Suite/BenchmarkOpen-14 111.0 ± 1% 111.0 ± 1% ~ (p=1.000 n=10) Suite/BenchmarkNamedParams-14 664.0 ± 0% 600.0 ± 0% -9.64% (p=0.000 n=10) Suite/BenchmarkParseTime-14 444.0 ± 0% 444.0 ± 0% ~ (p=1.000 n=10) ¹ geomean 479.5 482.4 +0.60% ¹ all samples are equal │ o10.txt │ n10.txt │ │ allocs/op │ allocs/op vs base │ Scan10Cols-14 19.00 ± 0% 19.00 ± 0% ~ (p=1.000 n=10) ¹ Suite/BenchmarkExec/Params-14 8.000 ± 0% 8.000 ± 0% ~ (p=1.000 n=10) ¹ Suite/BenchmarkExec/NoParams-14 4.000 ± 0% 4.000 ± 0% ~ (p=1.000 n=10) ¹ Suite/BenchmarkExecContext/Params-14 10.00 ± 0% 10.00 ± 0% ~ (p=1.000 n=10) ¹ Suite/BenchmarkExecContext/NoParams-14 6.000 ± 0% 6.000 ± 0% ~ (p=1.000 n=10) ¹ Suite/BenchmarkExecStep-14 4.000 ± 0% 4.000 ± 0% ~ (p=1.000 n=10) ¹ Suite/BenchmarkExecContextStep-14 6.000 ± 0% 6.000 ± 0% ~ (p=1.000 n=10) ¹ Suite/BenchmarkExecTx-14 18.00 ± 0% 18.00 ± 0% ~ (p=1.000 n=10) ¹ Suite/BenchmarkQuery-14 22.00 ± 0% 22.00 ± 0% ~ (p=1.000 n=10) ¹ Suite/BenchmarkQuerySimple-14 13.00 ± 0% 13.00 ± 0% ~ (p=1.000 n=10) ¹ Suite/BenchmarkQueryContext/Background-14 10.00 ± 0% 10.00 ± 0% ~ (p=1.000 n=10) ¹ Suite/BenchmarkQueryContext/WithCancel-14 26.00 ± 0% 26.00 ± 0% ~ (p=1.000 n=10) ¹ Suite/BenchmarkParams-14 23.00 ± 0% 23.00 ± 0% ~ (p=1.000 n=10) ¹ Suite/BenchmarkStmt-14 23.00 ± 0% 23.00 ± 0% ~ (p=1.000 n=10) ¹ Suite/BenchmarkRows-14 418.0 ± 0% 418.0 ± 0% ~ (p=1.000 n=10) ¹ Suite/BenchmarkStmtRows-14 418.0 ± 0% 418.0 ± 0% ~ (p=1.000 n=10) ¹ Suite/BenchmarkStmt10Cols-14 19.00 ± 0% 19.00 ± 0% ~ (p=1.000 n=10) ¹ Suite/BenchmarkScanRawBytes-14 18.00 ± 0% 18.00 ± 0% ~ (p=1.000 n=10) ¹ Suite/BenchmarkQueryParallel-14 15.00 ± 0% 15.00 ± 0% ~ (p=1.000 n=10) ¹ Suite/BenchmarkOpen-14 3.000 ± 0% 3.000 ± 0% ~ (p=1.000 n=10) ¹ Suite/BenchmarkNamedParams-14 14.00 ± 0% 13.00 ± 0% -7.14% (p=0.000 n=10) Suite/BenchmarkParseTime-14 12.00 ± 0% 12.00 ± 0% ~ (p=1.000 n=10) ¹ geomean 15.93 15.87 -0.34% ¹ all samples are equal ```
1 parent 8d4a09e commit a842b42

File tree

2 files changed

+251
-54
lines changed

2 files changed

+251
-54
lines changed

sqlite3.go

+94-51
Original file line numberDiff line numberDiff line change
@@ -602,12 +602,13 @@ type SQLiteTx struct {
602602

603603
// SQLiteStmt implements driver.Stmt.
604604
type SQLiteStmt struct {
605-
mu sync.Mutex
606-
c *SQLiteConn
607-
s *C.sqlite3_stmt
608-
closed bool
609-
cls bool // True if the statement was created by SQLiteConn.Query
610-
reset bool // True if the statement needs to reset before re-use
605+
mu sync.Mutex
606+
c *SQLiteConn
607+
s *C.sqlite3_stmt
608+
namedParams map[string]int // Map of named parameter to index
609+
closed bool
610+
cls bool // True if the statement was created by SQLiteConn.Query
611+
reset bool // True if the statement needs to reset before re-use
611612
}
612613

613614
// SQLiteResult implements sql.Result.
@@ -2328,8 +2329,35 @@ func (s *SQLiteStmt) bind(args []driver.NamedValue) error {
23282329
return nil
23292330
}
23302331

2331-
func (s *SQLiteStmt) bindIndices(args []driver.NamedValue) error {
2332-
// Find the longest named parameter name.
2332+
func (s *SQLiteStmt) missingNamedParams(args []driver.NamedValue) bool {
2333+
for _, v := range args {
2334+
if v.Name != "" {
2335+
if _, ok := s.namedParams[v.Name]; !ok {
2336+
return true
2337+
}
2338+
}
2339+
}
2340+
return false
2341+
}
2342+
2343+
func (s *SQLiteStmt) createBindIndices(args []driver.NamedValue) {
2344+
if len(args) == 0 {
2345+
return
2346+
}
2347+
s.mu.Lock()
2348+
defer s.mu.Unlock()
2349+
2350+
// Check if we need to update or create the named params map.
2351+
// This check is necessary because a bad set of params could
2352+
// be passed to a prepared statement on its first invocation.
2353+
if !s.missingNamedParams(args) {
2354+
return
2355+
}
2356+
if s.namedParams == nil {
2357+
s.namedParams = make(map[string]int, len(args))
2358+
}
2359+
2360+
// Find the longest parameter name
23332361
n := 0
23342362
for _, v := range args {
23352363
if m := len(v.Name); m > n {
@@ -2338,60 +2366,75 @@ func (s *SQLiteStmt) bindIndices(args []driver.NamedValue) error {
23382366
}
23392367
buf := make([]byte, 0, n+2) // +2 for placeholder and null terminator
23402368

2341-
bindIndices := make([][3]int, len(args))
2369+
for _, v := range args {
2370+
if v.Name == "" {
2371+
continue
2372+
}
2373+
for _, c := range []byte{':', '@', '$'} {
2374+
buf = append(buf[:0], c)
2375+
buf = append(buf, v.Name...)
2376+
buf = append(buf, 0)
2377+
idx := int(C.sqlite3_bind_parameter_index(s.s, (*C.char)(unsafe.Pointer(&buf[0]))))
2378+
if idx != 0 {
2379+
s.namedParams[v.Name] = idx
2380+
break
2381+
}
2382+
}
2383+
}
2384+
}
2385+
2386+
func (s *SQLiteStmt) bindIndices(args []driver.NamedValue) error {
2387+
s.createBindIndices(args)
2388+
2389+
bindIndices := make([]int, len(args))
23422390
for i, v := range args {
2343-
bindIndices[i][0] = args[i].Ordinal
2391+
bindIndices[i] = args[i].Ordinal
23442392
if v.Name != "" {
2345-
for j, c := range []byte{':', '@', '$'} {
2346-
buf = append(buf[:0], c)
2347-
buf = append(buf, v.Name...)
2348-
buf = append(buf, 0)
2349-
bindIndices[i][j] = int(C.sqlite3_bind_parameter_index(s.s, (*C.char)(unsafe.Pointer(&buf[0]))))
2350-
}
2351-
args[i].Ordinal = bindIndices[i][0]
2393+
// NB: Parameters with unrecognized names should be ignored:
2394+
// https://github.com/mattn/go-sqlite3/issues/697
2395+
bindIndices[i] = s.namedParams[v.Name]
2396+
args[i].Ordinal = bindIndices[i]
23522397
}
23532398
}
23542399

23552400
var rv C.int
23562401
for i, arg := range args {
2357-
for j := range bindIndices[i] {
2358-
if bindIndices[i][j] == 0 {
2359-
continue
2402+
if bindIndices[i] == 0 {
2403+
continue
2404+
}
2405+
n := C.int(bindIndices[i])
2406+
switch v := arg.Value.(type) {
2407+
case nil:
2408+
rv = C.sqlite3_bind_null(s.s, n)
2409+
case string:
2410+
p := stringData(v)
2411+
rv = C._sqlite3_bind_text(s.s, n, (*C.char)(unsafe.Pointer(p)), C.int(len(v)))
2412+
case int64:
2413+
rv = C.sqlite3_bind_int64(s.s, n, C.sqlite3_int64(v))
2414+
case bool:
2415+
val := 0
2416+
if v {
2417+
val = 1
23602418
}
2361-
n := C.int(bindIndices[i][j])
2362-
switch v := arg.Value.(type) {
2363-
case nil:
2419+
rv = C.sqlite3_bind_int(s.s, n, C.int(val))
2420+
case float64:
2421+
rv = C.sqlite3_bind_double(s.s, n, C.double(v))
2422+
case []byte:
2423+
if v == nil {
23642424
rv = C.sqlite3_bind_null(s.s, n)
2365-
case string:
2366-
p := stringData(v)
2367-
rv = C._sqlite3_bind_text(s.s, n, (*C.char)(unsafe.Pointer(p)), C.int(len(v)))
2368-
case int64:
2369-
rv = C.sqlite3_bind_int64(s.s, n, C.sqlite3_int64(v))
2370-
case bool:
2371-
val := 0
2372-
if v {
2373-
val = 1
2374-
}
2375-
rv = C.sqlite3_bind_int(s.s, n, C.int(val))
2376-
case float64:
2377-
rv = C.sqlite3_bind_double(s.s, n, C.double(v))
2378-
case []byte:
2379-
if v == nil {
2380-
rv = C.sqlite3_bind_null(s.s, n)
2381-
} else {
2382-
ln := len(v)
2383-
if ln == 0 {
2384-
v = placeHolder
2385-
}
2386-
rv = C._sqlite3_bind_blob(s.s, n, unsafe.Pointer(&v[0]), C.int(ln))
2425+
} else {
2426+
ln := len(v)
2427+
if ln == 0 {
2428+
v = placeHolder
23872429
}
2388-
case time.Time:
2389-
b := timefmt.Format(v)
2390-
rv = C._sqlite3_bind_text(s.s, n, (*C.char)(unsafe.Pointer(&b[0])), C.int(len(b)))
2391-
}
2392-
if rv != C.SQLITE_OK {
2393-
return s.c.lastError(int(rv))
2430+
rv = C._sqlite3_bind_blob(s.s, n, unsafe.Pointer(&v[0]), C.int(ln))
23942431
}
2432+
case time.Time:
2433+
b := timefmt.Format(v)
2434+
rv = C._sqlite3_bind_text(s.s, n, (*C.char)(unsafe.Pointer(&b[0])), C.int(len(b)))
2435+
}
2436+
if rv != C.SQLITE_OK {
2437+
return s.c.lastError(int(rv))
23952438
}
23962439
}
23972440
return nil

sqlite3_test.go

+157-3
Original file line numberDiff line numberDiff line change
@@ -1093,8 +1093,6 @@ func TestExecer(t *testing.T) {
10931093
}
10941094

10951095
func newTestDB(t testing.TB) *sql.DB {
1096-
// fmt.Sprintf("file:%s?mode=rwc", filename)
1097-
// ?mode=rwc
10981096
db, err := sql.Open("sqlite3", fmt.Sprintf("file:%s?mode=rwc", t.TempDir()+"/test.sqlite3"))
10991097
if err != nil {
11001098
t.Fatal("Failed to open database:", err)
@@ -2346,7 +2344,9 @@ func TestNamedParam(t *testing.T) {
23462344
}
23472345
defer db.Close()
23482346

2349-
_, err = db.Exec("drop table foo")
2347+
if _, err = db.Exec("drop table if exists foo"); err != nil {
2348+
t.Fatal(err)
2349+
}
23502350
_, err = db.Exec("create table foo (id integer, name text, amount integer)")
23512351
if err != nil {
23522352
t.Fatal("Failed to create table:", err)
@@ -2376,6 +2376,67 @@ func TestNamedParam(t *testing.T) {
23762376
}
23772377
}
23782378

2379+
func TestNamedParamReorder(t *testing.T) {
2380+
db := newTestDB(t)
2381+
const createTableStmt = `
2382+
CREATE TABLE IF NOT EXISTS test_named_params (
2383+
r0 INTEGER NOT NULL,
2384+
r1 INTEGER NOT NULL
2385+
);
2386+
DELETE FROM test_named_params;
2387+
INSERT INTO test_named_params VALUES (10, 11);
2388+
INSERT INTO test_named_params VALUES (20, 21);`
2389+
2390+
if _, err := db.Exec(createTableStmt); err != nil {
2391+
t.Fatal(err)
2392+
}
2393+
2394+
const query = `
2395+
SELECT
2396+
r0, r1
2397+
FROM
2398+
test_named_params
2399+
WHERE r0 = :v1 AND r1 = :v2;
2400+
`
2401+
stmt, err := db.Prepare(query)
2402+
if err != nil {
2403+
t.Fatal(err)
2404+
}
2405+
defer stmt.Close()
2406+
2407+
test := func(t testing.TB, arg1, arg2 sql.NamedArg, v1, v2 int64) {
2408+
t.Helper()
2409+
var i1, i2 int64
2410+
err := stmt.QueryRow(arg1, arg2).Scan(&i1, &i2)
2411+
if err != nil {
2412+
t.Error(err)
2413+
return
2414+
}
2415+
if i1 != v1 && i2 != v2 {
2416+
t.Errorf("got: v1=%d v2=%d want: v1=%d v2=%d", i1, i2, v1, v2)
2417+
}
2418+
}
2419+
2420+
// Deliberately add invalid named params to make sure that they
2421+
// don't poison the named param cache.
2422+
test(ignoreError{t}, sql.Named("v1", 10), sql.Named("foo", 11), 10, 11)
2423+
test(ignoreError{t}, sql.Named("bar", 10), sql.Named("foo", 11), 10, 11)
2424+
2425+
test(t, sql.Named("v1", 10), sql.Named("v2", 11), 10, 11)
2426+
test(t, sql.Named("v2", 11), sql.Named("v1", 10), 10, 11) // Reverse arg order
2427+
2428+
// Change argument values
2429+
test(t, sql.Named("v1", 20), sql.Named("v2", 21), 20, 21)
2430+
test(t, sql.Named("v2", 21), sql.Named("v1", 20), 20, 21) // Reverse arg order
2431+
2432+
// Extra argument should error
2433+
var v1, v2 int64
2434+
err = stmt.QueryRow(sql.Named("v1", 10), sql.Named("v2", 11), sql.Named("v3", 12)).Scan(&v1, &v2)
2435+
if err == nil {
2436+
t.Fatal(err)
2437+
}
2438+
}
2439+
23792440
func TestRawBytes(t *testing.T) {
23802441
db, err := sql.Open("sqlite3", ":memory:")
23812442
if err != nil {
@@ -2555,6 +2616,7 @@ var benchmarks = []testing.InternalBenchmark{
25552616
{Name: "BenchmarkScanRawBytes", F: benchmarkScanRawBytes},
25562617
{Name: "BenchmarkQueryParallel", F: benchmarkQueryParallel},
25572618
{Name: "BenchmarkOpen", F: benchmarkOpen},
2619+
{Name: "BenchmarkNamedParams", F: benchmarkNamedParams},
25582620
{Name: "BenchmarkParseTime", F: benchmarkParseTime},
25592621
}
25602622

@@ -3337,6 +3399,69 @@ func benchmarkOpen(b *testing.B) {
33373399
}
33383400
}
33393401

3402+
func benchmarkNamedParams(b *testing.B) {
3403+
db, err := sql.Open("sqlite3", ":memory:")
3404+
if err != nil {
3405+
b.Fatal(err)
3406+
}
3407+
defer db.Close()
3408+
3409+
const createTableStmt = `
3410+
DROP TABLE IF EXISTS bench_named_params;
3411+
VACUUM;
3412+
CREATE TABLE bench_named_params (
3413+
r0 INTEGER NOT NULL,
3414+
r1 INTEGER NOT NULL,
3415+
r2 INTEGER NOT NULL,
3416+
r3 INTEGER NOT NULL
3417+
);`
3418+
if _, err := db.Exec(createTableStmt); err != nil {
3419+
b.Fatal(err)
3420+
}
3421+
for i := int64(0); i < 1; i++ {
3422+
_, err := db.Exec("INSERT INTO bench_named_params VALUES (?, ?, ?, ?);", i, i, i, i)
3423+
if err != nil {
3424+
b.Fatal(err)
3425+
}
3426+
}
3427+
// _, err = db.Exec("insert into foo(id, name, amount) values(:id, @name, $amount)",
3428+
const query = `
3429+
SELECT
3430+
r0
3431+
FROM
3432+
bench_named_params
3433+
WHERE
3434+
r0 >= :v0 AND r1 >= :v1 AND r2 >= :v2 AND r3 >= :v3;`
3435+
3436+
stmt, err := db.Prepare(query)
3437+
if err != nil {
3438+
b.Fatal(err)
3439+
}
3440+
defer stmt.Close()
3441+
3442+
args := []any{
3443+
sql.Named("v0", 0),
3444+
sql.Named("v1", 0),
3445+
sql.Named("v2", 0),
3446+
sql.Named("v3", 0),
3447+
}
3448+
for i := 0; i < b.N; i++ {
3449+
rows, err := stmt.Query(args...)
3450+
if err != nil {
3451+
b.Fatal(err)
3452+
}
3453+
var v int64
3454+
for rows.Next() {
3455+
if err := rows.Scan(&v); err != nil {
3456+
b.Fatal(err)
3457+
}
3458+
}
3459+
if err := rows.Err(); err != nil {
3460+
b.Fatal(err)
3461+
}
3462+
}
3463+
}
3464+
33403465
func benchmarkParseTime(b *testing.B) {
33413466
db, err := sql.Open("sqlite3", ":memory:")
33423467
if err != nil {
@@ -3379,3 +3504,32 @@ func benchmarkParseTime(b *testing.B) {
33793504
}
33803505
}
33813506
}
3507+
3508+
var _ testing.TB = ignoreError{}
3509+
3510+
// ignoreError prevents a testing.T from error'ing
3511+
type ignoreError struct {
3512+
*testing.T
3513+
}
3514+
3515+
func (t ignoreError) FailNow() {}
3516+
3517+
func (t ignoreError) Error(args ...any) {
3518+
t.Helper()
3519+
t.T.Log(args...)
3520+
}
3521+
3522+
func (t ignoreError) Errorf(format string, args ...any) {
3523+
t.Helper()
3524+
t.T.Logf(format, args...)
3525+
}
3526+
3527+
func (t ignoreError) Fatal(args ...any) {
3528+
t.Helper()
3529+
t.T.Log(args...)
3530+
}
3531+
3532+
func (t ignoreError) Fatalf(format string, args ...any) {
3533+
t.Helper()
3534+
t.T.Logf(format, args...)
3535+
}

0 commit comments

Comments
 (0)