-
Notifications
You must be signed in to change notification settings - Fork 25.7k
Correctly identify parent of copy_to destination field for synthetic source purposes #113153
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4035121
9ad3fba
270f5a9
5fef1c2
1650afc
baeda6e
e6deced
5ab4c31
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -808,6 +808,42 @@ protected void doXContent(XContentBuilder builder, Params params) throws IOExcep | |
|
|
||
| } | ||
|
|
||
| ObjectMapper findParentMapper(String leafFieldPath) { | ||
| var pathComponents = leafFieldPath.split("\\."); | ||
| int startPathComponent = 0; | ||
|
|
||
| ObjectMapper current = this; | ||
| String pathInCurrent = leafFieldPath; | ||
|
|
||
| while (current != null) { | ||
| if (current.mappers.containsKey(pathInCurrent)) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am wondering if we have to check for
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can't think of a scenario when we would add |
||
| return current; | ||
| } | ||
|
|
||
| // Go one level down if possible | ||
| var parent = current; | ||
| current = null; | ||
|
|
||
| var childMapperName = new StringBuilder(); | ||
| for (int i = startPathComponent; i < pathComponents.length - 1; i++) { | ||
| if (childMapperName.isEmpty() == false) { | ||
| childMapperName.append("."); | ||
| } | ||
| childMapperName.append(pathComponents[i]); | ||
|
|
||
| var childMapper = parent.mappers.get(childMapperName.toString()); | ||
| if (childMapper instanceof ObjectMapper objectMapper) { | ||
| current = objectMapper; | ||
| startPathComponent = i + 1; | ||
| pathInCurrent = pathInCurrent.substring(childMapperName.length() + 1); | ||
| break; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| protected SourceLoader.SyntheticFieldLoader syntheticFieldLoader(Stream<Mapper> mappers, boolean isFragment) { | ||
| var fields = mappers.sorted(Comparator.comparing(Mapper::fullPath)) | ||
| .map(Mapper::syntheticFieldLoader) | ||
|
|
@@ -828,10 +864,18 @@ public SourceLoader.SyntheticFieldLoader syntheticFieldLoader() { | |
| private class SyntheticSourceFieldLoader implements SourceLoader.SyntheticFieldLoader { | ||
| private final List<SourceLoader.SyntheticFieldLoader> fields; | ||
| private final boolean isFragment; | ||
|
|
||
| private boolean storedFieldLoadersHaveValues; | ||
| private boolean docValuesLoadersHaveValues; | ||
| private boolean ignoredValuesPresent; | ||
| private List<IgnoredSourceFieldMapper.NameValue> ignoredValues; | ||
| // If this loader has anything to write. | ||
| // In special cases this can be false even if doc values loaders or stored field loaders | ||
| // have values. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: reference the copy_to case as an example? |
||
| // F.e. objects that only contain fields that are destinations of copy_to. | ||
| private boolean writersHaveValues; | ||
| // Use an ordered map between field names and writers to order writing by field name. | ||
| private TreeMap<String, FieldWriter> currentWriters; | ||
|
|
||
| private SyntheticSourceFieldLoader(List<SourceLoader.SyntheticFieldLoader> fields, boolean isFragment) { | ||
| this.fields = fields; | ||
|
|
@@ -882,22 +926,69 @@ public boolean advanceToDoc(int docId) throws IOException { | |
| } | ||
| } | ||
|
|
||
| @Override | ||
| public void prepare() { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I kinda like this version, compared to running everything in |
||
| if ((storedFieldLoadersHaveValues || docValuesLoadersHaveValues || ignoredValuesPresent) == false) { | ||
| writersHaveValues = false; | ||
| return; | ||
| } | ||
|
|
||
| for (var loader : fields) { | ||
| // Currently this logic is only relevant for object loaders. | ||
| if (loader instanceof ObjectMapper.SyntheticSourceFieldLoader objectSyntheticFieldLoader) { | ||
| objectSyntheticFieldLoader.prepare(); | ||
| } | ||
| } | ||
|
|
||
| currentWriters = new TreeMap<>(); | ||
|
|
||
| if (ignoredValues != null && ignoredValues.isEmpty() == false) { | ||
| for (IgnoredSourceFieldMapper.NameValue value : ignoredValues) { | ||
| if (value.hasValue()) { | ||
| writersHaveValues |= true; | ||
| } | ||
|
|
||
| var existing = currentWriters.get(value.name()); | ||
| if (existing == null) { | ||
| currentWriters.put(value.name(), new FieldWriter.IgnoredSource(value)); | ||
| } else if (existing instanceof FieldWriter.IgnoredSource isw) { | ||
| isw.mergeWith(value); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| for (SourceLoader.SyntheticFieldLoader field : fields) { | ||
| if (field.hasValue()) { | ||
| if (currentWriters.containsKey(field.fieldName()) == false) { | ||
| writersHaveValues |= true; | ||
| currentWriters.put(field.fieldName(), new FieldWriter.FieldLoader(field)); | ||
| } else { | ||
| // Skip if the field source is stored separately, to avoid double-printing. | ||
| // Make sure to reset the state of loader so that values stored inside will not | ||
| // be used after this document is finished. | ||
| field.reset(); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public boolean hasValue() { | ||
| return storedFieldLoadersHaveValues || docValuesLoadersHaveValues || ignoredValuesPresent; | ||
| return writersHaveValues; | ||
| } | ||
|
|
||
| @Override | ||
| public void write(XContentBuilder b) throws IOException { | ||
| if (hasValue() == false) { | ||
| return; | ||
| } | ||
|
|
||
| if (isRoot() && isEnabled() == false) { | ||
| // If the root object mapper is disabled, it is expected to contain | ||
| // the source encapsulated within a single ignored source value. | ||
| assert ignoredValues.size() == 1 : ignoredValues.size(); | ||
| XContentDataHelper.decodeAndWrite(b, ignoredValues.get(0).value()); | ||
| ignoredValues = null; | ||
| softReset(); | ||
| return; | ||
| } | ||
|
|
||
|
|
@@ -907,41 +998,12 @@ public void write(XContentBuilder b) throws IOException { | |
| b.startObject(leafName()); | ||
| } | ||
|
|
||
| if (ignoredValues != null && ignoredValues.isEmpty() == false) { | ||
| // Use an ordered map between field names and writer functions, to order writing by field name. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Copy this comment. |
||
| Map<String, FieldWriter> orderedFields = new TreeMap<>(); | ||
| for (IgnoredSourceFieldMapper.NameValue value : ignoredValues) { | ||
| var existing = orderedFields.get(value.name()); | ||
| if (existing == null) { | ||
| orderedFields.put(value.name(), new FieldWriter.IgnoredSource(value)); | ||
| } else if (existing instanceof FieldWriter.IgnoredSource isw) { | ||
| isw.mergeWith(value); | ||
| } | ||
| } | ||
| for (SourceLoader.SyntheticFieldLoader field : fields) { | ||
| if (field.hasValue()) { | ||
| if (orderedFields.containsKey(field.fieldName()) == false) { | ||
| orderedFields.put(field.fieldName(), new FieldWriter.FieldLoader(field)); | ||
| } else { | ||
| // Skip if the field source is stored separately, to avoid double-printing. | ||
| // Make sure to reset the state of loader so that values stored inside will not | ||
| // be used after this document is finished. | ||
| field.reset(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| for (var writer : orderedFields.values()) { | ||
| for (var writer : currentWriters.values()) { | ||
| if (writer.hasValue()) { | ||
| writer.writeTo(b); | ||
| } | ||
| ignoredValues = null; | ||
| } else { | ||
| for (SourceLoader.SyntheticFieldLoader field : fields) { | ||
| if (field.hasValue()) { | ||
| field.write(b); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| b.endObject(); | ||
| softReset(); | ||
| } | ||
|
|
@@ -957,6 +1019,8 @@ private void softReset() { | |
| storedFieldLoadersHaveValues = false; | ||
| docValuesLoadersHaveValues = false; | ||
| ignoredValuesPresent = false; | ||
| ignoredValues = null; | ||
| writersHaveValues = false; | ||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -986,34 +1050,49 @@ public String fieldName() { | |
| interface FieldWriter { | ||
| void writeTo(XContentBuilder builder) throws IOException; | ||
|
|
||
| boolean hasValue(); | ||
|
|
||
| record FieldLoader(SourceLoader.SyntheticFieldLoader loader) implements FieldWriter { | ||
| @Override | ||
| public void writeTo(XContentBuilder builder) throws IOException { | ||
| loader.write(builder); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean hasValue() { | ||
| return loader.hasValue(); | ||
| } | ||
| } | ||
|
|
||
| class IgnoredSource implements FieldWriter { | ||
| private final String fieldName; | ||
| private final String leafName; | ||
| private final List<BytesRef> values; | ||
| private final List<BytesRef> encodedValues; | ||
|
|
||
| IgnoredSource(IgnoredSourceFieldMapper.NameValue initialValue) { | ||
| this.fieldName = initialValue.name(); | ||
| this.leafName = initialValue.getFieldName(); | ||
| this.values = new ArrayList<>(); | ||
| this.values.add(initialValue.value()); | ||
| this.encodedValues = new ArrayList<>(); | ||
| if (initialValue.hasValue()) { | ||
| this.encodedValues.add(initialValue.value()); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public void writeTo(XContentBuilder builder) throws IOException { | ||
| XContentDataHelper.writeMerged(builder, leafName, values); | ||
| XContentDataHelper.writeMerged(builder, leafName, encodedValues); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean hasValue() { | ||
| return encodedValues.isEmpty() == false; | ||
| } | ||
|
|
||
| public FieldWriter mergeWith(IgnoredSourceFieldMapper.NameValue nameValue) { | ||
| assert Objects.equals(nameValue.name(), fieldName) : "IgnoredSource is merged with wrong field data"; | ||
|
|
||
| values.add(nameValue.value()); | ||
| if (nameValue.hasValue()) { | ||
| encodedValues.add(nameValue.value()); | ||
| } | ||
| return this; | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -209,6 +209,9 @@ public void write(LeafStoredFieldLoader storedFieldLoader, int docId, XContentBu | |
| if (docValuesLoader != null) { | ||
| docValuesLoader.advanceToDoc(docId); | ||
| } | ||
|
|
||
| loader.prepare(); | ||
|
|
||
| // TODO accept a requested xcontent type | ||
| if (loader.hasValue()) { | ||
| loader.write(b); | ||
|
|
@@ -299,6 +302,16 @@ public String fieldName() { | |
| */ | ||
| DocValuesLoader docValuesLoader(LeafReader leafReader, int[] docIdsInLeaf) throws IOException; | ||
|
|
||
| /** | ||
| Perform any preprocessing needed before producing synthetic source | ||
| and deduce whether this mapper (and its children, if any) have values to write. | ||
| The expectation is for this method to be called before {@link SyntheticFieldLoader#hasValue()} | ||
| and {@link SyntheticFieldLoader#write(XContentBuilder)} are used. | ||
| */ | ||
| default void prepare() { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like this more because it's a verb and it does not have |
||
| // Noop | ||
| } | ||
|
|
||
| /** | ||
| * Has this field loaded any values for this document? | ||
| */ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: update text, no given field here..