Skip to content

Simplify ColumnReader SPI: drop iterator API, replace type checks with getValueType()#18918

Open
Jackie-Jiang wants to merge 1 commit into
apache:masterfrom
Jackie-Jiang:columnreader-cleanup
Open

Simplify ColumnReader SPI: drop iterator API, replace type checks with getValueType()#18918
Jackie-Jiang wants to merge 1 commit into
apache:masterfrom
Jackie-Jiang:columnreader-cleanup

Conversation

@Jackie-Jiang

@Jackie-Jiang Jackie-Jiang commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Cleans up the ColumnReader SPI (pinot-spi), used for column-major segment building, by removing a
redundant second read API and simplifying type introspection.

ColumnReader previously exposed two overlapping ways to read a column:

  • an iterator-style API — hasNext(), next(), isNextNull(), skipNext(), and the
    nextXxx() / nextXxxMV() type-specific methods, and
  • random access by document id — getXxx(docId) / getValue(docId).

Every production consumer can be expressed with random access alone, so this removes the iterator API
entirely. Both column-major consumers — SegmentColumnarIndexCreator.indexColumn(String, ColumnReader)
and ColumnarSegmentPreIndexStatsContainer.collectColumn(...) — now read via a
for (docId = 0; docId < getTotalDocs(); docId++) loop over getValue(docId), preserving the previous
null semantics.

The build traverses each column twice over the same cached reader — a statistics pass, then an
index-writing pass — so rewind() is retained (as default void rewind()), but with a narrower contract:
reset the reader to document 0 before a repeated forward traversal (it is no longer an iterator cursor
op). indexColumn(...) calls it before its pass; a random-access reader keeps the default no-op, while a
streaming/batched reader overrides it to reset its position.

It also replaces the boolean type-check methods (isSingleValue() + isInt()isBytes()) with a single
@Nullable PinotDataType getValueType() that encodes both value type and cardinality (e.g. INT vs
INT_ARRAY). It returns null when getValue(docId)'s logical type has no matching typed accessor —
BOOLEAN (a Boolean), TIMESTAMP (a Timestamp), and the complex types — which are read through
getValue(docId); a JSON column reports STRING, since it is stored and read as its text String. A
static ColumnReader.toValueType(DataType, boolean) helper centralizes the mapping for implementations
backed by a DataType, and DataTypeColumnTransformer.isNoOp() collapses to getValueType() == destType.

All implementations (PinotSegmentColumnReaderImpl, DefaultValueColumnReader, ArrowColumnReader,
BatchedArrowColumnReader) and their tests are migrated; the interface members are reordered and the
Javadoc rewritten for clarity.

Backward incompatibility

ColumnReader is a pinot-spi interface first shipped in 1.5.0. Removing methods from it is
binary-incompatible for any external plugin that implements or calls ColumnReader. It is a
recently-added, internal-facing SPI for the still-evolving column-major segment build, so the expected
blast radius is nil, but the PR is labeled backward-incompat accordingly.

Release note: the ColumnReader SPI no longer exposes the sequential/iterator read API
(hasNext/next/nextXxx/nextXxxMV/skipNext/isNextNull) or the boolean type-check methods
(isSingleValue/isInt/…/isBytes). Read column values by document id via getValue(docId) /
getXxx(docId), and use getValueType() to obtain the directly-readable value type and cardinality.
rewind() is retained as a default method that resets the reader to document 0 for a repeated forward
traversal.

@Jackie-Jiang Jackie-Jiang added release-notes Referenced by PRs that need attention when compiling the next release notes backward-incompat Introduces a backward-incompatible API or behavior change labels Jul 3, 2026
@Jackie-Jiang Jackie-Jiang requested review from Copilot and xiangfu0 July 3, 2026 18:46
@Jackie-Jiang Jackie-Jiang added ingestion Related to data ingestion pipeline cleanup Code cleanup or removal of dead code and removed release-notes Referenced by PRs that need attention when compiling the next release notes labels Jul 3, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR simplifies the ColumnReader SPI (in pinot-spi) by removing the sequential/iterator read API and boolean type-predicate methods, standardizing consumers on docId-based random access and a single @Nullable PinotDataType getValueType() for type introspection. This reduces SPI surface area and consolidates type/cardinality detection while keeping null semantics consistent for column-major segment building.

Changes:

  • Removed ColumnReader iterator-style methods (hasNext/next/isNextNull/skipNext/rewind + nextXxx/nextXxxMV) and boolean type checks, replacing them with getValueType() and docId-based reads.
  • Updated column-major consumers (SegmentColumnarIndexCreator, ColumnarSegmentPreIndexStatsContainer) to loop docId = 0..getTotalDocs() using getValue(docId) / typed accessors guarded by getValueType().
  • Migrated key implementations and tests (segment-local readers + Arrow readers) to the new SPI shape and updated test expectations accordingly.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
pinot-spi/src/main/java/org/apache/pinot/spi/data/readers/MultiValueResult.java Reformats documentation to align examples with docId-based access.
pinot-spi/src/main/java/org/apache/pinot/spi/data/readers/ColumnReader.java Removes iterator API + type predicates; adds getValueType() and toValueType(...) helper; rewrites contract docs.
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/readers/PinotSegmentColumnReaderImpl.java Drops iterator state; implements getValueType() + docId getValue.
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/readers/DefaultValueColumnReader.java Drops iterator state; implements getValueType() + docId getValue.
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/SegmentColumnarIndexCreator.java Switches column-major indexing loop to docId-based reads; preserves typed fast path via getValueType().
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/stats/ColumnarSegmentPreIndexStatsContainer.java Switches stats collection to docId-based reads; retains typed primitive fast path via getValueType().
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/columntransformer/DataTypeColumnTransformer.java Simplifies isNoOp() to reader.getValueType() == destType.
pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/readers/PinotSegmentColumnReaderImplTest.java Removes sequential-API assertions; validates getValueType() and docId-based consistency (formatting issues noted).
pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/readers/InMemoryColumnReader.java Updates test reader to implement getValueType() and docId-based access only.
pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/readers/DefaultValueColumnReaderTest.java Updates tests to docId-based reads; adds getValueType() checks (doc/comment nits noted).
pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/creator/impl/ColumnReaderInterfaceTest.java Updates factory/contract tests to docId-based reads.
pinot-segment-local/src/test/java/org/apache/pinot/segment/local/columntransformer/DataTypeColumnTransformerTest.java Updates mocks/tests to use getValueType() rather than boolean type predicates.
pinot-plugins/pinot-input-format/pinot-arrow/src/main/java/org/apache/pinot/plugin/inputformat/arrow/ArrowColumnReader.java Drops iterator API; adds getValueType(); keeps docId reads; doc/annotation mismatch noted.
pinot-plugins/pinot-input-format/pinot-arrow/src/main/java/org/apache/pinot/plugin/inputformat/arrow/BatchedArrowColumnReader.java Drops iterator API; adds getValueType(); doc/annotation mismatch noted.
pinot-plugins/pinot-input-format/pinot-arrow/src/test/java/org/apache/pinot/plugin/inputformat/arrow/ArrowFileColumnReaderFactoryTest.java Updates Arrow tests to docId-based reads + getValueType() expectations.
pinot-plugins/pinot-input-format/pinot-arrow/src/test/java/org/apache/pinot/plugin/inputformat/arrow/ArrowColumnReaderFactoryTest.java Removes sequential-API past-end behavior tests; updates remaining assertions to getValueType().

@Jackie-Jiang Jackie-Jiang force-pushed the columnreader-cleanup branch from 12e2420 to 1358cbc Compare July 3, 2026 18:55
@codecov-commenter

codecov-commenter commented Jul 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 72.72727% with 24 lines in your changes missing coverage. Please review.
✅ Project coverage is 64.84%. Comparing base (9ceca0f) to head (368838c).

Files with missing lines Patch % Lines
...l/stats/ColumnarSegmentPreIndexStatsContainer.java 50.00% 5 Missing and 8 partials ⚠️
...ment/creator/impl/SegmentColumnarIndexCreator.java 59.09% 1 Missing and 8 partials ⚠️
.../segment/readers/PinotSegmentColumnReaderImpl.java 60.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master   #18918      +/-   ##
============================================
+ Coverage     64.81%   64.84%   +0.03%     
  Complexity     1347     1347              
============================================
  Files          3396     3397       +1     
  Lines        212503   212358     -145     
  Branches      33484    33469      -15     
============================================
- Hits         137732   137707      -25     
+ Misses        63610    63524      -86     
+ Partials      11161    11127      -34     
Flag Coverage Δ
custom-integration1 100.00% <ø> (ø)
integration 100.00% <ø> (ø)
integration1 100.00% <ø> (ø)
integration2 0.00% <ø> (ø)
java-21 64.84% <72.72%> (+0.03%) ⬆️
temurin 64.84% <72.72%> (+0.03%) ⬆️
unittests 64.84% <72.72%> (+0.03%) ⬆️
unittests1 56.86% <26.13%> (+0.08%) ⬆️
unittests2 37.22% <50.00%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.

Comment thread pinot-spi/src/main/java/org/apache/pinot/spi/data/readers/ColumnReader.java Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated 3 comments.

Comment thread pinot-spi/src/main/java/org/apache/pinot/spi/data/readers/ColumnReader.java Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated 2 comments.

Comment on lines +64 to +72
/// Returns the [PinotDataType] that a column with the given Arrow element [Types.MinorType] can be read as through a
/// type-specific `ColumnReader` accessor, or `null` when it has no dedicated accessor and must be read through
/// `ColumnReader.getValue()`. `singleValue` selects the scalar vs `_ARRAY` variant.
///
/// The `MinorType` already encodes every distinction the accessors care about, so a plain switch suffices: only
/// signed 32/64-bit ints (`INT` / `BIGINT`), single/double floats, 128-bit `DECIMAL`, `VARCHAR`, and `VARBINARY`
/// have accessors. Everything else — unsigned/narrow ints, half floats, `DECIMAL256`, `LARGEVARCHAR` /
/// `LARGEVARBINARY` / fixed-size binary, `BIT` (boolean), temporal, and complex types — is a distinct `MinorType`
/// that falls through to `null` and is read via `getValue()`.
Comment on lines +87 to +90
@Override
public Object getValue(int docId) {
return _values[docId];
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backward-incompat Introduces a backward-incompatible API or behavior change cleanup Code cleanup or removal of dead code ingestion Related to data ingestion pipeline

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants