@@ -144,7 +144,7 @@ private <R> MapAccessor determineQueryRoot(MapAccessor mapAccessor, @Nullable Ne
144144 Node node = value .asNode ();
145145 if (primaryLabels .stream ().anyMatch (node ::hasLabel )) { // it has a matching label
146146 // We haven't seen this node yet, so we take it
147- if (! knownObjects .containsNode ( node )) {
147+ if (knownObjects .getObject ( "N" + IdentitySupport . getInternalId ( node )) == null ) {
148148 matchingNodes .add (node );
149149 } else {
150150 seenMatchingNodes .add (node );
@@ -264,25 +264,26 @@ private static MapAccessor mergeRootNodeWithRecord(Node node, MapAccessor record
264264 private <ET > ET map (MapAccessor queryResult , MapAccessor allValues , Neo4jPersistentEntity <ET > nodeDescription ) {
265265 Collection <Relationship > relationshipsFromResult = extractRelationships (allValues );
266266 Collection <Node > nodesFromResult = extractNodes (allValues );
267- return map (queryResult , nodeDescription , null , relationshipsFromResult , nodesFromResult );
267+ return map (queryResult , nodeDescription , null , null , relationshipsFromResult , nodesFromResult );
268268 }
269269
270270 private <ET > ET map (MapAccessor queryResult , Neo4jPersistentEntity <ET > nodeDescription ,
271- @ Nullable Object lastMappedEntity , Collection <Relationship > relationshipsFromResult , Collection <Node > nodesFromResult ) {
271+ @ Nullable Object lastMappedEntity , @ Nullable RelationshipDescription relationshipDescription , Collection <Relationship > relationshipsFromResult , Collection <Node > nodesFromResult ) {
272272
273273 // prior to SDN 7 local `getInternalId` didn't check relationships, so in that case, they have never been a known
274274 // object. The centralized methods checks those too now. The condition is to recreate the old behaviour without
275275 // losing the central access. The behaviour of knowObjects should take different sources of ids into account,
276276 // as relationships and nodes might have overlapping values
277- Long internalId = queryResult instanceof Relationship ? null : IdentitySupport .getInternalId (queryResult );
277+ String direction = relationshipDescription != null ? relationshipDescription .getDirection ().name () : null ;
278+ String internalId = IdentitySupport .getInternalId (queryResult , direction );
278279
279280 Supplier <ET > mappedObjectSupplier = () -> {
280281 if (knownObjects .isInCreation (internalId )) {
281282 throw new MappingException (
282283 String .format (
283284 "The node with id %s has a logical cyclic mapping dependency; " +
284285 "its creation caused the creation of another node that has a reference to this" ,
285- internalId )
286+ internalId . substring ( 1 ) )
286287 );
287288 }
288289 knownObjects .setInCreation (internalId );
@@ -330,7 +331,7 @@ private <ET> ET map(MapAccessor queryResult, Neo4jPersistentEntity<ET> nodeDescr
330331 }
331332
332333
333- private <ET > void populateProperties (MapAccessor queryResult , Neo4jPersistentEntity <ET > nodeDescription , Long internalId ,
334+ private <ET > void populateProperties (MapAccessor queryResult , Neo4jPersistentEntity <ET > nodeDescription , String internalId ,
334335 ET mappedObject , @ Nullable Object lastMappedEntity ,
335336 Collection <Relationship > relationshipsFromResult , Collection <Node > nodesFromResult , boolean objectAlreadyMapped ) {
336337
@@ -533,6 +534,23 @@ private AssociationHandler<Neo4jPersistentProperty> populateFrom(MapAccessor que
533534 boolean populatedScalarValue = !persistentProperty .isCollectionLike () && !persistentProperty .isMap ()
534535 && propertyValueNotNull ;
535536
537+ if (populatedCollection ) {
538+ createInstanceOfRelationships (persistentProperty , queryResult , (RelationshipDescription ) association , baseDescription , relationshipsFromResult , nodesFromResult , false )
539+ .ifPresent (value -> {
540+ Collection <?> providedCollection = (Collection <?>) value ;
541+ Collection <?> existingValue = (Collection <?>) propertyValue ;
542+ Collection <Object > newValue = CollectionFactory .createCollection (existingValue .getClass (), providedCollection .size () + existingValue .size ());
543+
544+ RelationshipDescription relationshipDescription = (RelationshipDescription ) association ;
545+ Map <Object , Object > mergedValues = new HashMap <>();
546+ mergeCollections (relationshipDescription , existingValue , mergedValues );
547+ mergeCollections (relationshipDescription , providedCollection , mergedValues );
548+
549+ newValue .addAll (mergedValues .values ());
550+ propertyAccessor .setProperty (persistentProperty , newValue );
551+ });
552+ }
553+
536554 boolean propertyAlreadyPopulated = populatedCollection || populatedMap || populatedScalarValue ;
537555
538556 // avoid unnecessary re-assignment of values
@@ -545,9 +563,33 @@ private AssociationHandler<Neo4jPersistentProperty> populateFrom(MapAccessor que
545563 };
546564 }
547565
566+ private void mergeCollections (RelationshipDescription relationshipDescription , Collection <?> values , Map <Object , Object > mergedValues ) {
567+ for (Object existingValueInCollection : values ) {
568+ if (relationshipDescription .hasRelationshipProperties ()) {
569+ Object existingIdPropertyValue = ((Neo4jPersistentEntity <?>) relationshipDescription .getRelationshipPropertiesEntity ())
570+ .getPropertyAccessor (existingValueInCollection )
571+ .getProperty (((Neo4jPersistentEntity <?>) relationshipDescription .getRelationshipPropertiesEntity ()).getIdProperty ());
572+
573+ mergedValues .put (existingIdPropertyValue , existingValueInCollection );
574+ } else if (!relationshipDescription .isDynamic ()) { // should not happen because this is all inside populatedCollection (but better safe than sorry)
575+ Object existingIdPropertyValue = ((Neo4jPersistentEntity <?>) relationshipDescription .getTarget ())
576+ .getPropertyAccessor (existingValueInCollection )
577+ .getProperty (((Neo4jPersistentEntity <?>) relationshipDescription .getTarget ()).getIdProperty ());
578+
579+ mergedValues .put (existingIdPropertyValue , existingValueInCollection );
580+ }
581+ }
582+ }
583+
584+ private Optional <Object > createInstanceOfRelationships (Neo4jPersistentProperty persistentProperty , MapAccessor values ,
585+ RelationshipDescription relationshipDescription , NodeDescription <?> baseDescription , Collection <Relationship > relationshipsFromResult ,
586+ Collection <Node > nodesFromResult ) {
587+ return createInstanceOfRelationships (persistentProperty , values , relationshipDescription , baseDescription , relationshipsFromResult , nodesFromResult , true );
588+ }
589+
548590 private Optional <Object > createInstanceOfRelationships (Neo4jPersistentProperty persistentProperty , MapAccessor values ,
549591 RelationshipDescription relationshipDescription , NodeDescription <?> baseDescription , Collection <Relationship > relationshipsFromResult ,
550- Collection <Node > nodesFromResult ) {
592+ Collection <Node > nodesFromResult , boolean fetchMore ) {
551593
552594 String typeOfRelationship = relationshipDescription .getType ();
553595 String targetLabel = relationshipDescription .getTarget ().getPrimaryLabel ();
@@ -588,6 +630,7 @@ private Optional<Object> createInstanceOfRelationships(Neo4jPersistentProperty p
588630
589631 if (Values .NULL .equals (list )) {
590632 Long sourceNodeId = IdentitySupport .getInternalId (values );
633+ //Long sourceNodeId = getInternalIdAsLong(values);
591634
592635 Function <Relationship , Long > sourceIdSelector = relationshipDescription .isIncoming () ? IdentitySupport ::getEndId : IdentitySupport ::getStartId ;
593636 Function <Relationship , Long > targetIdSelector = relationshipDescription .isIncoming () ? IdentitySupport ::getStartId : IdentitySupport ::getEndId ;
@@ -614,22 +657,37 @@ private Optional<Object> createInstanceOfRelationships(Neo4jPersistentProperty p
614657 // If this relationship got processed twice (OUTGOING, INCOMING), it is never needed again
615658 // and therefor should not be in the list.
616659 // Otherwise, for highly linked data it could potentially cause a StackOverflowError.
617- if (knownObjects .hasProcessedRelationshipCompletely (IdentitySupport .getInternalId (possibleRelationship ))) {
660+ String direction = relationshipDescription .getDirection ().name ();
661+ if (knownObjects .hasProcessedRelationshipCompletely ("R" + direction + IdentitySupport .getInternalId (possibleRelationship ))) {
618662 relationshipsFromResult .remove (possibleRelationship );
619663 }
620664 // If the target is the same(equal) node, get the related object from the cache.
621665 // Avoiding the call to the map method also breaks an endless cycle of trying to finish
622666 // the property population of _this_ object.
623667 // The initial population will happen at the end of this mapping. This is sufficient because
624668 // it only affects properties not changing the instance of the object.
625- Object mappedObject = sourceNodeId != null && sourceNodeId .equals (targetNodeId )
626- ? knownObjects .getObject (sourceNodeId )
627- : map (possibleValueNode , concreteTargetNodeDescription , null , relationshipsFromResult , nodesFromResult );
628- if (relationshipDescription .hasRelationshipProperties ()) {
669+ Object mappedObject ;
670+ if (fetchMore ) {
671+ mappedObject = sourceNodeId != null && sourceNodeId .equals (targetNodeId )
672+ ? knownObjects .getObject ("N" + sourceNodeId )
673+ : map (possibleValueNode , concreteTargetNodeDescription , null , null , relationshipsFromResult , nodesFromResult );
674+ } else {
675+ Object objectFromStore = knownObjects .getObject ("N" + targetNodeId );
676+ mappedObject = objectFromStore != null
677+ ? objectFromStore
678+ : map (possibleValueNode , concreteTargetNodeDescription , null , null , relationshipsFromResult , nodesFromResult );
679+ }
629680
630- Object relationshipProperties = map (possibleRelationship ,
631- (Neo4jPersistentEntity <?>) relationshipDescription .getRelationshipPropertiesEntity (),
632- mappedObject , relationshipsFromResult , nodesFromResult );
681+ if (relationshipDescription .hasRelationshipProperties ()) {
682+ Object relationshipProperties ;
683+ if (fetchMore ) {
684+ relationshipProperties = map (possibleRelationship , (Neo4jPersistentEntity <?>) relationshipDescription .getRelationshipPropertiesEntity (), mappedObject , relationshipDescription , relationshipsFromResult , nodesFromResult );
685+ } else {
686+ Object objectFromStore = knownObjects .getObject (IdentitySupport .getInternalId (possibleRelationship , relationshipDescription .getDirection ().name ()));
687+ relationshipProperties = objectFromStore != null
688+ ? objectFromStore
689+ : map (possibleRelationship , (Neo4jPersistentEntity <?>) relationshipDescription .getRelationshipPropertiesEntity (), mappedObject , relationshipDescription , relationshipsFromResult , nodesFromResult );
690+ }
633691 relationshipsAndProperties .add (relationshipProperties );
634692 mappedObjectHandler .accept (possibleRelationship .type (), relationshipProperties );
635693 } else {
@@ -646,7 +704,15 @@ private Optional<Object> createInstanceOfRelationships(Neo4jPersistentProperty p
646704 Neo4jPersistentEntity <?> concreteTargetNodeDescription =
647705 getMostConcreteTargetNodeDescription (genericTargetNodeDescription , relatedEntity );
648706
649- Object valueEntry = map (relatedEntity , concreteTargetNodeDescription , null , relationshipsFromResult , nodesFromResult );
707+ Object valueEntry ;
708+ if (fetchMore ) {
709+ valueEntry = map (relatedEntity , concreteTargetNodeDescription , null , null , relationshipsFromResult , nodesFromResult );
710+ } else {
711+ Object objectFromStore = knownObjects .getObject (IdentitySupport .getInternalId (relatedEntity , null ));
712+ valueEntry = objectFromStore != null
713+ ? objectFromStore
714+ : map (relatedEntity , concreteTargetNodeDescription , null , null , relationshipsFromResult , nodesFromResult );
715+ }
650716
651717 if (relationshipDescription .hasRelationshipProperties ()) {
652718 String sourceLabel = relationshipDescription .getSource ().getMostAbstractParentLabel (baseDescription );
@@ -655,9 +721,16 @@ private Optional<Object> createInstanceOfRelationships(Neo4jPersistentProperty p
655721 Relationship relatedEntityRelationship = relatedEntity .get (relationshipSymbolicName )
656722 .asRelationship ();
657723
658- Object relationshipProperties = map (relatedEntityRelationship ,
659- (Neo4jPersistentEntity <?>) relationshipDescription .getRelationshipPropertiesEntity (),
660- valueEntry , relationshipsFromResult , nodesFromResult );
724+ Object relationshipProperties ;
725+ if (fetchMore ) {
726+ relationshipProperties = map (relatedEntityRelationship , (Neo4jPersistentEntity <?>) relationshipDescription .getRelationshipPropertiesEntity (), valueEntry , relationshipDescription , relationshipsFromResult , nodesFromResult );
727+ } else {
728+ Object objectFromStore = knownObjects .getObject (IdentitySupport .getInternalId (relatedEntityRelationship , relationshipDescription .getDirection ().name ()));
729+ relationshipProperties = objectFromStore != null
730+ ? objectFromStore
731+ : map (relatedEntityRelationship , (Neo4jPersistentEntity <?>) relationshipDescription .getRelationshipPropertiesEntity (), valueEntry , relationshipDescription , relationshipsFromResult , nodesFromResult );
732+ }
733+
661734 relationshipsAndProperties .add (relationshipProperties );
662735 mappedObjectHandler .accept (relatedEntity .get (RelationshipDescription .NAME_OF_RELATIONSHIP_TYPE ).asString (), relationshipProperties );
663736 } else {
@@ -773,14 +846,14 @@ static class KnownObjects {
773846 private final Lock read = lock .readLock ();
774847 private final Lock write = lock .writeLock ();
775848
776- private final Map <Long , Object > internalIdStore = new HashMap <>();
777- private final Map <Long , Boolean > internalCurrentRecord = new HashMap <>();
778- private final Set <Long > previousRecords = new HashSet <>();
779- private final Set <Long > idsInCreation = new HashSet <>();
849+ private final Map <String , Object > internalIdStore = new HashMap <>();
850+ private final Map <String , Boolean > internalCurrentRecord = new HashMap <>();
851+ private final Set <String > previousRecords = new HashSet <>();
852+ private final Set <String > idsInCreation = new HashSet <>();
780853
781- private final Map <Long , Integer > processedRelationships = new HashMap <>();
854+ private final Map <String , Integer > processedRelationships = new HashMap <>();
782855
783- private void storeObject (@ Nullable Long internalId , Object object ) {
856+ private void storeObject (@ Nullable String internalId , Object object ) {
784857 if (internalId == null ) {
785858 return ;
786859 }
@@ -794,7 +867,7 @@ private void storeObject(@Nullable Long internalId, Object object) {
794867 }
795868 }
796869
797- private void setInCreation (@ Nullable Long internalId ) {
870+ private void setInCreation (@ Nullable String internalId ) {
798871 if (internalId == null ) {
799872 return ;
800873 }
@@ -806,7 +879,7 @@ private void setInCreation(@Nullable Long internalId) {
806879 }
807880 }
808881
809- private boolean isInCreation (@ Nullable Long internalId ) {
882+ private boolean isInCreation (@ Nullable String internalId ) {
810883 if (internalId == null ) {
811884 return false ;
812885 }
@@ -829,8 +902,7 @@ private boolean containsNode(Node node) {
829902 }
830903
831904 @ Nullable
832- private Object getObject (@ Nullable Long internalId ) {
833-
905+ private Object getObject (@ Nullable String internalId ) {
834906 if (internalId == null ) {
835907 return null ;
836908 }
@@ -842,7 +914,7 @@ private Object getObject(@Nullable Long internalId) {
842914 }
843915 }
844916
845- private void removeFromInCreation (@ Nullable Long internalId ) {
917+ private void removeFromInCreation (@ Nullable String internalId ) {
846918 if (internalId == null ) {
847919 return ;
848920 }
@@ -854,7 +926,7 @@ private void removeFromInCreation(@Nullable Long internalId) {
854926 }
855927 }
856928
857- private boolean alreadyMappedInPreviousRecord (@ Nullable Long internalId ) {
929+ private boolean alreadyMappedInPreviousRecord (@ Nullable String internalId ) {
858930 if (internalId == null ) {
859931 return false ;
860932 }
@@ -874,7 +946,7 @@ private boolean alreadyMappedInPreviousRecord(@Nullable Long internalId) {
874946 * It increases the process count of relationships (mapped by their ids)
875947 * AND checks if it was already processed twice (INCOMING/OUTGOING).
876948 */
877- private boolean hasProcessedRelationshipCompletely (Long relationshipId ) {
949+ private boolean hasProcessedRelationshipCompletely (String relationshipId ) {
878950 try {
879951 write .lock ();
880952
0 commit comments