From 3c3d2be4bc9d0cbd6d9d8bf547c6390f8a47aaa2 Mon Sep 17 00:00:00 2001 From: Alan Woodward Date: Wed, 11 Mar 2026 17:03:23 +0000 Subject: [PATCH] RandomScoreFunction requires a defined field when sequence numbers are disabled We can't fall back to using sequence numbers as a source for random scores when they are disabled, so instead a random score function defined with no seed and no source field will throw an error in this situation. --- .../RandomScoreFunctionBuilder.java | 16 ++++- .../RandomScoreFunctionBuilderTests.java | 72 +++++++++++++++++++ 2 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 server/src/test/java/org/elasticsearch/index/query/functionscore/RandomScoreFunctionBuilderTests.java diff --git a/server/src/main/java/org/elasticsearch/index/query/functionscore/RandomScoreFunctionBuilder.java b/server/src/main/java/org/elasticsearch/index/query/functionscore/RandomScoreFunctionBuilder.java index 80f54863b9227..ea43c63c6fc8f 100644 --- a/server/src/main/java/org/elasticsearch/index/query/functionscore/RandomScoreFunctionBuilder.java +++ b/server/src/main/java/org/elasticsearch/index/query/functionscore/RandomScoreFunctionBuilder.java @@ -136,7 +136,7 @@ protected ScoreFunction doToFunction(SearchExecutionContext context) { // DocID-based random score generation return new RandomScoreFunction(hash(context.nowInMillis()), salt, null); } else { - final String fieldName = Objects.requireNonNullElse(field, SeqNoFieldMapper.NAME); + final String fieldName = field(context); if (context.isFieldMapped(fieldName) == false) { if (context.hasMappings() == false) { // no mappings: the index is empty anyway @@ -155,6 +155,20 @@ protected ScoreFunction doToFunction(SearchExecutionContext context) { } } + private String field(SearchExecutionContext context) { + if (field != null) { + return field; + } + if (context.getIndexSettings().sequenceNumbersDisabled()) { + throw new IllegalArgumentException( + "random_score requires a [field] parameter when [index.disable_sequence_numbers] is [true] on index [" + + context.index() + + "]" + ); + } + return SeqNoFieldMapper.NAME; + } + private static int hash(long value) { return Long.hashCode(value); } diff --git a/server/src/test/java/org/elasticsearch/index/query/functionscore/RandomScoreFunctionBuilderTests.java b/server/src/test/java/org/elasticsearch/index/query/functionscore/RandomScoreFunctionBuilderTests.java new file mode 100644 index 0000000000000..908adbd2a911f --- /dev/null +++ b/server/src/test/java/org/elasticsearch/index/query/functionscore/RandomScoreFunctionBuilderTests.java @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.index.query.functionscore; + +import org.elasticsearch.common.lucene.search.function.RandomScoreFunction; +import org.elasticsearch.common.lucene.search.function.ScoreFunction; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.index.IndexSettings; +import org.elasticsearch.index.IndexVersion; +import org.elasticsearch.index.mapper.SeqNoFieldMapper; +import org.elasticsearch.index.query.SearchExecutionContext; +import org.elasticsearch.test.AbstractBuilderTestCase; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.instanceOf; + +/** + * Tests for {@link RandomScoreFunctionBuilder} on an index with sequence numbers disabled. + * The default _seq_no behavior is covered by {@link FunctionScoreQueryBuilderTests}. + */ +public class RandomScoreFunctionBuilderTests extends AbstractBuilderTestCase { + + @Override + protected Settings createTestIndexSettings() { + assumeTrue("Test requires disable_sequence_numbers feature flag", IndexSettings.DISABLE_SEQUENCE_NUMBERS_FEATURE_FLAG); + return Settings.builder() + .put("index.version.created", IndexVersion.current()) + .put(IndexSettings.DISABLE_SEQUENCE_NUMBERS.getKey(), true) + .put(IndexSettings.SEQ_NO_INDEX_OPTIONS_SETTING.getKey(), SeqNoFieldMapper.SeqNoIndexOptions.DOC_VALUES_ONLY) + .build(); + } + + public void testRandomScoreWithoutFieldRequiresFieldWhenSeqNoDisabled() throws Exception { + assumeTrue("Test requires disable_sequence_numbers feature flag", IndexSettings.DISABLE_SEQUENCE_NUMBERS_FEATURE_FLAG); + RandomScoreFunctionBuilder builder = new RandomScoreFunctionBuilder(); + builder.seed(42); + SearchExecutionContext context = createSearchExecutionContext(); + assertTrue(context.getIndexSettings().sequenceNumbersDisabled()); + IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> builder.toFunction(context)); + assertThat(e.getMessage(), containsString("random_score requires a [field] parameter")); + assertThat(e.getMessage(), containsString("index.disable_sequence_numbers")); + } + + public void testRandomScoreWithExplicitFieldWhenSeqNoDisabled() throws Exception { + assumeTrue("Test requires disable_sequence_numbers feature flag", IndexSettings.DISABLE_SEQUENCE_NUMBERS_FEATURE_FLAG); + RandomScoreFunctionBuilder builder = new RandomScoreFunctionBuilder(); + builder.seed(42); + builder.setField(KEYWORD_FIELD_NAME); + SearchExecutionContext context = createSearchExecutionContext(); + assertTrue(context.getIndexSettings().sequenceNumbersDisabled()); + ScoreFunction function = builder.toFunction(context); + assertNotNull(function); + assertThat(function, instanceOf(RandomScoreFunction.class)); + } + + public void testRandomScoreWithoutSeedFallsBackToDocIdWhenSeqNoDisabled() throws Exception { + assumeTrue("Test requires disable_sequence_numbers feature flag", IndexSettings.DISABLE_SEQUENCE_NUMBERS_FEATURE_FLAG); + RandomScoreFunctionBuilder builder = new RandomScoreFunctionBuilder(); + SearchExecutionContext context = createSearchExecutionContext(); + assertTrue(context.getIndexSettings().sequenceNumbersDisabled()); + ScoreFunction function = builder.toFunction(context); + assertNotNull(function); + assertThat(function, instanceOf(RandomScoreFunction.class)); + } +}