You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iceberg.apache.org by am...@apache.org on 2026/06/18 04:10:05 UTC

(iceberg) branch main updated: Core: Add writer_format_version field to TrackedFile (#16688)

This is an automated email from the ASF dual-hosted git repository.

amogh-jahagirdar pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg.git


The following commit(s) were added to refs/heads/main by this push:
     new 8924046f7a Core: Add writer_format_version field to TrackedFile (#16688)
8924046f7a is described below

commit 8924046f7ac5326e454d1cd48a6b30a6d16ba196
Author: gaborkaszab <ga...@gmail.com>
AuthorDate: Thu Jun 18 06:09:59 2026 +0200

    Core: Add writer_format_version field to TrackedFile (#16688)
---
 .../main/java/org/apache/iceberg/TrackedFile.java  |   7 ++
 .../java/org/apache/iceberg/TrackedFileStruct.java |  66 +++++++----
 .../java/org/apache/iceberg/TestTrackedFile.java   |   4 +-
 .../apache/iceberg/TestTrackedFileAdapters.java    |  13 ++-
 .../org/apache/iceberg/TestTrackedFileStruct.java  | 129 ++++++++++++++-------
 5 files changed, 150 insertions(+), 69 deletions(-)

diff --git a/core/src/main/java/org/apache/iceberg/TrackedFile.java b/core/src/main/java/org/apache/iceberg/TrackedFile.java
index 8a63359728..7dd7e1b3b4 100644
--- a/core/src/main/java/org/apache/iceberg/TrackedFile.java
+++ b/core/src/main/java/org/apache/iceberg/TrackedFile.java
@@ -35,6 +35,9 @@ interface TrackedFile {
           "content_type",
           Types.IntegerType.get(),
           "Type of content: 0=DATA, 2=EQUALITY_DELETES, 3=DATA_MANIFEST, 4=DELETE_MANIFEST");
+  Types.NestedField WRITER_FORMAT_VERSION =
+      Types.NestedField.required(
+          157, "writer_format_version", Types.IntegerType.get(), "Writer format version");
   Types.NestedField LOCATION =
       Types.NestedField.required(100, "location", Types.StringType.get(), "Location of the file");
   Types.NestedField FILE_FORMAT =
@@ -97,6 +100,7 @@ interface TrackedFile {
     return Types.StructType.of(
         TRACKING,
         CONTENT_TYPE,
+        WRITER_FORMAT_VERSION,
         LOCATION,
         FILE_FORMAT,
         RECORD_COUNT,
@@ -119,6 +123,9 @@ interface TrackedFile {
   /** Returns the type of content stored by this entry. */
   FileContent contentType();
 
+  /** Returns the version of the writer that wrote this file */
+  int writerFormatVersion();
+
   /** Returns the location of the file. */
   String location();
 
diff --git a/core/src/main/java/org/apache/iceberg/TrackedFileStruct.java b/core/src/main/java/org/apache/iceberg/TrackedFileStruct.java
index 4830f69d6b..3c350b8937 100644
--- a/core/src/main/java/org/apache/iceberg/TrackedFileStruct.java
+++ b/core/src/main/java/org/apache/iceberg/TrackedFileStruct.java
@@ -45,6 +45,7 @@ class TrackedFileStruct extends SupportsIndexProjection implements TrackedFile,
       Types.StructType.of(
           TrackedFile.TRACKING,
           TrackedFile.CONTENT_TYPE,
+          TrackedFile.WRITER_FORMAT_VERSION,
           TrackedFile.LOCATION,
           TrackedFile.FILE_FORMAT,
           TrackedFile.RECORD_COUNT,
@@ -68,6 +69,7 @@ class TrackedFileStruct extends SupportsIndexProjection implements TrackedFile,
           TrackedFile.EQUALITY_IDS);
 
   private FileContent contentType = null;
+  private int writerFormatVersion = -1;
   private String location = null;
   private FileFormat fileFormat = null;
   private long recordCount = -1L;
@@ -104,6 +106,7 @@ class TrackedFileStruct extends SupportsIndexProjection implements TrackedFile,
   TrackedFileStruct(
       Tracking tracking,
       FileContent contentType,
+      int writerFormatVersion,
       String location,
       FileFormat fileFormat,
       PartitionData partition,
@@ -112,6 +115,7 @@ class TrackedFileStruct extends SupportsIndexProjection implements TrackedFile,
     super(BASE_TYPE.fields().size());
     this.tracking = tracking;
     this.contentType = contentType;
+    this.writerFormatVersion = writerFormatVersion;
     this.location = location;
     this.fileFormat = fileFormat;
     this.recordCount = recordCount;
@@ -125,6 +129,7 @@ class TrackedFileStruct extends SupportsIndexProjection implements TrackedFile,
   private TrackedFileStruct(TrackedFileStruct toCopy, boolean withStats, Set<Integer> statsIds) {
     super(toCopy);
     this.contentType = toCopy.contentType;
+    this.writerFormatVersion = toCopy.writerFormatVersion;
     this.location = toCopy.location;
     this.fileFormat = toCopy.fileFormat;
     this.recordCount = toCopy.recordCount;
@@ -167,6 +172,11 @@ class TrackedFileStruct extends SupportsIndexProjection implements TrackedFile,
     return contentType;
   }
 
+  @Override
+  public int writerFormatVersion() {
+    return writerFormatVersion;
+  }
+
   @Override
   public String location() {
     return location;
@@ -254,30 +264,32 @@ class TrackedFileStruct extends SupportsIndexProjection implements TrackedFile,
       case 1:
         return contentType != null ? contentType.id() : null;
       case 2:
-        return location;
+        return writerFormatVersion;
       case 3:
-        return fileFormat != null ? fileFormat.toString() : null;
+        return location;
       case 4:
-        return recordCount;
+        return fileFormat != null ? fileFormat.toString() : null;
       case 5:
-        return fileSizeInBytes;
+        return recordCount;
       case 6:
-        return specId;
+        return fileSizeInBytes;
       case 7:
-        return partitionData;
+        return specId;
       case 8:
-        return contentStats;
+        return partitionData;
       case 9:
-        return sortOrderId;
+        return contentStats;
       case 10:
-        return deletionVector;
+        return sortOrderId;
       case 11:
-        return manifestInfo;
+        return deletionVector;
       case 12:
-        return keyMetadata();
+        return manifestInfo;
       case 13:
-        return splitOffsets();
+        return keyMetadata();
       case 14:
+        return splitOffsets();
+      case 15:
         return equalityIds();
       default:
         throw new UnsupportedOperationException("Unknown field ordinal: " + pos);
@@ -294,43 +306,46 @@ class TrackedFileStruct extends SupportsIndexProjection implements TrackedFile,
         this.contentType = FileContent.fromId((Integer) value);
         break;
       case 2:
-        // always coerce to String for Serializable
-        this.location = value.toString();
+        this.writerFormatVersion = (int) value;
         break;
       case 3:
-        this.fileFormat = FileFormat.fromString(value.toString());
+        // always coerce to String for Serializable
+        this.location = value.toString();
         break;
       case 4:
-        this.recordCount = (Long) value;
+        this.fileFormat = FileFormat.fromString(value.toString());
         break;
       case 5:
-        this.fileSizeInBytes = (Long) value;
+        this.recordCount = (long) value;
         break;
       case 6:
-        this.specId = (Integer) value;
+        this.fileSizeInBytes = (long) value;
         break;
       case 7:
-        this.partitionData = (PartitionData) value;
+        this.specId = (Integer) value;
         break;
       case 8:
-        this.contentStats = (ContentStats) value;
+        this.partitionData = (PartitionData) value;
         break;
       case 9:
-        this.sortOrderId = (Integer) value;
+        this.contentStats = (ContentStats) value;
         break;
       case 10:
-        this.deletionVector = (DeletionVector) value;
+        this.sortOrderId = (Integer) value;
         break;
       case 11:
-        this.manifestInfo = (ManifestInfo) value;
+        this.deletionVector = (DeletionVector) value;
         break;
       case 12:
-        this.keyMetadata = ByteBuffers.toByteArray((ByteBuffer) value);
+        this.manifestInfo = (ManifestInfo) value;
         break;
       case 13:
-        this.splitOffsets = ArrayUtil.toLongArray((List<Long>) value);
+        this.keyMetadata = ByteBuffers.toByteArray((ByteBuffer) value);
         break;
       case 14:
+        this.splitOffsets = ArrayUtil.toLongArray((List<Long>) value);
+        break;
+      case 15:
         this.equalityIds = ArrayUtil.toIntArray((List<Integer>) value);
         break;
       default:
@@ -342,6 +357,7 @@ class TrackedFileStruct extends SupportsIndexProjection implements TrackedFile,
   public String toString() {
     return MoreObjects.toStringHelper(this)
         .add("content", contentType != null ? contentType.lowerCaseName() : null)
+        .add("writer_format_version", writerFormatVersion)
         .add("location", location)
         .add("file_format", fileFormat)
         .add("record_count", recordCount)
diff --git a/core/src/test/java/org/apache/iceberg/TestTrackedFile.java b/core/src/test/java/org/apache/iceberg/TestTrackedFile.java
index 0d850ee4c8..2c4bf93e49 100644
--- a/core/src/test/java/org/apache/iceberg/TestTrackedFile.java
+++ b/core/src/test/java/org/apache/iceberg/TestTrackedFile.java
@@ -47,6 +47,7 @@ public class TestTrackedFile {
         .containsExactly(
             "tracking",
             "content_type",
+            "writer_format_version",
             "location",
             "file_format",
             "record_count",
@@ -69,7 +70,8 @@ public class TestTrackedFile {
 
     assertThat(fields)
         .extracting(Types.NestedField::fieldId)
-        .containsExactly(147, 134, 100, 101, 103, 104, 141, 102, 146, 140, 148, 150, 131, 132, 135);
+        .containsExactly(
+            147, 134, 157, 100, 101, 103, 104, 141, 102, 146, 140, 148, 150, 131, 132, 135);
   }
 
   @Test
diff --git a/core/src/test/java/org/apache/iceberg/TestTrackedFileAdapters.java b/core/src/test/java/org/apache/iceberg/TestTrackedFileAdapters.java
index 0a1dad8bdc..e13a342b8d 100644
--- a/core/src/test/java/org/apache/iceberg/TestTrackedFileAdapters.java
+++ b/core/src/test/java/org/apache/iceberg/TestTrackedFileAdapters.java
@@ -34,6 +34,7 @@ import org.junit.jupiter.params.provider.EnumSource;
 
 class TestTrackedFileAdapters {
 
+  private static final int WRITER_FORMAT_VERSION = 4;
   private static final String MANIFEST_LOCATION = "s3://bucket/table/manifest.parquet";
   private static final String DATA_FILE_LOCATION = "s3://bucket/data/file.parquet";
   private static final String DV_LOCATION = "s3://bucket/puffin/dv-file.bin";
@@ -91,6 +92,7 @@ class TestTrackedFileAdapters {
         new TrackedFileStruct(
             createTracking(),
             FileContent.DATA,
+            WRITER_FORMAT_VERSION,
             DATA_FILE_LOCATION,
             FileFormat.PARQUET,
             partition,
@@ -152,6 +154,7 @@ class TestTrackedFileAdapters {
         new TrackedFileStruct(
             createTracking(),
             FileContent.EQUALITY_DELETES,
+            WRITER_FORMAT_VERSION,
             "s3://bucket/eq-delete.avro",
             FileFormat.AVRO,
             partition,
@@ -222,6 +225,7 @@ class TestTrackedFileAdapters {
         new TrackedFileStruct(
             createTracking(),
             FileContent.DATA,
+            WRITER_FORMAT_VERSION,
             DATA_FILE_LOCATION,
             FileFormat.PARQUET,
             partition,
@@ -397,7 +401,14 @@ class TestTrackedFileAdapters {
   /** Minimal file with no tracking, used by the rejection and null-tracking tests. */
   private static TrackedFileStruct trackedFile(FileContent contentType) {
     return new TrackedFileStruct(
-        null, contentType, "s3://bucket/file", FileFormat.PARQUET, NO_PARTITION, 1L, 1L);
+        null,
+        contentType,
+        WRITER_FORMAT_VERSION,
+        "s3://bucket/file",
+        FileFormat.PARQUET,
+        NO_PARTITION,
+        1L,
+        1L);
   }
 
   private static TrackingStruct createTracking() {
diff --git a/core/src/test/java/org/apache/iceberg/TestTrackedFileStruct.java b/core/src/test/java/org/apache/iceberg/TestTrackedFileStruct.java
index 538590cc80..0b725a39fb 100644
--- a/core/src/test/java/org/apache/iceberg/TestTrackedFileStruct.java
+++ b/core/src/test/java/org/apache/iceberg/TestTrackedFileStruct.java
@@ -30,15 +30,50 @@ import org.apache.iceberg.types.Types;
 import org.junit.jupiter.api.Test;
 
 class TestTrackedFileStruct {
+  private static final int WRITER_FORMAT_VERSION_V4 = 4;
   private static final Types.StructType PARTITION_TYPE =
       Types.StructType.of(
           Types.NestedField.optional(1000, "id_bucket", Types.IntegerType.get()),
           Types.NestedField.optional(1001, "category", Types.StringType.get()));
 
+  // Ordinals looked up from the TrackedFile schema so tests don't hard-code positions.
+  private static final List<Types.NestedField> SCHEMA_FIELDS =
+      TrackedFile.schemaWithContentStats(Types.StructType.of(), Types.StructType.of()).fields();
+  private static final int TRACKING_ORDINAL = SCHEMA_FIELDS.indexOf(TrackedFile.TRACKING);
+  private static final int CONTENT_TYPE_ORDINAL = SCHEMA_FIELDS.indexOf(TrackedFile.CONTENT_TYPE);
+  private static final int WRITER_FORMAT_VERSION_ORDINAL =
+      SCHEMA_FIELDS.indexOf(TrackedFile.WRITER_FORMAT_VERSION);
+  private static final int LOCATION_ORDINAL = SCHEMA_FIELDS.indexOf(TrackedFile.LOCATION);
+  private static final int FILE_FORMAT_ORDINAL = SCHEMA_FIELDS.indexOf(TrackedFile.FILE_FORMAT);
+  private static final int RECORD_COUNT_ORDINAL = SCHEMA_FIELDS.indexOf(TrackedFile.RECORD_COUNT);
+  private static final int FILE_SIZE_IN_BYTES_ORDINAL =
+      SCHEMA_FIELDS.indexOf(TrackedFile.FILE_SIZE_IN_BYTES);
+  private static final int SPEC_ID_ORDINAL = SCHEMA_FIELDS.indexOf(TrackedFile.SPEC_ID);
+  // partition and content_stats are rebuilt with the supplied struct types inside
+  // schemaWithContentStats, so their ordinals are looked up by field ID.
+  private static final int PARTITION_ORDINAL = ordinalOf(TrackedFile.PARTITION_ID);
+  private static final int CONTENT_STATS_ORDINAL = ordinalOf(TrackedFile.CONTENT_STATS_ID);
+  private static final int SORT_ORDER_ID_ORDINAL = SCHEMA_FIELDS.indexOf(TrackedFile.SORT_ORDER_ID);
+  private static final int DELETION_VECTOR_ORDINAL =
+      SCHEMA_FIELDS.indexOf(TrackedFile.DELETION_VECTOR);
+  private static final int MANIFEST_INFO_ORDINAL = SCHEMA_FIELDS.indexOf(TrackedFile.MANIFEST_INFO);
+  private static final int KEY_METADATA_ORDINAL = SCHEMA_FIELDS.indexOf(TrackedFile.KEY_METADATA);
+  private static final int SPLIT_OFFSETS_ORDINAL = SCHEMA_FIELDS.indexOf(TrackedFile.SPLIT_OFFSETS);
+  private static final int EQUALITY_IDS_ORDINAL = SCHEMA_FIELDS.indexOf(TrackedFile.EQUALITY_IDS);
+
   // Ordinal of MetadataColumns.ROW_POSITION within TrackingStruct's BASE_TYPE,
   // which appends ROW_POSITION after the Tracking schema fields.
   private static final int MANIFEST_POS_ORDINAL = Tracking.schema().fields().size();
 
+  private static int ordinalOf(int fieldId) {
+    for (int i = 0; i < SCHEMA_FIELDS.size(); i++) {
+      if (SCHEMA_FIELDS.get(i).fieldId() == fieldId) {
+        return i;
+      }
+    }
+    throw new IllegalArgumentException("Field not found in TrackedFile schema: " + fieldId);
+  }
+
   @Test
   void testFieldAccess() {
     TrackedFileStruct file = new TrackedFileStruct();
@@ -63,24 +98,26 @@ class TestTrackedFileStruct {
             .minSequenceNumber(5L)
             .build();
 
-    file.set(0, tracking);
-    file.set(1, FileContent.EQUALITY_DELETES.id());
-    file.set(2, "s3://bucket/data/eq-delete.avro");
-    file.set(3, "avro");
-    file.set(4, 50L);
-    file.set(5, 512L);
-    file.set(6, 1);
-    file.set(9, 5);
-    file.set(10, dv);
-    file.set(11, info);
-    file.set(12, ByteBuffer.wrap(new byte[] {1, 2, 3}));
-    file.set(13, ImmutableList.of(100L, 200L));
-    file.set(14, ImmutableList.of(1, 2, 3));
+    file.set(TRACKING_ORDINAL, tracking);
+    file.set(CONTENT_TYPE_ORDINAL, FileContent.EQUALITY_DELETES.id());
+    file.set(WRITER_FORMAT_VERSION_ORDINAL, WRITER_FORMAT_VERSION_V4);
+    file.set(LOCATION_ORDINAL, "s3://bucket/data/eq-delete.avro");
+    file.set(FILE_FORMAT_ORDINAL, "avro");
+    file.set(RECORD_COUNT_ORDINAL, 50L);
+    file.set(FILE_SIZE_IN_BYTES_ORDINAL, 512L);
+    file.set(SPEC_ID_ORDINAL, 1);
+    file.set(SORT_ORDER_ID_ORDINAL, 5);
+    file.set(DELETION_VECTOR_ORDINAL, dv);
+    file.set(MANIFEST_INFO_ORDINAL, info);
+    file.set(KEY_METADATA_ORDINAL, ByteBuffer.wrap(new byte[] {1, 2, 3}));
+    file.set(SPLIT_OFFSETS_ORDINAL, ImmutableList.of(100L, 200L));
+    file.set(EQUALITY_IDS_ORDINAL, ImmutableList.of(1, 2, 3));
 
     assertThat(file.tracking()).isNotNull();
     assertThat(file.tracking().status()).isEqualTo(EntryStatus.ADDED);
     assertThat(file.tracking().snapshotId()).isEqualTo(42L);
     assertThat(file.contentType()).isEqualTo(FileContent.EQUALITY_DELETES);
+    assertThat(file.writerFormatVersion()).isEqualTo(WRITER_FORMAT_VERSION_V4);
     assertThat(file.location()).isEqualTo("s3://bucket/data/eq-delete.avro");
     assertThat(file.fileFormat()).isEqualTo(FileFormat.AVRO);
     assertThat(file.recordCount()).isEqualTo(50L);
@@ -105,12 +142,12 @@ class TestTrackedFileStruct {
     tracking.setManifestLocation("s3://bucket/metadata/manifest.avro");
     tracking.set(MANIFEST_POS_ORDINAL, 7L);
 
-    file.set(0, tracking);
-    file.set(1, FileContent.DATA.id());
-    file.set(2, "test");
-    file.set(3, "parquet");
-    file.set(4, 0L);
-    file.set(5, 0L);
+    file.set(TRACKING_ORDINAL, tracking);
+    file.set(CONTENT_TYPE_ORDINAL, FileContent.DATA.id());
+    file.set(LOCATION_ORDINAL, "test");
+    file.set(FILE_FORMAT_ORDINAL, "parquet");
+    file.set(RECORD_COUNT_ORDINAL, 0L);
+    file.set(FILE_SIZE_IN_BYTES_ORDINAL, 0L);
 
     assertThat(file.tracking().manifestLocation()).isEqualTo("s3://bucket/metadata/manifest.avro");
     assertThat(file.tracking().manifestPos()).isEqualTo(7L);
@@ -134,7 +171,7 @@ class TestTrackedFileStruct {
     PartitionData partition = newPartition(5, "books");
 
     TrackedFileStruct file = new TrackedFileStruct();
-    file.set(7, partition);
+    file.set(PARTITION_ORDINAL, partition);
 
     assertThat(file.partition()).isSameAs(partition);
     assertThat(file.partition().get(0, Integer.class)).isEqualTo(5);
@@ -145,7 +182,7 @@ class TestTrackedFileStruct {
   void partitionIsCopied() {
     PartitionData partition = newPartition(5, "books");
     TrackedFileStruct file = createFullTrackedFile();
-    file.set(7, partition);
+    file.set(PARTITION_ORDINAL, partition);
 
     TrackedFile copy = file.copy();
 
@@ -172,6 +209,7 @@ class TestTrackedFileStruct {
     assertThat(copy.sortOrderId()).isEqualTo(1);
     assertThat(copy.recordCount()).isEqualTo(100L);
     assertThat(copy.fileSizeInBytes()).isEqualTo(1024L);
+    assertThat(copy.writerFormatVersion()).isEqualTo(WRITER_FORMAT_VERSION_V4);
     assertThat(copy.keyMetadata()).isNotNull();
     assertThat(copy.splitOffsets()).containsExactly(50L);
     assertThat(copy.equalityIds()).isNull();
@@ -218,21 +256,24 @@ class TestTrackedFileStruct {
   @Test
   void testStructLikeSize() {
     TrackedFileStruct file = new TrackedFileStruct();
-    assertThat(file.size()).isEqualTo(15);
+    assertThat(file.size()).isEqualTo(16);
   }
 
   @Test
   void testStructLikeGetSet() {
     TrackedFileStruct file = new TrackedFileStruct();
 
-    file.set(1, FileContent.DATA.id());
-    assertThat(file.get(1, Integer.class)).isEqualTo(FileContent.DATA.id());
+    file.set(CONTENT_TYPE_ORDINAL, FileContent.DATA.id());
+    assertThat(file.get(CONTENT_TYPE_ORDINAL, Integer.class)).isEqualTo(FileContent.DATA.id());
+
+    file.set(LOCATION_ORDINAL, "test-location");
+    assertThat(file.get(LOCATION_ORDINAL, String.class)).isEqualTo("test-location");
 
-    file.set(2, "test-location");
-    assertThat(file.get(2, String.class)).isEqualTo("test-location");
+    file.set(RECORD_COUNT_ORDINAL, 999L);
+    assertThat(file.get(RECORD_COUNT_ORDINAL, Long.class)).isEqualTo(999L);
 
-    file.set(4, 999L);
-    assertThat(file.get(4, Long.class)).isEqualTo(999L);
+    file.set(SORT_ORDER_ID_ORDINAL, 3);
+    assertThat(file.get(SORT_ORDER_ID_ORDINAL, Integer.class)).isEqualTo(3);
   }
 
   @Test
@@ -265,12 +306,12 @@ class TestTrackedFileStruct {
   @Test
   void testContentStatsNullWhenNotSet() {
     TrackedFileStruct file = new TrackedFileStruct();
-    file.set(1, FileContent.DATA.id());
-    file.set(2, "test");
-    file.set(3, "parquet");
-    file.set(4, 0L);
-    file.set(5, 0L);
-    file.set(6, 0);
+    file.set(CONTENT_TYPE_ORDINAL, FileContent.DATA.id());
+    file.set(LOCATION_ORDINAL, "test");
+    file.set(FILE_FORMAT_ORDINAL, "parquet");
+    file.set(RECORD_COUNT_ORDINAL, 0L);
+    file.set(FILE_SIZE_IN_BYTES_ORDINAL, 0L);
+    file.set(SPEC_ID_ORDINAL, 0);
 
     assertThat(file.contentStats()).isNull();
   }
@@ -279,7 +320,7 @@ class TestTrackedFileStruct {
   void testAllFileContentTypesSupported() {
     for (FileContent content : FileContent.values()) {
       TrackedFileStruct file = new TrackedFileStruct();
-      file.set(1, content.id());
+      file.set(CONTENT_TYPE_ORDINAL, content.id());
       assertThat(file.contentType()).isEqualTo(content);
     }
   }
@@ -291,6 +332,7 @@ class TestTrackedFileStruct {
     TrackedFileStruct deserialized = TestHelpers.roundTripSerialize(file);
 
     assertThat(deserialized.contentType()).isEqualTo(FileContent.DATA);
+    assertThat(deserialized.writerFormatVersion()).isEqualTo(WRITER_FORMAT_VERSION_V4);
     assertThat(deserialized.location()).isEqualTo("s3://bucket/data/file.parquet");
     assertThat(deserialized.fileFormat()).isEqualTo(FileFormat.PARQUET);
     assertThat(deserialized.recordCount()).isEqualTo(100L);
@@ -314,6 +356,7 @@ class TestTrackedFileStruct {
     TrackedFileStruct deserialized = TestHelpers.KryoHelpers.roundTripSerialize(file);
 
     assertThat(deserialized.contentType()).isEqualTo(FileContent.DATA);
+    assertThat(deserialized.writerFormatVersion()).isEqualTo(WRITER_FORMAT_VERSION_V4);
     assertThat(deserialized.location()).isEqualTo("s3://bucket/data/file.parquet");
     assertThat(deserialized.fileFormat()).isEqualTo(FileFormat.PARQUET);
     assertThat(deserialized.recordCount()).isEqualTo(100L);
@@ -347,16 +390,17 @@ class TestTrackedFileStruct {
         new TrackedFileStruct(
             tracking,
             FileContent.DATA,
+            WRITER_FORMAT_VERSION_V4,
             "s3://bucket/data/file.parquet",
             FileFormat.PARQUET,
             newPartition(7, "music"),
             100L,
             1024L);
-    file.set(6, 0);
-    file.set(9, 1);
-    file.set(10, dv);
-    file.set(12, ByteBuffer.wrap(new byte[] {1, 2, 3}));
-    file.set(13, ImmutableList.of(50L));
+    file.set(SPEC_ID_ORDINAL, 0);
+    file.set(SORT_ORDER_ID_ORDINAL, 1);
+    file.set(DELETION_VECTOR_ORDINAL, dv);
+    file.set(KEY_METADATA_ORDINAL, ByteBuffer.wrap(new byte[] {1, 2, 3}));
+    file.set(SPLIT_OFFSETS_ORDINAL, ImmutableList.of(50L));
 
     return file;
   }
@@ -420,13 +464,14 @@ class TestTrackedFileStruct {
         new TrackedFileStruct(
             null,
             FileContent.DATA,
+            WRITER_FORMAT_VERSION_V4,
             "s3://bucket/data/file.parquet",
             FileFormat.PARQUET,
             new PartitionData(Types.StructType.of()),
             100L,
             1024L);
-    file.set(6, 0);
-    file.set(8, stats);
+    file.set(SPEC_ID_ORDINAL, 0);
+    file.set(CONTENT_STATS_ORDINAL, stats);
 
     return file;
   }