|
9 | 9 |
|
10 | 10 | import org.elasticsearch.index.IndexMode;
|
11 | 11 | import org.elasticsearch.test.ESTestCase;
|
| 12 | +import org.elasticsearch.xpack.esql.core.expression.Alias; |
| 13 | +import org.elasticsearch.xpack.esql.core.expression.Attribute; |
| 14 | +import org.elasticsearch.xpack.esql.core.expression.Expression; |
12 | 15 | import org.elasticsearch.xpack.esql.core.expression.FieldAttribute;
|
| 16 | +import org.elasticsearch.xpack.esql.core.expression.predicate.Predicates; |
13 | 17 | import org.elasticsearch.xpack.esql.core.expression.predicate.logical.And;
|
14 | 18 | import org.elasticsearch.xpack.esql.expression.function.aggregate.Count;
|
| 19 | +import org.elasticsearch.xpack.esql.expression.function.scalar.math.Pow; |
15 | 20 | import org.elasticsearch.xpack.esql.expression.function.scalar.string.RLike;
|
16 | 21 | import org.elasticsearch.xpack.esql.expression.function.scalar.string.WildcardLike;
|
17 | 22 | import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.GreaterThan;
|
|
20 | 25 | import org.elasticsearch.xpack.esql.index.EsIndex;
|
21 | 26 | import org.elasticsearch.xpack.esql.plan.logical.Aggregate;
|
22 | 27 | import org.elasticsearch.xpack.esql.plan.logical.EsRelation;
|
| 28 | +import org.elasticsearch.xpack.esql.plan.logical.Eval; |
23 | 29 | import org.elasticsearch.xpack.esql.plan.logical.Filter;
|
| 30 | +import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan; |
| 31 | +import org.elasticsearch.xpack.esql.plan.logical.Project; |
24 | 32 | import org.elasticsearch.xpack.esql.plan.logical.local.EsqlProject;
|
25 | 33 |
|
| 34 | +import java.util.ArrayList; |
26 | 35 | import java.util.List;
|
27 | 36 |
|
28 | 37 | import static java.util.Collections.emptyList;
|
29 | 38 | import static java.util.Collections.emptyMap;
|
30 | 39 | import static java.util.Collections.singletonList;
|
| 40 | +import static org.elasticsearch.xpack.esql.EsqlTestUtils.FOUR; |
31 | 41 | import static org.elasticsearch.xpack.esql.EsqlTestUtils.ONE;
|
32 | 42 | import static org.elasticsearch.xpack.esql.EsqlTestUtils.THREE;
|
33 | 43 | import static org.elasticsearch.xpack.esql.EsqlTestUtils.TWO;
|
| 44 | +import static org.elasticsearch.xpack.esql.EsqlTestUtils.as; |
34 | 45 | import static org.elasticsearch.xpack.esql.EsqlTestUtils.getFieldAttribute;
|
35 | 46 | import static org.elasticsearch.xpack.esql.EsqlTestUtils.greaterThanOf;
|
36 | 47 | import static org.elasticsearch.xpack.esql.EsqlTestUtils.greaterThanOrEqualOf;
|
@@ -77,6 +88,115 @@ public void testPushDownFilter() {
|
77 | 88 | assertEquals(new EsqlProject(EMPTY, combinedFilter, projections), new PushDownAndCombineFilters().apply(fb));
|
78 | 89 | }
|
79 | 90 |
|
| 91 | + public void testPushDownFilterPastRenamingProject() { |
| 92 | + FieldAttribute a = getFieldAttribute("a"); |
| 93 | + FieldAttribute b = getFieldAttribute("b"); |
| 94 | + EsRelation relation = relation(List.of(a, b)); |
| 95 | + |
| 96 | + Alias aRenamed = new Alias(EMPTY, "a_renamed", a); |
| 97 | + Alias aRenamedTwice = new Alias(EMPTY, "a_renamed_twice", aRenamed.toAttribute()); |
| 98 | + Alias bRenamed = new Alias(EMPTY, "b_renamed", b); |
| 99 | + |
| 100 | + Project project = new Project(EMPTY, relation, List.of(aRenamed, aRenamedTwice, bRenamed)); |
| 101 | + |
| 102 | + GreaterThan aRenamedTwiceGreaterThanOne = greaterThanOf(aRenamedTwice.toAttribute(), ONE); |
| 103 | + LessThan bRenamedLessThanTwo = lessThanOf(bRenamed.toAttribute(), TWO); |
| 104 | + Filter filter = new Filter(EMPTY, project, Predicates.combineAnd(List.of(aRenamedTwiceGreaterThanOne, bRenamedLessThanTwo))); |
| 105 | + |
| 106 | + LogicalPlan optimized = new PushDownAndCombineFilters().apply(filter); |
| 107 | + |
| 108 | + Project optimizedProject = as(optimized, Project.class); |
| 109 | + assertEquals(optimizedProject.projections(), project.projections()); |
| 110 | + Filter optimizedFilter = as(optimizedProject.child(), Filter.class); |
| 111 | + assertEquals(optimizedFilter.condition(), Predicates.combineAnd(List.of(greaterThanOf(a, ONE), lessThanOf(b, TWO)))); |
| 112 | + EsRelation optimizedRelation = as(optimizedFilter.child(), EsRelation.class); |
| 113 | + assertEquals(optimizedRelation, relation); |
| 114 | + } |
| 115 | + |
| 116 | + // ... | eval a_renamed = a, a_renamed_twice = a_renamed, a_squared = pow(a, 2) |
| 117 | + // | where a_renamed > 1 and a_renamed_twice < 2 and a_squared < 4 |
| 118 | + // -> |
| 119 | + // ... | where a > 1 and a < 2 | eval a_renamed = a, a_renamed_twice = a_renamed, non_pushable = pow(a, 2) | where a_squared < 4 |
| 120 | + public void testPushDownFilterOnAliasInEval() { |
| 121 | + FieldAttribute a = getFieldAttribute("a"); |
| 122 | + FieldAttribute b = getFieldAttribute("b"); |
| 123 | + EsRelation relation = relation(List.of(a, b)); |
| 124 | + |
| 125 | + Alias aRenamed = new Alias(EMPTY, "a_renamed", a); |
| 126 | + Alias aRenamedTwice = new Alias(EMPTY, "a_renamed_twice", aRenamed.toAttribute()); |
| 127 | + Alias bRenamed = new Alias(EMPTY, "b_renamed", b); |
| 128 | + Alias aSquared = new Alias(EMPTY, "a_squared", new Pow(EMPTY, a, TWO)); |
| 129 | + Eval eval = new Eval(EMPTY, relation, List.of(aRenamed, aRenamedTwice, aSquared, bRenamed)); |
| 130 | + |
| 131 | + // We'll construct a Filter after the Eval that has conditions that can or cannot be pushed before the Eval. |
| 132 | + List<Expression> pushableConditionsBefore = List.of( |
| 133 | + greaterThanOf(a.toAttribute(), TWO), |
| 134 | + greaterThanOf(aRenamed.toAttribute(), ONE), |
| 135 | + lessThanOf(aRenamedTwice.toAttribute(), TWO), |
| 136 | + lessThanOf(aRenamedTwice.toAttribute(), bRenamed.toAttribute()) |
| 137 | + ); |
| 138 | + List<Expression> pushableConditionsAfter = List.of( |
| 139 | + greaterThanOf(a.toAttribute(), TWO), |
| 140 | + greaterThanOf(a.toAttribute(), ONE), |
| 141 | + lessThanOf(a.toAttribute(), TWO), |
| 142 | + lessThanOf(a.toAttribute(), b.toAttribute()) |
| 143 | + ); |
| 144 | + List<Expression> nonPushableConditions = List.of( |
| 145 | + lessThanOf(aSquared.toAttribute(), FOUR), |
| 146 | + greaterThanOf(aRenamedTwice.toAttribute(), aSquared.toAttribute()) |
| 147 | + ); |
| 148 | + |
| 149 | + // Try different combinations of pushable and non-pushable conditions in the filter while also randomizing their order a bit. |
| 150 | + for (int numPushable = 0; numPushable <= pushableConditionsBefore.size(); numPushable++) { |
| 151 | + for (int numNonPushable = 0; numNonPushable <= nonPushableConditions.size(); numNonPushable++) { |
| 152 | + if (numPushable == 0 && numNonPushable == 0) { |
| 153 | + continue; |
| 154 | + } |
| 155 | + |
| 156 | + List<Expression> conditions = new ArrayList<>(); |
| 157 | + |
| 158 | + int pushableIndex = 0, nonPushableIndex = 0; |
| 159 | + // Loop and add either a pushable or non-pushable condition to the filter. |
| 160 | + boolean addPushable; |
| 161 | + while (pushableIndex < numPushable || nonPushableIndex < numNonPushable) { |
| 162 | + if (pushableIndex == numPushable) { |
| 163 | + addPushable = false; |
| 164 | + } else if (nonPushableIndex == numNonPushable) { |
| 165 | + addPushable = true; |
| 166 | + } else { |
| 167 | + addPushable = randomBoolean(); |
| 168 | + } |
| 169 | + |
| 170 | + if (addPushable) { |
| 171 | + conditions.add(pushableConditionsBefore.get(pushableIndex++)); |
| 172 | + } else { |
| 173 | + conditions.add(nonPushableConditions.get(nonPushableIndex++)); |
| 174 | + } |
| 175 | + } |
| 176 | + |
| 177 | + Filter filter = new Filter(EMPTY, eval, Predicates.combineAnd(conditions)); |
| 178 | + |
| 179 | + LogicalPlan plan = new PushDownAndCombineFilters().apply(filter); |
| 180 | + |
| 181 | + if (numNonPushable > 0) { |
| 182 | + Filter optimizedFilter = as(plan, Filter.class); |
| 183 | + assertEquals(optimizedFilter.condition(), Predicates.combineAnd(nonPushableConditions.subList(0, numNonPushable))); |
| 184 | + plan = optimizedFilter.child(); |
| 185 | + } |
| 186 | + Eval optimizedEval = as(plan, Eval.class); |
| 187 | + assertEquals(optimizedEval.fields(), eval.fields()); |
| 188 | + plan = optimizedEval.child(); |
| 189 | + if (numPushable > 0) { |
| 190 | + Filter pushedFilter = as(plan, Filter.class); |
| 191 | + assertEquals(pushedFilter.condition(), Predicates.combineAnd(pushableConditionsAfter.subList(0, numPushable))); |
| 192 | + plan = pushedFilter.child(); |
| 193 | + } |
| 194 | + EsRelation optimizedRelation = as(plan, EsRelation.class); |
| 195 | + assertEquals(optimizedRelation, relation); |
| 196 | + } |
| 197 | + } |
| 198 | + } |
| 199 | + |
80 | 200 | public void testPushDownLikeRlikeFilter() {
|
81 | 201 | EsRelation relation = relation();
|
82 | 202 | org.elasticsearch.xpack.esql.core.expression.predicate.regex.RLike conditionA = rlike(getFieldAttribute("a"), "foo");
|
@@ -125,7 +245,17 @@ public void testSelectivelyPushDownFilterPastFunctionAgg() {
|
125 | 245 | assertEquals(expected, new PushDownAndCombineFilters().apply(fb));
|
126 | 246 | }
|
127 | 247 |
|
128 |
| - private EsRelation relation() { |
129 |
| - return new EsRelation(EMPTY, new EsIndex(randomAlphaOfLength(8), emptyMap()), randomFrom(IndexMode.values()), randomBoolean()); |
| 248 | + private static EsRelation relation() { |
| 249 | + return relation(List.of()); |
| 250 | + } |
| 251 | + |
| 252 | + private static EsRelation relation(List<Attribute> fieldAttributes) { |
| 253 | + return new EsRelation( |
| 254 | + EMPTY, |
| 255 | + new EsIndex(randomAlphaOfLength(8), emptyMap()), |
| 256 | + fieldAttributes, |
| 257 | + randomFrom(IndexMode.values()), |
| 258 | + randomBoolean() |
| 259 | + ); |
130 | 260 | }
|
131 | 261 | }
|
0 commit comments