-
Notifications
You must be signed in to change notification settings - Fork 60
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add merge.Override
transform
#1428
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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) | ||
kanterov marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
// 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing call to the update visitor? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is very intentional, I can add a comment. We don't know if the value was changed or not. That's why we call "update" only on leafs |
||
|
||
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 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can allow to "undelete" value here, but there is no use-case for it right now.