Skip to content

Generate Avro schemas from Pinot logical data types (#19071)#19073

Open
xiangfu0 wants to merge 1 commit into
apache:masterfrom
xiangfu0:claude/pinot-issue-19071-6e1b3f
Open

Generate Avro schemas from Pinot logical data types (#19071)#19073
xiangfu0 wants to merge 1 commit into
apache:masterfrom
xiangfu0:claude/pinot-issue-19071-6e1b3f

Conversation

@xiangfu0

Copy link
Copy Markdown
Contributor

What

Resolves #19071.

AvroUtils.getAvroSchemaFromPinotSchema and SegmentProcessorAvroUtils.convertPinotSchemaToAvroSchema switched on fieldSpec.getDataType().getStoredType(), so the generated Avro schema described Pinot's physical storage rather than its logical type:

  • BOOLEAN was emitted as Avro int
  • TIMESTAMP was emitted as a bare long (no logical type)
  • UUID needed a one-off special case
  • BIG_DECIMAL fell through as unsupported

Change

Drive schema generation on the original (logical) DataType via a single shared mapping, AvroSchemaUtil.toAvroSchema(DataType/FieldSpec):

Pinot type Avro type Writer value
BOOLEAN boolean Boolean (coerced from stored int 0/1)
TIMESTAMP long{logicalType:timestamp-millis} Long epoch millis
BIG_DECIMAL bytes{logicalType:big-decimal} BigDecimal via BigDecimalConversion
UUID string{logicalType:uuid} 16-byte byte[] via UuidConversion

INT/LONG/FLOAT/DOUBLE/STRING/JSON/BYTES are unchanged. AvroUtils.getAvroSchemaFromPinotSchema and the AvroSchemaUtil.toAvroSchemaJsonObject JSON view now delegate to this one mapping, dropping the previous one-off UUID branches.

Coordinate the writer/value side to match:

  • Register Avro's BigDecimalConversion alongside the existing UuidConversion on the shared data model (SegmentProcessorAvroUtils.getAvroDataModel).
  • Make convertGenericRowToAvroRecord schema-driven: coerce Pinot's stored int 0/1 to Boolean for BOOLEAN fields and wrap byte[] as ByteBuffer for BYTES, while leaving values a registered Conversion owns (UUID byte[], BigDecimal) untouched. MV values keep a zero-copy view unless their element type actually needs a transform.

SegmentProcessorAvroUtils.convertPinotSchemaToAvroSchema keeps a private copy of the mapping because pinot-core cannot depend on the pinot-avro-base plugin (and vice-versa); a cross-module test pins the two together so they can't drift.

Compatibility

  • Reverse mapping (AvroSchemaUtil.valueOf(Schema)) is deliberately left one-way, preserving existing Pinot schema inference from Avro data.
  • None of these schemas flow to ZK, segment metadata, or inter-node wire protocols — the affected Avro/Parquet outputs are transient intra-process buffers or self-describing export files, so there is no rolling-upgrade concern.
  • Output-format note for downstream consumers: the segment -> Avro / segment -> Parquet export tools now emit boolean (was int) for BOOLEAN and timestamp-millis (was a bare long) for TIMESTAMP. Both stay self-describing and decodable.
  • The controller recommender's AvroWriter is intentionally not touched: its data generator cannot produce BIG_DECIMAL values, so wiring the conversion there would be untested/unreachable. The anonymizer's separate getAvroSchemaFromPinotSchema is likewise out of scope — it has its own value-generation model and fails loudly rather than silently mis-mapping.

Tests

  • Pinot-schema-to-Avro-schema assertions for every logical type, single-value and multi-value (AvroSchemaUtilTest, AvroUtilsTest, SegmentProcessorAvroUtilsTest).
  • Cross-module consistency test pinning SegmentProcessorAvroUtils's mapping to AvroUtils's.
  • End-to-end write/read round-trips through the shared data model and the production AvroRecordReader, exercising the same conversions as the segment-processing writers (SegmentProcessorAvroUtilsTest), plus a full segment-build round-trip (FileBasedSegmentWriterTest) and the Avro + Parquet converters incl. AvroParquetWriter.withDataModel (PinotSegmentConverterTest). BigDecimal scale preservation and MV byte[] element-wise equality are asserted.

AvroUtils.getAvroSchemaFromPinotSchema and
SegmentProcessorAvroUtils.convertPinotSchemaToAvroSchema switched on
fieldSpec.getDataType().getStoredType(), so the generated Avro schema
described Pinot's physical storage rather than its logical type: BOOLEAN
was emitted as Avro int, TIMESTAMP as a bare long, UUID needed a one-off
special case, and BIG_DECIMAL fell through as unsupported.

Drive schema generation on the original (logical) DataType via a single
shared mapping, AvroSchemaUtil.toAvroSchema(DataType/FieldSpec):
  BOOLEAN     -> boolean
  TIMESTAMP   -> long{logicalType:timestamp-millis}
  BIG_DECIMAL -> bytes{logicalType:big-decimal}
  UUID        -> string{logicalType:uuid}
with the rest unchanged. AvroUtils and the AvroSchemaUtil JSON view now
delegate to it, dropping the one-off UUID branches.

Coordinate the writer/value side to match: register Avro's
BigDecimalConversion alongside the existing UuidConversion on the shared
data model (getAvroDataModel), and make convertGenericRowToAvroRecord
schema-driven -- coerce Pinot's stored int 0/1 to Boolean for BOOLEAN
fields and wrap byte[] as ByteBuffer for BYTES, while leaving values a
registered Conversion owns (UUID byte[], BigDecimal) untouched. MV values
keep a zero-copy view unless their element type actually needs a
transform.

SegmentProcessorAvroUtils.convertPinotSchemaToAvroSchema keeps a private
copy of the mapping because pinot-core cannot depend on the pinot-avro-base
plugin (and vice versa); a cross-module test pins the two together.

The recommender's AvroWriter is intentionally left as-is: its data
generator cannot produce BIG_DECIMAL values, so registering the
conversion there would be untested/unreachable code.

Adds Pinot-schema-to-Avro-schema assertions for every logical type
(SV and MV) and end-to-end write/read round-trips through the shared data
model and the production AvroRecordReader / segment build path.

Note: the segment->Avro and segment->Parquet export tools now emit
boolean (was int) for BOOLEAN and timestamp-millis (was bare long) for
TIMESTAMP; both remain self-describing and decodable.
@codecov-commenter

codecov-commenter commented Jul 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.64516% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.52%. Comparing base (dded09d) to head (3bd7b2f).

Files with missing lines Patch % Lines
...che/pinot/core/util/SegmentProcessorAvroUtils.java 76.31% 3 Missing and 6 partials ⚠️
.../pinot/plugin/inputformat/avro/AvroSchemaUtil.java 86.36% 3 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master   #19073      +/-   ##
============================================
+ Coverage     65.45%   65.52%   +0.06%     
  Complexity     1421     1421              
============================================
  Files          3425     3425              
  Lines        216303   216252      -51     
  Branches      34266    34261       -5     
============================================
+ Hits         141590   141689      +99     
+ Misses        63317    63175     -142     
+ Partials      11396    11388       -8     
Flag Coverage Δ
custom-integration1 100.00% <ø> (ø)
integration 100.00% <ø> (ø)
integration1 100.00% <ø> (ø)
integration2 0.00% <ø> (ø)
java-25 65.52% <80.64%> (+0.06%) ⬆️
temurin 65.52% <80.64%> (+0.06%) ⬆️
unittests 65.51% <80.64%> (+0.06%) ⬆️
unittests1 56.90% <69.35%> (+0.08%) ⬆️
unittests2 37.89% <77.41%> (+0.02%) ⬆️

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.

@Jackie-Jiang Jackie-Jiang added enhancement Improvement to existing functionality ingestion Related to data ingestion pipeline labels Jul 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Improvement to existing functionality ingestion Related to data ingestion pipeline

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Avro] Generate schemas from Pinot logical data types

3 participants