1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
|
// Copyright 2021 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"
"errors"
"fmt"
"sort"
"strings"
"github.com/lib/pq"
"golang.org/x/pkgsite/internal"
"golang.org/x/pkgsite/internal/database"
"golang.org/x/pkgsite/internal/derrors"
)
// GetLatestMajorPathForV1Path reports the latest unit path in the series for
// the given v1path. It also returns the major version for that path.
func (db *DB) GetLatestMajorPathForV1Path(ctx context.Context, v1path string) (_ string, _ int, err error) {
defer derrors.WrapStack(&err, "DB.GetLatestPathForV1Path(ctx, %q)", v1path)
q := `
SELECT p.path, m.series_path
FROM paths p
INNER JOIN units u ON u.path_id = p.id
INNER JOIN modules m ON u.module_id = m.id
WHERE u.v1path_id = (
SELECT p.id
FROM paths p
INNER JOIN units u ON u.v1path_id = p.id
WHERE p.path = $1
ORDER BY p.path DESC
LIMIT 1
);`
paths := map[string]string{} // from unit path to series path
err = db.db.RunQuery(ctx, q, func(rows *sql.Rows) error {
var p, sp string
if err := rows.Scan(&p, &sp); err != nil {
return err
}
paths[p] = sp
return nil
}, v1path)
if err != nil {
return "", 0, err
}
var (
maj int
majPath string
)
for p, sp := range paths {
// Trim the series path and suffix from the unit path.
// Keep only the N following vN.
suffix := internal.Suffix(v1path, sp)
modPath := strings.TrimSuffix(p, "/"+suffix)
_, i := internal.SeriesPathAndMajorVersion(modPath)
if i == 0 {
return "", 0, fmt.Errorf("bad module path %q", modPath)
}
if maj <= i {
maj = i
majPath = p
}
}
if maj == 0 {
// Return 1 as the major version for all v0 or v1 majPaths.
maj = 1
}
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)
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.
func upsertPaths(ctx context.Context, db *database.DB, paths []string) (pathToID map[string]int, err error) {
defer derrors.WrapStack(&err, "upsertPaths(%d paths)", len(paths))
// Read all existing paths for this module, to avoid a large bulk upsert.
// (We've seen these bulk upserts hang for so long that they time out (10
// minutes)).
pathToID = map[string]int{}
collect := func(rows *sql.Rows) error {
var (
pathID int
path string
)
if err := rows.Scan(&pathID, &path); err != nil {
return err
}
pathToID[path] = pathID
return nil
}
if err := db.RunQuery(ctx, `SELECT id, path FROM paths WHERE path = ANY($1)`,
collect, pq.Array(paths)); err != nil {
return nil, err
}
// Insert any unit paths that we don't already have.
var values []any
for _, v := range paths {
if _, ok := pathToID[v]; !ok {
values = append(values, v)
}
}
if len(values) > 0 {
// Sort to avoid deadlock.
sort.Slice(values, func(i, j int) bool { return values[i].(string) < values[j].(string) })
// Insert data into the paths table.
pathCols := []string{"path"}
returningPathCols := []string{"id", "path"}
if err := db.BulkInsertReturning(ctx, "paths", pathCols, values,
database.OnConflictDoNothing, returningPathCols, collect); err != nil {
return nil, err
}
}
return pathToID, nil
}
func GetPathID(ctx context.Context, ddb *database.DB, path string) (id int, err error) {
err = ddb.QueryRow(ctx,
`SELECT id FROM paths WHERE path = $1`,
path).Scan(&id)
return id, err
}
|