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;
+ }
+}