ATLAS-2229: DSL implementation using ANTLR #2 Signed-off-by: Madhan Neethiraj <[email protected]>
Project: http://git-wip-us.apache.org/repos/asf/atlas/repo Commit: http://git-wip-us.apache.org/repos/asf/atlas/commit/ac0764be Tree: http://git-wip-us.apache.org/repos/asf/atlas/tree/ac0764be Diff: http://git-wip-us.apache.org/repos/asf/atlas/diff/ac0764be Branch: refs/heads/master Commit: ac0764beeabb3d7e7f93231c73095c867fb0ec13 Parents: 728aac6 Author: Ashutosh Mestry <[email protected]> Authored: Wed Dec 13 09:27:00 2017 -0800 Committer: Madhan Neethiraj <[email protected]> Committed: Tue Dec 19 14:40:57 2017 -0800 ---------------------------------------------------------------------- .../atlas/model/discovery/SearchParameters.java | 4 +- .../atlas/discovery/EntityDiscoveryService.java | 17 +- .../java/org/apache/atlas/query/DSLVisitor.java | 216 +++++-- .../apache/atlas/query/IdentifierHelper.java | 228 +++++++ .../java/org/apache/atlas/query/Lookup.java | 43 ++ .../org/apache/atlas/query/QueryProcessor.java | 636 +++++++++++-------- .../atlas/query/antlr4/AtlasDSLLexer.java | 2 +- .../apache/atlas/query/antlr4/AtlasDSLParser.g4 | 9 +- .../atlas/query/antlr4/AtlasDSLParser.java | 631 +++++++----------- .../query/antlr4/AtlasDSLParserBaseVisitor.java | 16 +- .../query/antlr4/AtlasDSLParserVisitor.java | 14 +- .../org/apache/atlas/query/BasicTestSetup.java | 155 +++-- .../org/apache/atlas/query/DSLQueriesTest.java | 468 +++----------- .../apache/atlas/query/QueryProcessorTest.java | 249 +++++--- .../resources/MetadataDiscoveryResource.java | 69 +- 15 files changed, 1450 insertions(+), 1307 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/atlas/blob/ac0764be/intg/src/main/java/org/apache/atlas/model/discovery/SearchParameters.java ---------------------------------------------------------------------- diff --git a/intg/src/main/java/org/apache/atlas/model/discovery/SearchParameters.java b/intg/src/main/java/org/apache/atlas/model/discovery/SearchParameters.java index b816e80..347a314 100644 --- a/intg/src/main/java/org/apache/atlas/model/discovery/SearchParameters.java +++ b/intg/src/main/java/org/apache/atlas/model/discovery/SearchParameters.java @@ -392,8 +392,8 @@ public class SearchParameters implements Serializable { GT(new String[]{">", "gt"}), LTE(new String[]{"<=", "lte"}), GTE(new String[]{">=", "gte"}), - EQ(new String[]{"eq", "="}), - NEQ(new String[]{"neq", "!="}), + EQ(new String[]{"=", "eq"}), + NEQ(new String[]{"!=", "neq"}), IN(new String[]{"in", "IN"}), LIKE(new String[]{"like", "LIKE"}), STARTS_WITH(new String[]{"startsWith", "STARTSWITH", "begins_with", "BEGINS_WITH"}), http://git-wip-us.apache.org/repos/asf/atlas/blob/ac0764be/repository/src/main/java/org/apache/atlas/discovery/EntityDiscoveryService.java ---------------------------------------------------------------------- diff --git a/repository/src/main/java/org/apache/atlas/discovery/EntityDiscoveryService.java b/repository/src/main/java/org/apache/atlas/discovery/EntityDiscoveryService.java index cbc08b8..e742496 100644 --- a/repository/src/main/java/org/apache/atlas/discovery/EntityDiscoveryService.java +++ b/repository/src/main/java/org/apache/atlas/discovery/EntityDiscoveryService.java @@ -685,7 +685,7 @@ public class EntityDiscoveryService implements AtlasDiscoveryService { throw new AtlasBaseException(DISCOVERY_QUERY_FAILED, query); } - QueryProcessor queryProcessor = new QueryProcessor(typeRegistry); + QueryProcessor queryProcessor = new QueryProcessor(typeRegistry, limit, offset); Expression validExpression = queryProcessor.validate(expression); GremlinQuery gremlinQuery = new GremlinTranslator(queryProcessor, validExpression).translate(); @@ -928,13 +928,24 @@ public class EntityDiscoveryService implements AtlasDiscoveryService { @Override public String getDslQueryUsingTypeNameClassification(String query, String typeName, String classification) { + final String whereDSLKeyword = "where"; + final String limitDSLKeyword = "limit"; + final String whereFormat = whereDSLKeyword + " %s"; + String queryStr = query == null ? "" : query; - if (org.apache.commons.lang3.StringUtils.isNoneEmpty(typeName)) { + if (StringUtils.isNotEmpty(typeName)) { + if(StringUtils.isNotEmpty(query)) { + String s = query.toLowerCase(); + if(!s.startsWith(whereDSLKeyword) && !s.startsWith(limitDSLKeyword)) { + queryStr = String.format(whereFormat, query); + } + } + queryStr = escapeTypeName(typeName) + " " + queryStr; } - if (org.apache.commons.lang3.StringUtils.isNoneEmpty(classification)) { + if (StringUtils.isNotEmpty(classification)) { // isa works with a type name only - like hive_column isa PII; it doesn't work with more complex query if (StringUtils.isEmpty(query)) { queryStr += (" isa " + classification); http://git-wip-us.apache.org/repos/asf/atlas/blob/ac0764be/repository/src/main/java/org/apache/atlas/query/DSLVisitor.java ---------------------------------------------------------------------- diff --git a/repository/src/main/java/org/apache/atlas/query/DSLVisitor.java b/repository/src/main/java/org/apache/atlas/query/DSLVisitor.java index a2c6b98..064f6dd 100644 --- a/repository/src/main/java/org/apache/atlas/query/DSLVisitor.java +++ b/repository/src/main/java/org/apache/atlas/query/DSLVisitor.java @@ -21,8 +21,20 @@ package org.apache.atlas.query; import org.apache.atlas.query.antlr4.AtlasDSLParser.*; import org.apache.atlas.query.antlr4.AtlasDSLParserBaseVisitor; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.tuple.MutablePair; +import org.apache.commons.lang3.tuple.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; public class DSLVisitor extends AtlasDSLParserBaseVisitor<String> { + private static final Logger LOG = LoggerFactory.getLogger(DSLVisitor.class); + + private static final String AND = "AND"; + private static final String OR = "OR"; + private final QueryProcessor queryProcessor; public DSLVisitor(QueryProcessor queryProcessor) { @@ -30,62 +42,50 @@ public class DSLVisitor extends AtlasDSLParserBaseVisitor<String> { } @Override - public String visitFromExpression(final FromExpressionContext ctx) { - FromSrcContext fromSrc = ctx.fromSrc(); - AliasExprContext aliasExpr = fromSrc.aliasExpr(); - - if (aliasExpr != null) { - queryProcessor.addFromAlias(aliasExpr.identifier(0).getText(), aliasExpr.identifier(1).getText()); - } else { - if (fromSrc.identifier() != null) { - queryProcessor.addFrom(fromSrc.identifier().getText()); - } else { - queryProcessor.addFrom(fromSrc.literal().getText()); - } + public String visitIsClause(IsClauseContext ctx) { + if (LOG.isDebugEnabled()) { + LOG.debug("=> DSLVisitor.visitIsClause({})", ctx); } - return super.visitFromExpression(ctx); + + queryProcessor.addFromIsA(ctx.arithE().getText(), ctx.identifier().getText()); + return super.visitIsClause(ctx); } @Override - public String visitWhereClause(WhereClauseContext ctx) { - ExprContext expr = ctx.expr(); - processExpr(expr.compE()); - - if (CollectionUtils.isNotEmpty(expr.exprRight())) { - for (ExprRightContext exprRight : expr.exprRight()) { - if (exprRight.K_AND() != null) { - // AND expression - processExpr(exprRight.compE()); - } - // OR is tricky - } + public String visitHasClause(HasClauseContext ctx) { + if (LOG.isDebugEnabled()) { + LOG.debug("=> DSLVisitor.visitHasClause({})", ctx); } - return super.visitWhereClause(ctx); - } - private void processExpr(final CompEContext compE) { - if (compE != null && compE.isClause() == null && compE.hasClause() == null && compE.isClause() == null) { - ComparisonClauseContext comparisonClause = compE.comparisonClause(); - if(comparisonClause == null) { - comparisonClause = compE.arithE().multiE().atomE().expr().compE().comparisonClause(); - } - - if (comparisonClause != null) { - String lhs = comparisonClause.arithE(0).getText(); - String op = comparisonClause.operator().getText().toUpperCase(); - String rhs = comparisonClause.arithE(1).getText(); + queryProcessor.addFromProperty(ctx.arithE().getText(), ctx.identifier().getText()); + return super.visitHasClause(ctx); + } - queryProcessor.addWhere(lhs, op, rhs); - } + @Override + public String visitLimitOffset(LimitOffsetContext ctx) { + if (LOG.isDebugEnabled()) { + LOG.debug("=> DSLVisitor.visitLimitOffset({})", ctx); } + + queryProcessor.addLimit(ctx.limitClause().NUMBER().toString(), + (ctx.offsetClause() == null ? "0" : ctx.offsetClause().NUMBER().getText())); + return super.visitLimitOffset(ctx); } @Override public String visitSelectExpr(SelectExprContext ctx) { + if (LOG.isDebugEnabled()) { + LOG.debug("=> DSLVisitor.visitSelectExpr({})", ctx); + } + if (!(ctx.getParent() instanceof GroupByExpressionContext)) { - String[] items = new String[ctx.selectExpression().size()]; + List<Pair<String, String>> items = new ArrayList<>(); for (int i = 0; i < ctx.selectExpression().size(); i++) { - items[i] = ctx.selectExpression(i).expr().getText(); + String idf = ctx.selectExpression(i).expr().getText(); + String alias = (ctx.selectExpression(i).K_AS() != null) ? + ctx.selectExpression(i).identifier().getText() : ""; + + items.add(new MutablePair<String, String>(idf, alias)); } queryProcessor.addSelect(items); @@ -94,34 +94,140 @@ public class DSLVisitor extends AtlasDSLParserBaseVisitor<String> { } @Override - public String visitLimitOffset(LimitOffsetContext ctx) { - queryProcessor.addLimit(ctx.limitClause().NUMBER().toString(), - (ctx.offsetClause() == null ? "0" : ctx.offsetClause().NUMBER().getText())); - return super.visitLimitOffset(ctx); - } - - @Override public String visitOrderByExpr(OrderByExprContext ctx) { + if (LOG.isDebugEnabled()) { + LOG.debug("=> DSLVisitor.visitOrderByExpr({})", ctx); + } + queryProcessor.addOrderBy(ctx.expr().getText(), (ctx.sortOrder() != null && ctx.sortOrder().getText().equalsIgnoreCase("desc"))); return super.visitOrderByExpr(ctx); } @Override - public String visitIsClause(IsClauseContext ctx) { - queryProcessor.addFromIsA(ctx.arithE().getText(), ctx.identifier().getText()); - return super.visitIsClause(ctx); + public String visitWhereClause(WhereClauseContext ctx) { + if (LOG.isDebugEnabled()) { + LOG.debug("=> DSLVisitor.visitWhereClause({})", ctx); + } + + + // The first expr shouldn't be processed if there are following exprs + ExprContext expr = ctx.expr(); + + processExpr(expr, queryProcessor); + return super.visitWhereClause(ctx); } @Override - public String visitHasClause(HasClauseContext ctx) { - queryProcessor.addFromProperty(ctx.arithE().getText(), ctx.identifier().getText()); - return super.visitHasClause(ctx); + public String visitFromExpression(final FromExpressionContext ctx) { + if (LOG.isDebugEnabled()) { + LOG.debug("=> DSLVisitor.visitFromExpression({})", ctx); + } + + FromSrcContext fromSrc = ctx.fromSrc(); + AliasExprContext aliasExpr = fromSrc.aliasExpr(); + + if (aliasExpr != null) { + queryProcessor.addFromAlias(aliasExpr.identifier(0).getText(), aliasExpr.identifier(1).getText()); + } else { + if (fromSrc.identifier() != null) { + queryProcessor.addFrom(fromSrc.identifier().getText()); + } else { + queryProcessor.addFrom(fromSrc.literal().getText()); + } + } + return super.visitFromExpression(ctx); } @Override public String visitGroupByExpression(GroupByExpressionContext ctx) { + if (LOG.isDebugEnabled()) { + LOG.debug("=> DSLVisitor.visitGroupByExpression({})", ctx); + } + String s = ctx.selectExpr().getText(); queryProcessor.addGroupBy(s); return super.visitGroupByExpression(ctx); } + + private void processExpr(final ExprContext expr, QueryProcessor queryProcessor) { + if (CollectionUtils.isNotEmpty(expr.exprRight())) { + processExprRight(expr, queryProcessor); + } else { + processExpr(expr.compE(), queryProcessor); + } + } + + private void processExprRight(final ExprContext expr, QueryProcessor queryProcessor) { + QueryProcessor nestedProcessor = queryProcessor.createNestedProcessor(); + + List<String> nestedQueries = new ArrayList<>(); + String prev = null; + + // Process first expression then proceed with the others + // expr -> compE exprRight* + processExpr(expr.compE(), nestedProcessor); + nestedQueries.add(nestedProcessor.getText()); + + for (ExprRightContext exprRight : expr.exprRight()) { + nestedProcessor = queryProcessor.createNestedProcessor(); + + // AND expression + if (exprRight.K_AND() != null) { + if (prev == null) prev = AND; + if (OR.equalsIgnoreCase(prev)) { + // Change of context + QueryProcessor orClause = nestedProcessor.createNestedProcessor(); + orClause.addOrClauses(nestedQueries); + nestedQueries.clear(); + nestedQueries.add(orClause.getText()); + } + prev = AND; + } + // OR expression + if (exprRight.K_OR() != null) { + if (prev == null) prev = OR; + if (AND.equalsIgnoreCase(prev)) { + // Change of context + QueryProcessor andClause = nestedProcessor.createNestedProcessor(); + andClause.addAndClauses(nestedQueries); + nestedQueries.clear(); + nestedQueries.add(andClause.getText()); + } + prev = OR; + } + processExpr(exprRight.compE(), nestedProcessor); + nestedQueries.add(nestedProcessor.getText()); + } + if (AND.equalsIgnoreCase(prev)) { + queryProcessor.addAndClauses(nestedQueries); + } + if (OR.equalsIgnoreCase(prev)) { + queryProcessor.addOrClauses(nestedQueries); + } + } + + private void processExpr(final CompEContext compE, final QueryProcessor queryProcessor) { + if (compE != null && compE.isClause() == null && compE.hasClause() == null && compE.isClause() == null) { + ComparisonClauseContext comparisonClause = compE.comparisonClause(); + + // The nested expression might have ANDs/ORs + if(comparisonClause == null) { + ExprContext exprContext = compE.arithE().multiE().atomE().expr(); + // Only extract comparison clause if there are no nested exprRight clauses + if (CollectionUtils.isEmpty(exprContext.exprRight())) { + comparisonClause = exprContext.compE().comparisonClause(); + } + } + + if (comparisonClause != null) { + String lhs = comparisonClause.arithE(0).getText(); + String op = comparisonClause.operator().getText().toUpperCase(); + String rhs = comparisonClause.arithE(1).getText(); + + queryProcessor.addWhere(lhs, op, rhs); + } else { + processExpr(compE.arithE().multiE().atomE().expr(), queryProcessor); + } + } + } } http://git-wip-us.apache.org/repos/asf/atlas/blob/ac0764be/repository/src/main/java/org/apache/atlas/query/IdentifierHelper.java ---------------------------------------------------------------------- diff --git a/repository/src/main/java/org/apache/atlas/query/IdentifierHelper.java b/repository/src/main/java/org/apache/atlas/query/IdentifierHelper.java new file mode 100644 index 0000000..6bbadc4 --- /dev/null +++ b/repository/src/main/java/org/apache/atlas/query/IdentifierHelper.java @@ -0,0 +1,228 @@ +/** + * 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.query; + +import org.apache.commons.lang.StringUtils; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class IdentifierHelper { + + public static String stripQuotes(String quotedIdentifier) { + String ret = quotedIdentifier; + + if (isQuoted(quotedIdentifier)) { + ret = quotedIdentifier.substring(1, quotedIdentifier.length() - 1); + } + + return ret; + } + + public static Advice create(QueryProcessor.Context context, + org.apache.atlas.query.Lookup lookup, + String identifier) { + Advice ia = new Advice(identifier); + ia.update(lookup, context); + return ia; + } + + private static String extract(Pattern p, String s) { + Matcher m = p.matcher(s); + return m.find() ? m.group(1) : s; + } + + public static String getQualifiedName(org.apache.atlas.query.Lookup lookup, + QueryProcessor.Context context, + String name) { + return lookup.getQualifiedName(context, name); + } + + public static boolean isQuoted(String val) { + boolean ret = false; + + if (val != null && val.length() > 1) { + char first = val.charAt(0); + char last = val.charAt(val.length() - 1); + + if (first == last && (first == '\'' || first == '"' || first == '`')) { + ret = true; + } + } + + return ret; + } + + public static String removeQuotes(String rhs) { + return rhs.replace("\"", "").replace("'", ""); + } + + public static String getQuoted(String s) { + return String.format("'%s'", s); + } + + public static class Advice { + private String raw; + private String actual; + private String[] parts; + private String typeName; + private String attributeName; + private boolean isPrimitive; + private String edgeLabel; + private String edgeDirection; + private boolean introduceType; + private boolean hasSubtypes; + private String subTypes; + private boolean isTrait; + private boolean newContext; + private boolean isAttribute; + private String qualifiedName; + private boolean isDate; + + public Advice(String s) { + this.raw = removeQuotes(s); + this.actual = IdentifierHelper.stripQuotes(raw); + } + + private void update(org.apache.atlas.query.Lookup lookup, QueryProcessor.Context context) { + newContext = context.isEmpty(); + if(!newContext) { + if(context.aliasMap.containsKey(this.raw)) { + raw = context.aliasMap.get(this.raw); + } + + updateParts(); + updateTypeInfo(lookup, context); + isTrait = lookup.isTraitType(context); + updateEdgeInfo(lookup, context); + introduceType = !context.hasAlias(parts[0]); + updateSubTypes(lookup, context); + } + } + + private void updateSubTypes(org.apache.atlas.query.Lookup lookup, QueryProcessor.Context context) { + if(isTrait) { + return; + } + + hasSubtypes = lookup.doesTypeHaveSubTypes(context); + if(hasSubtypes) { + subTypes = lookup.getTypeAndSubTypes(context); + } + } + + private void updateEdgeInfo(org.apache.atlas.query.Lookup lookup, QueryProcessor.Context context) { + if(isPrimitive == false && isTrait == false) { + edgeLabel = lookup.getRelationshipEdgeLabel(context, attributeName); + edgeDirection = "OUT"; + typeName = lookup.getTypeFromEdge(context, attributeName); + } + } + + private void updateTypeInfo(org.apache.atlas.query.Lookup lookup, QueryProcessor.Context context) { + if(parts.length == 1) { + typeName = context.getActiveTypeName(); + attributeName = parts[0]; + isAttribute = lookup.hasAttribute(context, typeName); + qualifiedName = lookup.getQualifiedName(context, attributeName); + isPrimitive = lookup.isPrimitive(context, attributeName); + + setIsDate(lookup, context); + } + + if(parts.length == 2) { + if(context.hasAlias(parts[0])) { + typeName = context.getTypeNameFromAlias(parts[0]); + attributeName = parts[1]; + isPrimitive = lookup.isPrimitive(context, attributeName); + setIsDate(lookup, context); + } + else { + isAttribute = lookup.hasAttribute(context, parts[0]); + if(isAttribute) { + attributeName = parts[0]; + isPrimitive = lookup.isPrimitive(context, attributeName); + setIsDate(lookup, context); + } else { + typeName = parts[0]; + attributeName = parts[1]; + isPrimitive = lookup.isPrimitive(context, attributeName); + setIsDate(lookup, context); + } + } + + qualifiedName = lookup.getQualifiedName(context, attributeName); + } + } + + private void setIsDate(Lookup lookup, QueryProcessor.Context context) { + if(isPrimitive) { + isDate = lookup.isDate(context, attributeName); + } + } + + private void updateParts() { + parts = StringUtils.split(raw, "."); + } + + public String getQualifiedName() { + return qualifiedName; + } + + public boolean isPrimitive() { + return isPrimitive; + } + + public String getEdgeLabel() { + return edgeLabel; + } + + public String getTypeName() { + return typeName; + } + + public boolean getIntroduceType() { + return introduceType; + } + + public boolean isTrait() { + return isTrait; + } + + public boolean hasSubtypes() { + return hasSubtypes; + } + + public String getSubTypes() { + return subTypes; + } + + public String get() { + return actual; + } + + public boolean isNewContext() { + return newContext; + } + + public boolean isDate() { + return isDate; + } + } +} http://git-wip-us.apache.org/repos/asf/atlas/blob/ac0764be/repository/src/main/java/org/apache/atlas/query/Lookup.java ---------------------------------------------------------------------- diff --git a/repository/src/main/java/org/apache/atlas/query/Lookup.java b/repository/src/main/java/org/apache/atlas/query/Lookup.java new file mode 100644 index 0000000..a64b688 --- /dev/null +++ b/repository/src/main/java/org/apache/atlas/query/Lookup.java @@ -0,0 +1,43 @@ +/** + * 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.query; + +import org.apache.atlas.type.AtlasType; + +public interface Lookup { + AtlasType getType(String typeName); + + String getQualifiedName(QueryProcessor.Context context, String name); + + boolean isPrimitive(QueryProcessor.Context context, String attributeName); + + String getRelationshipEdgeLabel(QueryProcessor.Context context, String attributeName); + + boolean hasAttribute(QueryProcessor.Context context, String typeName); + + boolean doesTypeHaveSubTypes(QueryProcessor.Context context); + + String getTypeAndSubTypes(QueryProcessor.Context context); + + boolean isTraitType(QueryProcessor.Context context); + + String getTypeFromEdge(QueryProcessor.Context context, String item); + + boolean isDate(QueryProcessor.Context context, String attributeName); +} http://git-wip-us.apache.org/repos/asf/atlas/blob/ac0764be/repository/src/main/java/org/apache/atlas/query/QueryProcessor.java ---------------------------------------------------------------------- diff --git a/repository/src/main/java/org/apache/atlas/query/QueryProcessor.java b/repository/src/main/java/org/apache/atlas/query/QueryProcessor.java index 60480a1..599f370 100644 --- a/repository/src/main/java/org/apache/atlas/query/QueryProcessor.java +++ b/repository/src/main/java/org/apache/atlas/query/QueryProcessor.java @@ -22,13 +22,10 @@ import org.apache.atlas.exception.AtlasBaseException; import org.apache.atlas.model.TypeCategory; import org.apache.atlas.model.discovery.SearchParameters; import org.apache.atlas.query.Expressions.Expression; -import org.apache.atlas.type.AtlasArrayType; -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.atlas.type.*; import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.joda.time.DateTime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,167 +35,196 @@ import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; public class QueryProcessor { private static final Logger LOG = LoggerFactory.getLogger(QueryProcessor.class); private final int DEFAULT_QUERY_RESULT_LIMIT = 25; + private final int DEFAULT_QUERY_RESULT_OFFSET = 0; - private final Pattern SINGLE_QUOTED_IDENTIFIER = Pattern.compile("'(\\w[\\w\\d\\.\\s]*)'"); - private final Pattern DOUBLE_QUOTED_IDENTIFIER = Pattern.compile("\"(\\w[\\w\\d\\.\\s]*)\""); - private final Pattern BACKTICK_QUOTED_IDENTIFIER = Pattern.compile("`(\\w[\\w\\d\\.\\s]*)`"); - - private final List<String> errorList = new ArrayList<>(); - private final GremlinClauseList queryClauses = new GremlinClauseList(errorList); + private final boolean isNestedQuery; + private final List<String> errorList = new ArrayList<>(); + private final GremlinClauseList queryClauses = new GremlinClauseList(); + private int providedLimit = DEFAULT_QUERY_RESULT_LIMIT; + private int providedOffset = DEFAULT_QUERY_RESULT_OFFSET; private int currentStep; - private final TypeRegistryLookup registryLookup; + private final org.apache.atlas.query.Lookup lookup; + private Context context; @Inject public QueryProcessor(AtlasTypeRegistry typeRegistry) { - registryLookup = new TypeRegistryLookup(errorList, typeRegistry); + this.isNestedQuery = false; + lookup = new Lookup(errorList, typeRegistry); + context = new Context(errorList, lookup); init(); } + public QueryProcessor(AtlasTypeRegistry typeRegistry, int limit, int offset) { + this(typeRegistry); + this.providedLimit = limit; + this.providedOffset = offset < 0 ? DEFAULT_QUERY_RESULT_OFFSET : offset; + } + @VisibleForTesting - public QueryProcessor(TypeRegistryLookup lookup) { - registryLookup = lookup; + QueryProcessor(org.apache.atlas.query.Lookup lookup, Context context) { + this.isNestedQuery = false; + this.lookup = lookup; + this.context = context; init(); } - private void init() { - add(GremlinClause.G); - add(GremlinClause.V); + public QueryProcessor(org.apache.atlas.query.Lookup registryLookup, boolean isNestedQuery) { + this.isNestedQuery = isNestedQuery; + this.lookup = registryLookup; + init(); } public Expression validate(Expression expression) { return expression.isReady(); } + private void init() { + if (!isNestedQuery) { + add(GremlinClause.G); + add(GremlinClause.V); + } else { + add(GremlinClause.NESTED_START); + } + } + public void addFrom(String typeName) { if (LOG.isDebugEnabled()) { LOG.debug("addFrom(typeName={})", typeName); } - String actualTypeName = extractIdentifier(typeName); + IdentifierHelper.Advice ta = getAdvice(typeName); + if(context.shouldRegister(ta.get())) { + context.registerActive(ta.get()); - if(registryLookup.isTypeTrait(actualTypeName)) { - addTraitAndRegister(actualTypeName); - } else if (!registryLookup.hasActiveType()) { - registryLookup.registerActive(actualTypeName); - if(registryLookup.doesActiveTypeHaveSubTypes()) { - add(GremlinClause.HAS_TYPE_WITHIN, registryLookup.getActiveTypeAndSubTypes()); + IdentifierHelper.Advice ia = getAdvice(ta.get()); + if (ia.isTrait()) { + add(GremlinClause.TRAIT, ia.get()); } else { - add(GremlinClause.HAS_TYPE, actualTypeName); + if (ia.hasSubtypes()) { + add(GremlinClause.HAS_TYPE_WITHIN, ia.getSubTypes()); + } else { + add(GremlinClause.HAS_TYPE, ia.get()); + } } } else { - add(GremlinClause.OUT, registryLookup.getRelationshipEdgeLabelForActiveType(actualTypeName)); - registryLookup.registerActive(registryLookup.getTypeFromEdge(actualTypeName)); + IdentifierHelper.Advice ia = getAdvice(ta.get()); + introduceType(ia); } } - private void addTraitAndRegister(String typeName) { - if (LOG.isDebugEnabled()) { - LOG.debug("addTraitAndRegister(typeName={})", typeName); + private void introduceType(IdentifierHelper.Advice ia) { + if (!ia.isPrimitive() && ia.getIntroduceType()) { + add(GremlinClause.OUT, ia.getEdgeLabel()); + context.registerActive(ia.getTypeName()); } + } - add(GremlinClause.TRAIT, typeName); - registryLookup.registerActive(typeName); + private IdentifierHelper.Advice getAdvice(String actualTypeName) { + return IdentifierHelper.create(context, lookup, actualTypeName); } - public void addFromIsA(String typeName, String trait) { + public void addFromProperty(String typeName, String attribute) { if (LOG.isDebugEnabled()) { - LOG.debug("addFromIsA(typeName={}, trait={})", typeName, trait); + LOG.debug("addFromProperty(typeName={}, attribute={})", typeName, attribute); } - if(!registryLookup.hasActiveType()) { - addFrom(typeName); - } + addFrom(typeName); + add(GremlinClause.HAS_PROPERTY, + IdentifierHelper.getQualifiedName(lookup, context, attribute)); + } - add(GremlinClause.TRAIT, trait); + + public void addFromIsA(String typeName, String traitName) { + addFrom(typeName); + add(GremlinClause.TRAIT, traitName); } - public void addFromProperty(String typeName, String attribute) { + public void addWhere(String lhs, String operator, String rhs) { if (LOG.isDebugEnabled()) { - LOG.debug("addFromIsA(typeName={}, attribute={})", typeName, attribute); + LOG.debug("addWhere(lhs={}, operator={}, rhs={})", lhs, operator, rhs); } - if(registryLookup.isSameAsActive(typeName) == false) { - addFrom(typeName); + String currentType = context.getActiveTypeName(); + SearchParameters.Operator op = SearchParameters.Operator.fromString(operator); + IdentifierHelper.Advice org = null; + IdentifierHelper.Advice lhsI = getAdvice(lhs); + if(lhsI.isPrimitive() == false) { + introduceType(lhsI); + org = lhsI; + lhsI = getAdvice(lhs); } - add(GremlinClause.HAS_PROPERTY, registryLookup.getQualifiedAttributeName(attribute)); - } + if(lhsI.isDate()) { + rhs = parseDate(rhs); + } - public void addFromAlias(String typeName, String alias) { - if (LOG.isDebugEnabled()) { - LOG.debug("addFromAlias(typeName={}, alias={})", typeName, alias); + rhs = addQuotesIfNecessary(rhs); + if(op == SearchParameters.Operator.LIKE) { + add(GremlinClause.TEXT_CONTAINS, lhsI.getQualifiedName(), rhs.replace("*", ".*").replace('?', '.')); + } else if(op == SearchParameters.Operator.IN) { + add(GremlinClause.HAS_OPERATOR, lhsI.getQualifiedName(), "within", rhs); + } else { + add(GremlinClause.HAS_OPERATOR, lhsI.getQualifiedName(), op.getSymbols()[1], rhs); } - addFrom(typeName); - addAsClause(alias); + if(org != null && org.isPrimitive() == false && org.getIntroduceType()) { + add(GremlinClause.IN, org.getEdgeLabel()); + context.registerActive(currentType); + } } - public void addWhere(String lhs, String operator, String rhs) { - if (LOG.isDebugEnabled()) { - LOG.debug("addWhere(lhs={}, operator={}, rhs={})", lhs, operator, rhs); - } + private String addQuotesIfNecessary(String rhs) { + if(IdentifierHelper.isQuoted(rhs)) return rhs; + return quoted(rhs); + } - lhs = registryLookup.getQualifiedAttributeName(lhs); + private static String quoted(String rhs) { + return IdentifierHelper.getQuoted(rhs); + } - SearchParameters.Operator op = SearchParameters.Operator.fromString(operator); - switch (op) { - case LT: - add(GremlinClause.HAS_OPERATOR, lhs, "lt", rhs); - break; - case GT: - add(GremlinClause.HAS_OPERATOR, lhs, "gt", rhs); - break; - case LTE: - add(GremlinClause.HAS_OPERATOR, lhs, "lte", rhs); - break; - case GTE: - add(GremlinClause.HAS_OPERATOR, lhs, "gte", rhs); - break; - case EQ: - add(GremlinClause.HAS_OPERATOR, lhs, "eq", rhs); - break; - case NEQ: - add(GremlinClause.HAS_OPERATOR, lhs, "neq", rhs); - break; - case IN: - // TODO: Handle multiple RHS values - add(GremlinClause.HAS_OPERATOR, lhs, "within", rhs); - break; - case LIKE: - add(GremlinClause.TEXT_CONTAINS, lhs, rhs.replace("*", ".*").replace('?', '.')); - break; - } - } - - public void addSelect(String[] items) { + private String parseDate(String rhs) { + String s = IdentifierHelper.isQuoted(rhs) ? + IdentifierHelper.removeQuotes(rhs) : + rhs; + return String.format("'%d'", DateTime.parse(s).getMillis()); + } + + public void addAndClauses(List<String> clauses) { + queryClauses.add(GremlinClause.AND, StringUtils.join(clauses, ',')); + } + + public void addOrClauses(List<String> clauses) { + queryClauses.add(GremlinClause.OR, StringUtils.join(clauses, ',')); + } + + public void addSelect(List<Pair<String, String>> items) { if (LOG.isDebugEnabled()) { - LOG.debug("addSelect(items.length={})", items != null ? items.length : -1); + LOG.debug("addSelect(items.length={})", items != null ? items.size() : -1); } StringBuilder sb = new StringBuilder(); + for (int i = 0; i < items.size(); i++) { + IdentifierHelper.Advice ia = getAdvice(items.get(i).getLeft()); + if(StringUtils.isNotEmpty(items.get(i).getRight())) { + context.aliasMap.put(items.get(i).getRight(), ia.getQualifiedName()); + } - for (int i = 0; i < items.length; i++) { - String s = registryLookup.getQualifiedAttributeName(items[i]); - - if (items[i].contains(".") || registryLookup.isAttributePrimitiveTypeForActiveType(items[i])) { - sb.append(String.format("'%s'", s)); - - if (i != items.length - 1) { - sb.append(", "); - } - } else { - add(GremlinClause.OUT, registryLookup.getRelationshipEdgeLabelForActiveType(items[i])); + if(!ia.isPrimitive() && ia.getIntroduceType()) { + add(GremlinClause.OUT, ia.getEdgeLabel()); add(GremlinClause.AS, getCurrentStep()); addSelectClause(getCurrentStep()); incrementCurrentStep(); + } else { + sb.append(quoted(ia.getQualifiedName())); + } + + if (i != items.size() - 1) { + sb.append(","); } } @@ -207,62 +233,44 @@ public class QueryProcessor { } } - public void addLimit(String limit, String offset) { + private void addSelectClause(String s) { if (LOG.isDebugEnabled()) { - LOG.debug("addLimit(limit={}, offset={})", limit, offset); + LOG.debug("addSelectClause(s={})", s); } - add(GremlinClause.ORDER); - - if (offset.equalsIgnoreCase("0")) { - add(GremlinClause.LIMIT, limit); - } else { - addRangeClause(offset, limit); - } + add(GremlinClause.SELECT, s); } - public void addGroupBy(String item) { - if (LOG.isDebugEnabled()) { - LOG.debug("addGroupBy(item={})", item); - } - - add(GremlinClause.GROUP); - addByClause(item, false); + private String getCurrentStep() { + return String.format("s%d", currentStep); } - private void addRangeClause(String startIndex, String endIndex) { - if (LOG.isDebugEnabled()) { - LOG.debug("addRangeClause(startIndex={}, endIndex={})", startIndex, endIndex); - } - - add(GremlinClause.RANGE, startIndex, startIndex, endIndex); + private void incrementCurrentStep() { + currentStep++; } - public String getText() { - String[] items = new String[queryClauses.size()]; - - for (int i = 0; i < queryClauses.size(); i++) { - items[i] = queryClauses.getValue(i); - } - - String ret = StringUtils.join(items, "."); + public QueryProcessor createNestedProcessor() { + QueryProcessor qp = new QueryProcessor(lookup, true); + qp.context = this.context; + return qp; + } + private void addValueMapClause(String s) { if (LOG.isDebugEnabled()) { - LOG.debug("getText() => {}", ret); + LOG.debug("addValueMapClause(s={})", s); } - return ret; + add(GremlinClause.VALUE_MAP, s); } - public void close() { - if(queryClauses.hasClause(GremlinClause.LIMIT) == -1) { - add(GremlinClause.LIMIT, "" + DEFAULT_QUERY_RESULT_LIMIT); + public void addFromAlias(String typeName, String alias) { + if (LOG.isDebugEnabled()) { + LOG.debug("addFromAlias(typeName={}, alias={})", typeName, alias); } - add(GremlinClause.TO_LIST); - } - public boolean hasSelect() { - return (queryClauses.hasClause(GremlinClause.VALUE_MAP) != -1); + addFrom(typeName); + addAsClause(alias); + context.registerAlias(alias); } public void addAsClause(String stepName) { @@ -271,32 +279,28 @@ public class QueryProcessor { } add(GremlinClause.AS, stepName); - registryLookup.registerStepType(stepName); } - public void addOrderBy(String name, boolean isDesc) { - if (LOG.isDebugEnabled()) { - LOG.debug("addOrderBy(name={}, isDesc={})", name, isDesc); - } - - add(GremlinClause.ORDER); - addByClause(registryLookup.getQualifiedAttributeName(name), isDesc); + private void add(GremlinClause clause, String... args) { + queryClauses.add(new GremlinClauseValue(clause, clause.get(args))); } - private void addValueMapClause(String s) { + private void addRangeClause(String startIndex, String endIndex) { if (LOG.isDebugEnabled()) { - LOG.debug("addValueMapClause(s={})", s); + LOG.debug("addRangeClause(startIndex={}, endIndex={})", startIndex, endIndex); } - add(GremlinClause.VALUE_MAP, s); + add(GremlinClause.RANGE, startIndex, startIndex, endIndex); } - private void addSelectClause(String s) { + + public void addGroupBy(String item) { if (LOG.isDebugEnabled()) { - LOG.debug("addSelectClause(s={})", s); + LOG.debug("addGroupBy(item={})", item); } - add(GremlinClause.SELECT, s); + add(GremlinClause.GROUP); + addByClause(item, false); } private void addByClause(String name, boolean descr) { @@ -304,47 +308,81 @@ public class QueryProcessor { LOG.debug("addByClause(name={})", name, descr); } + IdentifierHelper.Advice ia = getAdvice(name); add((!descr) ? GremlinClause.BY : GremlinClause.BY_DESC, - registryLookup.getQualifiedAttributeName(name)); + ia.getQualifiedName()); } - private String getCurrentStep() { - return String.format("s%d", currentStep); - } + public void addLimit(String limit, String offset) { + if (LOG.isDebugEnabled()) { + LOG.debug("addLimit(limit={}, offset={})", limit, offset); + } - private void incrementCurrentStep() { - currentStep++; + if (offset.equalsIgnoreCase("0")) { + add(GremlinClause.LIMIT, limit); + } else { + addRangeClause(offset, limit); + } } - private void add(GremlinClause clause, String... args) { - queryClauses.add(new GremlinClauseValue(clause, clause.get(args))); + public void close() { + if (queryClauses.isEmpty()) { + queryClauses.clear(); + return; + } + + if (queryClauses.hasClause(GremlinClause.LIMIT) == -1) { + addLimit(Integer.toString(providedLimit), Integer.toString(providedOffset)); + } + + add(GremlinClause.TO_LIST); } - private String extractIdentifier(String quotedIdentifier) { - String ret; + public String getText() { + String[] items = new String[queryClauses.size()]; + + for (int i = 0; i < queryClauses.size(); i++) { + items[i] = queryClauses.getValue(i); + } + + String ret = StringUtils.join(items, "."); - if (quotedIdentifier.charAt(0) == '`') { - ret = extract(BACKTICK_QUOTED_IDENTIFIER, quotedIdentifier); - } else if (quotedIdentifier.charAt(0) == '\'') { - ret = extract(SINGLE_QUOTED_IDENTIFIER, quotedIdentifier); - } else if (quotedIdentifier.charAt(0) == '"') { - ret = extract(DOUBLE_QUOTED_IDENTIFIER, quotedIdentifier); - } else { - ret = quotedIdentifier; + if (LOG.isDebugEnabled()) { + LOG.debug("getText() => {}", ret); } return ret; } - private String extract(Pattern p, String s) { - Matcher m = p.matcher(s); - return m.find() ? m.group(1) : s; + public boolean hasSelect() { + return (queryClauses.hasClause(GremlinClause.VALUE_MAP) != -1); + } + + public void addOrderBy(String name, boolean isDesc) { + if (LOG.isDebugEnabled()) { + LOG.debug("addOrderBy(name={}, isDesc={})", name, isDesc); + } + + add(GremlinClause.ORDER); + addByClause(name, isDesc); + updateSelectClausePosition(); + } + + private void updateSelectClausePosition() { + int selectClauseIndex = queryClauses.hasClause(GremlinClause.VALUE_MAP); + if(-1 == selectClauseIndex) { + return; + } + + GremlinClauseValue gcv = queryClauses.remove(selectClauseIndex); + queryClauses.add(gcv); } private enum GremlinClause { AS("as('%s')"), BY("by('%s')"), BY_DESC("by('%s', decr)"), + DEDUP("dedup()"), G("g"), GROUP("group()"), HAS("has('%s', %s)"), @@ -354,7 +392,11 @@ public class QueryProcessor { HAS_TYPE("has('__typeName', '%s')"), HAS_TYPE_WITHIN("has('__typeName', within(%s))"), HAS_WITHIN("has('%s', within(%s))"), - IN("in()"), + IN("in('%s')"), + OR("or(%s)"), + AND("and(%s)"), + NESTED_START("__"), + NESTED_HAS_OPERATOR("has('%s', %s(%s))"), LIMIT("limit(%s)"), ORDER("order()"), OUT("out('%s')"), @@ -400,13 +442,9 @@ public class QueryProcessor { } private static class GremlinClauseList { - private final List<String> errorList; - private AtlasEntityType activeType; - private final List<GremlinClauseValue> list; - private GremlinClauseList(List<String> errorList) { - this.errorList = errorList; + private GremlinClauseList() { this.list = new LinkedList<>(); } @@ -416,7 +454,6 @@ public class QueryProcessor { public void add(GremlinClauseValue g, AtlasEntityType t) { add(g); - activeType = t; } public void add(GremlinClause clause, String... args) { @@ -427,6 +464,10 @@ public class QueryProcessor { return list.get(i).value; } + public GremlinClauseValue get(int i) { + return list.get(i); + } + public int size() { return list.size(); } @@ -439,133 +480,218 @@ public class QueryProcessor { return -1; } + + public boolean isEmpty() { + return list.size() == 0 || list.size() == 2; + } + + public void clear() { + list.clear(); + } + + public GremlinClauseValue remove(int index) { + GremlinClauseValue gcv = get(index); + list.remove(index); + return gcv; + } } @VisibleForTesting - static class TypeRegistryLookup { + static class Context { private final List<String> errorList; - private final AtlasTypeRegistry typeRegistry; + org.apache.atlas.query.Lookup lookup; + private AtlasType activeType; + Map<String, String> aliasMap = new HashMap<>(); - private AtlasEntityType activeType; - private final Map<String, AtlasEntityType> asClauseContext = new HashMap<>(); - - public TypeRegistryLookup(List<String> errorList, AtlasTypeRegistry typeRegistry) { + public Context(List<String> errorList, org.apache.atlas.query.Lookup lookup) { + this.lookup = lookup; this.errorList = errorList; - this.typeRegistry = typeRegistry; } public void registerActive(String typeName) { - activeType = typeRegistry.getEntityTypeByName(typeName); + if(shouldRegister(typeName)) { + activeType = lookup.getType(typeName); + } + + aliasMap.put(typeName, typeName); } - public boolean hasActiveType() { - return (activeType != null); + public AtlasType getActiveType() { + return activeType; } - public void registerStepType(String stepName) { - if (!asClauseContext.containsKey(stepName)) { - asClauseContext.put(stepName, activeType); - } else { - addError(String.format("Multiple steps with same name detected: %s", stepName)); - } + public AtlasEntityType getActiveEntityType() { + return (activeType instanceof AtlasEntityType) ? + (AtlasEntityType) activeType : + null; } - protected void addError(String s) { - errorList.add(s); + public String getActiveTypeName() { + return activeType.getTypeName(); } - public String getRelationshipEdgeLabelForActiveType(String item) { - return getRelationshipEdgeLabel(activeType, item); + public boolean shouldRegister(String typeName) { + return activeType == null || + (activeType != null && !StringUtils.equals(getActiveTypeName(), typeName)) && + (activeType != null && !lookup.hasAttribute(this, typeName)); } - private String getRelationshipEdgeLabel(AtlasEntityType t, String item) { - if(t == null) { - return ""; + public void registerAlias(String alias) { + if(aliasMap.containsKey(alias)) { + errorList.add(String.format("Duplicate alias found: %s for type %s already present.", alias, getActiveEntityType())); + return; } - AtlasStructType.AtlasAttribute attr = t.getAttribute(item); - return (attr != null) ? attr.getRelationshipEdgeLabel() : ""; + aliasMap.put(alias, getActiveTypeName()); } - protected boolean isAttributePrimitiveTypeForActiveType(String name) { - return isAttributePrimitiveType(activeType, name); + public boolean hasAlias(String alias) { + return aliasMap.containsKey(alias); } - private boolean isAttributePrimitiveType(AtlasEntityType t, String name) { - if (activeType == null) { - return false; + public String getTypeNameFromAlias(String alias) { + return aliasMap.get(alias); + } + + public boolean isEmpty() { + return activeType == null; + } + } + + private static class Lookup implements org.apache.atlas.query.Lookup { + private final List<String> errorList; + private final AtlasTypeRegistry typeRegistry; + + public Lookup(List<String> errorList, AtlasTypeRegistry typeRegistry) { + this.errorList = errorList; + this.typeRegistry = typeRegistry; + } + + @Override + public AtlasType getType(String typeName) { + try { + return typeRegistry.getType(typeName); + } catch (AtlasBaseException e) { + addError(e.getMessage()); } - AtlasType attrType = t.getAttributeType(name); - TypeCategory attrTypeCategory = attrType.getTypeCategory(); + return null; + } + + @Override + public String getQualifiedName(Context context, String name) { + try { + AtlasEntityType et = context.getActiveEntityType(); + if(et == null) { + return ""; + } + + return et.getQualifiedAttributeName(name); + } catch (AtlasBaseException e) { + addError(e.getMessage()); + } - return (attrTypeCategory == TypeCategory.PRIMITIVE || attrTypeCategory == TypeCategory.ENUM); + return ""; } - public boolean isTypeTrait(String name) { - return (typeRegistry.getClassificationTypeByName(name) != null); + protected void addError(String s) { + errorList.add(s); } - public String getQualifiedAttributeName(String item) { - if (item.contains(".")) { - String[] keyValue = StringUtils.split(item, "."); + @Override + public boolean isPrimitive(Context context, String attributeName) { + AtlasEntityType et = context.getActiveEntityType(); + if(et == null) { + return false; + } - if (!asClauseContext.containsKey(keyValue[0])) { - return item; - } else { - String s = getStitchedString(keyValue, 1, keyValue.length - 1); - return getQualifiedAttributeNameFromType( - asClauseContext.get(keyValue[0]), s); - } + AtlasType attr = et.getAttributeType(attributeName); + if(attr == null) { + return false; } - return getQualifiedAttributeNameFromType(activeType, item); + TypeCategory attrTypeCategory = attr.getTypeCategory(); + return (attrTypeCategory != null) && (attrTypeCategory == TypeCategory.PRIMITIVE || attrTypeCategory == TypeCategory.ENUM); } - protected String getStitchedString(String[] keyValue, int startIndex, int endIndex) { - if(startIndex == endIndex) { - return keyValue[startIndex]; + @Override + public String getRelationshipEdgeLabel(Context context, String attributeName) { + AtlasEntityType et = context.getActiveEntityType(); + if(et == null) { + return ""; } - return StringUtils.join(keyValue, ".", startIndex, endIndex); + AtlasStructType.AtlasAttribute attr = et.getAttribute(attributeName); + return (attr != null) ? attr.getRelationshipEdgeLabel() : ""; } - private String getQualifiedAttributeNameFromType(AtlasEntityType t, String item) { - try { - return (t != null) ? t.getQualifiedAttributeName(item) : item; - } catch (AtlasBaseException e) { - addError(e.getMessage()); + @Override + public boolean hasAttribute(Context context, String typeName) { + return (context.getActiveEntityType() != null) && context.getActiveEntityType().getAttribute(typeName) != null; + } + + @Override + public boolean doesTypeHaveSubTypes(Context context) { + return (context.getActiveEntityType() != null && context.getActiveEntityType().getAllSubTypes().size() > 0); + } + + @Override + public String getTypeAndSubTypes(Context context) { + String[] str = context.getActiveEntityType() != null ? + context.getActiveEntityType().getTypeAndAllSubTypes().toArray(new String[]{}) : + new String[]{}; + if(str.length == 0) { + return null; } - return item; + String[] quoted = new String[str.length]; + for (int i = 0; i < str.length; i++) { + quoted[i] = quoted(str[i]); + } + + return StringUtils.join(quoted, ","); + } + + @Override + public boolean isTraitType(Context context) { + return (context.getActiveType() != null && + context.getActiveType().getTypeCategory() == TypeCategory.CLASSIFICATION); } - public String getTypeFromEdge(String item) { - AtlasType at = activeType.getAttribute(item).getAttributeType(); + @Override + public String getTypeFromEdge(Context context, String item) { + AtlasEntityType et = context.getActiveEntityType(); + if(et == null) { + return ""; + } + + AtlasStructType.AtlasAttribute attr = et.getAttribute(item); + if(attr == null) { + return null; + } + + AtlasType at = attr.getAttributeType(); if(at.getTypeCategory() == TypeCategory.ARRAY) { AtlasArrayType arrType = ((AtlasArrayType)at); return ((AtlasBuiltInTypes.AtlasObjectIdType) arrType.getElementType()).getObjectType(); } - return activeType.getAttribute(item).getTypeName(); + return context.getActiveEntityType().getAttribute(item).getTypeName(); } - public boolean doesActiveTypeHaveSubTypes() { - return (activeType.getAllSubTypes().size() != 0); - } - - public String getActiveTypeAndSubTypes() { - Set<String> set = activeType.getTypeAndAllSubTypes(); - String[] str = set.toArray(new String[]{}); - for (int i = 0; i < str.length; i++) { - str[i] = String.format("'%s'", str[i]); + @Override + public boolean isDate(Context context, String attributeName) { + AtlasEntityType et = context.getActiveEntityType(); + if(et == null) { + return false; } - return StringUtils.join(str, ","); - } + AtlasType attr = et.getAttributeType(attributeName); + if(attr == null) { + return false; + } - public boolean isSameAsActive(String typeName) { - return (activeType != null) && activeType.getTypeName().equalsIgnoreCase(typeName); + return attr.getTypeName().equals("date"); } } } http://git-wip-us.apache.org/repos/asf/atlas/blob/ac0764be/repository/src/main/java/org/apache/atlas/query/antlr4/AtlasDSLLexer.java ---------------------------------------------------------------------- diff --git a/repository/src/main/java/org/apache/atlas/query/antlr4/AtlasDSLLexer.java b/repository/src/main/java/org/apache/atlas/query/antlr4/AtlasDSLLexer.java index 85f8d61..576e129 100644 --- a/repository/src/main/java/org/apache/atlas/query/antlr4/AtlasDSLLexer.java +++ b/repository/src/main/java/org/apache/atlas/query/antlr4/AtlasDSLLexer.java @@ -1,4 +1,4 @@ -// Generated from AtlasDSLLexer.g4 by ANTLR 4.7 +// Generated from repository/src/main/java/org/apache/atlas/query/antlr4/AtlasDSLLexer.g4 by ANTLR 4.7 package org.apache.atlas.query.antlr4; import org.antlr.v4.runtime.Lexer; import org.antlr.v4.runtime.CharStream; http://git-wip-us.apache.org/repos/asf/atlas/blob/ac0764be/repository/src/main/java/org/apache/atlas/query/antlr4/AtlasDSLParser.g4 ---------------------------------------------------------------------- diff --git a/repository/src/main/java/org/apache/atlas/query/antlr4/AtlasDSLParser.g4 b/repository/src/main/java/org/apache/atlas/query/antlr4/AtlasDSLParser.g4 index bf6b7e3..058a5c8 100644 --- a/repository/src/main/java/org/apache/atlas/query/antlr4/AtlasDSLParser.g4 +++ b/repository/src/main/java/org/apache/atlas/query/antlr4/AtlasDSLParser.g4 @@ -20,8 +20,6 @@ parser grammar AtlasDSLParser; options { tokenVocab=AtlasDSLLexer; } -// Start of rules, bottom-up (rules at the end are built using the core rules) - // Core rules identifier: ID ; @@ -98,8 +96,6 @@ selectClause: K_SELECT selectExpr ; singleQrySrc: fromClause | whereClause | fromExpression | expr ; -loopExpression: K_LOOP K_LPAREN query K_RPAREN NUMBER? (K_AS identifier)? ; - groupByExpression: K_GROUPBY K_LPAREN selectExpr K_RPAREN ; commaDelimitedQueries: singleQrySrc (K_COMMA singleQrySrc)* ; @@ -108,10 +104,7 @@ spaceDelimitedQueries: singleQrySrc singleQrySrc* ; querySrc: commaDelimitedQueries | spaceDelimitedQueries ; -query: querySrc loopExpression? - groupByExpression? +query: querySrc groupByExpression? selectClause? orderByExpr? limitOffset? ; - -queryWithPath: query (K_WITHPATH)? ;
