Skip to content
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

Merged
merged 2 commits into from
May 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Contributor Author

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.

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing call to the update visitor?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
}
Loading
Loading