Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,13 @@ setup:
- match: { items.1.index._primary_term: 0 }

---
"requesting seq_no_primary_term is rejected":
"seq_no_primary_term returns sentinel values":
- do:
catch: bad_request
search:
index: test
body:
seq_no_primary_term: true
- match: { error.root_cause.0.type: "illegal_argument_exception" }
- match: { error.root_cause.0.reason: "Cannot request seq_no_primary_term on index [test] because [index.disable_sequence_numbers] is [true]" }
- match: { hits.total.value: 1 }
- match: { hits.hits.0._seq_no: -2 }
- match: { hits.hits.0._primary_term: 0 }

Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.elasticsearch.rest.action.RestActions;
import org.elasticsearch.rest.action.RestCancellableNodeClient;
import org.elasticsearch.rest.action.RestRefCountedChunkedToXContentListener;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchService;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.crossproject.CrossProjectModeDecider;
Expand All @@ -37,12 +38,14 @@
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.term.TermSuggestionBuilder;
import org.elasticsearch.usage.SearchUsageHolder;
import org.elasticsearch.xcontent.ToXContent;
import org.elasticsearch.xcontent.XContentParser;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.IntConsumer;
Expand Down Expand Up @@ -139,7 +142,8 @@ public RestChannelConsumer prepareRequest(final RestRequest request, final NodeC

return channel -> {
RestCancellableNodeClient cancelClient = new RestCancellableNodeClient(client, request.getHttpChannel());
cancelClient.execute(TransportSearchAction.TYPE, searchRequest, new RestRefCountedChunkedToXContentListener<>(channel));
var params = serializationParams(searchRequest, channel.request());
cancelClient.execute(TransportSearchAction.TYPE, searchRequest, new RestRefCountedChunkedToXContentListener<>(channel, params));
};
}

Expand Down Expand Up @@ -474,6 +478,15 @@ private static void checkSearchType(RestRequest restRequest, SearchRequest searc
}
}

private static ToXContent.Params serializationParams(SearchRequest searchRequest, ToXContent.Params channelParams) {
if (searchRequest.source() != null
&& searchRequest.source().seqNoAndPrimaryTerm() != null
&& searchRequest.source().seqNoAndPrimaryTerm()) {
return new ToXContent.DelegatingMapParams(Map.of(SearchHit.SEQ_NO_PRIMARY_TERM_PARAMS_KEY, "true"), channelParams);
}
return channelParams;
}

@Override
protected Set<String> responseParams() {
return RESPONSE_PARAMS;
Expand Down
8 changes: 7 additions & 1 deletion server/src/main/java/org/elasticsearch/search/SearchHit.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ public final class SearchHit implements Writeable, ToXContentObject, RefCounted
static final float DEFAULT_SCORE = Float.NaN;
private float score;

/**
* ToXContent param key that, when set to {@code true}, causes {@code _seq_no} and {@code _primary_term} to be
* emitted even when they hold sentinel (unassigned) values. Used for indices with sequence numbers disabled.
*/
public static final String SEQ_NO_PRIMARY_TERM_PARAMS_KEY = "seq_no_primary_term";

static final int NO_RANK = -1;
private int rank;

Expand Down Expand Up @@ -832,7 +838,7 @@ public XContentBuilder toInnerXContent(XContentBuilder builder, Params params) t
builder.field(Fields._VERSION, version);
}

if (seqNo != SequenceNumbers.UNASSIGNED_SEQ_NO) {
if (seqNo != SequenceNumbers.UNASSIGNED_SEQ_NO || params.paramAsBoolean(SEQ_NO_PRIMARY_TERM_PARAMS_KEY, false)) {
builder.field(Fields._SEQ_NO, seqNo);
builder.field(Fields._PRIMARY_TERM, primaryTerm);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1870,13 +1870,6 @@ private void parseSource(DefaultSearchContext context, SearchSourceBuilder sourc
}

if (source.seqNoAndPrimaryTerm() != null) {
if (source.seqNoAndPrimaryTerm() && context.getSearchExecutionContext().getIndexSettings().sequenceNumbersDisabled()) {
throw new IllegalArgumentException(
"Cannot request seq_no_primary_term on index ["
+ context.getSearchExecutionContext().index().getName()
+ "] because [index.disable_sequence_numbers] is [true]"
);
}
context.seqNoAndPrimaryTerm(source.seqNoAndPrimaryTerm());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,31 @@
import java.io.IOException;

public final class SeqNoPrimaryTermPhase implements FetchSubPhase {

private static final FetchSubPhaseProcessor UNASSIGNED_PROCESSOR = new FetchSubPhaseProcessor() {
@Override
public void setNextReader(LeafReaderContext readerContext) {}

@Override
public StoredFieldsSpec storedFieldsSpec() {
return StoredFieldsSpec.NO_REQUIREMENTS;
}

@Override
public void process(HitContext hitContext) {
hitContext.hit().setSeqNo(SequenceNumbers.UNASSIGNED_SEQ_NO);
hitContext.hit().setPrimaryTerm(SequenceNumbers.UNASSIGNED_PRIMARY_TERM);
}
};

@Override
public FetchSubPhaseProcessor getProcessor(FetchContext context) {
if (context.seqNoAndPrimaryTerm() == false) {
return null;
}
if (context.getSearchExecutionContext().getIndexSettings().sequenceNumbersDisabled()) {
return UNASSIGNED_PROCESSOR;
}
return new FetchSubPhaseProcessor() {

NumericDocValues seqNoField = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
import org.elasticsearch.index.query.SearchExecutionContext;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.index.search.stats.SearchStats;
import org.elasticsearch.index.seqno.SequenceNumbers;
import org.elasticsearch.index.shard.IndexShard;
import org.elasticsearch.index.shard.SearchOperationListener;
import org.elasticsearch.index.shard.ShardId;
Expand Down Expand Up @@ -166,6 +167,7 @@
import static org.elasticsearch.search.SearchService.SEARCH_WORKER_THREADS_ENABLED;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailuresAndResponse;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertResponse;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo;
Expand Down Expand Up @@ -2898,7 +2900,7 @@ public void testSlicingBehaviourForParallelCollection() throws Exception {
}
}

public void testSeqNoAndPrimaryTermRejectedWhenSequenceNumbersDisabled() throws IOException {
public void testSeqNoAndPrimaryTermReturnsSentinelsWhenSequenceNumbersDisabled() {
assumeTrue("Test should only run with feature flag", IndexSettings.DISABLE_SEQUENCE_NUMBERS_FEATURE_FLAG);
final Settings settings = Settings.builder()
.put(IndexSettings.DISABLE_SEQUENCE_NUMBERS.getKey(), true)
Expand All @@ -2907,37 +2909,11 @@ public void testSeqNoAndPrimaryTermRejectedWhenSequenceNumbersDisabled() throws
createIndex("test-no-seqno", settings);
prepareIndex("test-no-seqno").setId("1").setSource("field", "value").setRefreshPolicy(IMMEDIATE).get();

final SearchService service = getInstanceFromNode(SearchService.class);
final IndicesService indicesService = getInstanceFromNode(IndicesService.class);
final IndexService indexService = indicesService.indexServiceSafe(resolveIndex("test-no-seqno"));
final IndexShard indexShard = indexService.getShard(0);

SearchRequest searchRequest = new SearchRequest().allowPartialSearchResults(true);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.seqNoAndPrimaryTerm(true);
searchRequest.source(searchSourceBuilder);

final ShardSearchRequest request = new ShardSearchRequest(
OriginalIndices.NONE,
searchRequest,
indexShard.shardId(),
0,
1,
AliasFilter.EMPTY,
1.0f,
-1,
null
);
try (ReaderContext reader = createReaderContext(indexService, indexShard)) {
IllegalArgumentException ex = expectThrows(
IllegalArgumentException.class,
() -> service.createContext(reader, request, mock(SearchShardTask.class), ResultsType.NONE, randomBoolean())
);
assertEquals(
"Cannot request seq_no_primary_term on index [test-no-seqno] because [index.disable_sequence_numbers] is [true]",
ex.getMessage()
);
}
assertNoFailuresAndResponse(client().prepareSearch("test-no-seqno").seqNoAndPrimaryTerm(true), response -> {
assertHitCount(response, 1);
assertEquals(SequenceNumbers.UNASSIGNED_SEQ_NO, response.getHits().getAt(0).getSeqNo());
assertEquals(SequenceNumbers.UNASSIGNED_PRIMARY_TERM, response.getHits().getAt(0).getPrimaryTerm());
});
}

private static ReaderContext createReaderContext(IndexService indexService, IndexShard indexShard) {
Expand Down
Loading