@@ -493,185 +493,6 @@ BatchIncrementalResults(incrementalResults):
493493 of {hasNext} on the final item in the list.
494494 - Yield {batchedIncrementalResult}.
495495
496- ### Field Collection
497-
498- Before execution, the _ selection set_ is converted to a grouped field set by
499- calling {CollectFields()}. Each entry in the grouped field set is a list of
500- fields that share a response key (the alias if defined, otherwise the field
501- name). This ensures all fields with the same response key (including those in
502- referenced fragments) are executed at the same time.
503-
504- As an example, collecting the fields of this selection set would collect two
505- instances of the field ` a ` and one of field ` b ` :
506-
507- ``` graphql example
508- {
509- a {
510- subfield1
511- }
512- ... ExampleFragment
513- }
514-
515- fragment ExampleFragment on Query {
516- a {
517- subfield2
518- }
519- b
520- }
521- ```
522-
523- The depth-first-search order of the field groups produced by {CollectFields()}
524- is maintained through execution, ensuring that fields appear in the executed
525- response in a stable and predictable order.
526-
527- CollectFields(objectType, selectionSet, variableValues, deferUsage,
528- visitedFragments):
529-
530- - If {visitedFragments} is not provided, initialize it to the empty set.
531- - Initialize {groupedFields} to an empty ordered map of lists.
532- - Initialize {newDeferUsages} to an empty list.
533- - For each {selection} in {selectionSet}:
534- - If {selection} provides the directive ` @skip ` , let {skipDirective} be that
535- directive.
536- - If {skipDirective}'s {if} argument is {true} or is a variable in
537- {variableValues} with the value {true}, continue with the next {selection}
538- in {selectionSet}.
539- - If {selection} provides the directive ` @include ` , let {includeDirective} be
540- that directive.
541- - If {includeDirective}'s {if} argument is not {true} and is not a variable
542- in {variableValues} with the value {true}, continue with the next
543- {selection} in {selectionSet}.
544- - If {selection} is a {Field}:
545- - Let {responseKey} be the response key of {selection} (the alias if
546- defined, otherwise the field name).
547- - Let {fieldDetails} be a new unordered map containing {deferUsage}.
548- - Set the entry for {field} on {fieldDetails} to {selection}. and
549- {deferUsage}.
550- - Let {groupForResponseKey} be the list in {groupedFields} for
551- {responseKey}; if no such list exists, create it as an empty list.
552- - Append {fieldDetails} to the {groupForResponseKey}.
553- - If {selection} is a {FragmentSpread}:
554- - Let {fragmentSpreadName} be the name of {selection}.
555- - If {fragmentSpreadName} provides the directive ` @defer ` and its {if}
556- argument is not {false} and is not a variable in {variableValues} with the
557- value {false}:
558- - Let {deferDirective} be that directive.
559- - If this execution is for a subscription operation, raise a _ field
560- error_ .
561- - If {deferDirective} is not defined:
562- - If {fragmentSpreadName} is in {visitedFragments}, continue with the next
563- {selection} in {selectionSet}.
564- - Add {fragmentSpreadName} to {visitedFragments}.
565- - Let {fragment} be the Fragment in the current Document whose name is
566- {fragmentSpreadName}.
567- - If no such {fragment} exists, continue with the next {selection} in
568- {selectionSet}.
569- - Let {fragmentType} be the type condition on {fragment}.
570- - If {DoesFragmentTypeApply(objectType, fragmentType)} is {false}, continue
571- with the next {selection} in {selectionSet}.
572- - Let {fragmentSelectionSet} be the top-level selection set of {fragment}.
573- - If {deferDirective} is defined:
574- - Let {path} be the corresponding entry on {deferDirective}.
575- - Let {parentDeferUsage} be {deferUsage}.
576- - Let {fragmentDeferUsage} be an unordered map containing {path} and
577- {parentDeferUsage}.
578- - Otherwise, let {fragmentDeferUsage} be {deferUsage}.
579- - Let {fragmentGroupedFieldSet} and {fragmentNewDeferUsages} be the result
580- of calling {CollectFields(objectType, fragmentSelectionSet,
581- variableValues, fragmentDeferUsage, visitedFragments)}.
582- - For each {fragmentGroup} in {fragmentGroupedFieldSet}:
583- - Let {responseKey} be the response key shared by all fields in
584- {fragmentGroup}.
585- - Let {groupForResponseKey} be the list in {groupedFields} for
586- {responseKey}; if no such list exists, create it as an empty list.
587- - Append all items in {fragmentGroup} to {groupForResponseKey}.
588- - Append all items in {fragmentNewDeferUsages} to {newDeferUsages}.
589- - If {selection} is an {InlineFragment}:
590- - Let {fragmentType} be the type condition on {selection}.
591- - If {fragmentType} is not {null} and {DoesFragmentTypeApply(objectType,
592- fragmentType)} is {false}, continue with the next {selection} in
593- {selectionSet}.
594- - Let {fragmentSelectionSet} be the top-level selection set of {selection}.
595- - If {InlineFragment} provides the directive ` @defer ` and its {if} argument
596- is not {false} and is not a variable in {variableValues} with the value
597- {false}:
598- - Let {deferDirective} be that directive.
599- - If this execution is for a subscription operation, raise a _ field
600- error_ .
601- - If {deferDirective} is defined:
602- - Let {path} be the corresponding entry on {deferDirective}.
603- - Let {parentDeferUsage} be {deferUsage}.
604- - Let {fragmentDeferUsage} be an unordered map containing {path} and
605- {parentDeferUsage}.
606- - Otherwise, let {fragmentDeferUsage} be {deferUsage}.
607- - Let {fragmentGroupedFieldSet} and {fragmentNewDeferUsages} be the result
608- of calling {CollectFields(objectType, fragmentSelectionSet,
609- variableValues, fragmentDeferUsage, visitedFragments)}.
610- - For each {fragmentGroup} in {fragmentGroupedFieldSet}:
611- - Let {responseKey} be the response key shared by all fields in
612- {fragmentGroup}.
613- - Let {groupForResponseKey} be the list in {groupedFields} for
614- {responseKey}; if no such list exists, create it as an empty list.
615- - Append all items in {fragmentGroup} to {groupForResponseKey}.
616- - Append all items in {fragmentNewDeferUsages} to {newDeferUsages}.
617- - Return {groupedFields} and {newDeferUsages}.
618-
619- DoesFragmentTypeApply(objectType, fragmentType):
620-
621- - If {fragmentType} is an Object Type:
622- - If {objectType} and {fragmentType} are the same type, return {true},
623- otherwise return {false}.
624- - If {fragmentType} is an Interface Type:
625- - If {objectType} is an implementation of {fragmentType}, return {true}
626- otherwise return {false}.
627- - If {fragmentType} is a Union:
628- - If {objectType} is a possible type of {fragmentType}, return {true}
629- otherwise return {false}.
630-
631- Note: The steps in {CollectFields()} evaluating the ` @skip ` and ` @include `
632- directives may be applied in either order since they apply commutatively.
633-
634- ### Field Plan Generation
635-
636- BuildFieldPlan(originalGroupedFieldSet, parentDeferUsages):
637-
638- - If {parentDeferUsages} is not provided, initialize it to the empty set.
639- - Initialize {groupedFieldSet} to an empty ordered map.
640- - Initialize {newGroupedFieldSets} to an empty unordered map.
641- - Let {fieldPlan} be an unordered map containing {groupedFieldSet} and
642- {newGroupedFieldSets}.
643- - For each {responseKey} and {groupForResponseKey} of {groupedFieldSet}:
644- - Let {filteredDeferUsageSet} be the result of
645- {GetFilteredDeferUsageSet(groupForResponseKey)}.
646- - If {filteredDeferUsageSet} is the equivalent set to {parentDeferUsages}:
647- - Set the entry for {responseKey} in {groupedFieldSet} to
648- {groupForResponseKey}.
649- - Otherwise:
650- - Let {newGroupedFieldSet} be the entry in {newGroupedFieldSets} for any
651- equivalent set to {deferUsageSet}; if no such map exists, create it as an
652- empty ordered map.
653- - Set the entry for {responseKey} in {newGroupedFieldSet} to
654- {groupForResponseKey}.
655- - Return {fieldPlan}.
656-
657- GetFilteredDeferUsageSet(fieldGroup):
658-
659- - Initialize {filteredDeferUsageSet} to the empty set.
660- - For each {fieldDetails} of {fieldGroup}:
661- - Let {deferUsage} be the corresponding entry on {fieldDetails}.
662- - If {deferUsage} is not defined:
663- - Remove all entries from {filteredDeferUsageSet}.
664- - Return {filteredDeferUsageSet}.
665- - Add {deferUsage} to {filteredDeferUsageSet}.
666- - For each {deferUsage} in {filteredDeferUsageSet}:
667- - Let {parentDeferUsage} be the corresponding entry on {deferUsage}.
668- - While {parentDeferUsage} is defined:
669- - If {parentDeferUsage} is contained by {filteredDeferUsageSet}:
670- - Remove {deferUsage} from {filteredDeferUsageSet}.
671- - Continue to the next {deferUsage} in {filteredDeferUsageSet}.
672- - Reset {parentDeferUsage} to the corresponding entry on {parentDeferUsage}.
673- - Return {filteredDeferUsageSet}.
674-
675496## Executing a Field Plan
676497
677498To execute a field plan, the object value being evaluated and the object type
@@ -884,6 +705,185 @@ A correct executor must generate the following result for that _selection set_:
884705}
885706```
886707
708+ ### Field Collection
709+
710+ Before execution, the _ selection set_ is converted to a grouped field set by
711+ calling {CollectFields()}. Each entry in the grouped field set is a list of
712+ fields that share a response key (the alias if defined, otherwise the field
713+ name). This ensures all fields with the same response key (including those in
714+ referenced fragments) are executed at the same time.
715+
716+ As an example, collecting the fields of this selection set would collect two
717+ instances of the field ` a ` and one of field ` b ` :
718+
719+ ``` graphql example
720+ {
721+ a {
722+ subfield1
723+ }
724+ ... ExampleFragment
725+ }
726+
727+ fragment ExampleFragment on Query {
728+ a {
729+ subfield2
730+ }
731+ b
732+ }
733+ ```
734+
735+ The depth-first-search order of the field groups produced by {CollectFields()}
736+ is maintained through execution, ensuring that fields appear in the executed
737+ response in a stable and predictable order.
738+
739+ CollectFields(objectType, selectionSet, variableValues, deferUsage,
740+ visitedFragments):
741+
742+ - If {visitedFragments} is not provided, initialize it to the empty set.
743+ - Initialize {groupedFields} to an empty ordered map of lists.
744+ - Initialize {newDeferUsages} to an empty list.
745+ - For each {selection} in {selectionSet}:
746+ - If {selection} provides the directive ` @skip ` , let {skipDirective} be that
747+ directive.
748+ - If {skipDirective}'s {if} argument is {true} or is a variable in
749+ {variableValues} with the value {true}, continue with the next {selection}
750+ in {selectionSet}.
751+ - If {selection} provides the directive ` @include ` , let {includeDirective} be
752+ that directive.
753+ - If {includeDirective}'s {if} argument is not {true} and is not a variable
754+ in {variableValues} with the value {true}, continue with the next
755+ {selection} in {selectionSet}.
756+ - If {selection} is a {Field}:
757+ - Let {responseKey} be the response key of {selection} (the alias if
758+ defined, otherwise the field name).
759+ - Let {fieldDetails} be a new unordered map containing {deferUsage}.
760+ - Set the entry for {field} on {fieldDetails} to {selection}. and
761+ {deferUsage}.
762+ - Let {groupForResponseKey} be the list in {groupedFields} for
763+ {responseKey}; if no such list exists, create it as an empty list.
764+ - Append {fieldDetails} to the {groupForResponseKey}.
765+ - If {selection} is a {FragmentSpread}:
766+ - Let {fragmentSpreadName} be the name of {selection}.
767+ - If {fragmentSpreadName} provides the directive ` @defer ` and its {if}
768+ argument is not {false} and is not a variable in {variableValues} with the
769+ value {false}:
770+ - Let {deferDirective} be that directive.
771+ - If this execution is for a subscription operation, raise a _ field
772+ error_ .
773+ - If {deferDirective} is not defined:
774+ - If {fragmentSpreadName} is in {visitedFragments}, continue with the next
775+ {selection} in {selectionSet}.
776+ - Add {fragmentSpreadName} to {visitedFragments}.
777+ - Let {fragment} be the Fragment in the current Document whose name is
778+ {fragmentSpreadName}.
779+ - If no such {fragment} exists, continue with the next {selection} in
780+ {selectionSet}.
781+ - Let {fragmentType} be the type condition on {fragment}.
782+ - If {DoesFragmentTypeApply(objectType, fragmentType)} is {false}, continue
783+ with the next {selection} in {selectionSet}.
784+ - Let {fragmentSelectionSet} be the top-level selection set of {fragment}.
785+ - If {deferDirective} is defined:
786+ - Let {path} be the corresponding entry on {deferDirective}.
787+ - Let {parentDeferUsage} be {deferUsage}.
788+ - Let {fragmentDeferUsage} be an unordered map containing {path} and
789+ {parentDeferUsage}.
790+ - Otherwise, let {fragmentDeferUsage} be {deferUsage}.
791+ - Let {fragmentGroupedFieldSet} and {fragmentNewDeferUsages} be the result
792+ of calling {CollectFields(objectType, fragmentSelectionSet,
793+ variableValues, fragmentDeferUsage, visitedFragments)}.
794+ - For each {fragmentGroup} in {fragmentGroupedFieldSet}:
795+ - Let {responseKey} be the response key shared by all fields in
796+ {fragmentGroup}.
797+ - Let {groupForResponseKey} be the list in {groupedFields} for
798+ {responseKey}; if no such list exists, create it as an empty list.
799+ - Append all items in {fragmentGroup} to {groupForResponseKey}.
800+ - Append all items in {fragmentNewDeferUsages} to {newDeferUsages}.
801+ - If {selection} is an {InlineFragment}:
802+ - Let {fragmentType} be the type condition on {selection}.
803+ - If {fragmentType} is not {null} and {DoesFragmentTypeApply(objectType,
804+ fragmentType)} is {false}, continue with the next {selection} in
805+ {selectionSet}.
806+ - Let {fragmentSelectionSet} be the top-level selection set of {selection}.
807+ - If {InlineFragment} provides the directive ` @defer ` and its {if} argument
808+ is not {false} and is not a variable in {variableValues} with the value
809+ {false}:
810+ - Let {deferDirective} be that directive.
811+ - If this execution is for a subscription operation, raise a _ field
812+ error_ .
813+ - If {deferDirective} is defined:
814+ - Let {path} be the corresponding entry on {deferDirective}.
815+ - Let {parentDeferUsage} be {deferUsage}.
816+ - Let {fragmentDeferUsage} be an unordered map containing {path} and
817+ {parentDeferUsage}.
818+ - Otherwise, let {fragmentDeferUsage} be {deferUsage}.
819+ - Let {fragmentGroupedFieldSet} and {fragmentNewDeferUsages} be the result
820+ of calling {CollectFields(objectType, fragmentSelectionSet,
821+ variableValues, fragmentDeferUsage, visitedFragments)}.
822+ - For each {fragmentGroup} in {fragmentGroupedFieldSet}:
823+ - Let {responseKey} be the response key shared by all fields in
824+ {fragmentGroup}.
825+ - Let {groupForResponseKey} be the list in {groupedFields} for
826+ {responseKey}; if no such list exists, create it as an empty list.
827+ - Append all items in {fragmentGroup} to {groupForResponseKey}.
828+ - Append all items in {fragmentNewDeferUsages} to {newDeferUsages}.
829+ - Return {groupedFields} and {newDeferUsages}.
830+
831+ DoesFragmentTypeApply(objectType, fragmentType):
832+
833+ - If {fragmentType} is an Object Type:
834+ - If {objectType} and {fragmentType} are the same type, return {true},
835+ otherwise return {false}.
836+ - If {fragmentType} is an Interface Type:
837+ - If {objectType} is an implementation of {fragmentType}, return {true}
838+ otherwise return {false}.
839+ - If {fragmentType} is a Union:
840+ - If {objectType} is a possible type of {fragmentType}, return {true}
841+ otherwise return {false}.
842+
843+ Note: The steps in {CollectFields()} evaluating the ` @skip ` and ` @include `
844+ directives may be applied in either order since they apply commutatively.
845+
846+ ### Field Plan Generation
847+
848+ BuildFieldPlan(originalGroupedFieldSet, parentDeferUsages):
849+
850+ - If {parentDeferUsages} is not provided, initialize it to the empty set.
851+ - Initialize {groupedFieldSet} to an empty ordered map.
852+ - Initialize {newGroupedFieldSets} to an empty unordered map.
853+ - Let {fieldPlan} be an unordered map containing {groupedFieldSet} and
854+ {newGroupedFieldSets}.
855+ - For each {responseKey} and {groupForResponseKey} of {groupedFieldSet}:
856+ - Let {filteredDeferUsageSet} be the result of
857+ {GetFilteredDeferUsageSet(groupForResponseKey)}.
858+ - If {filteredDeferUsageSet} is the equivalent set to {parentDeferUsages}:
859+ - Set the entry for {responseKey} in {groupedFieldSet} to
860+ {groupForResponseKey}.
861+ - Otherwise:
862+ - Let {newGroupedFieldSet} be the entry in {newGroupedFieldSets} for any
863+ equivalent set to {deferUsageSet}; if no such map exists, create it as an
864+ empty ordered map.
865+ - Set the entry for {responseKey} in {newGroupedFieldSet} to
866+ {groupForResponseKey}.
867+ - Return {fieldPlan}.
868+
869+ GetFilteredDeferUsageSet(fieldGroup):
870+
871+ - Initialize {filteredDeferUsageSet} to the empty set.
872+ - For each {fieldDetails} of {fieldGroup}:
873+ - Let {deferUsage} be the corresponding entry on {fieldDetails}.
874+ - If {deferUsage} is not defined:
875+ - Remove all entries from {filteredDeferUsageSet}.
876+ - Return {filteredDeferUsageSet}.
877+ - Add {deferUsage} to {filteredDeferUsageSet}.
878+ - For each {deferUsage} in {filteredDeferUsageSet}:
879+ - Let {parentDeferUsage} be the corresponding entry on {deferUsage}.
880+ - While {parentDeferUsage} is defined:
881+ - If {parentDeferUsage} is contained by {filteredDeferUsageSet}:
882+ - Remove {deferUsage} from {filteredDeferUsageSet}.
883+ - Continue to the next {deferUsage} in {filteredDeferUsageSet}.
884+ - Reset {parentDeferUsage} to the corresponding entry on {parentDeferUsage}.
885+ - Return {filteredDeferUsageSet}.
886+
887887## Executing Fields
888888
889889Each field requested in the grouped field set that is defined on the selected
0 commit comments