All Downloads are FREE. Search and download functionalities are using the official Maven repository.

apoc.merge.Merge Maven / Gradle / Ivy

There is a newer version: 5.24.0
Show newest version
/*
 * 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(","));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy