Skip to content

Commit

Permalink
Merge branch '8.16' into bp-816-115477
Browse files Browse the repository at this point in the history
  • Loading branch information
davidkyle authored Nov 28, 2024
2 parents 98e8e3e + 84c04ef commit ee01e50
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 16 deletions.
6 changes: 6 additions & 0 deletions docs/changelog/117503.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pr: 117503
summary: Fix COUNT filter pushdown
area: ES|QL
type: bug
issues:
- 115522
32 changes: 16 additions & 16 deletions docs/reference/quickstart/full-text-filtering-tutorial.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -511,8 +511,9 @@ In this tutorial scenario it's useful for when users have complex requirements f

Let's create a query that addresses the following user needs:

* Must be a vegetarian main course
* Must be a vegetarian recipe
* Should contain "curry" or "spicy" in the title or description
* Should be a main course
* Must not be a dessert
* Must have a rating of at least 4.5
* Should prefer recipes published in the last month
Expand All @@ -524,16 +525,7 @@ GET /cooking_blog/_search
"query": {
"bool": {
"must": [
{
"term": {
"category.keyword": "Main Course"
}
},
{
"term": {
"tags": "vegetarian"
}
},
{ "term": { "tags": "vegetarian" } },
{
"range": {
"rating": {
Expand All @@ -543,10 +535,18 @@ GET /cooking_blog/_search
}
],
"should": [
{
"term": {
"category": "Main Course"
}
},
{
"multi_match": {
"query": "curry spicy",
"fields": ["title^2", "description"]
"fields": [
"title^2",
"description"
]
}
},
{
Expand Down Expand Up @@ -590,12 +590,12 @@ GET /cooking_blog/_search
"value": 1,
"relation": "eq"
},
"max_score": 7.9835095,
"max_score": 7.444513,
"hits": [
{
"_index": "cooking_blog",
"_id": "2",
"_score": 7.9835095,
"_score": 7.444513,
"_source": {
"title": "Spicy Thai Green Curry: A Vegetarian Adventure", <1>
"description": "Dive into the flavors of Thailand with this vibrant green curry. Packed with vegetables and aromatic herbs, this dish is both healthy and satisfying. Don't worry about the heat - you can easily adjust the spice level to your liking.", <2>
Expand All @@ -619,8 +619,8 @@ GET /cooking_blog/_search
<1> The title contains "Spicy" and "Curry", matching our should condition. With the default <<type-best-fields,best_fields>> behavior, this field contributes most to the relevance score.
<2> While the description also contains matching terms, only the best matching field's score is used by default.
<3> The recipe was published within the last month, satisfying our recency preference.
<4> The "Main Course" category matches our `must` condition.
<5> The "vegetarian" tag satisfies another `must` condition, while "curry" and "spicy" tags align with our `should` preferences.
<4> The "Main Course" category satisfies another `should` condition.
<5> The "vegetarian" tag satisfies a `must` condition, while "curry" and "spicy" tags align with our `should` preferences.
<6> The rating of 4.6 meets our minimum rating requirement of 4.5.
==============

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2537,6 +2537,57 @@ c2:l |c2_f:l |m2:i |m2_f:i |c:l
1 |1 |5 |5 |21
;

simpleCountOnFieldWithFilteringAndNoGrouping
required_capability: per_agg_filtering
from employees
| stats c1 = count(emp_no) where emp_no < 10042
;

c1:long
41
;

simpleCountOnFieldWithFilteringOnDifferentFieldAndNoGrouping
required_capability: per_agg_filtering
from employees
| stats c1 = count(hire_date) where emp_no < 10042
;

c1:long
41
;

simpleCountOnStarWithFilteringAndNoGrouping
required_capability: per_agg_filtering
from employees
| stats c1 = count(*) where emp_no < 10042
;

c1:long
41
;

simpleCountWithFilteringAndNoGroupingOnFieldWithNulls
required_capability: per_agg_filtering
from employees
| stats c1 = count(birth_date) where emp_no <= 10050
;

c1:long
40
;


simpleCountWithFilteringAndNoGroupingOnFieldWithMultivalues
required_capability: per_agg_filtering
from employees
| stats c1 = count(job_positions) where emp_no <= 10003
;

c1:long
3
;

filterIsAlwaysTrue
required_capability: per_agg_filtering
FROM employees
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.elasticsearch.xpack.esql.core.expression.Expression;
import org.elasticsearch.xpack.esql.core.expression.FieldAttribute;
import org.elasticsearch.xpack.esql.core.expression.NamedExpression;
import org.elasticsearch.xpack.esql.core.util.Queries;
import org.elasticsearch.xpack.esql.core.util.StringUtils;
import org.elasticsearch.xpack.esql.expression.function.aggregate.Count;
import org.elasticsearch.xpack.esql.optimizer.LocalPhysicalOptimizerContext;
Expand All @@ -25,12 +26,15 @@
import org.elasticsearch.xpack.esql.plan.physical.EsStatsQueryExec;
import org.elasticsearch.xpack.esql.plan.physical.PhysicalPlan;
import org.elasticsearch.xpack.esql.planner.AbstractPhysicalOperationProviders;
import org.elasticsearch.xpack.esql.planner.PlannerUtils;

import java.util.ArrayList;
import java.util.List;

import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static org.elasticsearch.xpack.esql.optimizer.rules.physical.local.PushFiltersToSource.canPushToSource;
import static org.elasticsearch.xpack.esql.plan.physical.EsStatsQueryExec.StatsType.COUNT;

/**
Expand Down Expand Up @@ -98,6 +102,13 @@ private Tuple<List<Attribute>, List<EsStatsQueryExec.Stat>> pushableStats(
}
}
if (fieldName != null) {
if (count.hasFilter()) {
if (canPushToSource(count.filter(), fa -> false) == false) {
return null; // can't push down
}
var countFilter = PlannerUtils.TRANSLATOR_HANDLER.asQuery(count.filter());
query = Queries.combine(Queries.Clause.MUST, asList(countFilter.asBuilder(), query));
}
return new EsStatsQueryExec.Stat(fieldName, COUNT, query);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Function;

import static java.util.Arrays.asList;
import static org.elasticsearch.compute.aggregation.AggregatorMode.FINAL;
Expand Down Expand Up @@ -373,6 +374,54 @@ public void testMultiCountAllWithFilter() {
assertThat(plan.anyMatch(EsQueryExec.class::isInstance), is(true));
}

@SuppressWarnings("unchecked")
public void testSingleCountWithStatsFilter() {
var analyzer = makeAnalyzer("mapping-default.json", new EnrichResolution());
var plannerOptimizer = new TestPlannerOptimizer(config, analyzer);
var plan = plannerOptimizer.plan("""
from test
| stats c = count(hire_date) where emp_no < 10042
""", IS_SV_STATS);

var limit = as(plan, LimitExec.class);
var agg = as(limit.child(), AggregateExec.class);
assertThat(agg.getMode(), is(FINAL));
var exchange = as(agg.child(), ExchangeExec.class);
var esStatsQuery = as(exchange.child(), EsStatsQueryExec.class);
assertThat(esStatsQuery.stats().size(), is(1));

Function<String, String> compact = s -> s.replaceAll("\\s+", "");
assertThat(compact.apply(esStatsQuery.stats().get(0).query().toString()), is(compact.apply("""
{
"bool": {
"must": [
{
"esql_single_value": {
"field": "emp_no",
"next": {
"range": {
"emp_no": {
"lt": 10042,
"boost": 1.0
}
}
},
"source": "emp_no < 10042@2:36"
}
},
{
"exists": {
"field": "hire_date",
"boost": 1.0
}
}
],
"boost": 1.0
}
}
""")));
}

/**
* Expecting
* LimitExec[1000[INTEGER]]
Expand Down

0 comments on commit ee01e50

Please sign in to comment.