| // 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/middleware/stats" |
| ) |
| |
| // GetSymbols returns all of the symbols for a given package path and module path. |
| func (db *DB) GetSymbols(ctx context.Context, pkgPath, modulePath, version string, bc internal.BuildContext) (_ []*internal.Symbol, err error) { |
| defer derrors.Wrap(&err, "DB.GetSymbols(ctx, %q, %q, %q, %v)", pkgPath, modulePath, version, bc) |
| defer stats.Elapsed(ctx, "DB.GetSymbols")() |
| |
| uc, err := db.getUnitContext(ctx, pkgPath, modulePath, version, bc) |
| if err != nil { |
| return nil, err |
| } |
| if uc.docID == 0 { |
| return nil, fmt.Errorf("%w: package has no documentation", derrors.NotFound) |
| } |
| |
| query := packageSymbolQuery( |
| squirrel.Select( |
| "s1.name AS symbol_name", |
| "s2.name AS parent_symbol_name", |
| "ps.section", |
| "ps.type", |
| "ps.synopsis")). |
| Where(squirrel.Eq{"ds.documentation_id": uc.docID}). |
| OrderBy("CASE WHEN ps.type='Type' THEN 0 ELSE 1 END"). |
| OrderBy("s1.name") |
| |
| q, args, err := query.PlaceholderFormat(squirrel.Dollar).ToSql() |
| if err != nil { |
| return nil, err |
| } |
| |
| var symbols []*internal.Symbol |
| symbolMap := make(map[string]*internal.Symbol) |
| collect := func(rows *sql.Rows) error { |
| var ( |
| name, parentName, synopsis string |
| section internal.SymbolSection |
| kind internal.SymbolKind |
| ) |
| if err := rows.Scan(&name, &parentName, §ion, &kind, &synopsis); err != nil { |
| return fmt.Errorf("row.Scan(): %v", err) |
| } |
| sm := internal.SymbolMeta{ |
| Name: name, |
| ParentName: parentName, |
| Section: section, |
| Kind: kind, |
| Synopsis: synopsis, |
| } |
| if sm.ParentName != "" && sm.ParentName != sm.Name { |
| if parent, ok := symbolMap[sm.ParentName]; ok { |
| parent.Children = append(parent.Children, &sm) |
| return nil |
| } |
| } |
| // Treat as top-level if no parent or parent not found in this build context. |
| s := &internal.Symbol{ |
| SymbolMeta: sm, |
| GOOS: uc.bestBC.GOOS, |
| GOARCH: uc.bestBC.GOARCH, |
| } |
| symbols = append(symbols, s) |
| symbolMap[sm.Name] = s |
| return nil |
| } |
| |
| if err := db.db.RunQuery(ctx, q, collect, args...); err != nil { |
| return nil, err |
| } |
| |
| if len(symbols) == 0 { |
| return nil, derrors.NotFound |
| } |
| return symbols, nil |
| } |
| |
| // getPackageSymbols returns all of the symbols for a given package path and module path. |
| func getPackageSymbols(ctx context.Context, ddb *database.DB, packagePath, modulePath string, |
| ) (_ *internal.SymbolHistory, err error) { |
| defer derrors.Wrap(&err, "getPackageSymbols(ctx, ddb, %q, %q)", packagePath, modulePath) |
| defer stats.Elapsed(ctx, "getPackageSymbols")() |
| |
| query := packageSymbolQueryJoin( |
| squirrel.Select( |
| "s1.name AS symbol_name", |
| "s2.name AS parent_symbol_name", |
| "ps.section", |
| "ps.type", |
| "ps.synopsis", |
| "m.version", |
| "d.goos", |
| "d.goarch"), packagePath, modulePath). |
| Where("NOT m.incompatible"). |
| Where(squirrel.Eq{"m.version_type": "release"}). |
| OrderBy("CASE WHEN ps.type='Type' THEN 0 ELSE 1 END"). |
| OrderBy("s1.name") |
| q, args, err := query.PlaceholderFormat(squirrel.Dollar).ToSql() |
| if err != nil { |
| return nil, err |
| } |
| sh, collect := collectSymbolHistory(func(sh *internal.SymbolHistory, sm internal.SymbolMeta, v string, build internal.BuildContext) error { |
| if sm.Section == internal.SymbolSectionTypes && sm.Kind != internal.SymbolKindType { |
| _, err := sh.GetSymbol(sm.ParentName, v, build) |
| if err != nil { |
| return fmt.Errorf("could not find parent for %q: %v", sm.Name, err) |
| } |
| return nil |
| } |
| return nil |
| }) |
| if err := ddb.RunQuery(ctx, q, collect, args...); err != nil { |
| return nil, err |
| } |
| return sh, nil |
| } |
| |
| func packageSymbolQuery(query squirrel.SelectBuilder) squirrel.SelectBuilder { |
| return query.From("documentation_symbols ds"). |
| Join("package_symbols ps ON ps.id = ds.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") |
| } |
| |
| func packageSymbolQueryJoin(query squirrel.SelectBuilder, pkgPath, modulePath string) squirrel.SelectBuilder { |
| return packageSymbolQuery(query). |
| Join("documentation d ON d.id = ds.documentation_id"). |
| Join("units u on u.id = d.unit_id"). |
| Join("modules m ON m.id = u.module_id"). |
| Join("paths p1 ON u.path_id = p1.id"). |
| Where(squirrel.Eq{"p1.path": pkgPath}). |
| Where(squirrel.Eq{"m.module_path": modulePath}) |
| } |
| |
| func collectSymbolHistory(check func(sh *internal.SymbolHistory, sm internal.SymbolMeta, v string, build internal.BuildContext) error) (*internal.SymbolHistory, func(rows *sql.Rows) error) { |
| sh := internal.NewSymbolHistory() |
| return sh, func(rows *sql.Rows) (err error) { |
| defer derrors.Wrap(&err, "collectSymbolHistory") |
| var ( |
| sm internal.SymbolMeta |
| build internal.BuildContext |
| v string |
| ) |
| if err := rows.Scan( |
| &sm.Name, |
| &sm.ParentName, |
| &sm.Section, |
| &sm.Kind, |
| &sm.Synopsis, |
| &v, |
| &build.GOOS, |
| &build.GOARCH, |
| ); err != nil { |
| return fmt.Errorf("row.Scan(): %v", err) |
| } |
| if err := check(sh, sm, v, build); err != nil { |
| return fmt.Errorf("check(): %v", err) |
| } |
| sh.AddSymbol(sm, v, build) |
| return nil |
| } |
| } |