diff --git a/pom.xml b/pom.xml index 198f34160a..40f660caa5 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ org.springframework.data spring-data-neo4j - 6.2.0-SNAPSHOT + 6.2.0-NODE-NAMES-SNAPSHOT Spring Data Neo4j Next generation Object-Graph-Mapping for Spring Data. diff --git a/src/main/java/org/springframework/data/neo4j/core/DynamicLabels.java b/src/main/java/org/springframework/data/neo4j/core/DynamicLabels.java index 8469ead8ef..474a459252 100644 --- a/src/main/java/org/springframework/data/neo4j/core/DynamicLabels.java +++ b/src/main/java/org/springframework/data/neo4j/core/DynamicLabels.java @@ -25,6 +25,8 @@ import org.neo4j.cypherdsl.core.Node; import org.neo4j.cypherdsl.core.StatementBuilder.OngoingMatchAndUpdate; import org.springframework.data.neo4j.core.mapping.Constants; +import org.springframework.data.neo4j.core.mapping.NodeDescription; +import org.springframework.lang.Nullable; /** * Decorator for an ongoing update statement that removes obsolete dynamic labels and adds new ones. @@ -33,16 +35,17 @@ */ final class DynamicLabels implements UnaryOperator { - public static final DynamicLabels EMPTY = new DynamicLabels(Collections.emptyList(), Collections.emptyList()); + public static final DynamicLabels EMPTY = new DynamicLabels(null, Collections.emptyList(), Collections.emptyList()); - private static final Node rootNode = Cypher.anyNode(Constants.NAME_OF_ROOT_NODE); + private final Node rootNode; private final List oldLabels; private final List newLabels; - DynamicLabels(Collection oldLabels, Collection newLabels) { + DynamicLabels(@Nullable NodeDescription nodeDescription, Collection oldLabels, Collection newLabels) { this.oldLabels = new ArrayList<>(oldLabels); this.newLabels = new ArrayList<>(newLabels); + this.rootNode = Cypher.anyNode(Constants.NAME_OF_TYPED_ROOT_NODE.apply(nodeDescription)); } @Override diff --git a/src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java b/src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java index 4d3849f7a3..aaa8eb3af9 100644 --- a/src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java +++ b/src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java @@ -413,7 +413,7 @@ private DynamicLabels determineDynamicLabels(T entityToBeSaved, Neo4jPersist } Optional> optionalResult = runnableQuery.fetch().one(); - return new DynamicLabels(optionalResult.map(r -> (Collection) r.get(Constants.NAME_OF_LABELS)) + return new DynamicLabels(entityMetaData, optionalResult.map(r -> (Collection) r.get(Constants.NAME_OF_LABELS)) .orElseGet(Collections::emptyList), (Collection) propertyAccessor.getProperty(p)); }).orElse(DynamicLabels.EMPTY); } @@ -545,12 +545,12 @@ public void deleteByIdWithVersion(Object id, Class domainType, Neo4jPersi String nameOfParameter = "id"; Condition condition = entityMetaData.getIdExpression().isEqualTo(parameter(nameOfParameter)) - .and(Cypher.property(Constants.NAME_OF_ROOT_NODE, versionProperty.getPropertyName()) + .and(Cypher.property(Constants.NAME_OF_TYPED_ROOT_NODE.apply(entityMetaData), versionProperty.getPropertyName()) .isEqualTo(parameter(Constants.NAME_OF_VERSION_PARAM)) - .or(Cypher.property(Constants.NAME_OF_ROOT_NODE, versionProperty.getPropertyName()).isNull())); + .or(Cypher.property(Constants.NAME_OF_TYPED_ROOT_NODE.apply(entityMetaData), versionProperty.getPropertyName()).isNull())); Statement statement = cypherGenerator.prepareMatchOf(entityMetaData, condition) - .returning(Constants.NAME_OF_ROOT_NODE).build(); + .returning(Constants.NAME_OF_TYPED_ROOT_NODE.apply(entityMetaData)).build(); Map parameters = new HashMap<>(); parameters.put(nameOfParameter, convertIdValues(entityMetaData.getRequiredIdProperty(), id)); @@ -971,7 +971,7 @@ private Optional> createFetchSpec() { if (nodesAndRelationshipsById.hasRootNodeIds()) { return Optional.empty(); } - cypherQuery = renderer.render(nodesAndRelationshipsById.toStatement()); + cypherQuery = renderer.render(nodesAndRelationshipsById.toStatement(entityMetaData)); finalParameters = nodesAndRelationshipsById.getParameters(); } else { Statement statement = queryFragments.toStatement(); @@ -1036,7 +1036,7 @@ private void iterateNextLevel(Collection nodeIds, Neo4jPersistentEntity Collection relationships = target.getRelationshipsInHierarchy(preparedQuery.getQueryFragmentsAndParameters().getQueryFragments()::includeField); for (RelationshipDescription relationshipDescription : relationships) { - Node node = anyNode(Constants.NAME_OF_ROOT_NODE); + Node node = anyNode(Constants.NAME_OF_TYPED_ROOT_NODE.apply(target)); Statement statement = cypherGenerator .prepareMatchOf(target, relationshipDescription, null, diff --git a/src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java b/src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java index 125be51347..ea6347d803 100644 --- a/src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java +++ b/src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java @@ -422,7 +422,7 @@ private Mono> determineDynamicLabels(T entityToBeSa return runnableQuery.fetch().one().map(m -> (Collection) m.get(Constants.NAME_OF_LABELS)) .switchIfEmpty(Mono.just(Collections.emptyList())) .zipWith(Mono.just((Collection) propertyAccessor.getProperty(p))) - .map(t -> Tuples.of(entityToBeSaved, new DynamicLabels(t.getT1(), t.getT2()))); + .map(t -> Tuples.of(entityToBeSaved, new DynamicLabels(entityMetaData, t.getT1(), t.getT2()))); }).orElse(Mono.just(Tuples.of(entityToBeSaved, DynamicLabels.EMPTY))); } @@ -548,12 +548,12 @@ public Mono deleteByIdWithVersion(Object id, Class domainType, Neo4 String nameOfParameter = "id"; Neo4jPersistentEntity entityMetaData = neo4jMappingContext.getPersistentEntity(domainType); Condition condition = entityMetaData.getIdExpression().isEqualTo(parameter(nameOfParameter)) - .and(Cypher.property(Constants.NAME_OF_ROOT_NODE, versionProperty.getPropertyName()) + .and(Cypher.property(Constants.NAME_OF_TYPED_ROOT_NODE.apply(entityMetaData), versionProperty.getPropertyName()) .isEqualTo(parameter(Constants.NAME_OF_VERSION_PARAM)) - .or(Cypher.property(Constants.NAME_OF_ROOT_NODE, versionProperty.getPropertyName()).isNull())); + .or(Cypher.property(Constants.NAME_OF_TYPED_ROOT_NODE.apply(entityMetaData), versionProperty.getPropertyName()).isNull())); Statement statement = cypherGenerator.prepareMatchOf(entityMetaData, condition) - .returning(Constants.NAME_OF_ROOT_NODE).build(); + .returning(Constants.NAME_OF_TYPED_ROOT_NODE.apply(entityMetaData)).build(); Map parameters = new HashMap<>(); parameters.put(nameOfParameter, convertIdValues(entityMetaData.getRequiredIdProperty(), id)); @@ -612,7 +612,7 @@ private Mono> createExecutableQuery(Class domainType, if (containsPossibleCircles && !queryFragments.isScalarValueReturn()) { return createNodesAndRelationshipsByIdStatementProvider(entityMetaData, queryFragments, queryFragmentsAndParameters.getParameters()) .flatMap(finalQueryAndParameters -> - createExecutableQuery(domainType, resultType, renderer.render(finalQueryAndParameters.toStatement()), + createExecutableQuery(domainType, resultType, renderer.render(finalQueryAndParameters.toStatement(entityMetaData)), finalQueryAndParameters.getParameters())); } @@ -665,7 +665,7 @@ private Flux, Collection>> iterateNextLevel(Collec return Flux.fromIterable(target.getRelationshipsInHierarchy(queryFragments::includeField)) .flatMap(relDe -> { - Node node = anyNode(Constants.NAME_OF_ROOT_NODE); + Node node = anyNode(Constants.NAME_OF_TYPED_ROOT_NODE.apply(target)); Statement statement = cypherGenerator .prepareMatchOf(target, relDe, null, @@ -995,7 +995,7 @@ public Mono> toExecutableQuery(PreparedQuery preparedQ return createNodesAndRelationshipsByIdStatementProvider(entityMetaData, queryFragments, finalParameters) .map(nodesAndRelationshipsById -> { ReactiveNeo4jClient.MappingSpec mappingSpec = this.neo4jClient.query(renderer.render( - nodesAndRelationshipsById.toStatement())) + nodesAndRelationshipsById.toStatement(entityMetaData))) .bindAll(nodesAndRelationshipsById.getParameters()).fetchAs(resultType); ReactiveNeo4jClient.RecordFetchSpec fetchSpec = preparedQuery.getOptionalMappingFunction() diff --git a/src/main/java/org/springframework/data/neo4j/core/TemplateSupport.java b/src/main/java/org/springframework/data/neo4j/core/TemplateSupport.java index b45a6fb5f7..9b754a5586 100644 --- a/src/main/java/org/springframework/data/neo4j/core/TemplateSupport.java +++ b/src/main/java/org/springframework/data/neo4j/core/TemplateSupport.java @@ -180,7 +180,7 @@ boolean hasRootNodeIds() { return parameters.get(ROOT_NODE_IDS).isEmpty(); } - Statement toStatement() { + Statement toStatement(NodeDescription nodeDescription) { String rootNodeIds = "rootNodeIds"; String relationshipIds = "relationshipIds"; @@ -195,12 +195,12 @@ Statement toStatement() { .optionalMatch(relatedNodes) .where(Functions.id(relatedNodes).in(Cypher.parameter(relatedNodeIds))) .with( - rootNodes.as(Constants.NAME_OF_ROOT_NODE.getValue()), + rootNodes.as(Constants.NAME_OF_TYPED_ROOT_NODE.apply(nodeDescription).getValue()), Functions.collectDistinct(relationships).as(Constants.NAME_OF_SYNTHESIZED_RELATIONS), Functions.collectDistinct(relatedNodes).as(Constants.NAME_OF_SYNTHESIZED_RELATED_NODES)) .orderBy(queryFragments.getOrderBy()) .returning( - Constants.NAME_OF_ROOT_NODE.as(Constants.NAME_OF_SYNTHESIZED_ROOT_NODE), + Constants.NAME_OF_TYPED_ROOT_NODE.apply(nodeDescription).as(Constants.NAME_OF_SYNTHESIZED_ROOT_NODE), Cypher.name(Constants.NAME_OF_SYNTHESIZED_RELATIONS), Cypher.name(Constants.NAME_OF_SYNTHESIZED_RELATED_NODES) ) diff --git a/src/main/java/org/springframework/data/neo4j/core/mapping/Constants.java b/src/main/java/org/springframework/data/neo4j/core/mapping/Constants.java index 7b52d658a4..225deff4cb 100644 --- a/src/main/java/org/springframework/data/neo4j/core/mapping/Constants.java +++ b/src/main/java/org/springframework/data/neo4j/core/mapping/Constants.java @@ -19,6 +19,9 @@ import org.apiguardian.api.API.Status; import org.neo4j.cypherdsl.core.Cypher; import org.neo4j.cypherdsl.core.SymbolicName; +import org.springframework.util.StringUtils; + +import java.util.function.Function; /** * A pool of constants used in our Cypher generation. These constants may change without further notice and are meant @@ -31,7 +34,12 @@ @API(status = Status.EXPERIMENTAL, since = "6.0") public final class Constants { - public static final SymbolicName NAME_OF_ROOT_NODE = Cypher.name("n"); + public static final Function, SymbolicName> NAME_OF_TYPED_ROOT_NODE = + (nodeDescription) -> nodeDescription != null + ? Cypher.name(StringUtils.uncapitalize(nodeDescription.getUnderlyingClass().getSimpleName())) + : Cypher.name("n"); + + public static final SymbolicName NAME_OF_ROOT_NODE = NAME_OF_TYPED_ROOT_NODE.apply(null); public static final String NAME_OF_INTERNAL_ID = "__internalNeo4jId__"; /** @@ -54,7 +62,6 @@ public final class Constants { public static final String NAME_OF_ENTITY_LIST_PARAM = "__entities__"; public static final String NAME_OF_KNOWN_RELATIONSHIP_PARAM = "__knownRelationShipId__"; public static final String NAME_OF_KNOWN_RELATIONSHIPS_PARAM = "__knownRelationShipIds__"; - public static final String NAME_OF_PATHS = "__paths__"; public static final String NAME_OF_ALL_PROPERTIES = "__allProperties__"; public static final String NAME_OF_SYNTHESIZED_ROOT_NODE = "__sn__"; diff --git a/src/main/java/org/springframework/data/neo4j/core/mapping/CypherGenerator.java b/src/main/java/org/springframework/data/neo4j/core/mapping/CypherGenerator.java index 2fd51f8d64..8ffa208132 100644 --- a/src/main/java/org/springframework/data/neo4j/core/mapping/CypherGenerator.java +++ b/src/main/java/org/springframework/data/neo4j/core/mapping/CypherGenerator.java @@ -109,7 +109,7 @@ public StatementBuilder.OrderableOngoingReadingAndWith prepareMatchOf(NodeDescri Node rootNode = createRootNode(nodeDescription); List expressions = new ArrayList<>(); - expressions.add(Constants.NAME_OF_ROOT_NODE); + expressions.add(rootNode.getRequiredSymbolicName()); expressions.add(Functions.id(rootNode).as(Constants.NAME_OF_INTERNAL_ID)); return match(rootNode).where(conditionOrNoCondition(condition)).with(expressions.toArray(new Expression[] {})); @@ -121,7 +121,7 @@ public StatementBuilder.OngoingReading prepareMatchOf(NodeDescription nodeDes String primaryLabel = nodeDescription.getPrimaryLabel(); List additionalLabels = nodeDescription.getAdditionalLabels(); - Node rootNode = node(primaryLabel, additionalLabels).named(Constants.NAME_OF_ROOT_NODE); + Node rootNode = node(primaryLabel, additionalLabels).named(Constants.NAME_OF_TYPED_ROOT_NODE.apply(nodeDescription)); StatementBuilder.OngoingReadingWithoutWhere match = null; if (initialMatchOn == null || initialMatchOn.isEmpty()) { @@ -150,7 +150,7 @@ public StatementBuilder.OngoingReading prepareMatchOf(NodeDescription nodeDes String primaryLabel = nodeDescription.getPrimaryLabel(); List additionalLabels = nodeDescription.getAdditionalLabels(); - Node rootNode = node(primaryLabel, additionalLabels).named(Constants.NAME_OF_ROOT_NODE); + Node rootNode = node(primaryLabel, additionalLabels).named(Constants.NAME_OF_TYPED_ROOT_NODE.apply(nodeDescription)); StatementBuilder.OngoingReadingWithoutWhere match = null; if (initialMatchOn == null || initialMatchOn.isEmpty()) { @@ -210,7 +210,7 @@ public Node createRootNode(NodeDescription nodeDescription) { String primaryLabel = nodeDescription.getPrimaryLabel(); List additionalLabels = nodeDescription.getAdditionalLabels(); - return node(primaryLabel, additionalLabels).named(Constants.NAME_OF_ROOT_NODE); + return node(primaryLabel, additionalLabels).named(Constants.NAME_OF_TYPED_ROOT_NODE.apply(nodeDescription)); } /** @@ -223,7 +223,7 @@ public Node createRootNode(NodeDescription nodeDescription) { */ public Statement createStatementReturningDynamicLabels(NodeDescription nodeDescription) { - final Node rootNode = Cypher.anyNode(Constants.NAME_OF_ROOT_NODE); + final Node rootNode = Cypher.anyNode(Constants.NAME_OF_TYPED_ROOT_NODE.apply(nodeDescription)); Condition versionCondition; if (((Neo4jPersistentEntity) nodeDescription).hasVersionProperty()) { @@ -254,7 +254,7 @@ public Statement prepareDeleteOf(NodeDescription nodeDescription, @Nullable C public Statement prepareDeleteOf(NodeDescription nodeDescription, @Nullable Condition condition, boolean count) { Node rootNode = node(nodeDescription.getPrimaryLabel(), nodeDescription.getAdditionalLabels()) - .named(Constants.NAME_OF_ROOT_NODE); + .named(Constants.NAME_OF_TYPED_ROOT_NODE.apply(nodeDescription)); OngoingUpdate ongoingUpdate = match(rootNode).where(conditionOrNoCondition(condition)).detachDelete(rootNode); if (count) { return ongoingUpdate.returning(Functions.count(rootNode)).build(); @@ -268,7 +268,7 @@ public Statement prepareSaveOf(NodeDescription nodeDescription, String primaryLabel = nodeDescription.getPrimaryLabel(); List additionalLabels = nodeDescription.getAdditionalLabels(); - Node rootNode = node(primaryLabel, additionalLabels).named(Constants.NAME_OF_ROOT_NODE); + Node rootNode = node(primaryLabel, additionalLabels).named(Constants.NAME_OF_TYPED_ROOT_NODE.apply(nodeDescription)); IdDescription idDescription = nodeDescription.getIdDescription(); Parameter idParameter = parameter(Constants.NAME_OF_ID); @@ -356,7 +356,7 @@ public Statement prepareSaveOfMultipleInstancesOf(NodeDescription nodeDescrip "Only entities that use external IDs can be saved in a batch."); Node rootNode = node(nodeDescription.getPrimaryLabel(), nodeDescription.getAdditionalLabels()) - .named(Constants.NAME_OF_ROOT_NODE); + .named(Constants.NAME_OF_TYPED_ROOT_NODE.apply(nodeDescription)); IdDescription idDescription = nodeDescription.getIdDescription(); String nameOfIdProperty = idDescription.getOptionalGraphPropertyName() @@ -519,7 +519,13 @@ public Collection createReturnStatementForMatch(Neo4jPersistentEntit if (nodeDescription.containsPossibleCircles(includeField)) { return createGenericReturnStatement(); } else { - return Collections.singleton(projectPropertiesAndRelationships(PropertyFilter.RelaxedPropertyPath.withRootType(nodeDescription.getUnderlyingClass()), nodeDescription, Constants.NAME_OF_ROOT_NODE, includeField, null, processedRelationships)); + return Collections.singleton(projectPropertiesAndRelationships( + PropertyFilter.RelaxedPropertyPath.withRootType(nodeDescription.getUnderlyingClass()), + nodeDescription, + Constants.NAME_OF_TYPED_ROOT_NODE.apply(nodeDescription), + includeField, + null, + processedRelationships)); } } @@ -612,7 +618,7 @@ private List generateListsFor(PropertyFilter.RelaxedPropertyPath parentP // if we already processed the other way before, do not try to jump in the infinite loop // unless it is a root node relationship - if (!nodeName.equals(Constants.NAME_OF_ROOT_NODE) && relationshipDescription.hasRelationshipObverse() + if (relationshipDescription.hasRelationshipObverse() && processedRelationships.contains(relationshipDescription.getRelationshipObverse())) { continue; } diff --git a/src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jPersistentEntity.java b/src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jPersistentEntity.java index 8d1bd6ae6a..417da4ddef 100644 --- a/src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jPersistentEntity.java +++ b/src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jPersistentEntity.java @@ -420,7 +420,7 @@ private IdDescription computeIdDescription() { // Assigned ids if (generatedValueAnnotation == null) { - return IdDescription.forAssignedIds(propertyName); + return IdDescription.forAssignedIds(Constants.NAME_OF_TYPED_ROOT_NODE.apply(this), propertyName); } Class> idGeneratorClass = generatedValueAnnotation.generatorClass(); @@ -443,11 +443,11 @@ private IdDescription computeIdDescription() { "Internally generated ids can only be assigned to one of " + VALID_GENERATED_ID_TYPES); } - return IdDescription.forInternallyGeneratedIds(); + return IdDescription.forInternallyGeneratedIds(Constants.NAME_OF_TYPED_ROOT_NODE.apply(this)); } // Externally generated ids. - return IdDescription.forExternallyGeneratedIds(idGeneratorClass, idGeneratorRef, propertyName); + return IdDescription.forExternallyGeneratedIds(Constants.NAME_OF_TYPED_ROOT_NODE.apply(this), idGeneratorClass, idGeneratorRef, propertyName); } @Override diff --git a/src/main/java/org/springframework/data/neo4j/core/mapping/IdDescription.java b/src/main/java/org/springframework/data/neo4j/core/mapping/IdDescription.java index 3fecc44357..1cadc96173 100644 --- a/src/main/java/org/springframework/data/neo4j/core/mapping/IdDescription.java +++ b/src/main/java/org/springframework/data/neo4j/core/mapping/IdDescription.java @@ -22,6 +22,7 @@ import org.neo4j.cypherdsl.core.Expression; import org.neo4j.cypherdsl.core.Functions; import org.neo4j.cypherdsl.core.Node; +import org.neo4j.cypherdsl.core.SymbolicName; import org.springframework.data.neo4j.core.schema.GeneratedValue; import org.springframework.data.neo4j.core.schema.IdGenerator; import org.springframework.data.util.Lazy; @@ -54,45 +55,48 @@ public final class IdDescription { private final Lazy idExpression; - public static IdDescription forAssignedIds(String graphPropertyName) { + public static IdDescription forAssignedIds(SymbolicName symbolicName, String graphPropertyName) { Assert.notNull(graphPropertyName, "Graph property name is required."); - return new IdDescription(null, null, graphPropertyName); + return new IdDescription(symbolicName, null, null, graphPropertyName); } - public static IdDescription forInternallyGeneratedIds() { - return new IdDescription(GeneratedValue.InternalIdGenerator.class, null, null); + public static IdDescription forInternallyGeneratedIds(SymbolicName symbolicName) { + return new IdDescription(symbolicName, GeneratedValue.InternalIdGenerator.class, null, null); } - public static IdDescription forExternallyGeneratedIds(@Nullable Class> idGeneratorClass, + public static IdDescription forExternallyGeneratedIds(SymbolicName symbolicName, + @Nullable Class> idGeneratorClass, @Nullable String idGeneratorRef, String graphPropertyName) { Assert.notNull(graphPropertyName, "Graph property name is required."); try { Assert.hasText(idGeneratorRef, "Reference to an ID generator has precedence."); - return new IdDescription(null, idGeneratorRef, graphPropertyName); + return new IdDescription(symbolicName, null, idGeneratorRef, graphPropertyName); } catch (IllegalArgumentException e) { Assert.notNull(idGeneratorClass, "Class of id generator is required."); Assert.isTrue(idGeneratorClass != GeneratedValue.InternalIdGenerator.class, "Cannot use InternalIdGenerator for externally generated ids."); - return new IdDescription(idGeneratorClass, null, graphPropertyName); + return new IdDescription(symbolicName, idGeneratorClass, null, graphPropertyName); } } - private IdDescription(@Nullable Class> idGeneratorClass, @Nullable String idGeneratorRef, - @Nullable String graphPropertyName) { + private IdDescription(SymbolicName symbolicName, @Nullable Class> idGeneratorClass, + @Nullable String idGeneratorRef, @Nullable String graphPropertyName) { + this.idGeneratorClass = idGeneratorClass; this.idGeneratorRef = idGeneratorRef != null && idGeneratorRef.isEmpty() ? null : idGeneratorRef; this.graphPropertyName = graphPropertyName; + this.idExpression = Lazy.of(() -> { - final Node rootNode = Cypher.anyNode(Constants.NAME_OF_ROOT_NODE); + final Node rootNode = Cypher.anyNode(symbolicName); if (this.isInternallyGeneratedId()) { return Functions.id(rootNode); } else { return this.getOptionalGraphPropertyName() - .map(propertyName -> Cypher.property(Constants.NAME_OF_ROOT_NODE, propertyName)).get(); + .map(propertyName -> Cypher.property(symbolicName, propertyName)).get(); } }); } diff --git a/src/main/java/org/springframework/data/neo4j/repository/query/CypherAdapterUtils.java b/src/main/java/org/springframework/data/neo4j/repository/query/CypherAdapterUtils.java index f65e56ffcf..67783772f6 100644 --- a/src/main/java/org/springframework/data/neo4j/repository/query/CypherAdapterUtils.java +++ b/src/main/java/org/springframework/data/neo4j/repository/query/CypherAdapterUtils.java @@ -55,7 +55,7 @@ public static Function sortAdapterFor(NodeDescription n boolean propertyIsQualified = domainProperty.contains("."); SymbolicName root; if (!propertyIsQualified) { - root = Constants.NAME_OF_ROOT_NODE; + root = Constants.NAME_OF_TYPED_ROOT_NODE.apply(nodeDescription); } else { int indexOfSeparator = domainProperty.indexOf("."); root = Cypher.name(domainProperty.substring(0, indexOfSeparator)); diff --git a/src/main/java/org/springframework/data/neo4j/repository/query/CypherQueryCreator.java b/src/main/java/org/springframework/data/neo4j/repository/query/CypherQueryCreator.java index 7d3288427a..6b2c7b2107 100644 --- a/src/main/java/org/springframework/data/neo4j/repository/query/CypherQueryCreator.java +++ b/src/main/java/org/springframework/data/neo4j/repository/query/CypherQueryCreator.java @@ -278,7 +278,7 @@ private QueryFragments createQueryFragments(@Nullable Condition condition, Sort // all the ways we could query for Node startNode = Cypher.node(nodeDescription.getPrimaryLabel(), nodeDescription.getAdditionalLabels()) - .named(Constants.NAME_OF_ROOT_NODE); + .named(Constants.NAME_OF_TYPED_ROOT_NODE.apply(nodeDescription)); Condition conditionFragment = Optional.ofNullable(condition).orElseGet(Conditions::noCondition); List relationshipChain = new ArrayList<>(); @@ -302,7 +302,7 @@ private QueryFragments createQueryFragments(@Nullable Condition condition, Sort if (queryType == Neo4jQueryType.COUNT) { queryFragments.setReturnExpression(Functions.count(Cypher.asterisk()), true); } else if (queryType == Neo4jQueryType.EXISTS) { - queryFragments.setReturnExpression(Functions.count(Constants.NAME_OF_ROOT_NODE).gt(Cypher.literalOf(0)), true); + queryFragments.setReturnExpression(Functions.count(Constants.NAME_OF_TYPED_ROOT_NODE.apply(nodeDescription)).gt(Cypher.literalOf(0)), true); } else { queryFragments.setReturnBasedOn(nodeDescription, includedProperties, isDistinct); queryFragments.setOrderBy(Stream @@ -535,7 +535,7 @@ private Condition createRangeConditionForProperty(Expression property, Parameter private Property toCypherProperty(Neo4jPersistentProperty persistentProperty) { - return Cypher.property(Constants.NAME_OF_ROOT_NODE, persistentProperty.getPropertyName()); + return Cypher.property(Constants.NAME_OF_TYPED_ROOT_NODE.apply(nodeDescription), persistentProperty.getPropertyName()); } private Expression toCypherProperty(PersistentPropertyPath path, boolean addToLower) { @@ -546,8 +546,8 @@ private Expression toCypherProperty(PersistentPropertyPath rp.getPropertyPath().equals(path)).findFirst().get(); diff --git a/src/main/java/org/springframework/data/neo4j/repository/query/Predicate.java b/src/main/java/org/springframework/data/neo4j/repository/query/Predicate.java index fcc163c10b..171c607771 100644 --- a/src/main/java/org/springframework/data/neo4j/repository/query/Predicate.java +++ b/src/main/java/org/springframework/data/neo4j/repository/query/Predicate.java @@ -84,7 +84,7 @@ static Predicate create(Neo4jMappingContext mappingContext, Example examp if (!optionalValue.isPresent()) { if (!internalId && matcherAccessor.getNullHandler().equals(ExampleMatcher.NullHandler.INCLUDE)) { - predicate.add(mode, property(Constants.NAME_OF_ROOT_NODE, propertyName).isNull()); + predicate.add(mode, property(Constants.NAME_OF_TYPED_ROOT_NODE.apply(probeNodeDescription), propertyName).isNull()); } continue; } @@ -97,7 +97,7 @@ static Predicate create(Neo4jMappingContext mappingContext, Example examp predicate.add(mode, predicate.neo4jPersistentEntity.getIdExpression().isEqualTo(literalOf(optionalValue.get()))); } else { - Expression property = property(Constants.NAME_OF_ROOT_NODE, propertyName); + Expression property = property(Constants.NAME_OF_TYPED_ROOT_NODE.apply(probeNodeDescription), propertyName); Expression parameter = parameter(propertyName); Condition condition = property.isEqualTo(parameter); diff --git a/src/test/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jIsNewStrategyTest.java b/src/test/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jIsNewStrategyTest.java index 987dc920c9..82a02fb90c 100644 --- a/src/test/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jIsNewStrategyTest.java +++ b/src/test/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jIsNewStrategyTest.java @@ -49,7 +49,7 @@ void shouldDealWithNonPrimitives() { Object a = new Object(); Object b = new Object(); - IdDescription idDescription = IdDescription.forInternallyGeneratedIds(); + IdDescription idDescription = IdDescription.forInternallyGeneratedIds(Constants.NAME_OF_ROOT_NODE); doReturn(Long.class).when(idProperty).getType(); doReturn(idDescription).when(entityMetaData).getIdDescription(); doReturn(idProperty).when(entityMetaData).getRequiredIdProperty(); @@ -67,7 +67,7 @@ void shouldDealWithPrimitives() { Object b = new Object(); Object c = new Object(); - IdDescription idDescription = IdDescription.forInternallyGeneratedIds(); + IdDescription idDescription = IdDescription.forInternallyGeneratedIds(Constants.NAME_OF_ROOT_NODE); doReturn(long.class).when(idProperty).getType(); doReturn(idDescription).when(entityMetaData).getIdDescription(); doReturn(idProperty).when(entityMetaData).getRequiredIdProperty(); @@ -89,7 +89,7 @@ void shouldDealWithNonPrimitives() { Object a = new Object(); Object b = new Object(); - IdDescription idDescription = IdDescription.forExternallyGeneratedIds(DummyIdGenerator.class, null, "na"); + IdDescription idDescription = IdDescription.forExternallyGeneratedIds(Constants.NAME_OF_ROOT_NODE, DummyIdGenerator.class, null, "na"); doReturn(String.class).when(idProperty).getType(); doReturn(idDescription).when(entityMetaData).getIdDescription(); doReturn(idProperty).when(entityMetaData).getRequiredIdProperty(); @@ -104,7 +104,7 @@ void shouldDealWithNonPrimitives() { @Test void doesntNeedToDealWithPrimitives() { - IdDescription idDescription = IdDescription.forExternallyGeneratedIds(DummyIdGenerator.class, null, "na"); + IdDescription idDescription = IdDescription.forExternallyGeneratedIds(Constants.NAME_OF_ROOT_NODE, DummyIdGenerator.class, null, "na"); doReturn(long.class).when(idProperty).getType(); doReturn(idDescription).when(entityMetaData).getIdDescription(); doReturn(idProperty).when(entityMetaData).getRequiredIdProperty(); @@ -121,7 +121,7 @@ class Assigned { @Test void shouldAlwaysTreatEntitiesAsNewWithoutVersion() { Object a = new Object(); - IdDescription idDescription = IdDescription.forAssignedIds("na"); + IdDescription idDescription = IdDescription.forAssignedIds(Constants.NAME_OF_ROOT_NODE, "na"); doReturn(String.class).when(idProperty).getType(); doReturn(idDescription).when(entityMetaData).getIdDescription(); doReturn(idProperty).when(entityMetaData).getRequiredIdProperty(); @@ -134,7 +134,7 @@ void shouldAlwaysTreatEntitiesAsNewWithoutVersion() { void shouldDealWithVersion() { Object a = new Object(); Object b = new Object(); - IdDescription idDescription = IdDescription.forAssignedIds("na"); + IdDescription idDescription = IdDescription.forAssignedIds(Constants.NAME_OF_ROOT_NODE, "na"); doReturn(String.class).when(idProperty).getType(); doReturn(String.class).when(versionProperty).getType(); @@ -161,7 +161,7 @@ void shouldDealWithVersion() { void shouldDealWithPrimitiveVersion() { Object a = new Object(); Object b = new Object(); - IdDescription idDescription = IdDescription.forAssignedIds("na"); + IdDescription idDescription = IdDescription.forAssignedIds(Constants.NAME_OF_ROOT_NODE, "na"); doReturn(String.class).when(idProperty).getType(); doReturn(int.class).when(versionProperty).getType(); diff --git a/src/test/java/org/springframework/data/neo4j/core/mapping/IdDescriptionTest.java b/src/test/java/org/springframework/data/neo4j/core/mapping/IdDescriptionTest.java index 75e7d0cd62..afab638251 100644 --- a/src/test/java/org/springframework/data/neo4j/core/mapping/IdDescriptionTest.java +++ b/src/test/java/org/springframework/data/neo4j/core/mapping/IdDescriptionTest.java @@ -28,34 +28,34 @@ class IdDescriptionTest { @Test void isAssignedShouldWork() { - assertThat(IdDescription.forAssignedIds("foobar").isAssignedId()).isTrue(); - assertThat(IdDescription.forAssignedIds("foobar").isExternallyGeneratedId()).isFalse(); - assertThat(IdDescription.forAssignedIds("foobar").isInternallyGeneratedId()).isFalse(); + assertThat(IdDescription.forAssignedIds(Constants.NAME_OF_ROOT_NODE, "foobar").isAssignedId()).isTrue(); + assertThat(IdDescription.forAssignedIds(Constants.NAME_OF_ROOT_NODE, "foobar").isExternallyGeneratedId()).isFalse(); + assertThat(IdDescription.forAssignedIds(Constants.NAME_OF_ROOT_NODE, "foobar").isInternallyGeneratedId()).isFalse(); } @Test void idIsGeneratedInternallyShouldWork() { - assertThat(IdDescription.forInternallyGeneratedIds().isAssignedId()).isFalse(); - assertThat(IdDescription.forInternallyGeneratedIds().isExternallyGeneratedId()).isFalse(); - assertThat(IdDescription.forInternallyGeneratedIds().isInternallyGeneratedId()).isTrue(); + assertThat(IdDescription.forInternallyGeneratedIds(Constants.NAME_OF_ROOT_NODE).isAssignedId()).isFalse(); + assertThat(IdDescription.forInternallyGeneratedIds(Constants.NAME_OF_ROOT_NODE).isExternallyGeneratedId()).isFalse(); + assertThat(IdDescription.forInternallyGeneratedIds(Constants.NAME_OF_ROOT_NODE).isInternallyGeneratedId()).isTrue(); } @Test void idIsGeneratedExternally() { - assertThat(IdDescription.forExternallyGeneratedIds(DummyIdGenerator.class, null, "foobar").isAssignedId()) + assertThat(IdDescription.forExternallyGeneratedIds(Constants.NAME_OF_ROOT_NODE, DummyIdGenerator.class, null, "foobar").isAssignedId()) .isFalse(); assertThat( - IdDescription.forExternallyGeneratedIds(DummyIdGenerator.class, null, "foobar").isExternallyGeneratedId()) + IdDescription.forExternallyGeneratedIds(Constants.NAME_OF_ROOT_NODE, DummyIdGenerator.class, null, "foobar").isExternallyGeneratedId()) .isTrue(); assertThat( - IdDescription.forExternallyGeneratedIds(DummyIdGenerator.class, null, "foobar").isInternallyGeneratedId()) + IdDescription.forExternallyGeneratedIds(Constants.NAME_OF_ROOT_NODE, DummyIdGenerator.class, null, "foobar").isInternallyGeneratedId()) .isFalse(); - assertThat(IdDescription.forExternallyGeneratedIds(null, "someId", "foobar").isAssignedId()).isFalse(); - assertThat(IdDescription.forExternallyGeneratedIds(null, "someId", "foobar").isExternallyGeneratedId()).isTrue(); - assertThat(IdDescription.forExternallyGeneratedIds(null, "someId", "foobar").isInternallyGeneratedId()).isFalse(); + assertThat(IdDescription.forExternallyGeneratedIds(Constants.NAME_OF_ROOT_NODE, null, "someId", "foobar").isAssignedId()).isFalse(); + assertThat(IdDescription.forExternallyGeneratedIds(Constants.NAME_OF_ROOT_NODE, null, "someId", "foobar").isExternallyGeneratedId()).isTrue(); + assertThat(IdDescription.forExternallyGeneratedIds(Constants.NAME_OF_ROOT_NODE, null, "someId", "foobar").isInternallyGeneratedId()).isFalse(); } private static class DummyIdGenerator implements IdGenerator { diff --git a/src/test/java/org/springframework/data/neo4j/core/mapping/callback/IdPopulatorTest.java b/src/test/java/org/springframework/data/neo4j/core/mapping/callback/IdPopulatorTest.java index c23793d817..9156790fb8 100644 --- a/src/test/java/org/springframework/data/neo4j/core/mapping/callback/IdPopulatorTest.java +++ b/src/test/java/org/springframework/data/neo4j/core/mapping/callback/IdPopulatorTest.java @@ -26,6 +26,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.neo4j.core.mapping.Constants; import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext; import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity; import org.springframework.data.neo4j.core.schema.GeneratedValue; @@ -57,7 +58,7 @@ void shouldRejectNullEntity() { @Test void shouldIgnoreInternalIdGenerator() { - IdDescription toBeReturned = IdDescription.forInternallyGeneratedIds(); + IdDescription toBeReturned = IdDescription.forInternallyGeneratedIds(Constants.NAME_OF_ROOT_NODE); doReturn(toBeReturned).when(nodeDescription).getIdDescription(); doReturn(nodeDescription).when(neo4jMappingContext).getRequiredPersistentEntity(Sample.class); diff --git a/src/test/java/org/springframework/data/neo4j/integration/conversion_imperative/TypeConversionIT.java b/src/test/java/org/springframework/data/neo4j/integration/conversion_imperative/TypeConversionIT.java index a4a2cb1994..3b057e421e 100644 --- a/src/test/java/org/springframework/data/neo4j/integration/conversion_imperative/TypeConversionIT.java +++ b/src/test/java/org/springframework/data/neo4j/integration/conversion_imperative/TypeConversionIT.java @@ -83,8 +83,6 @@ @Neo4jIntegrationTest class TypeConversionIT extends Neo4jConversionsITBase { - private final Driver driver; - @Autowired CypherTypesRepository cypherTypesRepository; private final AdditionalTypesRepository additionalTypesRepository; @@ -96,11 +94,10 @@ class TypeConversionIT extends Neo4jConversionsITBase { private final DefaultConversionService defaultConversionService; - @Autowired TypeConversionIT(Driver driver, CypherTypesRepository cypherTypesRepository, + @Autowired TypeConversionIT(CypherTypesRepository cypherTypesRepository, AdditionalTypesRepository additionalTypesRepository, SpatialTypesRepository spatialTypesRepository, CustomTypesRepository customTypesRepository, Neo4jConversions neo4jConversions, BookmarkCapture bookmarkCapture) { - this.driver = driver; this.cypherTypesRepository = cypherTypesRepository; this.additionalTypesRepository = additionalTypesRepository; this.spatialTypesRepository = spatialTypesRepository; @@ -116,7 +113,7 @@ void thereShallBeNoDefaultValuesForNonExistingAttributes(@Autowired NonExistingP assertThatExceptionOfType(MappingException.class) .isThrownBy(() -> repository.findById(ID_OF_NON_EXISTING_PRIMITIVES_NODE)) .withMessageMatching( - "Error mapping Record<\\{n: \\{__internalNeo4jId__: \\d+, id: NULL, someBoolean: NULL, __nodeLabels__: \\[\"NonExistingPrimitives\"\\]\\}\\}>") + "Error mapping Record<\\{thingWithNonExistingPrimitives: \\{__internalNeo4jId__: \\d+, id: NULL, someBoolean: NULL, __nodeLabels__: \\[\"NonExistingPrimitives\"\\]\\}\\}>") .withStackTraceContaining("unboxBoolean") .withRootCauseInstanceOf(NullPointerException.class); } diff --git a/src/test/java/org/springframework/data/neo4j/integration/imperative/CypherdslConditionExecutorIT.java b/src/test/java/org/springframework/data/neo4j/integration/imperative/CypherdslConditionExecutorIT.java index 8a9035d4f5..7a4cd9d3a8 100644 --- a/src/test/java/org/springframework/data/neo4j/integration/imperative/CypherdslConditionExecutorIT.java +++ b/src/test/java/org/springframework/data/neo4j/integration/imperative/CypherdslConditionExecutorIT.java @@ -33,7 +33,6 @@ import org.springframework.data.domain.Sort; import org.springframework.data.neo4j.config.AbstractNeo4jConfig; import org.springframework.data.neo4j.core.DatabaseSelectionProvider; -import org.springframework.data.neo4j.core.mapping.Constants; import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager; import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager; import org.springframework.data.neo4j.integration.shared.common.Person; @@ -73,7 +72,7 @@ class CypherdslConditionExecutorIT { //CHECKSTYLE:OFF // tag::sdn-mixins.dynamic-conditions.usage[] - Node person = Cypher.node("Person").named(Constants.NAME_OF_ROOT_NODE); // <.> + Node person = Cypher.node("Person").named("person"); // <.> Property firstName = person.property("firstName"); // <.> Property lastName = person.property("lastName"); // end::sdn-mixins.dynamic-conditions.usage[] diff --git a/src/test/java/org/springframework/data/neo4j/integration/imperative/QuerydslNeo4jPredicateExecutorIT.java b/src/test/java/org/springframework/data/neo4j/integration/imperative/QuerydslNeo4jPredicateExecutorIT.java index 8f37c13613..30876625c8 100644 --- a/src/test/java/org/springframework/data/neo4j/integration/imperative/QuerydslNeo4jPredicateExecutorIT.java +++ b/src/test/java/org/springframework/data/neo4j/integration/imperative/QuerydslNeo4jPredicateExecutorIT.java @@ -61,7 +61,7 @@ class QuerydslNeo4jPredicateExecutorIT { private final Path lastName; QuerydslNeo4jPredicateExecutorIT() { - this.person = Expressions.path(Person.class, "n"); + this.person = Expressions.path(Person.class, "person"); this.firstName = Expressions.path(String.class, person, "firstName"); this.lastName = Expressions.path(String.class, person, "lastName"); } diff --git a/src/test/java/org/springframework/data/neo4j/integration/movies/imperative/AdvancedMappingIT.java b/src/test/java/org/springframework/data/neo4j/integration/movies/imperative/AdvancedMappingIT.java index a79b0cabb6..c1a21f5684 100644 --- a/src/test/java/org/springframework/data/neo4j/integration/movies/imperative/AdvancedMappingIT.java +++ b/src/test/java/org/springframework/data/neo4j/integration/movies/imperative/AdvancedMappingIT.java @@ -237,7 +237,7 @@ void cyclicRelationshipsAreExcludedFromProjectionsWithProjections( .containsExactlyInAnyOrder("The Oracle", "Morpheus", "Trinity", "Agent Smith", "Emil", "Neo"); assertThat(logbackCapture.getFormattedMessages()).anyMatch(message -> - message.contains("MATCH (n:`Movie`) WHERE n.title = $0 RETURN n{.title")); + message.contains("MATCH (movie:`Movie`) WHERE movie.title = $0 RETURN movie{.title")); } finally { logbackCapture.resetLogLevel(); }