|
28 | 28 | import org.elasticsearch.xpack.esql.core.expression.Attribute; |
29 | 29 | import org.elasticsearch.xpack.esql.core.expression.AttributeSet; |
30 | 30 | import org.elasticsearch.xpack.esql.core.expression.EntryExpression; |
| 31 | +import org.elasticsearch.xpack.esql.core.expression.Expression; |
31 | 32 | import org.elasticsearch.xpack.esql.core.expression.Expressions; |
32 | 33 | import org.elasticsearch.xpack.esql.core.expression.FieldAttribute; |
33 | 34 | import org.elasticsearch.xpack.esql.core.expression.Literal; |
|
44 | 45 | import org.elasticsearch.xpack.esql.expression.function.fulltext.Match; |
45 | 46 | import org.elasticsearch.xpack.esql.expression.function.fulltext.MatchOperator; |
46 | 47 | import org.elasticsearch.xpack.esql.expression.function.fulltext.QueryString; |
| 48 | +import org.elasticsearch.xpack.esql.expression.function.grouping.Bucket; |
| 49 | +import org.elasticsearch.xpack.esql.expression.function.scalar.convert.ToInteger; |
| 50 | +import org.elasticsearch.xpack.esql.expression.predicate.operator.arithmetic.Add; |
| 51 | +import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.Equals; |
47 | 52 | import org.elasticsearch.xpack.esql.index.EsIndex; |
48 | 53 | import org.elasticsearch.xpack.esql.index.IndexResolution; |
49 | 54 | import org.elasticsearch.xpack.esql.parser.ParsingException; |
|
66 | 71 |
|
67 | 72 | import java.io.IOException; |
68 | 73 | import java.io.InputStream; |
| 74 | +import java.time.Period; |
69 | 75 | import java.util.ArrayList; |
70 | 76 | import java.util.List; |
71 | 77 | import java.util.Map; |
|
92 | 98 | import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.loadMapping; |
93 | 99 | import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.tsdbIndexResolution; |
94 | 100 | import static org.elasticsearch.xpack.esql.core.tree.Source.EMPTY; |
| 101 | +import static org.elasticsearch.xpack.esql.core.type.DataType.DATETIME; |
| 102 | +import static org.elasticsearch.xpack.esql.core.type.DataType.DATE_PERIOD; |
| 103 | +import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.dateTimeToString; |
95 | 104 | import static org.hamcrest.Matchers.contains; |
96 | 105 | import static org.hamcrest.Matchers.containsString; |
97 | 106 | import static org.hamcrest.Matchers.equalTo; |
@@ -347,7 +356,7 @@ public void testNoProjection() { |
347 | 356 | DataType.INTEGER, |
348 | 357 | DataType.KEYWORD, |
349 | 358 | DataType.TEXT, |
350 | | - DataType.DATETIME, |
| 359 | + DATETIME, |
351 | 360 | DataType.TEXT, |
352 | 361 | DataType.KEYWORD, |
353 | 362 | DataType.INTEGER, |
@@ -2819,6 +2828,147 @@ public void testFunctionNamedParamsAsFunctionArgument1() { |
2819 | 2828 | assertEquals(DataType.DOUBLE, ee.dataType()); |
2820 | 2829 | } |
2821 | 2830 |
|
| 2831 | + public void testResolveGroupingsBeforeResolvingImplicitReferencesToGroupings() { |
| 2832 | + var plan = analyze(""" |
| 2833 | + FROM test |
| 2834 | + | EVAL date = "2025-01-01"::datetime |
| 2835 | + | STATS c = count(emp_no) BY d = (date == "2025-01-01") |
| 2836 | + """, "mapping-default.json"); |
| 2837 | + |
| 2838 | + var limit = as(plan, Limit.class); |
| 2839 | + var agg = as(limit.child(), Aggregate.class); |
| 2840 | + var aggregates = agg.aggregates(); |
| 2841 | + assertThat(aggregates, hasSize(2)); |
| 2842 | + Alias a = as(aggregates.get(0), Alias.class); |
| 2843 | + assertEquals("c", a.name()); |
| 2844 | + Count c = as(a.child(), Count.class); |
| 2845 | + FieldAttribute fa = as(c.field(), FieldAttribute.class); |
| 2846 | + assertEquals("emp_no", fa.name()); |
| 2847 | + ReferenceAttribute ra = as(aggregates.get(1), ReferenceAttribute.class); // reference in aggregates is resolved |
| 2848 | + assertEquals("d", ra.name()); |
| 2849 | + List<Expression> groupings = agg.groupings(); |
| 2850 | + assertEquals(1, groupings.size()); |
| 2851 | + a = as(groupings.get(0), Alias.class); // reference in groupings is resolved |
| 2852 | + assertEquals("d", ra.name()); |
| 2853 | + Equals equals = as(a.child(), Equals.class); |
| 2854 | + ra = as(equals.left(), ReferenceAttribute.class); |
| 2855 | + assertEquals("date", ra.name()); |
| 2856 | + Literal literal = as(equals.right(), Literal.class); |
| 2857 | + assertEquals("2025-01-01T00:00:00.000Z", dateTimeToString(Long.parseLong(literal.value().toString()))); |
| 2858 | + assertEquals(DATETIME, literal.dataType()); |
| 2859 | + } |
| 2860 | + |
| 2861 | + public void testResolveGroupingsBeforeResolvingExplicitReferencesToGroupings() { |
| 2862 | + var plan = analyze(""" |
| 2863 | + FROM test |
| 2864 | + | EVAL date = "2025-01-01"::datetime |
| 2865 | + | STATS c = count(emp_no), x = d::int + 1 BY d = (date == "2025-01-01") |
| 2866 | + """, "mapping-default.json"); |
| 2867 | + |
| 2868 | + var limit = as(plan, Limit.class); |
| 2869 | + var agg = as(limit.child(), Aggregate.class); |
| 2870 | + var aggregates = agg.aggregates(); |
| 2871 | + assertThat(aggregates, hasSize(3)); |
| 2872 | + Alias a = as(aggregates.get(0), Alias.class); |
| 2873 | + assertEquals("c", a.name()); |
| 2874 | + Count c = as(a.child(), Count.class); |
| 2875 | + FieldAttribute fa = as(c.field(), FieldAttribute.class); |
| 2876 | + assertEquals("emp_no", fa.name()); |
| 2877 | + a = as(aggregates.get(1), Alias.class); // explicit reference to groupings is resolved |
| 2878 | + assertEquals("x", a.name()); |
| 2879 | + Add add = as(a.child(), Add.class); |
| 2880 | + ToInteger toInteger = as(add.left(), ToInteger.class); |
| 2881 | + ReferenceAttribute ra = as(toInteger.field(), ReferenceAttribute.class); |
| 2882 | + assertEquals("d", ra.name()); |
| 2883 | + ra = as(aggregates.get(2), ReferenceAttribute.class); // reference in aggregates is resolved |
| 2884 | + assertEquals("d", ra.name()); |
| 2885 | + List<Expression> groupings = agg.groupings(); |
| 2886 | + assertEquals(1, groupings.size()); |
| 2887 | + a = as(groupings.get(0), Alias.class); // reference in groupings is resolved |
| 2888 | + assertEquals("d", ra.name()); |
| 2889 | + Equals equals = as(a.child(), Equals.class); |
| 2890 | + ra = as(equals.left(), ReferenceAttribute.class); |
| 2891 | + assertEquals("date", ra.name()); |
| 2892 | + Literal literal = as(equals.right(), Literal.class); |
| 2893 | + assertEquals("2025-01-01T00:00:00.000Z", dateTimeToString(Long.parseLong(literal.value().toString()))); |
| 2894 | + assertEquals(DATETIME, literal.dataType()); |
| 2895 | + } |
| 2896 | + |
| 2897 | + public void testBucketWithIntervalInStringInBothAggregationAndGrouping() { |
| 2898 | + var plan = analyze(""" |
| 2899 | + FROM test |
| 2900 | + | STATS c = count(emp_no), b = BUCKET(hire_date, "1 year") + 1 year BY yr = BUCKET(hire_date, "1 year") |
| 2901 | + """, "mapping-default.json"); |
| 2902 | + |
| 2903 | + var limit = as(plan, Limit.class); |
| 2904 | + var agg = as(limit.child(), Aggregate.class); |
| 2905 | + var aggregates = agg.aggregates(); |
| 2906 | + assertThat(aggregates, hasSize(3)); |
| 2907 | + Alias a = as(aggregates.get(0), Alias.class); |
| 2908 | + assertEquals("c", a.name()); |
| 2909 | + Count c = as(a.child(), Count.class); |
| 2910 | + FieldAttribute fa = as(c.field(), FieldAttribute.class); |
| 2911 | + assertEquals("emp_no", fa.name()); |
| 2912 | + a = as(aggregates.get(1), Alias.class); // explicit reference to groupings is resolved |
| 2913 | + assertEquals("b", a.name()); |
| 2914 | + Add add = as(a.child(), Add.class); |
| 2915 | + Bucket bucket = as(add.left(), Bucket.class); |
| 2916 | + fa = as(bucket.field(), FieldAttribute.class); |
| 2917 | + assertEquals("hire_date", fa.name()); |
| 2918 | + Literal literal = as(bucket.buckets(), Literal.class); |
| 2919 | + Literal oneYear = new Literal(EMPTY, Period.ofYears(1), DATE_PERIOD); |
| 2920 | + assertEquals(oneYear, literal); |
| 2921 | + literal = as(add.right(), Literal.class); |
| 2922 | + assertEquals(oneYear, literal); |
| 2923 | + ReferenceAttribute ra = as(aggregates.get(2), ReferenceAttribute.class); // reference in aggregates is resolved |
| 2924 | + assertEquals("yr", ra.name()); |
| 2925 | + List<Expression> groupings = agg.groupings(); |
| 2926 | + assertEquals(1, groupings.size()); |
| 2927 | + a = as(groupings.get(0), Alias.class); // reference in groupings is resolved |
| 2928 | + assertEquals("yr", ra.name()); |
| 2929 | + bucket = as(a.child(), Bucket.class); |
| 2930 | + fa = as(bucket.field(), FieldAttribute.class); |
| 2931 | + assertEquals("hire_date", fa.name()); |
| 2932 | + literal = as(bucket.buckets(), Literal.class); |
| 2933 | + assertEquals(oneYear, literal); |
| 2934 | + } |
| 2935 | + |
| 2936 | + public void testBucketWithIntervalInStringInGroupingReferencedInAggregation() { |
| 2937 | + var plan = analyze(""" |
| 2938 | + FROM test |
| 2939 | + | STATS c = count(emp_no), b = yr + 1 year BY yr = BUCKET(hire_date, "1 year") |
| 2940 | + """, "mapping-default.json"); |
| 2941 | + |
| 2942 | + var limit = as(plan, Limit.class); |
| 2943 | + var agg = as(limit.child(), Aggregate.class); |
| 2944 | + var aggregates = agg.aggregates(); |
| 2945 | + assertThat(aggregates, hasSize(3)); |
| 2946 | + Alias a = as(aggregates.get(0), Alias.class); |
| 2947 | + assertEquals("c", a.name()); |
| 2948 | + Count c = as(a.child(), Count.class); |
| 2949 | + FieldAttribute fa = as(c.field(), FieldAttribute.class); |
| 2950 | + assertEquals("emp_no", fa.name()); |
| 2951 | + a = as(aggregates.get(1), Alias.class); // explicit reference to groupings is resolved |
| 2952 | + assertEquals("b", a.name()); |
| 2953 | + Add add = as(a.child(), Add.class); |
| 2954 | + ReferenceAttribute ra = as(add.left(), ReferenceAttribute.class); |
| 2955 | + assertEquals("yr", ra.name()); |
| 2956 | + Literal oneYear = new Literal(EMPTY, Period.ofYears(1), DATE_PERIOD); |
| 2957 | + Literal literal = as(add.right(), Literal.class); |
| 2958 | + assertEquals(oneYear, literal); |
| 2959 | + ra = as(aggregates.get(2), ReferenceAttribute.class); // reference in aggregates is resolved |
| 2960 | + assertEquals("yr", ra.name()); |
| 2961 | + List<Expression> groupings = agg.groupings(); |
| 2962 | + assertEquals(1, groupings.size()); |
| 2963 | + a = as(groupings.get(0), Alias.class); // reference in groupings is resolved |
| 2964 | + assertEquals("yr", ra.name()); |
| 2965 | + Bucket bucket = as(a.child(), Bucket.class); |
| 2966 | + fa = as(bucket.field(), FieldAttribute.class); |
| 2967 | + assertEquals("hire_date", fa.name()); |
| 2968 | + literal = as(bucket.buckets(), Literal.class); |
| 2969 | + assertEquals(oneYear, literal); |
| 2970 | + } |
| 2971 | + |
2822 | 2972 | private void verifyUnsupported(String query, String errorMessage) { |
2823 | 2973 | verifyUnsupported(query, errorMessage, "mapping-multi-field-variation.json"); |
2824 | 2974 | } |
|
0 commit comments