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 39e2ad07882 [Load] Handle empty path nodes during database
auto-creation (#18307)
39e2ad07882 is described below
commit 39e2ad078820d6c435010c5f582092c23e169ff1
Author: Caideyipi <[email protected]>
AuthorDate: Mon Jul 27 18:04:45 2026 +0800
[Load] Handle empty path nodes during database auto-creation (#18307)
* Reject illegal device paths during LOAD
* Remove duplicate ConfigNode validation
* Handle legacy invalid databases during LOAD
---
.../load/TreeSchemaAutoCreatorAndVerifier.java | 36 +++++++++--
.../plan/analyze/load/LoadTsFileAnalyzerTest.java | 75 ++++++++++++++++++++++
2 files changed, 104 insertions(+), 7 deletions(-)
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 6d1704c0cfd..3e968073cc0 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
@@ -359,6 +359,12 @@ public class TreeSchemaAutoCreatorAndVerifier {
final PartialPath devicePath = new PartialPath(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(
@@ -387,13 +393,7 @@ public class TreeSchemaAutoCreatorAndVerifier {
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);
}
@@ -413,6 +413,28 @@ public class TreeSchemaAutoCreatorAndVerifier {
}
}
+ 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;
+ }
+
+ // The path parser normalizes a trailing separator away, for example,
"root." to "root".
+ if (!databaseName.equals(databasePath.getFullPath())) {
+ continue;
+ }
+
+ schemaCache.addAlreadySetDatabase(databasePath);
+ databasesNeededToBeSet.removeIf(
+ database -> database.startsWithOrPrefixOf(databasePath.getNodes()));
+ }
+ }
+
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
index 68f4bde29e6..1a11de1eab5 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
@@ -200,6 +200,74 @@ public class LoadTsFileAnalyzerTest {
}
}
+ @Test
+ public void testTreeSchemaVerifierShouldRejectDeviceWithEmptyPathNode()
throws Exception {
+ final File tsFile = File.createTempFile("load-tree-illegal-device",
".tsfile");
+
+ try (final LoadTsFileAnalyzer analyzer =
+ new LoadTsFileAnalyzer(
+ LoadTsFileStatement.createUnchecked(tsFile.getAbsolutePath()),
+ false,
+ new MPPQueryContext(new
QueryId("load_tree_illegal_device_test")))) {
+ final TreeSchemaAutoCreatorAndVerifier verifier =
+ new TreeSchemaAutoCreatorAndVerifier(analyzer);
+ try {
+ final IDeviceID device = new StringArrayDeviceID(new String[] {"root",
""});
+ getTreeSchemaCache(verifier)
+ .addTimeSeries(device, new MeasurementSchema("s1",
TSDataType.INT32));
+
+ final InvocationTargetException exception =
+ Assert.assertThrows(
+ InvocationTargetException.class,
+ () -> getAutoCreateDatabaseMethod().invoke(verifier));
+ Assert.assertTrue(exception.getCause() instanceof
LoadAnalyzeException);
+ } finally {
+ verifier.close();
+ }
+ } finally {
+ Assert.assertTrue(tsFile.delete());
+ }
+ }
+
+ @Test
+ public void
testTreeSchemaVerifierShouldIgnoreLegacyDatabaseWithEmptyPathNode() throws
Exception {
+ final File tsFile = File.createTempFile("load-tree-legacy-database",
".tsfile");
+
+ try (final LoadTsFileAnalyzer analyzer =
+ new LoadTsFileAnalyzer(
+ LoadTsFileStatement.createUnchecked(tsFile.getAbsolutePath()),
+ false,
+ new MPPQueryContext(new
QueryId("load_tree_legacy_database_test")))) {
+ final TreeSchemaAutoCreatorAndVerifier verifier =
+ new TreeSchemaAutoCreatorAndVerifier(analyzer);
+ try {
+ final PartialPath database = new PartialPath("root.sg");
+ final PartialPath databaseWithSameStringPrefix = new
PartialPath("root.sg1");
+ final Set<PartialPath> databasesNeededToBeSet =
+ new HashSet<>(Arrays.asList(database,
databaseWithSameStringPrefix));
+
+ verifier.filterAlreadySetDatabases(databasesNeededToBeSet,
Collections.singleton("root."));
+
+ Assert.assertEquals(
+ new HashSet<>(Arrays.asList(database,
databaseWithSameStringPrefix)),
+ databasesNeededToBeSet);
+
Assert.assertTrue(getTreeSchemaCache(verifier).getAlreadySetDatabases().isEmpty());
+
+ verifier.filterAlreadySetDatabases(
+ databasesNeededToBeSet,
Collections.singleton(database.getFullPath()));
+
+ Assert.assertEquals(
+ Collections.singleton(databaseWithSameStringPrefix),
databasesNeededToBeSet);
+ Assert.assertEquals(
+ Collections.singleton(database),
getTreeSchemaCache(verifier).getAlreadySetDatabases());
+ } finally {
+ verifier.close();
+ }
+ } finally {
+ Assert.assertTrue(tsFile.delete());
+ }
+ }
+
@Test
public void
testPipeGeneratedLoadMissingSchemaShouldBeTemporaryWhenAutoCreateDisabled()
throws Exception {
@@ -316,6 +384,13 @@ public class LoadTsFileAnalyzerTest {
return method;
}
+ private Method getAutoCreateDatabaseMethod() throws NoSuchMethodException {
+ final Method method =
+
TreeSchemaAutoCreatorAndVerifier.class.getDeclaredMethod("autoCreateDatabase");
+ method.setAccessible(true);
+ return method;
+ }
+
private
org.apache.iotdb.commons.queryengine.plan.relational.metadata.TableSchema
createTableSchema(final TSDataType fieldType) {
return new
org.apache.iotdb.commons.queryengine.plan.relational.metadata.TableSchema(