blob: ab9df5f9bd42a3862d5a61ac310f2d2964836bfe [file] [log] [blame]
// Copyright 2021 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 postgres
import (
"context"
"database/sql"
"fmt"
"github.com/Masterminds/squirrel"
"golang.org/x/pkgsite/internal"
"golang.org/x/pkgsite/internal/database"
"golang.org/x/pkgsite/internal/derrors"
"golang.org/x/pkgsite/internal/experiment"
"golang.org/x/pkgsite/internal/middleware"
"golang.org/x/pkgsite/internal/symbol"
)
// LegacyGetSymbolHistory returns a map of the first version when a symbol name is
// added to the API, to the symbol name, to the UnitSymbol struct. The
// UnitSymbol.Children field will always be empty, as children names are also
// tracked.
func (db *DB) LegacyGetSymbolHistory(ctx context.Context, packagePath, modulePath string,
) (_ map[string]map[string]*internal.UnitSymbol, err error) {
defer derrors.Wrap(&err, "LegacyGetSymbolHistory(ctx, %q, %q)", packagePath, modulePath)
defer middleware.ElapsedStat(ctx, "LegacyGetSymbolHistory")()
if experiment.IsActive(ctx, internal.ExperimentReadSymbolHistory) {
return LegacyGetSymbolHistoryFromTable(ctx, db.db, packagePath, modulePath)
}
return LegacyGetSymbolHistoryWithPackageSymbols(ctx, db.db, packagePath, modulePath)
}
// LegacyGetSymbolHistoryFromTable fetches symbol history data from the symbol_history table.
//
// LegacyGetSymbolHistoryFromTable is exported for use in tests.
func LegacyGetSymbolHistoryFromTable(ctx context.Context, ddb *database.DB,
packagePath, modulePath string) (_ map[string]map[string]*internal.UnitSymbol, err error) {
defer derrors.WrapStack(&err, "GetSymbolHistoryFromTable(ctx, ddb, %q, %q)", packagePath, modulePath)
q := squirrel.Select(
"s1.name AS symbol_name",
"s2.name AS parent_symbol_name",
"ps.section",
"ps.type",
"ps.synopsis",
"sh.since_version",
"sh.goos",
"sh.goarch",
).From("symbol_history sh").
Join("package_symbols ps ON ps.id = sh.package_symbol_id").
Join("symbol_names s1 ON ps.symbol_name_id = s1.id").
Join("symbol_names s2 ON ps.parent_symbol_name_id = s2.id").
Join("paths p1 ON sh.package_path_id = p1.id").
Join("paths p2 ON sh.module_path_id = p2.id").
Where(squirrel.Eq{"p1.path": packagePath}).
Where(squirrel.Eq{"p2.path": modulePath})
query, args, err := q.PlaceholderFormat(squirrel.Dollar).ToSql()
if err != nil {
return nil, err
}
// versionToNameToUnitSymbol is a map of the version a symbol was
// introduced, to the name and unit symbol.
versionToNameToUnitSymbol := map[string]map[string]*internal.UnitSymbol{}
collect := func(rows *sql.Rows) error {
var (
newUS internal.UnitSymbol
build internal.BuildContext
v string
)
if err := rows.Scan(
&newUS.Name,
&newUS.ParentName,
&newUS.Section,
&newUS.Kind,
&newUS.Synopsis,
&v,
&build.GOOS,
&build.GOARCH,
); err != nil {
return fmt.Errorf("row.Scan(): %v", err)
}
nts, ok := versionToNameToUnitSymbol[v]
if !ok {
nts = map[string]*internal.UnitSymbol{}
versionToNameToUnitSymbol[v] = nts
}
us, ok := nts[newUS.Name]
if !ok {
us = &newUS
nts[newUS.Name] = us
}
us.AddBuildContext(build)
return nil
}
if err := ddb.RunQuery(ctx, query, collect, args...); err != nil {
return nil, err
}
return versionToNameToUnitSymbol, nil
}
// LegacyGetSymbolHistoryWithPackageSymbols fetches symbol history data by using data
// from package_symbols and documentation_symbols, and computed using
// symbol.IntroducedHistory.
//
// LegacyGetSymbolHistoryWithPackageSymbols is exported for use in tests.
func LegacyGetSymbolHistoryWithPackageSymbols(ctx context.Context, ddb *database.DB,
packagePath, modulePath string) (_ map[string]map[string]*internal.UnitSymbol, err error) {
defer derrors.WrapStack(&err, "GetSymbolHistoryWithPackageSymbols(ctx, ddb, %q, %q)", packagePath, modulePath)
defer middleware.ElapsedStat(ctx, "GetSymbolHistoryWithPackageSymbols")()
versionToNameToUnitSymbols, err := legacyGetPackageSymbols(ctx, ddb, packagePath, modulePath)
if err != nil {
return nil, err
}
return symbol.LegacyIntroducedHistory(versionToNameToUnitSymbols), nil
}
// getSymbolHistoryForBuildContext returns a map of the first version when a symbol name is
// added to the API for the specified build context, to the symbol name, to the
// UnitSymbol struct. The UnitSymbol.Children field will always be empty, as
// children names are also tracked.
func getSymbolHistoryForBuildContext(ctx context.Context, ddb *database.DB, pathID int, modulePath string,
bc internal.BuildContext) (_ map[string]string, err error) {
defer derrors.WrapStack(&err, "getSymbolHistoryForBuildContext(ctx, ddb, %d, %q)", pathID, modulePath)
defer middleware.ElapsedStat(ctx, "getSymbolHistoryForBuildContext")()
if bc == internal.BuildContextAll {
bc = internal.BuildContextLinux
}
q := squirrel.Select(
"s1.name AS symbol_name",
"sh.since_version",
).From("symbol_history sh").
Join("package_symbols ps ON ps.id = sh.package_symbol_id").
Join("symbol_names s1 ON ps.symbol_name_id = s1.id").
Join("symbol_names s2 ON ps.parent_symbol_name_id = s2.id").
Join("paths p2 ON sh.module_path_id = p2.id").
Where(squirrel.Eq{"sh.package_path_id": pathID}).
Where(squirrel.Eq{"p2.path": modulePath}).
Where(squirrel.Eq{"sh.goos": bc.GOOS}).
Where(squirrel.Eq{"sh.goarch": bc.GOARCH})
query, args, err := q.PlaceholderFormat(squirrel.Dollar).ToSql()
if err != nil {
return nil, err
}
// versionToNameToUnitSymbol is a map of the version a symbol was
// introduced, to the name and unit symbol.
nameToVersion := map[string]string{}
collect := func(rows *sql.Rows) error {
var n, v string
if err := rows.Scan(&n, &v); err != nil {
return fmt.Errorf("row.Scan(): %v", err)
}
nameToVersion[n] = v
return nil
}
if err := ddb.RunQuery(ctx, query, collect, args...); err != nil {
return nil, err
}
return nameToVersion, nil
}