This is an automated email from the ASF dual-hosted git repository.
yufei pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/polaris.git
The following commit(s) were added to refs/heads/main by this push:
new 40283b25a Service: Add location tests for views (#2496)
40283b25a is described below
commit 40283b25a6cc348ca0797f5c024e405497e7d641
Author: Yufei Gu <[email protected]>
AuthorDate: Wed Sep 3 16:11:23 2025 -0700
Service: Add location tests for views (#2496)
---
.../iceberg/IcebergAllowedLocationTest.java | 171 +++++++++++++++++++++
1 file changed, 171 insertions(+)
diff --git
a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergAllowedLocationTest.java
b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergAllowedLocationTest.java
index f24d3dd37..789122dd8 100644
---
a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergAllowedLocationTest.java
+++
b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergAllowedLocationTest.java
@@ -20,28 +20,39 @@
package org.apache.polaris.service.catalog.iceberg;
import static
org.apache.polaris.core.config.FeatureConfiguration.OPTIMIZED_SIBLING_CHECK;
+import static
org.apache.polaris.core.entity.table.IcebergTableLikeEntity.USER_SPECIFIED_WRITE_METADATA_LOCATION_KEY;
import static org.apache.polaris.service.admin.PolarisAuthzTestBase.SCHEMA;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import jakarta.ws.rs.core.Response;
import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
+import org.apache.iceberg.MetadataUpdate;
import org.apache.iceberg.catalog.Namespace;
+import org.apache.iceberg.catalog.TableIdentifier;
import org.apache.iceberg.exceptions.ForbiddenException;
import org.apache.iceberg.rest.requests.CreateNamespaceRequest;
import org.apache.iceberg.rest.requests.CreateTableRequest;
+import org.apache.iceberg.rest.requests.CreateViewRequest;
+import org.apache.iceberg.rest.requests.ImmutableCreateViewRequest;
+import org.apache.iceberg.rest.requests.UpdateTableRequest;
+import org.apache.iceberg.view.ImmutableSQLViewRepresentation;
+import org.apache.iceberg.view.ImmutableViewVersion;
import org.apache.polaris.core.admin.model.Catalog;
import org.apache.polaris.core.admin.model.CatalogProperties;
import org.apache.polaris.core.admin.model.CreateCatalogRequest;
import org.apache.polaris.core.admin.model.FileStorageConfigInfo;
import org.apache.polaris.core.admin.model.StorageConfigInfo;
import org.apache.polaris.service.TestServices;
+import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
@@ -49,6 +60,17 @@ public class IcebergAllowedLocationTest {
private static final String namespace = "ns";
private static final String catalog = "test-catalog";
+ private static final String VIEW_QUERY = "select * from ns.tbl";
+ public static final ImmutableViewVersion VIEW_VERSION =
+ ImmutableViewVersion.builder()
+ .versionId(1)
+ .timestampMillis(System.currentTimeMillis())
+ .schemaId(1)
+ .defaultNamespace(Namespace.of(namespace))
+ .addRepresentations(
+
ImmutableSQLViewRepresentation.builder().sql(VIEW_QUERY).dialect("spark").build())
+ .build();
+
private String getTableName() {
return "table_" + UUID.randomUUID();
}
@@ -131,6 +153,155 @@ public class IcebergAllowedLocationTest {
return services;
}
+ @Test
+ void testViewWithAllowedLocations(@TempDir Path tmpDir) {
+ var viewId = TableIdentifier.of(namespace, "view");
+ var services = getTestServices();
+ var catalogLocation =
tmpDir.resolve(catalog).toAbsolutePath().toUri().toString();
+ createCatalog(services, Map.of(), catalogLocation,
List.of(catalogLocation));
+ var namespaceLocation = catalogLocation + "/" + namespace;
+ createNamespace(services, namespaceLocation);
+
+ // create a view with allowed locations
+ String customAllowedLocation1 = Paths.get(namespaceLocation,
"custom-location1").toString();
+ String customAllowedLocation2 = Paths.get(namespaceLocation,
"custom-location2").toString();
+
+ CreateViewRequest createViewRequest =
+ getCreateViewRequest(customAllowedLocation2, viewId.name(),
customAllowedLocation1);
+ var response =
+ services
+ .restApi()
+ .createView(
+ catalog,
+ namespace,
+ createViewRequest,
+ services.realmContext(),
+ services.securityContext());
+
+ assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+
+ // update the view with allowed locations
+ String customAllowedLocation3 = Paths.get(namespaceLocation,
"custom-location3").toString();
+
+ Map<String, String> updatedProperties = new HashMap<>();
+ updatedProperties.put(USER_SPECIFIED_WRITE_METADATA_LOCATION_KEY,
customAllowedLocation3);
+
+ UpdateTableRequest updateRequest =
+ UpdateTableRequest.create(
+ viewId, List.of(), List.of(new
MetadataUpdate.SetProperties(updatedProperties)));
+
+ var updateResponse =
+ services
+ .catalogAdapter()
+ .newHandlerWrapper(services.securityContext(), catalog)
+ .replaceView(viewId, updateRequest);
+ assertEquals(
+
updateResponse.metadata().properties().get(USER_SPECIFIED_WRITE_METADATA_LOCATION_KEY),
+ customAllowedLocation3);
+ }
+
+ @Test
+ void testViewOutsideAllowedLocations(@TempDir Path tmpDir) {
+ var viewId = TableIdentifier.of(namespace, "view");
+ var services = getTestServices();
+
+ var catalogBaseLocation =
tmpDir.resolve(catalog).toAbsolutePath().toUri().toString();
+ var namespaceLocation = catalogBaseLocation + "/" + namespace;
+
+ createCatalog(services, Map.of(), catalogBaseLocation,
List.of(catalogBaseLocation));
+ createNamespace(services, namespaceLocation);
+
+ var locationNotAllowed =
+
tmpDir.resolve("location-not-allowed").toAbsolutePath().toUri().toString();
+ var locationAllowed = Paths.get(namespaceLocation,
"custom-location").toString();
+
+ // Test 1: Create a view with allowed location, and update it with a
location not allowed
+ var properties = new HashMap<String, String>();
+
+ CreateViewRequest createViewRequest =
+ ImmutableCreateViewRequest.builder()
+ .name(viewId.name())
+ .schema(SCHEMA)
+ .viewVersion(VIEW_VERSION)
+ .location(locationAllowed)
+ .properties(properties)
+ .build();
+
+ var response =
+ services
+ .restApi()
+ .createView(
+ catalog,
+ namespace,
+ createViewRequest,
+ services.realmContext(),
+ services.securityContext());
+ assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
+
+ Map<String, String> updatedProperties = new HashMap<>();
+ updatedProperties.put(USER_SPECIFIED_WRITE_METADATA_LOCATION_KEY,
locationNotAllowed);
+
+ var updateRequest =
+ UpdateTableRequest.create(
+ viewId,
+ List.of(), // requirements
+ List.of(new MetadataUpdate.SetProperties(updatedProperties)));
+
+ assertThatThrownBy(
+ () ->
+ services
+ .catalogAdapter()
+ .newHandlerWrapper(services.securityContext(), catalog)
+ .replaceView(viewId, updateRequest));
+
+ // Test 2: Try to create a view with location not allowed
+ var createViewRequestNotAllowed =
+ getCreateViewRequest(locationNotAllowed, "view2", locationNotAllowed);
+
+ assertThatThrownBy(
+ () ->
+ services
+ .restApi()
+ .createView(
+ catalog,
+ namespace,
+ createViewRequestNotAllowed,
+ services.realmContext(),
+ services.securityContext()))
+ .isInstanceOf(ForbiddenException.class)
+ .hasMessageContaining("Invalid locations");
+
+ // Test 3: Try to create a view with metadata location not allowed
+ var createViewRequestMetadataNotAllowed =
+ getCreateViewRequest(locationNotAllowed, "view3", locationAllowed);
+
+ assertThatThrownBy(
+ () ->
+ services
+ .restApi()
+ .createView(
+ catalog,
+ namespace,
+ createViewRequestMetadataNotAllowed,
+ services.realmContext(),
+ services.securityContext()))
+ .isInstanceOf(ForbiddenException.class)
+ .hasMessageContaining("Invalid locations");
+ }
+
+ private static @NotNull CreateViewRequest getCreateViewRequest(
+ String writeMetadataPath, String viewName, String location) {
+ var properties = new HashMap<String, String>();
+ properties.put(USER_SPECIFIED_WRITE_METADATA_LOCATION_KEY,
writeMetadataPath);
+ return ImmutableCreateViewRequest.builder()
+ .name(viewName)
+ .schema(SCHEMA)
+ .viewVersion(VIEW_VERSION)
+ .location(location)
+ .properties(properties)
+ .build();
+ }
+
private void createCatalog(
TestServices services,
Map<String, String> catalogConfig,