refactor(executor): introduce FieldProjectionCollector (PR-009d-i) - #1012
Open
AnthonyMDev wants to merge 1 commit into
Open
Conversation
✅ Docs preview readyThe preview is ready to be viewed. View the preview File Changes 0 new, 1 changed, 0 removedBuild ID: 062dcdcdfbdec055bf3d2103 URL: https://www.apollographql.com/docs/deploy-preview/062dcdcdfbdec055bf3d2103 ✅ AI Style Review — No Changes DetectedNo MDX files were changed in this pull request. Review Log: View detailed log
|
AnthonyMDev
force-pushed
the
cache-rewrite/phase-1a-cache-load-fields
branch
from
June 2, 2026 21:02
cd62335 to
fc90f51
Compare
AnthonyMDev
force-pushed
the
cache-rewrite/phase-1a-executor-upfront-projection
branch
from
June 2, 2026 21:03
d0e409a to
105ac37
Compare
This was referenced Jun 2, 2026
AnthonyMDev
force-pushed
the
cache-rewrite/phase-1a-cache-load-fields
branch
from
June 9, 2026 18:26
fc90f51 to
a6ccc43
Compare
AnthonyMDev
force-pushed
the
cache-rewrite/phase-1a-executor-upfront-projection
branch
from
June 9, 2026 18:26
105ac37 to
b365033
Compare
AnthonyMDev
force-pushed
the
cache-rewrite/phase-1a-cache-load-fields
branch
from
June 29, 2026 20:38
a6ccc43 to
f4e7692
Compare
AnthonyMDev
force-pushed
the
cache-rewrite/phase-1a-executor-upfront-projection
branch
from
June 29, 2026 20:38
b365033 to
fe36405
Compare
Adds the per-level selection-set traversal that drives the upfront-projection read pattern described by ADR 0007 Principle 5. `FieldProjectionCollector.collect(...)` walks a `[Selection]` for one record at one level and emits a `Set<FieldProjection>` ready to be handed to `NormalizedCache.loadFields(_:)`. This is the d-i half of the documented PR-009d split. The collector is additive — no existing executor caller uses it yet. PR-009d-ii reshapes `CacheDataExecutionSource` to drive the collector at each level, replacing the existing DataLoader-batched `loadRecords` pattern with `loadFields`-per-level batching. ## Selection-case handling Mirrors `DefaultFieldSelectionCollector.collectFields(...)` so the upfront-projection path and the lazy path always agree on which fields each `Selection` case contributes: - `.field` — emit one projection. `Selection.Field.cacheKey(with:)` produces the cache field name (compose with arguments per existing semantics). - `.conditional(conditions, _)` — enter only if `conditions .evaluate(with: variables) == true`. - `.fragment(_)` — always enter (fulfilled). - `.inlineFragment(_)` — enter only if `resolveRuntimeType()` returns an `Object` whose `__parentType.canBeConverted(from:)` returns true. - `.deferred(_, _, _)` — always enter, regardless of the `@defer (if:)` condition. The cache executor sets `shouldAttemptDeferredFragmentExecution = true` and eagerly resolves deferred selections, so the projection set must include them. The `if:` flag only controls whether the executor's grouping treats them as deferred-then-resolved or fulfilled-then-resolved; either path reads the fields. ## Why one level at a time Object/list-of-object fields' children live at separate cache keys that aren't known until the parent's `child_key_value` is loaded. The collector deliberately does NOT recurse past `.object` or `.customScalar` boundaries — the caller's per-level loop is responsible for resolving the parent, discovering child cache keys, and invoking `collect(...)` again for each child level. ## Tests 17 unit tests covering: scalar selection, list cardinality, object column shape (`.childKey`), nested-object boundary stop, field with arguments, `@include` true/false, `@skip` true, fragment (always fulfilled), Set deduplication across outer+fragment, inline fragment with matching / non-matching / nil runtime type, `.deferred` with no condition, with `if: false`, with `if: true`, and empty input. Full Apollo-UnitTestPlan: 1115 passed, 0 failed. ## Status `@_spi(Execution) public` for now, matching `FieldProjection` and `NormalizedCache.loadFields(_:)` from PR-009b/c. The SPI bracketing across all three types stays until the read-API shape is locked (targeted for PR-009e/f). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AnthonyMDev
force-pushed
the
cache-rewrite/phase-1a-cache-load-fields
branch
from
July 9, 2026 18:59
f4e7692 to
e5bb4df
Compare
AnthonyMDev
force-pushed
the
cache-rewrite/phase-1a-executor-upfront-projection
branch
from
July 9, 2026 18:59
fe36405 to
c7d861c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds the per-level selection-set traversal that drives the upfront-projection read pattern described by ADR 0007 Principle 5.
This is the d-i half of the documented PR-009d split. The ADR explicitly anticipates this split because the executor reshape is the highest-risk PR in sub-phase 1A.5; this PR is additive and unblocks the actual reshape (d-ii) by giving it a working unit-tested traversal primitive.
Stacked on PR #1009 (
NormalizedCache.loadFields(_:)).What's in this PR
FieldProjectionCollector.collect(selections:cacheKey:variables:resolveRuntimeType:)— walks[Selection]for one record at one level and emitsSet<FieldProjection>.Selectioncase + variable evaluation + dedup.The collector is additive: no existing executor caller uses it yet. PR-009d-ii reshapes
CacheDataExecutionSourceto drive the collector at each level, replacing the existing DataLoader-batchedloadRecordspattern withloadFields-per-level batching.Selection-case handling
Mirrors
DefaultFieldSelectionCollector.collectFields(...)so the upfront-projection path and the lazy path agree on which fields eachSelectioncase contributes:.fieldSelection.Field.cacheKey(with:)composes the cache field name including arguments..conditional(conditions, _)conditions.evaluate(with: variables) == true..fragment(_).inlineFragment(_)resolveRuntimeType()returns anObjectwhose__parentType.canBeConverted(from:)is true..deferred(_, _, _)@defer(if:). The cache executor setsshouldAttemptDeferredFragmentExecution = trueand eagerly resolves deferred selections, so the projection set must include them.One level at a time — by design
Object/list-of-object fields' children live at separate cache keys that aren't known until the parent's
child_key_valueis loaded. The collector deliberately does NOT recurse past.objector.customScalarboundaries — the caller's per-level loop is responsible for resolving the parent, discovering child cache keys, and invokingcollect(...)again for each child level.This is also why the cache layer can't take a
Selectiontree directly (a question that came up reviewing PR #1009): per-level loading needs the per-level decision to live in the executor, where variables + cache-key resolution + fragment dispatch live. The collector is the boundary between selection-set semantics and storage projection.Tests
17 unit tests covering:
@includetrue/false,@skiptrue.if: false,if: true.Full Apollo-UnitTestPlan: 1115 passed, 0 failed.
Visibility
@_spi(Execution) public, matchingFieldProjectionandNormalizedCache.loadFields(_:). The SPI bracketing across all three types stays until PR-009e/f.References
NormalizedCache.loadFields(_:); this PR's direct base)🤖 Generated with Claude Code