|
47 | 47 | import org.elasticsearch.xpack.esql.expression.function.fulltext.MatchOperator; |
48 | 48 | import org.elasticsearch.xpack.esql.expression.function.fulltext.MultiMatch; |
49 | 49 | import 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; |
50 | 52 | import org.elasticsearch.xpack.esql.expression.function.scalar.string.Concat; |
51 | 53 | import org.elasticsearch.xpack.esql.expression.function.scalar.string.Substring; |
| 54 | +import org.elasticsearch.xpack.esql.expression.predicate.operator.arithmetic.Add; |
52 | 55 | import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.Equals; |
53 | 56 | import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.GreaterThan; |
54 | 57 | import org.elasticsearch.xpack.esql.index.EsIndex; |
|
80 | 83 | import org.elasticsearch.xpack.esql.session.IndexResolver; |
81 | 84 |
|
82 | 85 | import java.io.IOException; |
| 86 | +import java.time.Period; |
83 | 87 | import java.util.ArrayList; |
84 | 88 | import java.util.List; |
85 | 89 | import java.util.Map; |
|
110 | 114 | import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.randomValueOtherThanTest; |
111 | 115 | import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.tsdbIndexResolution; |
112 | 116 | import static org.elasticsearch.xpack.esql.core.tree.Source.EMPTY; |
| 117 | +import static org.elasticsearch.xpack.esql.core.type.DataType.DATETIME; |
| 118 | +import static org.elasticsearch.xpack.esql.core.type.DataType.DATE_PERIOD; |
| 119 | +import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.dateTimeToString; |
113 | 120 | import static org.hamcrest.Matchers.contains; |
114 | 121 | import static org.hamcrest.Matchers.containsString; |
115 | 122 | import static org.hamcrest.Matchers.empty; |
@@ -368,7 +375,7 @@ public void testNoProjection() { |
368 | 375 | DataType.INTEGER, |
369 | 376 | DataType.KEYWORD, |
370 | 377 | DataType.TEXT, |
371 | | - DataType.DATETIME, |
| 378 | + DATETIME, |
372 | 379 | DataType.TEXT, |
373 | 380 | DataType.KEYWORD, |
374 | 381 | DataType.INTEGER, |
@@ -3789,6 +3796,147 @@ public void testResolveCompletionOutputField() { |
3789 | 3796 | assertThat(getAttributeByName(esRelation.output(), "description"), not(equalTo(completion.targetField()))); |
3790 | 3797 | } |
3791 | 3798 |
|
| 3799 | + public void testResolveGroupingsBeforeResolvingImplicitReferencesToGroupings() { |
| 3800 | + var plan = analyze(""" |
| 3801 | + FROM test |
| 3802 | + | EVAL date = "2025-01-01"::datetime |
| 3803 | + | STATS c = count(emp_no) BY d = (date == "2025-01-01") |
| 3804 | + """, "mapping-default.json"); |
| 3805 | + |
| 3806 | + var limit = as(plan, Limit.class); |
| 3807 | + var agg = as(limit.child(), Aggregate.class); |
| 3808 | + var aggregates = agg.aggregates(); |
| 3809 | + assertThat(aggregates, hasSize(2)); |
| 3810 | + Alias a = as(aggregates.get(0), Alias.class); |
| 3811 | + assertEquals("c", a.name()); |
| 3812 | + Count c = as(a.child(), Count.class); |
| 3813 | + FieldAttribute fa = as(c.field(), FieldAttribute.class); |
| 3814 | + assertEquals("emp_no", fa.name()); |
| 3815 | + ReferenceAttribute ra = as(aggregates.get(1), ReferenceAttribute.class); // reference in aggregates is resolved |
| 3816 | + assertEquals("d", ra.name()); |
| 3817 | + List<Expression> groupings = agg.groupings(); |
| 3818 | + assertEquals(1, groupings.size()); |
| 3819 | + a = as(groupings.get(0), Alias.class); // reference in groupings is resolved |
| 3820 | + assertEquals("d", ra.name()); |
| 3821 | + Equals equals = as(a.child(), Equals.class); |
| 3822 | + ra = as(equals.left(), ReferenceAttribute.class); |
| 3823 | + assertEquals("date", ra.name()); |
| 3824 | + Literal literal = as(equals.right(), Literal.class); |
| 3825 | + assertEquals("2025-01-01T00:00:00.000Z", dateTimeToString(Long.parseLong(literal.value().toString()))); |
| 3826 | + assertEquals(DATETIME, literal.dataType()); |
| 3827 | + } |
| 3828 | + |
| 3829 | + public void testResolveGroupingsBeforeResolvingExplicitReferencesToGroupings() { |
| 3830 | + var plan = analyze(""" |
| 3831 | + FROM test |
| 3832 | + | EVAL date = "2025-01-01"::datetime |
| 3833 | + | STATS c = count(emp_no), x = d::int + 1 BY d = (date == "2025-01-01") |
| 3834 | + """, "mapping-default.json"); |
| 3835 | + |
| 3836 | + var limit = as(plan, Limit.class); |
| 3837 | + var agg = as(limit.child(), Aggregate.class); |
| 3838 | + var aggregates = agg.aggregates(); |
| 3839 | + assertThat(aggregates, hasSize(3)); |
| 3840 | + Alias a = as(aggregates.get(0), Alias.class); |
| 3841 | + assertEquals("c", a.name()); |
| 3842 | + Count c = as(a.child(), Count.class); |
| 3843 | + FieldAttribute fa = as(c.field(), FieldAttribute.class); |
| 3844 | + assertEquals("emp_no", fa.name()); |
| 3845 | + a = as(aggregates.get(1), Alias.class); // explicit reference to groupings is resolved |
| 3846 | + assertEquals("x", a.name()); |
| 3847 | + Add add = as(a.child(), Add.class); |
| 3848 | + ToInteger toInteger = as(add.left(), ToInteger.class); |
| 3849 | + ReferenceAttribute ra = as(toInteger.field(), ReferenceAttribute.class); |
| 3850 | + assertEquals("d", ra.name()); |
| 3851 | + ra = as(aggregates.get(2), ReferenceAttribute.class); // reference in aggregates is resolved |
| 3852 | + assertEquals("d", ra.name()); |
| 3853 | + List<Expression> groupings = agg.groupings(); |
| 3854 | + assertEquals(1, groupings.size()); |
| 3855 | + a = as(groupings.get(0), Alias.class); // reference in groupings is resolved |
| 3856 | + assertEquals("d", ra.name()); |
| 3857 | + Equals equals = as(a.child(), Equals.class); |
| 3858 | + ra = as(equals.left(), ReferenceAttribute.class); |
| 3859 | + assertEquals("date", ra.name()); |
| 3860 | + Literal literal = as(equals.right(), Literal.class); |
| 3861 | + assertEquals("2025-01-01T00:00:00.000Z", dateTimeToString(Long.parseLong(literal.value().toString()))); |
| 3862 | + assertEquals(DATETIME, literal.dataType()); |
| 3863 | + } |
| 3864 | + |
| 3865 | + public void testBucketWithIntervalInStringInBothAggregationAndGrouping() { |
| 3866 | + var plan = analyze(""" |
| 3867 | + FROM test |
| 3868 | + | STATS c = count(emp_no), b = BUCKET(hire_date, "1 year") + 1 year BY yr = BUCKET(hire_date, "1 year") |
| 3869 | + """, "mapping-default.json"); |
| 3870 | + |
| 3871 | + var limit = as(plan, Limit.class); |
| 3872 | + var agg = as(limit.child(), Aggregate.class); |
| 3873 | + var aggregates = agg.aggregates(); |
| 3874 | + assertThat(aggregates, hasSize(3)); |
| 3875 | + Alias a = as(aggregates.get(0), Alias.class); |
| 3876 | + assertEquals("c", a.name()); |
| 3877 | + Count c = as(a.child(), Count.class); |
| 3878 | + FieldAttribute fa = as(c.field(), FieldAttribute.class); |
| 3879 | + assertEquals("emp_no", fa.name()); |
| 3880 | + a = as(aggregates.get(1), Alias.class); // explicit reference to groupings is resolved |
| 3881 | + assertEquals("b", a.name()); |
| 3882 | + Add add = as(a.child(), Add.class); |
| 3883 | + Bucket bucket = as(add.left(), Bucket.class); |
| 3884 | + fa = as(bucket.field(), FieldAttribute.class); |
| 3885 | + assertEquals("hire_date", fa.name()); |
| 3886 | + Literal literal = as(bucket.buckets(), Literal.class); |
| 3887 | + Literal oneYear = new Literal(EMPTY, Period.ofYears(1), DATE_PERIOD); |
| 3888 | + assertEquals(oneYear, literal); |
| 3889 | + literal = as(add.right(), Literal.class); |
| 3890 | + assertEquals(oneYear, literal); |
| 3891 | + ReferenceAttribute ra = as(aggregates.get(2), ReferenceAttribute.class); // reference in aggregates is resolved |
| 3892 | + assertEquals("yr", ra.name()); |
| 3893 | + List<Expression> groupings = agg.groupings(); |
| 3894 | + assertEquals(1, groupings.size()); |
| 3895 | + a = as(groupings.get(0), Alias.class); // reference in groupings is resolved |
| 3896 | + assertEquals("yr", ra.name()); |
| 3897 | + bucket = as(a.child(), Bucket.class); |
| 3898 | + fa = as(bucket.field(), FieldAttribute.class); |
| 3899 | + assertEquals("hire_date", fa.name()); |
| 3900 | + literal = as(bucket.buckets(), Literal.class); |
| 3901 | + assertEquals(oneYear, literal); |
| 3902 | + } |
| 3903 | + |
| 3904 | + public void testBucketWithIntervalInStringInGroupingReferencedInAggregation() { |
| 3905 | + var plan = analyze(""" |
| 3906 | + FROM test |
| 3907 | + | STATS c = count(emp_no), b = yr + 1 year BY yr = BUCKET(hire_date, "1 year") |
| 3908 | + """, "mapping-default.json"); |
| 3909 | + |
| 3910 | + var limit = as(plan, Limit.class); |
| 3911 | + var agg = as(limit.child(), Aggregate.class); |
| 3912 | + var aggregates = agg.aggregates(); |
| 3913 | + assertThat(aggregates, hasSize(3)); |
| 3914 | + Alias a = as(aggregates.get(0), Alias.class); |
| 3915 | + assertEquals("c", a.name()); |
| 3916 | + Count c = as(a.child(), Count.class); |
| 3917 | + FieldAttribute fa = as(c.field(), FieldAttribute.class); |
| 3918 | + assertEquals("emp_no", fa.name()); |
| 3919 | + a = as(aggregates.get(1), Alias.class); // explicit reference to groupings is resolved |
| 3920 | + assertEquals("b", a.name()); |
| 3921 | + Add add = as(a.child(), Add.class); |
| 3922 | + ReferenceAttribute ra = as(add.left(), ReferenceAttribute.class); |
| 3923 | + assertEquals("yr", ra.name()); |
| 3924 | + Literal oneYear = new Literal(EMPTY, Period.ofYears(1), DATE_PERIOD); |
| 3925 | + Literal literal = as(add.right(), Literal.class); |
| 3926 | + assertEquals(oneYear, literal); |
| 3927 | + ra = as(aggregates.get(2), ReferenceAttribute.class); // reference in aggregates is resolved |
| 3928 | + assertEquals("yr", ra.name()); |
| 3929 | + List<Expression> groupings = agg.groupings(); |
| 3930 | + assertEquals(1, groupings.size()); |
| 3931 | + a = as(groupings.get(0), Alias.class); // reference in groupings is resolved |
| 3932 | + assertEquals("yr", ra.name()); |
| 3933 | + Bucket bucket = as(a.child(), Bucket.class); |
| 3934 | + fa = as(bucket.field(), FieldAttribute.class); |
| 3935 | + assertEquals("hire_date", fa.name()); |
| 3936 | + literal = as(bucket.buckets(), Literal.class); |
| 3937 | + assertEquals(oneYear, literal); |
| 3938 | + } |
| 3939 | + |
3792 | 3940 | @Override |
3793 | 3941 | protected IndexAnalyzers createDefaultIndexAnalyzers() { |
3794 | 3942 | return super.createDefaultIndexAnalyzers(); |
|
0 commit comments