Select history using matched entry positions
This commit is contained in:
@@ -26,21 +26,43 @@ type historySensitivityCandidate struct {
|
||||
classify bool
|
||||
}
|
||||
|
||||
type historyQueryCoordinates struct {
|
||||
final Path
|
||||
entry Path
|
||||
hasEntry bool
|
||||
}
|
||||
|
||||
func (model *historyModel) queryCoordinates(path Path) historyQueryCoordinates {
|
||||
coordinate := historyQueryCoordinates{final: path.clone(), entry: path.clone(), hasEntry: true}
|
||||
if model != nil && model.resolutionPayload != nil {
|
||||
coordinate.entry, coordinate.hasEntry = model.resolutionPayload.correspondence.entryPath(path)
|
||||
}
|
||||
return coordinate
|
||||
}
|
||||
|
||||
func (model *historyModel) selectHistory(path Path) []historySelection {
|
||||
if model == nil || !path.valid() || !model.hasObservedShape(path) {
|
||||
if model == nil || !path.valid() {
|
||||
return nil
|
||||
}
|
||||
coordinate := model.queryCoordinates(path)
|
||||
if !model.hasObservedShape(path) && (!coordinate.hasEntry || !model.hasObservedShape(coordinate.entry)) {
|
||||
return nil
|
||||
}
|
||||
|
||||
candidateSet := make(map[int]struct{})
|
||||
for _, observationID := range model.observationsByPointer[path.String()] {
|
||||
candidateSet[observationID] = struct{}{}
|
||||
collect := func(query Path) {
|
||||
for _, observationID := range model.observationsByPointer[query.String()] {
|
||||
candidateSet[observationID] = struct{}{}
|
||||
}
|
||||
model.affectedPathIndex.visitDescendants(query.String(), func(observationID int) { candidateSet[observationID] = struct{}{} })
|
||||
}
|
||||
collect(path)
|
||||
if coordinate.hasEntry {
|
||||
collect(coordinate.entry)
|
||||
}
|
||||
// Producers register all affected before/after paths. Exact facts cover
|
||||
// ancestor replacements; descendants cover operations within an enclosing
|
||||
// query. Logical operations are still deduplicated by payload below.
|
||||
model.affectedPathIndex.visitDescendants(path.String(), func(observationID int) {
|
||||
candidateSet[observationID] = struct{}{}
|
||||
})
|
||||
candidates := make([]int, 0, len(candidateSet))
|
||||
for observationID := range candidateSet {
|
||||
candidates = append(candidates, observationID)
|
||||
@@ -54,18 +76,30 @@ func (model *historyModel) selectHistory(path Path) []historySelection {
|
||||
if observation.role == historyRoleInventory {
|
||||
continue
|
||||
}
|
||||
query := path
|
||||
if observation.phase != historyPhaseResolution {
|
||||
if !coordinate.hasEntry {
|
||||
continue
|
||||
}
|
||||
query = coordinate.entry
|
||||
} else if observation.beforePresent && !observation.afterPresent {
|
||||
if !coordinate.hasEntry {
|
||||
continue
|
||||
}
|
||||
query = coordinate.entry
|
||||
}
|
||||
reportedPath := observation.reportedPath
|
||||
attributionRef := observation.attributionRef
|
||||
if observation.role == historyRoleContribution {
|
||||
if !historyObservationMatchesQuery(path, observation.path) {
|
||||
if !historyObservationMatchesQuery(query, observation.path) {
|
||||
continue
|
||||
}
|
||||
application, found := model.applicationPayload(observation.payload)
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
if pathContains(observation.path, path) && len(observation.path.segments) < len(path.segments) {
|
||||
local, ok := historyApplicationLocalPath(application, path)
|
||||
if pathContains(observation.path, query) && len(observation.path.segments) < len(query.segments) {
|
||||
local, ok := historyApplicationLocalPath(application, query)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
@@ -73,12 +107,18 @@ func (model *historyModel) selectHistory(path Path) []historySelection {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if operationPath, relevant := applicationOperationPath(application, path, model.documentLookup); relevant {
|
||||
if operationPath, relevant := applicationOperationPath(application, query, model.documentLookup); relevant {
|
||||
reportedPath = operationPath
|
||||
}
|
||||
} else if !historyObservationMatchesQuery(path, observation.path) {
|
||||
} else if !historyObservationMatchesQuery(query, observation.path) {
|
||||
continue
|
||||
}
|
||||
if query.String() != path.String() && pathContains(query, reportedPath) {
|
||||
suffix := reportedPath.segments[len(query.segments):]
|
||||
reportedPath = Path{segments: append(clonePathSegments(path.segments, len(suffix)), suffix...)}
|
||||
} else if query.String() != path.String() && pathContains(reportedPath, query) && len(reportedPath.segments) >= len(query.segments)-1 {
|
||||
reportedPath = path.clone()
|
||||
}
|
||||
selectionIndex, found := selectionByPayload[observation.payload]
|
||||
if !found {
|
||||
selectionIndex = len(selections)
|
||||
|
||||
@@ -262,13 +262,6 @@ func metadataFieldLocation(metadata Metadata, path Path, origin Origin) (documen
|
||||
return location, true
|
||||
}
|
||||
if model := metadata.historyEvidence(); model != nil {
|
||||
if model.resolutionPayload != nil {
|
||||
var matched bool
|
||||
path, matched = model.resolutionPayload.correspondence.entryPath(path)
|
||||
if !matched {
|
||||
return document.Location{}, false
|
||||
}
|
||||
}
|
||||
location, ok := model.latestHistoricalLocation(path)
|
||||
if ok {
|
||||
return document.Location{SourceID: location.SourceID, Line: location.Line, Column: location.Column}, true
|
||||
|
||||
@@ -265,3 +265,48 @@ func TestRegisteredResolutionValidationLocationFollowsMovedRecord(t *testing.T)
|
||||
}
|
||||
t.Fatalf("diagnostics = %#v", failure.Diagnostics())
|
||||
}
|
||||
|
||||
func TestRegisteredResolutionHistoryFollowsMatchedSource(t *testing.T) {
|
||||
type item struct {
|
||||
ID string `yaml:"id"`
|
||||
Value string `yaml:"value"`
|
||||
}
|
||||
type config struct {
|
||||
Items []item `yaml:"items"`
|
||||
}
|
||||
definition := DefinitionWithSequenceIdentityRegistrations(Definition[config]{Resolve: func(value *config, r *Resolution) error {
|
||||
value.Items[0], value.Items[1] = value.Items[1], value.Items[0]
|
||||
value.Items = append(value.Items, item{ID: "new", Value: "added"})
|
||||
r.RecordNormalization(Keys("items"), "reorder and add")
|
||||
return nil
|
||||
}}, []SequenceIdentityRegistrationInput{{ExactPath: pathPointer(Keys("items")), IdentityFields: []string{"id"}}})
|
||||
result, err := testLoad(Bytes("records", []byte("items:\n - id: a\n value: first\n - id: b\n value: second\n")), Options[config]{Definition: definition})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, check := range []struct{ index, line int }{{0, 5}, {1, 3}} {
|
||||
path := Keys("items").Index(check.index).Key("value")
|
||||
if location, ok := result.history.latestHistoricalLocation(path); !ok || location.Line != check.line {
|
||||
t.Errorf("historical location for %s = %#v, %t", path.String(), location, ok)
|
||||
}
|
||||
history := result.Metadata().History(path)
|
||||
found := false
|
||||
for _, change := range history {
|
||||
for _, location := range change.Origin.Locations {
|
||||
if location.Line == check.line {
|
||||
found = true
|
||||
} else if location.Line == 3 || location.Line == 5 {
|
||||
t.Errorf("wrong record in %s: %#v", path.String(), history)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("missing source line %d in %#v", check.line, history)
|
||||
}
|
||||
}
|
||||
for _, change := range result.Metadata().History(Keys("items").Index(2).Key("value")) {
|
||||
if change.Origin.Layer == LayerMain {
|
||||
t.Fatalf("added record inherited input: %#v", change)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user