/**
* 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.atlas.gremlin.optimizer;
import java.util.ArrayList;
import java.util.List;
import org.apache.atlas.gremlin.GremlinExpressionFactory;
import org.apache.atlas.groovy.AbstractFunctionExpression;
import org.apache.atlas.groovy.FunctionCallExpression;
import org.apache.atlas.groovy.GroovyExpression;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Optimizer that pulls has expressions out of an 'and' expression.
*
* For example:
*
* g.V().and(has('x'),has('y')
*
* is optimized to:
*
* g.V().has('x').has('y')
*
* There are certain cases where it is not safe to move an expression out
* of the 'and'. For example, in the expression
*
* g.V().and(has('x').out('y'),has('z'))
*
* has('x').out('y') cannot be moved out of the 'and', since it changes the value of the traverser.
*
* At this time, the ExpandAndsOptimizer is not able to handle this scenario, so we don't extract
* that expression. In this case, the result is:
*
* g.V().has('z').and(has('x').out('y'))
*
* The optimizer will call ExpandAndsOptimization recursively on the children, so
* there is no need to recursively update the children here.
*
* @param expr
* @param context
* @return the expressions that should be unioned together to get the query result
*/
public class ExpandAndsOptimization implements GremlinOptimization {
private static final Logger logger_ = LoggerFactory.getLogger(ExpandAndsOptimization.class);
private final GremlinExpressionFactory factory;
public ExpandAndsOptimization(GremlinExpressionFactory factory) {
this.factory = factory;
}
@Override
public boolean appliesTo(GroovyExpression expr, OptimizationContext contxt) {
return expr instanceof FunctionCallExpression && ((FunctionCallExpression)expr).getFunctionName().equals("and");
}
/**
* Expands the given and expression. There is no need to recursively
* expand the children here. This method is called recursively by
* GremlinQueryOptimier on the children.
*
*/
@Override
public GroovyExpression apply(GroovyExpression expr, OptimizationContext context) {
FunctionCallExpression exprAsFunction = (FunctionCallExpression)expr;
GroovyExpression result = exprAsFunction.getCaller();
List<GroovyExpression> nonExtractableArguments = new ArrayList<>();
for(GroovyExpression argument : exprAsFunction.getArguments()) {
if (GremlinQueryOptimizer.isExtractable(argument)) {
//Set the caller of the deepest expression in the call hierarchy
//of the argument to point to the current result.
//For example, if result is "g.V()" and the updatedArgument is "has('x').has('y')",
//updatedArgument would be a tree like this:
//
// has('y')
// /
// / caller
// |/_
// has('x')
// /
// / caller
// |/_
// (null)
//
//We would set the caller of has('x') to be g.V(), so result would become g.V().has('x').has('y').
//
// Note: This operation is currently done by making a copy of the argument tree. That should
// be changed.
result = GremlinQueryOptimizer.copyWithNewLeafNode(
(AbstractFunctionExpression) argument, result);
} else {
logger_.warn("Found non-extractable argument '{}' in the 'and' expression '{}'",argument.toString(), expr.toString());
nonExtractableArguments.add(argument);
}
}
if (!nonExtractableArguments.isEmpty()) {
//add a final 'and' call with the arguments that could not be extracted
result = factory.generateLogicalExpression(result, "and", nonExtractableArguments);
}
return result;
}
@Override
public boolean isApplyRecursively() {
return true;
}
}