Skip to content

GH-51097: Fix Parquet null counts for fixed-width leaves - #51357

Open
AnuragRaut08 wants to merge 1 commit into
apache:mainfrom
AnuragRaut08:fix/parquet-fixed-width-null-count
Open

AnuragRaut08 wants to merge 1 commit into
apache:mainfrom
AnuragRaut08:fix/parquet-fixed-width-null-count

Conversation

@AnuragRaut08

@AnuragRaut08 AnuragRaut08 commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Rationale for this change

Parquet null-count statistics can be incorrect for fixed-width leaf columns nested below a repeated ancestor, such as list<struct<...>>.

When a list is null or empty, its descendant leaf does not produce a value in the leaf values buffer. DefLevelsToBitmap intentionally excludes these repeated-ancestor nulls from its null_count. MaybeCalculateValidityBits was using that value directly for the Parquet column statistics, causing the null count to be undercounted.

For example, a list<struct<string, int32>> column can report an incorrect null_count for the int32 leaf even though the encoded definition levels correctly represent the null and empty list entries.

What changes are included in this PR?

  • Update MaybeCalculateValidityBits to calculate the total null count from batch_size - out_values_to_write.
  • This includes nulls represented by repeated ancestors, such as null or empty lists, while preserving the existing validity bitmap behavior.
  • Add a regression test covering a fixed-width int32 leaf under list<struct<...>>.

Are these changes tested?

Yes.

Added a regression test that verifies the int32 leaf reports:

  • null_count = 3
  • num_values = 2

The focused regression test passes:

StatisticsTest.FixedWidthLeafUnderListStructNullCount

The existing parquet-writer-test also passes.

Are there any user-facing changes?

Yes. This fixes incorrect Parquet column statistics for affected nested fixed-width columns. The change does not alter the encoded data or public APIs.

This PR contains a "Critical Fix".

This fixes a bug that produces incorrect Parquet statistics. The underlying data remains correct, but the reported null_count for affected fixed-width leaf columns can be incorrect.

Was AI used for this PR?

PR code and description written by:

  • Human
  • AI

Reviewed before submission by:

@github-actions

Copy link
Copy Markdown

⚠️ GitHub issue #51097 has been automatically assigned in GitHub to PR creator.

Comment thread cpp/src/parquet/column_writer.cc Outdated
Comment thread cpp/src/parquet/arrow/arrow_statistics_test.cc Outdated
Comment thread cpp/src/parquet/arrow/arrow_statistics_test.cc Outdated
Comment thread cpp/src/parquet/arrow/arrow_statistics_test.cc
Comment thread cpp/src/parquet/arrow/arrow_statistics_test.cc
@github-actions github-actions Bot added awaiting committer review Awaiting committer review and removed awaiting review Awaiting review labels Sep 17, 2026
Comment thread cpp/src/parquet/column_writer.cc Outdated
@AnuragRaut08
AnuragRaut08 force-pushed the fix/parquet-fixed-width-null-count branch 2 times, most recently from 5f9696f to ac0b0a9 Compare September 19, 2026 14:27
Signed-off-by: AnuragRaut08 <anuragtraut2003@gmail.com>
@AnuragRaut08
AnuragRaut08 force-pushed the fix/parquet-fixed-width-null-count branch from ac0b0a9 to 49995ad Compare September 19, 2026 14:30
@AnuragRaut08

Copy link
Copy Markdown
Contributor Author

Hi @wgtmac ,
I’ve addressed this point in the latest push. The compact Arrow null_count is now kept separate from the total Parquet null count: null_count is used by MaybeReplaceValidity, while total_null_count includes repeated-ancestor nulls and is used for WriteValuesSpaced and Parquet page/statistics metadata.
This keeps the Arrow validity semantics unchanged while fixing the Parquet null count for null/empty repeated ancestors. Could you please take another look when you get a chance? Thanks!

@wgtmac wgtmac left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks @AnuragRaut08 for the quick update! I think the code change looks good now. I have some comments for better test coverage.


ASSERT_EQ(row_group->num_columns(), 2);

auto int32_stats = row_group->ColumnChunk(1)->statistics();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we run this test for both data page V1 and V2 and then check both leaf columns? The old change fixed i32 but broke the string leaf, so the current checks would not catch that error.

// had so we need to recompute it from def levels.
MaybeCalculateValidityBits(AddIfNotNull(def_levels, offset), batch_size,
&batch_num_values, &batch_num_spaced_values, &null_count);
const int64_t total_null_count = batch_size - batch_num_values;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please add the same nested test case with a dictionary leaf. Run it for data page V1 and V2 by checking the statistics and reading the values back.

MaybeCalculateValidityBits(AddIfNotNull(def_levels, offset), batch_size,
&batch_num_values, &batch_num_spaced_values,
&null_count);
const int64_t total_null_count = batch_size - batch_num_values;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we rename total_null_count to parquet_null_count because it means number of nulls in the parquet leaf column? null_count is for the Arrow leaf array but renaming it would result in a larger change.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants