| // Copyright 2020 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" |
| "strings" |
| |
| "github.com/lib/pq" |
| "golang.org/x/pkgsite/internal" |
| "golang.org/x/pkgsite/internal/derrors" |
| "golang.org/x/pkgsite/internal/stdlib" |
| ) |
| |
| // GetPathInfo returns information about the "best" entity (module, path or directory) with |
| // the given path. The module and version arguments provide additional constraints. |
| // If the module is unknown, pass internal.UnknownModulePath; if the version is unknown, pass |
| // internal.LatestVersion. |
| // |
| // The rules for picking the best are: |
| // 1. Match the module path and or version, if they are provided; |
| // 2. Prefer newer module versions to older, and release to pre-release; |
| // 3. In the unlikely event of two paths at the same version, pick the longer module path. |
| func (db *DB) GetPathInfo(ctx context.Context, path, inModulePath, inVersion string) (outModulePath, outVersion string, isPackage bool, err error) { |
| defer derrors.Wrap(&err, "DB.GetPathInfo(ctx, %q, %q, %q)", path, inModulePath, inVersion) |
| |
| var constraints []string |
| args := []interface{}{path} |
| if inModulePath != internal.UnknownModulePath { |
| constraints = append(constraints, fmt.Sprintf("AND m.module_path = $%d", len(args)+1)) |
| args = append(args, inModulePath) |
| } |
| if inVersion != internal.LatestVersion { |
| constraints = append(constraints, fmt.Sprintf("AND m.version = $%d", len(args)+1)) |
| args = append(args, inVersion) |
| } |
| query := fmt.Sprintf(` |
| SELECT m.module_path, m.version, p.name != '' |
| FROM paths p |
| INNER JOIN modules m ON (p.module_id = m.id) |
| WHERE p.path = $1 |
| %s |
| ORDER BY |
| m.version_type = 'release' DESC, |
| m.sort_version DESC, |
| m.module_path DESC |
| LIMIT 1 |
| `, strings.Join(constraints, " ")) |
| err = db.db.QueryRow(ctx, query, args...).Scan(&outModulePath, &outVersion, &isPackage) |
| switch err { |
| case sql.ErrNoRows: |
| return "", "", false, derrors.NotFound |
| case nil: |
| return outModulePath, outVersion, isPackage, nil |
| default: |
| return "", "", false, err |
| } |
| } |
| |
| type dbPath struct { |
| id int64 |
| path string |
| moduleID int64 |
| v1Path string |
| name string |
| licenseTypes []string |
| licensePaths []string |
| redistributable bool |
| } |
| |
| func (db *DB) getPathsInModule(ctx context.Context, modulePath, version string) (_ []*dbPath, err error) { |
| defer derrors.Wrap(&err, "DB.getPathsInModule(ctx, %q, %q)", modulePath, version) |
| query := ` |
| SELECT |
| p.id, |
| p.path, |
| p.module_id, |
| p.v1_path, |
| p.name, |
| p.license_types, |
| p.license_paths, |
| p.redistributable |
| FROM |
| paths p |
| INNER JOIN |
| modules m |
| ON |
| p.module_id = m.id |
| WHERE |
| m.module_path = $1 |
| AND m.version = $2 |
| ORDER BY path;` |
| |
| var paths []*dbPath |
| collect := func(rows *sql.Rows) error { |
| var p dbPath |
| if err := rows.Scan(&p.id, &p.path, &p.moduleID, &p.v1Path, &p.name, pq.Array(&p.licenseTypes), |
| pq.Array(&p.licensePaths), &p.redistributable); err != nil { |
| return fmt.Errorf("row.Scan(): %v", err) |
| } |
| paths = append(paths, &p) |
| return nil |
| } |
| if err := db.db.RunQuery(ctx, query, collect, modulePath, version); err != nil { |
| return nil, err |
| } |
| return paths, nil |
| } |
| |
| // GetStdlibPathsWithSuffix returns information about all paths in the latest version of the standard |
| // library whose last component is suffix. A path that exactly match suffix is not included; |
| // the path must end with "/" + suffix. |
| // |
| // We are only interested in actual standard library packages: not commands, which we happen to include |
| // in the stdlib module, and not directories (paths that do not contain a package). |
| func (db *DB) GetStdlibPathsWithSuffix(ctx context.Context, suffix string) (paths []string, err error) { |
| defer derrors.Wrap(&err, "DB.GetStdlibPaths(ctx, %q)", suffix) |
| |
| q := ` |
| SELECT path |
| FROM paths |
| WHERE module_id = ( |
| -- latest release version of stdlib |
| SELECT id |
| FROM modules |
| WHERE module_path = $1 |
| ORDER BY |
| version_type = 'release' DESC, |
| sort_version DESC |
| LIMIT 1) |
| AND name != '' |
| AND path NOT LIKE 'cmd/%' |
| AND path LIKE '%/' || $2 |
| ORDER BY path |
| ` |
| err = db.db.RunQuery(ctx, q, func(rows *sql.Rows) error { |
| var p string |
| if err := rows.Scan(&p); err != nil { |
| return err |
| } |
| paths = append(paths, p) |
| return nil |
| }, stdlib.ModulePath, suffix) |
| if err != nil { |
| return nil, err |
| } |
| return paths, nil |
| } |