Skip to content
Open
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
54 changes: 54 additions & 0 deletions core/src/main/java/org/apache/iceberg/ColumnFile.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.iceberg;

import java.util.List;
import org.apache.iceberg.types.Types;

/** Information about a column file. */
interface ColumnFile {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Efficient Column Updates proposal had sequence_number at the column file level. Is that stale? ie are we dropping per-file granularity?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is stale, we decided not to have per-column file granularity for sequence numbers, not snapshots. Tracking will track a single snapshot ID for the latest column file, that behaves similarly as dv_snapshot_id, and for sequence number (e.g. to answer last_updated_sequence_number) we'll repurpose the data_sequence_number from Tracking. Note this requires us to bump that sequence number whenever adding a new column file.

Types.NestedField FIELD_IDS =
Types.NestedField.required(
161,
"field_ids",
Types.ListType.ofRequired(162, Types.IntegerType.get()),
"Field IDs this column file contains");
Types.NestedField LOCATION =
Types.NestedField.required(
163, "location", Types.StringType.get(), "Location of the column file");
Types.NestedField FILE_SIZE_IN_BYTES =
Types.NestedField.required(
164, "file_size_in_bytes", Types.LongType.get(), "Total column file size in bytes");

static Types.StructType schema() {
return Types.StructType.of(FIELD_IDS, LOCATION, FILE_SIZE_IN_BYTES);
}

/** Returns the field IDs contained in this column file. */
List<Integer> fieldIds();

/** Returns the location of the column file. */
String location();

/** Returns the total size of the column file in bytes. */
long fileSizeInBytes();

/** Copies this column file. */
ColumnFile copy();
}
176 changes: 176 additions & 0 deletions core/src/main/java/org/apache/iceberg/ColumnFileStruct.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.iceberg;

import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import org.apache.iceberg.avro.SupportsIndexProjection;
import org.apache.iceberg.relocated.com.google.common.base.MoreObjects;
import org.apache.iceberg.relocated.com.google.common.base.Preconditions;
import org.apache.iceberg.relocated.com.google.common.collect.Sets;
import org.apache.iceberg.types.Types;
import org.apache.iceberg.util.ArrayUtil;

/** Mutable {@link StructLike} implementation of {@link ColumnFile}. */
class ColumnFileStruct extends SupportsIndexProjection implements ColumnFile, Serializable {
private static final Types.StructType BASE_TYPE =
Types.StructType.of(ColumnFile.FIELD_IDS, ColumnFile.LOCATION, ColumnFile.FILE_SIZE_IN_BYTES);

private int[] fieldIds = null;
private String location = null;
private long fileSizeInBytes = -1L;

/** Used by internal readers to instantiate this class with a projection schema. */
ColumnFileStruct(Types.StructType projection) {
super(BASE_TYPE, projection);
}

ColumnFileStruct(List<Integer> fieldIds, String location, long fileSizeInBytes) {
super(BASE_TYPE.fields().size());
this.fieldIds = ArrayUtil.toIntArray(fieldIds);
this.location = location;
this.fileSizeInBytes = fileSizeInBytes;
}

/** Copy constructor. */
private ColumnFileStruct(ColumnFileStruct toCopy) {
super(toCopy);
this.fieldIds =
toCopy.fieldIds != null ? Arrays.copyOf(toCopy.fieldIds, toCopy.fieldIds.length) : null;
this.location = toCopy.location;
this.fileSizeInBytes = toCopy.fileSizeInBytes;
}

/** Constructor for Java serialization. */
ColumnFileStruct() {
super(BASE_TYPE.fields().size());
}

@Override
public List<Integer> fieldIds() {
return fieldIds != null ? ArrayUtil.toUnmodifiableIntList(fieldIds) : null;
}

@Override
public String location() {
return location;
}

@Override
public long fileSizeInBytes() {
return fileSizeInBytes;
}

@Override
public ColumnFile copy() {
return new ColumnFileStruct(this);
}

@Override
protected <T> T internalGet(int pos, Class<T> javaClass) {
return javaClass.cast(getByPos(pos));
}

private Object getByPos(int pos) {
switch (pos) {
case 0:
return fieldIds();
case 1:
return location;
case 2:
return fileSizeInBytes;
default:
throw new UnsupportedOperationException("Unknown field ordinal: " + pos);
}
}

@Override
@SuppressWarnings("unchecked")
protected <T> void internalSet(int pos, T value) {
switch (pos) {
case 0:
this.fieldIds = ArrayUtil.toIntArray((List<Integer>) value);
break;
case 1:
// always coerce to String for Serializable
this.location = value.toString();
break;
case 2:
this.fileSizeInBytes = (long) value;
break;
default:
// ignore the object, it must be from a newer version of the format
}
}

static Builder builder() {
return new Builder();
}

@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("field_ids", fieldIds)
Comment thread
gaborkaszab marked this conversation as resolved.
.add("location", location)
.add("file_size_in_bytes", fileSizeInBytes)
.toString();
}

static class Builder {
private List<Integer> fieldIds = null;
private String location = null;
private Long fileSizeInBytes = null;

Builder fieldIds(List<Integer> newFieldIds) {
Preconditions.checkArgument(newFieldIds != null, "Invalid field IDs: null");
Preconditions.checkArgument(!newFieldIds.isEmpty(), "Invalid field IDs: empty");
Preconditions.checkArgument(
Sets.newHashSet(newFieldIds).size() == newFieldIds.size(),
"Invalid field IDs: duplicateD IDs found: %s",
newFieldIds);
this.fieldIds = newFieldIds;
return this;
}

Builder location(String newLocation) {
Preconditions.checkArgument(newLocation != null, "Invalid location: null");
Preconditions.checkArgument(!newLocation.isEmpty(), "Invalid location: empty");
this.location = newLocation;
return this;
}

Builder fileSizeInBytes(long newFileSizeInBytes) {
Preconditions.checkArgument(
newFileSizeInBytes >= 0,
"Invalid file size in bytes: %s (must be >= 0)",
newFileSizeInBytes);
this.fileSizeInBytes = newFileSizeInBytes;
return this;
}

ColumnFile build() {
Preconditions.checkArgument(fieldIds != null, "Missing required value: fieldIds");
Preconditions.checkArgument(location != null, "Missing required value: location");
Preconditions.checkArgument(
fileSizeInBytes != null, "Missing required value: fileSizeInBytes");
return new ColumnFileStruct(fieldIds, location, fileSizeInBytes);
}
}
}
9 changes: 8 additions & 1 deletion core/src/main/java/org/apache/iceberg/TrackedFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ interface TrackedFile {
"equality_ids",
Types.ListType.ofRequired(136, Types.IntegerType.get()),
"Field ids used to determine row equality in equality delete files");
Types.NestedField COLUMN_FILES =
Types.NestedField.optional(
158, "column_files", Types.ListType.ofRequired(159, ColumnFile.schema()), "Column files");

static Types.StructType schemaWithContentStats(
Types.StructType partitionType, Types.StructType contentStatsType) {
Expand All @@ -114,7 +117,8 @@ static Types.StructType schemaWithContentStats(
MANIFEST_INFO,
KEY_METADATA,
SPLIT_OFFSETS,
EQUALITY_IDS);
EQUALITY_IDS,
COLUMN_FILES);
}

/** Returns the tracking information for this entry. */
Expand Down Expand Up @@ -165,6 +169,9 @@ static Types.StructType schemaWithContentStats(
/** Returns the set of field IDs used for equality comparison in equality delete files. */
List<Integer> equalityIds();

/** Returns the column files for this file. */
List<ColumnFile> columnFiles();

/** Copies this tracked file. */
TrackedFile copy();

Expand Down
6 changes: 4 additions & 2 deletions core/src/main/java/org/apache/iceberg/TrackedFileBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,8 @@ private static TrackedFile terminal(TrackedFile source, Tracking tracking) {
source.manifestInfo(),
source.keyMetadata(),
source.splitOffsets(),
source.equalityIds());
source.equalityIds(),
null);
}

private TrackedFileBuilder(FileContent contentType, long snapshotId) {
Expand Down Expand Up @@ -356,6 +357,7 @@ TrackedFile build() {
manifestInfo,
keyMetadata,
splitOffsets,
equalityIds);
equalityIds,
null);
}
}
27 changes: 25 additions & 2 deletions core/src/main/java/org/apache/iceberg/TrackedFileStruct.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,14 @@
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.iceberg.avro.SupportsIndexProjection;
import org.apache.iceberg.relocated.com.google.common.base.MoreObjects;
import org.apache.iceberg.relocated.com.google.common.collect.Lists;
import org.apache.iceberg.types.Type;
import org.apache.iceberg.types.Types;
import org.apache.iceberg.util.ArrayUtil;
Expand Down Expand Up @@ -66,7 +70,8 @@ public PartitionData copy() {
TrackedFile.MANIFEST_INFO,
TrackedFile.KEY_METADATA,
TrackedFile.SPLIT_OFFSETS,
TrackedFile.EQUALITY_IDS);
TrackedFile.EQUALITY_IDS,
TrackedFile.COLUMN_FILES);
Comment thread
gaborkaszab marked this conversation as resolved.

private FileContent contentType = null;
private int formatVersion = -1;
Expand All @@ -86,6 +91,7 @@ public PartitionData copy() {
private byte[] keyMetadata = null;
private long[] splitOffsets = null;
private int[] equalityIds = null;
private List<ColumnFile> columnFiles = null;

/** Used by internal readers to instantiate this class with a projection schema. */
TrackedFileStruct(Types.StructType projection) {
Expand Down Expand Up @@ -118,7 +124,8 @@ public PartitionData copy() {
ManifestInfo manifestInfo,
ByteBuffer keyMetadata,
List<Long> splitOffsets,
List<Integer> equalityIds) {
List<Integer> equalityIds,
List<ColumnFile> columnFiles) {
super(BASE_TYPE.fields().size());
this.tracking = tracking;
this.contentType = contentType;
Expand All @@ -139,6 +146,7 @@ public PartitionData copy() {
this.keyMetadata = ByteBuffers.toByteArray(keyMetadata);
this.splitOffsets = ArrayUtil.toLongArray(splitOffsets);
this.equalityIds = ArrayUtil.toIntArray(equalityIds);
this.columnFiles = columnFiles != null ? Lists.newArrayList(columnFiles) : null;
}

/** Copy constructor. */
Expand Down Expand Up @@ -176,6 +184,13 @@ private TrackedFileStruct(TrackedFileStruct toCopy, boolean withStats, Set<Integ
toCopy.equalityIds != null
? Arrays.copyOf(toCopy.equalityIds, toCopy.equalityIds.length)
: null;
this.columnFiles =
toCopy.columnFiles != null
? toCopy.columnFiles.stream()
.filter(Objects::nonNull)
.map(ColumnFile::copy)
.collect(Collectors.toList())
: null;
}

@Override
Expand Down Expand Up @@ -258,6 +273,11 @@ public List<Integer> equalityIds() {
return equalityIds != null ? ArrayUtil.toUnmodifiableIntList(equalityIds) : null;
}

@Override
public List<ColumnFile> columnFiles() {
return columnFiles != null ? Collections.unmodifiableList(columnFiles) : null;
}

@Override
public TrackedFile copy() {
return new TrackedFileStruct(this, true, null);
Expand Down Expand Up @@ -291,6 +311,7 @@ private Object getByPos(int pos) {
case 13 -> keyMetadata();
case 14 -> splitOffsets();
case 15 -> equalityIds();
case 16 -> columnFiles();
default -> throw new UnsupportedOperationException("Unknown field ordinal: " + pos);
};
}
Expand All @@ -316,6 +337,7 @@ protected <T> void internalSet(int pos, T value) {
case 13 -> this.keyMetadata = ByteBuffers.toByteArray((ByteBuffer) value);
case 14 -> this.splitOffsets = ArrayUtil.toLongArray((List<Long>) value);
case 15 -> this.equalityIds = ArrayUtil.toIntArray((List<Integer>) value);
case 16 -> this.columnFiles = (List<ColumnFile>) value;
default -> {
// ignore the object, it must be from a newer version of the format
}
Expand All @@ -341,6 +363,7 @@ public String toString() {
.add("key_metadata", keyMetadata == null ? "null" : "(redacted)")
.add("split_offsets", splitOffsets == null ? "null" : splitOffsets())
.add("equality_ids", equalityIds == null ? "null" : equalityIds())
.add("column_files", columnFiles)
.toString();
}
}
Loading
Loading