| /* |
| * 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 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.ArrayBuffer |
| |
| |
| import org.apache.atlas.gremlin.GremlinExpressionFactory |
| import org.apache.atlas.gremlin.optimizer.GremlinQueryOptimizer |
| import org.apache.atlas.groovy.CastExpression |
| import org.apache.atlas.groovy.ClosureExpression |
| import org.apache.atlas.groovy.LabeledExpression |
| 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.groovy.TraversalStepType |
| 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.TypeCategory |
| import org.apache.atlas.typesystem.types.IDataType |
| import org.apache.atlas.typesystem.types.TypeSystem |
| import org.joda.time.format.ISODateTimeFormat |
| import org.apache.atlas.query.Expressions.GroupByExpression |
| import org.apache.atlas.query.Expressions.MaxExpression |
| import org.apache.atlas.query.Expressions.MinExpression |
| import org.apache.atlas.query.Expressions.SumExpression |
| import org.apache.atlas.query.Expressions.CountExpression |
| |
| import org.apache.atlas.util.AtlasRepositoryConfiguration |
| import java.util.HashSet |
| |
| trait IntSequence { |
| def next: Int |
| } |
| |
| case class GremlinQuery(expr: Expression, queryStr: String, resultMaping: Map[String, (String, Int)]) { |
| |
| def hasSelectList = resultMaping != null |
| |
| def isPathExpression = expr.isInstanceOf[PathExpression] |
| |
| def isGroupBy = expr.isInstanceOf[GroupByExpression] |
| } |
| |
| |
| trait SelectExpressionHandling { |
| |
| class AliasFinder extends PartialFunction[Expression,Unit] { |
| val aliases = new HashSet[String]() |
| |
| def isDefinedAt(e: Expression) = true |
| |
| def apply(e: Expression) = e match { |
| case e@AliasExpression(_, alias) => { |
| aliases.add(alias) |
| } |
| case x => Unit |
| } |
| } |
| |
| class ReplaceAliasWithBackReference(aliases: HashSet[String]) extends PartialFunction[Expression, Expression] { |
| |
| def isDefinedAt(e: Expression) = true |
| |
| def apply(e: Expression) = e match { |
| case e@AliasExpression(child,alias) if aliases.contains(alias) => { |
| new BackReference(alias, child, None) |
| } |
| case x => x |
| } |
| } |
| |
| // Removes back references in comparison expressions that are |
| // right after an alias expression. |
| // |
| //For example: |
| // .as('x').and(select('x').has(y),...) is changed to |
| // .as('x').and(has(y),...) |
| // |
| //This allows the "has" to be extracted out of the and/or by |
| //the GremlinQueryOptimizer so the index can be used to evaluate |
| //the predicate. |
| |
| val RemoveUnneededBackReferences : PartialFunction[Expression, Expression] = { |
| |
| case filterExpr@FilterExpression(aliasExpr@AliasExpression(_,aliasName), filterChild) => { |
| val updatedChild = removeUnneededBackReferences(filterChild, aliasName) |
| val changed = !(updatedChild eq filterChild) |
| if(changed) { |
| FilterExpression(aliasExpr, updatedChild) |
| } |
| else { |
| filterExpr |
| } |
| |
| } |
| case x => x |
| } |
| def removeUnneededBackReferences(expr: Expression, outerAlias: String) : Expression = expr match { |
| case logicalExpr@LogicalExpression(logicalOp,children) => { |
| var changed : Boolean = false; |
| val updatedChildren : List[Expression] = children.map { child => |
| val updatedChild = removeUnneededBackReferences(child, outerAlias); |
| changed |= ! (updatedChild eq child); |
| updatedChild |
| } |
| if(changed) { |
| LogicalExpression(logicalOp,updatedChildren) |
| } |
| else { |
| logicalExpr |
| } |
| } |
| case comparisonExpr@ComparisonExpression(_,_,_) => { |
| var changed = false |
| val updatedLeft = removeUnneededBackReferences(comparisonExpr.left, outerAlias); |
| changed |= !( updatedLeft eq comparisonExpr.left); |
| |
| val updatedRight = removeUnneededBackReferences(comparisonExpr.right, outerAlias); |
| changed |= !(updatedRight eq comparisonExpr.right); |
| |
| if (changed) { |
| ComparisonExpression(comparisonExpr.symbol, updatedLeft, updatedRight) |
| } else { |
| comparisonExpr |
| } |
| } |
| case FieldExpression(fieldName, fieldInfo, Some(br @ BackReference(brAlias, _, _))) if outerAlias.equals(brAlias) => { |
| //Remove the back reference, since the thing it references is right in front |
| //of the comparison expression we're in |
| FieldExpression(fieldName, fieldInfo, None) |
| } |
| case x => x |
| } |
| |
| //in groupby, convert alias expressions defined in the group by child to BackReferences |
| //in the groupby list and selectList. |
| val AddBackReferencesToGroupBy : PartialFunction[Expression, Expression] = { |
| case GroupByExpression(child, groupBy, selExpr) => { |
| |
| val aliases = ArrayBuffer[AliasExpression]() |
| val finder = new AliasFinder(); |
| child.traverseUp(finder); |
| |
| val replacer = new ReplaceAliasWithBackReference(finder.aliases) |
| |
| val newGroupBy = new SelectExpression( |
| groupBy.child.transformUp(replacer), |
| groupBy.selectList.map { |
| expr => expr.transformUp(replacer) |
| |
| }, |
| groupBy.forGroupBy); |
| |
| val newSelExpr = new SelectExpression( |
| selExpr.child.transformUp(replacer), |
| selExpr.selectList.map { |
| expr => expr.transformUp(replacer) |
| }, |
| selExpr.forGroupBy); |
| |
| new GroupByExpression(child, newGroupBy, newSelExpr) |
| } |
| case x => x |
| } |
| /** |
| * To aide in gremlinQuery generation add an alias to the input of SelectExpressions |
| */ |
| class AddAliasToSelectInput extends PartialFunction[Expression, Expression] { |
| |
| private var idx = 0 |
| |
| def isDefinedAt(e: Expression) = true |
| |
| class DecorateFieldWithAlias(aliasE: AliasExpression) |
| extends PartialFunction[Expression, Expression] { |
| def isDefinedAt(e: Expression) = true |
| |
| def apply(e: Expression) = e match { |
| case fe@FieldExpression(fieldName, fInfo, None) => |
| FieldExpression(fieldName, fInfo, Some(BackReference(aliasE.alias, aliasE.child, None))) |
| case _ => e |
| } |
| } |
| |
| def apply(e: Expression) = e match { |
| case SelectExpression(aliasE@AliasExpression(_, _), selList, forGroupBy) => { |
| idx = idx + 1 |
| SelectExpression(aliasE, selList.map(_.transformUp(new DecorateFieldWithAlias(aliasE))), forGroupBy) |
| } |
| case SelectExpression(child, selList, forGroupBy) => { |
| idx = idx + 1 |
| val aliasE = AliasExpression(child, s"_src$idx") |
| SelectExpression(aliasE, selList.map(_.transformUp(new DecorateFieldWithAlias(aliasE))), forGroupBy) |
| } |
| case OrderExpression(aliasE@AliasExpression(_, _), order, asc) => { |
| OrderExpression(aliasE, order.transformUp(new DecorateFieldWithAlias(aliasE)),asc) |
| } |
| case OrderExpression(child, order, asc) => { |
| idx = idx + 1 |
| val aliasE = AliasExpression(child, s"_src$idx") |
| OrderExpression(aliasE, order.transformUp(new DecorateFieldWithAlias(aliasE)),asc) |
| } |
| case _ => e |
| } |
| } |
| |
| def getSelectExpressionSrc(e: Expression): List[String] = { |
| val l = ArrayBuffer[String]() |
| e.traverseUp { |
| case BackReference(alias, _, _) => l += alias |
| case ClassExpression(clsName) => l += clsName |
| } |
| l.toSet.toList |
| } |
| |
| def validateSelectExprHaveOneSrc: PartialFunction[Expression, Unit] = { |
| case SelectExpression(_, selList, forGroupBy) => { |
| selList.foreach { se => |
| val srcs = getSelectExpressionSrc(se) |
| if (srcs.size > 1) { |
| throw new GremlinTranslationException(se, "Only one src allowed in a Select Expression") |
| } |
| } |
| } |
| } |
| |
| def groupSelectExpressionsBySrc(sel: SelectExpression): mutable.LinkedHashMap[String, List[Expression]] = { |
| val m = mutable.LinkedHashMap[String, List[Expression]]() |
| sel.selectListWithAlias.foreach { se => |
| val l = getSelectExpressionSrc(se.child) |
| if (!m.contains(l(0))) { |
| m(l(0)) = List() |
| } |
| m(l(0)) = m(l(0)) :+ se.child |
| } |
| m |
| } |
| |
| /** |
| * For each Output Column in the SelectExpression compute the ArrayList(Src) this maps to and the position within |
| * this list. |
| * |
| * @param sel |
| * @return |
| */ |
| def buildResultMapping(sel: SelectExpression): Map[String, (String, Int)] = { |
| val srcToExprs = groupSelectExpressionsBySrc(sel) |
| val m = new mutable.HashMap[String, (String, Int)] |
| sel.selectListWithAlias.foreach { se => |
| val src = getSelectExpressionSrc(se.child)(0) |
| val srcExprs = srcToExprs(src) |
| var idx = srcExprs.indexOf(se.child) |
| m(se.alias) = (src, idx) |
| } |
| m.toMap |
| } |
| } |
| |
| class GremlinTranslationException(expr: Expression, reason: String) extends |
| ExpressionException(expr, s"Unsupported Gremlin translation: $reason") |
| |
| class GremlinTranslator(expr: Expression, |
| gPersistenceBehavior: GraphPersistenceStrategies) |
| extends SelectExpressionHandling { |
| |
| val preStatements = ArrayBuffer[GroovyExpression]() |
| val postStatements = ArrayBuffer[GroovyExpression]() |
| |
| val wrapAndRule: PartialFunction[Expression, Expression] = { |
| case f: FilterExpression if ((!f.condExpr.isInstanceOf[LogicalExpression]) && |
| (f.condExpr.isInstanceOf[isTraitLeafExpression] || !f.namedExpressions.isEmpty)) => |
| FilterExpression(f.child, new LogicalExpression("and", List(f.condExpr))) |
| } |
| |
| val validateComparisonForm: PartialFunction[Expression, Unit] = { |
| case c@ComparisonExpression(op, left, right) => |
| if (!left.isInstanceOf[FieldExpression]) { |
| throw new GremlinTranslationException(c, s"lhs of comparison is not a field") |
| } |
| if (!right.isInstanceOf[Literal[_]] && !right.isInstanceOf[ListLiteral[_]]) { |
| throw new GremlinTranslationException(c, |
| s"rhs of comparison is not a literal") |
| } |
| |
| if(right.isInstanceOf[ListLiteral[_]] && (!op.equals("=") && !op.equals("!="))) { |
| throw new GremlinTranslationException(c, |
| s"operation not supported with list literal") |
| } |
| () |
| } |
| |
| val counter = new IntSequence { |
| var i: Int = -1; |
| |
| def next: Int = { |
| i += 1; i |
| } |
| } |
| |
| def addAliasToLoopInput(c: IntSequence = counter): PartialFunction[Expression, Expression] = { |
| case l@LoopExpression(aliasE@AliasExpression(_, _), _, _) => l |
| case l@LoopExpression(inputExpr, loopExpr, t) => { |
| val aliasE = AliasExpression(inputExpr, s"_loop${c.next}") |
| LoopExpression(aliasE, loopExpr, t) |
| } |
| } |
| |
| def instanceClauseToTop(topE : Expression) : PartialFunction[Expression, Expression] = { |
| case le : LogicalExpression if (le fastEquals topE) => { |
| le.instance() |
| } |
| case ce : ComparisonExpression if (ce fastEquals topE) => { |
| ce.instance() |
| } |
| case he : hasFieldUnaryExpression if (he fastEquals topE) => { |
| he.instance() |
| } |
| } |
| |
| def traitClauseWithInstanceForTop(topE : Expression) : PartialFunction[Expression, Expression] = { |
| // This topE check prevented the comparison of trait expression when it is a child. Like trait as t limit 2 |
| case te : TraitExpression => { |
| val theTrait = te.as("theTrait") |
| val theInstance = theTrait.traitInstance().as("theInstance") |
| val outE = |
| theInstance.select(id("theTrait").as("traitDetails"), |
| id("theInstance").as("instanceInfo")) |
| QueryProcessor.validate(outE) |
| } |
| } |
| |
| def typeTestExpression(parent: GroovyExpression, typeName : String) : GroovyExpression = { |
| val stats = GremlinExpressionFactory.INSTANCE.generateTypeTestExpression(gPersistenceBehavior, parent, typeName, counter) |
| |
| preStatements ++= stats.init |
| stats.last |
| |
| } |
| |
| val QUOTE = "\""; |
| |
| private def cleanStringLiteral(l : Literal[_]) : String = { |
| return l.toString.stripPrefix(QUOTE).stripSuffix(QUOTE); |
| } |
| |
| |
| private def genQuery(parent: GroovyExpression, expr: Expression, inClosure: Boolean): GroovyExpression = expr match { |
| case ClassExpression(clsName) => typeTestExpression(parent, clsName) |
| case TraitExpression(clsName) => typeTestExpression(parent, clsName) |
| case fe@FieldExpression(fieldName, fInfo, child) |
| if fe.dataType.getTypeCategory == TypeCategory.PRIMITIVE || fe.dataType.getTypeCategory == TypeCategory.ARRAY => { |
| val fN = gPersistenceBehavior.fieldNameInVertex(fInfo.dataType, fInfo.attrInfo) |
| val childExpr = translateOptChild(parent, child, inClosure); |
| return GremlinExpressionFactory.INSTANCE.generateFieldExpression(childExpr, fInfo, fN, inClosure); |
| } |
| case fe@FieldExpression(fieldName, fInfo, child) |
| if fe.dataType.getTypeCategory == TypeCategory.CLASS || fe.dataType.getTypeCategory == TypeCategory.STRUCT => { |
| val childExpr = translateOptChild(parent, child, inClosure); |
| val direction = if (fInfo.isReverse) AtlasEdgeDirection.IN else AtlasEdgeDirection.OUT |
| val edgeLbl = gPersistenceBehavior.edgeLabel(fInfo) |
| return GremlinExpressionFactory.INSTANCE.generateAdjacentVerticesExpression(childExpr, direction, edgeLbl) |
| |
| } |
| case fe@FieldExpression(fieldName, fInfo, child) if fInfo.traitName != null => { |
| val childExpr = translateOptChild(parent, child, inClosure); |
| val direction = gPersistenceBehavior.instanceToTraitEdgeDirection |
| val edgeLbl = gPersistenceBehavior.edgeLabel(fInfo) |
| return GremlinExpressionFactory.INSTANCE.generateAdjacentVerticesExpression(childExpr, direction, edgeLbl) |
| |
| } |
| case c@ComparisonExpression(symb, f@FieldExpression(fieldName, fInfo, ch), l) => { |
| val qualifiedPropertyName = s"${gPersistenceBehavior.fieldNameInVertex(fInfo.dataType, fInfo.attrInfo)}" |
| |
| val childExpr = translateOptChild(parent, ch, inClosure) |
| val persistentExprValue : GroovyExpression = if(l.isInstanceOf[Literal[_]]) { |
| translateLiteralValue(fInfo.attrInfo.dataType, l.asInstanceOf[Literal[_]]); |
| } |
| else { |
| genQuery(null, l, inClosure); |
| } |
| |
| return GremlinExpressionFactory.INSTANCE.generateHasExpression(gPersistenceBehavior, childExpr, qualifiedPropertyName, c.symbol, persistentExprValue, fInfo); |
| } |
| case fil@FilterExpression(child, condExpr) => { |
| var newParent = genQuery(parent, child, inClosure); |
| val alias = "a" + counter.next; |
| newParent = GremlinExpressionFactory.INSTANCE.generateAliasExpression(newParent, alias); |
| val translated = genQuery(newParent, condExpr, inClosure); |
| //we want the query to return instances of the class whose instances we are filtering out |
| //The act of filtering may traverse edges and have other side effects. |
| GremlinExpressionFactory.INSTANCE.generateBackReferenceExpression(translated, false, alias); |
| } |
| case l@LogicalExpression(symb, children) => { |
| val translatedChildren : java.util.List[GroovyExpression] = translateList(children, false); |
| return GremlinExpressionFactory.INSTANCE.generateLogicalExpression(parent, symb, translatedChildren); |
| } |
| case sel@SelectExpression(child, selList, forGroupBy) => { |
| val m = groupSelectExpressionsBySrc(sel) |
| var srcNamesList: java.util.List[LiteralExpression] = new ArrayList() |
| var srcExprsList: List[java.util.List[GroovyExpression]] = List() |
| val it = m.iterator |
| |
| while (it.hasNext) { |
| val (src, selExprs) = it.next |
| srcNamesList.add(new LiteralExpression(src)); |
| val translatedSelExprs : java.util.List[GroovyExpression] = translateList(selExprs, true); |
| srcExprsList = srcExprsList :+ translatedSelExprs |
| } |
| val srcExprsStringList : java.util.List[GroovyExpression] = new ArrayList(); |
| srcExprsList.foreach { it => |
| srcExprsStringList.add(new ListExpression(it)); |
| } |
| |
| val childExpr = genQuery(parent, child, inClosure) |
| return GremlinExpressionFactory.INSTANCE.generateSelectExpression(childExpr, srcNamesList, srcExprsStringList); |
| |
| } |
| case loop@LoopExpression(input, loopExpr, t) => { |
| |
| val times : Integer = if(t.isDefined) { |
| t.get.rawValue.asInstanceOf[Integer] |
| } |
| else { |
| null.asInstanceOf[Integer] |
| } |
| val alias = input.asInstanceOf[AliasExpression].alias; |
| val inputQry = genQuery(parent, input, inClosure) |
| val translatedLoopExpr = genQuery(GremlinExpressionFactory.INSTANCE.getLoopExpressionParent(inputQry), loopExpr, inClosure); |
| return GremlinExpressionFactory.INSTANCE.generateLoopExpression(inputQry, gPersistenceBehavior, input.dataType, translatedLoopExpr, alias, times); |
| } |
| case BackReference(alias, _, _) => { |
| |
| return GremlinExpressionFactory.INSTANCE.generateBackReferenceExpression(parent, inClosure, alias); |
| } |
| case AliasExpression(child, alias) => { |
| var childExpr = genQuery(parent, child, inClosure); |
| return GremlinExpressionFactory.INSTANCE.generateAliasExpression(childExpr, alias); |
| } |
| case isTraitLeafExpression(traitName, Some(clsExp)) => { |
| val label = gPersistenceBehavior.traitLabel(clsExp.dataType, traitName); |
| 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 hasFieldLeafExpression(fieldName, clsExp) => clsExp match { |
| case None => GremlinExpressionFactory.INSTANCE.generateUnaryHasExpression(parent, fieldName) |
| case Some(x) => { |
| val fi = TypeUtils.resolveReference(clsExp.get.dataType, fieldName); |
| if(! fi.isDefined) { |
| return GremlinExpressionFactory.INSTANCE.generateUnaryHasExpression(parent, fieldName); |
| } |
| else { |
| val fName = gPersistenceBehavior.fieldNameInVertex(fi.get.dataType, fi.get.attrInfo) |
| return GremlinExpressionFactory.INSTANCE.generateUnaryHasExpression(parent, fName); |
| } |
| } |
| } |
| case hasFieldUnaryExpression(fieldName, child) => |
| val childExpr = genQuery(parent, child, inClosure); |
| return GremlinExpressionFactory.INSTANCE.generateUnaryHasExpression(childExpr, fieldName); |
| case ArithmeticExpression(symb, left, right) => { |
| val leftExpr = genQuery(parent, left, inClosure); |
| val rightExpr = genQuery(parent, right, inClosure); |
| 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[_] => { |
| //Here, we are creating a Groovy list literal expression ([value1, value2, value3]). Because |
| //of this, any gremlin query expressions within the list must start with an anonymous traversal. |
| //We set 'inClosure' to true in this case to make that happen. |
| val values : java.util.List[GroovyExpression] = translateList(list.rawValue, true); |
| return new ListExpression(values); |
| } |
| case in@TraitInstanceExpression(child) => { |
| val childExpr = genQuery(parent, child, inClosure); |
| val direction = gPersistenceBehavior.traitToInstanceEdgeDirection; |
| return GremlinExpressionFactory.INSTANCE.generateAdjacentVerticesExpression(childExpr, direction); |
| } |
| case in@InstanceExpression(child) => { |
| return genQuery(parent, child, inClosure); |
| } |
| case pe@PathExpression(child) => { |
| val childExpr = genQuery(parent, child, inClosure) |
| return GremlinExpressionFactory.INSTANCE.generatePathExpression(childExpr); |
| } |
| case order@OrderExpression(child, odr, asc) => { |
| var orderExpression = odr |
| if(odr.isInstanceOf[BackReference]) { |
| orderExpression = odr.asInstanceOf[BackReference].reference |
| } |
| else if (odr.isInstanceOf[AliasExpression]) { |
| orderExpression = odr.asInstanceOf[AliasExpression].child |
| } |
| |
| val childExpr = genQuery(parent, child, inClosure); |
| 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, true)); |
| } |
| return GremlinExpressionFactory.INSTANCE.generateOrderByExpression(childExpr, translatedParents,asc); |
| |
| } |
| case limitOffset@LimitExpression(child, limit, offset) => { |
| val childExpr = genQuery(parent, child, inClosure); |
| val totalResultRows = limit.value + offset.value; |
| return GremlinExpressionFactory.INSTANCE.generateRangeExpression(childExpr, offset.value, totalResultRows); |
| } |
| case count@CountExpression() => { |
| val listExpr = GremlinExpressionFactory.INSTANCE.getClosureArgumentValue(); |
| GremlinExpressionFactory.INSTANCE.generateCountExpression(listExpr); |
| } |
| case max@MaxExpression(child) => { |
| //use "it" as the parent since the child will become |
| //part of a closure. Its value will be whatever vertex |
| //we are looking at in the collection. |
| val childExprParent = null; |
| val childExpr = genQuery(childExprParent, child, true); |
| val listExpr = GremlinExpressionFactory.INSTANCE.getClosureArgumentValue(); |
| GremlinExpressionFactory.INSTANCE.generateMaxExpression(listExpr, childExpr); |
| } |
| case min@MinExpression(child) => { |
| //use "it" as the parent since the child will become |
| //part of a closure. Its value will be whatever vertex |
| //we are looking at in the collection. |
| val childExprParent = null; |
| val childExpr = genQuery(childExprParent, child, true); |
| val listExpr = GremlinExpressionFactory.INSTANCE.getClosureArgumentValue(); |
| GremlinExpressionFactory.INSTANCE.generateMinExpression(listExpr, childExpr); |
| } |
| case sum@SumExpression(child) => { |
| //use "it" as the parent since the child will become |
| //part of a closure. Its value will be whatever vertex |
| //we are looking at in the collection. |
| val childExprParent = null; |
| val childExpr = genQuery(childExprParent, child, true); |
| val listExpr = GremlinExpressionFactory.INSTANCE.getClosureArgumentValue(); |
| GremlinExpressionFactory.INSTANCE.generateSumExpression(listExpr, childExpr); |
| } |
| case groupByExpr@GroupByExpression(child, groupBy, selExpr) => { |
| //remove aliases |
| val groupByExprListToTranslate = (groupBy.asInstanceOf[SelectExpression]).selectListWithAlias.map { |
| x => x.child; |
| } |
| val grpByExprsList = translateList(groupByExprListToTranslate, true); |
| val groupByValue = new ListExpression(grpByExprsList); |
| |
| //reduction only aggregate methods are supported here as of now.(Max, Min, Count) |
| //remove aliases |
| val srcExprListToTranslate = selExpr.selectListWithAlias.map { |
| x => x.child; |
| } |
| val srcExprsList = translateList(srcExprListToTranslate, true, true); |
| val srcExprsStringList = new ListExpression(srcExprsList) |
| |
| val childExpr = genQuery(parent, child, inClosure); |
| return GremlinExpressionFactory.INSTANCE.generateGroupByExpression(childExpr, groupByValue, srcExprsStringList); |
| } |
| case x => throw new GremlinTranslationException(x, "expression not yet supported") |
| } |
| |
| def translateList(exprs : List[Expressions.Expression], inClosure : Boolean, inGroupBy: Boolean = false) : java.util.List[GroovyExpression] = { |
| var parent = if (inGroupBy) { |
| GremlinExpressionFactory.INSTANCE.getGroupBySelectFieldParent(); |
| } |
| |
| else if(inClosure) { |
| null; |
| } |
| else { |
| GremlinExpressionFactory.INSTANCE.getAnonymousTraversalExpression() |
| } |
| var result : java.util.List[GroovyExpression] = new java.util.ArrayList(exprs.size); |
| exprs.foreach { it => |
| result.add(genQuery(parent, it, inClosure)); |
| } |
| return result; |
| } |
| |
| def translateOptChild(parent : GroovyExpression, child : Option[Expressions.Expression] , inClosure: Boolean) : GroovyExpression = child match { |
| |
| case Some(x) => genQuery(parent, x, inClosure) |
| case None => parent |
| } |
| |
| def translateLiteralValue(dataType: IDataType[_], l: Literal[_]): GroovyExpression = { |
| |
| |
| if (dataType == DataTypes.DATE_TYPE) { |
| try { |
| //Accepts both date, datetime formats |
| 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 { |
| return new LiteralExpression(l.rawValue); |
| } |
| } |
| |
| def genFullQuery(expr: Expression, hasSelect: Boolean): String = { |
| |
| var q : GroovyExpression = new FunctionCallExpression(TraversalStepType.START, new IdentifierExpression(TraversalStepType.SOURCE, "g"),"V"); |
| |
| val debug:Boolean = false |
| if(gPersistenceBehavior.addGraphVertexPrefix(preStatements)) { |
| q = gPersistenceBehavior.addInitialQueryCondition(q); |
| } |
| |
| q = genQuery(q, expr, false) |
| |
| q = GremlinExpressionFactory.INSTANCE.generateToListExpression(q); |
| q = gPersistenceBehavior.getGraph().addOutputTransformationPredicate(q, hasSelect, expr.isInstanceOf[PathExpression]); |
| |
| |
| if(AtlasRepositoryConfiguration.isGremlinOptimizerEnabled()) { |
| q = GremlinQueryOptimizer.getInstance().optimize(q); |
| } |
| |
| val closureExpression = new ClosureExpression(); |
| |
| closureExpression.addStatements(preStatements); |
| closureExpression.addStatement(q) |
| closureExpression.addStatements(postStatements); |
| |
| val overallExpression = new LabeledExpression("L", closureExpression); |
| |
| val qryStr = generateGremlin(overallExpression); |
| |
| if(debug) { |
| println(" query " + qryStr) |
| } |
| |
| qryStr; |
| |
| } |
| |
| def generateGremlin(expr: GroovyExpression) : String = { |
| val ctx : GroovyGenerationContext = new GroovyGenerationContext(); |
| ctx.setParametersAllowed(false); |
| expr.generateGroovy(ctx); |
| return ctx.getQuery; |
| } |
| |
| def translate(): GremlinQuery = { |
| var e1 = expr.transformUp(wrapAndRule) |
| |
| e1.traverseUp(validateComparisonForm) |
| e1 = e1.transformUp(AddBackReferencesToGroupBy) |
| e1 = e1.transformUp(new AddAliasToSelectInput) |
| e1.traverseUp(validateSelectExprHaveOneSrc) |
| e1 = e1.transformUp(addAliasToLoopInput()) |
| e1 = e1.transformUp(instanceClauseToTop(e1)) |
| e1 = e1.transformUp(traitClauseWithInstanceForTop(e1)) |
| e1 = e1.transformUp(RemoveUnneededBackReferences) |
| |
| //Following code extracts the select expressions from expression tree. |
| |
| val se = SelectExpressionHelper.extractSelectExpression(e1) |
| if (se.isDefined) { |
| val rMap = buildResultMapping(se.get) |
| GremlinQuery(e1, genFullQuery(e1, true), rMap) |
| } else { |
| GremlinQuery(e1, genFullQuery(e1, false), null) |
| } |
| |
| } |
| } |
| object SelectExpressionHelper { |
| /** |
| * This method extracts the child select expression from parent expression |
| */ |
| def extractSelectExpression(child: Expression): Option[SelectExpression] = { |
| child match { |
| case se@SelectExpression(child, selectList, false) =>{ |
| Some(se) |
| } |
| case limit@LimitExpression(child, lmt, offset) => { |
| extractSelectExpression(child) |
| } |
| case order@OrderExpression(child, odr, odrBy) => { |
| extractSelectExpression(child) |
| } |
| case path@PathExpression(child) => { |
| extractSelectExpression(child) |
| } |
| case _ => { |
| None |
| } |
| |
| } |
| } |
| } |
| /* |
| * TODO |
| * Translation Issues: |
| * 1. back references in filters. For e.g. testBackreference: 'DB as db Table where (db.name = "Reporting")' |
| * this is translated to: |
| * g.V.has("typeName","DB").as("db").in("Table.db").and(_().back("db").has("name", T.eq, "Reporting")).map().toList() |
| * But the '_().back("db") within the and is ignored, the has condition is applied on the current element. |
| * The solution is to to do predicate pushdown and apply the filter immediately on top of the referred Expression. |
| */ |
| |