Index resolution metadata updates
This commit is contained in:
@@ -218,7 +218,7 @@ Roadmap candidate 3, first integration. Owner: `internal/engine/resolution.go`.
|
|||||||
Validate: `go test ./internal/engine -run 'Resolution|Resolve|Callback|Path'`
|
Validate: `go test ./internal/engine -run 'Resolution|Resolve|Callback|Path'`
|
||||||
and the everyday check. Snapshot ownership/contents remain unchanged.
|
and the everyday check. Snapshot ownership/contents remain unchanged.
|
||||||
|
|
||||||
## Stage 8: Replace per-delta whole-metadata scans
|
## Stage 8: Replace per-delta whole-metadata scans — Complete
|
||||||
|
|
||||||
Roadmap candidate 3, second integration. Owner: engine `load_metadata.go`.
|
Roadmap candidate 3, second integration. Owner: engine `load_metadata.go`.
|
||||||
|
|
||||||
|
|||||||
@@ -280,8 +280,23 @@ func cloneMetadata(metadata Metadata) Metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func applyResolutionMetadata(metadata *Metadata, resolved resolutionResult) {
|
func applyResolutionMetadata(metadata *Metadata, resolved resolutionResult) {
|
||||||
for _, snapshot := range resolved.snapshots {
|
finalPaths := make(map[string]*document.Node)
|
||||||
pointer := snapshot.change.Path.String()
|
indexDocument(resolved.final, Path{}, finalPaths)
|
||||||
|
snapshotCoverage, snapshotPointers := newResolutionCoverageIndexFromSnapshots(resolved.snapshots)
|
||||||
|
var deltaCoverage pathPrefixIndex
|
||||||
|
snapshotIDs := make([]int, len(resolved.deltas))
|
||||||
|
for id, delta := range resolved.deltas {
|
||||||
|
if delta.path.valid() {
|
||||||
|
deltaCoverage.insert(delta.path.String(), id)
|
||||||
|
}
|
||||||
|
snapshotIDs[id] = -1
|
||||||
|
if snapshotID, ok := snapshotCoverage.deepestCoveringID(delta.path); ok {
|
||||||
|
snapshotIDs[id] = snapshotID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for snapshotID, snapshot := range resolved.snapshots {
|
||||||
|
pointer := snapshotPointers[snapshotID]
|
||||||
change := cloneChange(snapshot.change)
|
change := cloneChange(snapshot.change)
|
||||||
if info, ok := metadata.fields[pointer]; ok {
|
if info, ok := metadata.fields[pointer]; ok {
|
||||||
change.Previous = []Origin{cloneOrigin(info.Origin)}
|
change.Previous = []Origin{cloneOrigin(info.Origin)}
|
||||||
@@ -290,52 +305,40 @@ func applyResolutionMetadata(metadata *Metadata, resolved resolutionResult) {
|
|||||||
}
|
}
|
||||||
metadata.history[pointer] = append(metadata.history[pointer], change)
|
metadata.history[pointer] = append(metadata.history[pointer], change)
|
||||||
}
|
}
|
||||||
appendResolutionDescendantHistory(metadata, resolved)
|
|
||||||
for _, delta := range resolved.deltas {
|
appendResolutionDescendantHistory(metadata, resolved.snapshots, snapshotPointers, finalPaths, &deltaCoverage, snapshotIDs)
|
||||||
record := coveringResolutionSnapshot(resolved.snapshots, delta.path)
|
applyResolutionFieldOrigins(metadata.fields, resolved.snapshots, &deltaCoverage, snapshotIDs)
|
||||||
if record == nil {
|
refreshResolutionFields(metadata.fields, finalPaths, resolved.snapshots, snapshotCoverage)
|
||||||
continue
|
|
||||||
}
|
|
||||||
for pointer, info := range metadata.fields {
|
|
||||||
path, err := ParsePath(pointer)
|
|
||||||
if err != nil || !pathContains(delta.path, path) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
info.Origin = cloneOrigin(record.change.Origin)
|
|
||||||
metadata.fields[pointer] = info
|
|
||||||
}
|
|
||||||
}
|
|
||||||
refreshResolutionFields(metadata.fields, resolved.final, resolved.snapshots)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func appendResolutionDescendantHistory(metadata *Metadata, resolved resolutionResult) {
|
func appendResolutionDescendantHistory(metadata *Metadata, snapshots []resolutionSnapshot, snapshotPointers []string, finalPaths map[string]*document.Node, deltaIndex *pathPrefixIndex, snapshotIDs []int) {
|
||||||
finalPaths := make(map[string]*document.Node)
|
allPointers := make(map[string]struct{}, len(metadata.history)+len(finalPaths))
|
||||||
indexDocument(resolved.final, Path{}, finalPaths)
|
|
||||||
for _, delta := range resolved.deltas {
|
|
||||||
record := coveringResolutionSnapshot(resolved.snapshots, delta.path)
|
|
||||||
if record == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
affected := make(map[string]bool)
|
|
||||||
for pointer := range metadata.history {
|
for pointer := range metadata.history {
|
||||||
if path, err := ParsePath(pointer); err == nil && pathContains(delta.path, path) {
|
if _, err := ParsePath(pointer); err == nil {
|
||||||
affected[pointer] = true
|
allPointers[pointer] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for pointer := range finalPaths {
|
for pointer := range finalPaths {
|
||||||
if path, err := ParsePath(pointer); err == nil && pathContains(delta.path, path) {
|
allPointers[pointer] = struct{}{}
|
||||||
affected[pointer] = true
|
|
||||||
}
|
}
|
||||||
|
buckets := make([][]string, len(snapshotIDs))
|
||||||
|
for pointer := range allPointers {
|
||||||
|
deltaIndex.visitAncestors(pointer, func(deltaID int) {
|
||||||
|
snapshotID := snapshotIDs[deltaID]
|
||||||
|
if snapshotID < 0 || pointer == snapshotPointers[snapshotID] {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
pointers := make([]string, 0, len(affected))
|
buckets[deltaID] = append(buckets[deltaID], pointer)
|
||||||
for pointer := range affected {
|
})
|
||||||
if pointer != record.change.Path.String() {
|
|
||||||
pointers = append(pointers, pointer)
|
|
||||||
}
|
}
|
||||||
|
for deltaID, pointers := range buckets {
|
||||||
|
snapshotID := snapshotIDs[deltaID]
|
||||||
|
if snapshotID < 0 {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
slices.Sort(pointers)
|
slices.Sort(pointers)
|
||||||
for _, pointer := range pointers {
|
for _, pointer := range pointers {
|
||||||
change := cloneChange(record.change)
|
change := cloneChange(snapshots[snapshotID].change)
|
||||||
if info, ok := metadata.fields[pointer]; ok {
|
if info, ok := metadata.fields[pointer]; ok {
|
||||||
change.Previous = []Origin{cloneOrigin(info.Origin)}
|
change.Previous = []Origin{cloneOrigin(info.Origin)}
|
||||||
}
|
}
|
||||||
@@ -344,20 +347,33 @@ func appendResolutionDescendantHistory(metadata *Metadata, resolved resolutionRe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func coveringResolutionSnapshot(snapshots []resolutionSnapshot, path Path) *resolutionSnapshot {
|
func applyResolutionFieldOrigins(fields map[string]FieldInfo, snapshots []resolutionSnapshot, deltaIndex *pathPrefixIndex, snapshotIDs []int) {
|
||||||
var covering *resolutionSnapshot
|
buckets := make([][]string, len(snapshotIDs))
|
||||||
for index := range snapshots {
|
for pointer := range fields {
|
||||||
snapshot := &snapshots[index]
|
if _, err := ParsePath(pointer); err != nil {
|
||||||
if pathContains(snapshot.change.Path, path) && (covering == nil || len(snapshot.change.Path.segments) > len(covering.change.Path.segments)) {
|
continue
|
||||||
covering = snapshot
|
}
|
||||||
|
deltaIndex.visitAncestors(pointer, func(deltaID int) {
|
||||||
|
if snapshotIDs[deltaID] >= 0 {
|
||||||
|
buckets[deltaID] = append(buckets[deltaID], pointer)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for deltaID, pointers := range buckets {
|
||||||
|
snapshotID := snapshotIDs[deltaID]
|
||||||
|
if snapshotID < 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
origin := snapshots[snapshotID].change.Origin
|
||||||
|
for _, pointer := range pointers {
|
||||||
|
info := fields[pointer]
|
||||||
|
info.Origin = cloneOrigin(origin)
|
||||||
|
fields[pointer] = info
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return covering
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func refreshResolutionFields(fields map[string]FieldInfo, final *document.Node, snapshots []resolutionSnapshot) {
|
func refreshResolutionFields(fields map[string]FieldInfo, finalPaths map[string]*document.Node, snapshots []resolutionSnapshot, snapshotCoverage *resolutionCoverageIndex) {
|
||||||
finalPaths := make(map[string]*document.Node)
|
|
||||||
indexDocument(final, Path{}, finalPaths)
|
|
||||||
for pointer := range fields {
|
for pointer := range fields {
|
||||||
if _, ok := finalPaths[pointer]; !ok {
|
if _, ok := finalPaths[pointer]; !ok {
|
||||||
delete(fields, pointer)
|
delete(fields, pointer)
|
||||||
@@ -370,10 +386,8 @@ func refreshResolutionFields(fields map[string]FieldInfo, final *document.Node,
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
origin := Origin{Layer: LayerResolution}
|
origin := Origin{Layer: LayerResolution}
|
||||||
if path, err := ParsePath(pointer); err == nil {
|
if snapshotID, ok := snapshotCoverage.deepestCoveringPointerID(pointer); ok {
|
||||||
if covering := coveringResolutionSnapshot(snapshots, path); covering != nil {
|
origin = cloneOrigin(snapshots[snapshotID].change.Origin)
|
||||||
origin = cloneOrigin(covering.change.Origin)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
fields[pointer] = FieldInfo{Null: node.Kind == document.Null, Origin: origin}
|
fields[pointer] = FieldInfo{Null: node.Kind == document.Null, Origin: origin}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -361,12 +361,30 @@ func newResolutionCoverageIndexFromRecords(records []*resolutionRecord) *resolut
|
|||||||
return index
|
return index
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newResolutionCoverageIndexFromSnapshots(snapshots []resolutionSnapshot) (*resolutionCoverageIndex, []string) {
|
||||||
|
index := &resolutionCoverageIndex{depths: make([]int, len(snapshots))}
|
||||||
|
pointers := make([]string, len(snapshots))
|
||||||
|
for id, snapshot := range snapshots {
|
||||||
|
path := snapshot.change.Path
|
||||||
|
index.depths[id] = len(path.segments)
|
||||||
|
pointers[id] = path.String()
|
||||||
|
if path.valid() {
|
||||||
|
index.prefix.insert(pointers[id], id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return index, pointers
|
||||||
|
}
|
||||||
|
|
||||||
func (index *resolutionCoverageIndex) deepestCoveringID(path Path) (int, bool) {
|
func (index *resolutionCoverageIndex) deepestCoveringID(path Path) (int, bool) {
|
||||||
if !path.valid() {
|
if !path.valid() {
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
return index.deepestCoveringPointerID(path.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (index *resolutionCoverageIndex) deepestCoveringPointerID(pointer string) (int, bool) {
|
||||||
bestID, bestDepth, found := 0, -1, false
|
bestID, bestDepth, found := 0, -1, false
|
||||||
index.prefix.visitAncestors(path.String(), func(id int) {
|
index.prefix.visitAncestors(pointer, func(id int) {
|
||||||
depth := index.depths[id]
|
depth := index.depths[id]
|
||||||
if !found || depth > bestDepth || (depth == bestDepth && id < bestID) {
|
if !found || depth > bestDepth || (depth == bestDepth && id < bestID) {
|
||||||
bestID, bestDepth, found = id, depth, true
|
bestID, bestDepth, found = id, depth, true
|
||||||
|
|||||||
@@ -71,3 +71,48 @@ func TestDocumentLookupMatchesPathScan(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestApplyResolutionMetadataPreservesOverlappingDeltaOrder(t *testing.T) {
|
||||||
|
oldOrigin := Origin{Layer: LayerBase}
|
||||||
|
parentOrigin := Origin{Layer: LayerResolution, Operation: ChangeNormalization}
|
||||||
|
childOrigin := Origin{Layer: LayerResolution, Operation: ChangeDerivedDefault}
|
||||||
|
leaf := &document.Node{Kind: document.String, Text: "value"}
|
||||||
|
final := &document.Node{Kind: document.Mapping, Mapping: []document.Entry{{
|
||||||
|
Key: "group",
|
||||||
|
Value: &document.Node{Kind: document.Mapping, Mapping: []document.Entry{{
|
||||||
|
Key: "item",
|
||||||
|
Value: &document.Node{Kind: document.Mapping, Mapping: []document.Entry{{Key: "leaf", Value: leaf}}},
|
||||||
|
}}},
|
||||||
|
}}}
|
||||||
|
metadata := &Metadata{
|
||||||
|
fields: map[string]FieldInfo{"/group/item/leaf": {Origin: oldOrigin}},
|
||||||
|
history: make(map[string][]Change),
|
||||||
|
}
|
||||||
|
resolved := resolutionResult{
|
||||||
|
final: final,
|
||||||
|
deltas: []documentDelta{{path: Keys("group")}, {path: Keys("group", "item")}},
|
||||||
|
snapshots: []resolutionSnapshot{
|
||||||
|
{change: Change{Path: Keys("group"), Origin: parentOrigin}},
|
||||||
|
{change: Change{Path: Keys("group", "item"), Origin: childOrigin}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
applyResolutionMetadata(metadata, resolved)
|
||||||
|
|
||||||
|
field := metadata.fields["/group/item/leaf"]
|
||||||
|
if field.Origin.Operation != childOrigin.Operation {
|
||||||
|
t.Fatalf("final leaf origin operation = %q, want %q", field.Origin.Operation, childOrigin.Operation)
|
||||||
|
}
|
||||||
|
changes := metadata.history["/group/item/leaf"]
|
||||||
|
if len(changes) != 2 {
|
||||||
|
t.Fatalf("leaf history has %d entries, want 2: %#v", len(changes), changes)
|
||||||
|
}
|
||||||
|
if changes[0].Origin.Operation != parentOrigin.Operation || changes[1].Origin.Operation != childOrigin.Operation {
|
||||||
|
t.Fatalf("leaf history operations = %q, %q; want %q, %q", changes[0].Origin.Operation, changes[1].Origin.Operation, parentOrigin.Operation, childOrigin.Operation)
|
||||||
|
}
|
||||||
|
for index, change := range changes {
|
||||||
|
if len(change.Previous) != 1 || change.Previous[0].Layer != oldOrigin.Layer {
|
||||||
|
t.Errorf("leaf history[%d] Previous = %#v, want original origin", index, change.Previous)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user