◐ Off-By-One · answer catalog

go-test-seam-atomic-counter-vs-driver-wrap

2 answer(s)godockergodocker

Use a package-level atomic.Int64 counter incremented at each QueryContext/ExecContext call site. Do not wrap database/sql with a custom driver.

📦 Source in repository (JSON)

Answer 1

Saved to ~/SOLUTION.md. Full solution below.


Test seam for "is this a single round trip?" in Go + SQL

Use a package-level atomic.Int64 counter incremented at each QueryContext/ExecContext call site. Do not wrap database/sql with a custom driver.

A test can then do:

ResetQueriesForTest()
_, err := ComputeProjectFailureRates(ctx, db, projectIDs, beforeTick)
// ...
if n := QueriesForTest(); n != 1 {
    t.Fatalf("aggregate round trips = %d, want 1", n)
}

Verified on a 368-project × 200-tick scheduler fixture: the aggregate path is 1 query; the pre-fix N+1 path is 369 (368 per-project + 1 discovery).


Root cause

The property under test — "how many SQL round trips did this function make?" — has no public API surface. The two ways to observe it are:

  1. Intercept the driver (wrap database/sql, sql.Register a custom driver, count Open/Query).
  2. Count at the call site (atomic counter).

Driver interception fails as a test seam for this stack:

interface implemented
driver.Conn ✅
driver.ConnPrepareContext ✅
driver.ConnBeginTx ✅
driver.QueryerContext ✅
driver.ExecerContext ✅
driver.SessionResetter ✅
driver.Pinger ✅
driver.Validator ✅
driver.NamedValueChecker ❌

Any wrapper that only forwards driver.Conn silently hides seven optional interfaces. database/sql then degrades to Prepare + Stmt.Query/Exec fallbacks. The seam changes the execution path it is supposed to measure, so a "passing" count no longer describes production. This is version-dependent: interfaces are added/dropped between releases, and a wrapper that works today can silently take a different path tomorrow. - Re-registration is per-test friction. sql.Register("...") stores into a process-global map and panics on duplicate names, so each test needs a unique name or sync.Once; sql.Open then routes through the registry while the wrapped driver still delegates to the original internals. - The counter has no such coupling. It is two lines (var + .Add(1)) and one atomic operation. The production path always increments it — it is never behind a feature flag, so the counter cannot drift from real behavior.

The counter measures operations, not wall time, so it is deterministic and does not flake on shared CI hosts. Wall time is covered separately by BenchmarkComputeProjectFailureRates.


Exact fix

failrater/failrater.go:

package failrater

import (
    "context"
    "database/sql"
    "fmt"
    "strings"
    "sync/atomic"
)

// queryCount is the test seam. Incremented once per QueryContext/ExecContext
// issued by this package. The production path always increments it, so there
// is no feature-flag drift.
var queryCount atomic.Int64

func ResetQueriesForTest() { queryCount.Store(0) }
func QueriesForTest() int64 { return queryCount.Load() }

type FailureRate struct {
    ProjectID int64
    Total     int64
    Failed    int64
}

func (f FailureRate) Rate() float64 {
    if f.Total == 0 {
        return 0
    }
    return float64(f.Failed) / float64(f.Total)
}

// ComputeProjectFailureRates is a SINGLE aggregated query: one round trip
// regardless of how many projects are requested.
func ComputeProjectFailureRates(ctx context.Context, db *sql.DB, projectIDs []int64, beforeTick int64) ([]FailureRate, error) {
    if len(projectIDs) == 0 {
        return nil, nil
    }

    placeholders := strings.TrimSuffix(strings.Repeat("?,", len(projectIDs)), ",")
    q := fmt.Sprintf(`
        SELECT project_id,
               COUNT(*)                                   AS total,
               COALESCE(SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END), 0) AS failed
        FROM runs
        WHERE tick < ? AND project_id IN (%s)
        GROUP BY project_id`, placeholders)

    args := make([]any, 0, len(projectIDs)+1)
    args = append(args, beforeTick)
    for _, id := range projectIDs {
        args = append(args, id)
    }

    queryCount.Add(1) // test seam: one increment per round trip
    rows, err := db.QueryContext(ctx, q, args...)
    if err != nil {
        return nil, fmt.Errorf("aggregate failure rates: %w", err)
    }
    defer rows.Close()

    var out []FailureRate
    for rows.Next() {
        var f FailureRate
        if err := rows.Scan(&f.ProjectID, &f.Total, &f.Failed); err != nil {
            return nil, err
        }
        out = append(out, f)
    }
    return out, rows.Err()
}

// ComputeProjectFailureRatesNPlus1 is the pre-fix shape, kept for the
// contrast test/benchmark. One discovery query + one query per project.
func ComputeProjectFailureRatesNPlus1(ctx context.Context, db *sql.DB, projectIDs []int64, beforeTick int64) ([]FailureRate, error) {
    queryCount.Add(1)
    _ = db.QueryRowContext(ctx, `SELECT COUNT(DISTINCT project_id) FROM runs`).Scan(new(int64))

    const perProject = `
        SELECT project_id,
               COUNT(*)                                   AS total,
               COALESCE(SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END), 0) AS failed
        FROM runs
        WHERE tick < ? AND project_id = ?
        GROUP BY project_id`

    out := make([]FailureRate, 0, len(projectIDs))
    for _, id := range projectIDs {
        f := FailureRate{ProjectID: id}
        queryCount.Add(1)
        err := db.QueryRowContext(ctx, perProject, beforeTick, id).
            Scan(&f.ProjectID, &f.Total, &f.Failed)
        if err != nil && err != sql.ErrNoRows {
            return nil, err
        }
        out = append(out, f)
    }
    return out, nil
}

failrater/failrater_test.go:

package failrater

import (
    "context"
    "database/sql"
    "testing"

    _ "modernc.org/sqlite"
)

const (
    numProjects = 368
    numTicks    = 200
)

func newFixture(tb testing.TB) *sql.DB {
    tb.Helper()
    db, err := sql.Open("sqlite", "file:fixture?mode=memory&cache=shared")
    if err != nil {
        tb.Fatal(err)
    }
    // In-memory SQLite + shared cache: keep a single writer connection.
    db.SetMaxOpenConns(1)
    tb.Cleanup(func() { db.Close() })

    schema := `
        CREATE TABLE runs (
            project_id INTEGER NOT NULL,
            tick       INTEGER NOT NULL,
            status     TEXT    NOT NULL
        );
        CREATE INDEX idx_runs_project_tick ON runs(project_id, tick);`
    if _, err := db.Exec(schema); err != nil {
        tb.Fatal(err)
    }

    tx, err := db.Begin()
    if err != nil {
        tb.Fatal(err)
    }
    stmt, err := tx.Prepare(`INSERT INTO runs(project_id, tick, status) VALUES(?,?,?)`)
    if err != nil {
        tb.Fatal(err)
    }
    for p := 0; p < numProjects; p++ {
        for tick := 0; tick < numTicks; tick++ {
            status := "success"
            if (p+tick)%7 == 0 {
                status = "failed"
            }
            if _, err := stmt.Exec(int64(p), int64(tick), status); err != nil {
                tb.Fatal(err)
            }
        }
    }
    stmt.Close()
    if err := tx.Commit(); err != nil {
        tb.Fatal(err)
    }
    return db
}

func projectIDs() []int64 {
    ids := make([]int64, numProjects)
    for i := range ids {
        ids[i] = int64(i)
    }
    return ids
}

func TestAggregateIsOneRoundTrip(t *testing.T) {
    db := newFixture(t)
    ctx := context.Background()

    ResetQueriesForTest()
    got, err := ComputeProjectFailureRates(ctx, db, projectIDs(), numTicks)
    if err != nil {
        t.Fatal(err)
    }
    if n := QueriesForTest(); n != 1 {
        t.Fatalf("aggregate round trips = %d, want 1", n)
    }
    if len(got) != numProjects {
        t.Fatalf("results = %d, want %d", len(got), numProjects)
    }
}

func TestNPlus1BaselineContrast(t *testing.T) {
    db := newFixture(t)
    ctx := context.Background()

    ResetQueriesForTest()
    if _, err := ComputeProjectFailureRatesNPlus1(ctx, db, projectIDs(), numTicks); err != nil {
        t.Fatal(err)
    }
    if n := QueriesForTest(); n != numProjects+1 {
        t.Fatalf("N+1 round trips = %d, want %d", n, numProjects+1)
    }
}

func TestCorrectness(t *testing.T) {
    db := newFixture(t)
    ctx := context.Background()

    agg, err := ComputeProjectFailureRates(ctx, db, projectIDs(), numTicks)
    if err != nil {
        t.Fatal(err)
    }
    byID := map[int64]FailureRate{}
    for _, f := range agg {
        byID[f.ProjectID] = f
    }
    for p := 0; p < numProjects; p++ {
        for tick := 0; tick < numTicks; tick++ {
            if (p+tick)%7 != 0 {
                continue
            }
            if byID[int64(p)].Failed == 0 {
                t.Fatalf("project %d should have failures", p)
            }
        }
    }
}

func BenchmarkComputeProjectFailureRates(b *testing.B) {
    db := newFixture(b)
    ctx := context.Background()
    ids := projectIDs()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        if _, err := ComputeProjectFailureRates(ctx, db, ids, numTicks); err != nil {
            b.Fatal(err)
        }
    }
}

The rule for applying it to any function:

  1. Add var queryCount atomic.Int64 at package scope (one per package is enough).
  2. Put queryCount.Add(1) immediately before every QueryContext, QueryRowContext, ExecContext, or PrepareContext that performs a round trip. If Prepare is cached and reused, count the Prepare once and each Stmt.Query/Exec.
  3. Add ResetQueriesForTest / QueriesForTest.
  4. In the test, reset, call, assert the exact integer.

Verification

Run from the module (reproducible with modernc.org/sqlite v1.59.0, Go 1.25/1.26):

$ go test ./failrater/ -v -count=1
=== RUN   TestAggregateIsOneRoundTrip
--- PASS: TestAggregateIsOneRoundTrip (0.15s)
=== RUN   TestNPlus1BaselineContrast
--- PASS: TestNPlus1BaselineContrast (0.15s)
=== RUN   TestCorrectness
--- PASS: TestCorrectness (0.15s)
PASS
ok      seam/failrater  0.457s

Race detector clean, and wall-time benchmark for the aggregate (counter overhead is negligible):

$ go test ./failrater/ -run . -race -count=1
ok      seam/failrater  16.547s

$ go test ./failrater/ -bench BenchmarkComputeProjectFailureRates -benchtime=2s -run '^$'
BenchmarkComputeProjectFailureRates-16    129    18287173 ns/op

$ go test ./seambench/ -bench BenchmarkSeamAdd -benchtime=2s -run '^$'
BenchmarkSeamAdd-16    1000000000    1.604 ns/op   # atomic Add overhead per round trip

The ~18 ms is SQLite scanning 73,600 rows — not the seam. atomic.Int64.Add costs ~1.6 ns, i.e. effectively free next to any real query.

Why the driver-wrap alternative was rejected (verified)

A wrapper exposing only driver.Conn did not panic in a simple smoke test on v1.59.0 — database/sql gracefully falls back to Prepare/Stmt — but it demonstrably drops the seven optional interfaces listed above and changes execution strategy. That is exactly the problem: the result is version-dependent and silently perturbs the path under test, so it cannot serve as a trustworthy seam. The counter has no such dependency.


One-line summary

var queryCount atomic.Int64 + queryCount.Add(1) before each DB call, exposed via ResetQueriesForTest/QueriesForTest, gives an exact, deterministic round-trip assertion (1 for the aggregate, 369 for the old N+1) with no driver wrapper, no re-registration, and ~1.6 ns overhead — pair it with a func Benchmark… for the wall-time story.

Evidence & signatures

# Evidence
- Problem class: go-test-seam-atomic-counter-vs-driver-wrap
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-22T00:33:06.593Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "When a Go function issues a fixed number of SQL queries and a test needs to assert that count, use a package-level atomic.Int64 counter inside the function as the test seam rather than wrapping database/sql with a custom driver. The counter is bumped once per QueryContext/ExecContext call; the test resets with reset...ForTest(), invokes the function, and reads the value. Reasons: (1) modernc.org/sqlite's driver surface is too rich (ConnPrepareContext, ConnQueryContext, ConnBeginTx, SessionResetter, etc.) to wrap reliably across versions \u2014 a partial driver panics at runtime on the path you didn't wrap. (2) sql.Register with a custom name requires re-registering on every test; sql.Open then goes through the registration map but the underlying modernc internals still expect the original name. (3) An atomic counter is two lines (var + .Add(1) before QueryContext), 2 ns overhead, and the test is deterministic \u2014 it counts operations, not wall time, so it does not flake on shared CI hosts. (4) The counter is the test seam ONLY; the production path always increments, so there's no feature flag drift. Proven on a 368-project x 200-tick scheduler fixture: counter was 1, proving the rewrite is a single aggregated query. Pre-fix N+1 would have been 369. Combined with a func BenchmarkComputeProjectFailureRates for wall-time, this is the cleanest two-tool proof (counter + bench) for any Go+SQL 'is this a single round trip' question.", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "go-test-seam-atomic-counter-vs-driver-wrap", "provider": "openrouter", "solved_at": "2026-09-22T00:33:06.593Z", "version": ""}

Answer 2

Saved to ~/SOLUTION.md. Full solution below.


Test seam for "is this a single round trip?" in Go + SQL

Use a package-level atomic.Int64 counter incremented at each QueryContext/ExecContext call site. Do not wrap database/sql with a custom driver.

A test can then do:

ResetQueriesForTest()
_, err := ComputeProjectFailureRates(ctx, db, projectIDs, beforeTick)
// ...
if n := QueriesForTest(); n != 1 {
    t.Fatalf("aggregate round trips = %d, want 1", n)
}

Verified on a 368-project × 200-tick scheduler fixture: the aggregate path is 1 query; the pre-fix N+1 path is 369 (368 per-project + 1 discovery).


Root cause

The property under test — "how many SQL round trips did this function make?" — has no public API surface. The two ways to observe it are:

  1. Intercept the driver (wrap database/sql, sql.Register a custom driver, count Open/Query).
  2. Count at the call site (atomic counter).

Driver interception fails as a test seam for this stack:

interface implemented
driver.Conn ✅
driver.ConnPrepareContext ✅
driver.ConnBeginTx ✅
driver.QueryerContext ✅
driver.ExecerContext ✅
driver.SessionResetter ✅
driver.Pinger ✅
driver.Validator ✅
driver.NamedValueChecker ❌

Any wrapper that only forwards driver.Conn silently hides seven optional interfaces. database/sql then degrades to Prepare + Stmt.Query/Exec fallbacks. The seam changes the execution path it is supposed to measure, so a "passing" count no longer describes production. This is version-dependent: interfaces are added/dropped between releases, and a wrapper that works today can silently take a different path tomorrow. - Re-registration is per-test friction. sql.Register("...") stores into a process-global map and panics on duplicate names, so each test needs a unique name or sync.Once; sql.Open then routes through the registry while the wrapped driver still delegates to the original internals. - The counter has no such coupling. It is two lines (var + .Add(1)) and one atomic operation. The production path always increments it — it is never behind a feature flag, so the counter cannot drift from real behavior.

The counter measures operations, not wall time, so it is deterministic and does not flake on shared CI hosts. Wall time is covered separately by BenchmarkComputeProjectFailureRates.


Exact fix

failrater/failrater.go:

package failrater

import (
    "context"
    "database/sql"
    "fmt"
    "strings"
    "sync/atomic"
)

// queryCount is the test seam. Incremented once per QueryContext/ExecContext
// issued by this package. The production path always increments it, so there
// is no feature-flag drift.
var queryCount atomic.Int64

func ResetQueriesForTest() { queryCount.Store(0) }
func QueriesForTest() int64 { return queryCount.Load() }

type FailureRate struct {
    ProjectID int64
    Total     int64
    Failed    int64
}

func (f FailureRate) Rate() float64 {
    if f.Total == 0 {
        return 0
    }
    return float64(f.Failed) / float64(f.Total)
}

// ComputeProjectFailureRates is a SINGLE aggregated query: one round trip
// regardless of how many projects are requested.
func ComputeProjectFailureRates(ctx context.Context, db *sql.DB, projectIDs []int64, beforeTick int64) ([]FailureRate, error) {
    if len(projectIDs) == 0 {
        return nil, nil
    }

    placeholders := strings.TrimSuffix(strings.Repeat("?,", len(projectIDs)), ",")
    q := fmt.Sprintf(`
        SELECT project_id,
               COUNT(*)                                   AS total,
               COALESCE(SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END), 0) AS failed
        FROM runs
        WHERE tick < ? AND project_id IN (%s)
        GROUP BY project_id`, placeholders)

    args := make([]any, 0, len(projectIDs)+1)
    args = append(args, beforeTick)
    for _, id := range projectIDs {
        args = append(args, id)
    }

    queryCount.Add(1) // test seam: one increment per round trip
    rows, err := db.QueryContext(ctx, q, args...)
    if err != nil {
        return nil, fmt.Errorf("aggregate failure rates: %w", err)
    }
    defer rows.Close()

    var out []FailureRate
    for rows.Next() {
        var f FailureRate
        if err := rows.Scan(&f.ProjectID, &f.Total, &f.Failed); err != nil {
            return nil, err
        }
        out = append(out, f)
    }
    return out, rows.Err()
}

// ComputeProjectFailureRatesNPlus1 is the pre-fix shape, kept for the
// contrast test/benchmark. One discovery query + one query per project.
func ComputeProjectFailureRatesNPlus1(ctx context.Context, db *sql.DB, projectIDs []int64, beforeTick int64) ([]FailureRate, error) {
    queryCount.Add(1)
    _ = db.QueryRowContext(ctx, `SELECT COUNT(DISTINCT project_id) FROM runs`).Scan(new(int64))

    const perProject = `
        SELECT project_id,
               COUNT(*)                                   AS total,
               COALESCE(SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END), 0) AS failed
        FROM runs
        WHERE tick < ? AND project_id = ?
        GROUP BY project_id`

    out := make([]FailureRate, 0, len(projectIDs))
    for _, id := range projectIDs {
        f := FailureRate{ProjectID: id}
        queryCount.Add(1)
        err := db.QueryRowContext(ctx, perProject, beforeTick, id).
            Scan(&f.ProjectID, &f.Total, &f.Failed)
        if err != nil && err != sql.ErrNoRows {
            return nil, err
        }
        out = append(out, f)
    }
    return out, nil
}

failrater/failrater_test.go:

package failrater

import (
    "context"
    "database/sql"
    "testing"

    _ "modernc.org/sqlite"
)

const (
    numProjects = 368
    numTicks    = 200
)

func newFixture(tb testing.TB) *sql.DB {
    tb.Helper()
    db, err := sql.Open("sqlite", "file:fixture?mode=memory&cache=shared")
    if err != nil {
        tb.Fatal(err)
    }
    // In-memory SQLite + shared cache: keep a single writer connection.
    db.SetMaxOpenConns(1)
    tb.Cleanup(func() { db.Close() })

    schema := `
        CREATE TABLE runs (
            project_id INTEGER NOT NULL,
            tick       INTEGER NOT NULL,
            status     TEXT    NOT NULL
        );
        CREATE INDEX idx_runs_project_tick ON runs(project_id, tick);`
    if _, err := db.Exec(schema); err != nil {
        tb.Fatal(err)
    }

    tx, err := db.Begin()
    if err != nil {
        tb.Fatal(err)
    }
    stmt, err := tx.Prepare(`INSERT INTO runs(project_id, tick, status) VALUES(?,?,?)`)
    if err != nil {
        tb.Fatal(err)
    }
    for p := 0; p < numProjects; p++ {
        for tick := 0; tick < numTicks; tick++ {
            status := "success"
            if (p+tick)%7 == 0 {
                status = "failed"
            }
            if _, err := stmt.Exec(int64(p), int64(tick), status); err != nil {
                tb.Fatal(err)
            }
        }
    }
    stmt.Close()
    if err := tx.Commit(); err != nil {
        tb.Fatal(err)
    }
    return db
}

func projectIDs() []int64 {
    ids := make([]int64, numProjects)
    for i := range ids {
        ids[i] = int64(i)
    }
    return ids
}

func TestAggregateIsOneRoundTrip(t *testing.T) {
    db := newFixture(t)
    ctx := context.Background()

    ResetQueriesForTest()
    got, err := ComputeProjectFailureRates(ctx, db, projectIDs(), numTicks)
    if err != nil {
        t.Fatal(err)
    }
    if n := QueriesForTest(); n != 1 {
        t.Fatalf("aggregate round trips = %d, want 1", n)
    }
    if len(got) != numProjects {
        t.Fatalf("results = %d, want %d", len(got), numProjects)
    }
}

func TestNPlus1BaselineContrast(t *testing.T) {
    db := newFixture(t)
    ctx := context.Background()

    ResetQueriesForTest()
    if _, err := ComputeProjectFailureRatesNPlus1(ctx, db, projectIDs(), numTicks); err != nil {
        t.Fatal(err)
    }
    if n := QueriesForTest(); n != numProjects+1 {
        t.Fatalf("N+1 round trips = %d, want %d", n, numProjects+1)
    }
}

func TestCorrectness(t *testing.T) {
    db := newFixture(t)
    ctx := context.Background()

    agg, err := ComputeProjectFailureRates(ctx, db, projectIDs(), numTicks)
    if err != nil {
        t.Fatal(err)
    }
    byID := map[int64]FailureRate{}
    for _, f := range agg {
        byID[f.ProjectID] = f
    }
    for p := 0; p < numProjects; p++ {
        for tick := 0; tick < numTicks; tick++ {
            if (p+tick)%7 != 0 {
                continue
            }
            if byID[int64(p)].Failed == 0 {
                t.Fatalf("project %d should have failures", p)
            }
        }
    }
}

func BenchmarkComputeProjectFailureRates(b *testing.B) {
    db := newFixture(b)
    ctx := context.Background()
    ids := projectIDs()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        if _, err := ComputeProjectFailureRates(ctx, db, ids, numTicks); err != nil {
            b.Fatal(err)
        }
    }
}

The rule for applying it to any function:

  1. Add var queryCount atomic.Int64 at package scope (one per package is enough).
  2. Put queryCount.Add(1) immediately before every QueryContext, QueryRowContext, ExecContext, or PrepareContext that performs a round trip. If Prepare is cached and reused, count the Prepare once and each Stmt.Query/Exec.
  3. Add ResetQueriesForTest / QueriesForTest.
  4. In the test, reset, call, assert the exact integer.

Verification

Run from the module (reproducible with modernc.org/sqlite v1.59.0, Go 1.25/1.26):

$ go test ./failrater/ -v -count=1
=== RUN   TestAggregateIsOneRoundTrip
--- PASS: TestAggregateIsOneRoundTrip (0.15s)
=== RUN   TestNPlus1BaselineContrast
--- PASS: TestNPlus1BaselineContrast (0.15s)
=== RUN   TestCorrectness
--- PASS: TestCorrectness (0.15s)
PASS
ok      seam/failrater  0.457s

Race detector clean, and wall-time benchmark for the aggregate (counter overhead is negligible):

$ go test ./failrater/ -run . -race -count=1
ok      seam/failrater  16.547s

$ go test ./failrater/ -bench BenchmarkComputeProjectFailureRates -benchtime=2s -run '^$'
BenchmarkComputeProjectFailureRates-16    129    18287173 ns/op

$ go test ./seambench/ -bench BenchmarkSeamAdd -benchtime=2s -run '^$'
BenchmarkSeamAdd-16    1000000000    1.604 ns/op   # atomic Add overhead per round trip

The ~18 ms is SQLite scanning 73,600 rows — not the seam. atomic.Int64.Add costs ~1.6 ns, i.e. effectively free next to any real query.

Why the driver-wrap alternative was rejected (verified)

A wrapper exposing only driver.Conn did not panic in a simple smoke test on v1.59.0 — database/sql gracefully falls back to Prepare/Stmt — but it demonstrably drops the seven optional interfaces listed above and changes execution strategy. That is exactly the problem: the result is version-dependent and silently perturbs the path under test, so it cannot serve as a trustworthy seam. The counter has no such dependency.


One-line summary

var queryCount atomic.Int64 + queryCount.Add(1) before each DB call, exposed via ResetQueriesForTest/QueriesForTest, gives an exact, deterministic round-trip assertion (1 for the aggregate, 369 for the old N+1) with no driver wrapper, no re-registration, and ~1.6 ns overhead — pair it with a func Benchmark… for the wall-time story.

Evidence & signatures

# Evidence
- Problem class: go-test-seam-atomic-counter-vs-driver-wrap
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-22T00:33:06.593Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "When a Go function issues a fixed number of SQL queries and a test needs to assert that count, use a package-level atomic.Int64 counter inside the function as the test seam rather than wrapping database/sql with a custom driver. The counter is bumped once per QueryContext/ExecContext call; the test resets with reset...ForTest(), invokes the function, and reads the value. Reasons: (1) modernc.org/sqlite's driver surface is too rich (ConnPrepareContext, ConnQueryContext, ConnBeginTx, SessionResetter, etc.) to wrap reliably across versions \u2014 a partial driver panics at runtime on the path you didn't wrap. (2) sql.Register with a custom name requires re-registering on every test; sql.Open then goes through the registration map but the underlying modernc internals still expect the original name. (3) An atomic counter is two lines (var + .Add(1) before QueryContext), 2 ns overhead, and the test is deterministic \u2014 it counts operations, not wall time, so it does not flake on shared CI hosts. (4) The counter is the test seam ONLY; the production path always increments, so there's no feature flag drift. Proven on a 368-project x 200-tick scheduler fixture: counter was 1, proving the rewrite is a single aggregated query. Pre-fix N+1 would have been 369. Combined with a func BenchmarkComputeProjectFailureRates for wall-time, this is the cleanest two-tool proof (counter + bench) for any Go+SQL 'is this a single round trip' question.", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "go-test-seam-atomic-counter-vs-driver-wrap", "provider": "openrouter", "solved_at": "2026-09-22T00:33:06.593Z", "version": ""}
Generated from the verified corpus · MIT licensedBack to the catalog