Skip to content

Commit c34bc9b

Browse files
committed
cache: make the sqlc binary an implicit input of every action
Version strings under-invalidate: every dev build reports the same version, so a rebuilt sqlc with different analysis or codegen logic would keep hitting entries produced by the old binary. Hash the running executable once per process and mix it into every action digest as the first input, the way Bazel treats the toolchain as action input. This subsumes the explicit version, wazero version, and GOOS/GOARCH inputs, which are all determined by the binary, so drop them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MPgq3rwip76D554BktbqR4
1 parent 687ebb6 commit c34bc9b

5 files changed

Lines changed: 58 additions & 28 deletions

File tree

internal/analyzer/analyzer.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"github.com/sqlc-dev/sqlc/internal/analysis"
1212
"github.com/sqlc-dev/sqlc/internal/cache"
1313
"github.com/sqlc-dev/sqlc/internal/config"
14-
"github.com/sqlc-dev/sqlc/internal/info"
1514
"github.com/sqlc-dev/sqlc/internal/sql/ast"
1615
"github.com/sqlc-dev/sqlc/internal/sql/named"
1716
)
@@ -66,10 +65,10 @@ func (c *CachedAnalyzer) analyze(ctx context.Context, n ast.Node, q string, sche
6665
}
6766
}
6867

69-
// Analyzing a query is an action whose inputs are the sqlc version, the
70-
// configuration, the schema migrations, and the query itself.
68+
// Analyzing a query is an action whose inputs are the configuration, the
69+
// schema migrations, and the query itself. (The sqlc binary is an
70+
// implicit input of every action.)
7171
action := cache.NewAction("QueryAnalysis").
72-
AddInput("version", []byte(info.Version)).
7372
AddInput("config", c.configBytes)
7473
for _, m := range schema {
7574
action.AddInput("schema", []byte(m))

internal/cache/cache_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,18 @@ func TestCASDeclaredChecksumLoad(t *testing.T) {
134134
}
135135
}
136136

137+
func TestToolDigest(t *testing.T) {
138+
d := toolDigest()
139+
if len(d) == 0 {
140+
t.Fatal("toolDigest returned no bytes")
141+
}
142+
// The digest is hashed once and must be stable within a process, or
143+
// identical actions would stop matching.
144+
if string(toolDigest()) != string(d) {
145+
t.Error("toolDigest is not stable across calls")
146+
}
147+
}
148+
137149
func TestActionDigestFraming(t *testing.T) {
138150
a := NewAction("Test").AddInput("query", []byte("ab")).AddInput("schema", []byte("c"))
139151
b := NewAction("Test").AddInput("query", []byte("a")).AddInput("schema", []byte("bc"))

internal/cache/digest.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,14 @@ type Action struct {
6464
}
6565

6666
// NewAction starts building an action key for the given mnemonic, e.g.
67-
// "QueryAnalysis".
67+
// "QueryAnalysis". The sha256 of the sqlc binary itself is always the first
68+
// input: the tool that executes an action determines its outputs just as
69+
// much as the declared inputs do, so a rebuilt sqlc never reuses stale
70+
// entries.
6871
func NewAction(mnemonic string) *Action {
6972
a := &Action{hasher: sha256.New()}
7073
a.write([]byte(mnemonic))
74+
a.AddInput("tool", toolDigest())
7175
return a
7276
}
7377

internal/cache/tool.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package cache
2+
3+
import (
4+
"crypto/sha256"
5+
"io"
6+
"os"
7+
"sync"
8+
9+
"github.com/sqlc-dev/sqlc/internal/info"
10+
)
11+
12+
// toolDigest returns the sha256 of the running sqlc binary, hashed once per
13+
// process. The binary is an input to every action — a rebuilt sqlc may
14+
// analyze queries or embed a different wazero than the one that produced a
15+
// cache entry, even when the version string is unchanged (dev builds). If
16+
// the executable can't be read, fall back to the version string.
17+
var toolDigest = sync.OnceValue(func() []byte {
18+
path, err := os.Executable()
19+
if err != nil {
20+
return []byte(info.Version)
21+
}
22+
f, err := os.Open(path)
23+
if err != nil {
24+
return []byte(info.Version)
25+
}
26+
defer f.Close()
27+
h := sha256.New()
28+
if _, err := io.Copy(h, f); err != nil {
29+
return []byte(info.Version)
30+
}
31+
return h.Sum(nil)
32+
})

internal/ext/wasm/wasm.go

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"net/http"
1212
"os"
1313
"runtime"
14-
"runtime/debug"
1514
"strings"
1615

1716
"github.com/tetratelabs/wazero"
@@ -135,16 +134,14 @@ func (r *Runner) loadAndCompileWASM(ctx context.Context, store *cache.Cache, exp
135134
return nil, err
136135
}
137136

138-
// Compiling the module to machine code is itself a cacheable action,
139-
// keyed by the module's checksum and everything else that determines the
140-
// generated code. Compiled artifacts are materialized into an exec
141-
// directory for wazero's compilation cache to find; the authoritative
142-
// copies live in the CAS.
137+
// Compiling the module to machine code is itself a cacheable action.
138+
// Its only declared input is the module's checksum: the embedded wazero
139+
// version and the target platform are determined by the sqlc binary,
140+
// which is an implicit input of every action. Compiled artifacts are
141+
// materialized into an exec directory for wazero's compilation cache to
142+
// find; the authoritative copies live in the CAS.
143143
compileAction := cache.NewAction("CompileModule").
144144
AddInput("wasm", []byte(expected)).
145-
AddInput("wazero", []byte(wazeroVersion())).
146-
AddInput("goos", []byte(runtime.GOOS)).
147-
AddInput("goarch", []byte(runtime.GOARCH)).
148145
Digest()
149146

150147
execDir, err := store.ExecDir(compileAction)
@@ -187,20 +184,6 @@ func (r *Runner) loadAndCompileWASM(ctx context.Context, store *cache.Cache, exp
187184
return &runtimeAndCode{rt: rt, code: code}, nil
188185
}
189186

190-
// wazeroVersion returns the version of the wazero dependency, an input to
191-
// the CompileModule action: its generated machine code changes between
192-
// wazero releases.
193-
func wazeroVersion() string {
194-
if bi, ok := debug.ReadBuildInfo(); ok {
195-
for _, dep := range bi.Deps {
196-
if dep.Path == "github.com/tetratelabs/wazero" {
197-
return dep.Version
198-
}
199-
}
200-
}
201-
return info.Version
202-
}
203-
204187
// removePGCatalog removes the pg_catalog schema from the request. There is a
205188
// mysterious (reason unknown) bug with wasm plugins when a large amount of
206189
// tables (like there are in the catalog) are sent.

0 commit comments

Comments
 (0)