This is an automated email from the ASF dual-hosted git repository.
JackieTien97 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 552acfed78d Fix template activation path resolution and ancestor
template matching (#19041)
552acfed78d is described below
commit 552acfed78d5f3f1fcc940dc73f80b2f44a05dc4
Author: alpass163 <[email protected]>
AuthorDate: Fri Oct 30 12:08:49 2026 +0800
Fix template activation path resolution and ancestor template matching
(#19041)
---
.../InternalBatchActivateTemplateStatement.java | 15 +--
.../template/BatchActivateTemplateStatement.java | 15 +--
.../template/ClusterTemplateManager.java | 2 +-
.../BatchActivateTemplateStatementTest.java | 141 +++++++++++++++++++++
.../template/ClusterTemplateManagerTest.java | 61 +++++++++
.../org/apache/iotdb/commons/path/PartialPath.java | 10 ++
.../apache/iotdb/commons/path/PartialPathTest.java | 19 +++
7 files changed, 246 insertions(+), 17 deletions(-)
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/internal/InternalBatchActivateTemplateStatement.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/internal/InternalBatchActivateTemplateStatement.java
index 44a1a66465e..2a63f543a30 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/internal/InternalBatchActivateTemplateStatement.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/internal/InternalBatchActivateTemplateStatement.java
@@ -35,7 +35,6 @@ import org.apache.tsfile.utils.Pair;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import java.util.stream.Collectors;
// This is only used for auto activate template on multi devices while
inserting data
public class InternalBatchActivateTemplateStatement extends Statement {
@@ -57,18 +56,18 @@ public class InternalBatchActivateTemplateStatement extends
Statement {
@Override
public List<PartialPath> getPaths() {
ClusterTemplateManager clusterTemplateManager =
ClusterTemplateManager.getInstance();
- List<String> templatePaths = new ArrayList<>();
- for (PartialPath path : deviceMap.keySet()) {
+ List<PartialPath> paths = new ArrayList<>();
+ for (PartialPath devicePath : deviceMap.keySet()) {
Pair<Template, PartialPath> templateSetInfo =
- clusterTemplateManager.checkTemplateSetInfo(path);
+ clusterTemplateManager.checkTemplateSetInfo(devicePath);
if (templateSetInfo == null) {
continue;
}
- templatePaths.addAll(templateSetInfo.left.getSchemaMap().keySet());
+ for (String measurement : templateSetInfo.left.getSchemaMap().keySet()) {
+ paths.add(devicePath.concatAsMeasurementPath(measurement));
+ }
}
- return deviceMap.keySet().stream()
- .flatMap(path -> templatePaths.stream().map(path::concatNode))
- .collect(Collectors.toList());
+ return paths;
}
@Override
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/metadata/template/BatchActivateTemplateStatement.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/metadata/template/BatchActivateTemplateStatement.java
index c941c8d900b..25ffdbf1c7a 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/metadata/template/BatchActivateTemplateStatement.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/metadata/template/BatchActivateTemplateStatement.java
@@ -34,7 +34,6 @@ import org.apache.tsfile.utils.Pair;
import java.util.ArrayList;
import java.util.List;
-import java.util.stream.Collectors;
public class BatchActivateTemplateStatement extends Statement {
@@ -62,18 +61,18 @@ public class BatchActivateTemplateStatement extends
Statement {
@Override
public List<PartialPath> getPaths() {
ClusterTemplateManager clusterTemplateManager =
ClusterTemplateManager.getInstance();
- List<String> templatePaths = new ArrayList<>();
- for (PartialPath path : devicePathList) {
+ List<PartialPath> paths = new ArrayList<>();
+ for (PartialPath devicePath : devicePathList) {
Pair<Template, PartialPath> templateSetInfo =
- clusterTemplateManager.checkTemplateSetInfo(path);
+ clusterTemplateManager.checkTemplateSetInfo(devicePath);
if (templateSetInfo == null) {
continue;
}
- templatePaths.addAll(templateSetInfo.left.getSchemaMap().keySet());
+ for (String measurement : templateSetInfo.left.getSchemaMap().keySet()) {
+ paths.add(devicePath.concatAsMeasurementPath(measurement));
+ }
}
- return devicePathList.stream()
- .flatMap(path -> templatePaths.stream().map(path::concatNode))
- .collect(Collectors.toList());
+ return paths;
}
public List<PartialPath> getDevicePathList() {
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/template/ClusterTemplateManager.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/template/ClusterTemplateManager.java
index 8e19e191da5..7cdb6c40227 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/template/ClusterTemplateManager.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/template/ClusterTemplateManager.java
@@ -296,7 +296,7 @@ public class ClusterTemplateManager implements
ITemplateManager {
readWriteLock.readLock().lock();
try {
for (PartialPath templateSetPath : pathSetTemplateMap.keySet()) {
- if (devicePath.startsWithOrPrefixOf(templateSetPath.getNodes())) {
+ if (devicePath.startsWithOrEqual(templateSetPath.getNodes())) {
return new Pair<>(
templateIdMap.get(pathSetTemplateMap.get(templateSetPath)),
templateSetPath);
}
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/statement/metadata/template/BatchActivateTemplateStatementTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/statement/metadata/template/BatchActivateTemplateStatementTest.java
new file mode 100644
index 00000000000..a8c13c88a17
--- /dev/null
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/statement/metadata/template/BatchActivateTemplateStatementTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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.statement.metadata.template;
+
+import org.apache.iotdb.commons.exception.IllegalPathException;
+import org.apache.iotdb.commons.path.PartialPath;
+import
org.apache.iotdb.db.queryengine.plan.statement.internal.InternalBatchActivateTemplateStatement;
+import org.apache.iotdb.db.schemaengine.template.ClusterTemplateManager;
+import org.apache.iotdb.db.schemaengine.template.Template;
+import org.apache.iotdb.db.schemaengine.template.TemplateInternalRPCUtil;
+
+import org.apache.tsfile.enums.TSDataType;
+import org.apache.tsfile.file.metadata.enums.CompressionType;
+import org.apache.tsfile.file.metadata.enums.TSEncoding;
+import org.apache.tsfile.utils.Pair;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+public class BatchActivateTemplateStatementTest {
+
+ private Template template1;
+ private Template template2;
+
+ @Before
+ public void setUp() throws Exception {
+ final ClusterTemplateManager templateManager =
ClusterTemplateManager.getInstance();
+ templateManager.clear();
+
+ template1 = createTemplate("t1", 1, Arrays.asList("s1", "s2"));
+ templateManager.addTemplateSetInfo(
+ TemplateInternalRPCUtil.generateAddTemplateSetInfoBytes(template1,
"root.sg.v1"));
+
+ template2 = createTemplate("t2", 2, Collections.singletonList("s3"));
+ templateManager.addTemplateSetInfo(
+ TemplateInternalRPCUtil.generateAddTemplateSetInfoBytes(template2,
"root.sg.v2"));
+ }
+
+ @After
+ public void tearDown() {
+ ClusterTemplateManager.getInstance().clear();
+ }
+
+ @Test
+ public void testGetPathsUsesTheTemplateOfEachDevice() throws
IllegalPathException {
+ final BatchActivateTemplateStatement statement =
+ new BatchActivateTemplateStatement(
+ Arrays.asList(
+ new PartialPath("root.sg.v1.d1"),
+ new PartialPath("root.sg.v2.d1"),
+ // no template is set on this path, so it contributes no path
+ new PartialPath("root.sg.v3.d1"),
+ // A template mounted on a descendant cannot be used by its
ancestor.
+ new PartialPath("root.sg")));
+
+ final List<PartialPath> paths = statement.getPaths();
+
+ // s1 and s2 belong to template1, s3 belongs to template2; each path
appears exactly once
+ Assert.assertEquals(3, paths.size());
+ Assert.assertEquals(
+ new HashSet<>(
+ Arrays.asList(
+ new PartialPath("root.sg.v1.d1.s1"),
+ new PartialPath("root.sg.v1.d1.s2"),
+ new PartialPath("root.sg.v2.d1.s3"))),
+ new HashSet<>(paths));
+ }
+
+ @Test
+ public void testInternalGetPathsUsesTheTemplateOfEachDevice() throws
IllegalPathException {
+ final Map<PartialPath, Pair<Template, PartialPath>> deviceMap = new
HashMap<>();
+ deviceMap.put(
+ new PartialPath("root.sg.v1.d1"), new Pair<>(template1, new
PartialPath("root.sg.v1")));
+ deviceMap.put(
+ new PartialPath("root.sg.v2.d1"), new Pair<>(template2, new
PartialPath("root.sg.v2")));
+
+ deviceMap.put(new PartialPath("root.sg"), new Pair<>(template1, new
PartialPath("root.sg.v1")));
+
+ final InternalBatchActivateTemplateStatement statement =
+ new InternalBatchActivateTemplateStatement(deviceMap);
+
+ final List<PartialPath> paths = statement.getPaths();
+
+ Assert.assertEquals(3, paths.size());
+ Assert.assertEquals(
+ new HashSet<>(
+ Arrays.asList(
+ new PartialPath("root.sg.v1.d1.s1"),
+ new PartialPath("root.sg.v1.d1.s2"),
+ new PartialPath("root.sg.v2.d1.s3"))),
+ new HashSet<>(paths));
+ }
+
+ @Test
+ public void testSingleGetPathsRequiresMountPathOrDescendant() throws
IllegalPathException {
+ Assert.assertTrue(
+ new ActivateTemplateStatement(new
PartialPath("root.sg")).getPaths().isEmpty());
+ Assert.assertEquals(
+ new HashSet<>(
+ Arrays.asList(new PartialPath("root.sg.v1.s1"), new
PartialPath("root.sg.v1.s2"))),
+ new HashSet<>(new ActivateTemplateStatement(new
PartialPath("root.sg.v1")).getPaths()));
+ }
+
+ private static Template createTemplate(
+ final String name, final int id, final List<String> measurements) throws
Exception {
+ final Template template =
+ new Template(
+ name,
+ measurements,
+ Collections.nCopies(measurements.size(), TSDataType.DOUBLE),
+ Collections.nCopies(measurements.size(), TSEncoding.RLE),
+ Collections.nCopies(measurements.size(), CompressionType.SNAPPY));
+ template.setId(id);
+ return template;
+ }
+}
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/template/ClusterTemplateManagerTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/template/ClusterTemplateManagerTest.java
index c029542daf3..a29e2e6b3bd 100644
---
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/template/ClusterTemplateManagerTest.java
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/template/ClusterTemplateManagerTest.java
@@ -21,6 +21,7 @@ package org.apache.iotdb.db.schemaengine.template;
import org.apache.iotdb.common.rpc.thrift.TSStatus;
import org.apache.iotdb.commons.client.IClientManager;
import org.apache.iotdb.commons.consensus.ConfigRegionId;
+import org.apache.iotdb.commons.path.PartialPath;
import org.apache.iotdb.db.protocol.client.ConfigNodeClient;
import org.apache.iotdb.db.protocol.client.ConfigNodeClientManager;
import org.apache.iotdb.db.protocol.client.ConfigNodeInfo;
@@ -33,6 +34,7 @@ import org.apache.thrift.TException;
import org.apache.tsfile.enums.TSDataType;
import org.apache.tsfile.file.metadata.enums.CompressionType;
import org.apache.tsfile.file.metadata.enums.TSEncoding;
+import org.apache.tsfile.utils.Pair;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -45,6 +47,7 @@ import org.powermock.modules.junit4.PowerMockRunner;
import java.util.Collections;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
@@ -117,6 +120,64 @@ public class ClusterTemplateManagerTest {
assertTrue(status.getMessage().contains("test_template"));
}
+ @Test
+ public void testCheckTemplateSetInfo() throws Exception {
+ ClusterTemplateManager manager = new ClusterTemplateManager();
+ Template template =
+ new Template(
+ "test_template",
+ Collections.singletonList("s1"),
+ Collections.singletonList(TSDataType.INT32),
+ Collections.singletonList(TSEncoding.PLAIN),
+ Collections.singletonList(CompressionType.UNCOMPRESSED));
+ template.setId(1);
+ PartialPath setPath = new PartialPath("root.db1.sg01.plant01");
+ manager.addTemplateSetInfo(
+ TemplateInternalRPCUtil.generateAddTemplateSetInfoBytes(template,
setPath.getFullPath()));
+
+ Pair<Template, PartialPath> onMount =
manager.checkTemplateSetInfo(setPath);
+ assertEquals(template.getId(), onMount.left.getId());
+ assertEquals(setPath, onMount.right);
+ Pair<Template, PartialPath> onDescendant =
+ manager.checkTemplateSetInfo(new
PartialPath("root.db1.sg01.plant01.d1"));
+ assertEquals(template.getId(), onDescendant.left.getId());
+ assertEquals(setPath, onDescendant.right);
+
+ assertNull(manager.checkTemplateSetInfo(new PartialPath("root.db1.sg01")));
+ assertNull(manager.checkTemplateSetInfo(new PartialPath("root.db1")));
+ assertNull(manager.checkTemplateSetInfo(new PartialPath("root")));
+ assertNull(manager.checkTemplateSetInfo(new
PartialPath("root.db1.sg01.plant02.d1")));
+ assertNull(manager.checkTemplateSetInfo(new
PartialPath("root.db1.sg01.plant01_extra.d1")));
+ assertNull(manager.checkTemplateSetInfo(new
PartialPath("root.db2.sg01.plant01.d1")));
+
+ // An ancestor is still a conflict when creating a measurement, not a
template device.
+ assertEquals(
+ setPath,
+ manager.checkTemplateSetAndPreSetInfo(new
PartialPath("root.db1.sg01"), null).right);
+
+ Template otherTemplate =
+ new Template(
+ "other_template",
+ Collections.singletonList("s2"),
+ Collections.singletonList(TSDataType.INT32),
+ Collections.singletonList(TSEncoding.PLAIN),
+ Collections.singletonList(CompressionType.UNCOMPRESSED));
+ otherTemplate.setId(2);
+ PartialPath otherSetPath = new PartialPath("root.db1.sg01.plant02");
+ manager.addTemplateSetInfo(
+ TemplateInternalRPCUtil.generateAddTemplateSetInfoBytes(
+ otherTemplate, otherSetPath.getFullPath()));
+
+ // A common ancestor must not pick either descendant's template.
+ assertNull(manager.checkTemplateSetInfo(new PartialPath("root.db1.sg01")));
+ assertEquals(
+ otherTemplate.getId(),
+ manager.checkTemplateSetInfo(new
PartialPath("root.db1.sg01.plant02.d1")).left.getId());
+ assertEquals(
+ template.getId(),
+ manager.checkTemplateSetInfo(new
PartialPath("root.db1.sg01.plant01.d1")).left.getId());
+ }
+
private static TSStatus assertRpcFailure(
RuntimeException exception, TException rpcFailure, TSStatusCode
statusCode) {
TSStatus status =
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/path/PartialPath.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/path/PartialPath.java
index d9d940bfad1..0f9c96b4d12 100644
---
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/path/PartialPath.java
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/path/PartialPath.java
@@ -833,6 +833,16 @@ public class PartialPath extends Path implements
Comparable<Path>, Cloneable {
return true;
}
+ /** Returns whether the given nodes are a prefix of this path, including
equality. */
+ public boolean startsWithOrEqual(String[] otherNodes) {
+ for (int i = 0; i < otherNodes.length; i++) {
+ if ((i >= nodes.length) || !nodes[i].equals(otherNodes[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
public boolean startsWith(String prefix) {
return getFullPath().startsWith(prefix);
}
diff --git
a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/path/PartialPathTest.java
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/path/PartialPathTest.java
index a2a437d988a..d35aa1ae87d 100644
---
a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/path/PartialPathTest.java
+++
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/path/PartialPathTest.java
@@ -401,6 +401,25 @@ public class PartialPathTest {
Assert.assertEquals("root", b.getFirstNode());
}
+ @Test
+ public void testStartsWithOrEqual() throws IllegalPathException {
+ PartialPath setPath = new PartialPath("root.db1.sg01.plant01");
+ String[] setNodes = setPath.getNodes();
+
+ Assert.assertTrue(setPath.startsWithOrEqual(setNodes));
+ Assert.assertTrue(new
PartialPath("root.db1.sg01.plant01.d1").startsWithOrEqual(setNodes));
+ Assert.assertFalse(new
PartialPath("root.db1.sg01").startsWithOrEqual(setNodes));
+ Assert.assertFalse(new PartialPath("root").startsWithOrEqual(setNodes));
+ Assert.assertFalse(new
PartialPath("root.db1.sg01.plant02").startsWithOrEqual(setNodes));
+ Assert.assertFalse(new
PartialPath("root.db1.sg01.plant01_extra").startsWithOrEqual(setNodes));
+ Assert.assertFalse(new
PartialPath("root.db2.sg01.plant01.d1").startsWithOrEqual(setNodes));
+ Assert.assertFalse(new
PartialPath("root.db1.sg01.*").startsWithOrEqual(setNodes));
+ Assert.assertTrue(setPath.startsWithOrEqual(new String[0]));
+
+ // The bidirectional check is still needed for path-conflict detection.
+ Assert.assertTrue(new
PartialPath("root.db1.sg01").startsWithOrPrefixOf(setNodes));
+ }
+
@Test
public void testAlterPrefixPath() throws IllegalPathException {
// Plain path.