Skip to content

Commit 68cbfe6

Browse files
committed
Detect Cypher capabilities correctly.
Prior to this commit, CypherRenderer with Cypher DSL dialects creating prefixed Cypher statements, like `CYPHER 5 RETURN...` would not correctly be reported as `elementId` capable renderers. This led to the situation that there were a missmatch between String-based elementIds and Cypher using the number-based `id` function. Closes #3091 Signed-off-by: Gerrit Meier <meistermeier@gmail.com>
1 parent 5fc81aa commit 68cbfe6

3 files changed

Lines changed: 57 additions & 12 deletions

File tree

src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -468,8 +468,8 @@ private <T> T saveImpl(T instance, @Nullable Collection<PropertyFilter.Projected
468468
TemplateSupport.FilteredBinderFunction<T> binderFunction = TemplateSupport.createAndApplyPropertyFilter(
469469
includedProperties, entityMetaData,
470470
this.neo4jMappingContext.getRequiredBinderFunctionFor((Class<T>) entityToBeSaved.getClass()));
471-
var statement = this.cypherGenerator.prepareSaveOf(entityMetaData, dynamicLabels,
472-
TemplateSupport.rendererRendersElementId(this.renderer));
471+
var canUseElementId = TemplateSupport.rendererRendersElementId(this.renderer);
472+
var statement = this.cypherGenerator.prepareSaveOf(entityMetaData, dynamicLabels, canUseElementId);
473473
Optional<Entity> newOrUpdatedNode = this.neo4jClient.query(() -> this.renderer.render(statement))
474474
.bind(entityToBeSaved)
475475
.with(binderFunction)
@@ -486,8 +486,7 @@ private <T> T saveImpl(T instance, @Nullable Collection<PropertyFilter.Projected
486486
}
487487

488488
Object elementId = newOrUpdatedNode.map(node -> {
489-
if (!entityMetaData.isUsingDeprecatedInternalId()
490-
&& TemplateSupport.rendererRendersElementId(this.renderer)) {
489+
if (!entityMetaData.isUsingDeprecatedInternalId() && canUseElementId) {
491490
return IdentitySupport.getElementId(node);
492491
}
493492
@SuppressWarnings("deprecation")
@@ -504,7 +503,8 @@ private <T> T saveImpl(T instance, @Nullable Collection<PropertyFilter.Projected
504503
}
505504

506505
stateMachine.markEntityAsProcessed(instance, elementId);
507-
processRelations(entityMetaData, propertyAccessor, isEntityNew, stateMachine, binderFunction.filter);
506+
processRelations(entityMetaData, propertyAccessor, isEntityNew, stateMachine, binderFunction.filter,
507+
canUseElementId);
508508

509509
T bean = propertyAccessor.getBean();
510510
stateMachine.markAsAliased(instance, bean);
@@ -648,7 +648,8 @@ class Tuple3<T> {
648648
TemplateSupport.computeIncludePropertyPredicate(
649649
((includedProperties != null && !includedProperties.isEmpty()) || includeProperty != null)
650650
? pps : includedPropertiesByClass.get(t.modifiedInstance.getClass()),
651-
entityMetaData));
651+
entityMetaData),
652+
TemplateSupport.rendererRendersElementId(this.renderer));
652653
}).collect(Collectors.toList());
653654
}
654655

@@ -856,24 +857,26 @@ private <T> ExecutableQuery<T> createExecutableQuery(Class<T> domainType, @Nulla
856857
* @param stateMachine initial state of entity processing
857858
* @param includeProperty a predicate telling to include a relationship property or
858859
* not
860+
* @param canUseElementId flag if the Cypher DSL renderer supports elementId function
859861
* @param <T> the type of the object being initially processed
860862
* @return the owner of the relations being processed
861863
*/
862864
private <T> T processRelations(Neo4jPersistentEntity<?> neo4jPersistentEntity,
863865
PersistentPropertyAccessor<?> parentPropertyAccessor, boolean isParentObjectNew,
864-
NestedRelationshipProcessingStateMachine stateMachine, PropertyFilter includeProperty) {
866+
NestedRelationshipProcessingStateMachine stateMachine, PropertyFilter includeProperty,
867+
boolean canUseElementId) {
865868

866869
PropertyFilter.RelaxedPropertyPath startingPropertyPath = PropertyFilter.RelaxedPropertyPath
867870
.withRootType(neo4jPersistentEntity.getUnderlyingClass());
868871
return processNestedRelations(neo4jPersistentEntity, parentPropertyAccessor, isParentObjectNew, stateMachine,
869-
includeProperty, startingPropertyPath);
872+
includeProperty, canUseElementId, startingPropertyPath);
870873
}
871874

872875
@SuppressWarnings("deprecation")
873876
private <T> T processNestedRelations(Neo4jPersistentEntity<?> sourceEntity,
874877
PersistentPropertyAccessor<?> propertyAccessor, boolean isParentObjectNew,
875878
NestedRelationshipProcessingStateMachine stateMachine, PropertyFilter includeProperty,
876-
PropertyFilter.RelaxedPropertyPath previousPath) {
879+
boolean canUseElementId, PropertyFilter.RelaxedPropertyPath previousPath) {
877880

878881
Object fromId = propertyAccessor.getProperty(sourceEntity.getRequiredIdProperty());
879882

@@ -921,7 +924,6 @@ private <T> T processNestedRelations(Neo4jPersistentEntity<?> sourceEntity,
921924
// has not been processed before.
922925
// This avoids the usage of cache but might have significant impact on overall
923926
// performance
924-
boolean canUseElementId = TemplateSupport.rendererRendersElementId(this.renderer);
925927
if (!isParentObjectNew && !stateMachine.hasProcessedRelationship(fromId, relationshipDescription)) {
926928

927929
List<Object> knownRelationshipsIds = new ArrayList<>();
@@ -1121,7 +1123,7 @@ else if (relationshipDescription.hasRelationshipProperties() && fromId != null)
11211123

11221124
if (processState != ProcessState.PROCESSED_ALL_VALUES) {
11231125
processNestedRelations(targetEntity, targetPropertyAccessor, isNewEntity, stateMachine,
1124-
includeProperty, currentPropertyPath);
1126+
includeProperty, canUseElementId, currentPropertyPath);
11251127
}
11261128

11271129
Object potentiallyRecreatedNewRelatedObject = MappingSupport

src/main/java/org/springframework/data/neo4j/core/TemplateSupport.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ static boolean rendererCanUseElementIdIfPresent(Renderer renderer, Neo4jPersiste
341341

342342
static boolean rendererRendersElementId(Renderer renderer) {
343343
return renderer.render(Cypher.returning(Cypher.elementId(Cypher.anyNode("n"))).build())
344-
.equals("RETURN elementId(n)");
344+
.endsWith("RETURN elementId(n)");
345345
}
346346

347347
public static String convertIdOrElementIdToString(Object value) {

src/test/java/org/springframework/data/neo4j/core/TemplateSupportTests.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,16 @@
1717

1818
import java.util.Arrays;
1919
import java.util.Collections;
20+
import java.util.stream.Stream;
2021

22+
import org.junit.jupiter.api.Nested;
2123
import org.junit.jupiter.api.Test;
24+
import org.junit.jupiter.params.ParameterizedTest;
25+
import org.junit.jupiter.params.provider.Arguments;
26+
import org.junit.jupiter.params.provider.MethodSource;
27+
import org.neo4j.cypherdsl.core.renderer.Configuration;
28+
import org.neo4j.cypherdsl.core.renderer.Dialect;
29+
import org.neo4j.cypherdsl.core.renderer.Renderer;
2230

2331
import static org.assertj.core.api.Assertions.assertThat;
2432

@@ -99,6 +107,41 @@ void shouldNotFindCommonElementTypeWhenThereIsNone() {
99107
assertThat(type).isNull();
100108
}
101109

110+
@Nested
111+
class CypherRendering {
112+
113+
@ParameterizedTest
114+
@MethodSource("createSupportedCypherRenderers")
115+
void rendersElementId(Renderer renderer, boolean supportsElementId) {
116+
assertThat(TemplateSupport.rendererRendersElementId(renderer)).isEqualTo(supportsElementId);
117+
}
118+
119+
static Stream<Arguments> createSupportedCypherRenderers() {
120+
return Stream.of(Arguments
121+
.arguments(Renderer.getRenderer(Configuration.newConfig().withDialect(Dialect.NEO4J_4).build()), false),
122+
Arguments.arguments(
123+
Renderer.getRenderer(Configuration.newConfig().withDialect(Dialect.NEO4J_5).build()), true),
124+
Arguments.arguments(
125+
Renderer.getRenderer(Configuration.newConfig().withDialect(Dialect.NEO4J_5_23).build()),
126+
true),
127+
Arguments.arguments(
128+
Renderer.getRenderer(Configuration.newConfig().withDialect(Dialect.NEO4J_5_26).build()),
129+
true),
130+
Arguments.arguments(
131+
Renderer.getRenderer(
132+
Configuration.newConfig().withDialect(Dialect.NEO4J_5_DEFAULT_CYPHER).build()),
133+
true),
134+
Arguments.arguments(Renderer
135+
.getRenderer(Configuration.newConfig().withDialect(Dialect.NEO4J_5_CYPHER_5).build()), true),
136+
Arguments.arguments(Renderer
137+
.getRenderer(Configuration.newConfig().withDialect(Dialect.NEO4J_5_CYPHER_25).build()), true),
138+
Arguments.arguments(
139+
Renderer.getRenderer(Configuration.newConfig().withDialect(Dialect.NEO4J_2025).build()),
140+
true));
141+
}
142+
143+
}
144+
102145
interface IA {
103146

104147
}

0 commit comments

Comments
 (0)