Commit 69af0ae7 by Jeff Hagelberg Committed by Dave Kantor

ATLAS-1195 Clean up DSL Translation (jnhagelb via dkantor)

parent aa15cd0a
/**
* 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.groovy;
/**
* Abstract implementation of GroovyExpression that adds a convenient
* toString method.
*
*/
public abstract class AbstractGroovyExpression implements GroovyExpression {
@Override
public String toString() {
GroovyGenerationContext ctx = new GroovyGenerationContext();
ctx.setParametersAllowed(false);
generateGroovy(ctx);
return ctx.getQuery();
}
}
/**
* 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.groovy;
import org.apache.atlas.AtlasException;
/**
* Represents an arithmetic expression such as a+b.
*/
public class ArithmeticExpression extends BinaryExpression {
/**
* Allowed arithmetic operators.
*/
public enum ArithmeticOperator {
PLUS("+"),
MINUS("-"),
TIMES("*"),
DIVIDE("/"),
MODULUS("%");
private String groovyValue;
ArithmeticOperator(String groovyValue) {
this.groovyValue = groovyValue;
}
public String getGroovyValue() {
return groovyValue;
}
public static ArithmeticOperator lookup(String groovyValue) throws AtlasException {
for(ArithmeticOperator op : ArithmeticOperator.values()) {
if (op.getGroovyValue().equals(groovyValue)) {
return op;
}
}
throw new AtlasException("Unknown Operator:" + groovyValue);
}
}
public ArithmeticExpression(GroovyExpression left, ArithmeticOperator op, GroovyExpression right) {
super(left, op.getGroovyValue(), right);
}
}
/**
* 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.groovy;
/**
* Represents any kind of binary expression. This could
* be an arithmetic expression, such as a + 3, a boolean
* expression such as a < 5, or even a comparison function
* such as a <=> b.
*
*/
public abstract class BinaryExpression extends AbstractGroovyExpression {
private GroovyExpression left;
private GroovyExpression right;
private String op;
public BinaryExpression(GroovyExpression left, String op, GroovyExpression right) {
this.left = left;
this.op = op;
this.right = right;
}
@Override
public void generateGroovy(GroovyGenerationContext context) {
left.generateGroovy(context);
context.append(" ");
context.append(op);
context.append(" ");
right.generateGroovy(context);
}
}
/**
* 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.groovy;
/**
* Groovy expression that represents a cast.
*/
public class CastExpression extends AbstractGroovyExpression {
private GroovyExpression expr;
private String className;
public CastExpression(GroovyExpression expr, String className) {
this.expr = expr;
this.className =className;
}
@Override
public void generateGroovy(GroovyGenerationContext context) {
context.append("((");
context.append(className);
context.append(")");
expr.generateGroovy(context);
context.append(")");
}
}
/**
* 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.groovy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
/**
* Groovy expression that represents a Groovy closure.
*/
public class ClosureExpression extends AbstractGroovyExpression {
private List<String> varNames = new ArrayList<>();
private GroovyExpression body;
public ClosureExpression(GroovyExpression body, String... varNames) {
this.body = body;
this.varNames.addAll(Arrays.asList(varNames));
}
public ClosureExpression(List<String> varNames, GroovyExpression body) {
this.body = body;
this.varNames.addAll(varNames);
}
@Override
public void generateGroovy(GroovyGenerationContext context) {
context.append("{");
if (!varNames.isEmpty()) {
Iterator<String> varIt = varNames.iterator();
while(varIt.hasNext()) {
String varName = varIt.next();
context.append(varName);
if (varIt.hasNext()) {
context.append(", ");
}
}
context.append("->");
}
body.generateGroovy(context);
context.append("}");
}
}
/**
* 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.groovy;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Groovy expression that represents a block of code
* that contains 0 or more statements that are delimited
* by semicolons.
*/
public class CodeBlockExpression extends AbstractGroovyExpression {
private List<GroovyExpression> body = new ArrayList<>();
public void addStatement(GroovyExpression expr) {
body.add(expr);
}
public void addStatements(List<GroovyExpression> exprs) {
body.addAll(exprs);
}
@Override
public void generateGroovy(GroovyGenerationContext context) {
/*
* the L:{} represents a groovy code block; the label is needed
* to distinguish it from a groovy closure.
*/
context.append("L:{");
Iterator<GroovyExpression> stmtIt = body.iterator();
while(stmtIt.hasNext()) {
GroovyExpression stmt = stmtIt.next();
stmt.generateGroovy(context);
if (stmtIt.hasNext()) {
context.append(";");
}
}
context.append("}");
}
}
/**
* 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.groovy;
import org.apache.atlas.AtlasException;
/**
* Represents an expression that compares two expressions
* using a comparison operator (==, <, >, etc).
*
*/
public class ComparisonExpression extends BinaryExpression {
/**
* Allowed comparison operators.
*
*/
public enum ComparisonOperator {
GREATER_THAN_EQ_(">="),
GREATER_THAN(">"),
EQUALS("=="),
NOT_EQUALS("!="),
LESS_THAN("<"),
LESS_THAN_EQ("<=");
private String groovyValue;
ComparisonOperator(String groovyValue) {
this.groovyValue = groovyValue;
}
public String getGroovyValue() {
return groovyValue;
}
public static ComparisonOperator lookup(String groovyValue) throws AtlasException {
for(ComparisonOperator op : ComparisonOperator.values()) {
if (op.getGroovyValue().equals(groovyValue)) {
return op;
}
}
throw new AtlasException("Unknown Operator:" + groovyValue);
}
}
public ComparisonExpression(GroovyExpression left, ComparisonOperator op, GroovyExpression right) {
super(left, op.getGroovyValue(), right);
}
}
/**
* 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.groovy;
/**
* Represents an expression that compares two expressions using
* the Groovy "spaceship" operator. This is basically the
* same as calling left.compareTo(right), except that it has
* built-in null handling and some other nice features.
*
*/
public class ComparisonOperatorExpression extends BinaryExpression {
public ComparisonOperatorExpression(GroovyExpression left, GroovyExpression right) {
super(left, "<=>", right);
}
}
/**
* 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.groovy;
/**
* Groovy expression that accesses a field in an object.
*/
public class FieldExpression extends AbstractGroovyExpression {
private GroovyExpression target;
private String fieldName;
public FieldExpression(GroovyExpression target, String fieldName) {
this.target = target;
this.fieldName = fieldName;
}
@Override
public void generateGroovy(GroovyGenerationContext context) {
target.generateGroovy(context);
context.append(".'");
context.append(fieldName);
context.append("'");
}
}
/**
* 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.groovy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
/**
* Groovy expression that calls a method on an object.
*/
public class FunctionCallExpression extends AbstractGroovyExpression {
// null for global functions
private GroovyExpression target;
private String functionName;
private List<GroovyExpression> arguments = new ArrayList<GroovyExpression>();
public FunctionCallExpression(String functionName, List<? extends GroovyExpression> arguments) {
this.target = null;
this.functionName = functionName;
this.arguments.addAll(arguments);
}
public FunctionCallExpression(GroovyExpression target, String functionName,
List<? extends GroovyExpression> arguments) {
this.target = target;
this.functionName = functionName;
this.arguments.addAll(arguments);
}
public FunctionCallExpression(String functionName, GroovyExpression... arguments) {
this.target = null;
this.functionName = functionName;
this.arguments.addAll(Arrays.asList(arguments));
}
public FunctionCallExpression(GroovyExpression target, String functionName, GroovyExpression... arguments) {
this.target = target;
this.functionName = functionName;
this.arguments.addAll(Arrays.asList(arguments));
}
public void addArgument(GroovyExpression expr) {
arguments.add(expr);
}
@Override
public void generateGroovy(GroovyGenerationContext context) {
if (target != null) {
target.generateGroovy(context);
context.append(".");
}
context.append(functionName);
context.append("(");
Iterator<GroovyExpression> it = arguments.iterator();
while (it.hasNext()) {
GroovyExpression expr = it.next();
expr.generateGroovy(context);
if (it.hasNext()) {
context.append(", ");
}
}
context.append(")");
}
}
/**
* 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.groovy;
/**
* Represents an expression in the Groovy programming language, which
* is the language that Gremlin scripts are written and interpreted in.
*/
public interface GroovyExpression {
/**
* Generates a groovy script from the expression.
*
* @param context
*/
void generateGroovy(GroovyGenerationContext context);
}
/**
* 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.groovy;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* Context used when generating Groovy queries. Keeps track of query parameters
* that are created during the process as well as the portion of the query that
* has been generated so far.
*
*/
public class GroovyGenerationContext {
private boolean parametersAllowed = true;
private int parameterCount = 0;
private Map<String, Object> parameterValues = new HashMap<>();
//used to build up the groovy script
private StringBuilder generatedQuery = new StringBuilder();
public void setParametersAllowed(boolean value) {
this.parametersAllowed = value;
}
public GroovyExpression addParameter(Object value) {
if (this.parametersAllowed) {
String parameterName = "p" + (++parameterCount);
this.parameterValues.put(parameterName, value);
return new IdentifierExpression(parameterName);
} else {
LiteralExpression expr = new LiteralExpression(value);
expr.setTranslateToParameter(false);
return expr;
}
}
public StringBuilder append(String gremlin) {
this.generatedQuery.append(gremlin);
return generatedQuery;
}
public String getQuery() {
return generatedQuery.toString();
}
public Map<String, Object> getParameters() {
return Collections.unmodifiableMap(parameterValues);
}
}
/**
* 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.groovy;
/**
* Groovy expression that references the variable with the given name.
*
*/
public class IdentifierExpression extends AbstractGroovyExpression {
private String varName;
public IdentifierExpression(String varName) {
this.varName = varName;
}
public String getVariableName() {
return varName;
}
@Override
public void generateGroovy(GroovyGenerationContext context) {
context.append(varName);
}
}
/**
* 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.groovy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
/**
* Groovy expression that represents a list literal.
*/
public class ListExpression extends AbstractGroovyExpression {
private List<GroovyExpression> values = new ArrayList<>();
public ListExpression(GroovyExpression... values) {
this.values = Arrays.asList(values);
}
public ListExpression(List<? extends GroovyExpression> values) {
this.values.addAll(values);
}
public void addArgument(GroovyExpression expr) {
values.add(expr);
}
public void generateGroovy(GroovyGenerationContext context) {
context.append("[");
Iterator<GroovyExpression> it = values.iterator();
while(it.hasNext()) {
GroovyExpression expr = it.next();
expr.generateGroovy(context);
if (it.hasNext()) {
context.append(", ");
}
}
context.append("]");
}
}
/**
* 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.groovy;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Represents a literal value.
*/
public class LiteralExpression implements GroovyExpression {
public static final LiteralExpression TRUE = new LiteralExpression(true);
public static final LiteralExpression FALSE = new LiteralExpression(false);
public static final LiteralExpression NULL = new LiteralExpression(null);
private Object value;
private boolean translateToParameter = true;
private boolean addTypeSuffix = false;
public LiteralExpression(Object value, boolean addTypeSuffix) {
this.value = value;
this.translateToParameter = value instanceof String;
this.addTypeSuffix = addTypeSuffix;
}
public LiteralExpression(Object value) {
this.value = value;
this.translateToParameter = value instanceof String;
}
private String getTypeSuffix() {
if (!addTypeSuffix) {
return "";
}
if (value.getClass() == Long.class) {
return "L";
}
if (value.getClass() == Float.class) {
return "f";
}
if (value.getClass() == Double.class) {
return "d";
}
return "";
}
@Override
public void generateGroovy(GroovyGenerationContext context) {
if (translateToParameter) {
GroovyExpression result = context.addParameter(value);
result.generateGroovy(context);
return;
}
if (value instanceof String) {
String escapedValue = getEscapedValue();
context.append("'");
context.append(escapedValue);
context.append("'");
} else {
context.append(String.valueOf(value));
context.append(getTypeSuffix());
}
}
private String getEscapedValue() {
String escapedValue = (String)value;
escapedValue = escapedValue.replaceAll(Pattern.quote("\\"), Matcher.quoteReplacement("\\\\"));
escapedValue = escapedValue.replaceAll(Pattern.quote("'"), Matcher.quoteReplacement("\\'"));
return escapedValue;
}
public void setTranslateToParameter(boolean translateToParameter) {
this.translateToParameter = translateToParameter;
}
}
/**
* 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.groovy;
/**
* Represents a logical (and/or) expression.
*
*/
public class LogicalExpression extends BinaryExpression {
/**
* Allowed logical operators.
*
*/
public enum LogicalOperator {
AND("&&"),
OR("||");
private String groovyValue;
LogicalOperator(String groovyValue) {
this.groovyValue = groovyValue;
}
public String getGroovyValue() {
return groovyValue;
}
}
public LogicalExpression(GroovyExpression left, LogicalOperator op, GroovyExpression right) {
super(left, op.getGroovyValue(), right);
}
}
/**
* 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.groovy;
/**
* Represents an "exclusive" range expression, e.g. [0..&lt;10].
*/
public class RangeExpression extends AbstractGroovyExpression {
private GroovyExpression parent;
private int offset;
private int count;
public RangeExpression(GroovyExpression parent, int offset, int count) {
this.parent = parent;
this.offset = offset;
this.count = count;
}
@Override
public void generateGroovy(GroovyGenerationContext context) {
parent.generateGroovy(context);
context.append(" [");
new LiteralExpression(offset).generateGroovy(context);
context.append("..<");
new LiteralExpression(count).generateGroovy(context);
context.append("]");
}
}
/**
* 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.groovy;
/**
* Groovy expression that represents the ternary operator (expr ? trueValue :
* falseValue)
*/
public class TernaryOperatorExpression extends AbstractGroovyExpression {
private GroovyExpression booleanExpr;
private GroovyExpression trueValue;
private GroovyExpression falseValue;
public TernaryOperatorExpression(GroovyExpression booleanExpr, GroovyExpression trueValue,
GroovyExpression falseValue) {
this.booleanExpr = booleanExpr;
this.trueValue = trueValue;
this.falseValue = falseValue;
}
@Override
public void generateGroovy(GroovyGenerationContext context) {
context.append("((");
booleanExpr.generateGroovy(context);
context.append(") ? (");
trueValue.generateGroovy(context);
context.append(") : (");
falseValue.generateGroovy(context);
context.append("))");
}
public String toString() {
GroovyGenerationContext context = new GroovyGenerationContext();
generateGroovy(context);
return context.getQuery();
}
}
/**
* 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.groovy;
/**
* Groovy expression that represents a type coersion (e.g obj as Set).
*/
public class TypeCoersionExpression extends AbstractGroovyExpression {
private GroovyExpression expr;
private String className;
public TypeCoersionExpression(GroovyExpression expr, String className) {
this.expr = expr;
this.className =className;
}
@Override
public void generateGroovy(GroovyGenerationContext context) {
context.append("(");
expr.generateGroovy(context);
context.append(")");
context.append(" as ");
context.append(className);
}
}
/**
* 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.groovy;
/**
* Groovy statement that assigns a value to a variable.
*/
public class VariableAssignmentExpression extends AbstractGroovyExpression {
private String type = null;
private String name;
private GroovyExpression value;
/**
* @param string
* @param v
*/
public VariableAssignmentExpression(String type, String name, GroovyExpression v) {
this.type = type;
this.name = name;
this.value = v;
}
public VariableAssignmentExpression(String name, GroovyExpression v) {
this(null, name, v);
}
@Override
public void generateGroovy(GroovyGenerationContext context) {
if (type == null) {
context.append("def ");
} else {
context.append(type);
context.append(" ");
}
context.append(name);
context.append(" = ");
value.generateGroovy(context);
}
}
...@@ -23,6 +23,7 @@ import java.util.Set; ...@@ -23,6 +23,7 @@ import java.util.Set;
import javax.script.ScriptException; import javax.script.ScriptException;
import org.apache.atlas.groovy.GroovyExpression;
import org.apache.atlas.typesystem.types.IDataType; import org.apache.atlas.typesystem.types.IDataType;
/** /**
...@@ -204,7 +205,7 @@ public interface AtlasGraph<V, E> { ...@@ -204,7 +205,7 @@ public interface AtlasGraph<V, E> {
* @param type * @param type
* @return * @return
*/ */
String generatePersisentToLogicalConversionExpression(String valueExpr, IDataType<?> type); GroovyExpression generatePersisentToLogicalConversionExpression(GroovyExpression valueExpr, IDataType<?> type);
/** /**
* Indicates whether or not stored values with the specified type need to be converted * Indicates whether or not stored values with the specified type need to be converted
...@@ -240,7 +241,7 @@ public interface AtlasGraph<V, E> { ...@@ -240,7 +241,7 @@ public interface AtlasGraph<V, E> {
* *
* @return * @return
*/ */
String getInitialIndexedPredicate(); GroovyExpression getInitialIndexedPredicate(GroovyExpression parent);
/** /**
* As an optimization, a graph database implementation may want to retrieve additional * As an optimization, a graph database implementation may want to retrieve additional
...@@ -249,7 +250,7 @@ public interface AtlasGraph<V, E> { ...@@ -249,7 +250,7 @@ public interface AtlasGraph<V, E> {
* avoid the need to make an extra REST API call to look up those edges. For implementations * avoid the need to make an extra REST API call to look up those edges. For implementations
* that do not require any kind of transform, an empty String should be returned. * that do not require any kind of transform, an empty String should be returned.
*/ */
String getOutputTransformationPredicate(boolean isSelect, boolean isPath); GroovyExpression addOutputTransformationPredicate(GroovyExpression expr, boolean isSelect, boolean isPath);
/** /**
* Executes a Gremlin script, returns an object with the result. * Executes a Gremlin script, returns an object with the result.
......
...@@ -34,6 +34,7 @@ import javax.script.ScriptEngine; ...@@ -34,6 +34,7 @@ import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager; import javax.script.ScriptEngineManager;
import javax.script.ScriptException; import javax.script.ScriptException;
import org.apache.atlas.groovy.GroovyExpression;
import org.apache.atlas.repository.graphdb.AtlasEdge; import org.apache.atlas.repository.graphdb.AtlasEdge;
import org.apache.atlas.repository.graphdb.AtlasGraph; import org.apache.atlas.repository.graphdb.AtlasGraph;
import org.apache.atlas.repository.graphdb.AtlasGraphManagement; import org.apache.atlas.repository.graphdb.AtlasGraphManagement;
...@@ -286,7 +287,7 @@ public class Titan0Graph implements AtlasGraph<Titan0Vertex, Titan0Edge> { ...@@ -286,7 +287,7 @@ public class Titan0Graph implements AtlasGraph<Titan0Vertex, Titan0Edge> {
} }
@Override @Override
public String generatePersisentToLogicalConversionExpression(String expr, IDataType<?> type) { public GroovyExpression generatePersisentToLogicalConversionExpression(GroovyExpression expr, IDataType<?> type) {
//nothing special needed, value is stored in required type //nothing special needed, value is stored in required type
return expr; return expr;
...@@ -304,13 +305,13 @@ public class Titan0Graph implements AtlasGraph<Titan0Vertex, Titan0Edge> { ...@@ -304,13 +305,13 @@ public class Titan0Graph implements AtlasGraph<Titan0Vertex, Titan0Edge> {
} }
@Override @Override
public String getInitialIndexedPredicate() { public GroovyExpression getInitialIndexedPredicate(GroovyExpression expr) {
return ""; return expr;
} }
@Override @Override
public String getOutputTransformationPredicate(boolean inSelect, boolean isPath) { public GroovyExpression addOutputTransformationPredicate(GroovyExpression expr, boolean inSelect, boolean isPath) {
return ""; return expr;
} }
public Iterable<AtlasEdge<Titan0Vertex, Titan0Edge>> wrapEdges(Iterator<Edge> it) { public Iterable<AtlasEdge<Titan0Vertex, Titan0Edge>> wrapEdges(Iterator<Edge> it) {
......
...@@ -9,6 +9,7 @@ ATLAS-1060 Add composite indexes for exact match performance improvements for al ...@@ -9,6 +9,7 @@ ATLAS-1060 Add composite indexes for exact match performance improvements for al
ATLAS-1127 Modify creation and modification timestamps to Date instead of Long(sumasai) ATLAS-1127 Modify creation and modification timestamps to Date instead of Long(sumasai)
ALL CHANGES: ALL CHANGES:
ATLAS-1195 Clean up DSL Translation (jnhagelb via dkantor)
ATLAS-1139 Parameter name of a HDFS DataSet entity should contain filesystem path (svimal2106 via sumasai) ATLAS-1139 Parameter name of a HDFS DataSet entity should contain filesystem path (svimal2106 via sumasai)
ATLAS-1200 Error Catalog enhancement (apoorvnaik via sumasai) ATLAS-1200 Error Catalog enhancement (apoorvnaik via sumasai)
ATLAS-1207 Dataset exists query in lineage APIs takes longer (shwethags) ATLAS-1207 Dataset exists query in lineage APIs takes longer (shwethags)
......
...@@ -23,16 +23,16 @@ import java.util.List; ...@@ -23,16 +23,16 @@ import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import org.apache.atlas.AtlasException; import org.apache.atlas.AtlasException;
import org.apache.atlas.query.Expressions; import org.apache.atlas.groovy.GroovyExpression;
import org.apache.atlas.query.GraphPersistenceStrategies; import org.apache.atlas.query.GraphPersistenceStrategies;
import org.apache.atlas.query.GraphPersistenceStrategies$class; import org.apache.atlas.query.GraphPersistenceStrategies$class;
import org.apache.atlas.query.IntSequence;
import org.apache.atlas.query.TypeUtils; import org.apache.atlas.query.TypeUtils;
import org.apache.atlas.repository.Constants; import org.apache.atlas.repository.Constants;
import org.apache.atlas.repository.MetadataRepository; import org.apache.atlas.repository.MetadataRepository;
import org.apache.atlas.repository.RepositoryException; import org.apache.atlas.repository.RepositoryException;
import org.apache.atlas.repository.graph.GraphBackedMetadataRepository; import org.apache.atlas.repository.graph.GraphBackedMetadataRepository;
import org.apache.atlas.repository.graph.GraphHelper; import org.apache.atlas.repository.graph.GraphHelper;
import org.apache.atlas.repository.graphdb.AtlasEdgeDirection;
import org.apache.atlas.repository.graphdb.AtlasGraph; import org.apache.atlas.repository.graphdb.AtlasGraph;
import org.apache.atlas.repository.graphdb.AtlasVertex; import org.apache.atlas.repository.graphdb.AtlasVertex;
import org.apache.atlas.repository.graphdb.GremlinVersion; import org.apache.atlas.repository.graphdb.GremlinVersion;
...@@ -105,11 +105,6 @@ public class DefaultGraphPersistenceStrategy implements GraphPersistenceStrategi ...@@ -105,11 +105,6 @@ public class DefaultGraphPersistenceStrategy implements GraphPersistenceStrategi
} }
@Override @Override
public String fieldPrefixInSelect() {
return "it";
}
@Override
public Id getIdFromVertex(String dataTypeName, AtlasVertex vertex) { public Id getIdFromVertex(String dataTypeName, AtlasVertex vertex) {
return GraphHelper.getIdFromVertex(dataTypeName, vertex); return GraphHelper.getIdFromVertex(dataTypeName, vertex);
} }
...@@ -212,28 +207,13 @@ public class DefaultGraphPersistenceStrategy implements GraphPersistenceStrategi ...@@ -212,28 +207,13 @@ public class DefaultGraphPersistenceStrategy implements GraphPersistenceStrategi
} }
@Override @Override
public String gremlinCompOp(Expressions.ComparisonExpression op) { public AtlasEdgeDirection instanceToTraitEdgeDirection() {
return GraphPersistenceStrategies$class.gremlinCompOp(this, op); return AtlasEdgeDirection.OUT;
}
@Override
public String gremlinPrimitiveOp(Expressions.ComparisonExpression op) {
return GraphPersistenceStrategies$class.gremlinPrimitiveOp(this, op);
}
@Override
public String loopObjectExpression(IDataType<?> dataType) {
return GraphPersistenceStrategies$class.loopObjectExpression(this, dataType);
} }
@Override @Override
public String instanceToTraitEdgeDirection() { public AtlasEdgeDirection traitToInstanceEdgeDirection() {
return "out"; return AtlasEdgeDirection.IN;
}
@Override
public String traitToInstanceEdgeDirection() {
return "in";
} }
@Override @Override
...@@ -247,33 +227,28 @@ public class DefaultGraphPersistenceStrategy implements GraphPersistenceStrategi ...@@ -247,33 +227,28 @@ public class DefaultGraphPersistenceStrategy implements GraphPersistenceStrategi
} }
@Override @Override
public scala.collection.Seq<String> typeTestExpression(String typeName, IntSequence intSeq) {
return GraphPersistenceStrategies$class.typeTestExpression(this, typeName, intSeq);
}
@Override
public boolean collectTypeInstancesIntoVar() { public boolean collectTypeInstancesIntoVar() {
return GraphPersistenceStrategies$class.collectTypeInstancesIntoVar(this); return GraphPersistenceStrategies$class.collectTypeInstancesIntoVar(this);
} }
@Override @Override
public boolean addGraphVertexPrefix(scala.collection.Traversable<String> preStatements) { public boolean addGraphVertexPrefix(scala.collection.Traversable<GroovyExpression> preStatements) {
return GraphPersistenceStrategies$class.addGraphVertexPrefix(this, preStatements); return GraphPersistenceStrategies$class.addGraphVertexPrefix(this, preStatements);
} }
@Override @Override
public GremlinVersion getSupportedGremlinVersion() { public GremlinVersion getSupportedGremlinVersion() {
return GraphPersistenceStrategies$class.getSupportedGremlinVersion(this); return GraphPersistenceStrategies$class.getSupportedGremlinVersion(this);
} }
@Override @Override
public String generatePersisentToLogicalConversionExpression(String expr, IDataType<?> t) { public GroovyExpression generatePersisentToLogicalConversionExpression(GroovyExpression expr, IDataType<?> t) {
return GraphPersistenceStrategies$class.generatePersisentToLogicalConversionExpression(this,expr, t); return GraphPersistenceStrategies$class.generatePersisentToLogicalConversionExpression(this,expr, t);
} }
@Override @Override
public String initialQueryCondition() { public GroovyExpression addInitialQueryCondition(GroovyExpression expr) {
return GraphPersistenceStrategies$class.initialQueryCondition(this); return GraphPersistenceStrategies$class.addInitialQueryCondition(this, expr);
} }
@Override @Override
......
/**
* 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.List;
import org.apache.atlas.AtlasException;
import org.apache.atlas.groovy.ClosureExpression;
import org.apache.atlas.groovy.ComparisonExpression;
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.RangeExpression;
import org.apache.atlas.groovy.TernaryOperatorExpression;
import org.apache.atlas.groovy.ComparisonExpression.ComparisonOperator;
import org.apache.atlas.groovy.LogicalExpression.LogicalOperator;
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";
@Override
public GroovyExpression generateLogicalExpression(GroovyExpression parent, String operator, List<GroovyExpression> operands) {
return new FunctionCallExpression(parent, operator, operands);
}
@Override
public GroovyExpression generateBackReferenceExpression(GroovyExpression parent, boolean inSelect, String alias) {
if (inSelect) {
return getFieldInSelect();
}
else {
return new FunctionCallExpression(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(loopExpr, LOOP_METHOD, new LiteralExpression(alias), whileFunction, emitFunction);
return new FunctionCallExpression(loopCall, ENABLE_PATH_METHOD);
}
@Override
public GroovyExpression typeTestExpression(GraphPersistenceStrategies s, String typeName, GroovyExpression itRef) {
GroovyExpression typeAttrExpr = new FieldExpression(itRef, s.typeAttributeName());
GroovyExpression superTypeAttrExpr = new FieldExpression(itRef, s.superTypeAttributeName());
GroovyExpression typeNameExpr = new LiteralExpression(typeName);
GroovyExpression typeMatchesExpr = new ComparisonExpression(typeAttrExpr, ComparisonOperator.EQUALS, typeNameExpr);
GroovyExpression isSuperTypeExpr = new FunctionCallExpression(superTypeAttrExpr, CONTAINS, typeNameExpr);
GroovyExpression hasSuperTypeAttrExpr = superTypeAttrExpr;
GroovyExpression superTypeMatchesExpr = new TernaryOperatorExpression(hasSuperTypeAttrExpr, isSuperTypeExpr, LiteralExpression.FALSE);
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(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(parent, HAS_METHOD, propertyNameExpr, op, requiredValue);
}
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");
}
throw new AtlasException("Comparison operator " + op + " not supported in Gremlin");
}
@Override
protected GroovyExpression initialExpression(GraphPersistenceStrategies s, GroovyExpression varExpr) {
return new FunctionCallExpression(varExpr, "_");
}
@Override
public GroovyExpression generateLimitExpression(GroovyExpression parent, int offset, int totalRows) {
return new RangeExpression(parent, offset, totalRows);
}
@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 itExpr = getItVariable();
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(parent, ORDER_METHOD, new ClosureExpression(comparisonFunction));
}
@Override
public GroovyExpression getAnonymousTraversalExpression() {
return new FunctionCallExpression("_");
}
@Override
public GroovyExpression getFieldInSelect() {
return getItVariable();
}
}
/**
* 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.List;
import org.apache.atlas.AtlasException;
import org.apache.atlas.groovy.CastExpression;
import org.apache.atlas.groovy.ClosureExpression;
import org.apache.atlas.groovy.ComparisonExpression;
import org.apache.atlas.groovy.ComparisonOperatorExpression;
import org.apache.atlas.groovy.FunctionCallExpression;
import org.apache.atlas.groovy.GroovyExpression;
import org.apache.atlas.groovy.IdentifierExpression;
import org.apache.atlas.groovy.LiteralExpression;
import org.apache.atlas.groovy.LogicalExpression;
import org.apache.atlas.groovy.TypeCoersionExpression;
import org.apache.atlas.groovy.ComparisonExpression.ComparisonOperator;
import org.apache.atlas.groovy.LogicalExpression.LogicalOperator;
import org.apache.atlas.query.GraphPersistenceStrategies;
import org.apache.atlas.query.TypeUtils.FieldInfo;
import org.apache.atlas.repository.graph.AtlasGraphProvider;
import org.apache.atlas.repository.graphdb.AtlasGraph;
import org.apache.atlas.typesystem.types.AttributeInfo;
import org.apache.atlas.typesystem.types.IDataType;
/**
* Generates gremlin query expressions using Gremlin 3 syntax.
*
*/
public class Gremlin3ExpressionFactory extends GremlinExpressionFactory {
private static final String VERTEX_LIST_CLASS = "List<Vertex>";
private static final String VERTEX_ARRAY_CLASS = "Vertex[]";
private static final String OBJECT_ARRAY_CLASS = "Object[]";
private static final String VERTEX_CLASS = "Vertex";
private static final String FUNCTION_CLASS = "Function";
private static final String VALUE_METHOD = "value";
private static final String IS_PRESENT_METHOD = "isPresent";
private static final String MAP_METHOD = "map";
private static final String VALUES_METHOD = "values";
private static final String GET_METHOD = "get";
private static final String OR_ELSE_METHOD = "orElse";
private static final String PROPERTY_METHOD = "property";
private static final String BY_METHOD = "by";
private static final String EQ_METHOD = "eq";
private static final String EMIT_METHOD = "emit";
private static final String TIMES_METHOD = "times";
private static final String REPEAT_METHOD = "repeat";
private static final String RANGE_METHOD = "range";
private static final String LAST_METHOD = "last";
private static final String WHERE_METHOD = "where";
private static final String TO_STRING_METHOD = "toString";
@Override
public GroovyExpression generateLogicalExpression(GroovyExpression parent, String operator,
List<GroovyExpression> operands) {
if (operands.size() == 1) {
// gremlin 3 treats one element expressions as 'false'. Avoid
// creating a boolean expression in this case. Inline the expression
// note: we can't simply omit it, since it will cause us to traverse
// the edge!
// use 'where' instead
GroovyExpression expr = operands.get(0);
// if child is a back expression, that expression becomes an
// argument to where
return new FunctionCallExpression(parent, WHERE_METHOD, expr);
} else {
// Gremlin 3 does not support _() syntax
//
return new FunctionCallExpression(parent, operator, operands);
}
}
@Override
public GroovyExpression generateBackReferenceExpression(GroovyExpression parent, boolean inSelect, String alias) {
if (inSelect) {
return getFieldInSelect();
} else {
return new FunctionCallExpression(parent, SELECT_METHOD, new LiteralExpression(alias));
}
}
@Override
public GroovyExpression typeTestExpression(GraphPersistenceStrategies s, String typeName, GroovyExpression itRef) {
LiteralExpression typeAttrExpr = new LiteralExpression(s.typeAttributeName());
LiteralExpression superTypeAttrExpr = new LiteralExpression(s.superTypeAttributeName());
LiteralExpression typeNameExpr = new LiteralExpression(typeName);
FunctionCallExpression result = new FunctionCallExpression(HAS_METHOD, typeAttrExpr, new FunctionCallExpression(EQ_METHOD, typeNameExpr));
result = new FunctionCallExpression(result, "or");
result = new FunctionCallExpression(HAS_METHOD, superTypeAttrExpr, new FunctionCallExpression(EQ_METHOD, typeNameExpr));
return result;
}
@Override
public GroovyExpression generateLoopExpression(GroovyExpression parent,GraphPersistenceStrategies s, IDataType dataType, GroovyExpression loopExpr, String alias, Integer times) {
GroovyExpression emitExpr = generateLoopEmitExpression(s, dataType);
GroovyExpression result = new FunctionCallExpression(parent, REPEAT_METHOD, loopExpr);
if (times != null) {
GroovyExpression timesExpr = new LiteralExpression(times);
result = new FunctionCallExpression(result, TIMES_METHOD, timesExpr);
}
result = new FunctionCallExpression(result, EMIT_METHOD, emitExpr);
return result;
}
@Override
public GroovyExpression getLoopExpressionParent(GroovyExpression inputQry) {
GroovyExpression curTraversal = new IdentifierExpression("__");
return curTraversal;
}
@Override
public GroovyExpression generateSelectExpression(GroovyExpression parent, List<LiteralExpression> sourceNames,
List<GroovyExpression> srcExprs) {
FunctionCallExpression result = new FunctionCallExpression(parent, SELECT_METHOD, sourceNames);
for (GroovyExpression expr : srcExprs) {
GroovyExpression closure = new ClosureExpression(expr);
GroovyExpression castClosure = new TypeCoersionExpression(closure, FUNCTION_CLASS);
result = new FunctionCallExpression(result, BY_METHOD, castClosure);
}
return result;
}
@Override
public GroovyExpression generateFieldExpression(GroovyExpression parent, FieldInfo fInfo,
String propertyName, boolean inSelect) {
AttributeInfo attrInfo = fInfo.attrInfo();
IDataType attrType = attrInfo.dataType();
GroovyExpression propertyNameExpr = new LiteralExpression(propertyName);
AtlasGraph graph = AtlasGraphProvider.getGraphInstance();
if (inSelect) {
GroovyExpression expr = new FunctionCallExpression(parent, PROPERTY_METHOD, propertyNameExpr);
expr = new FunctionCallExpression(expr, OR_ELSE_METHOD, LiteralExpression.NULL);
return graph.generatePersisentToLogicalConversionExpression(expr, attrType);
} else {
GroovyExpression unmapped = new FunctionCallExpression(parent, VALUES_METHOD, propertyNameExpr);
if (graph.isPropertyValueConversionNeeded(attrType)) {
GroovyExpression toConvert = new FunctionCallExpression(getItVariable(), GET_METHOD);
GroovyExpression conversionFunction = graph.generatePersisentToLogicalConversionExpression(toConvert,
attrType);
return new FunctionCallExpression(unmapped, MAP_METHOD, new ClosureExpression(conversionFunction));
} else {
return unmapped;
}
}
}
private ComparisonOperator getGroovyOperator(String symbol) throws AtlasException {
String toFind = symbol;
if (toFind.equals("=")) {
toFind = "==";
}
return ComparisonOperator.lookup(toFind);
}
private String getComparisonFunction(String op) throws AtlasException {
if (op.equals("=")) {
return "eq";
}
if (op.equals("!=")) {
return "neq";
}
if (op.equals(">")) {
return "gt";
}
if (op.equals(">=")) {
return "gte";
}
if (op.equals("<")) {
return "lt";
}
if (op.equals("<=")) {
return "lte";
}
throw new AtlasException("Comparison operator " + op + " not supported in Gremlin");
}
@Override
public GroovyExpression generateHasExpression(GraphPersistenceStrategies s, GroovyExpression parent,
String propertyName, String symbol, GroovyExpression requiredValue, FieldInfo fInfo) throws AtlasException {
AttributeInfo attrInfo = fInfo.attrInfo();
IDataType attrType = attrInfo.dataType();
GroovyExpression propertNameExpr = new LiteralExpression(propertyName);
if (s.isPropertyValueConversionNeeded(attrType)) {
// for some types, the logical value cannot be stored directly in
// the underlying graph,
// and conversion logic is needed to convert the persistent form of
// the value
// to the actual value. In cases like this, we generate a conversion
// expression to
// do this conversion and use the filter step to perform the
// comparsion in the gremlin query
GroovyExpression itExpr = getItVariable();
GroovyExpression vertexExpr = new CastExpression(new FunctionCallExpression(itExpr, GET_METHOD), VERTEX_CLASS);
GroovyExpression propertyValueExpr = new FunctionCallExpression(vertexExpr, VALUE_METHOD, propertNameExpr);
GroovyExpression conversionExpr = s.generatePersisentToLogicalConversionExpression(propertyValueExpr,
attrType);
GroovyExpression propertyIsPresentExpression = new FunctionCallExpression(
new FunctionCallExpression(vertexExpr, PROPERTY_METHOD, propertNameExpr), IS_PRESENT_METHOD);
GroovyExpression valueMatchesExpr = new ComparisonExpression(conversionExpr, getGroovyOperator(symbol),
requiredValue);
GroovyExpression filterCondition = new LogicalExpression(propertyIsPresentExpression, LogicalOperator.AND,
valueMatchesExpr);
GroovyExpression filterFunction = new ClosureExpression(filterCondition);
return new FunctionCallExpression(parent, FILTER_METHOD, filterFunction);
} else {
GroovyExpression valueMatches = new FunctionCallExpression(getComparisonFunction(symbol), requiredValue);
return new FunctionCallExpression(parent, HAS_METHOD, propertNameExpr, valueMatches);
}
}
@Override
protected GroovyExpression initialExpression(GraphPersistenceStrategies s, GroovyExpression varExpr) {
// this bit of groovy magic converts the set of vertices in varName into
// a String containing the ids of all the vertices. This becomes the
// argument
// to g.V(). This is needed because Gremlin 3 does not support
// _()
// s"g.V(${varName}.collect{it.id()} as String[])"
GroovyExpression gExpr = getGraph();
GroovyExpression varRefExpr = new TypeCoersionExpression(varExpr, OBJECT_ARRAY_CLASS);
FunctionCallExpression expr = new FunctionCallExpression(gExpr, V_METHOD, varRefExpr);
return s.addInitialQueryCondition(expr);
}
@Override
public GroovyExpression generateLimitExpression(GroovyExpression parent, int offset, int totalRows) {
return new FunctionCallExpression(parent, RANGE_METHOD, new LiteralExpression(offset), new LiteralExpression(totalRows));
}
@Override
public List<GroovyExpression> getOrderFieldParents() {
List<GroovyExpression> result = new ArrayList<>(1);
result.add(null);
return result;
}
@Override
public GroovyExpression generateOrderByExpression(GroovyExpression parent, List<GroovyExpression> translatedOrderBy,
boolean isAscending) {
GroovyExpression orderByClause = translatedOrderBy.get(0);
GroovyExpression aExpr = new IdentifierExpression("a");
GroovyExpression bExpr = new IdentifierExpression("b");
GroovyExpression aCompExpr = new FunctionCallExpression(new FunctionCallExpression(aExpr, TO_STRING_METHOD), TO_LOWER_CASE_METHOD);
GroovyExpression bCompExpr = new FunctionCallExpression(new FunctionCallExpression(bExpr, TO_STRING_METHOD), TO_LOWER_CASE_METHOD);
GroovyExpression comparisonExpr = null;
if (isAscending) {
comparisonExpr = new ComparisonOperatorExpression(aCompExpr, bCompExpr);
} else {
comparisonExpr = new ComparisonOperatorExpression(bCompExpr, aCompExpr);
}
ClosureExpression comparisonFunction = new ClosureExpression(comparisonExpr, "a", "b");
return new FunctionCallExpression(new FunctionCallExpression(parent, ORDER_METHOD), BY_METHOD, orderByClause, comparisonFunction);
}
@Override
public GroovyExpression getAnonymousTraversalExpression() {
return null;
}
@Override
public GroovyExpression getFieldInSelect() {
// this logic is needed to remove extra results from
// what is emitted by repeat loops. Technically
// for queries that don't have a loop in them we could just use "it"
// the reason for this is that in repeat loops with an alias,
// although the alias gets set to the right value, for some
// reason the select actually includes all vertices that were traversed
// through in the loop. In these cases, we only want the last vertex
// traversed in the loop to be selected. The logic here handles that
// case by converting the result to a list and just selecting the
// last item from it.
GroovyExpression itExpr = getItVariable();
GroovyExpression expr1 = new TypeCoersionExpression(itExpr, VERTEX_ARRAY_CLASS);
GroovyExpression expr2 = new TypeCoersionExpression(expr1, VERTEX_LIST_CLASS);
return new FunctionCallExpression(expr2, LAST_METHOD);
}
}
/**
* 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.List;
import org.apache.atlas.AtlasException;
import org.apache.atlas.groovy.ArithmeticExpression;
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.TypeCoersionExpression;
import org.apache.atlas.groovy.VariableAssignmentExpression;
import org.apache.atlas.groovy.ArithmeticExpression.ArithmeticOperator;
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;
/**
* 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";
private 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 FILL_METHOD = "fill";
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";
public static final GremlinExpressionFactory INSTANCE = AtlasGraphProvider.getGraphInstance()
.getSupportedGremlinVersion() == GremlinVersion.THREE ? new Gremlin3ExpressionFactory()
: new Gremlin2ExpressionFactory();
/**
* 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;
/**
* Generates a limit expression
*
* @param parent
* @param offset
* @param totalRows
* @return
*/
public abstract GroovyExpression generateLimitExpression(GroovyExpression parent, int offset, int totalRows);
/**
* Generates an order by expression
*
* @param parent
* @param translatedOrderBy
* @param isAscending
* @return
*/
public abstract GroovyExpression generateOrderByExpression(GroovyExpression parent,
List<GroovyExpression> translatedOrderBy, boolean isAscending);
/**
* 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();
/**
* Returns an expression representing
*
* @return
*/
public abstract GroovyExpression getFieldInSelect();
/**
* Generates the expression the serves as the root of the Gremlin query.
* @param s
* @param varExpr variable containing the vertices to traverse
* @return
*/
protected abstract GroovyExpression initialExpression(GraphPersistenceStrategies s, GroovyExpression varExpr);
/**
* 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, the vertices are put into a variable whose name is geneated 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) {
if (s.collectTypeInstancesIntoVar()) {
return typeTestExpressionMultiStep(s, typeName, intSeq);
} else {
return typeTestExpressionUsingFilter(s, parent, typeName);
}
}
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(s, varExpr));
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(parent, HAS_METHOD, new LiteralExpression(fieldName));
}
/**
* Generates a path expression
*
* @param parent
* @return
*/
public GroovyExpression generatePathExpression(GroovyExpression parent) {
return new FunctionCallExpression(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(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(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(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);
}
protected GroovyExpression getItVariable() {
return new IdentifierExpression(IT_VARIABLE);
}
protected GroovyExpression getAllVerticesExpr() {
GroovyExpression gExpr = getGraph();
return new FunctionCallExpression(gExpr, V_METHOD);
}
protected IdentifierExpression getGraph() {
return new IdentifierExpression(G_VARIABLE);
}
protected GroovyExpression getCurrentObjectExpression() {
return new FieldExpression(getItVariable(), OBJECT_FIELD);
}
}
\ No newline at end of file
...@@ -21,23 +21,20 @@ package org.apache.atlas.query ...@@ -21,23 +21,20 @@ package org.apache.atlas.query
import java.util import java.util
import java.util.Date import java.util.Date
import scala.collection.JavaConversions._
import scala.collection.JavaConversions.seqAsJavaList import scala.collection.JavaConversions.seqAsJavaList
import scala.language.existentials
import org.apache.atlas.query.Expressions.{ComparisonExpression, ExpressionException} import org.apache.atlas.groovy.GroovyExpression
import org.apache.atlas.query.TypeUtils.FieldInfo import org.apache.atlas.query.TypeUtils.FieldInfo
import org.apache.atlas.repository.graph.{GraphHelper, GraphBackedMetadataRepository}
import org.apache.atlas.repository.RepositoryException import org.apache.atlas.repository.RepositoryException
import org.apache.atlas.repository.graph.GraphHelper
import org.apache.atlas.repository.graphdb._ import org.apache.atlas.repository.graphdb._
import org.apache.atlas.typesystem.persistence.Id import org.apache.atlas.typesystem.ITypedInstance
import org.apache.atlas.typesystem.types.DataTypes._ import org.apache.atlas.typesystem.ITypedReferenceableInstance
import org.apache.atlas.typesystem.persistence.Id import org.apache.atlas.typesystem.persistence.Id
import org.apache.atlas.typesystem.types._ import org.apache.atlas.typesystem.types._
import org.apache.atlas.typesystem.{ITypedInstance, ITypedReferenceableInstance} import org.apache.atlas.typesystem.types.DataTypes._
import scala.collection.JavaConversions._
import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer
/** /**
* Represents the Bridge between the QueryProcessor and the Graph Persistence scheme used. * Represents the Bridge between the QueryProcessor and the Graph Persistence scheme used.
...@@ -47,19 +44,19 @@ import scala.collection.mutable.ArrayBuffer ...@@ -47,19 +44,19 @@ import scala.collection.mutable.ArrayBuffer
* - how are attribute names mapped to Property Keys in Vertices. * - how are attribute names mapped to Property Keys in Vertices.
* *
* This is a work in progress. * This is a work in progress.
* *
*/ */
trait GraphPersistenceStrategies { trait GraphPersistenceStrategies {
@throws(classOf[RepositoryException]) @throws(classOf[RepositoryException])
def getGraph() : AtlasGraph[_,_] def getGraph() : AtlasGraph[_,_]
def getSupportedGremlinVersion() : GremlinVersion = getGraph().getSupportedGremlinVersion; def getSupportedGremlinVersion() : GremlinVersion = getGraph().getSupportedGremlinVersion;
def generatePersisentToLogicalConversionExpression(expr: String, t: IDataType[_]) : String = getGraph().generatePersisentToLogicalConversionExpression(expr, t); def generatePersisentToLogicalConversionExpression(expr: GroovyExpression, t: IDataType[_]) : GroovyExpression = getGraph().generatePersisentToLogicalConversionExpression(expr, t);
def isPropertyValueConversionNeeded(attrType: IDataType[_]) : Boolean = getGraph().isPropertyValueConversionNeeded(attrType); def isPropertyValueConversionNeeded(attrType: IDataType[_]) : Boolean = getGraph().isPropertyValueConversionNeeded(attrType);
def initialQueryCondition = if (getGraph().requiresInitialIndexedPredicate()) { s""".${getGraph().getInitialIndexedPredicate}""" } else {""}; def addInitialQueryCondition(parent: GroovyExpression) : GroovyExpression = if (getGraph().requiresInitialIndexedPredicate()) { getGraph().getInitialIndexedPredicate(parent) } else { parent };
/** /**
* Name of attribute used to store typeName in vertex * Name of attribute used to store typeName in vertex
*/ */
...@@ -87,11 +84,12 @@ trait GraphPersistenceStrategies { ...@@ -87,11 +84,12 @@ trait GraphPersistenceStrategies {
def traitLabel(cls: IDataType[_], traitName: String): String def traitLabel(cls: IDataType[_], traitName: String): String
def instanceToTraitEdgeDirection : String = "out" def instanceToTraitEdgeDirection : AtlasEdgeDirection = AtlasEdgeDirection.OUT;
def traitToInstanceEdgeDirection = instanceToTraitEdgeDirection match {
case "out" => "in" def traitToInstanceEdgeDirection : AtlasEdgeDirection = instanceToTraitEdgeDirection match {
case "in" => "out" case AtlasEdgeDirection.OUT => AtlasEdgeDirection.IN;
case x => x case AtlasEdgeDirection.IN => AtlasEdgeDirection.OUT;
case x => AtlasEdgeDirection.IN;
} }
/** /**
...@@ -115,27 +113,6 @@ trait GraphPersistenceStrategies { ...@@ -115,27 +113,6 @@ trait GraphPersistenceStrategies {
case FieldInfo(dataType, null, null, traitName) => traitLabel(dataType, traitName) case FieldInfo(dataType, null, null, traitName) => traitLabel(dataType, traitName)
} }
def fieldPrefixInSelect(): String = {
if(getSupportedGremlinVersion() == GremlinVersion.THREE) {
//this logic is needed to remove extra results from
//what is emitted by repeat loops. Technically
//for queries that don't have a loop in them we could just use "it"
//the reason for this is that in repeat loops with an alias,
//although the alias gets set to the right value, for some
//reason the select actually includes all vertices that were traversed
//through in the loop. In these cases, we only want the last vertex
//traversed in the loop to be selected. The logic here handles that
//case by converting the result to a list and just selecting the
//last item from it.
"((it as Vertex[]) as List<Vertex>).last()"
}
else {
"it"
}
}
/** /**
* extract the Id from a Vertex. * extract the Id from a Vertex.
* @param dataTypeNm the dataType of the instance that the given vertex represents * @param dataTypeNm the dataType of the instance that the given vertex represents
...@@ -146,50 +123,7 @@ trait GraphPersistenceStrategies { ...@@ -146,50 +123,7 @@ trait GraphPersistenceStrategies {
def constructInstance[U](dataType: IDataType[U], v: java.lang.Object): U def constructInstance[U](dataType: IDataType[U], v: java.lang.Object): U
def gremlinCompOp(op: ComparisonExpression) = { def addGraphVertexPrefix(preStatements : Traversable[GroovyExpression]) = !collectTypeInstancesIntoVar
if( getSupportedGremlinVersion() == GremlinVersion.TWO) {
gremlin2CompOp(op);
}
else {
gremlin3CompOp(op);
}
}
def gremlinPrimitiveOp(op: ComparisonExpression) = op.symbol match {
case "=" => "=="
case "!=" => "!="
case ">" => ">"
case ">=" => ">="
case "<" => "<"
case "<=" => "<="
case _ => throw new ExpressionException(op, "Comparison operator not supported in Gremlin")
}
private def gremlin2CompOp(op: ComparisonExpression) = op.symbol match {
case "=" => "T.eq"
case "!=" => "T.neq"
case ">" => "T.gt"
case ">=" => "T.gte"
case "<" => "T.lt"
case "<=" => "T.lte"
case _ => throw new ExpressionException(op, "Comparison operator not supported in Gremlin")
}
private def gremlin3CompOp(op: ComparisonExpression) = op.symbol match {
case "=" => "eq"
case "!=" => "neq"
case ">" => "gt"
case ">=" => "gte"
case "<" => "lt"
case "<=" => "lte"
case _ => throw new ExpressionException(op, "Comparison operator not supported in Gremlin")
}
def loopObjectExpression(dataType: IDataType[_]) = {
_typeTestExpression(dataType.getName, "it.object")
}
def addGraphVertexPrefix(preStatements : Traversable[String]) = !collectTypeInstancesIntoVar
/** /**
* Controls behavior of how instances of a Type are discovered. * Controls behavior of how instances of a Type are discovered.
...@@ -213,76 +147,14 @@ trait GraphPersistenceStrategies { ...@@ -213,76 +147,14 @@ trait GraphPersistenceStrategies {
*/ */
def collectTypeInstancesIntoVar = true def collectTypeInstancesIntoVar = true
def typeTestExpression(typeName : String, intSeq : IntSequence) : Seq[String] = {
if (collectTypeInstancesIntoVar)
typeTestExpressionMultiStep(typeName, intSeq)
else
typeTestExpressionUsingFilter(typeName)
}
private def typeTestExpressionUsingFilter(typeName : String) : Seq[String] = {
Seq(s"""filter${_typeTestExpression(typeName, "it")}""")
}
/**
* type test expression that ends up in the emit clause in
* loop/repeat steps and a few other places
*/
private def _typeTestExpression(typeName: String, itRef: String): String = {
if( getSupportedGremlinVersion() == GremlinVersion.TWO) {
s"""{(${itRef}.'${typeAttributeName}' == '${typeName}') |
| (${itRef}.'${superTypeAttributeName}' ?
| ${itRef}.'${superTypeAttributeName}'.contains('${typeName}') : false)}""".
stripMargin.replace(System.getProperty("line.separator"), "")
}
else {
//gremlin 3
s"""has('${typeAttributeName}',eq('${typeName}')).or().has('${superTypeAttributeName}',eq('${typeName}'))"""
}
}
private def propertyValueSet(vertexRef : String, attrName: String) : String = { private def propertyValueSet(vertexRef : String, attrName: String) : String = {
s"""org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils.set(${vertexRef}.values('${attrName})""" s"""org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils.set(${vertexRef}.values('${attrName})"""
} }
private def typeTestExpressionMultiStep(typeName : String, intSeq : IntSequence) : Seq[String] = {
val varName = s"_var_${intSeq.next}"
Seq(
newSetVar(varName),
fillVarWithTypeInstances(typeName, varName),
fillVarWithSubTypeInstances(typeName, varName),
if(getSupportedGremlinVersion() == GremlinVersion.TWO) {
s"$varName._()"
}
else {
//this bit of groovy magic converts the set of vertices in varName into
//a String containing the ids of all the vertices. This becomes the argument
//to g.V(). This is needed because Gremlin 3 does not support
// _()
//s"g.V(${varName}.collect{it.id()} as String[])"
s"g.V(${varName} as Object[])${initialQueryCondition}"
}
)
}
private def newSetVar(varName : String) = s"def $varName = [] as Set"
private def fillVarWithTypeInstances(typeName : String, fillVar : String) = {
s"""g.V().has("${typeAttributeName}", "${typeName}").fill($fillVar)"""
}
private def fillVarWithSubTypeInstances(typeName : String, fillVar : String) = {
s"""g.V().has("${superTypeAttributeName}", "${typeName}").fill($fillVar)"""
}
} }
import scala.language.existentials;
import org.apache.atlas.repository.RepositoryException
import org.apache.atlas.repository.RepositoryException
import org.apache.atlas.repository.RepositoryException
import org.apache.atlas.repository.RepositoryException
case class GraphPersistenceStrategy1(g: AtlasGraph[_,_]) extends GraphPersistenceStrategies { case class GraphPersistenceStrategy1(g: AtlasGraph[_,_]) extends GraphPersistenceStrategies {
...@@ -293,8 +165,8 @@ case class GraphPersistenceStrategy1(g: AtlasGraph[_,_]) extends GraphPersistenc ...@@ -293,8 +165,8 @@ case class GraphPersistenceStrategy1(g: AtlasGraph[_,_]) extends GraphPersistenc
override def getGraph() : AtlasGraph[_,_] = { override def getGraph() : AtlasGraph[_,_] = {
return g; return g;
} }
def edgeLabel(dataType: IDataType[_], aInfo: AttributeInfo) = s"__${dataType.getName}.${aInfo.name}" def edgeLabel(dataType: IDataType[_], aInfo: AttributeInfo) = s"__${dataType.getName}.${aInfo.name}"
def edgeLabel(propertyName: String) = s"__${propertyName}" def edgeLabel(propertyName: String) = s"__${propertyName}"
...@@ -458,8 +330,6 @@ case class GraphPersistenceStrategy1(g: AtlasGraph[_,_]) extends GraphPersistenc ...@@ -458,8 +330,6 @@ case class GraphPersistenceStrategy1(g: AtlasGraph[_,_]) extends GraphPersistenc
} }
} }
private def mapVertexToCollectionEntry(instanceVertex: AtlasVertex[_,_], attributeInfo: AttributeInfo, elementType: IDataType[_], i: ITypedInstance, value: Any): Any = { private def mapVertexToCollectionEntry(instanceVertex: AtlasVertex[_,_], attributeInfo: AttributeInfo, elementType: IDataType[_], i: ITypedInstance, value: Any): Any = {
elementType.getTypeCategory match { elementType.getTypeCategory match {
case DataTypes.TypeCategory.PRIMITIVE => value case DataTypes.TypeCategory.PRIMITIVE => value
...@@ -474,5 +344,6 @@ case class GraphPersistenceStrategy1(g: AtlasGraph[_,_]) extends GraphPersistenc ...@@ -474,5 +344,6 @@ case class GraphPersistenceStrategy1(g: AtlasGraph[_,_]) extends GraphPersistenc
throw new UnsupportedOperationException(s"load for ${attributeInfo.dataType()} not supported") throw new UnsupportedOperationException(s"load for ${attributeInfo.dataType()} not supported")
} }
} }
} }
...@@ -18,30 +18,61 @@ ...@@ -18,30 +18,61 @@
package org.apache.atlas.query package org.apache.atlas.query
import java.lang.Boolean
import java.lang.Byte
import java.lang.Double
import java.lang.Float
import java.lang.Integer
import java.lang.Long
import java.lang.Short
import java.util.ArrayList
import scala.collection.JavaConversions.asScalaBuffer
import scala.collection.JavaConversions.bufferAsJavaList
import scala.collection.mutable import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer import scala.collection.mutable.ArrayBuffer
import org.apache.atlas.query.TypeUtils.FieldInfo; import org.apache.atlas.gremlin.GremlinExpressionFactory
import org.apache.atlas.query.Expressions._ import org.apache.atlas.groovy.CastExpression
import org.apache.atlas.repository.graphdb.GremlinVersion import org.apache.atlas.groovy.CodeBlockExpression
import org.apache.atlas.groovy.FunctionCallExpression
import org.apache.atlas.groovy.GroovyExpression
import org.apache.atlas.groovy.GroovyGenerationContext
import org.apache.atlas.groovy.IdentifierExpression
import org.apache.atlas.groovy.ListExpression
import org.apache.atlas.groovy.LiteralExpression
import org.apache.atlas.query.Expressions.AliasExpression
import org.apache.atlas.query.Expressions.ArithmeticExpression
import org.apache.atlas.query.Expressions.BackReference
import org.apache.atlas.query.Expressions.ClassExpression
import org.apache.atlas.query.Expressions.ComparisonExpression
import org.apache.atlas.query.Expressions.Expression
import org.apache.atlas.query.Expressions.ExpressionException
import org.apache.atlas.query.Expressions.FieldExpression
import org.apache.atlas.query.Expressions.FilterExpression
import org.apache.atlas.query.Expressions.InstanceExpression
import org.apache.atlas.query.Expressions.LimitExpression
import org.apache.atlas.query.Expressions.ListLiteral
import org.apache.atlas.query.Expressions.Literal
import org.apache.atlas.query.Expressions.LogicalExpression
import org.apache.atlas.query.Expressions.LoopExpression
import org.apache.atlas.query.Expressions.OrderExpression
import org.apache.atlas.query.Expressions.PathExpression
import org.apache.atlas.query.Expressions.SelectExpression
import org.apache.atlas.query.Expressions.TraitExpression
import org.apache.atlas.query.Expressions.TraitInstanceExpression
import org.apache.atlas.query.Expressions.hasFieldLeafExpression
import org.apache.atlas.query.Expressions.hasFieldUnaryExpression
import org.apache.atlas.query.Expressions.id
import org.apache.atlas.query.Expressions.isTraitLeafExpression
import org.apache.atlas.query.Expressions.isTraitUnaryExpression
import org.apache.atlas.repository.RepositoryException
import org.apache.atlas.repository.graphdb.AtlasEdgeDirection
import org.apache.atlas.typesystem.types.DataTypes import org.apache.atlas.typesystem.types.DataTypes
import org.apache.atlas.typesystem.types.DataTypes.TypeCategory import org.apache.atlas.typesystem.types.DataTypes.TypeCategory
import org.apache.atlas.typesystem.types.IDataType import org.apache.atlas.typesystem.types.IDataType
import org.apache.commons.lang.StringEscapeUtils
import org.apache.atlas.typesystem.types.TypeSystem import org.apache.atlas.typesystem.types.TypeSystem
import org.apache.atlas.typesystem.types.AttributeInfo
import org.joda.time.format.ISODateTimeFormat import org.joda.time.format.ISODateTimeFormat
import org.apache.atlas.typesystem.types.DataTypes.BigDecimalType
import org.apache.atlas.typesystem.types.DataTypes.ByteType
import org.apache.atlas.typesystem.types.DataTypes.BooleanType
import org.apache.atlas.typesystem.types.DataTypes.DateType
import org.apache.atlas.typesystem.types.DataTypes.BigIntegerType
import org.apache.atlas.typesystem.types.DataTypes.IntType
import org.apache.atlas.typesystem.types.DataTypes.StringType
import org.apache.atlas.typesystem.types.DataTypes.LongType
import org.apache.atlas.typesystem.types.DataTypes.DoubleType
import org.apache.atlas.typesystem.types.DataTypes.FloatType
import org.apache.atlas.typesystem.types.DataTypes.ShortType
trait IntSequence { trait IntSequence {
def next: Int def next: Int
...@@ -127,7 +158,7 @@ trait SelectExpressionHandling { ...@@ -127,7 +158,7 @@ trait SelectExpressionHandling {
/** /**
* For each Output Column in the SelectExpression compute the ArrayList(Src) this maps to and the position within * For each Output Column in the SelectExpression compute the ArrayList(Src) this maps to and the position within
* this list. * this list.
* *
* @param sel * @param sel
* @return * @return
*/ */
...@@ -151,8 +182,8 @@ class GremlinTranslator(expr: Expression, ...@@ -151,8 +182,8 @@ class GremlinTranslator(expr: Expression,
gPersistenceBehavior: GraphPersistenceStrategies) gPersistenceBehavior: GraphPersistenceStrategies)
extends SelectExpressionHandling { extends SelectExpressionHandling {
val preStatements = ArrayBuffer[String]() val preStatements = ArrayBuffer[GroovyExpression]()
val postStatements = ArrayBuffer[String]() val postStatements = ArrayBuffer[GroovyExpression]()
val wrapAndRule: PartialFunction[Expression, Expression] = { val wrapAndRule: PartialFunction[Expression, Expression] = {
case f: FilterExpression if !f.condExpr.isInstanceOf[LogicalExpression] => case f: FilterExpression if !f.condExpr.isInstanceOf[LogicalExpression] =>
...@@ -216,351 +247,287 @@ class GremlinTranslator(expr: Expression, ...@@ -216,351 +247,287 @@ class GremlinTranslator(expr: Expression,
} }
} }
def typeTestExpression(typeName : String) : String = { def typeTestExpression(parent: GroovyExpression, typeName : String) : GroovyExpression = {
val stats = gPersistenceBehavior.typeTestExpression(escape(typeName), counter) val stats = GremlinExpressionFactory.INSTANCE.generateTypeTestExpression(gPersistenceBehavior, parent, typeName, counter)
preStatements ++= stats.init preStatements ++= stats.init
stats.last stats.last
} }
def escape(str: String): String = { val QUOTE = "\"";
if (str != null) {
return str.replace("\"", "\\\"").replace("$", "\\$"); private def cleanStringLiteral(l : Literal[_]) : String = {
} return l.toString.stripPrefix(QUOTE).stripSuffix(QUOTE);
str
} }
private def genQuery(expr: Expression, inSelect: Boolean): String = expr match {
case ClassExpression(clsName) => private def genQuery(parent: GroovyExpression, expr: Expression, inSelect: Boolean): GroovyExpression = expr match {
typeTestExpression(clsName) case ClassExpression(clsName) => typeTestExpression(parent, clsName)
case TraitExpression(clsName) => case TraitExpression(clsName) => typeTestExpression(parent, clsName)
typeTestExpression(clsName)
case fe@FieldExpression(fieldName, fInfo, child) case fe@FieldExpression(fieldName, fInfo, child)
if fe.dataType.getTypeCategory == TypeCategory.PRIMITIVE || fe.dataType.getTypeCategory == TypeCategory.ARRAY => { if fe.dataType.getTypeCategory == TypeCategory.PRIMITIVE || fe.dataType.getTypeCategory == TypeCategory.ARRAY => {
val fN = "\"" + gPersistenceBehavior.fieldNameInVertex(fInfo.dataType, fInfo.attrInfo) + "\"" val fN = gPersistenceBehavior.fieldNameInVertex(fInfo.dataType, fInfo.attrInfo)
genPropertyAccessExpr(child, fInfo, fN, inSelect) val childExpr = translateOptChild(parent, child, inSelect);
return GremlinExpressionFactory.INSTANCE.generateFieldExpression(childExpr, fInfo, fN, inSelect);
} }
case fe@FieldExpression(fieldName, fInfo, child) case fe@FieldExpression(fieldName, fInfo, child)
if fe.dataType.getTypeCategory == TypeCategory.CLASS || fe.dataType.getTypeCategory == TypeCategory.STRUCT => { if fe.dataType.getTypeCategory == TypeCategory.CLASS || fe.dataType.getTypeCategory == TypeCategory.STRUCT => {
val direction = if (fInfo.isReverse) "in" else "out" val childExpr = translateOptChild(parent, child, inSelect);
val direction = if (fInfo.isReverse) AtlasEdgeDirection.IN else AtlasEdgeDirection.OUT
val edgeLbl = gPersistenceBehavior.edgeLabel(fInfo) val edgeLbl = gPersistenceBehavior.edgeLabel(fInfo)
val step = s"""$direction("$edgeLbl")""" return GremlinExpressionFactory.INSTANCE.generateAdjacentVerticesExpression(childExpr, direction, edgeLbl)
generateAndPrependExpr(child, inSelect, s"""$step""")
} }
case fe@FieldExpression(fieldName, fInfo, child) if fInfo.traitName != null => { case fe@FieldExpression(fieldName, fInfo, child) if fInfo.traitName != null => {
val childExpr = translateOptChild(parent, child, inSelect);
val direction = gPersistenceBehavior.instanceToTraitEdgeDirection val direction = gPersistenceBehavior.instanceToTraitEdgeDirection
val edgeLbl = gPersistenceBehavior.edgeLabel(fInfo) val edgeLbl = gPersistenceBehavior.edgeLabel(fInfo)
val step = s"""$direction("$edgeLbl")""" return GremlinExpressionFactory.INSTANCE.generateAdjacentVerticesExpression(childExpr, direction, edgeLbl)
generateAndPrependExpr(child, inSelect, s"""$step""")
} }
case c@ComparisonExpression(symb, f@FieldExpression(fieldName, fInfo, ch), l) => { case c@ComparisonExpression(symb, f@FieldExpression(fieldName, fInfo, ch), l) => {
return genHasPredicate(ch, fInfo, fieldName, inSelect, c, l) val qualifiedPropertyName = s"${gPersistenceBehavior.fieldNameInVertex(fInfo.dataType, fInfo.attrInfo)}"
val childExpr = translateOptChild(parent, ch, inSelect)
val persistentExprValue : GroovyExpression = if(l.isInstanceOf[Literal[_]]) {
translateLiteralValue(fInfo.attrInfo.dataType, l.asInstanceOf[Literal[_]]);
}
else {
genQuery(null, l, inSelect);
}
return GremlinExpressionFactory.INSTANCE.generateHasExpression(gPersistenceBehavior, childExpr, qualifiedPropertyName, c.symbol, persistentExprValue, fInfo);
} }
case fil@FilterExpression(child, condExpr) => { case fil@FilterExpression(child, condExpr) => {
s"${genQuery(child, inSelect)}.${genQuery(condExpr, inSelect)}" val newParent = genQuery(parent, child, inSelect);
return genQuery(newParent, condExpr, inSelect);
} }
case l@LogicalExpression(symb, children) => { case l@LogicalExpression(symb, children) => {
if(gPersistenceBehavior.getSupportedGremlinVersion() == GremlinVersion.THREE) { val translatedChildren : java.util.List[GroovyExpression] = translateList(children, true, inSelect);
if(children.length == 1) { return GremlinExpressionFactory.INSTANCE.generateLogicalExpression(parent, symb, translatedChildren);
//gremlin 3 treats one element expressions as 'false'. Avoid
//creating a boolean expression in this case. Inline the expression
//note: we can't simply omit it, since it will cause us to traverse the edge!
//use 'where' instead
var child : Expression = children.head;
//if child is a back expression, that expression becomes an argument to where
return s"""where(${genQuery(child, inSelect)})""";
}
else {
// Gremlin 3 does not support _() syntax
//
return s"""$symb${children.map( genQuery(_, inSelect)).mkString("(", ",", ")")}"""
}
}
else {
s"""$symb${children.map("_()." + genQuery(_, inSelect)).mkString("(", ",", ")")}"""
}
} }
case sel@SelectExpression(child, selList) => { case sel@SelectExpression(child, selList) => {
val m = groupSelectExpressionsBySrc(sel) val m = groupSelectExpressionsBySrc(sel)
var srcNamesList: List[String] = List() var srcNamesList: java.util.List[LiteralExpression] = new ArrayList()
var srcExprsList: List[List[String]] = List() var srcExprsList: List[java.util.List[GroovyExpression]] = List()
val it = m.iterator val it = m.iterator
while (it.hasNext) { while (it.hasNext) {
val (src, selExprs) = it.next val (src, selExprs) = it.next
srcNamesList = srcNamesList :+ s""""$src"""" srcNamesList.add(new LiteralExpression(src));
srcExprsList = srcExprsList :+ selExprs.map { selExpr => val translatedSelExprs : java.util.List[GroovyExpression] = translateList(selExprs, false, true);
genQuery(selExpr, true) srcExprsList = srcExprsList :+ translatedSelExprs
}
} }
val srcExprsStringList = srcExprsList.map { val srcExprsStringList : java.util.List[GroovyExpression] = new ArrayList();
_.mkString("[", ",", "]") srcExprsList.foreach { it =>
srcExprsStringList.add(new ListExpression(it));
} }
if(gPersistenceBehavior.getSupportedGremlinVersion() == GremlinVersion.TWO) { val childExpr = genQuery(parent, child, inSelect)
val srcNamesString = srcNamesList.mkString("[", ",", "]") return GremlinExpressionFactory.INSTANCE.generateSelectExpression(childExpr, srcNamesList, srcExprsStringList);
val srcExprsString = srcExprsStringList.foldLeft("")(_ + "{" + _ + "}")
s"${genQuery(child, inSelect)}.select($srcNamesString)$srcExprsString"
}
else {
//gremlin 3
val srcNamesString = srcNamesList.mkString("", ",", "")
val srcExprsString = srcExprsStringList.foldLeft("")(_ + ".by({" + _ + "} as Function)")
s"${genQuery(child, inSelect)}.select($srcNamesString)$srcExprsString"
}
} }
case loop@LoopExpression(input, loopExpr, t) => { case loop@LoopExpression(input, loopExpr, t) => {
if(gPersistenceBehavior.getSupportedGremlinVersion() == GremlinVersion.TWO) { val times : Integer = if(t.isDefined) {
val inputQry = genQuery(input, inSelect) t.get.rawValue.asInstanceOf[Integer]
val loopingPathGExpr = genQuery(loopExpr, inSelect)
val loopGExpr = s"""loop("${input.asInstanceOf[AliasExpression].alias}")"""
val untilCriteria = if (t.isDefined) s"{it.loops < ${t.get.value}}" else "{it.path.contains(it.object)?false:true}"
val loopObjectGExpr = gPersistenceBehavior.loopObjectExpression(input.dataType)
val enablePathExpr = s".enablePath()"
s"""${inputQry}.${loopingPathGExpr}.${loopGExpr}${untilCriteria}${loopObjectGExpr}${enablePathExpr}"""
} }
else { else {
//gremlin 3 - TODO - add support for circular lineage null.asInstanceOf[Integer]
val inputQry = genQuery(input, inSelect)
val repeatExpr = s"""repeat(__.${genQuery(loopExpr, inSelect)})"""
val optTimesExpr = if (t.isDefined) s".times(${t.get.value})" else ""
val emitExpr = s""".emit(${gPersistenceBehavior.loopObjectExpression(input.dataType)})"""
s"""${inputQry}.${repeatExpr}${optTimesExpr}${emitExpr}"""
} }
val alias = input.asInstanceOf[AliasExpression].alias;
val inputQry = genQuery(parent, input, inSelect)
val translatedLoopExpr = genQuery(GremlinExpressionFactory.INSTANCE.getLoopExpressionParent(inputQry), loopExpr, inSelect);
return GremlinExpressionFactory.INSTANCE.generateLoopExpression(inputQry, gPersistenceBehavior, input.dataType, translatedLoopExpr, alias, times);
} }
case BackReference(alias, _, _) => { case BackReference(alias, _, _) => {
if (inSelect) { return GremlinExpressionFactory.INSTANCE.generateBackReferenceExpression(parent, inSelect, alias);
gPersistenceBehavior.fieldPrefixInSelect() }
} case AliasExpression(child, alias) => {
else { var childExpr = genQuery(parent, child, inSelect);
if(gPersistenceBehavior.getSupportedGremlinVersion() == GremlinVersion.TWO) { return GremlinExpressionFactory.INSTANCE.generateAliasExpression(childExpr, alias);
s"""back("$alias")""" }
} case isTraitLeafExpression(traitName, Some(clsExp)) => {
else { val label = gPersistenceBehavior.traitLabel(clsExp.dataType, traitName);
s"""select("$alias")""" return GremlinExpressionFactory.INSTANCE.generateAdjacentVerticesExpression(parent, AtlasEdgeDirection.OUT, label);
} }
} case isTraitUnaryExpression(traitName, child) => {
val label = gPersistenceBehavior.traitLabel(child.dataType, traitName);
return GremlinExpressionFactory.INSTANCE.generateAdjacentVerticesExpression(parent, AtlasEdgeDirection.OUT, label);
} }
case AliasExpression(child, alias) => s"""${genQuery(child, inSelect)}.as("$alias")"""
case isTraitLeafExpression(traitName, Some(clsExp)) =>
s"""out("${gPersistenceBehavior.traitLabel(clsExp.dataType, traitName)}")"""
case isTraitUnaryExpression(traitName, child) =>
s"""out("${gPersistenceBehavior.traitLabel(child.dataType, traitName)}")"""
case hasFieldLeafExpression(fieldName, clsExp) => clsExp match { case hasFieldLeafExpression(fieldName, clsExp) => clsExp match {
case None => s"""has("$fieldName")""" case None => GremlinExpressionFactory.INSTANCE.generateUnaryHasExpression(parent, fieldName)
case Some(x) => { case Some(x) => {
val fi = TypeUtils.resolveReference(clsExp.get.dataType, fieldName); val fi = TypeUtils.resolveReference(clsExp.get.dataType, fieldName);
if(! fi.isDefined) { if(! fi.isDefined) {
s"""has("$fieldName")""" return GremlinExpressionFactory.INSTANCE.generateUnaryHasExpression(parent, fieldName);
} }
else { else {
s"""has("${gPersistenceBehavior.fieldNameInVertex(fi.get.dataType, fi.get.attrInfo)}")""" val fName = gPersistenceBehavior.fieldNameInVertex(fi.get.dataType, fi.get.attrInfo)
return GremlinExpressionFactory.INSTANCE.generateUnaryHasExpression(parent, fName);
} }
} }
} }
case hasFieldUnaryExpression(fieldName, child) => case hasFieldUnaryExpression(fieldName, child) =>
s"""${genQuery(child, inSelect)}.has("$fieldName")""" val childExpr = genQuery(parent, child, inSelect);
case ArithmeticExpression(symb, left, right) => s"${genQuery(left, inSelect)} $symb ${genQuery(right, inSelect)}" return GremlinExpressionFactory.INSTANCE.generateUnaryHasExpression(childExpr, fieldName);
case l: Literal[_] => l.toString case ArithmeticExpression(symb, left, right) => {
case list: ListLiteral[_] => list.toString val leftExpr = genQuery(parent, left, inSelect);
val rightExpr = genQuery(parent, right, inSelect);
return GremlinExpressionFactory.INSTANCE.generateArithmeticExpression(leftExpr, symb, rightExpr);
}
case l: Literal[_] => {
if(parent != null) {
return new org.apache.atlas.groovy.FieldExpression(parent, cleanStringLiteral(l));
}
return translateLiteralValue(l.dataType, l);
}
case list: ListLiteral[_] => {
val values : java.util.List[GroovyExpression] = translateList(list.rawValue, false, inSelect);
return new ListExpression(values);
}
case in@TraitInstanceExpression(child) => { case in@TraitInstanceExpression(child) => {
val direction = gPersistenceBehavior.traitToInstanceEdgeDirection val childExpr = genQuery(parent, child, inSelect);
s"${genQuery(child, inSelect)}.$direction()" val direction = gPersistenceBehavior.traitToInstanceEdgeDirection;
return GremlinExpressionFactory.INSTANCE.generateAdjacentVerticesExpression(childExpr, direction);
} }
case in@InstanceExpression(child) => { case in@InstanceExpression(child) => {
s"${genQuery(child, inSelect)}" return genQuery(parent, child, inSelect);
} }
case pe@PathExpression(child) => { case pe@PathExpression(child) => {
if(gPersistenceBehavior.getSupportedGremlinVersion() == GremlinVersion.TWO) { val childExpr = genQuery(parent, child, inSelect)
s"${genQuery(child, inSelect)}.path" return GremlinExpressionFactory.INSTANCE.generatePathExpression(childExpr);
}
else {
s"${genQuery(child, inSelect)}.path()"
}
} }
case order@OrderExpression(child, odr, asc) => { case order@OrderExpression(child, odr, asc) => {
var orderby = "" var orderby = ""
var orderExpression = odr var orderExpression = odr
if(odr.isInstanceOf[BackReference]) { if(odr.isInstanceOf[BackReference]) {
orderExpression = odr.asInstanceOf[BackReference].reference orderExpression = odr.asInstanceOf[BackReference].reference
} }
else if (odr.isInstanceOf[AliasExpression]) { else if (odr.isInstanceOf[AliasExpression]) {
orderExpression = odr.asInstanceOf[AliasExpression].child orderExpression = odr.asInstanceOf[AliasExpression].child
} }
val orderbyProperty = genQuery(orderExpression, false)
if(gPersistenceBehavior.getSupportedGremlinVersion() == GremlinVersion.TWO) {
val bProperty = s"it.b.$orderbyProperty"
val aProperty = s"it.a.$orderbyProperty"
val aCondition = s"($aProperty != null ? $aProperty.toLowerCase(): $aProperty)"
val bCondition = s"($bProperty != null ? $bProperty.toLowerCase(): $bProperty)"
orderby = asc match {
//builds a closure comparison function based on provided order by clause in DSL. This will be used to sort the results by gremlin order pipe.
//Ordering is case insensitive.
case false=> s"order{$bCondition <=> $aCondition}"//descending
case _ => s"order{$aCondition <=> $bCondition}"
}
}
else {
val orderbyProperty = genQuery(orderExpression, true);
val aPropertyExpr = gremlin3ToLowerCase("a");
val bPropertyExpr = gremlin3ToLowerCase("b");
orderby = asc match {
//builds a closure comparison function based on provided order by clause in DSL. This will be used to sort the results by gremlin order pipe.
//Ordering is case insensitive.
case false=> s"""order().by({$orderbyProperty'}, { a,b -> $bPropertyExpr <=> $aPropertyExpr })"""
case _ => s"""order().by({$orderbyProperty},{ a,b -> $aPropertyExpr <=> $bPropertyExpr })"""
} val childExpr = genQuery(parent, child, inSelect);
var orderByParents : java.util.List[GroovyExpression] = GremlinExpressionFactory.INSTANCE.getOrderFieldParents();
val translatedParents : java.util.List[GroovyExpression] = new ArrayList[GroovyExpression]();
var translatedOrderParents = orderByParents.foreach { it =>
translatedParents.add(genQuery(it, orderExpression, false));
} }
s"""${genQuery(child, inSelect)}.$orderby""" return GremlinExpressionFactory.INSTANCE.generateOrderByExpression(childExpr, translatedParents,asc);
} }
case limitOffset@LimitExpression(child, limit, offset) => { case limitOffset@LimitExpression(child, limit, offset) => {
if(gPersistenceBehavior.getSupportedGremlinVersion() == GremlinVersion.TWO) { val childExpr = genQuery(parent, child, inSelect);
val totalResultRows = limit.value + offset.value val totalResultRows = limit.value + offset.value;
s"""${genQuery(child, inSelect)} [$offset..<$totalResultRows]""" return GremlinExpressionFactory.INSTANCE.generateLimitExpression(childExpr, offset.value, totalResultRows);
}
else {
val totalResultRows = limit.value + offset.value
s"""${genQuery(child, inSelect)}.range($offset,$totalResultRows)"""
}
} }
case x => throw new GremlinTranslationException(x, "expression not yet supported") case x => throw new GremlinTranslationException(x, "expression not yet supported")
} }
def gremlin3ToLowerCase(varName : String) : String = { def translateList(exprs : List[Expressions.Expression], isAnonymousTraveral: Boolean, inSelect : Boolean) : java.util.List[GroovyExpression] = {
s"""($varName != null ? $varName.toString().toLowerCase() : null)""" var parent = if(isAnonymousTraveral) {GremlinExpressionFactory.INSTANCE.getAnonymousTraversalExpression() } else { null }
} var result : java.util.List[GroovyExpression] = new java.util.ArrayList(exprs.size);
exprs.foreach { it =>
def genPropertyAccessExpr(e: Option[Expression], fInfo : FieldInfo, quotedPropertyName: String, inSelect: Boolean) : String = { result.add(genQuery(parent, it, inSelect));
if(gPersistenceBehavior.getSupportedGremlinVersion() == GremlinVersion.TWO) {
generateAndPrependExpr(e, inSelect, s"""$quotedPropertyName""")
}
else {
val attrInfo : AttributeInfo = fInfo.attrInfo;
val attrType : IDataType[_] = attrInfo.dataType;
if(inSelect) {
val expr = generateAndPrependExpr(e, inSelect, s"""property($quotedPropertyName).orElse(null)""");
return gPersistenceBehavior.generatePersisentToLogicalConversionExpression(expr, attrType);
}
else {
val unmapped = s"""values($quotedPropertyName)"""
val expr = if(gPersistenceBehavior.isPropertyValueConversionNeeded(attrType)) {
val conversionFunction = gPersistenceBehavior.generatePersisentToLogicalConversionExpression(s"""it.get()""", attrType);
s"""$unmapped.map{ $conversionFunction }"""
}
else {
unmapped
}
generateAndPrependExpr(e, inSelect, expr)
}
} }
return result;
} }
def generateAndPrependExpr(e1: Option[Expression], inSelect: Boolean, e2: String) : String = e1 match { def translateOptChild(parent : GroovyExpression, child : Option[Expressions.Expression] , inSelect: Boolean) : GroovyExpression = child match {
case Some(x) => s"""${genQuery(x, inSelect)}.$e2""" case Some(x) => genQuery(parent, x, inSelect)
case None => e2 case None => parent
} }
def genHasPredicate(e: Option[Expression], fInfo : FieldInfo, fieldName: String, inSelect: Boolean, c: ComparisonExpression, expr: Expression) : String = { def translateLiteralValue(dataType: IDataType[_], l: Literal[_]): GroovyExpression = {
val qualifiedPropertyName = s"${gPersistenceBehavior.fieldNameInVertex(fInfo.dataType, fInfo.attrInfo)}" if (dataType == DataTypes.DATE_TYPE) {
val persistentExprValue = translateValueToPersistentForm(fInfo, expr); try {
if(gPersistenceBehavior.getSupportedGremlinVersion() == GremlinVersion.TWO) { //Accepts both date, datetime formats
return generateAndPrependExpr(e, inSelect, s"""has("${qualifiedPropertyName}", ${gPersistenceBehavior.gremlinCompOp(c)}, $persistentExprValue)"""); val dateStr = cleanStringLiteral(l)
val dateVal = ISODateTimeFormat.dateOptionalTimeParser().parseDateTime(dateStr).getMillis
return new LiteralExpression(dateVal)
} catch {
case pe: java.text.ParseException =>
throw new GremlinTranslationException(l,
"Date format " + l + " not supported. Should be of the format " +
TypeSystem.getInstance().getDateFormat.toPattern);
}
}
else if(dataType == DataTypes.BYTE_TYPE) {
//cast needed, otherwise get class cast exception when trying to compare, since the
//persist value is assumed to be an Integer
return new CastExpression(new LiteralExpression(Byte.valueOf(s"""${l}"""), true),"byte");
}
else if(dataType == DataTypes.INT_TYPE) {
return new LiteralExpression(Integer.valueOf(s"""${l}"""));
}
else if(dataType == DataTypes.BOOLEAN_TYPE) {
return new LiteralExpression(Boolean.valueOf(s"""${l}"""));
}
else if(dataType == DataTypes.SHORT_TYPE) {
return new CastExpression(new LiteralExpression(Short.valueOf(s"""${l}"""), true),"short");
}
else if(dataType == DataTypes.LONG_TYPE) {
return new LiteralExpression(Long.valueOf(s"""${l}"""), true);
}
else if(dataType == DataTypes.FLOAT_TYPE) {
return new LiteralExpression(Float.valueOf(s"""${l}"""), true);
}
else if(dataType == DataTypes.DOUBLE_TYPE) {
return new LiteralExpression(Double.valueOf(s"""${l}"""), true);
}
else if(dataType == DataTypes.STRING_TYPE) {
return new LiteralExpression(cleanStringLiteral(l));
} }
else { else {
val attrInfo : AttributeInfo = fInfo.attrInfo; return new LiteralExpression(l.rawValue);
val attrType : IDataType[_] = attrInfo.dataType; }
if(gPersistenceBehavior.isPropertyValueConversionNeeded(attrType)) {
//for some types, the logical value cannot be stored directly in the underlying graph,
//and conversion logic is needed to convert the persistent form of the value
//to the actual value. In cases like this, we generate a conversion expression to
//do this conversion and use the filter step to perform the comparsion in the gremlin query
val vertexExpr = "((Vertex)it.get())";
val conversionExpr = gPersistenceBehavior.generatePersisentToLogicalConversionExpression(s"""$vertexExpr.value("$qualifiedPropertyName")""", attrType);
return generateAndPrependExpr(e, inSelect, s"""filter{$vertexExpr.property("$qualifiedPropertyName").isPresent() && $conversionExpr ${gPersistenceBehavior.gremlinPrimitiveOp(c)} $persistentExprValue}""");
}
else {
return generateAndPrependExpr(e, inSelect, s"""has("${qualifiedPropertyName}", ${gPersistenceBehavior.gremlinCompOp(c)}($persistentExprValue))""");
}
}
} }
def translateValueToPersistentForm(fInfo: FieldInfo, l: Expression): Any = {
val dataType = fInfo.attrInfo.dataType;
val QUOTE = "\"";
if (dataType == DataTypes.DATE_TYPE) {
try {
//Accepts both date, datetime formats
val dateStr = l.toString.stripPrefix(QUOTE).stripSuffix(QUOTE)
val dateVal = ISODateTimeFormat.dateOptionalTimeParser().parseDateTime(dateStr).getMillis
return dateVal
} catch {
case pe: java.text.ParseException =>
throw new GremlinTranslationException(l,
"Date format " + l + " not supported. Should be of the format " +
TypeSystem.getInstance().getDateFormat.toPattern);
}
}
else if(dataType == DataTypes.BYTE_TYPE) {
//cast needed, otherwise get class cast exception when trying to compare, since the
//persist value is assumed to be an Integer
return s"""(byte)$l"""
}
else if(dataType == DataTypes.SHORT_TYPE) {
return s"""(short)$l"""
}
else if(dataType == DataTypes.LONG_TYPE) {
return s"""${l}L"""
}
else if(dataType == DataTypes.FLOAT_TYPE) {
return s"""${l}f"""
}
else if(dataType == DataTypes.DOUBLE_TYPE) {
return s"""${l}d"""
}
else if(dataType == DataTypes.STRING_TYPE) {
return string(escape(l.toString.stripPrefix(QUOTE).stripSuffix(QUOTE)));
}
else {
return l
}
}
def genFullQuery(expr: Expression, hasSelect: Boolean): String = { def genFullQuery(expr: Expression, hasSelect: Boolean): String = {
var q = genQuery(expr, false) var q : GroovyExpression = new FunctionCallExpression(new IdentifierExpression("g"),"V");
val debug:Boolean = false val debug:Boolean = false
if(gPersistenceBehavior.addGraphVertexPrefix(preStatements)) { if(gPersistenceBehavior.addGraphVertexPrefix(preStatements)) {
q = s"g.V()${gPersistenceBehavior.initialQueryCondition}.$q" q = gPersistenceBehavior.addInitialQueryCondition(q);
} }
q = s"$q.toList()${gPersistenceBehavior.getGraph().getOutputTransformationPredicate(hasSelect, expr.isInstanceOf[PathExpression])}" q = genQuery(q, expr, false)
q = new FunctionCallExpression(q, "toList");
q = gPersistenceBehavior.getGraph().addOutputTransformationPredicate(q, hasSelect, expr.isInstanceOf[PathExpression]);
var overallExpression = new CodeBlockExpression();
overallExpression.addStatements(preStatements);
overallExpression.addStatement(q)
overallExpression.addStatements(postStatements);
var qryStr = generateGremlin(overallExpression);
if(debug) { if(debug) {
println(" query " + q) println(" query " + qryStr)
} }
q = (preStatements ++ Seq(q) ++ postStatements).mkString("", ";", "") qryStr;
/* }
* the L:{} represents a groovy code block; the label is needed
* to distinguish it from a groovy closure. def generateGremlin(expr: GroovyExpression) : String = {
*/ val ctx : GroovyGenerationContext = new GroovyGenerationContext();
s"L:{$q}" ctx.setParametersAllowed(false);
expr.generateGroovy(ctx);
return ctx.getQuery;
} }
def translate(): GremlinQuery = { def translate(): GremlinQuery = {
var e1 = expr.transformUp(wrapAndRule) var e1 = expr.transformUp(wrapAndRule)
...@@ -570,9 +537,9 @@ class GremlinTranslator(expr: Expression, ...@@ -570,9 +537,9 @@ class GremlinTranslator(expr: Expression,
e1 = e1.transformUp(addAliasToLoopInput()) e1 = e1.transformUp(addAliasToLoopInput())
e1 = e1.transformUp(instanceClauseToTop(e1)) e1 = e1.transformUp(instanceClauseToTop(e1))
e1 = e1.transformUp(traitClauseWithInstanceForTop(e1)) e1 = e1.transformUp(traitClauseWithInstanceForTop(e1))
//Following code extracts the select expressions from expression tree. //Following code extracts the select expressions from expression tree.
val se = SelectExpressionHelper.extractSelectExpression(e1) val se = SelectExpressionHelper.extractSelectExpression(e1)
if (se.isDefined) { if (se.isDefined) {
val rMap = buildResultMapping(se.get) val rMap = buildResultMapping(se.get)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment