GH-2619 - Correctly count most matching, static labels.

In case another matching node description is found, the value of the `mostMatchingStaticLabels` must be updated.

Fixes #2619.

# Conflicts:
#	src/test/java/org/springframework/data/neo4j/integration/reactive/ReactiveDynamicLabelsIT.java

# Conflicts:
#	src/main/java/org/springframework/data/neo4j/core/mapping/NodeDescriptionStore.java
This commit is contained in:
Michael Simons
2022-10-27 13:20:31 +02:00
parent d397076562
commit 1b05891964
7 changed files with 237 additions and 14 deletions

View File

@@ -16,6 +16,7 @@
package org.springframework.data.neo4j.core.mapping;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -121,15 +122,12 @@ final class NodeDescriptionStore {
Map<NodeDescription<?>, Integer> unmatchedLabelsCache = new HashMap<>();
List<String> mostMatchingStaticLabels = null;
// Remove is faster than "stream, filter, count".
BiFunction<NodeDescription<?>, List<String>, Integer> unmatchedLabelsCount =
(nodeDescription, staticLabels) -> {
Set<String> staticLabelsClone = new HashSet<>(staticLabels);
labels.forEach(staticLabelsClone::remove);
return staticLabelsClone.size();
};
for (NodeDescription<?> nd : haystack) {
if (Modifier.isAbstract(nd.getUnderlyingClass().getModifiers())) {
continue;
}
List<String> staticLabels = nd.getStaticLabels();
if (staticLabels.containsAll(labels)) {
@@ -138,15 +136,20 @@ final class NodeDescriptionStore {
return new NodeDescriptionAndLabels(nd, surplusLabels);
}
unmatchedLabelsCache.put(nd, unmatchedLabelsCount.apply(nd, staticLabels));
if (mostMatchingNodeDescription == null) {
mostMatchingNodeDescription = nd;
mostMatchingStaticLabels = staticLabels;
continue;
int unmatchedLabelsCount = 0;
List<String> matchingLabels = new ArrayList<>();
for (String staticLabel : staticLabels) {
if (labels.contains(staticLabel)) {
matchingLabels.add(staticLabel);
} else {
unmatchedLabelsCount++;
}
}
if (unmatchedLabelsCache.get(nd) < unmatchedLabelsCache.get(mostMatchingNodeDescription)) {
unmatchedLabelsCache.put(nd, unmatchedLabelsCount);
if (mostMatchingNodeDescription == null || unmatchedLabelsCount < unmatchedLabelsCache.get(mostMatchingNodeDescription)) {
mostMatchingNodeDescription = nd;
mostMatchingStaticLabels = matchingLabels;
}
}

View File

@@ -19,12 +19,23 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.neo4j.cypherdsl.core.Conditions.not;
import static org.neo4j.cypherdsl.core.Predicates.exists;
import org.junit.jupiter.api.RepeatedTest;
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
import org.springframework.data.neo4j.integration.shared.common.CounterMetric;
import org.springframework.data.neo4j.integration.shared.common.GaugeMetric;
import org.springframework.data.neo4j.integration.shared.common.HistogramMetric;
import org.springframework.data.neo4j.integration.shared.common.Metric;
import org.springframework.data.neo4j.integration.shared.common.SummaryMetric;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
@@ -80,6 +91,38 @@ public class ReactiveDynamicLabelsIT {
protected static Neo4jExtension.Neo4jConnectionSupport neo4jConnectionSupport;
@Nested
class DynamicLabelsAndOrderOfClassesBeingLoaded extends SpringTestBase {
@Override
Long createTestEntity(Transaction t) {
return t.run("CREATE (m:Metric:Counter:A:B:C:D {timestamp: datetime()}) RETURN id(m)").single().get(0).asLong();
}
@RepeatedTest(100) // GH-2619
void ownLabelsShouldNotEndUpWithDynamicLabels(@Autowired Neo4jMappingContext mappingContext, @Autowired ReactiveNeo4jTemplate template) {
List<Class<? extends Metric>> metrics = Arrays.asList(GaugeMetric.class, SummaryMetric.class, HistogramMetric.class, CounterMetric.class);
Collections.shuffle(metrics);
for (Class<?> type : metrics) {
assertThat(mappingContext.getPersistentEntity(type)).isNotNull();
}
Map<String, Object> args = new HashMap<>();
args.put("agentIdLabel", "B");
template.findAll("MATCH (m:Metric) WHERE $agentIdLabel in labels(m) RETURN m ORDER BY m.timestamp DESC", args, Metric.class)
.as(StepVerifier::create)
.assertNext(cm -> {
assertThat(cm).isInstanceOf(CounterMetric.class);
assertThat(cm.getId()).isEqualTo(existingEntityId);
assertThat(cm.getDynamicLabels()).containsExactlyInAnyOrder("A", "B", "C", "D");
})
.verifyComplete();
}
}
@Nested
class EntityWithSingleStaticLabelAndGeneratedId extends SpringTestBase {

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2011-2022 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.data.neo4j.integration.shared.common;
import org.springframework.data.neo4j.core.schema.Node;
/**
* @author Michael J. Simons
*/
@Node("Counter")
public class CounterMetric extends Metric {
public CounterMetric(String name) {
super(name);
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2011-2022 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.data.neo4j.integration.shared.common;
import org.springframework.data.neo4j.core.schema.Node;
/**
* @author Michael J. Simons
*/
@Node("Gauge")
public class GaugeMetric extends Metric {
public GaugeMetric(String name) {
super(name);
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2011-2022 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.data.neo4j.integration.shared.common;
import org.springframework.data.neo4j.core.schema.Node;
/**
* @author Michael J. Simons
*/
@Node("Histogram")
public class HistogramMetric extends Metric {
public HistogramMetric(String name) {
super(name);
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2011-2022 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.data.neo4j.integration.shared.common;
import java.util.ArrayList;
import java.util.List;
import org.springframework.data.neo4j.core.schema.DynamicLabels;
import org.springframework.data.neo4j.core.schema.GeneratedValue;
import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;
/**
* @author Michael J. Simons
*/
@Node("Metric")
public abstract class Metric {
@Id
@GeneratedValue
Long id;
@DynamicLabels
public List<String> dynamicLabels = new ArrayList<>();
public Long getId() {
return id;
}
public List<String> getDynamicLabels() {
return dynamicLabels;
}
private String name;
public Metric(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2011-2022 the original author or authors.
*
* Licensed 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
*
* https://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.springframework.data.neo4j.integration.shared.common;
import org.springframework.data.neo4j.core.schema.Node;
/**
* @author Michael J. Simons
*/
@Node("Summary")
public class SummaryMetric extends Metric {
public SummaryMetric(String name) {
super(name);
}
}