Index inheritance history lookups
This commit is contained in:
@@ -351,7 +351,7 @@ Roadmap candidate 7. Owners: engine `history.go`, `load.go`, `load_result.go`.
|
|||||||
Validate: `go test ./internal/engine -run 'Resolution|History|Explain|Ownership'`,
|
Validate: `go test ./internal/engine -run 'Resolution|History|Explain|Ownership'`,
|
||||||
engine race tests, and the everyday check. Full replay semantics stay intact.
|
engine race tests, and the everyday check. Full replay semantics stay intact.
|
||||||
|
|
||||||
## Stage 14: Index relevant inheritance history operations
|
## Stage 14: Index relevant inheritance history operations — Complete
|
||||||
|
|
||||||
Roadmap candidate 7. Owners: engine metadata, metadata cloning, inheritance history.
|
Roadmap candidate 7. Owners: engine metadata, metadata cloning, inheritance history.
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,57 @@
|
|||||||
package engine
|
package engine
|
||||||
|
|
||||||
import "gitea.maximumdirect.net/eric/ConfigKit/internal/document"
|
import (
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"gitea.maximumdirect.net/eric/ConfigKit/internal/document"
|
||||||
|
)
|
||||||
|
|
||||||
|
type inheritanceHistoryIndex struct {
|
||||||
|
paths pathPrefixIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
func newInheritanceHistoryIndex(operations []inheritanceOperation) *inheritanceHistoryIndex {
|
||||||
|
if len(operations) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
index := &inheritanceHistoryIndex{}
|
||||||
|
for operationID, operation := range operations {
|
||||||
|
if operation.control != nil {
|
||||||
|
index.paths.insert(operation.control.path.String(), operationID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if operation.transfer != nil {
|
||||||
|
index.paths.insert(operation.transfer.to.String(), operationID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
|
||||||
|
func (index *inheritanceHistoryIndex) matchingOperations(path Path, operations []inheritanceOperation) []inheritanceOperation {
|
||||||
|
if index == nil {
|
||||||
|
return operations
|
||||||
|
}
|
||||||
|
var ids []int
|
||||||
|
seen := make(map[int]struct{})
|
||||||
|
collect := func(id int) {
|
||||||
|
if _, found := seen[id]; found {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
seen[id] = struct{}{}
|
||||||
|
ids = append(ids, id)
|
||||||
|
}
|
||||||
|
pointer := path.String()
|
||||||
|
index.paths.visitAncestors(pointer, collect)
|
||||||
|
index.paths.visitDescendants(pointer, collect)
|
||||||
|
sort.Ints(ids)
|
||||||
|
matching := make([]inheritanceOperation, 0, len(ids))
|
||||||
|
for _, id := range ids {
|
||||||
|
if id >= 0 && id < len(operations) {
|
||||||
|
matching = append(matching, operations[id])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matching
|
||||||
|
}
|
||||||
|
|
||||||
// Retain source history at original paths. Structural operations are held once
|
// Retain source history at original paths. Structural operations are held once
|
||||||
// per relationship and projected onto a requested destination path only when
|
// per relationship and projected onto a requested destination path only when
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package engine
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -311,6 +312,51 @@ func TestInheritanceHistoryRetainsControlsAndDisplacedPaths(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIndexedInheritanceHistoryMatchesOperationScan(t *testing.T) {
|
||||||
|
inspection, _ := inheritanceInspectionForTest(t,
|
||||||
|
parseCompositionDocument(t, "source-1", "parent: {branch: {detail: inherited, retained: present}}\n"),
|
||||||
|
parseCompositionDocument(t, "source-2", "child: {extends: parent, branch: replaced}\n"),
|
||||||
|
parseCompositionDocument(t, "source-3", "sibling: {extends: parent}\n"))
|
||||||
|
metadata := inspection.Metadata()
|
||||||
|
if len(metadata.inheritance) < 4 {
|
||||||
|
t.Fatalf("inheritance operations = %d, want multiple control and transfer scopes", len(metadata.inheritance))
|
||||||
|
}
|
||||||
|
queries := []Path{
|
||||||
|
Keys("child"),
|
||||||
|
Keys("child", inheritanceKey),
|
||||||
|
Keys("child", "branch", "detail"),
|
||||||
|
Keys("child", "branch"),
|
||||||
|
Keys("sibling", "branch", "detail"),
|
||||||
|
Path{},
|
||||||
|
}
|
||||||
|
for _, path := range queries {
|
||||||
|
if !metadata.hasHistoricalPath(path) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
stored := metadata.history[path.String()]
|
||||||
|
want := historyWithInheritance(path, stored, metadata.inheritance)
|
||||||
|
if got := metadata.History(path); !reflect.DeepEqual(got, want) {
|
||||||
|
t.Errorf("indexed history for %s differs from operation scan:\n got: %#v\nwant: %#v", path, got, want)
|
||||||
|
}
|
||||||
|
unprepared := Metadata{history: metadata.history, historicalPaths: metadata.historicalPaths, inheritance: metadata.inheritance}
|
||||||
|
if got := unprepared.History(path); !reflect.DeepEqual(got, want) {
|
||||||
|
t.Errorf("unprepared history for %s differs from operation scan:\n got: %#v\nwant: %#v", path, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removed := Keys("child", "branch", "detail")
|
||||||
|
first := metadata.History(removed)
|
||||||
|
if len(first) == 0 || len(first[0].Path.segments) == 0 {
|
||||||
|
t.Fatalf("removed-path history = %#v", first)
|
||||||
|
}
|
||||||
|
first[0].Path.segments[0].key = "mutated"
|
||||||
|
second := cloneMetadata(metadata).History(removed)
|
||||||
|
want := historyWithInheritance(removed, metadata.history[removed.String()], metadata.inheritance)
|
||||||
|
if !reflect.DeepEqual(second, want) {
|
||||||
|
t.Fatalf("returned history mutation affected a repeated or cloned query:\n got: %#v\nwant: %#v", second, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestInheritanceHistoryPrecedesApplicationResolution(t *testing.T) {
|
func TestInheritanceHistoryPrecedesApplicationResolution(t *testing.T) {
|
||||||
inspection, _ := inheritanceInspectionForTest(t,
|
inspection, _ := inheritanceInspectionForTest(t,
|
||||||
parseCompositionDocument(t, "source-1", "parent: {value: original}\nchild: {extends: parent}\n"))
|
parseCompositionDocument(t, "source-1", "parent: {value: original}\nchild: {extends: parent}\n"))
|
||||||
|
|||||||
@@ -254,9 +254,10 @@ func cloneMetadata(metadata Metadata) Metadata {
|
|||||||
return Metadata{}
|
return Metadata{}
|
||||||
}
|
}
|
||||||
cloned := Metadata{
|
cloned := Metadata{
|
||||||
fields: make(map[string]FieldInfo, len(metadata.fields)),
|
fields: make(map[string]FieldInfo, len(metadata.fields)),
|
||||||
history: make(map[string][]Change, len(metadata.history)),
|
history: make(map[string][]Change, len(metadata.history)),
|
||||||
inheritance: append([]inheritanceOperation(nil), metadata.inheritance...),
|
inheritance: append([]inheritanceOperation(nil), metadata.inheritance...),
|
||||||
|
inheritanceIndex: metadata.inheritanceIndex,
|
||||||
}
|
}
|
||||||
cloned.effectivePaths = make(map[string]Path, len(metadata.effectivePaths))
|
cloned.effectivePaths = make(map[string]Path, len(metadata.effectivePaths))
|
||||||
for pointer, path := range metadata.effectivePaths {
|
for pointer, path := range metadata.effectivePaths {
|
||||||
|
|||||||
@@ -9,11 +9,12 @@ import (
|
|||||||
// Metadata provides detached effective presence/origin and chronological history
|
// Metadata provides detached effective presence/origin and chronological history
|
||||||
// queries for a load. Its zero value contains no paths.
|
// queries for a load. Its zero value contains no paths.
|
||||||
type Metadata struct {
|
type Metadata struct {
|
||||||
effectivePaths map[string]Path
|
effectivePaths map[string]Path
|
||||||
historicalPaths map[string][]Path
|
historicalPaths map[string][]Path
|
||||||
fields map[string]FieldInfo
|
fields map[string]FieldInfo
|
||||||
history map[string][]Change
|
history map[string][]Change
|
||||||
inheritance []inheritanceOperation
|
inheritance []inheritanceOperation
|
||||||
|
inheritanceIndex *inheritanceHistoryIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lookup returns information for an effective path. Unknown and invalid paths
|
// Lookup returns information for an effective path. Unknown and invalid paths
|
||||||
@@ -57,7 +58,11 @@ func (m Metadata) History(path Path) []Change {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
history := m.history[path.String()]
|
history := m.history[path.String()]
|
||||||
return historyWithInheritance(path, history, m.inheritance)
|
operations := m.inheritance
|
||||||
|
if m.inheritanceIndex != nil {
|
||||||
|
operations = m.inheritanceIndex.matchingOperations(path, m.inheritance)
|
||||||
|
}
|
||||||
|
return historyWithInheritance(path, history, operations)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HistoricalPaths returns the paths observed during loading, including effective
|
// HistoricalPaths returns the paths observed during loading, including effective
|
||||||
@@ -161,6 +166,7 @@ func finalizeMetadataPaths(m *Metadata, final *document.Node, applications []his
|
|||||||
walkDocument(operation.effective, operation.transfer.to, add)
|
walkDocument(operation.effective, operation.transfer.to, add)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
m.inheritanceIndex = newInheritanceHistoryIndex(m.inheritance)
|
||||||
}
|
}
|
||||||
|
|
||||||
// effectivePathIndex records document-derived paths with their contextual segment
|
// effectivePathIndex records document-derived paths with their contextual segment
|
||||||
|
|||||||
Reference in New Issue
Block a user