blob: 9179d3aac0221a50877be756eb9968c7013d29b1 [file] [log] [blame]
/*
* 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.step.map.VertexProgramStep;
import org.apache.tinkerpop.gremlin.process.traversal.Pop;
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.PathProcessor;
import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
import org.apache.tinkerpop.gremlin.process.traversal.step.filter.TraversalFilterStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.filter.WhereTraversalStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.PathStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.SelectOneStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.SelectStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.TraversalMapStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.TreeStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.IdentityStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.util.EmptyStep;
import org.apache.tinkerpop.gremlin.process.traversal.strategy.AbstractTraversalStrategy;
import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalHelper;
import org.apache.tinkerpop.gremlin.structure.Graph;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
/**
* {@code PathProcessStrategy} is an OLAP strategy that does its best to turn non-local children in {@code where()}
* and {@code select()} into local children by inlining components of the non-local child. In this way,
* {@code PathProcessStrategy} helps to ensure that more traversals meet the local child constraint imposed on OLAP
* traversals.
*
* @author Marko A. Rodriguez (http://markorodriguez.com)
* @example <pre>
* __.select(a).by(x) // is replaced by select(a).map(x)
* __.select(a,b).by(x).by(y) // is replaced by select(a).by(x).as(a).select(b).by(y).as(b).select(a,b)
* __.where(as(a).out().as(b)) // is replaced by as(xyz).select(a).where(out().as(b)).select(xyz)
* __.where(as(a).out()) // is replaced by as(xyz).select(a).filter(out()).select(xyz)
* </pre>
*/
public final class PathProcessorStrategy extends AbstractTraversalStrategy<TraversalStrategy.OptimizationStrategy> implements TraversalStrategy.OptimizationStrategy {
private static final PathProcessorStrategy INSTANCE = new PathProcessorStrategy();
private static final boolean IS_TESTING = Boolean.valueOf(System.getProperty("is.testing", "false"));
private static final String MARKER = Graph.Hidden.hide("gremlin.pathProcessor");
private static final Set<Class> INVALIDATING_STEP_CLASSES = new HashSet<>(Arrays.asList(PathStep.class, TreeStep.class, LambdaHolder.class));
private PathProcessorStrategy() {
}
@Override
public Set<Class<? extends OptimizationStrategy>> applyPrior() {
return Collections.singleton(MatchPredicateStrategy.class);
}
@Override
public Set<Class<? extends OptimizationStrategy>> applyPost() {
return Collections.singleton(InlineFilterStrategy.class);
}
@Override
public void apply(final Traversal.Admin<?, ?> traversal) {
// using a hidden label marker to denote whether the traversal should not be processed by this strategy
if ((traversal.getParent() instanceof EmptyStep || traversal.getParent() instanceof VertexProgramStep) &&
TraversalHelper.hasStepOfAssignableClassRecursively(INVALIDATING_STEP_CLASSES, traversal))
TraversalHelper.applyTraversalRecursively(t -> t.getStartStep().addLabel(MARKER), traversal);
if (traversal.getStartStep().getLabels().contains(MARKER)) {
traversal.getStartStep().removeLabel(MARKER);
return;
}
// process where(as("a").out()...) => select("a").where(out()...)
final List<WhereTraversalStep> whereTraversalSteps = TraversalHelper.getStepsOfClass(WhereTraversalStep.class, traversal);
for (final WhereTraversalStep<?> whereTraversalStep : whereTraversalSteps) {
final Traversal.Admin<?, ?> localChild = whereTraversalStep.getLocalChildren().get(0);
if ((localChild.getStartStep() instanceof WhereTraversalStep.WhereStartStep) &&
!((WhereTraversalStep.WhereStartStep) localChild.getStartStep()).getScopeKeys().isEmpty()) {
boolean done = false;
while (!done) {
done = true;
final int index = TraversalHelper.stepIndex(whereTraversalStep, traversal);
if (whereTraversalStep.getPreviousStep() instanceof SelectStep) {
done = false;
traversal.removeStep(index);
traversal.addStep(index - 1, whereTraversalStep);
}
}
final WhereTraversalStep.WhereStartStep<?> whereStartStep = (WhereTraversalStep.WhereStartStep<?>) localChild.getStartStep();
int index = TraversalHelper.stepIndex(whereTraversalStep, traversal);
final SelectOneStep<?, ?> selectOneStep = new SelectOneStep<>(traversal, Pop.last, whereStartStep.getScopeKeys().iterator().next());
traversal.addStep(index, selectOneStep);
final String generatedLabel = PathProcessorStrategy.generateLabel();
if (selectOneStep.getPreviousStep() instanceof EmptyStep) {
TraversalHelper.insertBeforeStep(new IdentityStep<>(traversal), selectOneStep, traversal);
index++;
}
selectOneStep.getPreviousStep().addLabel(generatedLabel);
TraversalHelper.insertAfterStep(new SelectOneStep<>(traversal, Pop.last, generatedLabel), whereTraversalStep, traversal);
whereStartStep.removeScopeKey();
// process where(as("a").out()) => as('xyz').select("a").filter(out()).select('xyz')
if (!(localChild.getEndStep() instanceof WhereTraversalStep.WhereEndStep)) {
localChild.removeStep(localChild.getStartStep());
traversal.addStep(index + 1, new TraversalFilterStep<>(traversal, localChild));
traversal.removeStep(whereTraversalStep);
}
}
}
// process select("a","b").by(...).by(...)
final List<SelectStep> selectSteps = TraversalHelper.getStepsOfClass(SelectStep.class, traversal);
for (final SelectStep<?, Object> selectStep : selectSteps) {
if (selectStep.getPop() != Pop.all && selectStep.getPop() != Pop.mixed && // TODO: necessary?
selectStep.getMaxRequirement().compareTo(PathProcessor.ElementRequirement.ID) > 0) {
boolean oneLabel = true;
for (final String key : selectStep.getScopeKeys()) {
if (labelCount(key, TraversalHelper.getRootTraversal(traversal)) > 1) {
oneLabel = false;
break;
}
}
if (!oneLabel)
continue;
final int index = TraversalHelper.stepIndex(selectStep, traversal);
final Map<String, Traversal.Admin<Object, Object>> byTraversals = selectStep.getByTraversals();
final String[] keys = new String[byTraversals.size()];
int counter = 0;
for (final Map.Entry<String, Traversal.Admin<Object, Object>> entry : byTraversals.entrySet()) {
final SelectOneStep selectOneStep = new SelectOneStep(traversal, selectStep.getPop(), entry.getKey());
final TraversalMapStep<?, ?> mapStep = new TraversalMapStep<>(traversal, entry.getValue().clone());
mapStep.addLabel(entry.getKey());
traversal.addStep(index + 1, mapStep);
traversal.addStep(index + 1, selectOneStep);
keys[counter++] = entry.getKey();
}
traversal.addStep(index + 1 + (byTraversals.size() * 2), new SelectStep(traversal, Pop.last, keys));
traversal.removeStep(index);
}
}
// process select("a").by(...)
final List<SelectOneStep> selectOneSteps = TraversalHelper.getStepsOfClass(SelectOneStep.class, traversal);
for (final SelectOneStep<?, ?> selectOneStep : selectOneSteps) {
if (selectOneStep.getPop() != Pop.all && selectOneStep.getPop() != Pop.mixed && // TODO: necessary?
selectOneStep.getMaxRequirement().compareTo(PathProcessor.ElementRequirement.ID) > 0 &&
labelCount(selectOneStep.getScopeKeys().iterator().next(), TraversalHelper.getRootTraversal(traversal)) <= 1) {
final int index = TraversalHelper.stepIndex(selectOneStep, traversal);
final Traversal.Admin<?, ?> localChild = selectOneStep.getLocalChildren().get(0);
selectOneStep.removeLocalChild(localChild);
final TraversalMapStep<?, ?> mapStep = new TraversalMapStep<>(traversal, localChild.clone());
traversal.addStep(index + 1, mapStep);
}
}
}
public static PathProcessorStrategy instance() {
return INSTANCE;
}
private static String generateLabel() {
return IS_TESTING ? "xyz" : UUID.randomUUID().toString();
}
private static int labelCount(final String label, final Traversal.Admin<?, ?> traversal) {
int count = 0;
for (final Step step : traversal.getSteps()) {
if (step.getLabels().contains(label))
count++;
if (step instanceof TraversalParent) {
count = count + ((TraversalParent) step).getLocalChildren().stream().map(t -> labelCount(label, t)).reduce(0, (a, b) -> a + b);
count = count + ((TraversalParent) step).getGlobalChildren().stream().map(t -> labelCount(label, t)).reduce(0, (a, b) -> a + b);
}
}
return count;
}
}