|
48 | 48 | import java.util.HashSet; |
49 | 49 | import java.util.List; |
50 | 50 | import java.util.Map; |
| 51 | +import java.util.concurrent.atomic.AtomicBoolean; |
51 | 52 | import org.apache.hadoop.conf.Configuration; |
52 | 53 | import org.apache.hadoop.fs.FSDataInputStream; |
53 | 54 | import org.apache.hadoop.fs.FileStatus; |
|
76 | 77 | import org.apache.parquet.column.statistics.LongStatistics; |
77 | 78 | import org.apache.parquet.column.values.bloomfilter.BlockSplitBloomFilter; |
78 | 79 | import org.apache.parquet.column.values.bloomfilter.BloomFilter; |
| 80 | +import org.apache.parquet.crypto.ColumnEncryptionProperties; |
| 81 | +import org.apache.parquet.crypto.FileEncryptionProperties; |
| 82 | +import org.apache.parquet.crypto.ParquetCryptoRuntimeException; |
79 | 83 | import org.apache.parquet.example.data.Group; |
80 | 84 | import org.apache.parquet.example.data.simple.SimpleGroup; |
81 | 85 | import org.apache.parquet.format.Statistics; |
|
84 | 88 | import org.apache.parquet.hadoop.example.GroupWriteSupport; |
85 | 89 | import org.apache.parquet.hadoop.metadata.BlockMetaData; |
86 | 90 | import org.apache.parquet.hadoop.metadata.ColumnChunkMetaData; |
| 91 | +import org.apache.parquet.hadoop.metadata.ColumnPath; |
87 | 92 | import org.apache.parquet.hadoop.metadata.CompressionCodecName; |
88 | 93 | import org.apache.parquet.hadoop.metadata.ConcatenatingKeyValueMetadataMergeStrategy; |
89 | 94 | import org.apache.parquet.hadoop.metadata.FileMetaData; |
|
98 | 103 | import org.apache.parquet.internal.column.columnindex.BoundaryOrder; |
99 | 104 | import org.apache.parquet.internal.column.columnindex.ColumnIndex; |
100 | 105 | import org.apache.parquet.internal.column.columnindex.OffsetIndex; |
| 106 | +import org.apache.parquet.io.OutputFile; |
101 | 107 | import org.apache.parquet.io.ParquetEncodingException; |
| 108 | +import org.apache.parquet.io.PositionOutputStream; |
102 | 109 | import org.apache.parquet.io.api.Binary; |
103 | 110 | import org.apache.parquet.schema.MessageType; |
104 | 111 | import org.apache.parquet.schema.MessageTypeParser; |
@@ -1443,6 +1450,89 @@ public void testMergeMetadataWithNoConflictingKeyValues(boolean vectoredRead) { |
1443 | 1450 | assertThat(mergedValues.get("c")).isEqualTo("d"); |
1444 | 1451 | } |
1445 | 1452 |
|
| 1453 | + @Test |
| 1454 | + public void testConstructorClosesStreamWhenEncryptedColumnMissing() throws Exception { |
| 1455 | + ColumnEncryptionProperties missingColumn = ColumnEncryptionProperties.builder("not_in_schema") |
| 1456 | + .withKey("0123456789012345".getBytes(StandardCharsets.UTF_8)) |
| 1457 | + .build(); |
| 1458 | + Map<ColumnPath, ColumnEncryptionProperties> encryptedColumns = new HashMap<>(); |
| 1459 | + encryptedColumns.put(missingColumn.getPath(), missingColumn); |
| 1460 | + FileEncryptionProperties encryptionProperties = FileEncryptionProperties.builder( |
| 1461 | + "0123456789012345".getBytes(StandardCharsets.UTF_8)) |
| 1462 | + .withEncryptedColumns(encryptedColumns) |
| 1463 | + .build(); |
| 1464 | + |
| 1465 | + RecordingOutputFile file = new RecordingOutputFile(); |
| 1466 | + assertThatThrownBy(() -> new ParquetFileWriter( |
| 1467 | + file, |
| 1468 | + SCHEMA, |
| 1469 | + CREATE, |
| 1470 | + DEFAULT_BLOCK_SIZE, |
| 1471 | + MAX_PADDING_SIZE_DEFAULT, |
| 1472 | + ParquetProperties.DEFAULT_COLUMN_INDEX_TRUNCATE_LENGTH, |
| 1473 | + ParquetProperties.DEFAULT_STATISTICS_TRUNCATE_LENGTH, |
| 1474 | + ParquetProperties.DEFAULT_PAGE_WRITE_CHECKSUM_ENABLED, |
| 1475 | + encryptionProperties)) |
| 1476 | + .isInstanceOf(ParquetCryptoRuntimeException.class) |
| 1477 | + .hasMessage("Encrypted column [not_in_schema] not in file schema column list: [a.b], [c.d]"); |
| 1478 | + |
| 1479 | + assertThat(file.isStreamClosed()).isTrue(); |
| 1480 | + } |
| 1481 | + |
| 1482 | + /** |
| 1483 | + * An {@link OutputFile} whose {@link PositionOutputStream} records whether it was closed, used to |
| 1484 | + * assert that a failed {@link ParquetFileWriter} construction does not leak the open stream. |
| 1485 | + */ |
| 1486 | + private static class RecordingOutputFile implements OutputFile { |
| 1487 | + |
| 1488 | + private final AtomicBoolean streamClosed = new AtomicBoolean(false); |
| 1489 | + |
| 1490 | + boolean isStreamClosed() { |
| 1491 | + return streamClosed.get(); |
| 1492 | + } |
| 1493 | + |
| 1494 | + private PositionOutputStream newRecordingStream() { |
| 1495 | + return new PositionOutputStream() { |
| 1496 | + private long pos = 0; |
| 1497 | + |
| 1498 | + @Override |
| 1499 | + public long getPos() { |
| 1500 | + return pos; |
| 1501 | + } |
| 1502 | + |
| 1503 | + @Override |
| 1504 | + public void write(int b) { |
| 1505 | + pos++; |
| 1506 | + } |
| 1507 | + |
| 1508 | + @Override |
| 1509 | + public void close() { |
| 1510 | + streamClosed.set(true); |
| 1511 | + } |
| 1512 | + }; |
| 1513 | + } |
| 1514 | + |
| 1515 | + @Override |
| 1516 | + public PositionOutputStream create(long blockSizeHint) { |
| 1517 | + return newRecordingStream(); |
| 1518 | + } |
| 1519 | + |
| 1520 | + @Override |
| 1521 | + public PositionOutputStream createOrOverwrite(long blockSizeHint) { |
| 1522 | + return newRecordingStream(); |
| 1523 | + } |
| 1524 | + |
| 1525 | + @Override |
| 1526 | + public boolean supportsBlockSize() { |
| 1527 | + return false; |
| 1528 | + } |
| 1529 | + |
| 1530 | + @Override |
| 1531 | + public long defaultBlockSize() { |
| 1532 | + return 0; |
| 1533 | + } |
| 1534 | + } |
| 1535 | + |
1446 | 1536 | private org.apache.parquet.column.statistics.Statistics<?> statsC1(Binary... values) { |
1447 | 1537 | org.apache.parquet.column.statistics.Statistics<?> stats = |
1448 | 1538 | org.apache.parquet.column.statistics.Statistics.createStats(C1.getPrimitiveType()); |
|
0 commit comments