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

apoc.meta.Meta Maven / Gradle / Ivy

There is a newer version: 5.25.1
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.meta;

import static apoc.util.MapUtil.map;
import static java.lang.String.format;
import static org.neo4j.token.api.TokenConstants.ANY_LABEL;
import static org.neo4j.token.api.TokenConstants.ANY_RELATIONSHIP_TYPE;

import apoc.export.util.NodesAndRelsSubGraph;
import apoc.result.GraphResult;
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 {
        @Description("The label or type name.")
        public String label;

        @Description("The property name.")
        public String property;

        @Description("The count of seen values.")
        public long count;

        @Description("If all seen values are unique.")
        public boolean unique;

        @Description("If an index exists for this property.")
        public boolean index;

        @Description("If an existence constraint exists for this property.")
        public boolean existence;

        @Description("The type represented by this row.")
        public String type;

        @Description(
                "Indicates whether the property is an array. If the type column is \"RELATIONSHIP,\" this will be true if there is at least one node with two outgoing relationships of the type specified by the label or property column.")
        public boolean array;

        @Description("This is always null.")
        public List sample;

        @Description(
                "The ratio (rounded down) of the count of outgoing relationships for a specific label and relationship type relative to the total count of those patterns.")
        public long left; // 0,1,

        @Description(
                "The ratio (rounded down) of the count of incoming relationships for a specific label and relationship type relative to the total count of those patterns.")
        public long right; // 0,1,many

        @Description("The labels of connect nodes.")
        public List other = new ArrayList<>();

        @Description(
                "For uniqueness constraints, this field shows other labels present on nodes that also contain the uniqueness constraint.")
        public List otherLabels = new ArrayList<>();

        @Description("Whether this refers to a node or a relationship.")
        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 = "value", description = "An object to check the type of.") Object value,
            @Name(value = "type", description = "The verification 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 = "value", description = "An object to get the type of.") 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(value = "props", description = "A relationship, node or map to get the property types from.")
                    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 {
        @Description("The total number of distinct node labels.")
        public final long labelCount;

        @Description("The total number of distinct relationship types.")
        public final long relTypeCount;

        @Description("The count of property keys.")
        public final long propertyKeyCount;

        @Description("The total number of nodes.")
        public final long nodeCount;

        @Description("The total number of relationships.")
        public final long relCount;

        @Description("A map of labels to their count.")
        public final Map labels;

        @Description("A map of relationship types per start or end node label.")
        public final Map relTypes;

        @Description("A map of relationship types to their count.")
        public final Map relTypesCount;

        @Description("A map containing all the given return fields from this procedure.")
        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 = "[]", description = "A list of node labels.") List nodes,
            @Name(
                            value = "config",
                            defaultValue = "{}",
                            description =
                                    "A relationship, node or map to get the property types from. { includeRels = [] :: LIST }")
                    Map config) {
        MetaConfig conf = new MetaConfig(config);
        final var subGraph = DatabaseSubGraph.optimizedForCount(transaction, kernelTx);
        Stream