diff --git a/server/src/main/java/org/elasticsearch/common/xcontent/support/XContentMapValues.java b/server/src/main/java/org/elasticsearch/common/xcontent/support/XContentMapValues.java
index 74bf3b9b5ff62..cf65754f01ac8 100644
--- a/server/src/main/java/org/elasticsearch/common/xcontent/support/XContentMapValues.java
+++ b/server/src/main/java/org/elasticsearch/common/xcontent/support/XContentMapValues.java
@@ -21,6 +21,7 @@
import org.elasticsearch.core.TimeValue;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -574,4 +575,118 @@ public static String[] nodeStringArrayValue(Object node) {
return Strings.splitStringByCommaToArray(node.toString());
}
}
+
+ public static void insertValue(String path, Map, ?> map, Object newValue) {
+ insertValue(path, map, newValue, true);
+ }
+
+ /**
+ *
+ * Insert or replace the path's value in the map with the provided new value. The map will be modified in-place.
+ * If the complete path does not exist in the map, it will be added to the deepest (sub-)map possible.
+ *
+ *
+ * For example, given the map:
+ *
+ *
+ * {
+ * "path1": {
+ * "path2": {
+ * "key1": "value1"
+ * }
+ * }
+ * }
+ *
+ *
+ * And the caller wanted to insert {@code "path1.path2.path3.key2": "value2"}, the method would emit the modified map:
+ *
+ *
+ * {
+ * "path1": {
+ * "path2": {
+ * "key1": "value1",
+ * "path3.key2": "value2"
+ * }
+ * }
+ * }
+ *
+ *
+ * @param path the value's path in the map.
+ * @param map the map to search and modify in-place.
+ * @param newValue the new value to assign to the path.
+ * @param failOnMultiMap whether the insertion should fail with an {@link IllegalArgumentException}
+ * if the path can be resolved in multiple ways.
+ *
+ * @throws IllegalArgumentException If either the path cannot be fully traversed.
+ */
+ public static void insertValue(String path, Map, ?> map, Object newValue, boolean failOnMultiMap) {
+ String[] pathElements = path.split("\\.");
+ if (pathElements.length == 0) {
+ return;
+ }
+
+ List suffixMaps = extractSuffixMaps(pathElements, 0, map);
+ if (suffixMaps.isEmpty()) {
+ // This should never happen. Throw in case it does for some reason.
+ throw new IllegalStateException("extractSuffixMaps returned an empty suffix map list");
+ } else if (suffixMaps.size() > 1 && failOnMultiMap) {
+ throw new IllegalArgumentException(
+ "Path [" + path + "] could be inserted in " + suffixMaps.size() + " distinct ways, it is ambiguous which one to use"
+ );
+ }
+ SuffixMap suffixMap = suffixMaps.getFirst();
+ suffixMap.map().put(suffixMap.suffix(), newValue);
+ }
+
+ record SuffixMap(String suffix, Map map) {}
+
+ private static List extractSuffixMaps(String[] pathElements, int index, Object currentValue) {
+ if (currentValue instanceof List> valueList) {
+ List suffixMaps = new ArrayList<>(valueList.size());
+ for (Object o : valueList) {
+ suffixMaps.addAll(extractSuffixMaps(pathElements, index, o));
+ }
+
+ return suffixMaps;
+ } else if (currentValue instanceof Map, ?>) {
+ @SuppressWarnings("unchecked")
+ Map map = (Map) currentValue;
+ List suffixMaps = new ArrayList<>(map.size());
+
+ String key = pathElements[index];
+ while (index < pathElements.length) {
+ if (map.containsKey(key)) {
+ if (index + 1 == pathElements.length) {
+ // We found the complete path
+ suffixMaps.add(new SuffixMap(key, map));
+ } else {
+ // We've matched that path partially, keep traversing to try to match it fully
+ suffixMaps.addAll(extractSuffixMaps(pathElements, index + 1, map.get(key)));
+ }
+ }
+
+ if (++index < pathElements.length) {
+ key += "." + pathElements[index];
+ }
+ }
+
+ if (suffixMaps.isEmpty()) {
+ // We checked for all remaining elements in the path, and they do not exist. This means we found a leaf map that we should
+ // add the value to.
+ suffixMaps.add(new SuffixMap(key, map));
+ }
+
+ return suffixMaps;
+ } else {
+ throw new IllegalArgumentException(
+ "Path ["
+ + String.join(".", Arrays.copyOfRange(pathElements, 0, index))
+ + "] has value ["
+ + currentValue
+ + "] of type ["
+ + currentValue.getClass().getSimpleName()
+ + "], which cannot be traversed into further"
+ );
+ }
+ }
}
diff --git a/server/src/main/java/org/elasticsearch/index/mapper/SourceLoader.java b/server/src/main/java/org/elasticsearch/index/mapper/SourceLoader.java
index 54d44219231f0..d3380a9a8f05b 100644
--- a/server/src/main/java/org/elasticsearch/index/mapper/SourceLoader.java
+++ b/server/src/main/java/org/elasticsearch/index/mapper/SourceLoader.java
@@ -476,7 +476,7 @@ private static void applyPatches(String rootPath, Map map, List<
for (SyntheticVectorPatch patch : patches) {
if (patch instanceof LeafSyntheticVectorPath leaf) {
String key = extractRelativePath(rootPath, leaf.fullPath());
- map.put(key, leaf.value());
+ XContentMapValues.insertValue(key, map, leaf.value(), false);
} else if (patch instanceof NestedSyntheticVectorPath nested) {
String nestedPath = extractRelativePath(rootPath, nested.fullPath());
List> nestedMaps = XContentMapValues.extractNestedSources(nestedPath, map);
diff --git a/server/src/test/java/org/elasticsearch/common/xcontent/support/XContentMapValuesTests.java b/server/src/test/java/org/elasticsearch/common/xcontent/support/XContentMapValuesTests.java
index 82bb6113a3b0f..a658f57e55699 100644
--- a/server/src/test/java/org/elasticsearch/common/xcontent/support/XContentMapValuesTests.java
+++ b/server/src/test/java/org/elasticsearch/common/xcontent/support/XContentMapValuesTests.java
@@ -28,6 +28,8 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Stream;
import static org.elasticsearch.common.xcontent.XContentHelper.convertToMap;
import static org.elasticsearch.common.xcontent.XContentHelper.toXContent;
@@ -730,4 +732,326 @@ public void testExtractNestedSources() {
)
);
}
+
+ public void testInsertValueMapTraversal() throws IOException {
+ {
+ XContentBuilder builder = XContentFactory.jsonBuilder().startObject().field("test", "value").endObject();
+
+ Map map = toSourceMap(Strings.toString(builder));
+ XContentMapValues.insertValue("test", map, "value2");
+ assertThat(getMapValue(map, "test"), Matchers.equalTo("value2"));
+ XContentMapValues.insertValue("something.else", map, "something_else_value");
+ assertThat(getMapValue(map, "something\\.else"), Matchers.equalTo("something_else_value"));
+ }
+ {
+ XContentBuilder builder = XContentFactory.jsonBuilder().startObject();
+ builder.startObject("path1").startObject("path2").field("test", "value").endObject().endObject();
+ builder.endObject();
+
+ Map map = toSourceMap(Strings.toString(builder));
+ XContentMapValues.insertValue("path1.path2.test", map, "value2");
+ assertThat(getMapValue(map, "path1.path2.test"), Matchers.equalTo("value2"));
+ XContentMapValues.insertValue("path1.path2.test_me", map, "test_me_value");
+ assertThat(getMapValue(map, "path1.path2.test_me"), Matchers.equalTo("test_me_value"));
+ XContentMapValues.insertValue("path1.non_path2.test", map, "test_value");
+ assertThat(getMapValue(map, "path1.non_path2\\.test"), Matchers.equalTo("test_value"));
+
+ XContentMapValues.insertValue("path1.path2", map, Map.of("path3", "bar"));
+ assertThat(getMapValue(map, "path1.path2"), Matchers.equalTo(Map.of("path3", "bar")));
+
+ XContentMapValues.insertValue("path1", map, "baz");
+ assertThat(getMapValue(map, "path1"), Matchers.equalTo("baz"));
+
+ XContentMapValues.insertValue("path3.path4", map, Map.of("test", "foo"));
+ assertThat(getMapValue(map, "path3\\.path4"), Matchers.equalTo(Map.of("test", "foo")));
+ }
+ {
+ XContentBuilder builder = XContentFactory.jsonBuilder().startObject();
+ builder.startObject("path1").array("test", "value1", "value2").endObject();
+ builder.endObject();
+ Map map = toSourceMap(Strings.toString(builder));
+
+ XContentMapValues.insertValue("path1.test", map, List.of("value3", "value4", "value5"));
+ assertThat(getMapValue(map, "path1.test"), Matchers.equalTo(List.of("value3", "value4", "value5")));
+
+ XContentMapValues.insertValue("path2.test", map, List.of("value6", "value7", "value8"));
+ assertThat(getMapValue(map, "path2\\.test"), Matchers.equalTo(List.of("value6", "value7", "value8")));
+ }
+ }
+
+ public void testInsertValueListTraversal() throws IOException {
+ {
+ XContentBuilder builder = XContentFactory.jsonBuilder().startObject();
+ {
+ builder.startObject("path1");
+ {
+ builder.startArray("path2");
+ builder.startObject().field("test", "value1").endObject();
+ builder.endArray();
+ }
+ builder.endObject();
+ }
+ {
+ builder.startObject("path3");
+ {
+ builder.startArray("path4");
+ builder.startObject().field("test", "value1").endObject();
+ builder.endArray();
+ }
+ builder.endObject();
+ }
+ builder.endObject();
+ Map map = toSourceMap(Strings.toString(builder));
+
+ XContentMapValues.insertValue("path1.path2.test", map, "value2");
+ assertThat(getMapValue(map, "path1.path2.test"), Matchers.equalTo("value2"));
+ XContentMapValues.insertValue("path1.path2.test2", map, "value3");
+ assertThat(getMapValue(map, "path1.path2.test2"), Matchers.equalTo("value3"));
+ assertThat(getMapValue(map, "path1.path2"), Matchers.equalTo(List.of(Map.of("test", "value2", "test2", "value3"))));
+
+ XContentMapValues.insertValue("path3.path4.test", map, "value4");
+ assertThat(getMapValue(map, "path3.path4.test"), Matchers.equalTo("value4"));
+ }
+ {
+ XContentBuilder builder = XContentFactory.jsonBuilder().startObject();
+ {
+ builder.startObject("path1");
+ {
+ builder.startArray("path2");
+ builder.startArray();
+ builder.startObject().field("test", "value1").endObject();
+ builder.endArray();
+ builder.endArray();
+ }
+ builder.endObject();
+ }
+ builder.endObject();
+ Map map = toSourceMap(Strings.toString(builder));
+
+ XContentMapValues.insertValue("path1.path2.test", map, "value2");
+ assertThat(getMapValue(map, "path1.path2.test"), Matchers.equalTo("value2"));
+ XContentMapValues.insertValue("path1.path2.test2", map, "value3");
+ assertThat(getMapValue(map, "path1.path2.test2"), Matchers.equalTo("value3"));
+ assertThat(getMapValue(map, "path1.path2"), Matchers.equalTo(List.of(List.of(Map.of("test", "value2", "test2", "value3")))));
+ }
+ }
+
+ public void testInsertValueFieldsWithDots() throws IOException {
+ {
+ XContentBuilder builder = XContentFactory.jsonBuilder().startObject().field("xxx.yyy", "value1").endObject();
+ Map map = toSourceMap(Strings.toString(builder));
+
+ XContentMapValues.insertValue("xxx.yyy", map, "value2");
+ assertThat(getMapValue(map, "xxx\\.yyy"), Matchers.equalTo("value2"));
+
+ XContentMapValues.insertValue("xxx", map, "value3");
+ assertThat(getMapValue(map, "xxx"), Matchers.equalTo("value3"));
+ }
+ {
+ XContentBuilder builder = XContentFactory.jsonBuilder().startObject();
+ {
+ builder.startObject("path1.path2");
+ {
+ builder.startObject("path3.path4");
+ builder.field("test", "value1");
+ builder.endObject();
+ }
+ builder.endObject();
+ }
+ builder.endObject();
+ Map map = toSourceMap(Strings.toString(builder));
+
+ XContentMapValues.insertValue("path1.path2.path3.path4.test", map, "value2");
+ assertThat(getMapValue(map, "path1\\.path2.path3\\.path4.test"), Matchers.equalTo("value2"));
+
+ XContentMapValues.insertValue("path1.path2.path3.path4.test2", map, "value3");
+ assertThat(getMapValue(map, "path1\\.path2.path3\\.path4.test2"), Matchers.equalTo("value3"));
+ assertThat(getMapValue(map, "path1\\.path2.path3\\.path4"), Matchers.equalTo(Map.of("test", "value2", "test2", "value3")));
+ }
+ }
+
+ public void testInsertValueAmbiguousPath() throws IOException {
+ // Mixed dotted object notation
+ {
+ XContentBuilder builder = XContentFactory.jsonBuilder().startObject();
+ {
+ builder.startObject("path1.path2");
+ {
+ builder.startObject("path3");
+ builder.field("test1", "value1");
+ builder.endObject();
+ }
+ builder.endObject();
+ }
+ {
+ builder.startObject("path1");
+ {
+ builder.startObject("path2.path3");
+ builder.field("test2", "value2");
+ builder.endObject();
+ }
+ builder.endObject();
+ }
+ builder.endObject();
+ Map map = toSourceMap(Strings.toString(builder));
+ final Map originalMap = Collections.unmodifiableMap(toSourceMap(Strings.toString(builder)));
+
+ IllegalArgumentException ex = assertThrows(
+ IllegalArgumentException.class,
+ () -> XContentMapValues.insertValue("path1.path2.path3.test1", map, "value3")
+ );
+ assertThat(
+ ex.getMessage(),
+ Matchers.equalTo("Path [path1.path2.path3.test1] could be inserted in 2 distinct ways, it is ambiguous which one to use")
+ );
+
+ ex = assertThrows(
+ IllegalArgumentException.class,
+ () -> XContentMapValues.insertValue("path1.path2.path3.test3", map, "value4")
+ );
+ assertThat(
+ ex.getMessage(),
+ Matchers.equalTo("Path [path1.path2.path3.test3] could be inserted in 2 distinct ways, it is ambiguous which one to use")
+ );
+ assertThat(map, Matchers.equalTo(originalMap));
+
+ XContentMapValues.insertValue("path1.path2.path3.test3", map, "value4", false);
+ assertThat(getMapValue(map, "path1.path2\\.path3.test3"), Matchers.equalTo("value4"));
+ }
+
+ // traversal through lists
+ {
+ XContentBuilder builder = XContentFactory.jsonBuilder().startObject();
+ {
+ builder.startObject("path1.path2");
+ {
+ builder.startArray("path3");
+ builder.startObject().field("test1", "value1").endObject();
+ builder.endArray();
+ }
+ builder.endObject();
+ }
+ {
+ builder.startObject("path1");
+ {
+ builder.startArray("path2.path3");
+ builder.startObject().field("test2", "value2").endObject();
+ builder.endArray();
+ }
+ builder.endObject();
+ }
+ builder.endObject();
+ Map map = toSourceMap(Strings.toString(builder));
+ final Map originalMap = Collections.unmodifiableMap(toSourceMap(Strings.toString(builder)));
+
+ IllegalArgumentException ex = assertThrows(
+ IllegalArgumentException.class,
+ () -> XContentMapValues.insertValue("path1.path2.path3.test1", map, "value3")
+ );
+ assertThat(
+ ex.getMessage(),
+ Matchers.equalTo("Path [path1.path2.path3.test1] could be inserted in 2 distinct ways, it is ambiguous which one to use")
+ );
+
+ ex = assertThrows(
+ IllegalArgumentException.class,
+ () -> XContentMapValues.insertValue("path1.path2.path3.test3", map, "value4")
+ );
+ assertThat(
+ ex.getMessage(),
+ Matchers.equalTo("Path [path1.path2.path3.test3] could be inserted in 2 distinct ways, it is ambiguous which one to use")
+ );
+ assertThat(map, Matchers.equalTo(originalMap));
+
+ XContentMapValues.insertValue("path1.path2.path3.test3", map, "value4", false);
+ assertThat(getMapValue(map, "path1.path2\\.path3.test3"), Matchers.equalTo("value4"));
+ }
+ }
+
+ public void testInsertValueCannotTraversePath() throws IOException {
+ XContentBuilder builder = XContentFactory.jsonBuilder().startObject();
+ {
+ builder.startObject("path1");
+ {
+ builder.startArray("path2");
+ builder.startArray();
+ builder.startObject().field("test", "value1").endObject();
+ builder.endArray();
+ builder.endArray();
+ }
+ builder.endObject();
+ }
+ builder.endObject();
+ Map map = toSourceMap(Strings.toString(builder));
+ final Map originalMap = Collections.unmodifiableMap(toSourceMap(Strings.toString(builder)));
+
+ IllegalArgumentException ex = assertThrows(
+ IllegalArgumentException.class,
+ () -> XContentMapValues.insertValue("path1.path2.test.test2", map, "value2")
+ );
+ assertThat(
+ ex.getMessage(),
+ Matchers.equalTo("Path [path1.path2.test] has value [value1] of type [String], which cannot be traversed into further")
+ );
+
+ assertThat(map, Matchers.equalTo(originalMap));
+ }
+
+ private static Object getMapValue(Map map, String key) {
+ // Split the path on unescaped "." chars and then unescape the escaped "." chars
+ final String[] pathElements = Arrays.stream(key.split("(? k.replace("\\.", ".")).toArray(String[]::new);
+
+ Object value = null;
+ Object nextLayer = map;
+ for (int i = 0; i < pathElements.length; i++) {
+ if (nextLayer instanceof Map, ?> nextMap) {
+ value = nextMap.get(pathElements[i]);
+ } else if (nextLayer instanceof List> nextList) {
+ final String pathElement = pathElements[i];
+ List> values = nextList.stream().flatMap(v -> {
+ Stream.Builder streamBuilder = Stream.builder();
+ if (v instanceof List> innerList) {
+ traverseList(innerList, streamBuilder);
+ } else {
+ streamBuilder.add(v);
+ }
+ return streamBuilder.build();
+ }).filter(v -> v instanceof Map, ?>).map(v -> ((Map, ?>) v).get(pathElement)).filter(Objects::nonNull).toList();
+
+ if (values.isEmpty()) {
+ return null;
+ } else if (values.size() > 1) {
+ throw new AssertionError("List " + nextList + " contains multiple values for [" + pathElement + "]");
+ } else {
+ value = values.getFirst();
+ }
+ } else if (nextLayer == null) {
+ break;
+ } else {
+ throw new AssertionError(
+ "Path ["
+ + String.join(".", Arrays.copyOfRange(pathElements, 0, i))
+ + "] has value ["
+ + value
+ + "] of type ["
+ + value.getClass().getSimpleName()
+ + "], which cannot be traversed into further"
+ );
+ }
+
+ nextLayer = value;
+ }
+
+ return value;
+ }
+
+ private static void traverseList(List> list, Stream.Builder streamBuilder) {
+ for (Object value : list) {
+ if (value instanceof List> innerList) {
+ traverseList(innerList, streamBuilder);
+ } else {
+ streamBuilder.add(value);
+ }
+ }
+ }
}
diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/action/filter/ShardBulkInferenceActionFilter.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/action/filter/ShardBulkInferenceActionFilter.java
index ecf73ed004194..5b73ba6dbcbdf 100644
--- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/action/filter/ShardBulkInferenceActionFilter.java
+++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/action/filter/ShardBulkInferenceActionFilter.java
@@ -728,7 +728,7 @@ private void applyInferenceResponses(BulkItemRequest item, FieldInferenceRespons
if (useLegacyFormat) {
var newDocMap = indexRequest.sourceAsMap();
for (var entry : inferenceFieldsMap.entrySet()) {
- SemanticTextUtils.insertValue(entry.getKey(), newDocMap, entry.getValue());
+ XContentMapValues.insertValue(entry.getKey(), newDocMap, entry.getValue());
}
indexRequest.source(newDocMap, indexRequest.getContentType());
} else {
diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/mapper/SemanticTextUtils.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/mapper/SemanticTextUtils.java
index 4d3c2e8752367..2816ec54e11de 100644
--- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/mapper/SemanticTextUtils.java
+++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/mapper/SemanticTextUtils.java
@@ -11,10 +11,8 @@
import org.elasticsearch.rest.RestStatus;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.List;
-import java.util.Map;
public interface SemanticTextUtils {
/**
@@ -50,114 +48,4 @@ static List nodeStringValues(String field, Object valueObj) {
valueObj.getClass().getSimpleName()
);
}
-
- /**
- *
- * Insert or replace the path's value in the map with the provided new value. The map will be modified in-place.
- * If the complete path does not exist in the map, it will be added to the deepest (sub-)map possible.
- *
- *
- * For example, given the map:
- *
- *
- * {
- * "path1": {
- * "path2": {
- * "key1": "value1"
- * }
- * }
- * }
- *
- *
- * And the caller wanted to insert {@code "path1.path2.path3.key2": "value2"}, the method would emit the modified map:
- *
- *
- * {
- * "path1": {
- * "path2": {
- * "key1": "value1",
- * "path3.key2": "value2"
- * }
- * }
- * }
- *
- *
- * @param path the value's path in the map.
- * @param map the map to search and modify in-place.
- * @param newValue the new value to assign to the path.
- *
- * @throws IllegalArgumentException If either the path cannot be fully traversed or there is ambiguity about where to insert the new
- * value.
- */
- static void insertValue(String path, Map, ?> map, Object newValue) {
- String[] pathElements = path.split("\\.");
- if (pathElements.length == 0) {
- return;
- }
-
- List suffixMaps = extractSuffixMaps(pathElements, 0, map);
- if (suffixMaps.isEmpty()) {
- // This should never happen. Throw in case it does for some reason.
- throw new IllegalStateException("extractSuffixMaps returned an empty suffix map list");
- } else if (suffixMaps.size() == 1) {
- SuffixMap suffixMap = suffixMaps.getFirst();
- suffixMap.map().put(suffixMap.suffix(), newValue);
- } else {
- throw new IllegalArgumentException(
- "Path [" + path + "] could be inserted in " + suffixMaps.size() + " distinct ways, it is ambiguous which one to use"
- );
- }
- }
-
- record SuffixMap(String suffix, Map map) {}
-
- private static List extractSuffixMaps(String[] pathElements, int index, Object currentValue) {
- if (currentValue instanceof List> valueList) {
- List suffixMaps = new ArrayList<>(valueList.size());
- for (Object o : valueList) {
- suffixMaps.addAll(extractSuffixMaps(pathElements, index, o));
- }
-
- return suffixMaps;
- } else if (currentValue instanceof Map, ?>) {
- @SuppressWarnings("unchecked")
- Map map = (Map) currentValue;
- List suffixMaps = new ArrayList<>(map.size());
-
- String key = pathElements[index];
- while (index < pathElements.length) {
- if (map.containsKey(key)) {
- if (index + 1 == pathElements.length) {
- // We found the complete path
- suffixMaps.add(new SuffixMap(key, map));
- } else {
- // We've matched that path partially, keep traversing to try to match it fully
- suffixMaps.addAll(extractSuffixMaps(pathElements, index + 1, map.get(key)));
- }
- }
-
- if (++index < pathElements.length) {
- key += "." + pathElements[index];
- }
- }
-
- if (suffixMaps.isEmpty()) {
- // We checked for all remaining elements in the path, and they do not exist. This means we found a leaf map that we should
- // add the value to.
- suffixMaps.add(new SuffixMap(key, map));
- }
-
- return suffixMaps;
- } else {
- throw new IllegalArgumentException(
- "Path ["
- + String.join(".", Arrays.copyOfRange(pathElements, 0, index))
- + "] has value ["
- + currentValue
- + "] of type ["
- + currentValue.getClass().getSimpleName()
- + "], which cannot be traversed into further"
- );
- }
- }
}
diff --git a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/mapper/SemanticTextUtilsTests.java b/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/mapper/SemanticTextUtilsTests.java
deleted file mode 100644
index e334335d6c78d..0000000000000
--- a/x-pack/plugin/inference/src/test/java/org/elasticsearch/xpack/inference/mapper/SemanticTextUtilsTests.java
+++ /dev/null
@@ -1,351 +0,0 @@
-/*
- * 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; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-package org.elasticsearch.xpack.inference.mapper;
-
-import org.elasticsearch.common.Strings;
-import org.elasticsearch.test.ESTestCase;
-import org.elasticsearch.xcontent.XContentBuilder;
-import org.elasticsearch.xcontent.XContentFactory;
-import org.elasticsearch.xcontent.XContentParser;
-import org.elasticsearch.xcontent.json.JsonXContent;
-
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.stream.Stream;
-
-import static org.hamcrest.Matchers.equalTo;
-
-public class SemanticTextUtilsTests extends ESTestCase {
- public void testInsertValueMapTraversal() throws IOException {
- {
- XContentBuilder builder = XContentFactory.jsonBuilder().startObject().field("test", "value").endObject();
-
- Map map = toSourceMap(Strings.toString(builder));
- SemanticTextUtils.insertValue("test", map, "value2");
- assertThat(getMapValue(map, "test"), equalTo("value2"));
- SemanticTextUtils.insertValue("something.else", map, "something_else_value");
- assertThat(getMapValue(map, "something\\.else"), equalTo("something_else_value"));
- }
- {
- XContentBuilder builder = XContentFactory.jsonBuilder().startObject();
- builder.startObject("path1").startObject("path2").field("test", "value").endObject().endObject();
- builder.endObject();
-
- Map map = toSourceMap(Strings.toString(builder));
- SemanticTextUtils.insertValue("path1.path2.test", map, "value2");
- assertThat(getMapValue(map, "path1.path2.test"), equalTo("value2"));
- SemanticTextUtils.insertValue("path1.path2.test_me", map, "test_me_value");
- assertThat(getMapValue(map, "path1.path2.test_me"), equalTo("test_me_value"));
- SemanticTextUtils.insertValue("path1.non_path2.test", map, "test_value");
- assertThat(getMapValue(map, "path1.non_path2\\.test"), equalTo("test_value"));
-
- SemanticTextUtils.insertValue("path1.path2", map, Map.of("path3", "bar"));
- assertThat(getMapValue(map, "path1.path2"), equalTo(Map.of("path3", "bar")));
-
- SemanticTextUtils.insertValue("path1", map, "baz");
- assertThat(getMapValue(map, "path1"), equalTo("baz"));
-
- SemanticTextUtils.insertValue("path3.path4", map, Map.of("test", "foo"));
- assertThat(getMapValue(map, "path3\\.path4"), equalTo(Map.of("test", "foo")));
- }
- {
- XContentBuilder builder = XContentFactory.jsonBuilder().startObject();
- builder.startObject("path1").array("test", "value1", "value2").endObject();
- builder.endObject();
- Map map = toSourceMap(Strings.toString(builder));
-
- SemanticTextUtils.insertValue("path1.test", map, List.of("value3", "value4", "value5"));
- assertThat(getMapValue(map, "path1.test"), equalTo(List.of("value3", "value4", "value5")));
-
- SemanticTextUtils.insertValue("path2.test", map, List.of("value6", "value7", "value8"));
- assertThat(getMapValue(map, "path2\\.test"), equalTo(List.of("value6", "value7", "value8")));
- }
- }
-
- public void testInsertValueListTraversal() throws IOException {
- {
- XContentBuilder builder = XContentFactory.jsonBuilder().startObject();
- {
- builder.startObject("path1");
- {
- builder.startArray("path2");
- builder.startObject().field("test", "value1").endObject();
- builder.endArray();
- }
- builder.endObject();
- }
- {
- builder.startObject("path3");
- {
- builder.startArray("path4");
- builder.startObject().field("test", "value1").endObject();
- builder.endArray();
- }
- builder.endObject();
- }
- builder.endObject();
- Map map = toSourceMap(Strings.toString(builder));
-
- SemanticTextUtils.insertValue("path1.path2.test", map, "value2");
- assertThat(getMapValue(map, "path1.path2.test"), equalTo("value2"));
- SemanticTextUtils.insertValue("path1.path2.test2", map, "value3");
- assertThat(getMapValue(map, "path1.path2.test2"), equalTo("value3"));
- assertThat(getMapValue(map, "path1.path2"), equalTo(List.of(Map.of("test", "value2", "test2", "value3"))));
-
- SemanticTextUtils.insertValue("path3.path4.test", map, "value4");
- assertThat(getMapValue(map, "path3.path4.test"), equalTo("value4"));
- }
- {
- XContentBuilder builder = XContentFactory.jsonBuilder().startObject();
- {
- builder.startObject("path1");
- {
- builder.startArray("path2");
- builder.startArray();
- builder.startObject().field("test", "value1").endObject();
- builder.endArray();
- builder.endArray();
- }
- builder.endObject();
- }
- builder.endObject();
- Map map = toSourceMap(Strings.toString(builder));
-
- SemanticTextUtils.insertValue("path1.path2.test", map, "value2");
- assertThat(getMapValue(map, "path1.path2.test"), equalTo("value2"));
- SemanticTextUtils.insertValue("path1.path2.test2", map, "value3");
- assertThat(getMapValue(map, "path1.path2.test2"), equalTo("value3"));
- assertThat(getMapValue(map, "path1.path2"), equalTo(List.of(List.of(Map.of("test", "value2", "test2", "value3")))));
- }
- }
-
- public void testInsertValueFieldsWithDots() throws IOException {
- {
- XContentBuilder builder = XContentFactory.jsonBuilder().startObject().field("xxx.yyy", "value1").endObject();
- Map map = toSourceMap(Strings.toString(builder));
-
- SemanticTextUtils.insertValue("xxx.yyy", map, "value2");
- assertThat(getMapValue(map, "xxx\\.yyy"), equalTo("value2"));
-
- SemanticTextUtils.insertValue("xxx", map, "value3");
- assertThat(getMapValue(map, "xxx"), equalTo("value3"));
- }
- {
- XContentBuilder builder = XContentFactory.jsonBuilder().startObject();
- {
- builder.startObject("path1.path2");
- {
- builder.startObject("path3.path4");
- builder.field("test", "value1");
- builder.endObject();
- }
- builder.endObject();
- }
- builder.endObject();
- Map map = toSourceMap(Strings.toString(builder));
-
- SemanticTextUtils.insertValue("path1.path2.path3.path4.test", map, "value2");
- assertThat(getMapValue(map, "path1\\.path2.path3\\.path4.test"), equalTo("value2"));
-
- SemanticTextUtils.insertValue("path1.path2.path3.path4.test2", map, "value3");
- assertThat(getMapValue(map, "path1\\.path2.path3\\.path4.test2"), equalTo("value3"));
- assertThat(getMapValue(map, "path1\\.path2.path3\\.path4"), equalTo(Map.of("test", "value2", "test2", "value3")));
- }
- }
-
- public void testInsertValueAmbiguousPath() throws IOException {
- // Mixed dotted object notation
- {
- XContentBuilder builder = XContentFactory.jsonBuilder().startObject();
- {
- builder.startObject("path1.path2");
- {
- builder.startObject("path3");
- builder.field("test1", "value1");
- builder.endObject();
- }
- builder.endObject();
- }
- {
- builder.startObject("path1");
- {
- builder.startObject("path2.path3");
- builder.field("test2", "value2");
- builder.endObject();
- }
- builder.endObject();
- }
- builder.endObject();
- Map map = toSourceMap(Strings.toString(builder));
- final Map originalMap = Collections.unmodifiableMap(toSourceMap(Strings.toString(builder)));
-
- IllegalArgumentException ex = assertThrows(
- IllegalArgumentException.class,
- () -> SemanticTextUtils.insertValue("path1.path2.path3.test1", map, "value3")
- );
- assertThat(
- ex.getMessage(),
- equalTo("Path [path1.path2.path3.test1] could be inserted in 2 distinct ways, it is ambiguous which one to use")
- );
-
- ex = assertThrows(
- IllegalArgumentException.class,
- () -> SemanticTextUtils.insertValue("path1.path2.path3.test3", map, "value4")
- );
- assertThat(
- ex.getMessage(),
- equalTo("Path [path1.path2.path3.test3] could be inserted in 2 distinct ways, it is ambiguous which one to use")
- );
-
- assertThat(map, equalTo(originalMap));
- }
-
- // traversal through lists
- {
- XContentBuilder builder = XContentFactory.jsonBuilder().startObject();
- {
- builder.startObject("path1.path2");
- {
- builder.startArray("path3");
- builder.startObject().field("test1", "value1").endObject();
- builder.endArray();
- }
- builder.endObject();
- }
- {
- builder.startObject("path1");
- {
- builder.startArray("path2.path3");
- builder.startObject().field("test2", "value2").endObject();
- builder.endArray();
- }
- builder.endObject();
- }
- builder.endObject();
- Map map = toSourceMap(Strings.toString(builder));
- final Map originalMap = Collections.unmodifiableMap(toSourceMap(Strings.toString(builder)));
-
- IllegalArgumentException ex = assertThrows(
- IllegalArgumentException.class,
- () -> SemanticTextUtils.insertValue("path1.path2.path3.test1", map, "value3")
- );
- assertThat(
- ex.getMessage(),
- equalTo("Path [path1.path2.path3.test1] could be inserted in 2 distinct ways, it is ambiguous which one to use")
- );
-
- ex = assertThrows(
- IllegalArgumentException.class,
- () -> SemanticTextUtils.insertValue("path1.path2.path3.test3", map, "value4")
- );
- assertThat(
- ex.getMessage(),
- equalTo("Path [path1.path2.path3.test3] could be inserted in 2 distinct ways, it is ambiguous which one to use")
- );
-
- assertThat(map, equalTo(originalMap));
- }
- }
-
- public void testInsertValueCannotTraversePath() throws IOException {
- XContentBuilder builder = XContentFactory.jsonBuilder().startObject();
- {
- builder.startObject("path1");
- {
- builder.startArray("path2");
- builder.startArray();
- builder.startObject().field("test", "value1").endObject();
- builder.endArray();
- builder.endArray();
- }
- builder.endObject();
- }
- builder.endObject();
- Map map = toSourceMap(Strings.toString(builder));
- final Map originalMap = Collections.unmodifiableMap(toSourceMap(Strings.toString(builder)));
-
- IllegalArgumentException ex = assertThrows(
- IllegalArgumentException.class,
- () -> SemanticTextUtils.insertValue("path1.path2.test.test2", map, "value2")
- );
- assertThat(
- ex.getMessage(),
- equalTo("Path [path1.path2.test] has value [value1] of type [String], which cannot be traversed into further")
- );
-
- assertThat(map, equalTo(originalMap));
- }
-
- private Map toSourceMap(String source) throws IOException {
- try (XContentParser parser = createParser(JsonXContent.jsonXContent, source)) {
- return parser.map();
- }
- }
-
- private static Object getMapValue(Map map, String key) {
- // Split the path on unescaped "." chars and then unescape the escaped "." chars
- final String[] pathElements = Arrays.stream(key.split("(? k.replace("\\.", ".")).toArray(String[]::new);
-
- Object value = null;
- Object nextLayer = map;
- for (int i = 0; i < pathElements.length; i++) {
- if (nextLayer instanceof Map, ?> nextMap) {
- value = nextMap.get(pathElements[i]);
- } else if (nextLayer instanceof List> nextList) {
- final String pathElement = pathElements[i];
- List> values = nextList.stream().flatMap(v -> {
- Stream.Builder streamBuilder = Stream.builder();
- if (v instanceof List> innerList) {
- traverseList(innerList, streamBuilder);
- } else {
- streamBuilder.add(v);
- }
- return streamBuilder.build();
- }).filter(v -> v instanceof Map, ?>).map(v -> ((Map, ?>) v).get(pathElement)).filter(Objects::nonNull).toList();
-
- if (values.isEmpty()) {
- return null;
- } else if (values.size() > 1) {
- throw new AssertionError("List " + nextList + " contains multiple values for [" + pathElement + "]");
- } else {
- value = values.getFirst();
- }
- } else if (nextLayer == null) {
- break;
- } else {
- throw new AssertionError(
- "Path ["
- + String.join(".", Arrays.copyOfRange(pathElements, 0, i))
- + "] has value ["
- + value
- + "] of type ["
- + value.getClass().getSimpleName()
- + "], which cannot be traversed into further"
- );
- }
-
- nextLayer = value;
- }
-
- return value;
- }
-
- private static void traverseList(List> list, Stream.Builder streamBuilder) {
- for (Object value : list) {
- if (value instanceof List> innerList) {
- traverseList(innerList, streamBuilder);
- } else {
- streamBuilder.add(value);
- }
- }
- }
-}