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

jerryshao pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/main by this push:
     new 17e50084e2 [#11004] docs(open-api): add logical view management 
OpenAPI spec (#11010)
17e50084e2 is described below

commit 17e50084e213deabee253e36f4241da106262c19
Author: mchades <[email protected]>
AuthorDate: Mon May 11 16:11:07 2026 +0800

    [#11004] docs(open-api): add logical view management OpenAPI spec (#11010)
    
    ### What changes were proposed in this pull request?
    
    - Add OpenAPI path references for view resources in
    docs/open-api/openapi.yaml.
    - Add a new view API specification file docs/open-api/views.yaml,
    including:
      - List/Create/Get/Update/Delete view operations
      - View-related schemas and examples
      - Error response examples for already-exists and not-found cases
    - Align View response schema with server DTO contract by including
    required audit.
    - Align ViewCreateRequest required fields with server validation (name +
    representations).
    
    ### Why are the changes needed?
    
    - The REST server already provides view management endpoints, but
    OpenAPI documentation was incomplete.
    - This PR documents view APIs for client generation and API
    discoverability, and keeps schema behavior consistent with server
    implementation.
    
    Fix: #11004
    
    ### Does this PR introduce _any_ user-facing change?
    
    - Yes. API documentation now includes view management endpoints and
    payload schemas.
    - No runtime behavior change in server code.
    
    ### How was this patch tested?
    
    - Run docs OpenAPI validation via: ./gradlew :docs:build
    - Confirm openapi lint passes successfully.
    
    ---------
    
    Co-authored-by: Copilot <[email protected]>
---
 AGENTS.md                                        |   3 +-
 design-docs/gravitino-logical-view-management.md | 317 +++++++------
 docs/open-api/openapi.yaml                       |  14 +
 docs/open-api/views.yaml                         | 544 +++++++++++++++++++++++
 4 files changed, 749 insertions(+), 129 deletions(-)

diff --git a/AGENTS.md b/AGENTS.md
index cae35a7b23..b8899be872 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -22,6 +22,7 @@
 ## General Coding Standards
 - **Language**: Use English for all code, comments, and documentation.
 - **Style**: Follow rigid Google Java Style. Run `./gradlew spotlessApply` to 
format.
+- **Javadoc**: All new `public` and `protected` classes, methods, and fields 
must have Javadoc. Missing Javadoc fails the checkstyle CI step (runs with 
`-Werror`).
 - **Imports**: Always use normal `import` statements instead of Fully 
Qualified Class Names (FQN) in Java code whenever possible.
   - **Bad**: `org.apache.gravitino.rel.Table table = ...;`
   - **Good**: `Table table = ...;` (with `import 
org.apache.gravitino.rel.Table;`)
@@ -33,7 +34,7 @@
   - Write unit tests for ALL new logic. NO tests = NO merge.
   - Use `TestXxx` naming pattern (e.g., `TestCatalogService`).
   - Run tests: `./gradlew test -PskipITs`.
-  - Docker tests: Tag with `@Tag("gravitino-docker-test")`.
+  - Docker tests: Tag with `@Tag("gravitino-docker-test")`; run them with 
`-PskipDockerTests=false`.
 - **Class Member Ordering**: Follow the order:
   1. `static` constants (e.g., `LOG`).
   2. `static` fields.
diff --git a/design-docs/gravitino-logical-view-management.md 
b/design-docs/gravitino-logical-view-management.md
index b2b800a404..774c7c057b 100644
--- a/design-docs/gravitino-logical-view-management.md
+++ b/design-docs/gravitino-logical-view-management.md
@@ -37,7 +37,7 @@ Apache Gravitino, as a unified metadata management system, is 
well-positioned to
 2. **Unified View Management**: Provide standard CRUD operations for views:
    - Create view
    - Get/List views
-   - Alter view (update SQL, add representations, modify properties)
+   - Alter view (rename, update SQL, add/remove representations, replace the 
view body, modify properties)
    - Drop view
 
 3. **Capability-Driven Storage Strategy**: Automatically select the optimal 
storage strategy based on each catalog's capabilities — no user-facing storage 
mode configuration needed. Gravitino transparently handles delegation, 
extension, and full management per catalog type.
@@ -75,7 +75,7 @@ metalake
               └── view
 ```
 
-This is consistent with Gravitino's existing namespace design for tables and 
functions. **Views and tables share the same namespace within a schema** — a 
view and a table cannot have the same name under the same schema. This follows 
the standard behavior of most relational databases (MySQL, PostgreSQL, Hive, 
etc.).
+This is consistent with Gravitino's existing namespace design for tables and 
functions. In the current common implementation, views are addressed as 
`schema.view`. Whether views and tables share one underlying namespace, 
including same-name conflict handling, is delegated to the catalog 
implementation rather than enforced by the shared view layer.
 
 ---
 
@@ -85,22 +85,16 @@ This is consistent with Gravitino's existing namespace 
design for tables and fun
 
 ```
 View
-├── name: string                          # View name (unique within schema, 
shared namespace with tables)
+├── name: string                          # View name
 ├── comment: string                       # Optional description
-├── columns: array<ViewColumn>            # View schema definition
-│   └── ViewColumn
-│       ├── name: string
-│       ├── type: DataType
-│       └── comment: string (optional)
-├── representations: array<Representation>    # Multi-dialect view definitions 
(one per dialect)
-│   └── Representation
-│       ├── type: string                      # Representation type, currently 
only "sql"
-│       └── SQLRepresentation (type="sql")
-│           ├── dialect: string               # e.g., "trino", "spark", "hive" 
(unique within a view)
-│           ├── sql: string                   # The view definition SQL
-│           ├── defaultCatalog: string        # Default catalog for 
unqualified refs
-│           └── defaultSchema: string         # Default schema for unqualified 
refs
-├── securityMode: enum                    # DEFINER | INVOKER
+├── columns: array<Column>                # Reuses Gravitino Column model; may 
be empty
+├── representations: array<Representation>
+│   └── SQLRepresentation (type="sql")
+│       ├── dialect: string               # e.g., "trino", "spark", "hive"
+│       └── sql: string                   # The view definition SQL
+├── defaultCatalog: string                # Optional, shared across all 
representations
+├── defaultSchema: string                 # Optional, shared across all 
representations
+├── securityMode: enum                    # DEFINER | INVOKER (planned field)
 ├── properties: map<string, string>       # Extensible key-value properties
 └── auditInfo: AuditInfo                  # Creation/modification timestamps 
and users
 ```
@@ -121,8 +115,9 @@ View
   - Gravitino provides a set of standard dialect constants (e.g., 
`Dialects.TRINO`, `Dialects.SPARK`) for engine connectors to use, reducing the 
risk of typos while preserving extensibility.
   - Engine connectors use this value to locate the appropriate representation 
when loading a view.
 
-- **defaultCatalog / defaultSchema**: The catalog and schema context in which 
the SQL was authored. Optional, per-representation.
+- **defaultCatalog / defaultSchema**: The catalog and schema context in which 
the SQL was authored. Optional, stored at the view level and shared across all 
representations.
   - Used by engines to resolve unqualified table references (e.g., `FROM 
orders` → `FROM defaultCatalog.defaultSchema.orders`).
+    - In storage, these fields are versioned together with the rest of the 
replaceable view body.
   - View SQL may contain cross-catalog references (e.g., 
`catalog_a.schema.table JOIN catalog_b.schema.table`). The SQL is stored as-is; 
neither Gravitino, the IRC, nor the HMS validates, rewrites, or transforms view 
SQL at any point. The compute engine is responsible for resolving and executing 
cross-catalog queries at runtime.
 
 - **securityMode**: Declares the security execution model of the view. This is 
a metadata property stored by Gravitino and **passed through to the compute 
engine** — Gravitino does not enforce it. Whether it takes effect depends on 
the engine's capability (e.g., MySQL natively supports DEFINER/INVOKER; Iceberg 
and Hive do not).
@@ -294,20 +289,20 @@ CREATE TABLE IF NOT EXISTS `view_meta` (
     `metalake_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'metalake id',
     `catalog_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'catalog id',
     `schema_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'schema id',
-    `audit_info` MEDIUMTEXT NOT NULL COMMENT 'view audit info (JSON)',
-    `current_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'current version 
pointer',
-    `latest_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'latest version 
number',
-    `deleted_at` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'soft delete 
timestamp',
+    `audit_info` MEDIUMTEXT NOT NULL COMMENT 'view audit info',
+    `current_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'view current 
version',
+    `last_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'view last version',
+    `deleted_at` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'view deleted 
at',
     PRIMARY KEY (`view_id`),
     UNIQUE KEY `uk_sid_vn_del` (`schema_id`, `view_name`, `deleted_at`),
-    KEY `idx_mid` (`metalake_id`),
-    KEY `idx_cid` (`catalog_id`)
+    KEY `idx_vemid` (`metalake_id`),
+    KEY `idx_vecid` (`catalog_id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT 'view 
metadata';
 ```
 
 #### view_version_info table
 
-Each alter operation creates a new version. The version table stores 
version-specific metadata including comment, columns snapshot, properties, 
representations, and audit info. Representations are stored as a JSON array.
+Each stored alter operation creates a new version row. The version table 
stores version-specific metadata including comment, columns snapshot, 
properties, shared default catalog/schema, representations, and audit info. 
Representations are stored as a JSON array.
 
 ```sql
 CREATE TABLE IF NOT EXISTS `view_version_info` (
@@ -317,18 +312,19 @@ CREATE TABLE IF NOT EXISTS `view_version_info` (
     `schema_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'schema id',
     `view_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'view id',
     `version` INT UNSIGNED NOT NULL COMMENT 'view version',
-    `view_comment` TEXT DEFAULT NULL COMMENT 'version-specific comment',
-    `columns` MEDIUMTEXT NOT NULL COMMENT 'view columns definition snapshot 
(JSON)',
+    `view_comment` TEXT DEFAULT NULL COMMENT 'view version comment',
+    `columns` MEDIUMTEXT NOT NULL COMMENT 'view columns snapshot (JSON)',
     `properties` MEDIUMTEXT DEFAULT NULL COMMENT 'view properties (JSON)',
-    `security_mode` VARCHAR(32) NOT NULL DEFAULT 'DEFINER' COMMENT 'DEFINER or 
INVOKER, immutable in V1',
-    `representations` MEDIUMTEXT NOT NULL COMMENT 'SQL representations (JSON 
array)',
-    `audit_info` MEDIUMTEXT NOT NULL COMMENT 'version audit info (JSON)',
-    `deleted_at` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'soft delete 
timestamp',
+    `default_catalog` VARCHAR(128) DEFAULT NULL COMMENT 'default catalog for 
view SQL resolution',
+    `default_schema` VARCHAR(128) DEFAULT NULL COMMENT 'default schema for 
view SQL resolution',
+    `representations` MEDIUMTEXT NOT NULL COMMENT 'view representations (JSON 
array)',
+    `audit_info` MEDIUMTEXT NOT NULL COMMENT 'view version audit info',
+    `deleted_at` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'view version 
deleted at',
     PRIMARY KEY (`id`),
     UNIQUE KEY `uk_vid_ver_del` (`view_id`, `version`, `deleted_at`),
-    KEY `idx_mid` (`metalake_id`),
-    KEY `idx_cid` (`catalog_id`),
-    KEY `idx_sid` (`schema_id`)
+    KEY `idx_vvmid` (`metalake_id`),
+    KEY `idx_vvcid` (`catalog_id`),
+    KEY `idx_vvsid` (`schema_id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT 'view 
version info';
 ```
 
@@ -553,33 +549,59 @@ POST 
/api/metalakes/{metalake}/catalogs/{catalog}/schemas/{schema}/views
 {
   "name": "customer_summary",
   "comment": "Aggregated customer data view",
-  "columns": [
-    {"name": "customer_id", "type": "bigint", "comment": "Customer 
identifier"},
-    {"name": "total_orders", "type": "int", "comment": "Total number of 
orders"},
-    {"name": "total_amount", "type": "decimal(18,2)", "comment": "Total order 
amount"}
-  ],
+  "columns": [],
   "representations": [
     {
+      "type": "sql",
       "dialect": "trino",
-      "sql": "SELECT customer_id, COUNT(*) as total_orders, SUM(amount) as 
total_amount FROM orders GROUP BY customer_id",
-      "defaultCatalog": "iceberg_prod",
-      "defaultSchema": "sales"
+      "sql": "SELECT customer_id, COUNT(*) AS total_orders, SUM(amount) AS 
total_amount FROM orders GROUP BY customer_id"
     },
     {
+      "type": "sql",
       "dialect": "spark",
-      "sql": "SELECT customer_id, COUNT(*) as total_orders, SUM(amount) as 
total_amount FROM orders GROUP BY customer_id",
-      "defaultCatalog": "iceberg_prod",
-      "defaultSchema": "sales"
+      "sql": "SELECT customer_id, COUNT(*) AS total_orders, SUM(amount) AS 
total_amount FROM orders GROUP BY customer_id"
     }
   ],
-  "securityMode": "DEFINER",
+  "defaultCatalog": "iceberg_prod",
+  "defaultSchema": "sales",
   "properties": {
     "description": "Customer order summary for analytics"
   }
 }
 ```
 
-**Response:** `200 OK` with the created view object.
+**Response:** `200 OK`
+
+```json
+{
+  "code": 0,
+  "view": {
+    "name": "customer_summary",
+    "comment": "Aggregated customer data view",
+    "columns": [],
+    "representations": [
+      {
+        "type": "sql",
+        "dialect": "trino",
+        "sql": "SELECT customer_id, COUNT(*) AS total_orders, SUM(amount) AS 
total_amount FROM orders GROUP BY customer_id"
+      }
+    ],
+    "defaultCatalog": "iceberg_prod",
+    "defaultSchema": "sales",
+    "properties": {
+      "description": "Customer order summary for analytics"
+    },
+    "audit": {
+      "creator": "admin",
+      "createTime": "2026-01-31T10:00:00Z",
+      "lastModifier": "admin",
+      "lastModifiedTime": "2026-01-31T10:00:00Z"
+    }
+  }
+}
+```
+
+> **Planned field:** `securityMode` remains part of the API design, but the 
current shared REST DTOs (`ViewCreateRequest`, `ViewDTO`, `ViewUpdateRequest`) 
do not expose it yet.
 
 ##### Get View
 
@@ -591,17 +613,29 @@ GET 
/api/metalakes/{metalake}/catalogs/{catalog}/schemas/{schema}/views/{view}
 
 ```json
 {
-  "name": "customer_summary",
-  "comment": "Aggregated customer data view",
-  "columns": [...],
-  "representations": [...],
-  "securityMode": "...",
-  "properties": {...},
-  "auditInfo": {
-    "creator": "admin",
-    "createTime": "2026-01-31T10:00:00Z",
-    "lastModifier": "admin",
-    "lastModifiedTime": "2026-01-31T10:00:00Z"
+  "code": 0,
+  "view": {
+    "name": "customer_summary",
+    "comment": "Aggregated customer data view",
+    "columns": [],
+    "representations": [
+      {
+        "type": "sql",
+        "dialect": "trino",
+        "sql": "SELECT customer_id, COUNT(*) AS total_orders, SUM(amount) AS 
total_amount FROM orders GROUP BY customer_id"
+      }
+    ],
+    "defaultCatalog": "iceberg_prod",
+    "defaultSchema": "sales",
+    "properties": {
+      "description": "Customer order summary for analytics"
+    },
+    "audit": {
+      "creator": "admin",
+      "createTime": "2026-01-31T10:00:00Z",
+      "lastModifier": "admin",
+      "lastModifiedTime": "2026-01-31T10:00:00Z"
+    }
   }
 }
 ```
@@ -616,9 +650,10 @@ GET 
/api/metalakes/{metalake}/catalogs/{catalog}/schemas/{schema}/views
 
 ```json
 {
+  "code": 0,
   "identifiers": [
-    {"namespace": ["catalog", "schema"], "name": "customer_summary"},
-    {"namespace": ["catalog", "schema"], "name": "order_details"}
+    {"namespace": ["metalake", "catalog", "schema"], "name": 
"customer_summary"},
+    {"namespace": ["metalake", "catalog", "schema"], "name": "order_details"}
   ]
 }
 ```
@@ -627,19 +662,22 @@ GET 
/api/metalakes/{metalake}/catalogs/{catalog}/schemas/{schema}/views
 
 Alter View supports fine-grained modification operations, following the same 
`ViewChange` pattern as `TableChange` and `FunctionChange`. Multiple changes 
can be submitted in a single request and are applied atomically.
 
+The current shared implementation already supports `rename`, `setProperty`, 
`removeProperty`, and `replaceView`. Other designed change types below are 
retained here as planned API surface.
+
 **Supported change types:**
 
-| Change Type | Description | Notes |
-|------------|-------------|-------|
-| `rename` | Rename the view | Also renames in the underlying catalog if 
delegated |
-| `updateComment` | Update view comment | |
-| `setProperty` | Set a view property | |
-| `removeProperty` | Remove a view property | |
-| `addRepresentation` | Add a new dialect representation | If the dialect 
matches the underlying catalog's native dialect, the operation is also 
delegated to the underlying catalog |
-| `updateRepresentation` | Update SQL for an existing dialect | If the dialect 
matches the underlying catalog's native dialect, the update is also synced to 
the underlying catalog |
-| `removeRepresentation` | Remove a dialect representation | Delegated to the 
underlying catalog if dialect matches; removing the last representation is 
prohibited — use `dropView` instead |
+| Change Type | Description | Notes | Status |
+|------------|-------------|-------|--------|
+| `rename` | Rename the view | Also renames in the underlying catalog if 
delegated | Implemented |
+| `updateComment` | Update view comment | | Planned |
+| `setProperty` | Set a view property | | Implemented |
+| `removeProperty` | Remove a view property | | Implemented |
+| `addRepresentation` | Add a new dialect representation | If the dialect 
matches the underlying catalog's native dialect, the operation is also 
delegated to the underlying catalog | Planned |
+| `updateRepresentation` | Update SQL for an existing dialect | If the dialect 
matches the underlying catalog's native dialect, the update is also synced to 
the underlying catalog | Planned |
+| `removeRepresentation` | Remove a dialect representation | Delegated to the 
underlying catalog if dialect matches; removing the last representation is 
prohibited and callers should use `dropView` instead | Planned |
+| `replaceView` | Atomically replace `columns`, `representations`, 
`defaultCatalog`, `defaultSchema`, and `comment` | The current implementation 
uses this coarse-grained operation for view body replacement | Implemented |
 
-**Versioning behavior**: Every alter operation internally creates a new 
version in storage (comment, columns snapshot, and all representations are 
captured as a new version). This is transparent to the user in V1 — no version 
management API is exposed. Future versions may add `listVersions` / 
`rollbackToVersion` capabilities.
+**Versioning behavior**: Every stored alter operation creates a new version in 
relational storage. In the current shared implementation, each successful alter 
writes a new `view_version_info` row. This is transparent to the user in V1 — 
no version management API is exposed. Future versions may add `listVersions` / 
`rollbackToVersion` capabilities.
 
 ```
 PUT /api/metalakes/{metalake}/catalogs/{catalog}/schemas/{schema}/views/{view}
@@ -647,39 +685,38 @@ PUT 
/api/metalakes/{metalake}/catalogs/{catalog}/schemas/{schema}/views/{view}
 
 **Request Body:**
 
+The example below uses only the update types that are currently implemented in 
the shared REST layer.
+
 ```json
 {
   "updates": [
     {
-      "@type": "updateComment",
-      "newComment": "Updated customer summary view"
-    },
-    {
-      "@type": "addRepresentation",
-      "representation": {
-        "dialect": "hive",
-        "sql": "SELECT customer_id, COUNT(*) as total_orders, SUM(amount) as 
total_amount FROM orders GROUP BY customer_id",
-        "defaultCatalog": "hive_prod",
-        "defaultSchema": "sales"
-      }
-    },
-    {
-      "@type": "updateRepresentation",
-      "dialect": "trino",
-      "newSql": "SELECT customer_id, COUNT(*) as total_orders, SUM(amount) as 
total_amount, MAX(order_date) as last_order FROM orders GROUP BY customer_id"
-    },
-    {
-      "@type": "removeRepresentation",
-      "dialect": "spark"
+      "@type": "replaceView",
+      "columns": [],
+      "representations": [
+        {
+          "type": "sql",
+          "dialect": "trino",
+          "sql": "SELECT customer_id, COUNT(*) AS total_orders, SUM(amount) AS 
total_amount, MAX(order_date) AS last_order FROM orders GROUP BY customer_id"
+        },
+        {
+          "type": "sql",
+          "dialect": "spark",
+          "sql": "SELECT customer_id, COUNT(*) AS total_orders, SUM(amount) AS 
total_amount, MAX(order_date) AS last_order FROM orders GROUP BY customer_id"
+        }
+      ],
+      "defaultCatalog": "iceberg_prod",
+      "defaultSchema": "sales",
+      "comment": "Updated customer summary view"
     },
     {
       "@type": "setProperty",
-      "property": "key",
-      "value": "value"
+      "property": "description",
+      "value": "Customer order summary for analytics"
     },
     {
       "@type": "removeProperty",
-      "property": "key"
+      "property": "deprecatedKey"
     }
   ]
 }
@@ -693,59 +730,83 @@ DELETE 
/api/metalakes/{metalake}/catalogs/{catalog}/schemas/{schema}/views/{view
 
 **Response:** `200 OK`
 
+```json
+{
+  "code": 0,
+  "dropped": true
+}
+```
+
 ---
 
 #### Java API
 
+The current `client-java` surface exposes the implemented subset above, so 
alter examples use `replaceView` / property changes today. Planned fine-grained 
helpers such as `addRepresentation`, `updateRepresentation`, and 
`updateComment` are not exposed yet. The design-level `securityMode` field is 
also not part of the current `createView(...)` signature.
+
 ```java
 // Get ViewCatalog interface from catalog
 ViewCatalog viewCatalog = catalog.asViewCatalog();
 
-// Create a view with multiple dialect representations
-View view = viewCatalog.createView(
-    NameIdentifier.of("analytics_schema", "customer_summary"),
-    ViewBuilder.builder()
-        .withComment("Aggregated customer data view")
-        .withColumn("customer_id", Types.LongType.get(), "Customer identifier")
-        .withColumn("total_orders", Types.IntegerType.get(), "Total number of 
orders")
-        .withColumn("total_amount", Types.DecimalType.of(18, 2), "Total order 
amount")
-        .withRepresentation(
-            SQLRepresentation.builder()
-                .withDialect("trino")
-                .withSql("SELECT customer_id, COUNT(*) as total_orders, 
SUM(amount) as total_amount FROM orders GROUP BY customer_id")
-                .withDefaultCatalog("iceberg_prod")
-                .withDefaultSchema("sales")
-                .build())
-        .withRepresentation(
-            SQLRepresentation.builder()
-                .withDialect("spark")
-                .withSql("SELECT customer_id, COUNT(*) as total_orders, 
SUM(amount) as total_amount FROM orders GROUP BY customer_id")
-                .withDefaultCatalog("iceberg_prod")
-                .withDefaultSchema("sales")
-                .build())
-        .withSecurityMode(SecurityMode.DEFINER)
-        .withProperty("description", "Customer order summary for analytics")
-        .build());
+Column[] columns =
+    new Column[] {
+      Column.of("customer_id", Types.LongType.get(), "Customer identifier"),
+      Column.of("total_orders", Types.IntegerType.get(), "Total number of 
orders"),
+      Column.of("total_amount", Types.DecimalType.of(18, 2), "Total order 
amount")
+    };
+
+Representation[] representations =
+    new Representation[] {
+      SQLRepresentation.builder()
+          .withDialect(Dialects.TRINO)
+          .withSql(
+              "SELECT customer_id, COUNT(*) AS total_orders, SUM(amount) AS 
total_amount FROM orders GROUP BY customer_id")
+          .build(),
+      SQLRepresentation.builder()
+          .withDialect(Dialects.SPARK)
+          .withSql(
+              "SELECT customer_id, COUNT(*) AS total_orders, SUM(amount) AS 
total_amount FROM orders GROUP BY customer_id")
+          .build()
+    };
+
+View view =
+    viewCatalog.createView(
+        NameIdentifier.of("analytics_schema", "customer_summary"),
+        "Aggregated customer data view",
+        columns,
+        representations,
+        "iceberg_prod",
+        "sales",
+        ImmutableMap.of("description", "Customer order summary for 
analytics"));
 
 // Get a view
 View gotView = viewCatalog.loadView(NameIdentifier.of("analytics_schema", 
"customer_summary"));
 
 // Get SQL for specific dialect
-Optional<SQLRepresentation> trinoSql = gotView.getRepresentation("trino");
+Optional<SQLRepresentation> trinoSql = gotView.sqlFor(Dialects.TRINO);
 
 // List views in a schema
 NameIdentifier[] views = 
viewCatalog.listViews(Namespace.of("analytics_schema"));
 
-// Alter a view - add new representation
+// Alter a view
 ViewChange[] changes = {
-    ViewChange.addRepresentation(
-        SQLRepresentation.builder()
-            .withDialect("hive")
-            .withSql("SELECT customer_id, COUNT(*) as total_orders, 
SUM(amount) as total_amount FROM orders GROUP BY customer_id")
-            .withDefaultCatalog("hive_prod")
-            .withDefaultSchema("sales")
-            .build()),
-    ViewChange.updateComment("Updated customer summary view")
+    ViewChange.replaceView(
+        columns,
+        new Representation[] {
+          SQLRepresentation.builder()
+              .withDialect(Dialects.TRINO)
+              .withSql(
+                  "SELECT customer_id, COUNT(*) AS total_orders, SUM(amount) 
AS total_amount, MAX(order_date) AS last_order FROM orders GROUP BY 
customer_id")
+              .build(),
+          SQLRepresentation.builder()
+              .withDialect(Dialects.SPARK)
+              .withSql(
+                  "SELECT customer_id, COUNT(*) AS total_orders, SUM(amount) 
AS total_amount, MAX(order_date) AS last_order FROM orders GROUP BY 
customer_id")
+              .build()
+        },
+        "iceberg_prod",
+        "sales",
+        "Updated customer summary view"),
+    ViewChange.setProperty("description", "Customer order summary for 
analytics")
 };
 View alteredView = viewCatalog.alterView(
     NameIdentifier.of("analytics_schema", "customer_summary"), 
diff --git a/docs/open-api/openapi.yaml b/docs/open-api/openapi.yaml
index e922d7e1d7..d42aa77ee8 100644
--- a/docs/open-api/openapi.yaml
+++ b/docs/open-api/openapi.yaml
@@ -149,6 +149,12 @@ paths:
   /metalakes/{metalake}/catalogs/{catalog}/schemas/{schema}/topics/{topic}:
     $ref: 
"./topics.yaml#/paths/~1metalakes~1%7Bmetalake%7D~1catalogs~1%7Bcatalog%7D~1schemas~1%7Bschema%7D~1topics~1%7Btopic%7D"
 
+  /metalakes/{metalake}/catalogs/{catalog}/schemas/{schema}/views:
+    $ref: 
"./views.yaml#/paths/~1metalakes~1%7Bmetalake%7D~1catalogs~1%7Bcatalog%7D~1schemas~1%7Bschema%7D~1views"
+
+  /metalakes/{metalake}/catalogs/{catalog}/schemas/{schema}/views/{view}:
+    $ref: 
"./views.yaml#/paths/~1metalakes~1%7Bmetalake%7D~1catalogs~1%7Bcatalog%7D~1schemas~1%7Bschema%7D~1views~1%7Bview%7D"
+
   /metalakes/{metalake}/catalogs/{catalog}/schemas/{schema}/functions:
     $ref: 
"./functions.yaml#/paths/~1metalakes~1%7Bmetalake%7D~1catalogs~1%7Bcatalog%7D~1schemas~1%7Bschema%7D~1functions"
 
@@ -513,6 +519,14 @@ components:
       schema:
         type: string
 
+    view:
+      name: view
+      in: path
+      description: The name of the view
+      required: true
+      schema:
+        type: string
+
     model:
       name: model
       in: path
diff --git a/docs/open-api/views.yaml b/docs/open-api/views.yaml
new file mode 100644
index 0000000000..483d3532de
--- /dev/null
+++ b/docs/open-api/views.yaml
@@ -0,0 +1,544 @@
+# 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.
+
+---
+
+paths:
+
+  /metalakes/{metalake}/catalogs/{catalog}/schemas/{schema}/views:
+    parameters:
+      - $ref: "./openapi.yaml#/components/parameters/metalake"
+      - $ref: "./openapi.yaml#/components/parameters/catalog"
+      - $ref: "./openapi.yaml#/components/parameters/schema"
+
+    get:
+      tags:
+        - view
+      summary: List views
+      operationId: listViews
+      responses:
+        "200":
+          $ref: "./openapi.yaml#/components/responses/EntityListResponse"
+        "400":
+          $ref: "./openapi.yaml#/components/responses/BadRequestErrorResponse"
+        "404":
+          description: Not Found - The target schema does not exist
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+              examples:
+                NoSuchMetalakeException:
+                  $ref: 
"./metalakes.yaml#/components/examples/NoSuchMetalakeException"
+                NoSuchCatalogException:
+                  $ref: 
"./catalogs.yaml#/components/examples/NoSuchCatalogException"
+                NoSuchSchemaException:
+                  $ref: 
"./schemas.yaml#/components/examples/NoSuchSchemaException"
+        "5xx":
+          $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
+
+    post:
+      tags:
+        - view
+      summary: Create view
+      operationId: createView
+      requestBody:
+        content:
+          application/json:
+            schema:
+              $ref: "#/components/schemas/ViewCreateRequest"
+            examples:
+              ViewCreateRequest:
+                $ref: "#/components/examples/ViewCreateRequest"
+      responses:
+        "200":
+          $ref: "#/components/responses/ViewResponse"
+        "400":
+          $ref: "./openapi.yaml#/components/responses/BadRequestErrorResponse"
+        "404":
+          description: Not Found - The target schema does not exist
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+              examples:
+                NoSuchMetalakeException:
+                  $ref: 
"./metalakes.yaml#/components/examples/NoSuchMetalakeException"
+                NoSuchCatalogException:
+                  $ref: 
"./catalogs.yaml#/components/examples/NoSuchCatalogException"
+                NoSuchSchemaException:
+                  $ref: 
"./schemas.yaml#/components/examples/NoSuchSchemaException"
+        "409":
+          description: Conflict - The target view already exists
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+              examples:
+                ViewAlreadyExistsErrorResponse:
+                  $ref: "#/components/examples/ViewAlreadyExistsException"
+        "5xx":
+          $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
+
+  /metalakes/{metalake}/catalogs/{catalog}/schemas/{schema}/views/{view}:
+    parameters:
+      - $ref: "./openapi.yaml#/components/parameters/metalake"
+      - $ref: "./openapi.yaml#/components/parameters/catalog"
+      - $ref: "./openapi.yaml#/components/parameters/schema"
+      - $ref: "./openapi.yaml#/components/parameters/view"
+
+    get:
+      tags:
+        - view
+      summary: Get view
+      operationId: loadView
+      description: Return the specified view object
+      responses:
+        "200":
+          $ref: "#/components/responses/ViewResponse"
+        "404":
+          description: Not Found - The target view does not exist
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+              examples:
+                NoSuchMetalakeException:
+                  $ref: 
"./metalakes.yaml#/components/examples/NoSuchMetalakeException"
+                NoSuchCatalogException:
+                  $ref: 
"./catalogs.yaml#/components/examples/NoSuchCatalogException"
+                NoSuchSchemaException:
+                  $ref: 
"./schemas.yaml#/components/examples/NoSuchSchemaException"
+                NoSuchViewException:
+                  $ref: "#/components/examples/NoSuchViewException"
+        "5xx":
+          $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
+
+    put:
+      tags:
+        - view
+      summary: Update view
+      operationId: alterView
+      description: Update the specified view in a schema
+      requestBody:
+        content:
+          application/json:
+            schema:
+              $ref: "#/components/schemas/ViewUpdatesRequest"
+      responses:
+        "200":
+          $ref: "#/components/responses/ViewResponse"
+        "400":
+          $ref: "./openapi.yaml#/components/responses/BadRequestErrorResponse"
+        "404":
+          description: Not Found - The target view does not exist
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+              examples:
+                NoSuchMetalakeException:
+                  $ref: 
"./metalakes.yaml#/components/examples/NoSuchMetalakeException"
+                NoSuchCatalogException:
+                  $ref: 
"./catalogs.yaml#/components/examples/NoSuchCatalogException"
+                NoSuchSchemaException:
+                  $ref: 
"./schemas.yaml#/components/examples/NoSuchSchemaException"
+                NoSuchViewException:
+                  $ref: "#/components/examples/NoSuchViewException"
+        "409":
+          description: Conflict - The target view already exists
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+              examples:
+                ViewAlreadyExistsErrorResponse:
+                  $ref: "#/components/examples/ViewAlreadyExistsException"
+        "5xx":
+          $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
+
+    delete:
+      tags:
+        - view
+      summary: Delete view
+      operationId: dropView
+      responses:
+        "200":
+          $ref: "./openapi.yaml#/components/responses/DropResponse"
+        "400":
+          $ref: "./openapi.yaml#/components/responses/BadRequestErrorResponse"
+        "404":
+          description: Not Found - The target metalake or catalog does not 
exist
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+              examples:
+                NoSuchMetalakeException:
+                  $ref: 
"./metalakes.yaml#/components/examples/NoSuchMetalakeException"
+                NoSuchCatalogException:
+                  $ref: 
"./catalogs.yaml#/components/examples/NoSuchCatalogException"
+        "5xx":
+          $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
+
+components:
+
+  schemas:
+    SQLRepresentation:
+      type: object
+      required:
+        - type
+        - dialect
+        - sql
+      properties:
+        type:
+          type: string
+          enum:
+            - "sql"
+          description: The representation type discriminator
+        dialect:
+          type: string
+          description: The SQL dialect of this representation
+        sql:
+          type: string
+          description: The SQL text of the view
+
+    Representation:
+      oneOf:
+        - $ref: "#/components/schemas/SQLRepresentation"
+      discriminator:
+        propertyName: type
+        mapping:
+          sql: "#/components/schemas/SQLRepresentation"
+
+    View:
+      type: object
+      required:
+        - name
+        - audit
+        - columns
+        - representations
+      properties:
+        name:
+          type: string
+          description: The name of the view
+        comment:
+          type: string
+          description: The comment of the view
+          nullable: true
+        columns:
+          type: array
+          description: The output columns of the view
+          items:
+            $ref: "./tables.yaml#/components/schemas/Column"
+        representations:
+          type: array
+          description: The representations of the view, keyed by dialect
+          items:
+            $ref: "#/components/schemas/Representation"
+        defaultCatalog:
+          type: string
+          description: The default catalog used to resolve unqualified 
identifiers in the view representations
+          nullable: true
+        defaultSchema:
+          type: string
+          description: The default schema used to resolve unqualified 
identifiers in the view representations
+          nullable: true
+        properties:
+          type: object
+          description: The properties of the view
+          nullable: true
+          default: {}
+          additionalProperties:
+            type: string
+        audit:
+          $ref: "./openapi.yaml#/components/schemas/Audit"
+
+    ViewCreateRequest:
+      type: object
+      required:
+        - name
+        - representations
+      properties:
+        name:
+          type: string
+          description: The name of the view
+        comment:
+          type: string
+          description: The comment of the view
+          nullable: true
+        columns:
+          type: array
+          description: The output columns of the view
+          items:
+            $ref: "./tables.yaml#/components/schemas/Column"
+        representations:
+          type: array
+          description: The representations of the view, at least one is 
required
+          minItems: 1
+          items:
+            $ref: "#/components/schemas/Representation"
+        defaultCatalog:
+          type: string
+          description: The default catalog used to resolve unqualified 
identifiers in the view representations
+          nullable: true
+        defaultSchema:
+          type: string
+          description: The default schema used to resolve unqualified 
identifiers in the view representations
+          nullable: true
+        properties:
+          type: object
+          description: The properties of the view
+          nullable: true
+          default: {}
+          additionalProperties:
+            type: string
+
+    ViewUpdatesRequest:
+      type: object
+      required:
+        - updates
+      properties:
+        updates:
+          type: array
+          minItems: 1
+          items:
+            $ref: "#/components/schemas/ViewUpdateRequest"
+
+    ViewUpdateRequest:
+      oneOf:
+        - $ref: "#/components/schemas/RenameViewRequest"
+        - $ref: "#/components/schemas/SetViewPropertyRequest"
+        - $ref: "#/components/schemas/RemoveViewPropertyRequest"
+        - $ref: "#/components/schemas/ReplaceViewRequest"
+      discriminator:
+        propertyName: "@type"
+        mapping:
+          rename: "#/components/schemas/RenameViewRequest"
+          setProperty: "#/components/schemas/SetViewPropertyRequest"
+          removeProperty: "#/components/schemas/RemoveViewPropertyRequest"
+          replaceView: "#/components/schemas/ReplaceViewRequest"
+
+    RenameViewRequest:
+      type: object
+      required:
+        - "@type"
+        - newName
+      properties:
+        "@type":
+          type: string
+          enum:
+            - "rename"
+        newName:
+          type: string
+          description: The new name of the view
+      example: {
+        "@type": "rename",
+        "newName": "view2"
+      }
+
+    SetViewPropertyRequest:
+      type: object
+      required:
+        - "@type"
+        - property
+        - value
+      properties:
+        "@type":
+          type: string
+          enum:
+            - "setProperty"
+        property:
+          type: string
+          description: The name of the property to set
+        value:
+          type: string
+          description: The value of the property to set
+      example: {
+        "@type": "setProperty",
+        "property": "key",
+        "value": "value"
+      }
+
+    RemoveViewPropertyRequest:
+      type: object
+      required:
+        - "@type"
+        - property
+      properties:
+        "@type":
+          type: string
+          enum:
+            - "removeProperty"
+        property:
+          type: string
+          description: The name of the property to remove
+      example: {
+        "@type": "removeProperty",
+        "property": "key"
+      }
+
+    ReplaceViewRequest:
+      type: object
+      description: |
+        Atomically replaces the body of a view. Columns, representations, 
default catalog, default
+        schema and comment are all replaced. The view name and properties are 
not affected.
+      required:
+        - "@type"
+        - representations
+      properties:
+        "@type":
+          type: string
+          enum:
+            - "replaceView"
+        columns:
+          type: array
+          description: The new output columns of the view
+          items:
+            $ref: "./tables.yaml#/components/schemas/Column"
+        representations:
+          type: array
+          description: The new representations of the view, at least one is 
required
+          minItems: 1
+          items:
+            $ref: "#/components/schemas/Representation"
+        defaultCatalog:
+          type: string
+          description: The new default catalog, or null to unset it
+          nullable: true
+        defaultSchema:
+          type: string
+          description: The new default schema, or null to unset it
+          nullable: true
+        comment:
+          type: string
+          description: The new comment, or null to unset it
+          nullable: true
+      example: {
+        "@type": "replaceView",
+        "columns": [],
+        "representations": [
+          {
+            "type": "sql",
+            "dialect": "trino",
+            "sql": "SELECT 2"
+          }
+        ],
+        "defaultCatalog": "cat1",
+        "defaultSchema": "sch1",
+        "comment": "new comment"
+      }
+
+  responses:
+    ViewResponse:
+      description: Returns include the view object
+      content:
+        application/vnd.gravitino.v1+json:
+          schema:
+            type: object
+            properties:
+              code:
+                type: integer
+                format: int32
+                description: Status code of the response
+                enum:
+                  - 0
+              view:
+                $ref: "#/components/schemas/View"
+          examples:
+            ViewResponse:
+              $ref: "#/components/examples/ViewResponse"
+
+  examples:
+    ViewCreateRequest:
+      value: {
+        "name": "view1",
+        "comment": "This is a view",
+        "columns": [
+          {
+            "name": "id",
+            "type": "long",
+            "comment": "id column",
+            "nullable": true
+          }
+        ],
+        "representations": [
+          {
+            "type": "sql",
+            "dialect": "trino",
+            "sql": "SELECT id FROM t"
+          }
+        ],
+        "properties": {
+          "key": "value"
+        }
+      }
+
+    ViewResponse:
+      value: {
+        "code": 0,
+        "view": {
+          "name": "view1",
+          "comment": "This is a view",
+          "audit": {
+            "creator": "gravitino",
+            "createTime": "2024-01-01T00:00:00Z",
+            "lastModifier": "gravitino",
+            "lastModifiedTime": "2024-01-01T00:00:00Z"
+          },
+          "columns": [
+            {
+              "name": "id",
+              "type": "long",
+              "comment": "id column",
+              "nullable": true
+            }
+          ],
+          "representations": [
+            {
+              "type": "sql",
+              "dialect": "trino",
+              "sql": "SELECT id FROM t"
+            }
+          ],
+          "properties": {
+            "key": "value"
+          }
+        }
+      }
+
+    ViewAlreadyExistsException:
+      value: {
+        "code": 1004,
+        "type": "ViewAlreadyExistsException",
+        "message": "Failed to operate view(s) [view1] operation [CREATE] under 
schema [test_schema], reason [ViewAlreadyExistsException]",
+        "stack": [
+          "org.apache.gravitino.exceptions.ViewAlreadyExistsException: View 
already exists: view1",
+          "..."
+        ]
+      }
+
+    NoSuchViewException:
+      value: {
+        "code": 1003,
+        "type": "NoSuchViewException",
+        "message": "Failed to operate view(s) [test_view] operation [LOAD] 
under schema [test_schema], reason [NoSuchViewException]",
+        "stack": [
+          "org.apache.gravitino.exceptions.NoSuchViewException: View test_view 
does not exist",
+          "..."
+        ]
+      }


Reply via email to