Skip to content

Commit 7199347

Browse files
author
Lamine Idjeraoui
committed
add a new sort tiebreaker based on shard id and docid
Signed-off-by: Lamine Idjeraoui <lidjeraoui@apple.com>
1 parent d6edffd commit 7199347

File tree

2 files changed

+207
-0
lines changed

2 files changed

+207
-0
lines changed
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*/
8+
9+
package org.opensearch.search.sort;
10+
11+
import org.apache.lucene.index.LeafReaderContext;
12+
import org.apache.lucene.search.FieldComparator;
13+
import org.apache.lucene.search.LeafFieldComparator;
14+
import org.apache.lucene.search.Pruning;
15+
import org.apache.lucene.search.Scorable;
16+
import org.apache.lucene.search.SortField;
17+
import org.opensearch.common.util.BigArrays;
18+
import org.opensearch.index.fielddata.IndexFieldData;
19+
import org.opensearch.search.DocValueFormat;
20+
import org.opensearch.search.MultiValueMode;
21+
import org.opensearch.search.sort.BucketedSort.ExtraData;
22+
23+
/**
24+
* A pseudo‑field (_shard_doc) comparator that tiebreaks by {@code (shardOrd << 32) | globalDocId}
25+
*/
26+
public class ShardDocFieldComparatorSource extends IndexFieldData.XFieldComparatorSource {
27+
public static final String NAME = "_shard_doc";
28+
29+
private final int shardId;
30+
31+
/**
32+
* @param shardId the ordinal of this shard within the coordinating node’s shard list
33+
*/
34+
public ShardDocFieldComparatorSource(int shardId) {
35+
super(null, MultiValueMode.MIN, null);
36+
this.shardId = shardId;
37+
}
38+
39+
@Override
40+
public SortField.Type reducedType() {
41+
return SortField.Type.LONG;
42+
}
43+
44+
@Override
45+
public BucketedSort newBucketedSort(BigArrays bigArrays, SortOrder sortOrder, DocValueFormat format, int bucketSize, ExtraData extra) {
46+
throw new UnsupportedOperationException("bucketed sort not supported for " + NAME);
47+
}
48+
49+
@Override
50+
public FieldComparator<Long> newComparator(String fieldname, int numHits, Pruning pruning, boolean reversed) {
51+
return new FieldComparator<Long>() {
52+
private final long[] values = new long[numHits];
53+
private long bottom;
54+
private long topValue;
55+
56+
@Override
57+
public LeafFieldComparator getLeafComparator(LeafReaderContext context) {
58+
// derive a stable shard ordinal per-segment
59+
long shardOrd = shardId;
60+
final int docBase = context.docBase;
61+
62+
return new LeafFieldComparator() {
63+
Scorable scorer;
64+
65+
@Override
66+
public void setScorer(Scorable scorer) {
67+
this.scorer = scorer;
68+
}
69+
70+
@Override
71+
public void setBottom(int slot) {
72+
bottom = values[slot];
73+
}
74+
75+
@Override
76+
public int compareBottom(int doc) {
77+
long key = ((long) shardId << 32) | (docBase + doc);
78+
return Long.compare(bottom, key);
79+
}
80+
81+
@Override
82+
public void copy(int slot, int doc) {
83+
long key = ((long) shardId << 32) | (docBase + doc);
84+
values[slot] = key;
85+
}
86+
87+
@Override
88+
public int compareTop(int doc) {
89+
long key = ((long) shardId << 32) | (docBase + doc);
90+
return Long.compare(topValue, key);
91+
}
92+
};
93+
}
94+
95+
@Override
96+
public int compare(int slot1, int slot2) {
97+
return Long.compare(values[slot1], values[slot2]);
98+
}
99+
100+
@Override
101+
public Long value(int slot) {
102+
return values[slot];
103+
}
104+
105+
@Override
106+
public void setTopValue(Long value) {
107+
this.topValue = value;
108+
}
109+
};
110+
}
111+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*/
8+
9+
package org.opensearch.search.sort;
10+
11+
import org.apache.lucene.search.SortField;
12+
import org.opensearch.core.common.io.stream.StreamInput;
13+
import org.opensearch.core.common.io.stream.StreamOutput;
14+
import org.opensearch.core.xcontent.ObjectParser;
15+
import org.opensearch.core.xcontent.XContentBuilder;
16+
import org.opensearch.core.xcontent.XContentParser;
17+
import org.opensearch.index.query.QueryRewriteContext;
18+
import org.opensearch.index.query.QueryShardContext;
19+
import org.opensearch.search.DocValueFormat;
20+
21+
import java.io.IOException;
22+
import java.util.Objects;
23+
24+
/**
25+
* Sort builder for the pseudo‐field "_shard_doc", which tiebreaks by {@code (shardOrd << 32) | globalDocId}.
26+
*/
27+
public class ShardDocSortBuilder extends SortBuilder<ShardDocSortBuilder> {
28+
29+
public static final String NAME = "_shard_doc";
30+
31+
// parser for JSON: { "_shard_doc": { "order":"asc" } }
32+
private static final ObjectParser<ShardDocSortBuilder, Void> PARSER = new ObjectParser<>(NAME, ShardDocSortBuilder::new);
33+
34+
static {
35+
PARSER.declareString((b, s) -> b.order(SortOrder.fromString(s)), ORDER_FIELD);
36+
}
37+
38+
public ShardDocSortBuilder() {}
39+
40+
public ShardDocSortBuilder(StreamInput in) throws IOException {
41+
this.order = SortOrder.readFromStream(in);
42+
43+
}
44+
45+
@Override
46+
public void writeTo(StreamOutput out) throws IOException {
47+
order.writeTo(out);
48+
}
49+
50+
public static ShardDocSortBuilder fromXContent(XContentParser parser, String fieldName) throws IOException {
51+
return PARSER.parse(parser, null);
52+
}
53+
54+
@Override
55+
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
56+
builder.startObject(NAME);
57+
builder.field(ORDER_FIELD.getPreferredName(), order);
58+
builder.endObject();
59+
return builder;
60+
}
61+
62+
@Override
63+
protected SortFieldAndFormat build(QueryShardContext context) {
64+
final int shardId = context.getShardId();
65+
SortField sf = new SortField(NAME, new ShardDocFieldComparatorSource(shardId), order == SortOrder.DESC);
66+
return new SortFieldAndFormat(sf, DocValueFormat.RAW);
67+
}
68+
69+
@Override
70+
public BucketedSort buildBucketedSort(QueryShardContext context, int bucketSize, BucketedSort.ExtraData extra) throws IOException {
71+
throw new UnsupportedOperationException("bucketed sort not supported for " + NAME);
72+
}
73+
74+
@Override
75+
public ShardDocSortBuilder rewrite(QueryRewriteContext ctx) {
76+
return this;
77+
}
78+
79+
@Override
80+
public String getWriteableName() {
81+
return NAME;
82+
}
83+
84+
@Override
85+
public boolean equals(Object obj) {
86+
if (this == obj) return true;
87+
if (obj == null || getClass() != obj.getClass()) return false;
88+
ShardDocSortBuilder other = (ShardDocSortBuilder) obj;
89+
return order == other.order;
90+
}
91+
92+
@Override
93+
public int hashCode() {
94+
return Objects.hash(order);
95+
}
96+
}

0 commit comments

Comments
 (0)