diff --git a/spectator-reg-atlas/src/main/java/com/netflix/spectator/atlas/impl/QueryIndex.java b/spectator-reg-atlas/src/main/java/com/netflix/spectator/atlas/impl/QueryIndex.java index 7a6824369..fd486e2e5 100644 --- a/spectator-reg-atlas/src/main/java/com/netflix/spectator/atlas/impl/QueryIndex.java +++ b/spectator-reg-atlas/src/main/java/com/netflix/spectator/atlas/impl/QueryIndex.java @@ -26,6 +26,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Supplier; /** @@ -362,6 +363,7 @@ public void forEachMatch(Id id, Consumer consumer) { forEachMatch(id, 0, consumer); } + @SuppressWarnings("PMD.NPathComplexity") private void forEachMatch(Id tags, int i, Consumer consumer) { // Matches for this level matches.forEach(consumer); @@ -414,7 +416,10 @@ private void forEachMatch(Id tags, int i, Consumer consumer) { if (hasKeyIdx != null) { hasKeyIdx.forEachMatch(tags, i, consumer); } - } else if (cmp > 0) { + } + + // Quit loop if the key was found or not present + if (cmp >= 0) { break; } } @@ -431,6 +436,93 @@ private void forEachMatch(Id tags, int i, Consumer consumer) { } } + /** + * Find all values where the corresponding queries match the specified tags. This can be + * used if the tags are not already structured as a spectator Id. + * + * @param tags + * Function to look up the value for a given tag key. The function should return + * {@code null} if there is no value for the key. + * @return + * List of all matching values for the id. + */ + public List findMatches(Function tags) { + List result = new ArrayList<>(); + forEachMatch(tags, result::add); + return result; + } + + /** + * Invoke the consumer for all values where the corresponding queries match the specified tags. + * This can be used if the tags are not already structured as a spectator Id. + * + * @param tags + * Function to look up the value for a given tag key. The function should return + * {@code null} if there is no value for the key. + * @param consumer + * Function to invoke for values associated with a query that matches the id. + */ + public void forEachMatch(Function tags, Consumer consumer) { + // Matches for this level + matches.forEach(consumer); + + boolean keyPresent = false; + if (key != null) { + String v = tags.apply(key); + if (v != null) { + keyPresent = true; + + // Find exact matches + QueryIndex eqIdx = equalChecks.get(v); + if (eqIdx != null) { + eqIdx.forEachMatch(tags, consumer); + } + + // Scan for matches with other conditions + List> otherMatches = otherChecksCache.get(v); + if (otherMatches == null) { + // Avoid the list and cache allocations if there are no other checks at + // this level + if (!otherChecks.isEmpty()) { + List> tmp = new ArrayList<>(); + otherChecksTree.forEach(v, kq -> { + if (kq.matches(v)) { + QueryIndex idx = otherChecks.get(kq); + if (idx != null) { + tmp.add(idx); + idx.forEachMatch(tags, consumer); + } + } + }); + otherChecksCache.put(v, tmp); + } + } else { + // Enhanced for loop typically results in iterator being allocated. Using + // size/get avoids the allocation and has better throughput. + int n = otherMatches.size(); + for (int p = 0; p < n; ++p) { + otherMatches.get(p).forEachMatch(tags, consumer); + } + } + + // Check matches for has key + if (hasKeyIdx != null) { + hasKeyIdx.forEachMatch(tags, consumer); + } + } + } + + // Check matches with other keys + if (otherKeysIdx != null) { + otherKeysIdx.forEachMatch(tags, consumer); + } + + // Check matches with missing keys + if (missingKeysIdx != null && !keyPresent) { + missingKeysIdx.forEachMatch(tags, consumer); + } + } + @Override public String toString() { StringBuilder builder = new StringBuilder(); buildString(builder, 0); diff --git a/spectator-reg-atlas/src/test/java/com/netflix/spectator/atlas/impl/QueryIndexTest.java b/spectator-reg-atlas/src/test/java/com/netflix/spectator/atlas/impl/QueryIndexTest.java index 99e8934f0..6e04f14ea 100644 --- a/spectator-reg-atlas/src/test/java/com/netflix/spectator/atlas/impl/QueryIndexTest.java +++ b/spectator-reg-atlas/src/test/java/com/netflix/spectator/atlas/impl/QueryIndexTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2022 Netflix, Inc. + * Copyright 2014-2023 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,22 +19,71 @@ import com.netflix.spectator.api.Id; import com.netflix.spectator.api.NoopRegistry; import com.netflix.spectator.api.Registry; +import com.netflix.spectator.impl.Cache; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; -import java.util.HashSet; +import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Random; import java.util.Set; +import java.util.function.Function; public class QueryIndexTest { private final Registry registry = new DefaultRegistry(); + private final QueryIndex.CacheSupplier cacheSupplier = new QueryIndex.CacheSupplier() { + @Override + public Cache>> get() { + return new Cache>>() { + private final Map>> data = new HashMap<>(); + + @Override + public List> get(String key) { + // Cache for a single call + return data.remove(key); + } + + @Override + public List> peek(String key) { + return null; + } + + @Override + public void put(String key, List> value) { + data.put(key, value); + } + + @Override + public List> computeIfAbsent(String key, Function>> f) { + return data.computeIfAbsent(key, f); + } + + @Override + public void clear() { + data.clear(); + } + + @Override + public int size() { + return data.size(); + } + + @Override + public Map>> asMap() { + return new HashMap<>(data); + } + }; + } + }; + private Id id(String name, String... tags) { return registry.createId(name, tags); } @@ -48,40 +97,46 @@ private List list(Query... vs) { return sort(Arrays.asList(vs)); } - private List findMatches(QueryIndex idx, Id id) { - return sort(idx.findMatches(id)); - } - @Test public void empty() { - QueryIndex idx = QueryIndex.newInstance(registry); - Assertions.assertTrue(idx.findMatches(id("a")).isEmpty()); + QueryIndex idx = QueryIndex.newInstance(cacheSupplier); + assertEquals(Collections.emptyList(), idx, id("a")); } private static final Query SIMPLE_QUERY = Parser.parseQuery("name,a,:eq,key,b,:eq,:and"); private QueryIndex simpleIdx() { - return QueryIndex.newInstance(registry).add(SIMPLE_QUERY, SIMPLE_QUERY); + return QueryIndex.newInstance(cacheSupplier).add(SIMPLE_QUERY, SIMPLE_QUERY); + } + + private void assertEquals(List expected, QueryIndex idx, Id id) { + // Do multiple iterations just to exercise caching and cache expiration paths + for (int i = 0; i < 4; ++i) { + Assertions.assertEquals(expected, sort(idx.findMatches(id))); + } + for (int i = 0; i < 4; ++i) { + Assertions.assertEquals(expected, sort(idx.findMatches(Query.toMap(id)::get))); + } } @Test public void simpleMissingKey() { Id id = id("a", "foo", "bar"); - Assertions.assertTrue(simpleIdx().findMatches(id).isEmpty()); + assertEquals(Collections.emptyList(), simpleIdx(), id); } @Test public void simpleMatches() { Id id1 = id("a", "key", "b"); Id id2 = id("a", "foo", "bar", "key", "b"); - Assertions.assertEquals(list(SIMPLE_QUERY), simpleIdx().findMatches(id1)); - Assertions.assertEquals(list(SIMPLE_QUERY), simpleIdx().findMatches(id2)); + assertEquals(list(SIMPLE_QUERY), simpleIdx(), id1); + assertEquals(list(SIMPLE_QUERY), simpleIdx(),id2); } @Test public void simpleNameDoesNotMatch() { Id id = id("b", "foo", "bar"); - Assertions.assertTrue(simpleIdx().findMatches(id).isEmpty()); + assertEquals(Collections.emptyList(), simpleIdx(), id); } @Test @@ -97,21 +152,21 @@ public void simpleRemoveValue() { private static final Query HASKEY_QUERY = Parser.parseQuery("name,a,:eq,key,b,:eq,:and,c,:has,:and"); private QueryIndex hasKeyIdx() { - return QueryIndex.newInstance(registry).add(HASKEY_QUERY, HASKEY_QUERY); + return QueryIndex.newInstance(cacheSupplier).add(HASKEY_QUERY, HASKEY_QUERY); } @Test public void hasKeyMissingKey() { Id id = id("a", "key", "b", "foo", "bar"); - Assertions.assertTrue(hasKeyIdx().findMatches(id).isEmpty()); + assertEquals(Collections.emptyList(), hasKeyIdx(), id); } @Test public void hasKeyMatches() { Id id1 = id("a", "key", "b", "c", "12345"); Id id2 = id("a", "foo", "bar", "key", "b", "c", "foobar"); - Assertions.assertEquals(list(HASKEY_QUERY), hasKeyIdx().findMatches(id1)); - Assertions.assertEquals(list(HASKEY_QUERY), hasKeyIdx().findMatches(id2)); + assertEquals(list(HASKEY_QUERY), hasKeyIdx(), id1); + assertEquals(list(HASKEY_QUERY), hasKeyIdx(), id2); } @Test @@ -120,77 +175,77 @@ public void hasKeyRepeat() { QueryIndex idx = hasKeyIdx(); for (int i = 0; i < 10; ++i) { // Subsequent checks for :has operation should come from cache - Assertions.assertEquals(list(HASKEY_QUERY), idx.findMatches(id1)); + assertEquals(list(HASKEY_QUERY), idx, id1); } } private static final Query IN_QUERY = Parser.parseQuery("name,a,:eq,key,(,b,c,),:in,:and"); private QueryIndex inIdx() { - return QueryIndex.newInstance(registry).add(IN_QUERY, IN_QUERY); + return QueryIndex.newInstance(cacheSupplier).add(IN_QUERY, IN_QUERY); } @Test public void inMissingKey() { Id id = id("a", "key2", "b", "foo", "bar"); - Assertions.assertTrue(inIdx().findMatches(id).isEmpty()); + assertEquals(Collections.emptyList(), inIdx(), id); } @Test public void inMatches() { Id id1 = id("a", "key", "b", "c", "12345"); Id id2 = id("a", "foo", "bar", "key", "c", "c", "foobar"); - Assertions.assertEquals(list(IN_QUERY), inIdx().findMatches(id1)); - Assertions.assertEquals(list(IN_QUERY), inIdx().findMatches(id2)); + assertEquals(list(IN_QUERY), inIdx(), id1); + assertEquals(list(IN_QUERY), inIdx(), id2); } @Test public void inValueNotInSet() { Id id = id("a", "key", "d", "c", "12345"); - Assertions.assertTrue(inIdx().findMatches(id).isEmpty()); + assertEquals(Collections.emptyList(), inIdx(), id); } @Test public void trueMatches() { - QueryIndex idx = QueryIndex.newInstance(registry).add(Query.TRUE, Query.TRUE); + QueryIndex idx = QueryIndex.newInstance(cacheSupplier).add(Query.TRUE, Query.TRUE); Id id1 = id("a", "key", "b", "c", "12345"); Id id2 = id("a", "foo", "bar", "key", "b", "c", "foobar"); - Assertions.assertEquals(list(Query.TRUE), idx.findMatches(id1)); - Assertions.assertEquals(list(Query.TRUE), idx.findMatches(id2)); + assertEquals(list(Query.TRUE), idx, id1); + assertEquals(list(Query.TRUE), idx, id2); } @Test public void falseDoesNotMatch() { - QueryIndex idx = QueryIndex.newInstance(registry).add(Query.FALSE, Query.FALSE); + QueryIndex idx = QueryIndex.newInstance(cacheSupplier).add(Query.FALSE, Query.FALSE); Assertions.assertTrue(idx.isEmpty()); } @Test public void removals() { - QueryIndex idx = QueryIndex.newInstance(registry); + QueryIndex idx = QueryIndex.newInstance(cacheSupplier); idx.add(SIMPLE_QUERY, SIMPLE_QUERY); idx.add(HASKEY_QUERY, HASKEY_QUERY); idx.add(IN_QUERY, IN_QUERY); Id id1 = id("a", "key", "b", "c", "12345"); - Assertions.assertEquals(list(SIMPLE_QUERY, IN_QUERY, HASKEY_QUERY), findMatches(idx, id1)); + assertEquals(list(SIMPLE_QUERY, IN_QUERY, HASKEY_QUERY), idx, id1); Query q = Parser.parseQuery("name,a,:eq"); Assertions.assertFalse(idx.remove(q, q)); - Assertions.assertEquals(list(SIMPLE_QUERY, IN_QUERY, HASKEY_QUERY), findMatches(idx, id1)); + assertEquals(list(SIMPLE_QUERY, IN_QUERY, HASKEY_QUERY), idx, id1); Assertions.assertTrue(idx.remove(IN_QUERY, IN_QUERY)); - Assertions.assertEquals(list(SIMPLE_QUERY, HASKEY_QUERY), findMatches(idx, id1)); + assertEquals(list(SIMPLE_QUERY, HASKEY_QUERY), idx, id1); Assertions.assertTrue(idx.remove(SIMPLE_QUERY, SIMPLE_QUERY)); - Assertions.assertEquals(list(HASKEY_QUERY), findMatches(idx, id1)); + assertEquals(list(HASKEY_QUERY), idx, id1); Assertions.assertTrue(idx.remove(HASKEY_QUERY, HASKEY_QUERY)); Assertions.assertTrue(idx.isEmpty()); - Assertions.assertTrue(idx.findMatches(id1).isEmpty()); + assertEquals(Collections.emptyList(), idx, id1); idx.add(SIMPLE_QUERY, SIMPLE_QUERY); - Assertions.assertEquals(list(SIMPLE_QUERY), findMatches(idx, id1)); + assertEquals(list(SIMPLE_QUERY), idx, id1); } private boolean remove(QueryIndex idx, Query value) { @@ -199,29 +254,29 @@ private boolean remove(QueryIndex idx, Query value) { @Test public void removalsUsingQuery() { - QueryIndex idx = QueryIndex.newInstance(registry); + QueryIndex idx = QueryIndex.newInstance(cacheSupplier); idx.add(SIMPLE_QUERY, SIMPLE_QUERY); idx.add(HASKEY_QUERY, HASKEY_QUERY); idx.add(IN_QUERY, IN_QUERY); Id id1 = id("a", "key", "b", "c", "12345"); - Assertions.assertEquals(list(SIMPLE_QUERY, IN_QUERY, HASKEY_QUERY), findMatches(idx, id1)); + assertEquals(list(SIMPLE_QUERY, IN_QUERY, HASKEY_QUERY), idx, id1); Assertions.assertFalse(remove(idx, Parser.parseQuery("name,a,:eq"))); - Assertions.assertEquals(list(SIMPLE_QUERY, IN_QUERY, HASKEY_QUERY), findMatches(idx, id1)); + assertEquals(list(SIMPLE_QUERY, IN_QUERY, HASKEY_QUERY), idx, id1); Assertions.assertTrue(remove(idx, IN_QUERY)); - Assertions.assertEquals(list(SIMPLE_QUERY, HASKEY_QUERY), findMatches(idx, id1)); + assertEquals(list(SIMPLE_QUERY, HASKEY_QUERY), idx, id1); Assertions.assertTrue(remove(idx, SIMPLE_QUERY)); - Assertions.assertEquals(list(HASKEY_QUERY), findMatches(idx, id1)); + assertEquals(list(HASKEY_QUERY), idx, id1); Assertions.assertTrue(remove(idx, HASKEY_QUERY)); Assertions.assertTrue(idx.isEmpty()); - Assertions.assertTrue(idx.findMatches(id1).isEmpty()); + assertEquals(Collections.emptyList(), idx, id1); idx.add(SIMPLE_QUERY, SIMPLE_QUERY); - Assertions.assertEquals(list(SIMPLE_QUERY), idx.findMatches(id1)); + assertEquals(list(SIMPLE_QUERY), idx, id1); } private Set set(int n) { @@ -235,10 +290,10 @@ private Set set(int n) { @Test public void queryNormalization() { Query q = Parser.parseQuery("name,a,:eq,name,b,:eq,:or,key,b,:eq,:and"); - QueryIndex idx = QueryIndex.newInstance(registry); + QueryIndex idx = QueryIndex.newInstance(cacheSupplier); idx.add(q, q); - Assertions.assertEquals(list(q), idx.findMatches(id("a", "key", "b"))); - Assertions.assertEquals(list(q), idx.findMatches(id("b", "key", "b"))); + assertEquals(list(q), idx, id("a", "key", "b")); + assertEquals(list(q), idx, id("b", "key", "b")); } @Test @@ -249,10 +304,10 @@ public void inClauseExpansion() { Query q2 = new Query.In("b", set(10000)); Query q3 = new Query.In("c", set(10000)); Query query = q1.and(q2).and(q3); - QueryIndex idx = QueryIndex.newInstance(registry).add(query, query); + QueryIndex idx = QueryIndex.newInstance(cacheSupplier).add(query, query); Id id1 = id("cpu", "a", "1", "b", "9999", "c", "727"); - Assertions.assertEquals(list(query), idx.findMatches(id1)); + assertEquals(list(query), idx, id1); } @Test @@ -269,7 +324,7 @@ public void manyQueries() { diskUsagePerNode.add(q); } - QueryIndex idx = QueryIndex.newInstance(registry) + QueryIndex idx = QueryIndex.newInstance(cacheSupplier) .add(cpuUsage, cpuUsage) .add(diskUsage, diskUsage); for (Query q : diskUsagePerNode) { @@ -277,115 +332,121 @@ public void manyQueries() { } // Matching - Assertions.assertEquals( + assertEquals( list(cpuUsage), - idx.findMatches(id("cpuUsage", "nf.node", "unknown"))); - Assertions.assertEquals( + idx, + id("cpuUsage", "nf.node", "unknown")); + assertEquals( list(cpuUsage), - idx.findMatches(id("cpuUsage", "nf.node", "i-00099"))); - Assertions.assertEquals( + idx, + id("cpuUsage", "nf.node", "i-00099")); + assertEquals( list(diskUsage), - idx.findMatches(id("diskUsage", "nf.node", "unknown"))); - Assertions.assertEquals( + idx, + id("diskUsage", "nf.node", "unknown")); + assertEquals( list(diskUsage, diskUsagePerNode.get(diskUsagePerNode.size() - 1)), - idx.findMatches(id("diskUsage", "nf.node", "i-00099"))); + idx, + id("diskUsage", "nf.node", "i-00099")); // Shouldn't match - Assertions.assertTrue( - idx.findMatches(id("memoryUsage", "nf.node", "i-00099")).isEmpty()); + assertEquals( + Collections.emptyList(), + idx, + id("memoryUsage", "nf.node", "i-00099")); } @Test public void multipleClausesForSameKey() { Query q = Parser.parseQuery("name,abc.*,:re,name,.*def,:re,:and"); - QueryIndex idx = QueryIndex.newInstance(registry).add(q, q); + QueryIndex idx = QueryIndex.newInstance(cacheSupplier).add(q, q); // Doesn't match prefix check - Assertions.assertTrue(idx.findMatches(id("foodef")).isEmpty()); + assertEquals(Collections.emptyList(), idx, id("foodef")); // Doesn't match suffix check - Assertions.assertTrue(idx.findMatches(id("abcbar")).isEmpty()); + assertEquals(Collections.emptyList(), idx, id("abcbar")); // Matches both - Assertions.assertFalse(idx.findMatches(id("abcdef")).isEmpty()); - Assertions.assertFalse(idx.findMatches(id("abc def")).isEmpty()); + assertEquals(list(q), idx, id("abcdef")); + assertEquals(list(q), idx, id("abc def")); } @Test public void notEqClause() { Query q = Parser.parseQuery("name,cpu,:eq,id,user,:eq,:not,:and"); - QueryIndex idx = QueryIndex.newInstance(registry).add(q, q); + QueryIndex idx = QueryIndex.newInstance(cacheSupplier).add(q, q); - Assertions.assertFalse(idx.findMatches(id("cpu", "id", "system")).isEmpty()); - Assertions.assertTrue(idx.findMatches(id("cpu", "id", "user")).isEmpty()); + assertEquals(list(q), idx, id("cpu", "id", "system")); + assertEquals(Collections.emptyList(), idx, id("cpu", "id", "user")); } @Test public void notEqMissingKey() { Query q = Parser.parseQuery("name,cpu,:eq,id,user,:eq,:not,:and"); - QueryIndex idx = QueryIndex.newInstance(registry).add(q, q); - Assertions.assertFalse(idx.findMatches(id("cpu")).isEmpty()); + QueryIndex idx = QueryIndex.newInstance(cacheSupplier).add(q, q); + assertEquals(list(q), idx, id("cpu")); } @Test public void notEqMissingKeyMiddle() { Query q = Parser.parseQuery("name,cpu,:eq,mm,foo,:eq,:not,:and,zz,bar,:eq,:and"); - QueryIndex idx = QueryIndex.newInstance(registry).add(q, q); + QueryIndex idx = QueryIndex.newInstance(cacheSupplier).add(q, q); - Assertions.assertFalse(idx.findMatches(id("cpu", "zz", "bar")).isEmpty()); + assertEquals(list(q), idx, id("cpu", "zz", "bar")); } @Test public void notEqMissingKeyEnd() { Query q = Parser.parseQuery("name,cpu,:eq,zz,foo,:eq,:not,:and"); - QueryIndex idx = QueryIndex.newInstance(registry).add(q, q); + QueryIndex idx = QueryIndex.newInstance(cacheSupplier).add(q, q); - Assertions.assertFalse(idx.findMatches(id("cpu")).isEmpty()); + assertEquals(list(q), idx, id("cpu")); } @Test public void multiNotEqClause() { Query q = Parser.parseQuery("name,cpu,:eq,id,system,:eq,:and,id,user,:eq,:not,:and"); - QueryIndex idx = QueryIndex.newInstance(registry).add(q, q); + QueryIndex idx = QueryIndex.newInstance(cacheSupplier).add(q, q); - Assertions.assertFalse(idx.findMatches(id("cpu", "id", "system")).isEmpty()); - Assertions.assertTrue(idx.findMatches(id("cpu", "id", "user")).isEmpty()); + assertEquals(list(q), idx, id("cpu", "id", "system")); + assertEquals(Collections.emptyList(), idx, id("cpu", "id", "user")); } @Test public void notInClause() { Query q = Parser.parseQuery("name,cpu,:eq,id,(,user,iowait,),:in,:not,:and"); - QueryIndex idx = QueryIndex.newInstance(registry).add(q, q); + QueryIndex idx = QueryIndex.newInstance(cacheSupplier).add(q, q); - Assertions.assertFalse(idx.findMatches(id("cpu", "id", "system")).isEmpty()); - Assertions.assertTrue(idx.findMatches(id("cpu", "id", "user")).isEmpty()); - Assertions.assertTrue(idx.findMatches(id("cpu", "id", "iowait")).isEmpty()); + assertEquals(list(q), idx, id("cpu", "id", "system")); + assertEquals(Collections.emptyList(), idx, id("cpu", "id", "user")); + assertEquals(Collections.emptyList(), idx, id("cpu", "id", "iowait")); } @Test public void multiNotInClause() { Query q = Parser.parseQuery("name,cpu,:eq,id,system,:eq,:and,id,(,user,iowait,),:in,:not,:and"); - QueryIndex idx = QueryIndex.newInstance(registry).add(q, q); + QueryIndex idx = QueryIndex.newInstance(cacheSupplier).add(q, q); - Assertions.assertFalse(idx.findMatches(id("cpu", "id", "system")).isEmpty()); - Assertions.assertTrue(idx.findMatches(id("cpu", "id", "user")).isEmpty()); - Assertions.assertTrue(idx.findMatches(id("cpu", "id", "iowait")).isEmpty()); + assertEquals(list(q), idx, id("cpu", "id", "system")); + assertEquals(Collections.emptyList(), idx, id("cpu", "id", "user")); + assertEquals(Collections.emptyList(), idx, id("cpu", "id", "iowait")); } @Test public void doubleNotsSameKey() { Query q = Parser.parseQuery("a,1,:eq,b,2,:eq,:and,c,3,:eq,:not,:and,c,4,:eq,:not,:and"); - QueryIndex idx = QueryIndex.newInstance(registry).add(q, q); - Assertions.assertFalse(idx.findMatches(id("cpu", "a", "1", "b", "2", "c", "5")).isEmpty()); - Assertions.assertTrue(idx.findMatches(id("cpu", "a", "1", "b", "2", "c", "3")).isEmpty()); - Assertions.assertTrue(idx.findMatches(id("cpu", "a", "1", "b", "2", "c", "4")).isEmpty()); - Assertions.assertFalse(idx.findMatches(id("cpu", "a", "1", "b", "2")).isEmpty()); + QueryIndex idx = QueryIndex.newInstance(cacheSupplier).add(q, q); + assertEquals(list(q), idx, id("cpu", "a", "1", "b", "2", "c", "5")); + assertEquals(Collections.emptyList(), idx, id("cpu", "a", "1", "b", "2", "c", "3")); + assertEquals(Collections.emptyList(), idx, id("cpu", "a", "1", "b", "2", "c", "4")); + assertEquals(list(q), idx, id("cpu", "a", "1", "b", "2")); } @Test public void removalOfNotQuery() { Query q = Parser.parseQuery("name,cpu,:eq,id,user,:eq,:not,:and"); - QueryIndex idx = QueryIndex.newInstance(registry).add(q, q); + QueryIndex idx = QueryIndex.newInstance(cacheSupplier).add(q, q); Assertions.assertTrue(idx.remove(q, q)); Assertions.assertTrue(idx.isEmpty()); } @@ -393,7 +454,7 @@ public void removalOfNotQuery() { @Test public void removalOfNotQueryUsingQuery() { Query q = Parser.parseQuery("name,cpu,:eq,id,user,:eq,:not,:and"); - QueryIndex idx = QueryIndex.newInstance(registry).add(q, q); + QueryIndex idx = QueryIndex.newInstance(cacheSupplier).add(q, q); Assertions.assertTrue(remove(idx, q)); Assertions.assertTrue(idx.isEmpty()); } @@ -402,25 +463,21 @@ public void removalOfNotQueryUsingQuery() { public void removalPrefixRegexSubtree() { Query q1 = Parser.parseQuery("name,test,:eq,a,foo,:re,:and,b,bar,:eq,:and"); Query q2 = Parser.parseQuery("name,test,:eq,a,foo,:re,:and"); - QueryIndex idx = QueryIndex.newInstance(registry) + QueryIndex idx = QueryIndex.newInstance(cacheSupplier) .add(q1, q1) .add(q2, q2); Id id = id("test", "a", "foo", "b", "bar"); - Set expected = new HashSet<>(); - expected.add(q1); - expected.add(q2); - Assertions.assertEquals(expected, new HashSet<>(idx.findMatches(id))); + assertEquals(list(q2, q1), idx, id); idx.remove(q1, q1); - expected.remove(q1); - Assertions.assertEquals(expected, new HashSet<>(idx.findMatches(id))); + assertEquals(list(q2), idx, id); } @Test public void toStringMethod() { - QueryIndex idx = QueryIndex.newInstance(registry); + QueryIndex idx = QueryIndex.newInstance(cacheSupplier); idx.add(SIMPLE_QUERY, SIMPLE_QUERY); idx.add(HASKEY_QUERY, HASKEY_QUERY); idx.add(IN_QUERY, IN_QUERY);