From 7d5ad3974656ae1072d666307222e08236523fd8 Mon Sep 17 00:00:00 2001
From: Carlos Delgado <6339205+carlosdelest@users.noreply.github.com>
Date: Tue, 18 Mar 2025 14:37:20 +0100
Subject: [PATCH] ES\QL: Fix scoring for non full text functions (#124540)
(cherry picked from commit 7950751788ab7e52ad1fa76392f5d9d4766f500c)
# Conflicts:
# x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/QueryBuilderResolver.java
# x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/fulltext/QueryStringTests.java
# x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalPhysicalPlanOptimizerTests.java
---
docs/changelog/124540.yaml | 5 +
.../esql/core/querydsl/query/BoolQuery.java | 12 +-
.../esql/core/querydsl/query/ExistsQuery.java | 2 +-
.../core/querydsl/query/GeoDistanceQuery.java | 2 +-
.../esql/core/querydsl/query/MatchAll.java | 2 +-
.../esql/core/querydsl/query/NotQuery.java | 4 +-
.../esql/core/querydsl/query/PrefixQuery.java | 2 +-
.../xpack/esql/core/querydsl/query/Query.java | 33 +-
.../core/querydsl/query/QueryStringQuery.java | 7 +-
.../esql/core/querydsl/query/RangeQuery.java | 2 +-
.../esql/core/querydsl/query/RegexQuery.java | 2 +-
.../esql/core/querydsl/query/TermQuery.java | 22 +-
.../esql/core/querydsl/query/TermsQuery.java | 2 +-
.../core/querydsl/query/WildcardQuery.java | 2 +-
.../core/querydsl/query/LeafQueryTests.java | 2 +-
.../src/main/resources/scoring.csv-spec | 46 ++-
.../xpack/esql/plugin/ScoringIT.java | 336 ++++++++++++------
.../xpack/esql/action/EsqlCapabilities.java | 7 +-
.../fulltext/QueryBuilderResolver.java | 2 +-
.../expression/function/fulltext/Term.java | 3 +-
.../physical/local/PushFiltersToSource.java | 2 +-
.../physical/local/PushStatsToSource.java | 2 +-
.../esql/plan/physical/EsStatsQueryExec.java | 2 +-
.../planner/EsPhysicalOperationProviders.java | 2 +-
.../xpack/esql/planner/PlannerUtils.java | 2 +-
.../xpack/esql/querydsl/query/KqlQuery.java | 7 +-
.../xpack/esql/querydsl/query/MatchQuery.java | 7 +-
.../esql/querydsl/query/MultiMatchQuery.java | 2 +-
.../esql/querydsl/query/SingleValueQuery.java | 4 +-
.../querydsl/query/SpatialRelatesQuery.java | 9 +-
.../TranslationAwareExpressionQuery.java | 8 +-
.../function/fulltext/MatchErrorTests.java | 2 +-
.../function/fulltext/MatchTests.java | 2 +-
.../LocalPhysicalPlanOptimizerTests.java | 89 ++---
.../optimizer/PhysicalPlanOptimizerTests.java | 36 +-
.../xpack/esql/planner/FilterTests.java | 56 ++-
.../esql/planner/QueryTranslatorTests.java | 16 +-
.../querydsl/query/QueryStringQueryTests.java | 2 +-
38 files changed, 502 insertions(+), 243 deletions(-)
create mode 100644 docs/changelog/124540.yaml
diff --git a/docs/changelog/124540.yaml b/docs/changelog/124540.yaml
new file mode 100644
index 0000000000000..9a6846f1bdb22
--- /dev/null
+++ b/docs/changelog/124540.yaml
@@ -0,0 +1,5 @@
+pr: 124540
+summary: "ES|QL: Fix scoring for full text functions"
+area: ES|QL
+type: bug
+issues: []
diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/BoolQuery.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/BoolQuery.java
index dbd75c93ee0e7..2525eb8778488 100644
--- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/BoolQuery.java
+++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/BoolQuery.java
@@ -43,13 +43,14 @@ public BoolQuery(Source source, boolean isAnd, List queries) {
}
@Override
- public QueryBuilder asBuilder() {
+ protected QueryBuilder asBuilder() {
BoolQueryBuilder boolQuery = boolQuery();
for (Query query : queries) {
+ QueryBuilder queryBuilder = query.toQueryBuilder();
if (isAnd) {
- boolQuery.must(query.asBuilder());
+ boolQuery.must(queryBuilder);
} else {
- boolQuery.should(query.asBuilder());
+ boolQuery.should(queryBuilder);
}
}
return boolQuery;
@@ -94,4 +95,9 @@ public Query negate(Source source) {
}
return new BoolQuery(source, isAnd == false, negated);
}
+
+ @Override
+ public boolean scorable() {
+ return true;
+ }
}
diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/ExistsQuery.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/ExistsQuery.java
index be585232cf8d6..42876f2a7ead3 100644
--- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/ExistsQuery.java
+++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/ExistsQuery.java
@@ -21,7 +21,7 @@ public ExistsQuery(Source source, String name) {
}
@Override
- public QueryBuilder asBuilder() {
+ protected QueryBuilder asBuilder() {
return existsQuery(name);
}
diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/GeoDistanceQuery.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/GeoDistanceQuery.java
index f7843cec0c88c..f0806660c3f7a 100644
--- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/GeoDistanceQuery.java
+++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/GeoDistanceQuery.java
@@ -45,7 +45,7 @@ public double distance() {
}
@Override
- public QueryBuilder asBuilder() {
+ protected QueryBuilder asBuilder() {
return QueryBuilders.geoDistanceQuery(field).distance(distance, DistanceUnit.METERS).point(lat, lon);
}
diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/MatchAll.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/MatchAll.java
index 6415a69e2201d..3a868349a183d 100644
--- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/MatchAll.java
+++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/MatchAll.java
@@ -17,7 +17,7 @@ public MatchAll(Source source) {
}
@Override
- public QueryBuilder asBuilder() {
+ protected QueryBuilder asBuilder() {
return matchAllQuery();
}
diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/NotQuery.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/NotQuery.java
index 4e36a4ee9f053..1a37fc8f42b9a 100644
--- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/NotQuery.java
+++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/NotQuery.java
@@ -32,8 +32,8 @@ public Query child() {
}
@Override
- public QueryBuilder asBuilder() {
- return boolQuery().mustNot(child.asBuilder());
+ protected QueryBuilder asBuilder() {
+ return boolQuery().mustNot(child.toQueryBuilder());
}
@Override
diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/PrefixQuery.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/PrefixQuery.java
index 1d98ff53be2f2..ede4aee3d117e 100644
--- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/PrefixQuery.java
+++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/PrefixQuery.java
@@ -34,7 +34,7 @@ public String query() {
}
@Override
- public QueryBuilder asBuilder() {
+ protected QueryBuilder asBuilder() {
return prefixQuery(field, query).caseInsensitive(caseInsensitive);
}
diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/Query.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/Query.java
index f3154eb6cd377..456275a054899 100644
--- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/Query.java
+++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/Query.java
@@ -28,6 +28,10 @@
*
*/
public abstract class Query {
+
+ // Boosting used to remove scoring from queries that don't contribute to it
+ public static final float NO_SCORE_BOOST = 0.0f;
+
private final Source source;
protected Query(Source source) {
@@ -46,9 +50,20 @@ public Source source() {
/**
* Convert to an Elasticsearch {@link QueryBuilder} all set up to execute
- * the query.
+ * the query. This ensures that queries have appropriate boosting for scoring.
+ */
+ public final QueryBuilder toQueryBuilder() {
+ QueryBuilder builder = asBuilder();
+ if (scorable() == false) {
+ builder = unscore(builder);
+ }
+ return builder;
+ }
+
+ /**
+ * Used internally to convert to retrieve a {@link QueryBuilder} by queries.
*/
- public abstract QueryBuilder asBuilder();
+ protected abstract QueryBuilder asBuilder();
/**
* Used by {@link Query#toString()} to produce a pretty string.
@@ -85,4 +100,18 @@ public String toString() {
public Query negate(Source source) {
return new NotQuery(source, this);
}
+
+ /**
+ * Defines whether a query should contribute to the overall score
+ */
+ public boolean scorable() {
+ return false;
+ }
+
+ /**
+ * Removes score from a query builder, so score is not affected by the query
+ */
+ public static QueryBuilder unscore(QueryBuilder builder) {
+ return builder.boost(NO_SCORE_BOOST);
+ }
}
diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/QueryStringQuery.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/QueryStringQuery.java
index 8dcb87749ae48..4b459aefe95e1 100644
--- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/QueryStringQuery.java
+++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/QueryStringQuery.java
@@ -64,7 +64,7 @@ public QueryStringQuery(Source source, String query, Map fields,
}
@Override
- public QueryBuilder asBuilder() {
+ protected QueryBuilder asBuilder() {
final QueryStringQueryBuilder queryBuilder = QueryBuilders.queryStringQuery(query);
queryBuilder.fields(fields);
options.forEach((k, v) -> {
@@ -108,4 +108,9 @@ public boolean equals(Object obj) {
protected String innerToString() {
return fields + ":" + query;
}
+
+ @Override
+ public boolean scorable() {
+ return true;
+ }
}
diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/RangeQuery.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/RangeQuery.java
index 2d66ee86d0f61..e7ddfd1735b28 100644
--- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/RangeQuery.java
+++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/RangeQuery.java
@@ -77,7 +77,7 @@ public ZoneId zoneId() {
}
@Override
- public QueryBuilder asBuilder() {
+ protected QueryBuilder asBuilder() {
RangeQueryBuilder queryBuilder = rangeQuery(field).from(lower, includeLower).to(upper, includeUpper);
if (Strings.hasText(format)) {
queryBuilder.format(format);
diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/RegexQuery.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/RegexQuery.java
index a8e48de654196..b12802de4e715 100644
--- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/RegexQuery.java
+++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/RegexQuery.java
@@ -42,7 +42,7 @@ public Boolean caseInsensitive() {
}
@Override
- public QueryBuilder asBuilder() {
+ protected QueryBuilder asBuilder() {
return regexpQuery(field, regex).caseInsensitive(caseInsensitive);
}
diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/TermQuery.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/TermQuery.java
index 240f9f581b27e..03c3b29ba15ec 100644
--- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/TermQuery.java
+++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/TermQuery.java
@@ -14,21 +14,31 @@
import static org.elasticsearch.index.query.QueryBuilders.termQuery;
+/**
+ * Term query. It can be considered for scoring or not - filters that use term query as implementation will not use scoring,
+ * but the Term full text function will
+ */
public class TermQuery extends Query {
private final String term;
private final Object value;
private final boolean caseInsensitive;
+ private final boolean scorable;
public TermQuery(Source source, String term, Object value) {
this(source, term, value, false);
}
public TermQuery(Source source, String term, Object value, boolean caseInsensitive) {
+ this(source, term, value, caseInsensitive, false);
+ }
+
+ public TermQuery(Source source, String term, Object value, boolean caseInsensitive, boolean scorable) {
super(source);
this.term = term;
this.value = value;
this.caseInsensitive = caseInsensitive;
+ this.scorable = scorable;
}
public String term() {
@@ -44,7 +54,7 @@ public Boolean caseInsensitive() {
}
@Override
- public QueryBuilder asBuilder() {
+ protected QueryBuilder asBuilder() {
TermQueryBuilder qb = termQuery(term, value);
// ES does not allow case_insensitive to be set to "false", it should be either "true" or not specified
return caseInsensitive == false ? qb : qb.caseInsensitive(caseInsensitive);
@@ -52,7 +62,7 @@ public QueryBuilder asBuilder() {
@Override
public int hashCode() {
- return Objects.hash(term, value, caseInsensitive);
+ return Objects.hash(term, value, caseInsensitive, scorable);
}
@Override
@@ -68,11 +78,17 @@ public boolean equals(Object obj) {
TermQuery other = (TermQuery) obj;
return Objects.equals(term, other.term)
&& Objects.equals(value, other.value)
- && Objects.equals(caseInsensitive, other.caseInsensitive);
+ && Objects.equals(caseInsensitive, other.caseInsensitive)
+ && scorable == other.scorable;
}
@Override
protected String innerToString() {
return term + ":" + value;
}
+
+ @Override
+ public boolean scorable() {
+ return scorable;
+ }
}
diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/TermsQuery.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/TermsQuery.java
index 5b0920929853a..68f8dd711f87a 100644
--- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/TermsQuery.java
+++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/querydsl/query/TermsQuery.java
@@ -26,7 +26,7 @@ public TermsQuery(Source source, String term, Set