Skip to content

Commit d5c2ef4

Browse files
committed
Update FieldPath to parse array syntax
1 parent 2bc5498 commit d5c2ef4

File tree

1 file changed

+88
-14
lines changed

1 file changed

+88
-14
lines changed

server/src/main/java/org/elasticsearch/ingest/IngestDocument.java

Lines changed: 88 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import java.util.Set;
4444
import java.util.function.BiConsumer;
4545
import java.util.stream.Collectors;
46+
import java.util.stream.Stream;
4647

4748
/**
4849
* Represents a single document being captured before indexing and holds the source and metadata (like id, type and index).
@@ -201,7 +202,7 @@ public <T> T getFieldValue(String path, Class<T> clazz) {
201202
* or if the field that is found at the provided path is not of the expected type.
202203
*/
203204
public <T> T getFieldValue(String path, Class<T> clazz, boolean ignoreMissing) {
204-
final FieldPath fieldPath = FieldPath.of(path);
205+
final FieldPath fieldPath = FieldPath.of(path, getCurrentAccessPattern());
205206
Object context = fieldPath.initialContext(this);
206207
ResolveResult result = resolve(fieldPath.pathElements, fieldPath.pathElements.length, path, context, getCurrentAccessPatternSafe());
207208
if (result.wasSuccessful) {
@@ -270,7 +271,7 @@ public boolean hasField(String path) {
270271
* @throws IllegalArgumentException if the path is null, empty or invalid.
271272
*/
272273
public boolean hasField(String path, boolean failOutOfRange) {
273-
final FieldPath fieldPath = FieldPath.of(path);
274+
final FieldPath fieldPath = FieldPath.of(path, getCurrentAccessPattern());
274275
Object context = fieldPath.initialContext(this);
275276
int leafKeyIndex = fieldPath.pathElements.length - 1;
276277
int lastContainerIndex = fieldPath.pathElements.length - 2;
@@ -424,7 +425,7 @@ public void removeField(String path) {
424425
* @throws IllegalArgumentException if the path is null, empty, or invalid; or if the field doesn't exist (and ignoreMissing is false).
425426
*/
426427
public void removeField(String path, boolean ignoreMissing) {
427-
final FieldPath fieldPath = FieldPath.of(path);
428+
final FieldPath fieldPath = FieldPath.of(path, getCurrentAccessPattern());
428429
Object context = fieldPath.initialContext(this);
429430
String leafKey = fieldPath.pathElements[fieldPath.pathElements.length - 1];
430431
ResolveResult result = resolve(
@@ -734,7 +735,7 @@ public void setFieldValue(String path, Object value, boolean ignoreEmptyValue) {
734735
}
735736

736737
private void setFieldValue(String path, Object value, boolean append, boolean allowDuplicates) {
737-
final FieldPath fieldPath = FieldPath.of(path);
738+
final FieldPath fieldPath = FieldPath.of(path, getCurrentAccessPattern());
738739
Object context = fieldPath.initialContext(this);
739740
int leafKeyIndex = fieldPath.pathElements.length - 1;
740741
int lastContainerIndex = fieldPath.pathElements.length - 2;
@@ -1288,8 +1289,37 @@ public String getFieldName() {
12881289

12891290
private static final class FieldPath {
12901291

1292+
private record Element(String fieldName, Integer arrayIndex) {
1293+
private static final String EMPTY_STRING = "";
1294+
1295+
static Element field(String fieldName) {
1296+
Objects.requireNonNull(fieldName, "fieldName cannot be null");
1297+
if (fieldName.isEmpty()) {
1298+
throw new IllegalArgumentException("fieldName cannot be empty");
1299+
}
1300+
return new Element(fieldName, null);
1301+
}
1302+
1303+
static Element index(int arrayIndex) {
1304+
if (arrayIndex < 0) {
1305+
throw new IndexOutOfBoundsException(arrayIndex);
1306+
}
1307+
return new Element(EMPTY_STRING, arrayIndex);
1308+
}
1309+
1310+
boolean isFieldName() {
1311+
return fieldName.isEmpty() == false && arrayIndex == null;
1312+
}
1313+
1314+
boolean isArrayIndex() {
1315+
return fieldName.isEmpty() &&
1316+
}
1317+
}
1318+
1319+
private record CacheKey(String path, IngestPipelineFieldAccessPattern accessPattern) {}
1320+
12911321
private static final int MAX_SIZE = 512;
1292-
private static final Map<String, FieldPath> CACHE = ConcurrentCollections.newConcurrentMapWithAggressiveConcurrency();
1322+
private static final Map<CacheKey, FieldPath> CACHE = ConcurrentCollections.newConcurrentMapWithAggressiveConcurrency();
12931323

12941324
// constructing a new FieldPath requires that we parse a String (e.g. "foo.bar.baz") into an array
12951325
// of path elements (e.g. ["foo", "bar", "baz"]). Calling String#split results in the allocation
@@ -1298,27 +1328,28 @@ private static final class FieldPath {
12981328
// do some processing ourselves on the path and path elements to validate and prepare them.
12991329
// the above CACHE and the below 'FieldPath.of' method allow us to almost always avoid this work.
13001330

1301-
static FieldPath of(String path) {
1331+
static FieldPath of(String path, IngestPipelineFieldAccessPattern accessPattern) {
13021332
if (Strings.isEmpty(path)) {
13031333
throw new IllegalArgumentException("path cannot be null nor empty");
13041334
}
1305-
FieldPath res = CACHE.get(path);
1335+
CacheKey cacheKey = new CacheKey(path, accessPattern);
1336+
FieldPath res = CACHE.get(cacheKey);
13061337
if (res != null) {
13071338
return res;
13081339
}
1309-
res = new FieldPath(path);
1340+
res = new FieldPath(path, accessPattern);
13101341
if (CACHE.size() > MAX_SIZE) {
13111342
CACHE.clear();
13121343
}
1313-
CACHE.put(path, res);
1344+
CACHE.put(cacheKey, res);
13141345
return res;
13151346
}
13161347

1317-
private final String[] pathElements;
1348+
private final Element[] pathElements;
13181349
private final boolean useIngestContext;
13191350

13201351
// you shouldn't call this directly, use the FieldPath.of method above instead!
1321-
private FieldPath(String path) {
1352+
private FieldPath(String path, IngestPipelineFieldAccessPattern accessPattern) {
13221353
String newPath;
13231354
if (path.startsWith(INGEST_KEY_PREFIX)) {
13241355
useIngestContext = true;
@@ -1331,10 +1362,53 @@ private FieldPath(String path) {
13311362
newPath = path;
13321363
}
13331364
}
1334-
this.pathElements = newPath.split("\\.");
1335-
if (pathElements.length == 1 && pathElements[0].isEmpty()) {
1336-
throw new IllegalArgumentException("path [" + path + "] is not valid");
1365+
String[] pathParts = newPath.split("\\.");
1366+
this.pathElements = processPathParts(path, pathParts, accessPattern);
1367+
}
1368+
1369+
private static Element[] processPathParts(String fullPath, String[] pathParts, IngestPipelineFieldAccessPattern accessPattern) {
1370+
if (pathParts.length == 1 && pathParts[0].isEmpty()) {
1371+
throw new IllegalArgumentException("path [" + fullPath + "] is not valid");
13371372
}
1373+
return Arrays.stream(pathParts)
1374+
.flatMap(pathPart -> {
1375+
int openBracket = pathPart.indexOf('[');
1376+
if (openBracket == -1) {
1377+
return Stream.of(Element.field(pathPart));
1378+
} else if (openBracket == 0) {
1379+
throw new IllegalArgumentException("path [" + fullPath + "] is not valid");
1380+
} else {
1381+
List<Element> resultElements = new ArrayList<>();
1382+
String rootField = pathPart.substring(0, openBracket);
1383+
resultElements.add(Element.field(rootField));
1384+
1385+
boolean elementsRemain = true;
1386+
while (elementsRemain) {
1387+
int closeBracket = pathPart.indexOf(']', openBracket);
1388+
if (closeBracket <= openBracket) {
1389+
throw new IllegalArgumentException("path [" + fullPath + "] is not valid");
1390+
}
1391+
1392+
String rawIndex = pathPart.substring(openBracket + 1, closeBracket);
1393+
try {
1394+
resultElements.add(Element.index(Integer.parseInt(rawIndex)));
1395+
} catch (NumberFormatException numberFormatException) {
1396+
throw new IllegalArgumentException("path [" + fullPath + "] is not valid");
1397+
}
1398+
1399+
if (closeBracket == pathPart.length() - 1) {
1400+
elementsRemain = false;
1401+
} else {
1402+
if (pathPart.charAt(closeBracket + 1) != '[') {
1403+
throw new IllegalArgumentException("path [" + fullPath + "] is not valid");
1404+
}
1405+
openBracket = closeBracket + 1;
1406+
}
1407+
}
1408+
return resultElements.stream();
1409+
}
1410+
})
1411+
.toArray(Element[]::new);
13381412
}
13391413

13401414
public Object initialContext(IngestDocument document) {

0 commit comments

Comments
 (0)