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 2003fdfcc95d1..56240432f69a2 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,10 +59,9 @@ public class PathPatternNode> impleme private final S serializer; - // Children names with wildcard, for accelerating wildcard searching - // Here we do not include "*" or "**" - // to ensure that the set is empty in most of the time, in order to save memory. - private final Set 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(final String name, final S serializer) { this.name = name; @@ -97,10 +97,12 @@ public List> getMatchChildren(final 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; } @@ -113,13 +115,16 @@ public void addChild(final 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(final PathPatternNode tmpNode) { - children.remove(tmpNode.getName()); + final String nodeName = tmpNode.getName(); + children.remove(nodeName); + childrenPatternsWithNonTrivialWildcard.remove(nodeName); } public void appendValue(final V value, final BiConsumer> remappingFunction) { @@ -256,6 +261,7 @@ void clear() { valueSet.clear(); } children.clear(); + childrenPatternsWithNonTrivialWildcard.clear(); } public static > PathPatternNode deserializeNode( @@ -289,7 +295,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 caf65e849a1f3..44347b8959f3a 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 @@ -53,6 +53,10 @@ public static boolean isNodeMatch(final String patternNode, final String nodeNam || 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()); + } +}