Reuse document indexes for collection policy checks

This commit is contained in:
2026-10-24 14:17:09 +00:00
parent a0e5693d07
commit ea46001f78
4 changed files with 135 additions and 8 deletions

View File

@@ -0,0 +1,98 @@
package engine
import (
"fmt"
"strings"
"testing"
"gitea.maximumdirect.net/eric/ConfigKit/internal/document"
)
func TestOverlayBroadCollectionPoliciesAndRetainedDeclarations(t *testing.T) {
var baseBody, overlayBody strings.Builder
basePolicies := collectionPolicyScope{}
var declarations []sequenceDeclarationSpec
for index := range 64 {
name := fmt.Sprintf("group/%03d~", index)
fmt.Fprintf(&baseBody, "%s: {items: [{id: base}], retained: [{id: retained}], scalar: pending}\n", name)
fmt.Fprintf(&overlayBody, "%s: {items: [{id: overlay}], added: true}\n", name)
basePolicies.set(Keys(name, "items"), collectionPolicy{
identityFields: []string{"id"}, identityPresent: true,
explicitMode: sequenceModeAppend, explicitModePresent: true,
})
for _, key := range []string{"retained", "missing", "scalar"} {
declarations = append(declarations, sequenceDeclarationSpec{
destination: Keys(name, key), identityFields: []string{"id"}, identityPresent: true,
mode: sequenceModeAppend, modePresent: true,
})
}
}
base, diagnostics := composeDocuments(parseCompositionDocument(t, "base", baseBody.String()))
if len(diagnostics) != 0 {
t.Fatal(diagnostics)
}
base.collectionPolicies = basePolicies
assembly := newAssemblyAccumulator(base)
overlay := parseCompositionDocument(t, "overlay", overlayBody.String())
diagnostics = assembly.applyOverlayWithCollectionPolicy(overlay, nil, collectionPolicyScope{}, nil, LayerOverlay, sequencePolicy{}, declarations)
if len(diagnostics) != 0 {
t.Fatal(diagnostics)
}
lookup := buildDocumentLookup(assembly.current.root)
for index := range 64 {
name := fmt.Sprintf("group/%03d~", index)
items, found := documentAtPathWithLookup(assembly.current.root, Keys(name, "items"), lookup)
if !found || items.Kind != document.Sequence || len(items.Sequence) != 2 || items.Sequence[0].Mapping[0].Value.Text != "base" || items.Sequence[1].Mapping[0].Value.Text != "overlay" {
t.Fatalf("collection %s did not append in order", name)
}
for _, key := range []string{"retained", "missing", "scalar"} {
policy, found := assembly.current.collectionPolicies.at(Keys(name, key))
if !found || !policy.identityPresent || !policy.explicitModePresent || policy.explicitMode != sequenceModeAppend {
t.Fatalf("retained/pending declaration lost at %s/%s", name, key)
}
}
if _, found := documentAtPathWithLookup(assembly.current.root, Keys(name, "missing"), lookup); found {
t.Fatal("pending declaration created a value")
}
if origin := assembly.current.origins[Keys(name, "items").Index(1).Key("id").String()]; origin.Layer != LayerOverlay {
t.Fatal("appended item lost overlay attribution")
}
}
}
func TestInheritanceBroadCollectionsInOneDefinition(t *testing.T) {
var body strings.Builder
policies := collectionPolicyScope{}
for _, definition := range []string{"base", "child"} {
fmt.Fprintf(&body, "%s:\n", definition)
if definition == "child" {
body.WriteString(" extends: base\n")
}
for index := range 64 {
name := fmt.Sprintf("group/%03d~", index)
fmt.Fprintf(&body, " %s: {items: [{id: %s}]}\n", name, definition)
policy := collectionPolicy{identityFields: []string{"id"}, identityPresent: true}
if definition == "child" {
policy.explicitMode, policy.explicitModePresent = sequenceModeAppend, true
}
policies.set(Keys(definition, name, "items"), policy)
policies.set(Keys(definition, name, "pending"), collectionPolicy{identityFields: []string{"id"}, identityPresent: true})
}
}
root := parseCompositionDocument(t, "wide-collections", body.String())
result, diagnostics := expandInheritanceWithPolicies(root, nil, policies, defaultProjectionLimits())
if len(diagnostics) != 0 {
t.Fatal(diagnostics)
}
lookup := buildDocumentLookup(result.root)
for index := range 64 {
name := fmt.Sprintf("group/%03d~", index)
items, found := documentAtPathWithLookup(result.root, Keys("child", name, "items"), lookup)
if !found || len(items.Sequence) != 2 || items.Sequence[0].Mapping[0].Value.Text != "base" || items.Sequence[1].Mapping[0].Value.Text != "child" {
t.Fatalf("inherited collection %s did not append in order", name)
}
if policy, found := result.collectionPolicies.at(Keys("child", name, "pending")); !found || !policy.identityPresent {
t.Fatalf("pending inherited policy lost at %s", name)
}
}
}

View File

@@ -103,15 +103,19 @@ func mergeInheritanceCollectionPolicies(
for key, entry := range child.collections {
paths[key] = entry.destination.clone()
}
var lookup *documentLookup
if len(paths) != 0 {
lookup = buildDocumentLookup(parentValue, childValue)
}
var diagnostics []Diagnostic
for _, path := range paths {
relative, inside := relativeHistoryPath(parentRootPath, path)
var retained, incoming *document.Node
if inside {
if !replaced.contains(path, false) {
retained, _ = documentAtPath(parentValue, relative)
retained, _ = documentAtPathWithLookup(parentValue, relative, lookup)
}
incoming, _ = documentAtPath(childValue, relative)
incoming, _ = documentAtPathWithLookup(childValue, relative, lookup)
}
resolution, lower, _, active, issues := inheritanceCollectionResolution(path, parent, child, applicationPolicy, incoming)
if len(issues) != 0 {

View File

@@ -138,8 +138,12 @@ func mergeOverlayMappingsWithCollectionPolicy(
for key := range declarationsByPath {
registered[key] = true
}
var overlayLookup *documentLookup
if len(result.collectionPolicies.collections) != 0 {
overlayLookup = buildDocumentLookup(overlay)
}
for key := range result.collectionPolicies.collections {
if node, found := documentAtPath(overlay, result.collectionPolicies.collections[key].destination); found && node != nil {
if node, found := documentAtPathWithLookup(overlay, result.collectionPolicies.collections[key].destination, overlayLookup); found && node != nil {
registered[key] = true
registeredPaths[key] = result.collectionPolicies.collections[key].destination.clone()
}
@@ -210,6 +214,9 @@ func mergeOverlayMappingsWithCollectionPolicy(
return nil, canonicalDiagnostics(combinationDiagnostics)
}
// Values are now complete. Index lazily for declaration-only checks;
// this lookup is never retained across the value-changing merge above.
var retainedLookup *documentLookup
for key, grouped := range declarationsByPath {
if policyValues[key] {
continue
@@ -226,7 +233,10 @@ func mergeOverlayMappingsWithCollectionPolicy(
if found {
lowerPolicy = &lower
}
retained, _ := documentAtPath(assembly.current.root, path)
if retainedLookup == nil {
retainedLookup = buildDocumentLookup(assembly.current.root)
}
retained, _ := documentAtPathWithLookup(assembly.current.root, path, retainedLookup)
if retained != nil && retained.Kind == document.Sequence && len(current.effectiveIdentityFields) != 0 {
if _, issues := validateSequenceIdentity(retained, path, current.effectiveIdentityFields, sequenceIdentityDiagnosticCodes{invalid: CodeSequenceInvalid, conflict: CodeSequenceConflict}); len(issues) != 0 {
combinationDiagnostics = append(combinationDiagnostics, issues...)