This is an automated email from the ASF dual-hosted git repository.
ahmedabu98 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/beam.git
The following commit(s) were added to refs/heads/master by this push:
new 668c6a38f37 (IcebergIO) bugfix: propagate config properties to
RecordWriter (#39250)
668c6a38f37 is described below
commit 668c6a38f37191e9d5e92be939d9da7e18542a3c
Author: Claire McGinty <[email protected]>
AuthorDate: Thu Jul 9 23:11:47 2026 -0400
(IcebergIO) bugfix: propagate config properties to RecordWriter (#39250)
* (IcebergIO) bugfix: propagate config properties to RecordWriter
* Refactor: add new writeProperties option to
IcebergWriteSchemaTransformProvider
---
.../org/apache/beam/sdk/io/iceberg/IcebergIO.java | 15 ++++-
.../IcebergWriteSchemaTransformProvider.java | 12 ++++
.../apache/beam/sdk/io/iceberg/RecordWriter.java | 24 ++++++--
.../beam/sdk/io/iceberg/RecordWriterManager.java | 14 ++++-
.../sdk/io/iceberg/WriteDirectRowsToFiles.java | 15 +++--
.../sdk/io/iceberg/WriteGroupedRowsToFiles.java | 17 ++++--
.../io/iceberg/WritePartitionedRowsToFiles.java | 17 ++++--
.../beam/sdk/io/iceberg/WriteToDestinations.java | 29 +++++++--
.../beam/sdk/io/iceberg/WriteToPartitions.java | 9 ++-
.../sdk/io/iceberg/WriteUngroupedRowsToFiles.java | 16 +++--
.../sdk/io/iceberg/RecordWriterManagerTest.java | 69 ++++++++++++++++++++++
11 files changed, 207 insertions(+), 30 deletions(-)
diff --git
a/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/IcebergIO.java
b/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/IcebergIO.java
index 5c5f934ea20..04feea5037d 100644
---
a/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/IcebergIO.java
+++
b/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/IcebergIO.java
@@ -22,6 +22,7 @@ import static
org.apache.beam.sdk.util.Preconditions.checkStateNotNull;
import com.google.auto.value.AutoValue;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
import org.apache.beam.sdk.annotations.Internal;
import org.apache.beam.sdk.io.Read;
import org.apache.beam.sdk.schemas.Schema;
@@ -406,6 +407,8 @@ public class IcebergIO {
abstract boolean getAutoSharding();
+ abstract @Nullable Map<String, String> getWriteProperties();
+
abstract Builder toBuilder();
@AutoValue.Builder
@@ -424,6 +427,8 @@ public class IcebergIO {
abstract Builder setAutoSharding(boolean autoSharding);
+ abstract Builder setWriteProperties(Map<String, String> writeProperties);
+
abstract WriteRows build();
}
@@ -474,6 +479,10 @@ public class IcebergIO {
return toBuilder().setAutoSharding(true).build();
}
+ public WriteRows withWriteProperties(Map<String, String> writeProperties) {
+ return toBuilder().setWriteProperties(writeProperties).build();
+ }
+
@Override
public IcebergWriteResult expand(PCollection<Row> input) {
List<?> allToArgs = Arrays.asList(getTableIdentifier(),
getDynamicDestinations());
@@ -509,7 +518,8 @@ public class IcebergIO {
getCatalogConfig(),
destinations,
getTriggeringFrequency(),
- getDirectWriteByteLimit()));
+ getDirectWriteByteLimit(),
+ getWriteProperties()));
case HASH:
return input
.apply(
@@ -521,7 +531,8 @@ public class IcebergIO {
getCatalogConfig(),
destinations,
getTriggeringFrequency(),
- getAutoSharding()));
+ getAutoSharding(),
+ getWriteProperties()));
default:
throw new UnsupportedOperationException(
"Unsupported distribution mode: " + getDistributionMode());
diff --git
a/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/IcebergWriteSchemaTransformProvider.java
b/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/IcebergWriteSchemaTransformProvider.java
index 8db4fb77a8e..0ae9d5fb0ec 100644
---
a/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/IcebergWriteSchemaTransformProvider.java
+++
b/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/IcebergWriteSchemaTransformProvider.java
@@ -158,6 +158,11 @@ public class IcebergWriteSchemaTransformProvider
+ "during high-throughput writes. Only available with 'hash'
distribution mode.")
public abstract @Nullable Boolean getAutosharding();
+ @SchemaFieldDescription(
+ "Properties applied to the underlying file writer (e.g. Parquet write
properties like "
+ + "'write.parquet.bloom-filter-enabled.column.<col>').")
+ public abstract @Nullable Map<String, String> getWriteProperties();
+
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder setTable(String table);
@@ -188,6 +193,8 @@ public class IcebergWriteSchemaTransformProvider
public abstract Builder setAutosharding(Boolean autosharding);
+ public abstract Builder setWriteProperties(Map<String, String>
writeProperties);
+
public abstract Configuration build();
}
@@ -279,6 +286,11 @@ public class IcebergWriteSchemaTransformProvider
writeTransform = writeTransform.withAutosharding();
}
+ @Nullable Map<String, String> writeProperties =
configuration.getWriteProperties();
+ if (writeProperties != null && !writeProperties.isEmpty()) {
+ writeTransform = writeTransform.withWriteProperties(writeProperties);
+ }
+
// TODO: support dynamic destinations
IcebergWriteResult result = rows.apply(writeTransform);
diff --git
a/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/RecordWriter.java
b/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/RecordWriter.java
index fd3d5d63327..c3b63b2a336 100644
---
a/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/RecordWriter.java
+++
b/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/RecordWriter.java
@@ -18,6 +18,7 @@
package org.apache.beam.sdk.io.iceberg;
import java.io.IOException;
+import java.util.Map;
import org.apache.beam.sdk.metrics.Counter;
import org.apache.beam.sdk.metrics.Metrics;
import org.apache.iceberg.DataFile;
@@ -34,6 +35,7 @@ import org.apache.iceberg.encryption.EncryptionKeyMetadata;
import org.apache.iceberg.io.DataWriter;
import org.apache.iceberg.io.OutputFile;
import org.apache.iceberg.parquet.Parquet;
+import org.checkerframework.checker.nullness.qual.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -54,11 +56,22 @@ class RecordWriter {
catalog.loadTable(destination.getTableIdentifier()),
destination.getFileFormat(),
filename,
- partitionKey);
+ partitionKey,
+ null);
}
RecordWriter(Table table, FileFormat fileFormat, String filename, StructLike
partitionKey)
throws IOException {
+ this(table, fileFormat, filename, partitionKey, null);
+ }
+
+ RecordWriter(
+ Table table,
+ FileFormat fileFormat,
+ String filename,
+ StructLike partitionKey,
+ @Nullable Map<String, String> writeProperties)
+ throws IOException {
this.table = table;
this.fileFormat = fileFormat;
@@ -91,14 +104,17 @@ class RecordWriter {
.build();
break;
case PARQUET:
- icebergDataWriter =
+ Parquet.DataWriteBuilder parquetBuilder =
Parquet.writeData(outputFile)
.forTable(table)
.createWriterFunc(GenericParquetWriter::create)
.withPartition(partitionKey)
.withKeyMetadata(keyMetadata)
- .overwrite()
- .build();
+ .overwrite();
+ if (writeProperties != null && !writeProperties.isEmpty()) {
+ parquetBuilder.setAll(writeProperties);
+ }
+ icebergDataWriter = parquetBuilder.build();
break;
case ORC:
throw new UnsupportedOperationException("ORC file format not currently
supported.");
diff --git
a/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/RecordWriterManager.java
b/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/RecordWriterManager.java
index d5439ad8383..6893c743f43 100644
---
a/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/RecordWriterManager.java
+++
b/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/RecordWriterManager.java
@@ -202,7 +202,8 @@ class RecordWriterManager implements AutoCloseable {
table,
icebergDestination.getFileFormat(),
filePrefix + "_" + stateToken + "_" + recordIndex,
- partitionKey);
+ partitionKey,
+ writeProperties);
openWriters++;
return writer;
} catch (IOException e) {
@@ -253,6 +254,7 @@ class RecordWriterManager implements AutoCloseable {
private final String filePrefix;
private final long maxFileSize;
private final int maxNumWriters;
+ private final @Nullable Map<String, String> writeProperties;
@VisibleForTesting int openWriters = 0;
@VisibleForTesting
@@ -265,10 +267,20 @@ class RecordWriterManager implements AutoCloseable {
RecordWriterManager(
IcebergCatalogConfig catalogConfig, String filePrefix, long maxFileSize,
int maxNumWriters) {
+ this(catalogConfig, filePrefix, maxFileSize, maxNumWriters, null);
+ }
+
+ RecordWriterManager(
+ IcebergCatalogConfig catalogConfig,
+ String filePrefix,
+ long maxFileSize,
+ int maxNumWriters,
+ @Nullable Map<String, String> writeProperties) {
this.catalogConfig = catalogConfig;
this.filePrefix = filePrefix;
this.maxFileSize = maxFileSize;
this.maxNumWriters = maxNumWriters;
+ this.writeProperties = writeProperties;
}
/**
diff --git
a/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/WriteDirectRowsToFiles.java
b/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/WriteDirectRowsToFiles.java
index 5cf095dc3c3..e03085e6be7 100644
---
a/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/WriteDirectRowsToFiles.java
+++
b/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/WriteDirectRowsToFiles.java
@@ -40,16 +40,19 @@ class WriteDirectRowsToFiles
private final IcebergCatalogConfig catalogConfig;
private final String filePrefix;
private final long maxBytesPerFile;
+ private final @Nullable Map<String, String> writeProperties;
WriteDirectRowsToFiles(
IcebergCatalogConfig catalogConfig,
DynamicDestinations dynamicDestinations,
String filePrefix,
- long maxBytesPerFile) {
+ long maxBytesPerFile,
+ @Nullable Map<String, String> writeProperties) {
this.catalogConfig = catalogConfig;
this.dynamicDestinations = dynamicDestinations;
this.filePrefix = filePrefix;
this.maxBytesPerFile = maxBytesPerFile;
+ this.writeProperties = writeProperties;
}
@Override
@@ -57,7 +60,7 @@ class WriteDirectRowsToFiles
return input.apply(
ParDo.of(
new WriteDirectRowsToFilesDoFn(
- catalogConfig, dynamicDestinations, maxBytesPerFile,
filePrefix)));
+ catalogConfig, dynamicDestinations, maxBytesPerFile,
filePrefix, writeProperties)));
}
private static class WriteDirectRowsToFilesDoFn extends DoFn<KV<String,
Row>, FileWriteResult> {
@@ -66,24 +69,28 @@ class WriteDirectRowsToFiles
private final IcebergCatalogConfig catalogConfig;
private final String filePrefix;
private final long maxFileSize;
+ private final @Nullable Map<String, String> writeProperties;
private transient @Nullable RecordWriterManager recordWriterManager;
WriteDirectRowsToFilesDoFn(
IcebergCatalogConfig catalogConfig,
DynamicDestinations dynamicDestinations,
long maxFileSize,
- String filePrefix) {
+ String filePrefix,
+ @Nullable Map<String, String> writeProperties) {
this.catalogConfig = catalogConfig;
this.dynamicDestinations = dynamicDestinations;
this.filePrefix = filePrefix;
this.maxFileSize = maxFileSize;
+ this.writeProperties = writeProperties;
this.recordWriterManager = null;
}
@StartBundle
public void startBundle() {
recordWriterManager =
- new RecordWriterManager(catalogConfig, filePrefix, maxFileSize,
Integer.MAX_VALUE);
+ new RecordWriterManager(
+ catalogConfig, filePrefix, maxFileSize, Integer.MAX_VALUE,
writeProperties);
}
@ProcessElement
diff --git
a/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/WriteGroupedRowsToFiles.java
b/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/WriteGroupedRowsToFiles.java
index b16496240d1..e74715a7eeb 100644
---
a/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/WriteGroupedRowsToFiles.java
+++
b/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/WriteGroupedRowsToFiles.java
@@ -18,6 +18,7 @@
package org.apache.beam.sdk.io.iceberg;
import java.util.List;
+import java.util.Map;
import org.apache.beam.sdk.transforms.DoFn;
import org.apache.beam.sdk.transforms.PTransform;
import org.apache.beam.sdk.transforms.ParDo;
@@ -30,6 +31,7 @@ import org.apache.beam.sdk.values.Row;
import org.apache.beam.sdk.values.WindowedValue;
import org.apache.beam.sdk.values.WindowedValues;
import
org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.base.Preconditions;
+import org.checkerframework.checker.nullness.qual.Nullable;
class WriteGroupedRowsToFiles
extends PTransform<
@@ -39,16 +41,19 @@ class WriteGroupedRowsToFiles
private final DynamicDestinations dynamicDestinations;
private final IcebergCatalogConfig catalogConfig;
private final String filePrefix;
+ private final @Nullable Map<String, String> writeProperties;
WriteGroupedRowsToFiles(
IcebergCatalogConfig catalogConfig,
DynamicDestinations dynamicDestinations,
String filePrefix,
- long maxBytesPerFile) {
+ long maxBytesPerFile,
+ @Nullable Map<String, String> writeProperties) {
this.catalogConfig = catalogConfig;
this.dynamicDestinations = dynamicDestinations;
this.filePrefix = filePrefix;
this.maxBytesPerFile = maxBytesPerFile;
+ this.writeProperties = writeProperties;
}
@Override
@@ -57,7 +62,7 @@ class WriteGroupedRowsToFiles
return input.apply(
ParDo.of(
new WriteGroupedRowsToFilesDoFn(
- catalogConfig, dynamicDestinations, maxBytesPerFile,
filePrefix)));
+ catalogConfig, dynamicDestinations, maxBytesPerFile,
filePrefix, writeProperties)));
}
private static class WriteGroupedRowsToFilesDoFn
@@ -67,16 +72,19 @@ class WriteGroupedRowsToFiles
private final IcebergCatalogConfig catalogConfig;
private final String filePrefix;
private final long maxFileSize;
+ private final @Nullable Map<String, String> writeProperties;
WriteGroupedRowsToFilesDoFn(
IcebergCatalogConfig catalogConfig,
DynamicDestinations dynamicDestinations,
long maxFileSize,
- String filePrefix) {
+ String filePrefix,
+ @Nullable Map<String, String> writeProperties) {
this.catalogConfig = catalogConfig;
this.dynamicDestinations = dynamicDestinations;
this.filePrefix = filePrefix;
this.maxFileSize = maxFileSize;
+ this.writeProperties = writeProperties;
}
@ProcessElement
@@ -93,7 +101,8 @@ class WriteGroupedRowsToFiles
WindowedValues.of(destination, window.maxTimestamp(), window,
paneInfo);
RecordWriterManager writer;
try (RecordWriterManager openWriter =
- new RecordWriterManager(catalogConfig, filePrefix, maxFileSize,
Integer.MAX_VALUE)) {
+ new RecordWriterManager(
+ catalogConfig, filePrefix, maxFileSize, Integer.MAX_VALUE,
writeProperties)) {
writer = openWriter;
for (Row e : element.getValue()) {
writer.write(windowedDestination, e);
diff --git
a/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/WritePartitionedRowsToFiles.java
b/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/WritePartitionedRowsToFiles.java
index 8b4ae0863f7..84e7d05b124 100644
---
a/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/WritePartitionedRowsToFiles.java
+++
b/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/WritePartitionedRowsToFiles.java
@@ -59,14 +59,17 @@ class WritePartitionedRowsToFiles
private final DynamicDestinations dynamicDestinations;
private final IcebergCatalogConfig catalogConfig;
private final String filePrefix;
+ private final @Nullable Map<String, String> writeProperties;
WritePartitionedRowsToFiles(
IcebergCatalogConfig catalogConfig,
DynamicDestinations dynamicDestinations,
- String filePrefix) {
+ String filePrefix,
+ @Nullable Map<String, String> writeProperties) {
this.catalogConfig = catalogConfig;
this.dynamicDestinations = dynamicDestinations;
this.filePrefix = filePrefix;
+ this.writeProperties = writeProperties;
}
@Override
@@ -78,7 +81,9 @@ class WritePartitionedRowsToFiles
.getElemCoder())
.getSchema();
return input.apply(
- ParDo.of(new WriteDoFn(catalogConfig, dynamicDestinations, filePrefix,
dataSchema)));
+ ParDo.of(
+ new WriteDoFn(
+ catalogConfig, dynamicDestinations, filePrefix, dataSchema,
writeProperties)));
}
private static class WriteDoFn extends DoFn<KV<Row, Iterable<Row>>,
FileWriteResult> {
@@ -87,6 +92,7 @@ class WritePartitionedRowsToFiles
private final IcebergCatalogConfig catalogConfig;
private final String filePrefix;
private final Schema dataSchema;
+ private final @Nullable Map<String, String> writeProperties;
private transient @MonotonicNonNull Map<TableIdentifier, Integer> specIds;
private transient @MonotonicNonNull Map<TableIdentifier, Map<String,
PartitionField>>
partitionFieldMaps;
@@ -95,11 +101,13 @@ class WritePartitionedRowsToFiles
IcebergCatalogConfig catalogConfig,
DynamicDestinations dynamicDestinations,
String filePrefix,
- Schema dataSchema) {
+ Schema dataSchema,
+ @Nullable Map<String, String> writeProperties) {
this.catalogConfig = catalogConfig;
this.dynamicDestinations = dynamicDestinations;
this.filePrefix = filePrefix;
this.dataSchema = dataSchema;
+ this.writeProperties = writeProperties;
}
@Setup
@@ -132,7 +140,8 @@ class WritePartitionedRowsToFiles
.addExtension(String.format("%s-%s", filePrefix,
UUID.randomUUID()));
RecordWriter writer =
- new RecordWriter(table, destination.getFileFormat(), fileName,
partitionData);
+ new RecordWriter(
+ table, destination.getFileFormat(), fileName, partitionData,
writeProperties);
try {
for (Row row : element.getValue()) {
Record record = IcebergUtils.beamRowToIcebergRecord(table.schema(),
row);
diff --git
a/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/WriteToDestinations.java
b/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/WriteToDestinations.java
index bea84fc826b..684ef350a20 100644
---
a/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/WriteToDestinations.java
+++
b/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/WriteToDestinations.java
@@ -59,16 +59,19 @@ class WriteToDestinations extends
PTransform<PCollection<KV<String, Row>>, Icebe
private final @Nullable Duration triggeringFrequency;
private final String filePrefix;
private final @Nullable Integer directWriteByteLimit;
+ private final @Nullable Map<String, String> writeProperties;
WriteToDestinations(
IcebergCatalogConfig catalogConfig,
DynamicDestinations dynamicDestinations,
@Nullable Duration triggeringFrequency,
- @Nullable Integer directWriteByteLimit) {
+ @Nullable Integer directWriteByteLimit,
+ @Nullable Map<String, String> writeProperties) {
this.dynamicDestinations = dynamicDestinations;
this.catalogConfig = catalogConfig;
this.triggeringFrequency = triggeringFrequency;
this.directWriteByteLimit = directWriteByteLimit;
+ this.writeProperties = writeProperties;
// single unique prefix per write transform
this.filePrefix = UUID.randomUUID().toString();
}
@@ -112,7 +115,11 @@ class WriteToDestinations extends
PTransform<PCollection<KV<String, Row>>, Icebe
return groupedRecords.apply(
"WriteGroupedRows",
new WriteGroupedRowsToFiles(
- catalogConfig, dynamicDestinations, filePrefix,
DEFAULT_MAX_BYTES_PER_FILE));
+ catalogConfig,
+ dynamicDestinations,
+ filePrefix,
+ DEFAULT_MAX_BYTES_PER_FILE,
+ writeProperties));
}
private PCollection<FileWriteResult>
applyUserTriggering(PCollection<FileWriteResult> input) {
@@ -157,7 +164,11 @@ class WriteToDestinations extends
PTransform<PCollection<KV<String, Row>>, Icebe
largeBatches.apply(
"WriteDirectRowsToFiles",
new WriteDirectRowsToFiles(
- catalogConfig, dynamicDestinations, filePrefix,
DEFAULT_MAX_BYTES_PER_FILE));
+ catalogConfig,
+ dynamicDestinations,
+ filePrefix,
+ DEFAULT_MAX_BYTES_PER_FILE,
+ writeProperties));
PCollection<FileWriteResult> groupedFileWrites =
groupAndWriteRecords(smallBatches);
@@ -189,7 +200,11 @@ class WriteToDestinations extends
PTransform<PCollection<KV<String, Row>>, Icebe
input.apply(
"Fast-path write rows",
new WriteUngroupedRowsToFiles(
- catalogConfig, dynamicDestinations, filePrefix,
DEFAULT_MAX_BYTES_PER_FILE));
+ catalogConfig,
+ dynamicDestinations,
+ filePrefix,
+ DEFAULT_MAX_BYTES_PER_FILE,
+ writeProperties));
// Then write the rest by shuffling on the destination
PCollection<FileWriteResult> writeGroupedResult =
@@ -199,7 +214,11 @@ class WriteToDestinations extends
PTransform<PCollection<KV<String, Row>>, Icebe
.apply(
"Write remaining rows to files",
new WriteGroupedRowsToFiles(
- catalogConfig, dynamicDestinations, filePrefix,
DEFAULT_MAX_BYTES_PER_FILE));
+ catalogConfig,
+ dynamicDestinations,
+ filePrefix,
+ DEFAULT_MAX_BYTES_PER_FILE,
+ writeProperties));
return PCollectionList.of(writeUngroupedResult.getWrittenFiles())
.and(writeGroupedResult)
diff --git
a/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/WriteToPartitions.java
b/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/WriteToPartitions.java
index 310fa1bede4..84b9c6a9c0f 100644
---
a/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/WriteToPartitions.java
+++
b/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/WriteToPartitions.java
@@ -22,6 +22,7 @@ import static
org.apache.beam.sdk.values.TypeDescriptors.iterables;
import static org.apache.beam.sdk.values.TypeDescriptors.kvs;
import static org.apache.beam.sdk.values.TypeDescriptors.rows;
+import java.util.Map;
import java.util.UUID;
import org.apache.beam.sdk.coders.IterableCoder;
import org.apache.beam.sdk.coders.KvCoder;
@@ -46,18 +47,21 @@ class WriteToPartitions extends
PTransform<PCollection<KV<Row, Row>>, IcebergWri
private final @Nullable Duration triggeringFrequency;
private final String filePrefix;
private final boolean autoSharding;
+ private final @Nullable Map<String, String> writeProperties;
WriteToPartitions(
IcebergCatalogConfig catalogConfig,
DynamicDestinations dynamicDestinations,
@Nullable Duration triggeringFrequency,
- boolean autoSharding) {
+ boolean autoSharding,
+ @Nullable Map<String, String> writeProperties) {
this.dynamicDestinations = dynamicDestinations;
this.catalogConfig = catalogConfig;
this.triggeringFrequency = triggeringFrequency;
// single unique prefix per write transform
this.filePrefix = UUID.randomUUID().toString();
this.autoSharding = autoSharding;
+ this.writeProperties = writeProperties;
}
private PCollection<KV<Row, Iterable<Row>>>
groupByPartition(PCollection<KV<Row, Row>> input) {
@@ -95,7 +99,8 @@ class WriteToPartitions extends
PTransform<PCollection<KV<Row, Row>>, IcebergWri
PCollection<FileWriteResult> writtenFiles =
groupedRows.apply(
- new WritePartitionedRowsToFiles(catalogConfig,
dynamicDestinations, filePrefix));
+ new WritePartitionedRowsToFiles(
+ catalogConfig, dynamicDestinations, filePrefix,
writeProperties));
if (IcebergUtils.isUnbounded(input) && triggeringFrequency != null) {
writtenFiles =
diff --git
a/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/WriteUngroupedRowsToFiles.java
b/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/WriteUngroupedRowsToFiles.java
index ff9a98c6200..7c780e6395d 100644
---
a/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/WriteUngroupedRowsToFiles.java
+++
b/sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/WriteUngroupedRowsToFiles.java
@@ -72,16 +72,19 @@ class WriteUngroupedRowsToFiles
private final DynamicDestinations dynamicDestinations;
private final IcebergCatalogConfig catalogConfig;
private final long maxBytesPerFile;
+ private final @Nullable Map<String, String> writeProperties;
WriteUngroupedRowsToFiles(
IcebergCatalogConfig catalogConfig,
DynamicDestinations dynamicDestinations,
String filePrefix,
- long maxBytesPerFile) {
+ long maxBytesPerFile,
+ @Nullable Map<String, String> writeProperties) {
this.catalogConfig = catalogConfig;
this.dynamicDestinations = dynamicDestinations;
this.filePrefix = filePrefix;
this.maxBytesPerFile = maxBytesPerFile;
+ this.writeProperties = writeProperties;
}
@Override
@@ -95,7 +98,8 @@ class WriteUngroupedRowsToFiles
dynamicDestinations,
filePrefix,
DEFAULT_MAX_WRITERS_PER_BUNDLE,
- maxBytesPerFile))
+ maxBytesPerFile,
+ writeProperties))
.withOutputTags(
WRITTEN_FILES_TAG,
TupleTagList.of(ImmutableList.of(WRITTEN_ROWS_TAG,
SPILLED_ROWS_TAG))));
@@ -191,6 +195,7 @@ class WriteUngroupedRowsToFiles
private final long maxFileSize;
private final DynamicDestinations dynamicDestinations;
private final IcebergCatalogConfig catalogConfig;
+ private final @Nullable Map<String, String> writeProperties;
private transient @Nullable RecordWriterManager recordWriterManager;
private int spilledShardNumber;
@@ -199,18 +204,21 @@ class WriteUngroupedRowsToFiles
DynamicDestinations dynamicDestinations,
String filename,
int maximumWritersPerBundle,
- long maxFileSize) {
+ long maxFileSize,
+ @Nullable Map<String, String> writeProperties) {
this.catalogConfig = catalogConfig;
this.dynamicDestinations = dynamicDestinations;
this.filename = filename;
this.maxWritersPerBundle = maximumWritersPerBundle;
this.maxFileSize = maxFileSize;
+ this.writeProperties = writeProperties;
}
@StartBundle
public void startBundle() {
recordWriterManager =
- new RecordWriterManager(catalogConfig, filename, maxFileSize,
maxWritersPerBundle);
+ new RecordWriterManager(
+ catalogConfig, filename, maxFileSize, maxWritersPerBundle,
writeProperties);
this.spilledShardNumber =
ThreadLocalRandom.current().nextInt(SPILLED_RECORD_SHARDING_FACTOR);
}
diff --git
a/sdks/java/io/iceberg/src/test/java/org/apache/beam/sdk/io/iceberg/RecordWriterManagerTest.java
b/sdks/java/io/iceberg/src/test/java/org/apache/beam/sdk/io/iceberg/RecordWriterManagerTest.java
index 390e8d87af2..821fb2ac7b2 100644
---
a/sdks/java/io/iceberg/src/test/java/org/apache/beam/sdk/io/iceberg/RecordWriterManagerTest.java
+++
b/sdks/java/io/iceberg/src/test/java/org/apache/beam/sdk/io/iceberg/RecordWriterManagerTest.java
@@ -53,6 +53,8 @@ import org.apache.beam.sdk.values.WindowedValue;
import org.apache.beam.sdk.values.WindowedValues;
import
org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.ImmutableMap;
import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.Path;
import org.apache.iceberg.AppendFiles;
import org.apache.iceberg.DataFile;
import org.apache.iceberg.FileFormat;
@@ -77,6 +79,10 @@ import org.apache.iceberg.types.Conversions;
import org.apache.iceberg.types.Type;
import org.apache.iceberg.types.Types;
import org.apache.iceberg.util.DateTimeUtil;
+import org.apache.parquet.hadoop.ParquetFileReader;
+import org.apache.parquet.hadoop.metadata.BlockMetaData;
+import org.apache.parquet.hadoop.metadata.ColumnChunkMetaData;
+import org.apache.parquet.hadoop.util.HadoopInputFile;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
@@ -1300,4 +1306,67 @@ public class RecordWriterManagerTest {
assertTrue(
"Bundle 2 should produce data files",
bundle2.getSerializableDataFiles().containsKey(dest));
}
+
+ @Test
+ public void testWritePropertiesAppliedToParquetFiles() throws IOException {
+ Schema bloomSchema =
+
Schema.builder().addInt32Field("colWithBf").addInt32Field("colWithoutBf").build();
+ org.apache.iceberg.Schema icebergBloomSchema =
+ IcebergUtils.beamSchemaToIcebergSchema(bloomSchema);
+
+ TableIdentifier tableId = TableIdentifier.of("default",
"test_write_properties");
+ warehouse.createTable(tableId, icebergBloomSchema);
+
+ Map<String, String> writeProperties =
+ ImmutableMap.of(
+ "write.parquet.bloom-filter-enabled.column.colWithBf", "true",
+ "write.parquet.bloom-filter-enabled.column.colWithoutBf", "false");
+
+ IcebergDestination destination =
+ IcebergDestination.builder()
+ .setTableIdentifier(tableId)
+ .setFileFormat(FileFormat.PARQUET)
+ .build();
+ WindowedValue<IcebergDestination> dest =
WindowedValues.valueInGlobalWindow(destination);
+
+ RecordWriterManager writerManager =
+ new RecordWriterManager(catalogConfig, "test_bloom", Long.MAX_VALUE,
3, writeProperties);
+ for (int i = 0; i < 10; i++) {
+ Row row = Row.withSchema(bloomSchema).addValues(i, 100 + i).build();
+ assertTrue(writerManager.write(dest, row));
+ }
+ writerManager.close();
+
+ List<SerializableDataFile> dataFiles =
writerManager.getSerializableDataFiles().get(dest);
+ assertEquals(1, dataFiles.size());
+
+ String dataFilePath = dataFiles.get(0).getPath();
+ assertNotNull(dataFilePath);
+
+ try (ParquetFileReader reader =
+ ParquetFileReader.open(
+ HadoopInputFile.fromPath(new Path(dataFilePath), new
Configuration()))) {
+ List<BlockMetaData> blocks = reader.getFooter().getBlocks();
+ assertFalse("Parquet file should have at least one row group",
blocks.isEmpty());
+
+ for (int i = 0; i < blocks.size(); i++) {
+ BlockMetaData block = blocks.get(i);
+ assertEquals("Each row group should have 2 columns", 2,
block.getColumns().size());
+
+ for (ColumnChunkMetaData col : block.getColumns()) {
+ boolean hasBloomFilter = col.getBloomFilterOffset() > 0;
+ String colName = col.getPath().toDotString();
+ if (colName.equals("colWithBf")) {
+ assertTrue(
+ "Column 'colWithBf' in row group " + i + " should have a bloom
filter",
+ hasBloomFilter);
+ } else if (colName.equals("colWithoutBf")) {
+ assertFalse(
+ "Column 'colWithoutBf' in row group " + i + " should not have
a bloom filter",
+ hasBloomFilter);
+ }
+ }
+ }
+ }
+ }
}