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

Simplify dynvar: replace variables in a single pass #2173

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
81 changes: 13 additions & 68 deletions libs/dyn/dynvar/resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@ import (
"errors"
"fmt"
"slices"
"sort"
"strings"

"github.com/databricks/cli/libs/dyn"
"golang.org/x/exp/maps"
)

// Resolve resolves variable references in the given input value using the provided lookup function.
Expand Down Expand Up @@ -47,72 +45,33 @@ type resolver struct {
in dyn.Value
fn Lookup

refs map[string]ref
resolved map[string]dyn.Value

// Memoization for lookups.
lookups map[string]lookupResult
}

func (r resolver) run() (out dyn.Value, err error) {
err = r.collectVariableReferences()
if err != nil {
return dyn.InvalidValue, err
}

err = r.resolveVariableReferences()
if err != nil {
return dyn.InvalidValue, err
}

out, err = r.replaceVariableReferences()
if err != nil {
return dyn.InvalidValue, err
}

return out, nil
}
r.lookups = make(map[string]lookupResult, 8)

func (r *resolver) collectVariableReferences() (err error) {
r.refs = make(map[string]ref)

// First walk the input to gather all values with a variable reference.
_, err = dyn.Walk(r.in, func(p dyn.Path, v dyn.Value) (dyn.Value, error) {
out, err = dyn.Walk(r.in, func(p dyn.Path, v dyn.Value) (dyn.Value, error) {
ref, ok := newRef(v)
if !ok {
// Skip values without variable references.
return v, nil
}

r.refs[p.String()] = ref
return v, nil
})

return err
}

func (r *resolver) resolveVariableReferences() (err error) {
// Initialize cache for lookups.
r.lookups = make(map[string]lookupResult)

// Initialize cache for resolved variable references.
r.resolved = make(map[string]dyn.Value)

// Resolve each variable reference (in order).
// We sort the keys here to ensure that we always resolve the same variable reference first.
// This is done such that the cycle detection error is deterministic. If we did not do this,
// we could enter the cycle at any point in the cycle and return varying errors.
keys := maps.Keys(r.refs)
sort.Strings(keys)
for _, key := range keys {
v, err := r.resolveRef(r.refs[key], []string{key})
if err != nil {
return err
rVal, rErr := r.resolveRef(ref, []string{p.String()})
if rErr == nil {
// rVal could a be a complex variable. While it would be good to process it as well,
// doing so might result in continuously adding work in front and never finishing.
// acceptance/bundle/variables/complex-cycle catches this case.
// Note, this means we will keep parts of the graph unresolved and require multiple
// passes (as we do currently).
rErr = dyn.ErrSkip
}
r.resolved[key] = v
}
return rVal, rErr
})

return nil
return out, err
}

func (r *resolver) resolveRef(ref ref, seen []string) (dyn.Value, error) {
Expand Down Expand Up @@ -215,17 +174,3 @@ func (r *resolver) resolveKey(key string, seen []string) (dyn.Value, error) {
r.lookups[key] = lookupResult{v: v, err: err}
return v, err
}

func (r *resolver) replaceVariableReferences() (dyn.Value, error) {
// Walk the input and replace all variable references.
return dyn.Walk(r.in, func(p dyn.Path, v dyn.Value) (dyn.Value, error) {
nv, ok := r.resolved[p.String()]
if !ok {
// No variable reference; return the original value.
return v, nil
}

// We have a variable reference; return the resolved value.
return nv, nil
})
}
Loading