Skip to content

fix(reader): read INT32/INT64 decimals through the Avro parquet reader - #20387

Draft
yihua wants to merge 1 commit into
apache:masterfrom
yihua:fix-avro-reader-int-decimals
Draft

yihua wants to merge 1 commit into
apache:masterfrom
yihua:fix-avro-reader-int-decimals

Conversation

@yihua

@yihua yihua commented Oct 18, 2026

Copy link
Copy Markdown
Contributor

Describe the issue this Pull Request addresses

closes #20386

The Avro-based parquet reader returns wrong values for decimal columns stored as INT32 or INT64, the encoding Spark, Iceberg, Trino, DuckDB and parquet-rs use for precision up to 18. parquet-avro only keeps the DECIMAL annotation on BINARY and FIXED_LEN_BYTE_ARRAY, so the column surfaces as a plain long, and Hudi then promotes the unscaled integer to the table's decimal as if it were the value: 17.00 reads as 1700.00 with no error. The Hive reader in hudi-hadoop-mr shares the problem since it derives its row schema the same way.

Summary and Changelog

Integer-backed decimals now read correctly through HoodieAvroParquetReader and the Hive reader, and a shape the reader cannot handle fails instead of returning wrong numbers.

  • AvroSchemaConverterWithTimestampNTZ maps an INT32/INT64 DECIMAL column to a fixed Avro decimal of the minimal width for its precision, the type Spark derives for the same column; HoodieSchema.minBytesForDecimalPrecision is the shared width helper.
  • SchemaRepair.alignDecimalPhysicalTypes re-types decimal leaves of the requested projection to the file's INT32/INT64 physical type so the projection matches the column chunks.
  • HoodieAvroReadSupport applies that alignment in init, supplies the Hudi-converted schema as the read schema when neither the caller nor the footer provides one, and wraps the root converter of the parent's materializer in IntegerDecimalRecordConverter, whose IntegerDecimalConverter turns plain and dictionary-encoded int/long values into the fixed or bytes decimal of the read schema, including inside nested records. Such columns inside arrays or maps throw UnsupportedOperationException rather than read wrong. The wrapper keeps the parent's converters for every other field, and the parent materializer is obtained from AvroReadSupport itself (through a super-call method handle on parquet 1.14+, whose record reader dispatches through ParquetConfiguration), so no parquet-avro constructor is named: hudi-hadoop-common compiles against parquet 1.12.3, 1.13.1, 1.15.2 and 1.17.0.
  • The hudi-hadoop-mr HoodieAvroParquetReader reads through HoodieAvroReadSupport instead of plain AvroReadSupport.
  • A table schema that recorded such a column as plain int or long, which is what schema inference produced before this change, keeps reading the unscaled integer it read before: HoodieAvroUtils and HoodieArrayWritableSchemaUtils rewrite a decimal to a plain int/long target that way.

Tests: TestHoodieAvroParquetReader (both modules) read INT32/INT64 decimal files written with parquet-mr's example writer against fixed, bytes, file-derived and reordered reader schemas, with and without dictionary encoding, plus nulls, a nested record, a wider and a narrower scale, and the array rejection. Without the fix they fail with Cannot encode decimal with precision 11 as max precision 9, a LongWritable where a HiveDecimalWritable is expected, and cannot support rewrite value for schema type ... decimal ... since the old schema type is: "int". The two TestAvroSchemaConverter cases that pinned the lossy int/long conversion now expect the decimal.

Impact

Reader-only. Base files with FIXED_LEN_BYTE_ARRAY decimals, which is everything Hudi writes today, take the same path as before. Two visible changes for files with integer-backed decimals: schema inference from such a file now yields a decimal instead of int/long, and a projection containing such a column is read with parquet-avro's regular record converter rather than its compat converter (strings arrive as Utf8 instead of String on paths that default to compat, such as Hive). ParquetUtils.readAvroRecords still uses the stock parquet-avro reader and is left for a follow-up.

Risk Level

low. The new converter only engages when the requested projection contains an INT32/INT64 decimal column. The Hive reader additionally picks up HoodieAvroReadSupport's legacy list and map handling; the hudi-hadoop-common and hudi-hadoop-mr suites were run against the change.

Documentation Update

none

Contributor's checklist

  • Read through contributor's guide
  • Enough context is provided in the sections above
  • Adequate tests were added if applicable

parquet-avro keeps the DECIMAL annotation only on BINARY and
FIXED_LEN_BYTE_ARRAY columns, so a decimal stored as INT32 or INT64 surfaced
to the Avro reader as a plain int or long and Hudi then promoted the unscaled
integer to the table's decimal as if it were the value (17.00 read as
1700.00). The Hudi schema converter now maps such columns to a fixed decimal,
the requested projection is re-typed to the file's physical type, and the
parent materializer's root converter is wrapped so the values are re-encoded
as the Avro decimal of the read schema. The Hive reader goes through the same
read support. A table schema that typed such a column as int or long keeps
reading the unscaled value.
@codecov-commenter

codecov-commenter commented Oct 18, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 81.31313% with 37 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.74%. Comparing base (9b7a713) to head (2960bc5).
⚠️ Report is 30 commits behind head on master.

Files with missing lines Patch % Lines
...org/apache/parquet/avro/HoodieAvroReadSupport.java 71.42% 10 Missing and 6 partials ⚠️
...he/parquet/avro/IntegerDecimalRecordConverter.java 86.11% 5 Missing and 5 partials ⚠️
...a/org/apache/hudi/common/avro/HoodieAvroUtils.java 54.54% 0 Missing and 5 partials ⚠️
...n/java/org/apache/parquet/schema/SchemaRepair.java 92.50% 1 Missing and 2 partials ⚠️
...i/hadoop/utils/HoodieArrayWritableSchemaUtils.java 71.42% 1 Missing and 1 partial ⚠️
...quet/avro/AvroSchemaConverterWithTimestampNTZ.java 83.33% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master   #20387      +/-   ##
============================================
- Coverage     80.75%   80.74%   -0.01%     
- Complexity    35317    35369      +52     
============================================
  Files          2562     2563       +1     
  Lines        144143   144329     +186     
  Branches      17560    17602      +42     
============================================
+ Hits         116401   116545     +144     
- Misses        19768    19790      +22     
- Partials       7974     7994      +20     
Components Coverage Δ
hudi-common 84.17% <68.75%> (-0.01%) ⬇️
hudi-client 83.95% <ø> (-0.02%) ⬇️
hudi-flink 86.08% <ø> (ø)
hudi-spark-datasource 74.19% <ø> (+<0.01%) ⬆️
hudi-utilities 78.32% <ø> (-0.01%) ⬇️
hudi-cli 70.80% <ø> (ø)
hudi-hadoop 71.41% <82.41%> (+0.23%) ⬆️
hudi-sync 76.78% <ø> (-0.08%) ⬇️
hudi-io 81.57% <ø> (ø)
hudi-timeline-service 83.41% <ø> (ø)
hudi-cloud 81.01% <ø> (ø)
hudi-kafka-connect 57.90% <ø> (ø)
Flag Coverage Δ
common-and-other-modules 52.65% <27.77%> (-0.04%) ⬇️
flink-integration-tests 49.58% <18.68%> (-0.07%) ⬇️
hadoop-mr-java-client 44.00% <56.56%> (+0.04%) ⬆️
integration-tests 13.41% <17.17%> (+<0.01%) ⬆️
spark-client-hadoop-common 38.57% <67.67%> (+0.05%) ⬆️
spark-java-tests 52.53% <27.27%> (-0.07%) ⬇️
spark-scala-tests 47.50% <21.71%> (-0.05%) ⬇️
utilities 36.97% <21.71%> (-0.03%) ⬇️

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

Files with missing lines Coverage Δ
...va/org/apache/hudi/common/schema/HoodieSchema.java 87.48% <100.00%> (+0.05%) ⬆️
.../org/apache/hudi/common/schema/internal/Types.java 82.83% <100.00%> (-0.23%) ⬇️
...ache/hudi/hadoop/avro/HoodieAvroParquetReader.java 86.20% <100.00%> (+6.89%) ⬆️
...quet/avro/AvroSchemaConverterWithTimestampNTZ.java 80.27% <83.33%> (-0.64%) ⬇️
...i/hadoop/utils/HoodieArrayWritableSchemaUtils.java 66.30% <71.42%> (+0.57%) ⬆️
...n/java/org/apache/parquet/schema/SchemaRepair.java 90.38% <92.50%> (+1.32%) ⬆️
...a/org/apache/hudi/common/avro/HoodieAvroUtils.java 78.41% <54.54%> (-0.41%) ⬇️
...he/parquet/avro/IntegerDecimalRecordConverter.java 86.11% <86.11%> (ø)
...org/apache/parquet/avro/HoodieAvroReadSupport.java 85.71% <71.42%> (-11.01%) ⬇️

... and 12 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions github-actions Bot added the size:XL PR with lines of changes > 1000 label Oct 18, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL PR with lines of changes > 1000

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Avro parquet reader misreads decimal columns stored as INT32/INT64

2 participants