зеркало из https://github.com/golang/pkgsite.git
internal/postgres: add functionality to get and insert version rows
GetVersion and InsertVersion are implemented. GetVersion takes a primary key, the name and version, and returns the corresponding Version if it exists in the Versions table. InsertVersion takes a Version and inserts it into the Versions table if there are no pre-existing Versions with the same primary key, failing otherwise. GetVersion particularly will be used to get the information needed to write the html header for the discovery site. Note that the updated_at field in the Versions schema should use a trigger to automatically update but that will be handled in a future CL. Fixes b/124338357 Change-Id: If6ecc43a35381814f74df024581a135f16c34771 Reviewed-on: https://team-review.git.corp.google.com/c/417750 Reviewed-by: Katie Hockman <katiehockman@google.com>
This commit is contained in:
Родитель
42a10443c1
Коммит
ef655c1f99
|
@ -9,23 +9,27 @@ import "time"
|
|||
// A Series is a group of modules that share the same base path and are assumed
|
||||
// to be major-version variants.
|
||||
type Series struct {
|
||||
Name string
|
||||
Modules []*Module
|
||||
Name string
|
||||
CreatedAt time.Time
|
||||
Modules []*Module
|
||||
}
|
||||
|
||||
// A Module is a collection of packages that share a common path prefix (the
|
||||
// module path) and are versioned as a single unit, along with a go.mod file
|
||||
// listing other required modules.
|
||||
type Module struct {
|
||||
Name string
|
||||
Series *Series
|
||||
Versions []*Version
|
||||
Name string
|
||||
CreatedAt time.Time
|
||||
Series *Series
|
||||
Versions []*Version
|
||||
}
|
||||
|
||||
// A Version is a specific, reproducible build of a module.
|
||||
type Version struct {
|
||||
Module *Module
|
||||
Version string
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Synopsis string
|
||||
CommitTime time.Time
|
||||
License *License
|
||||
|
|
|
@ -6,6 +6,10 @@ package postgres
|
|||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"golang.org/x/discovery/internal"
|
||||
)
|
||||
|
||||
type DB struct {
|
||||
|
@ -43,3 +47,128 @@ func (db *DB) Transact(txFunc func(*sql.Tx) error) (err error) {
|
|||
|
||||
return txFunc(tx)
|
||||
}
|
||||
|
||||
// LatestProxyIndexUpdate reports the last time the Proxy Index Cron
|
||||
// successfully fetched data from the Module Proxy Index.
|
||||
func (db *DB) LatestProxyIndexUpdate() (time.Time, error) {
|
||||
query := `
|
||||
SELECT created_at
|
||||
FROM version_logs
|
||||
WHERE source=$1
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1`
|
||||
|
||||
var createdAt time.Time
|
||||
row := db.QueryRow(query, internal.VersionLogProxyIndex)
|
||||
switch err := row.Scan(&createdAt); err {
|
||||
case sql.ErrNoRows:
|
||||
return time.Time{}, nil
|
||||
case nil:
|
||||
return createdAt, nil
|
||||
default:
|
||||
return time.Time{}, err
|
||||
}
|
||||
}
|
||||
|
||||
// InsertVersionLogs inserts a VersionLog into the database and
|
||||
// insertion fails and returns an error if the VersionLog's primary
|
||||
// key already exists in the database.
|
||||
func (db *DB) InsertVersionLogs(logs []*internal.VersionLog) error {
|
||||
return db.Transact(func(tx *sql.Tx) error {
|
||||
for _, l := range logs {
|
||||
if _, err := tx.Exec(
|
||||
`INSERT INTO version_logs(name, version, created_at, source, error)
|
||||
VALUES ($1, $2, $3, $4, $5);`,
|
||||
l.Name, l.Version, l.CreatedAt, l.Source, l.Error,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// GetVersion fetches a Version from the database with the primary key
|
||||
// (name, version).
|
||||
func (db *DB) GetVersion(name string, version string) (*internal.Version, error) {
|
||||
var synopsis string
|
||||
var commitTime, createdAt, updatedAt time.Time
|
||||
var license string
|
||||
|
||||
query := `
|
||||
SELECT
|
||||
created_at,
|
||||
updated_at,
|
||||
synopsis,
|
||||
commit_time,
|
||||
license
|
||||
FROM versions
|
||||
WHERE name = $1 and version = $2;`
|
||||
row := db.QueryRow(query, name, version)
|
||||
|
||||
if err := row.Scan(&createdAt, &updatedAt, &synopsis, &commitTime, &license); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &internal.Version{
|
||||
CreatedAt: createdAt,
|
||||
UpdatedAt: updatedAt,
|
||||
Module: &internal.Module{
|
||||
Name: name,
|
||||
},
|
||||
Version: version,
|
||||
Synopsis: synopsis,
|
||||
CommitTime: commitTime,
|
||||
License: &internal.License{
|
||||
Type: license,
|
||||
},
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
// InsertVersion inserts a Version into the database along with any
|
||||
// necessary series and modules. Insertion fails and returns an error
|
||||
// if the Version's primary key already exists in the database.
|
||||
// Inserting a Version connected to a series or module that already
|
||||
// exists in the database will not update the existing series or
|
||||
// module.
|
||||
func (db *DB) InsertVersion(version *internal.Version) error {
|
||||
if version == nil {
|
||||
return errors.New("postgres: cannot insert nil version")
|
||||
}
|
||||
|
||||
return db.Transact(func(tx *sql.Tx) error {
|
||||
if _, err := tx.Exec(
|
||||
`INSERT INTO series(name)
|
||||
VALUES($1)
|
||||
ON CONFLICT DO NOTHING`,
|
||||
version.Module.Series.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := tx.Exec(
|
||||
`INSERT INTO modules(name, series_name)
|
||||
VALUES($1,$2)
|
||||
ON CONFLICT DO NOTHING`,
|
||||
version.Module.Name, version.Module.Series.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO(ckimblebrown, julieqiu): Update Versions schema and insert readmes,
|
||||
// licenses, dependencies, and packages (the rest of the fields in the
|
||||
// internal.Version struct)
|
||||
if _, err := tx.Exec(
|
||||
`INSERT INTO versions(name, version, synopsis, commit_time, license, deleted)
|
||||
VALUES($1,$2,$3,$4,$5,$6)`,
|
||||
version.Module.Name,
|
||||
version.Version,
|
||||
version.Synopsis,
|
||||
version.CommitTime,
|
||||
version.License.Type,
|
||||
false,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
package postgres
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
"golang.org/x/discovery/internal"
|
||||
)
|
||||
|
||||
var (
|
||||
user = getEnv("GO_DISCOVERY_DATABASE_TEST_USER", "postgres")
|
||||
password = getEnv("GO_DISCOVERY_DATABASE_TEST_PASSWORD", "")
|
||||
host = getEnv("GO_DISCOVERY_DATABASE_TEST_HOST", "localhost")
|
||||
testdbname = getEnv("GO_DISCOVERY_DATABASE_TEST_NAME", "discovery-database-test")
|
||||
testdb = fmt.Sprintf("user=%s host=%s dbname=%s sslmode=disable", user, host, testdbname)
|
||||
)
|
||||
|
||||
func getEnv(key, fallback string) string {
|
||||
if value, ok := os.LookupEnv(key); ok {
|
||||
return value
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func setupCleanDB(t *testing.T) (func(t *testing.T), *DB) {
|
||||
t.Helper()
|
||||
db, err := Open(testdb)
|
||||
if err != nil {
|
||||
t.Fatalf("Open(%q), error: %v", testdb, err)
|
||||
}
|
||||
cleanup := func(t *testing.T) {
|
||||
db.Exec(`TRUNCATE version_logs;`) // truncates version_logs
|
||||
db.Exec(`TRUNCATE versions CASCADE;`) // truncates versions and any tables that use versions as a foreign key.
|
||||
}
|
||||
return cleanup, db
|
||||
}
|
||||
|
||||
func TestPostgres_ReadAndWriteVersion(t *testing.T) {
|
||||
var series = &internal.Series{
|
||||
Name: "myseries",
|
||||
Modules: []*internal.Module{},
|
||||
}
|
||||
|
||||
var module = &internal.Module{
|
||||
Name: "valid_module_name",
|
||||
Series: series,
|
||||
Versions: []*internal.Version{},
|
||||
}
|
||||
|
||||
var testVersion = &internal.Version{
|
||||
Module: module,
|
||||
Version: "v1.0.0",
|
||||
Synopsis: "This is a synopsis",
|
||||
License: &internal.License{},
|
||||
ReadMe: &internal.ReadMe{},
|
||||
CommitTime: time.Now(),
|
||||
Packages: []*internal.Package{},
|
||||
Dependencies: []*internal.Version{},
|
||||
Dependents: []*internal.Version{},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name, moduleName, version string
|
||||
versionData *internal.Version
|
||||
wantReadErr, wantWriteErr bool
|
||||
}{
|
||||
{
|
||||
name: "nil_version_write_error",
|
||||
moduleName: "valid_module_name",
|
||||
version: "v1.0.0",
|
||||
wantReadErr: true,
|
||||
wantWriteErr: true,
|
||||
},
|
||||
{
|
||||
name: "valid_test",
|
||||
moduleName: "valid_module_name",
|
||||
version: "v1.0.0",
|
||||
versionData: testVersion,
|
||||
},
|
||||
{
|
||||
name: "nonexistent_version_test",
|
||||
moduleName: "valid_module_name",
|
||||
version: "v1.2.3",
|
||||
versionData: testVersion,
|
||||
wantReadErr: true,
|
||||
},
|
||||
{
|
||||
name: "nonexistent_module_test",
|
||||
moduleName: "nonexistent_module_name",
|
||||
version: "v1.0.0",
|
||||
versionData: testVersion,
|
||||
wantReadErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
teardownTestCase, db := setupCleanDB(t)
|
||||
defer teardownTestCase(t)
|
||||
|
||||
if err := db.InsertVersion(tc.versionData); tc.wantWriteErr != (err != nil) {
|
||||
t.Errorf("db.InsertVersion(%+v) error: %v, want write error: %t", tc.versionData, err, tc.wantWriteErr)
|
||||
}
|
||||
|
||||
// Test that insertion of duplicate primary key fails when the first insert worked
|
||||
if err := db.InsertVersion(tc.versionData); err == nil {
|
||||
t.Errorf("db.InsertVersion(%+v) on duplicate version did not produce error", testVersion)
|
||||
}
|
||||
|
||||
got, err := db.GetVersion(tc.moduleName, tc.version)
|
||||
if tc.wantReadErr != (err != nil) {
|
||||
t.Fatalf("db.GetVersion(%q, %q) error: %v, want read error: %t", tc.moduleName, tc.version, err, tc.wantReadErr)
|
||||
}
|
||||
|
||||
if !tc.wantReadErr && got == nil {
|
||||
t.Fatalf("db.GetVersion(%q, %q) = %v, want %v",
|
||||
tc.moduleName, tc.version, got, tc.versionData)
|
||||
}
|
||||
|
||||
if !tc.wantReadErr && reflect.DeepEqual(*got, *tc.versionData) {
|
||||
t.Errorf("db.GetVersion(%q, %q) = %v, want %v",
|
||||
tc.moduleName, tc.version, got, tc.versionData)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostgress_InsertVersionLogs(t *testing.T) {
|
||||
teardownTestCase, db := setupCleanDB(t)
|
||||
defer teardownTestCase(t)
|
||||
|
||||
now := time.Now().UTC()
|
||||
newVersions := []*internal.VersionLog{
|
||||
&internal.VersionLog{
|
||||
Name: "testModule",
|
||||
Version: "v.1.0.0",
|
||||
CreatedAt: now.Add(-10 * time.Minute),
|
||||
Source: internal.VersionLogProxyIndex,
|
||||
},
|
||||
&internal.VersionLog{
|
||||
Name: "testModule",
|
||||
Version: "v.1.1.0",
|
||||
CreatedAt: now,
|
||||
Source: internal.VersionLogProxyIndex,
|
||||
},
|
||||
&internal.VersionLog{
|
||||
Name: "testModule/v2",
|
||||
Version: "v.2.0.0",
|
||||
CreatedAt: now,
|
||||
Source: internal.VersionLogProxyIndex,
|
||||
},
|
||||
}
|
||||
|
||||
if err := db.InsertVersionLogs(newVersions); err != nil {
|
||||
t.Errorf("db.InsertVersionLogs(newVersions) error: %v", err)
|
||||
}
|
||||
|
||||
dbTime, err := db.LatestProxyIndexUpdate()
|
||||
if err != nil {
|
||||
t.Errorf("db.LatestProxyIndexUpdate error: %v", err)
|
||||
}
|
||||
if !dbTime.Equal(now) {
|
||||
t.Errorf("db.LatestProxyIndexUpdate() = %v, want %v", dbTime, now)
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
// Copyright 2019 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 (
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"golang.org/x/discovery/internal"
|
||||
)
|
||||
|
||||
// LatestProxyIndexUpdate reports the last time the Proxy Index Cron
|
||||
// successfully fetched data from the Module Proxy Index.
|
||||
func (db *DB) LatestProxyIndexUpdate() (time.Time, error) {
|
||||
query := `
|
||||
SELECT created_at
|
||||
FROM version_logs
|
||||
WHERE source=$1
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1`
|
||||
|
||||
var createdAt time.Time
|
||||
row := db.QueryRow(query, internal.VersionLogProxyIndex)
|
||||
switch err := row.Scan(&createdAt); err {
|
||||
case sql.ErrNoRows:
|
||||
return time.Time{}, nil
|
||||
case nil:
|
||||
return createdAt, nil
|
||||
default:
|
||||
return time.Time{}, err
|
||||
}
|
||||
}
|
||||
|
||||
func (db *DB) InsertVersionLogs(logs []*internal.VersionLog) error {
|
||||
return db.Transact(func(tx *sql.Tx) error {
|
||||
for _, l := range logs {
|
||||
if _, err := tx.Exec(
|
||||
`INSERT INTO version_logs(name, version, created_at, source, error)
|
||||
VALUES ($1, $2, $3, $4, $5);`,
|
||||
l.Name, l.Version, l.CreatedAt, l.Source, l.Error,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
|
@ -1,126 +0,0 @@
|
|||
// Copyright 2019 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 (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/discovery/internal"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
)
|
||||
|
||||
var (
|
||||
user = getEnv("GO_DISCOVERY_DATABASE_TEST_USER", "postgres")
|
||||
password = getEnv("GO_DISCOVERY_DATABASE_TEST_PASSWORD", "")
|
||||
host = getEnv("GO_DISCOVERY_DATABASE_TEST_HOST", "localhost")
|
||||
dbname = getEnv("GO_DISCOVERY_DATABASE_TEST_NAME", "discovery-database")
|
||||
testdb = fmt.Sprintf("user=%s host=%s dbname=%s sslmode=disable", user, host, testdbname)
|
||||
)
|
||||
|
||||
func getEnv(key, fallback string) string {
|
||||
if value, ok := os.LookupEnv(key); ok {
|
||||
return value
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func setupTestCase(t *testing.T) (func(t *testing.T), *DB) {
|
||||
db, err := Open(testdb)
|
||||
if err != nil {
|
||||
t.Fatalf("Open(testdb) error: %v", err)
|
||||
}
|
||||
fn := func(t *testing.T) {
|
||||
db.Exec(`TRUNCATE version_logs;`) // truncates the version_logs table
|
||||
}
|
||||
return fn, db
|
||||
}
|
||||
|
||||
func TestLatestProxyIndexUpdateReturnsNilWithNoRows(t *testing.T) {
|
||||
teardownTestCase, db := setupTestCase(t)
|
||||
defer teardownTestCase(t)
|
||||
|
||||
dbTime, err := db.LatestProxyIndexUpdate()
|
||||
if err != nil {
|
||||
t.Errorf("db.LatestProxyIndexUpdate error: %v", err)
|
||||
}
|
||||
|
||||
if !dbTime.IsZero() {
|
||||
t.Errorf("db.LatestProxyIndexUpdate() = %v, want %v", dbTime, time.Time{})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLatestProxyIndexUpdateReturnsLatestTimestamp(t *testing.T) {
|
||||
teardownTestCase, db := setupTestCase(t)
|
||||
defer teardownTestCase(t)
|
||||
|
||||
now := time.Now().UTC()
|
||||
newVersions := []*internal.VersionLog{
|
||||
&internal.VersionLog{
|
||||
Name: "testModule",
|
||||
Version: "v.1.0.0",
|
||||
CreatedAt: now.Add(-10 * time.Minute),
|
||||
Source: internal.VersionLogProxyIndex,
|
||||
},
|
||||
&internal.VersionLog{
|
||||
Name: "testModule",
|
||||
Version: "v.1.1.0",
|
||||
CreatedAt: now,
|
||||
Source: internal.VersionLogProxyIndex,
|
||||
},
|
||||
&internal.VersionLog{
|
||||
Name: "testModule/v2",
|
||||
Version: "v.2.0.0",
|
||||
CreatedAt: now,
|
||||
Source: internal.VersionLogProxyIndex,
|
||||
},
|
||||
}
|
||||
|
||||
if err := db.InsertVersionLogs(newVersions); err != nil {
|
||||
t.Errorf("db.InsertVersionLogs(newVersions) error: %v", err)
|
||||
}
|
||||
|
||||
dbTime, err := db.LatestProxyIndexUpdate()
|
||||
if err != nil {
|
||||
t.Errorf("db.LatestProxyIndexUpdate error: %v", err)
|
||||
}
|
||||
if !dbTime.Equal(now) {
|
||||
t.Errorf("db.LatestProxyIndexUpdate() = %v, want %v", dbTime, now)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsertVersionLogs(t *testing.T) {
|
||||
teardownTestCase, db := setupTestCase(t)
|
||||
defer teardownTestCase(t)
|
||||
|
||||
now := time.Now().UTC()
|
||||
newVersions := []*internal.VersionLog{
|
||||
&internal.VersionLog{
|
||||
Name: "testModule",
|
||||
Version: "v.1.0.0",
|
||||
CreatedAt: now.Add(-10 * time.Minute),
|
||||
Source: internal.VersionLogProxyIndex,
|
||||
},
|
||||
&internal.VersionLog{
|
||||
Name: "testModule",
|
||||
Version: "v.1.1.0",
|
||||
CreatedAt: now,
|
||||
Source: internal.VersionLogProxyIndex,
|
||||
},
|
||||
&internal.VersionLog{
|
||||
Name: "testModule/v2",
|
||||
Version: "v.2.0.0",
|
||||
CreatedAt: now,
|
||||
Source: internal.VersionLogProxyIndex,
|
||||
},
|
||||
}
|
||||
|
||||
if err := db.InsertVersionLogs(newVersions); err != nil {
|
||||
t.Errorf("db.InsertVersionLogs(newVersions) error: %v", err)
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче