This is an automated email from the ASF dual-hosted git repository.
jt2594838 pushed a commit to branch dev/1.3
in repository https://gitbox.apache.org/repos/asf/iotdb.git
The following commit(s) were added to refs/heads/dev/1.3 by this push:
new 3d37b6041ff [Load] Handle empty path nodes during database
auto-creation (#18307) (#18319)
3d37b6041ff is described below
commit 3d37b6041fffcd81e5348031ecc1b42fba235707
Author: Caideyipi <[email protected]>
AuthorDate: Tue Jul 28 09:45:50 2026 +0800
[Load] Handle empty path nodes during database auto-creation (#18307)
(#18319)
* Reject illegal device paths during LOAD
* Remove duplicate ConfigNode validation
* Handle legacy invalid databases during LOAD
---
.../plan/analyze/load/LoadTsFileAnalyzer.java | 49 +++++--
.../plan/analyze/load/LoadTsFileAnalyzerTest.java | 154 +++++++++++++++++++++
2 files changed, 195 insertions(+), 8 deletions(-)
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 56511e8acb2..596086cb2b3 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
@@ -793,9 +793,18 @@ public class LoadTsFileAnalyzer implements AutoCloseable {
final Set<PartialPath> databasesNeededToBeSet = new HashSet<>();
for (final IDeviceID device :
schemaCache.getDevice2TimeSeries().keySet()) {
- final PartialPath devicePath = new PartialPath(device);
+ final PartialPath devicePath;
+ try {
+ devicePath = new PartialPath(device);
+ } catch (final IllegalPathException e) {
+ throw new LoadAnalyzeException(e.getMessage());
+ }
final String[] devicePrefixNodes = devicePath.getNodes();
+ if (hasEmptyPathNode(devicePath)) {
+ throw new LoadAnalyzeException(
+ new IllegalPathException(devicePath.getFullPath()).getMessage());
+ }
if (devicePrefixNodes.length < databasePrefixNodesLength) {
throw new LoadAnalyzeException(
String.format(
@@ -822,13 +831,7 @@ public class LoadTsFileAnalyzer implements AutoCloseable {
SchemaConstant.ALL_MATCH_SCOPE.serialize());
final TShowDatabaseResp resp = configNodeClient.showDatabase(req);
- for (final String databaseName : resp.getDatabaseInfoMap().keySet())
{
- schemaCache.addAlreadySetDatabase(new PartialPath(databaseName));
- databasesNeededToBeSet.removeIf(
- database ->
- database.startsWith(databaseName)
- || databaseName.startsWith(database.getFullPath()));
- }
+ filterAlreadySetDatabases(databasesNeededToBeSet,
resp.getDatabaseInfoMap().keySet());
} catch (IOException | TException | ClientManagerException e) {
throw new LoadFileException(e);
}
@@ -848,6 +851,36 @@ public class LoadTsFileAnalyzer implements AutoCloseable {
}
}
+ private void filterAlreadySetDatabases(
+ final Set<PartialPath> databasesNeededToBeSet, final Set<String>
alreadySetDatabaseNames) {
+ for (final String databaseName : alreadySetDatabaseNames) {
+ final PartialPath databasePath;
+ try {
+ databasePath = new PartialPath(databaseName);
+ } catch (final IllegalPathException e) {
+ // Ignore malformed databases left by older versions so they do not
block valid loads.
+ continue;
+ }
+
+ if (hasEmptyPathNode(databasePath)) {
+ continue;
+ }
+
+ schemaCache.addAlreadySetDatabase(databasePath);
+ databasesNeededToBeSet.removeIf(
+ database ->
database.startsWithOrPrefixOf(databasePath.getNodes()));
+ }
+ }
+
+ private boolean hasEmptyPathNode(final PartialPath path) {
+ for (final String node : path.getNodes()) {
+ if (node == null || node.isEmpty()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private void executeSetDatabaseStatement(Statement statement)
throws LoadFileException, AuthException {
// 1.check Authority
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
new file mode 100644
index 00000000000..bea5e0aa981
--- /dev/null
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/analyze/load/LoadTsFileAnalyzerTest.java
@@ -0,0 +1,154 @@
+/*
+ * 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.queryengine.plan.analyze.load;
+
+import org.apache.iotdb.commons.path.PartialPath;
+import org.apache.iotdb.db.exception.LoadAnalyzeException;
+import org.apache.iotdb.db.queryengine.common.MPPQueryContext;
+import org.apache.iotdb.db.queryengine.common.QueryId;
+import org.apache.iotdb.db.queryengine.plan.analyze.IPartitionFetcher;
+import org.apache.iotdb.db.queryengine.plan.analyze.schema.ISchemaFetcher;
+import org.apache.iotdb.db.queryengine.plan.statement.crud.LoadTsFileStatement;
+
+import org.apache.tsfile.enums.TSDataType;
+import org.apache.tsfile.file.metadata.IDeviceID;
+import org.apache.tsfile.file.metadata.PlainDeviceID;
+import org.apache.tsfile.write.schema.MeasurementSchema;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.mockito.Mockito.mock;
+
+public class LoadTsFileAnalyzerTest {
+
+ @Test
+ public void testSchemaVerifierShouldRejectDeviceWithEmptyPathNode() throws
Exception {
+ final File tsFile = File.createTempFile("load-tree-illegal-device",
".tsfile");
+
+ try (final LoadTsFileAnalyzer analyzer = createAnalyzer(tsFile)) {
+ final Object verifier = getSchemaAutoCreatorAndVerifier(analyzer);
+ final Object schemaCache = getSchemaCache(verifier);
+ addTimeSeries(
+ schemaCache, new PlainDeviceID("root."), new MeasurementSchema("s1",
TSDataType.INT32));
+
+ final InvocationTargetException exception =
+ Assert.assertThrows(
+ InvocationTargetException.class,
+ () -> getAutoCreateDatabaseMethod(verifier).invoke(verifier));
+ Assert.assertTrue(
+ String.valueOf(exception.getCause()),
+ exception.getCause() instanceof LoadAnalyzeException);
+ } finally {
+ Assert.assertTrue(tsFile.delete());
+ }
+ }
+
+ @Test
+ public void testSchemaVerifierShouldIgnoreLegacyDatabaseWithEmptyPathNode()
throws Exception {
+ final File tsFile = File.createTempFile("load-tree-legacy-database",
".tsfile");
+
+ try (final LoadTsFileAnalyzer analyzer = createAnalyzer(tsFile)) {
+ final Object verifier = getSchemaAutoCreatorAndVerifier(analyzer);
+ final PartialPath database = new PartialPath("root.sg");
+ final PartialPath databaseWithSameStringPrefix = new
PartialPath("root.sg1");
+ final Set<PartialPath> databasesNeededToBeSet =
+ new HashSet<>(Arrays.asList(database, databaseWithSameStringPrefix));
+
+ getFilterAlreadySetDatabasesMethod(verifier)
+ .invoke(verifier, databasesNeededToBeSet,
Collections.singleton("root."));
+
+ Assert.assertEquals(
+ new HashSet<>(Arrays.asList(database, databaseWithSameStringPrefix)),
+ databasesNeededToBeSet);
+
Assert.assertTrue(getAlreadySetDatabases(getSchemaCache(verifier)).isEmpty());
+
+ getFilterAlreadySetDatabasesMethod(verifier)
+ .invoke(verifier, databasesNeededToBeSet,
Collections.singleton(database.getFullPath()));
+
+ Assert.assertEquals(
+ Collections.singleton(databaseWithSameStringPrefix),
databasesNeededToBeSet);
+ Assert.assertEquals(
+ Collections.singleton(database),
getAlreadySetDatabases(getSchemaCache(verifier)));
+ } finally {
+ Assert.assertTrue(tsFile.delete());
+ }
+ }
+
+ private LoadTsFileAnalyzer createAnalyzer(final File tsFile) throws
Exception {
+ return new LoadTsFileAnalyzer(
+ LoadTsFileStatement.createUnchecked(tsFile.getAbsolutePath()),
+ new MPPQueryContext(new QueryId("load_tree_test")),
+ mock(IPartitionFetcher.class),
+ mock(ISchemaFetcher.class));
+ }
+
+ private Object getSchemaAutoCreatorAndVerifier(final LoadTsFileAnalyzer
analyzer)
+ throws Exception {
+ final Field field =
LoadTsFileAnalyzer.class.getDeclaredField("schemaAutoCreatorAndVerifier");
+ field.setAccessible(true);
+ return field.get(analyzer);
+ }
+
+ private Object getSchemaCache(final Object verifier) throws Exception {
+ final Field field = verifier.getClass().getDeclaredField("schemaCache");
+ field.setAccessible(true);
+ return field.get(verifier);
+ }
+
+ private void addTimeSeries(
+ final Object schemaCache, final IDeviceID device, final
MeasurementSchema measurementSchema)
+ throws Exception {
+ final Method method =
+ schemaCache
+ .getClass()
+ .getDeclaredMethod("addTimeSeries", IDeviceID.class,
MeasurementSchema.class);
+ method.setAccessible(true);
+ method.invoke(schemaCache, device, measurementSchema);
+ }
+
+ @SuppressWarnings("unchecked")
+ private Set<PartialPath> getAlreadySetDatabases(final Object schemaCache)
throws Exception {
+ final Method method =
schemaCache.getClass().getDeclaredMethod("getAlreadySetDatabases");
+ method.setAccessible(true);
+ return (Set<PartialPath>) method.invoke(schemaCache);
+ }
+
+ private Method getAutoCreateDatabaseMethod(final Object verifier) throws
Exception {
+ final Method method =
verifier.getClass().getDeclaredMethod("autoCreateDatabase");
+ method.setAccessible(true);
+ return method;
+ }
+
+ private Method getFilterAlreadySetDatabasesMethod(final Object verifier)
throws Exception {
+ final Method method =
+ verifier.getClass().getDeclaredMethod("filterAlreadySetDatabases",
Set.class, Set.class);
+ method.setAccessible(true);
+ return method;
+ }
+}