codeintel: Fix test database at revision for oobmigrations (#46141)

This commit is contained in:
Eric Fritz 2023-01-05 16:27:31 -06:00 committed by GitHub
parent eb315f680b
commit b0b5822b55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 4773 additions and 51 deletions

View File

@ -7,7 +7,7 @@ client/browser/build
**/coverage
internal/database/schema*.md
internal/database/schema*.json
internal/database/migration/shared/upgradedata/stitched-migration-graph.json
internal/database/migration/shared/data/**/*.json
cmd/xlang-python/python-langserver/
package-lock.json
package.json

View File

@ -314,7 +314,7 @@ Note that it is not advised to set the deprecated version to the minor release o
#### Step 6: Deprecation
Despite an out of band migration being marked deprecated, it may still need to be executed by multi-version upgrades in a later version. For this reason it is not safe to delete any code from the out of band migrations until _after_ the deprecation version falls out of the [supported multi-version upgrade window](https://sourcegraph.com/github.com/sourcegraph/sourcegraph@39996f3159a0466624bc3a57689560c6bdebb60c/-/blob/internal/database/migration/shared/upgradedata/cmd/generator/consts.go?L24).
Despite an out of band migration being marked deprecated, it may still need to be executed by multi-version upgrades in a later version. For this reason it is not safe to delete any code from the out of band migrations until _after_ the deprecation version falls out of the [supported multi-version upgrade window](https://sourcegraph.com/github.com/sourcegraph/sourcegraph@39996f3159a0466624bc3a57689560c6bdebb60c/-/blob/internal/database/migration/shared/data/cmd/generator/consts.go?L24).
As an alternative to deleting the code, the out of band migration can be isolated from any dependencies outside of the out of band migration. For example copying any types, functions, and other code that is used to execute the migration. Once isolated, the migration can be considered frozen and effectively ignored. To see an example, [see the Code Insights settings migration](https://sourcegraph.com/github.com/sourcegraph/sourcegraph@39996f3159a0466624bc3a57689560c6bdebb60c/-/tree/enterprise/internal/oobmigration/migrations/insights)

View File

@ -21,7 +21,9 @@ func init() {
func TestSCIPMigrator(t *testing.T) {
logger := logtest.Scoped(t)
// TODO - use the AtRev constructor after this has been deprecated
rawDB := dbtest.NewDB(logger, t)
// rawDB := dbtest.NewDBAtRev(logger, t, "4.3.0")
db := database.NewDB(logger, rawDB)
codeIntelDB := stores.NewCodeIntelDB(logger, rawDB)
store := basestore.NewWithHandle(db.Handle())

View File

@ -4,6 +4,7 @@ import (
crand "crypto/rand"
"database/sql"
"encoding/binary"
"fmt"
"hash/fnv"
"math/rand"
"net/url"
@ -60,49 +61,67 @@ var rng = rand.New(rand.NewSource(func() int64 {
}()))
var rngLock sync.Mutex
var dbTemplateOnce sync.Once
// NewDB returns a connection to a clean, new temporary testing database with
// the same schema as Sourcegraph's production Postgres database.
func NewDB(logger log.Logger, t testing.TB) *sql.DB {
if testing.Short() {
t.Skip("DB tests disabled since go test -short is specified")
}
dbTemplateOnce.Do(func() {
initTemplateDB(logger, t, "migrated", []*schemas.Schema{schemas.Frontend, schemas.CodeIntel})
})
return newFromDSN(logger, t, "migrated")
return newDB(logger, t, "migrated", schemas.Frontend, schemas.CodeIntel)
}
var insightsTemplateOnce sync.Once
// NewDBAtRev returns a connection to a clean, new temporary testing database with
// the same schema as Sourcegraph's production Postgres database at the given revision.
func NewDBAtRev(logger log.Logger, t testing.TB, rev string) *sql.DB {
return newDB(
logger,
t,
fmt.Sprintf("migrated-%s", rev),
getSchemaAtRev(t, "frontend", rev),
getSchemaAtRev(t, "codeintel", rev),
)
}
func getSchemaAtRev(t testing.TB, name, rev string) *schemas.Schema {
schema, err := schemas.ResolveSchemaAtRev(name, rev)
if err != nil {
t.Fatalf("failed to resolve %q schema: %s", name, err)
}
return schema
}
// NewInsightsDB returns a connection to a clean, new temporary testing database with
// the same schema as Sourcegraph's CodeInsights production Postgres database.
func NewInsightsDB(logger log.Logger, t testing.TB) *sql.DB {
if testing.Short() {
t.Skip("DB tests disabled since go test -short is specified")
}
insightsTemplateOnce.Do(func() {
initTemplateDB(logger, t, "insights", []*schemas.Schema{schemas.CodeInsights})
})
return newFromDSN(logger, t, "insights")
return newDB(logger, t, "insights", schemas.CodeInsights)
}
var rawTemplateOnce sync.Once
// NewRawDB returns a connection to a clean, new temporary testing database.
func NewRawDB(logger log.Logger, t testing.TB) *sql.DB {
return newDB(logger, t, "raw")
}
func newDB(logger log.Logger, t testing.TB, name string, schemas ...*schemas.Schema) *sql.DB {
if testing.Short() {
t.Skip("DB tests disabled since go test -short is specified")
}
rawTemplateOnce.Do(func() {
initTemplateDB(logger, t, "raw", nil)
})
return newFromDSN(logger, t, "raw")
onceByName(name).Do(func() { initTemplateDB(logger, t, name, schemas) })
return newFromDSN(logger, t, name)
}
var onceByNameMap = map[string]*sync.Once{}
var onceByNameMutex sync.Mutex
func onceByName(name string) *sync.Once {
onceByNameMutex.Lock()
defer onceByNameMutex.Unlock()
if once, ok := onceByNameMap[name]; ok {
return once
}
once := new(sync.Once)
onceByNameMap[name] = once
return once
}
func newFromDSN(logger log.Logger, t testing.TB, templateNamespace string) *sql.DB {

View File

@ -35,7 +35,6 @@ func makeTestSchema(t *testing.T, name string) *schemas.Schema {
return &schemas.Schema{
Name: name,
MigrationsTableName: fmt.Sprintf("%s_migrations_table", name),
FS: fs,
Definitions: definitions,
}
}

View File

@ -1,8 +1,6 @@
package schemas
import (
"io/fs"
"github.com/sourcegraph/sourcegraph/internal/database/migration/definition"
)
@ -14,9 +12,6 @@ type Schema struct {
// MigrationsTableName is the name of the table that tracks the schema version.
MigrationsTableName string
// FS describes the raw migration assets of the schema.
FS fs.FS
// Definitions describes the parsed migration assets of the schema.
Definitions *definition.Definitions
}

View File

@ -7,6 +7,7 @@ import (
"strings"
"github.com/sourcegraph/sourcegraph/internal/database/migration/definition"
"github.com/sourcegraph/sourcegraph/internal/database/migration/shared"
"github.com/sourcegraph/sourcegraph/lib/errors"
"github.com/sourcegraph/sourcegraph/migrations"
)
@ -46,7 +47,19 @@ func ResolveSchema(fs fs.FS, name string) (*Schema, error) {
return &Schema{
Name: name,
MigrationsTableName: MigrationsTableName(name),
FS: fs,
Definitions: definitions,
}, nil
}
func ResolveSchemaAtRev(name, rev string) (*Schema, error) {
definitions, err := shared.GetFrozenDefinitions(name, rev)
if err != nil {
return nil, err
}
return &Schema{
Name: name,
MigrationsTableName: MigrationsTableName(name),
Definitions: definitions,
}, nil
}

View File

@ -6,9 +6,8 @@ import (
"github.com/sourcegraph/sourcegraph/internal/oobmigration"
)
// NOTE: This should be kept up-to-date with cmd/migrator/build.sh
// so that we "bake in" fallback schemas everything we support migrating
// to.
// NOTE: This should be kept up-to-date with cmd/migrator/build.sh so that we "bake in"
// fallback schemas everything we support migrating to.
const maxVersionString = "4.3.0"
// MaxVersion is the highest known released version at the time the migrator was built.
@ -20,5 +19,13 @@ var MaxVersion = func() oobmigration.Version {
panic(fmt.Sprintf("malformed maxVersionString %q", maxVersionString))
}()
// MinVersion is the minimum version a migrator can support upgrading to a newer version of Sourcegraph.
// MinVersion is the minimum version a migrator can support upgrading to a newer version of
// Sourcegraph.
var MinVersion = oobmigration.NewVersion(3, 20)
// FrozenRevisions are schemas at a point-in-time for which out-of-band migration unit tests
// can continue to run on their last pre-deprecation version. This code is still ran by the
// migrator, but only on a schema shape that existed in the past.
var FrozenRevisions = []string{
"4.3.0",
}

View File

@ -19,26 +19,41 @@ func main() {
}
func mainErr() error {
// This script is invoked via a go:generate directive in
// internal/database/migration/shared (embed.go)
wd, err := os.Getwd()
if err != nil {
return err
}
// This script is invoked via a go:generate directive in internal/database/migration/shared (embed.go)
repoRoot := filepath.Join(wd, "..", "..", "..", "..")
filepath := filepath.Join(wd, "upgradedata", "stitched-migration-graph.json")
//
// Write stitched migrations
versions, err := oobmigration.UpgradeRange(MinVersion, MaxVersion)
if err != nil {
return err
}
versionTags := make([]string, 0, len(versions))
for _, version := range versions {
versionTags = append(versionTags, version.GitTag())
}
if err := stitchAndWrite(repoRoot, filepath.Join(wd, "data", "stitched-migration-graph.json"), versionTags); err != nil {
return err
}
//
// Write frozen migrations
for _, rev := range FrozenRevisions {
if err := stitchAndWrite(repoRoot, filepath.Join(wd, "data", "frozen", fmt.Sprintf("%s.json", rev)), []string{rev}); err != nil {
return err
}
}
return nil
}
func stitchAndWrite(repoRoot, filepath string, versionTags []string) error {
stitchedMigrationBySchemaName := map[string]shared.StitchedMigration{}
for _, schemaName := range schemas.SchemaNames {
stitched, err := stitch.StitchDefinitions(schemaName, repoRoot, versionTags)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,23 +1,66 @@
package shared
import (
_ "embed"
"embed"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"github.com/sourcegraph/sourcegraph/internal/database/migration/definition"
"github.com/sourcegraph/sourcegraph/lib/errors"
)
//go:generate go run ./upgradedata/cmd/generator
// Ensure upgradedata/stitched-migration-graph.json is generated
//go:generate go run ./data/cmd/generator
// Ensure data/* files are generated
//go:embed upgradedata/stitched-migration-graph.json
var upgradeDataPayloadContents string
var (
root = "internal/database/migration/shared/data"
stitchfile = filepath.Join(root, "stitched-migration-graph.json")
constfile = filepath.Join(root, "cmd/generator/consts.go")
)
//go:embed data/stitched-migration-graph.json
var stitchedPayloadContents string
// StitchedMigationsBySchemaName is a map from schema name to migration upgrade metadata.
// The data backing the map is updated by `go generating` this package.
var StitchedMigationsBySchemaName = map[string]StitchedMigration{}
func init() {
if err := json.Unmarshal([]byte(upgradeDataPayloadContents), &StitchedMigationsBySchemaName); err != nil {
panic(fmt.Sprintf("failed to load upgrade data (check the contents of internal/database/migration/shared/upgradedata/stitched-migration-graph.json): %s", err))
if err := json.Unmarshal([]byte(stitchedPayloadContents), &StitchedMigationsBySchemaName); err != nil {
panic(fmt.Sprintf("failed to load upgrade data (check the contents of %s): %s", stitchfile, err))
}
}
//go:embed data/frozen/*
var frozenDataDir embed.FS
// GetFrozenDefinitions returns the schema definitions frozen at a given revision. This
// function returns an error if the given schema has not been generated into data/frozen.
func GetFrozenDefinitions(schemaName, rev string) (*definition.Definitions, error) {
f, err := frozenDataDir.Open(fmt.Sprintf("data/frozen/%s.json", rev))
if err != nil {
if os.IsNotExist(err) {
return nil, errors.Newf("failed to load schema at revision %q (check the versions listed in %s)", rev, constfile)
}
return nil, err
}
defer f.Close()
content, err := io.ReadAll(f)
if err != nil {
return nil, err
}
var definitionBySchema map[string]struct {
Definitions *definition.Definitions
}
if err := json.Unmarshal(content, &definitionBySchema); err != nil {
return nil, err
}
return definitionBySchema[schemaName].Definitions, nil
}