From 738976e34ec61459580a8909dd73f738dcd8fe36 Mon Sep 17 00:00:00 2001
From: Nik Everett
Date: Fri, 31 Oct 2025 12:50:55 -0400
Subject: [PATCH 1/9] Block loaders for MV_MIN and MV_MAX for keywords
Adds special purpose `BlockLoader` implementations for the `MV_MIN` and
`MV_MAX` functions for `keyword` fields with doc values. These are a
noop for single valued keywords but should be *much* faster for
multivalued keywords.
These aren't plugged in yet. We can plug them in and performance test
them in #137382. And they give us two more functions we can use to
demonstrate #137382.
---
.../AbstractBytesRefsFromOrdsBlockLoader.java | 206 ++++++++++++++++++
.../BytesRefsFromOrdsBlockLoader.java | 179 +--------------
.../MvMaxBytesRefsFromOrdsBlockLoader.java | 114 ++++++++++
.../MvMinBytesRefsFromOrdsBlockLoader.java | 104 +++++++++
.../Utf8CodePointsFromOrdsBlockLoader.java | 20 +-
.../AbstractFromOrdsBlockLoaderTests.java | 100 +++++++++
...vMaxBytesRefsFromOrdsBlockLoaderTests.java | 86 ++++++++
...vMinBytesRefsFromOrdsBlockLoaderTests.java | 86 ++++++++
...tf8CodePointsFromOrdsBlockLoaderTests.java | 143 ++++--------
9 files changed, 758 insertions(+), 280 deletions(-)
create mode 100644 server/src/main/java/org/elasticsearch/index/mapper/blockloader/docvalues/AbstractBytesRefsFromOrdsBlockLoader.java
create mode 100644 server/src/main/java/org/elasticsearch/index/mapper/blockloader/docvalues/MvMaxBytesRefsFromOrdsBlockLoader.java
create mode 100644 server/src/main/java/org/elasticsearch/index/mapper/blockloader/docvalues/MvMinBytesRefsFromOrdsBlockLoader.java
create mode 100644 server/src/test/java/org/elasticsearch/index/mapper/blockloader/docvalues/AbstractFromOrdsBlockLoaderTests.java
create mode 100644 server/src/test/java/org/elasticsearch/index/mapper/blockloader/docvalues/MvMaxBytesRefsFromOrdsBlockLoaderTests.java
create mode 100644 server/src/test/java/org/elasticsearch/index/mapper/blockloader/docvalues/MvMinBytesRefsFromOrdsBlockLoaderTests.java
diff --git a/server/src/main/java/org/elasticsearch/index/mapper/blockloader/docvalues/AbstractBytesRefsFromOrdsBlockLoader.java b/server/src/main/java/org/elasticsearch/index/mapper/blockloader/docvalues/AbstractBytesRefsFromOrdsBlockLoader.java
new file mode 100644
index 0000000000000..00edc40002247
--- /dev/null
+++ b/server/src/main/java/org/elasticsearch/index/mapper/blockloader/docvalues/AbstractBytesRefsFromOrdsBlockLoader.java
@@ -0,0 +1,206 @@
+/*
+ * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
+ * Public License v 1"; you may not use this file except in compliance with, at
+ * your election, the "Elastic License 2.0", the "GNU Affero General Public
+ * License v3.0 only", or the "Server Side Public License, v 1".
+ */
+
+package org.elasticsearch.index.mapper.blockloader.docvalues;
+
+import org.apache.lucene.index.DocValues;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.SortedDocValues;
+import org.apache.lucene.index.SortedSetDocValues;
+import org.apache.lucene.util.BytesRef;
+
+import java.io.IOException;
+
+/**
+ * Loads {@code keyword} style fields that are stored as a lookup table.
+ */
+public abstract class AbstractBytesRefsFromOrdsBlockLoader extends BlockDocValuesReader.DocValuesBlockLoader {
+ protected final String fieldName;
+
+ public AbstractBytesRefsFromOrdsBlockLoader(String fieldName) {
+ this.fieldName = fieldName;
+ }
+
+ @Override
+ public BytesRefBuilder builder(BlockFactory factory, int expectedCount) {
+ return factory.bytesRefs(expectedCount);
+ }
+
+ @Override
+ public AllReader reader(LeafReaderContext context) throws IOException {
+ SortedSetDocValues docValues = context.reader().getSortedSetDocValues(fieldName);
+ if (docValues != null) {
+ SortedDocValues singleton = DocValues.unwrapSingleton(docValues);
+ if (singleton != null) {
+ return singletonReader(singleton);
+ }
+ return sortedSetReader(docValues);
+ }
+ SortedDocValues singleton = context.reader().getSortedDocValues(fieldName);
+ if (singleton != null) {
+ return singletonReader(singleton);
+ }
+ return new ConstantNullsReader();
+ }
+
+ protected abstract AllReader singletonReader(SortedDocValues docValues);
+
+ protected abstract AllReader sortedSetReader(SortedSetDocValues docValues);
+
+ protected static class Singleton extends BlockDocValuesReader {
+ private final SortedDocValues ordinals;
+
+ Singleton(SortedDocValues ordinals) {
+ this.ordinals = ordinals;
+ }
+
+ private Block readSingleDoc(BlockFactory factory, int docId) throws IOException {
+ if (ordinals.advanceExact(docId)) {
+ BytesRef v = ordinals.lookupOrd(ordinals.ordValue());
+ // the returned BytesRef can be reused
+ return factory.constantBytes(BytesRef.deepCopyOf(v), 1);
+ } else {
+ return factory.constantNulls(1);
+ }
+ }
+
+ @Override
+ public Block read(BlockFactory factory, Docs docs, int offset, boolean nullsFiltered) throws IOException {
+ if (docs.count() - offset == 1) {
+ return readSingleDoc(factory, docs.get(offset));
+ }
+ if (ordinals instanceof OptionalColumnAtATimeReader direct) {
+ Block block = direct.tryRead(factory, docs, offset, nullsFiltered, null, false);
+ if (block != null) {
+ return block;
+ }
+ }
+ try (var builder = factory.singletonOrdinalsBuilder(ordinals, docs.count() - offset, false)) {
+ for (int i = offset; i < docs.count(); i++) {
+ int doc = docs.get(i);
+ if (ordinals.advanceExact(doc)) {
+ builder.appendOrd(ordinals.ordValue());
+ } else {
+ builder.appendNull();
+ }
+ }
+ return builder.build();
+ }
+ }
+
+ @Override
+ public void read(int docId, StoredFields storedFields, Builder builder) throws IOException {
+ if (ordinals.advanceExact(docId)) {
+ ((BytesRefBuilder) builder).appendBytesRef(ordinals.lookupOrd(ordinals.ordValue()));
+ } else {
+ builder.appendNull();
+ }
+ }
+
+ @Override
+ public int docId() {
+ return ordinals.docID();
+ }
+
+ @Override
+ public String toString() {
+ return "BytesRefsFromOrds.Singleton";
+ }
+ }
+
+ protected static class SortedSet extends BlockDocValuesReader {
+ private final SortedSetDocValues ordinals;
+
+ SortedSet(SortedSetDocValues ordinals) {
+ this.ordinals = ordinals;
+ }
+
+ @Override
+ public Block read(BlockFactory factory, Docs docs, int offset, boolean nullsFiltered) throws IOException {
+ if (docs.count() - offset == 1) {
+ return readSingleDoc(factory, docs.get(offset));
+ }
+ try (var builder = factory.sortedSetOrdinalsBuilder(ordinals, docs.count() - offset)) {
+ for (int i = offset; i < docs.count(); i++) {
+ int doc = docs.get(i);
+ if (doc < ordinals.docID()) {
+ throw new IllegalStateException("docs within same block must be in order");
+ }
+ if (ordinals.advanceExact(doc) == false) {
+ builder.appendNull();
+ continue;
+ }
+ int count = ordinals.docValueCount();
+ if (count == 1) {
+ builder.appendOrd(Math.toIntExact(ordinals.nextOrd()));
+ } else {
+ builder.beginPositionEntry();
+ for (int c = 0; c < count; c++) {
+ builder.appendOrd(Math.toIntExact(ordinals.nextOrd()));
+ }
+ builder.endPositionEntry();
+ }
+ }
+ return builder.build();
+ }
+ }
+
+ @Override
+ public void read(int docId, StoredFields storedFields, Builder builder) throws IOException {
+ read(docId, (BytesRefBuilder) builder);
+ }
+
+ private Block readSingleDoc(BlockFactory factory, int docId) throws IOException {
+ if (ordinals.advanceExact(docId) == false) {
+ return factory.constantNulls(1);
+ }
+ int count = ordinals.docValueCount();
+ if (count == 1) {
+ BytesRef v = ordinals.lookupOrd(ordinals.nextOrd());
+ return factory.constantBytes(BytesRef.deepCopyOf(v), 1);
+ }
+ try (var builder = factory.bytesRefsFromDocValues(count)) {
+ builder.beginPositionEntry();
+ for (int c = 0; c < count; c++) {
+ BytesRef v = ordinals.lookupOrd(ordinals.nextOrd());
+ builder.appendBytesRef(v);
+ }
+ builder.endPositionEntry();
+ return builder.build();
+ }
+ }
+
+ private void read(int docId, BytesRefBuilder builder) throws IOException {
+ if (false == ordinals.advanceExact(docId)) {
+ builder.appendNull();
+ return;
+ }
+ int count = ordinals.docValueCount();
+ if (count == 1) {
+ builder.appendBytesRef(ordinals.lookupOrd(ordinals.nextOrd()));
+ return;
+ }
+ builder.beginPositionEntry();
+ for (int v = 0; v < count; v++) {
+ builder.appendBytesRef(ordinals.lookupOrd(ordinals.nextOrd()));
+ }
+ builder.endPositionEntry();
+ }
+
+ @Override
+ public int docId() {
+ return ordinals.docID();
+ }
+
+ @Override
+ public String toString() {
+ return "BytesRefsFromOrds.SortedSet";
+ }
+ }
+}
diff --git a/server/src/main/java/org/elasticsearch/index/mapper/blockloader/docvalues/BytesRefsFromOrdsBlockLoader.java b/server/src/main/java/org/elasticsearch/index/mapper/blockloader/docvalues/BytesRefsFromOrdsBlockLoader.java
index f6b3daf536d8d..fc829a798f18f 100644
--- a/server/src/main/java/org/elasticsearch/index/mapper/blockloader/docvalues/BytesRefsFromOrdsBlockLoader.java
+++ b/server/src/main/java/org/elasticsearch/index/mapper/blockloader/docvalues/BytesRefsFromOrdsBlockLoader.java
@@ -13,41 +13,25 @@
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.SortedDocValues;
import org.apache.lucene.index.SortedSetDocValues;
-import org.apache.lucene.util.BytesRef;
-import org.elasticsearch.index.mapper.BlockLoader;
import java.io.IOException;
/**
* Loads {@code keyword} style fields that are stored as a lookup table and ordinals.
*/
-public class BytesRefsFromOrdsBlockLoader extends BlockDocValuesReader.DocValuesBlockLoader {
- private final String fieldName;
-
+public class BytesRefsFromOrdsBlockLoader extends AbstractBytesRefsFromOrdsBlockLoader {
public BytesRefsFromOrdsBlockLoader(String fieldName) {
- this.fieldName = fieldName;
+ super(fieldName);
}
@Override
- public BytesRefBuilder builder(BlockFactory factory, int expectedCount) {
- return factory.bytesRefs(expectedCount);
+ protected AllReader singletonReader(SortedDocValues docValues) {
+ return new Singleton(docValues);
}
@Override
- public AllReader reader(LeafReaderContext context) throws IOException {
- SortedSetDocValues docValues = context.reader().getSortedSetDocValues(fieldName);
- if (docValues != null) {
- SortedDocValues singleton = DocValues.unwrapSingleton(docValues);
- if (singleton != null) {
- return new SingletonOrdinals(singleton);
- }
- return new Ordinals(docValues);
- }
- SortedDocValues singleton = context.reader().getSortedDocValues(fieldName);
- if (singleton != null) {
- return new SingletonOrdinals(singleton);
- }
- return new ConstantNullsReader();
+ protected AllReader sortedSetReader(SortedSetDocValues docValues) {
+ return new SortedSet(docValues);
}
@Override
@@ -64,155 +48,4 @@ public SortedSetDocValues ordinals(LeafReaderContext context) throws IOException
public String toString() {
return "BytesRefsFromOrds[" + fieldName + "]";
}
-
- private static class SingletonOrdinals extends BlockDocValuesReader {
- private final SortedDocValues ordinals;
-
- SingletonOrdinals(SortedDocValues ordinals) {
- this.ordinals = ordinals;
- }
-
- private BlockLoader.Block readSingleDoc(BlockFactory factory, int docId) throws IOException {
- if (ordinals.advanceExact(docId)) {
- BytesRef v = ordinals.lookupOrd(ordinals.ordValue());
- // the returned BytesRef can be reused
- return factory.constantBytes(BytesRef.deepCopyOf(v), 1);
- } else {
- return factory.constantNulls(1);
- }
- }
-
- @Override
- public BlockLoader.Block read(BlockFactory factory, Docs docs, int offset, boolean nullsFiltered) throws IOException {
- if (docs.count() - offset == 1) {
- return readSingleDoc(factory, docs.get(offset));
- }
- if (ordinals instanceof BlockLoader.OptionalColumnAtATimeReader direct) {
- BlockLoader.Block block = direct.tryRead(factory, docs, offset, nullsFiltered, null, false);
- if (block != null) {
- return block;
- }
- }
- try (var builder = factory.singletonOrdinalsBuilder(ordinals, docs.count() - offset, false)) {
- for (int i = offset; i < docs.count(); i++) {
- int doc = docs.get(i);
- if (ordinals.advanceExact(doc)) {
- builder.appendOrd(ordinals.ordValue());
- } else {
- builder.appendNull();
- }
- }
- return builder.build();
- }
- }
-
- @Override
- public void read(int docId, BlockLoader.StoredFields storedFields, Builder builder) throws IOException {
- if (ordinals.advanceExact(docId)) {
- ((BytesRefBuilder) builder).appendBytesRef(ordinals.lookupOrd(ordinals.ordValue()));
- } else {
- builder.appendNull();
- }
- }
-
- @Override
- public int docId() {
- return ordinals.docID();
- }
-
- @Override
- public String toString() {
- return "BlockDocValuesReader.SingletonOrdinals";
- }
- }
-
- private static class Ordinals extends BlockDocValuesReader {
- private final SortedSetDocValues ordinals;
-
- Ordinals(SortedSetDocValues ordinals) {
- this.ordinals = ordinals;
- }
-
- @Override
- public BlockLoader.Block read(BlockFactory factory, Docs docs, int offset, boolean nullsFiltered) throws IOException {
- if (docs.count() - offset == 1) {
- return readSingleDoc(factory, docs.get(offset));
- }
- try (var builder = factory.sortedSetOrdinalsBuilder(ordinals, docs.count() - offset)) {
- for (int i = offset; i < docs.count(); i++) {
- int doc = docs.get(i);
- if (doc < ordinals.docID()) {
- throw new IllegalStateException("docs within same block must be in order");
- }
- if (ordinals.advanceExact(doc) == false) {
- builder.appendNull();
- continue;
- }
- int count = ordinals.docValueCount();
- if (count == 1) {
- builder.appendOrd(Math.toIntExact(ordinals.nextOrd()));
- } else {
- builder.beginPositionEntry();
- for (int c = 0; c < count; c++) {
- builder.appendOrd(Math.toIntExact(ordinals.nextOrd()));
- }
- builder.endPositionEntry();
- }
- }
- return builder.build();
- }
- }
-
- @Override
- public void read(int docId, BlockLoader.StoredFields storedFields, Builder builder) throws IOException {
- read(docId, (BytesRefBuilder) builder);
- }
-
- private BlockLoader.Block readSingleDoc(BlockFactory factory, int docId) throws IOException {
- if (ordinals.advanceExact(docId) == false) {
- return factory.constantNulls(1);
- }
- int count = ordinals.docValueCount();
- if (count == 1) {
- BytesRef v = ordinals.lookupOrd(ordinals.nextOrd());
- return factory.constantBytes(BytesRef.deepCopyOf(v), 1);
- }
- try (var builder = factory.bytesRefsFromDocValues(count)) {
- builder.beginPositionEntry();
- for (int c = 0; c < count; c++) {
- BytesRef v = ordinals.lookupOrd(ordinals.nextOrd());
- builder.appendBytesRef(v);
- }
- builder.endPositionEntry();
- return builder.build();
- }
- }
-
- private void read(int docId, BytesRefBuilder builder) throws IOException {
- if (false == ordinals.advanceExact(docId)) {
- builder.appendNull();
- return;
- }
- int count = ordinals.docValueCount();
- if (count == 1) {
- builder.appendBytesRef(ordinals.lookupOrd(ordinals.nextOrd()));
- return;
- }
- builder.beginPositionEntry();
- for (int v = 0; v < count; v++) {
- builder.appendBytesRef(ordinals.lookupOrd(ordinals.nextOrd()));
- }
- builder.endPositionEntry();
- }
-
- @Override
- public int docId() {
- return ordinals.docID();
- }
-
- @Override
- public String toString() {
- return "BlockDocValuesReader.Ordinals";
- }
- }
}
diff --git a/server/src/main/java/org/elasticsearch/index/mapper/blockloader/docvalues/MvMaxBytesRefsFromOrdsBlockLoader.java b/server/src/main/java/org/elasticsearch/index/mapper/blockloader/docvalues/MvMaxBytesRefsFromOrdsBlockLoader.java
new file mode 100644
index 0000000000000..25f836d465930
--- /dev/null
+++ b/server/src/main/java/org/elasticsearch/index/mapper/blockloader/docvalues/MvMaxBytesRefsFromOrdsBlockLoader.java
@@ -0,0 +1,114 @@
+/*
+ * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
+ * Public License v 1"; you may not use this file except in compliance with, at
+ * your election, the "Elastic License 2.0", the "GNU Affero General Public
+ * License v3.0 only", or the "Server Side Public License, v 1".
+ */
+
+package org.elasticsearch.index.mapper.blockloader.docvalues;
+
+import org.apache.lucene.index.SortedDocValues;
+import org.apache.lucene.index.SortedSetDocValues;
+import org.apache.lucene.util.BytesRef;
+
+import java.io.IOException;
+
+/**
+ * Loads {@code MV_MIN} applied to {@code keyword} style fields that are stored as
+ * a lookup table and ordinals.
+ */
+public class MvMaxBytesRefsFromOrdsBlockLoader extends AbstractBytesRefsFromOrdsBlockLoader {
+ private final String fieldName;
+
+ public MvMaxBytesRefsFromOrdsBlockLoader(String fieldName) {
+ super(fieldName);
+ this.fieldName = fieldName;
+ }
+
+ @Override
+ protected AllReader singletonReader(SortedDocValues docValues) {
+ return new Singleton(docValues);
+ }
+
+ @Override
+ protected AllReader sortedSetReader(SortedSetDocValues docValues) {
+ return new MvMaxSortedSet(docValues);
+ }
+
+ @Override
+ public String toString() {
+ return "MvMaxBytesRefsFromOrds[" + fieldName + "]";
+ }
+
+ private static class MvMaxSortedSet extends BlockDocValuesReader {
+ private final SortedSetDocValues ordinals;
+
+ MvMaxSortedSet(SortedSetDocValues ordinals) {
+ this.ordinals = ordinals;
+ }
+
+ @Override
+ public Block read(BlockFactory factory, Docs docs, int offset, boolean nullsFiltered) throws IOException {
+ if (docs.count() - offset == 1) {
+ return readSingleDoc(factory, docs.get(offset));
+ }
+ try (var builder = factory.sortedSetOrdinalsBuilder(ordinals, docs.count() - offset)) {
+ for (int i = offset; i < docs.count(); i++) {
+ int doc = docs.get(i);
+ if (doc < ordinals.docID()) {
+ throw new IllegalStateException("docs within same block must be in order");
+ }
+ if (ordinals.advanceExact(doc) == false) {
+ builder.appendNull();
+ continue;
+ }
+ discardAllButLast();
+ builder.appendOrd(Math.toIntExact(ordinals.nextOrd()));
+ }
+ return builder.build();
+ }
+ }
+
+ @Override
+ public void read(int docId, StoredFields storedFields, Builder builder) throws IOException {
+ read(docId, (BytesRefBuilder) builder);
+ }
+
+ private Block readSingleDoc(BlockFactory factory, int docId) throws IOException {
+ if (ordinals.advanceExact(docId) == false) {
+ return factory.constantNulls(1);
+ }
+ discardAllButLast();
+ BytesRef v = ordinals.lookupOrd(ordinals.nextOrd());
+ return factory.constantBytes(BytesRef.deepCopyOf(v), 1);
+ }
+
+ private void read(int docId, BytesRefBuilder builder) throws IOException {
+ if (false == ordinals.advanceExact(docId)) {
+ builder.appendNull();
+ return;
+ }
+ discardAllButLast();
+ builder.appendBytesRef(ordinals.lookupOrd(ordinals.nextOrd()));
+ }
+
+ private void discardAllButLast() throws IOException {
+ int count = ordinals.docValueCount();
+ for (int i = 0; i < count - 1; i++) {
+ ordinals.nextOrd();
+ }
+ }
+
+ @Override
+ public int docId() {
+ return ordinals.docID();
+ }
+
+ @Override
+ public String toString() {
+ return "MvMaxBytesRefsFromOrds.SortedSet";
+ }
+ }
+}
diff --git a/server/src/main/java/org/elasticsearch/index/mapper/blockloader/docvalues/MvMinBytesRefsFromOrdsBlockLoader.java b/server/src/main/java/org/elasticsearch/index/mapper/blockloader/docvalues/MvMinBytesRefsFromOrdsBlockLoader.java
new file mode 100644
index 0000000000000..f7b48e92b7df0
--- /dev/null
+++ b/server/src/main/java/org/elasticsearch/index/mapper/blockloader/docvalues/MvMinBytesRefsFromOrdsBlockLoader.java
@@ -0,0 +1,104 @@
+/*
+ * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
+ * Public License v 1"; you may not use this file except in compliance with, at
+ * your election, the "Elastic License 2.0", the "GNU Affero General Public
+ * License v3.0 only", or the "Server Side Public License, v 1".
+ */
+
+package org.elasticsearch.index.mapper.blockloader.docvalues;
+
+import org.apache.lucene.index.SortedDocValues;
+import org.apache.lucene.index.SortedSetDocValues;
+import org.apache.lucene.util.BytesRef;
+
+import java.io.IOException;
+
+/**
+ * Loads {@code MV_MIN} applied to {@code keyword} style fields that are stored as
+ * a lookup table and ordinals.
+ */
+public class MvMinBytesRefsFromOrdsBlockLoader extends AbstractBytesRefsFromOrdsBlockLoader {
+ private final String fieldName;
+
+ public MvMinBytesRefsFromOrdsBlockLoader(String fieldName) {
+ super(fieldName);
+ this.fieldName = fieldName;
+ }
+
+ @Override
+ protected AllReader singletonReader(SortedDocValues docValues) {
+ return new Singleton(docValues);
+ }
+
+ @Override
+ protected AllReader sortedSetReader(SortedSetDocValues docValues) {
+ return new MvMinSortedSet(docValues);
+ }
+
+ @Override
+ public String toString() {
+ return "MvMinBytesRefsFromOrds[" + fieldName + "]";
+ }
+
+ private static class MvMinSortedSet extends BlockDocValuesReader {
+ private final SortedSetDocValues ordinals;
+
+ MvMinSortedSet(SortedSetDocValues ordinals) {
+ this.ordinals = ordinals;
+ }
+
+ @Override
+ public Block read(BlockFactory factory, Docs docs, int offset, boolean nullsFiltered) throws IOException {
+ if (docs.count() - offset == 1) {
+ return readSingleDoc(factory, docs.get(offset));
+ }
+ try (var builder = factory.sortedSetOrdinalsBuilder(ordinals, docs.count() - offset)) {
+ for (int i = offset; i < docs.count(); i++) {
+ int doc = docs.get(i);
+ if (doc < ordinals.docID()) {
+ throw new IllegalStateException("docs within same block must be in order");
+ }
+ if (ordinals.advanceExact(doc) == false) {
+ builder.appendNull();
+ continue;
+ }
+ builder.appendOrd(Math.toIntExact(ordinals.nextOrd()));
+ }
+ return builder.build();
+ }
+ }
+
+ @Override
+ public void read(int docId, StoredFields storedFields, Builder builder) throws IOException {
+ read(docId, (BytesRefBuilder) builder);
+ }
+
+ private Block readSingleDoc(BlockFactory factory, int docId) throws IOException {
+ if (ordinals.advanceExact(docId) == false) {
+ return factory.constantNulls(1);
+ }
+ BytesRef v = ordinals.lookupOrd(ordinals.nextOrd());
+ return factory.constantBytes(BytesRef.deepCopyOf(v), 1);
+ }
+
+ private void read(int docId, BytesRefBuilder builder) throws IOException {
+ if (false == ordinals.advanceExact(docId)) {
+ builder.appendNull();
+ return;
+ }
+ builder.appendBytesRef(ordinals.lookupOrd(ordinals.nextOrd()));
+ }
+
+ @Override
+ public int docId() {
+ return ordinals.docID();
+ }
+
+ @Override
+ public String toString() {
+ return "MvMinBytesRefsFromOrds.SortedSet";
+ }
+ }
+}
diff --git a/server/src/main/java/org/elasticsearch/index/mapper/blockloader/docvalues/Utf8CodePointsFromOrdsBlockLoader.java b/server/src/main/java/org/elasticsearch/index/mapper/blockloader/docvalues/Utf8CodePointsFromOrdsBlockLoader.java
index 730ef2a58a6f3..b5f044e813e31 100644
--- a/server/src/main/java/org/elasticsearch/index/mapper/blockloader/docvalues/Utf8CodePointsFromOrdsBlockLoader.java
+++ b/server/src/main/java/org/elasticsearch/index/mapper/blockloader/docvalues/Utf8CodePointsFromOrdsBlockLoader.java
@@ -56,16 +56,16 @@ public AllReader reader(LeafReaderContext context) throws IOException {
}
SortedDocValues singleton = DocValues.unwrapSingleton(docValues);
if (singleton != null) {
- return new SingletonOrdinals(singleton);
+ return new Singleton(singleton);
}
- return new Ordinals(warnings, docValues);
+ return new SortedSet(warnings, docValues);
}
SortedDocValues singleton = context.reader().getSortedDocValues(fieldName);
if (singleton != null) {
if (singleton.getValueCount() > LOW_CARDINALITY) {
return new ImmediateOrdinals(warnings, DocValues.singleton(singleton));
}
- return new SingletonOrdinals(singleton);
+ return new Singleton(singleton);
}
return new ConstantNullsReader();
}
@@ -93,13 +93,13 @@ public String toString() {
* ordinals and look them up in the cache immediately.
*
*/
- private static class SingletonOrdinals extends BlockDocValuesReader {
+ private static class Singleton extends BlockDocValuesReader {
private final SortedDocValues ordinals;
private final int[] cache;
private int cacheEntriesFilled;
- SingletonOrdinals(SortedDocValues ordinals) {
+ Singleton(SortedDocValues ordinals) {
this.ordinals = ordinals;
// TODO track this memory. we can't yet because this isn't Closeable
@@ -142,7 +142,7 @@ public int docId() {
@Override
public String toString() {
- return "Utf8CodePointsFromOrds.SingletonOrdinals";
+ return "Utf8CodePointsFromOrds.Singleton";
}
private Block blockForSingleDoc(BlockFactory factory, int docId) throws IOException {
@@ -225,16 +225,16 @@ private int codePointsAtOrd(int ord) throws IOException {
/**
* Loads low cardinality non-singleton ordinals in using a cache of code point counts.
- * See {@link SingletonOrdinals} for the process
+ * See {@link Singleton} for the process
*/
- private static class Ordinals extends BlockDocValuesReader {
+ private static class SortedSet extends BlockDocValuesReader {
private final Warnings warnings;
private final SortedSetDocValues ordinals;
private final int[] cache;
private int cacheEntriesFilled;
- Ordinals(Warnings warnings, SortedSetDocValues ordinals) {
+ SortedSet(Warnings warnings, SortedSetDocValues ordinals) {
this.warnings = warnings;
this.ordinals = ordinals;
@@ -283,7 +283,7 @@ public int docId() {
@Override
public String toString() {
- return "Utf8CodePointsFromOrds.Ordinals";
+ return "Utf8CodePointsFromOrds.SortedSet";
}
private Block blockForSingleDoc(BlockFactory factory, int docId) throws IOException {
diff --git a/server/src/test/java/org/elasticsearch/index/mapper/blockloader/docvalues/AbstractFromOrdsBlockLoaderTests.java b/server/src/test/java/org/elasticsearch/index/mapper/blockloader/docvalues/AbstractFromOrdsBlockLoaderTests.java
new file mode 100644
index 0000000000000..2f0c1e618ef90
--- /dev/null
+++ b/server/src/test/java/org/elasticsearch/index/mapper/blockloader/docvalues/AbstractFromOrdsBlockLoaderTests.java
@@ -0,0 +1,100 @@
+/*
+ * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
+ * Public License v 1"; you may not use this file except in compliance with, at
+ * your election, the "Elastic License 2.0", the "GNU Affero General Public
+ * License v3.0 only", or the "Server Side Public License, v 1".
+ */
+
+package org.elasticsearch.index.mapper.blockloader.docvalues;
+
+import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
+
+import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.index.IndexableField;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.tests.index.RandomIndexWriter;
+import org.apache.lucene.util.BytesRef;
+import org.elasticsearch.index.mapper.BlockLoader;
+import org.elasticsearch.index.mapper.KeywordFieldMapper;
+import org.elasticsearch.index.mapper.TestBlock;
+import org.elasticsearch.test.ESTestCase;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.elasticsearch.index.mapper.blockloader.docvalues.Utf8CodePointsFromOrdsBlockLoader.LOW_CARDINALITY;
+
+public abstract class AbstractFromOrdsBlockLoaderTests extends ESTestCase {
+ @ParametersFactory(argumentFormatting = "blockAtATime=%s, lowCardinality=%s, multiValues=%s, missingValues=%s")
+ public static List