Skip to content

Commit 1a73690

Browse files
committed
update
1 parent 9db6236 commit 1a73690

2 files changed

Lines changed: 118 additions & 1 deletion

File tree

parquet-column/src/main/java/org/apache/parquet/internal/filter2/columnindex/ColumnIndexFilter.java

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,12 @@ private RowRanges applyPredicate(
192192
return allRows();
193193
}
194194

195-
return RowRanges.create(rowCount, func.apply(ci), oi);
195+
if (!isValidMetadata(ci, oi, columnPath)) {
196+
return allRows();
197+
}
198+
199+
PrimitiveIterator.OfInt pageIndexes = func.apply(ci);
200+
return RowRanges.create(rowCount, pageIndexes, oi);
196201
}
197202

198203
@Override
@@ -220,4 +225,35 @@ public RowRanges visit(Not not) {
220225
throw new IllegalArgumentException(
221226
"Predicates containing a NOT must be run through LogicalInverseRewriter. " + not);
222227
}
228+
229+
/**
230+
* Validates that column index and offset index metadata are consistent and can be used safely.
231+
*
232+
* @param columnIndex the column index to validate
233+
* @param offsetIndex the offset index to validate
234+
* @param columnPath the column path for error reporting
235+
* @return true if metadata is valid and safe to use, false if corrupt and should be ignored
236+
*/
237+
private static boolean isValidMetadata(ColumnIndex columnIndex, OffsetIndex offsetIndex, ColumnPath columnPath) {
238+
if (columnIndex == null || offsetIndex == null) {
239+
return true;
240+
}
241+
242+
int columnIndexSize = columnIndex.getMinValues().size();
243+
int offsetIndexSize = offsetIndex.getPageCount();
244+
245+
if (columnIndexSize != offsetIndexSize) {
246+
LOGGER.warn(
247+
"Column index and offset index size mismatch for column {}: "
248+
+ "column index has {} entries but offset index has {} pages. "
249+
+ "This indicates corrupted metadata from the writer. "
250+
+ "Ignoring column index for filtering to avoid errors.",
251+
columnPath,
252+
columnIndexSize,
253+
offsetIndexSize);
254+
return false;
255+
}
256+
257+
return true;
258+
}
223259
}

parquet-hadoop/src/test/java/org/apache/parquet/hadoop/TestColumnIndexFiltering.java

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
import java.util.Iterator;
5959
import java.util.List;
6060
import java.util.Map;
61+
import java.util.Optional;
6162
import java.util.Random;
6263
import java.util.Set;
6364
import java.util.function.Predicate;
@@ -69,6 +70,7 @@
6970
import org.apache.parquet.bytes.TrackingByteBufferAllocator;
7071
import org.apache.parquet.column.ParquetProperties;
7172
import org.apache.parquet.column.ParquetProperties.WriterVersion;
73+
import org.apache.parquet.column.statistics.BinaryStatistics;
7274
import org.apache.parquet.crypto.ColumnEncryptionProperties;
7375
import org.apache.parquet.crypto.DecryptionKeyRetrieverMock;
7476
import org.apache.parquet.crypto.FileDecryptionProperties;
@@ -86,8 +88,16 @@
8688
import org.apache.parquet.hadoop.example.ExampleParquetWriter;
8789
import org.apache.parquet.hadoop.example.GroupReadSupport;
8890
import org.apache.parquet.hadoop.metadata.ColumnPath;
91+
import org.apache.parquet.internal.column.columnindex.ColumnIndex;
92+
import org.apache.parquet.internal.column.columnindex.ColumnIndexBuilder;
93+
import org.apache.parquet.internal.column.columnindex.OffsetIndex;
94+
import org.apache.parquet.internal.column.columnindex.OffsetIndexBuilder;
95+
import org.apache.parquet.internal.filter2.columnindex.ColumnIndexFilter;
96+
import org.apache.parquet.internal.filter2.columnindex.ColumnIndexStore;
97+
import org.apache.parquet.internal.filter2.columnindex.RowRanges;
8998
import org.apache.parquet.io.api.Binary;
9099
import org.apache.parquet.schema.MessageType;
100+
import org.apache.parquet.schema.PrimitiveType;
91101
import org.apache.parquet.schema.Types;
92102
import org.junit.After;
93103
import org.junit.AfterClass;
@@ -650,4 +660,75 @@ public void testFilteringWithProjection() throws IOException {
650660
false,
651661
true));
652662
}
663+
664+
@Test
665+
public void testValidMetadata() throws Exception {
666+
String testColumnName = "test_column";
667+
long testRowCount = 100L;
668+
Binary testMinValue = Binary.fromString("a");
669+
Binary testMaxValue = Binary.fromString("z");
670+
Binary filterValue = Binary.fromString("");
671+
672+
OffsetIndex offsetIndex = createValidOffsetIndex();
673+
ColumnIndex columnIndex = createValidColumnIndex(testColumnName, testMinValue, testMaxValue);
674+
675+
MockColumnIndexStore store = new MockColumnIndexStore(columnIndex, offsetIndex);
676+
RowRanges result = ColumnIndexFilter.calculateRowRanges(
677+
FilterCompat.get(gtEq(binaryColumn(testColumnName), filterValue)),
678+
store,
679+
Collections.singleton(ColumnPath.fromDotString(testColumnName)),
680+
testRowCount);
681+
682+
assertEquals("Should return all rows for this filter", testRowCount, result.rowCount());
683+
assertEquals("Should have single range", 1, result.getRanges().size());
684+
assertEquals("Range should start at 0", 0L, result.getRanges().get(0).from);
685+
assertEquals(
686+
"Range should end at last row",
687+
testRowCount - 1,
688+
result.getRanges().get(0).to);
689+
}
690+
691+
private OffsetIndex createValidOffsetIndex() {
692+
OffsetIndexBuilder builder = OffsetIndexBuilder.getBuilder();
693+
builder.add(1000L, 100, 0L, Optional.empty());
694+
builder.add(1100L, 100, 50L, Optional.empty());
695+
return builder.build();
696+
}
697+
698+
private ColumnIndex createValidColumnIndex(String columnName, Binary minValue, Binary maxValue) {
699+
MessageType schema = Types.buildMessage()
700+
.required(PrimitiveType.PrimitiveTypeName.BINARY)
701+
.named(columnName)
702+
.named("test_schema");
703+
PrimitiveType primitiveType = schema.getColumns().get(0).getPrimitiveType();
704+
ColumnIndexBuilder builder = ColumnIndexBuilder.getBuilder(primitiveType, Integer.MAX_VALUE);
705+
706+
for (int i = 0; i < 2; i++) {
707+
BinaryStatistics stats = new BinaryStatistics();
708+
stats.updateStats(minValue);
709+
stats.updateStats(maxValue);
710+
builder.add(stats);
711+
}
712+
return builder.build();
713+
}
714+
715+
private static class MockColumnIndexStore implements ColumnIndexStore {
716+
private final ColumnIndex columnIndex;
717+
private final OffsetIndex offsetIndex;
718+
719+
public MockColumnIndexStore(ColumnIndex columnIndex, OffsetIndex offsetIndex) {
720+
this.columnIndex = columnIndex;
721+
this.offsetIndex = offsetIndex;
722+
}
723+
724+
@Override
725+
public ColumnIndex getColumnIndex(ColumnPath column) {
726+
return columnIndex;
727+
}
728+
729+
@Override
730+
public OffsetIndex getOffsetIndex(ColumnPath column) {
731+
return offsetIndex;
732+
}
733+
}
653734
}

0 commit comments

Comments
 (0)