@@ -808,6 +808,42 @@ protected void doXContent(XContentBuilder builder, Params params) throws IOExcep
808808
809809 }
810810
811+ ObjectMapper findParentMapper (String leafFieldPath ) {
812+ var pathComponents = leafFieldPath .split ("\\ ." );
813+ int startPathComponent = 0 ;
814+
815+ ObjectMapper current = this ;
816+ String pathInCurrent = leafFieldPath ;
817+
818+ while (current != null ) {
819+ if (current .mappers .containsKey (pathInCurrent )) {
820+ return current ;
821+ }
822+
823+ // Go one level down if possible
824+ var parent = current ;
825+ current = null ;
826+
827+ var childMapperName = new StringBuilder ();
828+ for (int i = startPathComponent ; i < pathComponents .length - 1 ; i ++) {
829+ if (childMapperName .isEmpty () == false ) {
830+ childMapperName .append ("." );
831+ }
832+ childMapperName .append (pathComponents [i ]);
833+
834+ var childMapper = parent .mappers .get (childMapperName .toString ());
835+ if (childMapper instanceof ObjectMapper objectMapper ) {
836+ current = objectMapper ;
837+ startPathComponent = i + 1 ;
838+ pathInCurrent = pathInCurrent .substring (childMapperName .length () + 1 );
839+ break ;
840+ }
841+ }
842+ }
843+
844+ return null ;
845+ }
846+
811847 protected SourceLoader .SyntheticFieldLoader syntheticFieldLoader (Stream <Mapper > mappers , boolean isFragment ) {
812848 var fields = mappers .sorted (Comparator .comparing (Mapper ::fullPath ))
813849 .map (Mapper ::syntheticFieldLoader )
@@ -828,10 +864,18 @@ public SourceLoader.SyntheticFieldLoader syntheticFieldLoader() {
828864 private class SyntheticSourceFieldLoader implements SourceLoader .SyntheticFieldLoader {
829865 private final List <SourceLoader .SyntheticFieldLoader > fields ;
830866 private final boolean isFragment ;
867+
831868 private boolean storedFieldLoadersHaveValues ;
832869 private boolean docValuesLoadersHaveValues ;
833870 private boolean ignoredValuesPresent ;
834871 private List <IgnoredSourceFieldMapper .NameValue > ignoredValues ;
872+ // If this loader has anything to write.
873+ // In special cases this can be false even if doc values loaders or stored field loaders
874+ // have values.
875+ // F.e. objects that only contain fields that are destinations of copy_to.
876+ private boolean writersHaveValues ;
877+ // Use an ordered map between field names and writers to order writing by field name.
878+ private TreeMap <String , FieldWriter > currentWriters ;
835879
836880 private SyntheticSourceFieldLoader (List <SourceLoader .SyntheticFieldLoader > fields , boolean isFragment ) {
837881 this .fields = fields ;
@@ -882,22 +926,69 @@ public boolean advanceToDoc(int docId) throws IOException {
882926 }
883927 }
884928
929+ @ Override
930+ public void prepare () {
931+ if ((storedFieldLoadersHaveValues || docValuesLoadersHaveValues || ignoredValuesPresent ) == false ) {
932+ writersHaveValues = false ;
933+ return ;
934+ }
935+
936+ for (var loader : fields ) {
937+ // Currently this logic is only relevant for object loaders.
938+ if (loader instanceof ObjectMapper .SyntheticSourceFieldLoader objectSyntheticFieldLoader ) {
939+ objectSyntheticFieldLoader .prepare ();
940+ }
941+ }
942+
943+ currentWriters = new TreeMap <>();
944+
945+ if (ignoredValues != null && ignoredValues .isEmpty () == false ) {
946+ for (IgnoredSourceFieldMapper .NameValue value : ignoredValues ) {
947+ if (value .hasValue ()) {
948+ writersHaveValues |= true ;
949+ }
950+
951+ var existing = currentWriters .get (value .name ());
952+ if (existing == null ) {
953+ currentWriters .put (value .name (), new FieldWriter .IgnoredSource (value ));
954+ } else if (existing instanceof FieldWriter .IgnoredSource isw ) {
955+ isw .mergeWith (value );
956+ }
957+ }
958+ }
959+
960+ for (SourceLoader .SyntheticFieldLoader field : fields ) {
961+ if (field .hasValue ()) {
962+ if (currentWriters .containsKey (field .fieldName ()) == false ) {
963+ writersHaveValues |= true ;
964+ currentWriters .put (field .fieldName (), new FieldWriter .FieldLoader (field ));
965+ } else {
966+ // Skip if the field source is stored separately, to avoid double-printing.
967+ // Make sure to reset the state of loader so that values stored inside will not
968+ // be used after this document is finished.
969+ field .reset ();
970+ }
971+ }
972+ }
973+ }
974+
885975 @ Override
886976 public boolean hasValue () {
887- return storedFieldLoadersHaveValues || docValuesLoadersHaveValues || ignoredValuesPresent ;
977+ return writersHaveValues ;
888978 }
889979
890980 @ Override
891981 public void write (XContentBuilder b ) throws IOException {
892982 if (hasValue () == false ) {
893983 return ;
894984 }
985+
895986 if (isRoot () && isEnabled () == false ) {
896987 // If the root object mapper is disabled, it is expected to contain
897988 // the source encapsulated within a single ignored source value.
898989 assert ignoredValues .size () == 1 : ignoredValues .size ();
899990 XContentDataHelper .decodeAndWrite (b , ignoredValues .get (0 ).value ());
900- ignoredValues = null ;
991+ softReset () ;
901992 return ;
902993 }
903994
@@ -907,41 +998,12 @@ public void write(XContentBuilder b) throws IOException {
907998 b .startObject (leafName ());
908999 }
9091000
910- if (ignoredValues != null && ignoredValues .isEmpty () == false ) {
911- // Use an ordered map between field names and writer functions, to order writing by field name.
912- Map <String , FieldWriter > orderedFields = new TreeMap <>();
913- for (IgnoredSourceFieldMapper .NameValue value : ignoredValues ) {
914- var existing = orderedFields .get (value .name ());
915- if (existing == null ) {
916- orderedFields .put (value .name (), new FieldWriter .IgnoredSource (value ));
917- } else if (existing instanceof FieldWriter .IgnoredSource isw ) {
918- isw .mergeWith (value );
919- }
920- }
921- for (SourceLoader .SyntheticFieldLoader field : fields ) {
922- if (field .hasValue ()) {
923- if (orderedFields .containsKey (field .fieldName ()) == false ) {
924- orderedFields .put (field .fieldName (), new FieldWriter .FieldLoader (field ));
925- } else {
926- // Skip if the field source is stored separately, to avoid double-printing.
927- // Make sure to reset the state of loader so that values stored inside will not
928- // be used after this document is finished.
929- field .reset ();
930- }
931- }
932- }
933-
934- for (var writer : orderedFields .values ()) {
1001+ for (var writer : currentWriters .values ()) {
1002+ if (writer .hasValue ()) {
9351003 writer .writeTo (b );
9361004 }
937- ignoredValues = null ;
938- } else {
939- for (SourceLoader .SyntheticFieldLoader field : fields ) {
940- if (field .hasValue ()) {
941- field .write (b );
942- }
943- }
9441005 }
1006+
9451007 b .endObject ();
9461008 softReset ();
9471009 }
@@ -957,6 +1019,8 @@ private void softReset() {
9571019 storedFieldLoadersHaveValues = false ;
9581020 docValuesLoadersHaveValues = false ;
9591021 ignoredValuesPresent = false ;
1022+ ignoredValues = null ;
1023+ writersHaveValues = false ;
9601024 }
9611025
9621026 @ Override
@@ -986,34 +1050,49 @@ public String fieldName() {
9861050 interface FieldWriter {
9871051 void writeTo (XContentBuilder builder ) throws IOException ;
9881052
1053+ boolean hasValue ();
1054+
9891055 record FieldLoader (SourceLoader .SyntheticFieldLoader loader ) implements FieldWriter {
9901056 @ Override
9911057 public void writeTo (XContentBuilder builder ) throws IOException {
9921058 loader .write (builder );
9931059 }
1060+
1061+ @ Override
1062+ public boolean hasValue () {
1063+ return loader .hasValue ();
1064+ }
9941065 }
9951066
9961067 class IgnoredSource implements FieldWriter {
9971068 private final String fieldName ;
9981069 private final String leafName ;
999- private final List <BytesRef > values ;
1070+ private final List <BytesRef > encodedValues ;
10001071
10011072 IgnoredSource (IgnoredSourceFieldMapper .NameValue initialValue ) {
10021073 this .fieldName = initialValue .name ();
10031074 this .leafName = initialValue .getFieldName ();
1004- this .values = new ArrayList <>();
1005- this .values .add (initialValue .value ());
1075+ this .encodedValues = new ArrayList <>();
1076+ if (initialValue .hasValue ()) {
1077+ this .encodedValues .add (initialValue .value ());
1078+ }
10061079 }
10071080
10081081 @ Override
10091082 public void writeTo (XContentBuilder builder ) throws IOException {
1010- XContentDataHelper .writeMerged (builder , leafName , values );
1083+ XContentDataHelper .writeMerged (builder , leafName , encodedValues );
1084+ }
1085+
1086+ @ Override
1087+ public boolean hasValue () {
1088+ return encodedValues .isEmpty () == false ;
10111089 }
10121090
10131091 public FieldWriter mergeWith (IgnoredSourceFieldMapper .NameValue nameValue ) {
10141092 assert Objects .equals (nameValue .name (), fieldName ) : "IgnoredSource is merged with wrong field data" ;
1015-
1016- values .add (nameValue .value ());
1093+ if (nameValue .hasValue ()) {
1094+ encodedValues .add (nameValue .value ());
1095+ }
10171096 return this ;
10181097 }
10191098 }
0 commit comments