@@ -11,6 +11,8 @@ import (
1111 "sync"
1212 "time"
1313
14+ "github.com/n9te9/go-graphql-federation-gateway/federation/executor/merger"
15+ "github.com/n9te9/go-graphql-federation-gateway/federation/executor/query_builder"
1416 "github.com/n9te9/go-graphql-federation-gateway/federation/graph"
1517 "github.com/n9te9/go-graphql-federation-gateway/federation/planner"
1618 "github.com/n9te9/graphql-parser/ast"
@@ -29,7 +31,8 @@ type ExecutorV2 struct {
2931 httpClient * http.Client
3032 pool sync.Pool // pool of *ExecutionContext
3133 bufPool sync.Pool // pool of *bytes.Buffer for request body serialization
32- queryBuilder * QueryBuilderV2
34+ queryBuilder query_builder.QueryBuilderV2
35+ merger merger.Merger
3336 superGraph * graph.SuperGraphV2
3437 subgraphTimeout time.Duration // per-subgraph request timeout; 0 means no extra timeout
3538}
@@ -66,8 +69,9 @@ func newExecutorV2(httpClient *http.Client, superGraph *graph.SuperGraphV2, time
6669 return bytes .NewBuffer (make ([]byte , 0 , 4096 ))
6770 },
6871 },
69- queryBuilder : NewQueryBuilderV2 (superGraph ),
72+ queryBuilder : query_builder . NewQueryBuilderV2 (superGraph ),
7073 superGraph : superGraph ,
74+ merger : merger .NewMerger (),
7175 }
7276}
7377
@@ -82,11 +86,7 @@ type ExecutionContext struct {
8286
8387// Execute executes a query plan and returns the merged result.
8488// It validates the plan is a DAG, then executes steps in dependency order.
85- func (e * ExecutorV2 ) Execute (
86- ctx context.Context ,
87- plan * planner.PlanV2 ,
88- variables map [string ]interface {},
89- ) (map [string ]interface {}, error ) {
89+ func (e * ExecutorV2 ) Execute (ctx context.Context , plan * planner.PlanV2 , variables map [string ]interface {}) (map [string ]interface {}, error ) {
9090 // Validate DAG
9191 if err := e .validateDAG (plan ); err != nil {
9292 return nil , fmt .Errorf ("invalid plan: %w" , err )
@@ -206,11 +206,7 @@ func (e *ExecutorV2) validateDAG(plan *planner.PlanV2) error {
206206}
207207
208208// executeSteps executes a group of steps in parallel and then recursively executes dependent steps.
209- func (e * ExecutorV2 ) executeSteps (
210- execCtx * ExecutionContext ,
211- stepIDs []int ,
212- variables map [string ]interface {},
213- ) error {
209+ func (e * ExecutorV2 ) executeSteps (execCtx * ExecutionContext , stepIDs []int , variables map [string ]interface {}) error {
214210 if len (stepIDs ) == 0 {
215211 return nil
216212 }
@@ -335,12 +331,7 @@ func (e *ExecutorV2) findReadySteps(execCtx *ExecutionContext) []int {
335331}
336332
337333// processStep processes a single step.
338- func (e * ExecutorV2 ) processStep (
339- ctx context.Context ,
340- execCtx * ExecutionContext ,
341- step * planner.StepV2 ,
342- variables map [string ]interface {},
343- ) error {
334+ func (e * ExecutorV2 ) processStep (ctx context.Context , execCtx * ExecutionContext , step * planner.StepV2 , variables map [string ]interface {}) error {
344335 // Guard against nil subgraph
345336 if step .SubGraph == nil {
346337 err := fmt .Errorf ("step %d has nil subgraph" , step .ID )
@@ -398,14 +389,22 @@ func (e *ExecutorV2) processStep(
398389 execCtx .results [step .ID ] = result
399390 execCtx .mu .Unlock ()
400391 } else {
401- // Merge entity results into parent
402- if err := e . mergeEntityResults ( execCtx , step , result ); err != nil {
403- e .recordError (execCtx , step , fmt .Errorf ("failed to merge entity results : %w" , err ))
392+ rootResult , rootResultIndex , err := e . extractRootResult ( execCtx )
393+ if err != nil {
394+ e .recordError (execCtx , step , fmt .Errorf ("failed to extract root result for merging : %w" , err ))
404395 e .setNullForFailedStep (execCtx , step )
405- return nil // Don't propagate error
396+ return nil
406397 }
398+
407399 execCtx .mu .Lock ()
408- execCtx .results [step .ID ] = result
400+ mergedRootResult , err := e .merger .MergeEntities (rootResult , result , step )
401+ if err != nil {
402+ e .recordError (execCtx , step , fmt .Errorf ("failed to merge entities: %w" , err ))
403+ e .setNullForFailedStep (execCtx , step )
404+ return nil
405+ }
406+ execCtx .results [rootResultIndex ] = mergedRootResult
407+ execCtx .results [step .ID ] = mergedRootResult
409408 execCtx .mu .Unlock ()
410409 }
411410
@@ -982,21 +981,12 @@ func (e *ExecutorV2) buildRepresentation(entity map[string]interface{}, typeName
982981 return representation
983982}
984983
985- // mergeEntityResults merges entity query results back into parent results.
986- func (e * ExecutorV2 ) mergeEntityResults (execCtx * ExecutionContext , step * planner.StepV2 , result map [string ]interface {}) error {
987- execCtx .mu .Lock ()
988- defer execCtx .mu .Unlock ()
989-
990- // Get parent step result
991- if len (step .DependsOn ) == 0 {
992- return nil
993- }
984+ func (e * ExecutorV2 ) extractRootResult (execCtx * ExecutionContext ) (map [string ]interface {}, int , error ) {
985+ execCtx .mu .RLock ()
986+ defer execCtx .mu .RUnlock ()
994987
995- // Always merge into the root step (Step 0), not the immediate parent
996- // This is because nested entity steps (e.g., Step 2 depends on Step 1)
997- // cannot merge into Step 1's _entities result format
998988 var rootStepID int
999- var rootResult interface {}
989+ var rootResult any
1000990 for _ , s := range execCtx .plan .Steps {
1001991 if len (s .DependsOn ) == 0 {
1002992 rootStepID = s .ID
@@ -1006,196 +996,15 @@ func (e *ExecutorV2) mergeEntityResults(execCtx *ExecutionContext, step *planner
1006996 }
1007997
1008998 if rootResult == nil {
1009- return fmt .Errorf ("root step result not found" )
999+ return nil , - 1 , fmt .Errorf ("root step result not found" )
10101000 }
10111001
1012- // Extract data from root result
10131002 rootResultMap , ok := rootResult .(map [string ]interface {})
10141003 if ! ok {
1015- return fmt .Errorf ("root result is not a map" )
1016- }
1017-
1018- rootData , ok := rootResultMap ["data" ].(map [string ]interface {})
1019- if ! ok {
1020- return fmt .Errorf ("root result does not have data field" )
1021- }
1022-
1023- // Extract _entities from entity query result
1024- resultData , ok := result ["data" ].(map [string ]interface {})
1025- if ! ok {
1026- return nil // No data to merge
1027- }
1028-
1029- entitiesData , ok := resultData ["_entities" ]
1030- if ! ok {
1031- return nil // No entities to merge
1032- }
1033-
1034- // Build merge path (skip root type name)
1035- mergePath := make ([]string , 0 )
1036- for i , segment := range step .InsertionPath {
1037- // Skip root type names (Query, Mutation, Subscription)
1038- if i == 0 && (segment == "Query" || segment == "Mutation" || segment == "Subscription" ) {
1039- continue
1040- }
1041- mergePath = append (mergePath , segment )
1042- }
1043-
1044- // Navigate to the target field to check if it's an array or object
1045- // Also collect all array positions in the path for nested array handling
1046- var current interface {} = rootData
1047- var firstArrayIndex = - 1 // Index of the first array in the path
1048-
1049- for i , segment := range mergePath {
1050- if currentMap , ok := current .(map [string ]interface {}); ok {
1051- if next , exists := currentMap [segment ]; exists {
1052- current = next
1053-
1054- // Check if the value we just navigated to is an array
1055- if _ , isArray := current .([]interface {}); isArray {
1056- // We hit an array - mark it
1057- if firstArrayIndex < 0 {
1058- firstArrayIndex = i
1059- }
1060- break
1061- }
1062- } else {
1063- // Path doesn't exist yet
1064- current = nil
1065- break
1066- }
1067- } else {
1068- // Not a map or array, can't navigate further
1069- current = nil
1070- break
1071- }
1072- }
1073-
1074- // Handle different merge scenarios
1075- if firstArrayIndex >= 0 {
1076- // We encountered an array - need to handle nested array merging
1077- entities , ok := entitiesData .([]interface {})
1078- if ! ok {
1079- return fmt .Errorf ("entities data is not an array" )
1080- }
1081-
1082- // Navigate to the first array
1083- var arrayContainer interface {} = rootData
1084- arrayPath := mergePath [:firstArrayIndex + 1 ] // Include the array field itself
1085- for _ , segment := range arrayPath {
1086- if containerMap , ok := arrayContainer .(map [string ]interface {}); ok {
1087- arrayContainer = containerMap [segment ]
1088- }
1089- }
1090-
1091- arrayData , ok := arrayContainer .([]interface {})
1092- if ! ok {
1093- return fmt .Errorf ("expected array at merge path %v" , arrayPath )
1094- }
1095-
1096- // The remaining path after the array
1097- remainingPath := mergePath [firstArrayIndex + 1 :]
1098-
1099- // Merge entities into the nested structure
1100- entityIndex := 0
1101- for _ , elem := range arrayData {
1102- elemMap , ok := elem .(map [string ]interface {})
1103- if ! ok {
1104- continue
1105- }
1106-
1107- // Recursively merge entities into potentially nested arrays
1108- entityIndex = e .mergeIntoNestedArrays (elemMap , entities , remainingPath , entityIndex , step )
1109- }
1110-
1111- } else if current == nil {
1112- // Path doesn't exist yet, treat as single object and let Merge handle it
1113- entities , ok := entitiesData .([]interface {})
1114- if ! ok || len (entities ) == 0 {
1115- return nil
1116- }
1117-
1118- firstEntity , ok := entities [0 ].(map [string ]interface {})
1119- if ! ok {
1120- return fmt .Errorf ("first entity is not a map" )
1121- }
1122-
1123- if err := Merge (rootData , firstEntity , mergePath ); err != nil {
1124- return fmt .Errorf ("failed to merge entity object: %w" , err )
1125- }
1126- } else if _ , isArray := current .([]interface {}); isArray {
1127- // Target is an array, merge entities directly
1128- if err := Merge (rootData , entitiesData , mergePath ); err != nil {
1129- return fmt .Errorf ("failed to merge entities array: %w" , err )
1130- }
1131- } else {
1132- // Target is a single object, merge first entity
1133- entities , ok := entitiesData .([]interface {})
1134- if ! ok || len (entities ) == 0 {
1135- return nil
1136- }
1137-
1138- // For single object, merge the first entity's fields
1139- firstEntity , ok := entities [0 ].(map [string ]interface {})
1140- if ! ok {
1141- return fmt .Errorf ("first entity is not a map" )
1142- }
1143-
1144- if err := Merge (rootData , firstEntity , mergePath ); err != nil {
1145- return fmt .Errorf ("failed to merge entity object: %w" , err )
1146- }
1147- }
1148-
1149- // Update the root step's result to reflect the merge
1150- execCtx .results [rootStepID ] = rootResultMap
1151-
1152- return nil
1153- }
1154-
1155- // mergeIntoNestedArrays recursively merges entities into potentially nested array structures
1156- // Returns the next entity index to use
1157- func (e * ExecutorV2 ) mergeIntoNestedArrays (
1158- current map [string ]interface {},
1159- entities []interface {},
1160- path []string ,
1161- entityIndex int ,
1162- step * planner.StepV2 ,
1163- ) int {
1164- if len (path ) == 0 {
1165- // Reached the target - merge the entity here
1166- if entityIndex < len (entities ) {
1167- if entityMap , ok := entities [entityIndex ].(map [string ]interface {}); ok {
1168- // Deep merge entity fields into current
1169- // Use the Merge function to properly handle nested structures
1170- Merge (current , entityMap , []string {})
1171- }
1172- return entityIndex + 1
1173- }
1174- return entityIndex
1175- }
1176-
1177- segment := path [0 ]
1178- remainingPath := path [1 :]
1179-
1180- next , exists := current [segment ]
1181- if ! exists {
1182- return entityIndex
1183- }
1184-
1185- // Check if next is an array
1186- if arr , isArray := next .([]interface {}); isArray {
1187- // Process each array element
1188- for _ , elem := range arr {
1189- if elemMap , ok := elem .(map [string ]interface {}); ok {
1190- entityIndex = e .mergeIntoNestedArrays (elemMap , entities , remainingPath , entityIndex , step )
1191- }
1192- }
1193- } else if nextMap , ok := next .(map [string ]interface {}); ok {
1194- // Continue navigating
1195- entityIndex = e .mergeIntoNestedArrays (nextMap , entities , remainingPath , entityIndex , step )
1004+ return nil , - 1 , fmt .Errorf ("root result is not a map" )
11961005 }
11971006
1198- return entityIndex
1007+ return rootResultMap , rootStepID , nil
11991008}
12001009
12011010// sendRequest sends a GraphQL request to a subgraph.
0 commit comments