You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@polaris.apache.org by ad...@apache.org on 2026/03/16 13:36:57 UTC

(polaris) branch feature/iceberg-1.11 updated: [Iceberg 1.11] View registration (#3689)

This is an automated email from the ASF dual-hosted git repository.

adutra pushed a commit to branch feature/iceberg-1.11
in repository https://gitbox.apache.org/repos/asf/polaris.git


The following commit(s) were added to refs/heads/feature/iceberg-1.11 by this push:
     new c41624c02e [Iceberg 1.11] View registration (#3689)
c41624c02e is described below

commit c41624c02e9dc12f3188b8b9c5e2bf140ef7e52e
Author: Alexandre Dutra <ad...@apache.org>
AuthorDate: Mon Mar 16 14:36:51 2026 +0100

    [Iceberg 1.11] View registration (#3689)
---
 api/iceberg-service/build.gradle.kts               |  1 +
 .../PolarisRestCatalogViewIntegrationBase.java     | 16 ------
 .../core/auth/PolarisAuthorizableOperation.java    |  1 +
 .../catalog/iceberg/CatalogHandlerUtils.java       | 49 +++++++++++++++--
 .../service/catalog/iceberg/IcebergCatalog.java    | 49 +++++++++++++++++
 .../catalog/iceberg/IcebergCatalogAdapter.java     | 16 ++++++
 .../catalog/iceberg/IcebergCatalogHandler.java     | 10 ++++
 .../IcebergRestCatalogEventServiceDelegator.java   | 34 ++++++++++++
 .../polaris/service/events/EventAttributes.java    |  3 ++
 .../polaris/service/events/PolarisEventType.java   |  2 +
 .../iceberg/AbstractIcebergCatalogViewTest.java    | 16 ------
 .../generated/bundled-polaris-catalog-service.yaml | 61 ++++++++++++++++++++++
 spec/polaris-catalog-service.yaml                  |  3 ++
 13 files changed, 224 insertions(+), 37 deletions(-)

diff --git a/api/iceberg-service/build.gradle.kts b/api/iceberg-service/build.gradle.kts
index 9ed83e7b3f..0009747b64 100644
--- a/api/iceberg-service/build.gradle.kts
+++ b/api/iceberg-service/build.gradle.kts
@@ -109,6 +109,7 @@ openApiGenerate {
       "CommitTransactionRequest" to "org.apache.iceberg.rest.requests.CommitTransactionRequest",
       "CreateViewRequest" to "org.apache.iceberg.rest.requests.CreateViewRequest",
       "RegisterTableRequest" to "org.apache.iceberg.rest.requests.RegisterTableRequest",
+      "RegisterViewRequest" to "org.apache.iceberg.rest.requests.RegisterViewRequest",
       "IcebergErrorResponse" to "org.apache.iceberg.rest.responses.ErrorResponse",
       "OAuthError" to "org.apache.iceberg.rest.responses.ErrorResponse",
 
diff --git a/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisRestCatalogViewIntegrationBase.java b/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisRestCatalogViewIntegrationBase.java
index a12e2a8661..778ea30a54 100644
--- a/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisRestCatalogViewIntegrationBase.java
+++ b/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisRestCatalogViewIntegrationBase.java
@@ -51,7 +51,6 @@ import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInfo;
 import org.junit.jupiter.api.extension.ExtendWith;
@@ -237,19 +236,4 @@ public abstract class PolarisRestCatalogViewIntegrationBase extends ViewCatalogT
         .isNotNull()
         .startsWith(customLocation);
   }
-
-  @Test
-  @Disabled("Polaris REST catalog does not support view registration")
-  @Override
-  public void registerView() {}
-
-  @Test
-  @Disabled("Polaris REST catalog does not support view registration")
-  @Override
-  public void registerExistingView() {}
-
-  @Test
-  @Disabled("Polaris REST catalog does not support view registration")
-  @Override
-  public void registerViewThatAlreadyExistsAsTable() {}
 }
diff --git a/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizableOperation.java b/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizableOperation.java
index 12276bdbd1..ffd3f0fac6 100644
--- a/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizableOperation.java
+++ b/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizableOperation.java
@@ -141,6 +141,7 @@ public enum PolarisAuthorizableOperation {
   COMMIT_TRANSACTION(EnumSet.of(TABLE_WRITE_PROPERTIES, TABLE_CREATE)),
   LIST_VIEWS(VIEW_LIST),
   CREATE_VIEW(VIEW_CREATE),
+  REGISTER_VIEW(VIEW_CREATE),
   LOAD_VIEW(VIEW_READ_PROPERTIES),
   REPLACE_VIEW(VIEW_WRITE_PROPERTIES),
   DROP_VIEW(VIEW_DROP),
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/CatalogHandlerUtils.java b/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/CatalogHandlerUtils.java
index 68a9198161..a71bb06578 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/CatalogHandlerUtils.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/CatalogHandlerUtils.java
@@ -61,10 +61,12 @@ import org.apache.iceberg.catalog.SupportsNamespaces;
 import org.apache.iceberg.catalog.TableIdentifier;
 import org.apache.iceberg.catalog.ViewCatalog;
 import org.apache.iceberg.exceptions.CommitFailedException;
+import org.apache.iceberg.rest.RESTCatalogProperties;
 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.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;
@@ -94,8 +96,16 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * CODE_COPIED_TO_POLARIS Copied from CatalogHandler in Iceberg 1.8.0 Contains a collection of
- * utilities related to managing Iceberg entities
+ * CODE_COPIED_TO_POLARIS
+ *
+ * <p>Copied from CatalogHandlers in Iceberg 1.8.0, then cherry-picked from 1.11.0.
+ *
+ * <p>Last imported commit SHA: b0c236512ae38b7668fa2a332b100db0f5b64d30.
+ *
+ * <p>Contains a collection of utilities related to managing Iceberg entities.
+ *
+ * <p>WARNING: this isn't a full copy of the Iceberg version, but rather, a cherry-pick of relevant
+ * functions. For instance, the idempotency store is not copied.
  */
 @ApplicationScoped
 public class CatalogHandlerUtils {
@@ -321,13 +331,33 @@ public class CatalogHandlerUtils {
     }
   }
 
+  /**
+   * @deprecated since 1.11.0, will be removed in 1.12.0. Use {@link #loadTable(Catalog,
+   *     TableIdentifier, RESTCatalogProperties.SnapshotMode)} instead.
+   */
+  @Deprecated
   public LoadTableResponse loadTable(Catalog catalog, TableIdentifier ident) {
+    return loadTable(catalog, ident, RESTCatalogProperties.SnapshotMode.ALL);
+  }
+
+  public LoadTableResponse loadTable(
+      Catalog catalog, TableIdentifier ident, RESTCatalogProperties.SnapshotMode mode) {
     Table table = catalog.loadTable(ident);
 
     if (table instanceof BaseTable baseTable) {
-      return LoadTableResponse.builder()
-          .withTableMetadata(baseTable.operations().current())
-          .build();
+      TableMetadata loadedMetadata = baseTable.operations().current();
+
+      TableMetadata metadata =
+          switch (mode) {
+            case ALL -> loadedMetadata;
+            case REFS ->
+                TableMetadata.buildFrom(loadedMetadata)
+                    .withMetadataLocation(loadedMetadata.metadataFileLocation())
+                    .suppressHistoricalSnapshots()
+                    .build();
+          };
+
+      return LoadTableResponse.builder().withTableMetadata(metadata).build();
     } else if (table instanceof BaseMetadataTable) {
       // metadata tables are loaded on the client side, return NoSuchTableException for now
       throw notFoundExceptionForTableLikeEntity(ident, PolarisEntitySubType.ICEBERG_TABLE);
@@ -746,6 +776,15 @@ public class CatalogHandlerUtils {
     }
   }
 
+  public 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 viewResponse(view);
+  }
+
   protected ViewMetadata commit(ViewOperations ops, UpdateTableRequest request) {
     AtomicBoolean isRetry = new AtomicBoolean(false);
     try {
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalog.java b/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalog.java
index 901eeafd04..f43158524e 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalog.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalog.java
@@ -950,6 +950,55 @@ public class IcebergCatalog extends BaseMetastoreViewCatalog
     throw new NoSuchViewException("Invalid view identifier: %s", identifier);
   }
 
+  @Override
+  public View registerView(TableIdentifier identifier, String metadataFileLocation) {
+    Preconditions.checkArgument(
+        identifier != null && isValidIdentifier(identifier), "Invalid identifier: %s", identifier);
+    Preconditions.checkArgument(
+        metadataFileLocation != null && !metadataFileLocation.isEmpty(),
+        "Cannot register an empty metadata file location as a view");
+
+    int lastSlashIndex = metadataFileLocation.lastIndexOf("/");
+    Preconditions.checkArgument(
+        lastSlashIndex != -1,
+        "Invalid metadata file location; metadata file location must be absolute and contain a '/': %s",
+        metadataFileLocation);
+
+    // Throw an exception if this view already exists in the catalog.
+    if (viewExists(identifier)) {
+      throw new AlreadyExistsException("View already exists: %s", identifier);
+    }
+
+    if (tableExists(identifier)) {
+      throw new AlreadyExistsException("Table with same name already exists: %s", identifier);
+    }
+
+    String locationDir = metadataFileLocation.substring(0, lastSlashIndex);
+
+    ViewOperations ops = newViewOps(identifier);
+
+    PolarisResolvedPathWrapper resolvedParent =
+        resolvedEntityView.getResolvedPath(identifier.namespace());
+    if (resolvedParent == null) {
+      // Illegal state because the namespace should've already been in the static resolution set.
+      throw new IllegalStateException(
+          String.format("Failed to fetch resolved parent for TableIdentifier '%s'", identifier));
+    }
+    FileIO fileIO =
+        loadFileIOForTableLike(
+            identifier,
+            Set.of(locationDir),
+            resolvedParent,
+            new HashMap<>(tableDefaultProperties),
+            Set.of(PolarisStorageActions.READ, PolarisStorageActions.LIST));
+
+    InputFile metadataFile = fileIO.newInputFile(metadataFileLocation);
+    ViewMetadata metadata = ViewMetadataParser.read(metadataFile);
+    ops.commit(null, metadata);
+
+    return new BaseView(ops, ViewUtil.fullViewName(name(), identifier));
+  }
+
   @Override
   public boolean dropView(TableIdentifier identifier) {
     boolean purge =
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogAdapter.java b/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogAdapter.java
index 229b635b9c..8afab9dd9d 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogAdapter.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogAdapter.java
@@ -44,6 +44,7 @@ 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.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;
@@ -502,6 +503,21 @@ public class IcebergCatalogAdapter
         catalog -> Response.ok(catalog.createView(ns, revisedRequest)).build());
   }
 
+  @Override
+  public Response registerView(
+      String prefix,
+      String namespace,
+      RegisterViewRequest registerViewRequest,
+      UUID idempotencyKey,
+      RealmContext realmContext,
+      SecurityContext securityContext) {
+    Namespace ns = decodeNamespace(namespace);
+    return withCatalog(
+        securityContext,
+        prefix,
+        catalog -> Response.ok(catalog.registerView(ns, registerViewRequest)).build());
+  }
+
   @Override
   public Response listViews(
       String prefix,
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandler.java b/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandler.java
index 965c0915b8..bd0009da03 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandler.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogHandler.java
@@ -75,6 +75,7 @@ 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.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;
@@ -180,6 +181,7 @@ public abstract class IcebergCatalogHandler extends CatalogHandler implements Au
           .add(Endpoint.V1_UPDATE_VIEW)
           .add(Endpoint.V1_DELETE_VIEW)
           .add(Endpoint.V1_RENAME_VIEW)
+          .add(Endpoint.V1_REGISTER_VIEW)
           .build();
 
   protected abstract PolarisDiagnostics diagnostics();
@@ -1210,6 +1212,14 @@ public abstract class IcebergCatalogHandler extends CatalogHandler implements Au
     return catalogHandlerUtils().createView(viewCatalog, namespace, request);
   }
 
+  public LoadViewResponse registerView(Namespace namespace, RegisterViewRequest request) {
+    PolarisAuthorizableOperation op = PolarisAuthorizableOperation.REGISTER_VIEW;
+    authorizeCreateTableLikeUnderNamespaceOperationOrThrow(
+        op, TableIdentifier.of(namespace, request.name()));
+
+    return catalogHandlerUtils().registerView(viewCatalog, namespace, request);
+  }
+
   public LoadViewResponse loadView(TableIdentifier viewIdentifier) {
     PolarisAuthorizableOperation op = PolarisAuthorizableOperation.LOAD_VIEW;
     authorizeBasicTableLikeOperationOrThrow(op, PolarisEntitySubType.ICEBERG_VIEW, viewIdentifier);
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergRestCatalogEventServiceDelegator.java b/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergRestCatalogEventServiceDelegator.java
index bc51c278bb..bc832b8ab5 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergRestCatalogEventServiceDelegator.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergRestCatalogEventServiceDelegator.java
@@ -37,6 +37,7 @@ 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.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;
@@ -596,6 +597,39 @@ public class IcebergRestCatalogEventServiceDelegator
     return resp;
   }
 
+  @Override
+  public Response registerView(
+      String prefix,
+      String namespace,
+      RegisterViewRequest registerViewRequest,
+      UUID idempotencyKey,
+      RealmContext realmContext,
+      SecurityContext securityContext) {
+    String catalogName = prefixParser.prefixToCatalogName(prefix);
+    Namespace namespaceObj = decodeNamespace(namespace);
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.BEFORE_REGISTER_VIEW,
+            eventMetadataFactory.create(),
+            new EventAttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, namespaceObj)
+                .put(EventAttributes.REGISTER_VIEW_REQUEST, registerViewRequest)));
+    Response resp =
+        delegate.registerView(
+            prefix, namespace, registerViewRequest, idempotencyKey, realmContext, securityContext);
+    polarisEventListener.onEvent(
+        new PolarisEvent(
+            PolarisEventType.AFTER_REGISTER_VIEW,
+            eventMetadataFactory.create(),
+            new EventAttributeMap()
+                .put(EventAttributes.CATALOG_NAME, catalogName)
+                .put(EventAttributes.NAMESPACE, namespaceObj)
+                .put(EventAttributes.VIEW_NAME, registerViewRequest.name())
+                .put(EventAttributes.LOAD_VIEW_RESPONSE, (LoadViewResponse) resp.getEntity())));
+    return resp;
+  }
+
   @Override
   public Response listViews(
       String prefix,
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/events/EventAttributes.java b/runtime/service/src/main/java/org/apache/polaris/service/events/EventAttributes.java
index 138133d62a..27e58a148a 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/events/EventAttributes.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/events/EventAttributes.java
@@ -29,6 +29,7 @@ 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.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;
@@ -132,6 +133,8 @@ public final class EventAttributes {
       new AttributeKey<>("view_identifier", TableIdentifier.class);
   public static final AttributeKey<CreateViewRequest> CREATE_VIEW_REQUEST =
       new AttributeKey<>("create_view_request", CreateViewRequest.class);
+  public static final AttributeKey<RegisterViewRequest> REGISTER_VIEW_REQUEST =
+      new AttributeKey<>("register_view_request", RegisterViewRequest.class);
   public static final AttributeKey<CommitViewRequest> COMMIT_VIEW_REQUEST =
       new AttributeKey<>("commit_view_request", CommitViewRequest.class);
   public static final AttributeKey<ViewMetadata> VIEW_METADATA_BEFORE =
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/events/PolarisEventType.java b/runtime/service/src/main/java/org/apache/polaris/service/events/PolarisEventType.java
index de6b994608..9480fd188c 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/events/PolarisEventType.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/events/PolarisEventType.java
@@ -145,6 +145,8 @@ public enum PolarisEventType {
   AFTER_REPLACE_VIEW(713),
   BEFORE_REFRESH_VIEW(714),
   AFTER_REFRESH_VIEW(715),
+  BEFORE_REGISTER_VIEW(716),
+  AFTER_REGISTER_VIEW(717),
 
   // Credential Events
   BEFORE_LOAD_CREDENTIALS(800),
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/AbstractIcebergCatalogViewTest.java b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/AbstractIcebergCatalogViewTest.java
index d8269cc693..a6fc777b0c 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/AbstractIcebergCatalogViewTest.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/AbstractIcebergCatalogViewTest.java
@@ -72,7 +72,6 @@ import org.assertj.core.configuration.PreferredAssumptionException;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInfo;
 import org.junit.jupiter.api.io.TempDir;
@@ -269,19 +268,4 @@ public abstract class AbstractIcebergCatalogViewTest extends ViewCatalogTests<Ic
             afterRefreshEvent.attributes().getRequired(EventAttributes.VIEW_IDENTIFIER))
         .isEqualTo(TestData.TABLE);
   }
-
-  @Test
-  @Disabled("Polaris REST catalog does not support view registration")
-  @Override
-  public void registerView() {}
-
-  @Test
-  @Disabled("Polaris REST catalog does not support view registration")
-  @Override
-  public void registerExistingView() {}
-
-  @Test
-  @Disabled("Polaris REST catalog does not support view registration")
-  @Override
-  public void registerViewThatAlreadyExistsAsTable() {}
 }
diff --git a/spec/generated/bundled-polaris-catalog-service.yaml b/spec/generated/bundled-polaris-catalog-service.yaml
index c580a237fb..5f439eee59 100644
--- a/spec/generated/bundled-polaris-catalog-service.yaml
+++ b/spec/generated/bundled-polaris-catalog-service.yaml
@@ -1092,6 +1092,57 @@ paths:
           $ref: '#/components/responses/ServiceUnavailableResponse'
         5XX:
           $ref: '#/components/responses/ServerErrorResponse'
+  /v1/{prefix}/namespaces/{namespace}/register-view:
+    parameters:
+      - $ref: '#/components/parameters/prefix'
+      - $ref: '#/components/parameters/namespace'
+    post:
+      tags:
+        - Catalog API
+      summary: Register a view in the catalog
+      parameters:
+        - $ref: '#/components/parameters/idempotency-key'
+      description: Register a view in the given namespace using given metadata file location.
+      operationId: registerView
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/RegisterViewRequest'
+      responses:
+        '200':
+          $ref: '#/components/responses/LoadViewResponse'
+        '400':
+          $ref: '#/components/responses/BadRequestErrorResponse'
+        '401':
+          $ref: '#/components/responses/UnauthorizedResponse'
+        '403':
+          $ref: '#/components/responses/ForbiddenResponse'
+        '404':
+          description: Not Found - The namespace specified does not exist
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/IcebergErrorResponse'
+              examples:
+                NamespaceNotFound:
+                  $ref: '#/components/examples/NoSuchNamespaceError'
+        '409':
+          description: Conflict - The identifier already exists as a table or view
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/IcebergErrorResponse'
+              examples:
+                ViewAlreadyExists:
+                  $ref: '#/components/examples/ViewAlreadyExistsError'
+        '419':
+          $ref: '#/components/responses/AuthenticationTimeoutResponse'
+        '503':
+          $ref: '#/components/responses/ServiceUnavailableResponse'
+        5XX:
+          $ref: '#/components/responses/ServerErrorResponse'
   /v1/{prefix}/namespaces/{namespace}/views/{view}:
     parameters:
       - $ref: '#/components/parameters/prefix'
@@ -3891,6 +3942,16 @@ components:
           type: object
           additionalProperties:
             type: string
+    RegisterViewRequest:
+      type: object
+      required:
+        - name
+        - metadata-location
+      properties:
+        name:
+          type: string
+        metadata-location:
+          type: string
     AssertViewUUID:
       description: The view UUID must match the requirement's `uuid`
       required:
diff --git a/spec/polaris-catalog-service.yaml b/spec/polaris-catalog-service.yaml
index 973ea46072..d5372239ad 100644
--- a/spec/polaris-catalog-service.yaml
+++ b/spec/polaris-catalog-service.yaml
@@ -112,6 +112,9 @@ paths:
   /v1/{prefix}/namespaces/{namespace}/views:
     $ref: './iceberg-rest-catalog-open-api.yaml#/paths/~1v1~1{prefix}~1namespaces~1{namespace}~1views'
 
+  /v1/{prefix}/namespaces/{namespace}/register-view:
+    $ref: './iceberg-rest-catalog-open-api.yaml#/paths/~1v1~1{prefix}~1namespaces~1{namespace}~1register-view'
+
   /v1/{prefix}/namespaces/{namespace}/views/{view}:
     $ref: './iceberg-rest-catalog-open-api.yaml#/paths/~1v1~1{prefix}~1namespaces~1{namespace}~1views~1{view}'