1515import org .elasticsearch .index .query .MultiTermQueryBuilder ;
1616import org .elasticsearch .index .query .PrefixQueryBuilder ;
1717import org .elasticsearch .index .query .QueryBuilder ;
18+ import org .elasticsearch .index .query .RangeQueryBuilder ;
1819import org .elasticsearch .index .query .RegexpQueryBuilder ;
1920import org .elasticsearch .index .query .TermsQueryBuilder ;
2021import org .elasticsearch .index .query .WildcardQueryBuilder ;
4041import org .elasticsearch .xpack .esql .plan .physical .EsQueryExec ;
4142import org .elasticsearch .xpack .esql .plan .physical .EvalExec ;
4243import org .elasticsearch .xpack .esql .plan .physical .PhysicalPlan ;
44+ import org .elasticsearch .xpack .esql .querydsl .query .SingleValueQuery ;
45+ import org .elasticsearch .xpack .esql .stats .SearchStats ;
4346
4447import java .time .ZoneId ;
4548import java .util .ArrayList ;
@@ -286,6 +289,7 @@ protected PhysicalPlan rule(EvalExec evalExec, LocalPhysicalOptimizerContext ctx
286289 RoundTo roundTo = roundTos .get (0 );
287290 int count = roundTo .points ().size ();
288291 int roundingPointsUpperLimit = adjustedRoundingPointsThreshold (
292+ ctx .searchStats (),
289293 roundingPointsThreshold (ctx ),
290294 queryExec .query (),
291295 queryExec .indexMode ()
@@ -507,16 +511,16 @@ private int roundingPointsThreshold(LocalPhysicalOptimizerContext ctx) {
507511 * that the total number of clauses does not exceed the limit by too much. Some expensive queries count as more than one clause;
508512 * for example, a wildcard query counts as 5 clauses, and a terms query counts as the number of terms.
509513 */
510- static int adjustedRoundingPointsThreshold (int threshold , QueryBuilder query , IndexMode indexMode ) {
511- int clauses = estimateQueryClauses (query ) + 1 ;
514+ static int adjustedRoundingPointsThreshold (SearchStats stats , int threshold , QueryBuilder query , IndexMode indexMode ) {
515+ int clauses = estimateQueryClauses (stats , query ) + 1 ;
512516 if (indexMode == IndexMode .TIME_SERIES ) {
513517 // No doc partitioning for time_series sources; increase the threshold to trade overhead for parallelism.
514518 threshold *= 2 ;
515519 }
516520 return Math .ceilDiv (threshold , clauses );
517521 }
518522
519- static int estimateQueryClauses (QueryBuilder q ) {
523+ static int estimateQueryClauses (SearchStats stats , QueryBuilder q ) {
520524 if (q == null || q instanceof MatchAllQueryBuilder || q instanceof MatchNoneQueryBuilder ) {
521525 return 0 ;
522526 }
@@ -526,25 +530,33 @@ static int estimateQueryClauses(QueryBuilder q) {
526530 || q instanceof FuzzyQueryBuilder ) {
527531 return 5 ;
528532 }
533+ if (q instanceof RangeQueryBuilder r ) {
534+ // with points count 1, without count 3
535+ return stats .min (new FieldAttribute .FieldName (r .fieldName ())) != null ? 1 : 3 ;
536+ }
529537 if (q instanceof MultiTermQueryBuilder ) {
530538 return 3 ;
531539 }
532540 if (q instanceof TermsQueryBuilder terms && terms .values () != null ) {
533541 return terms .values ().size ();
534542 }
543+ if (q instanceof SingleValueQuery .Builder b ) {
544+ // ignore the single_value clause
545+ return Math .max (1 , estimateQueryClauses (stats , b .next ()));
546+ }
535547 if (q instanceof BoolQueryBuilder bq ) {
536548 int total = 0 ;
537549 for (var c : bq .filter ()) {
538- total += estimateQueryClauses (c );
550+ total += estimateQueryClauses (stats , c );
539551 }
540552 for (var c : bq .must ()) {
541- total += estimateQueryClauses (c );
553+ total += estimateQueryClauses (stats , c );
542554 }
543555 for (var c : bq .should ()) {
544- total += estimateQueryClauses (c );
556+ total += estimateQueryClauses (stats , c );
545557 }
546558 for (var c : bq .mustNot ()) {
547- total += Math .max (2 , estimateQueryClauses (c ));
559+ total += Math .max (2 , estimateQueryClauses (stats , c ));
548560 }
549561 return total ;
550562 }
0 commit comments