88package org .elasticsearch .xpack .esql .expression .function .grouping ;
99
1010import org .apache .lucene .util .BytesRef ;
11+ import org .elasticsearch .TransportVersion ;
1112import org .elasticsearch .common .Rounding ;
1213import org .elasticsearch .common .io .stream .NamedWriteableRegistry ;
1314import org .elasticsearch .common .io .stream .StreamInput ;
@@ -67,6 +68,7 @@ public class Bucket extends GroupingFunction.EvaluatableGroupingFunction
6768 TwoOptionalArguments ,
6869 ConfigurationFunction {
6970 public static final NamedWriteableRegistry .Entry ENTRY = new NamedWriteableRegistry .Entry (Expression .class , "Bucket" , Bucket ::new );
71+ public static final TransportVersion ESQL_BUCKET_OFFSET = TransportVersion .fromName ("esql_bucket_offset" );
7072
7173 // TODO maybe we should just cover the whole of representable dates here - like ten years, 100 years, 1000 years, all the way up.
7274 // That way you never end up with more than the target number of buckets.
@@ -113,6 +115,7 @@ public Rounding apply(ZoneId zoneId) {
113115 private final Expression buckets ;
114116 private final Expression from ;
115117 private final Expression to ;
118+ private final long offset ;
116119
117120 @ FunctionInfo (
118121 returnType = { "double" , "date" , "date_nanos" },
@@ -233,13 +236,26 @@ public Bucket(
233236 description = "End of the range. Can be a number, a date or a date expressed as a string."
234237 ) Expression to ,
235238 Configuration configuration
239+ ) {
240+ this (source , field , buckets , from , to , configuration , 0L );
241+ }
242+
243+ public Bucket (
244+ Source source ,
245+ Expression field ,
246+ Expression buckets ,
247+ Expression from ,
248+ Expression to ,
249+ Configuration configuration ,
250+ long offset
236251 ) {
237252 super (source , fields (field , buckets , from , to ));
238253 this .field = field ;
239254 this .buckets = buckets ;
240255 this .from = from ;
241256 this .to = to ;
242257 this .configuration = configuration ;
258+ this .offset = offset ;
243259 }
244260
245261 private Bucket (StreamInput in ) throws IOException {
@@ -249,7 +265,8 @@ private Bucket(StreamInput in) throws IOException {
249265 in .readNamedWriteable (Expression .class ),
250266 in .readOptionalNamedWriteable (Expression .class ),
251267 in .readOptionalNamedWriteable (Expression .class ),
252- ((PlanStreamInput ) in ).configuration ()
268+ ((PlanStreamInput ) in ).configuration (),
269+ in .getTransportVersion ().supports (ESQL_BUCKET_OFFSET ) ? in .readZLong () : 0L
253270 );
254271 }
255272
@@ -273,6 +290,16 @@ public void writeTo(StreamOutput out) throws IOException {
273290 out .writeNamedWriteable (buckets );
274291 out .writeOptionalNamedWriteable (from );
275292 out .writeOptionalNamedWriteable (to );
293+ TransportVersion transportVersion = out .getTransportVersion ();
294+ if (transportVersion .supports (ESQL_BUCKET_OFFSET )) {
295+ out .writeZLong (offset );
296+ } else if (offset != 0L ) {
297+ throw new EsqlIllegalArgumentException (
298+ "bucket with offset is not supported in peer node's version [{}]. Upgrade to version [{}] or newer." ,
299+ transportVersion ,
300+ ESQL_BUCKET_OFFSET
301+ );
302+ }
276303 }
277304
278305 @ Override
@@ -334,16 +361,16 @@ public Rounding.Prepared getDateRounding(FoldContext foldContext, Long min, Long
334361 long f = foldToLong (foldContext , from );
335362 long t = foldToLong (foldContext , to );
336363 if (min != null && max != null ) {
337- return new DateRoundingPicker (b , f , t , configuration .zoneId ()).pickRounding ().prepare (min , max );
364+ return new DateRoundingPicker (b , f , t , configuration .zoneId (), offset ).pickRounding ().prepare (min , max );
338365 }
339- return new DateRoundingPicker (b , f , t , configuration .zoneId ()).pickRounding ().prepareForUnknown ();
366+ return new DateRoundingPicker (b , f , t , configuration .zoneId (), offset ).pickRounding ().prepareForUnknown ();
340367 } else {
341368 assert DataType .isTemporalAmount (buckets .dataType ()) : "Unexpected span data type [" + buckets .dataType () + "]" ;
342- return DateTrunc .createRounding (buckets .fold (foldContext ), configuration .zoneId (), min , max );
369+ return DateTrunc .createRounding (buckets .fold (foldContext ), configuration .zoneId (), min , max , offset );
343370 }
344371 }
345372
346- private record DateRoundingPicker (int buckets , long from , long to , ZoneId zoneId ) {
373+ private record DateRoundingPicker (int buckets , long from , long to , ZoneId zoneId , long offset ) {
347374 Rounding pickRounding () {
348375 Rounding best = findLastOk (DAY_OF_MONTH_OR_FINER );
349376 if (best != null ) {
@@ -511,12 +538,12 @@ public DataType dataType() {
511538 public Expression replaceChildren (List <Expression > newChildren ) {
512539 Expression from = newChildren .size () > 2 ? newChildren .get (2 ) : null ;
513540 Expression to = newChildren .size () > 3 ? newChildren .get (3 ) : null ;
514- return new Bucket (source (), newChildren .get (0 ), newChildren .get (1 ), from , to , configuration );
541+ return new Bucket (source (), newChildren .get (0 ), newChildren .get (1 ), from , to , configuration , offset );
515542 }
516543
517544 @ Override
518545 protected NodeInfo <? extends Expression > info () {
519- return NodeInfo .create (this , Bucket ::new , field , buckets , from , to , configuration );
546+ return NodeInfo .create (this , Bucket ::new , field , buckets , from , to , configuration , offset );
520547 }
521548
522549 public Expression field () {
@@ -535,14 +562,18 @@ public Expression to() {
535562 return to ;
536563 }
537564
565+ public long offset () {
566+ return offset ;
567+ }
568+
538569 @ Override
539570 public String toString () {
540- return "Bucket{" + "field=" + field + ", buckets=" + buckets + ", from=" + from + ", to=" + to + '}' ;
571+ return "Bucket{" + "field=" + field + ", buckets=" + buckets + ", from=" + from + ", to=" + to + ", offset=" + offset + '}' ;
541572 }
542573
543574 @ Override
544575 public int hashCode () {
545- return Objects .hash (getClass (), children (), configuration );
576+ return Objects .hash (getClass (), children (), configuration , offset );
546577 }
547578
548579 @ Override
@@ -552,6 +583,6 @@ public boolean equals(Object obj) {
552583 }
553584 Bucket other = (Bucket ) obj ;
554585
555- return configuration .equals (other .configuration );
586+ return configuration .equals (other .configuration ) && offset == other . offset ;
556587 }
557588}
0 commit comments