Skip to content

Commit

Permalink
[pLz5YbCW] add checks for node labels and relationship types for apoc…
Browse files Browse the repository at this point in the history
….merge procedures
  • Loading branch information
nadja-muller committed Oct 24, 2023
1 parent 15ea860 commit 9adfaff
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 55 deletions.
48 changes: 31 additions & 17 deletions core/src/main/java/apoc/merge/Merge.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,60 +47,69 @@ public class Merge {
@Description("apoc.merge.node.eager(['Label'], identProps:{key:value, ...}, onCreateProps:{key:value,...}, onMatchProps:{key:value,...}}) - merge nodes eagerly, with dynamic labels, with support for setting properties ON CREATE or ON MATCH")
public Stream<NodeResult> nodesEager(@Name("label") List<String> labelNames,
@Name("identProps") Map<String, Object> identProps,
@Name(value = "props",defaultValue = "{}") Map<String, Object> props,
@Name(value = "onCreateProps",defaultValue = "{}") Map<String, Object> onCreateProps,
@Name(value = "onMatchProps",defaultValue = "{}") Map<String, Object> onMatchProps) {
return nodes(labelNames, identProps,props,onMatchProps);
return nodes(labelNames, identProps, onCreateProps, onMatchProps);
}

@Procedure(value="apoc.merge.node", mode = Mode.WRITE)
@Description("\"apoc.merge.node(['Label'], identProps:{key:value, ...}, onCreateProps:{key:value,...}, onMatchProps:{key:value,...}}) - merge nodes with dynamic labels, with support for setting properties ON CREATE or ON MATCH")
public Stream<NodeResult> nodes(@Name("label") List<String> labelNames,
@Name("identProps") Map<String, Object> identProps,
@Name(value = "props",defaultValue = "{}") Map<String, Object> props,
@Name(value = "onCreateProps",defaultValue = "{}") Map<String, Object> onCreateProps,
@Name(value = "onMatchProps",defaultValue = "{}") Map<String, Object> onMatchProps) {
final Result nodeResult = getNodeResult(labelNames, identProps, props, onMatchProps);
final Result nodeResult = getNodeResult(labelNames, identProps, onCreateProps, onMatchProps);
return nodeResult.columnAs("n").stream().map(node -> new NodeResult((Node) node));
}

@Procedure(value="apoc.merge.nodeWithStats.eager", mode = Mode.WRITE, eager = true)
@Description("apoc.merge.nodeWithStats.eager - same as apoc.merge.node.eager providing queryStatistics into result")
public Stream<NodeResultWithStats> nodeWithStatsEager(@Name("label") List<String> labelNames,
@Name("identProps") Map<String, Object> identProps,
@Name(value = "props",defaultValue = "{}") Map<String, Object> props,
@Name(value = "onCreateProps",defaultValue = "{}") Map<String, Object> onCreateProps,
@Name(value = "onMatchProps",defaultValue = "{}") Map<String, Object> onMatchProps) {
return nodeWithStats(labelNames, identProps,props,onMatchProps);
return nodeWithStats(labelNames, identProps, onCreateProps, onMatchProps);
}

@Procedure(value="apoc.merge.nodeWithStats", mode = Mode.WRITE)
@Description("apoc.merge.nodeWithStats - same as apoc.merge.node providing queryStatistics into result")
public Stream<NodeResultWithStats> nodeWithStats(@Name("label") List<String> labelNames,
@Name("identProps") Map<String, Object> identProps,
@Name(value = "props",defaultValue = "{}") Map<String, Object> props,
@Name(value = "onCreateProps",defaultValue = "{}") Map<String, Object> onCreateProps,
@Name(value = "onMatchProps",defaultValue = "{}") Map<String, Object> onMatchProps) {
final Result nodeResult = getNodeResult(labelNames, identProps, props, onMatchProps);
final Result nodeResult = getNodeResult(labelNames, identProps, onCreateProps, onMatchProps);
return nodeResult.columnAs("n").stream()
.map(node -> new NodeResultWithStats((Node) node, Cypher.toMap(nodeResult.getQueryStatistics())));
}

private Result getNodeResult(List<String> labelNames, Map<String, Object> identProps, Map<String, Object> props, Map<String, Object> onMatchProps) {
if (identProps ==null || identProps.isEmpty()) {
private Result getNodeResult(List<String> labelNames, Map<String, Object> identProps, Map<String, Object> onCreateProps, Map<String, Object> onMatchProps) {
if (identProps == null || identProps.isEmpty()) {
throw new IllegalArgumentException("you need to supply at least one identifying property for a merge");
}

String labels = labelString(labelNames);
if (labelNames != null && (labelNames.contains(null) || labelNames.contains(""))) {
throw new IllegalArgumentException("The list of label names may not contain any `NULL` or empty `STRING` values. If you wish to merge a `NODE` without a label, pass an empty list instead.");
}

String labels;
if (labelNames == null || labelNames.isEmpty()) {
labels = "";
} else {
labels = ":" + labelNames.stream().map(Util::quote).collect(Collectors.joining(":"));
}

Map<String, Object> params = Util.map("identProps", identProps, "onCreateProps", props, "onMatchProps", onMatchProps);
Map<String, Object> params = Util.map("identProps", identProps, "onCreateProps", onCreateProps, "onMatchProps", onMatchProps);
String identPropsString = buildIdentPropsString(identProps);

final String cypher = "MERGE (n:" + labels + "{" + identPropsString + "}) ON CREATE SET n += $onCreateProps ON MATCH SET n += $onMatchProps RETURN n";
final String cypher = "MERGE (n" + labels + "{" + identPropsString + "}) ON CREATE SET n += $onCreateProps ON MATCH SET n += $onMatchProps RETURN n";
return tx.execute(cypher, params);
}

@Procedure(value = "apoc.merge.relationship", mode = Mode.WRITE)
@Description("apoc.merge.relationship(startNode, relType, identProps:{key:value, ...}, onCreateProps:{key:value, ...}, endNode, onMatchProps:{key:value, ...}) - merge relationship with dynamic type, with support for setting properties ON CREATE or ON MATCH")
public Stream<RelationshipResult> relationship(@Name("startNode") Node startNode, @Name("relationshipType") String relType,
@Name("identProps") Map<String, Object> identProps,
@Name("props") Map<String, Object> onCreateProps,
@Name("onCreateProps") Map<String, Object> onCreateProps,
@Name("endNode") Node endNode,
@Name(value = "onMatchProps",defaultValue = "{}") Map<String, Object> onMatchProps) {
final Result execute = getRelResult(startNode, relType, identProps, onCreateProps, endNode, onMatchProps);
Expand All @@ -111,7 +120,7 @@ public Stream<RelationshipResult> relationship(@Name("startNode") Node startNode
@Description("apoc.merge.relationshipWithStats - same as apoc.merge.relationship providing queryStatistics into result")
public Stream<RelationshipResultWithStats> relationshipWithStats(@Name("startNode") Node startNode, @Name("relationshipType") String relType,
@Name("identProps") Map<String, Object> identProps,
@Name("props") Map<String, Object> onCreateProps,
@Name("onCreateProps") Map<String, Object> onCreateProps,
@Name("endNode") Node endNode,
@Name(value = "onMatchProps",defaultValue = "{}") Map<String, Object> onMatchProps) {
final Result relResult = getRelResult(startNode, relType, identProps, onCreateProps, endNode, onMatchProps);
Expand All @@ -122,6 +131,11 @@ public Stream<RelationshipResultWithStats> relationshipWithStats(@Name("startNod
private Result getRelResult(Node startNode, String relType, Map<String, Object> identProps, Map<String, Object> onCreateProps, Node endNode, Map<String, Object> onMatchProps) {
String identPropsString = buildIdentPropsString(identProps);

if (relType == null || relType.isEmpty()) {
throw new IllegalArgumentException("It is not possible to merge a `RELATIONSHIP` without a `RELATIONSHIP` type.");
}

Map<String, Object> params = Util.map("identProps", identProps, "onCreateProps", onCreateProps == null ? emptyMap() : onCreateProps,
Map<String, Object> params = Util.map("identProps", identProps, "onCreateProps", onCreateProps ==null ? emptyMap() : onCreateProps,
"onMatchProps", onMatchProps == null ? emptyMap() : onMatchProps, "startNode", startNode, "endNode", endNode);

Expand All @@ -138,7 +152,7 @@ private Result getRelResult(Node startNode, String relType, Map<String, Object>
@Description("apoc.merge.relationship(startNode, relType, identProps:{key:value, ...}, onCreateProps:{key:value, ...}, endNode, onMatchProps:{key:value, ...}) - merge relationship with dynamic type, with support for setting properties ON CREATE or ON MATCH")
public Stream<RelationshipResult> relationshipEager(@Name("startNode") Node startNode, @Name("relationshipType") String relType,
@Name("identProps") Map<String, Object> identProps,
@Name("props") Map<String, Object> onCreateProps,
@Name("onCreateProps") Map<String, Object> onCreateProps,
@Name("endNode") Node endNode,
@Name(value = "onMatchProps",defaultValue = "{}") Map<String, Object> onMatchProps) {
return relationship(startNode, relType, identProps, onCreateProps, endNode, onMatchProps );
Expand All @@ -148,7 +162,7 @@ public Stream<RelationshipResult> relationshipEager(@Name("startNode") Node star
@Description("apoc.merge.relationshipWithStats.eager - same as apoc.merge.relationship.eager providing queryStatistics into result")
public Stream<RelationshipResultWithStats> relationshipWithStatsEager(@Name("startNode") Node startNode, @Name("relationshipType") String relType,
@Name("identProps") Map<String, Object> identProps,
@Name("props") Map<String, Object> onCreateProps,
@Name("onCreateProps") Map<String, Object> onCreateProps,
@Name("endNode") Node endNode,
@Name(value = "onMatchProps",defaultValue = "{}") Map<String, Object> onMatchProps) {
return relationshipWithStats(startNode, relType, identProps, onCreateProps, endNode, onMatchProps );
Expand Down
4 changes: 0 additions & 4 deletions core/src/main/java/apoc/util/Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -143,10 +143,6 @@ public class Util {
public static String INVALID_QUERY_MODE_ERROR = "This procedure allows for READ-only, non-schema based queries. " +
"It is therefore not possible to perform writes or query the database with commands such as SHOW CONSTRAINTS/INDEXES.";

public static String labelString(List<String> labelNames) {
return labelNames.stream().map(Util::quote).collect(Collectors.joining(":"));
}

public static String labelString(Node n) {
return joinLabels(n.getLabels(), ":");
}
Expand Down
Loading

0 comments on commit 9adfaff

Please sign in to comment.