GH-2809 - Find the right id/element id function for assignedIds.

* Avoid using the outdated id function wherever possible.
* Never fall back to `toString(id((element))` (in Neo4j 4.4 scenarios)

Closes #2809
This commit is contained in:
Gerrit Meier
2023-11-02 11:31:22 +01:00
parent 54d5a529e4
commit 9ae846660f
7 changed files with 75 additions and 61 deletions

View File

@@ -401,7 +401,7 @@ public final class Neo4jTemplate implements
neo4jMappingContext.getRequiredBinderFunctionFor((Class<T>) entityToBeSaved.getClass())
);
Optional<Entity> newOrUpdatedNode = neo4jClient
.query(() -> renderer.render(cypherGenerator.prepareSaveOf(entityMetaData, dynamicLabels)))
.query(() -> renderer.render(cypherGenerator.prepareSaveOf(entityMetaData, dynamicLabels, TemplateSupport.rendererRendersElementId(renderer))))
.bind(entityToBeSaved)
.with(binderFunction)
.fetchAs(Entity.class)
@@ -416,7 +416,7 @@ public final class Neo4jTemplate implements
}
Object elementId = newOrUpdatedNode.map(node -> {
if (!entityMetaData.isUsingDeprecatedInternalId() && entityMetaData.isUsingInternalIds()) {
if (!entityMetaData.isUsingDeprecatedInternalId() && TemplateSupport.rendererRendersElementId(renderer)) {
return IdentitySupport.getElementId(node);
}
return node.id();
@@ -764,6 +764,7 @@ public final class Neo4jTemplate implements
// Remove all relationships before creating all new if the entity is not new and the relationship
// has not been processed before.
// This avoids the usage of cache but might have significant impact on overall performance
boolean canUseElementId = TemplateSupport.rendererRendersElementId(renderer);
if (!isParentObjectNew && !stateMachine.hasProcessedRelationship(fromId, relationshipDescription)) {
List<Object> knownRelationshipsIds = new ArrayList<>();
@@ -780,7 +781,7 @@ public final class Neo4jTemplate implements
}
}
Statement relationshipRemoveQuery = cypherGenerator.prepareDeleteOf(sourceEntity, relationshipDescription);
Statement relationshipRemoveQuery = cypherGenerator.prepareDeleteOf(sourceEntity, relationshipDescription, canUseElementId);
neo4jClient.query(renderer.render(relationshipRemoveQuery))
.bind(convertIdValues(sourceEntity.getIdProperty(), fromId)) //
@@ -858,7 +859,7 @@ public final class Neo4jTemplate implements
// create new dynamic relationship properties
if (relationshipDescription.hasRelationshipProperties() && isNewRelationship && idProperty != null) {
CreateRelationshipStatementHolder statementHolder = neo4jMappingContext.createStatementForSingleRelationship(
sourceEntity, relationshipDescription, relatedValueToStore, true);
sourceEntity, relationshipDescription, relatedValueToStore, true, canUseElementId);
List<Object> row = Collections.singletonList(properties);
statementHolder = statementHolder.addProperty(Constants.NAME_OF_RELATIONSHIP_LIST_PARAM, row);
@@ -879,7 +880,7 @@ public final class Neo4jTemplate implements
} else { // plain (new or to update) dynamic relationship or dynamic relationships with properties to update
CreateRelationshipStatementHolder statementHolder = neo4jMappingContext.createStatementForSingleRelationship(
sourceEntity, relationshipDescription, relatedValueToStore, false);
sourceEntity, relationshipDescription, relatedValueToStore, false, canUseElementId);
List<Object> row = Collections.singletonList(properties);
statementHolder = statementHolder.addProperty(Constants.NAME_OF_RELATIONSHIP_LIST_PARAM, row);
@@ -921,7 +922,7 @@ public final class Neo4jTemplate implements
// batch operations
if (!(relationshipDescription.hasRelationshipProperties() || relationshipDescription.isDynamic() || plainRelationshipRows.isEmpty())) {
CreateRelationshipStatementHolder statementHolder = neo4jMappingContext.createStatementForImperativeSimpleRelationshipBatch(
sourceEntity, relationshipDescription, plainRelationshipRows);
sourceEntity, relationshipDescription, plainRelationshipRows, canUseElementId);
statementHolder = statementHolder.addProperty(Constants.NAME_OF_RELATIONSHIP_LIST_PARAM, plainRelationshipRows);
neo4jClient.query(renderer.render(statementHolder.getStatement()))
.bindAll(statementHolder.getProperties())
@@ -929,7 +930,7 @@ public final class Neo4jTemplate implements
} else if (relationshipDescription.hasRelationshipProperties()) {
if (!relationshipPropertiesRows.isEmpty()) {
CreateRelationshipStatementHolder statementHolder = neo4jMappingContext.createStatementForImperativeRelationshipsWithPropertiesBatch(false,
sourceEntity, relationshipDescription, updateRelatedValuesToStore, relationshipPropertiesRows);
sourceEntity, relationshipDescription, updateRelatedValuesToStore, relationshipPropertiesRows, canUseElementId);
statementHolder = statementHolder.addProperty(Constants.NAME_OF_RELATIONSHIP_LIST_PARAM, relationshipPropertiesRows);
neo4jClient.query(renderer.render(statementHolder.getStatement()))
@@ -938,7 +939,7 @@ public final class Neo4jTemplate implements
}
if (!newRelatedValuesToStore.isEmpty()) {
CreateRelationshipStatementHolder statementHolder = neo4jMappingContext.createStatementForImperativeRelationshipsWithPropertiesBatch(true,
sourceEntity, relationshipDescription, newRelatedValuesToStore, newRelationshipPropertiesRows);
sourceEntity, relationshipDescription, newRelatedValuesToStore, newRelationshipPropertiesRows, canUseElementId);
List<Object> all = new ArrayList<>(neo4jClient.query(renderer.render(statementHolder.getStatement()))
.bindAll(statementHolder.getProperties())
.fetchAs(Object.class)
@@ -996,7 +997,7 @@ public final class Neo4jTemplate implements
return tree;
});
Optional<Entity> optionalSavedNode = neo4jClient
.query(() -> renderer.render(cypherGenerator.prepareSaveOf(targetNodeDescription, dynamicLabels)))
.query(() -> renderer.render(cypherGenerator.prepareSaveOf(targetNodeDescription, dynamicLabels, TemplateSupport.rendererRendersElementId(renderer))))
.bind(entity).with(binderFunction)
.fetchAs(Entity.class)
.one();

View File

@@ -438,7 +438,8 @@ public final class ReactiveNeo4jTemplate implements
includedProperties, entityMetaData,
neo4jMappingContext.getRequiredBinderFunctionFor((Class<T>) entityToBeSaved.getClass()));
Mono<Entity> idMono = this.neo4jClient.query(() -> renderer.render(cypherGenerator.prepareSaveOf(entityMetaData, dynamicLabels)))
boolean canUseElementId = TemplateSupport.rendererRendersElementId(renderer);
Mono<Entity> idMono = this.neo4jClient.query(() -> renderer.render(cypherGenerator.prepareSaveOf(entityMetaData, dynamicLabels, canUseElementId)))
.bind(entityToBeSaved)
.with(binderFunction)
.fetchAs(Entity.class)
@@ -452,7 +453,7 @@ public final class ReactiveNeo4jTemplate implements
PersistentPropertyAccessor<T> propertyAccessor = entityMetaData.getPropertyAccessor(entityToBeSaved);
return idMono.doOnNext(newOrUpdatedNode -> {
var elementId = !entityMetaData.isUsingDeprecatedInternalId() && entityMetaData.isUsingInternalIds()
var elementId = !entityMetaData.isUsingDeprecatedInternalId() && canUseElementId
? IdentitySupport.getElementId(newOrUpdatedNode)
: newOrUpdatedNode.id();
TemplateSupport.setGeneratedIdIfNecessary(entityMetaData, propertyAccessor, elementId, Optional.of(newOrUpdatedNode));
@@ -901,6 +902,7 @@ public final class ReactiveNeo4jTemplate implements
// Remove all relationships before creating all new if the entity is not new and the relationship
// has not been processed before.
// This avoids the usage of cache but might have significant impact on overall performance
boolean canUseElementId = TemplateSupport.rendererRendersElementId(renderer);
if (!isParentObjectNew && !stateMachine.hasProcessedRelationship(fromId, relationshipDescription)) {
List<Object> knownRelationshipsIds = new ArrayList<>();
@@ -919,7 +921,7 @@ public final class ReactiveNeo4jTemplate implements
}
}
Statement relationshipRemoveQuery = cypherGenerator.prepareDeleteOf(sourceEntity, relationshipDescription);
Statement relationshipRemoveQuery = cypherGenerator.prepareDeleteOf(sourceEntity, relationshipDescription, canUseElementId);
relationshipDeleteMonos.add(
neo4jClient.query(renderer.render(relationshipRemoveQuery))
@@ -990,7 +992,7 @@ public final class ReactiveNeo4jTemplate implements
boolean isNewRelationship = idValue == null;
CreateRelationshipStatementHolder statementHolder = neo4jMappingContext.createStatementForSingleRelationship(
sourceEntity, relationshipDescription, relatedValueToStore, isNewRelationship);
sourceEntity, relationshipDescription, relatedValueToStore, isNewRelationship, canUseElementId);
Map<String, Object> properties = new HashMap<>();
properties.put(Constants.FROM_ID_PARAMETER_NAME, convertIdValues(sourceEntity.getRequiredIdProperty(), fromId));
@@ -1086,7 +1088,7 @@ public final class ReactiveNeo4jTemplate implements
return tree;
});
return neo4jClient
.query(() -> renderer.render(cypherGenerator.prepareSaveOf(targetNodeDescription, dynamicLabels)))
.query(() -> renderer.render(cypherGenerator.prepareSaveOf(targetNodeDescription, dynamicLabels, TemplateSupport.rendererRendersElementId(renderer))))
.bind(entity).with(binderFunction)
.fetchAs(Entity.class)
.one();

View File

@@ -427,10 +427,10 @@ public final class TemplateSupport {
* @return {@literal true} if renderer will use elementId
*/
static boolean rendererCanUseElementIdIfPresent(Renderer renderer, Neo4jPersistentEntity<?> targetEntity) {
return !targetEntity.isUsingDeprecatedInternalId() && targetEntity.isUsingInternalIds() && rendererRendersElementId(renderer);
return !targetEntity.isUsingDeprecatedInternalId() && rendererRendersElementId(renderer);
}
private static boolean rendererRendersElementId(Renderer renderer) {
static boolean rendererRendersElementId(Renderer renderer) {
return renderer.render(Cypher.returning(Functions.elementId(Cypher.anyNode("n"))).build())
.equals("RETURN elementId(n)");
}

View File

@@ -299,7 +299,7 @@ public enum CypherGenerator {
}
public Statement prepareSaveOf(NodeDescription<?> nodeDescription,
UnaryOperator<OngoingMatchAndUpdate> updateDecorator) {
UnaryOperator<OngoingMatchAndUpdate> updateDecorator, boolean canUseElementId) {
String primaryLabel = nodeDescription.getPrimaryLabel();
List<String> additionalLabels = nodeDescription.getAdditionalLabels();
@@ -367,7 +367,7 @@ public enum CypherGenerator {
Statement updateIfExists;
var neo4jPersistentEntity = (Neo4jPersistentEntity<?>) nodeDescription;
var nodeIdFunction = getNodeIdFunction(neo4jPersistentEntity);
var nodeIdFunction = getNodeIdFunction(neo4jPersistentEntity, canUseElementId);
if (neo4jPersistentEntity.hasVersionProperty()) {
Property versionProperty = rootNode.property(neo4jPersistentEntity.getRequiredVersionProperty().getName());
@@ -436,7 +436,7 @@ public enum CypherGenerator {
@NonNull
public Statement prepareSaveOfRelationship(Neo4jPersistentEntity<?> neo4jPersistentEntity,
RelationshipDescription relationship, @Nullable String dynamicRelationshipType) {
RelationshipDescription relationship, @Nullable String dynamicRelationshipType, boolean canUseElementId) {
final Node startNode = neo4jPersistentEntity.isUsingInternalIds()
? anyNode(START_NODE_NAME)
: node(neo4jPersistentEntity.getPrimaryLabel(), neo4jPersistentEntity.getAdditionalLabels())
@@ -450,22 +450,22 @@ public enum CypherGenerator {
startNode.relationshipTo(endNode, type) :
startNode.relationshipFrom(endNode, type)).named(RELATIONSHIP_NAME);
var startNodeIdFunction = getNodeIdFunction(neo4jPersistentEntity);
var startNodeIdFunction = getNodeIdFunction(neo4jPersistentEntity, canUseElementId);
return match(startNode)
.where(startNodeIdFunction.apply(startNode).isEqualTo(idParameter))
.match(endNode)
.where(getEndNodeIdFunction((Neo4jPersistentEntity<?>) relationship.getTarget()).apply(endNode).isEqualTo(parameter(Constants.TO_ID_PARAMETER_NAME)))
.where(getEndNodeIdFunction((Neo4jPersistentEntity<?>) relationship.getTarget(), canUseElementId).apply(endNode).isEqualTo(parameter(Constants.TO_ID_PARAMETER_NAME)))
.merge(relationshipFragment)
.returning(getReturnedIdExpressionsForRelationship(relationship, relationshipFragment))
.build();
}
private static Function<Node, Expression> getNodeIdFunction(@Nullable Neo4jPersistentEntity<?> entity) {
private static Function<Node, Expression> getNodeIdFunction(@Nullable Neo4jPersistentEntity<?> entity, boolean canUseElementId) {
Function<Node, Expression> startNodeIdFunction;
var idProperty = entity.getRequiredIdProperty();
if (entity.isUsingInternalIds()) {
if (entity.isUsingDeprecatedInternalId()) {
if (entity.isUsingDeprecatedInternalId() || !canUseElementId) {
startNodeIdFunction = Functions::id;
} else {
startNodeIdFunction = Functions::elementId;
@@ -476,13 +476,13 @@ public enum CypherGenerator {
return startNodeIdFunction;
}
private static Function<Node, Expression> getEndNodeIdFunction(@Nullable Neo4jPersistentEntity<?> entity) {
private static Function<Node, Expression> getEndNodeIdFunction(@Nullable Neo4jPersistentEntity<?> entity, boolean canUseElementId) {
Function<Node, Expression> startNodeIdFunction;
if (entity == null) {
return Functions::elementId;
}
if (!entity.isUsingDeprecatedInternalId() && entity.isUsingInternalIds()) {
if (!entity.isUsingDeprecatedInternalId() && canUseElementId) {
startNodeIdFunction = Functions::elementId;
} else {
startNodeIdFunction = Functions::id;
@@ -490,12 +490,12 @@ public enum CypherGenerator {
return startNodeIdFunction;
}
private static Function<Relationship, Expression> getRelationshipIdFunction(RelationshipDescription relationshipDescription) {
private static Function<Relationship, Expression> getRelationshipIdFunction(RelationshipDescription relationshipDescription, boolean canUseElementId) {
Function<Relationship, Expression> result = Functions::elementId;
Function<Relationship, Expression> result = canUseElementId ? Functions::elementId : Functions::id;
if (relationshipDescription.hasRelationshipProperties()) {
Neo4jPersistentEntity<?> entity = (Neo4jPersistentEntity<?>) relationshipDescription.getRelationshipPropertiesEntity();
if (entity != null && entity.isUsingDeprecatedInternalId()) {
if ((entity != null && entity.isUsingDeprecatedInternalId()) || !canUseElementId) {
result = Functions::id;
} else {
result = Functions::elementId;
@@ -506,7 +506,7 @@ public enum CypherGenerator {
@NonNull
public Statement prepareSaveOfRelationships(Neo4jPersistentEntity<?> neo4jPersistentEntity,
RelationshipDescription relationship, @Nullable String dynamicRelationshipType) {
RelationshipDescription relationship, @Nullable String dynamicRelationshipType, boolean canUseElementId) {
final Node startNode = neo4jPersistentEntity.isUsingInternalIds()
? anyNode(START_NODE_NAME)
@@ -525,9 +525,9 @@ public enum CypherGenerator {
return Cypher.unwind(parameter(Constants.NAME_OF_RELATIONSHIP_LIST_PARAM)).as(row)
.with(row)
.match(startNode)
.where(getNodeIdFunction(neo4jPersistentEntity).apply(startNode).isEqualTo(idProperty))
.where(getNodeIdFunction(neo4jPersistentEntity, canUseElementId).apply(startNode).isEqualTo(idProperty))
.match(endNode)
.where(getEndNodeIdFunction((Neo4jPersistentEntity<?>) relationship.getTarget()).apply(endNode).isEqualTo(Cypher.property(row, Constants.TO_ID_PARAMETER_NAME)))
.where(getEndNodeIdFunction((Neo4jPersistentEntity<?>) relationship.getTarget(), canUseElementId).apply(endNode).isEqualTo(Cypher.property(row, Constants.TO_ID_PARAMETER_NAME)))
.merge(relationshipFragment)
.returning(getReturnedIdExpressionsForRelationship(relationship, relationshipFragment))
.build();
@@ -537,7 +537,8 @@ public enum CypherGenerator {
public Statement prepareSaveOfRelationshipWithProperties(Neo4jPersistentEntity<?> neo4jPersistentEntity,
RelationshipDescription relationship,
boolean isNew,
@Nullable String dynamicRelationshipType) {
@Nullable String dynamicRelationshipType,
boolean canUseElementId) {
Assert.isTrue(relationship.hasRelationshipProperties(),
"Properties required to create a relationship with properties");
@@ -555,13 +556,13 @@ public enum CypherGenerator {
startNode.relationshipFrom(endNode, type))
.named(RELATIONSHIP_NAME);
var nodeIdFunction = getNodeIdFunction(neo4jPersistentEntity);
var relationshipIdFunction = getRelationshipIdFunction(relationship);
var nodeIdFunction = getNodeIdFunction(neo4jPersistentEntity, canUseElementId);
var relationshipIdFunction = getRelationshipIdFunction(relationship, canUseElementId);
StatementBuilder.OngoingReadingWithWhere startAndEndNodeMatch = match(startNode)
.where(nodeIdFunction.apply(startNode).isEqualTo(idParameter))
.match(endNode)
.where(getEndNodeIdFunction((Neo4jPersistentEntity<?>) relationship.getTarget()).apply(endNode).isEqualTo(parameter(Constants.TO_ID_PARAMETER_NAME)));
.where(getEndNodeIdFunction((Neo4jPersistentEntity<?>) relationship.getTarget(), canUseElementId).apply(endNode).isEqualTo(parameter(Constants.TO_ID_PARAMETER_NAME)));
StatementBuilder.ExposesSet createOrMatch = isNew
? startAndEndNodeMatch.create(relationshipFragment)
@@ -575,7 +576,7 @@ public enum CypherGenerator {
@NonNull
public Statement prepareUpdateOfRelationshipsWithProperties(Neo4jPersistentEntity<?> neo4jPersistentEntity,
RelationshipDescription relationship, boolean isNew) {
RelationshipDescription relationship, boolean isNew, boolean canUseElementId) {
Assert.isTrue(relationship.hasRelationshipProperties(),
"Properties required to create a relationship with properties");
@@ -598,8 +599,8 @@ public enum CypherGenerator {
.as(row)
.with(row);
var nodeIdFunction = getNodeIdFunction(neo4jPersistentEntity);
var relationshipIdFunction = getRelationshipIdFunction(relationship);
var nodeIdFunction = getNodeIdFunction(neo4jPersistentEntity, canUseElementId);
var relationshipIdFunction = getRelationshipIdFunction(relationship, canUseElementId);
// we only need start and end node querying if we have to create a new relationship...
if (isNew) {
@@ -607,7 +608,7 @@ public enum CypherGenerator {
.match(startNode)
.where(nodeIdFunction.apply(startNode).isEqualTo(idProperty))
.match(endNode)
.where(getEndNodeIdFunction((Neo4jPersistentEntity<?>) relationship.getTarget()).apply(endNode).isEqualTo(Cypher.property(row, Constants.TO_ID_PARAMETER_NAME)))
.where(getEndNodeIdFunction((Neo4jPersistentEntity<?>) relationship.getTarget(), canUseElementId).apply(endNode).isEqualTo(Cypher.property(row, Constants.TO_ID_PARAMETER_NAME)))
.create(relationshipFragment)
.mutate(RELATIONSHIP_NAME, relationshipProperties)
.returning(getReturnedIdExpressionsForRelationship(relationship, relationshipFragment))
@@ -632,7 +633,8 @@ public enum CypherGenerator {
@NonNull
public Statement prepareDeleteOf(
Neo4jPersistentEntity<?> neo4jPersistentEntity,
RelationshipDescription relationshipDescription
RelationshipDescription relationshipDescription,
boolean canUseElementId
) {
final Node startNode = neo4jPersistentEntity.isUsingInternalIds() ? anyNode(START_NODE_NAME)
: node(neo4jPersistentEntity.getPrimaryLabel(), neo4jPersistentEntity.getAdditionalLabels())
@@ -652,8 +654,8 @@ public enum CypherGenerator {
Parameter<?> idParameter = parameter(Constants.FROM_ID_PARAMETER_NAME);
return match(relationship)
.where(getNodeIdFunction(neo4jPersistentEntity).apply(startNode).isEqualTo(idParameter))
.and(getRelationshipIdFunction(relationshipDescription).apply(relationship).in(Cypher.parameter(Constants.NAME_OF_KNOWN_RELATIONSHIPS_PARAM)).not())
.where(getNodeIdFunction(neo4jPersistentEntity, canUseElementId).apply(startNode).isEqualTo(idParameter))
.and(getRelationshipIdFunction(relationshipDescription, canUseElementId).apply(relationship).in(Cypher.parameter(Constants.NAME_OF_KNOWN_RELATIONSHIPS_PARAM)).not())
.delete(relationship.getRequiredSymbolicName())
.build();
}

View File

@@ -538,29 +538,31 @@ public final class Neo4jMappingContext extends AbstractMappingContext<Neo4jPersi
public CreateRelationshipStatementHolder createStatementForImperativeSimpleRelationshipBatch(Neo4jPersistentEntity<?> neo4jPersistentEntity,
RelationshipDescription relationshipDescription,
List<Object> plainRelationshipRows) {
List<Object> plainRelationshipRows, boolean canUseElementId) {
return createStatementForSingleRelationship(neo4jPersistentEntity, (DefaultRelationshipDescription) relationshipDescription,
plainRelationshipRows);
plainRelationshipRows, canUseElementId);
}
public CreateRelationshipStatementHolder createStatementForImperativeRelationshipsWithPropertiesBatch(boolean isNew,
Neo4jPersistentEntity<?> neo4jPersistentEntity,
RelationshipDescription relationshipDescription,
Object relatedValues,
List<Map<String, Object>> relationshipPropertiesRows) {
List<Map<String, Object>> relationshipPropertiesRows,
boolean canUseElementId) {
List<MappingSupport.RelationshipPropertiesWithEntityHolder> relationshipPropertyValues = ((Collection<?>) relatedValues).stream()
.map(MappingSupport.RelationshipPropertiesWithEntityHolder.class::cast).collect(Collectors.toList());
return createStatementForRelationshipWithPropertiesBatch(isNew, neo4jPersistentEntity, relationshipDescription,
relationshipPropertyValues, relationshipPropertiesRows);
relationshipPropertyValues, relationshipPropertiesRows, canUseElementId);
}
public CreateRelationshipStatementHolder createStatementForSingleRelationship(Neo4jPersistentEntity<?> neo4jPersistentEntity,
RelationshipDescription relationshipContext,
Object relatedValue,
boolean isNewRelationship) {
boolean isNewRelationship,
boolean canUseElementId) {
if (relationshipContext.hasRelationshipProperties()) {
MappingSupport.RelationshipPropertiesWithEntityHolder relatedValueEntityHolder =
@@ -586,22 +588,22 @@ public final class Neo4jMappingContext extends AbstractMappingContext<Neo4jPersi
}
return createStatementForRelationshipWithProperties(
neo4jPersistentEntity, relationshipContext,
dynamicRelationshipType, relatedValueEntityHolder, isNewRelationship
dynamicRelationshipType, relatedValueEntityHolder, isNewRelationship, canUseElementId
);
} else {
return createStatementForSingleRelationship(neo4jPersistentEntity, (DefaultRelationshipDescription) relationshipContext,
relatedValue);
relatedValue, canUseElementId);
}
}
private CreateRelationshipStatementHolder createStatementForRelationshipWithProperties(
Neo4jPersistentEntity<?> neo4jPersistentEntity,
RelationshipDescription relationshipDescription, @Nullable String dynamicRelationshipType,
MappingSupport.RelationshipPropertiesWithEntityHolder relatedValue, boolean isNewRelationship) {
MappingSupport.RelationshipPropertiesWithEntityHolder relatedValue, boolean isNewRelationship, boolean canUseElementId) {
Statement relationshipCreationQuery = CypherGenerator.INSTANCE.prepareSaveOfRelationshipWithProperties(
neo4jPersistentEntity, relationshipDescription, isNewRelationship,
dynamicRelationshipType);
dynamicRelationshipType, canUseElementId);
Map<String, Object> propMap = new HashMap<>();
// write relationship properties
@@ -615,10 +617,11 @@ public final class Neo4jMappingContext extends AbstractMappingContext<Neo4jPersi
Neo4jPersistentEntity<?> neo4jPersistentEntity,
RelationshipDescription relationshipDescription,
List<MappingSupport.RelationshipPropertiesWithEntityHolder> relatedValues,
List<Map<String, Object>> relationshipPropertiesRows) {
List<Map<String, Object>> relationshipPropertiesRows,
boolean canUseElementId) {
Statement relationshipCreationQuery = CypherGenerator.INSTANCE
.prepareUpdateOfRelationshipsWithProperties(neo4jPersistentEntity, relationshipDescription, isNew);
.prepareUpdateOfRelationshipsWithProperties(neo4jPersistentEntity, relationshipDescription, isNew, canUseElementId);
List<Object> relationshipRows = new ArrayList<>();
Map<String, Object> relationshipPropertiesEntries = new HashMap<>();
if (isNew) {
@@ -636,7 +639,7 @@ public final class Neo4jMappingContext extends AbstractMappingContext<Neo4jPersi
private CreateRelationshipStatementHolder createStatementForSingleRelationship(
Neo4jPersistentEntity<?> neo4jPersistentEntity,
DefaultRelationshipDescription relationshipDescription, Object relatedValue) {
DefaultRelationshipDescription relationshipDescription, Object relatedValue, boolean canUseElementId) {
String relationshipType;
if (!relationshipDescription.isDynamic()) {
@@ -649,7 +652,7 @@ public final class Neo4jMappingContext extends AbstractMappingContext<Neo4jPersi
}
Statement relationshipCreationQuery = CypherGenerator.INSTANCE.prepareSaveOfRelationships(
neo4jPersistentEntity, relationshipDescription, relationshipType);
neo4jPersistentEntity, relationshipDescription, relationshipType, canUseElementId);
return new CreateRelationshipStatementHolder(relationshipCreationQuery, Collections.emptyMap());
}

View File

@@ -60,6 +60,12 @@ public interface Neo4jPersistentEntity<T>
* @return True if the underlying domain classes uses {@code id()} to compute internally generated ids.
*/
default boolean isUsingDeprecatedInternalId() {
for (NodeDescription<?> nodeDescription : getChildNodeDescriptionsInHierarchy()) {
if (nodeDescription.isUsingInternalIds() && ((Neo4jPersistentEntity<?>) nodeDescription).getIdProperty() != null
&& Neo4jPersistentEntity.DEPRECATED_GENERATED_ID_TYPES.contains(((Neo4jPersistentEntity<?>) nodeDescription).getIdProperty().getType())) {
return true;
}
}
return isUsingInternalIds() && Neo4jPersistentEntity.DEPRECATED_GENERATED_ID_TYPES.contains(getRequiredIdProperty().getType());
}
}

View File

@@ -56,7 +56,7 @@ class CypherGeneratorTest {
when(relationshipDescription.isDynamic()).thenReturn(true);
Statement statement = CypherGenerator.INSTANCE.prepareSaveOfRelationship(persistentEntity,
relationshipDescription, "REL");
relationshipDescription, "REL", true);
String expectedQuery = "MATCH (startNode:`Entity1`) WHERE startNode.id = $fromId MATCH (endNode)"
+ " WHERE elementId(endNode) = $toId MERGE (startNode)<-[relProps:`REL`]-(endNode) RETURN elementId(relProps) AS __elementId__";
@@ -71,7 +71,7 @@ class CypherGeneratorTest {
when(relationshipDescription.isDynamic()).thenReturn(true);
Statement statement = CypherGenerator.INSTANCE.prepareSaveOfRelationship(persistentEntity,
relationshipDescription, "REL");
relationshipDescription, "REL", true);
String expectedQuery =
"MATCH (startNode:`Entity1`:`MultipleLabel`) WHERE startNode.id = $fromId MATCH (endNode)"
@@ -92,7 +92,7 @@ class CypherGeneratorTest {
when(persistentEntity.isUsingDeprecatedInternalId()).thenReturn(true);
Statement statement = CypherGenerator.INSTANCE.prepareSaveOfRelationship(persistentEntity,
relationshipDescription, "REL");
relationshipDescription, "REL", true);
String expectedQuery = "MATCH (startNode) WHERE id(startNode) = $fromId MATCH (endNode)"
+ " WHERE elementId(endNode) = $toId MERGE (startNode)<-[relProps:`REL`]-(endNode) RETURN elementId(relProps) AS __elementId__";
@@ -106,7 +106,7 @@ class CypherGeneratorTest {
RelationshipDescription relationshipDescription = Mockito.mock(RelationshipDescription.class);
doReturn(relatedEntity).when(relationshipDescription).getTarget();
Statement statement = CypherGenerator.INSTANCE.prepareDeleteOf(persistentEntity, relationshipDescription);
Statement statement = CypherGenerator.INSTANCE.prepareDeleteOf(persistentEntity, relationshipDescription, true);
String expectedQuery = "MATCH (startNode:`Entity1`)<-[rel]-(:`Entity2`) WHERE (startNode.id = $fromId AND NOT (elementId(rel) IN $__knownRelationShipIds__)) DELETE rel";
Assertions.assertEquals(expectedQuery, Renderer.getRenderer(Configuration.newConfig().withDialect(Dialect.NEO4J_5).build()).render(statement));
@@ -121,7 +121,7 @@ class CypherGeneratorTest {
RelationshipDescription relationshipDescription = Mockito.mock(RelationshipDescription.class);
doReturn(relatedEntity).when(relationshipDescription).getTarget();
Statement statement = CypherGenerator.INSTANCE.prepareDeleteOf(persistentEntity, relationshipDescription);
Statement statement = CypherGenerator.INSTANCE.prepareDeleteOf(persistentEntity, relationshipDescription, true);
String expectedQuery = "MATCH (startNode:`Entity1`:`MultipleLabel`)<-[rel]-(:`Entity2`:`MultipleLabel`) WHERE (startNode.id = $fromId AND NOT (elementId(rel) IN $__knownRelationShipIds__)) DELETE rel";
Assertions.assertEquals(expectedQuery, Renderer.getRenderer(Configuration.newConfig().withDialect(Dialect.NEO4J_5).build()).render(statement));
@@ -143,7 +143,7 @@ class CypherGeneratorTest {
when(persistentEntity.getRequiredIdProperty()).thenReturn(persistentProperty);
when(persistentEntity.isUsingDeprecatedInternalId()).thenReturn(true);
Statement statement = CypherGenerator.INSTANCE.prepareDeleteOf(persistentEntity, relationshipDescription);
Statement statement = CypherGenerator.INSTANCE.prepareDeleteOf(persistentEntity, relationshipDescription, true);
String expectedQuery = "MATCH (startNode)<-[rel]-(:`Entity2`) WHERE (id(startNode) = $fromId AND NOT (elementId(rel) IN $__knownRelationShipIds__)) DELETE rel";
Assertions.assertEquals(expectedQuery, Renderer.getRenderer(Configuration.newConfig().withDialect(Dialect.NEO4J_5).build()).render(statement));