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

jt2594838 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 6a3c8d667f0 [Load] Preserve invalid device path failures during tablet 
conversion (#18614)
6a3c8d667f0 is described below

commit 6a3c8d667f0552966f4ccf7e660c5ea356e8985d
Author: Caideyipi <[email protected]>
AuthorDate: Thu Sep 10 17:34:59 2026 +0800

    [Load] Preserve invalid device path failures during tablet conversion 
(#18614)
---
 .../load/LoadAnalyzeInvalidPathException.java      |  28 +++
 .../plan/analyze/load/LoadTsFileAnalyzer.java      |   4 +-
 .../load/TreeSchemaAutoCreatorAndVerifier.java     |  20 ++-
 .../db/storageengine/load/LoadTsFilePathUtils.java |  47 +++++
 .../converter/LoadTreeTsFileTabletIterator.java    |  26 ++-
 .../plan/analyze/load/LoadTsFileAnalyzerTest.java  |  16 +-
 .../load/converter/LoadTsFileInvalidPathTest.java  | 195 +++++++++++++++++++++
 7 files changed, 323 insertions(+), 13 deletions(-)

diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/exception/load/LoadAnalyzeInvalidPathException.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/exception/load/LoadAnalyzeInvalidPathException.java
new file mode 100644
index 00000000000..905221e50a6
--- /dev/null
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/exception/load/LoadAnalyzeInvalidPathException.java
@@ -0,0 +1,28 @@
+/*
+ * 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.db.exception.load;
+
+/** An invalid tree path cannot be repaired by converting the TsFile to 
tablets. */
+public class LoadAnalyzeInvalidPathException extends LoadAnalyzeException {
+
+  public LoadAnalyzeInvalidPathException(String message) {
+    super(message);
+  }
+}
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/load/LoadTsFileAnalyzer.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/load/LoadTsFileAnalyzer.java
index e11fa2c977a..8e92e00c280 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/load/LoadTsFileAnalyzer.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/load/LoadTsFileAnalyzer.java
@@ -28,6 +28,7 @@ import org.apache.iotdb.commons.queryengine.common.SqlDialect;
 import org.apache.iotdb.commons.queryengine.utils.TimestampPrecisionUtils;
 import org.apache.iotdb.db.conf.IoTDBDescriptor;
 import org.apache.iotdb.db.exception.load.LoadAnalyzeException;
+import org.apache.iotdb.db.exception.load.LoadAnalyzeInvalidPathException;
 import org.apache.iotdb.db.exception.load.LoadAnalyzeMissingSchemaException;
 import org.apache.iotdb.db.exception.load.LoadAnalyzeTypeMismatchException;
 import org.apache.iotdb.db.exception.load.LoadEmptyFileException;
@@ -893,7 +894,8 @@ public class LoadTsFileAnalyzer implements AutoCloseable {
   }
 
   private boolean shouldSkipConversion(LoadAnalyzeException e) {
-    return (e instanceof LoadAnalyzeTypeMismatchException) && 
!isConvertOnTypeMismatch;
+    return e instanceof LoadAnalyzeInvalidPathException
+        || (e instanceof LoadAnalyzeTypeMismatchException) && 
!isConvertOnTypeMismatch;
   }
 
   private void getFileModelInfoBeforeTabletConversion() throws IOException {
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/load/TreeSchemaAutoCreatorAndVerifier.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/load/TreeSchemaAutoCreatorAndVerifier.java
index d92c5954668..37911092405 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/load/TreeSchemaAutoCreatorAndVerifier.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/load/TreeSchemaAutoCreatorAndVerifier.java
@@ -36,6 +36,7 @@ import 
org.apache.iotdb.confignode.rpc.thrift.TShowDatabaseResp;
 import org.apache.iotdb.db.auth.AuthorityChecker;
 import org.apache.iotdb.db.conf.IoTDBDescriptor;
 import org.apache.iotdb.db.exception.load.LoadAnalyzeException;
+import org.apache.iotdb.db.exception.load.LoadAnalyzeInvalidPathException;
 import org.apache.iotdb.db.exception.load.LoadAnalyzeMissingSchemaException;
 import org.apache.iotdb.db.exception.load.LoadAnalyzeTypeMismatchException;
 import org.apache.iotdb.db.exception.load.LoadFileException;
@@ -81,6 +82,8 @@ import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 
+import static 
org.apache.iotdb.db.storageengine.load.LoadTsFilePathUtils.getValidatedDevicePath;
+
 public class TreeSchemaAutoCreatorAndVerifier {
 
   private static final Logger LOGGER =
@@ -115,6 +118,8 @@ public class TreeSchemaAutoCreatorAndVerifier {
         device2TimeseriesMetadataList.entrySet()) {
       final IDeviceID device = entry.getKey();
 
+      getValidatedDevicePath(device);
+
       try {
         if (schemaCache.isDeviceDeletedByMods(device)) {
           continue;
@@ -179,11 +184,14 @@ public class TreeSchemaAutoCreatorAndVerifier {
   }
 
   public void checkWritePermission(
-      Map<IDeviceID, List<TimeseriesMetadata>> device2TimeseriesMetadataList) 
throws AuthException {
+      Map<IDeviceID, List<TimeseriesMetadata>> device2TimeseriesMetadataList)
+      throws AuthException, LoadAnalyzeInvalidPathException {
     for (final Map.Entry<IDeviceID, List<TimeseriesMetadata>> entry :
         device2TimeseriesMetadataList.entrySet()) {
       final IDeviceID device = entry.getKey();
 
+      getValidatedDevicePath(device);
+
       try {
         if (schemaCache.isDeviceDeletedByMods(device)) {
           continue;
@@ -273,7 +281,7 @@ public class TreeSchemaAutoCreatorAndVerifier {
       if (loadTsFileAnalyzer.isVerifySchema()) {
         verifySchema(schemaTree);
       }
-    } catch (AuthException e) {
+    } catch (AuthException | LoadAnalyzeInvalidPathException e) {
       throw e;
     } catch (LoadAnalyzeTypeMismatchException e) {
       if (loadTsFileAnalyzer.isConvertOnTypeMismatch()) {
@@ -356,15 +364,9 @@ public class TreeSchemaAutoCreatorAndVerifier {
     final Set<PartialPath> databasesNeededToBeSet = new HashSet<>();
 
     for (final IDeviceID device : schemaCache.getDevice2TimeSeries().keySet()) 
{
-      final PartialPath devicePath = new PartialPath(device);
+      final PartialPath devicePath = getValidatedDevicePath(device);
 
       final String[] devicePrefixNodes = devicePath.getNodes();
-      for (final String node : devicePrefixNodes) {
-        if (node == null || node.isEmpty()) {
-          throw new LoadAnalyzeException(
-              new IllegalPathException(devicePath.getFullPath()).getMessage());
-        }
-      }
       if (devicePrefixNodes.length < databasePrefixNodesLength) {
         throw new LoadAnalyzeException(
             String.format(
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/load/LoadTsFilePathUtils.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/load/LoadTsFilePathUtils.java
new file mode 100644
index 00000000000..7bcd69aec37
--- /dev/null
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/load/LoadTsFilePathUtils.java
@@ -0,0 +1,47 @@
+/*
+ * 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.db.storageengine.load;
+
+import org.apache.iotdb.commons.exception.IllegalPathException;
+import org.apache.iotdb.commons.path.PartialPath;
+import org.apache.iotdb.db.exception.load.LoadAnalyzeInvalidPathException;
+
+import org.apache.tsfile.file.metadata.IDeviceID;
+
+public class LoadTsFilePathUtils {
+
+  private LoadTsFilePathUtils() {}
+
+  public static PartialPath getValidatedDevicePath(final IDeviceID device)
+      throws LoadAnalyzeInvalidPathException {
+    try {
+      final PartialPath devicePath = new PartialPath(device);
+      // Validate the original nodes before converting to a string, which 
loses null nodes.
+      for (final String node : devicePath.getNodes()) {
+        if (node == null || node.isEmpty()) {
+          throw new IllegalPathException(devicePath.getFullPath());
+        }
+      }
+      return devicePath;
+    } catch (final IllegalPathException e) {
+      throw new LoadAnalyzeInvalidPathException(e.getMessage());
+    }
+  }
+}
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/load/converter/LoadTreeTsFileTabletIterator.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/load/converter/LoadTreeTsFileTabletIterator.java
index 618f6bad889..7bfbb9b6df3 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/load/converter/LoadTreeTsFileTabletIterator.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/load/converter/LoadTreeTsFileTabletIterator.java
@@ -19,9 +19,11 @@
 
 package org.apache.iotdb.db.storageengine.load.converter;
 
+import org.apache.iotdb.commons.exception.IllegalPathException;
 import 
org.apache.iotdb.commons.exception.pipe.PipeRuntimeOutOfMemoryCriticalException;
 import org.apache.iotdb.commons.pipe.datastructure.pattern.IoTDBTreePattern;
 import org.apache.iotdb.commons.pipe.datastructure.pattern.TreePattern;
+import org.apache.iotdb.db.exception.load.LoadAnalyzeInvalidPathException;
 import org.apache.iotdb.db.exception.load.LoadRuntimeOutOfMemoryException;
 import org.apache.iotdb.db.i18n.StorageEngineMessages;
 import 
org.apache.iotdb.db.pipe.event.common.tablet.PipeRawTabletInsertionEvent;
@@ -30,6 +32,7 @@ import 
org.apache.iotdb.db.pipe.event.common.tsfile.parser.scan.TsFileInsertionE
 import 
org.apache.iotdb.db.storageengine.load.memory.LoadTsFileParserMemoryManager;
 import org.apache.iotdb.pipe.api.event.dml.insertion.TabletInsertionEvent;
 
+import org.apache.tsfile.exception.PathParseException;
 import org.apache.tsfile.file.metadata.IDeviceID;
 import org.apache.tsfile.file.metadata.TimeseriesMetadata;
 import org.apache.tsfile.read.TsFileSequenceReader;
@@ -54,6 +57,8 @@ import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Collectors;
 
+import static 
org.apache.iotdb.db.storageengine.load.LoadTsFilePathUtils.getValidatedDevicePath;
+
 /**
  * Load uses scan parsing first for throughput. If scan parsing hits 
corruption, fall back to query
  * parsing for the remaining measurements and devices so later data can still 
be loaded.
@@ -63,7 +68,18 @@ class LoadTreeTsFileTabletIterator
 
   private static final Logger LOGGER = 
LoggerFactory.getLogger(LoadTreeTsFileTabletIterator.class);
 
-  private static final TreePattern LOAD_TREE_PATTERN = new 
IoTDBTreePattern(null);
+  private static final TreePattern LOAD_TREE_PATTERN =
+      new IoTDBTreePattern(null) {
+        @Override
+        public boolean mayOverlapWithDevice(final IDeviceID device) {
+          try {
+            getValidatedDevicePath(device);
+          } catch (final LoadAnalyzeInvalidPathException e) {
+            throw new IllegalArgumentException(e.getMessage(), e);
+          }
+          return super.mayOverlapWithDevice(device);
+        }
+      };
 
   private final File file;
   private final boolean isWithMod;
@@ -334,6 +350,7 @@ class LoadTreeTsFileTabletIterator
     while (!pendingQueryTasks.isEmpty()) {
       activeQueryTask = pendingQueryTasks.removeFirst();
       try {
+        getValidatedDevicePath(activeQueryTask.device);
         activeQueryParser =
             new TsFileInsertionEventQueryParser(
                 file,
@@ -415,6 +432,10 @@ class LoadTreeTsFileTabletIterator
     Throwable current = e;
     while (Objects.nonNull(current)) {
       if (current instanceof InterruptedException
+          // Invalid paths cannot be recovered by query parsing or splitting 
measurements.
+          || current instanceof PathParseException
+          || current instanceof IllegalPathException
+          || current instanceof LoadAnalyzeInvalidPathException
           || current instanceof PipeRuntimeOutOfMemoryCriticalException
           || current instanceof LoadRuntimeOutOfMemoryException) {
         return true;
@@ -425,6 +446,9 @@ class LoadTreeTsFileTabletIterator
   }
 
   private RuntimeException toRuntimeException(final Exception e) {
+    if (e instanceof LoadAnalyzeInvalidPathException) {
+      return new IllegalArgumentException(e.getMessage(), e);
+    }
     return e instanceof RuntimeException
         ? (RuntimeException) e
         : new IllegalStateException("Failed to iterate tablets while loading 
TsFile.", e);
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/analyze/load/LoadTsFileAnalyzerTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/analyze/load/LoadTsFileAnalyzerTest.java
index bf248d7a600..1e9aae08428 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/analyze/load/LoadTsFileAnalyzerTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/analyze/load/LoadTsFileAnalyzerTest.java
@@ -24,9 +24,11 @@ import 
org.apache.iotdb.commons.queryengine.plan.relational.metadata.ColumnSchem
 import org.apache.iotdb.commons.schema.table.column.TsTableColumnCategory;
 import org.apache.iotdb.db.conf.IoTDBDescriptor;
 import org.apache.iotdb.db.exception.load.LoadAnalyzeException;
+import org.apache.iotdb.db.exception.load.LoadAnalyzeInvalidPathException;
 import org.apache.iotdb.db.exception.load.LoadAnalyzeMissingSchemaException;
 import org.apache.iotdb.db.exception.load.LoadAnalyzeTypeMismatchException;
 import org.apache.iotdb.db.exception.load.LoadRuntimeOutOfMemoryException;
+import org.apache.iotdb.db.i18n.DataNodeQueryMessages;
 import org.apache.iotdb.db.queryengine.common.MPPQueryContext;
 import org.apache.iotdb.db.queryengine.common.QueryId;
 import org.apache.iotdb.db.queryengine.common.schematree.ClusterSchemaTree;
@@ -126,7 +128,9 @@ public class LoadTsFileAnalyzerTest {
       new LoadTsFile(null, "", Collections.emptyMap());
       Assert.fail("Expected empty LOAD TSFILE path to be rejected.");
     } catch (final RuntimeException e) {
-      Assert.assertTrue(e.getMessage().contains("The LOAD TSFILE path cannot 
be empty."));
+      Assert.assertTrue(
+          e.getMessage()
+              
.contains(DataNodeQueryMessages.EXCEPTION_LOAD_TSFILE_PATH_CANNOT_BE_EMPTY_2B106181));
     }
   }
 
@@ -230,7 +234,8 @@ public class LoadTsFileAnalyzerTest {
             Assert.assertThrows(
                 InvocationTargetException.class,
                 () -> getAutoCreateDatabaseMethod().invoke(verifier));
-        Assert.assertTrue(exception.getCause() instanceof 
LoadAnalyzeException);
+        Assert.assertTrue(exception.getCause() instanceof 
LoadAnalyzeInvalidPathException);
+        Assert.assertThrows(LoadAnalyzeInvalidPathException.class, 
verifier::flush);
       } finally {
         verifier.close();
       }
@@ -270,6 +275,13 @@ public class LoadTsFileAnalyzerTest {
             Collections.singleton(databaseWithSameStringPrefix), 
databasesNeededToBeSet);
         Assert.assertEquals(
             Collections.singleton(database), 
getTreeSchemaCache(verifier).getAlreadySetDatabases());
+
+        getTreeSchemaCache(verifier)
+            .addTimeSeries(
+                new StringArrayDeviceID(new String[] {"root.sg", "d1"}),
+                new MeasurementSchema("s1", TSDataType.INT32));
+        // A valid device still uses its existing database despite the legacy 
root. entry.
+        getAutoCreateDatabaseMethod().invoke(verifier);
       } finally {
         verifier.close();
       }
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/load/converter/LoadTsFileInvalidPathTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/load/converter/LoadTsFileInvalidPathTest.java
new file mode 100644
index 00000000000..519e13d9715
--- /dev/null
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/load/converter/LoadTsFileInvalidPathTest.java
@@ -0,0 +1,195 @@
+/*
+ * 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.db.storageengine.load.converter;
+
+import org.apache.iotdb.common.rpc.thrift.TSStatus;
+import org.apache.iotdb.commons.exception.IllegalPathException;
+import org.apache.iotdb.commons.path.PartialPath;
+import org.apache.iotdb.commons.queryengine.common.SessionInfo;
+import org.apache.iotdb.db.conf.IoTDBDescriptor;
+import org.apache.iotdb.db.queryengine.common.MPPQueryContext;
+import org.apache.iotdb.db.queryengine.common.QueryId;
+import org.apache.iotdb.db.queryengine.plan.analyze.Analysis;
+import org.apache.iotdb.db.queryengine.plan.analyze.load.LoadTsFileAnalyzer;
+import org.apache.iotdb.db.queryengine.plan.statement.crud.LoadTsFileStatement;
+import org.apache.iotdb.db.storageengine.dataregion.tsfile.TsFileResource;
+import org.apache.iotdb.db.storageengine.load.memory.LoadTsFileMemoryManager;
+import org.apache.iotdb.rpc.TSStatusCode;
+
+import org.apache.tsfile.enums.TSDataType;
+import org.apache.tsfile.file.metadata.IDeviceID;
+import org.apache.tsfile.file.metadata.StringArrayDeviceID;
+import org.apache.tsfile.write.chunk.AlignedChunkWriterImpl;
+import org.apache.tsfile.write.schema.MeasurementSchema;
+import org.apache.tsfile.write.schema.Schema;
+import org.apache.tsfile.write.writer.TsFileIOWriter;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.time.ZoneId;
+import java.util.Arrays;
+import java.util.Optional;
+
+public class LoadTsFileInvalidPathTest {
+
+  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+  private int dataNodeId;
+
+  @Before
+  public void setUp() {
+    dataNodeId = IoTDBDescriptor.getInstance().getConfig().getDataNodeId();
+    IoTDBDescriptor.getInstance().getConfig().setDataNodeId(0);
+  }
+
+  @After
+  public void tearDown() {
+    IoTDBDescriptor.getInstance().getConfig().setDataNodeId(dataNodeId);
+  }
+
+  @Test
+  public void testAnalysisRejectsEmptyDeviceNodeWithAndWithoutResource() 
throws Exception {
+    final IDeviceID device = new StringArrayDeviceID(new String[] {"root", 
""});
+    final File file = writeTsFile(device);
+    assertAnalysisRejectsInvalidPath(file, device, true, true, -1);
+
+    final TsFileResource resource = new TsFileResource(file);
+    resource.updateStartTime(device, 1);
+    resource.updateEndTime(device, 1);
+    resource.serialize();
+    Assert.assertTrue(resource.resourceFileExists());
+    assertAnalysisRejectsInvalidPath(file, device, true, true, -1);
+  }
+
+  @Test
+  public void testAnalysisRejectsNullDeviceNode() throws Exception {
+    final IDeviceID device = new StringArrayDeviceID(new String[] {"root.sg", 
null, "d1"});
+    assertAnalysisRejectsInvalidPath(writeTsFile(device), device, true, true, 
-1);
+  }
+
+  @Test
+  public void testAnalysisRejectsInvalidDeviceWithoutAutoCreateDatabase() 
throws Exception {
+    final IDeviceID device = new StringArrayDeviceID(new String[] {"root", 
""});
+    assertAnalysisRejectsInvalidPath(writeTsFile(device), device, false, true, 
-1);
+  }
+
+  @Test
+  public void testAnalysisRejectsInvalidDeviceWithoutSchemaChecks() throws 
Exception {
+    final IDeviceID device = new StringArrayDeviceID(new String[] {"root", 
""});
+    assertAnalysisRejectsInvalidPath(writeTsFile(device), device, false, 
false, -1);
+  }
+
+  @Test
+  public void testMiniFileAnalysisRejectsInvalidDevice() throws Exception {
+    final IDeviceID device = new StringArrayDeviceID(new String[] {"root", 
""});
+    assertAnalysisRejectsInvalidPath(writeTsFile(device), device, true, true, 
Long.MAX_VALUE);
+  }
+
+  @Test
+  public void testConversionReportsInvalidPathAndReleasesMemory() throws 
Exception {
+    assertConversionRejectsInvalidPath(new StringArrayDeviceID(new String[] 
{"root", ""}));
+  }
+
+  @Test
+  public void testConversionRejectsNullDeviceNode() throws Exception {
+    assertConversionRejectsInvalidPath(
+        new StringArrayDeviceID(new String[] {"root.sg", null, "d1"}));
+  }
+
+  private void assertConversionRejectsInvalidPath(final IDeviceID device) 
throws Exception {
+    final File file = writeTsFile(device);
+    final long memoryBefore = 
LoadTsFileMemoryManager.getInstance().getUsedMemorySizeInBytes();
+    final LoadTreeStatementDataTypeConvertExecutionVisitor visitor =
+        new LoadTreeStatementDataTypeConvertExecutionVisitor(
+            statement -> {
+              Assert.fail("An invalid device must not be inserted.");
+              return null;
+            });
+
+    final Optional<TSStatus> status =
+        
visitor.visitLoadFile(LoadTsFileStatement.createUnchecked(file.getAbsolutePath()),
 null);
+
+    Assert.assertTrue(status.isPresent());
+    Assert.assertEquals(TSStatusCode.LOAD_FILE_ERROR.getStatusCode(), 
status.get().getCode());
+    Assert.assertNotNull(status.get().getMessage());
+    Assert.assertEquals(
+        new IllegalPathException(new 
PartialPath(device).getFullPath()).getMessage(),
+        status.get().getMessage());
+    Assert.assertEquals(
+        memoryBefore, 
LoadTsFileMemoryManager.getInstance().getUsedMemorySizeInBytes());
+  }
+
+  private void assertAnalysisRejectsInvalidPath(
+      final File file,
+      final IDeviceID device,
+      final boolean autoCreateDatabase,
+      final boolean checkSchema,
+      final long conversionThreshold)
+      throws Exception {
+    final LoadTsFileStatement statement =
+        LoadTsFileStatement.createUnchecked(file.getAbsolutePath());
+    statement.setAutoCreateDatabase(autoCreateDatabase);
+    statement.setConvertOnTypeMismatch(true);
+    statement.setAutoCreateSchema(checkSchema);
+    statement.setVerifySchema(checkSchema);
+    statement.setTabletConversionThresholdBytes(conversionThreshold);
+    final MPPQueryContext context = new MPPQueryContext(new 
QueryId("load_invalid_path_test"));
+    context.setSession(new SessionInfo(0, "root", ZoneId.systemDefault()));
+
+    try (final LoadTsFileAnalyzer analyzer = new LoadTsFileAnalyzer(statement, 
false, context)) {
+      final Analysis analysis = new Analysis();
+      analyzer.analyzeFileByFile(analysis);
+      Assert.assertTrue(analysis.isFinishQueryAfterAnalyze());
+      Assert.assertNotNull(analysis.getFailStatus());
+      Assert.assertEquals(
+          TSStatusCode.LOAD_FILE_ERROR.getStatusCode(), 
analysis.getFailStatus().getCode());
+      Assert.assertEquals(
+          new IllegalPathException(new 
PartialPath(device).getFullPath()).getMessage(),
+          analysis.getFailStatus().getMessage());
+      Assert.assertTrue(file.exists());
+    }
+  }
+
+  private File writeTsFile(final IDeviceID device) throws Exception {
+    final File file = new File(temporaryFolder.getRoot(), "1-1-0-0.tsfile");
+    // Use the low-level writer to preserve legacy empty/null device nodes in 
the file.
+    try (final TsFileIOWriter writer = new TsFileIOWriter(file)) {
+      writer.setSchema(new Schema());
+      writer.startChunkGroup(device);
+      final AlignedChunkWriterImpl chunkWriter =
+          new AlignedChunkWriterImpl(
+              Arrays.asList(
+                  new MeasurementSchema("quality", TSDataType.INT32),
+                  new MeasurementSchema("value", TSDataType.DOUBLE)));
+      chunkWriter.getTimeChunkWriter().write(1);
+      chunkWriter.getValueChunkWriterByIndex(0).write(1, 1, false);
+      chunkWriter.getValueChunkWriterByIndex(1).write(1, 1.0, false);
+      chunkWriter.writeToFileWriter(writer);
+      writer.endChunkGroup();
+      writer.endFile();
+    }
+    return file;
+  }
+}

Reply via email to