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",
+ "..."
+ ]
+ }