3535import org .elasticsearch .xpack .esql .expression .predicate .operator .arithmetic .Div ;
3636import org .elasticsearch .xpack .esql .expression .predicate .operator .arithmetic .Mul ;
3737import org .elasticsearch .xpack .esql .io .stream .PlanStreamInput ;
38+ import org .elasticsearch .xpack .esql .session .Configuration ;
3839
3940import java .io .IOException ;
41+ import java .time .ZoneId ;
4042import java .util .ArrayList ;
4143import java .util .List ;
44+ import java .util .Objects ;
4245
4346import static org .elasticsearch .common .logging .LoggerMessageFormat .format ;
4447import static org .elasticsearch .xpack .esql .core .expression .TypeResolutions .ParamOrdinal .FIRST ;
4851import static org .elasticsearch .xpack .esql .core .expression .TypeResolutions .isNumeric ;
4952import static org .elasticsearch .xpack .esql .core .expression .TypeResolutions .isType ;
5053import static org .elasticsearch .xpack .esql .expression .Validations .isFoldable ;
51- import static org .elasticsearch .xpack .esql .session .Configuration .DEFAULT_TZ ;
5254import static org .elasticsearch .xpack .esql .type .EsqlDataTypeConverter .dateTimeToLong ;
5355
5456/**
@@ -65,27 +67,34 @@ public class Bucket extends GroupingFunction.EvaluatableGroupingFunction
6567
6668 // TODO maybe we should just cover the whole of representable dates here - like ten years, 100 years, 1000 years, all the way up.
6769 // That way you never end up with more than the target number of buckets.
68- private static final Rounding LARGEST_HUMAN_DATE_ROUNDING = Rounding .builder (Rounding .DateTimeUnit .YEAR_OF_CENTURY ).build ();
69- private static final Rounding [] HUMAN_DATE_ROUNDINGS = new Rounding [] {
70- Rounding .builder (Rounding .DateTimeUnit .MONTH_OF_YEAR ).build (),
71- Rounding .builder (Rounding .DateTimeUnit .WEEK_OF_WEEKYEAR ).build (),
72- Rounding .builder (Rounding .DateTimeUnit .DAY_OF_MONTH ).build (),
73- Rounding .builder (TimeValue .timeValueHours (12 )).build (),
74- Rounding .builder (TimeValue .timeValueHours (3 )).build (),
75- Rounding .builder (TimeValue .timeValueHours (1 )).build (),
76- Rounding .builder (TimeValue .timeValueMinutes (30 )).build (),
77- Rounding .builder (TimeValue .timeValueMinutes (10 )).build (),
78- Rounding .builder (TimeValue .timeValueMinutes (5 )).build (),
79- Rounding .builder (TimeValue .timeValueMinutes (1 )).build (),
80- Rounding .builder (TimeValue .timeValueSeconds (30 )).build (),
81- Rounding .builder (TimeValue .timeValueSeconds (10 )).build (),
82- Rounding .builder (TimeValue .timeValueSeconds (5 )).build (),
83- Rounding .builder (TimeValue .timeValueSeconds (1 )).build (),
84- Rounding .builder (TimeValue .timeValueMillis (100 )).build (),
85- Rounding .builder (TimeValue .timeValueMillis (50 )).build (),
86- Rounding .builder (TimeValue .timeValueMillis (10 )).build (),
87- Rounding .builder (TimeValue .timeValueMillis (1 )).build (), };
88-
70+ private static final Rounding .Builder LARGEST_HUMAN_DATE_ROUNDING = Rounding .builder (Rounding .DateTimeUnit .YEAR_OF_CENTURY );
71+ private static final Rounding .Builder [] HUMAN_DATE_ROUNDINGS = new Rounding .Builder [] {
72+ Rounding .builder (Rounding .DateTimeUnit .MONTH_OF_YEAR ),
73+ Rounding .builder (Rounding .DateTimeUnit .WEEK_OF_WEEKYEAR ),
74+ Rounding .builder (Rounding .DateTimeUnit .DAY_OF_MONTH ),
75+ Rounding .builder (TimeValue .timeValueHours (12 )),
76+ Rounding .builder (TimeValue .timeValueHours (3 )),
77+ Rounding .builder (TimeValue .timeValueHours (1 )),
78+ Rounding .builder (TimeValue .timeValueMinutes (30 )),
79+ Rounding .builder (TimeValue .timeValueMinutes (10 )),
80+ Rounding .builder (TimeValue .timeValueMinutes (5 )),
81+ Rounding .builder (TimeValue .timeValueMinutes (1 )),
82+ Rounding .builder (TimeValue .timeValueSeconds (30 )),
83+ Rounding .builder (TimeValue .timeValueSeconds (10 )),
84+ Rounding .builder (TimeValue .timeValueSeconds (5 )),
85+ Rounding .builder (TimeValue .timeValueSeconds (1 )),
86+ Rounding .builder (TimeValue .timeValueMillis (100 )),
87+ Rounding .builder (TimeValue .timeValueMillis (50 )),
88+ Rounding .builder (TimeValue .timeValueMillis (10 )),
89+ Rounding .builder (TimeValue .timeValueMillis (1 )), };
90+
91+ /*
92+ * As Bucket already extends GroupingFunction, it can't extend EsqlConfigurationFunction, so we had to replicate here:
93+ * - The Configuration field
94+ * - HashCode
95+ * - Equals
96+ */
97+ private final Configuration configuration ;
8998 private final Expression field ;
9099 private final Expression buckets ;
91100 private final Expression from ;
@@ -208,13 +217,15 @@ public Bucket(
208217 type = { "integer" , "long" , "double" , "date" , "keyword" , "text" },
209218 optional = true ,
210219 description = "End of the range. Can be a number, a date or a date expressed as a string."
211- ) Expression to
220+ ) Expression to ,
221+ Configuration configuration
212222 ) {
213223 super (source , fields (field , buckets , from , to ));
214224 this .field = field ;
215225 this .buckets = buckets ;
216226 this .from = from ;
217227 this .to = to ;
228+ this .configuration = configuration ;
218229 }
219230
220231 private Bucket (StreamInput in ) throws IOException {
@@ -223,10 +234,15 @@ private Bucket(StreamInput in) throws IOException {
223234 in .readNamedWriteable (Expression .class ),
224235 in .readNamedWriteable (Expression .class ),
225236 in .readOptionalNamedWriteable (Expression .class ),
226- in .readOptionalNamedWriteable (Expression .class )
237+ in .readOptionalNamedWriteable (Expression .class ),
238+ ((PlanStreamInput ) in ).configuration ()
227239 );
228240 }
229241
242+ public Configuration configuration () {
243+ return configuration ;
244+ }
245+
230246 private static List <Expression > fields (Expression field , Expression buckets , Expression from , Expression to ) {
231247 List <Expression > list = new ArrayList <>(4 );
232248 list .add (field );
@@ -308,19 +324,20 @@ public Rounding.Prepared getDateRounding(FoldContext foldContext, Long min, Long
308324 long f = foldToLong (foldContext , from );
309325 long t = foldToLong (foldContext , to );
310326 if (min != null && max != null ) {
311- return new DateRoundingPicker (b , f , t ).pickRounding ().prepare (min , max );
327+ return new DateRoundingPicker (b , f , t , configuration . zoneId () ).pickRounding ().prepare (min , max );
312328 }
313- return new DateRoundingPicker (b , f , t ).pickRounding ().prepareForUnknown ();
329+ return new DateRoundingPicker (b , f , t , configuration . zoneId () ).pickRounding ().prepareForUnknown ();
314330 } else {
315331 assert DataType .isTemporalAmount (buckets .dataType ()) : "Unexpected span data type [" + buckets .dataType () + "]" ;
316- return DateTrunc .createRounding (buckets .fold (foldContext ), DEFAULT_TZ , min , max );
332+ return DateTrunc .createRounding (buckets .fold (foldContext ), configuration . zoneId () , min , max );
317333 }
318334 }
319335
320- private record DateRoundingPicker (int buckets , long from , long to ) {
336+ private record DateRoundingPicker (int buckets , long from , long to , ZoneId zoneId ) {
321337 Rounding pickRounding () {
322- Rounding prev = LARGEST_HUMAN_DATE_ROUNDING ;
323- for (Rounding r : HUMAN_DATE_ROUNDINGS ) {
338+ Rounding prev = LARGEST_HUMAN_DATE_ROUNDING .timeZone (zoneId ).build ();
339+ for (Rounding .Builder builder : HUMAN_DATE_ROUNDINGS ) {
340+ Rounding r = builder .timeZone (zoneId ).build ();
324341 if (roundingIsOk (r )) {
325342 prev = r ;
326343 } else {
@@ -464,12 +481,12 @@ public DataType dataType() {
464481 public Expression replaceChildren (List <Expression > newChildren ) {
465482 Expression from = newChildren .size () > 2 ? newChildren .get (2 ) : null ;
466483 Expression to = newChildren .size () > 3 ? newChildren .get (3 ) : null ;
467- return new Bucket (source (), newChildren .get (0 ), newChildren .get (1 ), from , to );
484+ return new Bucket (source (), newChildren .get (0 ), newChildren .get (1 ), from , to , configuration );
468485 }
469486
470487 @ Override
471488 protected NodeInfo <? extends Expression > info () {
472- return NodeInfo .create (this , Bucket ::new , field , buckets , from , to );
489+ return NodeInfo .create (this , Bucket ::new , field , buckets , from , to , configuration );
473490 }
474491
475492 public Expression field () {
@@ -492,4 +509,19 @@ public Expression to() {
492509 public String toString () {
493510 return "Bucket{" + "field=" + field + ", buckets=" + buckets + ", from=" + from + ", to=" + to + '}' ;
494511 }
512+
513+ @ Override
514+ public int hashCode () {
515+ return Objects .hash (getClass (), children (), configuration );
516+ }
517+
518+ @ Override
519+ public boolean equals (Object obj ) {
520+ if (super .equals (obj ) == false ) {
521+ return false ;
522+ }
523+ Bucket other = (Bucket ) obj ;
524+
525+ return configuration .equals (other .configuration );
526+ }
495527}
0 commit comments