@@ -64,6 +64,7 @@ public class EsqlExecutionInfo implements ChunkedToXContentObject, Writeable {
6464 public static final ParseField IS_PARTIAL_FIELD = new ParseField ("is_partial" );
6565
6666 private static final TransportVersion ESQL_QUERY_PLANNING_DURATION = TransportVersion .fromName ("esql_query_planning_duration" );
67+ public static final TransportVersion EXECUTION_METADATA_VERSION = TransportVersion .fromName ("esql_execution_metadata" );
6768
6869 // Map key is clusterAlias on the primary querying cluster of a CCS minimize_roundtrips=true query
6970 // The Map itself is immutable after construction - all Clusters will be accounted for at the start of the search.
@@ -72,8 +73,15 @@ public class EsqlExecutionInfo implements ChunkedToXContentObject, Writeable {
7273 public final ConcurrentMap <String , Cluster > clusterInfo ;
7374 // Is the clusterInfo map iinitialization in progress? If so, we should not try to serialize it.
7475 private transient volatile boolean clusterInfoInitializing ;
75- // whether the user has asked for CCS metadata to be in the JSON response (the overall took will always be present)
76- private final boolean includeCCSMetadata ;
76+
77+ public enum IncludeExecutionMetadata {
78+ ALWAYS ,
79+ CCS_ONLY ,
80+ NEVER
81+ }
82+
83+ // whether the user has asked for execution/CCS metadata to be in the JSON response (the overall took will always be present)
84+ private final IncludeExecutionMetadata includeExecutionMetadata ;
7785
7886 // fields that are not Writeable since they are only needed on the primary CCS coordinator
7987 private final transient Predicate <String > skipOnFailurePredicate ; // Predicate to determine if we should skip a cluster on failure
@@ -89,36 +97,53 @@ public class EsqlExecutionInfo implements ChunkedToXContentObject, Writeable {
8997 // Are we doing subplans? No need to serialize this because it is only relevant for the coordinator node.
9098 private transient boolean inSubplan = false ;
9199
92- // This is only used is tests.
100+ // FOR TESTS ONLY
93101 public EsqlExecutionInfo (boolean includeCCSMetadata ) {
94- this (Predicates .always (), includeCCSMetadata ); // default all clusters to being skippable on failure
102+ // default all clusters to being skippable on failure
103+ this (Predicates .always (), includeCCSMetadata ? IncludeExecutionMetadata .CCS_ONLY : IncludeExecutionMetadata .NEVER );
95104 }
96105
97106 /**
98- * @param skipOnPlanTimeFailurePredicate Decides whether we should skip the cluster that fails during planning phase.
99- * @param includeCCSMetadata (user defined setting) whether to include the CCS metadata in the HTTP response
107+ * FOR TESTING use with fromXContent parsing ONLY
100108 */
101- public EsqlExecutionInfo (Predicate <String > skipOnPlanTimeFailurePredicate , boolean includeCCSMetadata ) {
102- this .clusterInfo = new ConcurrentHashMap <>();
103- this .skipOnFailurePredicate = skipOnPlanTimeFailurePredicate ;
104- this .includeCCSMetadata = includeCCSMetadata ;
105- this .relativeStart = TimeSpan .start ();
109+ EsqlExecutionInfo (ConcurrentMap <String , Cluster > clusterInfo , boolean includeCCSMetadata ) {
110+ this (
111+ clusterInfo ,
112+ Predicates .always (),
113+ includeCCSMetadata ? IncludeExecutionMetadata .CCS_ONLY : IncludeExecutionMetadata .NEVER ,
114+ null
115+ );
106116 }
107117
108118 /**
109- * For testing use with fromXContent parsing only
119+ * @param skipOnPlanTimeFailurePredicate Decides whether we should skip the cluster that fails during planning phase.
120+ * @param includeExecutionMetadata (user defined setting) whether to include the execution/CCS metadata in the HTTP response
110121 */
111- EsqlExecutionInfo (ConcurrentMap <String , Cluster > clusterInfo , boolean includeCCSMetadata ) {
122+ public EsqlExecutionInfo (Predicate <String > skipOnPlanTimeFailurePredicate , IncludeExecutionMetadata includeExecutionMetadata ) {
123+ this (new ConcurrentHashMap <>(), skipOnPlanTimeFailurePredicate , includeExecutionMetadata , TimeSpan .start ());
124+ }
125+
126+ EsqlExecutionInfo (
127+ ConcurrentMap <String , Cluster > clusterInfo ,
128+ Predicate <String > skipOnPlanTimeFailurePredicate ,
129+ IncludeExecutionMetadata includeExecutionMetadata ,
130+ TimeSpan .Builder relativeStart
131+ ) {
132+ assert includeExecutionMetadata != null ;
112133 this .clusterInfo = clusterInfo ;
113- this .includeCCSMetadata = includeCCSMetadata ;
114- this .skipOnFailurePredicate = Predicates . always () ;
115- this .relativeStart = null ;
134+ this .skipOnFailurePredicate = skipOnPlanTimeFailurePredicate ;
135+ this .includeExecutionMetadata = includeExecutionMetadata ;
136+ this .relativeStart = relativeStart ;
116137 }
117138
118139 public EsqlExecutionInfo (StreamInput in ) throws IOException {
119140 this .overallTook = in .readOptionalTimeValue ();
120141 this .clusterInfo = in .readMapValues (EsqlExecutionInfo .Cluster ::new , Cluster ::getClusterAlias , ConcurrentHashMap ::new );
121- this .includeCCSMetadata = in .readBoolean ();
142+ if (in .getTransportVersion ().supports (EXECUTION_METADATA_VERSION )) {
143+ this .includeExecutionMetadata = in .readEnum (IncludeExecutionMetadata .class );
144+ } else {
145+ this .includeExecutionMetadata = in .readBoolean () ? IncludeExecutionMetadata .CCS_ONLY : IncludeExecutionMetadata .NEVER ;
146+ }
122147 this .isPartial = in .getTransportVersion ().supports (TransportVersions .V_8_18_0 ) ? in .readBoolean () : false ;
123148 this .skipOnFailurePredicate = Predicates .always ();
124149 this .relativeStart = null ;
@@ -136,7 +161,11 @@ public void writeTo(StreamOutput out) throws IOException {
136161 } else {
137162 out .writeCollection (Collections .emptyList ());
138163 }
139- out .writeBoolean (includeCCSMetadata );
164+ if (out .getTransportVersion ().supports (EXECUTION_METADATA_VERSION )) {
165+ out .writeEnum (includeExecutionMetadata );
166+ } else {
167+ out .writeBoolean (includeExecutionMetadata != IncludeExecutionMetadata .NEVER );
168+ }
140169 if (out .getTransportVersion ().supports (TransportVersions .V_8_18_0 )) {
141170 out .writeBoolean (isPartial );
142171 }
@@ -147,8 +176,13 @@ public void writeTo(StreamOutput out) throws IOException {
147176 assert inSubplan == false : "Should not be serializing execution info while in subplans" ;
148177 }
149178
179+ // this is still here for testing only, use includeExecutionMetadata() in production code
150180 public boolean includeCCSMetadata () {
151- return includeCCSMetadata ;
181+ return includeExecutionMetadata == IncludeExecutionMetadata .ALWAYS || includeExecutionMetadata == IncludeExecutionMetadata .CCS_ONLY ;
182+ }
183+
184+ public IncludeExecutionMetadata includeExecutionMetadata () {
185+ return includeExecutionMetadata ;
152186 }
153187
154188 public Predicate <String > skipOnFailurePredicate () {
@@ -232,7 +266,8 @@ public boolean isCrossClusterSearch() {
232266 * This is true on cross-cluster search with includeCCSMetadata=true or when there are partial failures.
233267 */
234268 public boolean hasMetadataToReport () {
235- return isCrossClusterSearch () && includeCCSMetadata
269+ return includeExecutionMetadata == IncludeExecutionMetadata .ALWAYS && clusterInfo .isEmpty () == false
270+ || isCrossClusterSearch () && includeExecutionMetadata == IncludeExecutionMetadata .CCS_ONLY
236271 || (isPartial && clusterInfo .values ().stream ().anyMatch (c -> c .getFailures ().isEmpty () == false ));
237272 }
238273
@@ -275,10 +310,11 @@ public Cluster swapCluster(String clusterAlias, BiFunction<String, Cluster, Clus
275310
276311 @ Override
277312 public Iterator <? extends ToXContent > toXContentChunked (ToXContent .Params params ) {
313+ // TODO remote this?
278314 if (clusterInfo .isEmpty ()) {
279315 return Collections .emptyIterator ();
280316 }
281- if (includeCCSMetadata == false ) {
317+ if (includeExecutionMetadata == IncludeExecutionMetadata . NEVER ) {
282318 // If includeCCSMetadata is false, the only reason we're here is partial failures, so just report them.
283319 return onlyFailuresToXContent ();
284320 }
0 commit comments