// Copyright 2017 JanusGraph Authors // // 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 org.janusgraph.graphdb.query.vertex; import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import org.janusgraph.core.*; import org.janusgraph.core.attribute.Cmp; import org.janusgraph.diskstorage.keycolumnvalue.SliceQuery; import org.janusgraph.graphdb.database.EdgeSerializer; import org.janusgraph.graphdb.internal.*; import org.janusgraph.graphdb.query.*; import org.janusgraph.graphdb.query.condition.*; import org.janusgraph.graphdb.query.profile.QueryProfiler; import org.janusgraph.graphdb.relations.StandardVertexProperty; import org.janusgraph.graphdb.transaction.StandardJanusGraphTx; import org.janusgraph.core.schema.SchemaStatus; import org.janusgraph.graphdb.types.system.ImplicitKey; import org.janusgraph.graphdb.types.system.SystemRelationType; import org.apache.tinkerpop.gremlin.structure.Direction; import org.janusgraph.util.datastructures.Interval; import org.janusgraph.util.datastructures.PointInterval; import org.janusgraph.util.datastructures.RangeInterval; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nullable; import java.util.*; /** * Builds a {@link BaseVertexQuery}, optimizes the query and compiles the result into a {@link org.janusgraph.graphdb.query.vertex.BaseVertexCentricQuery} which * is then executed by one of the extending classes. * * @author Matthias Broecheler (me@matthiasb.com) */ public abstract class BasicVertexCentricQueryBuilder<Q extends BaseVertexQuery<Q>> extends BaseVertexCentricQueryBuilder<Q> { @SuppressWarnings("unused") private static final Logger log = LoggerFactory.getLogger(BasicVertexCentricQueryBuilder.class); /** * Transaction in which this query is executed */ protected final StandardJanusGraphTx tx; /** * The query profiler used to observe this query */ protected QueryProfiler profiler = QueryProfiler.NO_OP; /** * Whether to query for system relations only */ private boolean querySystem = false; /** Whether to query only for persisted edges, i.e. ignore any modifications to the vertex made in this transaction. This is achieved by using the {@link SimpleVertexQueryProcessor} for execution. */ private boolean queryOnlyLoaded = false; /** * Whether this query should only focus on the provided vertex representative of a (potentially) partitioned vertex. * This effectively disables the automatic querying for other vertex representatives and focuses on the provided * vertex object only. This is used in combination with {@link org.janusgraph.graphdb.vertices.PreloadedVertex}, for example. */ private boolean queryOnlyGivenVertex = false; /** * Whether to restrict this query to the specified "local" partitions in this transaction */ private boolean restrict2Partitions = true; public BasicVertexCentricQueryBuilder(final StandardJanusGraphTx tx) { super(tx); Preconditions.checkArgument(tx!=null); this.tx = tx; } @Override public JanusGraphVertex getVertex(long vertexid) { return tx.getVertex(vertexid); } /* --------------------------------------------------------------- * Query Construction * --------------------------------------------------------------- */ /** * Removes any query partition restriction for this query * * @return */ public Q noPartitionRestriction() { this.restrict2Partitions = false; return getThis(); } /** * Sets the query profiler to observe this query. Must be set before the query is executed to take effect. * * @param profiler * @return */ public Q profiler(QueryProfiler profiler) { Preconditions.checkNotNull(profiler); this.profiler=profiler; return getThis(); } /** * Restricts the result set of this query to only system types. * @return */ public Q system() { this.querySystem = true; return getThis(); } /** * Calling this method will cause this query to only included loaded (i.e. unmodified) relations in the * result set. * @return */ public Q queryOnlyLoaded() { queryOnlyLoaded=true; return getThis(); } public Q queryOnlyGivenVertex() { queryOnlyGivenVertex=true; return getThis(); } /* --------------------------------------------------------------- * Inspection Methods * --------------------------------------------------------------- */ protected boolean hasAllCanonicalTypes() { if (types.length==0) return false; for (String typeName : types) { InternalRelationType type = QueryUtil.getType(tx, typeName); if (type!=null && !type.isPropertyKey() && !type.multiplicity().isUnique(dir)) return false; } return true; } public boolean hasQueryOnlyGivenVertex() { return queryOnlyGivenVertex; } public boolean hasQueryOnlyLoaded() { return queryOnlyLoaded; } /* --------------------------------------------------------------- * Utility Methods * --------------------------------------------------------------- */ protected static Iterable<JanusGraphVertex> edges2Vertices(final Iterable<JanusGraphEdge> edges, final JanusGraphVertex other) { return Iterables.transform(edges, new Function<JanusGraphEdge, JanusGraphVertex>() { @Nullable @Override public JanusGraphVertex apply(@Nullable JanusGraphEdge janusgraphEdge) { return janusgraphEdge.otherVertex(other); } }); } protected VertexList edges2VertexIds(final Iterable<JanusGraphEdge> edges, final JanusGraphVertex other) { VertexArrayList vertices = new VertexArrayList(tx); for (JanusGraphEdge edge : edges) vertices.add(edge.otherVertex(other)); return vertices; } /* --------------------------------------------------------------- * Query Execution (Helper methods) * --------------------------------------------------------------- */ /** * If {@link #isImplicitKeyQuery(org.janusgraph.graphdb.internal.RelationCategory)} is true, * this method provides the result set for the query based on the evaluation of the {@link ImplicitKey}. * </p> * Handling of implicit keys is completely distinct from "normal" query execution and handled extra * for completeness reasons. * * @param v * @return */ protected Iterable<JanusGraphRelation> executeImplicitKeyQuery(InternalVertex v) { assert isImplicitKeyQuery(RelationCategory.PROPERTY); if (dir==Direction.IN || limit<1) return ImmutableList.of(); ImplicitKey key = (ImplicitKey)tx.getRelationType(types[0]); return ImmutableList.of((JanusGraphRelation)new StandardVertexProperty(0,key,v,key.computeProperty(v), v.isNew()?ElementLifeCycle.New:ElementLifeCycle.Loaded)); } protected interface ResultConstructor<Q> { Q getResult(InternalVertex v, BaseVertexCentricQuery bq); Q emptyResult(); } protected class RelationConstructor implements ResultConstructor<Iterable<? extends JanusGraphRelation>> { @Override public Iterable<? extends JanusGraphRelation> getResult(InternalVertex v, BaseVertexCentricQuery bq) { return executeRelations(v,bq); } @Override public Iterable<? extends JanusGraphRelation> emptyResult() { return Collections.EMPTY_LIST; } } protected class VertexConstructor implements ResultConstructor<Iterable<JanusGraphVertex>> { @Override public Iterable<JanusGraphVertex> getResult(InternalVertex v, BaseVertexCentricQuery bq) { return executeVertices(v,bq); } @Override public Iterable<JanusGraphVertex> emptyResult() { return Collections.EMPTY_LIST; } } protected class VertexIdConstructor implements ResultConstructor<VertexList> { @Override public VertexList getResult(InternalVertex v, BaseVertexCentricQuery bq) { return executeVertexIds(v,bq); } @Override public VertexList emptyResult() { return new VertexArrayList(tx); } } protected List<InternalVertex> allRequiredRepresentatives(InternalVertex partitionedVertex) { if (hasAllCanonicalTypes()) { return ImmutableList.of(tx.getCanonicalVertex(partitionedVertex)); } return Arrays.asList(tx.getAllRepresentatives(partitionedVertex,restrict2Partitions)); } protected final boolean isPartitionedVertex(InternalVertex vertex) { return tx.isPartitionedVertex(vertex) && !queryOnlyGivenVertex; } protected boolean useSimpleQueryProcessor(BaseVertexCentricQuery query, InternalVertex... vertices) { assert vertices.length>0; if (!query.isSimple()) return false; if (queryOnlyLoaded) return true; for (InternalVertex vertex : vertices) if (!vertex.isLoaded()) return false; return true; } protected Iterable<JanusGraphRelation> executeRelations(InternalVertex vertex, BaseVertexCentricQuery baseQuery) { if (isPartitionedVertex(vertex)) { if (!hasAllCanonicalTypes()) { InternalVertex[] representatives = tx.getAllRepresentatives(vertex,restrict2Partitions); Iterable<JanusGraphRelation> merge = null; for (InternalVertex rep : representatives) { Iterable<JanusGraphRelation> iter = executeIndividualRelations(rep,baseQuery); if (merge==null) merge = iter; else merge = ResultMergeSortIterator.mergeSort(merge,iter,(Comparator)orders,false); } return ResultSetIterator.wrap(merge,baseQuery.getLimit()); } else vertex = tx.getCanonicalVertex(vertex); } return executeIndividualRelations(vertex,baseQuery); } private Iterable<JanusGraphRelation> executeIndividualRelations(InternalVertex vertex, BaseVertexCentricQuery baseQuery) { VertexCentricQuery query = constructQuery(vertex, baseQuery); if (useSimpleQueryProcessor(query,vertex)) return new SimpleVertexQueryProcessor(query,tx).relations(); else return new QueryProcessor<VertexCentricQuery,JanusGraphRelation,SliceQuery>(query, tx.edgeProcessor); } public Iterable<JanusGraphVertex> executeVertices(InternalVertex vertex, BaseVertexCentricQuery baseQuery) { if (isPartitionedVertex(vertex)) { //If there is a sort order, we need to first merge the relations (and sort) and then compute vertices if (!orders.isEmpty()) return edges2VertexIds((Iterable) executeRelations(vertex,baseQuery), vertex); if (!hasAllCanonicalTypes()) { InternalVertex[] representatives = tx.getAllRepresentatives(vertex,restrict2Partitions); Iterable<JanusGraphVertex> merge = null; for (InternalVertex rep : representatives) { Iterable<JanusGraphVertex> iter = executeIndividualVertices(rep,baseQuery); if (merge==null) merge = iter; else merge = ResultMergeSortIterator.mergeSort(merge,iter,VertexArrayList.VERTEX_ID_COMPARATOR,false); } return ResultSetIterator.wrap(merge,baseQuery.getLimit()); } else vertex = tx.getCanonicalVertex(vertex); } return executeIndividualVertices(vertex,baseQuery); } private Iterable<JanusGraphVertex> executeIndividualVertices(InternalVertex vertex, BaseVertexCentricQuery baseQuery) { VertexCentricQuery query = constructQuery(vertex, baseQuery); if (useSimpleQueryProcessor(query, vertex)) return new SimpleVertexQueryProcessor(query,tx).vertexIds(); else return edges2Vertices((Iterable) executeIndividualRelations(vertex,baseQuery), query.getVertex()); } public VertexList executeVertexIds(InternalVertex vertex, BaseVertexCentricQuery baseQuery) { if (isPartitionedVertex(vertex)) { //If there is a sort order, we need to first merge the relations (and sort) and then compute vertices if (!orders.isEmpty()) return edges2VertexIds((Iterable) executeRelations(vertex,baseQuery), vertex); if (!hasAllCanonicalTypes()) { InternalVertex[] representatives = tx.getAllRepresentatives(vertex,restrict2Partitions); VertexListInternal merge = null; for (InternalVertex rep : representatives) { if (merge!=null && merge.size()>=baseQuery.getLimit()) break; VertexList vlist = executeIndividualVertexIds(rep,baseQuery); if (merge==null) merge = (VertexListInternal)vlist; else merge.addAll(vlist); } if (merge.size()>baseQuery.getLimit()) merge = (VertexListInternal)merge.subList(0,baseQuery.getLimit()); return merge; } else vertex = tx.getCanonicalVertex(vertex); } return executeIndividualVertexIds(vertex,baseQuery); } private VertexList executeIndividualVertexIds(InternalVertex vertex, BaseVertexCentricQuery baseQuery) { VertexCentricQuery query = constructQuery(vertex, baseQuery); if (useSimpleQueryProcessor(query, vertex)) return new SimpleVertexQueryProcessor(query,tx).vertexIds(); return edges2VertexIds((Iterable) executeIndividualRelations(vertex,baseQuery), vertex); } /* --------------------------------------------------------------- * Query Optimization and Construction * --------------------------------------------------------------- */ private static final int HARD_MAX_LIMIT = 300000; /** * Constructs a {@link VertexCentricQuery} for this query builder. The query construction and optimization * logic is taken from {@link #constructQuery(org.janusgraph.graphdb.internal.RelationCategory)} * This method only adds the additional conditions that are based on the base vertex. * * @param vertex for which to construct this query * @param baseQuery as constructed by {@link #constructQuery(org.janusgraph.graphdb.internal.RelationCategory)} * @return */ protected VertexCentricQuery constructQuery(InternalVertex vertex, BaseVertexCentricQuery baseQuery) { Condition<JanusGraphRelation> condition = baseQuery.getCondition(); if (!baseQuery.isEmpty()) { //Add adjacent-vertex and direction related conditions; copy conditions to so that baseQuery does not change And<JanusGraphRelation> newcond = new And<JanusGraphRelation>(); if (condition instanceof And) newcond.addAll((And) condition); else newcond.add(condition); newcond.add(new DirectionCondition<JanusGraphRelation>(vertex,dir)); if (adjacentVertex != null) newcond.add(new IncidenceCondition<JanusGraphRelation>(vertex,adjacentVertex)); condition = newcond; } VertexCentricQuery query = new VertexCentricQuery(vertex, condition, baseQuery.getDirection(), baseQuery.getQueries(),baseQuery.getOrders(), baseQuery.getLimit()); Preconditions.checkArgument(!queryOnlyLoaded || query.isSimple(),"Query-only-loaded only works on simple queries"); return query; } protected BaseVertexCentricQuery constructQuery(RelationCategory returnType) { QueryProfiler optProfiler = profiler.addNested(QueryProfiler.OPTIMIZATION); optProfiler.startTimer(); BaseVertexCentricQuery query = constructQueryWithoutProfile(returnType); optProfiler.stopTimer(); query.observeWith(profiler); return query; } protected BaseVertexCentricQuery constructQueryWithoutProfile(RelationCategory returnType) { assert returnType != null; Preconditions.checkArgument(adjacentVertex==null || returnType == RelationCategory.EDGE,"Vertex constraints only apply to edges"); if (limit <= 0) return BaseVertexCentricQuery.emptyQuery(); //Prepare direction if (returnType == RelationCategory.PROPERTY) { if (dir == Direction.IN) return BaseVertexCentricQuery.emptyQuery(); dir = Direction.OUT; } //Prepare order orders.makeImmutable(); assert orders.hasCommonOrder(); //Prepare constraints And<JanusGraphRelation> conditions = QueryUtil.constraints2QNF(tx, constraints); if (conditions == null) return BaseVertexCentricQuery.emptyQuery(); //Don't be smart with query limit adjustments - it just messes up the caching layer and penalizes when appropriate limits are set by the user! int sliceLimit = limit; //Construct (optimal) SliceQueries EdgeSerializer serializer = tx.getEdgeSerializer(); List<BackendQueryHolder<SliceQuery>> queries; if (!hasTypes()) { BackendQueryHolder<SliceQuery> query = new BackendQueryHolder<SliceQuery>(serializer.getQuery(returnType, querySystem), ((dir == Direction.BOTH || (returnType == RelationCategory.PROPERTY && dir == Direction.OUT)) && !conditions.hasChildren()), orders.isEmpty()); if (sliceLimit!=Query.NO_LIMIT && sliceLimit<Integer.MAX_VALUE/3) { //If only one direction is queried, ask for twice the limit from backend since approximately half will be filtered if (dir != Direction.BOTH && (returnType == RelationCategory.EDGE || returnType == RelationCategory.RELATION)) sliceLimit *= 2; } query.getBackendQuery().setLimit(computeLimit(conditions.size(),sliceLimit)); queries = ImmutableList.of(query); conditions.add(returnType); conditions.add(new VisibilityFilterCondition<JanusGraphRelation>( //Need this to filter out newly created invisible relations in the transaction querySystem? VisibilityFilterCondition.Visibility.SYSTEM: VisibilityFilterCondition.Visibility.NORMAL)); } else { Set<RelationType> ts = new HashSet<RelationType>(types.length); queries = new ArrayList<BackendQueryHolder<SliceQuery>>(types.length + 2); Map<RelationType,Interval> intervalConstraints = new HashMap<RelationType, Interval>(conditions.size()); final boolean isIntervalFittedConditions = compileConstraints(conditions,intervalConstraints); for (Interval pint : intervalConstraints.values()) { //Check if one of the constraints leads to an empty result set if (pint.isEmpty()) return BaseVertexCentricQuery.emptyQuery(); } for (String typeName : types) { InternalRelationType type = QueryUtil.getType(tx, typeName); if (type==null) continue; Preconditions.checkArgument(!querySystem || (type instanceof SystemRelationType),"Can only query for system types: %s",type); if (type instanceof ImplicitKey) throw new UnsupportedOperationException("Implicit types are not supported in complex queries: "+type); ts.add(type); Direction typeDir = dir; if (type.isPropertyKey()) { if (returnType == RelationCategory.EDGE) throw new IllegalArgumentException("Querying for edges but including a property key: " + type.name()); returnType = RelationCategory.PROPERTY; typeDir = Direction.OUT; } if (type.isEdgeLabel()) { if (returnType == RelationCategory.PROPERTY) throw new IllegalArgumentException("Querying for properties but including an edge label: " + type.name()); returnType = RelationCategory.EDGE; if (!type.isUnidirected(Direction.BOTH)) { //Make sure unidirectionality lines up if (typeDir==Direction.BOTH) { if (type.isUnidirected(Direction.OUT)) typeDir=Direction.OUT; else typeDir=Direction.IN; } else if (!type.isUnidirected(typeDir)) continue; //Directions are incompatible } } if (type.isEdgeLabel() && typeDir==Direction.BOTH && intervalConstraints.isEmpty() && orders.isEmpty()) { //TODO: This if-condition is a little too restrictive - we also want to include those cases where there // ARE intervalConstraints or orders but those cannot be covered by any sort-keys SliceQuery q = serializer.getQuery(type, typeDir, null); q.setLimit(sliceLimit); queries.add(new BackendQueryHolder<SliceQuery>(q, isIntervalFittedConditions, true)); } else { //Optimize for each direction independently Direction[] dirs = {typeDir}; if (typeDir == Direction.BOTH) { if (type.isEdgeLabel()) dirs = new Direction[]{Direction.OUT, Direction.IN}; else dirs = new Direction[]{Direction.OUT}; //property key } for (Direction direction : dirs) { /* Find best scoring relation type to answer this query with. We score each candidate by the number of conditions that each sort-keys satisfy. Equality conditions score higher than interval conditions since they are more restrictive. We assign additional points if the sort key satisfies the order of this query. */ InternalRelationType bestCandidate = null; double bestScore = Double.NEGATIVE_INFINITY; boolean bestCandidateSupportsOrder = false; for (InternalRelationType candidate : type.getRelationIndexes()) { //Filter out those that don't apply if (!candidate.isUnidirected(Direction.BOTH) && !candidate.isUnidirected(direction)) continue; if (!candidate.equals(type) && candidate.getStatus()!= SchemaStatus.ENABLED) continue; boolean supportsOrder = orders.isEmpty()?true:orders.getCommonOrder()==candidate.getSortOrder(); int currentOrder = 0; double score = 0.0; PropertyKey[] extendedSortKey = getExtendedSortKey(candidate,direction,tx); for (int i=0;i<extendedSortKey.length;i++) { PropertyKey keyType = extendedSortKey[i]; if (currentOrder<orders.size() && orders.getKey(currentOrder).equals(keyType)) currentOrder++; Interval interval = intervalConstraints.get(keyType); if (interval==null || !interval.isPoints()) { if (interval!=null) score+=1; break; } else { assert interval.isPoints(); score+=5.0/interval.getPoints().size(); } } if (supportsOrder && currentOrder==orders.size()) score+=3; if (score>bestScore) { bestScore=score; bestCandidate=candidate; bestCandidateSupportsOrder=supportsOrder && currentOrder==orders.size(); } } Preconditions.checkArgument(bestCandidate!=null,"Current graph schema does not support the specified query constraints for type: %s",type.name()); //Construct sort key constraints for the best candidate and then serialize into a SliceQuery //that is wrapped into a BackendQueryHolder PropertyKey[] extendedSortKey = getExtendedSortKey(bestCandidate,direction,tx); EdgeSerializer.TypedInterval[] sortKeyConstraints = new EdgeSerializer.TypedInterval[extendedSortKey.length]; constructSliceQueries(extendedSortKey,sortKeyConstraints,0,bestCandidate,direction,intervalConstraints, sliceLimit,isIntervalFittedConditions,bestCandidateSupportsOrder,queries); } } } if (queries.isEmpty()) return BaseVertexCentricQuery.emptyQuery(); conditions.add(getTypeCondition(ts)); } return new BaseVertexCentricQuery(QueryUtil.simplifyQNF(conditions), dir, queries, orders, limit); } private void constructSliceQueries(PropertyKey[] extendedSortKey, EdgeSerializer.TypedInterval[] sortKeyConstraints, int position, InternalRelationType bestCandidate, Direction direction, Map<RelationType,Interval> intervalConstraints, int sliceLimit, boolean isIntervalFittedConditions, boolean bestCandidateSupportsOrder, List<BackendQueryHolder<SliceQuery>> queries) { if (position<extendedSortKey.length) { PropertyKey keyType = extendedSortKey[position]; Interval interval = intervalConstraints.get(keyType); if (interval!=null) { sortKeyConstraints[position]=new EdgeSerializer.TypedInterval(keyType,interval); position++; } if (interval!=null && interval.isPoints()) { //Keep invoking recursively to see if we can satisfy more constraints... for (Object point : interval.getPoints()) { EdgeSerializer.TypedInterval[] clonedSKC = Arrays.copyOf(sortKeyConstraints,sortKeyConstraints.length); clonedSKC[position-1]=new EdgeSerializer.TypedInterval(keyType,new PointInterval(point)); constructSliceQueries(extendedSortKey, clonedSKC, position, bestCandidate, direction, intervalConstraints, sliceLimit, isIntervalFittedConditions, bestCandidateSupportsOrder, queries); } return; } } //...otherwise this is it and we can construct the slicequery boolean isFitted = isIntervalFittedConditions && position==intervalConstraints.size(); if (isFitted && position>0) { //If the last interval is open ended toward the larger values, then its not fitted because we need to //filter out NULL values which are serialized with -1 (largest value) byte up front. EdgeSerializer.TypedInterval lastInterval = sortKeyConstraints[position-1]; if (!lastInterval.interval.isPoints() && lastInterval.interval.getEnd()==null) isFitted=false; } EdgeSerializer serializer = tx.getEdgeSerializer(); SliceQuery q = serializer.getQuery(bestCandidate, direction, sortKeyConstraints); q.setLimit(computeLimit(intervalConstraints.size()-position, sliceLimit)); queries.add(new BackendQueryHolder<SliceQuery>(q, isFitted, bestCandidateSupportsOrder)); } /** * Returns the extended sort key of the given type. The extended sort key extends the type's primary sort key * by ADJACENT_ID and ID depending on the multiplicity of the type in the given direction. * It also converts the type ids to actual types. * * @param type * @param dir * @param tx * @return */ private static PropertyKey[] getExtendedSortKey(InternalRelationType type, Direction dir, StandardJanusGraphTx tx) { int additional = 0; if (!type.multiplicity().isUnique(dir)) { if (!type.multiplicity().isConstrained()) additional++; if (type.isEdgeLabel()) additional++; } PropertyKey[] entireKey = new PropertyKey[type.getSortKey().length+additional]; int i; for (i=0;i<type.getSortKey().length;i++) { entireKey[i]=tx.getExistingPropertyKey(type.getSortKey()[i]); } if (type.isEdgeLabel() && !type.multiplicity().isUnique(dir)) entireKey[i++]=ImplicitKey.ADJACENT_ID; if (!type.multiplicity().isConstrained()) entireKey[i++]=ImplicitKey.JANUSGRAPHID; return entireKey; } /** * Converts the constraint conditions of this query into a constraintMap which is passed as an argument. * If all the constraint conditions could be accounted for in the constraintMap, this method returns true, else - * if some constraints cannot be captured in an interval - it returns false to indicate that further in-memory filtering * will be necessary. * </p> * This constraint map is used in constructing the SliceQueries and query optimization since this representation * is easier to handle. * * @param conditions * @param constraintMap * @return */ private boolean compileConstraints(And<JanusGraphRelation> conditions, Map<RelationType,Interval> constraintMap) { boolean isFitted = true; for (Condition<JanusGraphRelation> condition : conditions.getChildren()) { RelationType type=null; Interval newInterval=null; if (condition instanceof Or) { Map.Entry<RelationType,Collection> orEqual = QueryUtil.extractOrCondition((Or)condition); if (orEqual!=null) { type = orEqual.getKey(); newInterval = new PointInterval(orEqual.getValue()); } } else if (condition instanceof PredicateCondition) { PredicateCondition<RelationType, JanusGraphRelation> atom = (PredicateCondition)condition; type = atom.getKey(); Interval interval = constraintMap.get(type); newInterval = intersectConstraints(interval, type, atom.getPredicate(), atom.getValue()); } if (newInterval!=null) { constraintMap.put(type,newInterval); } else isFitted = false; } if (adjacentVertex!=null) { if (adjacentVertex.hasId()) constraintMap.put(ImplicitKey.ADJACENT_ID,new PointInterval(adjacentVertex.longId())); else isFitted=false; } return isFitted; } private static Interval intersectConstraints(Interval pint, RelationType type, JanusGraphPredicate predicate, Object value) { Interval newInt; if (predicate instanceof Cmp) { switch ((Cmp) predicate) { case EQUAL: if (value==null) return null; newInt = new PointInterval(value); break; case NOT_EQUAL: return null; case LESS_THAN: newInt = new RangeInterval().setEnd(value, false); break; case LESS_THAN_EQUAL: newInt = new RangeInterval().setEnd(value, true); break; case GREATER_THAN: newInt = new RangeInterval().setStart(value, false); break; case GREATER_THAN_EQUAL: newInt = new RangeInterval().setStart(value, true); break; default: throw new AssertionError(); } } else return null; assert newInt!=null; return pint!=null?pint.intersect(newInt):newInt; } /** * Constructs a condition that is equivalent to the type constraints of this query if there are any. * * @param types * @return */ private static Condition<JanusGraphRelation> getTypeCondition(Set<RelationType> types) { assert !types.isEmpty(); if (types.size() == 1) return new RelationTypeCondition<JanusGraphRelation>(types.iterator().next()); Or<JanusGraphRelation> typeCond = new Or<JanusGraphRelation>(types.size()); for (RelationType type : types) typeCond.add(new RelationTypeCondition<JanusGraphRelation>(type)); return typeCond; } /** * Updates a given user limit based on the number of conditions that can not be fulfilled by the backend query, i.e. the query * is not fitted and these remaining conditions must be enforced by filtering in-memory. By filtering in memory, we will discard * results returned from the backend and hence we should increase the limit to account for this "waste" in order to not have * to adjust the limit too often in {@link org.janusgraph.graphdb.query.LimitAdjustingIterator}. * * @param remainingConditions * @param baseLimit * @return */ private int computeLimit(int remainingConditions, int baseLimit) { if (baseLimit==Query.NO_LIMIT) return baseLimit; assert baseLimit>0; baseLimit = Math.max(baseLimit,Math.min(HARD_MAX_LIMIT, QueryUtil.adjustLimitForTxModifications(tx, remainingConditions, baseLimit))); assert baseLimit>0; return baseLimit; } }