This is an automated email from the ASF dual-hosted git repository.
singhpk234 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 67ed9d1165 CORE: Allow table level override for scan planning (#15572)
67ed9d1165 is described below
commit 67ed9d116581cc3d45d8c8f205eeaa1743d8d171
Author: Prashant Singh <[email protected]>
AuthorDate: Tue Mar 17 16:58:37 2026 -0700
CORE: Allow table level override for scan planning (#15572)
* Core: Simplify REST scan planning to 2 modes (client/catalog)
Simplified scan planning configuration from boolean to 2-mode enum:
- client (default): Use client-side scan planning
- catalog: Use server-side scan planning if supported
Changes:
- RESTCatalogProperties: Added ScanPlanningMode enum with CLIENT and CATALOG
- RESTSessionCatalog: Updated to check table config for scan planning mode
- Updated OpenAPI specs with simplified documentation
The mode can be configured per-table via LoadTableResponse.config() to
allow fine-grained control over which tables use server-side planning.
* address steven feedback
* address peters feedback
* address review feedback
* more clean-up
* Address feedback
* Convert scan planning mode mismatch from exception to log warning
When client and server have conflicting scan planning modes, log a
warning instead of throwing IllegalStateException. Server config
silently takes precedence.
* Address Amogh's review feedback
- Use Arrays.stream(values()) to dynamically list valid scan planning modes
in error message
- Simplify verbose comments in TestRESTScanPlanning
- Fix fileIOForRemotePlanningIsPropagated to use SCAN_PLANNING_MODE after
rebase
* Address nastra's comments
---------
Co-authored-by: Prashant Singh <[email protected]>
---
.../apache/iceberg/rest/RESTCatalogProperties.java | 34 +++-
.../apache/iceberg/rest/RESTSessionCatalog.java | 53 ++++--
.../iceberg/rest/TestBaseWithRESTServer.java | 6 +-
.../apache/iceberg/rest/TestRESTScanPlanning.java | 189 +++++++++++++++++----
.../spark/extensions/TestRemoteScanPlanning.java | 4 +-
.../spark/extensions/TestRemoteScanPlanning.java | 4 +-
.../spark/extensions/TestRemoteScanPlanning.java | 4 +-
.../spark/extensions/TestRemoteScanPlanning.java | 4 +-
8 files changed, 244 insertions(+), 54 deletions(-)
diff --git
a/core/src/main/java/org/apache/iceberg/rest/RESTCatalogProperties.java
b/core/src/main/java/org/apache/iceberg/rest/RESTCatalogProperties.java
index 7281862481..cda81b1d0d 100644
--- a/core/src/main/java/org/apache/iceberg/rest/RESTCatalogProperties.java
+++ b/core/src/main/java/org/apache/iceberg/rest/RESTCatalogProperties.java
@@ -18,7 +18,10 @@
*/
package org.apache.iceberg.rest;
+import java.util.Arrays;
+import java.util.Locale;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
public final class RESTCatalogProperties {
@@ -40,9 +43,9 @@ public final class RESTCatalogProperties {
public static final String NAMESPACE_SEPARATOR = "namespace-separator";
- // Enable planning on the REST server side
- public static final String REST_SCAN_PLANNING_ENABLED =
"rest-scan-planning-enabled";
- public static final boolean REST_SCAN_PLANNING_ENABLED_DEFAULT = false;
+ // Configure scan planning mode
+ // Can be set by server in LoadTableResponse.config() for table-level
override
+ public static final String SCAN_PLANNING_MODE = "scan-planning-mode";
public static final String REST_SCAN_PLAN_ID = "rest-scan-plan-id";
@@ -59,4 +62,29 @@ public final class RESTCatalogProperties {
ALL,
REFS
}
+
+ public enum ScanPlanningMode {
+ CLIENT,
+ SERVER;
+
+ public String modeName() {
+ return name().toLowerCase(Locale.ROOT);
+ }
+
+ public static ScanPlanningMode fromString(String mode) {
+ for (ScanPlanningMode planningMode : values()) {
+ if (planningMode.modeName().equalsIgnoreCase(mode)) {
+ return planningMode;
+ }
+ }
+
+ throw new IllegalArgumentException(
+ String.format(
+ "Invalid scan planning mode: %s. Valid values are: %s",
+ mode,
+ Arrays.stream(values())
+ .map(ScanPlanningMode::modeName)
+ .collect(Collectors.joining(", "))));
+ }
+ }
}
diff --git a/core/src/main/java/org/apache/iceberg/rest/RESTSessionCatalog.java
b/core/src/main/java/org/apache/iceberg/rest/RESTSessionCatalog.java
index cda71fccda..409615d624 100644
--- a/core/src/main/java/org/apache/iceberg/rest/RESTSessionCatalog.java
+++ b/core/src/main/java/org/apache/iceberg/rest/RESTSessionCatalog.java
@@ -166,7 +166,6 @@ public class RESTSessionCatalog extends
BaseViewSessionCatalog
private MetricsReporter reporter = null;
private boolean reportingViaRestEnabled;
private Integer pageSize = null;
- private boolean restScanPlanningEnabled;
private CloseableGroup closeables = null;
private Set<Endpoint> endpoints;
private Supplier<Map<String, String>> mutationHeaders = Map::of;
@@ -281,12 +280,6 @@ public class RESTSessionCatalog extends
BaseViewSessionCatalog
RESTCatalogProperties.NAMESPACE_SEPARATOR,
RESTUtil.NAMESPACE_SEPARATOR_URLENCODED_UTF_8);
- this.restScanPlanningEnabled =
- PropertyUtil.propertyAsBoolean(
- mergedProps,
- RESTCatalogProperties.REST_SCAN_PLANNING_ENABLED,
- RESTCatalogProperties.REST_SCAN_PLANNING_ENABLED_DEFAULT);
-
this.tableCache = createTableCache(mergedProps);
this.closeables.addCloseable(this.tableCache);
@@ -584,7 +577,7 @@ public class RESTSessionCatalog extends
BaseViewSessionCatalog
trackFileIO(ops);
- RESTTable table = restTableForScanPlanning(ops, identifier, tableClient);
+ RESTTable table = restTableForScanPlanning(ops, identifier, tableClient,
tableConf);
if (table != null) {
return table;
}
@@ -595,9 +588,41 @@ public class RESTSessionCatalog extends
BaseViewSessionCatalog
}
private RESTTable restTableForScanPlanning(
- TableOperations ops, TableIdentifier finalIdentifier, RESTClient
restClient) {
- // server supports remote planning endpoint and server / client wants to
do server side planning
- if (endpoints.contains(Endpoint.V1_SUBMIT_TABLE_SCAN_PLAN) &&
restScanPlanningEnabled) {
+ TableOperations ops,
+ TableIdentifier finalIdentifier,
+ RESTClient restClient,
+ Map<String, String> tableConf) {
+ // Get client-side and server-side scan planning modes
+ String planningModeClientConfig =
properties().get(RESTCatalogProperties.SCAN_PLANNING_MODE);
+ String planningModeServerConfig =
tableConf.get(RESTCatalogProperties.SCAN_PLANNING_MODE);
+
+ // Warn if client and server configs conflict; server config takes
precedence
+ if (planningModeClientConfig != null
+ && planningModeServerConfig != null
+ &&
!planningModeClientConfig.equalsIgnoreCase(planningModeServerConfig)) {
+ LOG.warn(
+ "Scan planning mode mismatch for table {}: client config={}, server
config={}. "
+ + "Server config will take precedence.",
+ finalIdentifier,
+ planningModeClientConfig,
+ planningModeServerConfig);
+ }
+
+ // Determine effective mode: prefer server config if present, otherwise
use client config
+ String effectiveModeConfig =
+ planningModeServerConfig != null ? planningModeServerConfig :
planningModeClientConfig;
+ RESTCatalogProperties.ScanPlanningMode effectiveMode =
+ effectiveModeConfig != null
+ ?
RESTCatalogProperties.ScanPlanningMode.fromString(effectiveModeConfig)
+ : RESTCatalogProperties.ScanPlanningMode.CLIENT;
+
+ if (effectiveMode == RESTCatalogProperties.ScanPlanningMode.SERVER) {
+ Preconditions.checkState(
+ endpoints.contains(Endpoint.V1_SUBMIT_TABLE_SCAN_PLAN),
+ "Server requires server-side scan planning for table %s but does not
support endpoint %s",
+ finalIdentifier,
+ Endpoint.V1_SUBMIT_TABLE_SCAN_PLAN);
+
return new RESTTable(
ops,
fullTableName(finalIdentifier),
@@ -610,6 +635,8 @@ public class RESTSessionCatalog extends
BaseViewSessionCatalog
properties(),
conf);
}
+
+ // Default to client-side planning
return null;
}
@@ -683,7 +710,7 @@ public class RESTSessionCatalog extends
BaseViewSessionCatalog
trackFileIO(ops);
- RESTTable restTable = restTableForScanPlanning(ops, ident, tableClient);
+ RESTTable restTable = restTableForScanPlanning(ops, ident, tableClient,
tableConf);
if (restTable != null) {
return restTable;
}
@@ -952,7 +979,7 @@ public class RESTSessionCatalog extends
BaseViewSessionCatalog
trackFileIO(ops);
- RESTTable restTable = restTableForScanPlanning(ops, ident, tableClient);
+ RESTTable restTable = restTableForScanPlanning(ops, ident, tableClient,
tableConf);
if (restTable != null) {
return restTable;
}
diff --git
a/core/src/test/java/org/apache/iceberg/rest/TestBaseWithRESTServer.java
b/core/src/test/java/org/apache/iceberg/rest/TestBaseWithRESTServer.java
index f1a172a423..a79977c246 100644
--- a/core/src/test/java/org/apache/iceberg/rest/TestBaseWithRESTServer.java
+++ b/core/src/test/java/org/apache/iceberg/rest/TestBaseWithRESTServer.java
@@ -81,7 +81,7 @@ public abstract class TestBaseWithRESTServer {
httpServer.setHandler(servletContext);
httpServer.start();
- restCatalog = initCatalog(catalogName(), ImmutableMap.of());
+ restCatalog = initCatalog(catalogName(), additionalCatalogProperties());
}
@AfterEach
@@ -119,6 +119,10 @@ public abstract class TestBaseWithRESTServer {
protected abstract String catalogName();
+ protected Map<String, String> additionalCatalogProperties() {
+ return ImmutableMap.of();
+ }
+
@SuppressWarnings("unchecked")
protected <T> T roundTripSerialize(T payload, String description) {
if (payload != null) {
diff --git
a/core/src/test/java/org/apache/iceberg/rest/TestRESTScanPlanning.java
b/core/src/test/java/org/apache/iceberg/rest/TestRESTScanPlanning.java
index 6ee257026b..cdde30abe0 100644
--- a/core/src/test/java/org/apache/iceberg/rest/TestRESTScanPlanning.java
+++ b/core/src/test/java/org/apache/iceberg/rest/TestRESTScanPlanning.java
@@ -35,15 +35,12 @@ import static org.mockito.ArgumentMatchers.any;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectReader;
import java.io.IOException;
-import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
-import java.util.stream.Collectors;
+import org.apache.iceberg.BaseTable;
import org.apache.iceberg.CatalogProperties;
-import org.apache.iceberg.ContentFile;
-import org.apache.iceberg.ContentScanTask;
import org.apache.iceberg.DataFile;
import org.apache.iceberg.DataFiles;
import org.apache.iceberg.DeleteFile;
@@ -69,6 +66,7 @@ import org.apache.iceberg.rest.requests.PlanTableScanRequest;
import org.apache.iceberg.rest.responses.ConfigResponse;
import org.apache.iceberg.rest.responses.ErrorResponse;
import org.apache.iceberg.rest.responses.FetchPlanningResultResponse;
+import org.apache.iceberg.rest.responses.LoadTableResponse;
import org.apache.iceberg.rest.responses.PlanTableScanResponse;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@@ -88,22 +86,22 @@ public class TestRESTScanPlanning extends
TestBaseWithRESTServer {
Class<T> responseType,
Consumer<ErrorResponse> errorHandler,
Consumer<Map<String, String>> responseHeaders) {
- if (ResourcePaths.config().equals(request.path())) {
- return castResponse(
- responseType,
- ConfigResponse.builder()
- .withEndpoints(
- Arrays.stream(Route.values())
- .map(r -> Endpoint.create(r.method().name(),
r.resourcePath()))
- .collect(Collectors.toList()))
- .withOverrides(
-
ImmutableMap.of(RESTCatalogProperties.REST_SCAN_PLANNING_ENABLED, "true"))
- .build());
- }
+ // roundTripSerialize before intercepting so we modify the
deserialized response
Object body = roundTripSerialize(request.body(), "request");
HTTPRequest req =
ImmutableHTTPRequest.builder().from(request).body(body).build();
T response = super.execute(req, responseType, errorHandler,
responseHeaders);
- return roundTripSerialize(response, "response");
+ response = roundTripSerialize(response, "response");
+
+ // Add scan planning mode to table config for LoadTableResponse
+ if (response instanceof LoadTableResponse) {
+ return castResponse(
+ responseType,
+ withPlanningMode(
+ (LoadTableResponse) response,
+
RESTCatalogProperties.ScanPlanningMode.SERVER.modeName()));
+ }
+
+ return response;
}
});
}
@@ -113,8 +111,24 @@ public class TestRESTScanPlanning extends
TestBaseWithRESTServer {
return "prod-with-scan-planning";
}
+ @Override
+ protected Map<String, String> additionalCatalogProperties() {
+ return ImmutableMap.of(
+ RESTCatalogProperties.SCAN_PLANNING_MODE,
+ RESTCatalogProperties.ScanPlanningMode.SERVER.modeName());
+ }
+
// ==================== Helper Methods ====================
+ private static LoadTableResponse withPlanningMode(LoadTableResponse
response, String mode) {
+ return LoadTableResponse.builder()
+ .withTableMetadata(response.tableMetadata())
+ .addAllConfig(response.config())
+ .addConfig(RESTCatalogProperties.SCAN_PLANNING_MODE, mode)
+ .addAllCredentials(response.credentials())
+ .build();
+ }
+
@Override
@SuppressWarnings("unchecked")
protected <T> T roundTripSerialize(T payload, String description) {
@@ -848,6 +862,41 @@ public class TestRESTScanPlanning extends
TestBaseWithRESTServer {
}
}
+ private CatalogWithAdapter catalogWithModes(String clientMode, String
serverMode) {
+ RESTCatalogAdapter adapter =
+ Mockito.spy(
+ new RESTCatalogAdapter(backendCatalog) {
+ @Override
+ public <T extends RESTResponse> T execute(
+ HTTPRequest request,
+ Class<T> responseType,
+ Consumer<ErrorResponse> errorHandler,
+ Consumer<Map<String, String>> responseHeaders) {
+ T response = super.execute(request, responseType,
errorHandler, responseHeaders);
+ if (response instanceof LoadTableResponse && serverMode !=
null) {
+ return castResponse(
+ responseType, withPlanningMode((LoadTableResponse)
response, serverMode));
+ }
+
+ return response;
+ }
+ });
+
+ RESTCatalog catalog =
+ new RESTCatalog(SessionCatalog.SessionContext.createEmpty(), (config)
-> adapter);
+
+ ImmutableMap.Builder<String, String> clientConfigBuilder =
+ ImmutableMap.<String, String>builder()
+ .put(CatalogProperties.FILE_IO_IMPL,
"org.apache.iceberg.inmemory.InMemoryFileIO");
+
+ if (clientMode != null) {
+ clientConfigBuilder.put(RESTCatalogProperties.SCAN_PLANNING_MODE,
clientMode);
+ }
+
+ catalog.initialize("test-scan-planning-modes",
clientConfigBuilder.build());
+ return new CatalogWithAdapter(catalog, adapter);
+ }
+
// Helper: Create base catalog endpoints (namespace and table operations)
private List<Endpoint> baseCatalogEndpoints() {
return ImmutableList.of(
@@ -883,7 +932,18 @@ public class TestRESTScanPlanning extends
TestBaseWithRESTServer {
return castResponse(
responseType,
ConfigResponse.builder().withEndpoints(endpoints).build());
}
- return super.execute(request, responseType, errorHandler,
responseHeaders);
+ T response = super.execute(request, responseType,
errorHandler, responseHeaders);
+
+ // Add scan planning mode to table config for LoadTableResponse
+ if (response instanceof LoadTableResponse) {
+ return castResponse(
+ responseType,
+ withPlanningMode(
+ (LoadTableResponse) response,
+
RESTCatalogProperties.ScanPlanningMode.SERVER.modeName()));
+ }
+
+ return response;
}
});
@@ -898,27 +958,25 @@ public class TestRESTScanPlanning extends
TestBaseWithRESTServer {
ImmutableMap.of(
CatalogProperties.FILE_IO_IMPL,
"org.apache.iceberg.inmemory.InMemoryFileIO",
- RESTCatalogProperties.REST_SCAN_PLANNING_ENABLED,
- "true"));
+ RESTCatalogProperties.SCAN_PLANNING_MODE,
+ RESTCatalogProperties.ScanPlanningMode.SERVER.modeName()));
return new CatalogWithAdapter(catalog, adapter);
}
@Test
public void serverDoesNotSupportPlanningEndpoint() throws IOException {
- // Server doesn't support scan planning at all - should fall back to
client-side planning
+ // Server requires server-side planning but doesn't support the endpoint -
should fail
CatalogWithAdapter catalogWithAdapter =
catalogWithEndpoints(baseCatalogEndpoints(), null);
RESTCatalog catalog = catalogWithAdapter.catalog;
- Table table = createTableWithScanPlanning(catalog, "no_planning_support");
- assertThat(table).isNotInstanceOf(RESTTable.class);
- table.newAppend().appendFile(FILE_A).commit();
- // Should fall back to client-side planning when endpoint is not supported
- assertThat(table.newScan().planFiles())
- .hasSize(1)
- .first()
- .extracting(ContentScanTask::file)
- .extracting(ContentFile::location)
- .isEqualTo(FILE_A.location());
+ catalog.createNamespace(NS);
+ assertThatThrownBy(
+ () ->
+ catalog.buildTable(TableIdentifier.of(NS,
"no_planning_support"), SCHEMA).create())
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage(
+ "Server requires server-side scan planning for table %s but does
not support endpoint %s",
+ TableIdentifier.of(NS, "no_planning_support"),
Endpoint.V1_SUBMIT_TABLE_SCAN_PLAN);
}
@Test
@@ -1027,8 +1085,8 @@ public class TestRESTScanPlanning extends
TestBaseWithRESTServer {
ImmutableMap.of(
CatalogProperties.FILE_IO_IMPL,
"org.apache.iceberg.inmemory.InMemoryFileIO",
- RESTCatalogProperties.REST_SCAN_PLANNING_ENABLED,
- "true"));
+ RESTCatalogProperties.SCAN_PLANNING_MODE,
+ RESTCatalogProperties.ScanPlanningMode.SERVER.modeName()));
Table table = restTableFor(catalog, "file_io_propagation");
@@ -1103,4 +1161,69 @@ public class TestRESTScanPlanning extends
TestBaseWithRESTServer {
return response;
}
+
+ @Test
+ public void serverConfigTakesPrecedenceOnMismatch() {
+ // Client=SERVER, Server=CLIENT
+ CatalogWithAdapter catalogWithAdapter1 =
+ catalogWithModes(
+ RESTCatalogProperties.ScanPlanningMode.SERVER.modeName(),
+ RESTCatalogProperties.ScanPlanningMode.CLIENT.modeName());
+ catalogWithAdapter1.catalog.createNamespace(NS);
+
+ Table table1 =
+ catalogWithAdapter1
+ .catalog
+ .buildTable(TableIdentifier.of(NS, "mismatch_test"), SCHEMA)
+ .create();
+
+
assertThat(table1).isNotInstanceOf(RESTTable.class).isInstanceOf(BaseTable.class);
+
+ // Client=CLIENT, Server=SERVER
+ CatalogWithAdapter catalogWithAdapter2 =
+ catalogWithModes(
+ RESTCatalogProperties.ScanPlanningMode.CLIENT.modeName(),
+ RESTCatalogProperties.ScanPlanningMode.SERVER.modeName());
+
+ Table table2 =
+ catalogWithAdapter2
+ .catalog
+ .buildTable(TableIdentifier.of(NS,
"client_override_rejected_test"), SCHEMA)
+ .create();
+
+ assertThat(table2).isInstanceOf(RESTTable.class);
+ }
+
+ @Test
+ public void clientExplicitlyRequestsClientSidePlanning() {
+ CatalogWithAdapter catalogWithAdapter =
+ catalogWithModes(
+ RESTCatalogProperties.ScanPlanningMode.CLIENT.modeName(),
+ RESTCatalogProperties.ScanPlanningMode.CLIENT.modeName());
+ catalogWithAdapter.catalog.createNamespace(NS);
+
+ Table table =
+ catalogWithAdapter
+ .catalog
+ .buildTable(TableIdentifier.of(NS, "client_explicit_test"), SCHEMA)
+ .create();
+
+
assertThat(table).isNotInstanceOf(RESTTable.class).isInstanceOf(BaseTable.class);
+ }
+
+ @Test
+ public void clientRequestsClientAndServerReturnsNothing() {
+ CatalogWithAdapter catalogWithAdapter =
+
catalogWithModes(RESTCatalogProperties.ScanPlanningMode.CLIENT.modeName(),
null);
+ catalogWithAdapter.catalog.createNamespace(NS);
+
+ Table table =
+ catalogWithAdapter
+ .catalog
+ .buildTable(TableIdentifier.of(NS, "client_server_null_test"),
SCHEMA)
+ .create();
+
+ assertThat(table).isNotInstanceOf(RESTTable.class);
+ assertThat(table).isInstanceOf(BaseTable.class);
+ }
}
diff --git
a/spark/v3.4/spark-extensions/src/test/java/org/apache/iceberg/spark/extensions/TestRemoteScanPlanning.java
b/spark/v3.4/spark-extensions/src/test/java/org/apache/iceberg/spark/extensions/TestRemoteScanPlanning.java
index ed90da7fd4..9c31eb970b 100644
---
a/spark/v3.4/spark-extensions/src/test/java/org/apache/iceberg/spark/extensions/TestRemoteScanPlanning.java
+++
b/spark/v3.4/spark-extensions/src/test/java/org/apache/iceberg/spark/extensions/TestRemoteScanPlanning.java
@@ -40,7 +40,9 @@ public class TestRemoteScanPlanning extends TestSelect {
.put(CatalogProperties.URI,
restCatalog.properties().get(CatalogProperties.URI))
// this flag is typically only set by the server, but we set it
from the client for
// testing
- .put(RESTCatalogProperties.REST_SCAN_PLANNING_ENABLED, "true")
+ .put(
+ RESTCatalogProperties.SCAN_PLANNING_MODE,
+ RESTCatalogProperties.ScanPlanningMode.SERVER.modeName())
.build(),
SparkCatalogConfig.REST.catalogName() + ".default.binary_table"
}
diff --git
a/spark/v3.5/spark-extensions/src/test/java/org/apache/iceberg/spark/extensions/TestRemoteScanPlanning.java
b/spark/v3.5/spark-extensions/src/test/java/org/apache/iceberg/spark/extensions/TestRemoteScanPlanning.java
index ed90da7fd4..9c31eb970b 100644
---
a/spark/v3.5/spark-extensions/src/test/java/org/apache/iceberg/spark/extensions/TestRemoteScanPlanning.java
+++
b/spark/v3.5/spark-extensions/src/test/java/org/apache/iceberg/spark/extensions/TestRemoteScanPlanning.java
@@ -40,7 +40,9 @@ public class TestRemoteScanPlanning extends TestSelect {
.put(CatalogProperties.URI,
restCatalog.properties().get(CatalogProperties.URI))
// this flag is typically only set by the server, but we set it
from the client for
// testing
- .put(RESTCatalogProperties.REST_SCAN_PLANNING_ENABLED, "true")
+ .put(
+ RESTCatalogProperties.SCAN_PLANNING_MODE,
+ RESTCatalogProperties.ScanPlanningMode.SERVER.modeName())
.build(),
SparkCatalogConfig.REST.catalogName() + ".default.binary_table"
}
diff --git
a/spark/v4.0/spark-extensions/src/test/java/org/apache/iceberg/spark/extensions/TestRemoteScanPlanning.java
b/spark/v4.0/spark-extensions/src/test/java/org/apache/iceberg/spark/extensions/TestRemoteScanPlanning.java
index ed90da7fd4..9c31eb970b 100644
---
a/spark/v4.0/spark-extensions/src/test/java/org/apache/iceberg/spark/extensions/TestRemoteScanPlanning.java
+++
b/spark/v4.0/spark-extensions/src/test/java/org/apache/iceberg/spark/extensions/TestRemoteScanPlanning.java
@@ -40,7 +40,9 @@ public class TestRemoteScanPlanning extends TestSelect {
.put(CatalogProperties.URI,
restCatalog.properties().get(CatalogProperties.URI))
// this flag is typically only set by the server, but we set it
from the client for
// testing
- .put(RESTCatalogProperties.REST_SCAN_PLANNING_ENABLED, "true")
+ .put(
+ RESTCatalogProperties.SCAN_PLANNING_MODE,
+ RESTCatalogProperties.ScanPlanningMode.SERVER.modeName())
.build(),
SparkCatalogConfig.REST.catalogName() + ".default.binary_table"
}
diff --git
a/spark/v4.1/spark-extensions/src/test/java/org/apache/iceberg/spark/extensions/TestRemoteScanPlanning.java
b/spark/v4.1/spark-extensions/src/test/java/org/apache/iceberg/spark/extensions/TestRemoteScanPlanning.java
index ed90da7fd4..9c31eb970b 100644
---
a/spark/v4.1/spark-extensions/src/test/java/org/apache/iceberg/spark/extensions/TestRemoteScanPlanning.java
+++
b/spark/v4.1/spark-extensions/src/test/java/org/apache/iceberg/spark/extensions/TestRemoteScanPlanning.java
@@ -40,7 +40,9 @@ public class TestRemoteScanPlanning extends TestSelect {
.put(CatalogProperties.URI,
restCatalog.properties().get(CatalogProperties.URI))
// this flag is typically only set by the server, but we set it
from the client for
// testing
- .put(RESTCatalogProperties.REST_SCAN_PLANNING_ENABLED, "true")
+ .put(
+ RESTCatalogProperties.SCAN_PLANNING_MODE,
+ RESTCatalogProperties.ScanPlanningMode.SERVER.modeName())
.build(),
SparkCatalogConfig.REST.catalogName() + ".default.binary_table"
}