Files
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

104 lines
3.3 KiB
Go

// Package cache implements sqlc's on-disk cache, designed after Bazel's
// local disk cache.
//
// It has two halves:
//
// - A content-addressable store (CAS) holding blobs keyed by the SHA-256
// hash of their contents, laid out as cas/<xx>/<hash>.
// - An action cache (AC) mapping the digest of an action — a description of
// cacheable work and all of its inputs — to the digests of the outputs
// that work produced, laid out as ac/<xx>/<hash>.
//
// Work whose output is not derivable from its inputs, like query analysis,
// uses both halves, just like Bazel: hash the action, look its digest up in
// the action cache, then fetch the referenced output blobs from the CAS.
//
// Remote fetches with a declared checksum, like WASM plugins, need no action
// cache entry at all: the declared sha256 is itself a content address, so the
// blob is stored and loaded directly from the CAS keyed by that checksum.
package cache
import (
"fmt"
"os"
"path/filepath"
)
// Cache bundles the CAS and the action cache that shares it. All storage
// I/O is confined to the cache directory through an os.Root; callers should
// Close the cache when finished with it to release the root.
type Cache struct {
root *os.Root
CAS *CAS
Actions *ActionCache
}
// Dir returns the cache root, defaulting to os.UserCacheDir(). The location
// can be overridden with the SQLCCACHE environment variable.
func Dir() (string, error) {
cache := os.Getenv("SQLCCACHE")
if cache != "" {
return cache, nil
}
cacheHome, err := os.UserCacheDir()
if err != nil {
return "", err
}
return filepath.Join(cacheHome, "sqlc"), nil
}
// Open returns the cache rooted at Dir().
func Open() (*Cache, error) {
root, err := Dir()
if err != nil {
return nil, err
}
return OpenAt(root)
}
// OpenAt returns the cache rooted at the given directory, creating it if
// necessary.
func OpenAt(dir string) (*Cache, error) {
if err := os.MkdirAll(dir, 0755); err != nil {
return nil, fmt.Errorf("failed to create %s directory: %w", dir, err)
}
root, err := os.OpenRoot(dir)
if err != nil {
return nil, fmt.Errorf("cache: %w", err)
}
cas, err := newCAS(root)
if err != nil {
root.Close()
return nil, err
}
return &Cache{
root: root,
CAS: cas,
Actions: newActionCache(root, cas),
}, nil
}
// Close releases the cache's handle on its root directory.
func (c *Cache) Close() error {
return c.root.Close()
}
// ExecDir creates a fresh, private scratch directory for materializing the
// output tree of the given action, for tools that need their outputs on disk
// (like wazero's compilation cache). Each call returns a new directory under
// exec/, so two concurrent processes never share one — otherwise a tool
// staging files there (wazero writes <key>.tmp files in place) could be swept
// into the other's PutTree. The caller must remove it when done; its contents
// are always reproducible from the CAS, so losing it is harmless.
func (c *Cache) ExecDir(action Digest) (string, error) {
base := filepath.Join(c.root.Name(), "exec")
if err := os.MkdirAll(base, 0755); err != nil {
return "", fmt.Errorf("failed to create %s directory: %w", base, err)
}
dir, err := os.MkdirTemp(base, action.Hash+"-")
if err != nil {
return "", fmt.Errorf("cache: %w", err)
}
return dir, nil
}