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");

Reply via email to