Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package org.apache.paimon.format.avro;

import org.apache.paimon.CoreOptions;
import org.apache.paimon.data.InternalRow;
import org.apache.paimon.format.FileFormat;
import org.apache.paimon.format.FileFormatFactory.FormatContext;
Expand Down Expand Up @@ -67,6 +68,11 @@ public class AvroFileFormat extends FileFormat {

private final Options options;
private final int zstdLevel;
/** Bounds enforced by {@code DataFileWriter#setSyncInterval}. */
private static final long MIN_SYNC_INTERVAL = 32;

private static final long MAX_SYNC_INTERVAL = 1 << 30;

@Nullable private final MemorySize blockSize;

public AvroFileFormat(FormatContext context) {
Expand Down Expand Up @@ -114,7 +120,7 @@ private AvroBlockWriter createBlockWriter(
}
writer.setCodec(createCodecFactory(compression));
if (blockSize != null) {
writer.setSyncInterval(Math.toIntExact(blockSize.getBytes()));
writer.setSyncInterval(avroSyncInterval(blockSize));
}
writer.setFlushOnEveryBlock(false);
writer.create(schema, new CloseShieldOutputStream(out));
Expand All @@ -135,6 +141,21 @@ public void validateDataFields(RowType rowType) {
}
}

/**
* Avro only accepts a sync interval between 32 bytes and 1 GiB; check it here so a bad {@code
* file.block-size} fails with the option name instead of inside the writer on an executor.
*/
static int avroSyncInterval(MemorySize blockSize) {
long bytes = blockSize.getBytes();
if (bytes < MIN_SYNC_INTERVAL || bytes > MAX_SYNC_INTERVAL) {
throw new IllegalArgumentException(
String.format(
"%s for avro must be between 32 bytes and 1 gb, but was %s bytes.",
CoreOptions.FILE_BLOCK_SIZE.key(), bytes));
}
return (int) bytes;
}

private CodecFactory createCodecFactory(String compression) {
if (options.contains(AVRO_OUTPUT_CODEC)) {
return CodecFactory.fromString(options.get(AVRO_OUTPUT_CODEC));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,10 @@ private void assertFileBlockSize(FileFormat format, int expectedBlockSize, Strin
}

@ParameterizedTest
@ValueSource(longs = {2147483648L, 4294968320L, Long.MAX_VALUE})
void testFileBlockSizeOverflow(long blockSize) throws IOException {
@ValueSource(longs = {1L, 6L, 31L, 1073741825L, 2147483648L, 4294968320L, Long.MAX_VALUE})
void testFileBlockSizeOutOfAvroRange(long blockSize) throws IOException {
// Avro accepts a sync interval of 32 bytes to 1 GiB; anything else must be rejected up
// front with the option name, not deep inside the writer with Avro's own message.
Options options = new Options();
options.setString("file.block-size", Long.toString(blockSize));
FileFormat format = FileFormat.fromIdentifier("avro", options);
Expand All @@ -173,8 +175,27 @@ void testFileBlockSizeOverflow(long blockSize) throws IOException {
writer.addElement(GenericRow.of(0));
}
})
.isInstanceOf(ArithmeticException.class)
.hasMessage("integer overflow");
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("file.block-size")
.hasMessageContaining(Long.toString(blockSize))
.hasMessageContaining("32 bytes")
.hasMessageContaining("1 gb");
}
}

@ParameterizedTest
@ValueSource(longs = {32L, 33L, 1073741824L})
void testFileBlockSizeAtAvroRangeBounds(long blockSize) throws IOException {
Options options = new Options();
options.setString("file.block-size", Long.toString(blockSize));
FileFormat format = FileFormat.fromIdentifier("avro", options);
RowType rowType = DataTypes.ROW(DataTypes.INT().notNull()).notNull();
LocalFileIO fileIO = LocalFileIO.create();
Path file = new Path(new Path(tempPath.toUri()), UUID.randomUUID().toString());

try (PositionOutputStream out = fileIO.newOutputStream(file, false);
FormatWriter writer = format.createWriterFactory(rowType).create(out, "null")) {
writer.addElement(GenericRow.of(0));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -622,7 +622,8 @@ class DeletionVectorTest extends PaimonSparkTestBase with AdaptiveSparkPlanHelpe

test("Paimon deletionVector: select with format filter push down") {
val format = Random.shuffle(Seq("parquet", "orc", "avro")).head
val blockSize = Random.nextInt(10240) + 1
// Avro rejects a block size below 32 bytes.
val blockSize = Random.nextInt(10240) + 32
spark.sql(s"""
|CREATE TABLE T (id INT, name STRING)
|TBLPROPERTIES (
Expand Down
Loading