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

apoc.nodes.Nodes Maven / Gradle / Ivy

There is a newer version: 4.4.0.31
Show newest version
package apoc.nodes;

import apoc.Pools;
import apoc.create.Create;
import apoc.refactor.util.PropertiesManager;
import apoc.refactor.util.RefactorConfig;
import apoc.result.LongResult;
import apoc.result.NodeResult;
import apoc.result.RelationshipResult;
import apoc.result.VirtualNode;
import apoc.result.VirtualPathResult;
import apoc.util.Util;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.Entity;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Label;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
import org.neo4j.graphdb.Transaction;
import org.neo4j.internal.helpers.collection.Iterables;
import org.neo4j.internal.helpers.collection.Pair;
import org.neo4j.internal.kernel.api.CursorFactory;
import org.neo4j.internal.kernel.api.NodeCursor;
import org.neo4j.internal.kernel.api.Read;
import org.neo4j.internal.kernel.api.RelationshipGroupCursor;
import org.neo4j.internal.kernel.api.RelationshipTraversalCursor;
import org.neo4j.internal.kernel.api.TokenRead;
import org.neo4j.kernel.api.KernelTransaction;
import org.neo4j.procedure.Context;
import org.neo4j.procedure.Description;
import org.neo4j.procedure.Mode;
import org.neo4j.procedure.Name;
import org.neo4j.procedure.Procedure;
import org.neo4j.procedure.UserFunction;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import static apoc.path.RelationshipTypeAndDirections.format;
import static apoc.path.RelationshipTypeAndDirections.parse;
import static apoc.refactor.util.RefactorUtil.copyProperties;
import static apoc.util.Util.map;
import static org.neo4j.internal.kernel.api.security.AccessMode.Static.READ;

public class Nodes {

    @Context
    public GraphDatabaseService db;

    @Context
    public Transaction tx;

    @Context
    public KernelTransaction ktx;

    @Context
    public Pools pools;

    @Procedure(mode = Mode.WRITE)
    @Description("apoc.nodes.link([nodes],'REL_TYPE') - creates a linked list of nodes from first to last")
    public void link(@Name("nodes") List nodes, @Name("type") String type) {
        Iterator it = nodes.iterator();
        if (it.hasNext()) {
            RelationshipType relType = RelationshipType.withName(type);
            Node node = it.next();
            while (it.hasNext()) {
                Node next = it.next();
                node.createRelationshipTo(next, relType);
                node = next;
            }
        }
    }

    @Procedure
    @Description("apoc.nodes.get(node|nodes|id|[ids]) - quickly returns all nodes with these ids")
    public Stream get(@Name("nodes") Object ids) {
        return Util.nodeStream(tx, ids).map(NodeResult::new);
    }

    @Procedure(mode = Mode.WRITE)
    @Description("apoc.nodes.delete(node|nodes|id|[ids]) - quickly delete all nodes with these ids")
    public Stream delete(@Name("nodes") Object ids, @Name("batchSize") long batchSize) {
        Iterator it = Util.nodeStream(tx, ids).iterator();
        long count = 0;
        while (it.hasNext()) {
            final List batch = Util.take(it, (int)batchSize);
//            count += Util.inTx(api,() -> batch.stream().peek( n -> {n.getRelationships().forEach(Relationship::delete);n.delete();}).count());
            count += Util.inTx(db, pools, (txInThread) -> {txInThread.execute("FOREACH (n in $nodes | DETACH DELETE n)",map("nodes",batch)).close();return batch.size();});
        }
        return Stream.of(new LongResult(count));
    }

    @Procedure
    @Description("apoc.get.rels(rel|id|[ids]) - quickly returns all relationships with these ids")
    public Stream rels(@Name("relationships") Object ids) {
        return Util.relsStream(tx, ids).map(RelationshipResult::new);
    }

    @UserFunction("apoc.node.relationship.exists")
    @Description("apoc.node.relationship.exists(node, rel-direction-pattern) - returns true when the node has the relationships of the pattern")
    public boolean hasRelationship(@Name("node") Node node, @Name(value = "types", defaultValue = "") String types) {
        if (types == null || types.isEmpty()) return node.hasRelationship();
        long id = node.getId();
        try ( NodeCursor nodeCursor = ktx.cursors().allocateNodeCursor()) {

            ktx.dataRead().singleNode(id, nodeCursor);
            nodeCursor.next();
            TokenRead tokenRead = ktx.tokenRead();

            for (Pair pair : parse(types)) {
                int typeId = tokenRead.relationshipType(pair.first().name());
                Direction direction = pair.other();

                int count;
                switch (direction) {
                    case INCOMING:
                        count = org.neo4j.internal.kernel.api.helpers.Nodes.countIncoming(nodeCursor, ktx.cursors(), typeId);
                        break;
                    case OUTGOING:
                        count = org.neo4j.internal.kernel.api.helpers.Nodes.countOutgoing(nodeCursor, ktx.cursors(), typeId);
                        break;
                    case BOTH:
                        count = org.neo4j.internal.kernel.api.helpers.Nodes.countAll(nodeCursor, ktx.cursors(), typeId);
                        break;
                    default:
                        throw new UnsupportedOperationException("invalid direction " + direction);
                }
                if (count > 0) {
                    return true;
                }
            }
        }
        return false;
    }

    @UserFunction("apoc.nodes.connected")
    @Description("apoc.nodes.connected(start, end, rel-direction-pattern) - returns true when the node is connected to the other node, optimized for dense nodes")
    public boolean connected(@Name("start") Node start, @Name("start") Node end, @Name(value = "types", defaultValue = "") String types)  {
        if (start == null || end == null) return false;
        if (start.equals(end)) return true;

        long startId = start.getId();
        long endId = end.getId();
        List> pairs = (types == null || types.isEmpty()) ? null : parse(types);

        Read dataRead = ktx.dataRead();
        TokenRead tokenRead = ktx.tokenRead();
        CursorFactory cursors = ktx.cursors();

        try (NodeCursor startNodeCursor = cursors.allocateNodeCursor();
             NodeCursor endNodeCursor = cursors.allocateNodeCursor()) {

            dataRead.singleNode(startId, startNodeCursor);
            if (!startNodeCursor.next()) {
                throw new IllegalArgumentException("node with id " + startId + " does not exist.");
            }

            boolean startDense = startNodeCursor.isDense();
            dataRead.singleNode(endId, endNodeCursor);
            if (!endNodeCursor.next()) {
                throw new IllegalArgumentException("node with id " + endId + " does not exist.");
            }
            boolean endDense = endNodeCursor.isDense();

            if (!startDense) return connected(startNodeCursor, endId, typedDirections(tokenRead, pairs, true));
            if (!endDense) return connected(endNodeCursor, startId, typedDirections(tokenRead, pairs, false));
            return connectedDense(startNodeCursor, endNodeCursor, typedDirections(tokenRead, pairs, true));
        }
    }

    @Procedure
    @Description("apoc.nodes.collapse([nodes...],[{properties:'overwrite' or 'discard' or 'combine'}]) yield from, rel, to merge nodes onto first in list")
    public Stream collapse(@Name("nodes") List nodes, @Name(value = "config", defaultValue = "{}") Map config) {
        if (nodes == null || nodes.isEmpty()) return Stream.empty();
        if (nodes.size() == 1) return Stream.of(new VirtualPathResult(nodes.get(0), null, null));
        Set nodeSet = new LinkedHashSet<>(nodes);
        RefactorConfig conf = new RefactorConfig(config);
        VirtualNode first = createVirtualNode(nodeSet, conf);
        if (first.getRelationships().iterator().hasNext()) {
            return StreamSupport.stream(first.getRelationships().spliterator(), false)
                    .map(relationship -> new VirtualPathResult(relationship.getStartNode(), relationship, relationship.getEndNode()));
        } else {
            return Stream.of(new VirtualPathResult(first, null, null));
        }
    }

    private VirtualNode createVirtualNode(Set nodes, RefactorConfig conf) {
        Create create = new Create();
        Node first = nodes.iterator().next();
        List labels = Util.labelStrings(first);
        if (conf.isCollapsedLabel()) {
            labels.add("Collapsed");
        }
        VirtualNode virtualNode = (VirtualNode) create.vNodeFunction(labels, first.getAllProperties());
        createVirtualRelationships(nodes, virtualNode, first, conf);
        nodes.stream().skip(1).forEach(node -> {
            virtualNode.addLabels(node.getLabels());
            PropertiesManager.mergeProperties(node.getAllProperties(), virtualNode, conf);
            createVirtualRelationships(nodes, virtualNode, node, conf);
        });
        if (conf.isCountMerge()) {
            virtualNode.setProperty("count", nodes.size());
        }
        return virtualNode;
    }

    private void createVirtualRelationships(Set nodes, VirtualNode virtualNode, Node node, RefactorConfig refactorConfig) {
        node.getRelationships().forEach(relationship -> {
            Node startNode = relationship.getStartNode();
            Node endNode = relationship.getEndNode();

            if (nodes.contains(startNode) && nodes.contains(endNode)) {
                if (refactorConfig.isSelfRel()) {
                    createOrMergeVirtualRelationship(virtualNode, refactorConfig, relationship, virtualNode,  Direction.OUTGOING);
                }
            } else {
                if (startNode.getId() == node.getId()) {
                    createOrMergeVirtualRelationship(virtualNode, refactorConfig, relationship, endNode,  Direction.OUTGOING);
                } else {
                    createOrMergeVirtualRelationship(virtualNode, refactorConfig, relationship, startNode,  Direction.INCOMING);
                }
            }
        });
    }

    private void createOrMergeVirtualRelationship(VirtualNode virtualNode, RefactorConfig refactorConfig, Relationship source, Node node, Direction direction) {
        Iterable rels = virtualNode.getRelationships(direction, source.getType());
        Optional first = StreamSupport.stream(rels.spliterator(), false).filter(relationship -> relationship.getOtherNode(virtualNode).equals(node)).findFirst();
        if (refactorConfig.isMergeVirtualRels() && first.isPresent()) {
            mergeRelationship(source, first.get(), refactorConfig);
        } else {
            if (direction==Direction.OUTGOING)
               copyProperties(source, virtualNode.createRelationshipTo(node, source.getType()));
            if (direction==Direction.INCOMING) 
               copyProperties(source, virtualNode.createRelationshipFrom(node, source.getType()));
        }
    }

    private void mergeRelationship(Relationship source, Relationship target, RefactorConfig refactorConfig) {
        if (refactorConfig.isCountMerge()) {
            target.setProperty("count", (Integer) target.getProperty("count", 0) + 1);
        }
        PropertiesManager.mergeProperties(source.getAllProperties(), target, refactorConfig);
    }

    /**
     * TODO: be more efficient, in
     * @param start
     * @param end
     * @param typedDirections
     * @return
     */
    private boolean connected(NodeCursor start, long end, int[][] typedDirections) {
        try (RelationshipTraversalCursor relationship = ktx.cursors().allocateRelationshipTraversalCursor()) {
            start.allRelationships(relationship);
            while (relationship.next()) {
                if (relationship.neighbourNodeReference() ==end) {
                    if (typedDirections==null) {
                        return true;
                    } else {
                        int direction = relationship.targetNodeReference() == end ? 0 : 1 ;
                        int[] types = typedDirections[direction];
                        if (arrayContains(types, relationship.type())) return true;
                    }
                }
            }
        }
        return false;
    }

    private boolean arrayContains(int[] array, int element) {
        for (int i=0; i> pairs, boolean outgoing) {
        if (pairs==null) return null;
        int from=0;int to=0;
        int[][] result = new int[2][pairs.size()];
        int outIdx = Direction.OUTGOING.ordinal();
        int inIdx = Direction.INCOMING.ordinal();
        for (Pair pair : pairs) {
            int type = ops.relationshipType(pair.first().name());
            if (type == -1) continue;
            if (pair.other() != Direction.INCOMING) {
                result[outIdx][from++]= type;
            }
            if (pair.other() != Direction.OUTGOING) {
                result[inIdx][to++]= type;
            }
        }
        result[outIdx] = Arrays.copyOf(result[outIdx], from);
        result[inIdx] = Arrays.copyOf(result[inIdx], to);
        if (!outgoing) {
            int[] tmp = result[outIdx];
            result[outIdx] = result[inIdx];
            result[inIdx] = tmp;
        }
        return result;
    }

    static class Degree implements Comparable {
        public final long node;
        private final long group;
        public final int degree;
        public final long other;

        public Degree(long node, long group, int degree, long other) {
            this.node = node;
            this.group = group;
            this.degree = degree;
            this.other = other;
        }

        @Override
        public int compareTo(Degree o) {
            return Integer.compare(degree, o.degree);
        }

        public boolean isConnected(Read read, RelationshipTraversalCursor relationship) {
            read.relationships(node, group, relationship);
            while (relationship.next()) {
                if (relationship.neighbourNodeReference()==other) {
                    return true;
                }
            }
            return false;
        }
    }

    private boolean connectedDense(NodeCursor start, NodeCursor end, int[][] typedDirections) {
        List degrees = new ArrayList<>(32);

        Read read = ktx.dataRead();

        // TODO: for 4.1 RelationshipGroupCursor, Read#relationshipGroups and Read#relationships will be removed
        // Instead, relationships can be selected by type/direction criteria using the NodeCursor#relationships(RelationshipTraversalCursor,RelationshipSelection) method and degrees using NodeCursor#degrees/#degree
        try (RelationshipGroupCursor relationshipGroup = ktx.cursors().allocateRelationshipGroupCursor()) {
            addDegreesForNode(read, start, end, degrees, relationshipGroup, typedDirections);
            addDegreesForNode(read, end, start, degrees, relationshipGroup, typedDirections);
        }


        Collections.sort(degrees);
        try (RelationshipTraversalCursor relationship = ktx.cursors().allocateRelationshipTraversalCursor()) {
            for (Degree degree : degrees) {
                if (degree.isConnected(ktx.dataRead(), relationship)) return true;
            }
            return false;
        }
    }

    private void addDegreesForNode(Read dataRead, NodeCursor node, NodeCursor other, List degrees, RelationshipGroupCursor relationshipGroup, int[][] typedDirections) {
        long nodeId = node.nodeReference();
        long otherId = other.nodeReference();

        dataRead.relationshipGroups(nodeId, node.relationshipGroupReference(), relationshipGroup);
        while (relationshipGroup.next()) {
            int type = relationshipGroup.type();
            if ((typedDirections==null) || (arrayContains(typedDirections[0], type))) {
                addDegreeWithDirection(degrees, relationshipGroup.outgoingReference(), relationshipGroup.outgoingCount(), nodeId, otherId);
            }

            if ((typedDirections==null) || (arrayContains(typedDirections[1], type))) {
                addDegreeWithDirection(degrees, relationshipGroup.incomingReference(), relationshipGroup.incomingCount(), nodeId, otherId);
            }
        }
    }

    private void addDegreeWithDirection(List degrees, long relationshipGroup, int degree, long nodeId, long otherId) {
        if (degree > 0 ) {
            degrees.add(new Degree(nodeId, relationshipGroup, degree, otherId));
        }
    }

    @UserFunction("apoc.node.labels")
    @Description("returns labels for (virtual) nodes")
    public List labels(@Name("node") Node node) {
        if (node == null) return null;
        Iterator




© 2015 - 2024 Weber Informatics LLC | Privacy Policy