From 74eea3648bbdeb335632b06cfd8e36d2309c8bd6 Mon Sep 17 00:00:00 2001 From: tschoen Date: Wed, 22 Apr 2020 11:05:49 -0400 Subject: [PATCH 01/15] test --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 3679c2f..37bd94d 100644 --- a/README.md +++ b/README.md @@ -52,3 +52,5 @@ We would appreciate your feedback about our Versioner Core, how to improve and f ## Buy us a coffee :coffee: This project is developed during our free time, and our free time is mostly during evening/night! So coffee is really helpful during our sessions :sweat_smile:. If you want to help us with that, you can buy us some :coffee: thanks to [PayPal](https://www.paypal.me/mfalcier/2)! + +test From 49dc5edc3e69c2bd5bed771e60ced1a180dc7fc3 Mon Sep 17 00:00:00 2001 From: tschoen Date: Wed, 22 Apr 2020 12:56:06 -0400 Subject: [PATCH 02/15] :tada: :bug: fix for https://github.com/h-omer/neo4j-versioner-core/issues/8 --- README.md | 4 +--- .../versioner/core/procedure/RelationshipProcedure.java | 7 ++++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 37bd94d..bd23940 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,4 @@ We would appreciate your feedback about our Versioner Core, how to improve and f ## Buy us a coffee :coffee: -This project is developed during our free time, and our free time is mostly during evening/night! So coffee is really helpful during our sessions :sweat_smile:. If you want to help us with that, you can buy us some :coffee: thanks to [PayPal](https://www.paypal.me/mfalcier/2)! - -test +This project is developed during our free time, and our free time is mostly during evening/night! So coffee is really helpful during our sessions :sweat_smile:. If you want to help us with that, you can buy us some :coffee: thanks to [PayPal](https://www.paypal.me/mfalcier/2)! \ No newline at end of file diff --git a/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java b/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java index e818066..6c28b64 100644 --- a/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java +++ b/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java @@ -70,8 +70,13 @@ public Stream relationshipDelete( Update updateProcedure = new UpdateBuilder().withLog(log).withDb(db).build().orElseThrow(() -> new VersionerCoreException("Unable to initialize update procedure")); if (sourceCurrentState.isPresent() && destinationRNode.isPresent()) { + final long destId = destinationRNode.get().getId(); updateProcedure.update(entitySource, sourceCurrentState.get().getAllProperties(), "", null); - getCurrentRelationship(entitySource).ifPresent(rel -> rel.getEndNode().getRelationships(RelationshipType.withName(type), Direction.OUTGOING).forEach(Relationship::delete)); + getCurrentRelationship(entitySource).ifPresent(rel -> rel.getEndNode().getRelationships(RelationshipType.withName(type), Direction.OUTGOING).forEach(rel2 -> { + if (rel2.getEndNode().getId() == destId) { + rel2.delete(); + } + })); return Stream.of(new BooleanOutput(Boolean.TRUE)); } else { return Stream.of(new BooleanOutput(Boolean.FALSE)); From dfb1c814199fd9cffeeca75b3677090b71ef523c Mon Sep 17 00:00:00 2001 From: tschoen Date: Thu, 23 Apr 2020 18:44:00 -0400 Subject: [PATCH 03/15] :bug: :sparkles: :zap: Allow creation of multiple relationships, useful for initial loading of large data sets, bug fixes --- build.gradle | 2 +- docs/index.md | 1 + .../org/homer/versioner/core/Utility.java | 11 ++++---- .../core/builders/CoreProcedureBuilder.java | 10 +++---- .../core/procedure/RelationshipProcedure.java | 26 ++++++++++++++++++- .../versioner/core/procedure/Update.java | 2 +- 6 files changed, 38 insertions(+), 14 deletions(-) diff --git a/build.gradle b/build.gradle index 921ccfc..ac759b0 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ apply plugin: 'java' group = 'org.homer' -version = '2.0.0' +version = '2.0.1' description = """Neo4j Procedures for Graph Versioning""" diff --git a/docs/index.md b/docs/index.md index b1a7061..d02a778 100644 --- a/docs/index.md +++ b/docs/index.md @@ -116,6 +116,7 @@ name | parameters | return values | description [graph.versioner.diff](#diff) | **stateFrom**, **stateTo** | operation, label, oldValue, newValue | Get a list of differences that must be applied to stateFrom in order to convert it into stateTo. [graph.versioner.diff.from.previous](#diff-from-previous) | **state** | operation, label, oldValue, newValue | Get a list of differences that must be applied to the previous status of the given one in order to become the given state. [graph.versioner.diff.from.current](#diff-from-current) | **state** | operation, label, oldValue, newValue | Get a list of differences that must be applied to the given state in order to become the current entity state. +[graph.versioner.relationships.create](#relationships-create) | **entitySource**, **List**, relationshipType, *{key:value,...}*, *date* | **relationship** | Creates a new state for the source entity connected to the R nodes of the destinations with a relationship of the given type. [graph.versioner.relationship.create](#relationship-create) | **entitySource**, **entityDestination**, relationshipType, *{key:value,...}*, *date* | **relationship** | Creates a new state for the source entity connected to the R node of the destination with a relationship of the given type. [graph.versioner.relationship.delete](#relationship-delete) | **entitySource**, **entityDestination**, relationshipType, *date* | **result** | Creates a new state for the source entity without a custom relationship of the given type. diff --git a/src/main/java/org/homer/versioner/core/Utility.java b/src/main/java/org/homer/versioner/core/Utility.java index 077633b..a5ba84e 100644 --- a/src/main/java/org/homer/versioner/core/Utility.java +++ b/src/main/java/org/homer/versioner/core/Utility.java @@ -143,10 +143,10 @@ public static void addCurrentState(Node state, Node entity, LocalDateTime instan * @param state a {@link Node} representing the State * @return {@link Boolean} result */ - public static Boolean checkRelationship(Node entity, Node state) { + public static void checkRelationship(Node entity, Node state) throws VersionerCoreException{ Spliterator stateRelIterator = state.getRelationships(RelationshipType.withName(Utility.HAS_STATE_TYPE), Direction.INCOMING).spliterator(); - Boolean check = StreamSupport.stream(stateRelIterator, false).map(hasStateRel -> { + StreamSupport.stream(stateRelIterator, false).map(hasStateRel -> { Node maybeEntity = hasStateRel.getStartNode(); if (maybeEntity.getId() != entity.getId()) { throw new VersionerCoreException("Can't patch the given entity, because the given State is owned by another entity."); @@ -156,7 +156,6 @@ public static Boolean checkRelationship(Node entity, Node state) { throw new VersionerCoreException("Can't find any entity node relate to the given State."); }); - return check; } /** @@ -195,9 +194,9 @@ public static LocalDateTime defaultToNow(LocalDateTime date) { * @param node the {@link Node} to check * @throws VersionerCoreException */ - public static void isEntityOrThrowException(Node node) throws VersionerCoreException { + public static void isEntityOrThrowException(Node node) { - streamOfIterable(node.getRelationships(RelationshipType.withName("CURRENT"), Direction.OUTGOING)).findAny() + streamOfIterable(node.getRelationships(RelationshipType.withName(CURRENT_TYPE), Direction.OUTGOING)).findAny() .map(ignored -> streamOfIterable(node.getRelationships(RelationshipType.withName("R"), Direction.INCOMING)).findAny()) .orElseThrow(() -> new VersionerCoreException("The given node is not a Versioner Core Entity")); } @@ -222,7 +221,7 @@ public static LocalDateTime convertEpochToLocalDateTime(Long epochDateTime) { return Instant.ofEpochMilli(epochDateTime).atZone(ZoneId.systemDefault()).toLocalDateTime(); } - public static Boolean isSystemType(String type) { + public static boolean isSystemType(String type) { return SYSTEM_RELS.contains(type); } } diff --git a/src/main/java/org/homer/versioner/core/builders/CoreProcedureBuilder.java b/src/main/java/org/homer/versioner/core/builders/CoreProcedureBuilder.java index 7c23207..b185d94 100644 --- a/src/main/java/org/homer/versioner/core/builders/CoreProcedureBuilder.java +++ b/src/main/java/org/homer/versioner/core/builders/CoreProcedureBuilder.java @@ -4,6 +4,7 @@ import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.logging.Log; +import java.lang.reflect.InvocationTargetException; import java.util.Optional; /** @@ -13,7 +14,7 @@ */ public abstract class CoreProcedureBuilder { - private Class clazz; + private final Class clazz; private GraphDatabaseService db; private Log log; @@ -62,13 +63,12 @@ public CoreProcedureBuilder withLog(Log log) { * @return instance */ Optional instantiate() { - T instance; + T instance = null; try { - instance = clazz.newInstance(); + instance = clazz.getDeclaredConstructor().newInstance(); instance.db = db; instance.log = log; - } catch (InstantiationException | IllegalAccessException e) { - instance = null; + } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e ) { log.error(e.getMessage()); } return Optional.ofNullable(instance); diff --git a/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java b/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java index 6c28b64..13388b8 100644 --- a/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java +++ b/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java @@ -17,6 +17,7 @@ import java.time.LocalDateTime; import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Stream; @@ -28,6 +29,29 @@ */ public class RelationshipProcedure extends CoreProcedure { + @Procedure(value = "graph.versioner.relationships.create", mode = Mode.WRITE) + @Description("graph.versioner.relationships.create(entityA, entitiesB, type, relProps, date) - Create multiple relationships from entitySource to each of the entityDestinations with the given type and/or properties for the specified date.") + public Stream relationshipsCreate( + @Name("entitySource") Node entitySource, + @Name("entityDestinations") List entityDestinations, + @Name(value = "type") String type, + @Name(value = "relProps", defaultValue = "{}") Map relProps, + @Name(value = "date", defaultValue = "null") LocalDateTime date) { + + Optional sourceCurrentState = createNewSourceState(entitySource, defaultToNow(date)); + isEntityOrThrowException(entitySource); + return sourceCurrentState.map(node -> entityDestinations.stream().map((Node entityDestination) -> { + isEntityOrThrowException(entityDestination); + Optional destinationRNode = getRNode(entityDestination); + if (destinationRNode.isPresent()) { + return streamOfRelationships(createRelationship(node, destinationRNode.get(), type, relProps)); + } else { + final Stream empty = Stream.empty(); + return empty; + } + }).reduce(Stream::concat).orElseGet(Stream::empty)).orElseGet(Stream::empty); + } + @Procedure(value = "graph.versioner.relationship.create", mode = Mode.WRITE) @Description("graph.versioner.relationship.create(entityA, entityB, type, relProps, date) - Create a relationship from entitySource to entityDestination with the given type and/or properties for the specified date.") public Stream relationshipCreate( @@ -95,7 +119,7 @@ private Optional getRNode(Node entity) { .map(Relationship::getStartNode); } - private Optional createNewSourceState(Node entitySource, LocalDateTime date) throws VersionerCoreException { + private Optional createNewSourceState(Node entitySource, LocalDateTime date) { Update updateProcedure = new UpdateBuilder().withLog(log).withDb(db).build().orElseThrow(() -> new VersionerCoreException("Unable to initialize update procedure")); return updateProcedure.patch(entitySource, Collections.emptyMap(), StringUtils.EMPTY, date) diff --git a/src/main/java/org/homer/versioner/core/procedure/Update.java b/src/main/java/org/homer/versioner/core/procedure/Update.java index 77a0c91..faf2e15 100644 --- a/src/main/java/org/homer/versioner/core/procedure/Update.java +++ b/src/main/java/org/homer/versioner/core/procedure/Update.java @@ -117,7 +117,7 @@ public Stream patchFrom( .orElseThrow(() -> new VersionerCoreException("Can't find any current State node for the given entity.")); //Copy all the relationships - if (useCurrentRel) { + if (Boolean.TRUE.equals(useCurrentRel)) { currentRelationshipOpt.ifPresent(rel -> connectStateToRs(rel.getEndNode() , newState)); } else { connectStateToRs(state, newState); From 13771941e41a4f2e9a27c58956294b41a7086237 Mon Sep 17 00:00:00 2001 From: tschoen Date: Thu, 30 Apr 2020 13:17:21 -0400 Subject: [PATCH 04/15] :bug: fix timezone issue in test --- .../procedure/RelationshipProcedureTest.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/test/java/org/homer/versioner/core/procedure/RelationshipProcedureTest.java b/src/test/java/org/homer/versioner/core/procedure/RelationshipProcedureTest.java index c88650d..1450222 100644 --- a/src/test/java/org/homer/versioner/core/procedure/RelationshipProcedureTest.java +++ b/src/test/java/org/homer/versioner/core/procedure/RelationshipProcedureTest.java @@ -104,18 +104,19 @@ public void shouldCreateTheRelationshipAssociatedToANewStateHavingRequestedDate( .driver(neo4j.boltURI(), Config.build().withEncryption().toConfig()); Session session = driver.session()) { // Given - Node entityA = initEntity(session); - Node entityB = initEntity(session); - String testType = "testType"; - Long date = 593920000000L; + final Node entityA = initEntity(session); + final Node entityB = initEntity(session); + final String testType = "testType"; + final Long date = 593920000000L; + final String dateString = convertEpochToLocalDateTime(date).toString(); // When - String query = "MATCH (a:Entity), (b:Entity) WHERE id(a) = %d AND id(b) = %d WITH a, b CALL graph.versioner.relationship.create(a, b, '%s', {}, localdatetime('1988-10-27T02:46:40')) YIELD relationship RETURN relationship"; - session.run(String.format(query, entityA.id(), entityB.id(), testType)); + final String query = "MATCH (a:Entity), (b:Entity) WHERE id(a) = %d AND id(b) = %d WITH a, b CALL graph.versioner.relationship.create(a, b, '%s', {}, localdatetime('%s')) YIELD relationship RETURN relationship"; + session.run(String.format(query, entityA.id(), entityB.id(), testType, dateString)); // Then - String querySourceCurrent = "MATCH (e:Entity)-[r:CURRENT]->(:State)-[:%s]->(:R) WHERE id(e) = %d RETURN r"; - Relationship currentRelationship = session.run(String.format(querySourceCurrent, testType, entityA.id())).single().get("r").asRelationship(); + final String querySourceCurrent = "MATCH (e:Entity)-[r:CURRENT]->(:State)-[:%s]->(:R) WHERE id(e) = %d RETURN r"; + final Relationship currentRelationship = session.run(String.format(querySourceCurrent, testType, entityA.id())).single().get("r").asRelationship(); assertThat(currentRelationship) .matches(rel -> rel.containsKey("date") && rel.get("date").asLocalDateTime().equals(convertEpochToLocalDateTime(date))); From 186deb7aee49bc69d833836d680e61523784613d Mon Sep 17 00:00:00 2001 From: tschoen Date: Wed, 6 May 2020 11:41:25 -0400 Subject: [PATCH 05/15] :sparkles: allow relationships created in batch mode to have a unique set of property values / labels --- .../org/homer/versioner/core/Utility.java | 15 ++++++++++- .../core/procedure/RelationshipProcedure.java | 26 ++++++++++--------- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/homer/versioner/core/Utility.java b/src/main/java/org/homer/versioner/core/Utility.java index a5ba84e..41bcbfc 100644 --- a/src/main/java/org/homer/versioner/core/Utility.java +++ b/src/main/java/org/homer/versioner/core/Utility.java @@ -1,9 +1,9 @@ package org.homer.versioner.core; +import org.apache.commons.lang3.tuple.Pair; import org.homer.versioner.core.exception.VersionerCoreException; import org.homer.versioner.core.output.NodeOutput; import org.homer.versioner.core.output.RelationshipOutput; -import org.homer.versioner.core.procedure.RelationshipProcedure; import org.neo4j.graphdb.*; import java.time.Instant; @@ -221,6 +221,19 @@ public static LocalDateTime convertEpochToLocalDateTime(Long epochDateTime) { return Instant.ofEpochMilli(epochDateTime).atZone(ZoneId.systemDefault()).toLocalDateTime(); } + public static List> zip(List listA, List listB) { + if (listA.size() != listB.size()) { + throw new IllegalArgumentException("Lists must have same size"); + } + + List> pairList = new LinkedList<>(); + + for (int index = 0; index < listA.size(); index++) { + pairList.add(Pair.of(listA.get(index), listB.get(index))); + } + return pairList; + } + public static boolean isSystemType(String type) { return SYSTEM_RELS.contains(type); } diff --git a/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java b/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java index 13388b8..83ad169 100644 --- a/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java +++ b/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java @@ -34,22 +34,24 @@ public class RelationshipProcedure extends CoreProcedure { public Stream relationshipsCreate( @Name("entitySource") Node entitySource, @Name("entityDestinations") List entityDestinations, - @Name(value = "type") String type, - @Name(value = "relProps", defaultValue = "{}") Map relProps, + @Name(value = "relProps", defaultValue = "[{}]") List> relProps, @Name(value = "date", defaultValue = "null") LocalDateTime date) { Optional sourceCurrentState = createNewSourceState(entitySource, defaultToNow(date)); isEntityOrThrowException(entitySource); - return sourceCurrentState.map(node -> entityDestinations.stream().map((Node entityDestination) -> { - isEntityOrThrowException(entityDestination); - Optional destinationRNode = getRNode(entityDestination); - if (destinationRNode.isPresent()) { - return streamOfRelationships(createRelationship(node, destinationRNode.get(), type, relProps)); - } else { - final Stream empty = Stream.empty(); - return empty; - } - }).reduce(Stream::concat).orElseGet(Stream::empty)).orElseGet(Stream::empty); + + return entityDestinations.size() == relProps.size() ? sourceCurrentState.map(node -> zip(entityDestinations, relProps).stream().map((item) -> { + final Node destinationNode = item.getLeft(); + isEntityOrThrowException(destinationNode); + Optional destinationRNode = getRNode(destinationNode); + Map props = item.getRight(); + + if (destinationRNode.isPresent()) + return streamOfRelationships(createRelationship(node, destinationRNode.get(), props.get("label") instanceof String ? ((String) props.get("label")) : "TYPE_UNDEFINED", props)); + + return Stream.empty(); + + }).reduce(Stream::concat).orElseGet(Stream::empty)).orElseGet(Stream::empty) : Stream.empty(); } @Procedure(value = "graph.versioner.relationship.create", mode = Mode.WRITE) From 0cda3e7c8ef1a6ab25075ad2eda4f4a71dd3dc2c Mon Sep 17 00:00:00 2001 From: tschoen Date: Wed, 6 May 2020 11:50:25 -0400 Subject: [PATCH 06/15] :books: update documentation for batch relationship creation --- .../homer/versioner/core/procedure/RelationshipProcedure.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java b/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java index 83ad169..f6ba151 100644 --- a/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java +++ b/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java @@ -30,7 +30,7 @@ public class RelationshipProcedure extends CoreProcedure { @Procedure(value = "graph.versioner.relationships.create", mode = Mode.WRITE) - @Description("graph.versioner.relationships.create(entityA, entitiesB, type, relProps, date) - Create multiple relationships from entitySource to each of the entityDestinations with the given type and/or properties for the specified date.") + @Description("graph.versioner.relationships.create(entityA, entitiesB, type, relProps, date) - Create multiple relationships from entitySource to each of the entityDestinations with the given type and/or properties for the specified date. The relationship 'label' along with properties for each relationship can be passed in via 'relProps' a default label ('LABEL_UNDEFINED') is assigned to relationships that are not supplied with a 'label' attribute in the props") public Stream relationshipsCreate( @Name("entitySource") Node entitySource, @Name("entityDestinations") List entityDestinations, @@ -47,7 +47,7 @@ public Stream relationshipsCreate( Map props = item.getRight(); if (destinationRNode.isPresent()) - return streamOfRelationships(createRelationship(node, destinationRNode.get(), props.get("label") instanceof String ? ((String) props.get("label")) : "TYPE_UNDEFINED", props)); + return streamOfRelationships(createRelationship(node, destinationRNode.get(), props.get("label") instanceof String ? ((String) props.get("label")) : "LABEL_UNDEFINED", props)); return Stream.empty(); From 4c989e89a85ac81c73a044ed5663b7dc81735faa Mon Sep 17 00:00:00 2001 From: tschoen Date: Thu, 7 May 2020 12:29:18 -0400 Subject: [PATCH 07/15] :rotating_light: Test for creating multiple relationships --- .../core/procedure/RelationshipProcedure.java | 10 +++- .../procedure/RelationshipProcedureTest.java | 48 +++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java b/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java index f6ba151..ebee8cc 100644 --- a/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java +++ b/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; import java.util.stream.Stream; import static org.homer.versioner.core.Utility.*; @@ -30,7 +31,7 @@ public class RelationshipProcedure extends CoreProcedure { @Procedure(value = "graph.versioner.relationships.create", mode = Mode.WRITE) - @Description("graph.versioner.relationships.create(entityA, entitiesB, type, relProps, date) - Create multiple relationships from entitySource to each of the entityDestinations with the given type and/or properties for the specified date. The relationship 'label' along with properties for each relationship can be passed in via 'relProps' a default label ('LABEL_UNDEFINED') is assigned to relationships that are not supplied with a 'label' attribute in the props") + @Description("graph.versioner.relationships.create(entityA, entitiesB, type, relProps, date) - Create multiple relationships from entitySource to each of the entityDestinations with the given type and/or properties for the specified date. The relationship 'versionerLabel' along with properties for each relationship can be passed in via 'relProps' a default label ('LABEL_UNDEFINED') is assigned to relationships that are not supplied with a 'versionerLabel' attribute in the props") public Stream relationshipsCreate( @Name("entitySource") Node entitySource, @Name("entityDestinations") List entityDestinations, @@ -46,8 +47,13 @@ public Stream relationshipsCreate( Optional destinationRNode = getRNode(destinationNode); Map props = item.getRight(); + // versionerLabel prop is a 'magic' prop + final String labelProp = "versionerLabel"; + Map filteredProps = props.entrySet().stream().filter(map -> !map.getKey().toString().equals(labelProp)) + .collect(Collectors.toMap(map -> map.getKey(), map -> map.getValue())); + if (destinationRNode.isPresent()) - return streamOfRelationships(createRelationship(node, destinationRNode.get(), props.get("label") instanceof String ? ((String) props.get("label")) : "LABEL_UNDEFINED", props)); + return streamOfRelationships(createRelationship(node, destinationRNode.get(), props.get(labelProp) instanceof String ? ((String) props.get(labelProp)) : "LABEL_UNDEFINED", filteredProps)); return Stream.empty(); diff --git a/src/test/java/org/homer/versioner/core/procedure/RelationshipProcedureTest.java b/src/test/java/org/homer/versioner/core/procedure/RelationshipProcedureTest.java index 1450222..2a345d5 100644 --- a/src/test/java/org/homer/versioner/core/procedure/RelationshipProcedureTest.java +++ b/src/test/java/org/homer/versioner/core/procedure/RelationshipProcedureTest.java @@ -9,6 +9,8 @@ import org.neo4j.harness.junit.Neo4jRule; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; @@ -97,6 +99,52 @@ public void shouldNotCreateTheRelationshipIfDestinationIsNotAnEntity() throws Th } } + @Test + public void shouldCreateTheRelationshipsAssociatedToANewStateEachHavingOwnLabelAndProps() { + + try (Driver driver = GraphDatabase + .driver(neo4j.boltURI(), Config.build().withEncryption().toConfig()); Session session = driver.session()) { + + // Given + final Node entityA = initEntity(session); + final Node entityB = initEntity(session); + final Node entityC = initEntity(session); + final Node entityD = initEntity(session); + final Node entityE = initEntity(session); + + final String testType = "testType"; + final Long date = 593920000000L; + final String dateString = convertEpochToLocalDateTime(date).toString(); + + // When + final String query = "WITH [%d, %d, %d, %d] as list MATCH (a:Entity), (b:Entity) WHERE id(a) = %d AND any(item in list where id(b) = item) WITH a, collect(b) as bs CALL graph.versioner.relationships.create(a, bs, [{versionerLabel:'b',name:'b'},{versionerLabel:\"c\",name:\"c\"},{versionerLabel:\"d\",name:\"d\"},{versionerLabel:\"e\",name:\"e\"}]) YIELD relationship RETURN relationship"; + session.run(String.format(query,entityB.id(), entityC.id(), entityD.id(), entityE.id(), entityA.id(), dateString)); + + // Then + final String querySourceCurrent = "MATCH (e:Entity)-[r:CURRENT]->(:State)-[l:%s]->(:R) WHERE id(e) = %d RETURN l"; + final Relationship bRelationship = session.run(String.format(querySourceCurrent, "b", entityA.id())).single().get("l").asRelationship(); + + assertThat(bRelationship) + .matches(rel -> rel.containsKey("name") && rel.get("name").asString().equals("b")); + + final Relationship cRelationship = session.run(String.format(querySourceCurrent, "c", entityA.id())).single().get("l").asRelationship(); + + assertThat(cRelationship) + .matches(rel -> rel.containsKey("name") && rel.get("name").asString().equals("c")); + + + final Relationship dRelationship = session.run(String.format(querySourceCurrent, "d", entityA.id())).single().get("l").asRelationship(); + + assertThat(dRelationship) + .matches(rel -> rel.containsKey("name") && rel.get("name").asString().equals("d")); + + final Relationship eRelationship = session.run(String.format(querySourceCurrent, "e", entityA.id())).single().get("l").asRelationship(); + + assertThat(eRelationship) + .matches(rel -> rel.containsKey("name") && rel.get("name").asString().equals("e")); + } + } + @Test public void shouldCreateTheRelationshipAssociatedToANewStateHavingRequestedDate() { From 0eb374575bbdd5005ea854b1686e3f66e2d5906f Mon Sep 17 00:00:00 2001 From: tschoen Date: Mon, 15 Jun 2020 19:33:45 -0400 Subject: [PATCH 08/15] :bug: bug fixes --- .../org/homer/versioner/core/Utility.java | 16 ++-- .../homer/versioner/core/procedure/Get.java | 78 ++++++++++--------- .../core/procedure/RelationshipProcedure.java | 2 +- .../versioner/core/procedure/Update.java | 2 +- .../versioner/core/procedure/GetTest.java | 32 +++++++- 5 files changed, 80 insertions(+), 50 deletions(-) diff --git a/src/main/java/org/homer/versioner/core/Utility.java b/src/main/java/org/homer/versioner/core/Utility.java index 41bcbfc..523c28f 100644 --- a/src/main/java/org/homer/versioner/core/Utility.java +++ b/src/main/java/org/homer/versioner/core/Utility.java @@ -143,7 +143,7 @@ public static void addCurrentState(Node state, Node entity, LocalDateTime instan * @param state a {@link Node} representing the State * @return {@link Boolean} result */ - public static void checkRelationship(Node entity, Node state) throws VersionerCoreException{ + public static void checkRelationship(Node entity, Node state) throws VersionerCoreException { Spliterator stateRelIterator = state.getRelationships(RelationshipType.withName(Utility.HAS_STATE_TYPE), Direction.INCOMING).spliterator(); StreamSupport.stream(stateRelIterator, false).map(hasStateRel -> { @@ -222,16 +222,16 @@ public static LocalDateTime convertEpochToLocalDateTime(Long epochDateTime) { } public static List> zip(List listA, List listB) { - if (listA.size() != listB.size()) { - throw new IllegalArgumentException("Lists must have same size"); - } - List> pairList = new LinkedList<>(); - - for (int index = 0; index < listA.size(); index++) { - pairList.add(Pair.of(listA.get(index), listB.get(index))); + if (listA.size() != listB.size()) { + System.out.println("Lists must have same size" + listA); + } else { + for (int index = 0; index < listA.size(); index++) { + pairList.add(Pair.of(listA.get(index), listB.get(index))); + } } return pairList; + } public static boolean isSystemType(String type) { diff --git a/src/main/java/org/homer/versioner/core/procedure/Get.java b/src/main/java/org/homer/versioner/core/procedure/Get.java index acacaad..2a07601 100644 --- a/src/main/java/org/homer/versioner/core/procedure/Get.java +++ b/src/main/java/org/homer/versioner/core/procedure/Get.java @@ -3,6 +3,7 @@ import org.homer.versioner.core.Utility; import org.homer.versioner.core.output.NodeOutput; import org.homer.versioner.core.output.PathOutput; +import org.neo4j.cypher.internal.compiler.v2_3.commands.expressions.PathValueBuilder; import org.neo4j.graphalgo.impl.util.PathImpl; import org.neo4j.graphdb.*; import org.neo4j.procedure.Description; @@ -44,22 +45,23 @@ public Stream getCurrentState( .map(Relationship::getEndNode).map(NodeOutput::new).orElse(null)); } + + //fix for error Node[xyz] not connected to this relationship[xyz] @Procedure(value = "graph.versioner.get.all", mode = DEFAULT) @Description("graph.versioner.get.all(entity) - Get all the State nodes for the given Entity.") public Stream getAllState( @Name("entity") Node entity) { - PathImpl.Builder builder = new PathImpl.Builder(entity) - .push(entity.getSingleRelationship(RelationshipType.withName(Utility.CURRENT_TYPE), Direction.OUTGOING)); - builder = StreamSupport.stream(entity.getRelationships(RelationshipType.withName(Utility.HAS_STATE_TYPE), Direction.OUTGOING).spliterator(), false) - //.sorted((a, b) -> -1 * Long.compare((long)a.getProperty(START_DATE_PROP), (long)b.getProperty(START_DATE_PROP))) - .reduce( - builder, - (build, rel) -> Optional.ofNullable(rel.getEndNode().getSingleRelationship(RelationshipType.withName(Utility.PREVIOUS_TYPE), Direction.OUTGOING)) - .map(build::push) - .orElse(build), - (a, b) -> a); - return Stream.of(new PathOutput(builder.build())); + PathValueBuilder builder = new PathValueBuilder(); + builder.addNode(entity); + builder.addOutgoingRelationship(entity.getSingleRelationship(RelationshipType.withName(Utility.CURRENT_TYPE), Direction.OUTGOING)); + StreamSupport.stream(entity.getRelationships(RelationshipType.withName(Utility.HAS_STATE_TYPE), Direction.OUTGOING).spliterator(), false) + .forEach(rel -> + Optional.ofNullable(rel.getEndNode().getSingleRelationship(RelationshipType.withName(Utility.PREVIOUS_TYPE), Direction.OUTGOING)) + .map(builder::addOutgoingRelationship) + ); + + return Stream.of(new PathOutput(builder.result())); } @Procedure(value = "graph.versioner.get.by.label", mode = DEFAULT) @@ -86,31 +88,31 @@ public Stream getStateByDate( .map(NodeOutput::new); } - @Procedure(value = "graph.versioner.get.nth.state", mode = DEFAULT) - @Description("graph.versioner.get.nth.state(entity, nth) - Get the nth State node for the given Entity.") - public Stream getNthState( - @Name("entity") Node entity, - @Name("nth") long nth) { - - return getCurrentState(entity) - .findFirst() - .flatMap(currentState -> getNthStateFrom(currentState.node, nth)) - .map(Utility::streamOfNodes) - .orElse(Stream.empty()); - } - - private Optional getNthStateFrom(Node state, long nth) { - - return Stream.iterate(Optional.of(state), s -> s.flatMap(this::jumpToPreviousState)) - .limit(nth + 1) - .reduce((a, b) -> b) //get only the last value (apply jumpToPreviousState n times - .orElse(Optional.empty()); - } - - private Optional jumpToPreviousState(Node state) { - - return StreamSupport.stream(state.getRelationships(RelationshipType.withName(Utility.PREVIOUS_TYPE), Direction.OUTGOING).spliterator(), false) - .findFirst() - .map(Relationship::getEndNode); - } + @Procedure(value = "graph.versioner.get.nth.state", mode = DEFAULT) + @Description("graph.versioner.get.nth.state(entity, nth) - Get the nth State node for the given Entity.") + public Stream getNthState( + @Name("entity") Node entity, + @Name("nth") long nth) { + + return getCurrentState(entity) + .findFirst() + .flatMap(currentState -> getNthStateFrom(currentState.node, nth)) + .map(Utility::streamOfNodes) + .orElse(Stream.empty()); + } + + private Optional getNthStateFrom(Node state, long nth) { + + return Stream.iterate(Optional.of(state), s -> s.flatMap(this::jumpToPreviousState)) + .limit(nth + 1) + .reduce((a, b) -> b) //get only the last value (apply jumpToPreviousState n times + .orElse(Optional.empty()); + } + + private Optional jumpToPreviousState(Node state) { + + return StreamSupport.stream(state.getRelationships(RelationshipType.withName(Utility.PREVIOUS_TYPE), Direction.OUTGOING).spliterator(), false) + .findFirst() + .map(Relationship::getEndNode); + } } diff --git a/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java b/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java index ebee8cc..9c56230 100644 --- a/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java +++ b/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java @@ -31,7 +31,7 @@ public class RelationshipProcedure extends CoreProcedure { @Procedure(value = "graph.versioner.relationships.create", mode = Mode.WRITE) - @Description("graph.versioner.relationships.create(entityA, entitiesB, type, relProps, date) - Create multiple relationships from entitySource to each of the entityDestinations with the given type and/or properties for the specified date. The relationship 'versionerLabel' along with properties for each relationship can be passed in via 'relProps' a default label ('LABEL_UNDEFINED') is assigned to relationships that are not supplied with a 'versionerLabel' attribute in the props") + @Description("graph.versioner.relationships.create(entityA, entitiesB, relProps, date) - Create multiple relationships from entitySource to each of the entityDestinations with the given type and/or properties for the specified date. The relationship 'versionerLabel' along with properties for each relationship can be passed in via 'relProps' a default label ('LABEL_UNDEFINED') is assigned to relationships that are not supplied with a 'versionerLabel' attribute in the props") public Stream relationshipsCreate( @Name("entitySource") Node entitySource, @Name("entityDestinations") List entityDestinations, diff --git a/src/main/java/org/homer/versioner/core/procedure/Update.java b/src/main/java/org/homer/versioner/core/procedure/Update.java index faf2e15..8358d1f 100644 --- a/src/main/java/org/homer/versioner/core/procedure/Update.java +++ b/src/main/java/org/homer/versioner/core/procedure/Update.java @@ -47,7 +47,7 @@ public Stream update( LocalDateTime currentDate = (LocalDateTime) currentRel.getProperty("date"); // Creating PREVIOUS relationship between the current and the new State - result.createRelationshipTo(currentState, RelationshipType.withName(PREVIOUS_TYPE)).setProperty(DATE_PROP, currentDate); + currentState.createRelationshipTo(result, RelationshipType.withName(PREVIOUS_TYPE)).setProperty(DATE_PROP, currentDate); // Updating the HAS_STATE rel for the current node, adding endDate currentState.getRelationships(RelationshipType.withName(HAS_STATE_TYPE), Direction.INCOMING) diff --git a/src/test/java/org/homer/versioner/core/procedure/GetTest.java b/src/test/java/org/homer/versioner/core/procedure/GetTest.java index 6f6768e..4561565 100644 --- a/src/test/java/org/homer/versioner/core/procedure/GetTest.java +++ b/src/test/java/org/homer/versioner/core/procedure/GetTest.java @@ -1,5 +1,6 @@ package org.homer.versioner.core.procedure; +import org.hamcrest.CoreMatchers; import org.homer.versioner.core.Utility; import org.junit.Rule; import org.junit.Test; @@ -9,6 +10,7 @@ import org.neo4j.driver.v1.types.Relationship; import org.neo4j.harness.junit.Neo4jRule; +import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -20,12 +22,12 @@ /** * GetTest class, it contains all the method used to test Get class methods */ -public class GetTest { +public class GetTest extends GenericProcedureTest{ @Rule public Neo4jRule neo4j = new Neo4jRule() // This is the function we want to test - .withProcedure(Get.class); + .withProcedure(Get.class).withProcedure(Update.class); /*------------------------------*/ /* get.current.path */ @@ -118,6 +120,32 @@ public void shouldGetAllStateNodesByGivenEntity() { } } + @Test + public void shouldGetAllStateNodesByGivenEntityMultipleStates() { + // This is in a try-block, to make sure we close the driver after the test + try (Driver driver = GraphDatabase + .driver(neo4j.boltURI(), Config.build().withEncryption().toConfig()); Session session = driver.session()) { + // Given + session.run("CREATE (e:Entity {key:'immutableValue'})-[:CURRENT {date:localdatetime('1988-10-27T00:00:00')}]->(s:State {key:'initialValue'})"); + session.run("MATCH (e:Entity)-[:CURRENT]->(s:State) CREATE (e)-[:HAS_STATE {startDate:localdatetime('1988-10-27T00:00:00')}]->(s)"); + + // When + session.run("MATCH (e:Entity) WITH e CALL graph.versioner.update(e, {key:'newValue'}, 'Error') YIELD node RETURN node"); + session.run("MATCH (e:Entity) WITH e CALL graph.versioner.update(e, {key:'newerValue'}, 'Error') YIELD node RETURN node"); + session.run("MATCH (e:Entity) WITH e CALL graph.versioner.update(e, {key:'newestValue'}, 'Error') YIELD node RETURN node"); + StatementResult countStateResult = session.run("MATCH (s:State) RETURN count(*) as ss"); + StatementResult allResult = session.run("MATCH (e:Entity) WITH e CALL graph.versioner.get.all(e) YIELD path RETURN nodes(path) as ns"); + StatementResult allResult2 = session.run("MATCH (e:Entity) WITH e CALL graph.versioner.get.all(e) YIELD path RETURN path"); + // Then + final Value ss = countStateResult.single().get("ss"); + assertThat(ss.asInt(), CoreMatchers.equalTo(4)); + final Value ns = allResult.single().get("ns"); + assertThat(ns.asList().size(), CoreMatchers.equalTo(5)); + final Iterable path = allResult2.single().get("path").asPath().nodes(); + assertThat(((Collection)path).size(), CoreMatchers.equalTo(5)); + } + } + @Test public void shouldGetAllStateNodesByGivenEntityWithOnlyOneCurrentState() { // This is in a try-block, to make sure we close the driver after the test From d8da61bc62b9390ff55d785bd66ca78d69f83ec3 Mon Sep 17 00:00:00 2001 From: tschoen Date: Mon, 19 Oct 2020 18:09:04 -0400 Subject: [PATCH 09/15] :cleanup: --- .../core/procedure/RelationshipProcedure.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java b/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java index 9c56230..5585779 100644 --- a/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java +++ b/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java @@ -16,10 +16,7 @@ import org.neo4j.procedure.Procedure; import java.time.LocalDateTime; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -29,7 +26,6 @@ * RelationshipProcedure class, it contains all the Procedures needed to create versioned relationships between Entities */ public class RelationshipProcedure extends CoreProcedure { - @Procedure(value = "graph.versioner.relationships.create", mode = Mode.WRITE) @Description("graph.versioner.relationships.create(entityA, entitiesB, relProps, date) - Create multiple relationships from entitySource to each of the entityDestinations with the given type and/or properties for the specified date. The relationship 'versionerLabel' along with properties for each relationship can be passed in via 'relProps' a default label ('LABEL_UNDEFINED') is assigned to relationships that are not supplied with a 'versionerLabel' attribute in the props") public Stream relationshipsCreate( @@ -40,14 +36,14 @@ public Stream relationshipsCreate( Optional sourceCurrentState = createNewSourceState(entitySource, defaultToNow(date)); isEntityOrThrowException(entitySource); + entityDestinations.sort(Comparator.comparing(o -> o.getProperty("id").toString())); + return entityDestinations.size() == relProps.size() ? sourceCurrentState.map(node -> zip(entityDestinations, relProps).stream().map((item) -> { final Node destinationNode = item.getLeft(); isEntityOrThrowException(destinationNode); Optional destinationRNode = getRNode(destinationNode); Map props = item.getRight(); - - // versionerLabel prop is a 'magic' prop final String labelProp = "versionerLabel"; Map filteredProps = props.entrySet().stream().filter(map -> !map.getKey().toString().equals(labelProp)) .collect(Collectors.toMap(map -> map.getKey(), map -> map.getValue())); From 780464adff63af43aee459035e027929e39391cf Mon Sep 17 00:00:00 2001 From: tschoen Date: Tue, 20 Oct 2020 14:28:59 -0400 Subject: [PATCH 10/15] :bug: fix previous relationship direction on update :sparkles: multiple relationship delete --- .../core/procedure/RelationshipProcedure.java | 31 +++++++++++++++++++ .../versioner/core/procedure/Update.java | 11 ++++--- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java b/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java index 5585779..74f2444 100644 --- a/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java +++ b/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java @@ -78,6 +78,37 @@ public Stream relationshipCreate( } } + @Procedure(value = "graph.versioner.relationships.delete", mode = Mode.WRITE) + @Description("graph.versioner.relationship.delete(entityA, entityB, type, date) - Delete a custom type relationship from entitySource's current State to entityDestination for the specified date.") + public Stream relationshipsDelete( + @Name("entitySource") Node entitySource, + @Name("entityDestinations") List entityDestinations, + @Name(value = "type") String type, + @Name(value = "date", defaultValue = "null") LocalDateTime date) { + + + Optional sourceCurrentState = createNewSourceState(entitySource, defaultToNow(date)); + + entityDestinations.stream().map((entityDestination) -> { + Optional destinationRNode = getRNode(entityDestination); + Update updateProcedure = new UpdateBuilder().withLog(log).withDb(db).build().orElseThrow(() -> new VersionerCoreException("Unable to initialize update procedure")); + + if (sourceCurrentState.isPresent() && destinationRNode.isPresent()) { + final long destId = destinationRNode.get().getId(); + updateProcedure.update(entitySource, sourceCurrentState.get().getAllProperties(), "", null); + getCurrentRelationship(entitySource).ifPresent(rel -> rel.getEndNode().getRelationships(RelationshipType.withName(type), Direction.OUTGOING).forEach(rel2 -> { + if (rel2.getEndNode().getId() == destId) { + rel2.delete(); + } + })); + return Stream.of(new BooleanOutput(Boolean.TRUE)); + } else { + return Stream.of(new BooleanOutput(Boolean.FALSE)); + } + }); + return Stream.of(new BooleanOutput(Boolean.FALSE)); + } + @Procedure(value = "graph.versioner.relationship.delete", mode = Mode.WRITE) @Description("graph.versioner.relationship.delete(entityA, entityB, type, date) - Delete a custom type relationship from entitySource's current State to entityDestination for the specified date.") public Stream relationshipDelete( diff --git a/src/main/java/org/homer/versioner/core/procedure/Update.java b/src/main/java/org/homer/versioner/core/procedure/Update.java index 8358d1f..5140eec 100644 --- a/src/main/java/org/homer/versioner/core/procedure/Update.java +++ b/src/main/java/org/homer/versioner/core/procedure/Update.java @@ -22,6 +22,9 @@ */ public class Update extends CoreProcedure { + public static final String DATE_FIELD = "date"; + public static final String R_LABEL = "R"; + @Procedure(value = "graph.versioner.update", mode = Mode.WRITE) @Description("graph.versioner.update(entity, {key:value,...}, additionalLabel, date) - Add a new State to the given Entity.") public Stream update( @@ -44,10 +47,10 @@ public Stream update( StreamSupport.stream(currentRelIterator, false).forEach(currentRel -> { Node currentState = currentRel.getEndNode(); - LocalDateTime currentDate = (LocalDateTime) currentRel.getProperty("date"); + LocalDateTime currentDate = (LocalDateTime) currentRel.getProperty(DATE_FIELD); // Creating PREVIOUS relationship between the current and the new State - currentState.createRelationshipTo(result, RelationshipType.withName(PREVIOUS_TYPE)).setProperty(DATE_PROP, currentDate); + result.createRelationshipTo(currentState, RelationshipType.withName(PREVIOUS_TYPE)).setProperty(DATE_PROP, currentDate); // Updating the HAS_STATE rel for the current node, adding endDate currentState.getRelationships(RelationshipType.withName(HAS_STATE_TYPE), Direction.INCOMING) @@ -131,7 +134,7 @@ public Stream patchFrom( private Node createPatchedState(Map stateProps, List labels, LocalDateTime instantDate, Relationship currentRelationship) { Node currentState = currentRelationship.getEndNode(); - LocalDateTime currentDate = (LocalDateTime) currentRelationship.getProperty("date"); + LocalDateTime currentDate = (LocalDateTime) currentRelationship.getProperty(DATE_FIELD); Node entity = currentRelationship.getStartNode(); // Patching the current node into the new one. @@ -145,7 +148,7 @@ private Node createPatchedState(Map stateProps, List lab protected static void connectStateToRs(Node sourceState, Node newState) { streamOfIterable(sourceState.getRelationships(Direction.OUTGOING)) - .filter(rel -> rel.getEndNode().hasLabel(Label.label("R"))) + .filter(rel -> rel.getEndNode().hasLabel(Label.label(R_LABEL))) .forEach(rel -> RelationshipProcedure.createRelationship(newState, rel.getEndNode(), rel.getType().name(), rel.getAllProperties())); } } From ffe73ee37384fd1f5a05d785d233c0372750cb18 Mon Sep 17 00:00:00 2001 From: tschoen Date: Tue, 20 Oct 2020 16:40:26 -0400 Subject: [PATCH 11/15] :bug: fix for 2 previous relationships getting created when deleting a relationship --- src/main/java/org/homer/versioner/core/Utility.java | 4 ++++ .../homer/versioner/core/procedure/RelationshipProcedure.java | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/homer/versioner/core/Utility.java b/src/main/java/org/homer/versioner/core/Utility.java index 523c28f..c3b74e7 100644 --- a/src/main/java/org/homer/versioner/core/Utility.java +++ b/src/main/java/org/homer/versioner/core/Utility.java @@ -217,6 +217,10 @@ public static Optional getCurrentRelationship(Node entity) { .findFirst(); } + public static Optional getCurrentState(Node entity) { + return streamOfIterable(entity.getRelationships(RelationshipType.withName(CURRENT_TYPE), Direction.OUTGOING)).map(relationship -> relationship.getEndNode()).findFirst(); + } + public static LocalDateTime convertEpochToLocalDateTime(Long epochDateTime) { return Instant.ofEpochMilli(epochDateTime).atZone(ZoneId.systemDefault()).toLocalDateTime(); } diff --git a/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java b/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java index 74f2444..4bbea03 100644 --- a/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java +++ b/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java @@ -123,7 +123,7 @@ public Stream relationshipDelete( throw new VersionerCoreException("It's not possible to delete a System Relationship like " + type + "."); } - Optional sourceCurrentState = createNewSourceState(entitySource, defaultToNow(date)); + Optional sourceCurrentState = getCurrentState(entitySource); Optional destinationRNode = getRNode(entityDestination); Update updateProcedure = new UpdateBuilder().withLog(log).withDb(db).build().orElseThrow(() -> new VersionerCoreException("Unable to initialize update procedure")); From a1ef417cd16043513d416cb4ca44ae46cb693d88 Mon Sep 17 00:00:00 2001 From: tschoen Date: Tue, 20 Oct 2020 16:56:57 -0400 Subject: [PATCH 12/15] fixes requested in github pull request --- .../org/homer/versioner/core/Utility.java | 12 ++-- .../core/procedure/RelationshipProcedure.java | 65 +++++++++---------- .../versioner/core/procedure/Rollback.java | 3 +- .../procedure/RelationshipProcedureTest.java | 3 - 4 files changed, 35 insertions(+), 48 deletions(-) diff --git a/src/main/java/org/homer/versioner/core/Utility.java b/src/main/java/org/homer/versioner/core/Utility.java index c3b74e7..c5dced6 100644 --- a/src/main/java/org/homer/versioner/core/Utility.java +++ b/src/main/java/org/homer/versioner/core/Utility.java @@ -11,6 +11,7 @@ import java.time.ZoneId; import java.util.*; import java.util.stream.Collectors; +import java.util.stream.IntStream; import java.util.stream.Stream; import java.util.stream.StreamSupport; @@ -227,14 +228,9 @@ public static LocalDateTime convertEpochToLocalDateTime(Long epochDateTime) { public static List> zip(List listA, List listB) { List> pairList = new LinkedList<>(); - if (listA.size() != listB.size()) { - System.out.println("Lists must have same size" + listA); - } else { - for (int index = 0; index < listA.size(); index++) { - pairList.add(Pair.of(listA.get(index), listB.get(index))); - } - } - return pairList; + return IntStream.range(0, listA.size()) + .mapToObj(i -> Pair.of(listA.get(i), listB.get(i))) + .collect(Collectors.toList()); } diff --git a/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java b/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java index 4bbea03..832503c 100644 --- a/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java +++ b/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java @@ -39,6 +39,11 @@ public Stream relationshipsCreate( entityDestinations.sort(Comparator.comparing(o -> o.getProperty("id").toString())); + Stream relationshipOutputStream = getRelationshipOutputStream(entityDestinations, relProps, sourceCurrentState); + return relationshipOutputStream; + } + + private Stream getRelationshipOutputStream(List entityDestinations, List> relProps, Optional sourceCurrentState) { return entityDestinations.size() == relProps.size() ? sourceCurrentState.map(node -> zip(entityDestinations, relProps).stream().map((item) -> { final Node destinationNode = item.getLeft(); isEntityOrThrowException(destinationNode); @@ -48,8 +53,10 @@ public Stream relationshipsCreate( Map filteredProps = props.entrySet().stream().filter(map -> !map.getKey().toString().equals(labelProp)) .collect(Collectors.toMap(map -> map.getKey(), map -> map.getValue())); - if (destinationRNode.isPresent()) - return streamOfRelationships(createRelationship(node, destinationRNode.get(), props.get(labelProp) instanceof String ? ((String) props.get(labelProp)) : "LABEL_UNDEFINED", filteredProps)); + if (destinationRNode.isPresent()) { + String type = props.get(labelProp) instanceof String ? props.get(labelProp).toString() : "LABEL_UNDEFINED"; + streamOfRelationships(createRelationship(node, destinationRNode.get(), type, filteredProps)); + } return Stream.empty(); @@ -87,45 +94,15 @@ public Stream relationshipsDelete( @Name(value = "date", defaultValue = "null") LocalDateTime date) { - Optional sourceCurrentState = createNewSourceState(entitySource, defaultToNow(date)); - + Optional sourceCurrentState = getCurrentState(entitySource); entityDestinations.stream().map((entityDestination) -> { - Optional destinationRNode = getRNode(entityDestination); - Update updateProcedure = new UpdateBuilder().withLog(log).withDb(db).build().orElseThrow(() -> new VersionerCoreException("Unable to initialize update procedure")); - - if (sourceCurrentState.isPresent() && destinationRNode.isPresent()) { - final long destId = destinationRNode.get().getId(); - updateProcedure.update(entitySource, sourceCurrentState.get().getAllProperties(), "", null); - getCurrentRelationship(entitySource).ifPresent(rel -> rel.getEndNode().getRelationships(RelationshipType.withName(type), Direction.OUTGOING).forEach(rel2 -> { - if (rel2.getEndNode().getId() == destId) { - rel2.delete(); - } - })); - return Stream.of(new BooleanOutput(Boolean.TRUE)); - } else { - return Stream.of(new BooleanOutput(Boolean.FALSE)); - } + return getBooleanOutputStream(entitySource, type, sourceCurrentState, entityDestination); }); return Stream.of(new BooleanOutput(Boolean.FALSE)); } - @Procedure(value = "graph.versioner.relationship.delete", mode = Mode.WRITE) - @Description("graph.versioner.relationship.delete(entityA, entityB, type, date) - Delete a custom type relationship from entitySource's current State to entityDestination for the specified date.") - public Stream relationshipDelete( - @Name("entitySource") Node entitySource, - @Name("entityDestination") Node entityDestination, - @Name(value = "type") String type, - @Name(value = "date", defaultValue = "null") LocalDateTime date) { - - isEntityOrThrowException(entitySource); - isEntityOrThrowException(entityDestination); - if (isSystemType(type)) { - throw new VersionerCoreException("It's not possible to delete a System Relationship like " + type + "."); - } - - Optional sourceCurrentState = getCurrentState(entitySource); + private Stream getBooleanOutputStream(@Name("entitySource") Node entitySource, @Name("type") String type, Optional sourceCurrentState, Node entityDestination) { Optional destinationRNode = getRNode(entityDestination); - Update updateProcedure = new UpdateBuilder().withLog(log).withDb(db).build().orElseThrow(() -> new VersionerCoreException("Unable to initialize update procedure")); if (sourceCurrentState.isPresent() && destinationRNode.isPresent()) { @@ -142,6 +119,24 @@ public Stream relationshipDelete( } } + @Procedure(value = "graph.versioner.relationship.delete", mode = Mode.WRITE) + @Description("graph.versioner.relationship.delete(entityA, entityB, type, date) - Delete a custom type relationship from entitySource's current State to entityDestination for the specified date.") + public Stream relationshipDelete( + @Name("entitySource") Node entitySource, + @Name("entityDestination") Node entityDestination, + @Name(value = "type") String type, + @Name(value = "date", defaultValue = "null") LocalDateTime date) { + + isEntityOrThrowException(entitySource); + isEntityOrThrowException(entityDestination); + if (isSystemType(type)) { + throw new VersionerCoreException("It's not possible to delete a System Relationship like " + type + "."); + } + + Optional sourceCurrentState = getCurrentState(entitySource); + return getBooleanOutputStream(entitySource, type, sourceCurrentState, entityDestination); + } + static Relationship createRelationship(Node source, Node destination, String type, Map relProps) { Relationship rel = source.createRelationshipTo(destination, RelationshipType.withName(type)); diff --git a/src/main/java/org/homer/versioner/core/procedure/Rollback.java b/src/main/java/org/homer/versioner/core/procedure/Rollback.java index 4c14311..d4f6aef 100644 --- a/src/main/java/org/homer/versioner/core/procedure/Rollback.java +++ b/src/main/java/org/homer/versioner/core/procedure/Rollback.java @@ -1,8 +1,8 @@ package org.homer.versioner.core.procedure; +import org.homer.versioner.core.Utility; import org.homer.versioner.core.builders.GetBuilder; import org.homer.versioner.core.core.CoreProcedure; -import org.homer.versioner.core.Utility; import org.homer.versioner.core.output.NodeOutput; import org.neo4j.graphdb.Direction; import org.neo4j.graphdb.Node; @@ -13,7 +13,6 @@ import org.neo4j.procedure.Procedure; import java.time.LocalDateTime; -import java.util.Calendar; import java.util.Objects; import java.util.Optional; import java.util.Spliterator; diff --git a/src/test/java/org/homer/versioner/core/procedure/RelationshipProcedureTest.java b/src/test/java/org/homer/versioner/core/procedure/RelationshipProcedureTest.java index 2a345d5..d0ca7e1 100644 --- a/src/test/java/org/homer/versioner/core/procedure/RelationshipProcedureTest.java +++ b/src/test/java/org/homer/versioner/core/procedure/RelationshipProcedureTest.java @@ -8,9 +8,6 @@ import org.neo4j.driver.v1.types.Relationship; import org.neo4j.harness.junit.Neo4jRule; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; From 04c1b806edbd157a81a1bd722eebe32e579ce0bf Mon Sep 17 00:00:00 2001 From: tschoen Date: Tue, 20 Oct 2020 17:04:49 -0400 Subject: [PATCH 13/15] fixes requested in github pull request --- src/main/java/org/homer/versioner/core/Utility.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/homer/versioner/core/Utility.java b/src/main/java/org/homer/versioner/core/Utility.java index c5dced6..3758faf 100644 --- a/src/main/java/org/homer/versioner/core/Utility.java +++ b/src/main/java/org/homer/versioner/core/Utility.java @@ -227,7 +227,6 @@ public static LocalDateTime convertEpochToLocalDateTime(Long epochDateTime) { } public static List> zip(List listA, List listB) { - List> pairList = new LinkedList<>(); return IntStream.range(0, listA.size()) .mapToObj(i -> Pair.of(listA.get(i), listB.get(i))) .collect(Collectors.toList()); From 708261d5b219e44d183a20806eee99dd5c091386 Mon Sep 17 00:00:00 2001 From: tschoen Date: Thu, 17 Dec 2020 17:32:12 -0500 Subject: [PATCH 14/15] :sparkles: creation of multiple relationships from multiple source nodes to single destination node --- build.gradle | 2 +- .../core/procedure/RelationshipProcedure.java | 45 +++++++++++++++++-- .../versioner/core/procedure/Rollback.java | 3 +- .../procedure/RelationshipProcedureTest.java | 5 ++- 4 files changed, 48 insertions(+), 7 deletions(-) diff --git a/build.gradle b/build.gradle index ac759b0..116e5bf 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ apply plugin: 'java' group = 'org.homer' -version = '2.0.1' +version = '2.0.2' description = """Neo4j Procedures for Graph Versioning""" diff --git a/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java b/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java index 832503c..5f23674 100644 --- a/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java +++ b/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java @@ -19,6 +19,7 @@ import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; +import java.util.stream.StreamSupport; import static org.homer.versioner.core.Utility.*; @@ -38,11 +39,44 @@ public Stream relationshipsCreate( isEntityOrThrowException(entitySource); entityDestinations.sort(Comparator.comparing(o -> o.getProperty("id").toString())); - Stream relationshipOutputStream = getRelationshipOutputStream(entityDestinations, relProps, sourceCurrentState); return relationshipOutputStream; } + @Procedure(value = "graph.versioner.relationships.createfrom", mode = Mode.WRITE) + @Description("graph.versioner.relationships.createfrom(entitiesA, entityB, relProps, date) - Create multiple relationships from each of the entitySources to the entityDestination with the given type and/or properties for the specified date. The relationship 'versionerLabel' along with properties for each relationship can be passed in via 'relProps' a default label ('LABEL_UNDEFINED') is assigned to relationships that are not supplied with a 'versionerLabel' attribute in the props") + public Stream relationshipsCreateFrom( + @Name("entitySources") List entitySources, + @Name("entityDestination") Node entityDestination, + @Name(value = "relProps", defaultValue = "[{}]") List> relProps, + @Name(value = "date", defaultValue = "null") LocalDateTime date) { + + entitySources.sort(Comparator.comparing(o -> o.getProperty("id").toString())); + Stream out = null; + Optional destinationRNode = getRNode(entityDestination); + isEntityOrThrowException(entityDestination); + if (entitySources.size() == relProps.size() && destinationRNode.isPresent()) { + out = zip(entitySources, relProps).stream().map((item) -> { + final Node entitySource = item.getLeft(); + log.info("entity source: "+entitySource.getProperty("id").toString()); + Map props = item.getRight(); + final String labelProp = "versionerLabel"; + Map filteredProps = props.entrySet().stream().filter(map -> !map.getKey().equals(labelProp)) + .collect(Collectors.toMap(map -> map.getKey(), map -> map.getValue())); + String type = props.get(labelProp) instanceof String ? props.get(labelProp).toString() : "LABEL_UNDEFINED"; + Optional sourceCurrentState = createNewSourceState(entitySource, defaultToNow(date)); + boolean exists = StreamSupport.stream(sourceCurrentState.get().getRelationships(Direction.OUTGOING, RelationshipType.withName(type)).spliterator(), false).anyMatch(relationship -> relationship.getEndNode().getId() == destinationRNode.get().getId()); + if (exists) { + return Stream.empty(); + } else { + log.info("creating relationship from: " + sourceCurrentState.get().getId() + " to: " + destinationRNode.get().getId()); + return streamOfRelationships(createRelationship(sourceCurrentState.get(), destinationRNode.get(), type, filteredProps)); + } + }).reduce(Stream::concat).orElseGet(Stream::empty); + } + return out; + } + private Stream getRelationshipOutputStream(List entityDestinations, List> relProps, Optional sourceCurrentState) { return entityDestinations.size() == relProps.size() ? sourceCurrentState.map(node -> zip(entityDestinations, relProps).stream().map((item) -> { final Node destinationNode = item.getLeft(); @@ -50,19 +84,18 @@ private Stream getRelationshipOutputStream(List entity Optional destinationRNode = getRNode(destinationNode); Map props = item.getRight(); final String labelProp = "versionerLabel"; - Map filteredProps = props.entrySet().stream().filter(map -> !map.getKey().toString().equals(labelProp)) + Map filteredProps = props.entrySet().stream().filter(map -> !map.getKey().equals(labelProp)) .collect(Collectors.toMap(map -> map.getKey(), map -> map.getValue())); - if (destinationRNode.isPresent()) { String type = props.get(labelProp) instanceof String ? props.get(labelProp).toString() : "LABEL_UNDEFINED"; streamOfRelationships(createRelationship(node, destinationRNode.get(), type, filteredProps)); } - return Stream.empty(); }).reduce(Stream::concat).orElseGet(Stream::empty)).orElseGet(Stream::empty) : Stream.empty(); } + @Procedure(value = "graph.versioner.relationship.create", mode = Mode.WRITE) @Description("graph.versioner.relationship.create(entityA, entityB, type, relProps, date) - Create a relationship from entitySource to entityDestination with the given type and/or properties for the specified date.") public Stream relationshipCreate( @@ -78,6 +111,10 @@ public Stream relationshipCreate( Optional sourceCurrentState = createNewSourceState(entitySource, defaultToNow(date)); Optional destinationRNode = getRNode(entityDestination); + boolean exists = StreamSupport.stream(sourceCurrentState.get().getRelationships(Direction.OUTGOING, RelationshipType.withName(type)).spliterator(), true).anyMatch(relationship -> relationship.getEndNode().getId() == destinationRNode.get().getId()); + if (exists) { + return null; + } if (sourceCurrentState.isPresent() && destinationRNode.isPresent()) { return streamOfRelationships(createRelationship(sourceCurrentState.get(), destinationRNode.get(), type, relProps)); } else { diff --git a/src/main/java/org/homer/versioner/core/procedure/Rollback.java b/src/main/java/org/homer/versioner/core/procedure/Rollback.java index d4f6aef..4c14311 100644 --- a/src/main/java/org/homer/versioner/core/procedure/Rollback.java +++ b/src/main/java/org/homer/versioner/core/procedure/Rollback.java @@ -1,8 +1,8 @@ package org.homer.versioner.core.procedure; -import org.homer.versioner.core.Utility; import org.homer.versioner.core.builders.GetBuilder; import org.homer.versioner.core.core.CoreProcedure; +import org.homer.versioner.core.Utility; import org.homer.versioner.core.output.NodeOutput; import org.neo4j.graphdb.Direction; import org.neo4j.graphdb.Node; @@ -13,6 +13,7 @@ import org.neo4j.procedure.Procedure; import java.time.LocalDateTime; +import java.util.Calendar; import java.util.Objects; import java.util.Optional; import java.util.Spliterator; diff --git a/src/test/java/org/homer/versioner/core/procedure/RelationshipProcedureTest.java b/src/test/java/org/homer/versioner/core/procedure/RelationshipProcedureTest.java index d0ca7e1..5f71670 100644 --- a/src/test/java/org/homer/versioner/core/procedure/RelationshipProcedureTest.java +++ b/src/test/java/org/homer/versioner/core/procedure/RelationshipProcedureTest.java @@ -8,6 +8,9 @@ import org.neo4j.driver.v1.types.Relationship; import org.neo4j.harness.junit.Neo4jRule; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; @@ -114,7 +117,7 @@ public void shouldCreateTheRelationshipsAssociatedToANewStateEachHavingOwnLabelA final String dateString = convertEpochToLocalDateTime(date).toString(); // When - final String query = "WITH [%d, %d, %d, %d] as list MATCH (a:Entity), (b:Entity) WHERE id(a) = %d AND any(item in list where id(b) = item) WITH a, collect(b) as bs CALL graph.versioner.relationships.create(a, bs, [{versionerLabel:'b',name:'b'},{versionerLabel:\"c\",name:\"c\"},{versionerLabel:\"d\",name:\"d\"},{versionerLabel:\"e\",name:\"e\"}]) YIELD relationship RETURN relationship"; + final String query = "WITH [%d, %d, %d, %d] as list MATCH (a:Entity), (b:Entity) WHERE id(a) = %d AND any(item in list where id(b) = item) WITH a, collect(b) as bs CALL graph.versioner.relationships.create(a, bs, [{versionerLabel:\"b\",name:\"b\"},{versionerLabel:\"c\",name:\"c\"},{versionerLabel:\"d\",name:\"d\"},{versionerLabel:\"e\",name:\"e\"}]) YIELD relationship RETURN relationship"; session.run(String.format(query,entityB.id(), entityC.id(), entityD.id(), entityE.id(), entityA.id(), dateString)); // Then From d879ee9584b5d9922536e0e715f4869f1cf9b6a9 Mon Sep 17 00:00:00 2001 From: tschoen Date: Fri, 15 Jan 2021 10:50:58 -0500 Subject: [PATCH 15/15] :books: documentation for added procedures --- docs/index.md | 4 ++- .../core/procedure/RelationshipProcedure.java | 30 +++++++++++-------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/docs/index.md b/docs/index.md index d02a778..c35ffab 100644 --- a/docs/index.md +++ b/docs/index.md @@ -116,9 +116,11 @@ name | parameters | return values | description [graph.versioner.diff](#diff) | **stateFrom**, **stateTo** | operation, label, oldValue, newValue | Get a list of differences that must be applied to stateFrom in order to convert it into stateTo. [graph.versioner.diff.from.previous](#diff-from-previous) | **state** | operation, label, oldValue, newValue | Get a list of differences that must be applied to the previous status of the given one in order to become the given state. [graph.versioner.diff.from.current](#diff-from-current) | **state** | operation, label, oldValue, newValue | Get a list of differences that must be applied to the given state in order to become the current entity state. -[graph.versioner.relationships.create](#relationships-create) | **entitySource**, **List**, relationshipType, *{key:value,...}*, *date* | **relationship** | Creates a new state for the source entity connected to the R nodes of the destinations with a relationship of the given type. +[graph.versioner.relationships.createTo](#relationships-createTo) | **entitySource**, **List**, relationshipType, *{key:value,...}*, *date* | **relationship** | Creates a new state for the source entity connected to each of the R nodes of the destinations with a relationship of the given type. +[graph.versioner.relationships.createFrom](#relationships-createFrom) | **List**, **entityDestination**, relationshipType, *{key:value,...}*, *date* | **relationship** | Creates a new state for each of the source entities connected to the R nodes of the destination with a relationship of the given type. [graph.versioner.relationship.create](#relationship-create) | **entitySource**, **entityDestination**, relationshipType, *{key:value,...}*, *date* | **relationship** | Creates a new state for the source entity connected to the R node of the destination with a relationship of the given type. [graph.versioner.relationship.delete](#relationship-delete) | **entitySource**, **entityDestination**, relationshipType, *date* | **result** | Creates a new state for the source entity without a custom relationship of the given type. +[graph.versioner.relationships.delete](#relationships-delete) | **entitySource**, **List**, relationshipType, *date* | **result** | Creates a new state for the source entity without a custom relationships for each of the destination nodes of the given type. ## init diff --git a/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java b/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java index 5f23674..892041b 100644 --- a/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java +++ b/src/main/java/org/homer/versioner/core/procedure/RelationshipProcedure.java @@ -27,7 +27,7 @@ * RelationshipProcedure class, it contains all the Procedures needed to create versioned relationships between Entities */ public class RelationshipProcedure extends CoreProcedure { - @Procedure(value = "graph.versioner.relationships.create", mode = Mode.WRITE) + @Procedure(value = "graph.versioner.relationships.createTo", mode = Mode.WRITE) @Description("graph.versioner.relationships.create(entityA, entitiesB, relProps, date) - Create multiple relationships from entitySource to each of the entityDestinations with the given type and/or properties for the specified date. The relationship 'versionerLabel' along with properties for each relationship can be passed in via 'relProps' a default label ('LABEL_UNDEFINED') is assigned to relationships that are not supplied with a 'versionerLabel' attribute in the props") public Stream relationshipsCreate( @Name("entitySource") Node entitySource, @@ -43,7 +43,7 @@ public Stream relationshipsCreate( return relationshipOutputStream; } - @Procedure(value = "graph.versioner.relationships.createfrom", mode = Mode.WRITE) + @Procedure(value = "graph.versioner.relationships.createFrom", mode = Mode.WRITE) @Description("graph.versioner.relationships.createfrom(entitiesA, entityB, relProps, date) - Create multiple relationships from each of the entitySources to the entityDestination with the given type and/or properties for the specified date. The relationship 'versionerLabel' along with properties for each relationship can be passed in via 'relProps' a default label ('LABEL_UNDEFINED') is assigned to relationships that are not supplied with a 'versionerLabel' attribute in the props") public Stream relationshipsCreateFrom( @Name("entitySources") List entitySources, @@ -52,26 +52,30 @@ public Stream relationshipsCreateFrom( @Name(value = "date", defaultValue = "null") LocalDateTime date) { entitySources.sort(Comparator.comparing(o -> o.getProperty("id").toString())); + entitySources.stream().map(node -> { + log.info("orderedNodes: " + node.getProperty("id").toString()); + return null; + }); Stream out = null; Optional destinationRNode = getRNode(entityDestination); isEntityOrThrowException(entityDestination); if (entitySources.size() == relProps.size() && destinationRNode.isPresent()) { out = zip(entitySources, relProps).stream().map((item) -> { final Node entitySource = item.getLeft(); - log.info("entity source: "+entitySource.getProperty("id").toString()); + log.info("entity source: " + entitySource.getProperty("id").toString()); Map props = item.getRight(); final String labelProp = "versionerLabel"; Map filteredProps = props.entrySet().stream().filter(map -> !map.getKey().equals(labelProp)) .collect(Collectors.toMap(map -> map.getKey(), map -> map.getValue())); - String type = props.get(labelProp) instanceof String ? props.get(labelProp).toString() : "LABEL_UNDEFINED"; - Optional sourceCurrentState = createNewSourceState(entitySource, defaultToNow(date)); - boolean exists = StreamSupport.stream(sourceCurrentState.get().getRelationships(Direction.OUTGOING, RelationshipType.withName(type)).spliterator(), false).anyMatch(relationship -> relationship.getEndNode().getId() == destinationRNode.get().getId()); - if (exists) { - return Stream.empty(); - } else { - log.info("creating relationship from: " + sourceCurrentState.get().getId() + " to: " + destinationRNode.get().getId()); - return streamOfRelationships(createRelationship(sourceCurrentState.get(), destinationRNode.get(), type, filteredProps)); - } + String type = props.get(labelProp) instanceof String ? props.get(labelProp).toString() : "LABEL_UNDEFINED"; + Optional sourceCurrentState = createNewSourceState(entitySource, defaultToNow(date)); + boolean exists = StreamSupport.stream(sourceCurrentState.get().getRelationships(Direction.OUTGOING, RelationshipType.withName(type)).spliterator(), false).anyMatch(relationship -> relationship.getEndNode().getId() == destinationRNode.get().getId()); + if (exists) { + return Stream.empty(); + } else { + log.info("creating relationship from: " + sourceCurrentState.get().getId() + " to: " + destinationRNode.get().getId()); + return streamOfRelationships(createRelationship(sourceCurrentState.get(), destinationRNode.get(), type, filteredProps)); + } }).reduce(Stream::concat).orElseGet(Stream::empty); } return out; @@ -123,7 +127,7 @@ public Stream relationshipCreate( } @Procedure(value = "graph.versioner.relationships.delete", mode = Mode.WRITE) - @Description("graph.versioner.relationship.delete(entityA, entityB, type, date) - Delete a custom type relationship from entitySource's current State to entityDestination for the specified date.") + @Description("graph.versioner.relationship.delete(entityA, entityB, type, date) - Delete multiple custom type relationship from entitySource's current State to each of the entityDestinations for the specified date.") public Stream relationshipsDelete( @Name("entitySource") Node entitySource, @Name("entityDestinations") List entityDestinations,