Files
upstream-sqlc/internal/cache/digest.go
upstream-mirror 0f4df23290
Some checks failed
kotlin / test (push) Has been skipped
python / test (push) Has been skipped
typescript / test (push) Has been skipped
go / build darwin/amd64 (push) Has been cancelled
go / build linux/amd64 (push) Has been cancelled
go / build windows/amd64 (push) Has been cancelled
go / build darwin/arm64 (push) Has been cancelled
go / build linux/arm64 (push) Has been cancelled
go / build windows/arm64 (push) Has been cancelled
go / test (push) Has been cancelled
go / vuln_check (push) Has been cancelled
snapshot of https://github.com/sqlc-dev/sqlc.git (main, 2026-09-16; blobs > 131072B omitted)
2026-09-16 02:17:34 +05:00

101 lines
3.0 KiB
Go

package cache
import (
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"fmt"
"hash"
)
// Digest identifies a blob by its SHA-256 hash and size, mirroring the
// Digest message from Bazel's remote execution API. The size is stored
// alongside the hash so that entries can be validated without reading blob
// contents; a negative size means the size is unknown, as with a checksum
// declared in a configuration file.
type Digest struct {
// Hash is the lowercase hex-encoded SHA-256 hash of the blob.
Hash string `json:"hash"`
// SizeBytes is the length of the blob in bytes, or negative if unknown.
SizeBytes int64 `json:"size_bytes"`
}
func (d Digest) String() string {
return fmt.Sprintf("sha256:%s/%d", d.Hash, d.SizeBytes)
}
func (d Digest) valid() bool {
if len(d.Hash) != 64 {
return false
}
_, err := hex.DecodeString(d.Hash)
return err == nil
}
// DigestOf returns the Digest of a blob.
func DigestOf(data []byte) Digest {
sum := sha256.Sum256(data)
return Digest{
Hash: hex.EncodeToString(sum[:]),
SizeBytes: int64(len(data)),
}
}
// SHA256Digest returns a Digest referencing a blob by a declared SHA-256
// checksum whose size is not known, suitable for looking up remotely fetched
// content in the CAS.
func SHA256Digest(hexhash string) Digest {
return Digest{
Hash: hexhash,
SizeBytes: -1,
}
}
// An Action describes a unit of cacheable work, playing the role of Bazel's
// Action message: a mnemonic naming the kind of work plus the complete set of
// inputs that determine its outputs. Two actions with the same digest are
// assumed to produce the same outputs.
//
// Inputs are hashed incrementally with length-prefixed framing so that the
// boundary between inputs is unambiguous ("ab"+"c" hashes differently from
// "a"+"bc").
type Action struct {
hasher hash.Hash
}
// NewAction starts building an action key for the given mnemonic, e.g.
// "QueryAnalysis". The sha256 of the sqlc binary itself is always the first
// input: the tool that executes an action determines its outputs just as
// much as the declared inputs do, so a rebuilt sqlc never reuses stale
// entries. The binary's digest is memoized in the cache — see toolDigest —
// which is why actions are created through a Cache.
func (c *Cache) NewAction(mnemonic string) *Action {
a := &Action{hasher: sha256.New()}
a.write([]byte(mnemonic))
a.AddInput("tool", c.toolDigest())
return a
}
// AddInput mixes a named input into the action key. Order matters: callers
// must add inputs in a deterministic order.
func (a *Action) AddInput(name string, data []byte) *Action {
a.write([]byte(name))
a.write(data)
return a
}
// Digest returns the action's digest, used as the action cache key.
func (a *Action) Digest() Digest {
return Digest{
Hash: hex.EncodeToString(a.hasher.Sum(nil)),
SizeBytes: 0,
}
}
func (a *Action) write(data []byte) {
var frame [8]byte
binary.LittleEndian.PutUint64(frame[:], uint64(len(data)))
a.hasher.Write(frame[:])
a.hasher.Write(data)
}