|
61 | 61 | import org.elasticsearch.xpack.esql.plan.logical.Enrich; |
62 | 62 | import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan; |
63 | 63 | import org.elasticsearch.xpack.esql.plan.physical.AggregateExec; |
| 64 | +import org.elasticsearch.xpack.esql.plan.physical.DissectExec; |
64 | 65 | import org.elasticsearch.xpack.esql.plan.physical.EsQueryExec; |
65 | 66 | import org.elasticsearch.xpack.esql.plan.physical.EsStatsQueryExec; |
66 | 67 | import org.elasticsearch.xpack.esql.plan.physical.EsStatsQueryExec.Stat; |
|
71 | 72 | import org.elasticsearch.xpack.esql.plan.physical.FilterExec; |
72 | 73 | import org.elasticsearch.xpack.esql.plan.physical.LimitExec; |
73 | 74 | import org.elasticsearch.xpack.esql.plan.physical.LocalSourceExec; |
| 75 | +import org.elasticsearch.xpack.esql.plan.physical.MvExpandExec; |
74 | 76 | import org.elasticsearch.xpack.esql.plan.physical.PhysicalPlan; |
75 | 77 | import org.elasticsearch.xpack.esql.plan.physical.ProjectExec; |
76 | 78 | import org.elasticsearch.xpack.esql.plan.physical.TimeSeriesAggregateExec; |
@@ -152,6 +154,18 @@ public boolean isSingleValue(String field) { |
152 | 154 | } |
153 | 155 | }; |
154 | 156 |
|
| 157 | + private final SearchStats CONSTANT_K_STATS = new TestSearchStats() { |
| 158 | + @Override |
| 159 | + public boolean isSingleValue(String field) { |
| 160 | + return true; |
| 161 | + } |
| 162 | + |
| 163 | + @Override |
| 164 | + public String constantValue(String name) { |
| 165 | + return name.startsWith("constant_keyword") ? "foo" : null; |
| 166 | + } |
| 167 | + }; |
| 168 | + |
155 | 169 | @ParametersFactory(argumentFormatting = PARAM_FORMATTING) |
156 | 170 | public static List<Object[]> readScriptSpec() { |
157 | 171 | return settings().stream().map(t -> { |
@@ -1858,6 +1872,113 @@ public void testPushDownFieldExtractToTimeSeriesSource() { |
1858 | 1872 | assertTrue(timeSeriesSource.attrs().stream().noneMatch(EsQueryExec::isSourceAttribute)); |
1859 | 1873 | } |
1860 | 1874 |
|
| 1875 | + /** |
| 1876 | + * LimitExec[1000[INTEGER]] |
| 1877 | + * \_ExchangeExec[[!alias_integer, boolean{f}#415, byte{f}#416, constant_keyword-foo{f}#417, date{f}#418, date_nanos{f}#419, |
| 1878 | + * double{f}#420, float{f}#421, half_float{f}#422, integer{f}#424, ip{f}#425, keyword{f}#426, long{f}#427, scaled_float{f}#423, |
| 1879 | + * !semantic_text, short{f}#429, text{f}#430, unsigned_long{f}#428, version{f}#431, wildcard{f}#432], false] |
| 1880 | + * \_ProjectExec[[!alias_integer, boolean{f}#415, byte{f}#416, constant_keyword-foo{f}#417, date{f}#418, date_nanos{f}#419, |
| 1881 | + * double{f}#420, float{f}#421, half_float{f}#422, integer{f}#424, ip{f}#425, keyword{f}#426, long{f}#427, scaled_float{f}#423, |
| 1882 | + * !semantic_text, short{f}#429, text{f}#430, unsigned_long{f}#428, version{f}#431, wildcard{f}#432]] |
| 1883 | + * \_FieldExtractExec[!alias_integer, boolean{f}#415, byte{f}#416, consta..] |
| 1884 | + * \_EsQueryExec[test], indexMode[standard], query[][_doc{f}#434], limit[1000], sort[] estimatedRowSize[412] |
| 1885 | + */ |
| 1886 | + public void testConstantKeywordWithMatchingFilter() { |
| 1887 | + String queryText = """ |
| 1888 | + from test |
| 1889 | + | where `constant_keyword-foo` == "foo" |
| 1890 | + """; |
| 1891 | + var analyzer = makeAnalyzer("mapping-all-types.json"); |
| 1892 | + var plan = plannerOptimizer.plan(queryText, CONSTANT_K_STATS, analyzer); |
| 1893 | + |
| 1894 | + var limit = as(plan, LimitExec.class); |
| 1895 | + var exchange = as(limit.child(), ExchangeExec.class); |
| 1896 | + var project = as(exchange.child(), ProjectExec.class); |
| 1897 | + var field = as(project.child(), FieldExtractExec.class); |
| 1898 | + var query = as(field.child(), EsQueryExec.class); |
| 1899 | + assertThat(as(query.limit(), Literal.class).value(), is(1000)); |
| 1900 | + assertNull(query.query()); |
| 1901 | + } |
| 1902 | + |
| 1903 | + /** |
| 1904 | + * LimitExec[1000[INTEGER]] |
| 1905 | + * \_ExchangeExec[[!alias_integer, boolean{f}#4, byte{f}#5, constant_keyword-foo{f}#6, date{f}#7, date_nanos{f}#8, double{f}#9, |
| 1906 | + * 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, |
| 1907 | + * short{f}#18, text{f}#19, unsigned_long{f}#17, version{f}#20, wildcard{f}#21], false] |
| 1908 | + * \_LocalSourceExec[[!alias_integer, boolean{f}#4, byte{f}#5, constant_keyword-foo{f}#6, date{f}#7, date_nanos{f}#8, double{f}#9, |
| 1909 | + * 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, |
| 1910 | + * short{f}#18, text{f}#19, unsigned_long{f}#17, version{f}#20, wildcard{f}#21], EMPTY] |
| 1911 | + */ |
| 1912 | + public void testConstantKeywordWithNonMatchingFilter() { |
| 1913 | + String queryText = """ |
| 1914 | + from test |
| 1915 | + | where `constant_keyword-foo` == "non-matching" |
| 1916 | + """; |
| 1917 | + var analyzer = makeAnalyzer("mapping-all-types.json"); |
| 1918 | + var plan = plannerOptimizer.plan(queryText, CONSTANT_K_STATS, analyzer); |
| 1919 | + |
| 1920 | + var limit = as(plan, LimitExec.class); |
| 1921 | + var exchange = as(limit.child(), ExchangeExec.class); |
| 1922 | + var source = as(exchange.child(), LocalSourceExec.class); |
| 1923 | + } |
| 1924 | + |
| 1925 | + /** |
| 1926 | + * LimitExec[1000[INTEGER]] |
| 1927 | + * \_ExchangeExec[[!alias_integer, boolean{f}#6, byte{f}#7, constant_keyword-foo{r}#25, date{f}#9, date_nanos{f}#10, double{f}#1... |
| 1928 | + * \_ProjectExec[[!alias_integer, boolean{f}#6, byte{f}#7, constant_keyword-foo{r}#25, date{f}#9, date_nanos{f}#10, double{f}#1... |
| 1929 | + * \_FieldExtractExec[!alias_integer, boolean{f}#6, byte{f}#7, date{f}#9, |
| 1930 | + * \_LimitExec[1000[INTEGER]] |
| 1931 | + * \_FilterExec[constant_keyword-foo{r}#25 == [66 6f 6f][KEYWORD]] |
| 1932 | + * \_MvExpandExec[constant_keyword-foo{f}#8,constant_keyword-foo{r}#25] |
| 1933 | + * \_FieldExtractExec[constant_keyword-foo{f}#8] |
| 1934 | + * \_EsQueryExec[test], indexMode[standard], query[][_doc{f}#26], limit[], sort[] estimatedRowSize[412] |
| 1935 | + */ |
| 1936 | + public void testConstantKeywordExpandFilter() { |
| 1937 | + String queryText = """ |
| 1938 | + from test |
| 1939 | + | mv_expand `constant_keyword-foo` |
| 1940 | + | where `constant_keyword-foo` == "foo" |
| 1941 | + """; |
| 1942 | + var analyzer = makeAnalyzer("mapping-all-types.json"); |
| 1943 | + var plan = plannerOptimizer.plan(queryText, CONSTANT_K_STATS, analyzer); |
| 1944 | + |
| 1945 | + var limit = as(plan, LimitExec.class); |
| 1946 | + var exchange = as(limit.child(), ExchangeExec.class); |
| 1947 | + var project = as(exchange.child(), ProjectExec.class); |
| 1948 | + var fieldExtract = as(project.child(), FieldExtractExec.class); |
| 1949 | + var limit2 = as(fieldExtract.child(), LimitExec.class); |
| 1950 | + var filter = as(limit2.child(), FilterExec.class); |
| 1951 | + var expand = as(filter.child(), MvExpandExec.class); |
| 1952 | + var field = as(expand.child(), FieldExtractExec.class); // MV_EXPAND is not optimized yet (it doesn't accept literals) |
| 1953 | + as(field.child(), EsQueryExec.class); |
| 1954 | + } |
| 1955 | + |
| 1956 | + /** |
| 1957 | + * DissectExec[constant_keyword-foo{f}#8,Parser[pattern=%{bar}, appendSeparator=, ... |
| 1958 | + * \_LimitExec[1000[INTEGER]] |
| 1959 | + * \_ExchangeExec[[!alias_integer, boolean{f}#6, byte{f}#7, constant_keyword-foo{f}#8, date{f}#9, date_nanos{f}#10, double{f}#11... |
| 1960 | + * \_ProjectExec[[!alias_integer, boolean{f}#6, byte{f}#7, constant_keyword-foo{f}#8, date{f}#9, date_nanos{f}#10, double{f}#11... |
| 1961 | + * \_FieldExtractExec[!alias_integer, boolean{f}#6, byte{f}#7, constant_k..] |
| 1962 | + * \_EsQueryExec[test], indexMode[standard], query[][_doc{f}#25], limit[1000], sort[] estimatedRowSize[462] |
| 1963 | + */ |
| 1964 | + public void testConstantKeywordDissectFilter() { |
| 1965 | + String queryText = """ |
| 1966 | + from test |
| 1967 | + | dissect `constant_keyword-foo` "%{bar}" |
| 1968 | + | where `constant_keyword-foo` == "foo" |
| 1969 | + """; |
| 1970 | + var analyzer = makeAnalyzer("mapping-all-types.json"); |
| 1971 | + var plan = plannerOptimizer.plan(queryText, CONSTANT_K_STATS, analyzer); |
| 1972 | + |
| 1973 | + var dissect = as(plan, DissectExec.class); |
| 1974 | + var limit = as(dissect.child(), LimitExec.class); |
| 1975 | + var exchange = as(limit.child(), ExchangeExec.class); |
| 1976 | + var project = as(exchange.child(), ProjectExec.class); |
| 1977 | + var field = as(project.child(), FieldExtractExec.class); |
| 1978 | + var query = as(field.child(), EsQueryExec.class); |
| 1979 | + assertNull(query.query()); |
| 1980 | + } |
| 1981 | + |
1861 | 1982 | public void testMatchFunctionWithStatsWherePushable() { |
1862 | 1983 | String query = """ |
1863 | 1984 | from test |
|
0 commit comments