|
55 | 55 | import org.elasticsearch.xpack.esql.plan.logical.Enrich;
|
56 | 56 | import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan;
|
57 | 57 | import org.elasticsearch.xpack.esql.plan.physical.AggregateExec;
|
| 58 | +import org.elasticsearch.xpack.esql.plan.physical.DissectExec; |
58 | 59 | import org.elasticsearch.xpack.esql.plan.physical.EsQueryExec;
|
59 | 60 | import org.elasticsearch.xpack.esql.plan.physical.EsStatsQueryExec;
|
60 | 61 | import org.elasticsearch.xpack.esql.plan.physical.EsStatsQueryExec.Stat;
|
|
65 | 66 | import org.elasticsearch.xpack.esql.plan.physical.FilterExec;
|
66 | 67 | import org.elasticsearch.xpack.esql.plan.physical.LimitExec;
|
67 | 68 | import org.elasticsearch.xpack.esql.plan.physical.LocalSourceExec;
|
| 69 | +import org.elasticsearch.xpack.esql.plan.physical.MvExpandExec; |
68 | 70 | import org.elasticsearch.xpack.esql.plan.physical.PhysicalPlan;
|
69 | 71 | import org.elasticsearch.xpack.esql.plan.physical.ProjectExec;
|
70 | 72 | import org.elasticsearch.xpack.esql.plan.physical.TopNExec;
|
@@ -141,6 +143,18 @@ public boolean isSingleValue(String field) {
|
141 | 143 | }
|
142 | 144 | };
|
143 | 145 |
|
| 146 | + private final SearchStats CONSTANT_K_STATS = new TestSearchStats() { |
| 147 | + @Override |
| 148 | + public boolean isSingleValue(String field) { |
| 149 | + return true; |
| 150 | + } |
| 151 | + |
| 152 | + @Override |
| 153 | + public String constantValue(String name) { |
| 154 | + return name.startsWith("constant_keyword") ? "foo" : null; |
| 155 | + } |
| 156 | + }; |
| 157 | + |
144 | 158 | @ParametersFactory(argumentFormatting = PARAM_FORMATTING)
|
145 | 159 | public static List<Object[]> readScriptSpec() {
|
146 | 160 | return settings().stream().map(t -> {
|
@@ -1736,6 +1750,113 @@ public void testMatchFunctionWithPushableDisjunction() {
|
1736 | 1750 | assertThat(esQuery.query().toString(), equalTo(expected.toString()));
|
1737 | 1751 | }
|
1738 | 1752 |
|
| 1753 | + /** |
| 1754 | + * LimitExec[1000[INTEGER]] |
| 1755 | + * \_ExchangeExec[[!alias_integer, boolean{f}#415, byte{f}#416, constant_keyword-foo{f}#417, date{f}#418, date_nanos{f}#419, |
| 1756 | + * 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, |
| 1757 | + * !semantic_text, short{f}#429, text{f}#430, unsigned_long{f}#428, version{f}#431, wildcard{f}#432], false] |
| 1758 | + * \_ProjectExec[[!alias_integer, boolean{f}#415, byte{f}#416, constant_keyword-foo{f}#417, date{f}#418, date_nanos{f}#419, |
| 1759 | + * 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, |
| 1760 | + * !semantic_text, short{f}#429, text{f}#430, unsigned_long{f}#428, version{f}#431, wildcard{f}#432]] |
| 1761 | + * \_FieldExtractExec[!alias_integer, boolean{f}#415, byte{f}#416, consta..] |
| 1762 | + * \_EsQueryExec[test], indexMode[standard], query[][_doc{f}#434], limit[1000], sort[] estimatedRowSize[412] |
| 1763 | + */ |
| 1764 | + public void testConstantKeywordWithMatchingFilter() { |
| 1765 | + String queryText = """ |
| 1766 | + from test |
| 1767 | + | where `constant_keyword-foo` == "foo" |
| 1768 | + """; |
| 1769 | + var analyzer = makeAnalyzer("mapping-all-types.json", new EnrichResolution()); |
| 1770 | + var plan = plannerOptimizer.plan(queryText, CONSTANT_K_STATS, analyzer); |
| 1771 | + |
| 1772 | + var limit = as(plan, LimitExec.class); |
| 1773 | + var exchange = as(limit.child(), ExchangeExec.class); |
| 1774 | + var project = as(exchange.child(), ProjectExec.class); |
| 1775 | + var field = as(project.child(), FieldExtractExec.class); |
| 1776 | + var query = as(field.child(), EsQueryExec.class); |
| 1777 | + assertThat(as(query.limit(), Literal.class).value(), is(1000)); |
| 1778 | + assertNull(query.query()); |
| 1779 | + } |
| 1780 | + |
| 1781 | + /** |
| 1782 | + * LimitExec[1000[INTEGER]] |
| 1783 | + * \_ExchangeExec[[!alias_integer, boolean{f}#4, byte{f}#5, constant_keyword-foo{f}#6, date{f}#7, date_nanos{f}#8, double{f}#9, |
| 1784 | + * 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, |
| 1785 | + * short{f}#18, text{f}#19, unsigned_long{f}#17, version{f}#20, wildcard{f}#21], false] |
| 1786 | + * \_LocalSourceExec[[!alias_integer, boolean{f}#4, byte{f}#5, constant_keyword-foo{f}#6, date{f}#7, date_nanos{f}#8, double{f}#9, |
| 1787 | + * 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, |
| 1788 | + * short{f}#18, text{f}#19, unsigned_long{f}#17, version{f}#20, wildcard{f}#21], EMPTY] |
| 1789 | + */ |
| 1790 | + public void testConstantKeywordWithNonMatchingFilter() { |
| 1791 | + String queryText = """ |
| 1792 | + from test |
| 1793 | + | where `constant_keyword-foo` == "non-matching" |
| 1794 | + """; |
| 1795 | + var analyzer = makeAnalyzer("mapping-all-types.json", new EnrichResolution()); |
| 1796 | + var plan = plannerOptimizer.plan(queryText, CONSTANT_K_STATS, analyzer); |
| 1797 | + |
| 1798 | + var limit = as(plan, LimitExec.class); |
| 1799 | + var exchange = as(limit.child(), ExchangeExec.class); |
| 1800 | + var source = as(exchange.child(), LocalSourceExec.class); |
| 1801 | + } |
| 1802 | + |
| 1803 | + /** |
| 1804 | + * LimitExec[1000[INTEGER]] |
| 1805 | + * \_ExchangeExec[[!alias_integer, boolean{f}#6, byte{f}#7, constant_keyword-foo{r}#25, date{f}#9, date_nanos{f}#10, double{f}#1... |
| 1806 | + * \_ProjectExec[[!alias_integer, boolean{f}#6, byte{f}#7, constant_keyword-foo{r}#25, date{f}#9, date_nanos{f}#10, double{f}#1... |
| 1807 | + * \_FieldExtractExec[!alias_integer, boolean{f}#6, byte{f}#7, date{f}#9, |
| 1808 | + * \_LimitExec[1000[INTEGER]] |
| 1809 | + * \_FilterExec[constant_keyword-foo{r}#25 == [66 6f 6f][KEYWORD]] |
| 1810 | + * \_MvExpandExec[constant_keyword-foo{f}#8,constant_keyword-foo{r}#25] |
| 1811 | + * \_FieldExtractExec[constant_keyword-foo{f}#8] |
| 1812 | + * \_EsQueryExec[test], indexMode[standard], query[][_doc{f}#26], limit[], sort[] estimatedRowSize[412] |
| 1813 | + */ |
| 1814 | + public void testConstantKeywordExpandFilter() { |
| 1815 | + String queryText = """ |
| 1816 | + from test |
| 1817 | + | mv_expand `constant_keyword-foo` |
| 1818 | + | where `constant_keyword-foo` == "foo" |
| 1819 | + """; |
| 1820 | + var analyzer = makeAnalyzer("mapping-all-types.json", new EnrichResolution()); |
| 1821 | + var plan = plannerOptimizer.plan(queryText, CONSTANT_K_STATS, analyzer); |
| 1822 | + |
| 1823 | + var limit = as(plan, LimitExec.class); |
| 1824 | + var exchange = as(limit.child(), ExchangeExec.class); |
| 1825 | + var project = as(exchange.child(), ProjectExec.class); |
| 1826 | + var fieldExtract = as(project.child(), FieldExtractExec.class); |
| 1827 | + var limit2 = as(fieldExtract.child(), LimitExec.class); |
| 1828 | + var filter = as(limit2.child(), FilterExec.class); |
| 1829 | + var expand = as(filter.child(), MvExpandExec.class); |
| 1830 | + var field = as(expand.child(), FieldExtractExec.class); // MV_EXPAND is not optimized yet (it doesn't accept literals) |
| 1831 | + as(field.child(), EsQueryExec.class); |
| 1832 | + } |
| 1833 | + |
| 1834 | + /** |
| 1835 | + * DissectExec[constant_keyword-foo{f}#8,Parser[pattern=%{bar}, appendSeparator=, ... |
| 1836 | + * \_LimitExec[1000[INTEGER]] |
| 1837 | + * \_ExchangeExec[[!alias_integer, boolean{f}#6, byte{f}#7, constant_keyword-foo{f}#8, date{f}#9, date_nanos{f}#10, double{f}#11... |
| 1838 | + * \_ProjectExec[[!alias_integer, boolean{f}#6, byte{f}#7, constant_keyword-foo{f}#8, date{f}#9, date_nanos{f}#10, double{f}#11... |
| 1839 | + * \_FieldExtractExec[!alias_integer, boolean{f}#6, byte{f}#7, constant_k..] |
| 1840 | + * \_EsQueryExec[test], indexMode[standard], query[][_doc{f}#25], limit[1000], sort[] estimatedRowSize[462] |
| 1841 | + */ |
| 1842 | + public void testConstantKeywordDissectFilter() { |
| 1843 | + String queryText = """ |
| 1844 | + from test |
| 1845 | + | dissect `constant_keyword-foo` "%{bar}" |
| 1846 | + | where `constant_keyword-foo` == "foo" |
| 1847 | + """; |
| 1848 | + var analyzer = makeAnalyzer("mapping-all-types.json", new EnrichResolution()); |
| 1849 | + var plan = plannerOptimizer.plan(queryText, CONSTANT_K_STATS, analyzer); |
| 1850 | + |
| 1851 | + var dissect = as(plan, DissectExec.class); |
| 1852 | + var limit = as(dissect.child(), LimitExec.class); |
| 1853 | + var exchange = as(limit.child(), ExchangeExec.class); |
| 1854 | + var project = as(exchange.child(), ProjectExec.class); |
| 1855 | + var field = as(project.child(), FieldExtractExec.class); |
| 1856 | + var query = as(field.child(), EsQueryExec.class); |
| 1857 | + assertNull(query.query()); |
| 1858 | + } |
| 1859 | + |
1739 | 1860 | private QueryBuilder wrapWithSingleQuery(String query, QueryBuilder inner, String fieldName, Source source) {
|
1740 | 1861 | return FilterTests.singleValueQuery(query, inner, fieldName, source);
|
1741 | 1862 | }
|
|
0 commit comments