Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,21 @@ default boolean viewExists(SessionCatalog.SessionContext context, TableIdentifie
*/
default void invalidateView(SessionCatalog.SessionContext context, TableIdentifier identifier) {}

/**
* Register a view if it does not exist.
*
* @param context session context
* @param ident a view identifier
* @param metadataFileLocation the location of a metadata file
* @return a View instance
* @throws AlreadyExistsException if a table/view with the same identifier already exists in the
* catalog.
*/
default View registerView(
SessionCatalog.SessionContext context, TableIdentifier ident, String metadataFileLocation) {
throw new UnsupportedOperationException("Registering views is not supported");
}

/**
* Initialize a view catalog given a custom name and a map of catalog properties.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ public void invalidateView(TableIdentifier identifier) {
BaseViewSessionCatalog.this.invalidateView(context, identifier);
}

@Override
public View registerView(TableIdentifier identifier, String metadataFileLocation) {
return BaseViewSessionCatalog.this.registerView(context, identifier, metadataFileLocation);
}

@Override
public void initialize(String name, Map<String, String> properties) {
throw new UnsupportedOperationException(
Expand Down
13 changes: 13 additions & 0 deletions core/src/main/java/org/apache/iceberg/rest/CatalogHandlers.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
import org.apache.iceberg.rest.requests.FetchScanTasksRequest;
import org.apache.iceberg.rest.requests.PlanTableScanRequest;
import org.apache.iceberg.rest.requests.RegisterTableRequest;
import org.apache.iceberg.rest.requests.RegisterViewRequest;
import org.apache.iceberg.rest.requests.RenameTableRequest;
import org.apache.iceberg.rest.requests.UpdateNamespacePropertiesRequest;
import org.apache.iceberg.rest.requests.UpdateTableRequest;
Expand Down Expand Up @@ -746,6 +747,18 @@ public static void dropView(ViewCatalog catalog, TableIdentifier viewIdentifier)
}
}

public static LoadViewResponse registerView(
ViewCatalog catalog, Namespace namespace, RegisterViewRequest request) {
request.validate();

TableIdentifier identifier = TableIdentifier.of(namespace, request.name());
View view = catalog.registerView(identifier, request.metadataLocation());
return ImmutableLoadViewResponse.builder()
.metadata(asBaseView(view).operations().current())
.metadataLocation(request.metadataLocation())
.build();
}

static ViewMetadata commit(ViewOperations ops, UpdateTableRequest request) {
AtomicBoolean isRetry = new AtomicBoolean(false);
try {
Expand Down
2 changes: 2 additions & 0 deletions core/src/main/java/org/apache/iceberg/rest/Endpoint.java
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ public class Endpoint {
public static final Endpoint V1_DELETE_VIEW = Endpoint.create("DELETE", ResourcePaths.V1_VIEW);
public static final Endpoint V1_RENAME_VIEW =
Endpoint.create("POST", ResourcePaths.V1_VIEW_RENAME);
public static final Endpoint V1_REGISTER_VIEW =
Endpoint.create("POST", ResourcePaths.V1_VIEW_REGISTER);

private static final Splitter ENDPOINT_SPLITTER = Splitter.on(" ");
private static final Joiner ENDPOINT_JOINER = Joiner.on(" ");
Expand Down
5 changes: 5 additions & 0 deletions core/src/main/java/org/apache/iceberg/rest/RESTCatalog.java
Original file line number Diff line number Diff line change
Expand Up @@ -329,4 +329,9 @@ public boolean viewExists(TableIdentifier identifier) {
public void invalidateView(TableIdentifier identifier) {
viewSessionCatalog.invalidateView(identifier);
}

@Override
public View registerView(TableIdentifier identifier, String metadataFileLocation) {
return viewSessionCatalog.registerView(identifier, metadataFileLocation);
}
}
45 changes: 45 additions & 0 deletions core/src/main/java/org/apache/iceberg/rest/RESTSessionCatalog.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
import org.apache.iceberg.metrics.MetricsReporters;
import org.apache.iceberg.relocated.com.google.common.annotations.VisibleForTesting;
import org.apache.iceberg.relocated.com.google.common.base.Preconditions;
import org.apache.iceberg.relocated.com.google.common.base.Strings;
import org.apache.iceberg.relocated.com.google.common.collect.ImmutableList;
import org.apache.iceberg.relocated.com.google.common.collect.ImmutableMap;
import org.apache.iceberg.relocated.com.google.common.collect.ImmutableSet;
Expand All @@ -81,7 +82,9 @@
import org.apache.iceberg.rest.requests.CreateViewRequest;
import org.apache.iceberg.rest.requests.ImmutableCreateViewRequest;
import org.apache.iceberg.rest.requests.ImmutableRegisterTableRequest;
import org.apache.iceberg.rest.requests.ImmutableRegisterViewRequest;
import org.apache.iceberg.rest.requests.RegisterTableRequest;
import org.apache.iceberg.rest.requests.RegisterViewRequest;
import org.apache.iceberg.rest.requests.RenameTableRequest;
import org.apache.iceberg.rest.requests.UpdateNamespacePropertiesRequest;
import org.apache.iceberg.rest.requests.UpdateTableRequest;
Expand Down Expand Up @@ -1457,6 +1460,48 @@ public void renameView(SessionContext context, TableIdentifier from, TableIdenti
.post(paths.renameView(), request, null, mutationHeaders, ErrorHandlers.viewErrorHandler());
}

@Override
public View registerView(
SessionContext context, TableIdentifier ident, String metadataFileLocation) {
Endpoint.check(endpoints, Endpoint.V1_REGISTER_VIEW);
checkViewIdentifierIsValid(ident);

Preconditions.checkArgument(
!Strings.isNullOrEmpty(metadataFileLocation),
"Invalid metadata file location: %s",
metadataFileLocation);

RegisterViewRequest request =
ImmutableRegisterViewRequest.builder()
.name(ident.name())
.metadataLocation(metadataFileLocation)
.build();

AuthSession contextualSession = authManager.contextualSession(context, catalogAuth);
LoadViewResponse response =
client
.withAuthSession(contextualSession)
.post(
paths.registerView(ident.namespace()),
request,
LoadViewResponse.class,
mutationHeaders,
ErrorHandlers.viewErrorHandler());

AuthSession tableSession =
authManager.tableSession(ident, response.config(), contextualSession);
RESTViewOperations ops =
newViewOps(
client.withAuthSession(tableSession),
paths.view(ident),
Map::of,
mutationHeaders,
response.metadata(),
endpoints);

return new BaseView(ops, ViewUtil.fullViewName(name(), ident));
}

private static Map<String, String> headersForLoadTable(TableWithETag tableWithETag) {
if (tableWithETag == null) {
return Map.of();
Expand Down
5 changes: 5 additions & 0 deletions core/src/main/java/org/apache/iceberg/rest/ResourcePaths.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public class ResourcePaths {
public static final String V1_VIEWS = "/v1/{prefix}/namespaces/{namespace}/views";
public static final String V1_VIEW = "/v1/{prefix}/namespaces/{namespace}/views/{view}";
public static final String V1_VIEW_RENAME = "/v1/{prefix}/views/rename";
public static final String V1_VIEW_REGISTER = "/v1/{prefix}/namespaces/{namespace}/register-view";

public static ResourcePaths forCatalogProperties(Map<String, String> properties) {
return new ResourcePaths(
Expand Down Expand Up @@ -151,6 +152,10 @@ public String renameView() {
return SLASH.join("v1", prefix, "views", "rename");
}

public String registerView(Namespace ns) {
return SLASH.join("v1", prefix, "namespaces", pathEncode(ns), "register-view");
}

public String planTableScan(TableIdentifier ident) {
return SLASH.join(
"v1",
Expand Down
12 changes: 12 additions & 0 deletions core/src/test/java/org/apache/iceberg/rest/RESTCatalogAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
import org.apache.iceberg.rest.requests.FetchScanTasksRequest;
import org.apache.iceberg.rest.requests.PlanTableScanRequest;
import org.apache.iceberg.rest.requests.RegisterTableRequest;
import org.apache.iceberg.rest.requests.RegisterViewRequest;
import org.apache.iceberg.rest.requests.RenameTableRequest;
import org.apache.iceberg.rest.requests.ReportMetricsRequest;
import org.apache.iceberg.rest.requests.UpdateNamespacePropertiesRequest;
Expand Down Expand Up @@ -516,6 +517,17 @@ public <T extends RESTResponse> T handleRequest(
break;
}

case REGISTER_VIEW:
{
if (null != asViewCatalog) {
Namespace namespace = namespaceFromPathVars(vars);
RegisterViewRequest request = castRequest(RegisterViewRequest.class, body);
return castResponse(
responseType, CatalogHandlers.registerView(asViewCatalog, namespace, request));
}
break;
}

default:
if (responseType == OAuthTokenResponse.class) {
return castResponse(responseType, handleOAuthRequest(body));
Expand Down
6 changes: 6 additions & 0 deletions core/src/test/java/org/apache/iceberg/rest/Route.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.apache.iceberg.rest.requests.FetchScanTasksRequest;
import org.apache.iceberg.rest.requests.PlanTableScanRequest;
import org.apache.iceberg.rest.requests.RegisterTableRequest;
import org.apache.iceberg.rest.requests.RegisterViewRequest;
import org.apache.iceberg.rest.requests.RenameTableRequest;
import org.apache.iceberg.rest.requests.ReportMetricsRequest;
import org.apache.iceberg.rest.requests.UpdateNamespacePropertiesRequest;
Expand Down Expand Up @@ -115,6 +116,11 @@ enum Route {
RENAME_VIEW(
HTTPRequest.HTTPMethod.POST, ResourcePaths.V1_VIEW_RENAME, RenameTableRequest.class, null),
DROP_VIEW(HTTPRequest.HTTPMethod.DELETE, ResourcePaths.V1_VIEW),
REGISTER_VIEW(
HTTPRequest.HTTPMethod.POST,
ResourcePaths.V1_VIEW_REGISTER,
RegisterViewRequest.class,
LoadViewResponse.class),
PLAN_TABLE_SCAN(
HTTPRequest.HTTPMethod.POST,
ResourcePaths.V1_TABLE_SCAN_PLAN_SUBMIT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
*/
package org.apache.iceberg.rest;

import static org.assertj.core.api.Assertions.assertThatThrownBy;

import java.io.File;
import java.net.InetAddress;
import java.net.InetSocketAddress;
Expand Down Expand Up @@ -107,4 +109,31 @@ public <T extends RESTResponse> T handleRequest(
CatalogProperties.VIEW_OVERRIDE_PREFIX + "key4",
"catalog-override-key4"));
}

@Override
public void registerView() {
// Older client doesn't support the newer endpoint.
assertThatThrownBy(super::registerView)
.isInstanceOf(UnsupportedOperationException.class)
.hasMessageStartingWith(
"Server does not support endpoint: POST /v1/{prefix}/namespaces/{namespace}/register-view");
}

@Override
public void registerExistingView() {
// Older client doesn't support the newer endpoint.
assertThatThrownBy(super::registerExistingView)
.isInstanceOf(UnsupportedOperationException.class)
.hasMessageStartingWith(
"Server does not support endpoint: POST /v1/{prefix}/namespaces/{namespace}/register-view");
}

@Override
public void registerViewThatAlreadyExistsAsTable() {
// Older client doesn't support the newer endpoint.
assertThatThrownBy(super::registerViewThatAlreadyExistsAsTable)
.isInstanceOf(UnsupportedOperationException.class)
.hasMessageStartingWith(
"Server does not support endpoint: POST /v1/{prefix}/namespaces/{namespace}/register-view");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,13 @@ public void viewWithMultipartNamespace() {
assertThat(withoutPrefix.view(ident)).isEqualTo("v1/namespaces/n%1Fs/views/view-name");
}

@Test
public void testRegisterView() {
Namespace ns = Namespace.of("ns");
assertThat(withPrefix.registerView(ns)).isEqualTo("v1/ws/catalog/namespaces/ns/register-view");
assertThat(withoutPrefix.registerView(ns)).isEqualTo("v1/namespaces/ns/register-view");
}

@Test
public void planEndpointPath() {
TableIdentifier tableId = TableIdentifier.of("test_namespace", "test_table");
Expand Down
17 changes: 3 additions & 14 deletions core/src/test/java/org/apache/iceberg/view/ViewCatalogTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
import org.apache.iceberg.exceptions.NoSuchTableException;
import org.apache.iceberg.exceptions.NoSuchViewException;
import org.apache.iceberg.relocated.com.google.common.collect.ImmutableList;
import org.apache.iceberg.rest.RESTCatalog;
import org.apache.iceberg.types.Types;
import org.apache.iceberg.util.LocationUtil;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -2013,10 +2012,6 @@ public void registerTableThatAlreadyExistsAsView() {
public void registerView() {
C catalog = catalog();

assumeThat(catalog)
.as("Registering a view is not yet supported for the REST catalog")
.isNotInstanceOf(RESTCatalog.class);

TableIdentifier identifier = TableIdentifier.of("ns", "view");

if (requiresNamespaceCreate()) {
Expand All @@ -2040,7 +2035,9 @@ public void registerView() {
assertThat(catalog.viewExists(identifier)).as("View must not exist").isFalse();

// view metadata should still exist after dropping the view as gc is disabled
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the REST catalog, it won't be BaseViewOperations, it is RESTViewOperations and doesn't expose fileIO. So, removed the check.

While Registering the view, if the file doesn't exist, It will throw the exception anyways and testcase will fail in that case. Now it is passing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: We may still want to just conditionally perform this assertion if it's a BaseViewOperations that the client doesn't just drop it. You're right that we'd expect the subsequent register to fail if the metadata file doesn't exist but if someone wants to run this test suite against some arbitrary catalog that has a bug on cleanup or maybe there's a bug in that implementation where register doesn't really check the metadata file, it'd be nice to have a clearer failure here rather than proceeding. Keeping the assertion in that case is a minor additional check that allows us to more clearly catch issues like that.

assertThat(((BaseViewOperations) ops).io().newInputFile(metadataLocation).exists()).isTrue();
if (ops instanceof BaseViewOperations) {
assertThat(((BaseViewOperations) ops).io().newInputFile(metadataLocation).exists()).isTrue();
}

View registeredView = catalog.registerView(identifier, metadataLocation);

Expand Down Expand Up @@ -2085,10 +2082,6 @@ public void registerView() {
public void registerExistingView() {
C catalog = catalog();

assumeThat(catalog)
.as("Registering a view is not yet supported for the REST catalog")
.isNotInstanceOf(RESTCatalog.class);

TableIdentifier identifier = TableIdentifier.of("ns", "view");

if (requiresNamespaceCreate()) {
Expand Down Expand Up @@ -2117,10 +2110,6 @@ public void registerExistingView() {
public void registerViewThatAlreadyExistsAsTable() {
C catalog = catalog();

assumeThat(catalog)
.as("Registering a view is not yet supported for the REST catalog")
.isNotInstanceOf(RESTCatalog.class);

TableIdentifier identifier = TableIdentifier.of("ns", "view");

if (requiresNamespaceCreate()) {
Expand Down