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

apoc.meta.Meta Maven / Gradle / Ivy

The 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.meta;

import static apoc.util.MapUtil.map;
import static java.lang.String.format;
import static org.neo4j.internal.kernel.api.TokenRead.ANY_LABEL;
import static org.neo4j.internal.kernel.api.TokenRead.ANY_RELATIONSHIP_TYPE;

import apoc.export.util.NodesAndRelsSubGraph;
import apoc.result.GraphResult;
import apoc.result.MapResult;
import apoc.result.VirtualGraph;
import apoc.result.VirtualNode;
import apoc.result.VirtualRelationship;
import apoc.util.CollectionUtils;
import apoc.util.MapUtil;
import apoc.util.collection.Iterables;
import com.google.common.collect.Sets;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.apache.commons.lang3.tuple.Pair;
import org.neo4j.cypher.export.CypherResultSubGraph;
import org.neo4j.cypher.export.DatabaseSubGraph;
import org.neo4j.cypher.export.SubGraph;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.Entity;
import org.neo4j.graphdb.Label;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
import org.neo4j.graphdb.ResourceIterator;
import org.neo4j.graphdb.Result;
import org.neo4j.graphdb.Transaction;
import org.neo4j.graphdb.schema.ConstraintDefinition;
import org.neo4j.graphdb.schema.ConstraintType;
import org.neo4j.graphdb.schema.IndexDefinition;
import org.neo4j.graphdb.schema.Schema;
import org.neo4j.internal.kernel.api.Read;
import org.neo4j.internal.kernel.api.TokenRead;
import org.neo4j.kernel.api.KernelTransaction;
import org.neo4j.logging.Log;
import org.neo4j.procedure.Context;
import org.neo4j.procedure.Description;
import org.neo4j.procedure.Name;
import org.neo4j.procedure.NotThreadSafe;
import org.neo4j.procedure.Procedure;
import org.neo4j.procedure.UserFunction;

/**
 * The Meta class provides metadata-related operations and functions for working with Neo4j graph database.
 * It is part of the APOC (Awesome Procedures on Cypher) library.
 */
public class Meta {
    private record MetadataKey(Types type, String key) {}

    @Context
    public Transaction tx;

    @Context
    public KernelTransaction kernelTx;

    @Context
    public Transaction transaction;

    @Context
    public Log log;

    /**
     * Represents the result of a metadata operation.
     */
    public static class MetaResult {
        public String label;
        public String property;
        public long count;
        public boolean unique;
        public boolean index;
        public boolean existence;
        public String type;
        public boolean array;
        public List sample;
        public long left; // 0,1,
        public long right; // 0,1,many
        public List other = new ArrayList<>();
        public List otherLabels = new ArrayList<>();
        public String elementType;
    }

    /**
     * Represents a specific metadata item, extending MetaResult.
     */
    public static class MetaItem extends MetaResult {
        public long leftCount; // 0,1,
        public long rightCount; // 0,1,many

        public MetaItem addLabel(String label) {
            this.otherLabels.add(label);
            return this;
        }

        public MetaItem(String label, String name) {
            this.label = label;
            this.property = name;
        }

        public MetaItem inc() {
            count++;
            return this;
        }

        public MetaItem rel(long out, long in) {
            this.type = Types.RELATIONSHIP.name();
            if (out > 1) array = true;
            leftCount += out;
            rightCount += in;
            left = leftCount / count;
            right = rightCount / count;
            return this;
        }

        public MetaItem other(List labels) {
            for (String l : labels) {
                if (!this.other.contains(l)) this.other.add(l);
            }
            return this;
        }

        public MetaItem type(String type) {
            this.type = type;
            return this;
        }

        public MetaItem array(boolean array) {
            this.array = array;
            return this;
        }

        public MetaItem elementType(String elementType) {
            switch (elementType) {
                case "NODE":
                    this.elementType = "node";
                    break;
                case "RELATIONSHIP":
                    this.elementType = "relationship";
                    break;
            }
            return this;
        }
    }

    @UserFunction("apoc.meta.cypher.isType")
    @Description("Returns true if the given value matches the given type.")
    public boolean isTypeCypher(@Name("value") Object value, @Name("type") String type) {
        return type.equalsIgnoreCase(typeCypher(value));
    }

    @UserFunction("apoc.meta.cypher.type")
    @Description("Returns the type name of the given value.")
    public String typeCypher(@Name("value") Object value) {
        Types type = Types.of(value);

        switch (type) {
            case ANY: // TODO Check if it's necessary
                return value.getClass().getSimpleName();
            default:
                return type.toString();
        }
    }

    @UserFunction("apoc.meta.cypher.types")
    @Description("Returns a `MAP` containing the type names of the given values.")
    public Map typesCypher(@Name("props") Object target) {
        Map properties = Collections.emptyMap();
        if (target instanceof Node) properties = ((Node) target).getAllProperties();
        if (target instanceof Relationship) properties = ((Relationship) target).getAllProperties();
        if (target instanceof Map) {
            //noinspection unchecked
            properties = (Map) target;
        }

        Map result = new LinkedHashMap<>(properties.size());
        properties.forEach((key, value) -> {
            result.put(key, typeCypher(value));
        });

        return result;
    }

    /**
     * The MetaStats class represents metadata statistics collected from the transactional database.
     * It includes counts for labels, relationship types, property keys, nodes, relationships, and various maps for stats.
     */
    public static class MetaStats {
        public final long labelCount;
        public final long relTypeCount;
        public final long propertyKeyCount;
        public final long nodeCount;
        public final long relCount;
        public final Map labels;
        public final Map relTypes;
        public final Map relTypesCount;
        public final Map stats;

        /**
         * Constructs a MetaStats object with the provided metadata statistics.
         *
         * @param labelCount       The count of labels in the database.
         * @param relTypeCount     The count of relationship types in the database.
         * @param propertyKeyCount The count of property keys in the database.
         * @param nodeCount        The count of nodes in the database.
         * @param relCount         The count of relationships in the database.
         * @param labels           A map of label names and their corresponding counts.
         * @param relTypes         A map of relationship type names and their corresponding counts.
         * @param relTypesCount    A map of relationship type names and their total count.
         */
        public MetaStats(
                long labelCount,
                long relTypeCount,
                long propertyKeyCount,
                long nodeCount,
                long relCount,
                Map labels,
                Map relTypes,
                Map relTypesCount) {
            this.labelCount = labelCount;
            this.relTypeCount = relTypeCount;
            this.propertyKeyCount = propertyKeyCount;
            this.nodeCount = nodeCount;
            this.relCount = relCount;
            this.labels = labels;
            this.relTypes = relTypes;
            this.relTypesCount = relTypesCount;
            this.stats = map(
                    "labelCount",
                    labelCount,
                    "relTypeCount",
                    relTypeCount,
                    "propertyKeyCount",
                    propertyKeyCount,
                    "nodeCount",
                    nodeCount,
                    "relCount",
                    relCount,
                    "labels",
                    labels,
                    "relTypes",
                    relTypes);
        }
    }

    /**
     * The StatsCallback interface defines callback methods for collecting label and relationship statistics.
     */
    interface StatsCallback {
        void label(String labelName, long count);

        void rel(String typeName, long count);

        void rel(String typeName, String labelName, long out, long in);
    }

    @NotThreadSafe
    @Procedure("apoc.meta.stats")
    @Description("Returns the metadata stored in the transactional database statistics.")
    public Stream stats() {
        return Stream.of(collectStats());
    }

    @NotThreadSafe
    @UserFunction(name = "apoc.meta.nodes.count")
    @Description("Returns the sum of the `NODE` values with the given labels in the `LIST`.")
    public long count(
            @Name(value = "nodes", defaultValue = "[]") List nodes,
            @Name(value = "config", defaultValue = "{}") Map config) {
        MetaConfig conf = new MetaConfig(config);
        final var subGraph = DatabaseSubGraph.optimizedForCount(transaction, kernelTx);
        Stream