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

jiangtian pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/master by this push:
     new a6d65cb36cb Support delete object file (#16945)
a6d65cb36cb is described below

commit a6d65cb36cbce39429e58d486bac6aa7f9f18d0b
Author: Haonan <[email protected]>
AuthorDate: Tue Dec 30 14:15:23 2025 +0800

    Support delete object file (#16945)
    
    * deving
    
    * dev
    
    * remove empty object dir
    
    * optimize drop table
    
    * add some IT
    
    * Fix error
    
    * Add IT
    
    * fix check object dir error
    
    * fix review
---
 .../relational/it/session/IoTDBObjectDeleteIT.java | 363 +++++++++++++++++++++
 .../it/session/IoTDBObjectDeleteIT2.java           |  48 +++
 .../iotdb/db/storageengine/StorageEngine.java      |   3 +
 .../db/storageengine/dataregion/DataRegion.java    | 107 +++++-
 .../dataregion/modification/DeletionPredicate.java |   4 +
 .../modification/TableDeletionEntry.java           |   9 +
 .../db/storageengine/rescon/disk/TierManager.java  |  49 +++
 .../org/apache/iotdb/db/utils/ObjectTypeUtils.java |  32 ++
 8 files changed, 611 insertions(+), 4 deletions(-)

diff --git 
a/integration-test/src/test/java/org/apache/iotdb/relational/it/session/IoTDBObjectDeleteIT.java
 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/session/IoTDBObjectDeleteIT.java
new file mode 100644
index 00000000000..0c697424873
--- /dev/null
+++ 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/session/IoTDBObjectDeleteIT.java
@@ -0,0 +1,363 @@
+/*
+ * 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.iotdb.relational.it.session;
+
+import org.apache.iotdb.isession.ITableSession;
+import org.apache.iotdb.isession.SessionDataSet;
+import org.apache.iotdb.it.env.EnvFactory;
+import org.apache.iotdb.it.env.cluster.node.DataNodeWrapper;
+import org.apache.iotdb.it.framework.IoTDBTestRunner;
+import org.apache.iotdb.itbase.category.TableClusterIT;
+import org.apache.iotdb.itbase.category.TableLocalStandaloneIT;
+import org.apache.iotdb.rpc.IoTDBConnectionException;
+import org.apache.iotdb.rpc.StatementExecutionException;
+
+import com.google.common.io.BaseEncoding;
+import org.apache.tsfile.enums.ColumnCategory;
+import org.apache.tsfile.enums.TSDataType;
+import org.apache.tsfile.utils.Binary;
+import org.apache.tsfile.write.record.Tablet;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.Assert.assertNull;
+
+@RunWith(IoTDBTestRunner.class)
+@Category({TableLocalStandaloneIT.class, TableClusterIT.class})
+public class IoTDBObjectDeleteIT {
+
+  @BeforeClass
+  public static void classSetUp() throws Exception {
+    EnvFactory.getEnv().initClusterEnvironment();
+  }
+
+  @Before
+  public void setUp() throws Exception {
+    try (ITableSession session = 
EnvFactory.getEnv().getTableSessionConnection()) {
+      session.executeNonQueryStatement("CREATE DATABASE IF NOT EXISTS db1");
+    }
+  }
+
+  @After
+  public void tearDown() throws Exception {
+    try (ITableSession session = 
EnvFactory.getEnv().getTableSessionConnection()) {
+      session.executeNonQueryStatement("DROP DATABASE IF EXISTS db1");
+    }
+  }
+
+  @AfterClass
+  public static void classTearDown() {
+    EnvFactory.getEnv().cleanClusterEnvironment();
+  }
+
+  @Test
+  public void dropObjectTableTest()
+      throws IoTDBConnectionException, StatementExecutionException, 
IOException {
+    String testObject =
+        System.getProperty("user.dir")
+            + File.separator
+            + "target"
+            + File.separator
+            + "test-classes"
+            + File.separator
+            + "object-example.pt";
+
+    try (ITableSession session = 
EnvFactory.getEnv().getTableSessionConnection()) {
+      session.executeNonQueryStatement("USE \"db1\"");
+      // insert table data by tablet
+      List<String> columnNameList =
+          Arrays.asList("region_id", "plant_id", "device_id", "temperature", 
"file");
+      List<TSDataType> dataTypeList =
+          Arrays.asList(
+              TSDataType.STRING,
+              TSDataType.STRING,
+              TSDataType.STRING,
+              TSDataType.FLOAT,
+              TSDataType.OBJECT);
+      List<ColumnCategory> columnTypeList =
+          new ArrayList<>(
+              Arrays.asList(
+                  ColumnCategory.TAG,
+                  ColumnCategory.TAG,
+                  ColumnCategory.TAG,
+                  ColumnCategory.FIELD,
+                  ColumnCategory.FIELD));
+      Tablet tablet = new Tablet("object_table", columnNameList, dataTypeList, 
columnTypeList, 1);
+      int rowIndex = tablet.getRowSize();
+      tablet.addTimestamp(rowIndex, 1);
+      tablet.addValue(rowIndex, 0, "1");
+      tablet.addValue(rowIndex, 1, "5");
+      tablet.addValue(rowIndex, 2, "3");
+      tablet.addValue(rowIndex, 3, 37.6F);
+      tablet.addValue(rowIndex, 4, true, 0, 
Files.readAllBytes(Paths.get(testObject)));
+      session.insert(tablet);
+      tablet.reset();
+
+      try (SessionDataSet dataSet =
+          session.executeQueryStatement(
+              "select READ_OBJECT(file) from object_table where time = 1")) {
+        SessionDataSet.DataIterator iterator = dataSet.iterator();
+        while (iterator.next()) {
+          Binary binary = iterator.getBlob(1);
+          Assert.assertArrayEquals(Files.readAllBytes(Paths.get(testObject)), 
binary.getValues());
+        }
+        session.executeNonQueryStatement("DROP TABLE IF EXISTS object_table");
+      }
+    }
+
+    // test object file path
+    boolean success = false;
+    for (DataNodeWrapper dataNodeWrapper : 
EnvFactory.getEnv().getDataNodeWrapperList()) {
+      String objectDirStr = dataNodeWrapper.getDataNodeObjectDir();
+      File objectDir = new File(objectDirStr);
+      if (objectDir.exists() && objectDir.isDirectory()) {
+        File[] regionDirs = objectDir.listFiles();
+        if (regionDirs != null) {
+          for (File regionDir : regionDirs) {
+            if (regionDir.isDirectory()) {
+              File objectFile =
+                  new File(
+                      regionDir,
+                      convertPathString("object_table")
+                          + File.separator
+                          + convertPathString("1")
+                          + File.separator
+                          + convertPathString("5")
+                          + File.separator
+                          + convertPathString("3")
+                          + File.separator
+                          + convertPathString("file")
+                          + File.separator
+                          + "1.bin");
+              if (objectFile.exists() && objectFile.isFile()) {
+                success = true;
+              }
+            }
+          }
+        }
+      }
+    }
+    Assert.assertFalse(success);
+  }
+
+  @Test
+  public void dropObjectColumnTest()
+      throws IoTDBConnectionException, StatementExecutionException, 
IOException {
+    String testObject =
+        System.getProperty("user.dir")
+            + File.separator
+            + "target"
+            + File.separator
+            + "test-classes"
+            + File.separator
+            + "object-example.pt";
+
+    try (ITableSession session = 
EnvFactory.getEnv().getTableSessionConnection()) {
+      session.executeNonQueryStatement("USE \"db1\"");
+      // insert table data by tablet
+      List<String> columnNameList =
+          Arrays.asList("region_id", "plant_id", "device_id", "temperature", 
"file");
+      List<TSDataType> dataTypeList =
+          Arrays.asList(
+              TSDataType.STRING,
+              TSDataType.STRING,
+              TSDataType.STRING,
+              TSDataType.FLOAT,
+              TSDataType.OBJECT);
+      List<ColumnCategory> columnTypeList =
+          new ArrayList<>(
+              Arrays.asList(
+                  ColumnCategory.TAG,
+                  ColumnCategory.TAG,
+                  ColumnCategory.TAG,
+                  ColumnCategory.FIELD,
+                  ColumnCategory.FIELD));
+      Tablet tablet = new Tablet("object_table", columnNameList, dataTypeList, 
columnTypeList, 1);
+      int rowIndex = tablet.getRowSize();
+      tablet.addTimestamp(rowIndex, 1);
+      tablet.addValue(rowIndex, 0, "1");
+      tablet.addValue(rowIndex, 1, "5");
+      tablet.addValue(rowIndex, 2, "3");
+      tablet.addValue(rowIndex, 3, 37.6F);
+      tablet.addValue(rowIndex, 4, true, 0, 
Files.readAllBytes(Paths.get(testObject)));
+      session.insert(tablet);
+      tablet.reset();
+
+      try (SessionDataSet dataSet =
+          session.executeQueryStatement(
+              "select READ_OBJECT(file) from object_table where time = 1")) {
+        SessionDataSet.DataIterator iterator = dataSet.iterator();
+        while (iterator.next()) {
+          Binary binary = iterator.getBlob(1);
+          Assert.assertArrayEquals(Files.readAllBytes(Paths.get(testObject)), 
binary.getValues());
+        }
+        session.executeNonQueryStatement("ALTER TABLE object_table drop column 
file");
+      }
+    }
+
+    // test object file path
+    boolean success = false;
+    for (DataNodeWrapper dataNodeWrapper : 
EnvFactory.getEnv().getDataNodeWrapperList()) {
+      String objectDirStr = dataNodeWrapper.getDataNodeObjectDir();
+      File objectDir = new File(objectDirStr);
+      if (objectDir.exists() && objectDir.isDirectory()) {
+        File[] regionDirs = objectDir.listFiles();
+        if (regionDirs != null) {
+          for (File regionDir : regionDirs) {
+            if (regionDir.isDirectory()) {
+              File objectFile =
+                  new File(
+                      regionDir,
+                      convertPathString("object_table")
+                          + File.separator
+                          + convertPathString("1")
+                          + File.separator
+                          + convertPathString("5")
+                          + File.separator
+                          + convertPathString("3")
+                          + File.separator
+                          + convertPathString("file")
+                          + File.separator
+                          + "1.bin");
+              if (objectFile.exists() && objectFile.isFile()) {
+                success = true;
+              }
+            }
+          }
+        }
+      }
+    }
+    Assert.assertFalse(success);
+  }
+
+  @Test
+  public void deleteObjectSegmentsTest()
+      throws IoTDBConnectionException, StatementExecutionException, 
IOException {
+    String testObject =
+        System.getProperty("user.dir")
+            + File.separator
+            + "target"
+            + File.separator
+            + "test-classes"
+            + File.separator
+            + "object-example.pt";
+    byte[] objectBytes = Files.readAllBytes(Paths.get(testObject));
+    List<byte[]> objectSegments = new ArrayList<>();
+    for (int i = 0; i < objectBytes.length; i += 512) {
+      objectSegments.add(Arrays.copyOfRange(objectBytes, i, Math.min(i + 512, 
objectBytes.length)));
+    }
+
+    try (ITableSession session = 
EnvFactory.getEnv().getTableSessionConnection()) {
+      session.executeNonQueryStatement("USE \"db1\"");
+      // insert table data by tablet
+      List<String> columnNameList =
+          Arrays.asList("region_id", "plant_id", "device_id", "temperature", 
"file");
+      List<TSDataType> dataTypeList =
+          Arrays.asList(
+              TSDataType.STRING,
+              TSDataType.STRING,
+              TSDataType.STRING,
+              TSDataType.FLOAT,
+              TSDataType.OBJECT);
+      List<ColumnCategory> columnTypeList =
+          new ArrayList<>(
+              Arrays.asList(
+                  ColumnCategory.TAG,
+                  ColumnCategory.TAG,
+                  ColumnCategory.TAG,
+                  ColumnCategory.FIELD,
+                  ColumnCategory.FIELD));
+      Tablet tablet = new Tablet("object_table", columnNameList, dataTypeList, 
columnTypeList, 1);
+      for (int i = 0; i < objectSegments.size() - 1; i++) {
+        int rowIndex = tablet.getRowSize();
+        tablet.addTimestamp(rowIndex, 1);
+        tablet.addValue(rowIndex, 0, "1");
+        tablet.addValue(rowIndex, 1, "5");
+        tablet.addValue(rowIndex, 2, "3");
+        tablet.addValue(rowIndex, 3, 37.6F);
+        tablet.addValue(rowIndex, 4, false, i * 512L, objectSegments.get(i));
+        session.insert(tablet);
+        tablet.reset();
+      }
+      session.executeNonQueryStatement("DELETE FROM object_table where time = 
1");
+
+      try (SessionDataSet dataSet =
+          session.executeQueryStatement("select file from object_table where 
time = 1")) {
+        SessionDataSet.DataIterator iterator = dataSet.iterator();
+        while (iterator.next()) {
+          assertNull(iterator.getString(1));
+        }
+      }
+    }
+
+    // test object file path
+    boolean success = false;
+    for (DataNodeWrapper dataNodeWrapper : 
EnvFactory.getEnv().getDataNodeWrapperList()) {
+      String objectDirStr = dataNodeWrapper.getDataNodeObjectDir();
+      File objectDir = new File(objectDirStr);
+      if (objectDir.exists() && objectDir.isDirectory()) {
+        File[] regionDirs = objectDir.listFiles();
+        if (regionDirs != null) {
+          for (File regionDir : regionDirs) {
+            if (regionDir.isDirectory()) {
+              File objectTmpFile =
+                  new File(
+                      regionDir,
+                      convertPathString("object_table")
+                          + File.separator
+                          + convertPathString("1")
+                          + File.separator
+                          + convertPathString("5")
+                          + File.separator
+                          + convertPathString("3")
+                          + File.separator
+                          + convertPathString("file")
+                          + File.separator
+                          + "1.bin.tmp");
+              if (objectTmpFile.exists() && objectTmpFile.isFile()) {
+                success = true;
+              }
+            }
+          }
+        }
+      }
+    }
+    Assert.assertFalse(success);
+  }
+
+  protected String convertPathString(String path) {
+    return 
BaseEncoding.base32().omitPadding().encode(path.getBytes(StandardCharsets.UTF_8));
+  }
+}
diff --git 
a/integration-test/src/test/java/org/apache/iotdb/relational/it/session/IoTDBObjectDeleteIT2.java
 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/session/IoTDBObjectDeleteIT2.java
new file mode 100644
index 00000000000..c7c7f208bdc
--- /dev/null
+++ 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/session/IoTDBObjectDeleteIT2.java
@@ -0,0 +1,48 @@
+/*
+ * 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.iotdb.relational.it.session;
+
+import org.apache.iotdb.it.env.EnvFactory;
+import org.apache.iotdb.itbase.category.TableClusterIT;
+import org.apache.iotdb.itbase.category.TableLocalStandaloneIT;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.experimental.categories.Category;
+
+@Category({TableLocalStandaloneIT.class, TableClusterIT.class})
+public class IoTDBObjectDeleteIT2 extends IoTDBObjectDeleteIT {
+
+  @BeforeClass
+  public static void classSetUp() throws Exception {
+    
EnvFactory.getEnv().getConfig().getCommonConfig().setRestrictObjectLimit(true);
+    EnvFactory.getEnv().initClusterEnvironment();
+  }
+
+  @AfterClass
+  public static void classTearDown() {
+    EnvFactory.getEnv().cleanClusterEnvironment();
+  }
+
+  @Override
+  protected String convertPathString(String path) {
+    return path;
+  }
+}
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/StorageEngine.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/StorageEngine.java
index 43e76e5a2b2..964f6edcd76 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/StorageEngine.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/StorageEngine.java
@@ -1082,6 +1082,9 @@ public class StorageEngine implements IService {
     List<String> folders = TierManager.getInstance().getAllObjectFileFolders();
     for (String baseDir : folders) {
       File fileFolder = fsFactory.getFile(baseDir);
+      if (!fileFolder.exists()) {
+        continue;
+      }
       try (Stream<Path> paths = Files.walk(fileFolder.toPath())) {
         paths
             .filter(Files::isRegularFile)
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/DataRegion.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/DataRegion.java
index 7d7fb8b9180..cc37f15d7a8 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/DataRegion.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/DataRegion.java
@@ -159,6 +159,7 @@ import org.apache.iotdb.db.utils.CommonUtils;
 import org.apache.iotdb.db.utils.DateTimeUtils;
 import org.apache.iotdb.db.utils.EncryptDBUtils;
 import org.apache.iotdb.db.utils.ModificationUtils;
+import org.apache.iotdb.db.utils.ObjectTypeUtils;
 import org.apache.iotdb.db.utils.ObjectWriter;
 import org.apache.iotdb.metrics.utils.MetricLevel;
 import org.apache.iotdb.rpc.RpcUtils;
@@ -166,11 +167,13 @@ import org.apache.iotdb.rpc.TSStatusCode;
 
 import com.github.benmanes.caffeine.cache.Cache;
 import com.github.benmanes.caffeine.cache.Caffeine;
+import com.google.common.io.BaseEncoding;
 import org.apache.thrift.TException;
 import org.apache.tsfile.external.commons.io.FileUtils;
 import org.apache.tsfile.external.commons.lang3.tuple.Triple;
 import org.apache.tsfile.file.metadata.ChunkMetadata;
 import org.apache.tsfile.file.metadata.IDeviceID;
+import org.apache.tsfile.file.metadata.IDeviceID.Factory;
 import org.apache.tsfile.file.metadata.TableSchema;
 import org.apache.tsfile.fileSystem.FSFactoryProducer;
 import org.apache.tsfile.fileSystem.FSType;
@@ -187,6 +190,7 @@ import org.slf4j.LoggerFactory;
 import java.io.File;
 import java.io.IOException;
 import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -214,6 +218,7 @@ import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.locks.Condition;
 import java.util.concurrent.locks.ReadWriteLock;
@@ -1995,6 +2000,8 @@ public class DataRegion implements IDataRegionForQuery {
   private void deleteAllObjectFiles(List<String> folders) {
     for (String objectFolder : folders) {
       File dataRegionObjectFolder = fsFactory.getFile(objectFolder, 
dataRegionIdString);
+      AtomicLong totalSize = new AtomicLong(0);
+      AtomicInteger count = new AtomicInteger(0);
       try (Stream<Path> paths = Files.walk(dataRegionObjectFolder.toPath())) {
         paths
             .filter(Files::isRegularFile)
@@ -2005,12 +2012,14 @@ public class DataRegion implements IDataRegionForQuery {
                 })
             .forEach(
                 path -> {
-                  FileMetrics.getInstance().decreaseObjectFileNum(1);
-                  
FileMetrics.getInstance().decreaseObjectFileSize(path.toFile().length());
+                  count.incrementAndGet();
+                  totalSize.addAndGet(path.toFile().length());
                 });
       } catch (IOException e) {
         logger.error("Failed to check Object Files: {}", e.getMessage());
       }
+      FileMetrics.getInstance().decreaseObjectFileNum(count.get());
+      FileMetrics.getInstance().decreaseObjectFileSize(totalSize.get());
       if (FSUtils.getFSType(dataRegionObjectFolder) != FSType.LOCAL) {
         try {
           fsFactory.deleteDirectory(dataRegionObjectFolder.getPath());
@@ -2752,8 +2761,8 @@ public class DataRegion implements IDataRegionForQuery {
       if (deleted) {
         return;
       }
-      TableDeviceSchemaCache.getInstance()
-          .invalidateLastCache(getDatabaseName(), 
modEntries.get(0).getTableName());
+      String tableName = modEntries.get(0).getTableName();
+      
TableDeviceSchemaCache.getInstance().invalidateLastCache(getDatabaseName(), 
tableName);
       List<WALFlushListener> walListeners = logDeletionInWAL(node);
 
       for (WALFlushListener walFlushListener : walListeners) {
@@ -2763,6 +2772,43 @@ public class DataRegion implements IDataRegionForQuery {
         }
       }
 
+      List<File> objectTableDirs =
+          
TierManager.getInstance().getAllMatchedObjectDirs(dataRegionIdString, 
tableName);
+      if (!objectTableDirs.isEmpty()) {
+        boolean droppingTable = false;
+        for (TableDeletionEntry entry : modEntries) {
+          if (entry.isDroppingTable()) {
+            AtomicLong totalSize = new AtomicLong(0);
+            AtomicInteger count = new AtomicInteger(0);
+            for (File objectTableDir : objectTableDirs) {
+              droppingTable = true;
+              try (Stream<Path> paths = Files.walk(objectTableDir.toPath())) {
+                paths
+                    .filter(Files::isRegularFile)
+                    .filter(
+                        path -> {
+                          String name = path.getFileName().toString();
+                          return name.endsWith(".bin");
+                        })
+                    .forEach(
+                        path -> {
+                          count.incrementAndGet();
+                          totalSize.addAndGet(path.toFile().length());
+                        });
+              } catch (IOException e) {
+                logger.error("Failed to check Object Files: {}", 
e.getMessage());
+              }
+              FileUtils.deleteQuietly(objectTableDir);
+            }
+            FileMetrics.getInstance().decreaseObjectFileNum(count.get());
+            FileMetrics.getInstance().decreaseObjectFileSize(totalSize.get());
+          }
+        }
+        if (!droppingTable) {
+          deleteObjectFiles(objectTableDirs, modEntries);
+        }
+      }
+
       List<List<TsFileResource>> sealedTsFileResourceLists = new 
ArrayList<>(modEntries.size());
       for (TableDeletionEntry modEntry : modEntries) {
         List<TsFileResource> sealedTsFileResource = new ArrayList<>();
@@ -2931,6 +2977,59 @@ public class DataRegion implements IDataRegionForQuery {
     return walFlushListeners;
   }
 
+  private void deleteObjectFiles(List<File> matchedObjectDirs, 
List<TableDeletionEntry> modEntries)
+      throws IOException {
+    for (File matchedObjectDir : matchedObjectDirs) {
+      try (Stream<Path> paths =
+          Files.find(
+              matchedObjectDir.toPath(),
+              Integer.MAX_VALUE,
+              (path, attrs) ->
+                  attrs.isRegularFile()
+                      && (path.getFileName().toString().endsWith(".bin")
+                          || path.getFileName().toString().endsWith(".tmp")))) 
{
+        paths.forEach(
+            path -> {
+              Path relativePath = 
matchedObjectDir.getParentFile().toPath().relativize(path);
+              String[] ideviceIdSegments = new 
String[relativePath.getNameCount() - 2];
+              for (int i = 0; i < ideviceIdSegments.length; i++) {
+                ideviceIdSegments[i] =
+                    
CommonDescriptor.getInstance().getConfig().isRestrictObjectLimit()
+                        ? relativePath.getName(i).toString()
+                        : new String(
+                            BaseEncoding.base32()
+                                .omitPadding()
+                                .decode(relativePath.getName(i).toString()),
+                            StandardCharsets.UTF_8);
+              }
+              IDeviceID iDeviceID = 
Factory.DEFAULT_FACTORY.create(ideviceIdSegments);
+              String measurementId =
+                  
CommonDescriptor.getInstance().getConfig().isRestrictObjectLimit()
+                      ? relativePath.getName(relativePath.getNameCount() - 
2).toString()
+                      : new String(
+                          BaseEncoding.base32()
+                              .omitPadding()
+                              .decode(
+                                  
relativePath.getName(relativePath.getNameCount() - 2).toString()),
+                          StandardCharsets.UTF_8);
+              String fileName = path.getFileName().toString();
+              long timestamp = Long.parseLong(fileName.substring(0, 
fileName.indexOf('.')));
+              logger.debug(
+                  "timestamp {}, measurementId {}, ideviceId {}",
+                  timestamp,
+                  measurementId,
+                  iDeviceID);
+              for (TableDeletionEntry modEntry : modEntries) {
+                if (modEntry.affects(iDeviceID, timestamp, timestamp)
+                    && modEntry.affects(measurementId)) {
+                  ObjectTypeUtils.deleteObjectPath(path.toFile());
+                }
+              }
+            });
+      }
+    }
+  }
+
   /**
    * For IoTConsensus sync. See <a 
href="https://github.com/apache/iotdb/pull/12955";>github pull
    * request</a> for details.
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/modification/DeletionPredicate.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/modification/DeletionPredicate.java
index 7e79e8f580d..0a22da2a90a 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/modification/DeletionPredicate.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/modification/DeletionPredicate.java
@@ -72,6 +72,10 @@ public class DeletionPredicate implements 
StreamSerializable, BufferSerializable
     this.idPredicate = idPredicate;
   }
 
+  public IDPredicate getIdPredicate() {
+    return idPredicate;
+  }
+
   public String getTableName() {
     return tableName;
   }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/modification/TableDeletionEntry.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/modification/TableDeletionEntry.java
index 858b6645b2f..61ec2e0d678 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/modification/TableDeletionEntry.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/modification/TableDeletionEntry.java
@@ -21,6 +21,7 @@ package 
org.apache.iotdb.db.storageengine.dataregion.modification;
 import org.apache.iotdb.commons.conf.IoTDBConstant;
 import org.apache.iotdb.commons.path.PartialPath;
 import org.apache.iotdb.db.queryengine.execution.MemoryEstimationHelper;
+import 
org.apache.iotdb.db.storageengine.dataregion.modification.IDPredicate.IDPredicateType;
 import org.apache.iotdb.db.utils.ModificationUtils;
 
 import org.apache.tsfile.file.metadata.IDeviceID;
@@ -136,6 +137,14 @@ public class TableDeletionEntry extends ModEntry {
     return predicate.getTableName();
   }
 
+  public boolean isDroppingTable() {
+    IDPredicate idPredicate = predicate.getIdPredicate();
+    return idPredicate.type == IDPredicateType.NOP
+        && predicate.getMeasurementNames().isEmpty()
+        && timeRange.getMin() == Long.MIN_VALUE
+        && timeRange.getMax() == Long.MAX_VALUE;
+  }
+
   @Override
   public boolean equals(Object o) {
     if (this == o) {
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/rescon/disk/TierManager.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/rescon/disk/TierManager.java
index a5fa8b54e7b..36bbe1cf063 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/rescon/disk/TierManager.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/rescon/disk/TierManager.java
@@ -18,6 +18,7 @@
  */
 package org.apache.iotdb.db.storageengine.rescon.disk;
 
+import org.apache.iotdb.commons.conf.CommonDescriptor;
 import org.apache.iotdb.commons.conf.IoTDBConstant;
 import org.apache.iotdb.db.conf.IoTDBConfig;
 import org.apache.iotdb.db.conf.IoTDBDescriptor;
@@ -28,6 +29,7 @@ import 
org.apache.iotdb.db.storageengine.rescon.disk.strategy.MinFolderOccupiedS
 import 
org.apache.iotdb.db.storageengine.rescon.disk.strategy.RandomOnDiskUsableSpaceStrategy;
 import org.apache.iotdb.metrics.utils.FileStoreUtils;
 
+import com.google.common.io.BaseEncoding;
 import org.apache.tsfile.fileSystem.FSFactoryProducer;
 import org.apache.tsfile.fileSystem.FSType;
 import org.apache.tsfile.utils.FSUtils;
@@ -36,7 +38,9 @@ import org.slf4j.LoggerFactory;
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.FileStore;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -172,6 +176,16 @@ public class TierManager {
       } catch (DiskSpaceInsufficientException e) {
         logger.error("All disks of tier {} are full.", tierLevel, e);
       }
+      // try to remove empty objectDirs
+      for (String dir : objectDirs) {
+        File dirFile = FSFactoryProducer.getFSFactory().getFile(dir);
+        if (dirFile.isDirectory() && 
Objects.requireNonNull(dirFile.list()).length == 0) {
+          try {
+            Files.delete(dirFile.toPath());
+          } catch (IOException ignore) {
+          }
+        }
+      }
     }
 
     tierDiskTotalSpace = getTierDiskSpace(DiskSpaceType.TOTAL);
@@ -272,6 +286,41 @@ public class TierManager {
     return Optional.empty();
   }
 
+  public List<File> getAllMatchedObjectDirs(String regionIdStr, String... 
path) {
+    List<File> matchedDirs = new ArrayList<>();
+    boolean hasObjectDir = false;
+    for (String objectDir : objectDirs) {
+      File objectDirPath = FSFactoryProducer.getFSFactory().getFile(objectDir);
+      if (objectDirPath.exists()) {
+        hasObjectDir = true;
+        break;
+      }
+    }
+    if (!hasObjectDir) {
+      return matchedDirs;
+    }
+    StringBuilder objectPath = new StringBuilder();
+    objectPath.append(regionIdStr);
+    for (String str : path) {
+      objectPath
+          .append(File.separator)
+          .append(
+              
CommonDescriptor.getInstance().getConfig().isRestrictObjectLimit()
+                  ? str
+                  : BaseEncoding.base32()
+                      .omitPadding()
+                      .encode(str.getBytes(StandardCharsets.UTF_8)));
+    }
+    for (String objectDir : objectDirs) {
+      File objectFilePath =
+          FSFactoryProducer.getFSFactory().getFile(objectDir, 
objectPath.toString());
+      if (objectFilePath.exists()) {
+        matchedDirs.add(objectFilePath);
+      }
+    }
+    return matchedDirs;
+  }
+
   public int getTiersNum() {
     return seqTiers.size();
   }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/ObjectTypeUtils.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/ObjectTypeUtils.java
index ec1fd592617..15964ffaddd 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/ObjectTypeUtils.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/ObjectTypeUtils.java
@@ -55,6 +55,7 @@ import java.nio.file.Paths;
 import java.nio.file.StandardOpenOption;
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 import java.util.Optional;
 
 public class ObjectTypeUtils {
@@ -294,6 +295,37 @@ public class ObjectTypeUtils {
     }
   }
 
+  public static void deleteObjectPath(File file) {
+    File tmpFile = new File(file.getPath() + ".tmp");
+    File bakFile = new File(file.getPath() + ".back");
+    for (int i = 0; i < 2; i++) {
+      if (file.exists()) {
+        FileMetrics.getInstance().decreaseObjectFileNum(1);
+        FileMetrics.getInstance().decreaseObjectFileSize(file.length());
+      }
+      try {
+        deleteObjectFile(file);
+        deleteObjectFile(tmpFile);
+        deleteObjectFile(bakFile);
+      } catch (IOException e) {
+        logger.error("Failed to remove object file {}", 
file.getAbsolutePath(), e);
+      }
+    }
+    deleteEmptyParentDir(file);
+  }
+
+  private static void deleteEmptyParentDir(File file) {
+    File dir = file.getParentFile();
+    if (dir.isDirectory() && Objects.requireNonNull(dir.list()).length == 0) {
+      try {
+        Files.deleteIfExists(dir.toPath());
+        deleteEmptyParentDir(dir);
+      } catch (IOException e) {
+        logger.error("Failed to remove empty object dir {}", 
dir.getAbsolutePath(), e);
+      }
+    }
+  }
+
   private static void deleteObjectFile(File file) throws IOException {
     if (file.exists()) {
       logger.info("Remove object file {}, size is {}(byte)", 
file.getAbsolutePath(), file.length());


Reply via email to