mirror of https://github.com/databricks/cli.git
212 lines
5.8 KiB
Go
212 lines
5.8 KiB
Go
package merge
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/databricks/cli/libs/dyn"
|
|
)
|
|
|
|
// OverrideVisitor is visiting the changes during the override process
|
|
// and allows to control what changes are allowed, or update the effective
|
|
// value.
|
|
//
|
|
// For instance, it can disallow changes outside the specific path(s), or update
|
|
// the location of the effective value.
|
|
//
|
|
// Values returned by 'VisitInsert' and 'VisitUpdate' are used as the final value
|
|
// of the node. 'VisitDelete' can return ErrOverrideUndoDelete to undo delete.
|
|
//
|
|
// 'VisitDelete' is called when a value is removed from mapping or sequence
|
|
// 'VisitInsert' is called when a new value is added to mapping or sequence
|
|
// 'VisitUpdate' is called when a leaf value is updated
|
|
type OverrideVisitor struct {
|
|
VisitDelete func(valuePath dyn.Path, left dyn.Value) error
|
|
VisitInsert func(valuePath dyn.Path, right dyn.Value) (dyn.Value, error)
|
|
VisitUpdate func(valuePath dyn.Path, left dyn.Value, right dyn.Value) (dyn.Value, error)
|
|
}
|
|
|
|
var ErrOverrideUndoDelete = errors.New("undo delete operation")
|
|
|
|
// Override overrides value 'leftRoot' with 'rightRoot', keeping 'location' if values
|
|
// haven't changed. Preserving 'location' is important to preserve the original source of the value
|
|
// for error reporting.
|
|
func Override(leftRoot dyn.Value, rightRoot dyn.Value, visitor OverrideVisitor) (dyn.Value, error) {
|
|
return override(dyn.EmptyPath, leftRoot, rightRoot, visitor)
|
|
}
|
|
|
|
func override(basePath dyn.Path, left dyn.Value, right dyn.Value, visitor OverrideVisitor) (dyn.Value, error) {
|
|
if left.Kind() != right.Kind() {
|
|
return visitor.VisitUpdate(basePath, left, right)
|
|
}
|
|
|
|
// NB: we only call 'VisitUpdate' on leaf values, and for sequences and mappings
|
|
// we don't know if value was updated or not
|
|
|
|
switch left.Kind() {
|
|
case dyn.KindMap:
|
|
merged, err := overrideMapping(basePath, left.MustMap(), right.MustMap(), visitor)
|
|
|
|
if err != nil {
|
|
return dyn.InvalidValue, err
|
|
}
|
|
|
|
return dyn.NewValue(merged, left.Locations()), nil
|
|
|
|
case dyn.KindSequence:
|
|
// some sequences are keyed, and we can detect which elements are added/removed/updated,
|
|
// but we don't have this information
|
|
merged, err := overrideSequence(basePath, left.MustSequence(), right.MustSequence(), visitor)
|
|
|
|
if err != nil {
|
|
return dyn.InvalidValue, err
|
|
}
|
|
|
|
return dyn.NewValue(merged, left.Locations()), nil
|
|
|
|
case dyn.KindString:
|
|
if left.MustString() == right.MustString() {
|
|
return left, nil
|
|
} else {
|
|
return visitor.VisitUpdate(basePath, left, right)
|
|
}
|
|
|
|
case dyn.KindFloat:
|
|
// TODO consider comparison with epsilon if normalization doesn't help, where do we use floats?
|
|
|
|
if left.MustFloat() == right.MustFloat() {
|
|
return left, nil
|
|
} else {
|
|
return visitor.VisitUpdate(basePath, left, right)
|
|
}
|
|
|
|
case dyn.KindBool:
|
|
if left.MustBool() == right.MustBool() {
|
|
return left, nil
|
|
} else {
|
|
return visitor.VisitUpdate(basePath, left, right)
|
|
}
|
|
|
|
case dyn.KindTime:
|
|
if left.MustTime() == right.MustTime() {
|
|
return left, nil
|
|
} else {
|
|
return visitor.VisitUpdate(basePath, left, right)
|
|
}
|
|
|
|
case dyn.KindInt:
|
|
if left.MustInt() == right.MustInt() {
|
|
return left, nil
|
|
} else {
|
|
return visitor.VisitUpdate(basePath, left, right)
|
|
}
|
|
case dyn.KindNil:
|
|
return left, nil
|
|
}
|
|
|
|
return dyn.InvalidValue, fmt.Errorf("unexpected kind %s at %s", left.Kind(), basePath.String())
|
|
}
|
|
|
|
func overrideMapping(basePath dyn.Path, leftMapping dyn.Mapping, rightMapping dyn.Mapping, visitor OverrideVisitor) (dyn.Mapping, error) {
|
|
out := dyn.NewMapping()
|
|
|
|
for _, leftPair := range leftMapping.Pairs() {
|
|
// detect if key was removed
|
|
if _, ok := rightMapping.GetPair(leftPair.Key); !ok {
|
|
path := basePath.Append(dyn.Key(leftPair.Key.MustString()))
|
|
|
|
err := visitor.VisitDelete(path, leftPair.Value)
|
|
|
|
// if 'delete' was undone, add it back
|
|
if errors.Is(err, ErrOverrideUndoDelete) {
|
|
err := out.Set(leftPair.Key, leftPair.Value)
|
|
if err != nil {
|
|
return dyn.NewMapping(), err
|
|
}
|
|
} else if err != nil {
|
|
return dyn.NewMapping(), err
|
|
}
|
|
}
|
|
}
|
|
|
|
// iterating only right mapping will remove keys not present anymore
|
|
// and insert new keys
|
|
|
|
for _, rightPair := range rightMapping.Pairs() {
|
|
if leftPair, ok := leftMapping.GetPair(rightPair.Key); ok {
|
|
path := basePath.Append(dyn.Key(rightPair.Key.MustString()))
|
|
newValue, err := override(path, leftPair.Value, rightPair.Value, visitor)
|
|
|
|
if err != nil {
|
|
return dyn.NewMapping(), err
|
|
}
|
|
|
|
// key was there before, so keep its location
|
|
err = out.Set(leftPair.Key, newValue)
|
|
|
|
if err != nil {
|
|
return dyn.NewMapping(), err
|
|
}
|
|
} else {
|
|
path := basePath.Append(dyn.Key(rightPair.Key.MustString()))
|
|
|
|
newValue, err := visitor.VisitInsert(path, rightPair.Value)
|
|
|
|
if err != nil {
|
|
return dyn.NewMapping(), err
|
|
}
|
|
|
|
err = out.Set(rightPair.Key, newValue)
|
|
|
|
if err != nil {
|
|
return dyn.NewMapping(), err
|
|
}
|
|
}
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
func overrideSequence(basePath dyn.Path, left []dyn.Value, right []dyn.Value, visitor OverrideVisitor) ([]dyn.Value, error) {
|
|
minLen := min(len(left), len(right))
|
|
var values []dyn.Value
|
|
|
|
for i := 0; i < minLen; i++ {
|
|
path := basePath.Append(dyn.Index(i))
|
|
merged, err := override(path, left[i], right[i], visitor)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
values = append(values, merged)
|
|
}
|
|
|
|
if len(right) > len(left) {
|
|
for i := minLen; i < len(right); i++ {
|
|
path := basePath.Append(dyn.Index(i))
|
|
newValue, err := visitor.VisitInsert(path, right[i])
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
values = append(values, newValue)
|
|
}
|
|
} else if len(left) > len(right) {
|
|
for i := minLen; i < len(left); i++ {
|
|
path := basePath.Append(dyn.Index(i))
|
|
err := visitor.VisitDelete(path, left[i])
|
|
|
|
// if 'delete' was undone, add it back
|
|
if errors.Is(err, ErrOverrideUndoDelete) {
|
|
values = append(values, left[i])
|
|
} else if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
|
|
return values, nil
|
|
}
|