Check for equality of type before shortcutting projection.

This fixes #2415 by checking for exact equality of the type before shortcutting an unnessary projection.
This commit is contained in:
Michael Simons
2021-10-28 12:08:14 +02:00
parent 90cc8d44a6
commit 8c262f9353
10 changed files with 261 additions and 3 deletions

View File

@@ -13,7 +13,7 @@ and annotations.
----
MATCH (c:Java)-[:ANNOTATED_BY]->(a)-[:OF_TYPE]->(t:Type {fqn: 'org.apiguardian.api.API'}),
(p)-[:DECLARES]->(c)
WHERE c:Member AND NOT c:Constructor
WHERE c:Member AND NOT (c:Constructor OR c:Method)
RETURN p.fqn, c.name
----

View File

@@ -330,7 +330,7 @@ public final class Neo4jTemplate implements
return null;
}
if (resultType.isInstance(instance)) {
if (resultType.equals(instance.getClass())) {
return (R) save(instance);
}

View File

@@ -314,7 +314,7 @@ public final class ReactiveNeo4jTemplate implements
return null;
}
if (resultType.isInstance(instance)) {
if (resultType.equals(instance.getClass())) {
return (Mono<R>) save(instance);
}

View File

@@ -135,6 +135,18 @@ public final class QueryFragmentsAndParameters {
return QueryFragmentsAndParameters.forExample(mappingContext, example, pageable, null);
}
/**
* Utility method for creating a query fragment including parameters for a given condition.
*
* @param entityMetaData The metadata of a given and known entity
* @param condition A Cypher-DSL condition
* @return Fully populated fragments and parameter
*/
@API(status = API.Status.EXPERIMENTAL, since = "6.1.7")
public static QueryFragmentsAndParameters forCondition(Neo4jPersistentEntity<?> entityMetaData, Condition condition) {
return forCondition(entityMetaData, condition, null, null);
}
static QueryFragmentsAndParameters forCondition(Neo4jPersistentEntity<?> entityMetaData,
Condition condition,
@Nullable Pageable pageable,

View File

@@ -51,6 +51,9 @@ import org.springframework.data.neo4j.core.DatabaseSelectionProvider;
import org.springframework.data.neo4j.core.Neo4jTemplate;
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager;
import org.springframework.data.neo4j.integration.issues.gh2415.BaseNodeEntity;
import org.springframework.data.neo4j.integration.issues.gh2415.NodeEntity;
import org.springframework.data.neo4j.integration.issues.gh2415.NodeWithDefinedCredentials;
import org.springframework.data.neo4j.integration.shared.common.Person;
import org.springframework.data.neo4j.integration.shared.common.PersonWithAllConstructor;
import org.springframework.data.neo4j.integration.shared.common.PersonWithAssignedId;
@@ -111,6 +114,14 @@ class Neo4jTemplateIT {
transaction.run("CREATE (p:Person{firstName: 'Bela', lastName: 'B.'})");
transaction.run("CREATE (p:PersonWithAssignedId{id: 'x', firstName: 'John', lastName: 'Doe'})");
transaction.run(
"CREATE (root:NodeEntity:BaseNodeEntity{nodeId: 'root'}) " +
"CREATE (company:NodeEntity:BaseNodeEntity{nodeId: 'comp'}) " +
"CREATE (cred:Credential{id: 'uuid-1', name: 'Creds'}) " +
"CREATE (company)-[:CHILD_OF]->(root) " +
"CREATE (root)-[:HAS_CREDENTIAL]->(cred) " +
"CREATE (company)-[:WITH_CREDENTIAL]->(cred)");
transaction.commit();
bookmarkCapture.seedWith(session.lastBookmark());
}
@@ -799,6 +810,23 @@ class Neo4jTemplateIT {
assertThat(people).extracting(Person::getLastName).containsExactly("Schnitzel");
}
@Test // GH-2415
void saveWithProjectionImplementedByEntity() {
NodeEntity nodeEntity = neo4jTemplate
.find(BaseNodeEntity.class)
.as(NodeEntity.class)
.matching(
"MATCH p=(n:BaseNodeEntity)-[r]-(t) WHERE n.nodeId = $nodeId WITH n, collect([x in relationships(p) |x]) AS r, collect(t) AS o RETURN n, r, o",
Collections.singletonMap("nodeId", "root")
)
.one().get();
neo4jTemplate.saveAs(nodeEntity, NodeWithDefinedCredentials.class);
nodeEntity = neo4jTemplate.findById(nodeEntity.getNodeId(), NodeEntity.class).get();
assertThat(nodeEntity.getChildren()).hasSize(1);
}
@Configuration
@EnableTransactionManagement
static class Config extends AbstractNeo4jConfig {

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2011-2021 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.issues.gh2415;
import lombok.AccessLevel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.NonFinal;
import lombok.experimental.SuperBuilder;
import org.springframework.data.neo4j.core.schema.GeneratedValue;
import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;
import org.springframework.data.neo4j.core.support.UUIDStringGenerator;
/**
* @author Andreas Berger
*/
@Node
@NonFinal
@Data
@Setter(AccessLevel.PRIVATE)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@SuperBuilder(toBuilder = true)
public class BaseNodeEntity {
@Id
@GeneratedValue(UUIDStringGenerator.class)
@EqualsAndHashCode.Include
private String nodeId;
private String name;
@Override
public String toString() {
return getClass().getSimpleName() + " - " + getName() + " (" + getNodeId() + ")";
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2011-2021 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.issues.gh2415;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Value;
import lombok.With;
import org.springframework.data.annotation.Immutable;
import org.springframework.data.neo4j.core.schema.GeneratedValue;
import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;
import org.springframework.data.neo4j.core.support.UUIDStringGenerator;
import com.fasterxml.jackson.annotation.JsonIgnore;
/**
* @author Andreas Berger
*/
@Node
@Value
@With
@AllArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Immutable
@Builder(toBuilder = true)
public class Credential {
@JsonIgnore
@Id
@GeneratedValue(UUIDStringGenerator.class)
@EqualsAndHashCode.Include
String id;
String name;
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2011-2021 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.issues.gh2415;
import lombok.AccessLevel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import java.util.Set;
import org.springframework.data.neo4j.core.schema.Node;
import org.springframework.data.neo4j.core.schema.Relationship;
import com.fasterxml.jackson.annotation.JsonIgnore;
/**
* @author Andreas Berger
*/
@Node
@Data
@Setter(AccessLevel.PRIVATE)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true)
@SuperBuilder(toBuilder = true)
public class NodeEntity extends BaseNodeEntity implements NodeWithDefinedCredentials {
@JsonIgnore
@Relationship(type = "CHILD_OF", direction = Relationship.Direction.INCOMING)
private Set<BaseNodeEntity> children;
@Relationship(type = "HAS_CREDENTIAL")
private Set<Credential> definedCredentials;
@Override
public String toString() {
return super.toString();
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2011-2021 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.issues.gh2415;
import java.util.Set;
/**
* @author Andreas Berger
*/
public interface NodeWithDefinedCredentials {
String getNodeId();
String getName();
Set<Credential> getDefinedCredentials();
}

View File

@@ -53,6 +53,9 @@ import org.springframework.data.neo4j.core.ReactiveDatabaseSelectionProvider;
import org.springframework.data.neo4j.core.ReactiveNeo4jTemplate;
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
import org.springframework.data.neo4j.core.transaction.ReactiveNeo4jTransactionManager;
import org.springframework.data.neo4j.integration.issues.gh2415.BaseNodeEntity;
import org.springframework.data.neo4j.integration.issues.gh2415.NodeEntity;
import org.springframework.data.neo4j.integration.issues.gh2415.NodeWithDefinedCredentials;
import org.springframework.data.neo4j.integration.shared.common.Person;
import org.springframework.data.neo4j.integration.shared.common.PersonWithAllConstructor;
import org.springframework.data.neo4j.integration.shared.common.PersonWithAssignedId;
@@ -116,6 +119,14 @@ class ReactiveNeo4jTemplateIT {
transaction.run("CREATE (p:Person{firstName: 'Bela', lastName: 'B.'})");
transaction.run("CREATE (p:PersonWithAssignedId{id: 'x', firstName: 'John', lastName: 'Doe'})");
transaction.run(
"CREATE (root:NodeEntity:BaseNodeEntity{nodeId: 'root'}) " +
"CREATE (company:NodeEntity:BaseNodeEntity{nodeId: 'comp'}) " +
"CREATE (cred:Credential{id: 'uuid-1', name: 'Creds'}) " +
"CREATE (company)-[:CHILD_OF]->(root) " +
"CREATE (root)-[:HAS_CREDENTIAL]->(cred) " +
"CREATE (company)-[:WITH_CREDENTIAL]->(cred)");
transaction.commit();
bookmarkCapture.seedWith(session.lastBookmark());
@@ -803,6 +814,24 @@ class ReactiveNeo4jTemplateIT {
.verifyComplete();
}
@Test // GH-2415
void saveWithProjectionImplementedByEntity() {
neo4jTemplate
.find(BaseNodeEntity.class)
.as(NodeEntity.class)
.matching(
"MATCH p=(n:BaseNodeEntity)-[r]-(t) WHERE n.nodeId = $nodeId WITH n, collect([x in relationships(p) |x]) AS r, collect(t) AS o RETURN n, r, o",
Collections.singletonMap("nodeId", "root")
)
.one()
.flatMap(nodeEntity -> neo4jTemplate.saveAs(nodeEntity, NodeWithDefinedCredentials.class))
.flatMap(nodeEntity -> neo4jTemplate.findById(nodeEntity.getNodeId(), NodeEntity.class))
.as(StepVerifier::create)
.consumeNextWith(nodeEntity -> assertThat(nodeEntity.getChildren()).hasSize(1))
.verifyComplete();
}
@Configuration
@EnableTransactionManagement
static class Config extends AbstractReactiveNeo4jConfig {