blob: d603150f78566cef9f39cdce44c37cd22af59293 [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_METHOD 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;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.atlas.AtlasException;
import org.apache.atlas.groovy.AbstractFunctionExpression;
import org.apache.atlas.groovy.ArithmeticExpression;
import org.apache.atlas.groovy.ArithmeticExpression.ArithmeticOperator;
import org.apache.atlas.groovy.CastExpression;
import org.apache.atlas.groovy.ClosureExpression;
import org.apache.atlas.groovy.FieldExpression;
import org.apache.atlas.groovy.FunctionCallExpression;
import org.apache.atlas.groovy.GroovyExpression;
import org.apache.atlas.groovy.IdentifierExpression;
import org.apache.atlas.groovy.ListExpression;
import org.apache.atlas.groovy.LiteralExpression;
import org.apache.atlas.groovy.TraversalStepType;
import org.apache.atlas.groovy.TypeCoersionExpression;
import org.apache.atlas.groovy.VariableAssignmentExpression;
import org.apache.atlas.query.GraphPersistenceStrategies;
import org.apache.atlas.query.IntSequence;
import org.apache.atlas.query.TypeUtils.FieldInfo;
import org.apache.atlas.repository.graph.AtlasGraphProvider;
import org.apache.atlas.repository.graphdb.AtlasEdgeDirection;
import org.apache.atlas.repository.graphdb.GremlinVersion;
import org.apache.atlas.typesystem.types.IDataType;
import org.apache.atlas.typesystem.types.TypeSystem;
import org.apache.atlas.typesystem.types.cache.TypeCache.TYPE_FILTER;
import org.apache.atlas.util.AtlasRepositoryConfiguration;
import com.google.common.collect.ImmutableList;
/**
* Factory to generate Groovy expressions representing Gremlin syntax that that
* are independent of the specific version of Gremlin that is being used.
*
*/
public abstract class GremlinExpressionFactory {
private static final String G_VARIABLE = "g";
private static final String IT_VARIABLE = "it";
protected static final String SET_CLASS = "Set";
private static final String OBJECT_FIELD = "object";
protected static final String V_METHOD = "V";
protected static final String FILTER_METHOD = "filter";
private static final String PATH_METHOD = "path";
private static final String AS_METHOD = "as";
private static final String IN_OPERATOR = "in";
protected static final String HAS_METHOD = "has";
protected static final String TO_LOWER_CASE_METHOD = "toLowerCase";
protected static final String SELECT_METHOD = "select";
protected static final String ORDER_METHOD = "order";
protected static final String FILL_METHOD = "fill";
protected static final String MATCHES = "matches";
public static final GremlinExpressionFactory INSTANCE = AtlasGraphProvider.getGraphInstance()
.getSupportedGremlinVersion() == GremlinVersion.THREE ? new Gremlin3ExpressionFactory()
: new Gremlin2ExpressionFactory();
/**
* Returns the unqualified name of the class used in this version of gremlin to
* represent Gremlin queries as they are being generated.
* @return
*/
public abstract String getTraversalExpressionClass();
/**
* Gets the expression to use as the parent when translating the loop
* expression in a loop
*
* @param inputQry
* the
* @return
*/
public abstract GroovyExpression getLoopExpressionParent(GroovyExpression inputQry);
/**
* Generates a loop expression.
*
* @param parent
* the parent of the loop expression
* @param emitExpr
* Expression with the value that should be emitted by the loop
* expression.
* @param loopExpr
* the query expression that is being executed repeatedly
* executed in a loop
* @param alias
* The alias of the expression being looped over
* @param times
* the number of times to repeat, or null if a times condition
* should not be used.
* @return
*/
public abstract GroovyExpression generateLoopExpression(GroovyExpression parent, GraphPersistenceStrategies s, IDataType dataType,
GroovyExpression loopExpr, String alias, Integer times);
/**
* Generates a logical (and/or) expression with the given operands.
* @param parent
* @param operator
* @param operands
* @return
*/
public abstract GroovyExpression generateLogicalExpression(GroovyExpression parent, String operator,
List<GroovyExpression> operands);
/**
* Generates a back reference expression that refers to the given alias.
*
* @param parent
* @param inSelect
* @param alias
* @return
*/
public abstract GroovyExpression generateBackReferenceExpression(GroovyExpression parent, boolean inSelect,
String alias);
/**
* Generates a select expression
*
* @param parent
* @param sourceNames
* the names of the select fields
* @param srcExprs
* the corresponding values to return
* @return
*/
public abstract GroovyExpression generateSelectExpression(GroovyExpression parent,
List<LiteralExpression> sourceNames, List<GroovyExpression> srcExprs);
/**
* Generates a an expression that gets the value of the given property from the
* vertex presented by the parent.
*
* @param parent
* @param fInfo
* @param propertyName
* @param inSelect
* @return
*/
public abstract GroovyExpression generateFieldExpression(GroovyExpression parent, FieldInfo fInfo,
String propertyName, boolean inSelect);
/**
* Generates a has expression that checks whether the vertices match a specific condition
*
* @param s
* @param parent the object that we should call apply the "has" condition to.
* @param propertyName the name of the property whose value we are comparing
* @param symbol comparsion operator symbol ('=','<', etc.)
* @param requiredValue the value to compare against
* @param fInfo info about the field whose value we are checking
* @return
* @throws AtlasException
*/
public abstract GroovyExpression generateHasExpression(GraphPersistenceStrategies s, GroovyExpression parent,
String propertyName, String symbol, GroovyExpression requiredValue, FieldInfo fInfo) throws AtlasException;
public abstract GroovyExpression generateLikeExpressionUsingFilter(GroovyExpression parent, String propertyName,
GroovyExpression propertyValue) throws AtlasException;
/**
* Generates a range expression
*
* @param parent
* @param startIndex
* @param endIndex
* @return
*/
public abstract GroovyExpression generateRangeExpression(GroovyExpression parent, int startIndex, int endIndex);
/**
* Determines if the specified expression is a range method call.
*
* @param expr
* @return
*/
public abstract boolean isRangeExpression(GroovyExpression expr);
/**
* Set the start index and end index of a range expression
*
* @param expr
* @param startIndex
* @param endIndex
*/
public abstract void setRangeParameters(GroovyExpression expr, int startIndex, int endIndex);
/**
* If the specified function expression is a range expression, returns the start and end index parameters
* otherwise returns null.
*
* @param expr
* @return int array with two elements - element 0 is start index, element 1 is end index
*/
public abstract int[] getRangeParameters(AbstractFunctionExpression expr);
/**
* Generates an order by expression
*
* @param parent
* @param translatedOrderBy
* @param isAscending
* @return
*/
public abstract GroovyExpression generateOrderByExpression(GroovyExpression parent,
List<GroovyExpression> translatedOrderBy, boolean isAscending);
/**
* Determines if specified expression is an order method call
*
* @param expr
* @return
*/
public boolean isOrderExpression(GroovyExpression expr) {
if (expr instanceof FunctionCallExpression) {
FunctionCallExpression functionCallExpression = (FunctionCallExpression) expr;
if (functionCallExpression.getFunctionName().equals(ORDER_METHOD)) {
return true;
}
}
return false;
}
/**
* Returns the Groovy expressions that should be used as the parents when
* translating an order by expression. This is needed because Gremlin 2 and
* 3 handle order by expressions very differently.
*
*/
public abstract List<GroovyExpression> getOrderFieldParents();
/**
* Returns the expression that represents an anonymous graph traversal.
*
* @return
*/
public abstract GroovyExpression getAnonymousTraversalExpression();
public boolean isLeafAnonymousTraversalExpression(GroovyExpression expr) {
if(!(expr instanceof FunctionCallExpression)) {
return false;
}
FunctionCallExpression functionCallExpr = (FunctionCallExpression)expr;
if(functionCallExpr.getCaller() != null) {
return false;
}
return functionCallExpr.getFunctionName().equals("_") & functionCallExpr.getArguments().size() == 0;
}
/**
* Returns an expression representing
*
* @return
*/
public abstract GroovyExpression getFieldInSelect();
/**
* Generates the expression the serves as the root of the Gremlin query.
* @param varExpr variable containing the vertices to traverse
* @return
*/
protected abstract GroovyExpression initialExpression(GroovyExpression varExpr, GraphPersistenceStrategies s);
/**
* Generates an expression that tests whether the vertex represented by the 'toTest'
* expression represents an instance of the specified type, checking both the type
* and super type names.
*
* @param s
* @param typeName
* @param itRef
* @return
*/
protected abstract GroovyExpression typeTestExpression(GraphPersistenceStrategies s, String typeName,
GroovyExpression vertexExpr);
/**
/**
* Generates a sequence of groovy expressions that filter the vertices to only
* those that match the specified type. If GraphPersistenceStrategies.collectTypeInstancesIntoVar()
* is set and the gremlin optimizer is disabled, the vertices are put into a variable whose name is generated
* from the specified IntSequence. The last item in the result will be a graph traversal restricted to only
* the matching vertices.
*/
public List<GroovyExpression> generateTypeTestExpression(GraphPersistenceStrategies s, GroovyExpression parent,
String typeName, IntSequence intSeq) throws AtlasException {
if(AtlasRepositoryConfiguration.isGremlinOptimizerEnabled()) {
GroovyExpression superTypeAttributeNameExpr = new LiteralExpression(s.superTypeAttributeName());
GroovyExpression typeNameExpr = new LiteralExpression(typeName);
GroovyExpression superTypeMatchesExpr = new FunctionCallExpression(TraversalStepType.FILTER, HAS_METHOD, superTypeAttributeNameExpr,
typeNameExpr);
GroovyExpression typeAttributeNameExpr = new LiteralExpression(s.typeAttributeName());
GroovyExpression typeMatchesExpr = new FunctionCallExpression(TraversalStepType.FILTER, HAS_METHOD, typeAttributeNameExpr,
typeNameExpr);
GroovyExpression result = new FunctionCallExpression(TraversalStepType.FILTER, parent, "or", typeMatchesExpr, superTypeMatchesExpr);
return Collections.singletonList(result);
}
else {
if (s.filterBySubTypes()) {
return typeTestExpressionUsingInFilter(s, parent, typeName);
} else if (s.collectTypeInstancesIntoVar()) {
return typeTestExpressionMultiStep(s, typeName, intSeq);
} else {
return typeTestExpressionUsingFilter(s, parent, typeName);
}
}
}
private List<GroovyExpression> typeTestExpressionUsingInFilter(GraphPersistenceStrategies s, GroovyExpression parent,
final String typeName) throws AtlasException {
List<GroovyExpression> typeNames = new ArrayList<>();
typeNames.add(new LiteralExpression(typeName));
Map<TYPE_FILTER, String> filters = new HashMap<TYPE_FILTER, String>() {{
put(TYPE_FILTER.SUPERTYPE, typeName);
}};
ImmutableList<String> subTypes = TypeSystem.getInstance().getTypeNames(filters);
if (!subTypes.isEmpty()) {
for (String subType : subTypes) {
typeNames.add(new LiteralExpression(subType));
}
}
GroovyExpression inFilterExpr = generateHasExpression(s, parent, s.typeAttributeName(), IN_OPERATOR,
new ListExpression(typeNames), null);
return Collections.singletonList(inFilterExpr);
}
private List<GroovyExpression> typeTestExpressionMultiStep(GraphPersistenceStrategies s, String typeName,
IntSequence intSeq) {
String varName = "_var_" + intSeq.next();
GroovyExpression varExpr = new IdentifierExpression(varName);
List<GroovyExpression> result = new ArrayList<>();
result.add(newSetVar(varName));
result.add(fillVarWithTypeInstances(s, typeName, varName));
result.add(fillVarWithSubTypeInstances(s, typeName, varName));
result.add(initialExpression(varExpr, s));
return result;
}
private GroovyExpression newSetVar(String varName) {
GroovyExpression castExpr = new TypeCoersionExpression(new ListExpression(), SET_CLASS);
return new VariableAssignmentExpression(varName, castExpr);
}
private GroovyExpression fillVarWithTypeInstances(GraphPersistenceStrategies s, String typeName, String fillVar) {
GroovyExpression graphExpr = getAllVerticesExpr();
GroovyExpression typeAttributeNameExpr = new LiteralExpression(s.typeAttributeName());
GroovyExpression typeNameExpr = new LiteralExpression(typeName);
GroovyExpression hasExpr = new FunctionCallExpression(graphExpr, HAS_METHOD, typeAttributeNameExpr, typeNameExpr);
GroovyExpression fillExpr = new FunctionCallExpression(hasExpr, FILL_METHOD, new IdentifierExpression(fillVar));
return fillExpr;
}
private GroovyExpression fillVarWithSubTypeInstances(GraphPersistenceStrategies s, String typeName,
String fillVar) {
GroovyExpression graphExpr = getAllVerticesExpr();
GroovyExpression superTypeAttributeNameExpr = new LiteralExpression(s.superTypeAttributeName());
GroovyExpression typeNameExpr = new LiteralExpression(typeName);
GroovyExpression hasExpr = new FunctionCallExpression(graphExpr, HAS_METHOD, superTypeAttributeNameExpr, typeNameExpr);
GroovyExpression fillExpr = new FunctionCallExpression(hasExpr, FILL_METHOD, new IdentifierExpression(fillVar));
return fillExpr;
}
private List<GroovyExpression> typeTestExpressionUsingFilter(GraphPersistenceStrategies s, GroovyExpression parent,
String typeName) {
GroovyExpression itExpr = getItVariable();
GroovyExpression typeTestExpr = typeTestExpression(s, typeName, itExpr);
GroovyExpression closureExpr = new ClosureExpression(typeTestExpr);
GroovyExpression filterExpr = new FunctionCallExpression(parent, FILTER_METHOD, closureExpr);
return Collections.singletonList(filterExpr);
}
/**
* Generates an expression which checks whether the vertices in the query have
* a field with the given name.
*
* @param parent
* @param fieldName
* @return
*/
public GroovyExpression generateUnaryHasExpression(GroovyExpression parent, String fieldName) {
return new FunctionCallExpression(TraversalStepType.FILTER, parent, HAS_METHOD, new LiteralExpression(fieldName));
}
/**
* Generates a path expression
*
* @param parent
* @return
*/
public GroovyExpression generatePathExpression(GroovyExpression parent) {
return new FunctionCallExpression(TraversalStepType.MAP_TO_VALUE, parent, PATH_METHOD);
}
/**
* Generates the emit expression used in loop expressions.
* @param s
* @param dataType
* @return
*/
protected GroovyExpression generateLoopEmitExpression(GraphPersistenceStrategies s, IDataType dataType) {
return typeTestExpression(s, dataType.getName(), getCurrentObjectExpression());
}
/**
* Generates an alias expression
*
* @param parent
* @param alias
* @return
*/
public GroovyExpression generateAliasExpression(GroovyExpression parent, String alias) {
return new FunctionCallExpression(TraversalStepType.SIDE_EFFECT, parent, AS_METHOD, new LiteralExpression(alias));
}
/**
* Generates an expression that gets the vertices adjacent to the vertex in 'parent'
* in the specified direction.
*
* @param parent
* @param dir
* @return
*/
public GroovyExpression generateAdjacentVerticesExpression(GroovyExpression parent, AtlasEdgeDirection dir) {
return new FunctionCallExpression(TraversalStepType.FLAT_MAP_TO_ELEMENTS, parent, getGremlinFunctionName(dir));
}
private String getGremlinFunctionName(AtlasEdgeDirection dir) {
switch(dir) {
case IN:
return "in";
case OUT:
return "out";
case BOTH:
return "both";
default:
throw new RuntimeException("Unknown Atlas Edge Direction: " + dir);
}
}
/**
* Generates an expression that gets the vertices adjacent to the vertex in 'parent'
* in the specified direction, following only edges with the given label.
*
* @param parent
* @param dir
* @return
*/
public GroovyExpression generateAdjacentVerticesExpression(GroovyExpression parent, AtlasEdgeDirection dir,
String label) {
return new FunctionCallExpression(TraversalStepType.FLAT_MAP_TO_ELEMENTS, parent, getGremlinFunctionName(dir), new LiteralExpression(label));
}
/**
* Generates an arithmetic expression, e.g. a + b
*
*/
public GroovyExpression generateArithmeticExpression(GroovyExpression left, String operator,
GroovyExpression right) throws AtlasException {
ArithmeticOperator op = ArithmeticOperator.lookup(operator);
return new ArithmeticExpression(left, op, right);
}
public abstract GroovyExpression generateGroupByExpression(GroovyExpression parent, GroovyExpression groupByExpression, GroovyExpression aggregationFunction);
protected GroovyExpression getItVariable() {
return new IdentifierExpression(IT_VARIABLE);
}
protected GroovyExpression getAllVerticesExpr() {
GroovyExpression gExpr = getGraphExpression();
return new FunctionCallExpression(TraversalStepType.START, gExpr, V_METHOD);
}
protected IdentifierExpression getGraphExpression() {
return new IdentifierExpression(TraversalStepType.SOURCE, G_VARIABLE);
}
protected GroovyExpression getCurrentObjectExpression() {
return new FieldExpression(getItVariable(), OBJECT_FIELD);
}
//assumes cast already performed
public GroovyExpression generateCountExpression(GroovyExpression itExpr) {
GroovyExpression collectionExpr = new CastExpression(itExpr,"Collection");
return new FunctionCallExpression(collectionExpr, "size");
}
public GroovyExpression generateMinExpression(GroovyExpression itExpr, GroovyExpression mapFunction) {
return getAggregrationExpression(itExpr, mapFunction, "min");
}
public GroovyExpression generateMaxExpression(GroovyExpression itExpr, GroovyExpression mapFunction) {
return getAggregrationExpression(itExpr, mapFunction, "max");
}
public GroovyExpression generateSumExpression(GroovyExpression itExpr, GroovyExpression mapFunction) {
return getAggregrationExpression(itExpr, mapFunction, "sum");
}
private GroovyExpression getAggregrationExpression(GroovyExpression itExpr,
GroovyExpression mapFunction, String functionName) {
GroovyExpression collectionExpr = new CastExpression(itExpr,"Collection");
ClosureExpression collectFunction = new ClosureExpression(mapFunction);
GroovyExpression transformedList = new FunctionCallExpression(collectionExpr, "collect", collectFunction);
return new FunctionCallExpression(transformedList, functionName);
}
public GroovyExpression getClosureArgumentValue() {
return getItVariable();
}
/**
* Specifies the parent to use when translating the select list in
* a group by statement.
*
* @return
*/
public abstract GroovyExpression getGroupBySelectFieldParent();
public GroovyExpression generateFillExpression(GroovyExpression parent, GroovyExpression variable) {
return new FunctionCallExpression(TraversalStepType.END,parent , "fill", variable);
}
/**
* Generates an anonymous graph traversal initialized with the specified value. In Gremlin 3, we need
* to use a different syntax for this when the object is a map, so that information needs to be provided
* to this method so that the correct syntax is used.
*
* @param isMap true if the value contains Map instances, false if it contains Vertex instances
* @param valueCollection the source objects to start the traversal from.
*/
public abstract GroovyExpression generateSeededTraversalExpresssion(boolean isMap, GroovyExpression valueCollection);
/**
* Returns the current value of the traverser. This is used when generating closure expressions that
* need to operate on the current value in the graph graversal.
*
* @param traverser
* @return
*/
public abstract GroovyExpression getCurrentTraverserObject(GroovyExpression traverser);
/**
* Generates an expression that transforms the current value of the traverser by
* applying the function specified
*
* @param parent
* @param closureExpression
* @return
*/
public abstract GroovyExpression generateMapExpression(GroovyExpression parent, ClosureExpression closureExpression);
/**
* Returns whether a select statement generates a map (or Gremlin 2 "Row") when it contains the specified
* number of aliases.
*
*/
public abstract boolean isSelectGeneratesMap(int aliasCount);
/**
* Generates an expression to get the value of the value from the row map
* generated by select() with the specified key.
*
*/
public abstract GroovyExpression generateGetSelectedValueExpression(LiteralExpression key,
GroovyExpression rowMapExpr);
public GroovyExpression removeExtraMapFromPathInResult(GroovyExpression parent) {
GroovyExpression listItem = getItVariable();
GroovyExpression tailExpr = new FunctionCallExpression(listItem, "tail");
return new FunctionCallExpression(parent, "collect", new ClosureExpression(tailExpr));
}
/**
* Generates a toList expression to execute the gremlin query and
* store the result in a new list.
*
* @param expr
* @return
*/
public GroovyExpression generateToListExpression(GroovyExpression expr) {
return new FunctionCallExpression(TraversalStepType.END, expr, "toList");
}
/**
* Finds aliases that absolutely must be brought along with this expression into
* the output expression and cannot just be recreated there. For example, in the
* Gremlin 2 loop expression, the loop semantics break of the alias is simply recreated
* in the output expression.
* @param expr
* @return
*/
public abstract List<String> getAliasesRequiredByExpression(GroovyExpression expr);
/**
* Checks if the given expression is an alias expression, and if so
* returns the alias from the expression. Otherwise, null is
* returned.
*/
public String getAliasNameIfRelevant(GroovyExpression expr) {
if(!(expr instanceof FunctionCallExpression)) {
return null;
}
FunctionCallExpression fc = (FunctionCallExpression)expr;
if(! fc.getFunctionName().equals(AS_METHOD)) {
return null;
}
LiteralExpression aliasName = (LiteralExpression)fc.getArguments().get(0);
return aliasName.getValue().toString();
}
public abstract boolean isRepeatExpression(GroovyExpression expr);
}