-
Notifications
You must be signed in to change notification settings - Fork 25.6k
Avoid dropping aggregate groupings in local plans #129370
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
fbef2ff
5bc9586
d172ff3
c846bce
d2b9fad
c99b9a9
edfb33e
13a30b0
1240d46
2865b44
2b6ae2f
186a040
2802ac0
71df645
b3f0c09
36eda9c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| pr: 129370 | ||
| summary: Avoid dropping aggregate groupings in local plans | ||
| area: ES|QL | ||
| type: bug | ||
| issues: | ||
| - 129811 | ||
| - 128054 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1679,6 +1679,39 @@ public void testQueryOnEmptyDataIndex() { | |
| } | ||
| } | ||
|
|
||
| public void testGroupingStatsOnMissingFields() { | ||
| assertAcked(client().admin().indices().prepareCreate("missing_field_index").setMapping("data", "type=long")); | ||
| long oneValue = between(1, 1000); | ||
| indexDoc("missing_field_index", "1", "data", oneValue); | ||
| refresh("missing_field_index"); | ||
| QueryPragmas pragmas = randomPragmas(); | ||
| pragmas = new QueryPragmas( | ||
| Settings.builder().put(pragmas.getSettings()).put(QueryPragmas.MAX_CONCURRENT_SHARDS_PER_NODE.getKey(), 1).build() | ||
| ); | ||
| EsqlQueryRequest request = new EsqlQueryRequest(); | ||
| request.query("FROM missing_field_index,test | STATS s = sum(data) BY color, tag | SORT color"); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess the point is to force a situation where both |
||
| request.pragmas(pragmas); | ||
| try (var r = run(request)) { | ||
| var rows = getValuesList(r); | ||
| assertThat(rows, hasSize(4)); | ||
| for (List<Object> row : rows) { | ||
| assertThat(row, hasSize(3)); | ||
| } | ||
| assertThat(rows.get(0).get(0), equalTo(20L)); | ||
| assertThat(rows.get(0).get(1), equalTo("blue")); | ||
| assertNull(rows.get(0).get(2)); | ||
| assertThat(rows.get(1).get(0), equalTo(10L)); | ||
| assertThat(rows.get(1).get(1), equalTo("green")); | ||
| assertNull(rows.get(1).get(2)); | ||
| assertThat(rows.get(2).get(0), equalTo(30L)); | ||
| assertThat(rows.get(2).get(1), equalTo("red")); | ||
| assertNull(rows.get(2).get(2)); | ||
| assertThat(rows.get(3).get(0), equalTo(oneValue)); | ||
| assertNull(rows.get(3).get(1)); | ||
| assertNull(rows.get(3).get(2)); | ||
| } | ||
| } | ||
|
|
||
| private void assertEmptyIndexQueries(String from) { | ||
| try (EsqlQueryResponse resp = run(from + "METADATA _source | KEEP _source | LIMIT 1")) { | ||
| assertFalse(resp.values().hasNext()); | ||
|
|
@@ -1816,6 +1849,8 @@ private void createAndPopulateIndex(String indexName, Settings additionalSetting | |
| "time", | ||
| "type=long", | ||
| "color", | ||
| "type=keyword", | ||
| "tag", | ||
| "type=keyword" | ||
| ) | ||
| ); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,7 +30,8 @@ | |
| * This class is part of the planner. Data node level logical optimizations. At this point we have access to | ||
| * {@link org.elasticsearch.xpack.esql.stats.SearchStats} which provides access to metadata about the index. | ||
| * | ||
| * <p>NB: This class also reapplies all the rules from {@link LogicalPlanOptimizer#operators()} and {@link LogicalPlanOptimizer#cleanup()} | ||
| * <p>NB: This class also reapplies all the rules from {@link LogicalPlanOptimizer#operators(boolean)} | ||
| * and {@link LogicalPlanOptimizer#cleanup()} | ||
| */ | ||
| public class LocalLogicalPlanOptimizer extends ParameterizedRuleExecutor<LogicalPlan, LocalLogicalOptimizerContext> { | ||
|
|
||
|
|
@@ -58,8 +59,8 @@ protected List<Batch<LogicalPlan>> batches() { | |
|
|
||
| @SuppressWarnings("unchecked") | ||
| private static Batch<LogicalPlan> localOperators() { | ||
| var operators = operators(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: this is a second mechanism for supplying local versions of rules; below is the original one, which looks for e.g. It'd be nicer to have just one such mechanism, but it's not the end of the world. |
||
| var rules = operators().rules(); | ||
| var operators = operators(true); | ||
| var rules = operators.rules(); | ||
| List<Rule<?, LogicalPlan>> newRules = new ArrayList<>(rules.length); | ||
|
|
||
| // apply updates to existing rules that have different applicability locally | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -744,6 +744,36 @@ public void testUnionTypesInferNonNullAggConstraint() { | |
| assertEquals("integer_long_field", unionTypeField.fieldName().string()); | ||
| } | ||
|
|
||
| /** | ||
| * \_Aggregate[[first_name{r}#7, $$first_name$temp_name$17{r}#18],[SUM(salary{f}#11,true[BOOLEAN]) AS SUM(salary)#5, first_nam | ||
| * e{r}#7, first_name{r}#7 AS last_name#10]] | ||
| * \_Eval[[null[KEYWORD] AS first_name#7, null[KEYWORD] AS $$first_name$temp_name$17#18]] | ||
| * \_EsRelation[test][_meta_field{f}#12, emp_no{f}#6, first_name{f}#7, ge..] | ||
| */ | ||
| public void testGroupingByMissingFields() { | ||
| var plan = plan("FROM test | STATS SUM(salary) BY first_name, last_name"); | ||
| var testStats = statsForMissingField("first_name", "last_name"); | ||
| var localPlan = localPlan(plan, testStats); | ||
| Limit limit = as(localPlan, Limit.class); | ||
| Aggregate aggregate = as(limit.child(), Aggregate.class); | ||
| assertThat(aggregate.groupings(), hasSize(2)); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd expand the test a little to assert the rest of the plan. With the alternative solution I pushed, there should be an |
||
| ReferenceAttribute grouping1 = as(aggregate.groupings().get(0), ReferenceAttribute.class); | ||
| ReferenceAttribute grouping2 = as(aggregate.groupings().get(1), ReferenceAttribute.class); | ||
| Eval eval = as(aggregate.child(), Eval.class); | ||
| assertThat(eval.fields(), hasSize(2)); | ||
| Alias eval1 = eval.fields().get(0); | ||
| Literal literal1 = as(eval1.child(), Literal.class); | ||
| assertNull(literal1.value()); | ||
| assertThat(literal1.dataType(), is(DataType.KEYWORD)); | ||
| Alias eval2 = eval.fields().get(1); | ||
| Literal literal2 = as(eval2.child(), Literal.class); | ||
| assertNull(literal2.value()); | ||
| assertThat(literal2.dataType(), is(DataType.KEYWORD)); | ||
| assertThat(grouping1.id(), equalTo(eval1.id())); | ||
| assertThat(grouping2.id(), equalTo(eval2.id())); | ||
| as(eval.child(), EsRelation.class); | ||
| } | ||
|
|
||
| private IsNotNull isNotNull(Expression field) { | ||
| return new IsNotNull(EMPTY, field); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test could use some comments to explain why we set it up in this specific way.