This is an automated email from the ASF dual-hosted git repository.
lzljs3620320 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git
The following commit(s) were added to refs/heads/master by this push:
new 1f7477b381 [rest] Support getTable by id (#7085)
1f7477b381 is described below
commit 1f7477b381b83525327cebd4cd07327e33784f98
Author: kevin <[email protected]>
AuthorDate: Thu Jan 22 09:19:55 2026 +0800
[rest] Support getTable by id (#7085)
---
docs/static/rest-catalog-open-api.yaml | 32 +++++++++++++++
.../main/java/org/apache/paimon/rest/RESTApi.java | 13 ++++++
.../java/org/apache/paimon/rest/ResourcePaths.java | 5 +++
.../paimon/rest/responses/GetTableResponse.java | 11 +++++
.../org/apache/paimon/catalog/AbstractCatalog.java | 5 +++
.../java/org/apache/paimon/catalog/Catalog.java | 48 ++++++++++++++++++++++
.../org/apache/paimon/catalog/DelegateCatalog.java | 5 +++
.../java/org/apache/paimon/rest/RESTCatalog.java | 13 ++++++
.../org/apache/paimon/rest/MockRESTMessage.java | 1 +
.../org/apache/paimon/rest/RESTCatalogServer.java | 29 ++++++++++++-
.../org/apache/paimon/rest/RESTCatalogTest.java | 19 +++++++++
11 files changed, 180 insertions(+), 1 deletion(-)
diff --git a/docs/static/rest-catalog-open-api.yaml
b/docs/static/rest-catalog-open-api.yaml
index 22aa310ed3..5c654e4a2f 100644
--- a/docs/static/rest-catalog-open-api.yaml
+++ b/docs/static/rest-catalog-open-api.yaml
@@ -412,6 +412,36 @@ paths:
$ref: '#/components/responses/UnauthorizedErrorResponse'
"500":
$ref: '#/components/responses/ServerErrorResponse'
+ /v1/{prefix}/tables/id/{tableId}:
+ get:
+ tags:
+ - table
+ summary: Get table by id
+ operationId: getTableById
+ parameters:
+ - name: prefix
+ in: path
+ required: true
+ schema:
+ type: string
+ - name: tableId
+ in: path
+ required: true
+ schema:
+ type: string
+ responses:
+ "200":
+ description: OK
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/GetTableResponse'
+ "401":
+ $ref: '#/components/responses/UnauthorizedErrorResponse'
+ "404":
+ $ref: '#/components/responses/TableNotExistErrorResponse'
+ "500":
+ $ref: '#/components/responses/ServerErrorResponse'
/v1/{prefix}/databases/{database}/tables/{table}:
get:
tags:
@@ -2548,6 +2578,8 @@ components:
properties:
id:
type: string
+ database:
+ type: string
name:
type: string
path:
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 99266d1dba..e135ef17a2 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
@@ -477,6 +477,19 @@ public class RESTApi {
restAuthFunction);
}
+ /**
+ * Get table by tableId.
+ *
+ * @param tableId id of the table.
+ * @return {@link GetTableResponse}
+ * @throws NoSuchResourceException Exception thrown on HTTP 404 means the
table not exists
+ * @throws ForbiddenException Exception thrown on HTTP 403 means don't
have the permission for
+ * this table
+ */
+ public GetTableResponse getTableById(String tableId) {
+ return client.get(resourcePaths.table(tableId),
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 8d288dc205..fdd183a3fc 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
@@ -41,6 +41,7 @@ public class ResourcePaths {
protected static final String REGISTER = "register";
protected static final String FUNCTIONS = "functions";
protected static final String FUNCTION_DETAILS = "function-details";
+ protected static final String ID = "id";
private static final Joiner SLASH = Joiner.on("/").skipNulls();
@@ -78,6 +79,10 @@ public class ResourcePaths {
return SLASH.join(V1, prefix, TABLES);
}
+ public String table(String tableId) {
+ return SLASH.join(V1, prefix, TABLES, ID, encodeString(tableId));
+ }
+
public String table(String databaseName, String objectName) {
return SLASH.join(
V1,
diff --git
a/paimon-api/src/main/java/org/apache/paimon/rest/responses/GetTableResponse.java
b/paimon-api/src/main/java/org/apache/paimon/rest/responses/GetTableResponse.java
index 470da0503a..39a6dcfe9b 100644
---
a/paimon-api/src/main/java/org/apache/paimon/rest/responses/GetTableResponse.java
+++
b/paimon-api/src/main/java/org/apache/paimon/rest/responses/GetTableResponse.java
@@ -31,6 +31,7 @@ import
org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonPro
public class GetTableResponse extends AuditRESTResponse implements
RESTResponse {
private static final String FIELD_ID = "id";
+ private static final String FIELD_DATABASE = "database";
private static final String FIELD_NAME = "name";
private static final String FIELD_PATH = "path";
private static final String FIELD_IS_EXTERNAL = "isExternal";
@@ -40,6 +41,9 @@ public class GetTableResponse extends AuditRESTResponse
implements RESTResponse
@JsonProperty(FIELD_ID)
private final String id;
+ @JsonProperty(FIELD_DATABASE)
+ private final String database;
+
@JsonProperty(FIELD_NAME)
private final String name;
@@ -58,6 +62,7 @@ public class GetTableResponse extends AuditRESTResponse
implements RESTResponse
@JsonCreator
public GetTableResponse(
@JsonProperty(FIELD_ID) String id,
+ @JsonProperty(FIELD_DATABASE) String database,
@JsonProperty(FIELD_NAME) String name,
@JsonProperty(FIELD_PATH) String path,
@JsonProperty(FIELD_IS_EXTERNAL) boolean isExternal,
@@ -70,6 +75,7 @@ public class GetTableResponse extends AuditRESTResponse
implements RESTResponse
@JsonProperty(FIELD_UPDATED_BY) String updatedBy) {
super(owner, createdAt, createdBy, updatedAt, updatedBy);
this.id = id;
+ this.database = database;
this.name = name;
this.path = path;
this.isExternal = isExternal;
@@ -82,6 +88,11 @@ public class GetTableResponse extends AuditRESTResponse
implements RESTResponse
return this.id;
}
+ @JsonGetter(FIELD_DATABASE)
+ public String getDatabase() {
+ return this.database;
+ }
+
@JsonGetter(FIELD_NAME)
public String getName() {
return this.name;
diff --git
a/paimon-core/src/main/java/org/apache/paimon/catalog/AbstractCatalog.java
b/paimon-core/src/main/java/org/apache/paimon/catalog/AbstractCatalog.java
index 5d6c102208..bfb608dcdd 100644
--- a/paimon-core/src/main/java/org/apache/paimon/catalog/AbstractCatalog.java
+++ b/paimon-core/src/main/java/org/apache/paimon/catalog/AbstractCatalog.java
@@ -481,6 +481,11 @@ public abstract class AbstractCatalog implements Catalog {
false);
}
+ @Override
+ public Table getTableById(String tableId) throws TableIdNotExistException {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public void createBranch(Identifier identifier, String branch, @Nullable
String fromTag)
throws TableNotExistException, BranchAlreadyExistException,
TagNotExistException {
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 175a74cf75..71467255c4 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
@@ -155,6 +155,15 @@ public interface Catalog extends AutoCloseable {
*/
Table getTable(Identifier identifier) throws TableNotExistException;
+ /**
+ * Return a {@link Table} identified by the given tableId.
+ *
+ * @param tableId Id of the table
+ * @return The requested table
+ * @throws TableIdNotExistException if the target does not exist
+ */
+ Table getTableById(String tableId) throws TableIdNotExistException;
+
/**
* Get names of all tables under this database. An empty list is returned
if none exists.
*
@@ -1215,6 +1224,23 @@ public interface Catalog extends AutoCloseable {
}
}
+ /** Exception for trying to operate on a table by ID that doesn't exist. */
+ class TableIdNotExistException extends Exception {
+
+ private static final String MSG = "Table id %s does not exist.";
+
+ private final String tableId;
+
+ public TableIdNotExistException(String tableId, Throwable cause) {
+ super(String.format(MSG, tableId), cause);
+ this.tableId = tableId;
+ }
+
+ public String getTableId() {
+ return tableId;
+ }
+ }
+
/** Exception for trying to operate on the table that doesn't have
permission. */
class TableNoPermissionException extends RuntimeException {
@@ -1242,6 +1268,28 @@ public interface Catalog extends AutoCloseable {
}
}
+ /** Exception for trying to operate on a table by ID that doesn't have
permission. */
+ class TableIdNoPermissionException extends RuntimeException {
+
+ private static final String MSG = "Table id %s has no permission.
Cause by %s.";
+
+ private final String tableId;
+
+ public TableIdNoPermissionException(String tableId, Throwable cause) {
+ super(
+ String.format(
+ MSG,
+ tableId,
+ cause != null && cause.getMessage() != null ?
cause.getMessage() : ""),
+ cause);
+ this.tableId = tableId;
+ }
+
+ public String getTableId() {
+ return tableId;
+ }
+ }
+
/** Exception for trying to alter a column that already exists. */
class ColumnAlreadyExistException extends Exception {
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 b92cd63d94..4f3405187f 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
@@ -148,6 +148,11 @@ public abstract class DelegateCatalog implements Catalog {
wrapped.createTable(identifier, schema, ignoreIfExists);
}
+ @Override
+ public Table getTableById(String tableId) throws TableIdNotExistException {
+ return wrapped.getTableById(tableId);
+ }
+
@Override
public void renameTable(Identifier fromTable, Identifier toTable, boolean
ignoreIfNotExists)
throws TableNotExistException, TableAlreadyExistException {
diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalog.java
b/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalog.java
index 16d2f599bd..c7c1a1e6d3 100644
--- a/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalog.java
+++ b/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalog.java
@@ -279,6 +279,19 @@ public class RESTCatalog implements Catalog {
return new PagedList<>(tables.getElements(),
tables.getNextPageToken());
}
+ @Override
+ public Table getTableById(String tableId) throws TableIdNotExistException {
+ try {
+ GetTableResponse response = api.getTableById(tableId);
+ String database = response.getDatabase();
+ return toTable(database, response);
+ } catch (NoSuchResourceException e) {
+ throw new TableIdNotExistException(tableId, e);
+ } catch (ForbiddenException e) {
+ throw new TableIdNoPermissionException(tableId, e);
+ }
+ }
+
@Override
public Table getTable(Identifier identifier) throws TableNotExistException
{
return CatalogUtils.loadTable(
diff --git
a/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTMessage.java
b/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTMessage.java
index f75e43dec2..4d4f101bd6 100644
--- a/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTMessage.java
+++ b/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTMessage.java
@@ -234,6 +234,7 @@ public class MockRESTMessage {
options.put("option-2", "value-2");
return new GetTableResponse(
UUID.randomUUID().toString(),
+ "default",
"",
"/tmp/",
false,
diff --git
a/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogServer.java
b/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogServer.java
index 6420bc956d..98d3974e16 100644
--- a/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogServer.java
+++ b/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogServer.java
@@ -324,10 +324,14 @@ public class RESTCatalogServer {
} else if (databaseUri.equals(request.getPath())
|| request.getPath().contains(databaseUri + "?")) {
return databasesApiHandler(restAuthParameter.method(),
data, parameters);
- } else if
(resourcePaths.renameTable().equals(request.getPath())) {
+ } else if ("POST".equals(request.getMethod())
+ &&
resourcePaths.renameTable().equals(request.getPath())) {
return renameTableHandle(restAuthParameter.data());
} else if
(resourcePaths.renameView().equals(request.getPath())) {
return renameViewHandle(restAuthParameter.data());
+ } else if ("GET".equals(request.getMethod())
+ && isTableByIdRequest(request.getPath())) {
+ return tableByIdHandle(request.getPath());
} else if (StringUtils.startsWith(request.getPath(),
resourcePaths.tables())) {
return tablesHandle(parameters);
} else if (StringUtils.startsWith(request.getPath(),
resourcePaths.views())) {
@@ -1502,6 +1506,7 @@ public class RESTCatalogServer {
GetTableResponse getTableResponse =
new GetTableResponse(
entry.getValue().uuid(),
+ identifier.getDatabaseName(),
identifier.getTableName(),
entry.getValue().schema().options().get(PATH.key()),
entry.getValue().isExternal(),
@@ -1564,6 +1569,27 @@ public class RESTCatalogServer {
return Options.fromMap(schema.options()).get(TYPE) == OBJECT_TABLE;
}
+ private boolean isTableByIdRequest(String requestPath) {
+ String tableByIdPath =
StringUtils.substringBeforeLast(resourcePaths.table("mock_id"), "/");
+ String tableByIdPathPrefix = tableByIdPath + "/";
+ return requestPath.startsWith(tableByIdPathPrefix);
+ }
+
+ private MockResponse tableByIdHandle(String requestPath) throws Exception {
+ String tableId = StringUtils.substringAfterLast(requestPath, "/");
+ for (Map.Entry<String, TableMetadata> entry :
tableMetadataStore.entrySet()) {
+ TableMetadata tableMetadata = entry.getValue();
+ if (tableId.equals(tableMetadata.uuid())) {
+ Identifier identifier = Identifier.fromString(entry.getKey());
+ return tableHandle("GET", "", identifier);
+ }
+ }
+ return mockResponse(
+ new ErrorResponse(
+ ErrorResponse.RESOURCE_TYPE_TABLE, tableId, "Table not
exist.", 404),
+ 404);
+ }
+
private MockResponse tableHandle(String method, String data, Identifier
identifier)
throws Exception {
RESTResponse response;
@@ -1586,6 +1612,7 @@ public class RESTCatalogServer {
response =
new GetTableResponse(
tableMetadata.uuid(),
+ identifier.getDatabaseName(),
identifier.getObjectName(),
path,
tableMetadata.isExternal(),
diff --git
a/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTest.java
b/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTest.java
index a1b00b502a..fe34113b22 100644
--- a/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTest.java
+++ b/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTest.java
@@ -113,6 +113,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
@@ -352,6 +353,24 @@ public abstract class RESTCatalogTest extends
CatalogTestBase {
new ArrayList<PartitionStatistics>()));
}
+ @Test
+ void testGetTableById() throws Exception {
+ Identifier identifier = Identifier.create("test_table_db",
"get_table_by_id");
+ createTable(identifier, Maps.newHashMap(), Lists.newArrayList("col1"));
+ Table table = restCatalog.getTable(identifier);
+ Table tableById = restCatalog.getTableById(table.uuid());
+ assertThat(tableById.uuid()).isEqualTo(table.uuid());
+ assertThat(tableById.name()).isEqualTo(identifier.getObjectName());
+ FileStoreTable fileStoreTable = (FileStoreTable) tableById;
+ assertThat(
+
Objects.requireNonNull(fileStoreTable.catalogEnvironment().identifier())
+ .getDatabaseName())
+ .isEqualTo("test_table_db");
+ assertThrows(
+ Catalog.TableIdNotExistException.class,
+ () -> restCatalog.getTableById("missing_table_id"));
+ }
+
@Test
void renameWhenTargetTableExist() throws Exception {
Identifier identifier = Identifier.create("test_table_db",
"rename_table");