This is an automated email from the ASF dual-hosted git repository.
emaynard 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 07ed4bb3c Implement GenericTableCatalogAdapter; admin-related fixes
(#1298)
07ed4bb3c is described below
commit 07ed4bb3c554793c9a54c516d5974cdbf10c368e
Author: Eric Maynard <[email protected]>
AuthorDate: Thu Apr 10 13:23:20 2025 -0700
Implement GenericTableCatalogAdapter; admin-related fixes (#1298)
* initial commit:
* debugging
* some polish
* autolint
* spec change
* bugfix
* bugfix
* various fixes
* another missing admin location
* autolint
* false by default
* fixes per review
* autolint
* more fixes
* DRY
* revert small change for a better error
* integration test
* extra test
* autolint
* stable
* wip
* rework subtypes a bit
* stable again
* autolint
* apply new lint rule
* errorprone again
* adjustments per review
* update golden files
* add another test
* clean up logic in PolarisAdminService
* autolint
* more fixes per review
* format
---
integration-tests/build.gradle.kts | 1 +
.../polaris/service/it/env/GenericTableApi.java | 96 +++++++++++++
.../polaris/service/it/env/PolarisClient.java | 10 ++
.../it/test/PolarisRestCatalogIntegrationTest.java | 148 +++++++++++++++++++++
.../polaris/core/config/FeatureConfiguration.java | 2 +-
regtests/t_spark_sql/ref/spark_sql_basic.sh.ref | 2 +-
regtests/t_spark_sql/ref/spark_sql_views.sh.ref | 2 +-
.../polaris/service/admin/PolarisAdminService.java | 94 ++++++++-----
.../service/catalog/common/CatalogAdapter.java | 34 +++++
.../service/catalog/common/CatalogHandler.java | 57 +++++---
.../generic/GenericTableCatalogAdapter.java | 75 ++++++++++-
.../generic/GenericTableCatalogHandler.java | 4 +-
.../catalog/iceberg/IcebergCatalogAdapter.java | 12 +-
13 files changed, 461 insertions(+), 76 deletions(-)
diff --git a/integration-tests/build.gradle.kts
b/integration-tests/build.gradle.kts
index 55aafe7b0..e5018457f 100644
--- a/integration-tests/build.gradle.kts
+++ b/integration-tests/build.gradle.kts
@@ -22,6 +22,7 @@ plugins { id("polaris-server") }
dependencies {
implementation(project(":polaris-core"))
implementation(project(":polaris-api-management-model"))
+ implementation(project(":polaris-api-catalog-service"))
implementation(libs.jakarta.ws.rs.api)
implementation(libs.guava)
diff --git
a/integration-tests/src/main/java/org/apache/polaris/service/it/env/GenericTableApi.java
b/integration-tests/src/main/java/org/apache/polaris/service/it/env/GenericTableApi.java
new file mode 100644
index 000000000..52935a8dc
--- /dev/null
+++
b/integration-tests/src/main/java/org/apache/polaris/service/it/env/GenericTableApi.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.polaris.service.it.env;
+
+import static jakarta.ws.rs.core.Response.Status.NO_CONTENT;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Response;
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+import org.apache.iceberg.catalog.Namespace;
+import org.apache.iceberg.catalog.TableIdentifier;
+import org.apache.iceberg.rest.RESTUtil;
+import org.apache.polaris.service.types.CreateGenericTableRequest;
+import org.apache.polaris.service.types.GenericTable;
+import org.apache.polaris.service.types.ListGenericTablesResponse;
+import org.apache.polaris.service.types.LoadGenericTableResponse;
+
+/**
+ * A simple, non-exhaustive set of helper methods for accessing the generic
tables REST API
+ *
+ * @see PolarisClient#genericTableApi(ClientCredentials)
+ */
+public class GenericTableApi extends RestApi {
+ GenericTableApi(Client client, PolarisApiEndpoints endpoints, String
authToken, URI uri) {
+ super(client, endpoints, authToken, uri);
+ }
+
+ public void purge(String catalog, Namespace ns) {
+ listGenericTables(catalog, ns).forEach(t -> dropGenericTable(catalog, t));
+ }
+
+ public List<TableIdentifier> listGenericTables(String catalog, Namespace
namespace) {
+ String ns = RESTUtil.encodeNamespace(namespace);
+ try (Response res =
+ request("polaris/v1/{cat}/namespaces/{ns}/generic-tables",
Map.of("cat", catalog, "ns", ns))
+ .get()) {
+
assertThat(res.getStatus()).isEqualTo(Response.Status.OK.getStatusCode());
+ return
res.readEntity(ListGenericTablesResponse.class).getIdentifiers().stream().toList();
+ }
+ }
+
+ public void dropGenericTable(String catalog, TableIdentifier id) {
+ String ns = RESTUtil.encodeNamespace(id.namespace());
+ try (Response res =
+ request(
+ "polaris/v1/{cat}/namespaces/{ns}/generic-tables/{table}",
+ Map.of("cat", catalog, "table", id.name(), "ns", ns))
+ .delete()) {
+ assertThat(res.getStatus()).isEqualTo(NO_CONTENT.getStatusCode());
+ }
+ }
+
+ public GenericTable getGenericTable(String catalog, TableIdentifier id) {
+ String ns = RESTUtil.encodeNamespace(id.namespace());
+ try (Response res =
+ request(
+ "polaris/v1/{cat}/namespaces/{ns}/generic-tables/{table}",
+ Map.of("cat", catalog, "table", id.name(), "ns", ns))
+ .get()) {
+ return res.readEntity(LoadGenericTableResponse.class).getTable();
+ }
+ }
+
+ public GenericTable createGenericTable(
+ String catalog, TableIdentifier id, String format, Map<String, String>
properties) {
+ String ns = RESTUtil.encodeNamespace(id.namespace());
+ try (Response res =
+ request(
+ "polaris/v1/{cat}/namespaces/{ns}/generic-tables/",
+ Map.of("cat", catalog, "ns", ns))
+ .post(
+ Entity.json(new CreateGenericTableRequest(id.name(), format,
"doc", properties)))) {
+ return res.readEntity(LoadGenericTableResponse.class).getTable();
+ }
+ }
+}
diff --git
a/integration-tests/src/main/java/org/apache/polaris/service/it/env/PolarisClient.java
b/integration-tests/src/main/java/org/apache/polaris/service/it/env/PolarisClient.java
index 163c217c2..e18f8736b 100644
---
a/integration-tests/src/main/java/org/apache/polaris/service/it/env/PolarisClient.java
+++
b/integration-tests/src/main/java/org/apache/polaris/service/it/env/PolarisClient.java
@@ -106,6 +106,16 @@ public final class PolarisClient implements AutoCloseable {
return new CatalogApi(client, endpoints, null,
endpoints.catalogApiEndpoint());
}
+ public GenericTableApi genericTableApi(PrincipalWithCredentials principal) {
+ return new GenericTableApi(
+ client, endpoints, obtainToken(principal),
endpoints.catalogApiEndpoint());
+ }
+
+ public GenericTableApi genericTableApi(ClientCredentials credentials) {
+ return new GenericTableApi(
+ client, endpoints, obtainToken(credentials),
endpoints.catalogApiEndpoint());
+ }
+
/**
* Requests an access token from the Polaris server for the client ID/secret
pair that is part of
* the given principal data object.
diff --git
a/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisRestCatalogIntegrationTest.java
b/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisRestCatalogIntegrationTest.java
index f7466b77f..c895b49cf 100644
---
a/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisRestCatalogIntegrationTest.java
+++
b/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisRestCatalogIntegrationTest.java
@@ -18,11 +18,13 @@
*/
package org.apache.polaris.service.it.test;
+import static jakarta.ws.rs.core.Response.Status.NOT_FOUND;
import static org.apache.polaris.service.it.env.PolarisClient.polarisClient;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import com.google.common.collect.ImmutableMap;
+import jakarta.ws.rs.ProcessingException;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.client.Invocation;
import jakarta.ws.rs.core.HttpHeaders;
@@ -32,11 +34,13 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
import java.net.URI;
import java.nio.file.Path;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
+import java.util.stream.Stream;
import org.apache.hadoop.conf.Configuration;
import org.apache.iceberg.BaseTable;
import org.apache.iceberg.BaseTransaction;
@@ -57,6 +61,7 @@ import org.apache.iceberg.exceptions.ForbiddenException;
import org.apache.iceberg.expressions.Expressions;
import org.apache.iceberg.io.ResolvingFileIO;
import org.apache.iceberg.rest.RESTCatalog;
+import org.apache.iceberg.rest.RESTUtil;
import org.apache.iceberg.rest.requests.CreateTableRequest;
import org.apache.iceberg.rest.responses.ErrorResponse;
import org.apache.iceberg.types.Types;
@@ -82,12 +87,14 @@ import org.apache.polaris.core.entity.CatalogEntity;
import org.apache.polaris.core.entity.PolarisEntityConstants;
import org.apache.polaris.service.it.env.CatalogApi;
import org.apache.polaris.service.it.env.ClientCredentials;
+import org.apache.polaris.service.it.env.GenericTableApi;
import org.apache.polaris.service.it.env.IcebergHelper;
import org.apache.polaris.service.it.env.IntegrationTestsHelper;
import org.apache.polaris.service.it.env.ManagementApi;
import org.apache.polaris.service.it.env.PolarisApiEndpoints;
import org.apache.polaris.service.it.env.PolarisClient;
import org.apache.polaris.service.it.ext.PolarisIntegrationTestExtension;
+import org.apache.polaris.service.types.GenericTable;
import org.assertj.core.api.Assertions;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.AfterAll;
@@ -127,6 +134,7 @@ public class PolarisRestCatalogIntegrationTest extends
CatalogTests<RESTCatalog>
private PrincipalWithCredentials principalCredentials;
private CatalogApi catalogApi;
+ private GenericTableApi genericTableApi;
private RESTCatalog restCatalog;
private String currentCatalogName;
private Map<String, String> restCatalogConfig;
@@ -178,6 +186,7 @@ public class PolarisRestCatalogIntegrationTest extends
CatalogTests<RESTCatalog>
principalCredentials =
managementApi.createPrincipalWithRole(principalName, principalRoleName);
catalogApi = client.catalogApi(principalCredentials);
+ genericTableApi = client.genericTableApi(principalCredentials);
Method method = testInfo.getTestMethod().orElseThrow();
currentCatalogName = client.newEntityName(method.getName());
@@ -1194,4 +1203,143 @@ public class PolarisRestCatalogIntegrationTest extends
CatalogTests<RESTCatalog>
assertThat(response).returns(Response.Status.OK.getStatusCode(),
Response::getStatus);
}
}
+
+ @Test
+ public void testCreateGenericTable() {
+ Namespace namespace = Namespace.of("ns1");
+ restCatalog.createNamespace(namespace);
+ TableIdentifier tableIdentifier = TableIdentifier.of(namespace, "tbl1");
+
+ GenericTable createResponse =
+ genericTableApi.createGenericTable(currentCatalogName,
tableIdentifier, "format", Map.of());
+ Assertions.assertThat(createResponse.getFormat()).isEqualTo("format");
+
+ genericTableApi.purge(currentCatalogName, namespace);
+ }
+
+ @Test
+ public void testLoadGenericTable() {
+ Namespace namespace = Namespace.of("ns1");
+ restCatalog.createNamespace(namespace);
+ TableIdentifier tableIdentifier = TableIdentifier.of(namespace, "tbl1");
+
+ genericTableApi.createGenericTable(currentCatalogName, tableIdentifier,
"format", Map.of());
+
+ GenericTable loadResponse =
+ genericTableApi.getGenericTable(currentCatalogName, tableIdentifier);
+ Assertions.assertThat(loadResponse.getFormat()).isEqualTo("format");
+
+ genericTableApi.purge(currentCatalogName, namespace);
+ }
+
+ @Test
+ public void testListGenericTables() {
+ Namespace namespace = Namespace.of("ns1");
+ restCatalog.createNamespace(namespace);
+ TableIdentifier tableIdentifier1 = TableIdentifier.of(namespace, "tbl1");
+ TableIdentifier tableIdentifier2 = TableIdentifier.of(namespace, "tbl2");
+
+ genericTableApi.createGenericTable(currentCatalogName, tableIdentifier1,
"format", Map.of());
+ genericTableApi.createGenericTable(currentCatalogName, tableIdentifier2,
"format", Map.of());
+
+ List<TableIdentifier> identifiers =
+ genericTableApi.listGenericTables(currentCatalogName, namespace);
+
+ Assertions.assertThat(identifiers).hasSize(2);
+ Assertions.assertThat(identifiers)
+ .containsExactlyInAnyOrder(tableIdentifier1, tableIdentifier2);
+
+ genericTableApi.purge(currentCatalogName, namespace);
+ }
+
+ @Test
+ public void testDropGenericTable() {
+ Namespace namespace = Namespace.of("ns1");
+ restCatalog.createNamespace(namespace);
+ TableIdentifier tableIdentifier = TableIdentifier.of(namespace, "tbl1");
+
+ genericTableApi.createGenericTable(currentCatalogName, tableIdentifier,
"format", Map.of());
+
+ GenericTable loadResponse =
+ genericTableApi.getGenericTable(currentCatalogName, tableIdentifier);
+ Assertions.assertThat(loadResponse.getFormat()).isEqualTo("format");
+
+ genericTableApi.dropGenericTable(currentCatalogName, tableIdentifier);
+
+ Assertions.assertThatCode(
+ () -> genericTableApi.getGenericTable(currentCatalogName,
tableIdentifier))
+ .isInstanceOf(ProcessingException.class);
+
+ genericTableApi.purge(currentCatalogName, namespace);
+ }
+
+ @Test
+ public void testGrantsOnGenericTable() {
+ Namespace namespace = Namespace.of("ns1");
+ restCatalog.createNamespace(namespace);
+ TableIdentifier tableIdentifier = TableIdentifier.of(namespace, "tbl1");
+ genericTableApi.createGenericTable(currentCatalogName, tableIdentifier,
"format", Map.of());
+
+ managementApi.createCatalogRole(currentCatalogName, "catalogrole1");
+
+ Stream<TableGrant> tableGrants =
+ Arrays.stream(TablePrivilege.values())
+ .map(
+ p -> {
+ return new TableGrant(List.of("ns1"), "tbl1", p,
GrantResource.TypeEnum.TABLE);
+ });
+
+ tableGrants.forEach(g -> managementApi.addGrant(currentCatalogName,
"catalogrole1", g));
+
+ genericTableApi.purge(currentCatalogName, namespace);
+ }
+
+ @Test
+ public void testGrantsOnNonExistingGenericTable() {
+ Namespace namespace = Namespace.of("ns1");
+ restCatalog.createNamespace(namespace);
+
+ managementApi.createCatalogRole(currentCatalogName, "catalogrole1");
+
+ Stream<TableGrant> tableGrants =
+ Arrays.stream(TablePrivilege.values())
+ .map(
+ p -> {
+ return new TableGrant(List.of("ns1"), "tbl1", p,
GrantResource.TypeEnum.TABLE);
+ });
+
+ tableGrants.forEach(
+ g -> {
+ try (Response response =
+ managementApi
+ .request(
+ "v1/catalogs/{cat}/catalog-roles/{role}/grants",
+ Map.of("cat", currentCatalogName, "role",
"catalogrole1"))
+ .put(Entity.json(g))) {
+
+
assertThat(response.getStatus()).isEqualTo(NOT_FOUND.getStatusCode());
+ }
+ });
+
+ genericTableApi.purge(currentCatalogName, namespace);
+ }
+
+ @Test
+ public void testDropNonExistingGenericTable() {
+ Namespace namespace = Namespace.of("ns1");
+ restCatalog.createNamespace(namespace);
+ TableIdentifier tableIdentifier = TableIdentifier.of(namespace, "tbl1");
+
+ String ns = RESTUtil.encodeNamespace(tableIdentifier.namespace());
+ try (Response res =
+ genericTableApi
+ .request(
+ "polaris/v1/{cat}/namespaces/{ns}/generic-tables/{table}",
+ Map.of("cat", currentCatalogName, "table",
tableIdentifier.name(), "ns", ns))
+ .delete()) {
+ assertThat(res.getStatus()).isEqualTo(NOT_FOUND.getStatusCode());
+ }
+
+ genericTableApi.purge(currentCatalogName, namespace);
+ }
}
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/config/FeatureConfiguration.java
b/polaris-core/src/main/java/org/apache/polaris/core/config/FeatureConfiguration.java
index cc8bae454..8e71d049c 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/config/FeatureConfiguration.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/config/FeatureConfiguration.java
@@ -188,6 +188,6 @@ public class FeatureConfiguration<T> extends
PolarisConfiguration<T> {
PolarisConfiguration.<Boolean>builder()
.key("ENABLE_GENERIC_TABLES")
.description("If true, the generic-tables endpoints are enabled")
- .defaultValue(false)
+ .defaultValue(true)
.buildFeatureConfiguration();
}
diff --git a/regtests/t_spark_sql/ref/spark_sql_basic.sh.ref
b/regtests/t_spark_sql/ref/spark_sql_basic.sh.ref
index 4cd8ce0f8..eaf0e18a8 100755
--- a/regtests/t_spark_sql/ref/spark_sql_basic.sh.ref
+++ b/regtests/t_spark_sql/ref/spark_sql_basic.sh.ref
@@ -1,4 +1,4 @@
-{"defaults":{"default-base-location":"file:///tmp/spark_sql_s3_catalog"},"overrides":{"prefix":"spark_sql_basic_catalog"},"endpoints":["GET
/v1/{prefix}/namespaces","GET /v1/{prefix}/namespaces/{namespace}","HEAD
/v1/{prefix}/namespaces/{namespace}","POST /v1/{prefix}/namespaces","POST
/v1/{prefix}/namespaces/{namespace}/properties","DELETE
/v1/{prefix}/namespaces/{namespace}","GET
/v1/{prefix}/namespaces/{namespace}/tables","GET
/v1/{prefix}/namespaces/{namespace}/tables/{table}","HEAD [...]
+{"defaults":{"default-base-location":"file:///tmp/spark_sql_s3_catalog"},"overrides":{"prefix":"spark_sql_basic_catalog"},"endpoints":["GET
/v1/{prefix}/namespaces","GET /v1/{prefix}/namespaces/{namespace}","HEAD
/v1/{prefix}/namespaces/{namespace}","POST /v1/{prefix}/namespaces","POST
/v1/{prefix}/namespaces/{namespace}/properties","DELETE
/v1/{prefix}/namespaces/{namespace}","GET
/v1/{prefix}/namespaces/{namespace}/tables","GET
/v1/{prefix}/namespaces/{namespace}/tables/{table}","HEAD [...]
Catalog created
spark-sql (default)> use polaris;
spark-sql ()> show namespaces;
diff --git a/regtests/t_spark_sql/ref/spark_sql_views.sh.ref
b/regtests/t_spark_sql/ref/spark_sql_views.sh.ref
index 853c736db..ffae79311 100755
--- a/regtests/t_spark_sql/ref/spark_sql_views.sh.ref
+++ b/regtests/t_spark_sql/ref/spark_sql_views.sh.ref
@@ -1,4 +1,4 @@
-{"defaults":{"default-base-location":"file:///tmp/spark_sql_s3_catalog"},"overrides":{"prefix":"spark_sql_views_catalog"},"endpoints":["GET
/v1/{prefix}/namespaces","GET /v1/{prefix}/namespaces/{namespace}","HEAD
/v1/{prefix}/namespaces/{namespace}","POST /v1/{prefix}/namespaces","POST
/v1/{prefix}/namespaces/{namespace}/properties","DELETE
/v1/{prefix}/namespaces/{namespace}","GET
/v1/{prefix}/namespaces/{namespace}/tables","GET
/v1/{prefix}/namespaces/{namespace}/tables/{table}","HEAD [...]
+{"defaults":{"default-base-location":"file:///tmp/spark_sql_s3_catalog"},"overrides":{"prefix":"spark_sql_views_catalog"},"endpoints":["GET
/v1/{prefix}/namespaces","GET /v1/{prefix}/namespaces/{namespace}","HEAD
/v1/{prefix}/namespaces/{namespace}","POST /v1/{prefix}/namespaces","POST
/v1/{prefix}/namespaces/{namespace}/properties","DELETE
/v1/{prefix}/namespaces/{namespace}","GET
/v1/{prefix}/namespaces/{namespace}/tables","GET
/v1/{prefix}/namespaces/{namespace}/tables/{table}","HEAD [...]
Catalog created
spark-sql (default)> use polaris;
spark-sql ()> show namespaces;
diff --git
a/service/common/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java
b/service/common/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java
index effcededd..8f1c9bc92 100644
---
a/service/common/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java
+++
b/service/common/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java
@@ -40,8 +40,6 @@ import org.apache.iceberg.exceptions.AlreadyExistsException;
import org.apache.iceberg.exceptions.BadRequestException;
import org.apache.iceberg.exceptions.CommitFailedException;
import org.apache.iceberg.exceptions.NoSuchNamespaceException;
-import org.apache.iceberg.exceptions.NoSuchTableException;
-import org.apache.iceberg.exceptions.NoSuchViewException;
import org.apache.iceberg.exceptions.NotFoundException;
import org.apache.iceberg.exceptions.ValidationException;
import org.apache.polaris.core.PolarisCallContext;
@@ -95,6 +93,7 @@ import
org.apache.polaris.core.storage.PolarisStorageConfigurationInfo;
import org.apache.polaris.core.storage.StorageLocation;
import org.apache.polaris.core.storage.aws.AwsStorageConfigurationInfo;
import org.apache.polaris.core.storage.azure.AzureStorageConfigurationInfo;
+import org.apache.polaris.service.catalog.common.CatalogHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -454,7 +453,7 @@ public class PolarisAdminService {
private void authorizeGrantOnTableLikeOperationOrThrow(
PolarisAuthorizableOperation op,
String catalogName,
- PolarisEntitySubType subType,
+ List<PolarisEntitySubType> subTypes,
TableIdentifier identifier,
String catalogRoleName) {
resolutionManifest =
@@ -472,18 +471,19 @@ public class PolarisAdminService {
throw new NotFoundException("Catalog not found: %s", catalogName);
} else if (status.getStatus() ==
ResolverStatus.StatusEnum.PATH_COULD_NOT_BE_FULLY_RESOLVED) {
if (status.getFailedToResolvePath().getLastEntityType() ==
PolarisEntityType.TABLE_LIKE) {
- if (subType == PolarisEntitySubType.ICEBERG_TABLE) {
- throw new NoSuchTableException("Table does not exist: %s",
identifier);
- } else {
- throw new NoSuchViewException("View does not exist: %s", identifier);
- }
+ CatalogHandler.throwNotFoundExceptionForTableLikeEntity(identifier,
subTypes);
} else {
throw new NotFoundException("CatalogRole not found: %s.%s",
catalogName, catalogRoleName);
}
}
PolarisResolvedPathWrapper tableLikeWrapper =
- resolutionManifest.getResolvedPath(identifier,
PolarisEntityType.TABLE_LIKE, subType, true);
+ resolutionManifest.getResolvedPath(
+ identifier, PolarisEntityType.TABLE_LIKE,
PolarisEntitySubType.ANY_SUBTYPE, true);
+ if (!subTypes.contains(tableLikeWrapper.getRawLeafEntity().getSubType())) {
+ CatalogHandler.throwNotFoundExceptionForTableLikeEntity(identifier,
subTypes);
+ }
+
PolarisResolvedPathWrapper catalogRoleWrapper =
resolutionManifest.getResolvedPath(catalogRoleName, true);
@@ -1495,10 +1495,18 @@ public class PolarisAdminService {
PolarisAuthorizableOperation op =
PolarisAuthorizableOperation.ADD_TABLE_GRANT_TO_CATALOG_ROLE;
authorizeGrantOnTableLikeOperationOrThrow(
- op, catalogName, PolarisEntitySubType.ICEBERG_TABLE, identifier,
catalogRoleName);
+ op,
+ catalogName,
+ List.of(PolarisEntitySubType.GENERIC_TABLE,
PolarisEntitySubType.ICEBERG_TABLE),
+ identifier,
+ catalogRoleName);
return grantPrivilegeOnTableLikeToRole(
- catalogName, catalogRoleName, identifier,
PolarisEntitySubType.ICEBERG_TABLE, privilege);
+ catalogName,
+ catalogRoleName,
+ identifier,
+ List.of(PolarisEntitySubType.GENERIC_TABLE,
PolarisEntitySubType.ICEBERG_TABLE),
+ privilege);
}
public boolean revokePrivilegeOnTableFromRole(
@@ -1510,10 +1518,18 @@ public class PolarisAdminService {
PolarisAuthorizableOperation.REVOKE_TABLE_GRANT_FROM_CATALOG_ROLE;
authorizeGrantOnTableLikeOperationOrThrow(
- op, catalogName, PolarisEntitySubType.ICEBERG_TABLE, identifier,
catalogRoleName);
+ op,
+ catalogName,
+ List.of(PolarisEntitySubType.GENERIC_TABLE,
PolarisEntitySubType.ICEBERG_TABLE),
+ identifier,
+ catalogRoleName);
return revokePrivilegeOnTableLikeFromRole(
- catalogName, catalogRoleName, identifier,
PolarisEntitySubType.ICEBERG_TABLE, privilege);
+ catalogName,
+ catalogRoleName,
+ identifier,
+ List.of(PolarisEntitySubType.GENERIC_TABLE,
PolarisEntitySubType.ICEBERG_TABLE),
+ privilege);
}
public boolean grantPrivilegeOnViewToRole(
@@ -1524,10 +1540,14 @@ public class PolarisAdminService {
PolarisAuthorizableOperation op =
PolarisAuthorizableOperation.ADD_VIEW_GRANT_TO_CATALOG_ROLE;
authorizeGrantOnTableLikeOperationOrThrow(
- op, catalogName, PolarisEntitySubType.ICEBERG_VIEW, identifier,
catalogRoleName);
+ op, catalogName, List.of(PolarisEntitySubType.ICEBERG_VIEW),
identifier, catalogRoleName);
return grantPrivilegeOnTableLikeToRole(
- catalogName, catalogRoleName, identifier,
PolarisEntitySubType.ICEBERG_VIEW, privilege);
+ catalogName,
+ catalogRoleName,
+ identifier,
+ List.of(PolarisEntitySubType.ICEBERG_VIEW),
+ privilege);
}
public boolean revokePrivilegeOnViewFromRole(
@@ -1539,10 +1559,14 @@ public class PolarisAdminService {
PolarisAuthorizableOperation.REVOKE_VIEW_GRANT_FROM_CATALOG_ROLE;
authorizeGrantOnTableLikeOperationOrThrow(
- op, catalogName, PolarisEntitySubType.ICEBERG_VIEW, identifier,
catalogRoleName);
+ op, catalogName, List.of(PolarisEntitySubType.ICEBERG_VIEW),
identifier, catalogRoleName);
return revokePrivilegeOnTableLikeFromRole(
- catalogName, catalogRoleName, identifier,
PolarisEntitySubType.ICEBERG_VIEW, privilege);
+ catalogName,
+ catalogRoleName,
+ identifier,
+ List.of(PolarisEntitySubType.ICEBERG_VIEW),
+ privilege);
}
public List<PolarisEntity> listAssigneePrincipalRolesForCatalogRole(
@@ -1606,7 +1630,8 @@ public class PolarisAdminService {
}
case TABLE_LIKE:
{
- if (baseEntity.getSubType() ==
PolarisEntitySubType.ICEBERG_TABLE) {
+ if (baseEntity.getSubType() == PolarisEntitySubType.ICEBERG_TABLE
+ || baseEntity.getSubType() ==
PolarisEntitySubType.GENERIC_TABLE) {
TableIdentifier identifier =
IcebergTableLikeEntity.of(baseEntity).getTableIdentifier();
TableGrant grant =
@@ -1616,7 +1641,7 @@ public class PolarisAdminService {
TablePrivilege.valueOf(privilege.toString()),
GrantResource.TypeEnum.TABLE);
tableGrants.add(grant);
- } else {
+ } else if (baseEntity.getSubType() ==
PolarisEntitySubType.ICEBERG_VIEW) {
TableIdentifier identifier =
IcebergTableLikeEntity.of(baseEntity).getTableIdentifier();
ViewGrant grant =
@@ -1626,6 +1651,9 @@ public class PolarisAdminService {
ViewPrivilege.valueOf(privilege.toString()),
GrantResource.TypeEnum.VIEW);
viewGrants.add(grant);
+ } else {
+ throw new IllegalStateException(
+ "Unrecognized entity subtype " + baseEntity.getSubType());
}
break;
}
@@ -1694,7 +1722,7 @@ public class PolarisAdminService {
String catalogName,
String catalogRoleName,
TableIdentifier identifier,
- PolarisEntitySubType subType,
+ List<PolarisEntitySubType> subTypes,
PolarisPrivilege privilege) {
if (findCatalogByName(catalogName).isEmpty()) {
throw new NotFoundException("Parent catalog %s not found", catalogName);
@@ -1704,13 +1732,11 @@ public class PolarisAdminService {
.orElseThrow(() -> new NotFoundException("CatalogRole %s not
found", catalogRoleName));
PolarisResolvedPathWrapper resolvedPathWrapper =
- resolutionManifest.getResolvedPath(identifier,
PolarisEntityType.TABLE_LIKE, subType);
- if (resolvedPathWrapper == null) {
- if (subType == PolarisEntitySubType.ICEBERG_VIEW) {
- throw new NotFoundException("View %s not found", identifier);
- } else {
- throw new NotFoundException("Table %s not found", identifier);
- }
+ resolutionManifest.getResolvedPath(
+ identifier, PolarisEntityType.TABLE_LIKE,
PolarisEntitySubType.ANY_SUBTYPE);
+ if (resolvedPathWrapper == null
+ ||
!subTypes.contains(resolvedPathWrapper.getRawLeafEntity().getSubType())) {
+ CatalogHandler.throwNotFoundExceptionForTableLikeEntity(identifier,
subTypes);
}
List<PolarisEntity> catalogPath = resolvedPathWrapper.getRawParentPath();
PolarisEntity tableLikeEntity = resolvedPathWrapper.getRawLeafEntity();
@@ -1732,7 +1758,7 @@ public class PolarisAdminService {
String catalogName,
String catalogRoleName,
TableIdentifier identifier,
- PolarisEntitySubType subType,
+ List<PolarisEntitySubType> subTypes,
PolarisPrivilege privilege) {
if (findCatalogByName(catalogName).isEmpty()) {
throw new NotFoundException("Parent catalog %s not found", catalogName);
@@ -1742,13 +1768,11 @@ public class PolarisAdminService {
.orElseThrow(() -> new NotFoundException("CatalogRole %s not
found", catalogRoleName));
PolarisResolvedPathWrapper resolvedPathWrapper =
- resolutionManifest.getResolvedPath(identifier,
PolarisEntityType.TABLE_LIKE, subType);
- if (resolvedPathWrapper == null) {
- if (subType == PolarisEntitySubType.ICEBERG_VIEW) {
- throw new NotFoundException("View %s not found", identifier);
- } else {
- throw new NotFoundException("Table %s not found", identifier);
- }
+ resolutionManifest.getResolvedPath(
+ identifier, PolarisEntityType.TABLE_LIKE,
PolarisEntitySubType.ANY_SUBTYPE);
+ if (resolvedPathWrapper == null
+ ||
!subTypes.contains(resolvedPathWrapper.getRawLeafEntity().getSubType())) {
+ CatalogHandler.throwNotFoundExceptionForTableLikeEntity(identifier,
subTypes);
}
List<PolarisEntity> catalogPath = resolvedPathWrapper.getRawParentPath();
PolarisEntity tableLikeEntity = resolvedPathWrapper.getRawLeafEntity();
diff --git
a/service/common/src/main/java/org/apache/polaris/service/catalog/common/CatalogAdapter.java
b/service/common/src/main/java/org/apache/polaris/service/catalog/common/CatalogAdapter.java
new file mode 100644
index 000000000..56be4d925
--- /dev/null
+++
b/service/common/src/main/java/org/apache/polaris/service/catalog/common/CatalogAdapter.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.polaris.service.catalog.common;
+
+import java.net.URLEncoder;
+import java.nio.charset.Charset;
+import org.apache.iceberg.catalog.Namespace;
+import org.apache.iceberg.rest.RESTUtil;
+
+/**
+ * A common interface for adapters between the REST interface and {@link
CatalogHandler}
+ * implementations
+ */
+public interface CatalogAdapter {
+ default Namespace decodeNamespace(String namespace) {
+ return RESTUtil.decodeNamespace(URLEncoder.encode(namespace,
Charset.defaultCharset()));
+ }
+}
diff --git
a/service/common/src/main/java/org/apache/polaris/service/catalog/common/CatalogHandler.java
b/service/common/src/main/java/org/apache/polaris/service/catalog/common/CatalogHandler.java
index 293c4c488..3a902c8ed 100644
---
a/service/common/src/main/java/org/apache/polaris/service/catalog/common/CatalogHandler.java
+++
b/service/common/src/main/java/org/apache/polaris/service/catalog/common/CatalogHandler.java
@@ -18,6 +18,8 @@
*/
package org.apache.polaris.service.catalog.common;
+import static
org.apache.polaris.core.entity.PolarisEntitySubType.ICEBERG_TABLE;
+
import jakarta.ws.rs.core.SecurityContext;
import java.util.Arrays;
import java.util.List;
@@ -220,16 +222,7 @@ public abstract class CatalogHandler {
PolarisResolvedPathWrapper target =
resolutionManifest.getResolvedPath(identifier,
PolarisEntityType.TABLE_LIKE, subType, true);
if (target == null) {
- switch (subType) {
- case PolarisEntitySubType.ICEBERG_TABLE:
- throw new NoSuchTableException("Table does not exist: %s",
identifier);
-
- case PolarisEntitySubType.GENERIC_TABLE:
- throw new NoSuchTableException("Generic table does not exist: %s",
identifier);
-
- default:
- throw new NoSuchViewException("View does not exist: %s", identifier);
- }
+ throwNotFoundExceptionForTableLikeEntity(identifier, List.of(subType));
}
authorizer.authorizeOrThrow(
authenticatedPrincipal,
@@ -263,11 +256,7 @@ public abstract class CatalogHandler {
TableIdentifier identifier =
PolarisCatalogHelpers.listToTableIdentifier(
status.getFailedToResolvePath().getEntityNames());
- if (subType == PolarisEntitySubType.ICEBERG_TABLE) {
- throw new NoSuchTableException("Table does not exist: %s", identifier);
- } else {
- throw new NoSuchViewException("View does not exist: %s", identifier);
- }
+ throwNotFoundExceptionForTableLikeEntity(identifier, List.of(subType));
}
List<PolarisResolvedPathWrapper> targets =
@@ -279,7 +268,7 @@ public abstract class CatalogHandler {
identifier, PolarisEntityType.TABLE_LIKE,
subType, true))
.orElseThrow(
() ->
- subType == PolarisEntitySubType.ICEBERG_TABLE
+ subType == ICEBERG_TABLE
? new NoSuchTableException(
"Table does not exist: %s", identifier)
: new NoSuchViewException(
@@ -322,11 +311,7 @@ public abstract class CatalogHandler {
throw new NoSuchNamespaceException("Namespace does not exist: %s",
dst.namespace());
} else if (resolutionManifest.getResolvedPath(src,
PolarisEntityType.TABLE_LIKE, subType)
== null) {
- if (subType == PolarisEntitySubType.ICEBERG_TABLE) {
- throw new NoSuchTableException("Table does not exist: %s", src);
- } else {
- throw new NoSuchViewException("View does not exist: %s", src);
- }
+ throwNotFoundExceptionForTableLikeEntity(dst, List.of(subType));
}
// Normally, since we added the dst as an optional path, we'd expect it to
only get resolved
@@ -339,7 +324,7 @@ public abstract class CatalogHandler {
PolarisEntitySubType dstLeafSubType =
resolutionManifest.getLeafSubType(dst);
switch (dstLeafSubType) {
- case PolarisEntitySubType.ICEBERG_TABLE:
+ case ICEBERG_TABLE:
throw new AlreadyExistsException("Cannot rename %s to %s. Table
already exists", src, dst);
case PolarisEntitySubType.ICEBERG_VIEW:
@@ -366,4 +351,32 @@ public abstract class CatalogHandler {
initializeCatalog();
}
+
+ /**
+ * Helper function for when a TABLE_LIKE entity is not found so we want to
throw the appropriate
+ * exception. Used in Iceberg APIs, so the Iceberg messages cannot be
changed.
+ *
+ * @param subTypes The subtypes of the entity that the exception should
report doesn't exist
+ */
+ public static void throwNotFoundExceptionForTableLikeEntity(
+ TableIdentifier identifier, List<PolarisEntitySubType> subTypes) {
+
+ // In this case, we assume it's a table
+ if (subTypes.size() > 1) {
+ throw new NoSuchTableException("Table does not exist: %s", identifier);
+ } else {
+ PolarisEntitySubType subType = subTypes.getFirst();
+ switch (subType) {
+ case ICEBERG_TABLE:
+ throw new NoSuchTableException("Table does not exist: %s",
identifier);
+ case ICEBERG_VIEW:
+ throw new NoSuchViewException("View does not exist: %s", identifier);
+ case GENERIC_TABLE:
+ throw new NoSuchTableException("Generic table does not exist: %s",
identifier);
+ default:
+ // Assume it's a table
+ throw new NoSuchTableException("Table does not exist: %s",
identifier);
+ }
+ }
+ }
}
diff --git
a/service/common/src/main/java/org/apache/polaris/service/catalog/generic/GenericTableCatalogAdapter.java
b/service/common/src/main/java/org/apache/polaris/service/catalog/generic/GenericTableCatalogAdapter.java
index cd8c2b5a6..bfd296904 100644
---
a/service/common/src/main/java/org/apache/polaris/service/catalog/generic/GenericTableCatalogAdapter.java
+++
b/service/common/src/main/java/org/apache/polaris/service/catalog/generic/GenericTableCatalogAdapter.java
@@ -19,14 +19,64 @@
package org.apache.polaris.service.catalog.generic;
import jakarta.enterprise.context.RequestScoped;
+import jakarta.inject.Inject;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.SecurityContext;
+import org.apache.iceberg.catalog.TableIdentifier;
+import org.apache.iceberg.exceptions.NotAuthorizedException;
+import org.apache.polaris.core.auth.AuthenticatedPolarisPrincipal;
+import org.apache.polaris.core.auth.PolarisAuthorizer;
+import org.apache.polaris.core.context.CallContext;
import org.apache.polaris.core.context.RealmContext;
+import org.apache.polaris.core.persistence.PolarisEntityManager;
+import org.apache.polaris.core.persistence.PolarisMetaStoreManager;
import
org.apache.polaris.service.catalog.api.PolarisCatalogGenericTableApiService;
+import org.apache.polaris.service.catalog.common.CatalogAdapter;
import org.apache.polaris.service.types.CreateGenericTableRequest;
+import org.apache.polaris.service.types.ListGenericTablesResponse;
+import org.apache.polaris.service.types.LoadGenericTableResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
@RequestScoped
-public class GenericTableCatalogAdapter implements
PolarisCatalogGenericTableApiService {
+public class GenericTableCatalogAdapter
+ implements PolarisCatalogGenericTableApiService, CatalogAdapter {
+
+ private static final Logger LOGGER =
LoggerFactory.getLogger(GenericTableCatalogAdapter.class);
+
+ private final CallContext callContext;
+ private final PolarisEntityManager entityManager;
+ private final PolarisMetaStoreManager metaStoreManager;
+ private final PolarisAuthorizer polarisAuthorizer;
+
+ @Inject
+ public GenericTableCatalogAdapter(
+ CallContext callContext,
+ PolarisEntityManager entityManager,
+ PolarisMetaStoreManager metaStoreManager,
+ PolarisAuthorizer polarisAuthorizer) {
+ this.callContext = callContext;
+ this.entityManager = entityManager;
+ this.metaStoreManager = metaStoreManager;
+ this.polarisAuthorizer = polarisAuthorizer;
+ }
+
+ private GenericTableCatalogHandler newHandlerWrapper(
+ SecurityContext securityContext, String catalogName) {
+ var authenticatedPrincipal = (AuthenticatedPolarisPrincipal)
securityContext.getUserPrincipal();
+ if (authenticatedPrincipal == null) {
+ throw new NotAuthorizedException("Failed to find authenticatedPrincipal
in SecurityContext");
+ }
+
+ return new GenericTableCatalogHandler(
+ callContext,
+ entityManager,
+ metaStoreManager,
+ securityContext,
+ catalogName,
+ polarisAuthorizer);
+ }
+
@Override
public Response createGenericTable(
String prefix,
@@ -34,7 +84,15 @@ public class GenericTableCatalogAdapter implements
PolarisCatalogGenericTableApi
CreateGenericTableRequest createGenericTableRequest,
RealmContext realmContext,
SecurityContext securityContext) {
- return Response.status(501).build(); // not implemented
+ GenericTableCatalogHandler handler = newHandlerWrapper(securityContext,
prefix);
+ LoadGenericTableResponse response =
+ handler.createGenericTable(
+ TableIdentifier.of(decodeNamespace(namespace),
createGenericTableRequest.getName()),
+ createGenericTableRequest.getFormat(),
+ createGenericTableRequest.getDoc(),
+ createGenericTableRequest.getProperties());
+
+ return Response.ok(response).build();
}
@Override
@@ -44,7 +102,9 @@ public class GenericTableCatalogAdapter implements
PolarisCatalogGenericTableApi
String genericTable,
RealmContext realmContext,
SecurityContext securityContext) {
- return Response.status(501).build(); // not implemented
+ GenericTableCatalogHandler handler = newHandlerWrapper(securityContext,
prefix);
+ handler.dropGenericTable(TableIdentifier.of(decodeNamespace(namespace),
genericTable));
+ return Response.noContent().build();
}
@Override
@@ -55,7 +115,9 @@ public class GenericTableCatalogAdapter implements
PolarisCatalogGenericTableApi
Integer pageSize,
RealmContext realmContext,
SecurityContext securityContext) {
- return Response.status(501).build(); // not implemented
+ GenericTableCatalogHandler handler = newHandlerWrapper(securityContext,
prefix);
+ ListGenericTablesResponse response =
handler.listGenericTables(decodeNamespace(namespace));
+ return Response.ok(response).build();
}
@Override
@@ -65,6 +127,9 @@ public class GenericTableCatalogAdapter implements
PolarisCatalogGenericTableApi
String genericTable,
RealmContext realmContext,
SecurityContext securityContext) {
- return Response.status(501).build(); // not implemented
+ GenericTableCatalogHandler handler = newHandlerWrapper(securityContext,
prefix);
+ LoadGenericTableResponse response =
+
handler.loadGenericTable(TableIdentifier.of(decodeNamespace(namespace),
genericTable));
+ return Response.ok(response).build();
}
}
diff --git
a/service/common/src/main/java/org/apache/polaris/service/catalog/generic/GenericTableCatalogHandler.java
b/service/common/src/main/java/org/apache/polaris/service/catalog/generic/GenericTableCatalogHandler.java
index 6df82dd7d..b1f2648f1 100644
---
a/service/common/src/main/java/org/apache/polaris/service/catalog/generic/GenericTableCatalogHandler.java
+++
b/service/common/src/main/java/org/apache/polaris/service/catalog/generic/GenericTableCatalogHandler.java
@@ -19,8 +19,8 @@
package org.apache.polaris.service.catalog.generic;
import jakarta.ws.rs.core.SecurityContext;
+import java.util.LinkedHashSet;
import java.util.Map;
-import java.util.TreeSet;
import org.apache.iceberg.catalog.Namespace;
import org.apache.iceberg.catalog.TableIdentifier;
import org.apache.polaris.core.auth.PolarisAuthorizableOperation;
@@ -77,7 +77,7 @@ public class GenericTableCatalogHandler extends
CatalogHandler {
authorizeBasicNamespaceOperationOrThrow(op, parent);
return ListGenericTablesResponse.builder()
- .setIdentifiers(new
TreeSet<>(genericTableCatalog.listGenericTables(parent)))
+ .setIdentifiers(new
LinkedHashSet<>(genericTableCatalog.listGenericTables(parent)))
.build();
}
diff --git
a/service/common/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogAdapter.java
b/service/common/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogAdapter.java
index 9bab20586..8d7b49a07 100644
---
a/service/common/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogAdapter.java
+++
b/service/common/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogAdapter.java
@@ -30,8 +30,6 @@ import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.SecurityContext;
-import java.net.URLEncoder;
-import java.nio.charset.Charset;
import java.util.EnumSet;
import java.util.Map;
import java.util.Optional;
@@ -72,6 +70,7 @@ import
org.apache.polaris.service.catalog.AccessDelegationMode;
import org.apache.polaris.service.catalog.CatalogPrefixParser;
import org.apache.polaris.service.catalog.api.IcebergRestCatalogApiService;
import
org.apache.polaris.service.catalog.api.IcebergRestConfigurationApiService;
+import org.apache.polaris.service.catalog.common.CatalogAdapter;
import org.apache.polaris.service.context.CallContextCatalogFactory;
import org.apache.polaris.service.http.IcebergHttpUtil;
import org.apache.polaris.service.http.IfNoneMatch;
@@ -88,7 +87,7 @@ import org.slf4j.LoggerFactory;
*/
@RequestScoped
public class IcebergCatalogAdapter
- implements IcebergRestCatalogApiService,
IcebergRestConfigurationApiService {
+ implements IcebergRestCatalogApiService,
IcebergRestConfigurationApiService, CatalogAdapter {
private static final Logger LOGGER =
LoggerFactory.getLogger(IcebergCatalogAdapter.class);
@@ -215,8 +214,7 @@ public class IcebergCatalogAdapter
String parent,
RealmContext realmContext,
SecurityContext securityContext) {
- Optional<Namespace> namespaceOptional =
-
Optional.ofNullable(parent).map(IcebergCatalogAdapter::decodeNamespace);
+ Optional<Namespace> namespaceOptional =
Optional.ofNullable(parent).map(this::decodeNamespace);
return withCatalog(
securityContext,
prefix,
@@ -232,10 +230,6 @@ public class IcebergCatalogAdapter
securityContext, prefix, catalog ->
Response.ok(catalog.loadNamespaceMetadata(ns)).build());
}
- private static Namespace decodeNamespace(String namespace) {
- return RESTUtil.decodeNamespace(URLEncoder.encode(namespace,
Charset.defaultCharset()));
- }
-
/**
* For situations where we typically expect a metadataLocation to be present
in the response and
* so expect to insert an etag header, this helper gracefully falls back to
omitting the header if