@@ -45,7 +45,7 @@ private OperationPlan BuildExecutionPlan(
4545 planSteps = TransformPlanSteps ( planSteps , operationDefinition ) ;
4646 IndexDependencies ( planSteps , ctx ) ;
4747 BuildExecutionNodes ( planSteps , ctx , _schema , hasVariables ) ;
48- MergeAndBatchOperations ( ctx , _options . EnableRequestGrouping ) ;
48+ MergeAndBatchOperations ( ctx , _options . EnableRequestGrouping , _options . MergePolicy ) ;
4949 WireExecutionDependencies ( ctx ) ;
5050
5151 var rootNodes = planSteps
@@ -347,10 +347,11 @@ private static OperationExecutionNode CreateOperationExecutionNode(
347347
348348 private static void MergeAndBatchOperations (
349349 ExecutionPlanBuildContext ctx ,
350- bool enableRequestGrouping )
350+ bool enableRequestGrouping ,
351+ OperationMergePolicy mergePolicy )
351352 {
352353 var nodeFieldBoundCache = new Dictionary < int , bool > ( ) ;
353- var mergeResults = MergeStructurallyIdenticalOperations ( ctx , nodeFieldBoundCache ) ;
354+ var mergeResults = MergeStructurallyIdenticalOperations ( ctx , nodeFieldBoundCache , mergePolicy ) ;
354355
355356 // Capture each node's dependency identifiers now, because the batching
356357 // step below will rewrite the dependency lookup as it merges nodes.
@@ -375,7 +376,8 @@ private static void MergeAndBatchOperations(
375376 /// </summary>
376377 private static Dictionary < int , MergeResult > MergeStructurallyIdenticalOperations (
377378 ExecutionPlanBuildContext ctx ,
378- Dictionary < int , bool > nodeFieldBoundCache )
379+ Dictionary < int , bool > nodeFieldBoundCache ,
380+ OperationMergePolicy mergePolicy )
379381 {
380382 var candidates = new Dictionary < string , List < OperationExecutionNode > > ( StringComparer . Ordinal ) ;
381383
@@ -411,7 +413,8 @@ private static Dictionary<int, MergeResult> MergeStructurallyIdenticalOperations
411413 continue ;
412414 }
413415
414- foreach ( var group in PartitionIntoMergeableGroups ( equivalentNodes , ctx . DependenciesByStepId ) )
416+ foreach ( var group in PartitionIntoMergeableGroups (
417+ equivalentNodes , ctx . DependenciesByStepId , mergePolicy ) )
415418 {
416419 if ( group . Count <= 1 )
417420 {
@@ -1154,12 +1157,30 @@ private static string ApplyPrefixReplacements(
11541157 /// Partitions structurally identical operations into groups that can
11551158 /// each be safely merged. Two operations cannot share a group if one
11561159 /// transitively depends on the other, because merging them would
1157- /// create a cycle in the dependency graph.
1160+ /// create a cycle in the dependency graph. The <paramref name="mergePolicy"/>
1161+ /// further restricts which candidates may share a group based on their
1162+ /// dependency depth.
11581163 /// </summary>
11591164 private static List < List < OperationExecutionNode > > PartitionIntoMergeableGroups (
11601165 List < OperationExecutionNode > candidates ,
1161- Dictionary < int , HashSet < int > > dependenciesByStepId )
1166+ Dictionary < int , HashSet < int > > dependenciesByStepId ,
1167+ OperationMergePolicy mergePolicy )
11621168 {
1169+ // Pre-compute dependency depths when the policy needs them.
1170+ Dictionary < int , int > ? depthLookup = null ;
1171+
1172+ if ( mergePolicy is OperationMergePolicy . Conservative
1173+ or OperationMergePolicy . Balanced )
1174+ {
1175+ depthLookup = [ ] ;
1176+ var recursionStack = new HashSet < int > ( ) ;
1177+
1178+ foreach ( var candidate in candidates )
1179+ {
1180+ GetDependencyDepth ( candidate . Id , dependenciesByStepId , depthLookup , recursionStack ) ;
1181+ }
1182+ }
1183+
11631184 var groups = new List < List < OperationExecutionNode > > ( ) ;
11641185 var visited = new HashSet < int > ( ) ;
11651186
@@ -1171,22 +1192,46 @@ private static List<List<OperationExecutionNode>> PartitionIntoMergeableGroups(
11711192 {
11721193 var canJoin = true ;
11731194
1174- foreach ( var existing in group )
1195+ // Policy-specific depth checks (applied before the more
1196+ // expensive transitive-reachability walk).
1197+ if ( depthLookup is not null )
11751198 {
1176- visited . Clear ( ) ;
1199+ var candidateDepth = depthLookup [ candidate . Id ] ;
1200+ var referenceDepth = depthLookup [ group [ 0 ] . Id ] ;
11771201
1178- if ( IsTransitivelyReachable ( candidate . Id , existing . Id , dependenciesByStepId , visited ) )
1202+ switch ( mergePolicy )
11791203 {
1180- canJoin = false ;
1181- break ;
1182- }
1204+ case OperationMergePolicy . Conservative
1205+ when candidateDepth != referenceDepth :
1206+ canJoin = false ;
1207+ break ;
11831208
1184- visited . Clear ( ) ;
1209+ case OperationMergePolicy . Balanced
1210+ when Math . Abs ( candidateDepth - referenceDepth ) > 1 :
1211+ canJoin = false ;
1212+ break ;
1213+ }
1214+ }
11851215
1186- if ( IsTransitivelyReachable ( existing . Id , candidate . Id , dependenciesByStepId , visited ) )
1216+ if ( canJoin )
1217+ {
1218+ foreach ( var existing in group )
11871219 {
1188- canJoin = false ;
1189- break ;
1220+ visited . Clear ( ) ;
1221+
1222+ if ( IsTransitivelyReachable ( candidate . Id , existing . Id , dependenciesByStepId , visited ) )
1223+ {
1224+ canJoin = false ;
1225+ break ;
1226+ }
1227+
1228+ visited . Clear ( ) ;
1229+
1230+ if ( IsTransitivelyReachable ( existing . Id , candidate . Id , dependenciesByStepId , visited ) )
1231+ {
1232+ canJoin = false ;
1233+ break ;
1234+ }
11901235 }
11911236 }
11921237
0 commit comments