Files
upstream-sqlc/internal/analyzer/analyzer.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

132 lines
3.3 KiB
Go

package analyzer
import (
"context"
"encoding/json"
"errors"
"log/slog"
"google.golang.org/protobuf/proto"
"github.com/sqlc-dev/sqlc/internal/analysis"
"github.com/sqlc-dev/sqlc/internal/cache"
"github.com/sqlc-dev/sqlc/internal/config"
"github.com/sqlc-dev/sqlc/internal/sql/ast"
"github.com/sqlc-dev/sqlc/internal/sql/named"
)
type CachedAnalyzer struct {
a Analyzer
config config.Config
configBytes []byte
db config.Database
store *cache.Cache
}
func Cached(a Analyzer, c config.Config, db config.Database) *CachedAnalyzer {
return &CachedAnalyzer{
a: a,
config: c,
db: db,
}
}
// Create a new error here
func (c *CachedAnalyzer) Analyze(ctx context.Context, n ast.Node, q string, schema []string, np *named.ParamSet) (*analysis.Analysis, error) {
result, rerun, err := c.analyze(ctx, n, q, schema, np)
if rerun {
if err != nil {
slog.Warn("first analysis failed with error", "err", err)
}
return c.a.Analyze(ctx, n, q, schema, np)
}
return result, err
}
// The name of the sole output blob a QueryAnalysis action produces.
const analysisOutput = "analysis.pb"
func (c *CachedAnalyzer) analyze(ctx context.Context, n ast.Node, q string, schema []string, np *named.ParamSet) (*analysis.Analysis, bool, error) {
// Only cache queries for managed databases. We can't be certain the
// database is in an unchanged state otherwise
if !c.db.Managed {
return nil, true, nil
}
if c.store == nil {
var err error
c.store, err = cache.Open()
if err != nil {
return nil, true, err
}
}
store := c.store
if c.configBytes == nil {
var err error
c.configBytes, err = json.Marshal(c.config)
if err != nil {
return nil, true, err
}
}
// Analyzing a query is an action whose inputs are the configuration, the
// schema migrations, and the query itself. (The sqlc binary is an
// implicit input of every action.)
action := store.NewAction("QueryAnalysis").
AddInput("config", c.configBytes)
for _, m := range schema {
action.AddInput("schema", []byte(m))
}
actionDigest := action.AddInput("query", []byte(q)).Digest()
if cached, err := store.Actions.Get(actionDigest); err == nil {
contents, err := store.CAS.Get(cached.Outputs[analysisOutput])
if err == nil {
var a analysis.Analysis
if err := proto.Unmarshal(contents, &a); err == nil {
return &a, false, nil
}
}
if !errors.Is(err, cache.ErrNotFound) {
slog.Warn("reading analysis from cache failed", "err", err)
}
}
result, err := c.a.Analyze(ctx, n, q, schema, np)
if err == nil {
contents, err := proto.Marshal(result)
if err != nil {
slog.Warn("unable to marshal analysis", "err", err)
return result, false, nil
}
outDigest, err := store.CAS.Put(contents)
if err != nil {
slog.Warn("saving analysis to cache failed", "err", err)
return result, false, nil
}
err = store.Actions.Put(actionDigest, &cache.ActionResult{
Outputs: map[string]cache.Digest{analysisOutput: outDigest},
})
if err != nil {
slog.Warn("saving analysis action result failed", "err", err)
}
}
return result, false, err
}
func (c *CachedAnalyzer) Close(ctx context.Context) error {
if c.store != nil {
c.store.Close()
}
return c.a.Close(ctx)
}
type Analyzer interface {
Analyze(context.Context, ast.Node, string, []string, *named.ParamSet) (*analysis.Analysis, error)
Close(context.Context) error
}