Skip to content

Expose common segment metadata as built-in virtual columns - #19179

Open
xiangfu0 wants to merge 3 commits into
apache:masterfrom
xiangfu0:xiangfu0/segment-metadata-virtual-columns-e22d12
Open

Expose common segment metadata as built-in virtual columns#19179
xiangfu0 wants to merge 3 commits into
apache:masterfrom
xiangfu0:xiangfu0/segment-metadata-virtual-columns-e22d12

Conversation

@xiangfu0

@xiangfu0 xiangfu0 commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds five built-in virtual columns that expose common segment metadata to queries, alongside the existing $docId / $hostName / $segmentName / $partitionId.

Column Type Value
$creationTime TIMESTAMP segment creation time
$startTime TIMESTAMP segment time-range start, normalized from the time column's own unit
$endTime TIMESTAMP segment time-range end
$totalDocs INT documents stored in the segment (documents indexed so far while CONSUMING)
$crc LONG segment CRC
SELECT $segmentName, $crc, $totalDocs, $creationTime
FROM myTable GROUP BY 1, 2, 3, 4

This makes a number of operational questions answerable directly in SQL — spotting replicas of a segment whose CRC has diverged, finding segments created before a rollout, or looking at per-segment document skew — without walking the segment metadata REST API.

Like the existing built-ins, these are excluded from SELECT * and only materialize when named explicitly.

Behavior

Each column is a constant single-value column within a segment. The value is read from SegmentMetadata when the column is built rather than baked into the field spec, so a mutable segment — which rebuilds its virtual data sources on every access — picks up metadata that was not yet available when it was created. Within one build the value is resolved exactly once, so the dictionary, the column metadata and the null value vector can never disagree.

Metadata that genuinely does not exist yet reads as SQL NULL, not a sentinel. A CONSUMING segment has no time range and no CRC until it is committed, and a table without a time column never has a time range. This required teaching the virtual column path to carry a null value vector: VirtualColumnIndexContainer now serves StandardIndexes.nullValueVector(), and VirtualColumnProvider gained a buildNullValueVector hook that defaults to "no nulls", so existing providers are unaffected. Without it the placeholder in the forward index would be indistinguishable from a real value once null handling is enabled — e.g. MIN($startTime) returning the epoch on a consuming segment, and IS NOT NULL matching every row.

The time columns are TIMESTAMP rather than LONG so the unit is carried by the type rather than by the column name, and results render as readable timestamps.

$totalDocs counts the documents physically stored in the segment, so for an upsert table it also includes documents that have been replaced and are no longer returned by queries. On a hybrid table the broker's time boundary can likewise hide rows that are present in the segment.

Supporting changes

  • New BuiltInVirtualColumnDefinitions in pinot-spi is the single source of each column's name, data type, and single-value/multi-value shape. The broker side (TableCache#addBuiltInVirtualColumns) and the server side (VirtualColumnProviderFactory#addBuiltInVirtualColumnsToSegmentSchema) both build their field specs from it, so the two can no longer disagree on a type. VirtualColumnProviderFactory resolves providers through a map whose key set is checked against the definitions in a static initializer, so a column added without a provider fails at class load rather than aborting every segment load on every server.

  • The constant-value machinery moves out of DefaultNullValueVirtualColumnProvider into BaseConstantValueVirtualColumnProvider; the former becomes a thin subclass. Its fully-qualified name is stored in field specs and resolved reflectively, so it keeps its name and package. It also gained a type check that names the offending column and provider instead of throwing a bare ClassCastException from inside segment loading.

  • Fixes a pre-existing bug in SchemaInforeviewable independently of the feature; happy to split it out if preferred, which computed getDimensionFieldSpecs().size() - 3 with a comment naming three virtual columns. That has been off by one since $partitionId was added, and these five columns would have made GET /schemas/info over-report user dimension counts by six in the controller UI. It now excludes built-in virtual columns by name, with a test pinning the invariant.

Note for reviewers: EXPLAIN assertions shift

Calcite plans reference fields positionally, and all $-prefixed columns sort first in a table's row type — so every added built-in virtual column shifts every table ordinal in every EXPLAIN assertion. Five new columns means +5 in NullHandlingIntegrationTest, MultiStageEngineExplainIntegrationTest and OfflineClusterIntegrationTest (and MultiNodesOfflineClusterIntegrationTest, which inherits). The $partitionId PR paid the same tax. Similarly the aggregate metadata API reports one entry per column of a loaded segment, so its expected count goes from 83 to 88.

SPI surface

pinot-spi gains one public class (BuiltInVirtualColumnDefinitions) and five public constants, and public BUILT_IN_VIRTUAL_COLUMNS grows from 4 to 9 entries. The addition is source- and binary-compatible, but that set is a filter predicate rather than an inert constant: its only non-test consumer outside this diff is SegmentMetadataImpl#addPhysicalColumns, which drops any name in the set from a segment's physical column list. Downstream/plugin code branching on the set inherits the same change.

Backward compatibility / rolling upgrade

The change is additive: no wire format, segment format, or ZK format change, and SELECT * is unaffected in both engines because $-prefixed columns are excluded from star expansion.

Broker-side and server-side schemas are updated in the same commit but roll out independently. Upgrade servers before brokers: an upgraded broker advertises the new columns, so a query naming one during the upgrade window returns partial results plus per-segment exceptions from servers still on the old build. No existing query is affected — only queries that opt into the new columns.

Two REST responses change content:

  • GET /tables/{table}/metadata gains 5 entries per table in columnLengthMap / columnCardinalityMap (the expected count in OfflineClusterIntegrationTest moves from 83 to 88). Loaded segments carry virtual columns in their column metadata, as they already did for the existing four.
  • GET /schemas/info numDimensionFields decreases by one for every existing schema. The old size() - 3 had been over-reporting by one ever since $partitionId was added; the new value is the true user-dimension count. Operators tracking that field will see a one-off shift.

SchemaUtils.validate now rejects a user column named after a built-in virtual column. Such a schema is already broken today (the column is excluded from SELECT * and, once the name is reserved, shadowed by the virtual column), but this is a new validation failure for anyone who has one.

Null semantics and segment pruning

A column whose metadata is unavailable stores a placeholder and reports every document as null. It deliberately publishes no ColumnMetadata min/max: segment pruners read min/max without consulting the null value vector, so publishing the placeholder would let an all-null column reorder and prune segments as if it held a real extreme value — e.g. a CONSUMING segment's epoch $creationTime sorting first in ORDER BY $creationTime ASC LIMIT n and pruning away the committed segments that hold the answer. Both SelectionQuerySegmentPruner and ColumnValueSegmentPruner keep a segment that reports no min/max.

Cost

Each column adds a constant dictionary, readers and column metadata per immutable segment — roughly 1–2 KB per segment across the five, so on the order of 35 MB on a server holding 20k segments. There is no config gate, matching the existing four built-ins.

Testing

  • SegmentMetadataVirtualColumnProviderTest — values, column metadata, and the NULL path for both a missing SegmentMetadata and a real consuming-segment SegmentMetadataImpl, across the several representations of an unset creation time, plus a negative test for the value type check.
  • BuiltInVirtualColumnDefinitionsTest — pins the definitions against the declared names, and that field specs are never shared between schemas.
  • LoaderTest iterates the full built-in set and asserts both the value path and the NULL path on a really-loaded segment.
  • MutableSegmentImplTest pins the CONSUMING-segment shape.
  • OfflineClusterIntegrationTest covers both query engines: data schema, per-segment $totalDocs summing to the table row count, millisecond normalization per segment checked against DaysSinceEpoch * 86_400_000, filtering by $crc, and enableNullHandling=true seeing no nulls where all metadata is available.
  • BaseClusterIntegrationTestSet (via HybridClusterIntegrationTest) asserts results on the CONSUMING path instead of only checking that nothing throws.
  • Full clean test across pinot-spipinot-commonpinot-segment-localpinot-corepinot-query-planner, plus every integration test class touched above. All green locally.

Docs

The virtual-columns page in pinot-docs needs a matching update for these five columns, the TIMESTAMP types, and the NULL-on-unavailable behavior.

Labels

feature, release-notes

@xiangfu0
xiangfu0 force-pushed the xiangfu0/segment-metadata-virtual-columns-e22d12 branch from 4084149 to bad5922 Compare August 7, 2026 05:55
@codecov-commenter

codecov-commenter commented Aug 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (3ffb614) to head (834a096).

Additional details and impacted files
@@              Coverage Diff               @@
##             master    #19179       +/-   ##
==============================================
+ Coverage     66.63%   100.00%   +33.36%     
+ Complexity     1423         6     -1417     
==============================================
  Files          3443         3     -3440     
  Lines        218663         6   -218657     
  Branches      34801         0    -34801     
==============================================
- Hits         145705         6   -145699     
+ Misses        61230         0    -61230     
+ Partials      11728         0    -11728     
Flag Coverage Δ
integration 100.00% <ø> (ø)
integration1 100.00% <ø> (ø)
integration2 0.00% <ø> (ø)
java-25 100.00% <ø> (+33.36%) ⬆️
lane-a 100.00% <ø> (ø)
lane-b 0.00% <ø> (ø)
temurin 100.00% <ø> (+33.36%) ⬆️
unittests ?
unittests1 ?
unittests2 ?

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.

@xiangfu0
xiangfu0 requested review from Jackie-Jiang and yashmayya and a lite review from Copilot August 8, 2026 07:32

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 expands Pinot’s built-in virtual column surface by adding five new $-prefixed, segment-scoped virtual columns that expose common SegmentMetadata via SQL, while ensuring correct NULL semantics when metadata is genuinely unavailable (e.g., CONSUMING segments).

Changes:

  • Introduces $creationTime, $startTime, $endTime, $totalDocs, and $crc as built-in virtual columns (excluded from SELECT *, materialized only when explicitly selected).
  • Centralizes built-in virtual column definitions (name + type + SV/MV shape) in pinot-spi and reuses them on both broker/controller and server paths to prevent type drift.
  • Extends the virtual column implementation to support null value vectors, and updates unit/integration tests and explain-plan ordinals accordingly.

Reviewed changes

Copilot reviewed 27 out of 27 changed files in this pull request and generated no comments.

Show a summary per file
File Description
pinot-spi/src/test/java/org/apache/pinot/spi/data/SchemaInfoTest.java Adds a regression test ensuring built-in virtual columns are excluded from user dimension counts.
pinot-spi/src/test/java/org/apache/pinot/spi/data/BuiltInVirtualColumnDefinitionsTest.java New tests pinning definitions ↔ declared-name set equality, $ prefixing, idempotency, and non-overwrite behavior.
pinot-spi/src/main/java/org/apache/pinot/spi/utils/CommonConstants.java Adds constants for the five new built-in virtual columns and expands BUILT_IN_VIRTUAL_COLUMNS.
pinot-spi/src/main/java/org/apache/pinot/spi/data/SchemaInfo.java Fixes dimension counting to exclude built-in virtual columns by name (instead of -3).
pinot-spi/src/main/java/org/apache/pinot/spi/data/BuiltInVirtualColumnDefinitions.java New SPI “single source of truth” for built-in virtual column field spec shape and schema injection helper.
pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/virtualcolumn/SegmentMetadataVirtualColumnProviderTest.java New unit tests covering value extraction, metadata consistency, and NULL vector behavior for segment-metadata columns.
pinot-segment-local/src/test/java/org/apache/pinot/segment/local/segment/index/loader/LoaderTest.java Extends loader assertions to cover the full built-in set and validates NULL-vector behavior for time-range columns.
pinot-segment-local/src/test/java/org/apache/pinot/segment/local/indexsegment/mutable/MutableSegmentImplTest.java Adds explicit assertions for CONSUMING-like mutable segment behavior for the new metadata columns and adjusts comparison exclusions.
pinot-segment-local/src/test/java/org/apache/pinot/segment/local/indexsegment/mutable/MutableSegmentImplRawMVTest.java Mirrors mutable/immutable comparison exclusions for segment-level metadata columns in raw MV tests.
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/virtualcolumn/VirtualColumnProviderFactory.java Refactors built-in virtual column schema injection to be definition-driven and adds provider coverage validation.
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/virtualcolumn/VirtualColumnProvider.java Adds an overridable buildNullValueVector() hook and wires it into virtual column index container construction.
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/virtualcolumn/VirtualColumnIndexContainer.java Extends the container to serve StandardIndexes.nullValueVector() and closes it when present.
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/virtualcolumn/SegmentTotalDocsVirtualColumnProvider.java New provider implementing $totalDocs as a constant derived from context doc count.
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/virtualcolumn/SegmentStartTimeVirtualColumnProvider.java New provider implementing $startTime from SegmentMetadata#getTimeInterval() with NULL when absent.
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/virtualcolumn/SegmentEndTimeVirtualColumnProvider.java New provider implementing $endTime from SegmentMetadata#getTimeInterval() with NULL when absent.
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/virtualcolumn/SegmentCreationTimeVirtualColumnProvider.java New provider implementing $creationTime with unset creation-time detection.
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/virtualcolumn/SegmentCrcVirtualColumnProvider.java New provider implementing $crc with unset CRC detection.
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/virtualcolumn/BaseSegmentMetadataVirtualColumnProvider.java New base provider adding “value or placeholder + all-null vector” behavior for unavailable segment metadata.
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/virtualcolumn/BaseConstantValueVirtualColumnProvider.java New reusable base for constant-value virtual columns with consistent dictionary/forward/inverted/metadata building and type checks.
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/column/DefaultNullValueVirtualColumnProvider.java Refactors existing provider to subclass the new constant-value base while preserving FQCN stability.
pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/OfflineClusterIntegrationTest.java Adds end-to-end validation of the new columns (types, normalization, filtering, null-handling semantics) and updates explain/metadata counts.
pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/NullHandlingIntegrationTest.java Updates explain-plan ordinal assertions (+5 shift) to account for the new $-prefixed columns.
pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/MultiStageEngineExplainIntegrationTest.java Updates explain-plan ordinal assertions (+5 shift) for MSE plans.
pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/BaseClusterIntegrationTestSet.java Adds hybrid-table assertions for $totalDocs, $crc, and $creationTime across potentially CONSUMING segments.
pinot-controller/src/test/java/org/apache/pinot/controller/helix/TableCacheTest.java Updates expected schema and column-name maps to include the new built-ins.
pinot-controller/src/test/java/org/apache/pinot/controller/api/resources/PinotQueryResourceStaticValidationTest.java Makes built-in virtual column count assertions size-driven rather than hard-coded.
pinot-common/src/main/java/org/apache/pinot/common/config/provider/TableCache.java Switches broker/controller built-in injection to use BuiltInVirtualColumnDefinitions.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@xiangfu0
xiangfu0 force-pushed the xiangfu0/segment-metadata-virtual-columns-e22d12 branch 2 times, most recently from 195314c to a3ab079 Compare August 8, 2026 23:02
Adds five built-in virtual columns that surface segment metadata to
queries, alongside the existing $docId / $hostName / $segmentName /
$partitionId:

| Column          | Type   | Value                                        |
|-----------------|--------|----------------------------------------------|
| $creationTime   | LONG   | segment creation time, epoch millis          |
| $startTimeMs    | LONG   | segment time range start, epoch millis       |
| $endTimeMs      | LONG   | segment time range end, epoch millis         |
| $totalDocs      | INT    | documents stored in the segment              |
| $segmentCrc     | STRING | segment CRC                                  |

Example:

  SELECT $segmentName, $segmentCrc, $totalDocs, $creationTime
  FROM myTable GROUP BY 1, 2, 3, 4

Each column is a constant single-value column within a segment. Values
are read from SegmentMetadata every time the column is built rather than
baked into the field spec, so mutable segments - which rebuild their
virtual data sources on every access - always observe current metadata.

Metadata that genuinely does not exist yet reads as SQL NULL rather than
a sentinel: a CONSUMING segment has no time range and no CRC until it is
committed. This required teaching the virtual column path to carry a null
value vector - VirtualColumnIndexContainer now serves
StandardIndexes.nullValueVector() and VirtualColumnProvider gained a
buildNullValueVector hook (defaulting to no nulls, so existing providers
are unaffected). Without it the placeholder stored in the forward index
would be indistinguishable from a real value once null handling is on,
e.g. MIN($startTimeMs) returning Long.MIN_VALUE on a consuming segment.

$totalDocs counts the documents physically stored in the segment, so for
an upsert table it also includes documents that have been replaced and
are no longer returned by queries.

Naming note: only the time range columns carry the "Ms" suffix.
SegmentMetadata#getStartTime()/#getEndTime() return values in the time
column's own unit, so unsuffixed names would be ambiguous; a creation
time is always epoch millis throughout Pinot.

Supporting changes:

- New BuiltInVirtualColumns in pinot-spi is the single source of each
  column's name, data type and single-value/multi-value shape. The broker
  side (TableCache#addBuiltInVirtualColumns) and the server side
  (VirtualColumnProviderFactory#addBuiltInVirtualColumnsToSegmentSchema)
  now both build their field specs from it, so the two can no longer
  disagree on a type. Previously these specs were declared twice by hand.

- DefaultNullValueVirtualColumnProvider grew an overridable
  getValue(context) so it can back any per-segment constant column, plus
  a type check that names the offending column and provider instead of
  throwing a bare ClassCastException from inside segment loading.
  Behavior is unchanged for existing callers.

- Fixes a pre-existing bug in SchemaInfo, which computed
  getDimensionFieldSpecs().size() - 3 with a comment naming three virtual
  columns. That has been off by one since $partitionId was added, and
  these five would have made GET /schemas/info over-report user dimension
  counts by six in the controller UI. It now excludes built-in virtual
  columns by name.
Follow-up to the segment metadata virtual columns, covering the review
comments and the CI failures on the first revision.

Column types and names
----------------------
The three time columns are now TIMESTAMP rather than LONG, so the unit is
carried by the type instead of by the column name and query results render
them as readable timestamps instead of raw millis. That removes the reason
the "Ms" suffix existed, so the columns are back to their natural names:

  $creationTime  TIMESTAMP
  $startTime     TIMESTAMP
  $endTime       TIMESTAMP
  $totalDocs     INT
  $crc           STRING

$segmentCrc is renamed to $crc so that the five names are consistent: none
of them repeats the "segment" scope, which every one of them shares.

Correctness
-----------
The segment metadata used to be read three separate times per build - once
for the dictionary, once for the column metadata, and once for the null
value vector - with nothing tying the three reads together. On a mutable
segment, whose metadata is live, they could disagree and the column would
then serve its placeholder as if it were a real value. The value is now
resolved exactly once per build and shared by all three.

VirtualColumnIndexContainer#close now also closes the null value vector.

Structure
---------
- The constant-value machinery moves out of DefaultNullValueVirtualColumnProvider
  into a new BaseConstantValueVirtualColumnProvider in the virtualcolumn
  package. DefaultNullValueVirtualColumnProvider becomes a thin subclass; its
  fully-qualified name is stored in field specs and resolved reflectively, so
  it keeps its name and package.
- VirtualColumnProviderFactory resolves providers through a map whose key set
  is checked against the column definitions in a static initializer. A column
  added without a provider now fails at class load instead of aborting every
  segment load on every server.
- BuiltInVirtualColumns is renamed to BuiltInVirtualColumnDefinitions: the old
  name differed from the pre-existing BuiltInVirtualColumn by a single trailing
  letter, and VirtualColumnProviderFactory imports both.

Test fixes
----------
Calcite plans reference fields positionally, and all $-prefixed columns sort
first in a table's row type, so five new virtual columns shift every ordinal
in an EXPLAIN assertion by five. Updated in NullHandlingIntegrationTest,
MultiStageEngineExplainIntegrationTest and OfflineClusterIntegrationTest.
MultiNodesOfflineClusterIntegrationTest inherits the latter's fixes.

The aggregate metadata API reports one entry per column of a loaded segment,
which includes the virtual columns, so its expected count grows from 83 to 88.

Test coverage
-------------
- BuiltInVirtualColumnDefinitionsTest pins the definitions against the
  declared names, and pins that field specs are never shared between schemas.
- A negative test covers the value type check.
- The CONSUMING-segment queries in BaseClusterIntegrationTestSet now assert
  their results instead of only checking that nothing throws. On a hybrid
  table the time boundary can hide rows that are physically present in a
  segment, so the assertion is that a segment's $totalDocs is at least the
  number of rows the query returns from it.
- OfflineClusterIntegrationTest additionally checks the millisecond
  normalization per segment, filtering by $crc, and that null handling sees
  no nulls on a table where all the metadata is available.
Fixes found by review on top of the segment metadata virtual columns.

Do not publish min/max for a column that reads as NULL
------------------------------------------------------
When the segment metadata is unavailable the column stores a placeholder
and reports every document as null, but it was still publishing that
placeholder as the column's min/max. Segment pruners read min/max without
consulting the null value vector, so:

  SET enableNullHandling=true;
  SELECT $segmentName, $creationTime FROM myTable ORDER BY $creationTime ASC LIMIT 10

let a CONSUMING segment - whose $creationTime placeholder is the epoch -
sort first in SelectionQuerySegmentPruner, consume the whole LIMIT, and
prune away every committed segment whose min was greater. The query then
returned rows that are all SQL NULL, which under NULLS-LAST ordering should
have sorted last. $crc had the mirror problem for ORDER BY ... DESC.

The min/max are now left unset whenever the value is a placeholder. Both
SelectionQuerySegmentPruner and ColumnValueSegmentPruner already keep a
segment that reports no min/max, which is the correct conservative
behavior for an all-null column.

Resolve the metadata once per data source, not once per index
-------------------------------------------------------------
buildDataSource calls buildMetadata and buildColumnIndexContainer
separately, so the column metadata came from a different read of
SegmentMetadata than the dictionary and the null value vector. Only the
container was internally consistent. BaseSegmentMetadataVirtualColumnProvider
now overrides buildDataSource and resolves the value once for all three.

Apply the value type check on the path that is actually taken
------------------------------------------------------------
The check was only reachable through the no-argument buildDictionary /
buildMetadata. Every one of the five new providers goes through the
value-taking overloads, which skipped it, so the diagnostic it exists to
give never applied to them. The check now runs on every path.

$crc is a LONG
--------------
A CRC is a long everywhere else in Pinot - SegmentZKMetadata#getCrc returns
one, and SegmentMetadata#getCrc merely renders that long as a String. Typing
the column STRING to match the latter left users unable to write
WHERE $crc = 12345, and contradicted the reasoning used for the time
columns, which are TIMESTAMP precisely so the type carries the semantics.

Other review fixes
------------------
- AllNullValueVector was a byte-for-byte duplicate of a nested class in
  OpenStructNullDataSource. Both now share
  AllNullValueVectorReader in the readers package.
- SchemaUtils.validate rejects a user column named after a built-in virtual
  column. Such a column is filtered out of the segment's physical columns
  when the metadata is read, and the virtual provider then takes over the
  name, so queries would silently return segment metadata instead of the
  user's data.
- The built-in providers are stateless, so they are now shared instances
  rather than reflectively constructed once per virtual column per segment.
  Segment load did that 9 times per segment after this feature, up from 4.
- Removed the static initializer asserting provider coverage. The factory is
  only ever loaded during segment load, so it did not fail any earlier than
  the per-column check, and it turned every subsequent load into a bare
  NoClassDefFoundError. The invariant is now asserted in a test.
- Corrected the AllNullValueVectorReader publication comment:
  toImmutableRoaringBitmap() returns `this`, so safety rests on the volatile
  write alone and the returned bitmap must be treated as read-only.
- Imported SegmentMetadata rather than naming it inline in Javadoc.

Naming note: the reviewers proposed prefixing all five columns with
"segment" ($segmentCreationTime, ...) to match $segmentName. The terse forms
are a deliberate choice, recorded here so it is not re-litigated as an
oversight.
@xiangfu0
xiangfu0 force-pushed the xiangfu0/segment-metadata-virtual-columns-e22d12 branch from a3ab079 to 834a096 Compare August 9, 2026 09:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants