From 49671481c57084283989e1ad41bd4240c28bff3c Mon Sep 17 00:00:00 2001 From: Alan Woodward Date: Mon, 13 Jul 2026 16:51:33 +0100 Subject: [PATCH 1/2] Remove IOException from LeafReader.getDocValuesSkipper() All of the global metadata available without calling advance() on a skipper is loaded at Skipper creation time and can therefore be returned without doing any IO. The only reason we need to do IO to create a Skipper is slicing an IndexInput, and that can be done lazily in advance() as skippers are designed to only be consumed in a single thread. This removes the throws declaration from LeafReader.getDocValuesSkipper(), which in combination with a similar change on PointValues will make numeric field metadata available without IO. --- .../org/apache/lucene/codecs/DocValuesProducer.java | 2 +- .../codecs/lucene90/Lucene90DocValuesProducer.java | 10 ++++++++-- .../codecs/perfield/PerFieldDocValuesFormat.java | 2 +- .../src/java/org/apache/lucene/index/CodecReader.java | 2 +- .../org/apache/lucene/index/DocValuesLeafReader.java | 2 +- .../java/org/apache/lucene/index/DocValuesSkipper.java | 6 +++--- .../java/org/apache/lucene/index/FilterLeafReader.java | 2 +- .../src/java/org/apache/lucene/index/LeafReader.java | 2 +- .../org/apache/lucene/index/ParallelLeafReader.java | 2 +- .../apache/lucene/index/SegmentDocValuesProducer.java | 2 +- .../apache/lucene/index/SlowCodecReaderWrapper.java | 2 +- .../lucene/index/SlowCompositeCodecReaderWrapper.java | 2 +- .../org/apache/lucene/index/SortingCodecReader.java | 2 +- .../org/apache/lucene/search/NumericFieldStats.java | 2 +- .../lucene/search/highlight/TermVectorLeafReader.java | 2 +- .../org/apache/lucene/index/memory/MemoryIndex.java | 2 +- .../codecs/asserting/AssertingDocValuesFormat.java | 2 +- .../apache/lucene/tests/index/AssertingLeafReader.java | 2 +- .../apache/lucene/tests/index/MergeReaderWrapper.java | 2 +- .../lucene/tests/index/MismatchedCodecReader.java | 2 +- .../org/apache/lucene/tests/search/QueryUtils.java | 2 +- 21 files changed, 30 insertions(+), 24 deletions(-) diff --git a/lucene/core/src/java/org/apache/lucene/codecs/DocValuesProducer.java b/lucene/core/src/java/org/apache/lucene/codecs/DocValuesProducer.java index ea9cb78b0ed4..7ed86e8f87fc 100644 --- a/lucene/core/src/java/org/apache/lucene/codecs/DocValuesProducer.java +++ b/lucene/core/src/java/org/apache/lucene/codecs/DocValuesProducer.java @@ -81,7 +81,7 @@ protected DocValuesProducer() {} * thread-safe: it will only be used by a single thread. The return value is undefined if {@link * FieldInfo#docValuesSkipIndexType()} returns {@link DocValuesSkipIndexType#NONE}. */ - public abstract DocValuesSkipper getSkipper(FieldInfo field) throws IOException; + public abstract DocValuesSkipper getSkipper(FieldInfo field); /** * Checks consistency of this producer diff --git a/lucene/core/src/java/org/apache/lucene/codecs/lucene90/Lucene90DocValuesProducer.java b/lucene/core/src/java/org/apache/lucene/codecs/lucene90/Lucene90DocValuesProducer.java index fcb1dad93d37..25eefc2660c7 100644 --- a/lucene/core/src/java/org/apache/lucene/codecs/lucene90/Lucene90DocValuesProducer.java +++ b/lucene/core/src/java/org/apache/lucene/codecs/lucene90/Lucene90DocValuesProducer.java @@ -2516,13 +2516,16 @@ long getLongValue(long index) throws IOException { } @Override - public DocValuesSkipper getSkipper(FieldInfo field) throws IOException { + public DocValuesSkipper getSkipper(FieldInfo field) { final DocValuesSkipperEntry entry = skippers.get(field.number); final IndexInput skipperSource = skipIndexData != null ? skipIndexData : data; - final IndexInput input = skipperSource.slice("doc value skipper", entry.offset, entry.length); + // TODO: should we write to disk the actual max level for this segment? return new DocValuesSkipper() { + + IndexInput input; + final int[] minDocID = new int[SKIP_INDEX_MAX_LEVEL]; final int[] maxDocID = new int[SKIP_INDEX_MAX_LEVEL]; @@ -2539,6 +2542,9 @@ public DocValuesSkipper getSkipper(FieldInfo field) throws IOException { @Override public void advance(int target) throws IOException { + if (input == null) { + input = skipperSource.slice("doc value skipper", entry.offset, entry.length); + } if (target > entry.maxDocId) { // skipper is exhausted for (int i = 0; i < SKIP_INDEX_MAX_LEVEL; i++) { diff --git a/lucene/core/src/java/org/apache/lucene/codecs/perfield/PerFieldDocValuesFormat.java b/lucene/core/src/java/org/apache/lucene/codecs/perfield/PerFieldDocValuesFormat.java index 9954c40de7db..b43d551032f4 100644 --- a/lucene/core/src/java/org/apache/lucene/codecs/perfield/PerFieldDocValuesFormat.java +++ b/lucene/core/src/java/org/apache/lucene/codecs/perfield/PerFieldDocValuesFormat.java @@ -343,7 +343,7 @@ public SortedSetDocValues getSortedSet(FieldInfo field) throws IOException { } @Override - public DocValuesSkipper getSkipper(FieldInfo field) throws IOException { + public DocValuesSkipper getSkipper(FieldInfo field) { DocValuesProducer producer = fields.get(field.number); return producer == null ? null : producer.getSkipper(field); } diff --git a/lucene/core/src/java/org/apache/lucene/index/CodecReader.java b/lucene/core/src/java/org/apache/lucene/index/CodecReader.java index 9e579a23a338..4f7a1ea213e9 100644 --- a/lucene/core/src/java/org/apache/lucene/index/CodecReader.java +++ b/lucene/core/src/java/org/apache/lucene/index/CodecReader.java @@ -197,7 +197,7 @@ public final SortedSetDocValues getSortedSetDocValues(String field) throws IOExc } @Override - public final DocValuesSkipper getDocValuesSkipper(String field) throws IOException { + public final DocValuesSkipper getDocValuesSkipper(String field) { ensureOpen(); FieldInfo fi = getFieldInfos().fieldInfo(field); if (fi == null || fi.docValuesSkipIndexType() == DocValuesSkipIndexType.NONE) { diff --git a/lucene/core/src/java/org/apache/lucene/index/DocValuesLeafReader.java b/lucene/core/src/java/org/apache/lucene/index/DocValuesLeafReader.java index c7635c08fd40..86f9482606d8 100644 --- a/lucene/core/src/java/org/apache/lucene/index/DocValuesLeafReader.java +++ b/lucene/core/src/java/org/apache/lucene/index/DocValuesLeafReader.java @@ -113,7 +113,7 @@ public final CacheHelper getReaderCacheHelper() { } @Override - public DocValuesSkipper getDocValuesSkipper(String field) throws IOException { + public DocValuesSkipper getDocValuesSkipper(String field) { throw new UnsupportedOperationException(); } } diff --git a/lucene/core/src/java/org/apache/lucene/index/DocValuesSkipper.java b/lucene/core/src/java/org/apache/lucene/index/DocValuesSkipper.java index 0dd105d6b465..0ab5cec33122 100644 --- a/lucene/core/src/java/org/apache/lucene/index/DocValuesSkipper.java +++ b/lucene/core/src/java/org/apache/lucene/index/DocValuesSkipper.java @@ -142,7 +142,7 @@ public final void advance(long minValue, long maxValue) throws IOException { * @param reader the index reader to be queried * @param field the field to retrieve values for */ - public static long globalMinValue(IndexReader reader, String field) throws IOException { + public static long globalMinValue(IndexReader reader, String field) { long minValue = Long.MAX_VALUE; for (LeafReaderContext ctx : reader.leaves()) { if (ctx.reader().getFieldInfos().fieldInfo(field) == null) { @@ -166,7 +166,7 @@ public static long globalMinValue(IndexReader reader, String field) throws IOExc * @param reader the index reader to be queried * @param field the field to retrieve values for */ - public static long globalMaxValue(IndexReader reader, String field) throws IOException { + public static long globalMaxValue(IndexReader reader, String field) { long maxValue = Long.MIN_VALUE; for (LeafReaderContext ctx : reader.leaves()) { if (ctx.reader().getFieldInfos().fieldInfo(field) == null) { @@ -189,7 +189,7 @@ public static long globalMaxValue(IndexReader reader, String field) throws IOExc * @param reader the index reader to be queried * @param field the field to retrieve values for */ - public static int globalDocCount(IndexReader reader, String field) throws IOException { + public static int globalDocCount(IndexReader reader, String field) { int docCount = 0; for (LeafReaderContext ctx : reader.leaves()) { DocValuesSkipper skipper = ctx.reader().getDocValuesSkipper(field); diff --git a/lucene/core/src/java/org/apache/lucene/index/FilterLeafReader.java b/lucene/core/src/java/org/apache/lucene/index/FilterLeafReader.java index e11dd33c0d07..e571f890c321 100644 --- a/lucene/core/src/java/org/apache/lucene/index/FilterLeafReader.java +++ b/lucene/core/src/java/org/apache/lucene/index/FilterLeafReader.java @@ -451,7 +451,7 @@ public SortedSetDocValues getSortedSetDocValues(String field) throws IOException } @Override - public DocValuesSkipper getDocValuesSkipper(String field) throws IOException { + public DocValuesSkipper getDocValuesSkipper(String field) { ensureOpen(); return in.getDocValuesSkipper(field); } diff --git a/lucene/core/src/java/org/apache/lucene/index/LeafReader.java b/lucene/core/src/java/org/apache/lucene/index/LeafReader.java index 8e010151274e..91aecdeb2286 100644 --- a/lucene/core/src/java/org/apache/lucene/index/LeafReader.java +++ b/lucene/core/src/java/org/apache/lucene/index/LeafReader.java @@ -209,7 +209,7 @@ public final PostingsEnum postings(Term term) throws IOException { * interest, or {@code null} if a skip index was not indexed. The returned instance should be * confined to the thread that created it. */ - public abstract DocValuesSkipper getDocValuesSkipper(String field) throws IOException; + public abstract DocValuesSkipper getDocValuesSkipper(String field); /** * Returns {@link FloatVectorValues} for this field, or null if no {@link FloatVectorValues} were diff --git a/lucene/core/src/java/org/apache/lucene/index/ParallelLeafReader.java b/lucene/core/src/java/org/apache/lucene/index/ParallelLeafReader.java index 237347ae94d0..036a8fbdd15c 100644 --- a/lucene/core/src/java/org/apache/lucene/index/ParallelLeafReader.java +++ b/lucene/core/src/java/org/apache/lucene/index/ParallelLeafReader.java @@ -428,7 +428,7 @@ public SortedSetDocValues getSortedSetDocValues(String field) throws IOException } @Override - public DocValuesSkipper getDocValuesSkipper(String field) throws IOException { + public DocValuesSkipper getDocValuesSkipper(String field) { ensureOpen(); LeafReader reader = fieldToReader.get(field); return reader == null ? null : reader.getDocValuesSkipper(field); diff --git a/lucene/core/src/java/org/apache/lucene/index/SegmentDocValuesProducer.java b/lucene/core/src/java/org/apache/lucene/index/SegmentDocValuesProducer.java index 912f833eec71..542325eaefb1 100644 --- a/lucene/core/src/java/org/apache/lucene/index/SegmentDocValuesProducer.java +++ b/lucene/core/src/java/org/apache/lucene/index/SegmentDocValuesProducer.java @@ -124,7 +124,7 @@ public SortedSetDocValues getSortedSet(FieldInfo field) throws IOException { } @Override - public DocValuesSkipper getSkipper(FieldInfo field) throws IOException { + public DocValuesSkipper getSkipper(FieldInfo field) { DocValuesProducer dvProducer = dvProducersByField.get(field.number); assert dvProducer != null; return dvProducer.getSkipper(field); diff --git a/lucene/core/src/java/org/apache/lucene/index/SlowCodecReaderWrapper.java b/lucene/core/src/java/org/apache/lucene/index/SlowCodecReaderWrapper.java index 555f459b6990..8b09766bf0c4 100644 --- a/lucene/core/src/java/org/apache/lucene/index/SlowCodecReaderWrapper.java +++ b/lucene/core/src/java/org/apache/lucene/index/SlowCodecReaderWrapper.java @@ -264,7 +264,7 @@ public SortedSetDocValues getSortedSet(FieldInfo field) throws IOException { } @Override - public DocValuesSkipper getSkipper(FieldInfo field) throws IOException { + public DocValuesSkipper getSkipper(FieldInfo field) { return reader.getDocValuesSkipper(field.name); } diff --git a/lucene/core/src/java/org/apache/lucene/index/SlowCompositeCodecReaderWrapper.java b/lucene/core/src/java/org/apache/lucene/index/SlowCompositeCodecReaderWrapper.java index a7d8bf2abd78..455bebadfcbc 100644 --- a/lucene/core/src/java/org/apache/lucene/index/SlowCompositeCodecReaderWrapper.java +++ b/lucene/core/src/java/org/apache/lucene/index/SlowCompositeCodecReaderWrapper.java @@ -488,7 +488,7 @@ public SortedSetDocValues getSortedSet(FieldInfo field) throws IOException { } @Override - public DocValuesSkipper getSkipper(FieldInfo field) throws IOException { + public DocValuesSkipper getSkipper(FieldInfo field) { throw new UnsupportedOperationException("This method is for searching not for merging"); } } diff --git a/lucene/core/src/java/org/apache/lucene/index/SortingCodecReader.java b/lucene/core/src/java/org/apache/lucene/index/SortingCodecReader.java index 4cc052cea9f8..0f83a75ebfbd 100644 --- a/lucene/core/src/java/org/apache/lucene/index/SortingCodecReader.java +++ b/lucene/core/src/java/org/apache/lucene/index/SortingCodecReader.java @@ -711,7 +711,7 @@ public void close() throws IOException { } @Override - public DocValuesSkipper getSkipper(FieldInfo field) throws IOException { + public DocValuesSkipper getSkipper(FieldInfo field) { // We can hardly return information about min/max values if doc IDs have been reordered. return null; } diff --git a/lucene/core/src/java/org/apache/lucene/search/NumericFieldStats.java b/lucene/core/src/java/org/apache/lucene/search/NumericFieldStats.java index 1a529947b3f7..9fda8a02da79 100644 --- a/lucene/core/src/java/org/apache/lucene/search/NumericFieldStats.java +++ b/lucene/core/src/java/org/apache/lucene/search/NumericFieldStats.java @@ -74,7 +74,7 @@ private static Stats getStatsFromPoints(IndexReader reader, String field) throws return new Stats(decodeLong(minPacked), decodeLong(maxPacked), docCount); } - private static Stats getStatsFromSkipper(IndexReader reader, String field) throws IOException { + private static Stats getStatsFromSkipper(IndexReader reader, String field) { Long min = null; Long max = null; int docCount = 0; diff --git a/lucene/highlighter/src/java/org/apache/lucene/search/highlight/TermVectorLeafReader.java b/lucene/highlighter/src/java/org/apache/lucene/search/highlight/TermVectorLeafReader.java index 85aa566a646a..0a03a61f5b1d 100644 --- a/lucene/highlighter/src/java/org/apache/lucene/search/highlight/TermVectorLeafReader.java +++ b/lucene/highlighter/src/java/org/apache/lucene/search/highlight/TermVectorLeafReader.java @@ -146,7 +146,7 @@ public SortedSetDocValues getSortedSetDocValues(String field) throws IOException } @Override - public DocValuesSkipper getDocValuesSkipper(String field) throws IOException { + public DocValuesSkipper getDocValuesSkipper(String field) { return null; } diff --git a/lucene/memory/src/java/org/apache/lucene/index/memory/MemoryIndex.java b/lucene/memory/src/java/org/apache/lucene/index/memory/MemoryIndex.java index 44959bdd7a3b..91300cbce63d 100644 --- a/lucene/memory/src/java/org/apache/lucene/index/memory/MemoryIndex.java +++ b/lucene/memory/src/java/org/apache/lucene/index/memory/MemoryIndex.java @@ -1724,7 +1724,7 @@ public SortedSetDocValues getSortedSetDocValues(String field) { } @Override - public DocValuesSkipper getDocValuesSkipper(String field) throws IOException { + public DocValuesSkipper getDocValuesSkipper(String field) { // Skipping isn't needed on a 1-doc index. return null; } diff --git a/lucene/test-framework/src/java/org/apache/lucene/tests/codecs/asserting/AssertingDocValuesFormat.java b/lucene/test-framework/src/java/org/apache/lucene/tests/codecs/asserting/AssertingDocValuesFormat.java index de0c536d888e..d9903cb9197e 100644 --- a/lucene/test-framework/src/java/org/apache/lucene/tests/codecs/asserting/AssertingDocValuesFormat.java +++ b/lucene/test-framework/src/java/org/apache/lucene/tests/codecs/asserting/AssertingDocValuesFormat.java @@ -294,7 +294,7 @@ public SortedSetDocValues getSortedSet(FieldInfo field) throws IOException { } @Override - public DocValuesSkipper getSkipper(FieldInfo field) throws IOException { + public DocValuesSkipper getSkipper(FieldInfo field) { assert fieldInfos.fieldInfo(field.name).number == field.number; assert field.docValuesSkipIndexType() != DocValuesSkipIndexType.NONE; DocValuesSkipper skipper = in.getSkipper(field); diff --git a/lucene/test-framework/src/java/org/apache/lucene/tests/index/AssertingLeafReader.java b/lucene/test-framework/src/java/org/apache/lucene/tests/index/AssertingLeafReader.java index 37c6b7de5c0c..c27be2d830a0 100644 --- a/lucene/test-framework/src/java/org/apache/lucene/tests/index/AssertingLeafReader.java +++ b/lucene/test-framework/src/java/org/apache/lucene/tests/index/AssertingLeafReader.java @@ -1823,7 +1823,7 @@ public SortedSetDocValues getSortedSetDocValues(String field) throws IOException } @Override - public DocValuesSkipper getDocValuesSkipper(String field) throws IOException { + public DocValuesSkipper getDocValuesSkipper(String field) { DocValuesSkipper skipper = super.getDocValuesSkipper(field); FieldInfo fi = getFieldInfos().fieldInfo(field); if (skipper != null) { diff --git a/lucene/test-framework/src/java/org/apache/lucene/tests/index/MergeReaderWrapper.java b/lucene/test-framework/src/java/org/apache/lucene/tests/index/MergeReaderWrapper.java index be846b6c6544..3d2c802413d2 100644 --- a/lucene/test-framework/src/java/org/apache/lucene/tests/index/MergeReaderWrapper.java +++ b/lucene/test-framework/src/java/org/apache/lucene/tests/index/MergeReaderWrapper.java @@ -190,7 +190,7 @@ public NumericDocValues getNormValues(String field) throws IOException { } @Override - public DocValuesSkipper getDocValuesSkipper(String field) throws IOException { + public DocValuesSkipper getDocValuesSkipper(String field) { ensureOpen(); FieldInfo fi = getFieldInfos().fieldInfo(field); if (fi == null) { diff --git a/lucene/test-framework/src/java/org/apache/lucene/tests/index/MismatchedCodecReader.java b/lucene/test-framework/src/java/org/apache/lucene/tests/index/MismatchedCodecReader.java index 12709de0a807..bad686794757 100644 --- a/lucene/test-framework/src/java/org/apache/lucene/tests/index/MismatchedCodecReader.java +++ b/lucene/test-framework/src/java/org/apache/lucene/tests/index/MismatchedCodecReader.java @@ -162,7 +162,7 @@ public SortedSetDocValues getSortedSet(FieldInfo field) throws IOException { } @Override - public DocValuesSkipper getSkipper(FieldInfo field) throws IOException { + public DocValuesSkipper getSkipper(FieldInfo field) { return in.getSkipper(remapFieldInfo(field)); } diff --git a/lucene/test-framework/src/java/org/apache/lucene/tests/search/QueryUtils.java b/lucene/test-framework/src/java/org/apache/lucene/tests/search/QueryUtils.java index 737e4184386a..41d94746d0d6 100644 --- a/lucene/test-framework/src/java/org/apache/lucene/tests/search/QueryUtils.java +++ b/lucene/test-framework/src/java/org/apache/lucene/tests/search/QueryUtils.java @@ -228,7 +228,7 @@ public NumericDocValues getNormValues(String field) throws IOException { } @Override - public DocValuesSkipper getDocValuesSkipper(String field) throws IOException { + public DocValuesSkipper getDocValuesSkipper(String field) { return null; } From 3ffe2563c94c28a53a942c9dc57e6c121ce4e76c Mon Sep 17 00:00:00 2001 From: Alan Woodward Date: Tue, 14 Jul 2026 12:10:36 +0100 Subject: [PATCH 2/2] changes --- lucene/CHANGES.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt index a1896e142c93..cfb3f46e77b2 100644 --- a/lucene/CHANGES.txt +++ b/lucene/CHANGES.txt @@ -98,6 +98,11 @@ API Changes * GITHUB#16052: Remove IOException from LeafReader#getPointValues, LeafReader#terms, LeafReader#getDocCount signatures. (Ignacio Vera) +* GITHUB#16388: Remove IOException from LeafReader#getDocValuesSkipper signature. + (Alan Woodward) + +* GITHUB#16387: Don't throw IOException from PointValues metadata methods. (Alan Woodward) + * GITHUB#16281: The checkIntegrity() method of codec Producer APIs gains a OneMerge parameter, which is used to check for aborted merges during checksum validation. (Tanguy Leroux) @@ -313,8 +318,6 @@ API Changes * GITHUB#16363: Add API to use a custom `DictionarySuggester` instead of hardcoded `GeneratingSuggester` -* GITHUB#16387: Don't throw IOException from PointValues metadata methods. (Alan Woodward) - New Features ---------------------