Jackie-Jiang commented on code in PR #20060:
URL: https://github.com/apache/pinot/pull/20060#discussion_r4396162585
##########
pinot-plugins/pinot-input-format/pinot-parquet/src/test/java/org/apache/pinot/plugin/inputformat/parquet/ParquetReaderLogicalTypesTest.java:
##########
@@ -177,6 +180,73 @@ public void testNestedUnsignedInt32TypeWithNativeReader()
}
}
+ @Test
+ public void testReadersPreserveUnsignedInt64Bits()
+ throws IOException {
+ MessageType schema = MessageTypeParser.parseMessageType(
+ "message m { required int64 value (INTEGER(64, false)); }");
+ File dataFile = new File(FileUtils.getTempDirectory(), "unsigned-int64-" +
System.nanoTime() + ".parquet");
+ try {
+ try (ParquetWriter<Group> writer = ExampleParquetWriter.builder(new
Path(dataFile.getAbsolutePath()))
+ .withType(schema).build()) {
+ SimpleGroupFactory groups = new SimpleGroupFactory(schema);
+ for (long value : new long[]{-1L, 7L, Long.MIN_VALUE}) {
+ writer.write(groups.newGroup().append("value", value));
+ }
+ }
+ for (RecordReader reader : List.of(new ParquetNativeRecordReader(), new
ParquetAvroRecordReader())) {
+ try (reader) {
+ reader.init(dataFile, null, null);
+ for (long value : new long[]{-1L, 7L, Long.MIN_VALUE}) {
+ assertTrue(reader.hasNext());
+ assertEquals(reader.next(new GenericRow()).getValue("value"),
value);
+ }
+ assertFalse(reader.hasNext());
+ }
+ }
+ } finally {
+ FileUtils.deleteQuietly(dataFile);
+ }
+ }
+
+ @Test
+ public void testReadersAdvancePastRejectedInt96()
+ throws IOException {
+ MessageType schema = MessageTypeParser.parseMessageType("message m {
required int96 value; }");
+ File dataFile = new File(FileUtils.getTempDirectory(), "rejected-int96-" +
System.nanoTime() + ".parquet");
+ try {
+ try (ParquetWriter<Group> writer = ExampleParquetWriter.builder(new
Path(dataFile.getAbsolutePath()))
+ .withType(schema).build()) {
+ SimpleGroupFactory groups = new SimpleGroupFactory(schema);
+ // Year 1600 cannot be represented as signed Long epoch nanos; the
next row is in range.
+ writer.write(groups.newGroup().append("value", new NanoTime(2305448,
0L)));
+ writer.write(groups.newGroup().append("value", new NanoTime(2440588,
7L)));
+ }
+ for (boolean useAvro : new boolean[]{false, true}) {
+ ParquetRecordReaderConfig config = new ParquetRecordReaderConfig();
+ config.setUseParquetAvroRecordReader(useAvro);
+ config.setUseParquetNativeRecordReader(!useAvro);
+ config.setExtractRawTimeValues(true);
+ try (ParquetRecordReader reader = new ParquetRecordReader()) {
+ reader.init(dataFile, null, config);
+ assertEquals(reader.useAvroParquetRecordReader(), useAvro);
+ for (int iteration = 0; iteration < 2; iteration++) {
+ if (iteration > 0) {
+ reader.rewind();
+ }
+ assertTrue(reader.hasNext());
+ expectThrows(IllegalArgumentException.class, () -> reader.next(new
GenericRow()));
+ assertTrue(reader.hasNext());
+ assertEquals(reader.next(new GenericRow()).getValue("value"), 7L);
+ assertFalse(reader.hasNext(), "Extraction failures must not retry
the rejected record");
Review Comment:
Nonblocking cleanup: test rejection directly at EOF
This regression covers rejected → valid → EOF. Add a trailing rejected INT96
row and assert `hasNext()` becomes false immediately after its exception,
retaining the rewind coverage. That directly protects advancement when there is
no subsequent successful extraction.
##########
pinot-plugins/pinot-input-format/pinot-parquet/src/main/java/org/apache/pinot/plugin/inputformat/parquet/ParquetAvroRecordReader.java:
##########
@@ -72,13 +72,16 @@ public boolean hasNext() {
@Override
public GenericRow next(GenericRow reuse)
throws IOException {
- // Data parsing: extract current record into GenericRow.
- _recordExtractor.extract(_nextRecord, reuse);
- // Record fetch: read next Parquet Avro record.
try {
- _nextRecord = _parquetReader.read();
- } catch (IOException e) {
- throw new RecordFetchException("Failed to read next Parquet Avro
record", e);
+ // Data parsing: extract current record into GenericRow.
+ _recordExtractor.extract(_nextRecord, reuse);
+ } finally {
+ // Advance even if extraction fails and the caller skips the rejected
record.
+ try {
+ _nextRecord = _parquetReader.read();
+ } catch (IOException e) {
+ throw new RecordFetchException("Failed to read next Parquet Avro
record", e);
Review Comment:
Pre-existing follow-up: classify unchecked fetch failures consistently
The prefetch catches only `IOException`, but parquet-hadoop 1.18.1 can
propagate unchecked `ParquetDecodingException` from
`InternalParquetRecordReader.nextKeyValue()` through `ParquetReader.read()`.
Those failures escape as parse errors in `SegmentMapper`, bypassing its
configured consecutive-fetch-failure threshold.
Wrap exceptions specifically around `_parquetReader.read()` as
`RecordFetchException`, consistently with the native reader, and add an
injected unchecked-read-failure test. The same narrow catch exists in the base
revision, so this is a separate follow-up and does not block approval of this
PR.
##########
pinot-plugins/pinot-input-format/pinot-parquet/src/test/java/org/apache/pinot/plugin/inputformat/parquet/ParquetNativeRecordExtractorTest.java:
##########
@@ -274,6 +315,33 @@ public void testInt96ExtractedAsRawLongEpochNanosWhenRaw()
{
epochMillis * 1_000_000L);
}
+ @Test
+ public void testInt96RangeAndNanosecondBoundaries() {
+ MessageType schema = MessageTypeParser.parseMessageType("message R {
required int96 col; }");
+ for (String text : new String[]{"1970-01-01T00:00:00Z",
"1969-12-31T23:59:59.999999999Z",
+ "1677-09-21T00:12:43.145224192Z", "2262-04-11T23:47:16.854775807Z",
+ "1677-09-21T00:12:43.145224191Z", "2262-04-11T23:47:16.854775808Z",
+ "1600-01-01T00:00:00.123456789Z", "2300-01-01T00:00:00.123456789Z"}) {
+ Instant instant = Instant.parse(text);
+ long day = Math.floorDiv(instant.getEpochSecond(), 86_400L);
+ byte[] bytes = ByteBuffer.allocate(12).order(ByteOrder.LITTLE_ENDIAN)
+ .putLong(Math.floorMod(instant.getEpochSecond(), 86_400L) *
1_000_000_000L + instant.getNano())
+ .putInt(Math.toIntExact(day +
ParquetUtils.JULIAN_DAY_NUMBER_FOR_UNIX_EPOCH)).array();
Review Comment:
Nonblocking cleanup: consolidate INT96 test encoding
The native and Avro tests duplicate this `Instant` → little-endian bytes
calculation, alongside older millisecond-based helpers. Add one
`ParquetTestUtils.int96Bytes(Instant)` helper using floor division, then reuse
it from both extractor tests. This removes duplicated date arithmetic and gives
negative dates the same canonical day/nanoseconds-of-day encoding.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]