This is an automated email from the ASF dual-hosted git repository.
pvary 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 f79932d112 Core: Add test for freshness-aware table loading with lazy
snapshot loading (#15274)
f79932d112 is described below
commit f79932d112b77dc4e49c21c3501c7b722d129462
Author: gaborkaszab <[email protected]>
AuthorDate: Mon Feb 23 16:49:56 2026 +0100
Core: Add test for freshness-aware table loading with lazy snapshot loading
(#15274)
---
.../iceberg/rest/TestFreshnessAwareLoading.java | 98 ++++++++++++++++++++++
1 file changed, 98 insertions(+)
diff --git
a/core/src/test/java/org/apache/iceberg/rest/TestFreshnessAwareLoading.java
b/core/src/test/java/org/apache/iceberg/rest/TestFreshnessAwareLoading.java
index 1cee12dfec..80981df1fc 100644
--- a/core/src/test/java/org/apache/iceberg/rest/TestFreshnessAwareLoading.java
+++ b/core/src/test/java/org/apache/iceberg/rest/TestFreshnessAwareLoading.java
@@ -19,6 +19,7 @@
package org.apache.iceberg.rest;
import static org.apache.iceberg.TestBase.FILE_A;
+import static org.apache.iceberg.TestBase.FILE_B;
import static org.apache.iceberg.TestBase.SCHEMA;
import static org.apache.iceberg.rest.RESTTableCache.SessionIdTableId;
import static org.apache.iceberg.rest.RESTTableCache.TableWithETag;
@@ -42,6 +43,7 @@ import org.apache.http.HttpHeaders;
import org.apache.iceberg.BaseMetadataTable;
import org.apache.iceberg.BaseTable;
import org.apache.iceberg.CatalogProperties;
+import org.apache.iceberg.Snapshot;
import org.apache.iceberg.Table;
import org.apache.iceberg.TableMetadata;
import org.apache.iceberg.catalog.SessionCatalog;
@@ -57,6 +59,7 @@ import org.apache.iceberg.rest.responses.ErrorResponse;
import org.apache.iceberg.rest.responses.LoadTableResponse;
import org.apache.iceberg.types.Types;
import org.apache.iceberg.util.FakeTicker;
+import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
@@ -784,6 +787,101 @@ public class TestFreshnessAwareLoading extends
TestBaseWithRESTServer {
assertThat(table.operations()).isInstanceOf(CustomTableOps.class);
}
+ @Test
+ public void tableCacheWithLazySnapshotLoading() {
+ Map<String, String> responseHeaders = Maps.newHashMap();
+ RESTCatalogAdapter adapter =
adapterCapturingResponseHeaders(responseHeaders);
+ SessionCatalog.SessionContext sessionContext =
SessionCatalog.SessionContext.createEmpty();
+ RESTCatalog catalog = new RESTCatalog(sessionContext, config -> adapter);
+ catalog.initialize(
+ "test",
+ ImmutableMap.of(
+ RESTCatalogProperties.SNAPSHOT_LOADING_MODE,
+ RESTCatalogProperties.SnapshotMode.REFS.name()));
+
+ catalog.createNamespace(TABLE.namespace());
+ Table table = catalog.createTable(TABLE, SCHEMA);
+ table.newAppend().appendFile(FILE_A).commit();
+ table.newAppend().appendFile(FILE_B).commit();
+
+ Table refsTable = catalog.loadTable(TABLE);
+ String eTag = responseHeaders.get(HttpHeaders.ETAG);
+ assertThat(eTag).isNotNull();
+
+ // Verify that only the current snapshot was loaded (refs mode). Access
the snapshots field
+ // directly to avoid triggering lazy loading of all snapshots.
+ assertThat(((BaseTable) refsTable).operations().current())
+ .extracting("snapshots")
+ .asInstanceOf(InstanceOfAssertFactories.list(Snapshot.class))
+ .hasSize(1);
+
+ Cache<SessionIdTableId, TableWithETag> tableCache =
+ catalog.sessionCatalog().tableCache().cache();
+ assertThat(tableCache.estimatedSize()).isEqualTo(1);
+ SessionIdTableId tableCacheKey =
SessionIdTableId.of(sessionContext.sessionId(), TABLE);
+ TableWithETag tableWithEtag = tableCache.asMap().get(tableCacheKey);
+ assertThat(tableWithEtag).isNotNull();
+
+ // Trigger loading all snapshots via the lazy loading mechanism
+ assertThat(refsTable.snapshots()).hasSize(2);
+
+ // After lazy snapshot loading, the cache entry remains the same object.
However, the
+ // underlying TableMetadata was refreshed with the full list of snapshots.
+ assertThat(tableCache.estimatedSize()).isEqualTo(1);
+ assertThat(tableWithEtag).isSameAs(tableCache.asMap().get(tableCacheKey));
+ // Lazy snapshot loading doesn't go through the cache
+ assertThat(tableCache.stats().hitCount()).isZero();
+
+ // The next loadTable should hit the cache and return a table with the
full snapshot list
+ Table reloadedTable = catalog.loadTable(TABLE);
+ assertThat(tableCache.stats().hitCount()).isOne();
+ assertThat(((BaseTable) reloadedTable).operations().current())
+ .extracting("snapshots")
+ .asInstanceOf(InstanceOfAssertFactories.list(Snapshot.class))
+ .hasSize(2);
+
+ // Accessing snapshots again doesn't trigger another load
+ assertThat(reloadedTable.snapshots()).hasSize(2);
+ // Verify the loaded snapshots match the original table's snapshots
+
assertThat(refsTable.snapshots()).containsExactlyInAnyOrderElementsOf(table.snapshots());
+
+ // Verify that the initial table load used the refs query parameter
+ verify(adapter, times(1))
+ .execute(
+ matches(
+ HTTPRequest.HTTPMethod.GET,
+ RESOURCE_PATHS.table(TABLE),
+ Map.of(),
+ Map.of("snapshots", "refs")),
+ eq(LoadTableResponse.class),
+ any(),
+ any());
+
+ // Verify the second load table (cache hit) included the IF_NONE_MATCH
header
+ verify(adapter, times(1))
+ .execute(
+ matches(
+ HTTPRequest.HTTPMethod.GET,
+ RESOURCE_PATHS.table(TABLE),
+ Map.of(HttpHeaders.IF_NONE_MATCH, eTag),
+ Map.of("snapshots", "refs")),
+ eq(LoadTableResponse.class),
+ any(),
+ any());
+
+ // Verify that lazy snapshot loading triggered exactly one request with
snapshots=all
+ verify(adapter, times(1))
+ .execute(
+ matches(
+ HTTPRequest.HTTPMethod.GET,
+ RESOURCE_PATHS.table(TABLE),
+ Map.of(),
+ Map.of("snapshots", "all")),
+ eq(LoadTableResponse.class),
+ any(),
+ any());
+ }
+
private RESTCatalogAdapter adapterCapturingResponseHeaders(Map<String,
String> respHeaders) {
return Mockito.spy(
new RESTCatalogAdapter(backendCatalog) {