This is an automated email from the ASF dual-hosted git repository.

wgtmac pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/parquet-java.git


The following commit(s) were added to refs/heads/master by this push:
     new b8b86e0b9 GH-3585: Fix DataPageHeaderV2.num_nulls=-1 when column 
statistics are disabled (#3586)
b8b86e0b9 is described below

commit b8b86e0b929f21d694a9f1d20f8fd6b0d51149ee
Author: Keuin <[email protected]>
AuthorDate: Wed Jun 24 14:24:16 2026 +0800

    GH-3585: Fix DataPageHeaderV2.num_nulls=-1 when column statistics are 
disabled (#3586)
---
 .../parquet/column/impl/ColumnWriterBase.java      |  5 ++
 .../apache/parquet/column/impl/ColumnWriterV2.java |  2 +-
 .../apache/parquet/hadoop/TestParquetWriter.java   | 69 ++++++++++++++++++++++
 3 files changed, 75 insertions(+), 1 deletion(-)

diff --git 
a/parquet-column/src/main/java/org/apache/parquet/column/impl/ColumnWriterBase.java
 
b/parquet-column/src/main/java/org/apache/parquet/column/impl/ColumnWriterBase.java
index 8fc4aa272..408627404 100644
--- 
a/parquet-column/src/main/java/org/apache/parquet/column/impl/ColumnWriterBase.java
+++ 
b/parquet-column/src/main/java/org/apache/parquet/column/impl/ColumnWriterBase.java
@@ -51,6 +51,9 @@ abstract class ColumnWriterBase implements ColumnWriter {
   private ValuesWriter definitionLevelColumn;
   private ValuesWriter dataColumn;
   private int valueCount;
+  // track the required field DataPageHeaderV2.num_nulls
+  // 
https://github.com/apache/parquet-format/blob/master/src/main/thrift/parquet.thrift
+  protected int nullCount;
 
   private long rowsWrittenSoFar = 0;
   private int pageRowCount;
@@ -115,6 +118,7 @@ abstract class ColumnWriterBase implements ColumnWriter {
       definitionLevel(definitionLevel);
       collector.writeNull(repetitionLevel, definitionLevel);
       ++valueCount;
+      ++nullCount;
     } catch (Throwable e) {
       statusManager.abort();
       throw e;
@@ -392,6 +396,7 @@ abstract class ColumnWriterBase implements ColumnWriter {
       definitionLevelColumn.reset();
       dataColumn.reset();
       valueCount = 0;
+      nullCount = 0;
       collector.resetPageStatistics();
       pageRowCount = 0;
     } catch (Throwable t) {
diff --git 
a/parquet-column/src/main/java/org/apache/parquet/column/impl/ColumnWriterV2.java
 
b/parquet-column/src/main/java/org/apache/parquet/column/impl/ColumnWriterV2.java
index e7af6aaad..e61bcef15 100644
--- 
a/parquet-column/src/main/java/org/apache/parquet/column/impl/ColumnWriterV2.java
+++ 
b/parquet-column/src/main/java/org/apache/parquet/column/impl/ColumnWriterV2.java
@@ -100,7 +100,7 @@ final class ColumnWriterV2 extends ColumnWriterBase {
     Encoding encoding = values.getEncoding();
     pageWriter.writePageV2(
         rowCount,
-        Math.toIntExact(statistics.getNumNulls()),
+        nullCount,
         valueCount,
         repetitionLevels.getBytes(),
         definitionLevels.getBytes(),
diff --git 
a/parquet-hadoop/src/test/java/org/apache/parquet/hadoop/TestParquetWriter.java 
b/parquet-hadoop/src/test/java/org/apache/parquet/hadoop/TestParquetWriter.java
index a7888b58d..6b3b0aae2 100644
--- 
a/parquet-hadoop/src/test/java/org/apache/parquet/hadoop/TestParquetWriter.java
+++ 
b/parquet-hadoop/src/test/java/org/apache/parquet/hadoop/TestParquetWriter.java
@@ -58,9 +58,14 @@ import org.apache.hadoop.fs.Path;
 import org.apache.parquet.ParquetReadOptions;
 import org.apache.parquet.bytes.HeapByteBufferAllocator;
 import org.apache.parquet.bytes.TrackingByteBufferAllocator;
+import org.apache.parquet.column.ColumnDescriptor;
 import org.apache.parquet.column.Encoding;
 import org.apache.parquet.column.ParquetProperties;
 import org.apache.parquet.column.ParquetProperties.WriterVersion;
+import org.apache.parquet.column.page.DataPage;
+import org.apache.parquet.column.page.DataPageV2;
+import org.apache.parquet.column.page.PageReadStore;
+import org.apache.parquet.column.page.PageReader;
 import org.apache.parquet.column.values.bloomfilter.BloomFilter;
 import org.apache.parquet.crypto.AesCipher;
 import org.apache.parquet.crypto.ColumnEncryptionProperties;
@@ -858,4 +863,68 @@ public class TestParquetWriter {
     FileSystem fs = file.getFileSystem(conf);
     assertTrue(!fs.exists(file) || fs.getFileStatus(file).getLen() == 0);
   }
+
+  @Test
+  public void testV2PageNullCountWithStatisticsDisabled() throws Exception {
+    // Regression test: when using PARQUET_2_0 with statistics disabled on a 
nullable column,
+    // DataPageHeaderV2.num_nulls must still contain the correct null count 
(not -1).
+    MessageType schema = Types.buildMessage()
+        .required(INT32)
+        .named("id")
+        .optional(BINARY)
+        .as(stringType())
+        .named("value")
+        .named("test_schema");
+
+    File file = temp.newFile();
+    file.delete();
+    Path path = new Path(file.getAbsolutePath());
+
+    int totalRecords = 10;
+    int expectedNulls = 4; // records where i % 3 == 0: i=0,3,6,9
+
+    // Write with PARQUET_2_0 and statistics disabled on the nullable "value" 
column
+    try (ParquetWriter<Group> writer = ExampleParquetWriter.builder(path)
+        .withType(schema)
+        .withWriterVersion(PARQUET_2_0)
+        .withStatisticsEnabled("value", false)
+        .withPageSize(1024 * 1024) // large page to keep all records in one 
page
+        .build()) {
+      SimpleGroupFactory factory = new SimpleGroupFactory(schema);
+      for (int i = 0; i < totalRecords; i++) {
+        Group group = factory.newGroup().append("id", i);
+        if (i % 3 != 0) {
+          group.append("value", "hello-" + i);
+        }
+        writer.write(group);
+      }
+    }
+
+    // Read back the page-level metadata and verify num_nulls
+    try (ParquetFileReader reader = 
ParquetFileReader.open(HadoopInputFile.fromPath(path, new Configuration()))) {
+      MessageType fileSchema = 
reader.getFooter().getFileMetaData().getSchema();
+
+      // Find the "value" column descriptor
+      ColumnDescriptor valueColumn = fileSchema.getColumns().stream()
+          .filter(c -> c.getPath()[0].equals("value"))
+          .findFirst()
+          .orElseThrow(() -> new AssertionError("Column 'value' not found"));
+
+      PageReadStore rowGroup = reader.readNextRowGroup();
+      PageReader pageReader = rowGroup.getPageReader(valueColumn);
+      DataPage page = pageReader.readPage();
+
+      // Verify it's a V2 page (because we used PARQUET_2_0)
+      assertTrue(
+          "PARQUET_2_0 writer should produce DataPageV2 pages, got: "
+              + page.getClass().getSimpleName(),
+          page instanceof DataPageV2);
+
+      DataPageV2 pageV2 = (DataPageV2) page;
+      assertEquals(
+          "DataPageV2.num_nulls should be the actual null count even when 
statistics are disabled",
+          expectedNulls,
+          pageV2.getNullCount());
+    }
+  }
 }

Reply via email to