diff --git a/internal/postgres/path.go b/internal/postgres/path.go
index 9b2edf3..cbd05ec 100644
--- a/internal/postgres/path.go
+++ b/internal/postgres/path.go
@@ -7,7 +7,6 @@
import (
"context"
"database/sql"
- "errors"
"fmt"
"sort"
"strings"
@@ -73,40 +72,6 @@
return majPath, maj, nil
}
-// upsertPath adds path into the paths table if it does not exist, and returns
-// its ID either way.
-// It assumes it is running inside a transaction.
-func upsertPath(ctx context.Context, tx *database.DB, path string) (id int, err error) {
- // Doing the select first and then the insert led to uniqueness constraint
- // violations even with fully serializable transactions; see
- // https://www.postgresql.org/message-id/CAOqyxwL4E_JmUScYrnwd0_sOtm3bt4c7G%2B%2BUiD2PnmdGJFiqyQ%40mail.gmail.com.
- // If the upsert is done first and then the select, then everything works
- // fine.
- defer derrors.WrapStack(&err, "upsertPath(%q)", path)
-
- if _, err := tx.Exec(ctx, `LOCK TABLE paths IN EXCLUSIVE MODE`); err != nil {
- return 0, err
- }
- err = tx.QueryRow(ctx,
- `INSERT INTO paths (path) VALUES ($1) ON CONFLICT DO NOTHING RETURNING id`,
- path).Scan(&id)
- if err == sql.ErrNoRows {
- err = tx.QueryRow(ctx,
- `SELECT id FROM paths WHERE path = $1`,
- path).Scan(&id)
- if err == sql.ErrNoRows {
- return 0, errors.New("got no rows; shouldn't happen")
- }
- }
- if err != nil {
- return 0, err
- }
- if id == 0 {
- return 0, errors.New("zero ID")
- }
- return id, nil
-}
-
// upsertPaths adds all the paths to the paths table if they aren't already
// there, and returns their ID either way.
// It assumes it is running inside a transaction.
diff --git a/internal/postgres/path_test.go b/internal/postgres/path_test.go
index 1b06d90..1c32fdb 100644
--- a/internal/postgres/path_test.go
+++ b/internal/postgres/path_test.go
@@ -7,7 +7,6 @@
import (
"context"
"database/sql"
- "errors"
"fmt"
"testing"
@@ -84,38 +83,6 @@
}
}
-func TestUpsertPathConcurrently(t *testing.T) {
- // Verify that we get no constraint violations or other errors when
- // the same path is upserted multiple times concurrently.
- t.Parallel()
- testDB, release := acquire(t)
- defer release()
- ctx := context.Background()
-
- const n = 10
- errc := make(chan error, n)
- for i := 0; i < n; i++ {
- go func() {
- errc <- testDB.db.Transact(ctx, sql.LevelRepeatableRead, func(tx *database.DB) error {
- id, err := upsertPath(ctx, tx, "a/path")
- if err != nil {
- return err
- }
- if id == 0 {
- return errors.New("zero id")
- }
- return nil
- })
- }()
-
- }
- for i := 0; i < n; i++ {
- if err := <-errc; err != nil {
- t.Fatal(err)
- }
- }
-}
-
func TestUpsertPaths(t *testing.T) {
t.Parallel()
testDB, release := acquire(t)
diff --git a/internal/postgres/version.go b/internal/postgres/version.go
index 602c322..caadb87 100644
--- a/internal/postgres/version.go
+++ b/internal/postgres/version.go
@@ -467,6 +467,14 @@
func (db *DB) UpdateLatestModuleVersions(ctx context.Context, vNew *internal.LatestModuleVersions) (_ *internal.LatestModuleVersions, err error) {
defer derrors.WrapStack(&err, "UpdateLatestModuleVersions(%q)", vNew.ModulePath)
+ pathToID, err := upsertPaths(ctx, db.db, []string{vNew.ModulePath})
+ if err != nil {
+ return nil, err
+ }
+ pathID, ok := pathToID[vNew.ModulePath]
+ if !ok {
+ return nil, fmt.Errorf("BUG: failed to acquire ID for path %q", vNew.ModulePath)
+ }
var vResult *internal.LatestModuleVersions
// We need RepeatableRead here because the INSERT...ON CONFLICT does a read.
err = db.db.Transact(ctx, sql.LevelRepeatableRead, func(tx *database.DB) error {
@@ -474,6 +482,9 @@
if err != nil {
return err
}
+ if vCur != nil && id != pathID {
+ return fmt.Errorf("inconsistent path ID: %d != %d", id, pathID)
+ }
// Is vNew the most recent information, or does the DB already have
// something more up to date?
update := vCur == nil || rawIsMoreRecent(vNew.RawVersion, vCur.RawVersion) ||
@@ -504,7 +515,7 @@
}
}
vResult = vNew
- return upsertLatestModuleVersions(ctx, tx, vNew.ModulePath, id, vNew, 200)
+ return upsertLatestModuleVersions(ctx, tx, vNew.ModulePath, pathID, vNew, 200)
})
if err != nil {
return nil, err
@@ -542,12 +553,6 @@
defer derrors.WrapStack(&err, "upsertLatestModuleVersions(%s, %d)", modulePath, status)
// If the row doesn't exist, get a path ID for the module path.
- if id == 0 {
- id, err = upsertPath(ctx, tx, modulePath)
- if err != nil {
- return err
- }
- }
var (
raw, cooked, good string
goModBytes = []byte{} // not nil, a zero-length slice