
org.opencypher.v9_0.expressions.Expression.scala Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) Neo4j Sweden AB (http://neo4j.com)
*
* Licensed 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.opencypher.v9_0.expressions
import org.opencypher.v9_0.expressions.Expression.TreeAcc
import org.opencypher.v9_0.expressions.functions.Rand
import org.opencypher.v9_0.expressions.functions.RandomUUID
import org.opencypher.v9_0.util.ASTNode
import org.opencypher.v9_0.util.Foldable.SkipChildren
import org.opencypher.v9_0.util.Foldable.TraverseChildren
import org.opencypher.v9_0.util.Foldable.TraverseChildrenNewAccForSiblings
import org.opencypher.v9_0.util.Ref
import org.opencypher.v9_0.util.Rewriter
import org.opencypher.v9_0.util.bottomUp
object Expression {
sealed trait SemanticContext
object SemanticContext {
case object Simple extends SemanticContext
case object Results extends SemanticContext
}
val DefaultTypeMismatchMessageGenerator = (expected: String, existing: String) => s"expected $expected but was $existing"
final case class TreeAcc[A](data: A, list: List[Set[LogicalVariable]] = List.empty) {
def mapData(f: A => A): TreeAcc[A] = copy(data = f(data))
def inScope(variable: LogicalVariable) = list.exists(_.contains(variable))
def variablesInScope: Set[LogicalVariable] = list.toSet.flatten
def pushScope(newVariable: LogicalVariable): TreeAcc[A] = pushScope(Set(newVariable))
def pushScope(newVariables: Set[LogicalVariable]): TreeAcc[A] = copy(list = newVariables::list)
def popScope: TreeAcc[A] = copy(list = list.tail)
}
def mapExpressionHasPropertyReadDependency(mapEntityName: String, mapExpression: Expression): Boolean =
mapExpression match {
case MapExpression(items) => items.exists {
case (k, v) => v.subExpressions.exists {
case LogicalProperty(LogicalVariable(entityName), propertyKey) =>
entityName == mapEntityName && propertyKey == k
case _ => false
}
}
case _ => false
}
def hasPropertyReadDependency(entityName: String, expression: Expression, propertyKey: PropertyKeyName): Boolean =
expression.subExpressions.exists {
case LogicalProperty(LogicalVariable(name), key) =>
name == entityName && key == propertyKey
case _ =>
false
}
}
abstract class Expression extends ASTNode {
self =>
def arguments: Seq[Expression] = this.treeFold(List.empty[Expression]) {
case e: Expression if e != this =>
acc => SkipChildren(acc :+ e)
}
// Collects all sub-expressions recursively
def subExpressions: Seq[Expression] = this.treeFold(List.empty[Expression]) {
case e: Expression if e != this =>
acc => TraverseChildren(acc :+ e)
}
// All variables referenced from this expression or any of its children
// that are not introduced inside this expression
def dependencies: Set[LogicalVariable] =
this.treeFold(Expression.TreeAcc[Set[LogicalVariable]](Set.empty)) {
case scope: ScopeExpression =>
acc =>
val newAcc = acc.pushScope(scope.introducedVariables)
TraverseChildrenNewAccForSiblings(newAcc, _.popScope)
case id: LogicalVariable => acc => {
val newAcc = if (acc.inScope(id)) acc else acc.mapData(_ + id)
TraverseChildren(newAcc)
}
}.data
// All (free) occurrences of variable in this expression or any of its children
// (i.e. excluding occurrences referring to shadowing redefinitions of variable)
def occurrences(variable: LogicalVariable): Set[Ref[Variable]] =
this.treeFold(Expression.TreeAcc[Set[Ref[Variable]]](Set.empty)) {
case scope: ScopeExpression =>
acc =>
val newAcc = acc.pushScope(scope.introducedVariables)
TraverseChildrenNewAccForSiblings(newAcc, _.popScope)
case occurrence: Variable if occurrence.name == variable.name => acc => {
val newAcc = if (acc.inScope(occurrence)) acc else acc.mapData(_ + Ref(occurrence))
TraverseChildren(newAcc)
}
}.data
def copyAndReplace(variable: LogicalVariable) = new {
def by(replacement: => Expression): Expression = {
val replacedOccurences = occurrences(variable)
self.endoRewrite(bottomUp(Rewriter.lift {
case occurrence: Variable if replacedOccurences(Ref(occurrence)) => replacement
}))
}
}
// List of child expressions together with any of its dependencies introduced
// by any of its parent expressions (where this expression is the root of the tree)
def inputs: Seq[(Expression, Set[LogicalVariable])] =
this.treeFold(TreeAcc[Seq[(Expression, Set[LogicalVariable])]](Seq.empty)) {
case scope: ScopeExpression=>
acc =>
val newAcc = acc.pushScope(scope.introducedVariables)
.mapData(pairs => pairs :+ (scope -> acc.variablesInScope))
TraverseChildrenNewAccForSiblings(newAcc, _.popScope)
case expr: Expression =>
acc =>
val newAcc = acc.mapData(pairs => pairs :+ (expr -> acc.variablesInScope))
TraverseChildren(newAcc)
}.data
/**
* Return true is this expression contains an aggregating expression.
*/
def containsAggregate: Boolean = this.treeExists {
case IsAggregate(_) => true
}
/**
* Returns the first encountered aggregate expression, or None if none existed.
*/
def findAggregate:Option[Expression] = this.treeFind[Expression] {
case IsAggregate(_) => true
}
def isDeterministic: Boolean = !this.treeExists {
case f: FunctionInvocation if f.function == Rand || f.function == RandomUUID => true
case _ => false
}
}
/**
* Signifies that this expression doesn't have to be coerced if used as a predicate
*/
trait BooleanExpression extends Expression
© 2015 - 2025 Weber Informatics LLC | Privacy Policy