ai.grakn.graql.internal.query.QueryOperationExecutor.java Source code

Java tutorial

Introduction

Here is the source code for ai.grakn.graql.internal.query.QueryOperationExecutor.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.query;

import ai.grakn.GraknTx;
import ai.grakn.concept.Concept;
import ai.grakn.exception.GraqlQueryException;
import ai.grakn.graql.DefineQuery;
import ai.grakn.graql.InsertQuery;
import ai.grakn.graql.Query;
import ai.grakn.graql.Var;
import ai.grakn.graql.admin.Answer;
import ai.grakn.graql.admin.VarPatternAdmin;
import ai.grakn.graql.admin.VarProperty;
import ai.grakn.graql.internal.pattern.Patterns;
import ai.grakn.graql.internal.pattern.property.PropertyExecutor;
import ai.grakn.graql.internal.pattern.property.VarPropertyInternal;
import ai.grakn.graql.internal.util.Partition;
import com.google.auto.value.AutoValue;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Sets;

import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import java.util.stream.Stream;

import static ai.grakn.util.CommonUtil.toImmutableSet;
import static java.util.stream.Collectors.toList;

/**
 * A class for executing {@link PropertyExecutor}s on {@link VarProperty}s within {@link Query}s.
 *
 * <p>
 *     Multiple query types share this class, such as {@link InsertQuery} and {@link DefineQuery}.
 * </p>
 *
 * @author Felix Chapman
 */
public class QueryOperationExecutor {

    private final GraknTx tx;

    // A mutable map associating each `Var` to the `Concept` in the graph it refers to.
    private final Map<Var, Concept> concepts = new HashMap<>();

    // A mutable map of concepts "under construction" that require more information before they can be built
    private final Map<Var, ConceptBuilder> conceptBuilders = new HashMap<>();

    // An immutable set of all properties
    private final ImmutableSet<VarAndProperty> properties;

    // A partition (disjoint set) indicating which `Var`s should refer to the same concept
    private final Partition<Var> equivalentVars;

    // A map, where `dependencies.containsEntry(x, y)` implies that `y` must be inserted before `x` is inserted.
    private final ImmutableMultimap<VarAndProperty, VarAndProperty> dependencies;

    // The method that is applied on every `VarProperty`
    private final ExecutionType executionType;

    private QueryOperationExecutor(GraknTx tx, ImmutableSet<VarAndProperty> properties,
            Partition<Var> equivalentVars, ImmutableMultimap<VarAndProperty, VarAndProperty> dependencies,
            ExecutionType executionType) {
        this.tx = tx;
        this.properties = properties;
        this.equivalentVars = equivalentVars;
        this.dependencies = dependencies;
        this.executionType = executionType;
    }

    /**
     * Insert all the Vars
     */
    static Answer insertAll(Collection<VarPatternAdmin> patterns, GraknTx graph) {
        return create(patterns, graph, ExecutionType.INSERT).insertAll(new QueryAnswer());
    }

    /**
     * Insert all the Vars
     * @param results the result after inserting
     */
    static Answer insertAll(Collection<VarPatternAdmin> patterns, GraknTx graph, Answer results) {
        return create(patterns, graph, ExecutionType.INSERT).insertAll(results);
    }

    static Answer defineAll(Collection<VarPatternAdmin> patterns, GraknTx graph) {
        return create(patterns, graph, ExecutionType.DEFINE).insertAll(new QueryAnswer());
    }

    static void undefineAll(ImmutableList<VarPatternAdmin> patterns, GraknTx tx) {
        create(patterns, tx, ExecutionType.UNDEFINE).insertAll(new QueryAnswer());
    }

    private static QueryOperationExecutor create(Collection<VarPatternAdmin> patterns, GraknTx graph,
            ExecutionType executionType) {
        ImmutableSet<VarAndProperty> properties = patterns.stream().flatMap(VarAndProperty::fromPattern)
                .collect(toImmutableSet());

        /*
        We build several many-to-many relations, indicated by a `Multimap<X, Y>`. These are used to represent
        the dependencies between properties and variables.
            
        `propDependencies.containsEntry(prop, var)` indicates that the property `prop` cannot be inserted until
        the concept represented by the variable `var` is created.
            
        For example, the property `$x isa $y` depends on the existence of the concept represented by `$y`.
         */
        Multimap<VarAndProperty, Var> propDependencies = HashMultimap.create();

        for (VarAndProperty property : properties) {
            for (Var requiredVar : property.executor(executionType).requiredVars()) {
                propDependencies.put(property, requiredVar);
            }
        }

        /*
        `varDependencies.containsEntry(var, prop)` indicates that the concept represented by the variable `var`
        cannot be created until the property `prop` is inserted.
            
        For example, the concept represented by `$x` will not exist before the property `$x isa $y` is inserted.
         */
        Multimap<Var, VarAndProperty> varDependencies = HashMultimap.create();

        for (VarAndProperty property : properties) {
            for (Var producedVar : property.executor(executionType).producedVars()) {
                varDependencies.put(producedVar, property);
            }
        }

        /*
        Equivalent vars are variables that must represent the same concept as another var.
            
             $X label movie, sub entity;
             $Y label movie;
             $z isa $Y;
            
        In this example, `$z isa $Y` must not be inserted before `$Y` is. However, `$Y` does not have enough
        information to insert on its own. It also needs a super type!
            
        We know `$Y` must represent the same concept as `$X`, because they both share the same label property.
        Therefore, we can share their dependencies, such that:
            
            varDependencies.containsEntry($X, prop) <=> varDependencies.containsEntry($Y, prop)
            
        Therefore:
            
            varDependencies.containsEntry($X, `$X sub entity`) => varDependencies.containsEntry($Y, `$X sub entity`)
            
        Now we know that `$Y` depends on `$X sub entity` as well as `$X label movie`, which is enough information to
        insert the type!
         */

        Partition<Var> equivalentVars = Partition.singletons(Collections.emptyList());

        equivalentProperties(properties).asMap().values().forEach(vars -> {
            // These vars must refer to the same concept, so share their dependencies
            Collection<VarAndProperty> producers = vars.stream().flatMap(var -> varDependencies.get(var).stream())
                    .collect(toList());

            Var first = vars.iterator().next();

            vars.forEach(var -> {
                varDependencies.replaceValues(var, producers);
                equivalentVars.merge(first, var);
            });
        });

        /*
        Together, `propDependencies` and `varDependencies` can be composed into a single many-to-many relation:
            
            dependencies = propDependencies  varDependencies
            
        By doing so, we map _directly_ between properties, skipping the vars. For example, if we previously had:
            
            propDependencies.containsEntry(`$x isa $y`, `$y`);         // `$x isa $y` depends on `$y`
            varDependencies.containsEntry(`$y`, `$y label movie`);     // `$y` depends on `$y label movie`
            
        Then it follows that:
            
            dependencies.containsEntry(`$x isa $y`, `$y label movie`); // `$x isa $y` depends on `$y label movie`
            
        The `dependencies` relation contains all the information to decide what order to execute the properties.
         */
        Multimap<VarAndProperty, VarAndProperty> dependencies = composeMultimaps(propDependencies, varDependencies);

        return new QueryOperationExecutor(graph, properties, equivalentVars, ImmutableMultimap.copyOf(dependencies),
                executionType);
    }

    private static Multimap<VarProperty, Var> equivalentProperties(Set<VarAndProperty> properties) {
        Multimap<VarProperty, Var> equivalentProperties = HashMultimap.create();

        for (VarAndProperty varAndProperty : properties) {
            if (varAndProperty.uniquelyIdentifiesConcept()) {
                equivalentProperties.put(varAndProperty.property(), varAndProperty.var());
            }
        }

        return equivalentProperties;
    }

    /**
     * <a href=https://en.wikipedia.org/wiki/Composition_of_relations>Compose</a> two {@link Multimap}s together,
     * treating them like many-to-many relations.
     */
    private static <K, T, V> Multimap<K, V> composeMultimaps(Multimap<K, T> map1, Multimap<T, V> map2) {
        Multimap<K, V> composed = HashMultimap.create();

        for (Map.Entry<K, T> entry1 : map1.entries()) {
            K key = entry1.getKey();
            T intermediateValue = entry1.getValue();

            for (V value : map2.get(intermediateValue)) {
                composed.put(key, value);
            }
        }

        return composed;
    }

    private Answer insertAll(Answer results) {
        concepts.putAll(results.map());

        sortProperties().forEach(property -> property.executor(executionType).execute(this));

        conceptBuilders.forEach((var, builder) -> concepts.put(var, builder.build()));

        ImmutableMap.Builder<Var, Concept> allConcepts = ImmutableMap.<Var, Concept>builder().putAll(concepts);

        // Make sure to include all equivalent vars in the result
        for (Var var : equivalentVars.getNodes()) {
            allConcepts.put(var, concepts.get(equivalentVars.componentOf(var)));
        }

        Map<Var, Concept> namedConcepts = Maps.filterKeys(allConcepts.build(), Var::isUserDefinedName);
        return new QueryAnswer(namedConcepts);
    }

    /**
     * Produce a valid ordering of the properties by using the given dependency information.
     *
     * <p>
     *     This method uses a topological sort (Kahn's algorithm) in order to find a valid ordering.
     * </p>
     */
    private ImmutableList<VarAndProperty> sortProperties() {
        ImmutableList.Builder<VarAndProperty> sorted = ImmutableList.builder();

        // invertedDependencies is intended to just be a 'view' on dependencies, so when dependencies is modified
        // we should always also modify invertedDependencies (and vice-versa).
        Multimap<VarAndProperty, VarAndProperty> dependencies = HashMultimap.create(this.dependencies);
        Multimap<VarAndProperty, VarAndProperty> invertedDependencies = HashMultimap.create();
        Multimaps.invertFrom(dependencies, invertedDependencies);

        Queue<VarAndProperty> propertiesWithoutDependencies = new ArrayDeque<>(
                Sets.filter(properties, property -> dependencies.get(property).isEmpty()));

        VarAndProperty property;

        // Retrieve the next property without any dependencies
        while ((property = propertiesWithoutDependencies.poll()) != null) {
            sorted.add(property);

            // We copy this into a new list because the underlying collection gets modified during iteration
            Collection<VarAndProperty> dependents = Lists.newArrayList(invertedDependencies.get(property));

            for (VarAndProperty dependent : dependents) {
                // Because the property has been removed, the dependent no longer needs to depend on it
                dependencies.remove(dependent, property);
                invertedDependencies.remove(property, dependent);

                boolean hasNoDependencies = dependencies.get(dependent).isEmpty();

                if (hasNoDependencies) {
                    propertiesWithoutDependencies.add(dependent);
                }
            }
        }

        if (!dependencies.isEmpty()) {
            // This means there must have been a loop. Pick an arbitrary remaining var to display
            Var var = dependencies.keys().iterator().next().var();
            throw GraqlQueryException.insertRecursive(printableRepresentation(var));
        }

        return sorted.build();
    }

    /**
     * Return a {@link ConceptBuilder} for given {@link Var}. This can be used to provide information for how to create
     * the concept that the variable represents.
     *
     * <p>
     * This method is expected to be called from implementations of
     * {@link VarPropertyInternal#insert(Var)}, provided they return the given {@link Var} in the
     * response to {@link PropertyExecutor#producedVars()}.
     * </p>
     * <p>
     * For example, a property may call {@code executor.builder(var).isa(type);} in order to provide a type for a var.
     * </p>
     *
     * @throws GraqlQueryException if the concept in question has already been created
     */
    public ConceptBuilder builder(Var var) {
        return tryBuilder(var).orElseThrow(() -> {
            Concept concept = concepts.get(equivalentVars.componentOf(var));
            return GraqlQueryException.insertExistingConcept(printableRepresentation(var), concept);
        });
    }

    /**
     * Return a {@link ConceptBuilder} for given {@link Var}. This can be used to provide information for how to create
     * the concept that the variable represents.
     *
     * <p>
     * This method is expected to be called from implementations of
     * {@link VarPropertyInternal#insert(Var)}, provided they return the given {@link Var} in the
     * response to {@link PropertyExecutor#producedVars()}.
     * </p>
     * <p>
     * For example, a property may call {@code executor.builder(var).isa(type);} in order to provide a type for a var.
     * </p>
     * <p>
     *     If the concept has already been created, this will return empty.
     * </p>
     */
    public Optional<ConceptBuilder> tryBuilder(Var var) {
        var = equivalentVars.componentOf(var);

        if (concepts.containsKey(var)) {
            return Optional.empty();
        }

        ConceptBuilder builder = conceptBuilders.get(var);

        if (builder != null) {
            return Optional.of(builder);
        }

        builder = ConceptBuilder.of(this, var);
        conceptBuilders.put(var, builder);
        return Optional.of(builder);
    }

    /**
     * Return a {@link Concept} for a given {@link Var}.
     *
     * <p>
     * This method is expected to be called from implementations of
     * {@link VarPropertyInternal#insert(Var)}, provided they return the given {@link Var} in the
     * response to {@link PropertyExecutor#requiredVars()}.
     * </p>
     */
    public Concept get(Var var) {
        var = equivalentVars.componentOf(var);

        Concept concept = concepts.get(var);

        if (concept == null) {
            ConceptBuilder builder = conceptBuilders.remove(var);

            if (builder != null) {
                concept = builder.build();
                concepts.put(var, concept);
            }
        }

        if (concept != null) {
            return concept;
        }

        throw GraqlQueryException.insertUndefinedVariable(printableRepresentation(var));
    }

    VarPatternAdmin printableRepresentation(Var var) {
        ImmutableSet.Builder<VarProperty> propertiesOfVar = ImmutableSet.builder();

        // This could be faster if we built a dedicated map Var -> VarPattern
        // However, this method is only used for displaying errors, so it's not worth the cost
        for (VarAndProperty vp : properties) {
            if (vp.var().equals(var)) {
                propertiesOfVar.add(vp.property());
            }
        }

        return Patterns.varPattern(var, propertiesOfVar.build());
    }

    GraknTx tx() {
        return tx;
    }

    /**
     * Represents a pairing of a {@link VarProperty} and its subject {@link Var}.
     * <p>
     *     e.g. {@code $x} and {@code isa $y}, together are {@code $x isa $y}.
     * </p>
     */
    @AutoValue
    static abstract class VarAndProperty {

        abstract Var var();

        abstract VarPropertyInternal property();

        static VarAndProperty of(Var var, VarProperty property) {
            return new AutoValue_QueryOperationExecutor_VarAndProperty(var, VarPropertyInternal.from(property));
        }

        static Stream<VarAndProperty> fromPattern(VarPatternAdmin pattern) {
            return pattern.getProperties().map(prop -> VarAndProperty.of(pattern.var(), prop));
        }

        private PropertyExecutor executor(ExecutionType executionType) {
            return executionType.executor(property(), var());
        }

        boolean uniquelyIdentifiesConcept() {
            return property().uniquelyIdentifiesConcept();
        }
    }

    private enum ExecutionType {
        INSERT {
            PropertyExecutor executor(VarPropertyInternal property, Var var) {
                return property.insert(var);
            }
        },
        DEFINE {
            PropertyExecutor executor(VarPropertyInternal property, Var var) {
                return property.define(var);
            }
        },
        UNDEFINE {
            PropertyExecutor executor(VarPropertyInternal property, Var var) {
                return property.undefine(var);
            }
        };

        abstract PropertyExecutor executor(VarPropertyInternal property, Var var);
    }
}