4747import org .elasticsearch .xpack .esql .expression .function .fulltext .MatchOperator ;
4848import org .elasticsearch .xpack .esql .expression .function .fulltext .MultiMatch ;
4949import org .elasticsearch .xpack .esql .expression .function .fulltext .QueryString ;
50+ import org .elasticsearch .xpack .esql .expression .function .grouping .Bucket ;
51+ import org .elasticsearch .xpack .esql .expression .function .scalar .convert .ToInteger ;
5052import org .elasticsearch .xpack .esql .expression .function .scalar .string .Concat ;
5153import org .elasticsearch .xpack .esql .expression .function .scalar .string .Substring ;
54+ import org .elasticsearch .xpack .esql .expression .predicate .operator .arithmetic .Add ;
5255import org .elasticsearch .xpack .esql .expression .predicate .operator .comparison .Equals ;
5356import org .elasticsearch .xpack .esql .expression .predicate .operator .comparison .GreaterThan ;
5457import org .elasticsearch .xpack .esql .index .EsIndex ;
8083import org .elasticsearch .xpack .esql .session .IndexResolver ;
8184
8285import java .io .IOException ;
86+ import java .time .Period ;
8387import java .util .ArrayList ;
8488import java .util .List ;
8589import java .util .Map ;
111115import static org .elasticsearch .xpack .esql .analysis .AnalyzerTestUtils .tsdbIndexResolution ;
112116import static org .elasticsearch .xpack .esql .core .tree .Source .EMPTY ;
113117import static org .elasticsearch .xpack .esql .core .type .DataType .DATETIME ;
118+ import static org .elasticsearch .xpack .esql .core .type .DataType .DATE_PERIOD ;
114119import static org .elasticsearch .xpack .esql .type .EsqlDataTypeConverter .dateTimeToString ;
115120import static org .hamcrest .Matchers .contains ;
116121import static org .hamcrest .Matchers .containsString ;
@@ -3778,7 +3783,7 @@ public void testResolveCompletionOutputField() {
37783783 assertThat (getAttributeByName (esRelation .output (), "description" ), not (equalTo (completion .targetField ())));
37793784 }
37803785
3781- public void testResolveAggregateWithGroupings () {
3786+ public void testResolveGroupingsBeforeResolvingImplicitReferencesToGroupings () {
37823787 var plan = analyze ("""
37833788 FROM test
37843789 | EVAL date = "2025-01-01"::datetime
@@ -3798,7 +3803,7 @@ public void testResolveAggregateWithGroupings() {
37983803 assertEquals ("d" , ra .name ());
37993804 List <Expression > groupings = agg .groupings ();
38003805 assertEquals (1 , groupings .size ());
3801- a = as (groupings .get (0 ), Alias .class ); // reference in grouping is resolved
3806+ a = as (groupings .get (0 ), Alias .class ); // reference in groupings is resolved
38023807 assertEquals ("d" , ra .name ());
38033808 Equals equals = as (a .child (), Equals .class );
38043809 ra = as (equals .left (), ReferenceAttribute .class );
@@ -3808,6 +3813,117 @@ public void testResolveAggregateWithGroupings() {
38083813 assertEquals (DATETIME , literal .dataType ());
38093814 }
38103815
3816+ public void testResolveGroupingsBeforeResolvingExplicitReferencesToGroupings () {
3817+ var plan = analyze ("""
3818+ FROM test
3819+ | EVAL date = "2025-01-01"::datetime
3820+ | STATS c = count(emp_no), x = d::int + 1 BY d = (date == "2025-01-01")
3821+ """ , "mapping-default.json" );
3822+
3823+ var limit = as (plan , Limit .class );
3824+ var agg = as (limit .child (), Aggregate .class );
3825+ var aggregates = agg .aggregates ();
3826+ assertThat (aggregates , hasSize (3 ));
3827+ Alias a = as (aggregates .get (0 ), Alias .class );
3828+ assertEquals ("c" , a .name ());
3829+ Count c = as (a .child (), Count .class );
3830+ FieldAttribute fa = as (c .field (), FieldAttribute .class );
3831+ assertEquals ("emp_no" , fa .name ());
3832+ a = as (aggregates .get (1 ), Alias .class ); // explicit reference to groupings is resolved
3833+ assertEquals ("x" , a .name ());
3834+ Add add = as (a .child (), Add .class );
3835+ ToInteger toInteger = as (add .left (), ToInteger .class );
3836+ ReferenceAttribute ra = as (toInteger .field (), ReferenceAttribute .class );
3837+ assertEquals ("d" , ra .name ());
3838+ ra = as (aggregates .get (2 ), ReferenceAttribute .class ); // reference in aggregates is resolved
3839+ assertEquals ("d" , ra .name ());
3840+ List <Expression > groupings = agg .groupings ();
3841+ assertEquals (1 , groupings .size ());
3842+ a = as (groupings .get (0 ), Alias .class ); // reference in groupings is resolved
3843+ assertEquals ("d" , ra .name ());
3844+ Equals equals = as (a .child (), Equals .class );
3845+ ra = as (equals .left (), ReferenceAttribute .class );
3846+ assertEquals ("date" , ra .name ());
3847+ Literal literal = as (equals .right (), Literal .class );
3848+ assertEquals ("2025-01-01T00:00:00.000Z" , dateTimeToString (Long .parseLong (literal .value ().toString ())));
3849+ assertEquals (DATETIME , literal .dataType ());
3850+ }
3851+
3852+ public void testBucketWithIntervalInStringInBothAggregationAndGrouping () {
3853+ var plan = analyze ("""
3854+ FROM test
3855+ | STATS c = count(emp_no), b = BUCKET(hire_date, "1 year") + 1 year BY yr = BUCKET(hire_date, "1 year")
3856+ """ , "mapping-default.json" );
3857+
3858+ var limit = as (plan , Limit .class );
3859+ var agg = as (limit .child (), Aggregate .class );
3860+ var aggregates = agg .aggregates ();
3861+ assertThat (aggregates , hasSize (3 ));
3862+ Alias a = as (aggregates .get (0 ), Alias .class );
3863+ assertEquals ("c" , a .name ());
3864+ Count c = as (a .child (), Count .class );
3865+ FieldAttribute fa = as (c .field (), FieldAttribute .class );
3866+ assertEquals ("emp_no" , fa .name ());
3867+ a = as (aggregates .get (1 ), Alias .class ); // explicit reference to groupings is resolved
3868+ assertEquals ("b" , a .name ());
3869+ Add add = as (a .child (), Add .class );
3870+ Bucket bucket = as (add .left (), Bucket .class );
3871+ fa = as (bucket .field (), FieldAttribute .class );
3872+ assertEquals ("hire_date" , fa .name ());
3873+ Literal literal = as (bucket .buckets (), Literal .class );
3874+ Literal oneYear = new Literal (EMPTY , Period .ofYears (1 ), DATE_PERIOD );
3875+ assertEquals (oneYear , literal );
3876+ literal = as (add .right (), Literal .class );
3877+ assertEquals (oneYear , literal );
3878+ ReferenceAttribute ra = as (aggregates .get (2 ), ReferenceAttribute .class ); // reference in aggregates is resolved
3879+ assertEquals ("yr" , ra .name ());
3880+ List <Expression > groupings = agg .groupings ();
3881+ assertEquals (1 , groupings .size ());
3882+ a = as (groupings .get (0 ), Alias .class ); // reference in groupings is resolved
3883+ assertEquals ("yr" , ra .name ());
3884+ bucket = as (a .child (), Bucket .class );
3885+ fa = as (bucket .field (), FieldAttribute .class );
3886+ assertEquals ("hire_date" , fa .name ());
3887+ literal = as (bucket .buckets (), Literal .class );
3888+ assertEquals (oneYear , literal );
3889+ }
3890+
3891+ public void testBucketWithIntervalInStringInGroupingReferencedInAggregation () {
3892+ var plan = analyze ("""
3893+ FROM test
3894+ | STATS c = count(emp_no), b = yr + 1 year BY yr = BUCKET(hire_date, "1 year")
3895+ """ , "mapping-default.json" );
3896+
3897+ var limit = as (plan , Limit .class );
3898+ var agg = as (limit .child (), Aggregate .class );
3899+ var aggregates = agg .aggregates ();
3900+ assertThat (aggregates , hasSize (3 ));
3901+ Alias a = as (aggregates .get (0 ), Alias .class );
3902+ assertEquals ("c" , a .name ());
3903+ Count c = as (a .child (), Count .class );
3904+ FieldAttribute fa = as (c .field (), FieldAttribute .class );
3905+ assertEquals ("emp_no" , fa .name ());
3906+ a = as (aggregates .get (1 ), Alias .class ); // explicit reference to groupings is resolved
3907+ assertEquals ("b" , a .name ());
3908+ Add add = as (a .child (), Add .class );
3909+ ReferenceAttribute ra = as (add .left (), ReferenceAttribute .class );
3910+ assertEquals ("yr" , ra .name ());
3911+ Literal oneYear = new Literal (EMPTY , Period .ofYears (1 ), DATE_PERIOD );
3912+ Literal literal = as (add .right (), Literal .class );
3913+ assertEquals (oneYear , literal );
3914+ ra = as (aggregates .get (2 ), ReferenceAttribute .class ); // reference in aggregates is resolved
3915+ assertEquals ("yr" , ra .name ());
3916+ List <Expression > groupings = agg .groupings ();
3917+ assertEquals (1 , groupings .size ());
3918+ a = as (groupings .get (0 ), Alias .class ); // reference in groupings is resolved
3919+ assertEquals ("yr" , ra .name ());
3920+ Bucket bucket = as (a .child (), Bucket .class );
3921+ fa = as (bucket .field (), FieldAttribute .class );
3922+ assertEquals ("hire_date" , fa .name ());
3923+ literal = as (bucket .buckets (), Literal .class );
3924+ assertEquals (oneYear , literal );
3925+ }
3926+
38113927 @ Override
38123928 protected IndexAnalyzers createDefaultIndexAnalyzers () {
38133929 return super .createDefaultIndexAnalyzers ();
0 commit comments