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
6 changes: 6 additions & 0 deletions docs/changelog/141534.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
area: Search
issues:
- 141075
pr: 141534
summary: Fixes bug that prevented retrieving _inference_fields via _source_includes
type: bug
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import org.elasticsearch.features.FeatureSpecification;
import org.elasticsearch.features.NodeFeature;
import org.elasticsearch.index.mapper.InferenceMetadataFieldsMapper;

import java.util.Set;

Expand Down Expand Up @@ -44,7 +45,8 @@ public Set<NodeFeature> getTestFeatures() {
SYNONYMS_SET_LENIENT_ON_NON_EXISTING,
THROW_EXCEPTION_FOR_UNKNOWN_TOKEN_IN_REST_INDEX_PUT_ALIAS_ACTION,
THROW_EXCEPTION_ON_INDEX_CREATION_IF_UNSUPPORTED_VALUE_TYPE_IN_ALIAS,
SHADOWING_DIMENSIONS_AND_METRICS_IS_VALID_IN_NON_TSDB
SHADOWING_DIMENSIONS_AND_METRICS_IS_VALID_IN_NON_TSDB,
InferenceMetadataFieldsMapper.INFERENCE_FIELDS_GET_VIA_SOURCE_INCLUDES
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -258,8 +258,7 @@ protected Source addSyntheticFields(Source originalSource, int segmentDocID) thr
if (values.isEmpty()) {
return originalSource;
}
originalSource.source().put(InferenceMetadataFieldsMapper.NAME, values.get(0));
return Source.fromMap(originalSource.source(), originalSource.sourceContentType());
return originalSource.withMutations(map -> map.put(InferenceMetadataFieldsMapper.NAME, values.get(0)));
}

static IndexSearcher newIndexSearcher(Engine.Searcher engineSearcher) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -623,9 +623,7 @@ private static Source addInferenceMetadataFields(MapperService mapperService, Le
inferenceLoader.setNextReader(readerContext);
List<Object> values = inferenceLoader.fetchValues(source, docId, List.of());
if (values.size() == 1) {
var newSource = source.source();
newSource.put(InferenceMetadataFieldsMapper.NAME, values.get(0));
return Source.fromMap(newSource, source.sourceContentType());
return source.withMutations(map -> map.put(InferenceMetadataFieldsMapper.NAME, values.get(0)));
}
return source;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.elasticsearch.cluster.metadata.IndexMetadata;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.features.NodeFeature;
import org.elasticsearch.index.IndexVersion;
import org.elasticsearch.index.IndexVersions;
import org.elasticsearch.index.query.SearchExecutionContext;
Expand Down Expand Up @@ -49,6 +50,8 @@ public abstract class InferenceMetadataFieldsMapper extends MetadataFieldMapper
public static final String NAME = "_inference_fields";
public static final String CONTENT_TYPE = "_inference_fields";

public static final NodeFeature INFERENCE_FIELDS_GET_VIA_SOURCE_INCLUDES = new NodeFeature("inference_fields.get_via_source_includes");

protected InferenceMetadataFieldsMapper(MappedFieldType inferenceFieldType) {
super(inferenceFieldType);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
Expand Down Expand Up @@ -467,13 +466,7 @@ public void write(LeafStoredFieldLoader storedFields, int docId, XContentBuilder
* @return a new {@link Source} with the patches applied
*/
static Source applySyntheticVectors(Source originalSource, List<SyntheticVectorPatch> patches) {
Map<String, Object> newMap = originalSource.source();
// Make sure we have a mutable map, empty implies `Map.of()`
if (newMap.isEmpty()) {
newMap = new LinkedHashMap<>();
}
applyPatches("", newMap, patches);
return Source.fromMap(newMap, originalSource.sourceContentType());
return originalSource.withMutations(map -> applyPatches("", map, patches));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import org.elasticsearch.search.lookup.Source;
import org.elasticsearch.search.lookup.SourceFilter;

import java.util.HashMap;
import java.util.Map;

public final class FetchSourcePhase implements FetchSubPhase {
Expand Down Expand Up @@ -99,13 +98,7 @@ private Source replaceInferenceMetadataFields(SearchHit hit, Source source) {
if (field == null || field.getValues().isEmpty()) {
return source;
}
var newSource = source.source();
if (newSource instanceof HashMap == false) {
// the map is not mutable
newSource = new HashMap<>(newSource);
}
newSource.put(InferenceMetadataFieldsMapper.NAME, field.getValues().get(0));
return Source.fromMap(newSource, source.sourceContentType());
return source.withMutations(map -> map.put(InferenceMetadataFieldsMapper.NAME, field.getValues().get(0)));
}

@Override
Expand Down
54 changes: 48 additions & 6 deletions server/src/main/java/org/elasticsearch/search/lookup/Source.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,21 @@

import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Supplier;

/**
* The source of a document. Note that, while objects returned from {@link #source()}
* and {@link #internalSourceRef()} are immutable, objects implementing this interface
* may not be immutable themselves.
* A read-only view of a document's source.
*
* <p>This interface provides access to source content as either bytes or a parsed map.
* A {@code Source} instance represents a stable snapshot of a particular document's
* data.
*
* <p>Note: The Map returned by {@link #source()} may or may not be immutable.
* Use {@link #withMutations(Consumer)} for any mutations on the Map returned by {@link #source()}.
*/
public interface Source {

Expand All @@ -36,9 +44,12 @@ public interface Source {
XContentType sourceContentType();

/**
* A map representation of the source
* <p>
* Important: This can lose precision on numbers with a decimal point. It
* A map representation of the source.
*
* <p><b>IMPORTANT</b>: The returned map may be immutable. To modify the source,
* use {@link #withMutations(java.util.function.Consumer)} instead.
*
* <p>This can lose precision on numbers with a decimal point. It
* converts numbers like {@code "n": 1234.567} to a {@code double} which
* only has 52 bits of precision in the mantissa. This will come up most
* frequently when folks write nanosecond precision dates as a decimal
Expand All @@ -56,6 +67,37 @@ public interface Source {
*/
Source filter(SourceFilter sourceFilter);

/**
* Returns a new Source with mutations applied to the map.
*
* <p>Use this when you need to add, remove, or update fields on a map that may or may
* not be mutable.
*
* <p><b>WARNING</b>: If the underlying source map is already a {@link java.util.HashMap}
* (including {@link java.util.LinkedHashMap}), the original map will be mutated directly
* rather than copied. This is intentional for performance, but callers should be aware
* that the original Source's map may be modified as a side effect.
*
* <pre>
* Source modified = source.withMutations(map -&gt; map.put("field", "value"));
* </pre>
*
* @param mutator a function that modifies the map
* @return a new Source with the mutations applied. There is no guarantee about the mutability of the returned value's source() map.
*/
default Source withMutations(java.util.function.Consumer<Map<String, Object>> mutator) {
Map<String, Object> map = source();
if (map == null) {
map = new LinkedHashMap<>();
} else if (map instanceof HashMap == false) {
// bit of a hack to test for mutability...there aren't great options
map = new LinkedHashMap<>(map);
}

mutator.accept(map);
return Source.fromMap(map, sourceContentType());
}

/**
* For the provided path, return its value in the source.
*
Expand Down
Loading