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.

Reply via email to