apoc.merge.Merge Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of apoc-core Show documentation
Show all versions of apoc-core Show documentation
Core package for Neo4j Procedures
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package apoc.merge;
import static java.util.Collections.emptyMap;
import apoc.cypher.Cypher;
import apoc.result.NodeResult;
import apoc.result.NodeResultWithStats;
import apoc.result.RelationshipResult;
import apoc.result.RelationshipResultWithStats;
import apoc.util.Util;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.Result;
import org.neo4j.graphdb.Transaction;
import org.neo4j.procedure.*;
public class Merge {
@Context
public Transaction tx;
@Procedure(value = "apoc.merge.node.eager", mode = Mode.WRITE, eager = true)
@Description("Merges the given `NODE` values with the given dynamic labels eagerly.")
public Stream nodesEager(
@Name("labels") List labelNames,
@Name("identProps") Map identProps,
@Name(value = "onCreateProps", defaultValue = "{}") Map onCreateProps,
@Name(value = "onMatchProps", defaultValue = "{}") Map onMatchProps) {
return nodes(labelNames, identProps, onCreateProps, onMatchProps);
}
@Procedure(value = "apoc.merge.node", mode = Mode.WRITE)
@Description("Merges the given `NODE` values with the given dynamic labels.")
public Stream nodes(
@Name("labels") List labelNames,
@Name("identProps") Map identProps,
@Name(value = "onCreateProps", defaultValue = "{}") Map onCreateProps,
@Name(value = "onMatchProps", defaultValue = "{}") Map 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(
"Merges the given `NODE` values with the given dynamic labels eagerly. Provides queryStatistics in the result.")
public Stream nodeWithStatsEager(
@Name("labels") List labelNames,
@Name("identProps") Map identProps,
@Name(value = "onCreateProps", defaultValue = "{}") Map onCreateProps,
@Name(value = "onMatchProps", defaultValue = "{}") Map onMatchProps) {
return nodeWithStats(labelNames, identProps, onCreateProps, onMatchProps);
}
@Procedure(value = "apoc.merge.nodeWithStats", mode = Mode.WRITE)
@Description(
"Merges the given `NODE` values with the given dynamic labels. Provides queryStatistics in the result.")
public Stream nodeWithStats(
@Name("labels") List labelNames,
@Name("identProps") Map identProps,
@Name(value = "onCreateProps", defaultValue = "{}") Map onCreateProps,
@Name(value = "onMatchProps", defaultValue = "{}") Map 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 labelNames,
Map identProps,
Map onCreateProps,
Map onMatchProps) {
if (identProps == null || identProps.isEmpty()) {
throw new IllegalArgumentException("you need to supply at least one identifying property for a merge");
}
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 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";
return tx.execute(cypher, params);
}
@Procedure(value = "apoc.merge.relationship", mode = Mode.WRITE)
@Description("Merges the given `RELATIONSHIP` values with the given dynamic types/properties.")
public Stream relationship(
@Name("startNode") Node startNode,
@Name("relType") String relType,
@Name("identProps") Map identProps,
@Name(value = "onCreateProps") Map onCreateProps,
@Name("endNode") Node endNode,
@Name(value = "onMatchProps", defaultValue = "{}") Map onMatchProps) {
final Result execute = getRelResult(startNode, relType, identProps, onCreateProps, endNode, onMatchProps);
return execute.columnAs("r").stream().map(rel -> new RelationshipResult((Relationship) rel));
}
@Procedure(value = "apoc.merge.relationshipWithStats", mode = Mode.WRITE)
@Description(
"Merges the given `RELATIONSHIP` values with the given dynamic types/properties. Provides queryStatistics in the result.")
public Stream relationshipWithStats(
@Name("startNode") Node startNode,
@Name("relType") String relType,
@Name("identProps") Map identProps,
@Name(value = "onCreateProps") Map onCreateProps,
@Name("endNode") Node endNode,
@Name(value = "onMatchProps", defaultValue = "{}") Map onMatchProps) {
final Result relResult = getRelResult(startNode, relType, identProps, onCreateProps, endNode, onMatchProps);
return relResult.columnAs("r").stream()
.map(rel -> new RelationshipResultWithStats(
(Relationship) rel, Cypher.toMap(relResult.getQueryStatistics())));
}
private Result getRelResult(
Node startNode,
String relType,
Map identProps,
Map onCreateProps,
Node endNode,
Map 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 params = Util.map(
"identProps",
identProps,
"onCreateProps",
onCreateProps == null ? emptyMap() : onCreateProps,
"onMatchProps",
onMatchProps == null ? emptyMap() : onMatchProps,
"startNode",
startNode,
"endNode",
endNode);
final String cypher = "WITH $startNode as startNode, $endNode as endNode " + "MERGE (startNode)-[r:"
+ Util.quote(relType) + "{" + identPropsString + "}]->(endNode) " + "ON CREATE SET r+= $onCreateProps "
+ "ON MATCH SET r+= $onMatchProps "
+ "RETURN r";
return tx.execute(cypher, params);
}
@Procedure(value = "apoc.merge.relationship.eager", mode = Mode.WRITE, eager = true)
@Description("Merges the given `RELATIONSHIP` values with the given dynamic types/properties eagerly.")
public Stream relationshipEager(
@Name("startNode") Node startNode,
@Name("relType") String relType,
@Name("identProps") Map identProps,
@Name(value = "onCreateProps") Map onCreateProps,
@Name(value = "endNode") Node endNode,
@Name(value = "onMatchProps", defaultValue = "{}") Map onMatchProps) {
return relationship(startNode, relType, identProps, onCreateProps, endNode, onMatchProps);
}
@Procedure(value = "apoc.merge.relationshipWithStats.eager", mode = Mode.WRITE, eager = true)
@Description(
"Merges the given `RELATIONSHIP` values with the given dynamic types/properties eagerly. Provides queryStatistics in the result.")
public Stream relationshipWithStatsEager(
@Name("startNode") Node startNode,
@Name("relType") String relType,
@Name("identProps") Map identProps,
@Name(value = "onCreateProps") Map onCreateProps,
@Name("endNode") Node endNode,
@Name(value = "onMatchProps", defaultValue = "{}") Map onMatchProps) {
return relationshipWithStats(startNode, relType, identProps, onCreateProps, endNode, onMatchProps);
}
private String buildIdentPropsString(Map identProps) {
if (identProps == null) return "";
return identProps.keySet().stream()
.map(Util::quote)
.map(s -> s + ":$identProps." + s)
.collect(Collectors.joining(","));
}
}