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..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 @@ -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( @@ -387,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); } @@ -413,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 68f4bde29e6a6..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 @@ -200,6 +200,74 @@ 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 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 { @@ -316,6 +384,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(