/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.tinkerpop.gremlin.process.traversal.strategy.optimization; import org.apache.tinkerpop.gremlin.process.computer.traversal.strategy.optimization.GraphFilterStrategy; import org.apache.tinkerpop.gremlin.process.traversal.Compare; import org.apache.tinkerpop.gremlin.process.traversal.Contains; import org.apache.tinkerpop.gremlin.process.traversal.P; import org.apache.tinkerpop.gremlin.process.traversal.Step; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy; import org.apache.tinkerpop.gremlin.process.traversal.step.LambdaHolder; import org.apache.tinkerpop.gremlin.process.traversal.step.filter.AndStep; import org.apache.tinkerpop.gremlin.process.traversal.step.filter.DedupGlobalStep; import org.apache.tinkerpop.gremlin.process.traversal.step.filter.DropStep; import org.apache.tinkerpop.gremlin.process.traversal.step.filter.FilterStep; import org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasStep; import org.apache.tinkerpop.gremlin.process.traversal.step.filter.OrStep; import org.apache.tinkerpop.gremlin.process.traversal.step.filter.RangeGlobalStep; import org.apache.tinkerpop.gremlin.process.traversal.step.filter.TraversalFilterStep; import org.apache.tinkerpop.gremlin.process.traversal.step.map.MatchStep; import org.apache.tinkerpop.gremlin.process.traversal.step.map.VertexStep; import org.apache.tinkerpop.gremlin.process.traversal.step.util.EmptyStep; import org.apache.tinkerpop.gremlin.process.traversal.step.util.HasContainer; import org.apache.tinkerpop.gremlin.process.traversal.strategy.AbstractTraversalStrategy; import org.apache.tinkerpop.gremlin.process.traversal.util.OrP; import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalHelper; import org.apache.tinkerpop.gremlin.structure.Edge; import org.apache.tinkerpop.gremlin.structure.T; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; /** * InlineFilterStrategy analyzes filter-steps with child traversals that themselves are pure filters. * If the child traversals are pure filters then the wrapping parent filter is not needed and thus, the * children can be "inlined." * <p/> * * @author Marko A. Rodriguez (http://markorodriguez.com) * @example <pre> * __.outE().hasLabel(eq("knows").or(eq("created"))).inV() // is replaced by __.outE("knows", "created").inV() * __.filter(has("name","marko")) // is replaced by __.has("name","marko") * __.and(has("name"),has("age")) // is replaced by __.has("name").has("age") * __.and(filter(has("name","marko").has("age")),hasNot("blah")) // is replaced by __.has("name","marko").has("age").hasNot("blah") * __.match(as('a').has(key,value),...) // is replaced by __.as('a').has(key,value).match(...) * </pre> */ public final class InlineFilterStrategy extends AbstractTraversalStrategy<TraversalStrategy.OptimizationStrategy> implements TraversalStrategy.OptimizationStrategy { private static final InlineFilterStrategy INSTANCE = new InlineFilterStrategy(); private static final Set<Class<? extends OptimizationStrategy>> POSTS = new HashSet<>(Arrays.asList( GraphFilterStrategy.class, AdjacentToIncidentStrategy.class)); private static final Set<Class<? extends OptimizationStrategy>> PRIORS = new HashSet<>(Arrays.asList( FilterRankingStrategy.class, IdentityRemovalStrategy.class, MatchPredicateStrategy.class)); private InlineFilterStrategy() { } @Override public void apply(final Traversal.Admin<?, ?> traversal) { boolean changed = true; // recursively walk child traversals trying to inline them into the current traversal line. while (changed) { changed = false; final Iterator<FilterStep> filterStepIterator = TraversalHelper.getStepsOfAssignableClass(FilterStep.class, traversal).iterator(); while (!changed && filterStepIterator.hasNext()) { final FilterStep<?> step = filterStepIterator.next(); changed = step instanceof HasStep && InlineFilterStrategy.processHasStep((HasStep) step, traversal) || step instanceof TraversalFilterStep && InlineFilterStrategy.processTraversalFilterStep((TraversalFilterStep) step, traversal) || step instanceof OrStep && InlineFilterStrategy.processOrStep((OrStep) step, traversal) || step instanceof AndStep && InlineFilterStrategy.processAndStep((AndStep) step, traversal); } if (!changed && traversal.getParent() instanceof EmptyStep) { final Iterator<MatchStep> matchStepIterator = TraversalHelper.getStepsOfClass(MatchStep.class, traversal).iterator(); while (!changed && matchStepIterator.hasNext()) { if (InlineFilterStrategy.processMatchStep(matchStepIterator.next(), traversal)) changed = true; } } } } //////////////////////////// /////////////////////////// private static final boolean processHasStep(final HasStep<?> step, final Traversal.Admin<?, ?> traversal) { if (step.getPreviousStep() instanceof HasStep) { final HasStep<?> previousStep = (HasStep<?>) step.getPreviousStep(); for (final HasContainer hasContainer : step.getHasContainers()) { previousStep.addHasContainer(hasContainer); } TraversalHelper.copyLabels(step, previousStep, false); traversal.removeStep(step); return true; } else if (step.getPreviousStep() instanceof VertexStep && ((VertexStep) step.getPreviousStep()).returnsEdge() && 0 == ((VertexStep) step.getPreviousStep()).getEdgeLabels().length) { final VertexStep<Edge> previousStep = (VertexStep<Edge>) step.getPreviousStep(); final List<String> edgeLabels = new ArrayList<>(); for (final HasContainer hasContainer : new ArrayList<>(step.getHasContainers())) { if (hasContainer.getKey().equals(T.label.getAccessor())) { if (hasContainer.getBiPredicate() == Compare.eq && hasContainer.getValue() instanceof String && edgeLabels.isEmpty()) { edgeLabels.add((String) hasContainer.getValue()); step.removeHasContainer(hasContainer); } else if (hasContainer.getBiPredicate() == Contains.within && hasContainer.getValue() instanceof Collection && ((Collection) hasContainer.getValue()).containsAll(edgeLabels)) { edgeLabels.addAll((Collection<String>) hasContainer.getValue()); step.removeHasContainer(hasContainer); } else if (hasContainer.getPredicate() instanceof OrP && edgeLabels.isEmpty()) { boolean removeContainer = true; final List<P<?>> orps = ((OrP) hasContainer.getPredicate()).getPredicates(); final List<String> newEdges = new ArrayList<>(); for (int i = 0; i < orps.size(); i++) { if (orps.get(i).getBiPredicate() == Compare.eq && orps.get(i).getValue() instanceof String) newEdges.add((String) orps.get(i).getValue()); else { removeContainer = false; break; } } if (removeContainer) { edgeLabels.addAll(newEdges); step.removeHasContainer(hasContainer); } } } } if (!edgeLabels.isEmpty()) { final VertexStep<Edge> newVertexStep = new VertexStep<>(traversal, Edge.class, previousStep.getDirection(), edgeLabels.toArray(new String[edgeLabels.size()])); TraversalHelper.replaceStep(previousStep, newVertexStep, traversal); TraversalHelper.copyLabels(previousStep, newVertexStep, false); if (step.getHasContainers().isEmpty()) { TraversalHelper.copyLabels(step, newVertexStep, false); traversal.removeStep(step); } return true; } return false; } else return false; } private static final boolean processTraversalFilterStep(final TraversalFilterStep<?> step, final Traversal.Admin<?, ?> traversal) { final Traversal.Admin<?, ?> childTraversal = step.getLocalChildren().get(0); if (TraversalHelper.hasAllStepsOfClass(childTraversal, FilterStep.class) && !TraversalHelper.hasStepOfClass(childTraversal, DropStep.class, RangeGlobalStep.class, DedupGlobalStep.class, LambdaHolder.class)) { TraversalHelper.applySingleLevelStrategies(traversal, childTraversal, InlineFilterStrategy.class); final Step<?, ?> finalStep = childTraversal.getEndStep(); TraversalHelper.insertTraversal((Step) step, childTraversal, traversal); TraversalHelper.copyLabels(step, finalStep, false); traversal.removeStep(step); return true; } return false; } private static final boolean processOrStep(final OrStep<?> step, final Traversal.Admin<?, ?> traversal) { boolean process = true; String key = null; P predicate = null; final List<String> labels = new ArrayList<>(); for (final Traversal.Admin<?, ?> childTraversal : step.getLocalChildren()) { InlineFilterStrategy.instance().apply(childTraversal); // todo: this may be a bad idea, but I can't seem to find a test case to break it for (final Step<?, ?> childStep : childTraversal.getSteps()) { if (childStep instanceof HasStep) { for (final HasContainer hasContainer : ((HasStep<?>) childStep).getHasContainers()) { if (null == key) key = hasContainer.getKey(); else if (!hasContainer.getKey().equals(key)) { process = false; break; } predicate = null == predicate ? hasContainer.getPredicate() : predicate.or(hasContainer.getPredicate()); } labels.addAll(childStep.getLabels()); } else { process = false; break; } } if (!process) break; } if (process) { final HasStep hasStep = new HasStep<>(traversal, new HasContainer(key, predicate)); TraversalHelper.replaceStep(step, hasStep, traversal); TraversalHelper.copyLabels(step, hasStep, false); for (final String label : labels) { hasStep.addLabel(label); } return true; } return false; } private static final boolean processAndStep(final AndStep<?> step, final Traversal.Admin<?, ?> traversal) { boolean process = true; for (final Traversal.Admin<?, ?> childTraversal : step.getLocalChildren()) { if (!TraversalHelper.hasAllStepsOfClass(childTraversal, FilterStep.class) || TraversalHelper.hasStepOfClass(childTraversal, DropStep.class, RangeGlobalStep.class, DedupGlobalStep.class, LambdaHolder.class)) { process = false; break; } } if (process) { final List<Traversal.Admin<?, ?>> childTraversals = (List) step.getLocalChildren(); Step<?, ?> finalStep = null; for (int i = childTraversals.size() - 1; i >= 0; i--) { final Traversal.Admin<?, ?> childTraversal = childTraversals.get(i); TraversalHelper.applySingleLevelStrategies(traversal, childTraversal, InlineFilterStrategy.class); if (null == finalStep) finalStep = childTraversal.getEndStep(); TraversalHelper.insertTraversal((Step) step, childTraversals.get(i), traversal); } if (null != finalStep) TraversalHelper.copyLabels(step, finalStep, false); traversal.removeStep(step); return true; } return false; } private static final boolean processMatchStep(final MatchStep<?, ?> step, final Traversal.Admin<?, ?> traversal) { if (step.getPreviousStep() instanceof EmptyStep) return false; boolean changed = false; final String startLabel = MatchStep.Helper.computeStartLabel(step.getGlobalChildren()); for (final Traversal.Admin<?, ?> matchTraversal : new ArrayList<>(step.getGlobalChildren())) { if (TraversalHelper.hasAllStepsOfClass(matchTraversal, HasStep.class, MatchStep.MatchStartStep.class, MatchStep.MatchEndStep.class) && matchTraversal.getStartStep() instanceof MatchStep.MatchStartStep && startLabel.equals(((MatchStep.MatchStartStep) matchTraversal.getStartStep()).getSelectKey().orElse(null))) { changed = true; step.removeGlobalChild(matchTraversal); final String endLabel = ((MatchStep.MatchEndStep) matchTraversal.getEndStep()).getMatchKey().orElse(null); // why would this exist? but just in case matchTraversal.removeStep(0); // remove MatchStartStep matchTraversal.removeStep(matchTraversal.getSteps().size() - 1); // remove MatchEndStep TraversalHelper.applySingleLevelStrategies(traversal, matchTraversal, InlineFilterStrategy.class); matchTraversal.getEndStep().addLabel(startLabel); if (null != endLabel) matchTraversal.getEndStep().addLabel(endLabel); TraversalHelper.insertTraversal((Step) step.getPreviousStep(), matchTraversal, traversal); } } if (step.getGlobalChildren().isEmpty()) traversal.removeStep(step); return changed; } @Override public Set<Class<? extends OptimizationStrategy>> applyPost() { return POSTS; } @Override public Set<Class<? extends OptimizationStrategy>> applyPrior() { return PRIORS; } public static InlineFilterStrategy instance() { return INSTANCE; } }