This is an automated email from the ASF dual-hosted git repository.
JackieTien97 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 bf1ea20e235 Prevent ancestor devices from matching descendant template
mounts (#19028)
bf1ea20e235 is described below
commit bf1ea20e2356734231a626ffa1c0a76373b59056
Author: alpass163 <[email protected]>
AuthorDate: Wed Oct 28 10:35:59 2026 +0800
Prevent ancestor devices from matching descendant template mounts (#19028)
---
.../template/ClusterTemplateManager.java | 2 +-
.../BatchActivateTemplateStatementTest.java | 6 ++-
.../template/ClusterTemplateManagerTest.java | 62 ++++++++++++++++++++++
.../org/apache/iotdb/commons/path/PartialPath.java | 10 ++++
.../apache/iotdb/commons/path/PartialPathTest.java | 19 +++++++
5 files changed, 97 insertions(+), 2 deletions(-)
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 816effce329..25f699e2770 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
@@ -292,7 +292,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
index 8ffc5f8691f..02ae8c1e7e9 100644
---
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
@@ -74,7 +74,9 @@ public class BatchActivateTemplateStatementTest {
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")));
+ new PartialPath("root.sg.v3.d1"),
+ // A template mounted below this ancestor must not be selected.
+ new PartialPath("root.sg")));
final List<PartialPath> paths = statement.getPaths();
@@ -96,6 +98,8 @@ public class BatchActivateTemplateStatementTest {
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")));
+ // A template mounted below this ancestor must not be selected.
+ deviceMap.put(new PartialPath("root.sg"), new Pair<>(template1, new
PartialPath("root.sg.v1")));
final InternalBatchActivateTemplateStatement statement =
new InternalBatchActivateTemplateStatement(deviceMap);
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 bc6cd1629b0..909ce7f544e 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
@@ -22,6 +22,8 @@ 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.commons.schema.template.Template;
import org.apache.iotdb.db.protocol.client.ConfigNodeClient;
import org.apache.iotdb.db.protocol.client.ConfigNodeClientManager;
import org.apache.iotdb.db.protocol.client.ConfigNodeInfo;
@@ -34,6 +36,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;
@@ -46,6 +49,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;
@@ -118,6 +122,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 9041402be46..5fe2ce72e3b 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
@@ -941,6 +941,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 777f06d2d39..b1102387b76 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
@@ -408,6 +408,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.