Table.Columns/Constraints/Indexes/Relationships are Go maps, and every writer, reader, diff, inspector, and merge code path that iterated them directly was subject to Go's randomized map order, so identical input could produce different output (or a different in-report violation/diff order) on every run. Most visibly this showed up as bun/gorm `unique:` struct tags changing order across consecutive `make models` runs with no source change. Fixed by sorting map iteration (by Sequence then Name, or alphabetically for string-keyed maps) everywhere the order affects generated output or first-match tie-break logic, across the bun, gorm, sqlite, dbml, drawdb, pgsql, prisma, graphql, typeorm, drizzle, and dctx writers; the dctx, prisma, and typeorm readers; the shared models.GetPrimaryKey/ GetForeignKeys helpers; pkg/diff, pkg/inspector, and pkg/merge; and the TUI column/relationship pickers in pkg/ui.
624 lines
18 KiB
Go
624 lines
18 KiB
Go
package diff
|
|
|
|
import (
|
|
"reflect"
|
|
"sort"
|
|
|
|
"git.warky.dev/wdevs/relspecgo/pkg/models"
|
|
)
|
|
|
|
// sortedKeys returns a map's keys sorted alphabetically, so callers get a
|
|
// deterministic iteration order instead of Go's randomized map order.
|
|
func sortedKeys[T any](m map[string]T) []string {
|
|
keys := make([]string, 0, len(m))
|
|
for k := range m {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
return keys
|
|
}
|
|
|
|
// CompareDatabases compares two database models and returns the differences
|
|
func CompareDatabases(source, target *models.Database) *DiffResult {
|
|
result := &DiffResult{
|
|
Source: source.Name,
|
|
Target: target.Name,
|
|
Schemas: compareSchemas(source.Schemas, target.Schemas),
|
|
}
|
|
return result
|
|
}
|
|
|
|
func compareSchemas(source, target []*models.Schema) *SchemaDiff {
|
|
diff := &SchemaDiff{
|
|
Missing: make([]*models.Schema, 0),
|
|
Extra: make([]*models.Schema, 0),
|
|
Modified: make([]*SchemaChange, 0),
|
|
}
|
|
|
|
sourceMap := make(map[string]*models.Schema)
|
|
targetMap := make(map[string]*models.Schema)
|
|
|
|
for _, s := range source {
|
|
sourceMap[s.SQLName()] = s
|
|
}
|
|
for _, t := range target {
|
|
targetMap[t.SQLName()] = t
|
|
}
|
|
|
|
// Find missing and modified schemas
|
|
for _, name := range sortedKeys(sourceMap) {
|
|
srcSchema := sourceMap[name]
|
|
if tgtSchema, exists := targetMap[name]; !exists {
|
|
diff.Missing = append(diff.Missing, srcSchema)
|
|
} else {
|
|
if change := compareSchemaDetails(srcSchema, tgtSchema); change != nil {
|
|
diff.Modified = append(diff.Modified, change)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find extra schemas
|
|
for _, name := range sortedKeys(targetMap) {
|
|
tgtSchema := targetMap[name]
|
|
if _, exists := sourceMap[name]; !exists {
|
|
diff.Extra = append(diff.Extra, tgtSchema)
|
|
}
|
|
}
|
|
|
|
return diff
|
|
}
|
|
|
|
func compareSchemaDetails(source, target *models.Schema) *SchemaChange {
|
|
change := &SchemaChange{
|
|
Name: source.Name,
|
|
}
|
|
|
|
hasChanges := false
|
|
|
|
// Compare tables
|
|
tableDiff := compareTables(source.Tables, target.Tables)
|
|
if !isEmpty(tableDiff) {
|
|
change.Tables = tableDiff
|
|
hasChanges = true
|
|
}
|
|
|
|
// Compare views
|
|
viewDiff := compareViews(source.Views, target.Views)
|
|
if !isEmpty(viewDiff) {
|
|
change.Views = viewDiff
|
|
hasChanges = true
|
|
}
|
|
|
|
// Compare sequences
|
|
sequenceDiff := compareSequences(source.Sequences, target.Sequences)
|
|
if !isEmpty(sequenceDiff) {
|
|
change.Sequences = sequenceDiff
|
|
hasChanges = true
|
|
}
|
|
|
|
if !hasChanges {
|
|
return nil
|
|
}
|
|
return change
|
|
}
|
|
|
|
func compareTables(source, target []*models.Table) *TableDiff {
|
|
diff := &TableDiff{
|
|
Missing: make([]*models.Table, 0),
|
|
Extra: make([]*models.Table, 0),
|
|
Modified: make([]*TableChange, 0),
|
|
}
|
|
|
|
sourceMap := make(map[string]*models.Table)
|
|
targetMap := make(map[string]*models.Table)
|
|
|
|
for _, t := range source {
|
|
sourceMap[t.SQLName()] = t
|
|
}
|
|
for _, t := range target {
|
|
targetMap[t.SQLName()] = t
|
|
}
|
|
|
|
// Find missing and modified tables
|
|
for _, name := range sortedKeys(sourceMap) {
|
|
srcTable := sourceMap[name]
|
|
if tgtTable, exists := targetMap[name]; !exists {
|
|
diff.Missing = append(diff.Missing, srcTable)
|
|
} else {
|
|
if change := compareTableDetails(srcTable, tgtTable); change != nil {
|
|
diff.Modified = append(diff.Modified, change)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find extra tables
|
|
for _, name := range sortedKeys(targetMap) {
|
|
tgtTable := targetMap[name]
|
|
if _, exists := sourceMap[name]; !exists {
|
|
diff.Extra = append(diff.Extra, tgtTable)
|
|
}
|
|
}
|
|
|
|
return diff
|
|
}
|
|
|
|
func compareTableDetails(source, target *models.Table) *TableChange {
|
|
change := &TableChange{
|
|
Name: source.Name,
|
|
Schema: source.Schema,
|
|
}
|
|
|
|
hasChanges := false
|
|
|
|
// Compare columns
|
|
columnDiff := compareColumns(source.Columns, target.Columns)
|
|
if !isEmpty(columnDiff) {
|
|
change.Columns = columnDiff
|
|
hasChanges = true
|
|
}
|
|
|
|
// Compare indexes
|
|
indexDiff := compareIndexes(source.Indexes, target.Indexes)
|
|
if !isEmpty(indexDiff) {
|
|
change.Indexes = indexDiff
|
|
hasChanges = true
|
|
}
|
|
|
|
// Compare constraints
|
|
constraintDiff := compareConstraints(source.Constraints, target.Constraints)
|
|
if !isEmpty(constraintDiff) {
|
|
change.Constraints = constraintDiff
|
|
hasChanges = true
|
|
}
|
|
|
|
// Compare relationships
|
|
relationshipDiff := compareRelationships(source.Relationships, target.Relationships)
|
|
if !isEmpty(relationshipDiff) {
|
|
change.Relationships = relationshipDiff
|
|
hasChanges = true
|
|
}
|
|
|
|
if !hasChanges {
|
|
return nil
|
|
}
|
|
return change
|
|
}
|
|
|
|
func compareColumns(source, target map[string]*models.Column) *ColumnDiff {
|
|
diff := &ColumnDiff{
|
|
Missing: make([]*models.Column, 0),
|
|
Extra: make([]*models.Column, 0),
|
|
Modified: make([]*ColumnChange, 0),
|
|
}
|
|
|
|
// Find missing and modified columns
|
|
for _, name := range sortedKeys(source) {
|
|
srcCol := source[name]
|
|
if tgtCol, exists := target[name]; !exists {
|
|
diff.Missing = append(diff.Missing, srcCol)
|
|
} else {
|
|
if changes := compareColumnDetails(srcCol, tgtCol); len(changes) > 0 {
|
|
diff.Modified = append(diff.Modified, &ColumnChange{
|
|
Name: name,
|
|
Source: srcCol,
|
|
Target: tgtCol,
|
|
Changes: changes,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find extra columns
|
|
for _, name := range sortedKeys(target) {
|
|
tgtCol := target[name]
|
|
if _, exists := source[name]; !exists {
|
|
diff.Extra = append(diff.Extra, tgtCol)
|
|
}
|
|
}
|
|
|
|
return diff
|
|
}
|
|
|
|
func compareColumnDetails(source, target *models.Column) map[string]any {
|
|
changes := make(map[string]any)
|
|
|
|
if source.Type != target.Type {
|
|
changes["type"] = map[string]string{"source": source.Type, "target": target.Type}
|
|
}
|
|
if source.Length != target.Length {
|
|
changes["length"] = map[string]int{"source": source.Length, "target": target.Length}
|
|
}
|
|
if source.Precision != target.Precision {
|
|
changes["precision"] = map[string]int{"source": source.Precision, "target": target.Precision}
|
|
}
|
|
if source.Scale != target.Scale {
|
|
changes["scale"] = map[string]int{"source": source.Scale, "target": target.Scale}
|
|
}
|
|
if source.NotNull != target.NotNull {
|
|
changes["not_null"] = map[string]bool{"source": source.NotNull, "target": target.NotNull}
|
|
}
|
|
if !reflect.DeepEqual(source.Default, target.Default) {
|
|
changes["default"] = map[string]any{"source": source.Default, "target": target.Default}
|
|
}
|
|
if source.AutoIncrement != target.AutoIncrement {
|
|
changes["auto_increment"] = map[string]bool{"source": source.AutoIncrement, "target": target.AutoIncrement}
|
|
}
|
|
if source.IsPrimaryKey != target.IsPrimaryKey {
|
|
changes["is_primary_key"] = map[string]bool{"source": source.IsPrimaryKey, "target": target.IsPrimaryKey}
|
|
}
|
|
|
|
return changes
|
|
}
|
|
|
|
func compareIndexes(source, target map[string]*models.Index) *IndexDiff {
|
|
diff := &IndexDiff{
|
|
Missing: make([]*models.Index, 0),
|
|
Extra: make([]*models.Index, 0),
|
|
Modified: make([]*IndexChange, 0),
|
|
}
|
|
|
|
// Find missing and modified indexes
|
|
for _, name := range sortedKeys(source) {
|
|
srcIdx := source[name]
|
|
if tgtIdx, exists := target[name]; !exists {
|
|
diff.Missing = append(diff.Missing, srcIdx)
|
|
} else {
|
|
if changes := compareIndexDetails(srcIdx, tgtIdx); len(changes) > 0 {
|
|
diff.Modified = append(diff.Modified, &IndexChange{
|
|
Name: name,
|
|
Source: srcIdx,
|
|
Target: tgtIdx,
|
|
Changes: changes,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find extra indexes
|
|
for _, name := range sortedKeys(target) {
|
|
tgtIdx := target[name]
|
|
if _, exists := source[name]; !exists {
|
|
diff.Extra = append(diff.Extra, tgtIdx)
|
|
}
|
|
}
|
|
|
|
return diff
|
|
}
|
|
|
|
func compareIndexDetails(source, target *models.Index) map[string]any {
|
|
changes := make(map[string]any)
|
|
|
|
if !reflect.DeepEqual(source.Columns, target.Columns) {
|
|
changes["columns"] = map[string][]string{"source": source.Columns, "target": target.Columns}
|
|
}
|
|
if source.Unique != target.Unique {
|
|
changes["unique"] = map[string]bool{"source": source.Unique, "target": target.Unique}
|
|
}
|
|
if source.Type != target.Type {
|
|
changes["type"] = map[string]string{"source": source.Type, "target": target.Type}
|
|
}
|
|
if source.Where != target.Where {
|
|
changes["where"] = map[string]string{"source": source.Where, "target": target.Where}
|
|
}
|
|
|
|
return changes
|
|
}
|
|
|
|
func compareConstraints(source, target map[string]*models.Constraint) *ConstraintDiff {
|
|
diff := &ConstraintDiff{
|
|
Missing: make([]*models.Constraint, 0),
|
|
Extra: make([]*models.Constraint, 0),
|
|
Modified: make([]*ConstraintChange, 0),
|
|
}
|
|
|
|
// Find missing and modified constraints
|
|
for _, name := range sortedKeys(source) {
|
|
srcCon := source[name]
|
|
if tgtCon, exists := target[name]; !exists {
|
|
diff.Missing = append(diff.Missing, srcCon)
|
|
} else {
|
|
if changes := compareConstraintDetails(srcCon, tgtCon); len(changes) > 0 {
|
|
diff.Modified = append(diff.Modified, &ConstraintChange{
|
|
Name: name,
|
|
Source: srcCon,
|
|
Target: tgtCon,
|
|
Changes: changes,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find extra constraints
|
|
for _, name := range sortedKeys(target) {
|
|
tgtCon := target[name]
|
|
if _, exists := source[name]; !exists {
|
|
diff.Extra = append(diff.Extra, tgtCon)
|
|
}
|
|
}
|
|
|
|
return diff
|
|
}
|
|
|
|
func compareConstraintDetails(source, target *models.Constraint) map[string]any {
|
|
changes := make(map[string]any)
|
|
|
|
if source.Type != target.Type {
|
|
changes["type"] = map[string]string{"source": string(source.Type), "target": string(target.Type)}
|
|
}
|
|
if !reflect.DeepEqual(source.Columns, target.Columns) {
|
|
changes["columns"] = map[string][]string{"source": source.Columns, "target": target.Columns}
|
|
}
|
|
if source.ReferencedTable != target.ReferencedTable {
|
|
changes["referenced_table"] = map[string]string{"source": source.ReferencedTable, "target": target.ReferencedTable}
|
|
}
|
|
if !reflect.DeepEqual(source.ReferencedColumns, target.ReferencedColumns) {
|
|
changes["referenced_columns"] = map[string][]string{"source": source.ReferencedColumns, "target": target.ReferencedColumns}
|
|
}
|
|
if source.OnDelete != target.OnDelete {
|
|
changes["on_delete"] = map[string]string{"source": source.OnDelete, "target": target.OnDelete}
|
|
}
|
|
if source.OnUpdate != target.OnUpdate {
|
|
changes["on_update"] = map[string]string{"source": source.OnUpdate, "target": target.OnUpdate}
|
|
}
|
|
|
|
return changes
|
|
}
|
|
|
|
func compareRelationships(source, target map[string]*models.Relationship) *RelationshipDiff {
|
|
diff := &RelationshipDiff{
|
|
Missing: make([]*models.Relationship, 0),
|
|
Extra: make([]*models.Relationship, 0),
|
|
Modified: make([]*RelationshipChange, 0),
|
|
}
|
|
|
|
// Find missing and modified relationships
|
|
for _, name := range sortedKeys(source) {
|
|
srcRel := source[name]
|
|
if tgtRel, exists := target[name]; !exists {
|
|
diff.Missing = append(diff.Missing, srcRel)
|
|
} else {
|
|
if changes := compareRelationshipDetails(srcRel, tgtRel); len(changes) > 0 {
|
|
diff.Modified = append(diff.Modified, &RelationshipChange{
|
|
Name: name,
|
|
Source: srcRel,
|
|
Target: tgtRel,
|
|
Changes: changes,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find extra relationships
|
|
for _, name := range sortedKeys(target) {
|
|
tgtRel := target[name]
|
|
if _, exists := source[name]; !exists {
|
|
diff.Extra = append(diff.Extra, tgtRel)
|
|
}
|
|
}
|
|
|
|
return diff
|
|
}
|
|
|
|
func compareRelationshipDetails(source, target *models.Relationship) map[string]any {
|
|
changes := make(map[string]any)
|
|
|
|
if source.Type != target.Type {
|
|
changes["type"] = map[string]string{"source": string(source.Type), "target": string(target.Type)}
|
|
}
|
|
if source.FromTable != target.FromTable {
|
|
changes["from_table"] = map[string]string{"source": source.FromTable, "target": target.FromTable}
|
|
}
|
|
if source.ToTable != target.ToTable {
|
|
changes["to_table"] = map[string]string{"source": source.ToTable, "target": target.ToTable}
|
|
}
|
|
if !reflect.DeepEqual(source.FromColumns, target.FromColumns) {
|
|
changes["from_columns"] = map[string][]string{"source": source.FromColumns, "target": target.FromColumns}
|
|
}
|
|
if !reflect.DeepEqual(source.ToColumns, target.ToColumns) {
|
|
changes["to_columns"] = map[string][]string{"source": source.ToColumns, "target": target.ToColumns}
|
|
}
|
|
|
|
return changes
|
|
}
|
|
|
|
func compareViews(source, target []*models.View) *ViewDiff {
|
|
diff := &ViewDiff{
|
|
Missing: make([]*models.View, 0),
|
|
Extra: make([]*models.View, 0),
|
|
Modified: make([]*ViewChange, 0),
|
|
}
|
|
|
|
sourceMap := make(map[string]*models.View)
|
|
targetMap := make(map[string]*models.View)
|
|
|
|
for _, v := range source {
|
|
sourceMap[v.SQLName()] = v
|
|
}
|
|
for _, v := range target {
|
|
targetMap[v.SQLName()] = v
|
|
}
|
|
|
|
// Find missing and modified views
|
|
for _, name := range sortedKeys(sourceMap) {
|
|
srcView := sourceMap[name]
|
|
if tgtView, exists := targetMap[name]; !exists {
|
|
diff.Missing = append(diff.Missing, srcView)
|
|
} else {
|
|
if changes := compareViewDetails(srcView, tgtView); len(changes) > 0 {
|
|
diff.Modified = append(diff.Modified, &ViewChange{
|
|
Name: name,
|
|
Source: srcView,
|
|
Target: tgtView,
|
|
Changes: changes,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find extra views
|
|
for _, name := range sortedKeys(targetMap) {
|
|
tgtView := targetMap[name]
|
|
if _, exists := sourceMap[name]; !exists {
|
|
diff.Extra = append(diff.Extra, tgtView)
|
|
}
|
|
}
|
|
|
|
return diff
|
|
}
|
|
|
|
func compareViewDetails(source, target *models.View) map[string]any {
|
|
changes := make(map[string]any)
|
|
|
|
if source.Definition != target.Definition {
|
|
changes["definition"] = map[string]string{"source": source.Definition, "target": target.Definition}
|
|
}
|
|
|
|
return changes
|
|
}
|
|
|
|
func compareSequences(source, target []*models.Sequence) *SequenceDiff {
|
|
diff := &SequenceDiff{
|
|
Missing: make([]*models.Sequence, 0),
|
|
Extra: make([]*models.Sequence, 0),
|
|
Modified: make([]*SequenceChange, 0),
|
|
}
|
|
|
|
sourceMap := make(map[string]*models.Sequence)
|
|
targetMap := make(map[string]*models.Sequence)
|
|
|
|
for _, s := range source {
|
|
sourceMap[s.SQLName()] = s
|
|
}
|
|
for _, s := range target {
|
|
targetMap[s.SQLName()] = s
|
|
}
|
|
|
|
// Find missing and modified sequences
|
|
for _, name := range sortedKeys(sourceMap) {
|
|
srcSeq := sourceMap[name]
|
|
if tgtSeq, exists := targetMap[name]; !exists {
|
|
diff.Missing = append(diff.Missing, srcSeq)
|
|
} else {
|
|
if changes := compareSequenceDetails(srcSeq, tgtSeq); len(changes) > 0 {
|
|
diff.Modified = append(diff.Modified, &SequenceChange{
|
|
Name: name,
|
|
Source: srcSeq,
|
|
Target: tgtSeq,
|
|
Changes: changes,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find extra sequences
|
|
for _, name := range sortedKeys(targetMap) {
|
|
tgtSeq := targetMap[name]
|
|
if _, exists := sourceMap[name]; !exists {
|
|
diff.Extra = append(diff.Extra, tgtSeq)
|
|
}
|
|
}
|
|
|
|
return diff
|
|
}
|
|
|
|
func compareSequenceDetails(source, target *models.Sequence) map[string]any {
|
|
changes := make(map[string]any)
|
|
|
|
if source.StartValue != target.StartValue {
|
|
changes["start_value"] = map[string]int64{"source": source.StartValue, "target": target.StartValue}
|
|
}
|
|
if source.IncrementBy != target.IncrementBy {
|
|
changes["increment_by"] = map[string]int64{"source": source.IncrementBy, "target": target.IncrementBy}
|
|
}
|
|
if source.MinValue != target.MinValue {
|
|
changes["min_value"] = map[string]int64{"source": source.MinValue, "target": target.MinValue}
|
|
}
|
|
if source.MaxValue != target.MaxValue {
|
|
changes["max_value"] = map[string]int64{"source": source.MaxValue, "target": target.MaxValue}
|
|
}
|
|
if source.Cycle != target.Cycle {
|
|
changes["cycle"] = map[string]bool{"source": source.Cycle, "target": target.Cycle}
|
|
}
|
|
|
|
return changes
|
|
}
|
|
|
|
// Helper function to check if a diff is empty
|
|
func isEmpty(v any) bool {
|
|
switch d := v.(type) {
|
|
case *TableDiff:
|
|
return len(d.Missing) == 0 && len(d.Extra) == 0 && len(d.Modified) == 0
|
|
case *ColumnDiff:
|
|
return len(d.Missing) == 0 && len(d.Extra) == 0 && len(d.Modified) == 0
|
|
case *IndexDiff:
|
|
return len(d.Missing) == 0 && len(d.Extra) == 0 && len(d.Modified) == 0
|
|
case *ConstraintDiff:
|
|
return len(d.Missing) == 0 && len(d.Extra) == 0 && len(d.Modified) == 0
|
|
case *RelationshipDiff:
|
|
return len(d.Missing) == 0 && len(d.Extra) == 0 && len(d.Modified) == 0
|
|
case *ViewDiff:
|
|
return len(d.Missing) == 0 && len(d.Extra) == 0 && len(d.Modified) == 0
|
|
case *SequenceDiff:
|
|
return len(d.Missing) == 0 && len(d.Extra) == 0 && len(d.Modified) == 0
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// ComputeSummary generates a summary with counts from a DiffResult
|
|
func ComputeSummary(result *DiffResult) *Summary {
|
|
summary := &Summary{}
|
|
|
|
if result.Schemas != nil {
|
|
summary.Schemas = SchemaSummary{
|
|
Missing: len(result.Schemas.Missing),
|
|
Extra: len(result.Schemas.Extra),
|
|
Modified: len(result.Schemas.Modified),
|
|
}
|
|
|
|
// Aggregate table/column/index/constraint counts
|
|
for _, schemaChange := range result.Schemas.Modified {
|
|
if schemaChange.Tables != nil {
|
|
summary.Tables.Missing += len(schemaChange.Tables.Missing)
|
|
summary.Tables.Extra += len(schemaChange.Tables.Extra)
|
|
summary.Tables.Modified += len(schemaChange.Tables.Modified)
|
|
|
|
for _, tableChange := range schemaChange.Tables.Modified {
|
|
if tableChange.Columns != nil {
|
|
summary.Columns.Missing += len(tableChange.Columns.Missing)
|
|
summary.Columns.Extra += len(tableChange.Columns.Extra)
|
|
summary.Columns.Modified += len(tableChange.Columns.Modified)
|
|
}
|
|
if tableChange.Indexes != nil {
|
|
summary.Indexes.Missing += len(tableChange.Indexes.Missing)
|
|
summary.Indexes.Extra += len(tableChange.Indexes.Extra)
|
|
summary.Indexes.Modified += len(tableChange.Indexes.Modified)
|
|
}
|
|
if tableChange.Constraints != nil {
|
|
summary.Constraints.Missing += len(tableChange.Constraints.Missing)
|
|
summary.Constraints.Extra += len(tableChange.Constraints.Extra)
|
|
summary.Constraints.Modified += len(tableChange.Constraints.Modified)
|
|
}
|
|
if tableChange.Relationships != nil {
|
|
summary.Relationships.Missing += len(tableChange.Relationships.Missing)
|
|
summary.Relationships.Extra += len(tableChange.Relationships.Extra)
|
|
summary.Relationships.Modified += len(tableChange.Relationships.Modified)
|
|
}
|
|
}
|
|
}
|
|
if schemaChange.Views != nil {
|
|
summary.Views.Missing += len(schemaChange.Views.Missing)
|
|
summary.Views.Extra += len(schemaChange.Views.Extra)
|
|
summary.Views.Modified += len(schemaChange.Views.Modified)
|
|
}
|
|
if schemaChange.Sequences != nil {
|
|
summary.Sequences.Missing += len(schemaChange.Sequences.Missing)
|
|
summary.Sequences.Extra += len(schemaChange.Sequences.Extra)
|
|
summary.Sequences.Modified += len(schemaChange.Sequences.Modified)
|
|
}
|
|
}
|
|
}
|
|
|
|
return summary
|
|
}
|