Skip to content

Commit

Permalink
Add merge.Override transform
Browse files Browse the repository at this point in the history
  • Loading branch information
kanterov committed May 16, 2024
1 parent a393c87 commit 602b86a
Show file tree
Hide file tree
Showing 2 changed files with 632 additions and 0 deletions.
198 changes: 198 additions & 0 deletions libs/dyn/merge/override.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package merge

import (
"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.
//
// '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)
}

// 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 == dyn.NilValue && right == dyn.NilValue {
return dyn.NilValue, nil
}

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.Location()), 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.Location()), 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)
}
}

return dyn.InvalidValue, fmt.Errorf("unexpected kind %s", left.Kind())
}

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 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 err != nil {
return nil, err
}
}
}

return values, nil
}
Loading

0 comments on commit 602b86a

Please sign in to comment.