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 531b653e5 [core] Support rename tag action and procedure (#4277)
531b653e5 is described below
commit 531b653e5ff7eb6b2985a1532e34bd8d4268a088
Author: xuzifu666 <[email protected]>
AuthorDate: Tue Oct 8 10:04:50 2024 +0800
[core] Support rename tag action and procedure (#4277)
---
docs/content/spark/procedures.md | 12 +++
.../paimon/privilege/PrivilegedFileStoreTable.java | 6 ++
.../paimon/table/AbstractFileStoreTable.java | 5 ++
.../paimon/table/DelegatedFileStoreTable.java | 5 ++
.../java/org/apache/paimon/table/FormatTable.java | 5 ++
.../org/apache/paimon/table/ReadonlyTable.java | 8 ++
.../main/java/org/apache/paimon/table/Table.java | 3 +
.../java/org/apache/paimon/utils/TagManager.java | 18 ++++
.../paimon/flink/action/RenameTagAction.java | 52 ++++++++++++
.../flink/action/RenameTagActionFactory.java | 71 ++++++++++++++++
.../paimon/flink/procedure/RenameTagProcedure.java | 59 ++++++++++++++
.../services/org.apache.paimon.factories.Factory | 2 +
.../paimon/flink/action/TagActionITCase.java | 95 ++++++++++++++++++++++
.../flink/procedure/CreateTagsProcedureITCase.java | 24 ++++++
.../sql/CreateAndDeleteTagProcedureTest.scala | 10 ++-
.../org/apache/paimon/spark/SparkProcedures.java | 2 +
.../paimon/spark/procedure/RenameTagProcedure.java | 95 ++++++++++++++++++++++
.../CreateAndDeleteTagProcedureTest.scala | 12 ++-
18 files changed, 482 insertions(+), 2 deletions(-)
diff --git a/docs/content/spark/procedures.md b/docs/content/spark/procedures.md
index 9f6e061c1..1557fd6a7 100644
--- a/docs/content/spark/procedures.md
+++ b/docs/content/spark/procedures.md
@@ -109,6 +109,18 @@ This section introduce all available spark procedures
about paimon.
CALL sys.create_tag_from_timestamp(`table` => 'default.T', `tag` =>
'my_tag', `timestamp` => 1724404318750, time_retained => '1 d')
</td>
</tr>
+ <tr>
+ <td>rename_tag</td>
+ <td>
+ Rename a tag with a new tag name. Arguments:
+ <li>table: the target table identifier. Cannot be empty.</li>
+ <li>tag_name: name of the tag. Cannot be empty.</li>
+ <li>target_tag_name: the new tag name to rename. Cannot be
empty.</li>
+ </td>
+ <td>
+ CALL sys.rename_tag(table => 'default.T', tag_name => 'tag1',
target_tag_name => 'tag2')
+ </td>
+ </tr>
<tr>
<td>delete_tag</td>
<td>
diff --git
a/paimon-core/src/main/java/org/apache/paimon/privilege/PrivilegedFileStoreTable.java
b/paimon-core/src/main/java/org/apache/paimon/privilege/PrivilegedFileStoreTable.java
index 6842f0142..4eb1a1a90 100644
---
a/paimon-core/src/main/java/org/apache/paimon/privilege/PrivilegedFileStoreTable.java
+++
b/paimon-core/src/main/java/org/apache/paimon/privilege/PrivilegedFileStoreTable.java
@@ -121,6 +121,12 @@ public class PrivilegedFileStoreTable extends
DelegatedFileStoreTable {
wrapped.createTag(tagName, timeRetained);
}
+ @Override
+ public void renameTag(String tagName, String targetTagName) {
+ privilegeChecker.assertCanInsert(identifier);
+ wrapped.renameTag(tagName, targetTagName);
+ }
+
@Override
public void createTag(String tagName, long fromSnapshotId, Duration
timeRetained) {
privilegeChecker.assertCanInsert(identifier);
diff --git
a/paimon-core/src/main/java/org/apache/paimon/table/AbstractFileStoreTable.java
b/paimon-core/src/main/java/org/apache/paimon/table/AbstractFileStoreTable.java
index a98f368c5..171b7325a 100644
---
a/paimon-core/src/main/java/org/apache/paimon/table/AbstractFileStoreTable.java
+++
b/paimon-core/src/main/java/org/apache/paimon/table/AbstractFileStoreTable.java
@@ -571,6 +571,11 @@ abstract class AbstractFileStoreTable implements
FileStoreTable {
tagManager().createTag(fromSnapshot, tagName, timeRetained,
store().createTagCallbacks());
}
+ @Override
+ public void renameTag(String tagName, String targetTagName) {
+ tagManager().renameTag(tagName, targetTagName);
+ }
+
@Override
public void deleteTag(String tagName) {
tagManager()
diff --git
a/paimon-core/src/main/java/org/apache/paimon/table/DelegatedFileStoreTable.java
b/paimon-core/src/main/java/org/apache/paimon/table/DelegatedFileStoreTable.java
index dc758a1af..5d6331aa4 100644
---
a/paimon-core/src/main/java/org/apache/paimon/table/DelegatedFileStoreTable.java
+++
b/paimon-core/src/main/java/org/apache/paimon/table/DelegatedFileStoreTable.java
@@ -182,6 +182,11 @@ public abstract class DelegatedFileStoreTable implements
FileStoreTable {
wrapped.createTag(tagName, fromSnapshotId, timeRetained);
}
+ @Override
+ public void renameTag(String tagName, String targetTagName) {
+ wrapped.renameTag(tagName, targetTagName);
+ }
+
@Override
public void deleteTag(String tagName) {
wrapped.deleteTag(tagName);
diff --git a/paimon-core/src/main/java/org/apache/paimon/table/FormatTable.java
b/paimon-core/src/main/java/org/apache/paimon/table/FormatTable.java
index efd4a00d0..3224131d4 100644
--- a/paimon-core/src/main/java/org/apache/paimon/table/FormatTable.java
+++ b/paimon-core/src/main/java/org/apache/paimon/table/FormatTable.java
@@ -266,6 +266,11 @@ public interface FormatTable extends Table {
throw new UnsupportedOperationException();
}
+ @Override
+ default void renameTag(String tagName, String targetTagName) {
+ throw new UnsupportedOperationException();
+ }
+
@Override
default void deleteTag(String tagName) {
throw new UnsupportedOperationException();
diff --git
a/paimon-core/src/main/java/org/apache/paimon/table/ReadonlyTable.java
b/paimon-core/src/main/java/org/apache/paimon/table/ReadonlyTable.java
index 82edaa667..4ae593b55 100644
--- a/paimon-core/src/main/java/org/apache/paimon/table/ReadonlyTable.java
+++ b/paimon-core/src/main/java/org/apache/paimon/table/ReadonlyTable.java
@@ -189,6 +189,14 @@ public interface ReadonlyTable extends InnerTable {
this.getClass().getSimpleName()));
}
+ @Override
+ default void renameTag(String tagName, String targetTagName) {
+ throw new UnsupportedOperationException(
+ String.format(
+ "Readonly Table %s does not support renameTag.",
+ this.getClass().getSimpleName()));
+ }
+
@Override
default void deleteTag(String tagName) {
throw new UnsupportedOperationException(
diff --git a/paimon-core/src/main/java/org/apache/paimon/table/Table.java
b/paimon-core/src/main/java/org/apache/paimon/table/Table.java
index d542732e2..613dfca31 100644
--- a/paimon-core/src/main/java/org/apache/paimon/table/Table.java
+++ b/paimon-core/src/main/java/org/apache/paimon/table/Table.java
@@ -117,6 +117,9 @@ public interface Table extends Serializable {
@Experimental
void createTag(String tagName, Duration timeRetained);
+ @Experimental
+ void renameTag(String tagName, String targetTagName);
+
/** Delete a tag by name. */
@Experimental
void deleteTag(String tagName);
diff --git a/paimon-core/src/main/java/org/apache/paimon/utils/TagManager.java
b/paimon-core/src/main/java/org/apache/paimon/utils/TagManager.java
index f6e69fb28..8aecb1ab7 100644
--- a/paimon-core/src/main/java/org/apache/paimon/utils/TagManager.java
+++ b/paimon-core/src/main/java/org/apache/paimon/utils/TagManager.java
@@ -144,6 +144,24 @@ public class TagManager {
}
}
+ public void renameTag(String tagName, String targetTagName) {
+ try {
+ if (!tagExists(tagName)) {
+ throw new RuntimeException(
+ String.format("The specified tag name [%s] does not
exist.", tagName));
+ }
+ if (tagExists(targetTagName)) {
+ throw new RuntimeException(
+ String.format(
+ "The specified target tag name [%s] existed,
please set a non-existent tag name.",
+ targetTagName));
+ }
+ fileIO.rename(tagPath(tagName), tagPath(targetTagName));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
/** Make sure the tagNames are ALL tags of one snapshot. */
public void deleteAllTagsOfOneSnapshot(
List<String> tagNames, TagDeletion tagDeletion, SnapshotManager
snapshotManager) {
diff --git
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/action/RenameTagAction.java
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/action/RenameTagAction.java
new file mode 100644
index 000000000..3b12b4c11
--- /dev/null
+++
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/action/RenameTagAction.java
@@ -0,0 +1,52 @@
+/*
+ * 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.action;
+
+import org.apache.paimon.utils.StringUtils;
+
+import java.util.Map;
+
+/** Rename Tag action for Flink. */
+public class RenameTagAction extends TableActionBase {
+ private final String tagName;
+ private final String targetTagName;
+
+ public RenameTagAction(
+ String warehouse,
+ String databaseName,
+ String tableName,
+ Map<String, String> catalogConfig,
+ String tagName,
+ String targetTagName) {
+ super(warehouse, databaseName, tableName, catalogConfig);
+ this.tagName = tagName;
+ this.targetTagName = targetTagName;
+ }
+
+ @Override
+ public void run() throws Exception {
+ if (StringUtils.isEmpty(tagName) ||
StringUtils.isEmpty(targetTagName)) {
+ throw new RuntimeException(
+ String.format(
+ "The specified tag name [%s] or target tag name
[%s] cannot be empty.",
+ tagName, targetTagName));
+ }
+ table.renameTag(tagName, targetTagName);
+ }
+}
diff --git
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/action/RenameTagActionFactory.java
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/action/RenameTagActionFactory.java
new file mode 100644
index 000000000..84f174d39
--- /dev/null
+++
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/action/RenameTagActionFactory.java
@@ -0,0 +1,71 @@
+/*
+ * 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.action;
+
+import org.apache.flink.api.java.tuple.Tuple3;
+
+import java.util.Map;
+import java.util.Optional;
+
+/** Factory to create {@link RenameTagActionFactory}. */
+public class RenameTagActionFactory implements ActionFactory {
+
+ private static final String IDENTIFIER = "rename_tag";
+
+ private static final String TAG_NAME = "tag_name";
+ private static final String TARGET_TAG_NAME = "target_tag_name";
+
+ @Override
+ public String identifier() {
+ return IDENTIFIER;
+ }
+
+ @Override
+ public Optional<Action> create(MultipleParameterToolAdapter params) {
+ checkRequiredArgument(params, TAG_NAME);
+ checkRequiredArgument(params, TARGET_TAG_NAME);
+
+ Tuple3<String, String, String> tablePath = getTablePath(params);
+ Map<String, String> catalogConfig = optionalConfigMap(params,
CATALOG_CONF);
+ String tagName = params.get(TAG_NAME);
+ String targetTagName = params.get(TARGET_TAG_NAME);
+
+ RenameTagAction action =
+ new RenameTagAction(
+ tablePath.f0,
+ tablePath.f1,
+ tablePath.f2,
+ catalogConfig,
+ tagName,
+ targetTagName);
+ return Optional.of(action);
+ }
+
+ @Override
+ public void printHelp() {
+ System.out.println("Action \"rename_tag\" rename a tag with a new tag
name.");
+ System.out.println();
+
+ System.out.println("Syntax:");
+ System.out.println(
+ " rename_tag --warehouse <warehouse_path> --tag_name
<tag_name> "
+ + "--target_tag_name <target_tag_name>");
+ System.out.println();
+ }
+}
diff --git
a/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/procedure/RenameTagProcedure.java
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/procedure/RenameTagProcedure.java
new file mode 100644
index 000000000..5476e807b
--- /dev/null
+++
b/paimon-flink/paimon-flink-common/src/main/java/org/apache/paimon/flink/procedure/RenameTagProcedure.java
@@ -0,0 +1,59 @@
+/*
+ * 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.procedure;
+
+import org.apache.paimon.catalog.Catalog;
+import org.apache.paimon.catalog.Identifier;
+import org.apache.paimon.table.Table;
+
+import org.apache.flink.table.annotation.ArgumentHint;
+import org.apache.flink.table.annotation.DataTypeHint;
+import org.apache.flink.table.annotation.ProcedureHint;
+import org.apache.flink.table.procedure.ProcedureContext;
+
+/**
+ * Create tag procedure. Usage:
+ *
+ * <pre><code>
+ * CALL sys.rename_tag('tableId', 'tagName', 'targetTagName')
+ * </code></pre>
+ */
+public class RenameTagProcedure extends ProcedureBase {
+ private static final String IDENTIFIER = "rename_tag";
+
+ @ProcedureHint(
+ argument = {
+ @ArgumentHint(name = "table", type = @DataTypeHint("STRING")),
+ @ArgumentHint(name = "tagName", type =
@DataTypeHint("STRING")),
+ @ArgumentHint(name = "targetTagName", type =
@DataTypeHint("STRING"))
+ })
+ public String[] call(
+ ProcedureContext procedureContext, String tableId, String tagName,
String targetTagName)
+ throws Catalog.TableNotExistException {
+ Table table = catalog.getTable(Identifier.fromString(tableId));
+ table.renameTag(tagName, targetTagName);
+ String ret = String.format("Rename [%s] to [%s] successfully.",
tagName, targetTagName);
+ return new String[] {ret};
+ }
+
+ @Override
+ public String identifier() {
+ return IDENTIFIER;
+ }
+}
diff --git
a/paimon-flink/paimon-flink-common/src/main/resources/META-INF/services/org.apache.paimon.factories.Factory
b/paimon-flink/paimon-flink-common/src/main/resources/META-INF/services/org.apache.paimon.factories.Factory
index 9eb450d33..3ae35ade5 100644
---
a/paimon-flink/paimon-flink-common/src/main/resources/META-INF/services/org.apache.paimon.factories.Factory
+++
b/paimon-flink/paimon-flink-common/src/main/resources/META-INF/services/org.apache.paimon.factories.Factory
@@ -36,6 +36,7 @@ org.apache.paimon.flink.action.MarkPartitionDoneActionFactory
org.apache.paimon.flink.action.CreateBranchActionFactory
org.apache.paimon.flink.action.DeleteBranchActionFactory
org.apache.paimon.flink.action.FastForwardActionFactory
+org.apache.paimon.flink.action.RenameTagActionFactory
org.apache.paimon.flink.action.RepairActionFactory
org.apache.paimon.flink.action.RewriteFileIndexActionFactory
org.apache.paimon.flink.action.ExpireSnapshotsActionFactory
@@ -67,6 +68,7 @@
org.apache.paimon.flink.procedure.privilege.DropPrivilegedUserProcedure
org.apache.paimon.flink.procedure.privilege.GrantPrivilegeToUserProcedure
org.apache.paimon.flink.procedure.privilege.RevokePrivilegeFromUserProcedure
org.apache.paimon.flink.procedure.RepairProcedure
+org.apache.paimon.flink.procedure.RenameTagProcedure
org.apache.paimon.flink.procedure.FastForwardProcedure
org.apache.paimon.flink.procedure.MarkPartitionDoneProcedure
org.apache.paimon.flink.procedure.CloneProcedure
diff --git
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/action/TagActionITCase.java
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/action/TagActionITCase.java
index aa98de387..7310a68df 100644
---
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/action/TagActionITCase.java
+++
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/action/TagActionITCase.java
@@ -240,6 +240,101 @@ public class TagActionITCase extends ActionITCaseBase {
assertThat(tagManager.tagExists("tag3")).isFalse();
}
+ @ParameterizedTest(name = "{0}")
+ @ValueSource(strings = {"action", "procedure_indexed", "procedure_named"})
+ public void testRenameTag(String invoker) throws Exception {
+ init(warehouse);
+
+ RowType rowType =
+ RowType.of(
+ new DataType[] {DataTypes.BIGINT(),
DataTypes.STRING()},
+ new String[] {"k", "v"});
+ FileStoreTable table =
+ createFileStoreTable(
+ rowType,
+ Collections.emptyList(),
+ Collections.singletonList("k"),
+ Collections.emptyList(),
+ Collections.emptyMap());
+
+ StreamWriteBuilder writeBuilder =
table.newStreamWriteBuilder().withCommitUser(commitUser);
+ write = writeBuilder.newWrite();
+ commit = writeBuilder.newCommit();
+
+ // 3 snapshots
+ writeData(rowData(1L, BinaryString.fromString("Hi")));
+ writeData(rowData(2L, BinaryString.fromString("Hello")));
+ writeData(rowData(3L, BinaryString.fromString("Paimon")));
+
+ TagManager tagManager = new TagManager(table.fileIO(),
table.location());
+ switch (invoker) {
+ case "action":
+ createAction(
+ CreateTagAction.class,
+ "create_tag",
+ "--warehouse",
+ warehouse,
+ "--database",
+ database,
+ "--table",
+ tableName,
+ "--tag_name",
+ "tag2")
+ .run();
+ break;
+ case "procedure_indexed":
+ executeSQL(
+ String.format(
+ "CALL sys.create_tag('%s.%s', 'tag2', 2)",
database, tableName));
+ break;
+ case "procedure_named":
+ executeSQL(
+ String.format(
+ "CALL sys.create_tag(`table` => '%s.%s', tag
=> 'tag2', snapshot_id => cast(2 as bigint))",
+ database, tableName));
+ break;
+ default:
+ throw new UnsupportedOperationException(invoker);
+ }
+ assertThat(tagManager.tagExists("tag2")).isTrue();
+
+ switch (invoker) {
+ case "action":
+ createAction(
+ RenameTagAction.class,
+ "rename_tag",
+ "--warehouse",
+ warehouse,
+ "--database",
+ database,
+ "--table",
+ tableName,
+ "--tag_name",
+ "tag2",
+ "--target_tag_name",
+ "tag3")
+ .run();
+ break;
+ case "procedure_indexed":
+ executeSQL(
+ String.format(
+ "CALL sys.rename_tag('%s.%s', 'tag2', 'tag3')",
+ database, tableName));
+ break;
+ case "procedure_named":
+ executeSQL(
+ String.format(
+ "CALL sys.rename_tag(`table` => '%s.%s',
tagName => 'tag2', targetTagName => 'tag3')",
+ database, tableName));
+ break;
+ default:
+ throw new UnsupportedOperationException(invoker);
+ }
+
+ assertThat(tagManager.tagExists("tag2")).isFalse();
+ assertThat(tagManager.tagExists("tag3")).isTrue();
+ }
+
@ParameterizedTest(name = "{0}")
@ValueSource(strings = {"action", "procedure_indexed", "procedure_named"})
public void testCreateLatestTag(String invoker) throws Exception {
diff --git
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/procedure/CreateTagsProcedureITCase.java
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/procedure/CreateTagsProcedureITCase.java
index aef952b6c..77e2b74c5 100644
---
a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/procedure/CreateTagsProcedureITCase.java
+++
b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/procedure/CreateTagsProcedureITCase.java
@@ -58,6 +58,30 @@ public class CreateTagsProcedureITCase extends
CatalogITCaseBase {
.containsExactlyInAnyOrder("+I[k, 2024-01-01]");
}
+ @Test
+ public void testRenameTag() {
+ sql(
+ "CREATE TABLE T ("
+ + " k STRING,"
+ + " dt STRING,"
+ + " PRIMARY KEY (k, dt) NOT ENFORCED"
+ + ") PARTITIONED BY (dt) WITH ("
+ + " 'bucket' = '1'"
+ + ")");
+ sql("insert into T values('k', '2024-01-01')");
+ sql("insert into T values('k2', '2024-01-02')");
+
+ sql("CALL sys.create_tag('default.T', 'tag1')");
+
+ assertThat(sql("select tag_name from
`T$tags`").stream().map(Row::toString))
+ .containsExactlyInAnyOrder("+I[tag1]");
+
+ sql("CALL sys.rename_tag('default.T', 'tag1', 'tag2')");
+
+ assertThat(sql("select tag_name from
`T$tags`").stream().map(Row::toString))
+ .containsExactlyInAnyOrder("+I[tag2]");
+ }
+
@Test
public void testThrowSnapshotNotExistException() {
sql(
diff --git
a/paimon-spark/paimon-spark-3.4/src/test/scala/org/apache/paimon/spark/sql/CreateAndDeleteTagProcedureTest.scala
b/paimon-spark/paimon-spark-3.4/src/test/scala/org/apache/paimon/spark/sql/CreateAndDeleteTagProcedureTest.scala
index c4b861b29..3f59e897e 100644
---
a/paimon-spark/paimon-spark-3.4/src/test/scala/org/apache/paimon/spark/sql/CreateAndDeleteTagProcedureTest.scala
+++
b/paimon-spark/paimon-spark-3.4/src/test/scala/org/apache/paimon/spark/sql/CreateAndDeleteTagProcedureTest.scala
@@ -75,8 +75,16 @@ class CreateAndDeleteTagProcedureTest extends
PaimonSparkTestBase with StreamTes
checkAnswer(
spark.sql("SELECT tag_name FROM paimon.test.`T$tags`"),
Row("test_tag") :: Nil)
+ // test rename_tag
checkAnswer(
- spark.sql("CALL paimon.sys.delete_tag(table => 'test.T', tag =>
'test_tag')"),
+ spark.sql(
+ "CALL paimon.sys.rename_tag(table => 'test.T', tag =>
'test_tag', target_tag => 'test_tag_1')"),
+ Row(true) :: Nil)
+ checkAnswer(
+ spark.sql("SELECT tag_name FROM paimon.test.`T$tags`"),
+ Row("test_tag_1") :: Nil)
+ checkAnswer(
+ spark.sql("CALL paimon.sys.delete_tag(table => 'test.T', tag =>
'test_tag_1')"),
Row(true) :: Nil)
checkAnswer(spark.sql("SELECT tag_name FROM
paimon.test.`T$tags`"), Nil)
checkAnswer(
diff --git
a/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/SparkProcedures.java
b/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/SparkProcedures.java
index b8e43c19c..1d5bd9df2 100644
---
a/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/SparkProcedures.java
+++
b/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/SparkProcedures.java
@@ -34,6 +34,7 @@ import
org.apache.paimon.spark.procedure.MigrateTableProcedure;
import org.apache.paimon.spark.procedure.Procedure;
import org.apache.paimon.spark.procedure.ProcedureBuilder;
import org.apache.paimon.spark.procedure.RemoveOrphanFilesProcedure;
+import org.apache.paimon.spark.procedure.RenameTagProcedure;
import org.apache.paimon.spark.procedure.RepairProcedure;
import org.apache.paimon.spark.procedure.ResetConsumerProcedure;
import org.apache.paimon.spark.procedure.RollbackProcedure;
@@ -61,6 +62,7 @@ public class SparkProcedures {
ImmutableMap.builder();
procedureBuilders.put("rollback", RollbackProcedure::builder);
procedureBuilders.put("create_tag", CreateTagProcedure::builder);
+ procedureBuilders.put("rename_tag", RenameTagProcedure::builder);
procedureBuilders.put(
"create_tag_from_timestamp",
CreateTagFromTimestampProcedure::builder);
procedureBuilders.put("delete_tag", DeleteTagProcedure::builder);
diff --git
a/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/procedure/RenameTagProcedure.java
b/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/procedure/RenameTagProcedure.java
new file mode 100644
index 000000000..73c0dc353
--- /dev/null
+++
b/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/procedure/RenameTagProcedure.java
@@ -0,0 +1,95 @@
+/*
+ * 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.spark.procedure;
+
+import org.apache.spark.sql.catalyst.InternalRow;
+import org.apache.spark.sql.connector.catalog.Identifier;
+import org.apache.spark.sql.connector.catalog.TableCatalog;
+import org.apache.spark.sql.types.DataTypes;
+import org.apache.spark.sql.types.Metadata;
+import org.apache.spark.sql.types.StructField;
+import org.apache.spark.sql.types.StructType;
+
+import static org.apache.spark.sql.types.DataTypes.StringType;
+
+/**
+ * Rename Tag Procedure. Usage:
+ *
+ * <pre><code>
+ * CALL sys.rename_tag(tag => 'tag0', target_tag => 'tag1')
+ * </code></pre>
+ */
+public class RenameTagProcedure extends BaseProcedure {
+
+ private static final ProcedureParameter[] PARAMETERS =
+ new ProcedureParameter[] {
+ ProcedureParameter.required("table", StringType),
+ ProcedureParameter.required("tag", StringType),
+ ProcedureParameter.required("target_tag", StringType)
+ };
+
+ private static final StructType OUTPUT_TYPE =
+ new StructType(
+ new StructField[] {
+ new StructField("result", DataTypes.BooleanType, true,
Metadata.empty())
+ });
+
+ protected RenameTagProcedure(TableCatalog tableCatalog) {
+ super(tableCatalog);
+ }
+
+ @Override
+ public ProcedureParameter[] parameters() {
+ return PARAMETERS;
+ }
+
+ @Override
+ public StructType outputType() {
+ return OUTPUT_TYPE;
+ }
+
+ @Override
+ public InternalRow[] call(InternalRow args) {
+ Identifier tableIdent = toIdentifier(args.getString(0),
PARAMETERS[0].name());
+ String tag = args.getString(1);
+ String targetTag = args.getString(2);
+
+ return modifyPaimonTable(
+ tableIdent,
+ table -> {
+ table.renameTag(tag, targetTag);
+ InternalRow outputRow = newInternalRow(true);
+ return new InternalRow[] {outputRow};
+ });
+ }
+
+ public static ProcedureBuilder builder() {
+ return new BaseProcedure.Builder<RenameTagProcedure>() {
+ @Override
+ public RenameTagProcedure doBuild() {
+ return new RenameTagProcedure(tableCatalog());
+ }
+ };
+ }
+
+ @Override
+ public String description() {
+ return "RenameTagProcedure";
+ }
+}
diff --git
a/paimon-spark/paimon-spark-common/src/test/scala/org/apache/paimon/spark/procedure/CreateAndDeleteTagProcedureTest.scala
b/paimon-spark/paimon-spark-common/src/test/scala/org/apache/paimon/spark/procedure/CreateAndDeleteTagProcedureTest.scala
index 296680919..3dc90db00 100644
---
a/paimon-spark/paimon-spark-common/src/test/scala/org/apache/paimon/spark/procedure/CreateAndDeleteTagProcedureTest.scala
+++
b/paimon-spark/paimon-spark-common/src/test/scala/org/apache/paimon/spark/procedure/CreateAndDeleteTagProcedureTest.scala
@@ -110,10 +110,20 @@ class CreateAndDeleteTagProcedureTest extends
PaimonSparkTestBase with StreamTes
spark.sql("SELECT tag_name FROM paimon.test.`T$tags`"),
Row("test_tag_1") :: Row("test_tag_2") :: Nil)
+ // test rename_tag
+ checkAnswer(
+ spark.sql(
+ "CALL paimon.sys.rename_tag(table => 'test.T', tag =>
'test_tag_1', target_tag => 'test_tag_3')"),
+ Row(true) :: Nil
+ )
+ checkAnswer(
+ spark.sql("SELECT tag_name FROM paimon.test.`T$tags`"),
+ Row("test_tag_2") :: Row("test_tag_3") :: Nil)
+
// delete test_tag_1 and test_tag_2
checkAnswer(
spark.sql(
- "CALL paimon.sys.delete_tag(table => 'test.T', tag =>
'test_tag_1,test_tag_2')"),
+ "CALL paimon.sys.delete_tag(table => 'test.T', tag =>
'test_tag_2,test_tag_3')"),
Row(true) :: Nil)
checkAnswer(spark.sql("SELECT tag_name FROM
paimon.test.`T$tags`"), Nil)