Build item policies without repeated accumulated copies
This commit is contained in:
@@ -635,7 +635,17 @@ existing inspection API and sensitivity behavior.
|
|||||||
|
|
||||||
## 16. Avoid Deep-Copying Accumulated Item Policies Per Record — P2
|
## 16. Avoid Deep-Copying Accumulated Item Policies Per Record — P2
|
||||||
|
|
||||||
Status: [ ] Open.
|
Status: [x] Complete.
|
||||||
|
|
||||||
|
Completion: item projection now takes one owned policy copy per destination,
|
||||||
|
updates that local state incrementally, and stores it once after projection.
|
||||||
|
Incoming scopes still cross detached ownership boundaries. Inspection confirms
|
||||||
|
that accumulated record scopes are no longer deep-copied per item; no timing
|
||||||
|
threshold or benchmark campaign was needed. Existing stable-index, sparse-range,
|
||||||
|
fallback-offset, and ownership coverage passes, with a new regression for
|
||||||
|
interleaved destinations, distinct record owners, and sibling/cross-load mutation
|
||||||
|
independence. Verified focused item-policy tests, `./scripts/check.sh`, and
|
||||||
|
`go test -race ./...`.
|
||||||
|
|
||||||
Owners: [`projectNestedCollectionPolicies`](../../internal/engine/imports.go)
|
Owners: [`projectNestedCollectionPolicies`](../../internal/engine/imports.go)
|
||||||
and its use of copying `collectionPolicyScope.at` / `set` helpers in
|
and its use of copying `collectionPolicyScope.at` / `set` helpers in
|
||||||
|
|||||||
@@ -960,7 +960,12 @@ func projectNestedCollectionPolicies(composed *composedDocument, scope acquisiti
|
|||||||
if composed == nil {
|
if composed == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
baseOffsets := make(map[string]int)
|
type itemPolicyProjection struct {
|
||||||
|
destination Path
|
||||||
|
policy collectionPolicy
|
||||||
|
base int
|
||||||
|
}
|
||||||
|
projected := make(map[string]*itemPolicyProjection)
|
||||||
var prefixLengths map[string]int
|
var prefixLengths map[string]int
|
||||||
for sourceIndex, contribution := range scope.contributions {
|
for sourceIndex, contribution := range scope.contributions {
|
||||||
if prefixLengths != nil && sourceIndex > 0 {
|
if prefixLengths != nil && sourceIndex > 0 {
|
||||||
@@ -983,17 +988,21 @@ func projectNestedCollectionPolicies(composed *composedDocument, scope acquisiti
|
|||||||
}
|
}
|
||||||
itemIndex = prefixLengths[key]
|
itemIndex = prefixLengths[key]
|
||||||
}
|
}
|
||||||
base, initialized := baseOffsets[key]
|
working, initialized := projected[key]
|
||||||
policy, _ := composed.collectionPolicies.at(contribution.destination)
|
|
||||||
if !initialized {
|
if !initialized {
|
||||||
base = len(policy.recordOrder)
|
// Read one owned copy per destination, then add each incoming
|
||||||
baseOffsets[key] = base
|
// record without recopying earlier records and their scopes.
|
||||||
|
policy, _ := composed.collectionPolicies.at(contribution.destination)
|
||||||
|
working = &itemPolicyProjection{destination: contribution.destination, policy: policy, base: len(policy.recordOrder)}
|
||||||
|
projected[key] = working
|
||||||
}
|
}
|
||||||
policy.setRecordPolicyAt(base+itemIndex, collectionRecordPolicy{
|
working.policy.setRecordPolicyAt(working.base+itemIndex, collectionRecordPolicy{
|
||||||
owner: newCollectionRecordPolicy().owner,
|
owner: newCollectionRecordPolicy().owner,
|
||||||
scope: contribution.nested.collectionPolicies,
|
scope: contribution.nested.collectionPolicies,
|
||||||
})
|
})
|
||||||
composed.collectionPolicies.set(contribution.destination, policy)
|
}
|
||||||
|
for _, working := range projected {
|
||||||
|
composed.collectionPolicies.set(working.destination, working.policy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -229,6 +229,64 @@ func TestItemPolicyProjectionCountsContributionsWithoutLocalPolicy(t *testing.T)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestItemPolicyProjectionDetachesRepeatedScopes(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
local := collectionPolicyScope{}
|
||||||
|
local.set(Keys("steps"), collectionPolicy{identityFields: []string{"id"}, identityPresent: true})
|
||||||
|
scope := acquisitionScope{}
|
||||||
|
ranges := make(map[sequenceRangeKey]sequenceRange)
|
||||||
|
for index, destination := range []string{"items", "other", "items"} {
|
||||||
|
path := Keys(destination)
|
||||||
|
scope.contributions = append(scope.contributions, acquiredContribution{
|
||||||
|
document: parseItemPolicyDocument(t, "item", destination+": [{}]\n"), destination: path,
|
||||||
|
nested: &composedDocument{collectionPolicies: local},
|
||||||
|
})
|
||||||
|
output := 0
|
||||||
|
if index == 2 {
|
||||||
|
output = 1
|
||||||
|
}
|
||||||
|
ranges[sequenceRangeKey{sourceIndex: index, path: path.String()}] = sequenceRange{start: output, end: output + 1}
|
||||||
|
}
|
||||||
|
first, second := &composedDocument{}, &composedDocument{}
|
||||||
|
projectNestedCollectionPolicies(first, scope, ranges)
|
||||||
|
projectNestedCollectionPolicies(second, scope, ranges)
|
||||||
|
policy := first.collectionPolicies.collections[Keys("items").String()].policy
|
||||||
|
if len(policy.recordOrder) != 2 || policy.recordOrder[0] == policy.recordOrder[1] {
|
||||||
|
t.Fatal("separately placed records did not retain distinct owners")
|
||||||
|
}
|
||||||
|
other := second.collectionPolicies.collections[Keys("items").String()].policy
|
||||||
|
if policy.recordOrder[0] == other.recordOrder[0] {
|
||||||
|
t.Fatal("independent projections reused a record owner")
|
||||||
|
}
|
||||||
|
// Mutate stored state directly: a detached query result would conceal an
|
||||||
|
// accidental alias between incrementally constructed record scopes.
|
||||||
|
steps := Keys("steps").String()
|
||||||
|
firstRecord := policy.recordPolicies[policy.recordOrder[0]]
|
||||||
|
firstRecord.collections[steps].policy.identityFields[0] = "changed"
|
||||||
|
for _, check := range []struct {
|
||||||
|
composed *composedDocument
|
||||||
|
path Path
|
||||||
|
index int
|
||||||
|
}{
|
||||||
|
{first, Keys("items"), 1}, {first, Keys("other"), 0},
|
||||||
|
{second, Keys("items"), 0}, {second, Keys("items"), 1}, {second, Keys("other"), 0},
|
||||||
|
} {
|
||||||
|
outer, _ := check.composed.collectionPolicies.at(check.path)
|
||||||
|
record, found := outer.recordPolicyAt(check.index)
|
||||||
|
identity, present := record.scope.at(Keys("steps"))
|
||||||
|
if !found || !present || !reflect.DeepEqual(identity.identityFields, []string{"id"}) {
|
||||||
|
t.Fatalf("mutation leaked into %s item %d: %#v", check.path, check.index, identity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if local.collections[steps].policy.identityFields[0] != "id" {
|
||||||
|
t.Fatal("projected state mutated the incoming scope")
|
||||||
|
}
|
||||||
|
local.collections[steps].policy.identityFields[0] = "source-changed"
|
||||||
|
if other.recordPolicies[other.recordOrder[0]].collections[steps].policy.identityFields[0] != "id" {
|
||||||
|
t.Fatal("incoming scope remained aliased to projected state")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestOverlaySequenceTransfersIncomingItemPolicyOwnership(t *testing.T) {
|
func TestOverlaySequenceTransfersIncomingItemPolicyOwnership(t *testing.T) {
|
||||||
path := Keys("records")
|
path := Keys("records")
|
||||||
registered := mustCompileSequenceIdentityPolicy(t, []sequenceIdentityRegistration{{selector: sequenceIdentitySelector{literalSequenceSelector("records")}, identityFields: []string{"id"}}})
|
registered := mustCompileSequenceIdentityPolicy(t, []sequenceIdentityRegistration{{selector: sequenceIdentitySelector{literalSequenceSelector("records")}, identityFields: []string{"id"}}})
|
||||||
|
|||||||
Reference in New Issue
Block a user