This is an automated email from the ASF dual-hosted git repository.
vinishjail97 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-xtable.git
The following commit(s) were added to refs/heads/main by this push:
new 77003d71 fix: Populate record_count for parquet source files (#929)
77003d71 is described below
commit 77003d7124b4221e9c5061cb3d743e7ccf49dcf8
Author: Arvin <[email protected]>
AuthorDate: Sat Sep 26 07:53:09 2026 +0800
fix: Populate record_count for parquet source files (#929)
* [Bug] Populate record_count for parquet source files
Fixes #926: InternalDataFile construction in ParquetConversionSource never
set recordCount, so targets registered every file with record_count = 0 and
optimized count(*) returned 0 on non-empty tables.
Sum BlockMetaData.getRowCount() from the parquet footer and set it on both
full-sync and incremental-sync file construction paths.
* Add tests for parquet source record counts
---------
Co-authored-by: Vinish Reddy <[email protected]>
---
.../xtable/parquet/ParquetConversionSource.java | 39 +++++-----
.../xtable/parquet/ParquetMetadataExtractor.java | 6 ++
.../xtable/parquet/ITParquetConversionSource.java | 12 +++
.../parquet/TestParquetMetadataExtractor.java | 90 ++++++++++++++++++++++
4 files changed, 128 insertions(+), 19 deletions(-)
diff --git
a/xtable-core/src/main/java/org/apache/xtable/parquet/ParquetConversionSource.java
b/xtable-core/src/main/java/org/apache/xtable/parquet/ParquetConversionSource.java
index 3a505bb8..9b132404 100644
---
a/xtable-core/src/main/java/org/apache/xtable/parquet/ParquetConversionSource.java
+++
b/xtable-core/src/main/java/org/apache/xtable/parquet/ParquetConversionSource.java
@@ -118,25 +118,28 @@ public class ParquetConversionSource implements
ConversionSource<Long> {
private Stream<InternalDataFile> getInternalDataFiles(
Stream<LocatedFileStatus> parquetFiles, InternalSchema schema) {
return parquetFiles.map(
- file ->
- InternalDataFile.builder()
- .physicalPath(file.getPath().toString())
- .fileFormat(FileFormat.APACHE_PARQUET)
- .fileSizeBytes(file.getLen())
- .partitionValues(
- partitionValueExtractor.extractPartitionValues(
- partitionSpecExtractor.spec(schema),
- HudiPathUtils.getPartitionPath(new Path(basePath),
file.getPath())))
- .lastModified(file.getModificationTime())
- .columnStats(
- parquetStatsExtractor.getStatsForFile(
-
parquetMetadataExtractor.readParquetMetadata(hadoopConf, file.getPath()),
- schema))
- .build());
+ file -> {
+ ParquetMetadata parquetMetadata =
+ parquetMetadataExtractor.readParquetMetadata(hadoopConf,
file.getPath());
+ return InternalDataFile.builder()
+ .physicalPath(file.getPath().toString())
+ .fileFormat(FileFormat.APACHE_PARQUET)
+ .fileSizeBytes(file.getLen())
+
.recordCount(parquetMetadataExtractor.getRowCount(parquetMetadata))
+ .partitionValues(
+ partitionValueExtractor.extractPartitionValues(
+ partitionSpecExtractor.spec(schema),
+ HudiPathUtils.getPartitionPath(new Path(basePath),
file.getPath())))
+ .lastModified(file.getModificationTime())
+
.columnStats(parquetStatsExtractor.getStatsForFile(parquetMetadata, schema))
+ .build();
+ });
}
public InternalDataFile createInternalDataFileFromParquetFile(
FileStatus parquetFile, InternalSchema schema) {
+ ParquetMetadata parquetMetadata =
+ parquetMetadataExtractor.readParquetMetadata(hadoopConf,
parquetFile.getPath());
return InternalDataFile.builder()
.physicalPath(parquetFile.getPath().toString())
.partitionValues(
@@ -145,10 +148,8 @@ public class ParquetConversionSource implements
ConversionSource<Long> {
HudiPathUtils.getPartitionPath(new Path(basePath),
parquetFile.getPath())))
.lastModified(parquetFile.getModificationTime())
.fileSizeBytes(parquetFile.getLen())
- .columnStats(
- parquetStatsExtractor.getStatsForFile(
- parquetMetadataExtractor.readParquetMetadata(hadoopConf,
parquetFile.getPath()),
- schema))
+ .recordCount(parquetMetadataExtractor.getRowCount(parquetMetadata))
+ .columnStats(parquetStatsExtractor.getStatsForFile(parquetMetadata,
schema))
.build();
}
diff --git
a/xtable-core/src/main/java/org/apache/xtable/parquet/ParquetMetadataExtractor.java
b/xtable-core/src/main/java/org/apache/xtable/parquet/ParquetMetadataExtractor.java
index f022b753..70259b40 100644
---
a/xtable-core/src/main/java/org/apache/xtable/parquet/ParquetMetadataExtractor.java
+++
b/xtable-core/src/main/java/org/apache/xtable/parquet/ParquetMetadataExtractor.java
@@ -28,6 +28,7 @@ import org.apache.hadoop.fs.Path;
import org.apache.parquet.HadoopReadOptions;
import org.apache.parquet.ParquetReadOptions;
import org.apache.parquet.hadoop.ParquetFileReader;
+import org.apache.parquet.hadoop.metadata.BlockMetaData;
import org.apache.parquet.hadoop.metadata.ParquetMetadata;
import org.apache.parquet.hadoop.util.HadoopInputFile;
import org.apache.parquet.schema.MessageType;
@@ -47,6 +48,11 @@ public class ParquetMetadataExtractor {
return footer.getFileMetaData().getSchema();
}
+ /** Returns the total number of rows in the parquet file by summing row
counts of all blocks. */
+ public long getRowCount(ParquetMetadata footer) {
+ return
footer.getBlocks().stream().mapToLong(BlockMetaData::getRowCount).sum();
+ }
+
public ParquetMetadata readParquetMetadata(Configuration conf, Path
filePath) {
ParquetReadOptions options = HadoopReadOptions.builder(conf,
filePath).build();
try (ParquetFileReader fileReader =
diff --git
a/xtable-core/src/test/java/org/apache/xtable/parquet/ITParquetConversionSource.java
b/xtable-core/src/test/java/org/apache/xtable/parquet/ITParquetConversionSource.java
index be3d0b43..cededf35 100644
---
a/xtable-core/src/test/java/org/apache/xtable/parquet/ITParquetConversionSource.java
+++
b/xtable-core/src/test/java/org/apache/xtable/parquet/ITParquetConversionSource.java
@@ -537,10 +537,22 @@ public class ITParquetConversionSource {
assertNotNull(result.getReadSchema());
InternalSnapshot snapshot = conversionSource.getCurrentSnapshot();
assertNotNull(snapshot);
+ // record counts come from the parquet footers: 4 initial rows plus 3
appended rows
+ assertEquals(
+ data.size() + futureDataToSync.size(),
+ snapshot.getPartitionedDataFiles().stream()
+ .flatMap(group -> group.getDataFiles().stream())
+ .mapToLong(InternalDataFile::getRecordCount)
+ .sum());
TableChange changes = conversionSource.getTableChangeForCommit(testTime);
assertNotNull(changes);
assertFalse(changes.getFilesDiff().dataFilesAdded().isEmpty(), "Should
have found added files");
assertEquals(expectedAddedFiles, changes.getFilesDiff().dataFilesAdded());
+ assertEquals(
+ futureDataToSync.size(),
+ changes.getFilesDiff().dataFilesAdded().stream()
+ .mapToLong(InternalDataFile::getRecordCount)
+ .sum());
// The committed source identifier must be the most-recent modification
time from the same
// listing used to compute filesAdded, otherwise a file could be marked
synced without being
// included in the changes.
diff --git
a/xtable-core/src/test/java/org/apache/xtable/parquet/TestParquetMetadataExtractor.java
b/xtable-core/src/test/java/org/apache/xtable/parquet/TestParquetMetadataExtractor.java
new file mode 100644
index 00000000..cadd12da
--- /dev/null
+++
b/xtable-core/src/test/java/org/apache/xtable/parquet/TestParquetMetadataExtractor.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.xtable.parquet;
+
+import static org.apache.parquet.column.Encoding.BIT_PACKED;
+import static org.apache.parquet.column.Encoding.PLAIN;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.Path;
+import org.apache.parquet.bytes.BytesInput;
+import org.apache.parquet.column.ColumnDescriptor;
+import org.apache.parquet.example.data.Group;
+import org.apache.parquet.example.data.simple.SimpleGroupFactory;
+import org.apache.parquet.hadoop.ParquetFileWriter;
+import org.apache.parquet.hadoop.ParquetWriter;
+import org.apache.parquet.hadoop.example.ExampleParquetWriter;
+import org.apache.parquet.hadoop.example.GroupWriteSupport;
+import org.apache.parquet.hadoop.metadata.CompressionCodecName;
+import org.apache.parquet.schema.MessageType;
+import org.apache.parquet.schema.PrimitiveType.PrimitiveTypeName;
+import org.apache.parquet.schema.Types;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+class TestParquetMetadataExtractor {
+
+ private static final MessageType SCHEMA =
+ new MessageType("message",
Types.required(PrimitiveTypeName.INT32).named("col"));
+
+ private final Configuration conf = new Configuration();
+ private final ParquetMetadataExtractor metadataExtractor =
ParquetMetadataExtractor.getInstance();
+ @TempDir java.nio.file.Path tempDir;
+
+ @Test
+ void getRowCountSingleRowGroup() throws IOException {
+ Path path = new Path(tempDir.resolve("single-rg.parquet").toUri());
+ GroupWriteSupport.setSchema(SCHEMA, conf);
+ SimpleGroupFactory factory = new SimpleGroupFactory(SCHEMA);
+ try (ParquetWriter<Group> writer =
ExampleParquetWriter.builder(path).withConf(conf).build()) {
+ for (int i = 0; i < 3; i++) {
+ writer.write(factory.newGroup().append("col", i));
+ }
+ }
+
+ assertEquals(
+ 3L,
metadataExtractor.getRowCount(metadataExtractor.readParquetMetadata(conf,
path)));
+ }
+
+ @Test
+ void getRowCountSumsAllRowGroups() throws IOException {
+ Path path = new Path(tempDir.resolve("multi-rg.parquet").toUri());
+ ColumnDescriptor col = SCHEMA.getColumns().get(0);
+ ParquetFileWriter writer = new ParquetFileWriter(conf, SCHEMA, path);
+ writer.start();
+ writer.startBlock(10);
+ writer.startColumn(col, 10, CompressionCodecName.UNCOMPRESSED);
+ writer.writeDataPage(10, 4, BytesInput.fromInt(0), BIT_PACKED, BIT_PACKED,
PLAIN);
+ writer.endColumn();
+ writer.endBlock();
+ writer.startBlock(5);
+ writer.startColumn(col, 5, CompressionCodecName.UNCOMPRESSED);
+ writer.writeDataPage(5, 4, BytesInput.fromInt(0), BIT_PACKED, BIT_PACKED,
PLAIN);
+ writer.endColumn();
+ writer.endBlock();
+ writer.end(new HashMap<>());
+
+ assertEquals(
+ 15L,
metadataExtractor.getRowCount(metadataExtractor.readParquetMetadata(conf,
path)));
+ }
+}