From ba78757db255cc0aa2b5d534ede54f834457c5ea Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Mon, 21 Sep 2026 03:11:03 +0000 Subject: [PATCH] Fix PathPatternNode wildcard cache lifecycle (#18672) --- .../iotdb/commons/path/PathPatternNode.java | 39 +++++++--- .../iotdb/commons/path/PathPatternUtil.java | 6 +- .../commons/path/PathPatternNodeTest.java | 71 +++++++++++++++++++ 3 files changed, 104 insertions(+), 12 deletions(-) create mode 100644 iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/path/PathPatternNodeTest.java diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/path/PathPatternNode.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/path/PathPatternNode.java index 3dd274b8c3704..21e8a445318c7 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/path/PathPatternNode.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/path/PathPatternNode.java @@ -38,6 +38,7 @@ import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Supplier; +import java.util.regex.Pattern; import static org.apache.iotdb.commons.conf.IoTDBConstant.MULTI_LEVEL_PATH_WILDCARD; import static org.apache.iotdb.commons.conf.IoTDBConstant.ONE_LEVEL_PATH_WILDCARD; @@ -58,9 +59,9 @@ public class PathPatternNode childrenNamesWithNonTrivialWildcard = new HashSet<>(); + // Compiled patterns for child names with wildcard, for accelerating wildcard searching. + // Here we do not include "*" or "**" to ensure that the map is empty most of the time. + private final Map childrenPatternsWithNonTrivialWildcard = new HashMap<>(); public PathPatternNode(String name, VSerializer serializer) { this.name = name; @@ -94,10 +95,12 @@ public List> getMatchChildren(String nodeName) { if (children.containsKey(MULTI_LEVEL_PATH_WILDCARD)) { res.add(children.get(MULTI_LEVEL_PATH_WILDCARD)); } - childrenNamesWithNonTrivialWildcard.stream() - .filter(path -> PathPatternUtil.isNodeMatch(path, nodeName)) - .map(children::get) - .forEach(res::add); + for (final Map.Entry entry : + childrenPatternsWithNonTrivialWildcard.entrySet()) { + if (entry.getValue().matcher(nodeName).matches()) { + res.add(children.get(entry.getKey())); + } + } return res; } @@ -110,13 +113,16 @@ public void addChild(PathPatternNode tmpNode) { if (PathPatternUtil.hasWildcard(nodeName) && !PathPatternUtil.isMultiLevelMatchWildcard(nodeName) && !ONE_LEVEL_PATH_WILDCARD.equals(nodeName)) { - childrenNamesWithNonTrivialWildcard.add(nodeName); + childrenPatternsWithNonTrivialWildcard.computeIfAbsent( + nodeName, PathPatternUtil::compileNodePattern); } children.put(nodeName, tmpNode); } - public void deleteChild(PathPatternNode tmpNode) { - children.remove(tmpNode.getName()); + public void deleteChild(final PathPatternNode tmpNode) { + final String nodeName = tmpNode.getName(); + children.remove(nodeName); + childrenPatternsWithNonTrivialWildcard.remove(nodeName); } public void appendValue(V value, BiConsumer> remappingFunction) { @@ -248,6 +254,14 @@ void serializeChildren(DataOutputStream outputStream) throws IOException { } } + void clear() { + if (Objects.nonNull(valueSet)) { + valueSet.clear(); + } + children.clear(); + childrenPatternsWithNonTrivialWildcard.clear(); + } + public static > PathPatternNode deserializeNode( ByteBuffer buffer, T serializer, Consumer nodeNameProcessor) { PathPatternNode node = @@ -279,7 +293,10 @@ public long ramBytesUsed() { return SHALLOW_SIZE + RamUsageEstimator.sizeOf(name) + RamUsageEstimator.sizeOfHashSet(valueSet) - + RamUsageEstimator.sizeOfHashSet(childrenNamesWithNonTrivialWildcard) + + RamUsageEstimator.sizeOfMapWithKnownShallowSize( + childrenPatternsWithNonTrivialWildcard, + RamUsageEstimator.SHALLOW_SIZE_OF_HASHMAP, + RamUsageEstimator.SHALLOW_SIZE_OF_HASHMAP_ENTRY) + RamUsageEstimator.sizeOfMapWithKnownShallowSize( children, RamUsageEstimator.SHALLOW_SIZE_OF_HASHMAP, diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/path/PathPatternUtil.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/path/PathPatternUtil.java index 6ee736453500e..23e8d506a12ea 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/path/PathPatternUtil.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/path/PathPatternUtil.java @@ -52,6 +52,10 @@ public static boolean isNodeMatch(String patternNode, String nodeName) { || patternNode.equals(MULTI_LEVEL_PATH_WILDCARD)) { return true; } - return Pattern.matches(patternNode.replace("*", ".*"), nodeName); + return compileNodePattern(patternNode).matcher(nodeName).matches(); + } + + static Pattern compileNodePattern(final String patternNode) { + return Pattern.compile(patternNode.replace("*", ".*")); } } diff --git a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/path/PathPatternNodeTest.java b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/path/PathPatternNodeTest.java new file mode 100644 index 0000000000000..7206c9cc8f400 --- /dev/null +++ b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/path/PathPatternNodeTest.java @@ -0,0 +1,71 @@ +/* + * 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.commons.path; + +import org.apache.iotdb.commons.path.PathPatternNode.VoidSerializer; + +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +public class PathPatternNodeTest { + + @Test + public void testNonTrivialWildcardChildCacheLifecycle() { + final PathPatternNode parent = newNode("parent"); + final PathPatternNode wildcardChild = newNode("device*"); + + parent.addChild(wildcardChild); + final List> matchedChildren = + parent.getMatchChildren("device1"); + assertEquals(1, matchedChildren.size()); + assertSame(wildcardChild, matchedChildren.get(0)); + + parent.deleteChild(wildcardChild); + assertTrue(parent.getMatchChildren("device1").isEmpty()); + + parent.addChild(wildcardChild); + parent.clear(); + assertTrue(parent.getMatchChildren("device1").isEmpty()); + } + + @Test + public void testReplacingNonTrivialWildcardChildKeepsCache() { + final PathPatternNode parent = newNode("parent"); + final PathPatternNode originalChild = newNode("device*"); + final PathPatternNode replacementChild = newNode("device*"); + + parent.addChild(originalChild); + parent.addChild(replacementChild); + + final List> matchedChildren = + parent.getMatchChildren("device1"); + assertEquals(1, matchedChildren.size()); + assertSame(replacementChild, matchedChildren.get(0)); + } + + private PathPatternNode newNode(final String name) { + return new PathPatternNode<>(name, VoidSerializer.getInstance()); + } +}