mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 17:51:57 +00:00
migration: Add colorization to drift output (#35735)
This commit is contained in:
parent
196bac71e9
commit
c348526e42
@ -3,9 +3,9 @@ package cliutil
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
descriptions "github.com/sourcegraph/sourcegraph/internal/database/migration/schemas"
|
||||
"github.com/sourcegraph/sourcegraph/lib/output"
|
||||
)
|
||||
|
||||
@ -38,13 +38,8 @@ func Drift(commandName string, factory RunnerFactory, outFactory func() *output.
|
||||
return err
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(prepareForSchemaComparison(schema, expected), expected); diff == "" {
|
||||
out.Write("No drift detected!")
|
||||
} else {
|
||||
out.Writef("Database schema drift detected: %s", diff)
|
||||
}
|
||||
|
||||
return nil
|
||||
descriptions.Canonicalize(schema)
|
||||
return compareSchemaDescriptions(out, schema, expected)
|
||||
})
|
||||
|
||||
return &cli.Command{
|
||||
|
||||
@ -5,8 +5,12 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/internal/database/migration/schemas"
|
||||
descriptions "github.com/sourcegraph/sourcegraph/internal/database/migration/schemas"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
"github.com/sourcegraph/sourcegraph/lib/output"
|
||||
)
|
||||
|
||||
// fetchSchema returns the schema description of the given schema at the given version. If the version
|
||||
@ -54,95 +58,142 @@ func getSchemaJSONFilename(schemaName, version string) (string, error) {
|
||||
return "", errors.Newf("unknown schema name %q", schemaName)
|
||||
}
|
||||
|
||||
// prepareForSchemaComparison modifies the given schema description to minimize the _spurious_ diff
|
||||
// between the actual and the given expected schema description.
|
||||
//
|
||||
// This function currently:
|
||||
// - removes entire objects that are not present in the expected schema to allow additional extensions
|
||||
// enums, functions, sequences, tables, and views that may be co-located (dev environments for one).
|
||||
func prepareForSchemaComparison(schemaDescription, expectedSchemaDescription descriptions.SchemaDescription) descriptions.SchemaDescription {
|
||||
var (
|
||||
expectedExtensions = map[string]struct{}{}
|
||||
expectedEnums = map[string]struct{}{}
|
||||
expectedFunctions = map[string]struct{}{}
|
||||
expectedSequences = map[string]struct{}{}
|
||||
expectedTables = map[string]struct{}{}
|
||||
expectedViews = map[string]struct{}{}
|
||||
)
|
||||
var errOutOfSync = errors.Newf("database schema is out of sync")
|
||||
|
||||
for _, extension := range expectedSchemaDescription.Extensions {
|
||||
func compareSchemaDescriptions(out *output.Output, actual, expected schemas.SchemaDescription) (err error) {
|
||||
missing := func(typeName, name string, value any) {
|
||||
out.WriteLine(output.Line(output.EmojiFailure, output.StyleBold, fmt.Sprintf("Missing %s %q", typeName, name)))
|
||||
jsonValue, _ := json.MarshalIndent(value, "", " ")
|
||||
out.WriteMarkdown(fmt.Sprintf("```json\n%s\n```", string(jsonValue)))
|
||||
err = errOutOfSync
|
||||
}
|
||||
|
||||
diff := func(typeName, fieldName, name string, a, b any) {
|
||||
diff := cmp.Diff(a, b)
|
||||
if diff != "" {
|
||||
out.WriteLine(output.Line(output.EmojiFailure, output.StyleBold, fmt.Sprintf("Mismatched %s of %s %q", fieldName, typeName, name)))
|
||||
out.WriteMarkdown(fmt.Sprintf("```diff\n%s```", diff))
|
||||
err = errOutOfSync
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Compare extensions
|
||||
|
||||
actualExtensions := map[string]struct{}{}
|
||||
for _, extension := range actual.Extensions {
|
||||
actualExtensions[extension] = struct{}{}
|
||||
}
|
||||
expectedExtensions := map[string]struct{}{}
|
||||
for _, extension := range expected.Extensions {
|
||||
expectedExtensions[extension] = struct{}{}
|
||||
}
|
||||
for _, enum := range expectedSchemaDescription.Enums {
|
||||
expectedEnums[enum.Name] = struct{}{}
|
||||
}
|
||||
for _, function := range expectedSchemaDescription.Functions {
|
||||
expectedFunctions[function.Name] = struct{}{}
|
||||
}
|
||||
for _, sequence := range expectedSchemaDescription.Sequences {
|
||||
expectedSequences[sequence.Name] = struct{}{}
|
||||
}
|
||||
for _, table := range expectedSchemaDescription.Tables {
|
||||
expectedTables[table.Name] = struct{}{}
|
||||
}
|
||||
for _, view := range expectedSchemaDescription.Views {
|
||||
expectedViews[view.Name] = struct{}{}
|
||||
}
|
||||
|
||||
var (
|
||||
filteredExtensions = schemaDescription.Extensions[:0]
|
||||
filteredEnums = schemaDescription.Enums[:0]
|
||||
filteredFunctions = schemaDescription.Functions[:0]
|
||||
filteredSequences = schemaDescription.Sequences[:0]
|
||||
filteredTables = schemaDescription.Tables[:0]
|
||||
filteredViews = schemaDescription.Views[:0]
|
||||
)
|
||||
|
||||
for _, extension := range schemaDescription.Extensions {
|
||||
if _, ok := expectedExtensions[extension]; ok {
|
||||
filteredExtensions = append(filteredExtensions, extension)
|
||||
for name := range expectedExtensions {
|
||||
if _, ok := actualExtensions[name]; !ok {
|
||||
missing("extension", name, nil)
|
||||
}
|
||||
}
|
||||
|
||||
for _, enum := range schemaDescription.Enums {
|
||||
if _, ok := expectedEnums[enum.Name]; ok {
|
||||
filteredEnums = append(filteredEnums, enum)
|
||||
//
|
||||
// Compare enums
|
||||
|
||||
actualEnums := map[string]schemas.EnumDescription{}
|
||||
for _, enum := range actual.Enums {
|
||||
actualEnums[enum.Name] = enum
|
||||
}
|
||||
expectedEnums := map[string]schemas.EnumDescription{}
|
||||
for _, enum := range expected.Enums {
|
||||
expectedEnums[enum.Name] = enum
|
||||
}
|
||||
for name, expectedEnum := range expectedEnums {
|
||||
if enum, ok := actualEnums[name]; !ok {
|
||||
missing("enum", name, expectedEnum)
|
||||
} else {
|
||||
diff("enum", "labels", name, expectedEnum.Labels, enum.Labels)
|
||||
}
|
||||
}
|
||||
|
||||
for _, function := range schemaDescription.Functions {
|
||||
if _, ok := expectedFunctions[function.Name]; ok {
|
||||
filteredFunctions = append(filteredFunctions, function)
|
||||
//
|
||||
// Compare functions
|
||||
|
||||
actualFunctions := map[string]schemas.FunctionDescription{}
|
||||
for _, function := range actual.Functions {
|
||||
actualFunctions[function.Name] = function
|
||||
}
|
||||
expectedFunctions := map[string]schemas.FunctionDescription{}
|
||||
for _, function := range expected.Functions {
|
||||
expectedFunctions[function.Name] = function
|
||||
}
|
||||
for name, expectedFunction := range expectedFunctions {
|
||||
if function, ok := actualFunctions[name]; !ok {
|
||||
missing("function", name, expectedFunction)
|
||||
} else {
|
||||
diff("function", "definition", name, expectedFunction.Definition, function.Definition)
|
||||
}
|
||||
}
|
||||
|
||||
for _, sequence := range schemaDescription.Sequences {
|
||||
if _, ok := expectedSequences[sequence.Name]; ok {
|
||||
filteredSequences = append(filteredSequences, sequence)
|
||||
//
|
||||
// Compare sequences
|
||||
|
||||
actualSequences := map[string]schemas.SequenceDescription{}
|
||||
for _, sequence := range actual.Sequences {
|
||||
actualSequences[sequence.Name] = sequence
|
||||
}
|
||||
expectedSequences := map[string]schemas.SequenceDescription{}
|
||||
for _, sequence := range expected.Sequences {
|
||||
expectedSequences[sequence.Name] = sequence
|
||||
}
|
||||
for name, expectedSequence := range expectedSequences {
|
||||
if sequence, ok := actualSequences[name]; !ok {
|
||||
missing("sequence", name, expectedSequence)
|
||||
} else {
|
||||
diff("sequence", "definition", name, expectedSequence, sequence)
|
||||
}
|
||||
}
|
||||
|
||||
for _, table := range schemaDescription.Tables {
|
||||
if _, ok := expectedTables[table.Name]; ok {
|
||||
filteredTables = append(filteredTables, table)
|
||||
//
|
||||
// Compare tables
|
||||
|
||||
actualTables := map[string]schemas.TableDescription{}
|
||||
for _, table := range actual.Tables {
|
||||
actualTables[table.Name] = table
|
||||
}
|
||||
expectedTables := map[string]schemas.TableDescription{}
|
||||
for _, Table := range expected.Tables {
|
||||
expectedTables[Table.Name] = Table
|
||||
}
|
||||
for name, expectedTable := range expectedTables {
|
||||
if table, ok := actualTables[name]; !ok {
|
||||
missing("table", name, expectedTable)
|
||||
} else {
|
||||
diff("table", "columns", name, expectedTable.Columns, table.Columns)
|
||||
diff("table", "columns", name, expectedTable.Constraints, table.Constraints)
|
||||
diff("table", "columns", name, expectedTable.Indexes, table.Indexes)
|
||||
diff("table", "columns", name, expectedTable.Triggers, table.Triggers)
|
||||
}
|
||||
}
|
||||
|
||||
for _, view := range schemaDescription.Views {
|
||||
if _, ok := expectedViews[view.Name]; ok {
|
||||
filteredViews = append(filteredViews, view)
|
||||
//
|
||||
// Compare views
|
||||
|
||||
actualViews := map[string]schemas.ViewDescription{}
|
||||
for _, view := range actual.Views {
|
||||
actualViews[view.Name] = view
|
||||
}
|
||||
expectedViews := map[string]schemas.ViewDescription{}
|
||||
for _, view := range expected.Views {
|
||||
expectedViews[view.Name] = view
|
||||
}
|
||||
for name, expectedView := range expectedViews {
|
||||
if view, ok := actualViews[name]; !ok {
|
||||
missing("view", name, expectedView)
|
||||
} else {
|
||||
diff("view", "definition", name, expectedView.Definition, view.Definition)
|
||||
}
|
||||
}
|
||||
|
||||
newSchema := descriptions.SchemaDescription{
|
||||
Extensions: filteredExtensions,
|
||||
Enums: filteredEnums,
|
||||
Functions: filteredFunctions,
|
||||
Sequences: filteredSequences,
|
||||
Tables: filteredTables,
|
||||
Views: filteredViews,
|
||||
if err == nil {
|
||||
out.Write("No drift detected")
|
||||
}
|
||||
|
||||
descriptions.Canonicalize(newSchema)
|
||||
return newSchema
|
||||
return err
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user