1414import org .elasticsearch .common .io .stream .StreamOutput ;
1515import org .elasticsearch .compute .operator .EvalOperator .ExpressionEvaluator ;
1616import org .elasticsearch .core .TimeValue ;
17- import org .elasticsearch .logging .LogManager ;
18- import org .elasticsearch .logging .Logger ;
1917import org .elasticsearch .xpack .esql .EsqlIllegalArgumentException ;
2018import org .elasticsearch .xpack .esql .capabilities .PostOptimizationVerificationAware ;
2119import org .elasticsearch .xpack .esql .common .Failures ;
2220import org .elasticsearch .xpack .esql .core .expression .Expression ;
23- import org .elasticsearch .xpack .esql .core .expression .FieldAttribute ;
2421import org .elasticsearch .xpack .esql .core .expression .FoldContext ;
2522import org .elasticsearch .xpack .esql .core .expression .Foldables ;
2623import org .elasticsearch .xpack .esql .core .expression .Literal ;
2724import org .elasticsearch .xpack .esql .core .expression .TypeResolutions ;
2825import org .elasticsearch .xpack .esql .core .tree .NodeInfo ;
2926import org .elasticsearch .xpack .esql .core .tree .Source ;
3027import org .elasticsearch .xpack .esql .core .type .DataType ;
31- import org .elasticsearch .xpack .esql .core .type .MultiTypeEsField ;
32- import org .elasticsearch .xpack .esql .expression .SurrogateExpression ;
28+ import org .elasticsearch .xpack .esql .expression .LocalSurrogateExpression ;
3329import org .elasticsearch .xpack .esql .expression .function .Example ;
3430import org .elasticsearch .xpack .esql .expression .function .FunctionInfo ;
3531import org .elasticsearch .xpack .esql .expression .function .FunctionType ;
3632import org .elasticsearch .xpack .esql .expression .function .Param ;
3733import org .elasticsearch .xpack .esql .expression .function .TwoOptionalArguments ;
3834import org .elasticsearch .xpack .esql .expression .function .scalar .date .DateTrunc ;
3935import org .elasticsearch .xpack .esql .expression .function .scalar .math .Floor ;
40- import org .elasticsearch .xpack .esql .expression .function .scalar .math .RoundTo ;
4136import org .elasticsearch .xpack .esql .expression .predicate .operator .arithmetic .Div ;
4237import org .elasticsearch .xpack .esql .expression .predicate .operator .arithmetic .Mul ;
4338import org .elasticsearch .xpack .esql .io .stream .PlanStreamInput ;
4439import org .elasticsearch .xpack .esql .stats .SearchStats ;
4540
4641import java .io .IOException ;
47- import java .time .ZoneId ;
48- import java .time .ZoneOffset ;
4942import java .util .ArrayList ;
50- import java .util .Arrays ;
5143import java .util .List ;
52- import java .util .stream .Collectors ;
5344
5445import static org .elasticsearch .common .logging .LoggerMessageFormat .format ;
5546import static org .elasticsearch .xpack .esql .core .expression .TypeResolutions .ParamOrdinal .FIRST ;
5849import static org .elasticsearch .xpack .esql .core .expression .TypeResolutions .ParamOrdinal .THIRD ;
5950import static org .elasticsearch .xpack .esql .core .expression .TypeResolutions .isNumeric ;
6051import static org .elasticsearch .xpack .esql .core .expression .TypeResolutions .isType ;
61- import static org .elasticsearch .xpack .esql .core .type .DataType .isDateTime ;
6252import static org .elasticsearch .xpack .esql .expression .Validations .isFoldable ;
53+ import static org .elasticsearch .xpack .esql .expression .function .scalar .date .DateTrunc .maybeSubstituteWithRoundTo ;
54+ import static org .elasticsearch .xpack .esql .session .Configuration .DEFAULT_TZ ;
6355import static org .elasticsearch .xpack .esql .type .EsqlDataTypeConverter .dateTimeToLong ;
64- import static org .elasticsearch .xpack .esql .type .EsqlDataTypeConverter .dateWithTypeToString ;
6556
6657/**
6758 * Splits dates and numbers into a given number of buckets. There are two ways to invoke
@@ -73,11 +64,9 @@ public class Bucket extends GroupingFunction.EvaluatableGroupingFunction
7364 implements
7465 PostOptimizationVerificationAware ,
7566 TwoOptionalArguments ,
76- SurrogateExpression {
67+ LocalSurrogateExpression {
7768 public static final NamedWriteableRegistry .Entry ENTRY = new NamedWriteableRegistry .Entry (Expression .class , "Bucket" , Bucket ::new );
7869
79- private static final Logger logger = LogManager .getLogger (Bucket .class );
80-
8170 // TODO maybe we should just cover the whole of representable dates here - like ten years, 100 years, 1000 years, all the way up.
8271 // That way you never end up with more than the target number of buckets.
8372 private static final Rounding LARGEST_HUMAN_DATE_ROUNDING = Rounding .builder (Rounding .DateTimeUnit .YEAR_OF_CENTURY ).build ();
@@ -101,8 +90,6 @@ public class Bucket extends GroupingFunction.EvaluatableGroupingFunction
10190 Rounding .builder (TimeValue .timeValueMillis (10 )).build (),
10291 Rounding .builder (TimeValue .timeValueMillis (1 )).build (), };
10392
104- private static final ZoneId DEFAULT_TZ = ZoneOffset .UTC ; // TODO: plug in the config
105-
10693 private final Expression field ;
10794 private final Expression buckets ;
10895 private final Expression from ;
@@ -510,41 +497,16 @@ public String toString() {
510497 return "Bucket{" + "field=" + field + ", buckets=" + buckets + ", from=" + from + ", to=" + to + '}' ;
511498 }
512499
513- @ Override
514- public Expression surrogate () {
515- return null ;
516- }
517-
518500 @ Override
519501 public Expression surrogate (SearchStats searchStats ) {
520- if (field () instanceof FieldAttribute fa && fa .field () instanceof MultiTypeEsField == false && isDateTime (fa .dataType ())) {
521- // Extract min/max from SearchStats
522- DataType fieldType = fa .dataType ();
523- FieldAttribute .FieldName fieldName = fa .fieldName ();
524- var min = searchStats .min (fieldName );
525- var max = searchStats .max (fieldName );
526- // If min/max is available create rounding with them
527- if (min instanceof Long minValue && max instanceof Long maxValue && buckets ().foldable ()) {
528- Rounding .Prepared rounding = getDateRounding (FoldContext .small (), minValue , maxValue );
529- long [] roundingPoints = rounding .fixedRoundingPoints ();
530- if (roundingPoints == null ) {
531- logger .trace (
532- "Fixed rounding point is null for field {}, minValue {} in string format {} and maxValue {} in string format {}" ,
533- fieldName ,
534- minValue ,
535- dateWithTypeToString (minValue , fieldType ),
536- maxValue ,
537- dateWithTypeToString (maxValue , fieldType )
538- );
539- return null ;
540- }
541- // Convert to round_to function with the roundings
542- List <Expression > points = Arrays .stream (roundingPoints )
543- .mapToObj (l -> new Literal (Source .EMPTY , l , fieldType ))
544- .collect (Collectors .toList ());
545- return new RoundTo (source (), field (), points );
546- }
547- }
548- return null ;
502+ // LocalSubstituteSurrogateExpressions should make sure this doesn't happen
503+ assert searchStats != null : "SearchStats cannot be null" ;
504+ return maybeSubstituteWithRoundTo (
505+ source (),
506+ field (),
507+ buckets (),
508+ searchStats ,
509+ (interval , minValue , maxValue ) -> getDateRounding (FoldContext .small (), minValue , maxValue )
510+ );
549511 }
550512}
0 commit comments