Skip to content

Commit c77667f

Browse files
pmpailiscbuescher
authored andcommitted
Adding RankDocsRetrieverBuilder and RankDocsQuery (elastic#111709)
1 parent 36967b6 commit c77667f

File tree

38 files changed

+2351
-96
lines changed

38 files changed

+2351
-96
lines changed

server/src/internalClusterTest/java/org/elasticsearch/search/retriever/RankDocRetrieverBuilderIT.java

Lines changed: 755 additions & 0 deletions
Large diffs are not rendered by default.

server/src/internalClusterTest/java/org/elasticsearch/search/retriever/RetrieverRewriteIT.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.elasticsearch.cluster.node.DiscoveryNodeRole;
2222
import org.elasticsearch.common.Priority;
2323
import org.elasticsearch.common.settings.Settings;
24+
import org.elasticsearch.index.query.QueryBuilder;
2425
import org.elasticsearch.index.query.QueryBuilders;
2526
import org.elasticsearch.index.query.QueryRewriteContext;
2627
import org.elasticsearch.plugins.Plugin;
@@ -141,6 +142,11 @@ private AssertingRetrieverBuilder(RetrieverBuilder innerRetriever) {
141142
this.innerRetriever = innerRetriever;
142143
}
143144

145+
@Override
146+
public QueryBuilder topDocsQuery() {
147+
return null;
148+
}
149+
144150
@Override
145151
public RetrieverBuilder rewrite(QueryRewriteContext ctx) throws IOException {
146152
assertNull(ctx.getPointInTimeBuilder());
@@ -200,6 +206,11 @@ public boolean isCompound() {
200206
return true;
201207
}
202208

209+
@Override
210+
public QueryBuilder topDocsQuery() {
211+
return null;
212+
}
213+
203214
@Override
204215
public RetrieverBuilder rewrite(QueryRewriteContext ctx) throws IOException {
205216
assertNotNull(ctx.getPointInTimeBuilder());

server/src/main/java/module-info.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@
364364
exports org.elasticsearch.search.rank.rerank;
365365
exports org.elasticsearch.search.rescore;
366366
exports org.elasticsearch.search.retriever;
367+
exports org.elasticsearch.search.retriever.rankdoc;
367368
exports org.elasticsearch.search.runtime;
368369
exports org.elasticsearch.search.searchafter;
369370
exports org.elasticsearch.search.slice;

server/src/main/java/org/elasticsearch/TransportVersions.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ static TransportVersion def(int id) {
196196
public static final TransportVersion ZDT_NANOS_SUPPORT = def(8_726_00_0);
197197
public static final TransportVersion LTR_SERVERLESS_RELEASE = def(8_727_00_0);
198198
public static final TransportVersion ALLOW_PARTIAL_SEARCH_RESULTS_IN_PIT = def(8_728_00_0);
199+
public static final TransportVersion RANK_DOCS_RETRIEVER = def(8_729_00_0);
199200
/*
200201
* STOP! READ THIS FIRST! No, really,
201202
* ____ _____ ___ ____ _ ____ _____ _ ____ _____ _ _ ___ ____ _____ ___ ____ ____ _____ _

server/src/main/java/org/elasticsearch/common/lucene/Lucene.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
import org.elasticsearch.index.analysis.NamedAnalyzer;
7474
import org.elasticsearch.index.fielddata.IndexFieldData;
7575
import org.elasticsearch.lucene.grouping.TopFieldGroups;
76+
import org.elasticsearch.search.retriever.rankdoc.RankDocsSortField;
7677
import org.elasticsearch.search.sort.ShardDocSortField;
7778

7879
import java.io.IOException;
@@ -551,6 +552,8 @@ private static SortField rewriteMergeSortField(SortField sortField) {
551552
return newSortField;
552553
} else if (sortField.getClass() == ShardDocSortField.class) {
553554
return new SortField(sortField.getField(), SortField.Type.LONG, sortField.getReverse());
555+
} else if (sortField.getClass() == RankDocsSortField.class) {
556+
return new SortField(sortField.getField(), SortField.Type.INT, sortField.getReverse());
554557
} else {
555558
return sortField;
556559
}

server/src/main/java/org/elasticsearch/lucene/search/uhighlight/CustomUnifiedHighlighter.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.elasticsearch.core.Nullable;
3333
import org.elasticsearch.index.IndexSettings;
3434
import org.elasticsearch.index.search.ESToParentBlockJoinQuery;
35+
import org.elasticsearch.search.retriever.rankdoc.RankDocsQuery;
3536
import org.elasticsearch.search.runtime.AbstractScriptFieldQuery;
3637
import org.elasticsearch.search.vectors.KnnScoreDocQuery;
3738

@@ -255,10 +256,10 @@ public void visitLeaf(Query leafQuery) {
255256
hasUnknownLeaf[0] = true;
256257
}
257258
/**
258-
* KnnScoreDocQuery requires the same reader that built the docs
259+
* KnnScoreDocQuery and RankDocsQuery requires the same reader that built the docs
259260
* When using {@link HighlightFlag#WEIGHT_MATCHES} different readers are used and isn't supported by this query
260261
*/
261-
if (leafQuery instanceof KnnScoreDocQuery) {
262+
if (leafQuery instanceof KnnScoreDocQuery || leafQuery instanceof RankDocsQuery) {
262263
hasUnknownLeaf[0] = true;
263264
}
264265
super.visitLeaf(query);

server/src/main/java/org/elasticsearch/search/SearchModule.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,8 @@
237237
import org.elasticsearch.search.retriever.RetrieverBuilder;
238238
import org.elasticsearch.search.retriever.RetrieverParserContext;
239239
import org.elasticsearch.search.retriever.StandardRetrieverBuilder;
240+
import org.elasticsearch.search.retriever.rankdoc.RankDocsQueryBuilder;
241+
import org.elasticsearch.search.retriever.rankdoc.RankDocsSortBuilder;
240242
import org.elasticsearch.search.sort.FieldSortBuilder;
241243
import org.elasticsearch.search.sort.GeoDistanceSortBuilder;
242244
import org.elasticsearch.search.sort.ScoreSortBuilder;
@@ -851,17 +853,20 @@ private void registerRescorer(RescorerSpec<?> spec) {
851853
}
852854

853855
private void registerRankers() {
856+
namedWriteables.add(new NamedWriteableRegistry.Entry(RankDoc.class, RankDoc.NAME, RankDoc::new));
854857
namedWriteables.add(new NamedWriteableRegistry.Entry(RankDoc.class, RankFeatureDoc.NAME, RankFeatureDoc::new));
855858
namedWriteables.add(
856859
new NamedWriteableRegistry.Entry(RankShardResult.class, RankFeatureShardResult.NAME, RankFeatureShardResult::new)
857860
);
861+
namedWriteables.add(new NamedWriteableRegistry.Entry(QueryBuilder.class, RankDocsQueryBuilder.NAME, RankDocsQueryBuilder::new));
858862
}
859863

860864
private void registerSorts() {
861865
namedWriteables.add(new NamedWriteableRegistry.Entry(SortBuilder.class, GeoDistanceSortBuilder.NAME, GeoDistanceSortBuilder::new));
862866
namedWriteables.add(new NamedWriteableRegistry.Entry(SortBuilder.class, ScoreSortBuilder.NAME, ScoreSortBuilder::new));
863867
namedWriteables.add(new NamedWriteableRegistry.Entry(SortBuilder.class, ScriptSortBuilder.NAME, ScriptSortBuilder::new));
864868
namedWriteables.add(new NamedWriteableRegistry.Entry(SortBuilder.class, FieldSortBuilder.NAME, FieldSortBuilder::new));
869+
namedWriteables.add(new NamedWriteableRegistry.Entry(SortBuilder.class, RankDocsSortBuilder.NAME, RankDocsSortBuilder::new));
865870
}
866871

867872
private static <T> void registerFromPlugin(List<SearchPlugin> plugins, Function<SearchPlugin, List<T>> producer, Consumer<T> consumer) {

server/src/main/java/org/elasticsearch/search/builder/SearchSourceBuilder.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2228,15 +2228,15 @@ public ActionRequestValidationException validate(
22282228
if (sorts() != null) {
22292229
specified.add(SORT_FIELD.getPreferredName());
22302230
}
2231-
if (rescores() != null) {
2232-
specified.add(RESCORE_FIELD.getPreferredName());
2233-
}
22342231
if (minScore() != null) {
22352232
specified.add(MIN_SCORE_FIELD.getPreferredName());
22362233
}
22372234
if (rankBuilder() != null) {
22382235
specified.add(RANK_FIELD.getPreferredName());
22392236
}
2237+
if (rescores() != null) {
2238+
specified.add(RESCORE_FIELD.getPreferredName());
2239+
}
22402240
if (specified.isEmpty() == false) {
22412241
validationException = addValidationError(
22422242
"cannot specify [" + RETRIEVER.getPreferredName() + "] and " + specified,

server/src/main/java/org/elasticsearch/search/rank/RankDoc.java

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,37 @@
88

99
package org.elasticsearch.search.rank;
1010

11+
import org.apache.lucene.search.Explanation;
1112
import org.apache.lucene.search.ScoreDoc;
1213
import org.elasticsearch.common.io.stream.NamedWriteable;
1314
import org.elasticsearch.common.io.stream.StreamInput;
1415
import org.elasticsearch.common.io.stream.StreamOutput;
16+
import org.elasticsearch.xcontent.ToXContentFragment;
17+
import org.elasticsearch.xcontent.XContentBuilder;
1518

1619
import java.io.IOException;
1720
import java.util.Objects;
1821

1922
/**
2023
* {@code RankDoc} is the base class for all ranked results.
21-
* Subclasses should extend this with additional information
22-
* required for their global ranking method.
24+
* Subclasses should extend this with additional information required for their global ranking method.
2325
*/
24-
public abstract class RankDoc extends ScoreDoc implements NamedWriteable {
26+
public class RankDoc extends ScoreDoc implements NamedWriteable, ToXContentFragment {
27+
28+
public static final String NAME = "rank_doc";
2529

2630
public static final int NO_RANK = -1;
2731

2832
/**
29-
* If this document has been ranked, this is its final
30-
* rrf ranking from all the result sets.
33+
* If this document has been ranked, this is its final rrf ranking from all the result sets.
3134
*/
3235
public int rank = NO_RANK;
3336

37+
@Override
38+
public String getWriteableName() {
39+
return NAME;
40+
}
41+
3442
public record RankKey(int doc, int shardIndex) {}
3543

3644
public RankDoc(int doc, float score, int shardIndex) {
@@ -51,7 +59,26 @@ public final void writeTo(StreamOutput out) throws IOException {
5159
doWriteTo(out);
5260
}
5361

54-
protected abstract void doWriteTo(StreamOutput out) throws IOException;
62+
protected void doWriteTo(StreamOutput out) throws IOException {};
63+
64+
/**
65+
* Explain the ranking of this document.
66+
*/
67+
public Explanation explain() {
68+
return Explanation.match(rank, "doc [" + doc + "] with an original score of [" + score + "] is at rank [" + rank + "].");
69+
}
70+
71+
@Override
72+
public final XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
73+
builder.field("_rank", rank);
74+
builder.field("_doc", doc);
75+
builder.field("_shard", shardIndex);
76+
builder.field("_score", score);
77+
doToXContent(builder, params);
78+
return builder;
79+
}
80+
81+
protected void doToXContent(XContentBuilder builder, Params params) throws IOException {}
5582

5683
@Override
5784
public final boolean equals(Object o) {
@@ -61,17 +88,21 @@ public final boolean equals(Object o) {
6188
return doc == rd.doc && score == rd.score && shardIndex == rd.shardIndex && rank == rd.rank && doEquals(rd);
6289
}
6390

64-
protected abstract boolean doEquals(RankDoc rd);
91+
protected boolean doEquals(RankDoc rd) {
92+
return true;
93+
}
6594

6695
@Override
6796
public final int hashCode() {
6897
return Objects.hash(doc, score, shardIndex, doHashCode());
6998
}
7099

71-
protected abstract int doHashCode();
100+
protected int doHashCode() {
101+
return 0;
102+
}
72103

73104
@Override
74105
public String toString() {
75-
return "RankDoc{" + "score=" + score + ", doc=" + doc + ", shardIndex=" + shardIndex + '}';
106+
return "RankDoc{" + "_rank=" + rank + ", _doc=" + doc + ", _shard=" + shardIndex + ", _score=" + score + '}';
76107
}
77108
}

server/src/main/java/org/elasticsearch/search/rank/feature/RankFeatureDoc.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88

99
package org.elasticsearch.search.rank.feature;
1010

11+
import org.apache.lucene.search.Explanation;
1112
import org.elasticsearch.common.io.stream.StreamInput;
1213
import org.elasticsearch.common.io.stream.StreamOutput;
1314
import org.elasticsearch.search.rank.RankDoc;
15+
import org.elasticsearch.xcontent.XContentBuilder;
1416

1517
import java.io.IOException;
1618
import java.util.Objects;
@@ -22,7 +24,7 @@ public class RankFeatureDoc extends RankDoc {
2224

2325
public static final String NAME = "rank_feature_doc";
2426

25-
// todo: update to support more than 1 fields; and not restrict to string data
27+
// TODO: update to support more than 1 fields; and not restrict to string data
2628
public String featureData;
2729

2830
public RankFeatureDoc(int doc, float score, int shardIndex) {
@@ -34,6 +36,11 @@ public RankFeatureDoc(StreamInput in) throws IOException {
3436
featureData = in.readOptionalString();
3537
}
3638

39+
@Override
40+
public Explanation explain() {
41+
throw new UnsupportedOperationException("explain is not supported for {" + getClass() + "}");
42+
}
43+
3744
public void featureData(String featureData) {
3845
this.featureData = featureData;
3946
}
@@ -58,4 +65,9 @@ protected int doHashCode() {
5865
public String getWriteableName() {
5966
return NAME;
6067
}
68+
69+
@Override
70+
protected void doToXContent(XContentBuilder builder, Params params) throws IOException {
71+
builder.field("featureData", featureData);
72+
}
6173
}

0 commit comments

Comments
 (0)