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

io.mindmaps.graql.internal.gremlin.ConjunctionQuery Maven / Gradle / Ivy

/*
 * MindmapsDB - A Distributed Semantic Database
 * Copyright (C) 2016  Mindmaps Research Ltd
 *
 * MindmapsDB 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.
 *
 * MindmapsDB 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 MindmapsDB. If not, see .
 */

package io.mindmaps.graql.internal.gremlin;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import io.mindmaps.MindmapsGraph;
import io.mindmaps.graql.admin.Conjunction;
import io.mindmaps.graql.admin.VarAdmin;
import io.mindmaps.util.ErrorMessage;
import org.apache.tinkerpop.gremlin.process.traversal.P;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
import org.apache.tinkerpop.gremlin.structure.Vertex;

import java.util.*;
import java.util.stream.Stream;

import static io.mindmaps.graql.internal.util.CommonUtil.toImmutableSet;
import static java.util.Comparator.naturalOrder;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toSet;

/**
 * A query that does not contain any disjunctions, so it can be represented as a single gremlin traversal.
 * 

* The {@code ConjunctionQuery} is passed a {@code Pattern.Conjunction}. A {@code VarTraversal} can be * extracted from each {@code Var} and {@code MultiTraversals} can be extracted from each {@code VarTraversal}. *

* The {@code MultiTraversals} are sorted to produce a set of lists of {@code Fragments}. Each list of fragments * describes a connected component in the query. Most queries are completely connected, so there will be only one * list of fragments in the set. If the query is disconnected (e.g. match $x isa movie, $y isa person), then there * will be multiple lists of fragments in the set. *

* A gremlin traversal is created by concatenating the traversals within each fragment. */ class ConjunctionQuery { private final Set vars; private final ImmutableSet multiTraversals; private final Set> fragments; private final MindmapsGraph graph; /** * @param patternConjunction a pattern containing no disjunctions to find in the graph */ ConjunctionQuery(MindmapsGraph graph, Conjunction patternConjunction) { this.graph = graph; vars = patternConjunction.getPatterns(); if (vars.size() == 0) { throw new IllegalArgumentException(ErrorMessage.MATCH_NO_PATTERNS.getMessage()); } this.multiTraversals = vars.stream() .map(VarTraversals::new) .flatMap(VarTraversals::getTraversals) .collect(toImmutableSet()); this.fragments = sortedFragments(); } /** * @return a gremlin traversal that represents this inner query */ GraphTraversal> getTraversal() { GraphTraversal traversal = graph.getTinkerTraversal(); Set foundNames = new HashSet<>(); Iterator> fragmentIterator = fragments.iterator(); // Apply fragments in order into one single traversal while (fragmentIterator.hasNext()) { String currentName = null; List fragmentList = fragmentIterator.next(); for (Fragment fragment : fragmentList) { applyFragment(fragment, traversal, currentName, foundNames); currentName = fragment.getEnd().orElse(fragment.getStart()); } // Restart traversal for each connected component if (fragmentIterator.hasNext()) traversal = traversal.V(); } // Select all the variable names String[] traversalNames = foundNames.toArray(new String[foundNames.size()]); return traversal.select(traversalNames[0], traversalNames[0], traversalNames); } /** * Apply the given fragment to the traversal. Keeps track of variable names so far so that it can decide whether * to use "as" or "select" steps in gremlin. * @param fragment the fragment to apply to the traversal * @param traversal the gremlin traversal to apply the fragment to * @param currentName the variable name that the traversal is currently at * @param names a set of variable names so far encountered in the query */ private void applyFragment( Fragment fragment, GraphTraversal traversal, String currentName, Set names ) { String start = fragment.getStart(); if (currentName != null) { if (!currentName.equals(start)) { // If the variable name has been visited but the traversal is not at that variable name, select it traversal.select(start); } } else { // If the variable name has not been visited yet, remember it and use the 'as' step names.add(start); traversal.as(start); } // Apply fragment to traversal fragment.applyTraversal(traversal); fragment.getEnd().ifPresent(end -> { if (!names.contains(end)) { // This variable name has not been encountered before, remember it and use the 'as' step names.add(end); traversal.as(end); } else { // This variable name has been encountered before, confirm it is the same traversal.where(P.eq(end)); } }); } /** * @return a stream of concept IDs mentioned in the query */ Stream getConcepts() { return vars.stream() .flatMap(v -> v.getInnerVars().stream()) .flatMap(v -> v.getTypeIds().stream()); } /** * Sort the fragments describing the query, such that every property is represented in the fragments and the * fragments are ordered by priority in order to perform the query quickly. * * There will be one list of fragments for every connected component of the query. * * @return a set of list of fragments, sorted by priority. */ private Set> sortedFragments() { // Sort fragments using a topological sort, such that each fragment leads to the next. // fragments are also sorted by "priority" to improve the performance of the search // Maintain a map of fragments grouped by starting variable for fast lookup Map> fragmentMap = getFragments().collect(groupingBy(Fragment::getStart, toSet())); // Track properties and fragments that have been used Set remainingFragments = getFragments().collect(toSet()); Set remainingTraversals = Sets.newHashSet(multiTraversals); Set matchedTraversals = new HashSet<>(); // Result set of fragments (one entry in the set for each connected part of the query) Set> allSortedFragments = new HashSet<>(); while (!remainingTraversals.isEmpty()) { // Traversal is started from the highest priority fragment Optional optionalFragment = remainingFragments.stream().min(naturalOrder()); Fragment highestFragment = optionalFragment.orElseThrow( () -> new RuntimeException(ErrorMessage.FAILED_TO_BUILD_TRAVERSAL.getMessage()) ); String start = highestFragment.getStart(); // A queue of reachable fragments, with the highest priority fragments always on top PriorityQueue reachableFragments = new PriorityQueue<>(fragmentMap.get(start)); List sortedFragments = new ArrayList<>(); while (!reachableFragments.isEmpty()) { // Take highest priority fragment from reachable fragments Fragment fragment = reachableFragments.poll(); MultiTraversal multiTraversal = fragment.getMultiTraversal(); // Only choose one fragment from each pattern if (matchedTraversals.contains(multiTraversal)) continue; remainingFragments.remove(fragment); remainingTraversals.remove(multiTraversal); matchedTraversals.add(multiTraversal); sortedFragments.add(fragment); // If the fragment has a variable at the end, then fragments starting at that variable are reachable fragment.getEnd().ifPresent( end -> { Set fragments = fragmentMap.remove(end); if (fragments != null) reachableFragments.addAll(fragments); } ); } allSortedFragments.add(sortedFragments); } return allSortedFragments; } /** * @return a stream of Fragments in this query */ private Stream getFragments() { return multiTraversals.stream().flatMap(MultiTraversal::getFragments); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy