-
Notifications
You must be signed in to change notification settings - Fork 4.6k
[IcebergIO] Spec aware file reserialization #39973
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,6 +28,7 @@ | |
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Objects; | ||
| import org.apache.beam.sdk.annotations.Internal; | ||
| import org.apache.beam.sdk.schemas.AutoValueSchema; | ||
| import org.apache.beam.sdk.schemas.annotations.DefaultSchema; | ||
| import org.apache.beam.sdk.schemas.annotations.SchemaFieldNumber; | ||
|
|
@@ -37,11 +38,14 @@ | |
| import org.apache.iceberg.FileMetadata; | ||
| import org.apache.iceberg.Metrics; | ||
| import org.apache.iceberg.PartitionSpec; | ||
| import org.apache.iceberg.SingleValueParser; | ||
| import org.apache.iceberg.SortOrder; | ||
| import org.apache.iceberg.StructLike; | ||
| import org.checkerframework.checker.nullness.qual.Nullable; | ||
|
|
||
| @DefaultSchema(AutoValueSchema.class) | ||
| @AutoValue | ||
| @Internal | ||
| public abstract class SerializableDeleteFile { | ||
| public static SerializableDeleteFile.Builder builder() { | ||
| return new AutoValue_SerializableDeleteFile.Builder(); | ||
|
|
@@ -62,7 +66,11 @@ public static SerializableDeleteFile.Builder builder() { | |
| @SchemaFieldNumber("4") | ||
| public abstract long getFileSizeInBytes(); | ||
|
|
||
| /** | ||
| * @deprecated Use {@link #getJsonPartition()} instead. | ||
| */ | ||
| @SchemaFieldNumber("5") | ||
| @Deprecated | ||
| public abstract String getPartitionPath(); | ||
|
|
||
| @SchemaFieldNumber("6") | ||
|
|
@@ -113,6 +121,9 @@ public static SerializableDeleteFile.Builder builder() { | |
| @SchemaFieldNumber("21") | ||
| public abstract @Nullable Long getFileSequenceNumber(); | ||
|
|
||
| @SchemaFieldNumber("22") | ||
| abstract @Nullable String getJsonPartition(); | ||
|
|
||
| @AutoValue.Builder | ||
| abstract static class Builder { | ||
| abstract Builder setContentType(FileContent content); | ||
|
|
@@ -127,6 +138,8 @@ abstract static class Builder { | |
|
|
||
| abstract Builder setPartitionPath(String partitionPath); | ||
|
|
||
| abstract Builder setJsonPartition(String jsonPartition); | ||
|
|
||
| abstract Builder setPartitionSpecId(int partitionSpec); | ||
|
|
||
| abstract Builder setSortOrderId(@Nullable Integer sortOrderId); | ||
|
|
@@ -163,14 +176,55 @@ abstract static class Builder { | |
| } | ||
|
|
||
| public static SerializableDeleteFile from( | ||
| DeleteFile deleteFile, String partitionPath, boolean includeMetrics) { | ||
| DeleteFile deleteFile, Map<Integer, PartitionSpec> specs) { | ||
| return from(deleteFile, specs, true); | ||
| } | ||
|
|
||
| /** | ||
| * Creates a {@link SerializableDeleteFile}, resolving the file's {@link PartitionSpec} by its own | ||
| * spec id. | ||
| * | ||
| * <p>Delete files reached from a scan task may carry a spec id that differs from the spec of the | ||
| * data file they apply to, so the lookup has to be per delete file rather than against a single | ||
| * "current" spec. | ||
| */ | ||
| public static SerializableDeleteFile from( | ||
| DeleteFile deleteFile, Map<Integer, PartitionSpec> specs, boolean includeMetrics) { | ||
| return from( | ||
| deleteFile, | ||
| checkStateNotNull( | ||
| specs.get(deleteFile.specId()), | ||
| "Could not create a SerializableDeleteFile because DeleteFile is written using a partition spec id '%s' that is not found in the provided specs: %s", | ||
| deleteFile.specId(), | ||
| specs.keySet()), | ||
| includeMetrics); | ||
| } | ||
|
|
||
| public static SerializableDeleteFile from(DeleteFile deleteFile, PartitionSpec spec) { | ||
| return from(deleteFile, spec, true); | ||
| } | ||
|
|
||
| public static SerializableDeleteFile from( | ||
| DeleteFile deleteFile, PartitionSpec spec, boolean includeMetrics) { | ||
| if (spec.specId() != deleteFile.specId()) { | ||
| throw new IllegalArgumentException( | ||
| String.format( | ||
| "Cannot serialize DeleteFile: its partition spec id %s does not match the provided " | ||
| + "spec id %s.", | ||
| deleteFile.specId(), spec.specId())); | ||
| } | ||
| // jsonPartition is the primary (handles evolved specs, special characters). | ||
| // partitionPath is the fallback for values that don't round-trip through JSON. | ||
| String jsonPartition = SingleValueParser.toJson(spec.partitionType(), deleteFile.partition()); | ||
| String partitionPath = spec.partitionToPath(deleteFile.partition()); | ||
|
|
||
| SerializableDeleteFile.Builder builder = | ||
| SerializableDeleteFile.builder() | ||
| .setLocation(deleteFile.location()) | ||
| .setFileFormat(deleteFile.format().name()) | ||
| .setFileSizeInBytes(deleteFile.fileSizeInBytes()) | ||
| .setPartitionPath(partitionPath) | ||
| .setJsonPartition(jsonPartition) | ||
| .setPartitionSpecId(deleteFile.specId()) | ||
| .setRecordCount(deleteFile.recordCount()) | ||
| .setColumnSizes(deleteFile.columnSizes()) | ||
|
|
@@ -228,7 +282,21 @@ public DeleteFile createDeleteFile( | |
| .withMetrics(metrics) | ||
| .withSplitOffsets(getSplitOffsets()) | ||
| .withEncryptionKeyMetadata(getKeyMetadata()) | ||
| .withPartitionPath(getPartitionPath()); | ||
| .withReferencedDataFile(getReferencedDataFile()); | ||
|
|
||
| @Nullable String jsonPartition = getJsonPartition(); | ||
| if (jsonPartition != null) { | ||
| try { | ||
| deleteFileBuilder = deleteFileBuilder.withPartition(partition(partitionSpec)); | ||
| } catch (RuntimeException e) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: seems like this catch might be too broad ? Should we either catch a Exception that is less broad or rethrow ?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Underlying utils throw different types of RuntimeExceptions (see similar comment here(#39705 (comment))) |
||
| // Some partition values (e.g. NaN / Infinity floating-point) don't round-trip through the | ||
| // JSON representation; fall back to the partition-path string | ||
| deleteFileBuilder = deleteFileBuilder.withPartitionPath(getPartitionPath()); | ||
| } | ||
| } else { | ||
| // Elements decoded from a pre-jsonPartition release carry only the partition path. | ||
| deleteFileBuilder = deleteFileBuilder.withPartitionPath(getPartitionPath()); | ||
| } | ||
|
|
||
| switch (getContentType()) { | ||
| case POSITION_DELETES: | ||
|
|
@@ -260,17 +328,22 @@ public DeleteFile createDeleteFile( | |
| "Unexpected content type for DeleteFile: " + getContentType()); | ||
| } | ||
|
|
||
| // needed for puffin files | ||
| // contentOffset / contentSizeInBytes really are Puffin-only: build() rejects a non-null value | ||
| // for either on any other format, and requires both (plus referencedDataFile) on Puffin. | ||
| if (getFileFormat().equalsIgnoreCase(FileFormat.PUFFIN.name())) { | ||
| deleteFileBuilder = | ||
| deleteFileBuilder | ||
| .withContentOffset(checkStateNotNull(getContentOffset())) | ||
| .withContentSizeInBytes(checkStateNotNull(getContentSizeInBytes())) | ||
| .withReferencedDataFile(checkStateNotNull(getReferencedDataFile())); | ||
| .withContentSizeInBytes(checkStateNotNull(getContentSizeInBytes())); | ||
| } | ||
| return deleteFileBuilder.build(); | ||
| } | ||
|
|
||
| private StructLike partition(PartitionSpec spec) { | ||
| return (StructLike) | ||
| SingleValueParser.fromJson(spec.partitionType(), checkStateNotNull(getJsonPartition())); | ||
| } | ||
|
|
||
| @Override | ||
| public final boolean equals(@Nullable Object o) { | ||
| if (this == o) { | ||
|
|
@@ -287,6 +360,7 @@ && getRecordCount() == that.getRecordCount() | |
| && getFileSizeInBytes() == that.getFileSizeInBytes() | ||
| && getPartitionPath().equals(that.getPartitionPath()) | ||
| && getPartitionSpecId() == that.getPartitionSpecId() | ||
| && Objects.equals(getJsonPartition(), that.getJsonPartition()) | ||
| && Objects.equals(getSortOrderId(), that.getSortOrderId()) | ||
| && Objects.equals(getEqualityFieldIds(), that.getEqualityFieldIds()) | ||
| && Objects.equals(getKeyMetadata(), that.getKeyMetadata()) | ||
|
|
@@ -314,6 +388,7 @@ public final int hashCode() { | |
| getRecordCount(), | ||
| getFileSizeInBytes(), | ||
| getPartitionPath(), | ||
| getJsonPartition(), | ||
| getPartitionSpecId(), | ||
| getSortOrderId(), | ||
| getEqualityFieldIds(), | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.