This is an automated email from the ASF dual-hosted git repository.

yiguolei pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-4.1 by this push:
     new aa135178aaf branch-4.1: [opt](compaction) support tablet-level 
compaction through SQL #66611 (#67203)
aa135178aaf is described below

commit aa135178aaf1ac0d012071d32838e748144a176f
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Fri Aug 28 01:34:05 2026 +0800

    branch-4.1: [opt](compaction) support tablet-level compaction through SQL 
#66611 (#67203)
    
    Cherry-picked from #66611
    
    Co-authored-by: dzr171712 <[email protected]>
---
 .../antlr4/org/apache/doris/nereids/DorisParser.g4 |   1 +
 .../main/java/org/apache/doris/catalog/Env.java    |  49 +++++++++
 .../org/apache/doris/cloud/catalog/CloudEnv.java   |  66 +++++++++++
 .../doris/nereids/parser/LogicalPlanBuilder.java   |   8 ++
 .../apache/doris/nereids/trees/plans/PlanType.java |   1 +
 .../plans/commands/AdminCompactTabletCommand.java  | 121 +++++++++++++++++++++
 .../trees/plans/visitor/CommandVisitor.java        |   5 +
 .../parser/AdminCompactTabletParserTest.java       |  45 ++++++++
 .../test_cloud_admin_compact_tablet.groovy         | 101 +++++++++++++++++
 .../compaction/test_admin_compact_tablet.groovy    | 101 +++++++++++++++++
 10 files changed, 498 insertions(+)

diff --git a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4 
b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4
index e7305beeb02..b7689c07a62 100644
--- a/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4
+++ b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4
@@ -654,6 +654,7 @@ supportedAdminStatement
     | ADMIN DIAGNOSE TABLET tabletId=INTEGER_VALUE                             
     #adminDiagnoseTablet
     | ADMIN SHOW REPLICA STATUS FROM baseTableRef (WHERE STATUS EQ|NEQ 
STRING_LITERAL)?   #adminShowReplicaStatus
     | ADMIN COMPACT TABLE baseTableRef (WHERE TYPE EQ STRING_LITERAL)?         
     #adminCompactTable
+    | ADMIN COMPACT TABLET tabletId=INTEGER_VALUE WHERE TYPE EQ STRING_LITERAL 
      #adminCompactTablet
     | ADMIN CHECK tabletList properties=propertyClause?                        
     #adminCheckTablets
     | ADMIN SHOW TABLET STORAGE FORMAT VERBOSE?                                
     #adminShowTabletStorageFormat
     | ADMIN SET (FRONTEND | (ALL FRONTENDS)) CONFIG
diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
index 3dad10564d1..2c79c25f417 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
@@ -7369,6 +7369,55 @@ public class Env {
         return result;
     }
 
+    public void compactTablet(long tabletId, String type) throws DdlException {
+        TabletMeta tabletMeta = 
getCurrentInvertedIndex().getTabletMeta(tabletId);
+        if (tabletMeta == null) {
+            throw new DdlException("Unknown tablet: " + tabletId);
+        }
+
+        Database db = getInternalCatalog().getDbNullable(tabletMeta.getDbId());
+        if (db == null) {
+            throw new DdlException("Unknown database for tablet: " + tabletId);
+        }
+        Table table = db.getTableNullable(tabletMeta.getTableId());
+        if (!(table instanceof OlapTable)) {
+            throw new DdlException("Unknown OLAP table for tablet: " + 
tabletId);
+        }
+        OlapTable olapTable = (OlapTable) table;
+
+        AgentBatchTask batchTask = new AgentBatchTask();
+        olapTable.readLock();
+        try {
+            Partition partition = 
olapTable.getPartition(tabletMeta.getPartitionId());
+            if (partition == null) {
+                throw new DdlException("Unknown partition for tablet: " + 
tabletId);
+            }
+            MaterializedIndex index = 
partition.getIndex(tabletMeta.getIndexId());
+            if (index == null || !index.getState().isVisible()) {
+                throw new DdlException("Tablet " + tabletId + " is not in a 
visible index");
+            }
+            Tablet tablet = index.getTablet(tabletId);
+            if (tablet == null) {
+                throw new DdlException("Tablet " + tabletId + " does not 
belong to its metadata index");
+            }
+
+            int schemaHash = olapTable.getSchemaHashByIndexId(index.getId());
+            LOG.info("Tablet compaction. database: {}, table: {}, tablet: {}, 
type: {}",
+                    db.getFullName(), olapTable.getName(), tabletId, type);
+            for (Replica replica : tablet.getReplicas()) {
+                batchTask.addTask(new 
CompactionTask(replica.getBackendIdWithoutException(), db.getId(),
+                        olapTable.getId(), partition.getId(), index.getId(), 
tabletId, schemaHash, type));
+            }
+        } finally {
+            olapTable.readUnlock();
+        }
+
+        if (batchTask.getTaskNum() == 0) {
+            throw new DdlException("No replica found for tablet: " + tabletId);
+        }
+        AgentTaskExecutor.submit(batchTask);
+    }
+
     public void compactTable(String dbName, String tableName, String type, 
List<String> partitionNames)
             throws DdlException {
         Database db = getInternalCatalog().getDbOrDdlException(dbName);
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/cloud/catalog/CloudEnv.java 
b/fe/fe-core/src/main/java/org/apache/doris/cloud/catalog/CloudEnv.java
index f4393b36e72..b482ddf260e 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/cloud/catalog/CloudEnv.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/cloud/catalog/CloudEnv.java
@@ -26,7 +26,9 @@ import 
org.apache.doris.catalog.MaterializedIndex.IndexExtState;
 import org.apache.doris.catalog.OlapTable;
 import org.apache.doris.catalog.Partition;
 import org.apache.doris.catalog.Replica;
+import org.apache.doris.catalog.Table;
 import org.apache.doris.catalog.Tablet;
+import org.apache.doris.catalog.TabletMeta;
 import org.apache.doris.cloud.CacheHotspotManager;
 import org.apache.doris.cloud.CloudWarmUpJob;
 import org.apache.doris.cloud.CloudWarmUpJob.JobState;
@@ -501,6 +503,70 @@ public class CloudEnv extends Env {
         }
     }
 
+    @Override
+    public void compactTablet(long tabletId, String type) throws DdlException {
+        TabletMeta tabletMeta = 
Env.getCurrentInvertedIndex().getTabletMeta(tabletId);
+        if (tabletMeta == null) {
+            throw new DdlException("Unknown tablet: " + tabletId);
+        }
+
+        Database db = getInternalCatalog().getDbNullable(tabletMeta.getDbId());
+        if (db == null) {
+            throw new DdlException("Unknown database for tablet: " + tabletId);
+        }
+        Table table = db.getTableNullable(tabletMeta.getTableId());
+        if (!(table instanceof OlapTable)) {
+            throw new DdlException("Unknown OLAP table for tablet: " + 
tabletId);
+        }
+        OlapTable olapTable = (OlapTable) table;
+
+        List<PendingCloudCompactionTablet> pending = new ArrayList<>();
+        olapTable.readLock();
+        try {
+            Partition partition = 
olapTable.getPartition(tabletMeta.getPartitionId());
+            if (partition == null) {
+                throw new DdlException("Unknown partition for tablet: " + 
tabletId);
+            }
+            MaterializedIndex index = 
partition.getIndex(tabletMeta.getIndexId());
+            if (index == null || !index.getState().isVisible()) {
+                throw new DdlException("Tablet " + tabletId + " is not in a 
visible index");
+            }
+            Tablet tablet = index.getTablet(tabletId);
+            if (tablet == null) {
+                throw new DdlException("Tablet " + tabletId + " does not 
belong to its metadata index");
+            }
+
+            int schemaHash = olapTable.getSchemaHashByIndexId(index.getId());
+            LOG.info("Cloud tablet compaction. database={}, table={}, 
tablet={}, type={}",
+                    db.getFullName(), olapTable.getName(), tabletId, type);
+            for (Replica replica : tablet.getReplicas()) {
+                pending.add(new 
PendingCloudCompactionTablet(partition.getId(), index.getId(), tabletId,
+                        schemaHash, replica));
+            }
+        } finally {
+            olapTable.readUnlock();
+        }
+
+        AgentBatchTask batchTask = new AgentBatchTask();
+        for (PendingCloudCompactionTablet pendingTablet : pending) {
+            long backendId;
+            try {
+                backendId = pendingTablet.replica.getBackendId();
+            } catch (UserException e) {
+                throw new DdlException("failed to resolve backend for tablet " 
+ tabletId
+                        + ": " + e.getMessage());
+            }
+            batchTask.addTask(new CompactionTask(backendId, db.getId(), 
olapTable.getId(),
+                    pendingTablet.partitionId, pendingTablet.indexId, 
pendingTablet.tabletId,
+                    pendingTablet.schemaHash, type));
+        }
+
+        if (batchTask.getTaskNum() == 0) {
+            throw new DdlException("No replica found for tablet: " + tabletId);
+        }
+        AgentTaskExecutor.submit(batchTask);
+    }
+
     @Override
     public void compactTable(String dbName, String tableName, String type, 
List<String> partitionNames)
             throws DdlException {
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
index 7d9d2f05e38..9f96cd0a0c7 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java
@@ -92,6 +92,7 @@ import 
org.apache.doris.nereids.DorisParser.AddRollupClauseContext;
 import org.apache.doris.nereids.DorisParser.AdminCancelRebalanceDiskContext;
 import org.apache.doris.nereids.DorisParser.AdminCheckTabletsContext;
 import org.apache.doris.nereids.DorisParser.AdminCompactTableContext;
+import org.apache.doris.nereids.DorisParser.AdminCompactTabletContext;
 import org.apache.doris.nereids.DorisParser.AdminDiagnoseTabletContext;
 import org.apache.doris.nereids.DorisParser.AdminRebalanceDiskContext;
 import org.apache.doris.nereids.DorisParser.AdminRotateTdeRootKeyContext;
@@ -638,6 +639,7 @@ import 
org.apache.doris.nereids.trees.plans.commands.AdminCancelRepairTableComma
 import org.apache.doris.nereids.trees.plans.commands.AdminCheckTabletsCommand;
 import org.apache.doris.nereids.trees.plans.commands.AdminCleanTrashCommand;
 import org.apache.doris.nereids.trees.plans.commands.AdminCompactTableCommand;
+import org.apache.doris.nereids.trees.plans.commands.AdminCompactTabletCommand;
 import org.apache.doris.nereids.trees.plans.commands.AdminCopyTabletCommand;
 import 
org.apache.doris.nereids.trees.plans.commands.AdminCreateClusterSnapshotCommand;
 import 
org.apache.doris.nereids.trees.plans.commands.AdminDropClusterSnapshotCommand;
@@ -1944,6 +1946,12 @@ public class LogicalPlanBuilder extends 
DorisParserBaseVisitor<Object> {
         return new AdminCompactTableCommand(tableRefInfo, equalTo);
     }
 
+    @Override
+    public AdminCompactTabletCommand 
visitAdminCompactTablet(AdminCompactTabletContext ctx) {
+        String compactionType = stripQuotes(ctx.STRING_LITERAL().getText());
+        return new 
AdminCompactTabletCommand(Long.parseLong(ctx.tabletId.getText()), 
compactionType);
+    }
+
     @Override
     public AlterMTMVCommand visitAlterMTMV(AlterMTMVContext ctx) {
         List<String> nameParts = visitMultipartIdentifier(ctx.mvName);
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java
index b6aea691fc9..683939e70c8 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java
@@ -204,6 +204,7 @@ public enum PlanType {
     ALTER_AUTHENTICATION_INTEGRATION_COMMAND,
     ADD_CONSTRAINT_COMMAND,
     ADMIN_COMPACT_TABLE_COMMAND,
+    ADMIN_COMPACT_TABLET_COMMAND,
     DROP_CONSTRAINT_COMMAND,
     SHOW_CONSTRAINTS_COMMAND,
     REFRESH_MTMV_COMMAND,
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AdminCompactTabletCommand.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AdminCompactTabletCommand.java
new file mode 100644
index 00000000000..fa4ed1fada3
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/AdminCompactTabletCommand.java
@@ -0,0 +1,121 @@
+// 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.doris.nereids.trees.plans.commands;
+
+import org.apache.doris.analysis.StmtType;
+import org.apache.doris.catalog.Database;
+import org.apache.doris.catalog.Env;
+import org.apache.doris.catalog.OlapTable;
+import org.apache.doris.catalog.Table;
+import org.apache.doris.catalog.TabletMeta;
+import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.ErrorReport;
+import org.apache.doris.common.UserException;
+import org.apache.doris.datasource.InternalCatalog;
+import org.apache.doris.mysql.privilege.PrivPredicate;
+import org.apache.doris.nereids.trees.plans.PlanType;
+import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
+import org.apache.doris.qe.ConnectContext;
+import org.apache.doris.qe.StmtExecutor;
+
+import java.util.Locale;
+
+/**
+ * Command for triggering compaction on one tablet.
+ */
+public class AdminCompactTabletCommand extends Command implements 
ForwardWithSync {
+    private enum CompactionType {
+        CUMULATIVE("cumulative"),
+        BASE("base"),
+        FULL("full");
+
+        private final String value;
+
+        CompactionType(String value) {
+            this.value = value;
+        }
+
+        private static CompactionType fromString(String compactionType) throws 
AnalysisException {
+            try {
+                return valueOf(compactionType.toUpperCase(Locale.ROOT));
+            } catch (IllegalArgumentException e) {
+                throw new AnalysisException("Where clause should looks like: 
type = 'BASE/CUMULATIVE/FULL'");
+            }
+        }
+    }
+
+    private final long tabletId;
+    private final String compactionType;
+    private CompactionType typeFilter;
+
+    public AdminCompactTabletCommand(long tabletId, String compactionType) {
+        super(PlanType.ADMIN_COMPACT_TABLET_COMMAND);
+        this.tabletId = tabletId;
+        this.compactionType = compactionType;
+    }
+
+    public long getTabletId() {
+        return tabletId;
+    }
+
+    @Override
+    public void run(ConnectContext ctx, StmtExecutor executor) throws 
Exception {
+        validate(ctx);
+        ctx.getEnv().compactTablet(tabletId, typeFilter.value);
+    }
+
+    private void validate(ConnectContext ctx) throws UserException {
+        validateTablet(ctx);
+        typeFilter = CompactionType.fromString(compactionType);
+    }
+
+    private void validateTablet(ConnectContext ctx) throws UserException {
+        TabletMeta tabletMeta = 
Env.getCurrentInvertedIndex().getTabletMeta(tabletId);
+        if (tabletMeta == null) {
+            throw new AnalysisException("Unknown tablet: " + tabletId);
+        }
+
+        Database db = 
Env.getCurrentEnv().getInternalCatalog().getDbNullable(tabletMeta.getDbId());
+        if (db == null) {
+            throw new AnalysisException("Unknown database for tablet: " + 
tabletId);
+        }
+        Table table = db.getTableNullable(tabletMeta.getTableId());
+        if (!(table instanceof OlapTable)) {
+            throw new AnalysisException("Unknown OLAP table for tablet: " + 
tabletId);
+        }
+
+        boolean hasGlobalAdmin = Env.getCurrentEnv().getAccessManager()
+                .checkGlobalPriv(ctx, PrivPredicate.ADMIN);
+        boolean hasTableAlter = 
Env.getCurrentEnv().getAccessManager().checkTblPriv(ctx,
+                InternalCatalog.INTERNAL_CATALOG_NAME, db.getFullName(), 
table.getName(), PrivPredicate.ALTER);
+        if (!hasGlobalAdmin && !hasTableAlter) {
+            
ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, 
"ALTER");
+        }
+    }
+
+    @Override
+    public <R, C> R accept(PlanVisitor<R, C> visitor, C context) {
+        return visitor.visitAdminCompactTabletCommand(this, context);
+    }
+
+    @Override
+    public StmtType stmtType() {
+        return StmtType.ADMIN;
+    }
+}
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/CommandVisitor.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/CommandVisitor.java
index ae20f80f36f..61f5ae23df0 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/CommandVisitor.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/CommandVisitor.java
@@ -23,6 +23,7 @@ import 
org.apache.doris.nereids.trees.plans.commands.AdminCancelRepairTableComma
 import org.apache.doris.nereids.trees.plans.commands.AdminCheckTabletsCommand;
 import org.apache.doris.nereids.trees.plans.commands.AdminCleanTrashCommand;
 import org.apache.doris.nereids.trees.plans.commands.AdminCompactTableCommand;
+import org.apache.doris.nereids.trees.plans.commands.AdminCompactTabletCommand;
 import org.apache.doris.nereids.trees.plans.commands.AdminCopyTabletCommand;
 import 
org.apache.doris.nereids.trees.plans.commands.AdminCreateClusterSnapshotCommand;
 import 
org.apache.doris.nereids.trees.plans.commands.AdminDropClusterSnapshotCommand;
@@ -434,6 +435,10 @@ public interface CommandVisitor<R, C> {
         return visitCommand(adminCompactTableCommand, context);
     }
 
+    default R visitAdminCompactTabletCommand(AdminCompactTabletCommand 
adminCompactTabletCommand, C context) {
+        return visitCommand(adminCompactTabletCommand, context);
+    }
+
     default R visitAdminCleanTrashCommand(AdminCleanTrashCommand 
adminCleanTrashCommand, C context) {
         return visitCommand(adminCleanTrashCommand, context);
     }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/AdminCompactTabletParserTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/AdminCompactTabletParserTest.java
new file mode 100644
index 00000000000..6baac7b9509
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/parser/AdminCompactTabletParserTest.java
@@ -0,0 +1,45 @@
+// 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.doris.nereids.parser;
+
+import org.apache.doris.analysis.StmtType;
+import org.apache.doris.nereids.exceptions.ParseException;
+import org.apache.doris.nereids.trees.plans.commands.AdminCompactTabletCommand;
+import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class AdminCompactTabletParserTest {
+    private final NereidsParser parser = new NereidsParser();
+
+    @Test
+    public void testParseTabletCompaction() {
+        LogicalPlan plan = parser.parseSingle(
+                "ADMIN COMPACT TABLET 12345 WHERE TYPE = 'CUMULATIVE'");
+        Assertions.assertInstanceOf(AdminCompactTabletCommand.class, plan);
+        Assertions.assertEquals(StmtType.ADMIN, plan.stmtType());
+        Assertions.assertEquals(12345L, ((AdminCompactTabletCommand) 
plan).getTabletId());
+    }
+
+    @Test
+    public void testRejectTabletCompactionWithoutType() {
+        Assertions.assertThrows(ParseException.class,
+                () -> parser.parseSingle("ADMIN COMPACT TABLET 12345"));
+    }
+}
diff --git 
a/regression-test/suites/cloud_p0/compaction/test_cloud_admin_compact_tablet.groovy
 
b/regression-test/suites/cloud_p0/compaction/test_cloud_admin_compact_tablet.groovy
new file mode 100644
index 00000000000..03cd33b7ca2
--- /dev/null
+++ 
b/regression-test/suites/cloud_p0/compaction/test_cloud_admin_compact_tablet.groovy
@@ -0,0 +1,101 @@
+// 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.
+
+suite("test_cloud_admin_compact_tablet", "p0") {
+    if (!isCloudMode()) {
+        return
+    }
+
+    def tableName = "test_cloud_admin_compact_tablet"
+    sql "DROP TABLE IF EXISTS ${tableName}"
+    sql """
+        CREATE TABLE ${tableName} (
+            k INT,
+            v INT
+        ) DUPLICATE KEY(k)
+        DISTRIBUTED BY HASH(k) BUCKETS 1
+        PROPERTIES (
+            "replication_num" = "1",
+            "disable_auto_compaction" = "true"
+        )
+    """
+
+    def tablets = sql_return_maparray "SHOW TABLETS FROM ${tableName}"
+    assertEquals(1, tablets.size())
+    def tabletId = tablets[0].TabletId
+    def backendId = tablets[0].BackendId
+
+    def backendIdToBackendIp = [:]
+    def backendIdToBackendHttpPort = [:]
+    getBackendIpHttpPort(backendIdToBackendIp, backendIdToBackendHttpPort)
+    def beHost = backendIdToBackendIp["${backendId}"]
+    def bePort = backendIdToBackendHttpPort["${backendId}"]
+
+    def showTabletCompaction = {
+        sql "SELECT COUNT(*) FROM ${tableName}"
+        def (code, stdout, stderr) = be_show_tablet_status(beHost, bePort, 
tabletId)
+        assertEquals(0, code)
+        return parseJson(stdout.trim())
+    }
+
+    def countDataRowsets = { json ->
+        return json.rowsets.findAll { it.contains(" DATA ") }.size()
+    }
+
+    def epochTime = "1970-01-01 08:00:00.000"
+    for (int i = 1; i <= 8; i++) {
+        sql "INSERT INTO ${tableName} VALUES (${i}, ${i})"
+    }
+
+    def before = showTabletCompaction()
+    def rowsetsBefore = countDataRowsets(before)
+    assertTrue(rowsetsBefore >= 8,
+            "expected >= 8 data rowsets before tablet compaction, got 
${rowsetsBefore}")
+    assertEquals(epochTime, before["last cumulative success time"])
+
+    sql "ADMIN COMPACT TABLET ${tabletId} WHERE TYPE = 'CUMULATIVE'"
+    def after = null
+    def deadline = System.currentTimeMillis() + 90 * 1000L
+    while (System.currentTimeMillis() < deadline) {
+        after = showTabletCompaction()
+        if (after["last cumulative success time"] != epochTime
+                && countDataRowsets(after) < rowsetsBefore) {
+            break
+        }
+        sleep(1000)
+    }
+
+    assertNotNull(after)
+    assertNotEquals(epochTime, after["last cumulative success time"])
+    assertTrue(countDataRowsets(after) < rowsetsBefore,
+            "tablet cumulative did not reduce rowset count: ${after.rowsets}")
+
+    test {
+        sql "ADMIN COMPACT TABLET ${tabletId} WHERE TYPE = 'UNKNOWN'"
+        exception "BASE/CUMULATIVE/FULL"
+    }
+
+    test {
+        sql "ADMIN COMPACT TABLET ${tabletId}"
+        exception "WHERE"
+    }
+
+    def rowCount = sql "SELECT COUNT(*) FROM ${tableName}"
+    assertEquals(8, rowCount[0][0])
+    def rows = sql "SELECT * FROM ${tableName} ORDER BY k"
+    assertEquals((1..8).collect { [it, it] }, rows)
+}
diff --git a/regression-test/suites/compaction/test_admin_compact_tablet.groovy 
b/regression-test/suites/compaction/test_admin_compact_tablet.groovy
new file mode 100644
index 00000000000..aca867d816f
--- /dev/null
+++ b/regression-test/suites/compaction/test_admin_compact_tablet.groovy
@@ -0,0 +1,101 @@
+// 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.
+
+suite("test_admin_compact_tablet", "p0") {
+    if (isCloudMode()) {
+        return
+    }
+
+    def tableName = "test_admin_compact_tablet"
+    sql "DROP TABLE IF EXISTS ${tableName}"
+    sql """
+        CREATE TABLE ${tableName} (
+            k INT,
+            v INT
+        ) DUPLICATE KEY(k)
+        DISTRIBUTED BY HASH(k) BUCKETS 1
+        PROPERTIES (
+            "replication_num" = "1",
+            "disable_auto_compaction" = "true"
+        )
+    """
+
+    def tablets = sql_return_maparray "SHOW TABLETS FROM ${tableName}"
+    assertEquals(1, tablets.size())
+    def tabletId = tablets[0].TabletId
+    def backendId = tablets[0].BackendId
+
+    def backendIdToBackendIp = [:]
+    def backendIdToBackendHttpPort = [:]
+    getBackendIpHttpPort(backendIdToBackendIp, backendIdToBackendHttpPort)
+    def beHost = backendIdToBackendIp["${backendId}"]
+    def bePort = backendIdToBackendHttpPort["${backendId}"]
+
+    def showTabletCompaction = {
+        def (code, stdout, stderr) = be_show_tablet_status(beHost, bePort, 
tabletId)
+        assertEquals(0, code)
+        return parseJson(stdout.trim())
+    }
+
+    def countDataRowsets = { json ->
+        return json.rowsets.findAll { it.contains(" DATA ") }.size()
+    }
+
+    def epochTime = "1970-01-01 08:00:00.000"
+    for (int i = 1; i <= 8; i++) {
+        sql "INSERT INTO ${tableName} VALUES (${i}, ${i})"
+    }
+
+    def before = showTabletCompaction()
+    def rowsetsBefore = countDataRowsets(before)
+    assertTrue(rowsetsBefore >= 8,
+            "expected >= 8 data rowsets before tablet compaction, got 
${rowsetsBefore}")
+    assertEquals(epochTime, before["last cumulative success time"])
+
+    sql "ADMIN COMPACT TABLET ${tabletId} WHERE TYPE = 'CUMULATIVE'"
+    def after = null
+    def deadline = System.currentTimeMillis() + 60 * 1000L
+    while (System.currentTimeMillis() < deadline) {
+        after = showTabletCompaction()
+        if (after["last cumulative success time"] != epochTime
+                && countDataRowsets(after) < rowsetsBefore) {
+            break
+        }
+        sleep(500)
+    }
+
+    assertNotNull(after)
+    assertNotEquals(epochTime, after["last cumulative success time"])
+    assertEquals("[OK]", after["last cumulative status"])
+    assertTrue(countDataRowsets(after) < rowsetsBefore,
+            "tablet cumulative did not reduce rowset count: ${after.rowsets}")
+
+    test {
+        sql "ADMIN COMPACT TABLET ${tabletId} WHERE TYPE = 'UNKNOWN'"
+        exception "BASE/CUMULATIVE/FULL"
+    }
+
+    test {
+        sql "ADMIN COMPACT TABLET ${tabletId}"
+        exception "WHERE"
+    }
+
+    def rowCount = sql "SELECT COUNT(*) FROM ${tableName}"
+    assertEquals(8, rowCount[0][0])
+    def rows = sql "SELECT * FROM ${tableName} ORDER BY k"
+    assertEquals((1..8).collect { [it, it] }, rows)
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to