Skip to content

Commit

Permalink
internal/postgres: add a function to get the version history for a path
Browse files Browse the repository at this point in the history
Adds a db method to get version history for a given path. It will
return pre-release and release versions of a package if tagged
versions exist or the first 10 pseudo versions.

In supporting getting version history based on path, this method
will replace the following legacy methods:
- LegacyGetTaggedVersionsForPackageSeries
- LegacyGetPsuedoVersionsForPackageSeries
- LegacyGetTaggedVersionsForModule
- LegacyGetPsuedoVersionsForModule

For golang/go#39629
For golang/go#40150

Change-Id: I251e80e5327e422579fd8c0854f1e93d1865d48d
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/242881
Reviewed-by: Julie Qiu <julie@golang.org>
  • Loading branch information
jamalc committed Jul 23, 2020
1 parent 94ee022 commit 0c5cd20
Show file tree
Hide file tree
Showing 2 changed files with 294 additions and 0 deletions.
80 changes: 80 additions & 0 deletions internal/postgres/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,83 @@ func getModuleVersions(ctx context.Context, db *DB, modulePath string, versionTy
}
return vinfos, nil
}

// GetVersionsForPath returns a list of tagged versions sorted in
// descending semver order if any exist. If none, it returns the 10 most
// recent from a list of pseudo-versions sorted in descending semver order.
func (db *DB) GetVersionsForPath(ctx context.Context, path string) (_ []*internal.ModuleInfo, err error) {
defer derrors.Wrap(&err, "GetVersionsForPath(ctx, %q)", path)

versions, err := getPathVersions(ctx, db, path, version.TypeRelease, version.TypePrerelease)
if err != nil {
return nil, err
}
if len(versions) != 0 {
return versions, nil
}
versions, err = getPathVersions(ctx, db, path, version.TypePseudo)
if err != nil {
return nil, err
}
return versions, nil
}

// getPathVersions returns a list of versions sorted in descending semver
// order. The version types included in the list are specified by a list of
// VersionTypes.
func getPathVersions(ctx context.Context, db *DB, path string, versionTypes ...version.Type) (_ []*internal.ModuleInfo, err error) {
defer derrors.Wrap(&err, "getPathVersions(ctx, db, %q, %v)", path, versionTypes)

baseQuery := `
SELECT
m.module_path,
m.version,
m.commit_time,
m.version_type,
m.redistributable,
m.has_go_mod,
m.source_info
FROM modules m
INNER JOIN paths p
ON p.module_id = m.id
WHERE
p.v1_path = (
SELECT p2.v1_path
FROM paths as p2
WHERE p2.path = $1
LIMIT 1
)
AND version_type in (%s)
ORDER BY
m.module_path DESC,
m.sort_version DESC %s`

queryEnd := `;`
if len(versionTypes) == 0 {
return nil, fmt.Errorf("error: must specify at least one version type")
} else if len(versionTypes) == 1 && versionTypes[0] == version.TypePseudo {
queryEnd = `LIMIT 10;`
}
query := fmt.Sprintf(baseQuery, versionTypeExpr(versionTypes), queryEnd)
var versions []*internal.ModuleInfo
collect := func(rows *sql.Rows) error {
var mi internal.ModuleInfo
if err := rows.Scan(
&mi.ModulePath,
&mi.Version,
&mi.CommitTime,
&mi.VersionType,
&mi.IsRedistributable,
&mi.HasGoMod,
jsonbScanner{&mi.SourceInfo},
); err != nil {
return fmt.Errorf("row.Scan(): %v", err)
}
versions = append(versions, &mi)
return nil
}
if err := db.db.RunQuery(ctx, query, collect, path); err != nil {
return nil, err
}
return versions, nil
}
214 changes: 214 additions & 0 deletions internal/postgres/version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/google/go-cmp/cmp"
"golang.org/x/pkgsite/internal"
"golang.org/x/pkgsite/internal/source"
"golang.org/x/pkgsite/internal/stdlib"
"golang.org/x/pkgsite/internal/testing/sample"
)

Expand Down Expand Up @@ -144,3 +145,216 @@ func TestPostgres_GetTaggedAndPseudoVersions(t *testing.T) {
})
}
}

func TestGetVersions(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
defer cancel()

var (
taggedAndPseudoModule = "path.to/foo"
taggedModuleV2 = "path.to/foo/v2"
taggedModuleV3 = "path.to/foo/v3"
pseudoModule = "golang.org/x/tools"
otherModule = "path.to/other"
incompatibleModule = "path.to/incompatible"
rootModule = "golang.org/foo/bar"
nestedModule = "golang.org/foo/bar/api"
testModules = []*internal.Module{
sample.Module(stdlib.ModulePath, "v1.15.0-beta.1", "cmd/go"),
sample.Module(stdlib.ModulePath, "v1.14.6", "cmd/go"),
sample.Module(taggedModuleV3, "v3.2.0-beta", "bar"),
sample.Module(taggedModuleV3, "v3.2.0-alpha.2", "bar"),
sample.Module(taggedModuleV3, "v3.2.0-alpha.1", "bar"),
sample.Module(taggedModuleV3, "v3.1.0", "bar"),
sample.Module(taggedModuleV3, "v3.0.0", "bar"),
sample.Module(taggedModuleV2, "v2.0.1", "bar"),
sample.Module(taggedAndPseudoModule, "v1.5.3-pre1", "bar"),
sample.Module(taggedAndPseudoModule, "v1.5.2", "bar"),
sample.Module(taggedAndPseudoModule, "v0.0.0-20200101120000-000000000000", "bar"),
sample.Module(otherModule, "v3.0.0", "thing"),
sample.Module(incompatibleModule, "v2.0.0+incompatible", "module"),
sample.Module(incompatibleModule, "v0.0.0", "module"),
sample.Module(rootModule, "v1.0.3", "api"),
sample.Module(rootModule, "v0.11.6", "api"),
sample.Module(nestedModule, "v1.0.4", "api"),
sample.Module(nestedModule, "v1.0.3", "api"),
}
)

// Add 12 pseudo versions to the test modules. Below we only
// expect to return the 10 most recent.
for i := 1; i <= 12; i++ {
testModules = append(testModules, sample.Module(pseudoModule, fmt.Sprintf("v0.0.0-202001011200%02d-000000000000", i), "blog"))
}

defer ResetTestDB(testDB, t)
for _, m := range testModules {
if err := testDB.InsertModule(ctx, m); err != nil {
t.Fatal(err)
}
}

stdModuleVersions := []*internal.ModuleInfo{
{
ModulePath: stdlib.ModulePath,
Version: "v1.15.0-beta.1",
},
{
ModulePath: stdlib.ModulePath,
Version: "v1.14.6",
},
}

fooModuleVersions := []*internal.ModuleInfo{
{
ModulePath: taggedModuleV3,
Version: "v3.2.0-beta",
},
{
ModulePath: taggedModuleV3,
Version: "v3.2.0-alpha.2",
},
{
ModulePath: taggedModuleV3,
Version: "v3.2.0-alpha.1",
},
{
ModulePath: taggedModuleV3,
Version: "v3.1.0",
},
{
ModulePath: taggedModuleV3,
Version: "v3.0.0",
},
{
ModulePath: taggedModuleV2,
Version: "v2.0.1",
},
{
ModulePath: taggedAndPseudoModule,
Version: "v1.5.3-pre1",
},
{
ModulePath: taggedAndPseudoModule,
Version: "v1.5.2",
},
}

testCases := []struct {
name, path string
want []*internal.ModuleInfo
}{
{
name: "std_module",
path: stdlib.ModulePath,
want: stdModuleVersions,
},
{
name: "stdlib_path",
path: "cmd",
want: stdModuleVersions,
},
{
name: "stdlib_package",
path: "cmd/go",
want: stdModuleVersions,
},
{
name: "want_tagged_versions_only",
path: "path.to/foo/bar",
want: fooModuleVersions,
},
{
name: "want_all_tagged_versions_at_v2_path",
path: "path.to/foo/v2/bar",
want: fooModuleVersions,
},
{
name: "want_pseudo_versions_only",
path: "golang.org/x/tools/blog",
want: func() []*internal.ModuleInfo {
versions := []*internal.ModuleInfo{}
// Expect the 10 most recent in DESC order
for i := 12; i > 2; i-- {
versions = append(versions, &internal.ModuleInfo{
ModulePath: pseudoModule,
Version: fmt.Sprintf("v0.0.0-202001011200%02d-000000000000", i),
})
}
return versions
}(),
},
{
name: "want_tagged_versions_includes_incompatible",
path: "path.to/incompatible/module",
want: []*internal.ModuleInfo{
{
ModulePath: incompatibleModule,
Version: "v2.0.0+incompatible",
},
{
ModulePath: incompatibleModule,
Version: "v0.0.0",
},
},
},
{
name: "nested_module_path",
path: "golang.org/foo/bar/api",
want: []*internal.ModuleInfo{
{
ModulePath: nestedModule,
Version: "v1.0.4",
},
{
ModulePath: nestedModule,
Version: "v1.0.3",
},
{
ModulePath: rootModule,
Version: "v1.0.3",
},
{
ModulePath: rootModule,
Version: "v0.11.6",
},
},
},
{
name: "root_module_path",
path: "golang.org/foo/bar",
want: []*internal.ModuleInfo{
{
ModulePath: rootModule,
Version: "v1.0.3",
},
{
ModulePath: rootModule,
Version: "v0.11.6",
},
},
},
{
name: "want_zero_results_in_non_empty_db",
path: "not.a/real/path",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var want []*internal.ModuleInfo
for _, w := range tc.want {
mod := sample.ModuleInfo(w.ModulePath, w.Version)
want = append(want, mod)
}

got, err := testDB.GetVersionsForPath(ctx, tc.path)
if err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(want, got, cmp.AllowUnexported(source.Info{})); diff != "" {
t.Errorf("testDB.GetVersionsForPath(%q) mismatch (-want +got):\n%s", tc.path, diff)
}
})
}
}

0 comments on commit 0c5cd20

Please sign in to comment.