blob: c36f5b005806971a27339185bae9060e5824ca26 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.query;
import com.google.common.annotations.VisibleForTesting;
import org.apache.atlas.AtlasErrorCode;
import org.apache.atlas.exception.AtlasBaseException;
import org.apache.atlas.glossary.GlossaryUtils;
import org.apache.atlas.model.TypeCategory;
import org.apache.atlas.model.discovery.SearchParameters;
import org.apache.atlas.model.typedef.AtlasStructDef;
import org.apache.atlas.repository.Constants;
import org.apache.atlas.type.AtlasBuiltInTypes;
import org.apache.atlas.type.AtlasEntityType;
import org.apache.atlas.type.AtlasStructType;
import org.apache.atlas.type.AtlasType;
import org.apache.atlas.type.AtlasTypeRegistry;
import org.apache.commons.lang.StringUtils;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TimeZone;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.apache.atlas.model.discovery.SearchParameters.ALL_CLASSIFICATIONS;
import static org.apache.atlas.model.discovery.SearchParameters.NO_CLASSIFICATIONS;
import static org.apache.atlas.type.AtlasStructType.AtlasAttribute.AtlasRelationshipEdgeDirection.IN;
import static org.apache.atlas.type.AtlasStructType.AtlasAttribute.AtlasRelationshipEdgeDirection.OUT;
public class GremlinQueryComposer {
private static final String ISO8601_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
private static final String ISO8601_DATE_FORMAT = "yyyy-MM-dd";
private static final String REGEX_ALPHA_NUMERIC_PATTERN = "[a-zA-Z0-9]+";
private static final String EMPTY_STRING = "";
private static final int DEFAULT_QUERY_RESULT_LIMIT = 25;
private static final int DEFAULT_QUERY_RESULT_OFFSET = 0;
private static final ThreadLocal<DateFormat[]> DSL_DATE_FORMAT = ThreadLocal.withInitial(() -> {
final String formats[] = { ISO8601_FORMAT, ISO8601_DATE_FORMAT };
DateFormat[] dfs = new DateFormat[formats.length];
for (int i = 0; i < formats.length; i++) {
dfs[i] = new SimpleDateFormat(formats[i]);
dfs[i].setTimeZone(TimeZone.getTimeZone("UTC"));
}
return dfs;
});
private final GremlinClauseList queryClauses = new GremlinClauseList();
private final Set<String> attributesProcessed = new HashSet<>();
private final Lookup lookup;
private final AtlasDSL.QueryMetadata queryMetadata;
private final int providedLimit;
private final int providedOffset;
private final Context context;
private final GremlinQueryComposer parent;
public GremlinQueryComposer(Lookup registryLookup, Context context, AtlasDSL.QueryMetadata qmd, int limit, int offset, GremlinQueryComposer parent) {
this.lookup = registryLookup;
this.context = context;
this.queryMetadata = qmd;
this.providedLimit = limit;
this.providedOffset = offset;
this.parent = parent;
init();
}
public GremlinQueryComposer(Lookup registryLookup, AtlasDSL.QueryMetadata qmd, int limit, int offset) {
this(registryLookup, new Context(registryLookup), qmd, limit, offset, null);
}
public GremlinQueryComposer(AtlasTypeRegistry typeRegistry, AtlasDSL.QueryMetadata qmd, int limit, int offset) {
this(new RegistryBasedLookup(typeRegistry), qmd, limit, offset);
}
@VisibleForTesting
GremlinQueryComposer(Lookup lookup, Context context, final AtlasDSL.QueryMetadata qmd) {
this(lookup, context, qmd, DEFAULT_QUERY_RESULT_LIMIT, DEFAULT_QUERY_RESULT_OFFSET, null);
}
public void addFrom(String typeName) {
IdentifierHelper.Info typeInfo = createInfo(typeName);
if (context.shouldRegister(typeInfo.get())) {
context.registerActive(typeInfo.get());
IdentifierHelper.Info ia = createInfo(typeInfo.get());
if (ia.isTrait()) {
String traitName = ia.get();
if (traitName.equals(ALL_CLASSIFICATIONS)) {
addTrait(GremlinClause.ANY_TRAIT, ia);
} else if (traitName.equals(NO_CLASSIFICATIONS)) {
addTrait(GremlinClause.NO_TRAIT, ia);
} else {
addTrait(GremlinClause.TRAIT, ia);
}
} else {
if (ia.hasSubtypes()) {
add(GremlinClause.HAS_TYPE_WITHIN, ia.getSubTypes());
} else {
add(GremlinClause.HAS_TYPE, ia);
}
}
} else {
IdentifierHelper.Info ia = createInfo(typeInfo.get());
introduceType(ia);
}
}
public void addFromProperty(String typeName, String attribute) {
if (!isNestedQuery()) {
addFrom(typeName);
}
add(GremlinClause.HAS_PROPERTY, createInfo(attribute));
}
public void addIsA(String typeName, String traitName) {
if (!isNestedQuery()) {
addFrom(typeName);
}
IdentifierHelper.Info traitInfo = createInfo(traitName);
if (StringUtils.equals(traitName, ALL_CLASSIFICATIONS)) {
addTrait(GremlinClause.ANY_TRAIT, traitInfo);
} else if (StringUtils.equals(traitName, NO_CLASSIFICATIONS)) {
addTrait(GremlinClause.NO_TRAIT, traitInfo);
} else {
addTrait(GremlinClause.TRAIT, traitInfo);
}
}
public void addHasTerm(String typeName, String termName) {
String attributeToSearch;
String qualifiedAttributeSeperator = String.valueOf(GlossaryUtils.invalidNameChars[0]);
String[] terms = termName.split(qualifiedAttributeSeperator);
if (terms.length > 1) {
attributeToSearch = GlossaryUtils.QUALIFIED_NAME_ATTR;;
} else {
termName = terms[0];
attributeToSearch = GlossaryUtils.NAME;;
}
add(GremlinClause.TERM, attributeToSearch, IdentifierHelper.removeQuotes(termName));
}
public void addWhere(String lhs, String operator, String rhs) {
String currentType = context.getActiveTypeName();
IdentifierHelper.Info org = null;
IdentifierHelper.Info lhsI = createInfo(lhs);
if (!lhsI.isPrimitive()) {
introduceType(lhsI);
org = lhsI;
lhsI = createInfo(lhs);
lhsI.setTypeName(org.getTypeName());
}
if (!context.validator.isValidQualifiedName(lhsI.getQualifiedName(), lhsI.getRaw())) {
return;
}
if (lhsI.isDate()) {
rhs = parseDate(rhs);
} else if (lhsI.isNumeric() && !StringUtils.equals(lhsI.getAttributeName(), Constants.IS_INCOMPLETE_PROPERTY_KEY)) {
rhs = parseNumber(rhs, this.context);
}
rhs = addQuotesIfNecessary(lhsI, rhs);
SearchParameters.Operator op = SearchParameters.Operator.fromString(operator);
if (StringUtils.equals(lhsI.getAttributeName(), Constants.IS_INCOMPLETE_PROPERTY_KEY)) {
addForIsIncompleteClause(lhsI, op, rhs);
} else {
if (op == SearchParameters.Operator.LIKE) {
final AtlasStructType.AtlasAttribute attribute = context.getActiveEntityType().getAttribute(lhsI.getAttributeName());
final AtlasStructDef.AtlasAttributeDef.IndexType indexType = attribute.getAttributeDef().getIndexType();
if (indexType == AtlasStructDef.AtlasAttributeDef.IndexType.STRING || !containsNumberAndLettersOnly(rhs)) {
add(GremlinClause.STRING_CONTAINS, getPropertyForClause(lhsI), IdentifierHelper.getFixedRegEx(rhs));
} else {
add(GremlinClause.TEXT_CONTAINS, getPropertyForClause(lhsI), IdentifierHelper.getFixedRegEx(rhs));
}
} else if (op == SearchParameters.Operator.IN) {
add(GremlinClause.HAS_OPERATOR, getPropertyForClause(lhsI), "within", rhs);
} else {
Object normalizedRhs = getNormalizedAttrVal(lhsI, IdentifierHelper.removeQuotes(rhs));
addWithNormalizedValue(GremlinClause.HAS_OPERATOR, getPropertyForClause(lhsI), op.getSymbols()[1], normalizedRhs, rhs);
}
}
// record that the attribute has been processed so that the select clause doesn't add a attr presence check
attributesProcessed.add(lhsI.getQualifiedName());
if (org != null && org.isReferredType()) {
add(GremlinClause.DEDUP);
if (org.getEdgeDirection() != null) {
GremlinClause gremlinClauseForEdgeLabel = org.getEdgeDirection().equals(IN) ? GremlinClause.OUT : GremlinClause.IN;
add(gremlinClauseForEdgeLabel, org.getEdgeLabel());
} else {
add(GremlinClause.OUT, org.getEdgeLabel());
}
context.registerActive(currentType);
}
}
private void addForIsIncompleteClause(IdentifierHelper.Info lhsI,SearchParameters.Operator op, String rhs ) {
GremlinClause clause = GremlinClause.HAS_OPERATOR;
rhs = rhs.replace("'", "").replace("\"", "");
switch (op) {
case EQ:
if (IdentifierHelper.isCompleteValue(rhs)) {
clause = GremlinClause.HAS_NOT_PROPERTY;
} else if (IdentifierHelper.isInCompleteValue(rhs)) {
rhs = Constants.INCOMPLETE_ENTITY_VALUE.toString();
}
break;
case NEQ:
if (IdentifierHelper.isCompleteValue(rhs)) {
op = SearchParameters.Operator.EQ;
rhs = Constants.INCOMPLETE_ENTITY_VALUE.toString();
} else if (IdentifierHelper.isInCompleteValue(rhs)) {
clause = GremlinClause.HAS_NOT_PROPERTY;
}
break;
}
Object normalizedRhs = getNormalizedAttrVal(lhsI, IdentifierHelper.removeQuotes(rhs));
addWithNormalizedValue(clause, getPropertyForClause(lhsI), op.getSymbols()[1], normalizedRhs, rhs);
}
private Object getNormalizedAttrVal(IdentifierHelper.Info attrInfo, String attrVal) {
AtlasEntityType entityType = context.getActiveEntityType();
String attrName = attrInfo.getAttributeName();
if (entityType == null || StringUtils.isEmpty(attrVal)) {
return attrVal;
}
AtlasType attributeType = entityType.getAttributeType(attrName);
if (attributeType == null) {
return attrVal;
}
Object normalizedValue = attributeType.getNormalizedValue(attrVal);
if (normalizedValue != null && attributeType instanceof AtlasBuiltInTypes.AtlasDateType) {
return ((Date) normalizedValue).getTime();
}
return normalizedValue;
}
private boolean containsNumberAndLettersOnly(String rhs) {
return Pattern.matches(REGEX_ALPHA_NUMERIC_PATTERN, IdentifierHelper.removeWildcards(rhs));
}
private String parseNumber(String rhs, Context context) {
return rhs.replace("'", "").replace("\"", "") + context.getNumericTypeFormatter();
}
public void addAndClauses(List<GremlinQueryComposer> queryComposers) {
List<String> clauses = addToSubClause(queryComposers);
add(GremlinClause.AND, String.join(",", clauses));
}
public void addOrClauses(List<GremlinQueryComposer> queryComposers) {
List<String> clauses = addToSubClause(queryComposers);
add(GremlinClause.OR, String.join(",", clauses));
}
public Set<String> getAttributesProcessed() {
return attributesProcessed;
}
public void addProcessedAttributes(Set<String> attributesProcessed) {
this.attributesProcessed.addAll(attributesProcessed);
}
public void addSelect(SelectClauseComposer selectClauseComposer) {
process(selectClauseComposer);
// If the query contains orderBy and groupBy then the transformation determination is deferred to the method processing orderBy
if (!(queryMetadata.hasOrderBy() && queryMetadata.hasGroupBy())) {
addSelectTransformation(selectClauseComposer, null, false);
}
this.context.setSelectClauseComposer(selectClauseComposer);
}
public GremlinQueryComposer createNestedProcessor() {
return new GremlinQueryComposer(lookup, this.context, queryMetadata, this.providedLimit, this.providedOffset, this);
}
public void addFromAlias(String typeName, String alias) {
addFrom(typeName);
addAsClause(alias);
context.registerAlias(alias);
}
public void addAsClause(String alias) {
add(GremlinClause.AS, alias);
}
public void addGroupBy(String item) {
addGroupByClause(item);
}
public void addLimit(String limit, String offset) {
SelectClauseComposer scc = context.getSelectClauseComposer();
if (scc == null) {
addLimitHelper(limit, offset);
} else {
if (!scc.hasAggregators()) {
addLimitHelper(limit, offset);
}
}
}
public void addDefaultLimit() {
addLimit(Integer.toString(providedLimit), Integer.toString(providedOffset));
}
public String get() {
close();
boolean mustTransform = !isNestedQuery() && queryMetadata.needTransformation();
String items[] = getFormattedClauses(mustTransform);
String s = mustTransform ?
getTransformedClauses(items) :
String.join(".", items);
return s;
}
public List<String> getErrorList() {
return context.getErrorList();
}
public void addOrderBy(String name, boolean isDesc) {
IdentifierHelper.Info ia = createInfo(name);
if (queryMetadata.hasSelect() && queryMetadata.hasGroupBy()) {
addSelectTransformation(this.context.selectClauseComposer, getPropertyForClause(ia), isDesc);
} else if (queryMetadata.hasGroupBy()) {
addOrderByClause(ia, isDesc);
moveToLast(GremlinClause.GROUP_BY);
} else {
addOrderByClause(ia, isDesc);
}
}
public boolean hasFromClause() {
return queryClauses.contains(GremlinClause.HAS_TYPE) != -1 ||
queryClauses.contains(GremlinClause.HAS_TYPE_WITHIN) != -1;
}
private void addWithNormalizedValue(GremlinClause clause, String propertyForClause, String symbol, Object normalizedRhs, String strValue) {
queryClauses.add(new GremlinClauseValue(clause, propertyForClause, symbol, normalizedRhs, strValue));
}
private long getDateFormat(String s) {
for (DateFormat dateFormat : DSL_DATE_FORMAT.get()) {
try {
return dateFormat.parse(s).getTime();
} catch (ParseException ignored) {
}
}
context.validator.check(false, AtlasErrorCode.INVALID_DSL_INVALID_DATE, s);
return -1;
}
private List<String> addToSubClause(List<GremlinQueryComposer> clauses) {
for (GremlinQueryComposer entry : clauses) {
this.addSubClauses(this.queryClauses.size(), entry.getQueryClauses());
}
return clauses.stream().map(x -> x.get()).collect(Collectors.toList());
}
private String getPropertyForClause(IdentifierHelper.Info ia) {
String vertexPropertyName = lookup.getVertexPropertyName(ia.getTypeName(), ia.getAttributeName());
if (StringUtils.isNotEmpty(vertexPropertyName)) {
return vertexPropertyName;
}
if (StringUtils.isNotEmpty(ia.getQualifiedName())) {
return ia.getQualifiedName();
}
return ia.getRaw();
}
private void process(SelectClauseComposer scc) {
if (scc.getItems() == null) {
return;
}
for (int i = 0; i < scc.getItems().length; i++) {
IdentifierHelper.Info ia = createInfo(scc.getItem(i));
if(StringUtils.isEmpty(ia.getQualifiedName())) {
context.getErrorList().add("Unable to find qualified name for " + ia.getAttributeName());
continue;
}
if (scc.isAggregatorWithArgument(i) && !ia.isPrimitive()) {
context.check(false, AtlasErrorCode.INVALID_DSL_SELECT_INVALID_AGG, ia.getQualifiedName());
return;
}
if (!scc.getItem(i).equals(scc.getLabel(i))) {
context.addAlias(scc.getLabel(i), ia.getQualifiedName());
}
if (scc.updateAsApplicable(i, getPropertyForClause(ia), ia.getQualifiedName())) {
continue;
}
scc.setIsSelectNoop(hasNoopCondition(ia));
if (scc.getIsSelectNoop()) {
return;
}
if (introduceType(ia)) {
scc.incrementTypesIntroduced();
scc.setIsSelectNoop(!ia.hasParts());
if (ia.hasParts()) {
scc.assign(i, getPropertyForClause(createInfo(ia.get())), GremlinClause.INLINE_GET_PROPERTY);
}
} else {
scc.assign(i, getPropertyForClause(ia), GremlinClause.INLINE_GET_PROPERTY);
scc.setIsPrimitiveAttr(i);
}
}
context.validator.check(!scc.hasMultipleReferredTypes(),
AtlasErrorCode.INVALID_DSL_SELECT_REFERRED_ATTR, Integer.toString(scc.getIntroducedTypesCount()));
context.validator.check(!scc.hasMixedAttributes(), AtlasErrorCode.INVALID_DSL_SELECT_ATTR_MIXING);
}
private boolean hasNoopCondition(IdentifierHelper.Info ia) {
return !ia.isPrimitive() && !ia.isAttribute() && context.hasAlias(ia.getRaw());
}
private void addLimitHelper(final String limit, final String offset) {
if (offset.equalsIgnoreCase("0")) {
add(GremlinClause.LIMIT, limit, limit);
} else {
addRangeClause(offset, limit);
}
}
private String getTransformedClauses(String[] items) {
String ret;
String body = String.join(".", Stream.of(items).filter(Objects::nonNull).collect(Collectors.toList()));
String inlineFn = queryClauses.getValue(queryClauses.size() - 1);
String funCall = String.format(inlineFn, body);
if (isNestedQuery()) {
ret = String.join(".", queryClauses.getValue(0), funCall);
} else {
ret = queryClauses.getValue(0) + funCall;
}
return ret;
}
private String[] getFormattedClauses(boolean needTransformation) {
String[] items = new String[queryClauses.size()];
int startIdx = needTransformation ? 1 : 0;
int endIdx = needTransformation ? queryClauses.size() - 1 : queryClauses.size();
for (int i = startIdx; i < endIdx; i++) {
items[i] = queryClauses.getValue(i);
}
return items;
}
private void addSelectTransformation(final SelectClauseComposer selectClauseComposer,
final String orderByQualifiedAttrName,
final boolean isDesc) {
GremlinClause gremlinClause;
if (selectClauseComposer.getIsSelectNoop()) {
gremlinClause = GremlinClause.SELECT_NOOP_FN;
} else if (queryMetadata.hasGroupBy()) {
gremlinClause = selectClauseComposer.onlyAggregators() ? GremlinClause.SELECT_ONLY_AGG_GRP_FN : GremlinClause.SELECT_MULTI_ATTR_GRP_FN;
} else {
gremlinClause = selectClauseComposer.onlyAggregators() ? GremlinClause.SELECT_ONLY_AGG_FN : GremlinClause.SELECT_FN;
}
if (StringUtils.isEmpty(orderByQualifiedAttrName)) {
add(0, gremlinClause,
selectClauseComposer.getLabelHeader(),
selectClauseComposer.getAssignmentExprString(),
selectClauseComposer.getItemsString(),
EMPTY_STRING);
} else {
int itemIdx = selectClauseComposer.getAttrIndex(orderByQualifiedAttrName);
GremlinClause sortClause = GremlinClause.INLINE_DEFAULT_TUPLE_SORT;
if (itemIdx != -1) {
sortClause = isDesc ? GremlinClause.INLINE_TUPLE_SORT_DESC : GremlinClause.INLINE_TUPLE_SORT_ASC;
}
String idxStr = String.valueOf(itemIdx);
add(0, gremlinClause,
selectClauseComposer.getLabelHeader(),
selectClauseComposer.getAssignmentExprString(),
selectClauseComposer.getItemsString(),
sortClause.get(idxStr, idxStr)
);
}
add(GremlinClause.INLINE_TRANSFORM_CALL);
}
private String addQuotesIfNecessary(IdentifierHelper.Info rhsI, String rhs) {
if(rhsI.isNumeric()) return rhs;
if (IdentifierHelper.isTrueOrFalse(rhs)) return rhs;
if (IdentifierHelper.isQuoted(rhs)) return rhs;
return IdentifierHelper.getQuoted(rhs);
}
private String parseDate(String rhs) {
String s = IdentifierHelper.isQuoted(rhs) ?
IdentifierHelper.removeQuotes(rhs) :
rhs;
return String.format("'%d'", getDateFormat(s));
}
private void close() {
if (isNestedQuery()) {
return;
}
// Need de-duping at the end so that correct results are fetched
if (queryClauses.size() > 2) {
// QueryClauses should've something more that just g.V() (hence 2)
add(GremlinClause.DEDUP);
// Range and limit must be present after the de-duping construct
moveToLast(GremlinClause.RANGE);
moveToLast(GremlinClause.LIMIT);
}
if (!queryMetadata.hasLimitOffset()) {
addDefaultLimit();
}
if (queryClauses.isEmpty()) {
queryClauses.clear();
return;
}
moveToLast(GremlinClause.LIMIT);
add(GremlinClause.TO_LIST);
moveToLast(GremlinClause.INLINE_TRANSFORM_CALL);
}
private boolean isNestedQuery() {
return this.parent != null;
}
private void addSubClauses(int index, GremlinClauseList queryClauses) {
this.queryClauses.addSubClauses(index, queryClauses);
}
private void moveToLast(GremlinClause clause) {
int index = queryClauses.contains(clause);
if (-1 == index) {
return;
}
GremlinClauseValue gcv = queryClauses.remove(index);
queryClauses.add(gcv);
}
public void remove(GremlinClause clause) {
int index = queryClauses.contains(clause);
if (-1 == index) {
return;
}
queryClauses.remove(index);
}
public GremlinClauseList getQueryClauses(){
return queryClauses;
}
private void init() {
if (!isNestedQuery()) {
add(GremlinClause.G);
add(GremlinClause.V);
} else {
add(GremlinClause.NESTED_START);
}
}
private boolean introduceType(IdentifierHelper.Info ia) {
if (ia.isReferredType()) {
if (ia.getEdgeDirection() != null) {
GremlinClause gremlinClauseForEdgeLabel = ia.getEdgeDirection().equals(OUT) ? GremlinClause.OUT : GremlinClause.IN;
add(gremlinClauseForEdgeLabel, ia.getEdgeLabel());
} else {
add(GremlinClause.OUT, ia.getEdgeLabel());
}
context.registerActive(ia);
}
return ia.isReferredType();
}
private IdentifierHelper.Info createInfo(String actualTypeName) {
return IdentifierHelper.create(context, lookup, actualTypeName);
}
private void addRangeClause(String startIndex, String endIndex) {
if (queryMetadata.hasSelect()) {
add(queryClauses.size() - 1, GremlinClause.RANGE, startIndex, startIndex, endIndex, startIndex, startIndex, endIndex);
} else {
add(GremlinClause.RANGE, startIndex, startIndex, endIndex, startIndex, startIndex, endIndex);
}
}
private void addOrderByClause(IdentifierHelper.Info ia, boolean descr) {
add((!descr) ? GremlinClause.ORDER_BY : GremlinClause.ORDER_BY_DESC, ia);
}
private void addGroupByClause(String name) {
IdentifierHelper.Info ia = createInfo(name);
add(GremlinClause.GROUP_BY, ia);
}
private void add(GremlinClause clause, IdentifierHelper.Info idInfo) {
if (context != null && !context.validator.isValid(context, clause, idInfo)) {
return;
}
add(clause, getPropertyForClause(idInfo));
}
private void add(GremlinClause clause, String... args) {
queryClauses.add(new GremlinClauseValue(clause, args));
}
public void add(GremlinClauseValue gv) {
queryClauses.add(gv);
}
private void add(int idx, GremlinClause clause, String... args) {
queryClauses.add(idx, new GremlinClauseValue(clause, args));
}
private void addTrait(GremlinClause clause, IdentifierHelper.Info idInfo) {
if (context != null && !context.validator.isValid(context, clause, idInfo)) {
return;
}
add(clause, idInfo.get(), idInfo.get());
}
public GremlinClauseList clauses() {
return queryClauses;
}
public SelectClauseComposer getSelectComposer() {
return this.context.selectClauseComposer;
}
public static class GremlinClauseValue {
private final GremlinClause clause;
private final String value;
private final String[] values;
private final Object rawValue;
public GremlinClauseValue(GremlinClause clause, String property, String operator, Object rawValue, String str) {
this.clause = clause;
this.value = clause.get(property, operator, str);
this.values = new String[] {property, operator, str};
this.rawValue = rawValue;
}
public GremlinClauseValue(GremlinClause clause, String... values) {
this.clause = clause;
this.value = clause.get(values);
this.values = values;
this.rawValue = null;
}
public GremlinClause getClause() {
return clause;
}
public String getClauseWithValue() {
return value;
}
public String[] getValues() {
return values;
}
public Object getRawValue() {
return this.rawValue;
}
@Override
public String toString() {
return String.format("%s", clause);
}
}
@VisibleForTesting
static class Context {
private static final AtlasStructType UNKNOWN_TYPE = new AtlasStructType(new AtlasStructDef());
private final Lookup lookup;
private final ClauseValidator validator;
private final Map<String, String> aliasMap = new HashMap<>();
private AtlasType activeType;
private SelectClauseComposer selectClauseComposer;
private String numericTypeFormatter = "";
public Context(Lookup lookup) {
this.lookup = lookup;
this.validator = new ClauseValidator();
}
public void registerActive(String typeName) {
if (shouldRegister(typeName)) {
try {
activeType = lookup.getType(typeName);
aliasMap.put(typeName, typeName);
} catch (AtlasBaseException e) {
validator.check(e, AtlasErrorCode.INVALID_DSL_UNKNOWN_TYPE, typeName);
activeType = UNKNOWN_TYPE;
}
}
}
public void registerActive(IdentifierHelper.Info info) {
if (validator.check(StringUtils.isNotEmpty(info.getTypeName()),
AtlasErrorCode.INVALID_DSL_UNKNOWN_TYPE, info.getRaw())) {
registerActive(info.getTypeName());
} else {
activeType = UNKNOWN_TYPE;
}
}
public AtlasEntityType getActiveEntityType() {
return (activeType instanceof AtlasEntityType) ?
(AtlasEntityType) activeType :
null;
}
public String getActiveTypeName() {
return activeType.getTypeName();
}
public AtlasType getActiveType() {
return activeType;
}
public boolean shouldRegister(String typeName) {
return activeType == null ||
(activeType != null && !StringUtils.equals(getActiveTypeName(), typeName)) &&
(activeType != null && !lookup.hasAttribute(this, typeName));
}
public void registerAlias(String alias) {
addAlias(alias, getActiveTypeName());
}
public boolean hasAlias(String alias) {
return aliasMap.containsKey(alias);
}
public String getTypeNameFromAlias(String alias) {
return aliasMap.get(alias);
}
public boolean isEmpty() {
return activeType == null;
}
public SelectClauseComposer getSelectClauseComposer() {
return selectClauseComposer;
}
public void setSelectClauseComposer(SelectClauseComposer selectClauseComposer) {
this.selectClauseComposer = selectClauseComposer;
}
public void addAlias(String alias, String typeName) {
if (aliasMap.containsKey(alias)) {
check(false, AtlasErrorCode.INVALID_DSL_DUPLICATE_ALIAS, alias, getActiveTypeName());
return;
}
aliasMap.put(alias, typeName);
}
public List<String> getErrorList() {
return validator.getErrorList();
}
public boolean error(AtlasBaseException e, AtlasErrorCode ec, String t, String name) {
return validator.check(e, ec, t, name);
}
public boolean check(boolean condition, AtlasErrorCode vm, String... args) {
return validator.check(condition, vm, args);
}
public void setNumericTypeFormatter(String formatter) {
this.numericTypeFormatter = formatter;
}
public String getNumericTypeFormatter() {
return this.numericTypeFormatter;
}
}
private static class ClauseValidator {
List<String> errorList = new ArrayList<>();
public ClauseValidator() {
}
public boolean isValid(Context ctx, GremlinClause clause, IdentifierHelper.Info ia) {
switch (clause) {
case TRAIT:
case ANY_TRAIT:
case NO_TRAIT:
return check(ia.isTrait(), AtlasErrorCode.INVALID_DSL_UNKNOWN_CLASSIFICATION, ia.getRaw());
case HAS_TYPE:
TypeCategory typeCategory = ctx.getActiveType().getTypeCategory();
return check(StringUtils.isNotEmpty(ia.getTypeName()) &&
typeCategory == TypeCategory.CLASSIFICATION || typeCategory == TypeCategory.ENTITY,
AtlasErrorCode.INVALID_DSL_UNKNOWN_TYPE, ia.getRaw());
case HAS_PROPERTY:
return check(ia.isPrimitive(), AtlasErrorCode.INVALID_DSL_HAS_PROPERTY, ia.getRaw());
case ORDER_BY:
return check(ia.isPrimitive(), AtlasErrorCode.INVALID_DSL_ORDERBY, ia.getRaw());
case GROUP_BY:
return check(ia.isPrimitive(), AtlasErrorCode.INVALID_DSL_SELECT_INVALID_AGG, ia.getRaw());
default:
return (getErrorList().size() == 0);
}
}
public boolean check(Exception ex, AtlasErrorCode vm, String... args) {
String[] extraArgs = getExtraSlotArgs(args, ex.getMessage());
return check(false, vm, extraArgs);
}
public boolean check(boolean condition, AtlasErrorCode vm, String... args) {
if (!condition) {
addError(vm, args);
}
return condition;
}
public void addError(AtlasErrorCode ec, String... messages) {
errorList.add(ec.getFormattedErrorMessage(messages));
}
public List<String> getErrorList() {
return errorList;
}
public boolean isValidQualifiedName(String qualifiedName, String raw) {
return check(StringUtils.isNotEmpty(qualifiedName), AtlasErrorCode.INVALID_DSL_QUALIFIED_NAME, raw);
}
private String[] getExtraSlotArgs(String[] args, String s) {
String[] argsPlus1 = new String[args.length + 1];
System.arraycopy(args, 0, argsPlus1, 0, args.length);
argsPlus1[args.length] = s;
return argsPlus1;
}
}
}