blob: 6b796aaaec6fbcd2d44c7bccbdd0d8f35be23ae3 [file] [log] [blame]
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package bigquery
import (
"context"
"time"
bq "cloud.google.com/go/bigquery"
"golang.org/x/pkgsite-metrics/internal/derrors"
)
const AnalysisTableName = "analysis"
// Note: before modifying AnalysisResult, make sure the change
// is a valid schema modification.
// The only supported changes are:
// - adding a nullable or repeated column
// - dropping a column
// - changing a column from required to nullable.
// See https://cloud.google.com/bigquery/docs/managing-table-schemas for details.
// AnalysisResult is a row in the BigQuery analysis table. It corresponds to a
// result from the output for an analysis.
type AnalysisResult struct {
CreatedAt time.Time `bigquery:"created_at"`
ModulePath string `bigquery:"module_path"`
Version string `bigquery:"version"`
SortVersion string `bigquery:"sort_version"`
CommitTime time.Time `bigquery:"commit_time"`
// The name of the analysis binary that was executed.
// A single binary may run multiple analyzers.
BinaryName string `bigquery:"binary_name"`
Error string `bigquery:"error"`
ErrorCategory string `bigquery:"error_category"`
AnalysisWorkVersion // InferSchema flattens embedded fields
Diagnostics []*Diagnostic `bigquery:"diagnostic"`
}
func (r *AnalysisResult) AddError(err error) {
if err == nil {
return
}
r.Error = err.Error()
r.ErrorCategory = derrors.CategorizeError(err)
}
// AnalysisWorkVersion contains information that can be used to avoid duplicate work.
// Given two AnalysisWorkVersion values v1 and v2 for the same module path and version,
// if v1 == v2 then it is not necessary to scan the module.
type AnalysisWorkVersion struct {
// A hash of the binary executed.
BinaryVersion string `bigquery:"binary_version"`
BinaryArgs string `bigquery:"binary_args"` // args passed to binary
// The version of the currently running code. This tracks changes in the
// logic of module scanning and processing.
WorkerVersion string `bigquery:"worker_version"`
// The version of the bigquery schema.
SchemaVersion string ` bigquery:"schema_version"`
}
// A Diagnostic is a single analyzer finding.
type Diagnostic struct {
// The package ID as reported by the analysis binary.
PackageID string `bigquery:"package_id"`
AnalyzerName string `bigquery:"analyzer_name"`
Error string `bigquery:"error"`
// These fields are from internal/worker.JSONDiagnostic.
Category string `bigquery:"category"`
Position string `bigquery:"position"`
Message string `bigquery:"message"`
}
// AnalysisSchemaVersion changes whenever the analysis schema changes.
var AnalysisSchemaVersion string
func init() {
s, err := bq.InferSchema(AnalysisResult{})
if err != nil {
panic(err)
}
AnalysisSchemaVersion = schemaVersion(s)
addTable(AnalysisTableName, s)
}
// ReadAnalysisWorkVersions reads the most recent WorkVersions in the analysis table.
func ReadAnalysisWorkVersions(ctx context.Context, c *Client) (_ map[[2]string]*AnalysisWorkVersion, err error) {
defer derrors.Wrap(&err, "ReadAnalysisWorkVersions")
m := map[[2]string]*AnalysisWorkVersion{}
query := partitionQuery(c.FullTableName(AnalysisTableName), "module_path, sort_version", "created_at DESC")
iter, err := c.Query(ctx, query)
if err != nil {
return nil, err
}
err = ForEachRow(iter, func(r *AnalysisResult) bool {
m[[2]string{r.ModulePath, r.Version}] = &r.AnalysisWorkVersion
return true
})
if err != nil {
return nil, err
}
return m, nil
}