ai.grakn.graql.internal.gremlin.fragment.Fragment.java Source code

Java tutorial

Introduction

Here is the source code for ai.grakn.graql.internal.gremlin.fragment.Fragment.java

Source

/*
 * Grakn - A Distributed Semantic Database
 * Copyright (C) 2016  Grakn Labs Limited
 *
 * Grakn is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Grakn is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Grakn. If not, see <http://www.gnu.org/licenses/gpl.txt>.
 */

package ai.grakn.graql.internal.gremlin.fragment;

import ai.grakn.GraknTx;
import ai.grakn.concept.AttributeType;
import ai.grakn.graql.Var;
import ai.grakn.graql.admin.VarProperty;
import ai.grakn.graql.internal.gremlin.spanningtree.graph.DirectedEdge;
import ai.grakn.graql.internal.gremlin.spanningtree.graph.Node;
import ai.grakn.graql.internal.gremlin.spanningtree.graph.NodeId;
import ai.grakn.graql.internal.gremlin.spanningtree.util.Weighted;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import org.apache.tinkerpop.gremlin.process.traversal.P;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
import org.apache.tinkerpop.gremlin.structure.Element;

import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import static ai.grakn.graql.internal.gremlin.spanningtree.util.Weighted.weighted;

/**
 * represents a graph traversal, with one start point and optionally an end point
 * <p>
 * A fragment is composed of four things:
 * <ul>
 * <li>A gremlin traversal function, that takes a gremlin traversal and appends some new gremlin steps</li>
 * <li>A starting variable name, where the gremlin traversal must start from</li>
 * <li>An optional ending variable name, if the gremlin traversal navigates to a new Graql variable</li>
 * <li>A priority, that describes how efficient this traversal is to help with ordering the traversals</li>
 * </ul>
 * <p>
 * Variable names refer to Graql variables. Some of these variable names may be randomly-generated UUIDs, such as for
 * castings.
 * <p>
 * A {@code Fragment} is usually contained in a {@code EquivalentFragmentSet}, which contains multiple fragments describing
 * the different directions the traversal can be followed in, with different starts and ends.
 * <p>
 * A gremlin traversal is created from a {@code Query} by appending together fragments in order of priority, one from
 * each {@code EquivalentFragmentSet} describing the {@code Query}.
 *
 * @author Felix Chapman
 */
public abstract class Fragment {

    // TODO: Find a better way to represent these values (either abstractly, or better estimates)

    private static final long NUM_INSTANCES_PER_TYPE = 100;
    private static final long NUM_SUBTYPES_PER_TYPE = 3;
    private static final long NUM_RELATIONS_PER_INSTANCE = 30;
    private static final long NUM_TYPES_PER_ROLE = 3;
    private static final long NUM_ROLES_PER_TYPE = 3;
    private static final long NUM_ROLE_PLAYERS_PER_RELATION = 2;
    private static final long NUM_ROLE_PLAYERS_PER_ROLE = 1;
    private static final long NUM_RESOURCES_PER_VALUE = 2;

    static final double COST_INSTANCES_PER_TYPE = Math.log1p(NUM_INSTANCES_PER_TYPE);
    static final double COST_SUBTYPES_PER_TYPE = Math.log1p(NUM_SUBTYPES_PER_TYPE);
    static final double COST_RELATIONS_PER_INSTANCE = Math.log1p(NUM_RELATIONS_PER_INSTANCE);
    static final double COST_TYPES_PER_ROLE = Math.log1p(NUM_TYPES_PER_ROLE);
    static final double COST_ROLES_PER_TYPE = Math.log1p(NUM_ROLES_PER_TYPE);
    static final double COST_ROLE_PLAYERS_PER_RELATION = Math.log1p(NUM_ROLE_PLAYERS_PER_RELATION);
    static final double COST_ROLE_PLAYERS_PER_ROLE = Math.log1p(NUM_ROLE_PLAYERS_PER_ROLE);

    static final double COST_SAME_AS_PREVIOUS = Math.log1p(1);

    static final double COST_NODE_INDEX = -Math.log(NUM_INSTANCES_PER_TYPE);
    static final double COST_NODE_INDEX_VALUE = -Math.log(NUM_INSTANCES_PER_TYPE / NUM_RESOURCES_PER_VALUE);

    static final double COST_NODE_NEQ = -Math.log(2D);
    static final double COST_NODE_DATA_TYPE = -Math.log(AttributeType.DataType.SUPPORTED_TYPES.size() / 2D);
    static final double COST_NODE_UNSPECIFIC_PREDICATE = -Math.log(2D);
    static final double COST_NODE_REGEX = -Math.log(2D);
    static final double COST_NODE_NOT_INTERNAL = -Math.log(1.1D);
    static final double COST_NODE_IS_ABSTRACT = -Math.log(1.1D);

    /**
     * Get the corresponding property
     */
    public abstract @Nullable VarProperty varProperty();

    /**
     * @return the variable name that this fragment starts from in the query
     */
    public abstract Var start();

    /**
     * @return the variable name that this fragment ends at in the query, if this query has an end variable
     */
    public @Nullable Var end() {
        return null;
    }

    ImmutableSet<Var> otherVars() {
        return ImmutableSet.of();
    }

    /**
     * @return the variable names that this fragment requires to have already been visited
     */
    public Set<Var> dependencies() {
        return ImmutableSet.of();
    }

    /**
     * Convert the fragment to a set of weighted edges for query planning
     *
     * @param nodes all nodes in the query
     * @param edges a mapping from edge(child, parent) to its corresponding fragment
     * @return a set of edges
     */
    public Set<Weighted<DirectedEdge<Node>>> directedEdges(Map<NodeId, Node> nodes,
            Map<Node, Map<Node, Fragment>> edges) {
        return Collections.emptySet();
    }

    /**
     * @param traversal the traversal to extend with this Fragment
     * @param graph     the graph to execute the traversal on
     */
    public final GraphTraversal<Element, ? extends Element> applyTraversal(
            GraphTraversal<Element, ? extends Element> traversal, GraknTx graph, Collection<Var> vars,
            @Nullable Var currentVar) {
        if (currentVar != null) {
            if (!currentVar.equals(start())) {
                if (vars.contains(start())) {
                    // If the variable name has been visited but the traversal is not at that variable name, select it
                    traversal.select(start().getValue());
                } else {
                    // Restart traversal when fragments are disconnected
                    traversal.V().as(start().getValue());
                }
            }
        } else {
            // If the variable name has not been visited yet, remember it and use the 'as' step
            traversal.as(start().getValue());
        }

        vars.add(start());

        traversal = applyTraversalInner(traversal, graph, vars);

        Var end = end();
        if (end != null) {
            assignVar(traversal, end, vars);
        }

        vars.addAll(vars());

        return traversal;
    }

    static <T, U> GraphTraversal<T, U> assignVar(GraphTraversal<T, U> traversal, Var var, Collection<Var> vars) {
        if (!vars.contains(var)) {
            // This variable name has not been encountered before, remember it and use the 'as' step
            return traversal.as(var.getValue());
        } else {
            // This variable name has been encountered before, confirm it is the same
            return traversal.where(P.eq(var.getValue()));
        }
    }

    /**
     * @param traversal the traversal to extend with this Fragment
     * @param graph     the graph to execute the traversal on
     * @param vars
     */
    abstract GraphTraversal<Element, ? extends Element> applyTraversalInner(
            GraphTraversal<Element, ? extends Element> traversal, GraknTx graph, Collection<Var> vars);

    /**
     * The name of the fragment
     */
    public abstract String name();

    /**
     * A starting fragment is a fragment that can start a traversal.
     * If any other fragment is present that refers to the same variable, the starting fragment can be omitted.
     */
    public boolean isStartingFragment() {
        return false;
    }

    /**
     * Get the cost for executing the fragment.
     */
    public abstract double fragmentCost();

    /**
     * If a fragment has fixed cost, the traversal is done using index. This makes the fragment a good starting point.
     * A plan should always start with these fragments when possible.
     */
    public boolean hasFixedFragmentCost() {
        return false;
    }

    /**
     * Indicates whether the fragment can be used on an {@link org.apache.tinkerpop.gremlin.structure.Edge} as well as
     * a {@link org.apache.tinkerpop.gremlin.structure.Vertex}.
     */
    public boolean canOperateOnEdges() {
        return false;
    }

    /**
     * Get all variables in the fragment including the start and end (if present)
     */
    public final Set<Var> vars() {
        ImmutableSet.Builder<Var> builder = ImmutableSet.<Var>builder().add(start());
        Var end = end();
        if (end != null)
            builder.add(end);
        builder.addAll(otherVars());
        return builder.build();
    }

    @Override
    public final String toString() {
        return start() + name() + Optional.ofNullable(end()).map(Object::toString).orElse("");
    }

    final Set<Weighted<DirectedEdge<Node>>> directedEdges(NodeId.NodeType nodeType, Map<NodeId, Node> nodes,
            Map<Node, Map<Node, Fragment>> edgeToFragment) {

        Node start = Node.addIfAbsent(NodeId.NodeType.VAR, start(), nodes);
        Node end = Node.addIfAbsent(NodeId.NodeType.VAR, end(), nodes);
        Node middle = Node.addIfAbsent(nodeType, Sets.newHashSet(start(), end()), nodes);
        middle.setInvalidStartingPoint();

        addEdgeToFragmentMapping(middle, start, edgeToFragment);
        return Sets.newHashSet(weighted(DirectedEdge.from(start).to(middle), -fragmentCost()),
                weighted(DirectedEdge.from(middle).to(end), 0));
    }

    final Set<Weighted<DirectedEdge<Node>>> directedEdges(Var edge, Map<NodeId, Node> nodes,
            Map<Node, Map<Node, Fragment>> edgeToFragment) {

        Node start = Node.addIfAbsent(NodeId.NodeType.VAR, start(), nodes);
        Node end = Node.addIfAbsent(NodeId.NodeType.VAR, end(), nodes);
        Node middle = Node.addIfAbsent(NodeId.NodeType.VAR, edge, nodes);
        middle.setInvalidStartingPoint();

        addEdgeToFragmentMapping(middle, start, edgeToFragment);
        return Sets.newHashSet(weighted(DirectedEdge.from(start).to(middle), -fragmentCost()),
                weighted(DirectedEdge.from(middle).to(end), 0));
    }

    private void addEdgeToFragmentMapping(Node child, Node parent, Map<Node, Map<Node, Fragment>> edgeToFragment) {
        if (!edgeToFragment.containsKey(child)) {
            edgeToFragment.put(child, new HashMap<>());
        }
        edgeToFragment.get(child).put(parent, this);
    }
}