{"action":"created","comment":{"url":"https://api.github.com/repos/graknlabs/grakn/pulls/comments/166916971","pull_request_review_id":95050992,"id":166916971,"diff_hunk":"@@ -0,0 +1,683 @@\n+/*\n+ * Grakn - A Distributed Semantic Database\n+ * Copyright (C) 2016-2018 Grakn Labs Limited\n+ *\n+ * Grakn is free software: you can redistribute it and/or modify\n+ * it under the terms of the GNU Affero General Public License as published by\n+ * the Free Software Foundation, either version 3 of the License, or\n+ * (at your option) any later version.\n+ *\n+ * Grakn is distributed in the hope that it will be useful,\n+ * but WITHOUT ANY WARRANTY; without even the implied warranty of\n+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n+ * GNU General Public License for more details.\n+ *\n+ * You should have received a copy of the GNU General Public License\n+ * along with Grakn. If not, see <http://www.gnu.org/licenses/gpl.txt>.\n+ */\n+\n+package ai.grakn.graql.internal.query.runner;\n+\n+import ai.grakn.ComputeJob;\n+import ai.grakn.GraknComputer;\n+import ai.grakn.GraknTx;\n+import ai.grakn.concept.AttributeType;\n+import ai.grakn.concept.Concept;\n+import ai.grakn.concept.ConceptId;\n+import ai.grakn.concept.Label;\n+import ai.grakn.concept.LabelId;\n+import ai.grakn.concept.RelationshipType;\n+import ai.grakn.concept.Role;\n+import ai.grakn.concept.SchemaConcept;\n+import ai.grakn.concept.Thing;\n+import ai.grakn.concept.Type;\n+import ai.grakn.exception.GraqlQueryException;\n+import ai.grakn.graql.ComputeQuery;\n+import ai.grakn.graql.Graql;\n+import ai.grakn.graql.Pattern;\n+import ai.grakn.graql.StatisticsQuery;\n+import ai.grakn.graql.analytics.ClusterQuery;\n+import ai.grakn.graql.analytics.CorenessQuery;\n+import ai.grakn.graql.analytics.CountQuery;\n+import ai.grakn.graql.analytics.DegreeQuery;\n+import ai.grakn.graql.analytics.KCoreQuery;\n+import ai.grakn.graql.analytics.MaxQuery;\n+import ai.grakn.graql.analytics.MeanQuery;\n+import ai.grakn.graql.analytics.MedianQuery;\n+import ai.grakn.graql.analytics.MinQuery;\n+import ai.grakn.graql.analytics.PathQuery;\n+import ai.grakn.graql.analytics.PathsQuery;\n+import ai.grakn.graql.analytics.StdQuery;\n+import ai.grakn.graql.analytics.SumQuery;\n+import ai.grakn.graql.internal.analytics.ClusterMemberMapReduce;\n+import ai.grakn.graql.internal.analytics.ClusterSizeMapReduce;\n+import ai.grakn.graql.internal.analytics.ConnectedComponentVertexProgram;\n+import ai.grakn.graql.internal.analytics.ConnectedComponentsVertexProgram;\n+import ai.grakn.graql.internal.analytics.CorenessVertexProgram;\n+import ai.grakn.graql.internal.analytics.CountMapReduceWithAttribute;\n+import ai.grakn.graql.internal.analytics.CountVertexProgram;\n+import ai.grakn.graql.internal.analytics.DegreeDistributionMapReduce;\n+import ai.grakn.graql.internal.analytics.DegreeStatisticsVertexProgram;\n+import ai.grakn.graql.internal.analytics.DegreeVertexProgram;\n+import ai.grakn.graql.internal.analytics.GraknMapReduce;\n+import ai.grakn.graql.internal.analytics.GraknVertexProgram;\n+import ai.grakn.graql.internal.analytics.KCoreVertexProgram;\n+import ai.grakn.graql.internal.analytics.MaxMapReduce;\n+import ai.grakn.graql.internal.analytics.MeanMapReduce;\n+import ai.grakn.graql.internal.analytics.MedianVertexProgram;\n+import ai.grakn.graql.internal.analytics.MinMapReduce;\n+import ai.grakn.graql.internal.analytics.NoResultException;\n+import ai.grakn.graql.internal.analytics.ShortestPathVertexProgram;\n+import ai.grakn.graql.internal.analytics.StdMapReduce;\n+import ai.grakn.graql.internal.analytics.SumMapReduce;\n+import ai.grakn.graql.internal.analytics.Utility;\n+import ai.grakn.util.CommonUtil;\n+import ai.grakn.util.Schema;\n+import com.google.common.collect.HashMultimap;\n+import com.google.common.collect.ImmutableSet;\n+import com.google.common.collect.Multimap;\n+import com.google.common.collect.Sets;\n+import org.apache.tinkerpop.gremlin.process.computer.ComputerResult;\n+import org.apache.tinkerpop.gremlin.process.computer.MapReduce;\n+import org.apache.tinkerpop.gremlin.process.computer.Memory;\n+import org.slf4j.Logger;\n+import org.slf4j.LoggerFactory;\n+\n+import javax.annotation.Nullable;\n+import java.io.Serializable;\n+import java.util.ArrayDeque;\n+import java.util.ArrayList;\n+import java.util.Collection;\n+import java.util.Collections;\n+import java.util.Deque;\n+import java.util.Iterator;\n+import java.util.List;\n+import java.util.Map;\n+import java.util.Optional;\n+import java.util.Set;\n+import java.util.function.Function;\n+import java.util.stream.Collectors;\n+import java.util.stream.Stream;\n+\n+/**\n+ *\n+ *\n+ * @author Felix Chapman\n+ */\n+public class TinkerComputeQueryRunner {\n+    private static final Logger LOG = LoggerFactory.getLogger(TinkerComputeQueryRunner.class);\n+    private final GraknTx tx;\n+\n+    private TinkerComputeQueryRunner(GraknTx tx) {\n+        this.tx = tx;\n+    }\n+\n+    static TinkerComputeQueryRunner create(GraknTx tx) {\n+        return new TinkerComputeQueryRunner(tx);\n+    }\n+\n+    public <T> ComputeJob<T> run(ClusterQuery<T> query) {\n+        return runCompute(query, computer -> {\n+            if (!selectedTypesHaveInstance(query)) {\n+                LOG.info(\"Selected types don't have instances\");\n+                return (T) Collections.emptyMap();\n+            }\n+\n+            Set<LabelId> subLabelIds = convertLabelsToIds(subLabels(query));\n+\n+            GraknVertexProgram<?> vertexProgram;\n+            if (query.sourceId().isPresent()) {\n+                ConceptId conceptId = query.sourceId().get();\n+                if (!verticesExistInSubgraph(query, conceptId)) {\n+                    throw GraqlQueryException.instanceDoesNotExist();\n+                }\n+                vertexProgram = new ConnectedComponentVertexProgram(conceptId);\n+            } else {\n+                vertexProgram = new ConnectedComponentsVertexProgram();\n+            }\n+\n+            Long clusterSize = query.clusterSize();\n+\n+            GraknMapReduce<?> mapReduce;\n+            if (query.isMembersSet()) {\n+                mapReduce = new ClusterMemberMapReduce(ConnectedComponentsVertexProgram.CLUSTER_LABEL, clusterSize);\n+            } else {\n+                mapReduce = new ClusterSizeMapReduce(ConnectedComponentsVertexProgram.CLUSTER_LABEL, clusterSize);\n+            }\n+\n+            Memory memory = computer.compute(vertexProgram, mapReduce, subLabelIds).memory();\n+            return memory.get(mapReduce.getClass().getName());\n+        });\n+    }\n+\n+    public ComputeJob<Map<Long, Set<String>>> run(CorenessQuery query) {\n+        return runCompute(query, computer -> {\n+            long k = query.minK();\n+\n+            if (k < 2L) throw GraqlQueryException.kValueSmallerThanTwo();\n+\n+            Set<Label> ofLabels;\n+\n+            // Check if ofType is valid before returning emptyMap\n+            if (query.targetLabels().isEmpty()) {\n+                ofLabels = subLabels(query);\n+            } else {\n+                ofLabels = query.targetLabels().stream()\n+                        .flatMap(typeLabel -> {\n+                            Type type = tx.getSchemaConcept(typeLabel);\n+                            if (type == null) throw GraqlQueryException.labelNotFound(typeLabel);\n+                            if (type.isRelationshipType()) throw GraqlQueryException.kCoreOnRelationshipType(typeLabel);\n+                            return type.subs();\n+                        })\n+                        .map(SchemaConcept::getLabel)\n+                        .collect(Collectors.toSet());\n+            }\n+\n+            Set<Label> subLabels = Sets.union(subLabels(query), ofLabels);\n+\n+            if (!selectedTypesHaveInstance(query)) {\n+                return Collections.emptyMap();\n+            }\n+\n+            ComputerResult result;\n+            Set<LabelId> subLabelIds = convertLabelsToIds(subLabels);\n+            Set<LabelId> ofLabelIds = convertLabelsToIds(ofLabels);\n+\n+            try {\n+                result = computer.compute(\n+                        new CorenessVertexProgram(k),\n+                        new DegreeDistributionMapReduce(ofLabelIds, CorenessVertexProgram.CORENESS),\n+                        subLabelIds);\n+            } catch (NoResultException e) {\n+                return Collections.emptyMap();\n+            }\n+\n+            return result.memory().get(DegreeDistributionMapReduce.class.getName());\n+        });\n+    }\n+\n+    public ComputeJob<Long> run(CountQuery query) {\n+        return runCompute(query, computer -> {\n+\n+            if (!selectedTypesHaveInstance(query)) {\n+                LOG.debug(\"Count = 0\");\n+                return 0L;\n+            }\n+\n+            Set<LabelId> typeLabelIds = convertLabelsToIds(subLabels(query));\n+            Map<Integer, Long> count;\n+\n+            Set<LabelId> rolePlayerLabelIds = getRolePlayerLabelIds(query);\n+            rolePlayerLabelIds.addAll(typeLabelIds);\n+\n+            ComputerResult result = computer.compute(\n+                    new CountVertexProgram(),\n+                    new CountMapReduceWithAttribute(),\n+                    rolePlayerLabelIds, false);\n+            count = result.memory().get(CountMapReduceWithAttribute.class.getName());\n+\n+            long finalCount = count.keySet().stream()\n+                    .filter(id -> typeLabelIds.contains(LabelId.of(id)))\n+                    .mapToLong(count::get).sum();\n+            if (count.containsKey(GraknMapReduce.RESERVED_TYPE_LABEL_KEY)) {\n+                finalCount += count.get(GraknMapReduce.RESERVED_TYPE_LABEL_KEY);\n+            }\n+\n+            LOG.debug(\"Count = \" + finalCount);\n+            return finalCount;\n+        });\n+    }\n+\n+    public ComputeJob<Map<Long, Set<String>>> run(DegreeQuery query) {\n+        return runCompute(query, computer -> {\n+            Set<Label> ofLabels;\n+\n+            // Check if ofType is valid before returning emptyMap\n+            if (query.targetLabels().isEmpty()) {\n+                ofLabels = subLabels(query);\n+            } else {\n+                ofLabels = query.targetLabels().stream()\n+                        .flatMap(typeLabel -> {\n+                            Type type = tx.getSchemaConcept(typeLabel);\n+                            if (type == null) throw GraqlQueryException.labelNotFound(typeLabel);\n+                            return type.subs();\n+                        })\n+                        .map(SchemaConcept::getLabel)\n+                        .collect(Collectors.toSet());\n+            }\n+\n+            Set<Label> subLabels = Sets.union(subLabels(query), ofLabels);\n+\n+            if (!selectedTypesHaveInstance(query)) {\n+                return Collections.emptyMap();\n+            }\n+\n+            Set<LabelId> subLabelIds = convertLabelsToIds(subLabels);\n+            Set<LabelId> ofLabelIds = convertLabelsToIds(ofLabels);\n+\n+            ComputerResult result = computer.compute(\n+                    new DegreeVertexProgram(ofLabelIds),\n+                    new DegreeDistributionMapReduce(ofLabelIds, DegreeVertexProgram.DEGREE),\n+                    subLabelIds);\n+\n+            return result.memory().get(DegreeDistributionMapReduce.class.getName());\n+        });\n+    }\n+\n+    public ComputeJob<Map<String, Set<String>>> run(KCoreQuery query) {\n+        return runCompute(query, computer -> {\n+            long k = query.kValue();\n+\n+            if (k < 2L) throw GraqlQueryException.kValueSmallerThanTwo();\n+\n+            if (!selectedTypesHaveInstance(query)) {\n+                return Collections.emptyMap();\n+            }\n+\n+            ComputerResult result;\n+            Set<LabelId> subLabelIds = convertLabelsToIds(subLabels(query));\n+            try {\n+                result = computer.compute(\n+                        new KCoreVertexProgram(k),\n+                        new ClusterMemberMapReduce(KCoreVertexProgram.K_CORE_LABEL),\n+                        subLabelIds);\n+            } catch (NoResultException e) {\n+                return Collections.emptyMap();\n+            }\n+\n+            return result.memory().get(ClusterMemberMapReduce.class.getName());\n+        });\n+    }\n+\n+    public ComputeJob<Optional<Number>> run(MaxQuery query) {\n+        return execWithMapReduce(query, MaxMapReduce::new);\n+    }\n+\n+    public ComputeJob<Optional<Double>> run(MeanQuery query) {\n+        return execWithMapReduce(query, MeanMapReduce::new, meanPair ->\n+            meanPair.get(MeanMapReduce.SUM) / meanPair.get(MeanMapReduce.COUNT)\n+        );\n+    }\n+\n+    public ComputeJob<Optional<Number>> run(MedianQuery query) {\n+        return runCompute(query, computer -> {\n+            AttributeType.DataType<?> dataType = getDataTypeOfSelectedResourceTypes(query);\n+            if (!selectedResourceTypesHaveInstance(query, statisticsResourceLabels(query))) {\n+                return Optional.empty();\n+            }\n+            Set<LabelId> allSubLabelIds = convertLabelsToIds(getCombinedSubTypes(query));\n+            Set<LabelId> statisticsResourceLabelIds = convertLabelsToIds(statisticsResourceLabels(query));\n+\n+            ComputerResult result = computer.compute(\n+                    new MedianVertexProgram(statisticsResourceLabelIds, dataType),\n+                    null, allSubLabelIds);\n+\n+            Number finalResult = result.memory().get(MedianVertexProgram.MEDIAN);\n+            LOG.debug(\"Median = \" + finalResult);\n+\n+            return Optional.of(finalResult);\n+        });\n+    }\n+\n+    public ComputeJob<Optional<Number>> run(MinQuery query) {\n+        return execWithMapReduce(query, MinMapReduce::new);\n+    }\n+\n+    public ComputeJob<Optional<List<Concept>>> run(PathQuery query) {\n+        PathsQuery pathsQuery = tx.graql().compute().paths();\n+        if (isAttributeIncluded(query)) pathsQuery = pathsQuery.includeAttribute();\n+        pathsQuery = pathsQuery.from(query.from()).to(query.to()).in(query.subLabels());\n+\n+        return run(pathsQuery).map(result -> result.stream().findAny());\n+    }\n+\n+    public TinkerComputeJob<List<List<Concept>>> run(PathsQuery query) {\n+        return runCompute(query, computer -> {\n+\n+            ConceptId sourceId = query.from();\n+            ConceptId destinationId = query.to();\n+\n+            if (!verticesExistInSubgraph(query, sourceId, destinationId)) {\n+                throw GraqlQueryException.instanceDoesNotExist();\n+            }\n+            if (sourceId.equals(destinationId)) {\n+                return Collections.singletonList(Collections.singletonList(tx.getConcept(sourceId)));\n+            }\n+\n+            ComputerResult result;\n+            Set<LabelId> subLabelIds = convertLabelsToIds(subLabels(query));\n+            try {\n+                result = computer.compute(\n+                        new ShortestPathVertexProgram(sourceId, destinationId), null, subLabelIds);\n+            } catch (NoResultException e) {\n+                return Collections.emptyList();\n+            }\n+\n+            Multimap<Concept, Concept> predecessorMapFromSource = getPredecessorMap(result);\n+            List<List<Concept>> allPaths = getAllPaths(predecessorMapFromSource, sourceId);\n+            if (isAttributeIncluded(query)) { // this can be slow\n+                return getExtendedPaths(allPaths);\n+            }\n+\n+            LOG.info(\"Number of paths: \" + allPaths.size());\n+            return allPaths;\n+        });\n+    }\n+\n+    public ComputeJob<Optional<Double>> run(StdQuery query) {\n+        return execWithMapReduce(query, StdMapReduce::new, stdTuple -> {\n+            double squareSum = stdTuple.get(StdMapReduce.SQUARE_SUM);\n+            double sum = stdTuple.get(StdMapReduce.SUM);\n+            double count = stdTuple.get(StdMapReduce.COUNT);\n+            return Math.sqrt(squareSum / count - (sum / count) * (sum / count));\n+        });\n+    }\n+\n+    public ComputeJob<Optional<Number>> run(SumQuery query) {\n+        return execWithMapReduce(query, SumMapReduce::new);\n+    }\n+\n+    private <T, Q extends ComputeQuery<?>> TinkerComputeJob<T> runCompute(Q query, ComputeRunner<T> runner) {\n+        return TinkerComputeJob.create(tx.session(), computer -> {\n+            LOG.info(query + \" started\");\n+            long startTime = System.currentTimeMillis();\n+\n+            T result = runner.apply(computer);\n+\n+            LOG.info(query + \" finished in \" + (System.currentTimeMillis() - startTime) + \" ms\");\n+\n+            return result;\n+        });\n+    }\n+\n+    private Set<LabelId> convertLabelsToIds(Set<Label> labelSet) {\n+        return labelSet.stream()\n+                .map(tx.admin()::convertToId)\n+                .filter(LabelId::isValid)\n+                .collect(Collectors.toSet());\n+    }\n+\n+    private boolean selectedTypesHaveInstance(ComputeQuery<?> query) {\n+        if (subLabels(query).isEmpty()) {\n+            LOG.info(\"No types found while looking for instances\");\n+            return false;\n+        }\n+\n+        List<Pattern> checkSubtypes = subLabels(query).stream()\n+                .map(type -> Graql.var(\"x\").isa(Graql.label(type))).collect(Collectors.toList());\n+        return tx.graql().infer(false).match(Graql.or(checkSubtypes)).iterator().hasNext();\n+    }\n+\n+    private ImmutableSet<Label> subLabels(ComputeQuery<?> query) {\n+        return subTypes(query).map(SchemaConcept::getLabel).collect(CommonUtil.toImmutableSet());\n+    }\n+\n+    private Stream<Type> subTypes(ComputeQuery<?> query) {\n+        // get all types if subGraph is empty, else get all subTypes of each type in subGraph\n+        // only include attributes and implicit \"has-xxx\" relationships when user specifically asked for them\n+        if (query.subLabels().isEmpty()) {\n+            ImmutableSet.Builder<Type> subTypesBuilder = ImmutableSet.builder();\n+\n+            if (isAttributeIncluded(query)) {\n+                tx.admin().getMetaConcept().subs().forEach(subTypesBuilder::add);\n+            } else {\n+                tx.admin().getMetaEntityType().subs().forEach(subTypesBuilder::add);\n+                tx.admin().getMetaRelationType().subs()\n+                        .filter(relationshipType -> !relationshipType.isImplicit()).forEach(subTypesBuilder::add);\n+            }\n+\n+            return subTypesBuilder.build().stream();\n+        } else {\n+            Stream<Type> subTypes = query.subLabels().stream().map(label -> {\n+                Type type = tx.getType(label);\n+                if (type == null) throw GraqlQueryException.labelNotFound(label);\n+                return type;\n+            }).flatMap(Type::subs);\n+\n+            if (!isAttributeIncluded(query)) {\n+                subTypes = subTypes.filter(relationshipType -> !relationshipType.isImplicit());\n+            }\n+\n+            return subTypes;\n+        }\n+    }\n+\n+    private boolean verticesExistInSubgraph(ComputeQuery<?> query, ConceptId... ids) {\n+        for (ConceptId id : ids) {\n+            Thing thing = tx.getConcept(id);\n+            if (thing == null || !subLabels(query).contains(thing.type().getLabel())) return false;\n+        }\n+        return true;\n+    }\n+\n+    private Set<LabelId> getRolePlayerLabelIds(ComputeQuery<?> query) {\n+        return subTypes(query)\n+                .filter(Concept::isRelationshipType)\n+                .map(Concept::asRelationshipType)\n+                .filter(RelationshipType::isImplicit)\n+                .flatMap(RelationshipType::relates)\n+                .flatMap(Role::playedByTypes)\n+                .map(type -> tx.admin().convertToId(type.getLabel()))\n+                .filter(LabelId::isValid)\n+                .collect(Collectors.toSet());\n+    }\n+\n+    @Nullable\n+    private AttributeType.DataType<?> getDataTypeOfSelectedResourceTypes(StatisticsQuery<?> query) {\n+        AttributeType.DataType<?> dataType = null;\n+        for (Type type : calcStatisticsResourceTypes(query)) {\n+            // check if the selected type is a resource-type\n+            if (!type.isAttributeType()) throw GraqlQueryException.mustBeAttributeType(type.getLabel());\n+            AttributeType<?> resourceType = type.asAttributeType();\n+            if (dataType == null) {\n+                // check if the resource-type has data-type LONG or DOUBLE\n+                dataType = resourceType.getDataType();\n+                if (!dataType.equals(AttributeType.DataType.LONG) &&\n+                        !dataType.equals(AttributeType.DataType.DOUBLE)) {\n+                    throw GraqlQueryException.resourceMustBeANumber(dataType, resourceType.getLabel());\n+                }\n+\n+            } else {\n+                // check if all the resource-types have the same data-type\n+                if (!dataType.equals(resourceType.getDataType())) {\n+                    throw GraqlQueryException.resourcesWithDifferentDataTypes(query.attributeLabels());\n+                }\n+            }\n+        }\n+        return dataType;\n+    }\n+\n+    private Set<Label> statisticsResourceLabels(StatisticsQuery<?> query) {\n+        return calcStatisticsResourceTypes(query).stream()\n+                .map(SchemaConcept::getLabel)\n+                .collect(CommonUtil.toImmutableSet());\n+    }\n+\n+    private ImmutableSet<Type> calcStatisticsResourceTypes(StatisticsQuery<?> query) {\n+        if (query.attributeLabels().isEmpty()) {\n+            throw GraqlQueryException.statisticsAttributeTypesNotSpecified();\n+        }\n+\n+        return query.attributeLabels().stream()\n+                .map((label) -> {\n+                    Type type = this.tx.getSchemaConcept(label);\n+                    if (type == null) throw GraqlQueryException.labelNotFound(label);\n+                    if (!type.isAttributeType()) throw GraqlQueryException.mustBeAttributeType(type.getLabel());\n+                    return type;\n+                })\n+                .flatMap(Type::subs)\n+                .collect(CommonUtil.toImmutableSet());\n+    }\n+\n+    private boolean selectedResourceTypesHaveInstance(ComputeQuery<?> query, Set<Label> statisticsResourceTypes) {\n+        for (Label resourceType : statisticsResourceTypes) {\n+            for (Label type : subLabels(query)) {\n+                Boolean patternExist = tx.graql().infer(false).match(\n+                        Graql.var(\"x\").has(resourceType, Graql.var()),\n+                        Graql.var(\"x\").isa(Graql.label(type))\n+                ).iterator().hasNext();\n+                if (patternExist) return true;\n+            }\n+        }\n+        return false;\n+        //TODO: should use the following ask query when ask query is even lazier\n+//        List<Pattern> checkResourceTypes = statisticsResourceTypes.stream()\n+//                .map(type -> var(\"x\").has(type, var())).collect(Collectors.toList());\n+//        List<Pattern> checkSubtypes = subLabels.stream()\n+//                .map(type -> var(\"x\").isa(Graql.label(type))).collect(Collectors.toList());\n+//\n+//        return tx.get().graql().infer(false)\n+//                .match(or(checkResourceTypes), or(checkSubtypes)).aggregate(ask()).execute();\n+    }\n+\n+    private Set<Label> getCombinedSubTypes(StatisticsQuery<?> query) {\n+        Set<Label> allSubTypes = getHasResourceRelationLabels(calcStatisticsResourceTypes(query));\n+        allSubTypes.addAll(subLabels(query));\n+        allSubTypes.addAll(query.attributeLabels());\n+        return allSubTypes;\n+    }\n+\n+    private Set<Label> getHasResourceRelationLabels(Set<Type> subTypes) {\n+        return subTypes.stream()\n+                .filter(Concept::isAttributeType)\n+                .map(resourceType -> Schema.ImplicitType.HAS.getLabel(resourceType.getLabel()))\n+                .collect(Collectors.toSet());\n+    }// If the sub graph contains attributes, we may need to add implicit relations to the paths\n+\n+    private List<List<Concept>> getExtendedPaths(List<List<Concept>> allPaths) {","path":"grakn-graql/src/main/java/ai/grakn/graql/internal/query/runner/TinkerComputeQueryRunner.java","position":547,"original_position":547,"commit_id":"8802038e36c065c9580248904dd5e59f3747129a","original_commit_id":"8802038e36c065c9580248904dd5e59f3747129a","user":{"login":"lolski","id":464164,"avatar_url":"https://avatars0.githubusercontent.com/u/464164?v=4","gravatar_id":"","url":"https://api.github.com/users/lolski","html_url":"https://github.com/lolski","followers_url":"https://api.github.com/users/lolski/followers","following_url":"https://api.github.com/users/lolski/following{/other_user}","gists_url":"https://api.github.com/users/lolski/gists{/gist_id}","starred_url":"https://api.github.com/users/lolski/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lolski/subscriptions","organizations_url":"https://api.github.com/users/lolski/orgs","repos_url":"https://api.github.com/users/lolski/repos","events_url":"https://api.github.com/users/lolski/events{/privacy}","received_events_url":"https://api.github.com/users/lolski/received_events","type":"User","site_admin":false},"body":"the method is big but you seem to manage to split them into several high-level structure here. maybe actually split them into several methods?","created_at":"2018-02-08T12:19:53Z","updated_at":"2018-02-08T12:20:22Z","html_url":"https://github.com/graknlabs/grakn/pull/2622#discussion_r166916971","pull_request_url":"https://api.github.com/repos/graknlabs/grakn/pulls/2622","author_association":"MEMBER","_links":{"self":{"href":"https://api.github.com/repos/graknlabs/grakn/pulls/comments/166916971"},"html":{"href":"https://github.com/graknlabs/grakn/pull/2622#discussion_r166916971"},"pull_request":{"href":"https://api.github.com/repos/graknlabs/grakn/pulls/2622"}}},"pull_request":{"url":"https://api.github.com/repos/graknlabs/grakn/pulls/2622","id":167729985,"html_url":"https://github.com/graknlabs/grakn/pull/2622","diff_url":"https://github.com/graknlabs/grakn/pull/2622.diff","patch_url":"https://github.com/graknlabs/grakn/pull/2622.patch","issue_url":"https://api.github.com/repos/graknlabs/grakn/issues/2622","number":2622,"state":"open","locked":false,"title":"Add `QueryRunner` interface for running queries","user":{"login":"aelred","id":4632588,"avatar_url":"https://avatars1.githubusercontent.com/u/4632588?v=4","gravatar_id":"","url":"https://api.github.com/users/aelred","html_url":"https://github.com/aelred","followers_url":"https://api.github.com/users/aelred/followers","following_url":"https://api.github.com/users/aelred/following{/other_user}","gists_url":"https://api.github.com/users/aelred/gists{/gist_id}","starred_url":"https://api.github.com/users/aelred/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aelred/subscriptions","organizations_url":"https://api.github.com/users/aelred/orgs","repos_url":"https://api.github.com/users/aelred/repos","events_url":"https://api.github.com/users/aelred/events{/privacy}","received_events_url":"https://api.github.com/users/aelred/received_events","type":"User","site_admin":false},"body":"# Why is this PR needed?\r\n\r\nThe gRPC client will need to provide an alternative way to run queries. This will be done using a new implementation of `QueryRunner` in this PR.\r\n\r\n# What does the PR do?\r\n\r\nIntroduce a `QueryRunner` interface, plus an implementation `TinkerQueryRunner`, that executes queries using Tinkerpop.\r\n\r\n# Does it break backwards compatibility?\r\n\r\n...maybe. I removed `ComputeQuery#isStatisticsQuery` but since we haven't got a defined API yet 🤷‍♂️ \r\n\r\n# List of future improvements not on this PR\r\n\r\nThere is still a _lot_ of Tinkerpop-specific code in `grakn-core` and `grakn-graql`. Eventually we will try and isolate this to just `grakn-kb`.","created_at":"2018-02-07T15:48:25Z","updated_at":"2018-02-08T12:20:22Z","closed_at":null,"merged_at":null,"merge_commit_sha":"0bc9a191a6529d6a7dc878a7314800e9fc2dc3b8","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"milestone":null,"commits_url":"https://api.github.com/repos/graknlabs/grakn/pulls/2622/commits","review_comments_url":"https://api.github.com/repos/graknlabs/grakn/pulls/2622/comments","review_comment_url":"https://api.github.com/repos/graknlabs/grakn/pulls/comments{/number}","comments_url":"https://api.github.com/repos/graknlabs/grakn/issues/2622/comments","statuses_url":"https://api.github.com/repos/graknlabs/grakn/statuses/8802038e36c065c9580248904dd5e59f3747129a","head":{"label":"aelred:query-runner","ref":"query-runner","sha":"8802038e36c065c9580248904dd5e59f3747129a","user":{"login":"aelred","id":4632588,"avatar_url":"https://avatars1.githubusercontent.com/u/4632588?v=4","gravatar_id":"","url":"https://api.github.com/users/aelred","html_url":"https://github.com/aelred","followers_url":"https://api.github.com/users/aelred/followers","following_url":"https://api.github.com/users/aelred/following{/other_user}","gists_url":"https://api.github.com/users/aelred/gists{/gist_id}","starred_url":"https://api.github.com/users/aelred/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aelred/subscriptions","organizations_url":"https://api.github.com/users/aelred/orgs","repos_url":"https://api.github.com/users/aelred/repos","events_url":"https://api.github.com/users/aelred/events{/privacy}","received_events_url":"https://api.github.com/users/aelred/received_events","type":"User","site_admin":false},"repo":{"id":65569565,"name":"grakn","full_name":"aelred/grakn","owner":{"login":"aelred","id":4632588,"avatar_url":"https://avatars1.githubusercontent.com/u/4632588?v=4","gravatar_id":"","url":"https://api.github.com/users/aelred","html_url":"https://github.com/aelred","followers_url":"https://api.github.com/users/aelred/followers","following_url":"https://api.github.com/users/aelred/following{/other_user}","gists_url":"https://api.github.com/users/aelred/gists{/gist_id}","starred_url":"https://api.github.com/users/aelred/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aelred/subscriptions","organizations_url":"https://api.github.com/users/aelred/orgs","repos_url":"https://api.github.com/users/aelred/repos","events_url":"https://api.github.com/users/aelred/events{/privacy}","received_events_url":"https://api.github.com/users/aelred/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/aelred/grakn","description":"A Knowledge Graph Platform","fork":true,"url":"https://api.github.com/repos/aelred/grakn","forks_url":"https://api.github.com/repos/aelred/grakn/forks","keys_url":"https://api.github.com/repos/aelred/grakn/keys{/key_id}","collaborators_url":"https://api.github.com/repos/aelred/grakn/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/aelred/grakn/teams","hooks_url":"https://api.github.com/repos/aelred/grakn/hooks","issue_events_url":"https://api.github.com/repos/aelred/grakn/issues/events{/number}","events_url":"https://api.github.com/repos/aelred/grakn/events","assignees_url":"https://api.github.com/repos/aelred/grakn/assignees{/user}","branches_url":"https://api.github.com/repos/aelred/grakn/branches{/branch}","tags_url":"https://api.github.com/repos/aelred/grakn/tags","blobs_url":"https://api.github.com/repos/aelred/grakn/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/aelred/grakn/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/aelred/grakn/git/refs{/sha}","trees_url":"https://api.github.com/repos/aelred/grakn/git/trees{/sha}","statuses_url":"https://api.github.com/repos/aelred/grakn/statuses/{sha}","languages_url":"https://api.github.com/repos/aelred/grakn/languages","stargazers_url":"https://api.github.com/repos/aelred/grakn/stargazers","contributors_url":"https://api.github.com/repos/aelred/grakn/contributors","subscribers_url":"https://api.github.com/repos/aelred/grakn/subscribers","subscription_url":"https://api.github.com/repos/aelred/grakn/subscription","commits_url":"https://api.github.com/repos/aelred/grakn/commits{/sha}","git_commits_url":"https://api.github.com/repos/aelred/grakn/git/commits{/sha}","comments_url":"https://api.github.com/repos/aelred/grakn/comments{/number}","issue_comment_url":"https://api.github.com/repos/aelred/grakn/issues/comments{/number}","contents_url":"https://api.github.com/repos/aelred/grakn/contents/{+path}","compare_url":"https://api.github.com/repos/aelred/grakn/compare/{base}...{head}","merges_url":"https://api.github.com/repos/aelred/grakn/merges","archive_url":"https://api.github.com/repos/aelred/grakn/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/aelred/grakn/downloads","issues_url":"https://api.github.com/repos/aelred/grakn/issues{/number}","pulls_url":"https://api.github.com/repos/aelred/grakn/pulls{/number}","milestones_url":"https://api.github.com/repos/aelred/grakn/milestones{/number}","notifications_url":"https://api.github.com/repos/aelred/grakn/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/aelred/grakn/labels{/name}","releases_url":"https://api.github.com/repos/aelred/grakn/releases{/id}","deployments_url":"https://api.github.com/repos/aelred/grakn/deployments","created_at":"2016-08-12T16:57:37Z","updated_at":"2016-11-30T09:25:36Z","pushed_at":"2018-02-08T11:12:32Z","git_url":"git://github.com/aelred/grakn.git","ssh_url":"git@github.com:aelred/grakn.git","clone_url":"https://github.com/aelred/grakn.git","svn_url":"https://github.com/aelred/grakn","homepage":"https://grakn.ai","size":48518,"stargazers_count":0,"watchers_count":0,"language":"Java","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":{"key":"gpl-3.0","name":"GNU General Public License v3.0","spdx_id":"GPL-3.0","url":"https://api.github.com/licenses/gpl-3.0"},"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"graknlabs:master","ref":"master","sha":"c161e2d6c3bd622730e876ca7b3de7cd91582232","user":{"login":"graknlabs","id":10490615,"avatar_url":"https://avatars1.githubusercontent.com/u/10490615?v=4","gravatar_id":"","url":"https://api.github.com/users/graknlabs","html_url":"https://github.com/graknlabs","followers_url":"https://api.github.com/users/graknlabs/followers","following_url":"https://api.github.com/users/graknlabs/following{/other_user}","gists_url":"https://api.github.com/users/graknlabs/gists{/gist_id}","starred_url":"https://api.github.com/users/graknlabs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/graknlabs/subscriptions","organizations_url":"https://api.github.com/users/graknlabs/orgs","repos_url":"https://api.github.com/users/graknlabs/repos","events_url":"https://api.github.com/users/graknlabs/events{/privacy}","received_events_url":"https://api.github.com/users/graknlabs/received_events","type":"Organization","site_admin":false},"repo":{"id":63110867,"name":"grakn","full_name":"graknlabs/grakn","owner":{"login":"graknlabs","id":10490615,"avatar_url":"https://avatars1.githubusercontent.com/u/10490615?v=4","gravatar_id":"","url":"https://api.github.com/users/graknlabs","html_url":"https://github.com/graknlabs","followers_url":"https://api.github.com/users/graknlabs/followers","following_url":"https://api.github.com/users/graknlabs/following{/other_user}","gists_url":"https://api.github.com/users/graknlabs/gists{/gist_id}","starred_url":"https://api.github.com/users/graknlabs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/graknlabs/subscriptions","organizations_url":"https://api.github.com/users/graknlabs/orgs","repos_url":"https://api.github.com/users/graknlabs/repos","events_url":"https://api.github.com/users/graknlabs/events{/privacy}","received_events_url":"https://api.github.com/users/graknlabs/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/graknlabs/grakn","description":"The Hyper-Relational Database for Knowledge Engineering","fork":false,"url":"https://api.github.com/repos/graknlabs/grakn","forks_url":"https://api.github.com/repos/graknlabs/grakn/forks","keys_url":"https://api.github.com/repos/graknlabs/grakn/keys{/key_id}","collaborators_url":"https://api.github.com/repos/graknlabs/grakn/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/graknlabs/grakn/teams","hooks_url":"https://api.github.com/repos/graknlabs/grakn/hooks","issue_events_url":"https://api.github.com/repos/graknlabs/grakn/issues/events{/number}","events_url":"https://api.github.com/repos/graknlabs/grakn/events","assignees_url":"https://api.github.com/repos/graknlabs/grakn/assignees{/user}","branches_url":"https://api.github.com/repos/graknlabs/grakn/branches{/branch}","tags_url":"https://api.github.com/repos/graknlabs/grakn/tags","blobs_url":"https://api.github.com/repos/graknlabs/grakn/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/graknlabs/grakn/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/graknlabs/grakn/git/refs{/sha}","trees_url":"https://api.github.com/repos/graknlabs/grakn/git/trees{/sha}","statuses_url":"https://api.github.com/repos/graknlabs/grakn/statuses/{sha}","languages_url":"https://api.github.com/repos/graknlabs/grakn/languages","stargazers_url":"https://api.github.com/repos/graknlabs/grakn/stargazers","contributors_url":"https://api.github.com/repos/graknlabs/grakn/contributors","subscribers_url":"https://api.github.com/repos/graknlabs/grakn/subscribers","subscription_url":"https://api.github.com/repos/graknlabs/grakn/subscription","commits_url":"https://api.github.com/repos/graknlabs/grakn/commits{/sha}","git_commits_url":"https://api.github.com/repos/graknlabs/grakn/git/commits{/sha}","comments_url":"https://api.github.com/repos/graknlabs/grakn/comments{/number}","issue_comment_url":"https://api.github.com/repos/graknlabs/grakn/issues/comments{/number}","contents_url":"https://api.github.com/repos/graknlabs/grakn/contents/{+path}","compare_url":"https://api.github.com/repos/graknlabs/grakn/compare/{base}...{head}","merges_url":"https://api.github.com/repos/graknlabs/grakn/merges","archive_url":"https://api.github.com/repos/graknlabs/grakn/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/graknlabs/grakn/downloads","issues_url":"https://api.github.com/repos/graknlabs/grakn/issues{/number}","pulls_url":"https://api.github.com/repos/graknlabs/grakn/pulls{/number}","milestones_url":"https://api.github.com/repos/graknlabs/grakn/milestones{/number}","notifications_url":"https://api.github.com/repos/graknlabs/grakn/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/graknlabs/grakn/labels{/name}","releases_url":"https://api.github.com/repos/graknlabs/grakn/releases{/id}","deployments_url":"https://api.github.com/repos/graknlabs/grakn/deployments","created_at":"2016-07-11T23:59:57Z","updated_at":"2018-02-08T06:05:24Z","pushed_at":"2018-02-08T11:38:44Z","git_url":"git://github.com/graknlabs/grakn.git","ssh_url":"git@github.com:graknlabs/grakn.git","clone_url":"https://github.com/graknlabs/grakn.git","svn_url":"https://github.com/graknlabs/grakn","homepage":"https://grakn.ai","size":48414,"stargazers_count":772,"watchers_count":772,"language":"Java","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":109,"mirror_url":null,"archived":false,"open_issues_count":37,"license":{"key":"agpl-3.0","name":"GNU Affero General Public License v3.0","spdx_id":"AGPL-3.0","url":"https://api.github.com/licenses/agpl-3.0"},"forks":109,"open_issues":37,"watchers":772,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/graknlabs/grakn/pulls/2622"},"html":{"href":"https://github.com/graknlabs/grakn/pull/2622"},"issue":{"href":"https://api.github.com/repos/graknlabs/grakn/issues/2622"},"comments":{"href":"https://api.github.com/repos/graknlabs/grakn/issues/2622/comments"},"review_comments":{"href":"https://api.github.com/repos/graknlabs/grakn/pulls/2622/comments"},"review_comment":{"href":"https://api.github.com/repos/graknlabs/grakn/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/graknlabs/grakn/pulls/2622/commits"},"statuses":{"href":"https://api.github.com/repos/graknlabs/grakn/statuses/8802038e36c065c9580248904dd5e59f3747129a"}},"author_association":"MEMBER"}}
{"actor":{"display_login":"lolski"}}