From 76617e9276a8a809ca72d11c9c40ce542aa95696 Mon Sep 17 00:00:00 2001 From: Prashant Kumar Singh Date: Tue, 30 Dec 2025 01:15:31 +0000 Subject: [PATCH 1/4] REST: Serializable table should reuse table metadata per LoadTable --- .../org/apache/iceberg/SerializableTable.java | 39 +++- .../apache/iceberg/rest/TestRESTCatalog.java | 201 ++++++++++++++++++ 2 files changed, 236 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/apache/iceberg/SerializableTable.java b/core/src/main/java/org/apache/iceberg/SerializableTable.java index dce7697319ff..7bc0868ad64d 100644 --- a/core/src/main/java/org/apache/iceberg/SerializableTable.java +++ b/core/src/main/java/org/apache/iceberg/SerializableTable.java @@ -66,6 +66,7 @@ public class SerializableTable implements Table, HasTableOperations, Serializabl private final UUID uuid; private final int formatVersion; private final Try locationProviderTry; + private final TableMetadata tableMetadata; private transient volatile Table lazyTable = null; private transient volatile Schema lazySchema = null; @@ -89,6 +90,14 @@ protected SerializableTable(Table table) { this.refs = SerializableMap.copyOf(table.refs()); this.uuid = table.uuid(); this.formatVersion = formatVersion(table); + + // Only store full TableMetadata if metadata location is not available + // and the table is a REST table. + if (metadataFileLocation == null && isRESTTable(table)) { + this.tableMetadata = getTableMetadata(table); + } else { + this.tableMetadata = null; + } } /** @@ -136,13 +145,21 @@ private Table lazyTable() { if (lazyTable == null) { synchronized (this) { if (lazyTable == null) { - if (metadataFileLocation == null) { + TableOperations ops; + + if (tableMetadata != null) { + // Use serialized TableMetadata (REST catalog without metadata location) + // This avoids storage access when the server doesn't provide metadataFileLocation + ops = new StaticTableOperations(tableMetadata, io, locationProvider()); + } else if (metadataFileLocation != null) { + // Read from storage using metadata location (traditional catalogs) + // This is more efficient when metadata location is available + ops = new StaticTableOperations(metadataFileLocation, io, locationProvider()); + } else { throw new UnsupportedOperationException( - "Cannot load metadata: metadata file location is null"); + "Cannot load metadata: both tableMetadata and metadataFileLocation are null"); } - TableOperations ops = - new StaticTableOperations(metadataFileLocation, io, locationProvider()); this.lazyTable = newTable(ops, name); } } @@ -186,6 +203,20 @@ private int formatVersion(Table table) { } } + private TableMetadata getTableMetadata(Table table) { + if (table instanceof HasTableOperations) { + return ((HasTableOperations) table).operations().current(); + } + return null; + } + + private boolean isRESTTable(Table table) { + // Check if the table is a REST table by examining its class name + // This avoids adding a direct dependency on the REST module + String className = table.getClass().getName(); + return className.equals("org.apache.iceberg.rest.RESTTable"); + } + @Override public Schema schema() { if (lazySchema == null) { diff --git a/core/src/test/java/org/apache/iceberg/rest/TestRESTCatalog.java b/core/src/test/java/org/apache/iceberg/rest/TestRESTCatalog.java index df4ba3214aea..f73942d65634 100644 --- a/core/src/test/java/org/apache/iceberg/rest/TestRESTCatalog.java +++ b/core/src/test/java/org/apache/iceberg/rest/TestRESTCatalog.java @@ -62,9 +62,11 @@ import org.apache.iceberg.MetadataUpdate; import org.apache.iceberg.PartitionSpec; import org.apache.iceberg.Schema; +import org.apache.iceberg.SerializableTable; import org.apache.iceberg.Snapshot; import org.apache.iceberg.Table; import org.apache.iceberg.TableMetadata; +import org.apache.iceberg.TestHelpers; import org.apache.iceberg.Transaction; import org.apache.iceberg.UpdatePartitionSpec; import org.apache.iceberg.UpdateSchema; @@ -3528,4 +3530,203 @@ private static List allRequests(RESTCatalogAdapter adapter) { verify(adapter, atLeastOnce()).execute(captor.capture(), any(), any(), any()); return captor.getAllValues(); } + + @Test + public void testSerializableTableWithoutMetadataLocationAccess() + throws IOException, ClassNotFoundException { + // Create a table with data through REST catalog + Schema schema = + new Schema( + required(1, "id", Types.IntegerType.get()), + required(2, "data", Types.StringType.get())); + + TableIdentifier ident = TableIdentifier.of(Namespace.of("ns"), "table"); + restCatalog.createNamespace(ident.namespace()); + Table table = restCatalog.createTable(ident, schema); + + // Add data files to create snapshots + table.newAppend().appendFile(FILE_A).commit(); + table.newAppend().appendFile(FILE_B).commit(); + + // Create SerializableTable from REST catalog + SerializableTable serializableTable = (SerializableTable) SerializableTable.copyOf(table); + + // Test with Java serialization + Table javaSerialized = TestHelpers.roundTripSerialize(serializableTable); + verifySerializedTable(javaSerialized, table, 2); + + // Test with Kryo serialization + Table kryoSerialized = TestHelpers.KryoHelpers.roundTripSerialize(serializableTable); + verifySerializedTable(kryoSerialized, table, 2); + } + + private void verifySerializedTable( + Table deserialized, Table original, int expectedSnapshotCount) { + // Verify that basic operations work even if metadata location is not accessible + // (executors don't have storage credentials) + assertThat(deserialized.schema().asStruct()).isEqualTo(original.schema().asStruct()); + assertThat(deserialized.spec().specId()).isEqualTo(original.spec().specId()); + assertThat(deserialized.location()).isEqualTo(original.location()); + assertThat(deserialized.properties()).containsAllEntriesOf(original.properties()); + + // Verify snapshot operations work using serialized metadata + assertThat(deserialized.currentSnapshot()).isNotNull(); + assertThat(deserialized.currentSnapshot().snapshotId()) + .isEqualTo(original.currentSnapshot().snapshotId()); + + // Verify snapshots are accessible + assertThat(deserialized.snapshots()).isNotNull(); + int snapshotCount = 0; + for (Snapshot snapshot : deserialized.snapshots()) { + snapshotCount++; + assertThat(snapshot).isNotNull(); + } + assertThat(snapshotCount).isEqualTo(expectedSnapshotCount); + + // Verify scan operations work + assertThat(deserialized.newScan()).isNotNull(); + } + + @Test + public void testSerializableTableWithRESTCatalogAndSchemaEvolution() + throws IOException, ClassNotFoundException { + // Create initial table + Schema initialSchema = + new Schema( + required(1, "id", Types.IntegerType.get()), + required(2, "data", Types.StringType.get())); + + TableIdentifier ident = TableIdentifier.of(Namespace.of("ns"), "schema_evolution_table"); + restCatalog.createNamespace(ident.namespace()); + Table table = restCatalog.createTable(ident, initialSchema); + + // Evolve schema + table.updateSchema().addColumn("new_col1", Types.IntegerType.get()).commit(); + table.newAppend().appendFile(FILE_A).commit(); + + // Evolve schema again + table.updateSchema().addColumn("new_col2", Types.StringType.get()).commit(); + table.newAppend().appendFile(FILE_B).commit(); + + // Create and serialize table + SerializableTable serializableTable = (SerializableTable) SerializableTable.copyOf(table); + + // Test with Java serialization + Table javaSerialized = TestHelpers.roundTripSerialize(serializableTable); + verifySchemaEvolution(javaSerialized, table); + + // Test with Kryo serialization + Table kryoSerialized = TestHelpers.KryoHelpers.roundTripSerialize(serializableTable); + verifySchemaEvolution(kryoSerialized, table); + } + + private void verifySchemaEvolution(Table deserialized, Table original) { + // Verify current schema + assertThat(deserialized.schema().columns()).hasSize(original.schema().columns().size()); + + // Verify all historical schemas are preserved + assertThat(deserialized.schemas()).isNotNull(); + assertThat(deserialized.schemas().size()).isEqualTo(original.schemas().size()); + + // Verify scans work + assertThat(deserialized.newScan()).isNotNull(); + } + + @Test + public void testSerializableTableWithRESTCatalogAndSnapshotRefs() + throws IOException, ClassNotFoundException { + // Create table with data + Schema schema = + new Schema( + required(1, "id", Types.IntegerType.get()), + required(2, "data", Types.StringType.get())); + + TableIdentifier ident = TableIdentifier.of(Namespace.of("ns"), "refs_table"); + restCatalog.createNamespace(ident.namespace()); + Table table = restCatalog.createTable(ident, schema); + + // Create first snapshot and tag it + table.newAppend().appendFile(FILE_A).commit(); + long taggedSnapshotId = table.currentSnapshot().snapshotId(); + table + .manageSnapshots() + .createTag("production", taggedSnapshotId) + .setMaxRefAgeMs("production", Long.MAX_VALUE) + .commit(); + + // Add more data + table.newAppend().appendFile(FILE_B).commit(); + + // Serialize and deserialize + SerializableTable serializableTable = (SerializableTable) SerializableTable.copyOf(table); + + // Test with Java serialization + Table javaSerialized = TestHelpers.roundTripSerialize(serializableTable); + verifySnapshotRefs(javaSerialized, taggedSnapshotId); + + // Test with Kryo serialization + Table kryoSerialized = TestHelpers.KryoHelpers.roundTripSerialize(serializableTable); + verifySnapshotRefs(kryoSerialized, taggedSnapshotId); + } + + private void verifySnapshotRefs(Table deserialized, long taggedSnapshotId) { + // Verify refs are preserved + assertThat(deserialized.refs()).isNotNull(); + assertThat(deserialized.refs()).containsKey("production"); + assertThat(deserialized.refs().get("production").snapshotId()).isEqualTo(taggedSnapshotId); + + // Verify we can access the tagged snapshot + assertThat(deserialized.snapshot(taggedSnapshotId)).isNotNull(); + } + + @Test + public void testSerializableTableConditionalMetadataSerialization() + throws IOException, ClassNotFoundException { + // Create a table with data through REST catalog + Schema schema = + new Schema( + required(1, "id", Types.IntegerType.get()), + required(2, "data", Types.StringType.get())); + + TableIdentifier ident = TableIdentifier.of(Namespace.of("ns"), "conditional_test"); + restCatalog.createNamespace(ident.namespace()); + Table table = restCatalog.createTable(ident, schema); + + // Add data with multiple snapshots + table.newAppend().appendFile(FILE_A).commit(); + table.newAppend().appendFile(FILE_B).commit(); + + // Case 1: Normal SerializableTable (with metadata location from REST catalog) + SerializableTable serializableWithLocation = + (SerializableTable) SerializableTable.copyOf(table); + + // Test with Java serialization + Table javaDeserialized = TestHelpers.roundTripSerialize(serializableWithLocation); + verifyConditionalSerialization(javaDeserialized, table); + + // Test with Kryo serialization + Table kryoDeserialized = TestHelpers.KryoHelpers.roundTripSerialize(serializableWithLocation); + verifyConditionalSerialization(kryoDeserialized, table); + } + + private void verifyConditionalSerialization(Table deserialized, Table original) { + // Verify it works + assertThat(deserialized.currentSnapshot()).isNotNull(); + assertThat(deserialized.currentSnapshot().snapshotId()) + .isEqualTo(original.currentSnapshot().snapshotId()); + + int snapshotCount = 0; + for (Snapshot snapshot : deserialized.snapshots()) { + snapshotCount++; + } + assertThat(snapshotCount).isEqualTo(2); + + // Case 2: Test the optimization - when metadata location is present, + // tableMetadataJson should not be serialized (saving space) + // When metadata location is null, tableMetadataJson should be serialized (enabling + // functionality) + // This test verifies the SerializableTable works correctly regardless of which path is taken + assertThat(deserialized.schema().asStruct()).isEqualTo(original.schema().asStruct()); + assertThat(deserialized.location()).isEqualTo(original.location()); + } } From 676dd483af6859395911811d4cb47a6d2b3b2b6f Mon Sep 17 00:00:00 2001 From: Prashant Kumar Singh Date: Mon, 5 Jan 2026 04:51:23 +0000 Subject: [PATCH 2/4] add E2E test with spark --- .../org/apache/iceberg/SerializableTable.java | 19 +++++-------------- .../org/apache/iceberg/TableMetadata.java | 2 +- .../apache/iceberg/rest/TestRESTCatalog.java | 16 +++++++--------- 3 files changed, 13 insertions(+), 24 deletions(-) diff --git a/core/src/main/java/org/apache/iceberg/SerializableTable.java b/core/src/main/java/org/apache/iceberg/SerializableTable.java index 7bc0868ad64d..2d8b7067ea2a 100644 --- a/core/src/main/java/org/apache/iceberg/SerializableTable.java +++ b/core/src/main/java/org/apache/iceberg/SerializableTable.java @@ -91,9 +91,8 @@ protected SerializableTable(Table table) { this.uuid = table.uuid(); this.formatVersion = formatVersion(table); - // Only store full TableMetadata if metadata location is not available - // and the table is a REST table. - if (metadataFileLocation == null && isRESTTable(table)) { + // Only store full TableMetadata if the table requires remote scan planning. + if (table instanceof RequiresRemoteScanPlanning) { this.tableMetadata = getTableMetadata(table); } else { this.tableMetadata = null; @@ -148,12 +147,11 @@ private Table lazyTable() { TableOperations ops; if (tableMetadata != null) { - // Use serialized TableMetadata (REST catalog without metadata location) - // This avoids storage access when the server doesn't provide metadataFileLocation + // Use serialized TableMetadata (tables requiring remote scan planning) + // This avoids storage access for distributed query planning ops = new StaticTableOperations(tableMetadata, io, locationProvider()); } else if (metadataFileLocation != null) { - // Read from storage using metadata location (traditional catalogs) - // This is more efficient when metadata location is available + // Read from storage using metadata location ops = new StaticTableOperations(metadataFileLocation, io, locationProvider()); } else { throw new UnsupportedOperationException( @@ -210,13 +208,6 @@ private TableMetadata getTableMetadata(Table table) { return null; } - private boolean isRESTTable(Table table) { - // Check if the table is a REST table by examining its class name - // This avoids adding a direct dependency on the REST module - String className = table.getClass().getName(); - return className.equals("org.apache.iceberg.rest.RESTTable"); - } - @Override public Schema schema() { if (lazySchema == null) { diff --git a/core/src/main/java/org/apache/iceberg/TableMetadata.java b/core/src/main/java/org/apache/iceberg/TableMetadata.java index 7dac5d401a80..eb180db85f78 100644 --- a/core/src/main/java/org/apache/iceberg/TableMetadata.java +++ b/core/src/main/java/org/apache/iceberg/TableMetadata.java @@ -197,7 +197,7 @@ public String toString() { } } - public static class MetadataLogEntry { + public static class MetadataLogEntry implements Serializable { private final long timestampMillis; private final String file; diff --git a/core/src/test/java/org/apache/iceberg/rest/TestRESTCatalog.java b/core/src/test/java/org/apache/iceberg/rest/TestRESTCatalog.java index f73942d65634..6b0304b48977 100644 --- a/core/src/test/java/org/apache/iceberg/rest/TestRESTCatalog.java +++ b/core/src/test/java/org/apache/iceberg/rest/TestRESTCatalog.java @@ -3532,8 +3532,7 @@ private static List allRequests(RESTCatalogAdapter adapter) { } @Test - public void testSerializableTableWithoutMetadataLocationAccess() - throws IOException, ClassNotFoundException { + public void testSerializableTableWithRESTCatalog() throws IOException, ClassNotFoundException { // Create a table with data through REST catalog Schema schema = new Schema( @@ -3562,8 +3561,8 @@ public void testSerializableTableWithoutMetadataLocationAccess() private void verifySerializedTable( Table deserialized, Table original, int expectedSnapshotCount) { - // Verify that basic operations work even if metadata location is not accessible - // (executors don't have storage credentials) + // Verify that basic operations work using serialized table metadata + // (tables requiring remote scan planning serialize full metadata) assertThat(deserialized.schema().asStruct()).isEqualTo(original.schema().asStruct()); assertThat(deserialized.spec().specId()).isEqualTo(original.spec().specId()); assertThat(deserialized.location()).isEqualTo(original.location()); @@ -3721,11 +3720,10 @@ private void verifyConditionalSerialization(Table deserialized, Table original) } assertThat(snapshotCount).isEqualTo(2); - // Case 2: Test the optimization - when metadata location is present, - // tableMetadataJson should not be serialized (saving space) - // When metadata location is null, tableMetadataJson should be serialized (enabling - // functionality) - // This test verifies the SerializableTable works correctly regardless of which path is taken + // Test the optimization - when table requires remote scan planning, + // tableMetadata is always serialized (enabling distributed query planning) + // For other tables, tableMetadata is not serialized (saving space) + // This test verifies the SerializableTable works correctly for both cases assertThat(deserialized.schema().asStruct()).isEqualTo(original.schema().asStruct()); assertThat(deserialized.location()).isEqualTo(original.location()); } From a6b9037683dbaffaa71f9af431678c5d76480d66 Mon Sep 17 00:00:00 2001 From: Prashant Singh Date: Wed, 7 Jan 2026 11:58:13 -0800 Subject: [PATCH 3/4] review feedback part 1 --- .../org/apache/iceberg/SerializableTable.java | 6 +++--- .../apache/iceberg/rest/TestRESTCatalog.java | 17 ++++++----------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/org/apache/iceberg/SerializableTable.java b/core/src/main/java/org/apache/iceberg/SerializableTable.java index 2d8b7067ea2a..4cc46ea8ce59 100644 --- a/core/src/main/java/org/apache/iceberg/SerializableTable.java +++ b/core/src/main/java/org/apache/iceberg/SerializableTable.java @@ -93,7 +93,7 @@ protected SerializableTable(Table table) { // Only store full TableMetadata if the table requires remote scan planning. if (table instanceof RequiresRemoteScanPlanning) { - this.tableMetadata = getTableMetadata(table); + this.tableMetadata = tableMetadata(table); } else { this.tableMetadata = null; } @@ -155,7 +155,7 @@ private Table lazyTable() { ops = new StaticTableOperations(metadataFileLocation, io, locationProvider()); } else { throw new UnsupportedOperationException( - "Cannot load metadata: both tableMetadata and metadataFileLocation are null"); + "Cannot load metadata: both table metadata and metadata file location are null"); } this.lazyTable = newTable(ops, name); @@ -201,7 +201,7 @@ private int formatVersion(Table table) { } } - private TableMetadata getTableMetadata(Table table) { + private TableMetadata tableMetadata(Table table) { if (table instanceof HasTableOperations) { return ((HasTableOperations) table).operations().current(); } diff --git a/core/src/test/java/org/apache/iceberg/rest/TestRESTCatalog.java b/core/src/test/java/org/apache/iceberg/rest/TestRESTCatalog.java index 6b0304b48977..9bcae0918e51 100644 --- a/core/src/test/java/org/apache/iceberg/rest/TestRESTCatalog.java +++ b/core/src/test/java/org/apache/iceberg/rest/TestRESTCatalog.java @@ -3532,16 +3532,13 @@ private static List allRequests(RESTCatalogAdapter adapter) { } @Test - public void testSerializableTableWithRESTCatalog() throws IOException, ClassNotFoundException { - // Create a table with data through REST catalog + public void serializableTable() throws IOException, ClassNotFoundException { Schema schema = new Schema( required(1, "id", Types.IntegerType.get()), required(2, "data", Types.StringType.get())); - - TableIdentifier ident = TableIdentifier.of(Namespace.of("ns"), "table"); - restCatalog.createNamespace(ident.namespace()); - Table table = restCatalog.createTable(ident, schema); + restCatalog.createNamespace(TABLE.namespace()); + Table table = restCatalog.createTable(TABLE, schema); // Add data files to create snapshots table.newAppend().appendFile(FILE_A).commit(); @@ -3587,8 +3584,7 @@ private void verifySerializedTable( } @Test - public void testSerializableTableWithRESTCatalogAndSchemaEvolution() - throws IOException, ClassNotFoundException { + public void serializableTableWithSchemaEvolution() throws IOException, ClassNotFoundException { // Create initial table Schema initialSchema = new Schema( @@ -3632,8 +3628,7 @@ private void verifySchemaEvolution(Table deserialized, Table original) { } @Test - public void testSerializableTableWithRESTCatalogAndSnapshotRefs() - throws IOException, ClassNotFoundException { + public void serializableTableSnapshotRefs() throws IOException, ClassNotFoundException { // Create table with data Schema schema = new Schema( @@ -3679,7 +3674,7 @@ private void verifySnapshotRefs(Table deserialized, long taggedSnapshotId) { } @Test - public void testSerializableTableConditionalMetadataSerialization() + public void serializableTableConditionalMetadataSerialization() throws IOException, ClassNotFoundException { // Create a table with data through REST catalog Schema schema = From a33ef604ce62e48fdbd8a4dfb348743f8c76699a Mon Sep 17 00:00:00 2001 From: Prashant Kumar Singh Date: Sat, 10 Jan 2026 03:00:55 +0000 Subject: [PATCH 4/4] simplify test --- .../apache/iceberg/rest/TestRESTCatalog.java | 43 +++++-------------- 1 file changed, 11 insertions(+), 32 deletions(-) diff --git a/core/src/test/java/org/apache/iceberg/rest/TestRESTCatalog.java b/core/src/test/java/org/apache/iceberg/rest/TestRESTCatalog.java index 9bcae0918e51..9b9684cff650 100644 --- a/core/src/test/java/org/apache/iceberg/rest/TestRESTCatalog.java +++ b/core/src/test/java/org/apache/iceberg/rest/TestRESTCatalog.java @@ -3549,15 +3549,14 @@ public void serializableTable() throws IOException, ClassNotFoundException { // Test with Java serialization Table javaSerialized = TestHelpers.roundTripSerialize(serializableTable); - verifySerializedTable(javaSerialized, table, 2); + verifySerializedTable(javaSerialized, table); // Test with Kryo serialization Table kryoSerialized = TestHelpers.KryoHelpers.roundTripSerialize(serializableTable); - verifySerializedTable(kryoSerialized, table, 2); + verifySerializedTable(kryoSerialized, table); } - private void verifySerializedTable( - Table deserialized, Table original, int expectedSnapshotCount) { + private void verifySerializedTable(Table deserialized, Table original) { // Verify that basic operations work using serialized table metadata // (tables requiring remote scan planning serialize full metadata) assertThat(deserialized.schema().asStruct()).isEqualTo(original.schema().asStruct()); @@ -3571,13 +3570,7 @@ private void verifySerializedTable( .isEqualTo(original.currentSnapshot().snapshotId()); // Verify snapshots are accessible - assertThat(deserialized.snapshots()).isNotNull(); - int snapshotCount = 0; - for (Snapshot snapshot : deserialized.snapshots()) { - snapshotCount++; - assertThat(snapshot).isNotNull(); - } - assertThat(snapshotCount).isEqualTo(expectedSnapshotCount); + assertThat(deserialized.snapshots()).isNotNull().hasSameSizeAs(original.snapshots()); // Verify scan operations work assertThat(deserialized.newScan()).isNotNull(); @@ -3617,11 +3610,10 @@ public void serializableTableWithSchemaEvolution() throws IOException, ClassNotF private void verifySchemaEvolution(Table deserialized, Table original) { // Verify current schema - assertThat(deserialized.schema().columns()).hasSize(original.schema().columns().size()); + assertThat(deserialized.schema().columns()).hasSameSizeAs(original.schema().columns()); // Verify all historical schemas are preserved - assertThat(deserialized.schemas()).isNotNull(); - assertThat(deserialized.schemas().size()).isEqualTo(original.schemas().size()); + assertThat(deserialized.schemas()).isNotNull().hasSameSizeAs(original.schemas()); // Verify scans work assertThat(deserialized.newScan()).isNotNull(); @@ -3656,21 +3648,13 @@ public void serializableTableSnapshotRefs() throws IOException, ClassNotFoundExc // Test with Java serialization Table javaSerialized = TestHelpers.roundTripSerialize(serializableTable); - verifySnapshotRefs(javaSerialized, taggedSnapshotId); + assertThat(javaSerialized.refs()).containsEntry("production", table.refs().get("production")); + assertThat(javaSerialized.snapshot(taggedSnapshotId)).isNotNull(); // Test with Kryo serialization Table kryoSerialized = TestHelpers.KryoHelpers.roundTripSerialize(serializableTable); - verifySnapshotRefs(kryoSerialized, taggedSnapshotId); - } - - private void verifySnapshotRefs(Table deserialized, long taggedSnapshotId) { - // Verify refs are preserved - assertThat(deserialized.refs()).isNotNull(); - assertThat(deserialized.refs()).containsKey("production"); - assertThat(deserialized.refs().get("production").snapshotId()).isEqualTo(taggedSnapshotId); - - // Verify we can access the tagged snapshot - assertThat(deserialized.snapshot(taggedSnapshotId)).isNotNull(); + assertThat(kryoSerialized.refs()).containsEntry("production", table.refs().get("production")); + assertThat(kryoSerialized.snapshot(taggedSnapshotId)).isNotNull(); } @Test @@ -3708,12 +3692,7 @@ private void verifyConditionalSerialization(Table deserialized, Table original) assertThat(deserialized.currentSnapshot()).isNotNull(); assertThat(deserialized.currentSnapshot().snapshotId()) .isEqualTo(original.currentSnapshot().snapshotId()); - - int snapshotCount = 0; - for (Snapshot snapshot : deserialized.snapshots()) { - snapshotCount++; - } - assertThat(snapshotCount).isEqualTo(2); + assertThat(deserialized.snapshots()).isNotNull().hasSameSizeAs(original.snapshots()); // Test the optimization - when table requires remote scan planning, // tableMetadata is always serialized (enabling distributed query planning)