Skip to content

Commit 5d047a8

Browse files
authored
test(scanner): benchmark Query/QueryRow (#29)
1 parent 9372c38 commit 5d047a8

File tree

4 files changed

+155
-102
lines changed

4 files changed

+155
-102
lines changed

README.md

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,6 @@ Integration tests cover the following databases and drivers:
110110

111111
See [integration_test.go](tests/integration_test.go) for details.
112112

113-
## 🚧 TODOs
114-
115-
- Add examples for tested databases and drivers.
116-
- Add benchmarks.
117-
118113
[1]: https://github.com/golang/go/issues/61637
119114
[2]: https://grpc.io/docs/guides/interceptors
120115
[3]: https://github.com/lib/pq

benchmark_test.go

Lines changed: 0 additions & 77 deletions
This file was deleted.

queriestest/driver.go

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,18 @@ type Driver struct {
1515
// ExecContext is a test implementation of [driver.ExecerContext].
1616
// If the code being tested uses [sql.Result],
1717
// ExecContext should return a [driver.Result] created with [NewResult].
18-
// Optional.
19-
ExecContext func(t *testing.T, query string, args []any) (driver.Result, error)
18+
ExecContext func(tb testing.TB, query string, args []any) (driver.Result, error)
2019

2120
// QueryContext is a test implementation of [driver.QueryerContext].
2221
// If the code being tested uses [sql.Rows],
2322
// QueryContext should return [Rows] created with [NewRows].
24-
// Optional.
25-
QueryContext func(t *testing.T, query string, args []any) (driver.Rows, error)
23+
QueryContext func(tb testing.TB, query string, args []any) (driver.Rows, error)
2624
}
2725

2826
// NewDB creates a test [sql.DB] backed by the given [Driver].
29-
func NewDB(t *testing.T, d Driver) *sql.DB {
30-
name := t.Name()
31-
sql.Register(name, testDriver{t, d})
27+
func NewDB(tb testing.TB, d Driver) *sql.DB {
28+
name := tb.Name()
29+
sql.Register(name, testDriver{tb, d})
3230
db, _ := sql.Open(name, "")
3331
return db
3432
}
@@ -43,25 +41,21 @@ var (
4341
)
4442

4543
type testDriver struct {
46-
t *testing.T
44+
tb testing.TB
4745
driver Driver
4846
}
4947

5048
// Open implements [driver.Driver].
5149
func (d testDriver) Open(string) (driver.Conn, error) { return d, nil }
5250

5351
// Prepare implements [driver.Conn].
54-
func (testDriver) Prepare(string) (driver.Stmt, error) {
55-
panic("unimplemented") // TODO: implement [driver.ConnPrepareContext]
56-
}
52+
func (testDriver) Prepare(string) (driver.Stmt, error) { panic("unimplemented") } // TODO: implement [driver.ConnPrepareContext]
5753

5854
// Close implements [driver.Conn].
5955
func (testDriver) Close() error { return nil }
6056

6157
// Begin implements [driver.Conn].
62-
func (testDriver) Begin() (driver.Tx, error) {
63-
panic("unreachable") // BeginTx always takes precedence over Begin.
64-
}
58+
func (testDriver) Begin() (driver.Tx, error) { panic("unreachable") } // BeginTx always takes precedence over Begin.
6559

6660
// BeginTx implements [driver.ConnBeginTx].
6761
func (d testDriver) BeginTx(context.Context, driver.TxOptions) (driver.Tx, error) { return d, nil }
@@ -77,15 +71,15 @@ func (d testDriver) ExecContext(_ context.Context, query string, args []driver.N
7771
if d.driver.ExecContext == nil {
7872
panic("queriestest: Driver.ExecContext is not set")
7973
}
80-
return d.driver.ExecContext(d.t, query, namedToAny(args))
74+
return d.driver.ExecContext(d.tb, query, namedToAny(args))
8175
}
8276

8377
// QueryContext implements [driver.QueryerContext].
8478
func (d testDriver) QueryContext(_ context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
8579
if d.driver.QueryContext == nil {
8680
panic("queriestest: Driver.QueryContext is not set")
8781
}
88-
return d.driver.QueryContext(d.t, query, namedToAny(args))
82+
return d.driver.QueryContext(d.tb, query, namedToAny(args))
8983
}
9084

9185
func namedToAny(values []driver.NamedValue) []any {
@@ -99,17 +93,17 @@ func namedToAny(values []driver.NamedValue) []any {
9993
var _ driver.Result = testResult{}
10094

10195
type testResult struct {
102-
lastInsertId int64
96+
lastInsertID int64
10397
rowsAffected int64
10498
}
10599

106100
// NewResult creates a test [driver.Result] from the given values.
107-
func NewResult(lastInsertId, rowsAffected int64) driver.Result {
108-
return testResult{lastInsertId, rowsAffected}
101+
func NewResult(lastInsertID, rowsAffected int64) driver.Result {
102+
return testResult{lastInsertID, rowsAffected}
109103
}
110104

111105
// LastInsertId implements [driver.Result].
112-
func (r testResult) LastInsertId() (int64, error) { return r.lastInsertId, nil }
106+
func (r testResult) LastInsertId() (int64, error) { return r.lastInsertID, nil }
113107

114108
// RowsAffected implements [driver.Result].
115109
func (r testResult) RowsAffected() (int64, error) { return r.rowsAffected, nil }

query_benchmark_test.go

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package queries
2+
3+
import (
4+
"database/sql"
5+
"database/sql/driver"
6+
"testing"
7+
8+
"go-simpler.org/queries/queriestest"
9+
)
10+
11+
func BenchmarkQuery_withScanner(b *testing.B) {
12+
db := newDB(b)
13+
b.ReportAllocs()
14+
for b.Loop() {
15+
for range Query[mediumRow](b.Context(), db, "") {
16+
}
17+
}
18+
}
19+
20+
func BenchmarkQuery_withoutScanner(b *testing.B) {
21+
db := newDB(b)
22+
b.ReportAllocs()
23+
for b.Loop() {
24+
rows, _ := db.QueryContext(b.Context(), "")
25+
for rows.Next() {
26+
var row mediumRow
27+
_ = rows.Scan(&row.A, &row.B, &row.C, &row.D, &row.E, &row.F, &row.G, &row.H)
28+
}
29+
_ = rows.Close()
30+
}
31+
}
32+
33+
func BenchmarkQueryRow_withScanner(b *testing.B) {
34+
db := newDB(b)
35+
b.ReportAllocs()
36+
for b.Loop() {
37+
_, _ = QueryRow[mediumRow](b.Context(), db, "")
38+
}
39+
}
40+
41+
func BenchmarkQueryRow_withoutScanner(b *testing.B) {
42+
db := newDB(b)
43+
b.ReportAllocs()
44+
for b.Loop() {
45+
var row mediumRow
46+
_ = db.QueryRowContext(b.Context(), "").
47+
Scan(&row.A, &row.B, &row.C, &row.D, &row.E, &row.F, &row.G, &row.H)
48+
}
49+
}
50+
51+
func newDB(tb testing.TB) *sql.DB {
52+
return queriestest.NewDB(tb, queriestest.Driver{
53+
QueryContext: func(testing.TB, string, []any) (driver.Rows, error) {
54+
return queriestest.NewRows("a", "b", "c", "d", "e", "f", "g", "h").
55+
Add(1, 2, 3, 4, 5, 6, 7, 8).
56+
Add(1, 2, 3, 4, 5, 6, 7, 8).
57+
Add(1, 2, 3, 4, 5, 6, 7, 8).
58+
Add(1, 2, 3, 4, 5, 6, 7, 8).
59+
Add(1, 2, 3, 4, 5, 6, 7, 8).
60+
Add(1, 2, 3, 4, 5, 6, 7, 8).
61+
Add(1, 2, 3, 4, 5, 6, 7, 8).
62+
Add(1, 2, 3, 4, 5, 6, 7, 8), nil
63+
},
64+
})
65+
}
66+
67+
func Benchmark_scan_smallRowWithCache(b *testing.B) { benchmarkScan[smallRow](b, true) }
68+
func Benchmark_scan_smallRowWithoutCache(b *testing.B) { benchmarkScan[smallRow](b, false) }
69+
func Benchmark_scan_mediumRowWithCache(b *testing.B) { benchmarkScan[mediumRow](b, true) }
70+
func Benchmark_scan_mediumRowWithoutCache(b *testing.B) { benchmarkScan[mediumRow](b, false) }
71+
func Benchmark_scan_largeRowWithCache(b *testing.B) { benchmarkScan[largeRow](b, true) }
72+
func Benchmark_scan_largeRowWithoutCache(b *testing.B) { benchmarkScan[largeRow](b, false) }
73+
74+
func benchmarkScan[T dst](b *testing.B, cache bool) {
75+
useCache = cache
76+
77+
var t T
78+
columns := t.columns()
79+
s := mockScanner{values: t.values()}
80+
81+
b.ReportAllocs()
82+
for b.Loop() {
83+
_, _ = scan[T](&s, columns)
84+
}
85+
}
86+
87+
type dst interface {
88+
columns() []string
89+
values() []any
90+
}
91+
92+
type smallRow struct {
93+
A int `sql:"a"`
94+
B int `sql:"b"`
95+
C int `sql:"c"`
96+
D int `sql:"d"`
97+
}
98+
99+
func (smallRow) columns() []string { return []string{"a", "b", "c", "d"} }
100+
func (smallRow) values() []any { return []any{1, 2, 3, 4} }
101+
102+
type mediumRow struct {
103+
A int `sql:"a"`
104+
B int `sql:"b"`
105+
C int `sql:"c"`
106+
D int `sql:"d"`
107+
E int `sql:"e"`
108+
F int `sql:"f"`
109+
G int `sql:"g"`
110+
H int `sql:"h"`
111+
}
112+
113+
func (mediumRow) columns() []string { return []string{"a", "b", "c", "d", "e", "f", "g", "h"} }
114+
func (mediumRow) values() []any { return []any{1, 2, 3, 4, 5, 6, 7, 8} }
115+
116+
type largeRow struct {
117+
A int `sql:"a"`
118+
B int `sql:"b"`
119+
C int `sql:"c"`
120+
D int `sql:"d"`
121+
E int `sql:"e"`
122+
F int `sql:"f"`
123+
G int `sql:"g"`
124+
H int `sql:"h"`
125+
I int `sql:"i"`
126+
J int `sql:"j"`
127+
K int `sql:"k"`
128+
L int `sql:"l"`
129+
M int `sql:"m"`
130+
N int `sql:"n"`
131+
O int `sql:"o"`
132+
P int `sql:"p"`
133+
}
134+
135+
func (largeRow) columns() []string {
136+
return []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p"}
137+
}
138+
139+
func (largeRow) values() []any {
140+
return []any{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
141+
}

0 commit comments

Comments
 (0)