blob: 5f8bb8001b1de6b232e52328b8ecf7e5fa3cbdca [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.atlas.gremlin;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.atlas.AtlasException;
import org.apache.atlas.groovy.AbstractFunctionExpression;
import org.apache.atlas.groovy.CastExpression;
import org.apache.atlas.groovy.ClosureExpression;
import org.apache.atlas.groovy.ComparisonExpression;
import org.apache.atlas.groovy.ComparisonExpression.ComparisonOperator;
import org.apache.atlas.groovy.ComparisonOperatorExpression;
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.LogicalExpression;
import org.apache.atlas.groovy.LogicalExpression.LogicalOperator;
import org.apache.atlas.groovy.RangeExpression;
import org.apache.atlas.groovy.TernaryOperatorExpression;
import org.apache.atlas.groovy.TraversalStepType;
import org.apache.atlas.query.Expressions;
import org.apache.atlas.query.GraphPersistenceStrategies;
import org.apache.atlas.query.TypeUtils.FieldInfo;
import org.apache.atlas.typesystem.types.IDataType;
/**
* Generates gremlin query expressions using Gremlin 2 syntax.
*
*/
public class Gremlin2ExpressionFactory extends GremlinExpressionFactory {
private static final String LOOP_METHOD = "loop";
private static final String CONTAINS = "contains";
private static final String LOOP_COUNT_FIELD = "loops";
private static final String PATH_FIELD = "path";
private static final String ENABLE_PATH_METHOD = "enablePath";
private static final String BACK_METHOD = "back";
private static final String LAST_METHOD = "last";
@Override
public GroovyExpression generateLogicalExpression(GroovyExpression parent, String operator, List<GroovyExpression> operands) {
return new FunctionCallExpression(TraversalStepType.FILTER, parent, operator, operands);
}
@Override
public GroovyExpression generateBackReferenceExpression(GroovyExpression parent, boolean inSelect, String alias) {
if (inSelect && parent == null) {
return getFieldInSelect();
}
else if (inSelect && parent != null) {
return parent;
}
else {
return new FunctionCallExpression(TraversalStepType.MAP_TO_ELEMENT, parent, BACK_METHOD, new LiteralExpression(alias));
}
}
@Override
public GroovyExpression getLoopExpressionParent(GroovyExpression inputQry) {
return inputQry;
}
@Override
public GroovyExpression generateLoopExpression(GroovyExpression parent,GraphPersistenceStrategies s, IDataType dataType, GroovyExpression loopExpr, String alias, Integer times) {
GroovyExpression emitExpr = generateLoopEmitExpression(s, dataType);
//note that in Gremlin 2 (unlike Gremlin 3), the parent is not explicitly used. It is incorporated
//in the loopExpr.
GroovyExpression whileFunction = null;
if(times != null) {
GroovyExpression loopsExpr = new FieldExpression(getItVariable(), LOOP_COUNT_FIELD);
GroovyExpression timesExpr = new LiteralExpression(times);
whileFunction = new ClosureExpression(new ComparisonExpression(loopsExpr, ComparisonOperator.LESS_THAN, timesExpr));
}
else {
GroovyExpression pathExpr = new FieldExpression(getItVariable(),PATH_FIELD);
GroovyExpression itObjectExpr = getCurrentObjectExpression();
GroovyExpression pathContainsExpr = new FunctionCallExpression(pathExpr, CONTAINS, itObjectExpr);
whileFunction = new ClosureExpression(new TernaryOperatorExpression(pathContainsExpr, LiteralExpression.FALSE, LiteralExpression.TRUE));
}
GroovyExpression emitFunction = new ClosureExpression(emitExpr);
GroovyExpression loopCall = new FunctionCallExpression(TraversalStepType.BRANCH, loopExpr, LOOP_METHOD, new LiteralExpression(alias), whileFunction, emitFunction);
return new FunctionCallExpression(TraversalStepType.SIDE_EFFECT, loopCall, ENABLE_PATH_METHOD);
}
@Override
public GroovyExpression typeTestExpression(GraphPersistenceStrategies s, String typeName, GroovyExpression itRef) {
GroovyExpression superTypeAttrExpr = new FieldExpression(itRef, s.superTypeAttributeName());
GroovyExpression typeNameExpr = new LiteralExpression(typeName);
GroovyExpression isSuperTypeExpr = new FunctionCallExpression(superTypeAttrExpr, CONTAINS, typeNameExpr);
GroovyExpression superTypeMatchesExpr = new TernaryOperatorExpression(superTypeAttrExpr, isSuperTypeExpr, LiteralExpression.FALSE);
GroovyExpression typeAttrExpr = new FieldExpression(itRef, s.typeAttributeName());
GroovyExpression typeMatchesExpr = new ComparisonExpression(typeAttrExpr, ComparisonOperator.EQUALS, typeNameExpr);
return new LogicalExpression(typeMatchesExpr, LogicalOperator.OR, superTypeMatchesExpr);
}
@Override
public GroovyExpression generateSelectExpression(GroovyExpression parent, List<LiteralExpression> sourceNames,
List<GroovyExpression> srcExprs) {
GroovyExpression srcNamesExpr = new ListExpression(sourceNames);
List<GroovyExpression> selectArgs = new ArrayList<>();
selectArgs.add(srcNamesExpr);
for(GroovyExpression expr : srcExprs) {
selectArgs.add(new ClosureExpression(expr));
}
return new FunctionCallExpression(TraversalStepType.MAP_TO_VALUE, parent, SELECT_METHOD, selectArgs);
}
@Override
public GroovyExpression generateFieldExpression(GroovyExpression parent, FieldInfo fInfo, String propertyName, boolean inSelect) {
return new FieldExpression(parent, propertyName);
}
@Override
public GroovyExpression generateHasExpression(GraphPersistenceStrategies s, GroovyExpression parent, String propertyName, String symbol,
GroovyExpression requiredValue, FieldInfo fInfo) throws AtlasException {
GroovyExpression op = gremlin2CompOp(symbol);
GroovyExpression propertyNameExpr = new LiteralExpression(propertyName);
return new FunctionCallExpression(TraversalStepType.FILTER, parent, HAS_METHOD, propertyNameExpr, op, requiredValue);
}
@Override
public GroovyExpression generateLikeExpressionUsingFilter(GroovyExpression parent, String propertyName, GroovyExpression propertyValue) throws AtlasException {
GroovyExpression itExpr = getItVariable();
GroovyExpression nameExpr = new FieldExpression(itExpr, propertyName);
GroovyExpression matchesExpr = new FunctionCallExpression(nameExpr, MATCHES, escapePropertyValue(propertyValue));
GroovyExpression closureExpr = new ClosureExpression(matchesExpr);
GroovyExpression filterExpr = new FunctionCallExpression(parent, FILTER_METHOD, closureExpr);
return filterExpr;
}
private GroovyExpression escapePropertyValue(GroovyExpression propertyValue) {
GroovyExpression ret = propertyValue;
if (propertyValue instanceof LiteralExpression) {
LiteralExpression exp = (LiteralExpression) propertyValue;
if (exp != null && exp.getValue() instanceof String) {
String stringValue = (String) exp.getValue();
// replace '*' with ".*", replace '?' with '.'
stringValue = stringValue.replaceAll("\\*", ".*")
.replaceAll("\\?", ".");
ret = new LiteralExpression(stringValue);
}
}
return ret;
}
private GroovyExpression gremlin2CompOp(String op) throws AtlasException {
GroovyExpression tExpr = new IdentifierExpression("T");
if(op.equals("=")) {
return new FieldExpression(tExpr, "eq");
}
if(op.equals("!=")) {
return new FieldExpression(tExpr, "neq");
}
if(op.equals(">")) {
return new FieldExpression(tExpr, "gt");
}
if(op.equals(">=")) {
return new FieldExpression(tExpr, "gte");
}
if(op.equals("<")) {
return new FieldExpression(tExpr, "lt");
}
if(op.equals("<=")) {
return new FieldExpression(tExpr, "lte");
}
if(op.equals("in")) {
return new FieldExpression(tExpr, "in");
}
throw new AtlasException("Comparison operator " + op + " not supported in Gremlin");
}
@Override
protected GroovyExpression initialExpression(GroovyExpression varExpr, GraphPersistenceStrategies s) {
return generateSeededTraversalExpresssion(false, varExpr);
}
@Override
public GroovyExpression generateSeededTraversalExpresssion(boolean isMap, GroovyExpression varExpr) {
return new FunctionCallExpression(TraversalStepType.START, varExpr, "_");
}
@Override
public GroovyExpression generateRangeExpression(GroovyExpression parent, int startIndex, int endIndex) {
//treat as barrier step, since limits need to be applied globally (even though it
//is technically a filter step)
return new RangeExpression(TraversalStepType.BARRIER, parent, startIndex, endIndex);
}
@Override
public boolean isRangeExpression(GroovyExpression expr) {
return (expr instanceof RangeExpression);
}
@Override
public int[] getRangeParameters(AbstractFunctionExpression expr) {
if (isRangeExpression(expr)) {
RangeExpression rangeExpression = (RangeExpression) expr;
return new int[] {rangeExpression.getStartIndex(), rangeExpression.getEndIndex()};
}
else {
return null;
}
}
@Override
public void setRangeParameters(GroovyExpression expr, int startIndex, int endIndex) {
if (isRangeExpression(expr)) {
RangeExpression rangeExpression = (RangeExpression) expr;
rangeExpression.setStartIndex(startIndex);
rangeExpression.setEndIndex(endIndex);
}
else {
throw new IllegalArgumentException(expr.getClass().getName() + " is not a valid range expression - must be an instance of " + RangeExpression.class.getName());
}
}
@Override
public List<GroovyExpression> getOrderFieldParents() {
GroovyExpression itExpr = getItVariable();
List<GroovyExpression> result = new ArrayList<>(2);
result.add(new FieldExpression(itExpr, "a"));
result.add(new FieldExpression(itExpr, "b"));
return result;
}
@Override
public GroovyExpression generateOrderByExpression(GroovyExpression parent, List<GroovyExpression> translatedOrderBy, boolean isAscending) {
GroovyExpression aPropertyExpr = translatedOrderBy.get(0);
GroovyExpression bPropertyExpr = translatedOrderBy.get(1);
GroovyExpression aPropertyNotNull = new ComparisonExpression(aPropertyExpr, ComparisonOperator.NOT_EQUALS, LiteralExpression.NULL);
GroovyExpression bPropertyNotNull = new ComparisonExpression(bPropertyExpr, ComparisonOperator.NOT_EQUALS, LiteralExpression.NULL);
GroovyExpression aCondition = new TernaryOperatorExpression(aPropertyNotNull, new FunctionCallExpression(aPropertyExpr,TO_LOWER_CASE_METHOD), aPropertyExpr);
GroovyExpression bCondition = new TernaryOperatorExpression(bPropertyNotNull, new FunctionCallExpression(bPropertyExpr,TO_LOWER_CASE_METHOD), bPropertyExpr);
GroovyExpression comparisonFunction = null;
if(isAscending) {
comparisonFunction = new ComparisonOperatorExpression(aCondition, bCondition);
}
else {
comparisonFunction = new ComparisonOperatorExpression(bCondition, aCondition);
}
return new FunctionCallExpression(TraversalStepType.BARRIER, parent, ORDER_METHOD, new ClosureExpression(comparisonFunction));
}
@Override
public GroovyExpression getAnonymousTraversalExpression() {
return new FunctionCallExpression(TraversalStepType.START, "_");
}
@Override
public GroovyExpression generateGroupByExpression(GroovyExpression parent, GroovyExpression groupByExpression,
GroovyExpression aggregationFunction) {
GroovyExpression groupByClosureExpr = new ClosureExpression(groupByExpression);
GroovyExpression itClosure = new ClosureExpression(getItVariable());
GroovyExpression result = new FunctionCallExpression(TraversalStepType.BARRIER, parent, "groupBy", groupByClosureExpr, itClosure);
result = new FunctionCallExpression(TraversalStepType.SIDE_EFFECT, result, "cap");
result = new FunctionCallExpression(TraversalStepType.END, result, "next");
result = new FunctionCallExpression(result, "values");
result = new FunctionCallExpression(result, "toList");
GroovyExpression aggregrationFunctionClosure = new ClosureExpression(aggregationFunction);
result = new FunctionCallExpression(result, "collect", aggregrationFunctionClosure);
return result;
}
@Override
public GroovyExpression getFieldInSelect() {
return getItVariable();
}
@Override
public GroovyExpression getGroupBySelectFieldParent() {
GroovyExpression itExpr = getItVariable();
return new FunctionCallExpression(itExpr, LAST_METHOD);
}
//assumes cast already performed
@Override
public GroovyExpression generateCountExpression(GroovyExpression itExpr) {
return new FunctionCallExpression(itExpr, "size");
}
@Override
public String getTraversalExpressionClass() {
return "GremlinPipeline";
}
@Override
public boolean isSelectGeneratesMap(int aliasCount) {
//in Gremlin 2 select always generates a map
return true;
}
@Override
public GroovyExpression generateMapExpression(GroovyExpression parent, ClosureExpression closureExpression) {
return new FunctionCallExpression(TraversalStepType.MAP_TO_ELEMENT, parent, "transform", closureExpression);
}
@Override
public GroovyExpression generateGetSelectedValueExpression(LiteralExpression key,
GroovyExpression rowMap) {
rowMap = new CastExpression(rowMap, "Row");
GroovyExpression getExpr = new FunctionCallExpression(rowMap, "getColumn", key);
return getExpr;
}
@Override
public GroovyExpression getCurrentTraverserObject(GroovyExpression traverser) {
return traverser;
}
public List<String> getAliasesRequiredByExpression(GroovyExpression expr) {
if(!(expr instanceof FunctionCallExpression)) {
return Collections.emptyList();
}
FunctionCallExpression fc = (FunctionCallExpression)expr;
if(! fc.getFunctionName().equals(LOOP_METHOD)) {
return Collections.emptyList();
}
LiteralExpression aliasName = (LiteralExpression)fc.getArguments().get(0);
return Collections.singletonList(aliasName.getValue().toString());
}
@Override
public boolean isRepeatExpression(GroovyExpression expr) {
if(!(expr instanceof FunctionCallExpression)) {
return false;
}
return ((FunctionCallExpression)expr).getFunctionName().equals(LOOP_METHOD);
}
}