|
42 | 42 | import org.elasticsearch.xpack.esql.core.expression.Expressions; |
43 | 43 | import org.elasticsearch.xpack.esql.core.expression.FieldAttribute; |
44 | 44 | import org.elasticsearch.xpack.esql.core.expression.Literal; |
| 45 | +import org.elasticsearch.xpack.esql.core.expression.NamedExpression; |
45 | 46 | import org.elasticsearch.xpack.esql.core.expression.ReferenceAttribute; |
46 | 47 | import org.elasticsearch.xpack.esql.core.tree.Source; |
47 | 48 | import org.elasticsearch.xpack.esql.core.type.DataType; |
|
69 | 70 | import org.elasticsearch.xpack.esql.plan.physical.FilterExec; |
70 | 71 | import org.elasticsearch.xpack.esql.plan.physical.LimitExec; |
71 | 72 | import org.elasticsearch.xpack.esql.plan.physical.LocalSourceExec; |
| 73 | +import org.elasticsearch.xpack.esql.plan.physical.MvExpandExec; |
72 | 74 | import org.elasticsearch.xpack.esql.plan.physical.PhysicalPlan; |
73 | 75 | import org.elasticsearch.xpack.esql.plan.physical.ProjectExec; |
74 | 76 | import org.elasticsearch.xpack.esql.plan.physical.TimeSeriesAggregateExec; |
@@ -150,6 +152,18 @@ public boolean isSingleValue(String field) { |
150 | 152 | } |
151 | 153 | }; |
152 | 154 |
|
| 155 | + private final SearchStats CONSTANT_K_STATS = new TestSearchStats() { |
| 156 | + @Override |
| 157 | + public boolean isSingleValue(String field) { |
| 158 | + return true; |
| 159 | + } |
| 160 | + |
| 161 | + @Override |
| 162 | + public String constantValue(String name) { |
| 163 | + return name.startsWith("constant_keyword") ? "foo" : null; |
| 164 | + } |
| 165 | + }; |
| 166 | + |
153 | 167 | @ParametersFactory(argumentFormatting = PARAM_FORMATTING) |
154 | 168 | public static List<Object[]> readScriptSpec() { |
155 | 169 | return settings().stream().map(t -> { |
@@ -1856,6 +1870,100 @@ public void testPushDownFieldExtractToTimeSeriesSource() { |
1856 | 1870 | assertTrue(timeSeriesSource.attrs().stream().noneMatch(EsQueryExec::isSourceAttribute)); |
1857 | 1871 | } |
1858 | 1872 |
|
| 1873 | + /** |
| 1874 | + * LimitExec[1000[INTEGER]] |
| 1875 | + * \_ExchangeExec[[!alias_integer, boolean{f}#4, byte{f}#5, constant_keyword-foo{f}#6, date{f}#7, date_nanos{f}#8, double{f}#9, |
| 1876 | + * float{f}#10, half_float{f}#11, integer{f}#13, ip{f}#14, keyword{f}#15, long{f}#16, scaled_float{f}#12, !semantic_text, |
| 1877 | + * short{f}#18, text{f}#19, unsigned_long{f}#17, version{f}#20, wildcard{f}#21], false] |
| 1878 | + * \_ProjectExec[[!alias_integer, boolean{f}#4, byte{f}#5, constant_keyword-foo{r}#6, date{f}#7, date_nanos{f}#8, double{f}#9, |
| 1879 | + * float{f}#10, half_float{f}#11, integer{f}#13, ip{f}#14, keyword{f}#15, long{f}#16, scaled_float{f}#12, |
| 1880 | + * !semantic_text, short{f}#18, text{f}#19, unsigned_long{f}#17, version{f}#20, wildcard{f}#21]] |
| 1881 | + * \_FieldExtractExec[!alias_integer, boolean{f}#4, byte{f}#5, date{f}#7, ] |
| 1882 | + * \_EvalExec[[[66 6f 6f][KEYWORD] AS constant_keyword-foo]] |
| 1883 | + * \_EsQueryExec[test], indexMode[standard], query[][_doc{f}#23], limit[1000], sort[] estimatedRowSize[412] |
| 1884 | + */ |
| 1885 | + public void testConstantKeywordWithMatchingFilter() { |
| 1886 | + String queryText = """ |
| 1887 | + from test |
| 1888 | + | where `constant_keyword-foo` == "foo" |
| 1889 | + """; |
| 1890 | + var analyzer = makeAnalyzer("mapping-all-types.json"); |
| 1891 | + var plan = plannerOptimizer.plan(queryText, CONSTANT_K_STATS, analyzer); |
| 1892 | + |
| 1893 | + var limit = as(plan, LimitExec.class); |
| 1894 | + var exchange = as(limit.child(), ExchangeExec.class); |
| 1895 | + var project = as(exchange.child(), ProjectExec.class); |
| 1896 | + var field = as(project.child(), FieldExtractExec.class); |
| 1897 | + var eval = as(field.child(), EvalExec.class); |
| 1898 | + var query = as(eval.child(), EsQueryExec.class); |
| 1899 | + assertThat(as(query.limit(), Literal.class).value(), is(1000)); |
| 1900 | + assertNull(query.query()); |
| 1901 | + assertFalse(field.attributesToExtract().stream().map(NamedExpression::name).anyMatch(x -> x.equals("constant_keyword-foo"))); |
| 1902 | + } |
| 1903 | + |
| 1904 | + /** |
| 1905 | + * LimitExec[1000[INTEGER]] |
| 1906 | + * \_ExchangeExec[[!alias_integer, boolean{f}#4, byte{f}#5, constant_keyword-foo{f}#6, date{f}#7, date_nanos{f}#8, double{f}#9, |
| 1907 | + * float{f}#10, half_float{f}#11, integer{f}#13, ip{f}#14, keyword{f}#15, long{f}#16, scaled_float{f}#12, !semantic_text, |
| 1908 | + * short{f}#18, text{f}#19, unsigned_long{f}#17, version{f}#20, wildcard{f}#21], false] |
| 1909 | + * \_LocalSourceExec[[!alias_integer, boolean{f}#4, byte{f}#5, constant_keyword-foo{f}#6, date{f}#7, date_nanos{f}#8, double{f}#9, |
| 1910 | + * float{f}#10, half_float{f}#11, integer{f}#13, ip{f}#14, keyword{f}#15, long{f}#16, scaled_float{f}#12, !semantic_text, |
| 1911 | + * short{f}#18, text{f}#19, unsigned_long{f}#17, version{f}#20, wildcard{f}#21], EMPTY] |
| 1912 | + */ |
| 1913 | + public void testConstantKeywordWithNonMatchingFilter() { |
| 1914 | + String queryText = """ |
| 1915 | + from test |
| 1916 | + | where `constant_keyword-foo` == "non-matching" |
| 1917 | + """; |
| 1918 | + var analyzer = makeAnalyzer("mapping-all-types.json"); |
| 1919 | + var plan = plannerOptimizer.plan(queryText, CONSTANT_K_STATS, analyzer); |
| 1920 | + |
| 1921 | + var limit = as(plan, LimitExec.class); |
| 1922 | + var exchange = as(limit.child(), ExchangeExec.class); |
| 1923 | + var source = as(exchange.child(), LocalSourceExec.class); |
| 1924 | + } |
| 1925 | + |
| 1926 | + /** |
| 1927 | + * LimitExec[1000[INTEGER]] |
| 1928 | + * \_ExchangeExec[[!alias_integer, boolean{f}#6, byte{f}#7, constant_keyword-foo{r}#25, date{f}#9, date_nanos{f}#10, double{f}#1 |
| 1929 | + * 1, float{f}#12, half_float{f}#13, integer{f}#15, ip{f}#16, keyword{f}#17, long{f}#18, scaled_float{f}#14, |
| 1930 | + * !semantic_text, short{f}#20, text{f}#21, unsigned_long{f}#19, version{f}#22, wildcard{f}#23], false] |
| 1931 | + * \_ProjectExec[[!alias_integer, boolean{f}#6, byte{f}#7, constant_keyword-foo{r}#25, date{f}#9, date_nanos{f}#10, double{f}#1 |
| 1932 | + * 1, float{f}#12, half_float{f}#13, integer{f}#15, ip{f}#16, keyword{f}#17, long{f}#18, scaled_float{f}#14, |
| 1933 | + * !semantic_text, short{f}#20, text{f}#21, unsigned_long{f}#19, version{f}#22, wildcard{f}#23]] |
| 1934 | + * \_LimitExec[1000[INTEGER]] |
| 1935 | + * \_FilterExec[constant_keyword-foo{r}#25 == [66 6f 6f][KEYWORD]] |
| 1936 | + * \_MvExpandExec[constant_keyword-foo{f}#8,constant_keyword-foo{r}#25] |
| 1937 | + * \_ProjectExec[[!alias_integer, boolean{f}#6, byte{f}#7, constant_keyword-foo{r}#8, date{f}#9, date_nanos{f}#10, |
| 1938 | + * double{f}#11, float{f}#12, half_float{f}#13, integer{f}#15, ip{f}#16, keyword{f}#17, long{f}#18, scaled_float{f}#14, |
| 1939 | + * !semantic_text, short{f}#20, text{f}#21, unsigned_long{f}#19, version{f}#22, wildcard{f}#23]] |
| 1940 | + * \_FieldExtractExec[!alias_integer, boolean{f}#6, byte{f}#7, date{f}#9, ..] |
| 1941 | + * \_EvalExec[[[66 6f 6f][KEYWORD] AS constant_keyword-foo]] |
| 1942 | + * \_EsQueryExec[test], indexMode[standard], query[][_doc{f}#26], limit[], sort[] estimatedRowSize[412] |
| 1943 | + */ |
| 1944 | + public void testConstantKeywordExpandFilter() { |
| 1945 | + String queryText = """ |
| 1946 | + from test |
| 1947 | + | mv_expand `constant_keyword-foo` |
| 1948 | + | where `constant_keyword-foo` == "foo" |
| 1949 | + """; |
| 1950 | + var analyzer = makeAnalyzer("mapping-all-types.json"); |
| 1951 | + var plan = plannerOptimizer.plan(queryText, CONSTANT_K_STATS, analyzer); |
| 1952 | + |
| 1953 | + var limit = as(plan, LimitExec.class); |
| 1954 | + var exchange = as(limit.child(), ExchangeExec.class); |
| 1955 | + var project = as(exchange.child(), ProjectExec.class); |
| 1956 | + var limit2 = as(project.child(), LimitExec.class); |
| 1957 | + var filter = as(limit2.child(), FilterExec.class); |
| 1958 | + var expand = as(filter.child(), MvExpandExec.class); |
| 1959 | + var project2 = as(expand.child(), ProjectExec.class); |
| 1960 | + var field = as(project2.child(), FieldExtractExec.class); |
| 1961 | + var eval = as(field.child(), EvalExec.class); |
| 1962 | + var query = as(eval.child(), EsQueryExec.class); |
| 1963 | + assertNull(query.query()); |
| 1964 | + assertFalse(field.attributesToExtract().stream().map(NamedExpression::name).anyMatch(x -> x.equals("constant_keyword-foo"))); |
| 1965 | + } |
| 1966 | + |
1859 | 1967 | private QueryBuilder wrapWithSingleQuery(String query, QueryBuilder inner, String fieldName, Source source) { |
1860 | 1968 | return FilterTests.singleValueQuery(query, inner, fieldName, source); |
1861 | 1969 | } |
|
0 commit comments