77
88package org .elasticsearch .xpack .esql .plan .physical ;
99
10- import org .elasticsearch .common .Strings ;
1110import org .elasticsearch .common .io .stream .NamedWriteableRegistry ;
1211import org .elasticsearch .common .io .stream .StreamInput ;
1312import org .elasticsearch .common .io .stream .StreamOutput ;
@@ -53,7 +52,6 @@ public class EsQueryExec extends LeafExec implements EstimatesRowSize {
5352 private final IndexMode indexMode ;
5453 private final Map <String , IndexMode > indexNameWithModes ;
5554 private final List <Attribute > attrs ;
56- private final QueryBuilder query ;
5755 private final Expression limit ;
5856 private final List <Sort > sorts ;
5957
@@ -64,8 +62,12 @@ public class EsQueryExec extends LeafExec implements EstimatesRowSize {
6462 private final Integer estimatedRowSize ;
6563
6664 /**
67- * queryBuilderAndTags is build on data node from the {@code RoundTo} function by {@code PushRoundToToSource} rule.
68- * It will be used by {@code sourcePhysicalOperation} to create {@code LuceneSliceQueue.QueryAndTags}
65+ * queryBuilderAndTags may contain one or multiple {@code QueryBuilder}s, it is built on data node.
66+ * If there is one {@code RoundTo} function in the query plan, {@code ReplaceRoundToWithQueryAndTags} rule will build multiple
67+ * {@code QueryBuilder}s in the list, otherwise it is expected to contain only one {@code QueryBuilder} and its tag is
68+ * null.
69+ * It will be used by {@code EsPhysicalOperationProviders}.{@code sourcePhysicalOperation} to create
70+ * {@code LuceneSliceQueue.QueryAndTags}
6971 */
7072 private final List <QueryBuilderAndTags > queryBuilderAndTags ;
7173
@@ -88,7 +90,7 @@ public SortBuilder<?> sortBuilder() {
8890 }
8991
9092 private static FieldSort readFrom (StreamInput in ) throws IOException {
91- return new EsQueryExec . FieldSort (
93+ return new FieldSort (
9294 FieldAttribute .readFrom (in ),
9395 in .readEnum (Order .OrderDirection .class ),
9496 in .readEnum (Order .NullsPosition .class )
@@ -118,7 +120,12 @@ public FieldAttribute field() {
118120 }
119121 }
120122
121- public record QueryBuilderAndTags (QueryBuilder query , List <Object > tags ) {};
123+ public record QueryBuilderAndTags (QueryBuilder query , List <Object > tags ) {
124+ @ Override
125+ public String toString () {
126+ return "QueryBuilderAndTags{" + "queryBuilder=[" + query + "], tags=" + tags .toString () + "}" ;
127+ }
128+ };
122129
123130 public EsQueryExec (
124131 Source source ,
@@ -128,21 +135,17 @@ public EsQueryExec(
128135 List <Attribute > attributes ,
129136 QueryBuilder query
130137 ) {
131- this (source , indexPattern , indexMode , indexNameWithModes , attributes , query , null , null , null , null );
132- }
133-
134- public EsQueryExec (
135- Source source ,
136- String indexPattern ,
137- IndexMode indexMode ,
138- Map <String , IndexMode > indexNameWithModes ,
139- List <Attribute > attrs ,
140- QueryBuilder query ,
141- Expression limit ,
142- List <Sort > sorts ,
143- Integer estimatedRowSize
144- ) {
145- this (source , indexPattern , indexMode , indexNameWithModes , attrs , query , limit , sorts , estimatedRowSize , null );
138+ this (
139+ source ,
140+ indexPattern ,
141+ indexMode ,
142+ indexNameWithModes ,
143+ attributes ,
144+ null ,
145+ null ,
146+ null ,
147+ List .of (new QueryBuilderAndTags (query , List .of ()))
148+ );
146149 }
147150
148151 public EsQueryExec (
@@ -151,7 +154,6 @@ public EsQueryExec(
151154 IndexMode indexMode ,
152155 Map <String , IndexMode > indexNameWithModes ,
153156 List <Attribute > attrs ,
154- QueryBuilder query ,
155157 Expression limit ,
156158 List <Sort > sorts ,
157159 Integer estimatedRowSize ,
@@ -162,10 +164,10 @@ public EsQueryExec(
162164 this .indexMode = indexMode ;
163165 this .indexNameWithModes = indexNameWithModes ;
164166 this .attrs = attrs ;
165- this .query = query ;
166167 this .limit = limit ;
167168 this .sorts = sorts ;
168169 this .estimatedRowSize = estimatedRowSize ;
170+ // cannot keep the ctor with QueryBuilder as it has the same number of arguments as this ctor, EsqlNodeSubclassTests will fail
169171 this .queryBuilderAndTags = queryBuilderAndTags ;
170172 }
171173
@@ -193,7 +195,17 @@ private static EsQueryExec readFrom(StreamInput in) throws IOException {
193195 var rowSize = in .readOptionalVInt ();
194196 // Ignore sorts from the old serialization format
195197 // queryBuilderAndTags is built on data node, serialization is not needed
196- return new EsQueryExec (source , indexPattern , indexMode , indexNameWithModes , attrs , query , limit , NO_SORTS , rowSize , null );
198+ return new EsQueryExec (
199+ source ,
200+ indexPattern ,
201+ indexMode ,
202+ indexNameWithModes ,
203+ attrs ,
204+ limit ,
205+ NO_SORTS ,
206+ rowSize ,
207+ List .of (new QueryBuilderAndTags (query , List .of ()))
208+ );
197209 }
198210
199211 private static Sort readSort (StreamInput in ) throws IOException {
@@ -215,7 +227,7 @@ public void writeTo(StreamOutput out) throws IOException {
215227 }
216228 EsRelation .writeIndexMode (out , indexMode ());
217229 out .writeNamedWriteableCollection (output ());
218- out .writeOptionalNamedWriteable (query ());
230+ out .writeOptionalNamedWriteable (query ()); // TODO Do we ever serialize EsQueryExec or query() ?
219231 out .writeOptionalNamedWriteable (limit ());
220232 out .writeOptionalCollection (NO_SORTS , EsQueryExec ::writeSort );
221233 out .writeOptionalVInt (estimatedRowSize ());
@@ -249,7 +261,6 @@ protected NodeInfo<EsQueryExec> info() {
249261 indexMode ,
250262 indexNameWithModes ,
251263 attrs ,
252- query ,
253264 limit ,
254265 sorts ,
255266 estimatedRowSize ,
@@ -269,8 +280,13 @@ public Map<String, IndexMode> indexNameWithModes() {
269280 return indexNameWithModes ;
270281 }
271282
283+ /**
284+ * query is merged into queryBuilderAndTags, keep this method as it is called by too many places.
285+ * If this method is called, the caller looks for the original queryBuilder, before {@code ReplaceRoundToWithQueryAndTags} converts it
286+ * to multiple queries with tags.
287+ */
272288 public QueryBuilder query () {
273- return query ;
289+ return queryWithoutTag () ;
274290 }
275291
276292 @ Override
@@ -312,7 +328,7 @@ public PhysicalPlan estimateRowSize(State state) {
312328 }
313329 return Objects .equals (this .estimatedRowSize , size )
314330 ? this
315- : new EsQueryExec (source (), indexPattern , indexMode , indexNameWithModes , attrs , query , limit , sorts , size , queryBuilderAndTags );
331+ : new EsQueryExec (source (), indexPattern , indexMode , indexNameWithModes , attrs , limit , sorts , size , queryBuilderAndTags );
316332 }
317333
318334 public EsQueryExec withLimit (Expression limit ) {
@@ -324,7 +340,6 @@ public EsQueryExec withLimit(Expression limit) {
324340 indexMode ,
325341 indexNameWithModes ,
326342 attrs ,
327- query ,
328343 limit ,
329344 sorts ,
330345 estimatedRowSize ,
@@ -349,43 +364,73 @@ public EsQueryExec withSorts(List<Sort> sorts) {
349364 indexMode ,
350365 indexNameWithModes ,
351366 attrs ,
352- query ,
353367 limit ,
354368 sorts ,
355369 estimatedRowSize ,
356370 queryBuilderAndTags
357371 );
358372 }
359373
374+ /**
375+ * query is merged into queryBuilderAndTags, keep this method as it is called by too many places.
376+ * If this method is called, the caller looks for the original queryBuilder, before {@code ReplaceRoundToWithQueryAndTags} converts it
377+ * to multiple queries with tags.
378+ */
360379 public EsQueryExec withQuery (QueryBuilder query ) {
361- return Objects .equals (this .query , query )
380+ QueryBuilder thisQuery = queryWithoutTag ();
381+ return Objects .equals (thisQuery , query )
362382 ? this
363383 : new EsQueryExec (
364384 source (),
365385 indexPattern ,
366386 indexMode ,
367387 indexNameWithModes ,
368388 attrs ,
369- query ,
370389 limit ,
371390 sorts ,
372391 estimatedRowSize ,
373- queryBuilderAndTags
392+ List . of ( new QueryBuilderAndTags ( query , List . of ()))
374393 );
375394 }
376395
377396 public List <QueryBuilderAndTags > queryBuilderAndTags () {
378397 return queryBuilderAndTags ;
379398 }
380399
400+ /**
401+ * Returns the original queryBuilder before {@code ReplaceRoundToWithQueryAndTags} converts it to multiple queryBuilder with tags.
402+ * If we reach here, the caller is looking for the original query before the rule converts it. If there are multiple queries in
403+ * queryBuilderAndTags or if the single query in queryBuilderAndTags already has a tag, that means
404+ * {@code ReplaceRoundToWithQueryAndTags} already applied to the original query, the original query cannot be retrieved any more,
405+ * exception will be thrown.
406+ */
407+ private QueryBuilder queryWithoutTag () {
408+ QueryBuilder queryWithoutTag ;
409+ if (queryBuilderAndTags == null || queryBuilderAndTags .isEmpty ()) {
410+ return null ;
411+ } else if (queryBuilderAndTags .size () == 1 ) {
412+ QueryBuilderAndTags firstQuery = this .queryBuilderAndTags .get (0 );
413+ if (firstQuery .tags ().isEmpty ()) {
414+ queryWithoutTag = firstQuery .query ();
415+ } else {
416+ throw new UnsupportedOperationException ("query is converted to query with tags: " + "[" + firstQuery + "]" );
417+ }
418+ } else {
419+ throw new UnsupportedOperationException (
420+ "query is converted to multiple queries and tags: " + "[" + this .queryBuilderAndTags + "]"
421+ );
422+ }
423+ return queryWithoutTag ;
424+ }
425+
381426 public boolean canSubstituteRoundToWithQueryBuilderAndTags () {
382427 // TimeSeriesSourceOperator and LuceneTopNSourceOperator do not support QueryAndTags
383428 return indexMode != IndexMode .TIME_SERIES && (sorts == null || sorts .isEmpty ());
384429 }
385430
386431 @ Override
387432 public int hashCode () {
388- return Objects .hash (indexPattern , indexMode , indexNameWithModes , attrs , query , limit , sorts , queryBuilderAndTags );
433+ return Objects .hash (indexPattern , indexMode , indexNameWithModes , attrs , limit , sorts , queryBuilderAndTags );
389434 }
390435
391436 @ Override
@@ -403,7 +448,6 @@ public boolean equals(Object obj) {
403448 && Objects .equals (indexMode , other .indexMode )
404449 && Objects .equals (indexNameWithModes , other .indexNameWithModes )
405450 && Objects .equals (attrs , other .attrs )
406- && Objects .equals (query , other .query )
407451 && Objects .equals (limit , other .limit )
408452 && Objects .equals (sorts , other .sorts )
409453 && Objects .equals (estimatedRowSize , other .estimatedRowSize )
@@ -419,9 +463,6 @@ public String nodeString() {
419463 + "indexMode["
420464 + indexMode
421465 + "], "
422- + "query["
423- + (query != null ? Strings .toString (query , false , true ) : "" )
424- + "]"
425466 + NodeUtils .limitedToString (attrs )
426467 + ", limit["
427468 + (limit != null ? limit .toString () : "" )
@@ -430,7 +471,7 @@ public String nodeString() {
430471 + "] estimatedRowSize["
431472 + estimatedRowSize
432473 + "] queryBuilderAndTags ["
433- + (queryBuilderAndTags != null ? queryBuilderAndTags . toString () : "" )
474+ + (queryBuilderAndTags != null ? queryBuilderAndTags : "" )
434475 + "]" ;
435476 }
436477
0 commit comments