| // Copyright 2019 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/lib/pq" |
| "golang.org/x/pkgsite/internal" |
| "golang.org/x/pkgsite/internal/database" |
| "golang.org/x/pkgsite/internal/derrors" |
| "golang.org/x/pkgsite/internal/stdlib" |
| ) |
| |
| // GetPackage returns the a package from the database with the corresponding |
| // pkgPath, modulePath and version. |
| // |
| // If version = internal.LatestVersion, the package corresponding to |
| // the latest matching module version will be fetched. |
| // |
| // If more than one module tie for a given dirPath and version pair, and |
| // modulePath = internal.UnknownModulePath, the package in the module with the |
| // longest module path will be fetched. |
| // For example, if there are |
| // two rows in the packages table: |
| // (1) path = "github.com/hashicorp/vault/api" |
| // module_path = "github.com/hashicorp/vault" |
| // AND |
| // (2) path = "github.com/hashicorp/vault/api" |
| // module_path = "github.com/hashicorp/vault/api" |
| // The latter will be returned. |
| // |
| // The returned error may be checked with |
| // errors.Is(err, derrors.InvalidArgument) to determine if it was caused by an |
| // invalid pkgPath, modulePath or version. |
| // |
| // The returned error may be checked with |
| // errors.Is(err, derrors.InvalidArgument) to determine if it was caused by an |
| // invalid path or version. |
| func (db *DB) GetPackage(ctx context.Context, pkgPath, modulePath, version string) (_ *internal.LegacyVersionedPackage, err error) { |
| defer derrors.Wrap(&err, "DB.GetPackage(ctx, %q, %q)", pkgPath, version) |
| if pkgPath == "" || modulePath == "" || version == "" { |
| return nil, fmt.Errorf("none of pkgPath, modulePath, or version can be empty: %w", derrors.InvalidArgument) |
| } |
| |
| args := []interface{}{pkgPath} |
| query := ` |
| SELECT |
| p.path, |
| p.name, |
| p.synopsis, |
| p.v1_path, |
| p.license_types, |
| p.license_paths, |
| p.redistributable, |
| p.documentation, |
| p.goos, |
| p.goarch, |
| m.version, |
| m.commit_time, |
| m.readme_file_path, |
| m.readme_contents, |
| m.module_path, |
| m.version_type, |
| m.source_info, |
| m.redistributable, |
| m.has_go_mod |
| FROM |
| modules m |
| INNER JOIN |
| packages p |
| ON |
| p.module_path = m.module_path |
| AND m.version = p.version` |
| |
| if modulePath == internal.UnknownModulePath || modulePath == stdlib.ModulePath { |
| if version == internal.LatestVersion { |
| // Only pkgPath is specified, so get the latest version of the |
| // package found in any module. |
| query += ` |
| WHERE |
| p.path = $1 |
| ORDER BY |
| -- Order the versions by release then prerelease. |
| -- The default version should be the first release |
| -- version available, if one exists. |
| m.version_type = 'release' DESC, |
| m.sort_version DESC, |
| m.module_path DESC |
| LIMIT 1;` |
| } else { |
| // pkgPath and version are specified, so get that package version |
| // from any module. If it exists in multiple modules, return the |
| // one with the longest path. |
| query += ` |
| WHERE |
| p.path = $1 |
| AND p.version = $2 |
| ORDER BY |
| p.module_path DESC |
| LIMIT 1;` |
| args = append(args, version) |
| } |
| } else if version == internal.LatestVersion { |
| // pkgPath and modulePath are specified, so get the latest version of |
| // the package in the specified module. |
| query += ` |
| WHERE |
| p.path = $1 |
| AND p.module_path = $2 |
| ORDER BY |
| -- Order the versions by release then prerelease. |
| -- The default version should be the first release |
| -- version available, if one exists. |
| m.version_type = 'release' DESC, |
| m.sort_version DESC |
| LIMIT 1;` |
| args = append(args, modulePath) |
| } else { |
| // pkgPath, modulePath and version were all specified. Only one |
| // directory should ever match this query. |
| query += ` |
| WHERE |
| p.path = $1 |
| AND p.version = $2 |
| AND p.module_path = $3` |
| args = append(args, version, modulePath) |
| } |
| |
| var ( |
| pkg internal.LegacyVersionedPackage |
| licenseTypes, licensePaths []string |
| hasGoMod sql.NullBool |
| ) |
| row := db.db.QueryRow(ctx, query, args...) |
| err = row.Scan(&pkg.Path, &pkg.Name, &pkg.Synopsis, |
| &pkg.V1Path, pq.Array(&licenseTypes), pq.Array(&licensePaths), &pkg.LegacyPackage.IsRedistributable, |
| database.NullIsEmpty(&pkg.DocumentationHTML), &pkg.GOOS, &pkg.GOARCH, &pkg.Version, |
| &pkg.CommitTime, database.NullIsEmpty(&pkg.LegacyReadmeFilePath), database.NullIsEmpty(&pkg.LegacyReadmeContents), |
| &pkg.ModulePath, &pkg.VersionType, jsonbScanner{&pkg.SourceInfo}, &pkg.LegacyModuleInfo.IsRedistributable, |
| &hasGoMod) |
| if err != nil { |
| if err == sql.ErrNoRows { |
| return nil, fmt.Errorf("package %s@%s: %w", pkgPath, version, derrors.NotFound) |
| } |
| return nil, fmt.Errorf("row.Scan(): %v", err) |
| } |
| setHasGoMod(&pkg.LegacyModuleInfo, hasGoMod) |
| lics, err := zipLicenseMetadata(licenseTypes, licensePaths) |
| if err != nil { |
| return nil, err |
| } |
| pkg.Licenses = lics |
| return &pkg, nil |
| } |