From 099ed5633061b5ccd98925979e1ecdb090a8c84a Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Sat, 25 Jul 2026 19:50:55 +0800 Subject: [PATCH 1/3] Reject illegal device paths during LOAD --- .../persistence/schema/ConfigMTree.java | 5 +++ .../persistence/schema/ConfigMTreeTest.java | 7 ++++ .../TreeSchemaAutoCreatorAndVerifier.java | 6 ++++ .../analyze/load/LoadTsFileAnalyzerTest.java | 36 +++++++++++++++++++ 4 files changed, 54 insertions(+) diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/ConfigMTree.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/ConfigMTree.java index 7456f339af7bd..b3160e9e76184 100644 --- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/ConfigMTree.java +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/ConfigMTree.java @@ -142,6 +142,11 @@ public void clear() { */ public void setStorageGroup(final PartialPath path) throws MetadataException { final String[] nodeNames = path.getNodes(); + for (final String nodeName : nodeNames) { + if (nodeName == null || nodeName.isEmpty()) { + throw new IllegalPathException(path.getFullPath()); + } + } MetaFormatUtils.checkDatabase(path.getFullPath()); if (nodeNames.length <= 1 || !nodeNames[0].equals(root.getName())) { throw new IllegalPathException(path.getFullPath()); diff --git a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/schema/ConfigMTreeTest.java b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/schema/ConfigMTreeTest.java index c2519dcbfae11..45b55e123e611 100644 --- a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/schema/ConfigMTreeTest.java +++ b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/schema/ConfigMTreeTest.java @@ -102,6 +102,13 @@ public void testSetStorageGroupExceptionMessage() { } } + @Test + public void testSetStorageGroupShouldRejectEmptyPathNode() { + Assert.assertThrows( + IllegalPathException.class, + () -> root.setStorageGroup(new PartialPath(new String[] {"root", ""}))); + } + @Test public void testAddAndPathExist() throws MetadataException { final String path1 = "root"; 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 6d1704c0cfd0a..2e50a88f5d13c 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 @@ private void autoCreateDatabase() 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( 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 68f4bde29e6a6..0b6e5350882a6 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,35 @@ public void testTreeSchemaVerifierShouldThrowMismatchWhenVerifyingDataType() thr } } + @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 testPipeGeneratedLoadMissingSchemaShouldBeTemporaryWhenAutoCreateDisabled() throws Exception { @@ -316,6 +345,13 @@ private Method getVerifyTreeSchemaMethod() throws NoSuchMethodException { 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( From e5d8c2661c0460fcfb64b5bb85fb57ca42c9687d Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Sat, 25 Jul 2026 20:41:15 +0800 Subject: [PATCH 2/3] Remove duplicate ConfigNode validation --- .../iotdb/confignode/persistence/schema/ConfigMTree.java | 5 ----- .../confignode/persistence/schema/ConfigMTreeTest.java | 7 ------- 2 files changed, 12 deletions(-) diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/ConfigMTree.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/ConfigMTree.java index b3160e9e76184..7456f339af7bd 100644 --- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/ConfigMTree.java +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/ConfigMTree.java @@ -142,11 +142,6 @@ public void clear() { */ public void setStorageGroup(final PartialPath path) throws MetadataException { final String[] nodeNames = path.getNodes(); - for (final String nodeName : nodeNames) { - if (nodeName == null || nodeName.isEmpty()) { - throw new IllegalPathException(path.getFullPath()); - } - } MetaFormatUtils.checkDatabase(path.getFullPath()); if (nodeNames.length <= 1 || !nodeNames[0].equals(root.getName())) { throw new IllegalPathException(path.getFullPath()); diff --git a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/schema/ConfigMTreeTest.java b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/schema/ConfigMTreeTest.java index 45b55e123e611..c2519dcbfae11 100644 --- a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/schema/ConfigMTreeTest.java +++ b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/persistence/schema/ConfigMTreeTest.java @@ -102,13 +102,6 @@ public void testSetStorageGroupExceptionMessage() { } } - @Test - public void testSetStorageGroupShouldRejectEmptyPathNode() { - Assert.assertThrows( - IllegalPathException.class, - () -> root.setStorageGroup(new PartialPath(new String[] {"root", ""}))); - } - @Test public void testAddAndPathExist() throws MetadataException { final String path1 = "root"; From e2e88d2e20be0491650d9844ebdde1e54f303779 Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Mon, 27 Jul 2026 10:27:00 +0800 Subject: [PATCH 3/3] Handle legacy invalid databases during LOAD --- .../TreeSchemaAutoCreatorAndVerifier.java | 30 ++++++++++---- .../analyze/load/LoadTsFileAnalyzerTest.java | 39 +++++++++++++++++++ 2 files changed, 62 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 2e50a88f5d13c..3e968073cc035 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 @@ -393,13 +393,7 @@ private void autoCreateDatabase() 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); } @@ -419,6 +413,28 @@ private void autoCreateDatabase() } } + void filterAlreadySetDatabases( + final Set databasesNeededToBeSet, final Set 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 0b6e5350882a6..1a11de1eab54e 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 @@ -229,6 +229,45 @@ public void testTreeSchemaVerifierShouldRejectDeviceWithEmptyPathNode() throws E } } + @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 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 {