diff --git a/server/src/main/java/org/elasticsearch/index/mapper/KeywordFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/KeywordFieldMapper.java index c1fe5ee41b87b..1dfd44d8a3d0d 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/KeywordFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/KeywordFieldMapper.java @@ -1078,21 +1078,27 @@ public BytesSyntheticFieldLoader(String name, String simpleName) { @Override public Leaf leaf(LeafReader reader) throws IOException { SortedSetDocValues leaf = DocValues.getSortedSet(reader, name); + if (leaf.getValueCount() == 0) { + return SourceLoader.SyntheticFieldLoader.NOTHING.leaf(reader); + } return new SourceLoader.SyntheticFieldLoader.Leaf() { private boolean hasValue; @Override - public void advanceToDoc(int docId) throws IOException { - hasValue = leaf.advanceExact(docId); + public boolean empty() { + return false; } @Override - public boolean hasValue() { - return hasValue; + public boolean advanceToDoc(int docId) throws IOException { + return hasValue = leaf.advanceExact(docId); } @Override - public void load(XContentBuilder b) throws IOException { + public void write(XContentBuilder b) throws IOException { + if (false == hasValue) { + return; + } long first = leaf.nextOrd(); long next = leaf.nextOrd(); if (next == SortedSetDocValues.NO_MORE_ORDS) { diff --git a/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java index c7199b1d20ba1..5b47d43fca80b 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java @@ -17,6 +17,7 @@ import org.apache.lucene.index.DocValues; import org.apache.lucene.index.LeafReader; import org.apache.lucene.index.LeafReaderContext; +import org.apache.lucene.index.NumericDocValues; import org.apache.lucene.index.SortedNumericDocValues; import org.apache.lucene.sandbox.document.HalfFloatPoint; import org.apache.lucene.sandbox.search.IndexSortSortedNumericDocValuesRangeQuery; @@ -1631,22 +1632,28 @@ protected NumericSyntheticFieldLoader(String name, String simpleName) { @Override public Leaf leaf(LeafReader reader) throws IOException { - SortedNumericDocValues leaf = DocValues.getSortedNumeric(reader, name); + SortedNumericDocValues leaf = dv(reader); + if (leaf == null) { + return SourceLoader.SyntheticFieldLoader.NOTHING.leaf(reader); + } return new SourceLoader.SyntheticFieldLoader.Leaf() { private boolean hasValue; @Override - public void advanceToDoc(int docId) throws IOException { - hasValue = leaf.advanceExact(docId); + public boolean empty() { + return false; } @Override - public boolean hasValue() { - return hasValue; + public boolean advanceToDoc(int docId) throws IOException { + return hasValue = leaf.advanceExact(docId); } @Override - public void load(XContentBuilder b) throws IOException { + public void write(XContentBuilder b) throws IOException { + if (false == hasValue) { + return; + } if (leaf.docValueCount() == 1) { b.field(simpleName); loadNextValue(b, leaf.nextValue()); @@ -1662,5 +1669,23 @@ public void load(XContentBuilder b) throws IOException { } protected abstract void loadNextValue(XContentBuilder b, long value) throws IOException; + + /** + * Returns a {@link SortedNumericDocValues} or null if it doesn't have any doc values. + * See {@link DocValues#getSortedNumeric} which is *nearly* the same, but it returns + * an "empty" implementation if there aren't any doc values. We need to be able to + * tell if there aren't any and return our empty leaf source loader. + */ + private SortedNumericDocValues dv(LeafReader reader) throws IOException { + SortedNumericDocValues dv = reader.getSortedNumericDocValues(name); + if (dv != null) { + return dv; + } + NumericDocValues single = reader.getNumericDocValues(name); + if (single != null) { + return DocValues.singleton(single); + } + return null; + } } } diff --git a/server/src/main/java/org/elasticsearch/index/mapper/ObjectMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/ObjectMapper.java index 3e629b4a21119..e98ccda06e352 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/ObjectMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/ObjectMapper.java @@ -554,53 +554,51 @@ protected void doXContent(XContentBuilder builder, Params params) throws IOExcep @Override public SourceLoader.SyntheticFieldLoader syntheticFieldLoader() { - List fields = new ArrayList<>(); - mappers.values().stream().sorted(Comparator.comparing(Mapper::name)).forEach(sub -> { - SourceLoader.SyntheticFieldLoader subLoader = sub.syntheticFieldLoader(); - if (subLoader != null) { - fields.add(subLoader); - } - }); + List fields = mappers.values() + .stream() + .sorted(Comparator.comparing(Mapper::name)) + .map(Mapper::syntheticFieldLoader) + .filter(l -> l != null) + .toList(); return new SourceLoader.SyntheticFieldLoader() { @Override public Leaf leaf(LeafReader reader) throws IOException { - List leaves = new ArrayList<>(); + List l = new ArrayList<>(); for (SourceLoader.SyntheticFieldLoader field : fields) { - leaves.add(field.leaf(reader)); + Leaf leaf = field.leaf(reader); + if (false == leaf.empty()) { + l.add(leaf); + } } + SourceLoader.SyntheticFieldLoader.Leaf[] leaves = l.toArray(SourceLoader.SyntheticFieldLoader.Leaf[]::new); return new SourceLoader.SyntheticFieldLoader.Leaf() { + private boolean hasValue; + @Override - public void advanceToDoc(int docId) throws IOException { - for (SourceLoader.SyntheticFieldLoader.Leaf leaf : leaves) { - leaf.advanceToDoc(docId); - } + public boolean empty() { + return leaves.length == 0; } @Override - public boolean hasValue() { + public boolean advanceToDoc(int docId) throws IOException { + hasValue = false; for (SourceLoader.SyntheticFieldLoader.Leaf leaf : leaves) { - if (leaf.hasValue()) { - return true; - } + boolean leafHasValue = leaf.advanceToDoc(docId); + hasValue |= leafHasValue; } - return false; + return hasValue; } @Override - public void load(XContentBuilder b) throws IOException { - boolean started = false; - for (SourceLoader.SyntheticFieldLoader.Leaf leaf : leaves) { - if (leaf.hasValue()) { - if (false == started) { - started = true; - startSyntheticField(b); - } - leaf.load(b); - } + public void write(XContentBuilder b) throws IOException { + if (hasValue == false) { + return; } - if (started) { - b.endObject(); + startSyntheticField(b); + for (SourceLoader.SyntheticFieldLoader.Leaf leaf : leaves) { + leaf.write(b); } + b.endObject(); } }; } 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 89a4638d66a79..03e41a45b7a9f 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/SourceLoader.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/SourceLoader.java @@ -42,6 +42,16 @@ interface Leaf { * @param docId the doc to load */ BytesReference source(FieldsVisitor fieldsVisitor, int docId) throws IOException; + + Leaf EMPTY_OBJECT = new Leaf() { + @Override + public BytesReference source(FieldsVisitor fieldsVisitor, int docId) throws IOException { + // TODO accept a requested xcontent type + try (XContentBuilder b = new XContentBuilder(JsonXContent.jsonXContent, new ByteArrayOutputStream())) { + return BytesReference.bytes(b.startObject().endObject()); + } + } + }; } /** @@ -82,14 +92,16 @@ public boolean reordersFieldValues() { @Override public Leaf leaf(LeafReader reader) throws IOException { SyntheticFieldLoader.Leaf leaf = loader.leaf(reader); + if (leaf.empty()) { + return Leaf.EMPTY_OBJECT; + } return new Leaf() { @Override public BytesReference source(FieldsVisitor fieldsVisitor, int docId) throws IOException { // TODO accept a requested xcontent type try (XContentBuilder b = new XContentBuilder(JsonXContent.jsonXContent, new ByteArrayOutputStream())) { - leaf.advanceToDoc(docId); - if (leaf.hasValue()) { - leaf.load(b); + if (leaf.advanceToDoc(docId)) { + leaf.write(b); } else { b.startObject().endObject(); } @@ -109,15 +121,17 @@ interface SyntheticFieldLoader { */ SyntheticFieldLoader NOTHING = r -> new Leaf() { @Override - public void advanceToDoc(int docId) throws IOException {} + public boolean empty() { + return true; + } @Override - public boolean hasValue() { + public boolean advanceToDoc(int docId) throws IOException { return false; } @Override - public void load(XContentBuilder b) throws IOException {} + public void write(XContentBuilder b) throws IOException {} }; /** @@ -130,19 +144,19 @@ public void load(XContentBuilder b) throws IOException {} */ interface Leaf { /** - * Position the loader at a document. + * Is this entirely empty? */ - void advanceToDoc(int docId) throws IOException; + boolean empty(); /** - * Is there a value for this field in this document? + * Position the loader at a document. */ - boolean hasValue(); + boolean advanceToDoc(int docId) throws IOException; /** - * Load values for this document. + * Write values for this document. */ - void load(XContentBuilder b) throws IOException; + void write(XContentBuilder b) throws IOException; } }