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 0d064f959 [flink] Introduce PartitionListener to enable custom actions 
in the Flink committer for partitions. (#4398)
0d064f959 is described below

commit 0d064f959679ab6ebc5a33cf981c8ab30b827e10
Author: WenjunMin <[email protected]>
AuthorDate: Wed Oct 30 10:35:18 2024 +0800

    [flink] Introduce PartitionListener to enable custom actions in the Flink 
committer for partitions. (#4398)
---
 .../generated/flink_connector_configuration.html   |   6 +
 .../apache/paimon/metastore/MetastoreClient.java   |   7 +
 .../apache/paimon/flink/FlinkConnectorOptions.java |   8 +
 .../apache/paimon/flink/sink/StoreCommitter.java   |  33 ++--
 .../paimon/flink/sink/partition/HmsReporter.java   | 115 +++++++++++++
 .../flink/sink/partition/PartitionListener.java    |  29 +---
 .../flink/sink/partition/PartitionListeners.java   |  72 ++++++++
 .../flink/sink/partition/PartitionMarkDone.java    |  16 +-
 .../flink/sink/partition/ReportHmsListener.java    | 189 +++++++++++++++++++++
 .../sink/partition/AddDonePartitionActionTest.java |  10 ++
 .../flink/sink/partition/HmsReporterTest.java      | 151 ++++++++++++++++
 .../sink/partition/PartitionMarkDoneTest.java      |   2 +-
 .../apache/paimon/hive/HiveMetastoreClient.java    |  27 +++
 13 files changed, 612 insertions(+), 53 deletions(-)

diff --git 
a/docs/layouts/shortcodes/generated/flink_connector_configuration.html 
b/docs/layouts/shortcodes/generated/flink_connector_configuration.html
index ea5dd5dfb..4e12c88bc 100644
--- a/docs/layouts/shortcodes/generated/flink_connector_configuration.html
+++ b/docs/layouts/shortcodes/generated/flink_connector_configuration.html
@@ -86,6 +86,12 @@ under the License.
             <td>Duration</td>
             <td>Set a time duration when a partition has no new data after 
this time duration, mark the done status to indicate that the data is 
ready.</td>
         </tr>
+        <tr>
+            <td><h5>partition.idle-time-to-report-statistic</h5></td>
+            <td style="word-wrap: break-word;">1 h</td>
+            <td>Duration</td>
+            <td>Set a time duration when a partition has no new data after 
this time duration, start to report the partition statistics to hms.</td>
+        </tr>
         <tr>
             <td><h5>partition.time-interval</h5></td>
             <td style="word-wrap: break-word;">(none)</td>
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/metastore/MetastoreClient.java 
b/paimon-core/src/main/java/org/apache/paimon/metastore/MetastoreClient.java
index de185155d..c9cafce32 100644
--- a/paimon-core/src/main/java/org/apache/paimon/metastore/MetastoreClient.java
+++ b/paimon-core/src/main/java/org/apache/paimon/metastore/MetastoreClient.java
@@ -22,6 +22,7 @@ import org.apache.paimon.data.BinaryRow;
 
 import java.io.Serializable;
 import java.util.LinkedHashMap;
+import java.util.Map;
 
 /**
  * A metastore client related to a table. All methods of this interface 
operate on the same specific
@@ -37,6 +38,12 @@ public interface MetastoreClient extends AutoCloseable {
 
     void markDone(LinkedHashMap<String, String> partitionSpec) throws 
Exception;
 
+    void alterPartition(
+            LinkedHashMap<String, String> partitionSpec,
+            Map<String, String> parameters,
+            long modifyTime)
+            throws Exception;
+
     /** Factory to create {@link MetastoreClient}. */
     interface Factory extends Serializable {
 
diff --git 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/FlinkConnectorOptions.java
 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/FlinkConnectorOptions.java
index 2c12b70a2..73b004601 100644
--- 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/FlinkConnectorOptions.java
+++ 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/FlinkConnectorOptions.java
@@ -355,6 +355,14 @@ public class FlinkConnectorOptions {
                             "You can specify time interval for partition, for 
example, "
                                     + "daily partition is '1 d', hourly 
partition is '1 h'.");
 
+    public static final ConfigOption<Duration> 
PARTITION_IDLE_TIME_TO_REPORT_STATISTIC =
+            key("partition.idle-time-to-report-statistic")
+                    .durationType()
+                    .defaultValue(Duration.ofHours(1))
+                    .withDescription(
+                            "Set a time duration when a partition has no new 
data after this time duration, "
+                                    + "start to report the partition 
statistics to hms.");
+
     public static final ConfigOption<String> CLUSTERING_COLUMNS =
             key("sink.clustering.by-columns")
                     .stringType()
diff --git 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/StoreCommitter.java
 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/StoreCommitter.java
index 9ae53f401..d237f4da5 100644
--- 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/StoreCommitter.java
+++ 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/StoreCommitter.java
@@ -20,7 +20,7 @@ package org.apache.paimon.flink.sink;
 
 import org.apache.paimon.annotation.VisibleForTesting;
 import org.apache.paimon.flink.metrics.FlinkMetricRegistry;
-import org.apache.paimon.flink.sink.partition.PartitionMarkDone;
+import org.apache.paimon.flink.sink.partition.PartitionListeners;
 import org.apache.paimon.io.DataFileMeta;
 import org.apache.paimon.manifest.ManifestCommittable;
 import org.apache.paimon.table.FileStoreTable;
@@ -43,7 +43,7 @@ public class StoreCommitter implements Committer<Committable, 
ManifestCommittabl
 
     private final TableCommitImpl commit;
     @Nullable private final CommitterMetrics committerMetrics;
-    @Nullable private final PartitionMarkDone partitionMarkDone;
+    private final PartitionListeners partitionListeners;
 
     public StoreCommitter(FileStoreTable table, TableCommit commit, Context 
context) {
         this.commit = (TableCommitImpl) commit;
@@ -56,12 +56,7 @@ public class StoreCommitter implements 
Committer<Committable, ManifestCommittabl
         }
 
         try {
-            this.partitionMarkDone =
-                    PartitionMarkDone.create(
-                            context.streamingCheckpointEnabled(),
-                            context.isRestored(),
-                            context.stateStore(),
-                            table);
+            this.partitionListeners = PartitionListeners.create(context, 
table);
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
@@ -111,29 +106,23 @@ public class StoreCommitter implements 
Committer<Committable, ManifestCommittabl
             throws IOException, InterruptedException {
         commit.commitMultiple(committables, false);
         calcNumBytesAndRecordsOut(committables);
-        if (partitionMarkDone != null) {
-            partitionMarkDone.notifyCommittable(committables);
-        }
+        partitionListeners.notifyCommittable(committables);
     }
 
     @Override
     public int filterAndCommit(
             List<ManifestCommittable> globalCommittables, boolean 
checkAppendFiles) {
         int committed = commit.filterAndCommitMultiple(globalCommittables, 
checkAppendFiles);
-        if (partitionMarkDone != null) {
-            partitionMarkDone.notifyCommittable(globalCommittables);
-        }
+        partitionListeners.notifyCommittable(globalCommittables);
         return committed;
     }
 
     @Override
     public Map<Long, List<Committable>> 
groupByCheckpoint(Collection<Committable> committables) {
-        if (partitionMarkDone != null) {
-            try {
-                partitionMarkDone.snapshotState();
-            } catch (Exception e) {
-                throw new RuntimeException(e);
-            }
+        try {
+            partitionListeners.snapshotState();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
         }
 
         Map<Long, List<Committable>> grouped = new HashMap<>();
@@ -146,9 +135,7 @@ public class StoreCommitter implements 
Committer<Committable, ManifestCommittabl
     @Override
     public void close() throws Exception {
         commit.close();
-        if (partitionMarkDone != null) {
-            partitionMarkDone.close();
-        }
+        partitionListeners.close();
     }
 
     private void calcNumBytesAndRecordsOut(List<ManifestCommittable> 
committables) {
diff --git 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/partition/HmsReporter.java
 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/partition/HmsReporter.java
new file mode 100644
index 000000000..9cb776340
--- /dev/null
+++ 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/partition/HmsReporter.java
@@ -0,0 +1,115 @@
+/*
+ * 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.flink.sink.partition;
+
+import org.apache.paimon.Snapshot;
+import org.apache.paimon.fs.Path;
+import org.apache.paimon.io.DataFileMeta;
+import org.apache.paimon.metastore.MetastoreClient;
+import org.apache.paimon.table.FileStoreTable;
+import org.apache.paimon.table.source.DataSplit;
+import org.apache.paimon.table.source.DeletionFile;
+import org.apache.paimon.table.source.ScanMode;
+import org.apache.paimon.table.source.snapshot.SnapshotReader;
+import org.apache.paimon.utils.Preconditions;
+import org.apache.paimon.utils.SnapshotManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import static 
org.apache.paimon.utils.PartitionPathUtils.extractPartitionSpecFromPath;
+
+/** Action to report the table statistic from the latest snapshot to HMS. */
+public class HmsReporter implements Closeable {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(HmsReporter.class);
+
+    private final MetastoreClient metastoreClient;
+    private final SnapshotReader snapshotReader;
+    private final SnapshotManager snapshotManager;
+
+    public HmsReporter(FileStoreTable table, MetastoreClient client) {
+        this.metastoreClient =
+                Preconditions.checkNotNull(client, "the metastore client 
factory is null");
+        this.snapshotReader = table.newSnapshotReader();
+        this.snapshotManager = table.snapshotManager();
+    }
+
+    public void report(String partition, long modifyTime) throws Exception {
+        Snapshot snapshot = snapshotManager.latestSnapshot();
+        if (snapshot != null) {
+            LinkedHashMap<String, String> partitionSpec =
+                    extractPartitionSpecFromPath(new Path(partition));
+            List<DataSplit> splits =
+                    new ArrayList<>(
+                            snapshotReader
+                                    .withMode(ScanMode.ALL)
+                                    .withPartitionFilter(partitionSpec)
+                                    .withSnapshot(snapshot)
+                                    .read()
+                                    .dataSplits());
+            long rowCount = 0;
+            long totalSize = 0;
+            long fileCount = 0;
+            for (DataSplit split : splits) {
+                List<DataFileMeta> fileMetas = split.dataFiles();
+                rowCount += split.rowCount();
+                fileCount += fileMetas.size();
+                for (DataFileMeta fileMeta : fileMetas) {
+                    totalSize += fileMeta.fileSize();
+                }
+
+                if (split.deletionFiles().isPresent()) {
+                    fileCount += split.deletionFiles().get().size();
+                    totalSize +=
+                            split.deletionFiles().get().stream()
+                                    .map(DeletionFile::length)
+                                    .reduce(0L, Long::sum);
+                }
+            }
+            Map<String, String> statistic = new HashMap<>();
+            // refer to org.apache.hadoop.hive.common.StatsSetupConst
+            statistic.put("numFiles", String.valueOf(fileCount));
+            statistic.put("totalSize", String.valueOf(totalSize));
+            statistic.put("numRows", String.valueOf(rowCount));
+            // refer to 
org.apache.hadoop.hive.metastore.api.hive_metastoreConstants
+            statistic.put("transient_lastDdlTime", String.valueOf(modifyTime / 
1000));
+
+            LOG.info("alter partition {} with statistic {}.", partition, 
statistic);
+            metastoreClient.alterPartition(partitionSpec, statistic, 
modifyTime);
+        }
+    }
+
+    @Override
+    public void close() throws IOException {
+        try {
+            metastoreClient.close();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/metastore/MetastoreClient.java 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/partition/PartitionListener.java
similarity index 51%
copy from 
paimon-core/src/main/java/org/apache/paimon/metastore/MetastoreClient.java
copy to 
paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/partition/PartitionListener.java
index de185155d..65d25fbc0 100644
--- a/paimon-core/src/main/java/org/apache/paimon/metastore/MetastoreClient.java
+++ 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/partition/PartitionListener.java
@@ -16,30 +16,17 @@
  * limitations under the License.
  */
 
-package org.apache.paimon.metastore;
+package org.apache.paimon.flink.sink.partition;
 
-import org.apache.paimon.data.BinaryRow;
+import org.apache.paimon.manifest.ManifestCommittable;
 
-import java.io.Serializable;
-import java.util.LinkedHashMap;
+import java.io.Closeable;
+import java.util.List;
 
-/**
- * A metastore client related to a table. All methods of this interface 
operate on the same specific
- * table.
- */
-public interface MetastoreClient extends AutoCloseable {
-
-    void addPartition(BinaryRow partition) throws Exception;
-
-    void addPartition(LinkedHashMap<String, String> partitionSpec) throws 
Exception;
-
-    void deletePartition(LinkedHashMap<String, String> partitionSpec) throws 
Exception;
-
-    void markDone(LinkedHashMap<String, String> partitionSpec) throws 
Exception;
+/** The partition listener. */
+public interface PartitionListener extends Closeable {
 
-    /** Factory to create {@link MetastoreClient}. */
-    interface Factory extends Serializable {
+    void notifyCommittable(List<ManifestCommittable> committables);
 
-        MetastoreClient create();
-    }
+    void snapshotState() throws Exception;
 }
diff --git 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/partition/PartitionListeners.java
 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/partition/PartitionListeners.java
new file mode 100644
index 000000000..dbdf77601
--- /dev/null
+++ 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/partition/PartitionListeners.java
@@ -0,0 +1,72 @@
+/*
+ * 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.flink.sink.partition;
+
+import org.apache.paimon.flink.sink.Committer;
+import org.apache.paimon.manifest.ManifestCommittable;
+import org.apache.paimon.table.FileStoreTable;
+import org.apache.paimon.utils.IOUtils;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/** Partition listeners. */
+public class PartitionListeners implements Closeable {
+
+    private final List<PartitionListener> listeners;
+
+    private PartitionListeners(List<PartitionListener> listeners) {
+        this.listeners = listeners;
+    }
+
+    public void notifyCommittable(List<ManifestCommittable> committables) {
+        for (PartitionListener trigger : listeners) {
+            trigger.notifyCommittable(committables);
+        }
+    }
+
+    public void snapshotState() throws Exception {
+        for (PartitionListener trigger : listeners) {
+            trigger.snapshotState();
+        }
+    }
+
+    @Override
+    public void close() throws IOException {
+        IOUtils.closeAllQuietly(listeners);
+    }
+
+    public static PartitionListeners create(Committer.Context context, 
FileStoreTable table)
+            throws Exception {
+        List<PartitionListener> listeners = new ArrayList<>();
+
+        ReportHmsListener.create(context.isRestored(), context.stateStore(), 
table)
+                .ifPresent(listeners::add);
+        PartitionMarkDone.create(
+                        context.streamingCheckpointEnabled(),
+                        context.isRestored(),
+                        context.stateStore(),
+                        table)
+                .ifPresent(listeners::add);
+
+        return new PartitionListeners(listeners);
+    }
+}
diff --git 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/partition/PartitionMarkDone.java
 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/partition/PartitionMarkDone.java
index d0825bcdb..8714e0006 100644
--- 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/partition/PartitionMarkDone.java
+++ 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/partition/PartitionMarkDone.java
@@ -33,28 +33,25 @@ import org.apache.paimon.utils.PartitionPathUtils;
 
 import org.apache.flink.api.common.state.OperatorStateStore;
 
-import javax.annotation.Nullable;
-
-import java.io.Closeable;
 import java.io.IOException;
 import java.time.Duration;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 
 import static org.apache.paimon.CoreOptions.PARTITION_MARK_DONE_WHEN_END_INPUT;
 import static 
org.apache.paimon.flink.FlinkConnectorOptions.PARTITION_IDLE_TIME_TO_DONE;
 
 /** Mark partition done. */
-public class PartitionMarkDone implements Closeable {
+public class PartitionMarkDone implements PartitionListener {
 
     private final InternalRowPartitionComputer partitionComputer;
     private final PartitionMarkDoneTrigger trigger;
     private final List<PartitionMarkDoneAction> actions;
     private final boolean waitCompaction;
 
-    @Nullable
-    public static PartitionMarkDone create(
+    public static Optional<PartitionMarkDone> create(
             boolean isStreaming,
             boolean isRestored,
             OperatorStateStore stateStore,
@@ -64,7 +61,7 @@ public class PartitionMarkDone implements Closeable {
         Options options = coreOptions.toConfiguration();
 
         if (disablePartitionMarkDone(isStreaming, table, options)) {
-            return null;
+            return Optional.empty();
         }
 
         InternalRowPartitionComputer partitionComputer =
@@ -87,7 +84,8 @@ public class PartitionMarkDone implements Closeable {
                         && (coreOptions.deletionVectorsEnabled()
                                 || coreOptions.mergeEngine() == 
MergeEngine.FIRST_ROW);
 
-        return new PartitionMarkDone(partitionComputer, trigger, actions, 
waitCompaction);
+        return Optional.of(
+                new PartitionMarkDone(partitionComputer, trigger, actions, 
waitCompaction));
     }
 
     private static boolean disablePartitionMarkDone(
@@ -116,6 +114,7 @@ public class PartitionMarkDone implements Closeable {
         this.waitCompaction = waitCompaction;
     }
 
+    @Override
     public void notifyCommittable(List<ManifestCommittable> committables) {
         Set<BinaryRow> partitions = new HashSet<>();
         boolean endInput = false;
@@ -153,6 +152,7 @@ public class PartitionMarkDone implements Closeable {
         }
     }
 
+    @Override
     public void snapshotState() throws Exception {
         trigger.snapshotState();
     }
diff --git 
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/partition/ReportHmsListener.java
 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/partition/ReportHmsListener.java
new file mode 100644
index 000000000..842dd012e
--- /dev/null
+++ 
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/sink/partition/ReportHmsListener.java
@@ -0,0 +1,189 @@
+/*
+ * 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.flink.sink.partition;
+
+import org.apache.paimon.CoreOptions;
+import org.apache.paimon.flink.FlinkConnectorOptions;
+import org.apache.paimon.manifest.ManifestCommittable;
+import org.apache.paimon.options.Options;
+import org.apache.paimon.table.FileStoreTable;
+import org.apache.paimon.table.sink.CommitMessage;
+import org.apache.paimon.table.sink.CommitMessageImpl;
+import org.apache.paimon.utils.InternalRowPartitionComputer;
+import org.apache.paimon.utils.PartitionPathUtils;
+
+import org.apache.flink.api.common.state.ListState;
+import org.apache.flink.api.common.state.ListStateDescriptor;
+import org.apache.flink.api.common.state.OperatorStateStore;
+import org.apache.flink.api.common.typeutils.base.LongSerializer;
+import org.apache.flink.api.common.typeutils.base.MapSerializer;
+import org.apache.flink.api.common.typeutils.base.StringSerializer;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * This listener will collect data from the newly touched partition and then 
decide when to trigger
+ * a report based on the partition's idle time.
+ */
+public class ReportHmsListener implements PartitionListener {
+
+    @SuppressWarnings("unchecked")
+    private static final ListStateDescriptor<Map<String, Long>> 
PENDING_REPORT_STATE_DESC =
+            new ListStateDescriptor<>(
+                    "pending-report-hms-partition",
+                    new MapSerializer<>(StringSerializer.INSTANCE, 
LongSerializer.INSTANCE));
+
+    private final InternalRowPartitionComputer partitionComputer;
+    private final HmsReporter hmsReporter;
+    private final ListState<Map<String, Long>> pendingPartitionsState;
+    private final Map<String, Long> pendingPartitions;
+    private final long idleTime;
+
+    private ReportHmsListener(
+            InternalRowPartitionComputer partitionComputer,
+            HmsReporter hmsReporter,
+            OperatorStateStore store,
+            boolean isRestored,
+            long idleTime)
+            throws Exception {
+        this.partitionComputer = partitionComputer;
+        this.hmsReporter = hmsReporter;
+        this.pendingPartitionsState = 
store.getListState(PENDING_REPORT_STATE_DESC);
+        this.pendingPartitions = new HashMap<>();
+        if (isRestored) {
+            Iterator<Map<String, Long>> it = 
pendingPartitionsState.get().iterator();
+            if (it.hasNext()) {
+                Map<String, Long> state = it.next();
+                pendingPartitions.putAll(state);
+            }
+        }
+        this.idleTime = idleTime;
+    }
+
+    public void notifyCommittable(List<ManifestCommittable> committables) {
+        Set<String> partition = new HashSet<>();
+        boolean endInput = false;
+        for (ManifestCommittable committable : committables) {
+            for (CommitMessage commitMessage : committable.fileCommittables()) 
{
+                CommitMessageImpl message = (CommitMessageImpl) commitMessage;
+                if (!message.newFilesIncrement().isEmpty()
+                        || !message.compactIncrement().isEmpty()) {
+                    partition.add(
+                            PartitionPathUtils.generatePartitionPath(
+                                    
partitionComputer.generatePartValues(message.partition())));
+                }
+            }
+            if (committable.identifier() == Long.MAX_VALUE) {
+                endInput = true;
+            }
+        }
+        // append to map
+        long current = System.currentTimeMillis();
+        partition.forEach(p -> pendingPartitions.put(p, current));
+
+        try {
+            Map<String, Long> partitions = reportPartition(endInput);
+            for (Map.Entry<String, Long> entry : partitions.entrySet()) {
+                hmsReporter.report(entry.getKey(), entry.getValue());
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private Map<String, Long> reportPartition(boolean endInput) {
+        if (endInput) {
+            return pendingPartitions;
+        }
+
+        Iterator<Map.Entry<String, Long>> iterator = 
pendingPartitions.entrySet().iterator();
+        Map<String, Long> result = new HashMap<>();
+        long current = System.currentTimeMillis();
+        while (iterator.hasNext()) {
+            Map.Entry<String, Long> entry = iterator.next();
+            if (current - entry.getValue() > idleTime) {
+                result.put(entry.getKey(), entry.getValue());
+                iterator.remove();
+            }
+        }
+
+        return result;
+    }
+
+    public void snapshotState() throws Exception {
+        
pendingPartitionsState.update(Collections.singletonList(pendingPartitions));
+    }
+
+    public static Optional<ReportHmsListener> create(
+            boolean isRestored, OperatorStateStore stateStore, FileStoreTable 
table)
+            throws Exception {
+
+        CoreOptions coreOptions = table.coreOptions();
+        Options options = coreOptions.toConfiguration();
+        if 
(options.get(FlinkConnectorOptions.PARTITION_IDLE_TIME_TO_REPORT_STATISTIC).toMillis()
+                <= 0) {
+            return Optional.empty();
+        }
+
+        if ((table.partitionKeys().isEmpty())) {
+            return Optional.empty();
+        }
+
+        if (!coreOptions.partitionedTableInMetastore()) {
+            return Optional.empty();
+        }
+
+        if (table.catalogEnvironment().metastoreClientFactory() == null) {
+            return Optional.empty();
+        }
+
+        InternalRowPartitionComputer partitionComputer =
+                new InternalRowPartitionComputer(
+                        coreOptions.partitionDefaultName(),
+                        table.schema().logicalPartitionType(),
+                        table.partitionKeys().toArray(new String[0]),
+                        coreOptions.legacyPartitionName());
+
+        return Optional.of(
+                new ReportHmsListener(
+                        partitionComputer,
+                        new HmsReporter(
+                                table,
+                                
table.catalogEnvironment().metastoreClientFactory().create()),
+                        stateStore,
+                        isRestored,
+                        
options.get(FlinkConnectorOptions.PARTITION_IDLE_TIME_TO_REPORT_STATISTIC)
+                                .toMillis()));
+    }
+
+    @Override
+    public void close() throws IOException {
+        if (hmsReporter != null) {
+            hmsReporter.close();
+        }
+    }
+}
diff --git 
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/sink/partition/AddDonePartitionActionTest.java
 
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/sink/partition/AddDonePartitionActionTest.java
index 5338b3886..19c22d137 100644
--- 
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/sink/partition/AddDonePartitionActionTest.java
+++ 
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/sink/partition/AddDonePartitionActionTest.java
@@ -26,6 +26,7 @@ import org.junit.jupiter.api.Test;
 
 import java.util.HashSet;
 import java.util.LinkedHashMap;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -61,6 +62,15 @@ class AddDonePartitionActionTest {
                         throw new UnsupportedOperationException();
                     }
 
+                    @Override
+                    public void alterPartition(
+                            LinkedHashMap<String, String> partitionSpec,
+                            Map<String, String> parameters,
+                            long modifyTime)
+                            throws Exception {
+                        throw new UnsupportedOperationException();
+                    }
+
                     @Override
                     public void close() throws Exception {
                         closed.set(true);
diff --git 
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/sink/partition/HmsReporterTest.java
 
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/sink/partition/HmsReporterTest.java
new file mode 100644
index 000000000..0050f3083
--- /dev/null
+++ 
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/sink/partition/HmsReporterTest.java
@@ -0,0 +1,151 @@
+/*
+ * 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.flink.sink.partition;
+
+import org.apache.paimon.data.BinaryRow;
+import org.apache.paimon.data.BinaryString;
+import org.apache.paimon.data.GenericRow;
+import org.apache.paimon.fs.Path;
+import org.apache.paimon.fs.local.LocalFileIO;
+import org.apache.paimon.metastore.MetastoreClient;
+import org.apache.paimon.schema.Schema;
+import org.apache.paimon.schema.SchemaManager;
+import org.apache.paimon.table.FileStoreTable;
+import org.apache.paimon.table.FileStoreTableFactory;
+import org.apache.paimon.table.sink.BatchTableCommit;
+import org.apache.paimon.table.sink.BatchTableWrite;
+import org.apache.paimon.table.sink.CommitMessage;
+import org.apache.paimon.types.DataField;
+import org.apache.paimon.types.DataTypes;
+import org.apache.paimon.utils.PartitionPathUtils;
+
+import org.apache.paimon.shade.guava30.com.google.common.collect.ImmutableMap;
+import org.apache.paimon.shade.guava30.com.google.common.collect.Lists;
+import org.apache.paimon.shade.guava30.com.google.common.collect.Maps;
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/** Test for {@link HmsReporter}. */
+public class HmsReporterTest {
+
+    @TempDir java.nio.file.Path tempDir;
+
+    @Test
+    public void testReportAction() throws Exception {
+        Path tablePath = new Path(tempDir.toString(), "table");
+        SchemaManager schemaManager = new SchemaManager(LocalFileIO.create(), 
tablePath);
+        Schema schema =
+                new Schema(
+                        Lists.newArrayList(
+                                new DataField(0, "c1", DataTypes.STRING()),
+                                new DataField(1, "c2", DataTypes.STRING()),
+                                new DataField(2, "c3", DataTypes.STRING())),
+                        Collections.singletonList("c1"),
+                        Collections.emptyList(),
+                        Maps.newHashMap(),
+                        "");
+        schemaManager.createTable(schema);
+
+        FileStoreTable table = 
FileStoreTableFactory.create(LocalFileIO.create(), tablePath);
+        BatchTableWrite writer = table.newBatchWriteBuilder().newWrite();
+        writer.write(
+                GenericRow.of(
+                        BinaryString.fromString("a"),
+                        BinaryString.fromString("a"),
+                        BinaryString.fromString("a")));
+        writer.write(
+                GenericRow.of(
+                        BinaryString.fromString("b"),
+                        BinaryString.fromString("a"),
+                        BinaryString.fromString("a")));
+        List<CommitMessage> messages = writer.prepareCommit();
+        BatchTableCommit committer = table.newBatchWriteBuilder().newCommit();
+        committer.commit(messages);
+        AtomicBoolean closed = new AtomicBoolean(false);
+        Map<String, Map<String, String>> partitionParams = Maps.newHashMap();
+
+        MetastoreClient client =
+                new MetastoreClient() {
+                    @Override
+                    public void addPartition(BinaryRow partition) throws 
Exception {
+                        throw new UnsupportedOperationException();
+                    }
+
+                    @Override
+                    public void addPartition(LinkedHashMap<String, String> 
partitionSpec)
+                            throws Exception {
+                        throw new UnsupportedOperationException();
+                    }
+
+                    @Override
+                    public void deletePartition(LinkedHashMap<String, String> 
partitionSpec)
+                            throws Exception {
+                        throw new UnsupportedOperationException();
+                    }
+
+                    @Override
+                    public void markDone(LinkedHashMap<String, String> 
partitionSpec)
+                            throws Exception {
+                        throw new UnsupportedOperationException();
+                    }
+
+                    @Override
+                    public void alterPartition(
+                            LinkedHashMap<String, String> partitionSpec,
+                            Map<String, String> parameters,
+                            long modifyTime)
+                            throws Exception {
+                        partitionParams.put(
+                                
PartitionPathUtils.generatePartitionPath(partitionSpec),
+                                parameters);
+                    }
+
+                    @Override
+                    public void close() throws Exception {
+                        closed.set(true);
+                    }
+                };
+
+        HmsReporter action = new HmsReporter(table, client);
+        long time = 1729598544974L;
+        action.report("c1=a/", time);
+        Assertions.assertThat(partitionParams).containsKey("c1=a/");
+        Assertions.assertThat(partitionParams.get("c1=a/"))
+                .isEqualTo(
+                        ImmutableMap.of(
+                                "numFiles",
+                                "1",
+                                "totalSize",
+                                "591",
+                                "numRows",
+                                "1",
+                                "transient_lastDdlTime",
+                                String.valueOf(time / 1000)));
+        action.close();
+        Assertions.assertThat(closed).isTrue();
+    }
+}
diff --git 
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/sink/partition/PartitionMarkDoneTest.java
 
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/sink/partition/PartitionMarkDoneTest.java
index ecef6d337..f0f4596c6 100644
--- 
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/sink/partition/PartitionMarkDoneTest.java
+++ 
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/sink/partition/PartitionMarkDoneTest.java
@@ -86,7 +86,7 @@ class PartitionMarkDoneTest extends TableTestBase {
         Path location = catalog.getTableLocation(identifier);
         Path successFile = new Path(location, "a=0/_SUCCESS");
         PartitionMarkDone markDone =
-                PartitionMarkDone.create(false, false, new 
MockOperatorStateStore(), table);
+                PartitionMarkDone.create(false, false, new 
MockOperatorStateStore(), table).get();
 
         notifyCommits(markDone, true);
         
assertThat(table.fileIO().exists(successFile)).isEqualTo(deletionVectors);
diff --git 
a/paimon-hive/paimon-hive-catalog/src/main/java/org/apache/paimon/hive/HiveMetastoreClient.java
 
b/paimon-hive/paimon-hive-catalog/src/main/java/org/apache/paimon/hive/HiveMetastoreClient.java
index 6ec3b086d..5856515bb 100644
--- 
a/paimon-hive/paimon-hive-catalog/src/main/java/org/apache/paimon/hive/HiveMetastoreClient.java
+++ 
b/paimon-hive/paimon-hive-catalog/src/main/java/org/apache/paimon/hive/HiveMetastoreClient.java
@@ -40,6 +40,7 @@ import org.apache.thrift.TException;
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 
 /** {@link MetastoreClient} for Hive tables. */
 public class HiveMetastoreClient implements MetastoreClient {
@@ -112,6 +113,32 @@ public class HiveMetastoreClient implements 
MetastoreClient {
         }
     }
 
+    @Override
+    public void alterPartition(
+            LinkedHashMap<String, String> partitionSpec,
+            Map<String, String> parameters,
+            long modifyTime)
+            throws Exception {
+        List<String> partitionValues = new ArrayList<>(partitionSpec.values());
+        int currentTime = (int) (modifyTime / 1000);
+        Partition hivePartition =
+                clients.run(
+                        client ->
+                                client.getPartition(
+                                        identifier.getDatabaseName(),
+                                        identifier.getObjectName(),
+                                        partitionValues));
+        hivePartition.setValues(partitionValues);
+        hivePartition.setLastAccessTime(currentTime);
+        hivePartition.getParameters().putAll(parameters);
+        clients.execute(
+                client ->
+                        client.alter_partition(
+                                identifier.getDatabaseName(),
+                                identifier.getObjectName(),
+                                hivePartition));
+    }
+
     @Override
     public void deletePartition(LinkedHashMap<String, String> partitionSpec) 
throws Exception {
         List<String> partitionValues = new ArrayList<>(partitionSpec.values());


Reply via email to