diff --git a/docs/static/rest-catalog-open-api.yaml b/docs/static/rest-catalog-open-api.yaml
index e310f398f821..76f69f74cff3 100644
--- a/docs/static/rest-catalog-open-api.yaml
+++ b/docs/static/rest-catalog-open-api.yaml
@@ -740,6 +740,69 @@ paths:
$ref: '#/components/responses/TableNotExistErrorResponse'
"500":
$ref: '#/components/responses/ServerErrorResponse'
+ /v1/{prefix}/databases/{database}/tables/{table}/via/{viaDatabase}/{viaObject}:
+ post:
+ tags:
+ - table
+ summary: Get table via view (view penetration)
+ description: >
+ Get the table metadata accessed via a view. If the caller has permission
+ on the view, they can access the underlying table referenced by the view.
+ This API can only be called by trusted engines. The server must
+ authenticate whether the caller is a trusted engine.
+ operationId: getTableVia
+ parameters:
+ - name: prefix
+ in: path
+ required: true
+ schema:
+ type: string
+ - name: database
+ in: path
+ required: true
+ schema:
+ type: string
+ - name: table
+ in: path
+ required: true
+ schema:
+ type: string
+ - name: viaDatabase
+ in: path
+ required: true
+ schema:
+ type: string
+ description: Database name of the view through which access is granted
+ - name: viaObject
+ in: path
+ required: true
+ schema:
+ type: string
+ description: Name of the view through which access is granted
+ responses:
+ "200":
+ description: Table metadata accessed via the view
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/GetTableResponse'
+ "401":
+ $ref: '#/components/responses/UnauthorizedErrorResponse'
+ "403":
+ $ref: '#/components/responses/ForbiddenErrorResponse'
+ 404:
+ description:
+ Not Found
+ - TableNotExistException, table does not exist
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorResponse'
+ examples:
+ TableNotExist:
+ $ref: '#/components/examples/TableNotExistError'
+ "500":
+ $ref: '#/components/responses/ServerErrorResponse'
/v1/{prefix}/databases/{database}/tables/{table}/auth:
post:
tags:
diff --git a/paimon-api/src/main/java/org/apache/paimon/rest/RESTApi.java b/paimon-api/src/main/java/org/apache/paimon/rest/RESTApi.java
index 263c4e2c0640..26be5bcb409b 100644
--- a/paimon-api/src/main/java/org/apache/paimon/rest/RESTApi.java
+++ b/paimon-api/src/main/java/org/apache/paimon/rest/RESTApi.java
@@ -519,6 +519,32 @@ public GetTableResponse getTableById(String tableId) {
return client.get(resourcePaths.table(tableId), GetTableResponse.class, restAuthFunction);
}
+ /**
+ * Get table via a view (view penetration). If the caller has permission on the view identified
+ * by {@code via}, they can access the underlying table referenced by the view.
+ *
+ *
This API can only be called by trusted engines. The server must authenticate whether the
+ * caller is a trusted engine.
+ *
+ * @param table database name and table name of the target table.
+ * @param via database name and object name of the view through which access is granted.
+ * @return {@link GetTableResponse}
+ * @throws NoSuchResourceException Exception thrown on HTTP 404 means the table or view not
+ * exists
+ * @throws ForbiddenException Exception thrown on HTTP 403 means don't have the permission
+ */
+ public GetTableResponse getTableVia(Identifier table, Identifier via) {
+ return client.post(
+ resourcePaths.tableVia(
+ table.getDatabaseName(),
+ table.getObjectName(),
+ via.getDatabaseName(),
+ via.getObjectName()),
+ new ForwardBranchRequest(),
+ GetTableResponse.class,
+ restAuthFunction);
+ }
+
/**
* Load latest snapshot for table.
*
diff --git a/paimon-api/src/main/java/org/apache/paimon/rest/ResourcePaths.java b/paimon-api/src/main/java/org/apache/paimon/rest/ResourcePaths.java
index 28f79d040995..6bb62d5c73c4 100644
--- a/paimon-api/src/main/java/org/apache/paimon/rest/ResourcePaths.java
+++ b/paimon-api/src/main/java/org/apache/paimon/rest/ResourcePaths.java
@@ -43,6 +43,7 @@ public class ResourcePaths {
protected static final String FUNCTIONS = "functions";
protected static final String FUNCTION_DETAILS = "function-details";
protected static final String ID = "id";
+ protected static final String VIA = "via";
private static final Joiner SLASH = Joiner.on("/").skipNulls();
@@ -94,6 +95,19 @@ public String table(String databaseName, String objectName) {
encodeString(objectName));
}
+ public String tableVia(String databaseName, String objectName, String viaDb, String viaObject) {
+ return SLASH.join(
+ V1,
+ prefix,
+ DATABASES,
+ encodeString(databaseName),
+ TABLES,
+ encodeString(objectName),
+ VIA,
+ encodeString(viaDb),
+ encodeString(viaObject));
+ }
+
public String renameTable() {
return SLASH.join(V1, prefix, TABLES, "rename");
}
diff --git a/paimon-core/src/main/java/org/apache/paimon/catalog/Catalog.java b/paimon-core/src/main/java/org/apache/paimon/catalog/Catalog.java
index 57fa040a2acd..184ca596f031 100644
--- a/paimon-core/src/main/java/org/apache/paimon/catalog/Catalog.java
+++ b/paimon-core/src/main/java/org/apache/paimon/catalog/Catalog.java
@@ -157,6 +157,22 @@ void alterDatabase(String name, List changes, boolean ignoreIfNo
*/
Table getTable(Identifier identifier) throws TableNotExistException;
+ /**
+ * Return a {@link Table} identified by the given {@link Identifier}, accessed via a view (view
+ * penetration). If the caller has permission on the view, they can access the underlying table.
+ *
+ * This API can only be called by trusted engines. The server must authenticate whether the
+ * caller is a trusted engine.
+ *
+ * @param table Path of the target table
+ * @param via Path of the view through which access is granted
+ * @return The requested table
+ * @throws TableNotExistException if the target does not exist
+ */
+ default Table getTableVia(Identifier table, Identifier via) throws TableNotExistException {
+ return getTable(table);
+ }
+
/**
* Return a {@link Table} identified by the given tableId.
*
diff --git a/paimon-core/src/main/java/org/apache/paimon/catalog/DelegateCatalog.java b/paimon-core/src/main/java/org/apache/paimon/catalog/DelegateCatalog.java
index 0f18f7d04540..1fb5c9b1f27d 100644
--- a/paimon-core/src/main/java/org/apache/paimon/catalog/DelegateCatalog.java
+++ b/paimon-core/src/main/java/org/apache/paimon/catalog/DelegateCatalog.java
@@ -375,6 +375,11 @@ public Table getTable(Identifier identifier) throws TableNotExistException {
return wrapped.getTable(identifier);
}
+ @Override
+ public Table getTableVia(Identifier table, Identifier via) throws TableNotExistException {
+ return wrapped.getTableVia(table, via);
+ }
+
@Override
public View getView(Identifier identifier) throws ViewNotExistException {
return wrapped.getView(identifier);
diff --git a/paimon-core/src/main/java/org/apache/paimon/privilege/PrivilegedCatalog.java b/paimon-core/src/main/java/org/apache/paimon/privilege/PrivilegedCatalog.java
index b408055e5112..97fe1e8f4617 100644
--- a/paimon-core/src/main/java/org/apache/paimon/privilege/PrivilegedCatalog.java
+++ b/paimon-core/src/main/java/org/apache/paimon/privilege/PrivilegedCatalog.java
@@ -158,6 +158,17 @@ public Table getTable(Identifier identifier) throws TableNotExistException {
}
}
+ @Override
+ public Table getTableVia(Identifier table, Identifier via) throws TableNotExistException {
+ Table result = wrapped.getTableVia(table, via);
+ if (result instanceof FileStoreTable) {
+ return PrivilegedFileStoreTable.wrap(
+ (FileStoreTable) result, privilegeManager.getPrivilegeChecker(), table);
+ } else {
+ return result;
+ }
+ }
+
@Override
public void markDonePartitions(Identifier identifier, List