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 7e99c594b2 [rest] Add TableSnapshot including basic statistics to 
SupportsSnapshots (#5249)
7e99c594b2 is described below

commit 7e99c594b26df1ca1fdc566456a48af27f9cae1b
Author: Jingsong Lee <[email protected]>
AuthorDate: Mon Mar 10 20:39:16 2025 +0800

    [rest] Add TableSnapshot including basic statistics to SupportsSnapshots 
(#5249)
---
 .../apache/paimon/catalog/SupportsSnapshots.java   |   4 +-
 .../java/org/apache/paimon/rest/RESTCatalog.java   |   4 +-
 .../rest/responses/GetTableSnapshotResponse.java   |   8 +-
 .../org/apache/paimon/table/TableSnapshot.java     | 136 +++++++++++++++++++++
 .../org/apache/paimon/tag/SnapshotLoaderImpl.java  |   5 +-
 .../apache/paimon/rest/MockRESTCatalogTest.java    |  16 ++-
 .../org/apache/paimon/rest/RESTCatalogServer.java  |  53 +++++++-
 .../apache/paimon/rest/RESTCatalogTestBase.java    |  21 +++-
 paimon-open-api/rest-catalog-open-api.yaml         |  19 ++-
 9 files changed, 245 insertions(+), 21 deletions(-)

diff --git 
a/paimon-core/src/main/java/org/apache/paimon/catalog/SupportsSnapshots.java 
b/paimon-core/src/main/java/org/apache/paimon/catalog/SupportsSnapshots.java
index 1e292b9d31..733146c68c 100644
--- a/paimon-core/src/main/java/org/apache/paimon/catalog/SupportsSnapshots.java
+++ b/paimon-core/src/main/java/org/apache/paimon/catalog/SupportsSnapshots.java
@@ -20,6 +20,7 @@ package org.apache.paimon.catalog;
 
 import org.apache.paimon.Snapshot;
 import org.apache.paimon.partition.Partition;
+import org.apache.paimon.table.TableSnapshot;
 
 import java.util.List;
 import java.util.Optional;
@@ -46,5 +47,6 @@ public interface SupportsSnapshots extends Catalog {
      * @return The requested snapshot of the table
      * @throws Catalog.TableNotExistException if the target does not exist
      */
-    Optional<Snapshot> loadSnapshot(Identifier identifier) throws 
Catalog.TableNotExistException;
+    Optional<TableSnapshot> loadSnapshot(Identifier identifier)
+            throws Catalog.TableNotExistException;
 }
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 d28a6cd670..d7a4d281c1 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
@@ -78,6 +78,7 @@ import org.apache.paimon.schema.Schema;
 import org.apache.paimon.schema.SchemaChange;
 import org.apache.paimon.schema.TableSchema;
 import org.apache.paimon.table.Table;
+import org.apache.paimon.table.TableSnapshot;
 import org.apache.paimon.table.sink.BatchTableCommit;
 import org.apache.paimon.utils.Pair;
 import org.apache.paimon.view.View;
@@ -349,7 +350,8 @@ public class RESTCatalog implements Catalog, 
SupportsSnapshots, SupportsBranches
     }
 
     @Override
-    public Optional<Snapshot> loadSnapshot(Identifier identifier) throws 
TableNotExistException {
+    public Optional<TableSnapshot> loadSnapshot(Identifier identifier)
+            throws TableNotExistException {
         GetTableSnapshotResponse response;
         try {
             response =
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/rest/responses/GetTableSnapshotResponse.java
 
b/paimon-core/src/main/java/org/apache/paimon/rest/responses/GetTableSnapshotResponse.java
index c1d25d0b40..47678c10d0 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/rest/responses/GetTableSnapshotResponse.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/rest/responses/GetTableSnapshotResponse.java
@@ -18,8 +18,8 @@
 
 package org.apache.paimon.rest.responses;
 
-import org.apache.paimon.Snapshot;
 import org.apache.paimon.rest.RESTResponse;
+import org.apache.paimon.table.TableSnapshot;
 
 import 
org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonCreator;
 import 
org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonGetter;
@@ -33,15 +33,15 @@ public class GetTableSnapshotResponse implements 
RESTResponse {
     private static final String FIELD_SNAPSHOT = "snapshot";
 
     @JsonProperty(FIELD_SNAPSHOT)
-    private final Snapshot snapshot;
+    private final TableSnapshot snapshot;
 
     @JsonCreator
-    public GetTableSnapshotResponse(@JsonProperty(FIELD_SNAPSHOT) Snapshot 
snapshot) {
+    public GetTableSnapshotResponse(@JsonProperty(FIELD_SNAPSHOT) 
TableSnapshot snapshot) {
         this.snapshot = snapshot;
     }
 
     @JsonGetter(FIELD_SNAPSHOT)
-    public Snapshot getSnapshot() {
+    public TableSnapshot getSnapshot() {
         return snapshot;
     }
 }
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/table/TableSnapshot.java 
b/paimon-core/src/main/java/org/apache/paimon/table/TableSnapshot.java
new file mode 100644
index 0000000000..b275ddd80b
--- /dev/null
+++ b/paimon-core/src/main/java/org/apache/paimon/table/TableSnapshot.java
@@ -0,0 +1,136 @@
+/*
+ * 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.
+ */
+
+package org.apache.paimon.table;
+
+import org.apache.paimon.Snapshot;
+import org.apache.paimon.annotation.Public;
+
+import 
org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonCreator;
+import 
org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonGetter;
+import 
org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import 
org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+/** Snapshot of a table, including basic statistics of this table. */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@Public
+public class TableSnapshot implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    public static final String FIELD_SNAPSHOT = "snapshot";
+    public static final String FIELD_RECORD_COUNT = "recordCount";
+    public static final String FIELD_FILE_SIZE_IN_BYTES = "fileSizeInBytes";
+    public static final String FIELD_FILE_COUNT = "fileCount";
+    public static final String FIELD_LAST_FILE_CREATION_TIME = 
"lastFileCreationTime";
+
+    @JsonProperty(FIELD_SNAPSHOT)
+    private final Snapshot snapshot;
+
+    @JsonProperty(FIELD_RECORD_COUNT)
+    private final long recordCount;
+
+    @JsonProperty(FIELD_FILE_SIZE_IN_BYTES)
+    private final long fileSizeInBytes;
+
+    @JsonProperty(FIELD_FILE_COUNT)
+    private final long fileCount;
+
+    @JsonProperty(FIELD_LAST_FILE_CREATION_TIME)
+    private final long lastFileCreationTime;
+
+    @JsonCreator
+    public TableSnapshot(
+            @JsonProperty(FIELD_SNAPSHOT) Snapshot snapshot,
+            @JsonProperty(FIELD_RECORD_COUNT) long recordCount,
+            @JsonProperty(FIELD_FILE_SIZE_IN_BYTES) long fileSizeInBytes,
+            @JsonProperty(FIELD_FILE_COUNT) long fileCount,
+            @JsonProperty(FIELD_LAST_FILE_CREATION_TIME) long 
lastFileCreationTime) {
+        this.snapshot = snapshot;
+        this.recordCount = recordCount;
+        this.fileSizeInBytes = fileSizeInBytes;
+        this.fileCount = fileCount;
+        this.lastFileCreationTime = lastFileCreationTime;
+    }
+
+    @JsonGetter(FIELD_SNAPSHOT)
+    public Snapshot snapshot() {
+        return snapshot;
+    }
+
+    @JsonGetter(FIELD_RECORD_COUNT)
+    public long recordCount() {
+        return recordCount;
+    }
+
+    @JsonGetter(FIELD_FILE_SIZE_IN_BYTES)
+    public long fileSizeInBytes() {
+        return fileSizeInBytes;
+    }
+
+    @JsonGetter(FIELD_FILE_COUNT)
+    public long fileCount() {
+        return fileCount;
+    }
+
+    @JsonGetter(FIELD_LAST_FILE_CREATION_TIME)
+    public long lastFileCreationTime() {
+        return lastFileCreationTime;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        TableSnapshot that = (TableSnapshot) o;
+        return recordCount == that.recordCount
+                && fileSizeInBytes == that.fileSizeInBytes
+                && fileCount == that.fileCount
+                && lastFileCreationTime == that.lastFileCreationTime
+                && Objects.equals(snapshot, that.snapshot);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(
+                snapshot, recordCount, fileSizeInBytes, fileCount, 
lastFileCreationTime);
+    }
+
+    @Override
+    public String toString() {
+        return "{"
+                + "snapshot="
+                + snapshot
+                + ", recordCount="
+                + recordCount
+                + ", fileSizeInBytes="
+                + fileSizeInBytes
+                + ", fileCount="
+                + fileCount
+                + ", lastFileCreationTime="
+                + lastFileCreationTime
+                + '}';
+    }
+}
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/tag/SnapshotLoaderImpl.java 
b/paimon-core/src/main/java/org/apache/paimon/tag/SnapshotLoaderImpl.java
index bd275ee29c..c9987af039 100644
--- a/paimon-core/src/main/java/org/apache/paimon/tag/SnapshotLoaderImpl.java
+++ b/paimon-core/src/main/java/org/apache/paimon/tag/SnapshotLoaderImpl.java
@@ -23,6 +23,7 @@ import org.apache.paimon.catalog.Catalog;
 import org.apache.paimon.catalog.CatalogLoader;
 import org.apache.paimon.catalog.Identifier;
 import org.apache.paimon.catalog.SupportsSnapshots;
+import org.apache.paimon.table.TableSnapshot;
 import org.apache.paimon.utils.SnapshotLoader;
 
 import java.io.IOException;
@@ -42,7 +43,9 @@ public class SnapshotLoaderImpl implements SnapshotLoader {
     @Override
     public Optional<Snapshot> load() throws IOException {
         try (Catalog catalog = catalogLoader.load()) {
-            return ((SupportsSnapshots) catalog).loadSnapshot(identifier);
+            return ((SupportsSnapshots) catalog)
+                    .loadSnapshot(identifier)
+                    .map(TableSnapshot::snapshot);
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTCatalogTest.java 
b/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTCatalogTest.java
index 1426c56f16..a58d3fcb83 100644
--- a/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTCatalogTest.java
+++ b/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTCatalogTest.java
@@ -211,7 +211,19 @@ class MockRESTCatalogTest extends RESTCatalogTestBase {
     }
 
     @Override
-    protected void updateSnapshotOnRestServer(Identifier identifier, Snapshot 
snapshot) {
-        restCatalogServer.setTableSnapshot(identifier, snapshot);
+    protected void updateSnapshotOnRestServer(
+            Identifier identifier,
+            Snapshot snapshot,
+            long recordCount,
+            long fileSizeInBytes,
+            long fileCount,
+            long lastFileCreationTime) {
+        restCatalogServer.setTableSnapshot(
+                identifier,
+                snapshot,
+                recordCount,
+                fileSizeInBytes,
+                fileCount,
+                lastFileCreationTime);
     }
 }
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 7ed259aad8..ed112e3843 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
@@ -77,6 +77,7 @@ import org.apache.paimon.schema.TableSchema;
 import org.apache.paimon.table.CatalogEnvironment;
 import org.apache.paimon.table.FileStoreTable;
 import org.apache.paimon.table.FileStoreTableFactory;
+import org.apache.paimon.table.TableSnapshot;
 import org.apache.paimon.utils.BranchManager;
 import org.apache.paimon.utils.Pair;
 import org.apache.paimon.view.View;
@@ -133,7 +134,7 @@ public class RESTCatalogServer {
     private final Map<String, TableMetadata> tableMetadataStore = new 
HashMap<>();
     private final Map<String, List<Partition>> tablePartitionsStore = new 
HashMap<>();
     private final Map<String, View> viewStore = new HashMap<>();
-    private final Map<String, Snapshot> tableSnapshotStore = new HashMap<>();
+    private final Map<String, TableSnapshot> tableSnapshotStore = new 
HashMap<>();
     private final List<String> noPermissionDatabases = new ArrayList<>();
     private final List<String> noPermissionTables = new ArrayList<>();
     public final ConfigResponse configResponse;
@@ -150,7 +151,7 @@ public class RESTCatalogServer {
         ResourcePaths resourcePaths = new ResourcePaths(prefix);
         this.databaseUri = resourcePaths.databases();
         Options conf = new Options();
-        this.configResponse.getDefaults().forEach((k, v) -> conf.setString(k, 
v));
+        this.configResponse.getDefaults().forEach(conf::setString);
         conf.setString(CatalogOptions.WAREHOUSE.key(), dataPath);
         CatalogContext context = CatalogContext.create(conf);
         Path warehousePath = new Path(dataPath);
@@ -180,8 +181,17 @@ public class RESTCatalogServer {
         server.shutdown();
     }
 
-    public void setTableSnapshot(Identifier identifier, Snapshot snapshot) {
-        tableSnapshotStore.put(identifier.getFullName(), snapshot);
+    public void setTableSnapshot(
+            Identifier identifier,
+            Snapshot snapshot,
+            long recordCount,
+            long fileSizeInBytes,
+            long fileCount,
+            long lastFileCreationTime) {
+        tableSnapshotStore.put(
+                identifier.getFullName(),
+                new TableSnapshot(
+                        snapshot, recordCount, fileSizeInBytes, fileCount, 
lastFileCreationTime));
     }
 
     public void setDataToken(Identifier identifier, RESTToken token) {
@@ -539,7 +549,7 @@ public class RESTCatalogServer {
 
     private MockResponse snapshotHandle(Identifier identifier) throws 
Exception {
         RESTResponse response;
-        Optional<Snapshot> snapshotOptional =
+        Optional<TableSnapshot> snapshotOptional =
                 
Optional.ofNullable(tableSnapshotStore.get(identifier.getFullName()));
         if (!snapshotOptional.isPresent()) {
             response =
@@ -1354,7 +1364,38 @@ public class RESTCatalogServer {
         }
         try {
             boolean success = commit.commit(snapshot, branchName, 
Collections.emptyList());
-            tableSnapshotStore.put(identifier.getFullName(), snapshot);
+            tableSnapshotStore.compute(
+                    identifier.getFullName(),
+                    (k, old) -> {
+                        long recordCount = 0;
+                        long fileSizeInBytes = 0;
+                        long fileCount = 0;
+                        long lastFileCreationTime = 0;
+                        if (statistics != null) {
+                            for (Partition partition : statistics) {
+                                recordCount += partition.recordCount();
+                                fileSizeInBytes += partition.fileSizeInBytes();
+                                fileCount += partition.fileCount();
+                                if (partition.lastFileCreationTime() > 
lastFileCreationTime) {
+                                    lastFileCreationTime = 
partition.lastFileCreationTime();
+                                }
+                            }
+                        }
+                        if (old != null) {
+                            recordCount += old.recordCount();
+                            fileSizeInBytes += old.fileSizeInBytes();
+                            fileCount += old.fileCount();
+                            if (old.lastFileCreationTime() > 
lastFileCreationTime) {
+                                lastFileCreationTime = 
old.lastFileCreationTime();
+                            }
+                        }
+                        return new TableSnapshot(
+                                snapshot,
+                                recordCount,
+                                fileCount,
+                                lastFileCreationTime,
+                                fileSizeInBytes);
+                    });
             return success;
         } catch (Exception e) {
             throw new RuntimeException(e);
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTestBase.java 
b/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTestBase.java
index 298e81a6b2..99b065bcbc 100644
--- a/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTestBase.java
+++ b/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTestBase.java
@@ -36,6 +36,7 @@ import org.apache.paimon.schema.Schema;
 import org.apache.paimon.schema.SchemaChange;
 import org.apache.paimon.table.FileStoreTable;
 import org.apache.paimon.table.Table;
+import org.apache.paimon.table.TableSnapshot;
 import org.apache.paimon.table.sink.BatchTableCommit;
 import org.apache.paimon.table.sink.BatchTableWrite;
 import org.apache.paimon.table.sink.BatchWriteBuilder;
@@ -728,11 +729,15 @@ public abstract class RESTCatalogTestBase extends 
CatalogTestBase {
         long id = 10086;
         long millis = System.currentTimeMillis();
         updateSnapshotOnRestServer(
-                hasSnapshotTableIdentifier, createSnapshotWithMillis(id, 
millis));
-        Optional<Snapshot> snapshot = 
catalog.loadSnapshot(hasSnapshotTableIdentifier);
+                hasSnapshotTableIdentifier, createSnapshotWithMillis(id, 
millis), 1, 2, 3, 4);
+        Optional<TableSnapshot> snapshot = 
catalog.loadSnapshot(hasSnapshotTableIdentifier);
         assertThat(snapshot).isPresent();
-        assertThat(snapshot.get().id()).isEqualTo(id);
-        assertThat(snapshot.get().timeMillis()).isEqualTo(millis);
+        assertThat(snapshot.get().snapshot().id()).isEqualTo(id);
+        assertThat(snapshot.get().snapshot().timeMillis()).isEqualTo(millis);
+        assertThat(snapshot.get().recordCount()).isEqualTo(1);
+        assertThat(snapshot.get().fileSizeInBytes()).isEqualTo(2);
+        assertThat(snapshot.get().fileCount()).isEqualTo(3);
+        assertThat(snapshot.get().lastFileCreationTime()).isEqualTo(4);
         Identifier noSnapshotTableIdentifier = 
Identifier.create("test_db_a_1", "unknown");
         createTable(noSnapshotTableIdentifier, Maps.newHashMap(), 
Lists.newArrayList("col1"));
         snapshot = catalog.loadSnapshot(noSnapshotTableIdentifier);
@@ -947,7 +952,13 @@ public abstract class RESTCatalogTestBase extends 
CatalogTestBase {
 
     protected abstract void resetDataTokenOnRestServer(Identifier identifier);
 
-    protected abstract void updateSnapshotOnRestServer(Identifier identifier, 
Snapshot snapshot);
+    protected abstract void updateSnapshotOnRestServer(
+            Identifier identifier,
+            Snapshot snapshot,
+            long recordCount,
+            long fileSizeInBytes,
+            long fileCount,
+            long lastFileCreationTime);
 
     protected void batchWrite(FileStoreTable tableTestWrite, List<Integer> 
data) throws Exception {
         BatchWriteBuilder writeBuilder = tableTestWrite.newBatchWriteBuilder();
diff --git a/paimon-open-api/rest-catalog-open-api.yaml 
b/paimon-open-api/rest-catalog-open-api.yaml
index 982f91bf85..688655901e 100644
--- a/paimon-open-api/rest-catalog-open-api.yaml
+++ b/paimon-open-api/rest-catalog-open-api.yaml
@@ -1516,6 +1516,23 @@ components:
           format: int64
         statistics:
           type: string
+    TableSnapshot:
+      type: object
+      properties:
+        snapshot:
+          $ref: '#/components/schemas/Snapshot'
+        recordCount:
+          type: integer
+          format: int64
+        fileSizeInBytes:
+          type: integer
+          format: int64
+        fileCount:
+          type: integer
+          format: int64
+        lastFileCreationTime:
+          type: integer
+          format: int64
     CommitTableResponse:
       type: object
       properties:
@@ -1535,7 +1552,7 @@ components:
       type: object
       properties:
         snapshot:
-          $ref: '#/components/schemas/Snapshot'
+          $ref: '#/components/schemas/TableSnapshot'
     AlterDatabaseRequest:
       type: object
       properties:

Reply via email to