internal/postgres: use latest-version info for latest major version

Use the latest_module_versions table to determine the latest
tagged major version of a series. This lets us take deprecations
and retractions into account.

For golang/go#43265

Change-Id: I90ed9697ddcaadf9b13a5b99c24bf741acff10ef
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/308030
Trust: Jonathan Amsterdam <jba@google.com>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
TryBot-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Julie Qiu <julie@golang.org>
diff --git a/internal/postgres/version.go b/internal/postgres/version.go
index f055dc8..8c2fffa 100644
--- a/internal/postgres/version.go
+++ b/internal/postgres/version.go
@@ -14,6 +14,7 @@
 
 	"github.com/Masterminds/squirrel"
 	"github.com/lib/pq"
+	"golang.org/x/mod/semver"
 	"golang.org/x/pkgsite/internal"
 	"golang.org/x/pkgsite/internal/database"
 	"golang.org/x/pkgsite/internal/derrors"
@@ -205,62 +206,12 @@
 //
 // getLatestMajorVersion only considers tagged (non-pseudo) versions. If there are none,
 // it returns empty strings.
-func (db *DB) getLatestMajorVersion(ctx context.Context, fullPath, modulePath string) (_ string, _ string, err error) {
-	defer derrors.WrapStack(&err, "DB.getLatestMajorVersion(ctx, %q, %q)", fullPath, modulePath)
-
-	var (
-		modID   int
-		modPath string
-	)
-	seriesPath := internal.SeriesPathForModule(modulePath)
-	q, args, err := squirrel.Select("module_path", "id").
-		From("modules").
-		Where(squirrel.Eq{"series_path": seriesPath}).
-		Where(squirrel.NotEq{"version_type": "pseudo"}).
-		OrderBy(
-			"incompatible", // ignore incompatible versions unless they're all we have
-			"sort_version DESC",
-		).
-		Limit(1).
-		PlaceholderFormat(squirrel.Dollar).
-		ToSql()
-	if err != nil {
-		return "", "", err
-	}
-
-	switch err := db.db.QueryRow(ctx, q, args...).Scan(&modPath, &modID); err {
-	case nil:
-		// fall through to next query
-	case sql.ErrNoRows:
-		return "", "", nil
-	default:
-		return "", "", err
-	}
-
-	v1Path := internal.V1Path(fullPath, modulePath)
-	row := db.db.QueryRow(ctx, `
-		SELECT p.path
-		FROM units u
-		INNER JOIN paths p ON p.id = u.path_id
-		INNER JOIN paths p2 ON p2.id = u.v1path_id
-		WHERE p2.path = $1 AND module_id = $2;`, v1Path, modID)
-	var path string
-	switch err := row.Scan(&path); err {
-	case nil:
-		return modPath, path, nil
-	case sql.ErrNoRows:
-		return modPath, modPath, nil
-	default:
-		return "", "", err
-	}
-}
-
-//lint:ignore U1000 We're going to replace getLatestMajorVersion with this once the new DB columns are populated.
-func (db *DB) getLatestMajorVersion2(ctx context.Context, fullPath, modulePath string) (modPath, pkgPath string, err error) {
+func (db *DB) getLatestMajorVersion(ctx context.Context, fullPath, modulePath string) (modPath, pkgPath string, err error) {
 	defer derrors.WrapStack(&err, "DB.getLatestMajorVersion2(%q)", modulePath)
 
 	// Collect all the non-deprecated module paths for the series that have at
-	// least one good version.
+	// least one good version, along with that good version. A good version
+	// is both servable (in the modules table) and not retracted.
 	seriesPath := internal.SeriesPathForModule(modulePath)
 	q, args, err := squirrel.Select("p.path", "l.good_version").
 		From("latest_module_versions l").
@@ -291,13 +242,16 @@
 		return "", "", err
 	}
 
-	// Find the highest tagged version.
+	// Find the highest tagged version from among the (module path, good
+	// version) pairs.
 	var max pathver
 	for _, pv := range pathvers {
 		if version.IsPseudo(pv.version) {
 			continue
 		}
-		if max.path == "" || version.Later(pv.version, max.version) {
+		// Use semver.Compare, not version.Later, because we don't want to prefer
+		// release to prerelease: we want v2.0.0-pre over v1.0.0.
+		if max.path == "" || semver.Compare(pv.version, max.version) > 0 {
 			max = pv
 		}
 	}
diff --git a/internal/postgres/version_test.go b/internal/postgres/version_test.go
index 4493447..33ffe5d 100644
--- a/internal/postgres/version_test.go
+++ b/internal/postgres/version_test.go
@@ -259,9 +259,9 @@
 	defer cancel()
 
 	for _, m := range []*internal.Module{
+		sample.Module("a.com/M", "v99.0.0+incompatible", "all", "most"),
 		sample.Module("a.com/M", "v1.1.1", "all", "most", "some", "one", "D/other"),
 		sample.Module("a.com/M", "v1.2.0", "all", "most"),
-		sample.Module("a.com/M", "v99.0.0+incompatible", "all", "most"),
 		sample.Module("a.com/M/v2", "v2.0.5", "all", "most"),
 		sample.Module("a.com/M/v3", "v3.0.1", "all", "some"),
 		sample.Module("a.com/M/D", "v1.3.0", "other"),
@@ -272,7 +272,7 @@
 		sample.Module("gopkg.in/M.v3", "v3.0.0-20200602140019-6ec2bf8d378b", ""),
 		sample.Module("c.com/M", "v0.0.0-20200602140019-6ec2bf8d378b", ""),
 	} {
-		MustInsertModule(ctx, t, testDB, m)
+		MustInsertModuleLatest(ctx, t, testDB, m)
 	}
 
 	for _, test := range []struct {