Skip to content

Commit cf14eba

Browse files
committed
REST: Implement register view for REST catalog
1 parent 09f6b36 commit cf14eba

16 files changed

Lines changed: 352 additions & 16 deletions

api/src/main/java/org/apache/iceberg/catalog/ViewSessionCatalog.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,21 @@ default boolean viewExists(SessionCatalog.SessionContext context, TableIdentifie
106106
*/
107107
default void invalidateView(SessionCatalog.SessionContext context, TableIdentifier identifier) {}
108108

109+
/**
110+
* Register a view if it does not exist.
111+
*
112+
* @param context session context
113+
* @param ident a view identifier
114+
* @param metadataFileLocation the location of a metadata file
115+
* @return a View instance
116+
* @throws AlreadyExistsException if a table/view with the same identifier already exists in the
117+
* catalog.
118+
*/
119+
default View registerView(
120+
SessionCatalog.SessionContext context, TableIdentifier ident, String metadataFileLocation) {
121+
throw new UnsupportedOperationException("Registering views is not supported");
122+
}
123+
109124
/**
110125
* Initialize a view catalog given a custom name and a map of catalog properties.
111126
*

core/src/main/java/org/apache/iceberg/catalog/BaseViewSessionCatalog.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ public void invalidateView(TableIdentifier identifier) {
8383
BaseViewSessionCatalog.this.invalidateView(context, identifier);
8484
}
8585

86+
@Override
87+
public View registerView(TableIdentifier identifier, String metadataFileLocation) {
88+
return BaseViewSessionCatalog.this.registerView(context, identifier, metadataFileLocation);
89+
}
90+
8691
@Override
8792
public void initialize(String name, Map<String, String> properties) {
8893
throw new UnsupportedOperationException(

core/src/main/java/org/apache/iceberg/rest/CatalogHandlers.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
import org.apache.iceberg.rest.requests.FetchScanTasksRequest;
7676
import org.apache.iceberg.rest.requests.PlanTableScanRequest;
7777
import org.apache.iceberg.rest.requests.RegisterTableRequest;
78+
import org.apache.iceberg.rest.requests.RegisterViewRequest;
7879
import org.apache.iceberg.rest.requests.RenameTableRequest;
7980
import org.apache.iceberg.rest.requests.UpdateNamespacePropertiesRequest;
8081
import org.apache.iceberg.rest.requests.UpdateTableRequest;
@@ -593,6 +594,18 @@ public static void dropView(ViewCatalog catalog, TableIdentifier viewIdentifier)
593594
}
594595
}
595596

597+
public static LoadViewResponse registerView(
598+
ViewCatalog catalog, Namespace namespace, RegisterViewRequest request) {
599+
request.validate();
600+
601+
TableIdentifier identifier = TableIdentifier.of(namespace, request.name());
602+
View view = catalog.registerView(identifier, request.metadataLocation());
603+
return ImmutableLoadViewResponse.builder()
604+
.metadata(asBaseView(view).operations().current())
605+
.metadataLocation(request.metadataLocation())
606+
.build();
607+
}
608+
596609
static ViewMetadata commit(ViewOperations ops, UpdateTableRequest request) {
597610
AtomicBoolean isRetry = new AtomicBoolean(false);
598611
try {

core/src/main/java/org/apache/iceberg/rest/Endpoint.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ public class Endpoint {
8686
public static final Endpoint V1_DELETE_VIEW = Endpoint.create("DELETE", ResourcePaths.V1_VIEW);
8787
public static final Endpoint V1_RENAME_VIEW =
8888
Endpoint.create("POST", ResourcePaths.V1_VIEW_RENAME);
89+
public static final Endpoint V1_REGISTER_VIEW =
90+
Endpoint.create("POST", ResourcePaths.V1_VIEW_REGISTER);
8991

9092
private static final Splitter ENDPOINT_SPLITTER = Splitter.on(" ");
9193
private static final Joiner ENDPOINT_JOINER = Joiner.on(" ");

core/src/main/java/org/apache/iceberg/rest/RESTCatalog.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,4 +325,9 @@ public boolean viewExists(TableIdentifier identifier) {
325325
public void invalidateView(TableIdentifier identifier) {
326326
viewSessionCatalog.invalidateView(identifier);
327327
}
328+
329+
@Override
330+
public View registerView(TableIdentifier identifier, String metadataFileLocation) {
331+
return viewSessionCatalog.registerView(identifier, metadataFileLocation);
332+
}
328333
}

core/src/main/java/org/apache/iceberg/rest/RESTSerializers.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,14 @@
5252
import org.apache.iceberg.rest.requests.FetchScanTasksRequestParser;
5353
import org.apache.iceberg.rest.requests.ImmutableCreateViewRequest;
5454
import org.apache.iceberg.rest.requests.ImmutableRegisterTableRequest;
55+
import org.apache.iceberg.rest.requests.ImmutableRegisterViewRequest;
5556
import org.apache.iceberg.rest.requests.ImmutableReportMetricsRequest;
5657
import org.apache.iceberg.rest.requests.PlanTableScanRequest;
5758
import org.apache.iceberg.rest.requests.PlanTableScanRequestParser;
5859
import org.apache.iceberg.rest.requests.RegisterTableRequest;
5960
import org.apache.iceberg.rest.requests.RegisterTableRequestParser;
61+
import org.apache.iceberg.rest.requests.RegisterViewRequest;
62+
import org.apache.iceberg.rest.requests.RegisterViewRequestParser;
6063
import org.apache.iceberg.rest.requests.ReportMetricsRequest;
6164
import org.apache.iceberg.rest.requests.ReportMetricsRequestParser;
6265
import org.apache.iceberg.rest.requests.UpdateTableRequest;
@@ -131,6 +134,11 @@ public static void registerAll(ObjectMapper mapper) {
131134
.addSerializer(ImmutableLoadViewResponse.class, new LoadViewResponseSerializer<>())
132135
.addDeserializer(LoadViewResponse.class, new LoadViewResponseDeserializer<>())
133136
.addDeserializer(ImmutableLoadViewResponse.class, new LoadViewResponseDeserializer<>())
137+
.addSerializer(RegisterViewRequest.class, new RegisterViewRequestSerializer<>())
138+
.addDeserializer(RegisterViewRequest.class, new RegisterViewRequestDeserializer<>())
139+
.addSerializer(ImmutableRegisterViewRequest.class, new RegisterViewRequestSerializer<>())
140+
.addDeserializer(
141+
ImmutableRegisterViewRequest.class, new RegisterViewRequestDeserializer<>())
134142
.addSerializer(ConfigResponse.class, new ConfigResponseSerializer<>())
135143
.addDeserializer(ConfigResponse.class, new ConfigResponseDeserializer<>())
136144
.addSerializer(LoadTableResponse.class, new LoadTableResponseSerializer<>())
@@ -444,6 +452,24 @@ public T deserialize(JsonParser p, DeserializationContext context) throws IOExce
444452
}
445453
}
446454

455+
public static class RegisterViewRequestSerializer<T extends RegisterViewRequest>
456+
extends JsonSerializer<T> {
457+
@Override
458+
public void serialize(T request, JsonGenerator gen, SerializerProvider serializers)
459+
throws IOException {
460+
RegisterViewRequestParser.toJson(request, gen);
461+
}
462+
}
463+
464+
public static class RegisterViewRequestDeserializer<T extends RegisterViewRequest>
465+
extends JsonDeserializer<T> {
466+
@Override
467+
public T deserialize(JsonParser p, DeserializationContext context) throws IOException {
468+
JsonNode jsonNode = p.getCodec().readTree(p);
469+
return (T) RegisterViewRequestParser.fromJson(jsonNode);
470+
}
471+
}
472+
447473
static class ConfigResponseSerializer<T extends ConfigResponse> extends JsonSerializer<T> {
448474
@Override
449475
public void serialize(T request, JsonGenerator gen, SerializerProvider serializers)

core/src/main/java/org/apache/iceberg/rest/RESTSessionCatalog.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,9 @@
7777
import org.apache.iceberg.rest.requests.CreateViewRequest;
7878
import org.apache.iceberg.rest.requests.ImmutableCreateViewRequest;
7979
import org.apache.iceberg.rest.requests.ImmutableRegisterTableRequest;
80+
import org.apache.iceberg.rest.requests.ImmutableRegisterViewRequest;
8081
import org.apache.iceberg.rest.requests.RegisterTableRequest;
82+
import org.apache.iceberg.rest.requests.RegisterViewRequest;
8183
import org.apache.iceberg.rest.requests.RenameTableRequest;
8284
import org.apache.iceberg.rest.requests.UpdateNamespacePropertiesRequest;
8385
import org.apache.iceberg.rest.requests.UpdateTableRequest;
@@ -1364,6 +1366,48 @@ public void renameView(SessionContext context, TableIdentifier from, TableIdenti
13641366
.post(paths.renameView(), request, null, mutationHeaders, ErrorHandlers.viewErrorHandler());
13651367
}
13661368

1369+
@Override
1370+
public View registerView(
1371+
SessionContext context, TableIdentifier ident, String metadataFileLocation) {
1372+
Endpoint.check(endpoints, Endpoint.V1_REGISTER_VIEW);
1373+
checkViewIdentifierIsValid(ident);
1374+
1375+
Preconditions.checkArgument(
1376+
metadataFileLocation != null && !metadataFileLocation.isEmpty(),
1377+
"Invalid metadata file location: %s",
1378+
metadataFileLocation);
1379+
1380+
RegisterViewRequest request =
1381+
ImmutableRegisterViewRequest.builder()
1382+
.name(ident.name())
1383+
.metadataLocation(metadataFileLocation)
1384+
.build();
1385+
1386+
AuthSession contextualSession = authManager.contextualSession(context, catalogAuth);
1387+
LoadViewResponse response =
1388+
client
1389+
.withAuthSession(contextualSession)
1390+
.post(
1391+
paths.registerView(ident.namespace()),
1392+
request,
1393+
LoadViewResponse.class,
1394+
mutationHeaders,
1395+
ErrorHandlers.viewErrorHandler());
1396+
1397+
AuthSession tableSession =
1398+
authManager.tableSession(ident, response.config(), contextualSession);
1399+
RESTViewOperations ops =
1400+
newViewOps(
1401+
client.withAuthSession(tableSession),
1402+
paths.view(ident),
1403+
Map::of,
1404+
mutationHeaders,
1405+
response.metadata(),
1406+
endpoints);
1407+
1408+
return new BaseView(ops, ViewUtil.fullViewName(name(), ident));
1409+
}
1410+
13671411
private class RESTViewBuilder implements ViewBuilder {
13681412
private final SessionContext context;
13691413
private final TableIdentifier identifier;

core/src/main/java/org/apache/iceberg/rest/ResourcePaths.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public class ResourcePaths {
4949
public static final String V1_VIEWS = "/v1/{prefix}/namespaces/{namespace}/views";
5050
public static final String V1_VIEW = "/v1/{prefix}/namespaces/{namespace}/views/{view}";
5151
public static final String V1_VIEW_RENAME = "/v1/{prefix}/views/rename";
52+
public static final String V1_VIEW_REGISTER = "/v1/{prefix}/namespaces/{namespace}/register-view";
5253

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

155+
public String registerView(Namespace ns) {
156+
return SLASH.join("v1", prefix, "namespaces", pathEncode(ns), "register-view");
157+
}
158+
154159
public String planTableScan(TableIdentifier ident) {
155160
return SLASH.join(
156161
"v1",
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.iceberg.rest.requests;
20+
21+
import org.apache.iceberg.rest.RESTRequest;
22+
import org.immutables.value.Value;
23+
24+
@Value.Immutable
25+
public interface RegisterViewRequest extends RESTRequest {
26+
27+
String name();
28+
29+
String metadataLocation();
30+
31+
@Override
32+
default void validate() {
33+
// nothing to validate as it's not possible to create an invalid instance
34+
}
35+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.iceberg.rest.requests;
20+
21+
import com.fasterxml.jackson.core.JsonGenerator;
22+
import com.fasterxml.jackson.databind.JsonNode;
23+
import java.io.IOException;
24+
import org.apache.iceberg.relocated.com.google.common.base.Preconditions;
25+
import org.apache.iceberg.util.JsonUtil;
26+
27+
public class RegisterViewRequestParser {
28+
29+
private static final String NAME = "name";
30+
private static final String METADATA_LOCATION = "metadata-location";
31+
32+
private RegisterViewRequestParser() {}
33+
34+
public static String toJson(RegisterViewRequest request) {
35+
return toJson(request, false);
36+
}
37+
38+
public static String toJson(RegisterViewRequest request, boolean pretty) {
39+
return JsonUtil.generate(gen -> toJson(request, gen), pretty);
40+
}
41+
42+
public static void toJson(RegisterViewRequest request, JsonGenerator gen) throws IOException {
43+
Preconditions.checkArgument(null != request, "Invalid register view request: null");
44+
45+
gen.writeStartObject();
46+
47+
gen.writeStringField(NAME, request.name());
48+
gen.writeStringField(METADATA_LOCATION, request.metadataLocation());
49+
50+
gen.writeEndObject();
51+
}
52+
53+
public static RegisterViewRequest fromJson(String json) {
54+
return JsonUtil.parse(json, RegisterViewRequestParser::fromJson);
55+
}
56+
57+
public static RegisterViewRequest fromJson(JsonNode json) {
58+
Preconditions.checkArgument(
59+
null != json, "Cannot parse register view request from null object");
60+
61+
String name = JsonUtil.getString(NAME, json);
62+
String metadataLocation = JsonUtil.getString(METADATA_LOCATION, json);
63+
64+
return ImmutableRegisterViewRequest.builder()
65+
.name(name)
66+
.metadataLocation(metadataLocation)
67+
.build();
68+
}
69+
}

0 commit comments

Comments
 (0)