internal/postgres: change UpsertModuleVersionStates to update

UpsertModuleVersionStates is changed to UpdateModuleVersionStates. There
should never be a situation where UpsertModuleVersionStates is called
and a row does not already exist for that module.

If that happens, an error is now returned.

For golang/go#46985
Fixes golang/go#39628

Change-Id: I357396cee6eb743513ae249609f76f4cd4c19e9b
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/341860
Trust: Julie Qiu <julie@golang.org>
Run-TryBot: Julie Qiu <julie@golang.org>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
TryBot-Result: kokoro <noreply+kokoro@google.com>
diff --git a/internal/postgres/insert_module_test.go b/internal/postgres/insert_module_test.go
index 00e178d..3c44d87 100644
--- a/internal/postgres/insert_module_test.go
+++ b/internal/postgres/insert_module_test.go
@@ -458,7 +458,7 @@
 		okVersion  = "v1.0.0"
 	)
 
-	mvs := &ModuleVersionStateForUpsert{
+	mvs := &ModuleVersionStateForUpdate{
 		ModulePath: "example.com/Mod",
 		Version:    altVersion,
 		AppVersion: "appVersion",
@@ -467,8 +467,17 @@
 		GoModPath:  "example.com/mod",
 		FetchErr:   derrors.AlternativeModule,
 	}
+	if err := testDB.InsertIndexVersions(ctx, []*internal.IndexVersion{
+		{
+			Path:      mvs.ModulePath,
+			Version:   mvs.Version,
+			Timestamp: mvs.Timestamp,
+		},
+	}); err != nil {
+		t.Fatal(err)
+	}
 	addLatest(ctx, t, testDB, mvs.ModulePath, altVersion, "")
-	if err := testDB.UpsertModuleVersionState(ctx, mvs); err != nil {
+	if err := testDB.UpdateModuleVersionState(ctx, mvs); err != nil {
 		t.Fatal(err)
 	}
 	m := sample.Module(mvs.ModulePath, okVersion, "p")
@@ -592,17 +601,27 @@
 		{modulePathA, "v1.4.0", 200, false},                 // new latest version is OK
 		{modulePathA, "v1.5.0", altModuleStatus, true},      // "I can do this all day." --Captain America
 	} {
-		addLatest(ctx, t, testDB, test.modulePath, test.version, "")
-		if err := testDB.UpsertModuleVersionState(ctx, &ModuleVersionStateForUpsert{
+		mvs := &ModuleVersionStateForUpdate{
 			ModulePath: test.modulePath,
 			Version:    test.version,
 			AppVersion: "appVersion",
 			Timestamp:  time.Now(),
 			Status:     test.status,
 			HasGoMod:   true,
+		}
+		if err := testDB.InsertIndexVersions(ctx, []*internal.IndexVersion{
+			{
+				Path:      mvs.ModulePath,
+				Version:   mvs.Version,
+				Timestamp: mvs.Timestamp,
+			},
 		}); err != nil {
 			t.Fatal(err)
 		}
+		addLatest(ctx, t, testDB, test.modulePath, test.version, "")
+		if err := testDB.UpdateModuleVersionState(ctx, mvs); err != nil {
+			t.Fatal(err)
+		}
 
 		got, err := isAlternativeModulePath(ctx, testDB.db, modulePathA)
 		if err != nil {
@@ -630,7 +649,17 @@
 			addLatest(ctx, t, testDB, modulePath, version, modfile)
 		}
 
-		if err := testDB.UpsertModuleVersionState(ctx, &ModuleVersionStateForUpsert{
+		if err := testDB.InsertIndexVersions(ctx,
+			[]*internal.IndexVersion{
+				{
+					Path:      modulePath,
+					Version:   version,
+					Timestamp: time.Now(),
+				},
+			}); err != nil {
+			t.Fatal(err)
+		}
+		if err := testDB.UpdateModuleVersionState(ctx, &ModuleVersionStateForUpdate{
 			ModulePath: modulePath,
 			Version:    version,
 			AppVersion: "app",
diff --git a/internal/postgres/requeue_test.go b/internal/postgres/requeue_test.go
index b2c7726..6198b65 100644
--- a/internal/postgres/requeue_test.go
+++ b/internal/postgres/requeue_test.go
@@ -159,7 +159,7 @@
 	checkNextToRequeue(want, len(mods))
 
 	for _, m := range mods {
-		mvs := &ModuleVersionStateForUpsert{
+		mvs := &ModuleVersionStateForUpdate{
 			ModulePath: m.modulePath,
 			Version:    m.version,
 			AppVersion: "2020-04-29t14",
@@ -168,7 +168,7 @@
 			GoModPath:  m.modulePath,
 			FetchErr:   derrors.FromStatus(m.status, "test string"),
 		}
-		if err := upsertModuleVersionState(ctx, testDB.db, &m.numPackages, mvs); err != nil {
+		if err := updateModuleVersionState(ctx, testDB.db, &m.numPackages, mvs); err != nil {
 			t.Fatal(err)
 		}
 	}
diff --git a/internal/postgres/search_test.go b/internal/postgres/search_test.go
index 77261b7..96b621c 100644
--- a/internal/postgres/search_test.go
+++ b/internal/postgres/search_test.go
@@ -1185,14 +1185,23 @@
 		// We add that information to module_version_states.
 		alternativeModulePath := strings.ToLower(canonicalModule.ModulePath)
 		alternativeStatus := derrors.ToStatus(derrors.AlternativeModule)
-		mvs := &ModuleVersionStateForUpsert{
+		mvs := &ModuleVersionStateForUpdate{
 			ModulePath: alternativeModulePath,
 			Version:    "v1.2.0",
 			Timestamp:  time.Now(),
 			Status:     alternativeStatus,
 			GoModPath:  canonicalModule.ModulePath,
 		}
-		if err := testDB.UpsertModuleVersionState(ctx, mvs); err != nil {
+		if err := testDB.InsertIndexVersions(ctx, []*internal.IndexVersion{
+			{
+				Path:      mvs.ModulePath,
+				Version:   mvs.Version,
+				Timestamp: mvs.Timestamp,
+			},
+		}); err != nil {
+			t.Fatal(err)
+		}
+		if err := testDB.UpdateModuleVersionState(ctx, mvs); err != nil {
 			t.Fatal(err)
 		}
 
diff --git a/internal/postgres/versionstate.go b/internal/postgres/versionstate.go
index 85f8d54..2ccaa02 100644
--- a/internal/postgres/versionstate.go
+++ b/internal/postgres/versionstate.go
@@ -105,7 +105,7 @@
 	})
 }
 
-type ModuleVersionStateForUpsert struct {
+type ModuleVersionStateForUpdate struct {
 	ModulePath           string
 	Version              string
 	AppVersion           string
@@ -117,9 +117,9 @@
 	PackageVersionStates []*internal.PackageVersionState
 }
 
-// UpsertModuleVersionState inserts or updates the module_version_state table with
+// UpdateModuleVersionState inserts or updates the module_version_state table with
 // the results of a fetch operation for a given module version.
-func (db *DB) UpsertModuleVersionState(ctx context.Context, mvs *ModuleVersionStateForUpsert) (err error) {
+func (db *DB) UpdateModuleVersionState(ctx context.Context, mvs *ModuleVersionStateForUpdate) (err error) {
 	defer derrors.WrapStack(&err, "UpsertModuleVersionState(ctx, %s@%s)", mvs.ModulePath, mvs.Version)
 	ctx, span := trace.StartSpan(ctx, "UpsertModuleVersionState")
 	defer span.End()
@@ -131,9 +131,8 @@
 		n := len(mvs.PackageVersionStates)
 		numPackages = &n
 	}
-
 	return db.db.Transact(ctx, sql.LevelDefault, func(tx *database.DB) error {
-		if err := upsertModuleVersionState(ctx, tx, numPackages, mvs); err != nil {
+		if err := updateModuleVersionState(ctx, tx, numPackages, mvs); err != nil {
 			return err
 		}
 		// Sync modules.status if the module exists in the modules table.
@@ -147,7 +146,7 @@
 	})
 }
 
-func upsertModuleVersionState(ctx context.Context, db *database.DB, numPackages *int, mvs *ModuleVersionStateForUpsert) (err error) {
+func updateModuleVersionState(ctx context.Context, db *database.DB, numPackages *int, mvs *ModuleVersionStateForUpdate) (err error) {
 	defer derrors.WrapStack(&err, "upsertModuleVersionState(%q, %q, ...)", mvs.ModulePath, mvs.Version)
 	ctx, span := trace.StartSpan(ctx, "upsertModuleVersionState")
 	defer span.End()
@@ -158,42 +157,35 @@
 	}
 
 	affected, err := db.Exec(ctx, `
-			INSERT INTO module_version_states AS mvs (
-				module_path,
-				version,
-				sort_version,
-				app_version,
-				index_timestamp,
-				status,
-				has_go_mod,
-				go_mod_path,
-				error,
-				num_packages,
-				incompatible)
-			VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
-			ON CONFLICT (module_path, version)
-			DO UPDATE
-			SET
-				app_version=excluded.app_version,
-				status=excluded.status,
-				has_go_mod=excluded.has_go_mod,
-				go_mod_path=excluded.go_mod_path,
-				error=excluded.error,
-				num_packages=excluded.num_packages,
-				try_count=mvs.try_count+1,
-				last_processed_at=CURRENT_TIMESTAMP,
-			    -- back off exponentially until 1 hour, then at constant 1-hour intervals
-				next_processed_after=CASE
-					WHEN mvs.last_processed_at IS NULL THEN
-						CURRENT_TIMESTAMP + INTERVAL '1 minute'
-					WHEN 2*(mvs.next_processed_after - mvs.last_processed_at) < INTERVAL '1 hour' THEN
-						CURRENT_TIMESTAMP + 2*(mvs.next_processed_after - mvs.last_processed_at)
-					ELSE
-						CURRENT_TIMESTAMP + INTERVAL '1 hour'
-					END;`,
-		mvs.ModulePath, mvs.Version, version.ForSorting(mvs.Version),
-		mvs.AppVersion, mvs.Timestamp, mvs.Status, mvs.HasGoMod, mvs.GoModPath, sqlErrorMsg, numPackages,
-		version.IsIncompatible(mvs.Version))
+		UPDATE module_version_states
+		SET app_version=$1,
+			status=$2,
+			has_go_mod=$3,
+			go_mod_path=$4,
+			error=$5,
+			num_packages=$6,
+			try_count=try_count+1,
+			last_processed_at=CURRENT_TIMESTAMP,
+			-- back off exponentially until 1 hour, then at constant 1-hour intervals
+			next_processed_after=CASE
+				WHEN last_processed_at IS NULL THEN
+					CURRENT_TIMESTAMP + INTERVAL '1 minute'
+				WHEN 2*(next_processed_after - last_processed_at) < INTERVAL '1 hour' THEN
+					CURRENT_TIMESTAMP + 2*(next_processed_after - last_processed_at)
+				ELSE
+					CURRENT_TIMESTAMP + INTERVAL '1 hour'
+				END
+		WHERE
+			module_path=$7
+			AND version=$8`,
+		mvs.AppVersion,
+		mvs.Status,
+		mvs.HasGoMod,
+		mvs.GoModPath,
+		sqlErrorMsg,
+		numPackages,
+		mvs.ModulePath,
+		mvs.Version)
 	if err != nil {
 		return err
 	}
diff --git a/internal/postgres/versionstate_test.go b/internal/postgres/versionstate_test.go
index cfd0f5f..953097c 100644
--- a/internal/postgres/versionstate_test.go
+++ b/internal/postgres/versionstate_test.go
@@ -137,7 +137,7 @@
 			Status:      500,
 		}
 	)
-	mvs := &ModuleVersionStateForUpsert{
+	mvs := &ModuleVersionStateForUpdate{
 		ModulePath:           fooVersion.Path,
 		Version:              fooVersion.Version,
 		Timestamp:            fooVersion.Timestamp,
@@ -146,7 +146,7 @@
 		FetchErr:             fetchErr,
 		PackageVersionStates: []*internal.PackageVersionState{pkgVersionState},
 	}
-	must(t, testDB.UpsertModuleVersionState(ctx, mvs))
+	must(t, testDB.UpdateModuleVersionState(ctx, mvs))
 	errString := fetchErr.Error()
 	numPackages := 1
 	wantFooState := &internal.ModuleVersionState{
@@ -274,7 +274,7 @@
 				MustInsertModule(ctx, t, testDB, m)
 			}
 
-			mvsu := &ModuleVersionStateForUpsert{
+			mvsu := &ModuleVersionStateForUpdate{
 				ModulePath: m.ModulePath,
 				Version:    m.Version,
 				AppVersion: appVersion,
@@ -282,7 +282,16 @@
 				Status:     test.status,
 				HasGoMod:   true,
 			}
-			err := testDB.UpsertModuleVersionState(ctx, mvsu)
+			if err := testDB.InsertIndexVersions(ctx, []*internal.IndexVersion{
+				{
+					Path:      mvsu.ModulePath,
+					Version:   mvsu.Version,
+					Timestamp: mvsu.Timestamp,
+				},
+			}); err != nil {
+				t.Fatal(err)
+			}
+			err := testDB.UpdateModuleVersionState(ctx, mvsu)
 			if test.wantUpsertMVSError != (err != nil) {
 				t.Fatalf("db.UpsertModuleVersionState(): %v, want error: %t", err, test.wantUpsertMVSError)
 			}
@@ -328,13 +337,17 @@
 	testDB, release := acquire(t)
 	defer release()
 
-	mvs := &ModuleVersionStateForUpsert{
+	mvs := &ModuleVersionStateForUpdate{
 		ModulePath: "m.com",
 		Version:    "v1.2.3",
 		Timestamp:  time.Now(),
 		Status:     200,
 	}
-	must(t, testDB.UpsertModuleVersionState(ctx, mvs))
+	must(t, testDB.InsertIndexVersions(
+		ctx,
+		[]*internal.IndexVersion{{Path: mvs.ModulePath, Version: mvs.Version, Timestamp: time.Now()}},
+	))
+	must(t, testDB.UpdateModuleVersionState(ctx, mvs))
 	wantStatus := 999
 	wantError := "Error"
 	must(t, testDB.UpdateModuleVersionStatus(ctx, mvs.ModulePath, mvs.Version, wantStatus, wantError))
diff --git a/internal/worker/fetch.go b/internal/worker/fetch.go
index a2fb408..4f0c26c 100644
--- a/internal/worker/fetch.go
+++ b/internal/worker/fetch.go
@@ -198,8 +198,6 @@
 			ft.Error = err
 			ft.Status = http.StatusInternalServerError
 		}
-		// Do not return an error here, because we want to insert into
-		// module_version_states below.
 	}
 	// Regardless of what the status code is, insert the result into
 	// version_map, so that a response can be returned for frontend_fetch.
@@ -220,6 +218,15 @@
 		// modules that have been published to index.golang.org.
 		return ft.Status, ft.ResolvedVersion, ft.Error
 	}
+	// Return an error here if a row does not exist in module_version_states.
+	// This can happen if the source is frontend fetch, since we don't insert
+	// rows to avoid cluttering module_version_states.
+	if _, err := f.DB.GetModuleVersionState(ctx, modulePath, ft.ResolvedVersion); err != nil {
+		if errors.Is(err, derrors.NotFound) {
+			return ft.Status, "", ft.Error
+		}
+		return http.StatusInternalServerError, "", err
+	}
 
 	// Make sure the latest version of the module is the one in search_documents
 	// and imports_unique.
@@ -237,10 +244,8 @@
 	// module@version. This must happen last, because if it succeeds with a
 	// code < 500 but a later action fails, we will never retry the later
 	// action.
-	// TODO(golang/go#39628): Split UpsertModuleVersionState into
-	// InsertModuleVersionState and UpdateModuleVersionState.
 	startUpdate := time.Now()
-	mvs := &postgres.ModuleVersionStateForUpsert{
+	mvs := &postgres.ModuleVersionStateForUpdate{
 		ModulePath:           ft.ModulePath,
 		Version:              ft.ResolvedVersion,
 		AppVersion:           appVersionLabel,
@@ -250,13 +255,13 @@
 		FetchErr:             ft.Error,
 		PackageVersionStates: ft.PackageVersionStates,
 	}
-	err = f.DB.UpsertModuleVersionState(ctx, mvs)
-	ft.timings["db.UpsertModuleVersionState"] = time.Since(startUpdate)
+	err = f.DB.UpdateModuleVersionState(ctx, mvs)
+	ft.timings["db.UpdateModuleVersionState"] = time.Since(startUpdate)
 	if err != nil {
 		log.Error(ctx, err)
 		if ft.Error != nil {
 			ft.Status = http.StatusInternalServerError
-			ft.Error = fmt.Errorf("db.UpsertModuleVersionState: %v, original error: %v", err, ft.Error)
+			ft.Error = fmt.Errorf("db.UpdateModuleVersionState: %v, original error: %v", err, ft.Error)
 		}
 		logTaskResult(ctx, ft, "Failed to update module version state")
 		return http.StatusInternalServerError, ft.ResolvedVersion, ft.Error
diff --git a/internal/worker/fetcherror_test.go b/internal/worker/fetcherror_test.go
index e130112..9e3ca87 100644
--- a/internal/worker/fetcherror_test.go
+++ b/internal/worker/fetcherror_test.go
@@ -16,7 +16,6 @@
 
 	"github.com/google/go-cmp/cmp"
 	"github.com/google/go-cmp/cmp/cmpopts"
-	"golang.org/x/mod/semver"
 	"golang.org/x/pkgsite/internal"
 	"golang.org/x/pkgsite/internal/derrors"
 	"golang.org/x/pkgsite/internal/fetch"
@@ -378,11 +377,6 @@
 			t.Fatalf("GetModuleInfo: got %v, want Is(NotFound)", err)
 		}
 	}
-	if semver.IsValid(version) {
-		if _, err := testDB.GetModuleVersionState(ctx, modulePath, version); err != nil {
-			t.Fatal(err)
-		}
-	}
 	vm, err := testDB.GetVersionMap(ctx, modulePath, version)
 	if err != nil {
 		t.Fatal(err)