org.neo4j.cypher.internal.logical.builder.AbstractLogicalPlanBuilder.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of neo4j-cypher-logical-plan-builder Show documentation
Show all versions of neo4j-cypher-logical-plan-builder Show documentation
Neo4j - Cypher logical plan builder
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [https://neo4j.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package org.neo4j.cypher.internal.logical.builder
import org.neo4j.configuration.GraphDatabaseSettings
import org.neo4j.cypher.internal.ast.SubqueryCall.InTransactionsOnErrorBehaviour
import org.neo4j.cypher.internal.ast.SubqueryCall.InTransactionsOnErrorBehaviour.OnErrorFail
import org.neo4j.cypher.internal.ast.semantics.SemanticTable
import org.neo4j.cypher.internal.expressions.ASTCachedProperty
import org.neo4j.cypher.internal.expressions.Ands
import org.neo4j.cypher.internal.expressions.AndsReorderable
import org.neo4j.cypher.internal.expressions.DecimalDoubleLiteral
import org.neo4j.cypher.internal.expressions.Equals
import org.neo4j.cypher.internal.expressions.ExplicitParameter
import org.neo4j.cypher.internal.expressions.Expression
import org.neo4j.cypher.internal.expressions.FunctionInvocation
import org.neo4j.cypher.internal.expressions.FunctionName
import org.neo4j.cypher.internal.expressions.HasLabels
import org.neo4j.cypher.internal.expressions.HasLabelsOrTypes
import org.neo4j.cypher.internal.expressions.HasTypes
import org.neo4j.cypher.internal.expressions.LabelName
import org.neo4j.cypher.internal.expressions.LabelToken
import org.neo4j.cypher.internal.expressions.ListComprehension
import org.neo4j.cypher.internal.expressions.ListLiteral
import org.neo4j.cypher.internal.expressions.LogicalProperty
import org.neo4j.cypher.internal.expressions.LogicalVariable
import org.neo4j.cypher.internal.expressions.MapExpression
import org.neo4j.cypher.internal.expressions.NODE_TYPE
import org.neo4j.cypher.internal.expressions.NodePattern
import org.neo4j.cypher.internal.expressions.Parameter
import org.neo4j.cypher.internal.expressions.PropertyKeyName
import org.neo4j.cypher.internal.expressions.PropertyKeyToken
import org.neo4j.cypher.internal.expressions.RELATIONSHIP_TYPE
import org.neo4j.cypher.internal.expressions.Range
import org.neo4j.cypher.internal.expressions.RelTypeName
import org.neo4j.cypher.internal.expressions.RelationshipChain
import org.neo4j.cypher.internal.expressions.RelationshipPattern
import org.neo4j.cypher.internal.expressions.RelationshipTypeToken
import org.neo4j.cypher.internal.expressions.SemanticDirection
import org.neo4j.cypher.internal.expressions.SemanticDirection.OUTGOING
import org.neo4j.cypher.internal.expressions.ShortestPathsPatternPart
import org.neo4j.cypher.internal.expressions.SignedDecimalIntegerLiteral
import org.neo4j.cypher.internal.expressions.StringLiteral
import org.neo4j.cypher.internal.expressions.UnPositionedVariable.varFor
import org.neo4j.cypher.internal.expressions.UnsignedDecimalIntegerLiteral
import org.neo4j.cypher.internal.expressions.Variable
import org.neo4j.cypher.internal.expressions.VariableGrouping
import org.neo4j.cypher.internal.frontend.phases.ProcedureSignature
import org.neo4j.cypher.internal.frontend.phases.QualifiedName
import org.neo4j.cypher.internal.frontend.phases.ResolvedCall
import org.neo4j.cypher.internal.frontend.phases.ResolvedFunctionInvocation
import org.neo4j.cypher.internal.frontend.phases.UserFunctionSignature
import org.neo4j.cypher.internal.ir.CSVFormat
import org.neo4j.cypher.internal.ir.CreateCommand
import org.neo4j.cypher.internal.ir.CreateNode
import org.neo4j.cypher.internal.ir.CreatePattern
import org.neo4j.cypher.internal.ir.CreateRelationship
import org.neo4j.cypher.internal.ir.EagernessReason
import org.neo4j.cypher.internal.ir.PatternRelationship
import org.neo4j.cypher.internal.ir.RemoveLabelPattern
import org.neo4j.cypher.internal.ir.SetDynamicPropertyPattern
import org.neo4j.cypher.internal.ir.SetLabelPattern
import org.neo4j.cypher.internal.ir.SetMutatingPattern
import org.neo4j.cypher.internal.ir.SetNodePropertiesFromMapPattern
import org.neo4j.cypher.internal.ir.SetNodePropertiesPattern
import org.neo4j.cypher.internal.ir.SetNodePropertyPattern
import org.neo4j.cypher.internal.ir.SetPropertiesFromMapPattern
import org.neo4j.cypher.internal.ir.SetPropertiesPattern
import org.neo4j.cypher.internal.ir.SetPropertyPattern
import org.neo4j.cypher.internal.ir.SetRelationshipPropertiesFromMapPattern
import org.neo4j.cypher.internal.ir.SetRelationshipPropertiesPattern
import org.neo4j.cypher.internal.ir.SetRelationshipPropertyPattern
import org.neo4j.cypher.internal.ir.ShortestRelationshipPattern
import org.neo4j.cypher.internal.ir.SimpleMutatingPattern
import org.neo4j.cypher.internal.ir.SimplePatternLength
import org.neo4j.cypher.internal.ir.VarPatternLength
import org.neo4j.cypher.internal.label_expressions.LabelExpression.disjoinRelTypesToLabelExpression
import org.neo4j.cypher.internal.logical.builder.AbstractLogicalPlanBuilder.Predicate
import org.neo4j.cypher.internal.logical.builder.AbstractLogicalPlanBuilder.TrailParameters
import org.neo4j.cypher.internal.logical.builder.AbstractLogicalPlanBuilder.pos
import org.neo4j.cypher.internal.logical.plans.Aggregation
import org.neo4j.cypher.internal.logical.plans.AllNodesScan
import org.neo4j.cypher.internal.logical.plans.Anti
import org.neo4j.cypher.internal.logical.plans.AntiConditionalApply
import org.neo4j.cypher.internal.logical.plans.AntiSemiApply
import org.neo4j.cypher.internal.logical.plans.Apply
import org.neo4j.cypher.internal.logical.plans.Argument
import org.neo4j.cypher.internal.logical.plans.ArgumentTracker
import org.neo4j.cypher.internal.logical.plans.AssertSameNode
import org.neo4j.cypher.internal.logical.plans.AssertSameRelationship
import org.neo4j.cypher.internal.logical.plans.BFSPruningVarExpand
import org.neo4j.cypher.internal.logical.plans.BidirectionalRepeatTrail
import org.neo4j.cypher.internal.logical.plans.CacheProperties
import org.neo4j.cypher.internal.logical.plans.CartesianProduct
import org.neo4j.cypher.internal.logical.plans.Column
import org.neo4j.cypher.internal.logical.plans.ColumnOrder
import org.neo4j.cypher.internal.logical.plans.ConditionalApply
import org.neo4j.cypher.internal.logical.plans.Create
import org.neo4j.cypher.internal.logical.plans.DeleteExpression
import org.neo4j.cypher.internal.logical.plans.DeleteNode
import org.neo4j.cypher.internal.logical.plans.DeletePath
import org.neo4j.cypher.internal.logical.plans.DeleteRelationship
import org.neo4j.cypher.internal.logical.plans.DetachDeleteExpression
import org.neo4j.cypher.internal.logical.plans.DetachDeleteNode
import org.neo4j.cypher.internal.logical.plans.DetachDeletePath
import org.neo4j.cypher.internal.logical.plans.DirectedAllRelationshipsScan
import org.neo4j.cypher.internal.logical.plans.DirectedRelationshipByElementIdSeek
import org.neo4j.cypher.internal.logical.plans.DirectedRelationshipByIdSeek
import org.neo4j.cypher.internal.logical.plans.DirectedRelationshipIndexSeek
import org.neo4j.cypher.internal.logical.plans.DirectedRelationshipTypeScan
import org.neo4j.cypher.internal.logical.plans.DirectedUnionRelationshipTypesScan
import org.neo4j.cypher.internal.logical.plans.Distinct
import org.neo4j.cypher.internal.logical.plans.DoNotGetValue
import org.neo4j.cypher.internal.logical.plans.Eager
import org.neo4j.cypher.internal.logical.plans.EmptyResult
import org.neo4j.cypher.internal.logical.plans.ErrorPlan
import org.neo4j.cypher.internal.logical.plans.ExhaustiveLimit
import org.neo4j.cypher.internal.logical.plans.Expand
import org.neo4j.cypher.internal.logical.plans.Expand.ExpandAll
import org.neo4j.cypher.internal.logical.plans.Expand.ExpandInto
import org.neo4j.cypher.internal.logical.plans.Expand.ExpansionMode
import org.neo4j.cypher.internal.logical.plans.Expand.VariablePredicate
import org.neo4j.cypher.internal.logical.plans.FindShortestPaths
import org.neo4j.cypher.internal.logical.plans.FindShortestPaths.DisallowSameNode
import org.neo4j.cypher.internal.logical.plans.FindShortestPaths.SameNodeMode
import org.neo4j.cypher.internal.logical.plans.Foreach
import org.neo4j.cypher.internal.logical.plans.ForeachApply
import org.neo4j.cypher.internal.logical.plans.GetValueFromIndexBehavior
import org.neo4j.cypher.internal.logical.plans.IndexOrder
import org.neo4j.cypher.internal.logical.plans.IndexOrderNone
import org.neo4j.cypher.internal.logical.plans.IndexSeek
import org.neo4j.cypher.internal.logical.plans.IndexedProperty
import org.neo4j.cypher.internal.logical.plans.InjectCompilationError
import org.neo4j.cypher.internal.logical.plans.Input
import org.neo4j.cypher.internal.logical.plans.IntersectionNodeByLabelsScan
import org.neo4j.cypher.internal.logical.plans.LeftOuterHashJoin
import org.neo4j.cypher.internal.logical.plans.LetAntiSemiApply
import org.neo4j.cypher.internal.logical.plans.LetSelectOrAntiSemiApply
import org.neo4j.cypher.internal.logical.plans.LetSelectOrSemiApply
import org.neo4j.cypher.internal.logical.plans.LetSemiApply
import org.neo4j.cypher.internal.logical.plans.Limit
import org.neo4j.cypher.internal.logical.plans.LoadCSV
import org.neo4j.cypher.internal.logical.plans.LogicalPlan
import org.neo4j.cypher.internal.logical.plans.ManySeekableArgs
import org.neo4j.cypher.internal.logical.plans.Merge
import org.neo4j.cypher.internal.logical.plans.MultiNodeIndexSeek
import org.neo4j.cypher.internal.logical.plans.NFA
import org.neo4j.cypher.internal.logical.plans.NestedPlanCollectExpression
import org.neo4j.cypher.internal.logical.plans.NestedPlanExistsExpression
import org.neo4j.cypher.internal.logical.plans.NestedPlanGetByNameExpression
import org.neo4j.cypher.internal.logical.plans.NodeByElementIdSeek
import org.neo4j.cypher.internal.logical.plans.NodeByIdSeek
import org.neo4j.cypher.internal.logical.plans.NodeByLabelScan
import org.neo4j.cypher.internal.logical.plans.NodeCountFromCountStore
import org.neo4j.cypher.internal.logical.plans.NodeHashJoin
import org.neo4j.cypher.internal.logical.plans.NodeIndexLeafPlan
import org.neo4j.cypher.internal.logical.plans.NodeIndexSeek
import org.neo4j.cypher.internal.logical.plans.NodeIndexSeekLeafPlan
import org.neo4j.cypher.internal.logical.plans.NonFuseable
import org.neo4j.cypher.internal.logical.plans.NonPipelined
import org.neo4j.cypher.internal.logical.plans.NonPipelinedStreaming
import org.neo4j.cypher.internal.logical.plans.Optional
import org.neo4j.cypher.internal.logical.plans.OptionalExpand
import org.neo4j.cypher.internal.logical.plans.OrderedAggregation
import org.neo4j.cypher.internal.logical.plans.OrderedDistinct
import org.neo4j.cypher.internal.logical.plans.OrderedUnion
import org.neo4j.cypher.internal.logical.plans.PartialSort
import org.neo4j.cypher.internal.logical.plans.PartialTop
import org.neo4j.cypher.internal.logical.plans.PartitionedAllNodesScan
import org.neo4j.cypher.internal.logical.plans.PartitionedDirectedAllRelationshipsScan
import org.neo4j.cypher.internal.logical.plans.PartitionedDirectedRelationshipTypeScan
import org.neo4j.cypher.internal.logical.plans.PartitionedDirectedUnionRelationshipTypesScan
import org.neo4j.cypher.internal.logical.plans.PartitionedIntersectionNodeByLabelsScan
import org.neo4j.cypher.internal.logical.plans.PartitionedNodeByLabelScan
import org.neo4j.cypher.internal.logical.plans.PartitionedSubtractionNodeByLabelsScan
import org.neo4j.cypher.internal.logical.plans.PartitionedUndirectedAllRelationshipsScan
import org.neo4j.cypher.internal.logical.plans.PartitionedUndirectedRelationshipTypeScan
import org.neo4j.cypher.internal.logical.plans.PartitionedUndirectedUnionRelationshipTypesScan
import org.neo4j.cypher.internal.logical.plans.PartitionedUnionNodeByLabelsScan
import org.neo4j.cypher.internal.logical.plans.PartitionedUnwindCollection
import org.neo4j.cypher.internal.logical.plans.PathPropagatingBFS
import org.neo4j.cypher.internal.logical.plans.PointBoundingBoxRange
import org.neo4j.cypher.internal.logical.plans.PointBoundingBoxSeekRangeWrapper
import org.neo4j.cypher.internal.logical.plans.PointDistanceRange
import org.neo4j.cypher.internal.logical.plans.PointDistanceSeekRangeWrapper
import org.neo4j.cypher.internal.logical.plans.Prober
import org.neo4j.cypher.internal.logical.plans.ProcedureCall
import org.neo4j.cypher.internal.logical.plans.ProduceResult
import org.neo4j.cypher.internal.logical.plans.ProjectEndpoints
import org.neo4j.cypher.internal.logical.plans.Projection
import org.neo4j.cypher.internal.logical.plans.PruningVarExpand
import org.neo4j.cypher.internal.logical.plans.QueryExpression
import org.neo4j.cypher.internal.logical.plans.RangeQueryExpression
import org.neo4j.cypher.internal.logical.plans.RelationshipCountFromCountStore
import org.neo4j.cypher.internal.logical.plans.RelationshipIndexLeafPlan
import org.neo4j.cypher.internal.logical.plans.RemoteBatchProperties
import org.neo4j.cypher.internal.logical.plans.RemoveLabels
import org.neo4j.cypher.internal.logical.plans.RepeatOptions
import org.neo4j.cypher.internal.logical.plans.RightOuterHashJoin
import org.neo4j.cypher.internal.logical.plans.RollUpApply
import org.neo4j.cypher.internal.logical.plans.RunQueryAt
import org.neo4j.cypher.internal.logical.plans.SelectOrAntiSemiApply
import org.neo4j.cypher.internal.logical.plans.SelectOrSemiApply
import org.neo4j.cypher.internal.logical.plans.Selection
import org.neo4j.cypher.internal.logical.plans.SemiApply
import org.neo4j.cypher.internal.logical.plans.SetDynamicProperty
import org.neo4j.cypher.internal.logical.plans.SetLabels
import org.neo4j.cypher.internal.logical.plans.SetNodeProperties
import org.neo4j.cypher.internal.logical.plans.SetNodePropertiesFromMap
import org.neo4j.cypher.internal.logical.plans.SetNodeProperty
import org.neo4j.cypher.internal.logical.plans.SetProperties
import org.neo4j.cypher.internal.logical.plans.SetPropertiesFromMap
import org.neo4j.cypher.internal.logical.plans.SetProperty
import org.neo4j.cypher.internal.logical.plans.SetRelationshipProperties
import org.neo4j.cypher.internal.logical.plans.SetRelationshipPropertiesFromMap
import org.neo4j.cypher.internal.logical.plans.SetRelationshipProperty
import org.neo4j.cypher.internal.logical.plans.SimulatedExpand
import org.neo4j.cypher.internal.logical.plans.SimulatedNodeScan
import org.neo4j.cypher.internal.logical.plans.SimulatedSelection
import org.neo4j.cypher.internal.logical.plans.Skip
import org.neo4j.cypher.internal.logical.plans.Sort
import org.neo4j.cypher.internal.logical.plans.StatefulShortestPath
import org.neo4j.cypher.internal.logical.plans.StatefulShortestPath.LengthBounds
import org.neo4j.cypher.internal.logical.plans.StatefulShortestPath.Mapping
import org.neo4j.cypher.internal.logical.plans.SubqueryForeach
import org.neo4j.cypher.internal.logical.plans.SubtractionNodeByLabelsScan
import org.neo4j.cypher.internal.logical.plans.Top
import org.neo4j.cypher.internal.logical.plans.Top1WithTies
import org.neo4j.cypher.internal.logical.plans.Trail
import org.neo4j.cypher.internal.logical.plans.TransactionApply
import org.neo4j.cypher.internal.logical.plans.TransactionConcurrency
import org.neo4j.cypher.internal.logical.plans.TransactionForeach
import org.neo4j.cypher.internal.logical.plans.TriadicBuild
import org.neo4j.cypher.internal.logical.plans.TriadicFilter
import org.neo4j.cypher.internal.logical.plans.TriadicSelection
import org.neo4j.cypher.internal.logical.plans.UndirectedAllRelationshipsScan
import org.neo4j.cypher.internal.logical.plans.UndirectedRelationshipByElementIdSeek
import org.neo4j.cypher.internal.logical.plans.UndirectedRelationshipByIdSeek
import org.neo4j.cypher.internal.logical.plans.UndirectedRelationshipIndexSeek
import org.neo4j.cypher.internal.logical.plans.UndirectedRelationshipTypeScan
import org.neo4j.cypher.internal.logical.plans.UndirectedUnionRelationshipTypesScan
import org.neo4j.cypher.internal.logical.plans.Union
import org.neo4j.cypher.internal.logical.plans.UnionNodeByLabelsScan
import org.neo4j.cypher.internal.logical.plans.UnwindCollection
import org.neo4j.cypher.internal.logical.plans.ValueHashJoin
import org.neo4j.cypher.internal.logical.plans.VarExpand
import org.neo4j.cypher.internal.rewriting.rewriters.HasLabelsAndHasTypeNormalizer
import org.neo4j.cypher.internal.rewriting.rewriters.combineHasLabels
import org.neo4j.cypher.internal.rewriting.rewriters.desugarMapProjection
import org.neo4j.cypher.internal.util.InputPosition
import org.neo4j.cypher.internal.util.InputPosition.NONE
import org.neo4j.cypher.internal.util.LabelId
import org.neo4j.cypher.internal.util.PropertyKeyId
import org.neo4j.cypher.internal.util.RelTypeId
import org.neo4j.cypher.internal.util.Repetition
import org.neo4j.cypher.internal.util.Rewritable.RewritableAny
import org.neo4j.cypher.internal.util.Rewriter
import org.neo4j.cypher.internal.util.UpperBound
import org.neo4j.cypher.internal.util.attribution.Id
import org.neo4j.cypher.internal.util.attribution.IdGen
import org.neo4j.cypher.internal.util.attribution.SameId
import org.neo4j.cypher.internal.util.attribution.SequentialIdGen
import org.neo4j.cypher.internal.util.collection.immutable.ListSet
import org.neo4j.cypher.internal.util.inSequence
import org.neo4j.cypher.internal.util.symbols.CTAny
import org.neo4j.cypher.internal.util.symbols.CTList
import org.neo4j.cypher.internal.util.symbols.CTNode
import org.neo4j.cypher.internal.util.symbols.CTRelationship
import org.neo4j.cypher.internal.util.symbols.CypherType
import org.neo4j.cypher.internal.util.symbols.TypeSpec
import org.neo4j.cypher.internal.util.topDown
import org.neo4j.graphdb.schema.IndexType
import scala.collection.mutable.ArrayBuffer
/**
* Used by [[AbstractLogicalPlanBuilder]] to resolve tokens and procedures
*/
trait Resolver {
/**
* Obtain the token of a label by name.
*/
def getLabelId(label: String): Int
def getRelTypeId(label: String): Int
def getPropertyKeyId(prop: String): Int
def procedureSignature(name: QualifiedName): ProcedureSignature
def functionSignature(name: QualifiedName): Option[UserFunctionSignature]
}
/**
* Test help utility for hand-writing objects needing logical plans.
* @param wholePlan Validate that we are creating a whole plan
*/
abstract class AbstractLogicalPlanBuilder[T, IMPL <: AbstractLogicalPlanBuilder[T, IMPL]](
protected val resolver: Resolver,
wholePlan: Boolean = true,
initialId: Int = 0
) {
self: IMPL =>
val patternParser = new PatternParser
protected var semanticTable = new SemanticTable()
sealed protected trait OperatorBuilder
protected case class LeafOperator(planToIdConstructor: IdGen => LogicalPlan) extends OperatorBuilder {
private val id = idGen.id()
_idOfLastPlan = id
def planConstructor(): LogicalPlan = planToIdConstructor(SameId(id))
}
protected case class UnaryOperator(planToIdConstructor: LogicalPlan => IdGen => LogicalPlan) extends OperatorBuilder {
private val id = idGen.id()
_idOfLastPlan = id
def planConstructor: LogicalPlan => LogicalPlan = planToIdConstructor(_)(SameId(id))
}
protected case class BinaryOperator(planToIdConstructor: (LogicalPlan, LogicalPlan) => IdGen => LogicalPlan)
extends OperatorBuilder {
private val id = idGen.id()
_idOfLastPlan = id
def planConstructor: (LogicalPlan, LogicalPlan) => LogicalPlan = planToIdConstructor(_, _)(SameId(id))
}
protected class Tree(operator: OperatorBuilder) {
private var _left: Option[Tree] = None
private var _right: Option[Tree] = None
def left: Option[Tree] = _left
def left_=(newVal: Option[Tree]): Unit = {
operator match {
case _: LeafOperator =>
throw new IllegalArgumentException(s"Cannot attach a LHS to a leaf plan.")
case _ =>
}
_left = newVal
}
def right: Option[Tree] = _right
def right_=(newVal: Option[Tree]): Unit = {
operator match {
case _: LeafOperator =>
throw new IllegalArgumentException(s"Cannot attach a RHS to a leaf plan.")
case _: UnaryOperator =>
throw new IllegalArgumentException(s"Cannot attach a RHS to a unary plan.")
case _ =>
}
_right = newVal
}
def build(): LogicalPlan = {
operator match {
case o: LeafOperator =>
o.planConstructor()
case o: UnaryOperator =>
o.planConstructor(left.get.build())
case o: BinaryOperator =>
(left, right) match {
case (Some(leftVal), Some(rightVal)) => o.planConstructor(leftVal.build(), rightVal.build())
case (None, _) =>
val fakePlan = o.planConstructor(Argument()(idGen), Argument()(idGen)).productPrefix
throw new IllegalStateException(s"Tried building plan '$fakePlan' but left operator is missing.")
case (_, None) =>
val fakePlan = o.planConstructor(Argument()(idGen), Argument()(idGen)).productPrefix
throw new IllegalStateException(s"Tried building plan '$fakePlan' but right operator is missing.")
}
}
}
}
val idGen: IdGen = new SequentialIdGen(initialId)
private var tree: Tree = _
private val looseEnds = new ArrayBuffer[Tree]
private var indent = 0
protected var resultColumns: Array[String] = _
private var _idOfLastPlan = Id.INVALID_ID
protected def idOfLastPlan: Id = _idOfLastPlan
/**
* Increase indent. The indent determines where the next
* logical plan will be appended to the tree.
*/
def | : IMPL = {
indent += 1
self
}
def resetIndent(): IMPL = {
indent = 0
self
}
def planIf(condition: Boolean)(builder: IMPL => IMPL): IMPL = {
if (condition) {
builder(self)
} else {
self
}
}
// OPERATORS
def produceResults(inputs: AnyRef*): IMPL = {
if (inputs.forall(_.isInstanceOf[String])) {
val vars = inputs.map(_.asInstanceOf[String])
val resultColumnsSeq = vars.map(VariableParser.unescaped)
resultColumns = resultColumnsSeq.toArray
tree =
new Tree(UnaryOperator(lp => ProduceResult(lp, resultColumnsSeq.map(c => Column(varFor(c), Set.empty)))(_)))
looseEnds += tree
self
} else if (inputs.forall(_.isInstanceOf[Column])) {
val cols = inputs.map(_.asInstanceOf[Column])
resultColumns = cols.map(_.variable.name).toArray
tree = new Tree(UnaryOperator(lp => ProduceResult(lp, cols)(_)))
looseEnds += tree
self
} else {
throw new IllegalArgumentException(
s"${inputs.mkString(", ")} must either all be of type String or all of type Column."
)
}
}
def procedureCall(call: String, withFakedFullDeclarations: Boolean = false): IMPL = {
val unresolvedCall = Parser.parseProcedureCall(call)
appendAtCurrentIndent(UnaryOperator(lp => {
val resolvedCall =
ResolvedCall(resolver.procedureSignature)(unresolvedCall)
.coerceArguments
val rewrittenResolvedCall =
if (withFakedFullDeclarations) resolvedCall.withFakedFullDeclarations else resolvedCall
ProcedureCall(lp, rewrittenResolvedCall)(_)
}))
self
}
def optional(protectedSymbols: String*): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp => Optional(lp, protectedSymbols.map(varFor).toSet)(_)))
self
}
def anti(): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp => Anti(lp)(_)))
self
}
def limit(count: Long): IMPL =
limit(literalInt(count))
def limit(countExpr: Expression): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp => Limit(lp, countExpr)(_)))
self
}
def exhaustiveLimit(count: Long): IMPL =
exhaustiveLimit(literalInt(count))
def exhaustiveLimit(countExpr: Expression): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp => ExhaustiveLimit(lp, countExpr)(_)))
self
}
def skip(count: Long): IMPL =
skip(literalInt(count))
def skip(countExpr: Expression): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp => Skip(lp, countExpr)(_)))
self
}
def argumentTracker(): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp => ArgumentTracker(lp)(_)))
self
}
def expand(
pattern: String,
expandMode: ExpansionMode = ExpandAll,
projectedDir: SemanticDirection = OUTGOING,
nodePredicates: Seq[Predicate] = Seq.empty,
relationshipPredicates: Seq[Predicate] = Seq.empty
): IMPL = {
expandExpr(
pattern,
expandMode,
projectedDir,
nodePredicates.map(_.asVariablePredicate),
relationshipPredicates.map(_.asVariablePredicate)
)
}
def expandExpr(
pattern: String,
expandMode: ExpansionMode = ExpandAll,
projectedDir: SemanticDirection = OUTGOING,
nodePredicates: Seq[VariablePredicate] = Seq.empty,
relationshipPredicates: Seq[VariablePredicate] = Seq.empty
): IMPL = {
val p = patternParser.parse(pattern)
newRelationship(varFor(p.relName))
if (expandMode == ExpandAll) {
newNode(varFor(p.to))
}
p.length match {
case SimplePatternLength =>
appendAtCurrentIndent(UnaryOperator(lp =>
Expand(lp, varFor(p.from), p.dir, p.relTypes, varFor(p.to), varFor(p.relName), expandMode)(_)
))
case varPatternLength: VarPatternLength =>
appendAtCurrentIndent(UnaryOperator(lp =>
VarExpand(
lp,
varFor(p.from),
p.dir,
projectedDir,
p.relTypes,
varFor(p.to),
varFor(p.relName),
varPatternLength,
expandMode,
nodePredicates,
relationshipPredicates
)(_)
))
}
self
}
def simulatedExpand(fromNode: String, rel: String, toNode: String, factor: Double): IMPL = {
val from = VariableParser.unescaped(fromNode)
val urel = VariableParser.unescaped(rel)
val to = VariableParser.unescaped(toNode)
newNode(varFor(from))
newRelationship(varFor(urel))
newNode(varFor(to))
appendAtCurrentIndent(UnaryOperator(lp =>
SimulatedExpand(lp, varFor(from), varFor(urel), varFor(to), factor)(_)
))
self
}
def shortestPath(
pattern: String,
pathName: Option[String] = None,
all: Boolean = false,
nodePredicates: Seq[Predicate] = Seq.empty,
relationshipPredicates: Seq[Predicate] = Seq.empty,
pathPredicates: Seq[String] = Seq.empty,
withFallback: Boolean = false,
sameNodeMode: SameNodeMode = DisallowSameNode
): IMPL =
shortestPathSolver(
pattern,
pathName,
all,
nodePredicates.map(_.asVariablePredicate),
relationshipPredicates.map(_.asVariablePredicate),
pathPredicates.map(parseExpression),
withFallback,
sameNodeMode
)
def shortestPathExpr(
pattern: String,
pathName: Option[String] = None,
all: Boolean = false,
nodePredicates: Seq[VariablePredicate] = Seq.empty,
relationshipPredicates: Seq[VariablePredicate] = Seq.empty,
pathPredicates: Seq[Expression] = Seq.empty,
withFallback: Boolean = false,
sameNodeMode: SameNodeMode = DisallowSameNode
): IMPL =
shortestPathSolver(
pattern,
pathName,
all,
nodePredicates,
relationshipPredicates,
pathPredicates,
withFallback,
sameNodeMode
)
def statefulShortestPathExpr(
sourceNode: String,
targetNode: String,
solvedExpressionString: String,
nonInlinedPreFilters: Option[Expression],
groupNodes: Set[(String, String)],
groupRelationships: Set[(String, String)],
singletonNodeVariables: Set[(String, String)],
singletonRelationshipVariables: Set[(String, String)],
selector: StatefulShortestPath.Selector,
nfa: NFA,
mode: ExpansionMode,
reverseGroupVariableProjections: Boolean = false,
minLength: Int = 0,
maxLength: Option[Int] = None
): IMPL = {
val nodeVariableGroupings = groupNodes.map { case (x, y) => VariableGrouping(varFor(x), varFor(y))(pos) }
val relationshipVariableGroupings = groupRelationships.map { case (x, y) =>
VariableGrouping(varFor(x), varFor(y))(pos)
}
val singletonNodeMappings = singletonNodeVariables.map { case (x, y) => Mapping(varFor(x), varFor(y)) }
val singletonRelMappings = singletonRelationshipVariables.map { case (x, y) => Mapping(varFor(x), varFor(y)) }
// Assign types to group variables
nodeVariableGroupings.map(_.group.asInstanceOf[Variable])
.foreach(newVariable(_, CTList(CTNode)))
relationshipVariableGroupings.map(_.group.asInstanceOf[Variable])
.foreach(newVariable(_, CTList(CTRelationship)))
// Assign types to singleton variables.
// Be aware that this will override the types from the group variables if they share the same name with
// the singleton variables. This will for instance happen if you use .enableDeduplicateNames(true),
// which is the default setting in any LogicalPlanning integration test.
// Overriding the type of the group variable is usually fine, except for Eagerness analysis on logical plans.
// For these tests, you might want to consider setting .enableDeduplicateNames(false).
newNodes(nfa.nodes.map(_.name) + targetNode)
newRelationships(nfa.relationships.map(_.name))
singletonNodeMappings.map(_.rowVar.asInstanceOf[Variable])
.foreach(newVariable(_, CTNode))
singletonRelMappings.map(_.rowVar.asInstanceOf[Variable])
.foreach(newVariable(_, CTRelationship))
appendAtCurrentIndent(UnaryOperator(lp =>
StatefulShortestPath(
lp,
varFor(sourceNode),
varFor(targetNode),
nfa.endoRewrite(expressionRewriter),
mode,
nonInlinedPreFilters.endoRewrite(expressionRewriter),
nodeVariableGroupings,
relationshipVariableGroupings,
singletonNodeMappings,
singletonRelMappings,
selector,
solvedExpressionString,
reverseGroupVariableProjections,
LengthBounds(minLength, maxLength)
)(_)
))
self
}
def statefulShortestPath(
sourceNode: String,
targetNode: String,
solvedExpressionString: String,
nonInlinedPreFilters: Option[String],
groupNodes: Set[(String, String)],
groupRelationships: Set[(String, String)],
singletonNodeVariables: Set[(String, String)],
singletonRelationshipVariables: Set[(String, String)],
selector: StatefulShortestPath.Selector,
nfa: NFA,
mode: ExpansionMode,
reverseGroupVariableProjections: Boolean = false,
minLength: Int = 0,
maxLength: Option[Int] = None
): IMPL = {
val predicates = nonInlinedPreFilters.map(parseExpression)
statefulShortestPathExpr(
sourceNode,
targetNode,
solvedExpressionString,
predicates,
groupNodes,
groupRelationships,
singletonNodeVariables,
singletonRelationshipVariables,
selector,
nfa,
mode,
reverseGroupVariableProjections,
minLength,
maxLength
)
}
private def shortestPathSolver(
pattern: String,
pathName: Option[String],
all: Boolean,
nodePredicates: Seq[VariablePredicate],
relationshipPredicates: Seq[VariablePredicate],
pathPredicates: Seq[Expression],
withFallback: Boolean,
sameNodeMode: SameNodeMode
): IMPL = {
val p = patternParser.parse(pattern)
newRelationship(varFor(p.relName))
val length = p.length match {
case SimplePatternLength => None
case VarPatternLength(min, max) => Some(Some(Range(
Some(UnsignedDecimalIntegerLiteral(min.toString)(pos)),
max.map(i => UnsignedDecimalIntegerLiteral(i.toString)(pos))
)(pos)))
}
appendAtCurrentIndent(UnaryOperator(lp =>
FindShortestPaths(
lp,
ShortestRelationshipPattern(
pathName.map(varFor),
PatternRelationship(varFor(p.relName), (varFor(p.from), varFor(p.to)), p.dir, p.relTypes, p.length),
!all
)(ShortestPathsPatternPart(
RelationshipChain(
NodePattern(Some(varFor(p.from)), None, None, None)(
pos
), // labels, properties and predicates are not used at runtime
RelationshipPattern(
Some(varFor(p.relName)),
disjoinRelTypesToLabelExpression(p.relTypes),
length,
None, // properties are not used at runtime
None,
p.dir
)(pos),
NodePattern(Some(varFor(p.to)), None, None, None)(
pos
) // labels, properties and predicates are not used at runtime
)(pos),
!all
)(pos)),
nodePredicates,
relationshipPredicates,
pathPredicates,
withFallback,
sameNodeMode
)(_)
))
}
def pruningVarExpand(
pattern: String,
nodePredicates: Seq[Predicate] = Seq.empty,
relationshipPredicates: Seq[Predicate] = Seq.empty
): IMPL = {
val p = patternParser.parse(pattern)
newRelationship(varFor(p.relName))
newNode(varFor(p.to))
p.length match {
case VarPatternLength(min, Some(max)) =>
appendAtCurrentIndent(UnaryOperator(lp =>
PruningVarExpand(
lp,
varFor(p.from),
p.dir,
p.relTypes,
varFor(p.to),
min,
max,
nodePredicates.map(_.asVariablePredicate),
relationshipPredicates.map(_.asVariablePredicate)
)(_)
))
case _ =>
throw new IllegalArgumentException("This pattern is not compatible with pruning var expand")
}
self
}
def bfsPruningVarExpand(
pattern: String,
depthName: Option[String] = None,
nodePredicates: Seq[Predicate] = Seq.empty,
relationshipPredicates: Seq[Predicate] = Seq.empty,
mode: ExpansionMode = ExpandAll
): IMPL = {
val p = patternParser.parse(pattern)
newRelationship(varFor(p.relName))
if (mode == ExpandAll) {
newNode(varFor(p.to))
}
p.length match {
case VarPatternLength(min, maybeMax) if min <= 1 =>
appendAtCurrentIndent(UnaryOperator(lp =>
BFSPruningVarExpand(
lp,
varFor(p.from),
p.dir,
p.relTypes,
varFor(p.to),
min == 0,
maxLength = maybeMax.getOrElse(Int.MaxValue),
depthName.map(varFor),
mode,
nodePredicates.map(_.asVariablePredicate),
relationshipPredicates.map(_.asVariablePredicate)
)(_)
))
case _ =>
throw new IllegalArgumentException("This pattern is not compatible with a bfs pruning var expand")
}
self
}
def pathPropagatingBFS(
pattern: String,
projectedDir: SemanticDirection = OUTGOING,
nodePredicates: Seq[Predicate] = Seq.empty,
relationshipPredicates: Seq[Predicate] = Seq.empty
): IMPL = {
val p = patternParser.parse(pattern)
val varPatternLength = p.length.asInstanceOf[VarPatternLength]
newRelationship(varFor(p.relName))
newNode(varFor(p.to))
appendAtCurrentIndent(BinaryOperator((lhs, rhs) =>
PathPropagatingBFS(
lhs,
rhs,
varFor(p.from),
p.dir,
projectedDir,
p.relTypes,
varFor(p.to),
varFor(p.relName),
varPatternLength,
nodePredicates.map(_.asVariablePredicate),
relationshipPredicates.map(_.asVariablePredicate)
)(_)
))
}
def expandInto(pattern: String): IMPL = expand(pattern, ExpandInto)
def optionalExpandAll(pattern: String, predicate: Option[String] = None): IMPL =
optionalExpandAll(
patternParser.parse(pattern),
predicate.map(parseExpression).map(p => Ands(ListSet(p))(p.position))
)
private def optionalExpandAll(pattern: PatternParser.Pattern, predicate: Option[Expression]): IMPL = {
val nodes = Set(pattern.from, pattern.to)
val rewrittenPredicate = predicate.map(_.endoRewrite(topDown(Rewriter.lift {
case p @ HasLabelsOrTypes(v: Variable, labelsOrTypes) if nodes.contains(v.name) =>
HasLabels(v, labelsOrTypes.map(l => LabelName(l.name)(l.position)))(p.position)
case p @ HasLabelsOrTypes(v: Variable, labelsOrTypes) if pattern.relName == v.name =>
HasTypes(v, labelsOrTypes.map(l => RelTypeName(l.name)(l.position)))(p.position)
})))
pattern.length match {
case SimplePatternLength =>
appendAtCurrentIndent(UnaryOperator(lp =>
OptionalExpand(
lp,
varFor(pattern.from),
pattern.dir,
pattern.relTypes,
varFor(pattern.to),
varFor(pattern.relName),
ExpandAll,
rewrittenPredicate
)(_)
))
case _ =>
throw new IllegalArgumentException("Cannot have optional expand with variable length pattern")
}
self
}
def optionalExpandInto(pattern: String, predicate: Option[String] = None): IMPL = {
val p = patternParser.parse(pattern)
p.length match {
case SimplePatternLength =>
val pred = predicate.map(parseExpression).map(p => Ands(ListSet(p))(p.position))
appendAtCurrentIndent(UnaryOperator(lp =>
OptionalExpand(lp, varFor(p.from), p.dir, p.relTypes, varFor(p.to), varFor(p.relName), ExpandInto, pred)(_)
))
case _ =>
throw new IllegalArgumentException("Cannot have optional expand with variable length pattern")
}
self
}
def projectEndpoints(pattern: String, startInScope: Boolean, endInScope: Boolean): IMPL = {
val p = patternParser.parse(pattern)
newNode(varFor(p.from))
newNode(varFor(p.to))
appendAtCurrentIndent(UnaryOperator(lp =>
ProjectEndpoints(
lp,
varFor(p.relName),
varFor(p.from),
startInScope,
varFor(p.to),
endInScope,
p.relTypes,
p.dir,
p.length
)(_)
))
}
def partialSort(alreadySortedPrefix: Seq[String], stillToSortSuffix: Seq[String]): IMPL =
partialSortColumns(Parser.parseSort(alreadySortedPrefix), Parser.parseSort(stillToSortSuffix))
def partialSortColumns(alreadySortedPrefix: Seq[ColumnOrder], stillToSortSuffix: Seq[ColumnOrder]): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp => PartialSort(lp, alreadySortedPrefix, stillToSortSuffix, None)(_)))
self
}
def partialSortColumns(
alreadySortedPrefix: Seq[ColumnOrder],
stillToSortSuffix: Seq[ColumnOrder],
skipSortingPrefixLength: Long
): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp =>
PartialSort(lp, alreadySortedPrefix, stillToSortSuffix, Some(literalInt(skipSortingPrefixLength)))(_)
))
self
}
def partialSort(
alreadySortedPrefix: Seq[String],
stillToSortSuffix: Seq[String],
skipSortingPrefixLength: Long = 0
): IMPL = {
val skipSort = if (skipSortingPrefixLength == 0) None else Some(literalInt(skipSortingPrefixLength))
appendAtCurrentIndent(UnaryOperator(lp =>
PartialSort(lp, Parser.parseSort(alreadySortedPrefix), Parser.parseSort(stillToSortSuffix), skipSort)(_)
))
self
}
def sortColumns(sortItems: Seq[ColumnOrder]): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp => Sort(lp, sortItems)(_)))
self
}
def sort(sortItems: String*): IMPL = sortColumns(Parser.parseSort(sortItems))
def top(limit: Long, sortItems: String*): IMPL = top(Parser.parseSort(sortItems), limit)
def top(sortItems: Seq[ColumnOrder], limit: Long): IMPL =
top(sortItems, literalInt(limit))
def top(sortItems: Seq[ColumnOrder], limitExpr: Expression): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp => Top(lp, sortItems, limitExpr)(_)))
self
}
def top1WithTies(sortItems: String*): IMPL = top1WithTiesColumns(Parser.parseSort(sortItems))
def top1WithTiesColumns(sortItems: Seq[ColumnOrder]): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp => Top1WithTies(lp, sortItems)(_)))
self
}
def partialTop(alreadySortedPrefix: Seq[ColumnOrder], stillToSortSuffix: Seq[ColumnOrder], limit: Long): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp =>
PartialTop(lp, alreadySortedPrefix, stillToSortSuffix, literalInt(limit), None)(_)
))
self
}
def partialTop(limit: Long, alreadySortedPrefix: Seq[String], stillToSortSuffix: Seq[String]): IMPL = {
partialTop(Parser.parseSort(alreadySortedPrefix), Parser.parseSort(stillToSortSuffix), limit)
}
def partialTop(
alreadySortedPrefix: Seq[ColumnOrder],
stillToSortSuffix: Seq[ColumnOrder],
limit: Long,
skipSortingPrefixLength: Long
): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp =>
PartialTop(
lp,
alreadySortedPrefix,
stillToSortSuffix,
literalInt(limit),
Some(literalInt(skipSortingPrefixLength))
)(_)
))
self
}
def partialTop(
limit: Long,
skipSortingPrefixLength: Long,
alreadySortedPrefix: Seq[String],
stillToSortSuffix: Seq[String]
): IMPL = {
partialTop(
Parser.parseSort(alreadySortedPrefix),
Parser.parseSort(stillToSortSuffix),
limit,
skipSortingPrefixLength
)
}
def eager(reasons: ListSet[EagernessReason] = ListSet(EagernessReason.Unknown)): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp => Eager(lp, reasons)(_)))
self
}
def emptyResult(): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp => EmptyResult(lp)(_)))
self
}
def deleteNode(node: String): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp => DeleteNode(lp, parseExpression(node))(_)))
self
}
def deleteNode(node: Expression): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp => DeleteNode(lp, node)(_)))
self
}
def detachDeleteNode(node: String): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp => DetachDeleteNode(lp, parseExpression(node))(_)))
self
}
def deleteRelationship(rel: String): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp => DeleteRelationship(lp, parseExpression(rel))(_)))
self
}
def deleteRelationship(rel: Expression): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp => DeleteRelationship(lp, rel)(_)))
self
}
def deletePath(path: String): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp => DeletePath(lp, parseExpression(path))(_)))
self
}
def detachDeletePath(path: String): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp => DetachDeletePath(lp, parseExpression(path))(_)))
self
}
def deleteExpression(expression: String): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp => DeleteExpression(lp, parseExpression(expression))(_)))
self
}
def deleteExpression(expression: Expression): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp => DeleteExpression(lp, expression)(_)))
self
}
def detachDeleteExpression(expression: String): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp => DetachDeleteExpression(lp, parseExpression(expression))(_)))
self
}
def setLabels(nodeVariable: String, labels: String*): IMPL = {
val labelNames = labels.map(l => LabelName(l)(InputPosition.NONE)).toSet
appendAtCurrentIndent(UnaryOperator(lp => SetLabels(lp, varFor(nodeVariable), labelNames, Set.empty)(_)))
}
def setLabels(nodeVariable: String, labelNames: Seq[String], labelExpressions: Seq[String]): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp =>
SetLabels(
lp,
varFor(nodeVariable),
labelNames.map(l => LabelName(l)(InputPosition.NONE)).toSet,
labelExpressions.map(l => Parser.parseExpression(l)).toSet
)(_)
))
}
def setDynamicLabels(nodeVariable: String, labels: String*): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp =>
SetLabels(lp, varFor(nodeVariable), Set.empty, labels.map(l => Parser.parseExpression(l)).toSet)(_)
))
}
def setDynamicLabelsWithExpression(nodeVariable: String, labelsExpressions: Set[Expression]): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp =>
SetLabels(lp, varFor(nodeVariable), Set.empty, labelsExpressions)(_)
))
}
def removeLabels(nodeVariable: String, labels: String*): IMPL = {
val labelNames = labels.map(l => LabelName(l)(InputPosition.NONE)).toSet
appendAtCurrentIndent(UnaryOperator(lp => RemoveLabels(lp, varFor(nodeVariable), labelNames, Set.empty)(_)))
}
def removeLabels(nodeVariable: String, labelNames: Seq[String], labelExpressions: Seq[String]): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp =>
RemoveLabels(
lp,
varFor(nodeVariable),
labelNames.map(l => LabelName(l)(InputPosition.NONE)).toSet,
labelExpressions.map(l => Parser.parseExpression(l)).toSet
)(_)
))
}
def removeDynamicLabels(nodeVariable: String, labels: String*): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp =>
RemoveLabels(lp, varFor(nodeVariable), Set.empty, labels.map(l => Parser.parseExpression(l)).toSet)(_)
))
}
def removeDynamicLabelsWithExpressions(nodeVariable: String, labelsExpressions: Set[Expression]): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp =>
RemoveLabels(lp, varFor(nodeVariable), Set.empty, labelsExpressions)(_)
))
}
def unwind(projectionString: String): IMPL = {
val (name, expression) = toVarMap(Parser.parseProjections(projectionString)).head
appendAtCurrentIndent(UnaryOperator(lp => UnwindCollection(lp, name, expression)(_)))
self
}
def partitionedUnwind(projectionString: String): IMPL = {
val (name, expression) = toVarMap(Parser.parseProjections(projectionString)).head
appendAtCurrentIndent(UnaryOperator(lp => PartitionedUnwindCollection(lp, name, expression)(_)))
self
}
def runQueryAt(
query: String,
graphReference: String,
parameters: Set[String] = Set.empty,
importsAsParameters: Map[String, String] = Map.empty,
columns: Set[String] = Set.empty
): IMPL = {
val properParameters = parameters.map(asParameter)
val properImports = importsAsParameters.map[Parameter, LogicalVariable] {
case (parameterName, variableName) => asParameter(parameterName) -> varFor(variableName)
}
appendAtCurrentIndent(UnaryOperator(source =>
RunQueryAt(
source,
query,
Parser.parseGraphReference(graphReference),
properParameters,
properImports,
columns.map(varFor)
)(_)
))
}
private def asParameter(parameterName: String): Parameter =
ExplicitParameter(parameterName.stripPrefix("$"), CTAny)(pos)
def projection(projectionStrings: String*): IMPL = {
doProjection(parseProjections(projectionStrings: _*))
}
def projection(projectExpressions: Map[String, Expression]): IMPL = {
doProjection(toVarMap(projectExpressions))
}
private def doProjection(projectExpressions: Map[LogicalVariable, Expression]): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp => {
val rewrittenProjections = projectExpressions.map { case (name, expr) =>
name -> expr.endoRewrite(expressionRewriter)
}
projectExpressions.foreach { case (name, expr) => newAlias(name, expr) }
Projection(lp, rewrittenProjections)(_)
}))
self
}
private def toVarMap(map: Map[String, Expression]): Map[LogicalVariable, Expression] = map.map {
case (key, value) => varFor(key) -> value
}
def distinct(projectionStrings: String*): IMPL = {
val projections = Parser.parseProjections(projectionStrings: _*)
appendAtCurrentIndent(UnaryOperator(lp => Distinct(lp, toVarMap(projections))(_)))
self
}
def orderedDistinct(orderToLeverage: Seq[String], projectionStrings: String*): IMPL = {
val order = orderToLeverage.map(parseExpression)
val projections = Parser.parseProjections(projectionStrings: _*)
appendAtCurrentIndent(UnaryOperator(lp => OrderedDistinct(lp, toVarMap(projections), order)(_)))
self
}
def allNodeScan(node: String, args: String*): IMPL = {
val n = VariableParser.unescaped(node)
newNode(varFor(n))
appendAtCurrentIndent(LeafOperator(AllNodesScan(
varFor(n),
args.map(a => varFor(VariableParser.unescaped(a))).toSet
)(_)))
}
def partitionedAllNodeScan(node: String, args: String*): IMPL = {
val n = VariableParser.unescaped(node)
newNode(varFor(n))
appendAtCurrentIndent(LeafOperator(PartitionedAllNodesScan(
varFor(n),
args.map(a => varFor(VariableParser.unescaped(a))).toSet
)(_)))
}
def simulatedNodeScan(node: String, numberOfRows: Long): IMPL = {
val n = VariableParser.unescaped(node)
newNode(varFor(n))
appendAtCurrentIndent(LeafOperator(SimulatedNodeScan(varFor(n), numberOfRows)(_)))
}
def argument(args: String*): IMPL = {
appendAtCurrentIndent(LeafOperator(Argument(args.map(varFor).toSet)(_)))
}
def nodeByLabelScan(node: String, label: String, args: String*): IMPL =
nodeByLabelScan(node, label, IndexOrderNone, args: _*)
def nodeByLabelScan(node: String, label: String, indexOrder: IndexOrder, args: String*): IMPL = {
val n = VariableParser.unescaped(node)
newNode(varFor(n))
appendAtCurrentIndent(LeafOperator(NodeByLabelScan(
varFor(n),
labelName(label),
args.map(a => varFor(VariableParser.unescaped(a))).toSet,
indexOrder
)(_)))
}
def partitionedNodeByLabelScan(node: String, label: String, args: String*): IMPL = {
val n = VariableParser.unescaped(node)
newNode(varFor(n))
appendAtCurrentIndent(LeafOperator(PartitionedNodeByLabelScan(
varFor(n),
labelName(label),
args.map(a => varFor(VariableParser.unescaped(a))).toSet
)(_)))
}
def unionNodeByLabelsScan(node: String, labels: Seq[String], args: String*): IMPL = {
unionNodeByLabelsScan(node, labels, IndexOrderNone, args: _*)
}
def unionNodeByLabelsScan(node: String, labels: Seq[String], indexOrder: IndexOrder, args: String*): IMPL = {
val n = VariableParser.unescaped(node)
newNode(varFor(n))
appendAtCurrentIndent(LeafOperator(UnionNodeByLabelsScan(
varFor(n),
labels.map(labelName),
args.map(a => varFor(VariableParser.unescaped(a))).toSet,
indexOrder
)(_)))
}
def partitionedUnionNodeByLabelsScan(node: String, labels: Seq[String], args: String*): IMPL = {
val n = VariableParser.unescaped(node)
newNode(varFor(n))
appendAtCurrentIndent(LeafOperator(PartitionedUnionNodeByLabelsScan(
varFor(n),
labels.map(labelName),
args.map(a => varFor(VariableParser.unescaped(a))).toSet
)(_)))
}
def intersectionNodeByLabelsScan(node: String, labels: Seq[String], args: String*): IMPL = {
intersectionNodeByLabelsScan(node, labels, IndexOrderNone, args: _*)
}
def intersectionNodeByLabelsScan(node: String, labels: Seq[String], indexOrder: IndexOrder, args: String*): IMPL = {
val n = VariableParser.unescaped(node)
newNode(varFor(n))
appendAtCurrentIndent(LeafOperator(IntersectionNodeByLabelsScan(
varFor(n),
labels.map(labelName),
args.map(a => varFor(VariableParser.unescaped(a))).toSet,
indexOrder
)(_)))
}
def partitionedIntersectionNodeByLabelsScan(node: String, labels: Seq[String], args: String*): IMPL = {
val n = VariableParser.unescaped(node)
newNode(varFor(n))
appendAtCurrentIndent(LeafOperator(PartitionedIntersectionNodeByLabelsScan(
varFor(n),
labels.map(labelName),
args.map(a => varFor(VariableParser.unescaped(a))).toSet
)(_)))
}
def subtractionNodeByLabelsScan(
node: String,
positiveLabel: String,
negativeLabel: String,
args: String*
): IMPL = {
subtractionNodeByLabelsScan(node, Seq(positiveLabel), Seq(negativeLabel), args: _*)
}
def subtractionNodeByLabelsScan(
node: String,
positiveLabel: String,
negativeLabel: String,
indexOrder: IndexOrder,
args: String*
): IMPL = {
subtractionNodeByLabelsScan(node, Seq(positiveLabel), Seq(negativeLabel), indexOrder, args: _*)
}
def subtractionNodeByLabelsScan(
node: String,
positiveLabels: Seq[String],
negativeLabels: Seq[String],
args: String*
): IMPL = {
subtractionNodeByLabelsScan(node, positiveLabels, negativeLabels, IndexOrderNone, args: _*)
}
def subtractionNodeByLabelsScan(
node: String,
positiveLabels: Seq[String],
negativeLabels: Seq[String],
indexOrder: IndexOrder,
args: String*
): IMPL = {
val n = VariableParser.unescaped(node)
newNode(varFor(n))
appendAtCurrentIndent(LeafOperator(SubtractionNodeByLabelsScan(
varFor(n),
positiveLabels.map(labelName),
negativeLabels.map(labelName),
args.map(a => varFor(VariableParser.unescaped(a))).toSet,
indexOrder
)(_)))
}
def partitionedSubtractionNodeByLabelsScan(
node: String,
positiveLabels: Seq[String],
negativeLabels: Seq[String],
args: String*
): IMPL = {
val n = VariableParser.unescaped(node)
newNode(varFor(n))
appendAtCurrentIndent(LeafOperator(PartitionedSubtractionNodeByLabelsScan(
varFor(n),
positiveLabels.map(labelName),
negativeLabels.map(labelName),
args.map(a => varFor(VariableParser.unescaped(a))).toSet
)(_)))
}
def unionRelationshipTypesScan(pattern: String, args: String*): IMPL = {
unionRelationshipTypesScan(pattern, IndexOrderNone, args: _*)
}
def unionRelationshipTypesScan(pattern: String, indexOrder: IndexOrder, args: String*): IMPL = {
val p = patternParser.parse(pattern)
newRelationship(varFor(p.relName))
newNode(varFor(p.from))
newNode(varFor(p.to))
if (!p.length.isSimple) throw new UnsupportedOperationException("Cannot do a scan from a variable pattern")
p.dir match {
case SemanticDirection.OUTGOING =>
appendAtCurrentIndent(LeafOperator(DirectedUnionRelationshipTypesScan(
varFor(p.relName),
varFor(p.from),
p.relTypes,
varFor(p.to),
args.map(varFor).toSet,
indexOrder
)(_)))
case SemanticDirection.INCOMING =>
appendAtCurrentIndent(LeafOperator(DirectedUnionRelationshipTypesScan(
varFor(p.relName),
varFor(p.to),
p.relTypes,
varFor(p.from),
args.map(varFor).toSet,
indexOrder
)(_)))
case SemanticDirection.BOTH =>
appendAtCurrentIndent(LeafOperator(UndirectedUnionRelationshipTypesScan(
varFor(p.relName),
varFor(p.from),
p.relTypes,
varFor(p.to),
args.map(varFor).toSet,
indexOrder
)(_)))
}
}
def partitionedUnionRelationshipTypesScan(pattern: String, args: String*): IMPL = {
val p = patternParser.parse(pattern)
newRelationship(varFor(p.relName))
newNode(varFor(p.from))
newNode(varFor(p.to))
if (!p.length.isSimple) throw new UnsupportedOperationException("Cannot do a scan from a variable pattern")
p.dir match {
case SemanticDirection.OUTGOING =>
appendAtCurrentIndent(LeafOperator(PartitionedDirectedUnionRelationshipTypesScan(
varFor(p.relName),
varFor(p.from),
p.relTypes,
varFor(p.to),
args.map(varFor).toSet
)(_)))
case SemanticDirection.INCOMING =>
appendAtCurrentIndent(LeafOperator(PartitionedDirectedUnionRelationshipTypesScan(
varFor(p.relName),
varFor(p.to),
p.relTypes,
varFor(p.from),
args.map(varFor).toSet
)(_)))
case SemanticDirection.BOTH =>
appendAtCurrentIndent(LeafOperator(PartitionedUndirectedUnionRelationshipTypesScan(
varFor(p.relName),
varFor(p.from),
p.relTypes,
varFor(p.to),
args.map(varFor).toSet
)(_)))
}
}
def nodeByIdSeek(node: String, args: Set[String], ids: Any*): IMPL = {
val n = VariableParser.unescaped(node)
newNode(varFor(n))
val input = idSeekInput(ids)
appendAtCurrentIndent(LeafOperator(NodeByIdSeek(varFor(n), input, args.map(varFor))(_)))
}
private def directedRelationshipByIdSeekSolver(
relationship: String,
from: String,
to: String,
args: Set[String],
expr: Seq[Expression]
): IMPL = {
newRelationship(varFor(relationship))
newNode(varFor(from))
newNode(varFor(to))
val input = ManySeekableArgs(ListLiteral(expr)(pos))
appendAtCurrentIndent(LeafOperator(DirectedRelationshipByIdSeek(
varFor(relationship),
input,
varFor(from),
varFor(to),
args.map(varFor)
)(_)))
}
def directedRelationshipByIdSeek(
relationship: String,
from: String,
to: String,
args: Set[String],
ids: AnyVal*
): IMPL = {
val idExpressions: Seq[Expression] = ids.map {
case x @ (_: Long | _: Int) => SignedDecimalIntegerLiteral(x.toString)(pos)
case x @ (_: Float | _: Double) => DecimalDoubleLiteral(x.toString)(pos)
case x => throw new IllegalArgumentException(s"$x is not a supported value for ID")
}
directedRelationshipByIdSeekSolver(relationship, from, to, args, idExpressions)
}
def directedRelationshipByIdSeekExpr(
relationship: String,
from: String,
to: String,
args: Set[String],
expr: Expression*
): IMPL = {
directedRelationshipByIdSeekSolver(relationship, from, to, args, expr)
}
private def undirectedRelationshipByIdSeekSolver(
relationship: String,
from: String,
to: String,
args: Set[String],
expr: Seq[Expression]
): IMPL = {
newRelationship(varFor(relationship))
newNode(varFor(from))
newNode(varFor(to))
val input = ManySeekableArgs(ListLiteral(expr)(pos))
appendAtCurrentIndent(LeafOperator(UndirectedRelationshipByIdSeek(
varFor(relationship),
input,
varFor(from),
varFor(to),
args.map(varFor)
)(_)))
}
def undirectedRelationshipByIdSeek(
relationship: String,
from: String,
to: String,
args: Set[String],
ids: AnyVal*
): IMPL = {
newRelationship(varFor(relationship))
newNode(varFor(from))
newNode(varFor(to))
val idExpressions = ids.map {
case x @ (_: Long | _: Int) => SignedDecimalIntegerLiteral(x.toString)(pos)
case x @ (_: Float | _: Double) => DecimalDoubleLiteral(x.toString)(pos)
case x => throw new IllegalArgumentException(s"$x is not a supported value for ID")
}
undirectedRelationshipByIdSeekSolver(relationship, from, to, args, idExpressions)
}
def undirectedRelationshipByIdSeekExpr(
relationship: String,
from: String,
to: String,
args: Set[String],
expr: Expression*
): IMPL = {
undirectedRelationshipByIdSeekSolver(relationship, from, to, args, expr)
}
def nodeByElementIdSeek(node: String, args: Set[String], ids: Any*): IMPL = {
val n = VariableParser.unescaped(node)
newNode(varFor(n))
val input = idSeekInput(ids)
appendAtCurrentIndent(LeafOperator(NodeByElementIdSeek(varFor(n), input, args.map(varFor))(_)))
}
def directedRelationshipByElementIdSeek(
relationship: String,
from: String,
to: String,
args: Set[String],
ids: Any*
): IMPL = {
newRelationship(varFor(relationship))
newNode(varFor(from))
newNode(varFor(to))
val input = idSeekInput(ids)
appendAtCurrentIndent(LeafOperator(DirectedRelationshipByElementIdSeek(
varFor(relationship),
input,
varFor(from),
varFor(to),
args.map(varFor)
)(_)))
}
def undirectedRelationshipByElementIdSeek(
relationship: String,
from: String,
to: String,
args: Set[String],
ids: Any*
): IMPL = {
newRelationship(varFor(relationship))
newNode(varFor(from))
newNode(varFor(to))
val input = idSeekInput(ids)
appendAtCurrentIndent(LeafOperator(UndirectedRelationshipByElementIdSeek(
varFor(relationship),
input,
varFor(from),
varFor(to),
args.map(varFor)
)(_)))
}
private def idSeekInput(ids: Seq[Any]): ManySeekableArgs = {
ids match {
case Seq(expression: Expression) =>
ManySeekableArgs(expression)
case _ =>
val idExpressions = ids.map {
case x: Expression => x
// This is a bit hacky but we cannot separate passing in an expression string
// like "$param" and an actual id string "thisisanelementid". If we don't do this hack
// the caller would have to quote elementIds all the time.
case x: String =>
try {
Parser.parseExpression(x)
} catch {
case _: Exception => StringLiteral(x)(pos.withInputLength(0))
}
case x @ (_: Long | _: Int) => SignedDecimalIntegerLiteral(x.toString)(pos)
case x @ (_: Float | _: Double) => DecimalDoubleLiteral(x.toString)(pos)
case x => throw new IllegalArgumentException(s"$x is not a supported value for ID")
}
ManySeekableArgs(ListLiteral(idExpressions)(pos))
}
}
def allRelationshipsScan(pattern: String, args: String*): IMPL = {
val p = patternParser.parse(pattern)
newRelationship(varFor(p.relName))
newNode(varFor(p.from))
newNode(varFor(p.to))
if (!p.length.isSimple) throw new UnsupportedOperationException("Cannot do a scan from a variable pattern")
p.dir match {
case SemanticDirection.OUTGOING =>
appendAtCurrentIndent(LeafOperator(DirectedAllRelationshipsScan(
varFor(p.relName),
varFor(p.from),
varFor(p.to),
args.map(varFor).toSet
)(_)))
case SemanticDirection.INCOMING =>
appendAtCurrentIndent(LeafOperator(DirectedAllRelationshipsScan(
varFor(p.relName),
varFor(p.to),
varFor(p.from),
args.map(varFor).toSet
)(_)))
case SemanticDirection.BOTH =>
appendAtCurrentIndent(LeafOperator(UndirectedAllRelationshipsScan(
varFor(p.relName),
varFor(p.from),
varFor(p.to),
args.map(varFor).toSet
)(_)))
}
}
def partitionedAllRelationshipsScan(pattern: String, args: String*): IMPL = {
val p = patternParser.parse(pattern)
newRelationship(varFor(p.relName))
newNode(varFor(p.from))
newNode(varFor(p.to))
if (!p.length.isSimple) throw new UnsupportedOperationException("Cannot do a scan from a variable pattern")
p.dir match {
case SemanticDirection.OUTGOING =>
appendAtCurrentIndent(LeafOperator(PartitionedDirectedAllRelationshipsScan(
varFor(p.relName),
varFor(p.from),
varFor(p.to),
args.map(varFor).toSet
)(_)))
case SemanticDirection.INCOMING =>
appendAtCurrentIndent(LeafOperator(PartitionedDirectedAllRelationshipsScan(
varFor(p.relName),
varFor(p.to),
varFor(p.from),
args.map(varFor).toSet
)(_)))
case SemanticDirection.BOTH =>
appendAtCurrentIndent(LeafOperator(PartitionedUndirectedAllRelationshipsScan(
varFor(p.relName),
varFor(p.from),
varFor(p.to),
args.map(varFor).toSet
)(_)))
}
}
def relationshipTypeScan(pattern: String, args: String*): IMPL = {
relationshipTypeScan(pattern, IndexOrderNone, args: _*)
}
def relationshipTypeScan(pattern: String, indexOrder: IndexOrder, args: String*): IMPL = {
val p = patternParser.parse(pattern)
newRelationship(varFor(p.relName))
newNode(varFor(p.from))
newNode(varFor(p.to))
if (!p.length.isSimple) throw new UnsupportedOperationException("Cannot do a scan from a variable pattern")
val typ =
if (p.relTypes.size == 1) p.relTypes.head
else throw new UnsupportedOperationException("Cannot do a scan with multiple types")
p.dir match {
case SemanticDirection.OUTGOING =>
appendAtCurrentIndent(LeafOperator(DirectedRelationshipTypeScan(
varFor(p.relName),
varFor(p.from),
typ,
varFor(p.to),
args.map(varFor).toSet,
indexOrder
)(_)))
case SemanticDirection.INCOMING =>
appendAtCurrentIndent(LeafOperator(DirectedRelationshipTypeScan(
varFor(p.relName),
varFor(p.to),
typ,
varFor(p.from),
args.map(varFor).toSet,
indexOrder
)(_)))
case SemanticDirection.BOTH =>
appendAtCurrentIndent(LeafOperator(UndirectedRelationshipTypeScan(
varFor(p.relName),
varFor(p.from),
typ,
varFor(p.to),
args.map(varFor).toSet,
indexOrder
)(_)))
}
}
def partitionedRelationshipTypeScan(pattern: String, args: String*): IMPL = {
val p = patternParser.parse(pattern)
newRelationship(varFor(p.relName))
newNode(varFor(p.from))
newNode(varFor(p.to))
if (!p.length.isSimple) throw new UnsupportedOperationException("Cannot do a scan from a variable pattern")
val typ =
if (p.relTypes.size == 1) p.relTypes.head
else throw new UnsupportedOperationException("Cannot do a scan with multiple types")
p.dir match {
case SemanticDirection.OUTGOING =>
appendAtCurrentIndent(LeafOperator(PartitionedDirectedRelationshipTypeScan(
varFor(p.relName),
varFor(p.from),
typ,
varFor(p.to),
args.map(varFor).toSet
)(_)))
case SemanticDirection.INCOMING =>
appendAtCurrentIndent(LeafOperator(PartitionedDirectedRelationshipTypeScan(
varFor(p.relName),
varFor(p.to),
typ,
varFor(p.from),
args.map(varFor).toSet
)(_)))
case SemanticDirection.BOTH =>
appendAtCurrentIndent(LeafOperator(PartitionedUndirectedRelationshipTypeScan(
varFor(p.relName),
varFor(p.from),
typ,
varFor(p.to),
args.map(varFor).toSet
)(_)))
}
}
def nodeCountFromCountStore(name: String, labels: Seq[Option[String]], args: String*): IMPL = {
val labelNames = labels.map(maybeLabel => maybeLabel.map(labelName)).toList
appendAtCurrentIndent(LeafOperator(NodeCountFromCountStore(
varFor(name),
labelNames,
args.map(a => varFor(VariableParser.unescaped(a))).toSet
)(_)))
}
def relationshipCountFromCountStore(
name: String,
maybeStartLabel: Option[String],
relTypes: Seq[String],
maybeEndLabel: Option[String],
args: String*
): IMPL = {
val startLabel = maybeStartLabel.map(labelName)
val relTypeNames = relTypes.map(relTypeName)
val endLabel = maybeEndLabel.map(labelName)
appendAtCurrentIndent(LeafOperator(RelationshipCountFromCountStore(
varFor(name),
startLabel,
relTypeNames,
endLabel,
args.map(a => varFor(VariableParser.unescaped(a))).toSet
)(_)))
}
def nodeIndexOperator(
indexSeekString: String,
getValue: String => GetValueFromIndexBehavior = _ => DoNotGetValue,
indexOrder: IndexOrder = IndexOrderNone,
paramExpr: IterableOnce[Expression] = None,
argumentIds: Set[String] = Set.empty,
unique: Boolean = false,
customQueryExpression: Option[QueryExpression[Expression]] = None,
indexType: IndexType = IndexType.RANGE,
supportPartitionedScan: Boolean = true
): IMPL = {
val planBuilder = (idGen: IdGen) => {
val plan = nodeIndexSeek(
indexSeekString,
getValue,
indexOrder,
paramExpr.iterator.toSeq,
argumentIds,
unique,
customQueryExpression,
indexType,
supportPartitionedScan
)(idGen)
plan
}
appendAtCurrentIndent(LeafOperator(planBuilder))
}
def partitionedNodeIndexOperator(
indexSeekString: String,
getValue: String => GetValueFromIndexBehavior = _ => DoNotGetValue,
paramExpr: IterableOnce[Expression] = None,
argumentIds: Set[String] = Set.empty,
customQueryExpression: Option[QueryExpression[Expression]] = None,
indexType: IndexType = IndexType.RANGE
): IMPL = {
val planBuilder = (idGen: IdGen) => {
val plan = partitionedNodeIndexSeek(
indexSeekString,
getValue,
paramExpr.iterator.toSeq,
argumentIds,
customQueryExpression,
indexType
)(idGen)
plan
}
appendAtCurrentIndent(LeafOperator(planBuilder))
}
def relationshipIndexOperator(
indexSeekString: String,
getValue: String => GetValueFromIndexBehavior = _ => DoNotGetValue,
indexOrder: IndexOrder = IndexOrderNone,
paramExpr: Iterable[Expression] = Seq.empty,
argumentIds: Set[String] = Set.empty,
unique: Boolean = false,
customQueryExpression: Option[QueryExpression[Expression]] = None,
indexType: IndexType = IndexType.RANGE,
supportPartitionedScan: Boolean = true
): IMPL = {
val planBuilder = (idGen: IdGen) => {
val plan = relationshipIndexSeek(
indexSeekString,
getValue,
indexOrder,
paramExpr,
argumentIds,
unique,
customQueryExpression,
indexType,
supportPartitionedScan
)(idGen)
plan
}
appendAtCurrentIndent(LeafOperator(planBuilder))
}
def partitionedRelationshipIndexOperator(
indexSeekString: String,
getValue: String => GetValueFromIndexBehavior = _ => DoNotGetValue,
paramExpr: Iterable[Expression] = Seq.empty,
argumentIds: Set[String] = Set.empty,
customQueryExpression: Option[QueryExpression[Expression]] = None,
indexType: IndexType = IndexType.RANGE
): IMPL = {
val planBuilder = (idGen: IdGen) => {
val plan = partitionedRelationshipIndexSeek(
indexSeekString,
getValue,
paramExpr,
argumentIds,
customQueryExpression,
indexType
)(idGen)
plan
}
appendAtCurrentIndent(LeafOperator(planBuilder))
}
def nodeIndexSeek(
indexSeekString: String,
getValue: String => GetValueFromIndexBehavior = _ => DoNotGetValue,
indexOrder: IndexOrder = IndexOrderNone,
paramExpr: Iterable[Expression] = Seq.empty,
argumentIds: Set[String] = Set.empty,
unique: Boolean = false,
customQueryExpression: Option[QueryExpression[Expression]] = None,
indexType: IndexType = IndexType.RANGE,
supportPartitionedScan: Boolean = true
): IdGen => NodeIndexLeafPlan = {
val label = resolver.getLabelId(IndexSeek.labelFromIndexSeekString(indexSeekString))
val propIds: PartialFunction[String, Int] = {
case x => resolver.getPropertyKeyId(x)
}
val planBuilder = (idGen: IdGen) => {
val plan = IndexSeek.nodeIndexSeek(
indexSeekString,
getValue,
indexOrder,
paramExpr,
argumentIds,
Some(propIds),
label,
unique,
customQueryExpression,
indexType,
supportPartitionedScan
)(idGen)
newNode(varFor(plan.idName.name))
plan
}
planBuilder
}
def partitionedNodeIndexSeek(
indexSeekString: String,
getValue: String => GetValueFromIndexBehavior = _ => DoNotGetValue,
paramExpr: Iterable[Expression] = Seq.empty,
argumentIds: Set[String] = Set.empty,
customQueryExpression: Option[QueryExpression[Expression]] = None,
indexType: IndexType = IndexType.RANGE
): IdGen => NodeIndexLeafPlan = {
val label = resolver.getLabelId(IndexSeek.labelFromIndexSeekString(indexSeekString))
val propIds: PartialFunction[String, Int] = {
case x => resolver.getPropertyKeyId(x)
}
val planBuilder = (idGen: IdGen) => {
val plan = IndexSeek.partitionedNodeIndexSeek(
indexSeekString,
getValue,
paramExpr,
argumentIds,
Some(propIds),
label,
customQueryExpression,
indexType
)(idGen)
newNode(varFor(plan.idName.name))
plan
}
planBuilder
}
def relationshipIndexSeek(
indexSeekString: String,
getValue: String => GetValueFromIndexBehavior = _ => DoNotGetValue,
indexOrder: IndexOrder = IndexOrderNone,
paramExpr: Iterable[Expression] = Seq.empty,
argumentIds: Set[String] = Set.empty,
unique: Boolean = false,
customQueryExpression: Option[QueryExpression[Expression]] = None,
indexType: IndexType = IndexType.RANGE,
supportPartitionedScan: Boolean = true
): IdGen => RelationshipIndexLeafPlan = {
val relType = resolver.getRelTypeId(IndexSeek.relTypeFromIndexSeekString(indexSeekString))
val propIds: PartialFunction[String, Int] = {
case x => resolver.getPropertyKeyId(x)
}
val planBuilder = (idGen: IdGen) => {
val plan = IndexSeek.relationshipIndexSeek(
indexSeekString,
getValue,
indexOrder,
paramExpr,
argumentIds,
Some(propIds),
relType,
unique,
customQueryExpression,
indexType,
supportPartitionedScan
)(idGen)
newRelationship(varFor(plan.idName.name))
newNode(varFor(plan.leftNode.name))
newNode(varFor(plan.rightNode.name))
plan
}
planBuilder
}
def partitionedRelationshipIndexSeek(
indexSeekString: String,
getValue: String => GetValueFromIndexBehavior = _ => DoNotGetValue,
paramExpr: Iterable[Expression] = Seq.empty,
argumentIds: Set[String] = Set.empty,
customQueryExpression: Option[QueryExpression[Expression]] = None,
indexType: IndexType = IndexType.RANGE
): IdGen => RelationshipIndexLeafPlan = {
val relType = resolver.getRelTypeId(IndexSeek.relTypeFromIndexSeekString(indexSeekString))
val propIds: PartialFunction[String, Int] = {
case x => resolver.getPropertyKeyId(x)
}
val planBuilder = (idGen: IdGen) => {
val plan = IndexSeek.partitionedRelationshipIndexSeek(
indexSeekString,
getValue,
paramExpr,
argumentIds,
Some(propIds),
relType,
customQueryExpression,
indexType
)(idGen)
newRelationship(varFor(plan.idName.name))
newNode(varFor(plan.leftNode.name))
newNode(varFor(plan.rightNode.name))
plan
}
planBuilder
}
def multiNodeIndexSeekOperator(seeks: (IMPL => IdGen => NodeIndexLeafPlan)*): IMPL = {
val planBuilder = (idGen: IdGen) => {
MultiNodeIndexSeek(seeks.map(_(this)(idGen).asInstanceOf[NodeIndexSeekLeafPlan]))(idGen)
}
appendAtCurrentIndent(LeafOperator(planBuilder))
}
def pointDistanceNodeIndexSeek(
node: String,
labelName: String,
property: String,
point: String,
distance: Double,
getValue: GetValueFromIndexBehavior = DoNotGetValue,
indexOrder: IndexOrder = IndexOrderNone,
inclusive: Boolean = false,
argumentIds: Set[String] = Set.empty,
indexType: IndexType = IndexType.POINT
): IMPL = {
pointDistanceNodeIndexSeekExpr(
node,
labelName,
property,
point,
literalFloat(distance),
getValue,
indexOrder,
inclusive,
argumentIds,
indexType
)
}
def pointBoundingBoxNodeIndexSeek(
node: String,
labelName: String,
property: String,
lowerLeft: String,
upperRight: String,
getValue: GetValueFromIndexBehavior = DoNotGetValue,
indexOrder: IndexOrder = IndexOrderNone,
argumentIds: Set[String] = Set.empty,
indexType: IndexType = IndexType.POINT
): IMPL = {
pointBoundingBoxNodeIndexSeekExpr(
node,
labelName,
property,
lowerLeft,
upperRight,
getValue,
indexOrder,
argumentIds,
indexType
)
}
def pointDistanceNodeIndexSeekExpr(
node: String,
labelName: String,
property: String,
point: String,
distanceExpr: Expression,
getValue: GetValueFromIndexBehavior = DoNotGetValue,
indexOrder: IndexOrder = IndexOrderNone,
inclusive: Boolean = false,
argumentIds: Set[String] = Set.empty,
indexType: IndexType = IndexType.POINT
): IMPL = {
val label = resolver.getLabelId(labelName)
val propId = resolver.getPropertyKeyId(property)
val planBuilder = (idGen: IdGen) => {
val labelToken = LabelToken(labelName, LabelId(label))
val propToken = PropertyKeyToken(PropertyKeyName(property)(NONE), PropertyKeyId(propId))
val indexedProperty = IndexedProperty(propToken, getValue, NODE_TYPE)
val e =
RangeQueryExpression(PointDistanceSeekRangeWrapper(
PointDistanceRange(function("point", parseExpression(point)), distanceExpr, inclusive)
)(NONE))
val plan = NodeIndexSeek(
varFor(node),
labelToken,
Seq(indexedProperty),
e,
argumentIds.map(varFor),
indexOrder,
indexType,
supportPartitionedScan = false
)(idGen)
newNode(plan.idName)
plan
}
appendAtCurrentIndent(LeafOperator(planBuilder))
}
def pointBoundingBoxNodeIndexSeekExpr(
node: String,
labelName: String,
property: String,
lowerLeft: String,
upperRight: String,
getValue: GetValueFromIndexBehavior = DoNotGetValue,
indexOrder: IndexOrder = IndexOrderNone,
argumentIds: Set[String] = Set.empty,
indexType: IndexType = IndexType.POINT
): IMPL = {
val label = resolver.getLabelId(labelName)
val propId = resolver.getPropertyKeyId(property)
val planBuilder = (idGen: IdGen) => {
val labelToken = LabelToken(labelName, LabelId(label))
val propToken = PropertyKeyToken(PropertyKeyName(property)(NONE), PropertyKeyId(propId))
val indexedProperty = IndexedProperty(propToken, getValue, NODE_TYPE)
val e =
RangeQueryExpression(PointBoundingBoxSeekRangeWrapper(
PointBoundingBoxRange(
function("point", parseExpression(lowerLeft)),
function("point", parseExpression(upperRight))
)
)(NONE))
val plan = NodeIndexSeek(
varFor(node),
labelToken,
Seq(indexedProperty),
e,
argumentIds.map(varFor),
indexOrder,
indexType,
supportPartitionedScan = false
)(idGen)
newNode(plan.idName)
plan
}
appendAtCurrentIndent(LeafOperator(planBuilder))
}
def pointDistanceRelationshipIndexSeek(
rel: String,
start: String,
end: String,
typeName: String,
property: String,
point: String,
distance: Double,
directed: Boolean = true,
inclusive: Boolean = false,
getValue: GetValueFromIndexBehavior = DoNotGetValue,
indexOrder: IndexOrder = IndexOrderNone,
argumentIds: Set[String] = Set.empty,
indexType: IndexType = IndexType.POINT
): IMPL = {
pointDistanceRelationshipIndexSeekExpr(
rel,
start,
end,
typeName,
property,
point,
literalFloat(distance),
directed,
inclusive,
getValue,
indexOrder,
argumentIds,
indexType
)
}
def pointDistanceRelationshipIndexSeekExpr(
relationship: String,
startNode: String,
endNode: String,
typeName: String,
property: String,
point: String,
distanceExpr: Expression,
directed: Boolean = true,
inclusive: Boolean = false,
getValue: GetValueFromIndexBehavior = DoNotGetValue,
indexOrder: IndexOrder = IndexOrderNone,
argumentIds: Set[String] = Set.empty,
indexType: IndexType = IndexType.POINT
): IMPL = {
val typ = resolver.getRelTypeId(typeName)
val propId = resolver.getPropertyKeyId(property)
val planBuilder = (idGen: IdGen) => {
val typeToken = RelationshipTypeToken(typeName, RelTypeId(typ))
val propToken = PropertyKeyToken(PropertyKeyName(property)(NONE), PropertyKeyId(propId))
val indexedProperty = IndexedProperty(propToken, getValue, RELATIONSHIP_TYPE)
val e =
RangeQueryExpression(PointDistanceSeekRangeWrapper(
PointDistanceRange(function("point", parseExpression(point)), distanceExpr, inclusive)
)(NONE))
val plan =
if (directed) {
DirectedRelationshipIndexSeek(
varFor(relationship),
varFor(startNode),
varFor(endNode),
typeToken,
Seq(indexedProperty),
e,
argumentIds.map(varFor),
indexOrder,
indexType,
supportPartitionedScan = false
)(idGen)
} else {
UndirectedRelationshipIndexSeek(
varFor(relationship),
varFor(startNode),
varFor(endNode),
typeToken,
Seq(indexedProperty),
e,
argumentIds.map(varFor),
indexOrder,
indexType,
supportPartitionedScan = false
)(idGen)
}
plan
}
appendAtCurrentIndent(LeafOperator(planBuilder))
}
def pointBoundingBoxRelationshipIndexSeek(
rel: String,
start: String,
end: String,
typeName: String,
property: String,
lowerLeft: String,
upperRight: String,
directed: Boolean = true,
getValue: GetValueFromIndexBehavior = DoNotGetValue,
indexOrder: IndexOrder = IndexOrderNone,
argumentIds: Set[String] = Set.empty,
indexType: IndexType = IndexType.POINT
): IMPL = {
pointBoundingBoxRelationshipIndexSeekExpr(
rel,
start,
end,
typeName,
property,
lowerLeft,
upperRight,
directed,
getValue,
indexOrder,
argumentIds,
indexType
)
}
def pointBoundingBoxRelationshipIndexSeekExpr(
relationship: String,
startNode: String,
endNode: String,
typeName: String,
property: String,
lowerLeft: String,
upperRight: String,
directed: Boolean = true,
getValue: GetValueFromIndexBehavior = DoNotGetValue,
indexOrder: IndexOrder = IndexOrderNone,
argumentIds: Set[String] = Set.empty,
indexType: IndexType = IndexType.POINT
): IMPL = {
val typ = resolver.getRelTypeId(typeName)
val propId = resolver.getPropertyKeyId(property)
val planBuilder = (idGen: IdGen) => {
val typeToken = RelationshipTypeToken(typeName, RelTypeId(typ))
val propToken = PropertyKeyToken(PropertyKeyName(property)(NONE), PropertyKeyId(propId))
val indexedProperty = IndexedProperty(propToken, getValue, RELATIONSHIP_TYPE)
val e =
RangeQueryExpression(PointBoundingBoxSeekRangeWrapper(
PointBoundingBoxRange(
function("point", parseExpression(lowerLeft)),
function("point", parseExpression(upperRight))
)
)(NONE))
val plan =
if (directed) {
DirectedRelationshipIndexSeek(
varFor(relationship),
varFor(startNode),
varFor(endNode),
typeToken,
Seq(indexedProperty),
e,
argumentIds.map(varFor),
indexOrder,
indexType,
supportPartitionedScan = false
)(idGen)
} else {
UndirectedRelationshipIndexSeek(
varFor(relationship),
varFor(startNode),
varFor(endNode),
typeToken,
Seq(indexedProperty),
e,
argumentIds.map(varFor),
indexOrder,
indexType,
supportPartitionedScan = false
)(idGen)
}
plan
}
appendAtCurrentIndent(LeafOperator(planBuilder))
}
def aggregation(groupingExpressions: Seq[String], aggregationExpression: Seq[String]): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp => {
Aggregation(
lp,
toVarMap(Parser.parseProjections(groupingExpressions: _*)),
parseAggregationProjections(aggregationExpression: _*)
)(_)
}))
}
def aggregation(
groupingExpressions: Map[String, Expression],
aggregationExpressions: Map[String, Expression]
): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp => {
Aggregation(lp, toVarMap(groupingExpressions), toVarMap(aggregationExpressions))(_)
}))
}
def orderedAggregation(
groupingExpressions: Seq[String],
aggregationExpression: Seq[String],
orderToLeverage: Seq[String]
): IMPL = {
val order = orderToLeverage.map(parseExpression)
appendAtCurrentIndent(UnaryOperator(lp =>
OrderedAggregation(
lp,
toVarMap(Parser.parseProjections(groupingExpressions: _*)),
parseAggregationProjections(aggregationExpression: _*),
order
)(_)
))
}
def orderedAggregation(
groupingExpressions: Map[String, Expression],
aggregationExpressions: Map[String, Expression],
orderToLeverage: Seq[String]
): IMPL = {
val order = orderToLeverage.map(parseExpression)
appendAtCurrentIndent(UnaryOperator(lp => {
OrderedAggregation(lp, toVarMap(groupingExpressions), toVarMap(aggregationExpressions), order)(_)
}))
}
def apply(): IMPL =
appendAtCurrentIndent(BinaryOperator((lhs, rhs) => Apply(lhs, rhs)(_)))
def antiSemiApply(): IMPL =
appendAtCurrentIndent(BinaryOperator((lhs, rhs) => AntiSemiApply(lhs, rhs)(_)))
def semiApply(): IMPL =
appendAtCurrentIndent(BinaryOperator((lhs, rhs) => SemiApply(lhs, rhs)(_)))
def letAntiSemiApply(item: String): IMPL =
appendAtCurrentIndent(BinaryOperator((lhs, rhs) => LetAntiSemiApply(lhs, rhs, varFor(item))(_)))
def letSemiApply(item: String): IMPL =
appendAtCurrentIndent(BinaryOperator((lhs, rhs) => LetSemiApply(lhs, rhs, varFor(item))(_)))
def conditionalApply(items: String*): IMPL =
appendAtCurrentIndent(BinaryOperator((lhs, rhs) => ConditionalApply(lhs, rhs, items.map(varFor))(_)))
def antiConditionalApply(items: String*): IMPL =
appendAtCurrentIndent(BinaryOperator((lhs, rhs) => AntiConditionalApply(lhs, rhs, items.map(varFor))(_)))
def selectOrSemiApply(predicateString: String): IMPL = {
appendAtCurrentIndent(BinaryOperator((lhs, rhs) => {
SelectOrSemiApply(lhs, rhs, parseExpression(predicateString))(_)
}))
}
def selectOrSemiApply(predicate: Expression): IMPL =
appendAtCurrentIndent(BinaryOperator((lhs, rhs) => SelectOrSemiApply(lhs, rhs, predicate)(_)))
def selectOrAntiSemiApply(predicateString: String): IMPL = {
appendAtCurrentIndent(BinaryOperator((lhs, rhs) => {
SelectOrAntiSemiApply(lhs, rhs, parseExpression(predicateString))(_)
}))
}
def letSelectOrSemiApply(idName: String, predicateString: String): IMPL = {
appendAtCurrentIndent(BinaryOperator((lhs, rhs) => {
LetSelectOrSemiApply(lhs, rhs, varFor(idName), parseExpression(predicateString))(_)
}))
}
def letSelectOrAntiSemiApply(idName: String, predicateString: String): IMPL = {
appendAtCurrentIndent(BinaryOperator((lhs, rhs) => {
LetSelectOrAntiSemiApply(lhs, rhs, varFor(idName), parseExpression(predicateString))(_)
}))
}
def rollUpApply(collectionName: String, variableToCollect: String): IMPL =
appendAtCurrentIndent(BinaryOperator((lhs, rhs) =>
RollUpApply(lhs, rhs, varFor(collectionName), varFor(variableToCollect))(_)
))
def foreachApply(variable: String, expression: String): IMPL =
appendAtCurrentIndent(BinaryOperator((lhs, rhs) =>
ForeachApply(lhs, rhs, varFor(variable), parseExpression(expression))(_)
))
def foreachApply(variable: String, expression: Expression): IMPL =
appendAtCurrentIndent(BinaryOperator((lhs, rhs) =>
ForeachApply(lhs, rhs, varFor(variable), expression)(_)
))
def foreach(variable: String, expression: String, mutations: Seq[SimpleMutatingPattern]): IMPL =
appendAtCurrentIndent(UnaryOperator(lp => Foreach(lp, varFor(variable), parseExpression(expression), mutations)(_)))
def subqueryForeach(): IMPL =
appendAtCurrentIndent(BinaryOperator((lhs, rhs) => SubqueryForeach(lhs, rhs)(_)))
def cartesianProduct(): IMPL =
appendAtCurrentIndent(BinaryOperator((lhs, rhs) => CartesianProduct(lhs, rhs)(_)))
def union(): IMPL =
appendAtCurrentIndent(BinaryOperator((lhs, rhs) => Union(lhs, rhs)(_)))
def assertSameNode(node: String): IMPL =
appendAtCurrentIndent(BinaryOperator((lhs, rhs) => AssertSameNode(varFor(node), lhs, rhs)(_)))
def assertSameRelationship(idName: String): IMPL =
appendAtCurrentIndent(BinaryOperator((lhs, rhs) => AssertSameRelationship(varFor(idName), lhs, rhs)(_)))
def orderedUnion(sortedOn: String*): IMPL =
orderedUnionColumns(Parser.parseSort(sortedOn))
def orderedUnionColumns(sortedOn: Seq[ColumnOrder]): IMPL =
appendAtCurrentIndent(BinaryOperator((lhs, rhs) => OrderedUnion(lhs, rhs, sortedOn)(_)))
def expandAll(pattern: String): IMPL = expand(pattern, ExpandAll)
def nonFuseable(): IMPL =
appendAtCurrentIndent(UnaryOperator(lp => NonFuseable(lp)(_)))
def nonPipelined(): IMPL =
appendAtCurrentIndent(UnaryOperator(lp => NonPipelined(lp)(_)))
def nonPipelinedStreaming(expandFactor: Long = 1L): IMPL =
appendAtCurrentIndent(UnaryOperator(lp => NonPipelinedStreaming(lp, expandFactor)(_)))
def prober(probe: Prober.Probe): IMPL =
appendAtCurrentIndent(UnaryOperator(lp => Prober(lp, probe)(_)))
def cacheProperties(properties: String*): IMPL = {
cacheProperties(properties.map(parseExpression(_).asInstanceOf[LogicalProperty]).toSet)
}
def cacheProperties(properties: Set[LogicalProperty]): IMPL = {
appendAtCurrentIndent(UnaryOperator(source => CacheProperties(source, properties)(_)))
}
def remoteBatchProperties(properties: String*): IMPL = {
remoteBatchProperties(properties.map(parseExpression(_).asInstanceOf[LogicalProperty]).toSet)
}
def remoteBatchProperties(properties: Set[LogicalProperty]): IMPL = {
appendAtCurrentIndent(UnaryOperator(source => RemoteBatchProperties(source, properties)(_)))
}
def setProperty(entity: String, propertyKey: String, value: String): IMPL = {
appendAtCurrentIndent(UnaryOperator(source =>
SetProperty(source, parseExpression(entity), PropertyKeyName(propertyKey)(pos), parseExpression(value))(_)
))
}
def setDynamicProperty(entityExpression: String, propertyString: String, valueExpression: String): IMPL = {
appendAtCurrentIndent(UnaryOperator(source =>
SetDynamicProperty(
source,
parseExpression(entityExpression),
parseExpression(propertyString),
parseExpression(valueExpression)
)(_)
))
}
def setProperty(entity: Expression, propertyKey: String, value: Expression): IMPL = {
appendAtCurrentIndent(UnaryOperator(source =>
SetProperty(source, entity, PropertyKeyName(propertyKey)(pos), value)(_)
))
}
def setNodeProperty(node: String, propertyKey: String, value: String): IMPL = {
appendAtCurrentIndent(UnaryOperator(source =>
SetNodeProperty(source, varFor(node), PropertyKeyName(propertyKey)(pos), parseExpression(value))(_)
))
}
def setNodeProperty(node: String, propertyKey: String, value: Expression): IMPL = {
appendAtCurrentIndent(UnaryOperator(source =>
SetNodeProperty(source, varFor(node), PropertyKeyName(propertyKey)(pos), value)(_)
))
}
def setRelationshipProperty(relationship: String, propertyKey: String, value: String): IMPL = {
appendAtCurrentIndent(
UnaryOperator(source =>
SetRelationshipProperty(
source,
varFor(relationship),
PropertyKeyName(propertyKey)(pos),
parseExpression(value)
)(_)
)
)
}
def setRelationshipProperty(relationship: String, propertyKey: String, value: Expression): IMPL = {
appendAtCurrentIndent(
UnaryOperator(source =>
SetRelationshipProperty(
source,
varFor(relationship),
PropertyKeyName(propertyKey)(pos),
value
)(_)
)
)
}
def setProperties(entity: String, items: (String, String)*): IMPL = {
appendAtCurrentIndent(UnaryOperator(source =>
SetProperties(
source,
parseExpression(entity),
items.map(item => (PropertyKeyName(item._1)(pos), parseExpression(item._2)))
)(_)
))
}
def setPropertiesExpression(entity: Expression, items: (String, Expression)*): IMPL = {
appendAtCurrentIndent(UnaryOperator(source =>
SetProperties(
source,
entity,
items.map(item => (PropertyKeyName(item._1)(pos), item._2))
)(_)
))
}
def setNodeProperties(node: String, items: (String, String)*): IMPL = {
appendAtCurrentIndent(UnaryOperator(source =>
SetNodeProperties(
source,
varFor(node),
items.map(item => (PropertyKeyName(item._1)(pos), parseExpression(item._2)))
)(_)
))
}
def setNodePropertiesExpression(node: String, items: (String, Expression)*): IMPL = {
appendAtCurrentIndent(UnaryOperator(source =>
SetNodeProperties(
source,
varFor(node),
items.map(item => (PropertyKeyName(item._1)(pos), item._2))
)(_)
))
}
def setRelationshipProperties(relationship: String, items: (String, String)*): IMPL = {
appendAtCurrentIndent(UnaryOperator(source =>
SetRelationshipProperties(
source,
varFor(relationship),
items.map(item => (PropertyKeyName(item._1)(pos), parseExpression(item._2)))
)(_)
))
}
def setRelationshipPropertiesExpression(relationship: String, items: (String, Expression)*): IMPL = {
appendAtCurrentIndent(UnaryOperator(source =>
SetRelationshipProperties(
source,
varFor(relationship),
items.map(item => (PropertyKeyName(item._1)(pos), item._2))
)(_)
))
}
def setPropertiesFromMap(entity: String, map: String, removeOtherProps: Boolean): IMPL = {
appendAtCurrentIndent(UnaryOperator(source =>
SetPropertiesFromMap(source, parseExpression(entity), parseExpression(map), removeOtherProps)(_)
))
}
def setPropertiesFromMap(entity: String, map: Expression, removeOtherProps: Boolean): IMPL = {
appendAtCurrentIndent(UnaryOperator(source =>
SetPropertiesFromMap(source, parseExpression(entity), map, removeOtherProps)(_)
))
}
def setNodePropertiesFromMap(node: String, map: String, removeOtherProps: Boolean): IMPL = {
appendAtCurrentIndent(UnaryOperator(source =>
SetNodePropertiesFromMap(source, varFor(node), parseExpression(map), removeOtherProps)(_)
))
}
def setNodePropertiesFromMap(node: String, map: Expression, removeOtherProps: Boolean): IMPL = {
appendAtCurrentIndent(UnaryOperator(source =>
SetNodePropertiesFromMap(source, varFor(node), map, removeOtherProps)(_)
))
}
def setRelationshipPropertiesFromMap(relationship: String, map: String, removeOtherProps: Boolean): IMPL = {
appendAtCurrentIndent(UnaryOperator(source =>
SetRelationshipPropertiesFromMap(source, varFor(relationship), parseExpression(map), removeOtherProps)(_)
))
}
def setRelationshipPropertiesFromMap(relationship: String, map: Expression, removeOtherProps: Boolean): IMPL = {
appendAtCurrentIndent(UnaryOperator(source =>
SetRelationshipPropertiesFromMap(source, varFor(relationship), map, removeOtherProps)(_)
))
}
def create(commands: CreateCommand*): IMPL = {
commands.foreach {
case node: CreateNode => newNode(VariableParser.unescaped(node.variable))
case relationship: CreateRelationship =>
newRelationship(VariableParser.unescaped(relationship.variable))
newNode(VariableParser.unescaped(relationship.startNode))
newNode(VariableParser.unescaped(relationship.endNode))
}
appendAtCurrentIndent(UnaryOperator(source => Create(source, commands)(_)))
}
def merge(
nodes: Seq[CreateNode] = Seq.empty,
relationships: Seq[CreateRelationship] = Seq.empty,
onMatch: Seq[SetMutatingPattern] = Seq.empty,
onCreate: Seq[SetMutatingPattern] = Seq.empty,
lockNodes: Set[String] = Set.empty
): IMPL = {
appendAtCurrentIndent(UnaryOperator(source =>
Merge(source, nodes, relationships, onMatch, onCreate, lockNodes.map(varFor))(_)
))
}
def nodeHashJoin(nodes: String*): IMPL = {
appendAtCurrentIndent(BinaryOperator((left, right) => NodeHashJoin(nodes.map(varFor).toSet, left, right)(_)))
}
def rightOuterHashJoin(nodes: String*): IMPL = {
appendAtCurrentIndent(BinaryOperator((left, right) => RightOuterHashJoin(nodes.map(varFor).toSet, left, right)(_)))
}
def leftOuterHashJoin(nodes: String*): IMPL = {
appendAtCurrentIndent(BinaryOperator((left, right) => LeftOuterHashJoin(nodes.map(varFor).toSet, left, right)(_)))
}
def valueHashJoin(predicate: String): IMPL = {
val expression = parseExpression(predicate)
expression match {
case e: Equals =>
appendAtCurrentIndent(BinaryOperator((left, right) => ValueHashJoin(left, right, e)(_)))
case _ => throw new IllegalArgumentException(s"can't join on $expression")
}
}
def input(
nodes: Seq[String] = Seq.empty,
relationships: Seq[String] = Seq.empty,
variables: Seq[String] = Seq.empty,
nullable: Boolean = true
): IMPL = {
if (indent != 0) {
throw new IllegalStateException("The input operator has to be the left-most leaf of the plan")
}
if (
nodes.toSet.size < nodes.size || relationships.toSet.size < relationships.size || variables.toSet.size < variables.size
) {
throw new IllegalArgumentException("Input must create unique variables")
}
newNodes(nodes)
newRelationships(relationships)
newVariables(variables)
appendAtCurrentIndent(LeafOperator(Input(
nodes.map(varFor),
relationships.map(varFor),
variables.map(varFor),
nullable
)(_)))
}
def injectCompilationError(): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp => {
InjectCompilationError(lp)(_)
}))
}
def filter(predicateStrings: String*): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp => {
Selection(predicateStrings.map(parseExpression), lp)(_)
}))
}
def simulatedFilter(selectivity: Double): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp => {
SimulatedSelection(lp, selectivity)(_)
}))
}
def filterExpression(predicateExpressions: Expression*): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp =>
Selection(predicateExpressions.map(_.endoRewrite(expressionRewriter)), lp)(_)
))
}
def filterExpressionOrString(predicateExpressionsOrStrings: AnyRef*): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp => {
val predicates = predicateExpressionsOrStrings.map {
case s: String => parseExpression(s)
case e: Expression => e.endoRewrite(expressionRewriter)
case other => throw new IllegalArgumentException(
s"Expected Expression or String, got [${other.getClass.getSimpleName}] $other}"
)
}
Selection(predicates, lp)(_)
}))
}
def errorPlan(e: Exception): IMPL = {
appendAtCurrentIndent(UnaryOperator(lp => ErrorPlan(lp, e)(_)))
}
def nestedPlanCollectExpressionProjection(resultList: String, resultPart: String): IMPL = {
val inner = parseExpression(resultPart)
appendAtCurrentIndent(BinaryOperator((lhs, rhs) =>
// TODO Set.empty?
Projection(
lhs,
Map(varFor(resultList) -> NestedPlanCollectExpression(rhs, inner, "collect(...)")(NONE))
)(_)
))
}
def nestedPlanExistsExpressionProjection(resultList: String): IMPL = {
appendAtCurrentIndent(BinaryOperator((lhs, rhs) =>
Projection(lhs, Map(varFor(resultList) -> NestedPlanExistsExpression(rhs, "exists(...)")(NONE)))(_)
))
}
def nestedPlanGetByNameExpressionProjection(columnNameToGet: String, resultName: String): IMPL = {
appendAtCurrentIndent(BinaryOperator((lhs, rhs) =>
// TODO Set.empty?
Projection(
lhs,
Map(varFor(resultName) -> NestedPlanGetByNameExpression(rhs, varFor(columnNameToGet), "getByName(...)")(NONE))
)(_)
))
}
/**
* The right-hand side is a nested plan of a NestedPlanGetByNameExpression that is placed inside of
* the extract expression of a list comprehension on a literal list of strings.
*/
def nestedPlanGetByNameExpressionInListComprehensionProjection(
listElementVariable: String,
list: Seq[String],
columnNameToGet: String,
resultName: String
): IMPL = {
appendAtCurrentIndent(BinaryOperator((lhs, rhs) => {
val nestedPlanExpression = NestedPlanGetByNameExpression(rhs, varFor(columnNameToGet), "getByName(...)")(NONE)
val literalList = ListLiteral(list.map(StringLiteral(_)(NONE.withInputLength(0))))(NONE)
val listComprehension =
ListComprehension(varFor(listElementVariable), literalList, None, Some(nestedPlanExpression))(NONE)
Projection(
lhs,
Map(varFor(resultName) -> listComprehension)
)(_)
}))
}
def triadicSelection(positivePredicate: Boolean, sourceId: String, seenId: String, targetId: String): IMPL =
appendAtCurrentIndent(BinaryOperator((lhs, rhs) =>
TriadicSelection(lhs, rhs, positivePredicate, varFor(sourceId), varFor(seenId), varFor(targetId))(_)
))
def triadicBuild(triadicSelectionId: Int, sourceId: String, seenId: String): IMPL =
appendAtCurrentIndent(UnaryOperator(lp =>
TriadicBuild(lp, varFor(sourceId), varFor(seenId), Some(Id(triadicSelectionId)))(_)
))
def triadicFilter(triadicSelectionId: Int, positivePredicate: Boolean, sourceId: String, targetId: String): IMPL =
appendAtCurrentIndent(UnaryOperator(lp =>
TriadicFilter(lp, positivePredicate, varFor(sourceId), varFor(targetId), Some(Id(triadicSelectionId)))(_)
))
def loadCSV(url: String, variableName: String, format: CSVFormat, fieldTerminator: Option[String] = None): IMPL = {
val urlExpr = parseExpression(url)
appendAtCurrentIndent(UnaryOperator(lp =>
LoadCSV(
lp,
urlExpr,
varFor(variableName),
format,
fieldTerminator,
legacyCsvQuoteEscaping = GraphDatabaseSettings.csv_legacy_quote_escaping.defaultValue(),
csvBufferSize = GraphDatabaseSettings.csv_buffer_size.defaultValue().toInt
)(_)
))
}
def injectValue(variable: String, value: String): IMPL = {
val collection = s"${variable}Collection"
val level = indent
unwind(s"$collection AS $variable")
indent = level
projection(s"$collection + [$value] AS $collection")
indent = level
aggregation(Seq(), Seq(s"collect($variable) AS $collection"))
}
def transactionForeach(
batchSize: Long = TransactionForeach.defaultBatchSize,
concurrency: TransactionConcurrency = TransactionConcurrency.Serial,
onErrorBehaviour: InTransactionsOnErrorBehaviour = OnErrorFail,
maybeReportAs: Option[String] = None
): IMPL =
appendAtCurrentIndent(BinaryOperator((lhs, rhs) =>
TransactionForeach(
lhs,
rhs,
literalInt(batchSize),
concurrency,
onErrorBehaviour,
maybeReportAs.map(varFor)
)(_)
))
def transactionApply(
batchSize: Long,
concurrency: Int
): IMPL =
transactionApply(batchSize = batchSize, concurrency = TransactionConcurrency.Concurrent(concurrency))
def transactionApply(
batchSize: Long = TransactionForeach.defaultBatchSize,
concurrency: TransactionConcurrency = TransactionConcurrency.Serial,
onErrorBehaviour: InTransactionsOnErrorBehaviour = OnErrorFail,
maybeReportAs: Option[String] = None
): IMPL =
appendAtCurrentIndent(BinaryOperator((lhs, rhs) =>
TransactionApply(
lhs,
rhs,
literalInt(batchSize),
concurrency,
onErrorBehaviour,
maybeReportAs.map(varFor)
)(_)
))
def trail(
trailParameters: TrailParameters
): IMPL = {
// This one comes in as an argument, so we need to declare it as a node here
newNode(varFor(trailParameters.innerStart))
// This is the node we "expand-to" , so we need to declare it as a node here
newNode(varFor(trailParameters.end))
appendAtCurrentIndent(BinaryOperator((lhs, rhs) =>
Trail(
lhs,
rhs,
Repetition(trailParameters.min, trailParameters.max),
varFor(trailParameters.start),
varFor(trailParameters.end),
varFor(trailParameters.innerStart),
varFor(trailParameters.innerEnd),
trailParameters.groupNodes.map { case (inner, outer) => VariableGrouping(varFor(inner), varFor(outer))(pos) },
trailParameters.groupRelationships.map { case (inner, outer) =>
VariableGrouping(varFor(inner), varFor(outer))(pos)
},
trailParameters.innerRelationships.map(varFor),
trailParameters.previouslyBoundRelationships.map(varFor),
trailParameters.previouslyBoundRelationshipGroups.map(varFor),
trailParameters.reverseGroupVariableProjections
)(_)
))
}
def bidirectionalRepeatTrail(
trailParameters: TrailParameters
): IMPL = {
// These come in as arguments, so we need to declare them as nodes here
newNode(varFor(trailParameters.innerStart))
newNode(varFor(trailParameters.innerEnd))
appendAtCurrentIndent(BinaryOperator((lhs, rhs) =>
rhs match {
case repeatOptions: RepeatOptions =>
BidirectionalRepeatTrail(
lhs,
repeatOptions,
Repetition(trailParameters.min, trailParameters.max),
varFor(trailParameters.start),
varFor(trailParameters.end),
varFor(trailParameters.innerStart),
varFor(trailParameters.innerEnd),
trailParameters.groupNodes.map { case (inner, outer) =>
VariableGrouping(varFor(inner), varFor(outer))(pos)
},
trailParameters.groupRelationships.map { case (inner, outer) =>
VariableGrouping(varFor(inner), varFor(outer))(pos)
},
trailParameters.innerRelationships.map(varFor),
trailParameters.previouslyBoundRelationships.map(varFor),
trailParameters.previouslyBoundRelationshipGroups.map(varFor),
trailParameters.reverseGroupVariableProjections
)(_)
case _ => throw new IllegalArgumentException("BidirectionalRepeatTrail must have RepeatOptions as its RHS.")
}
))
}
def repeatOptions(): IMPL = {
appendAtCurrentIndent(BinaryOperator((lhs, rhs) =>
RepeatOptions(lhs, rhs)(_)
))
}
// SHIP IT
protected def buildLogicalPlan(): LogicalPlan = tree.build()
def getSemanticTable: SemanticTable = semanticTable
/**
* Called every time a new node is introduced by some logical operator.
*/
def newNode(node: LogicalVariable): Unit = {
semanticTable = semanticTable.addNode(node.asInstanceOf[Variable])
}
/**
* Called every time a new relationship is introduced by some logical operator.
*/
def newRelationship(relationship: LogicalVariable): Unit = {
semanticTable = semanticTable.addRelationship(relationship.asInstanceOf[Variable])
}
/**
* Called every time a new variable is introduced by some logical operator.
*/
def newVariable(variable: Variable): Unit = {
semanticTable = semanticTable.addTypeInfoCTAny(variable)
}
/**
* Called if a variable needs to have a specific type
*/
def newVariable(variable: Variable, typ: CypherType): Unit = {
semanticTable = semanticTable.addTypeInfo(variable, typ.invariant)
}
/**
* Called if a variable needs to have a specific type
*/
def newVariable(variable: Variable, typ: TypeSpec): Unit = {
semanticTable = semanticTable.addTypeInfo(variable, typ)
}
protected def newAlias(variable: LogicalVariable, expression: Expression): Unit = {
if (!semanticTable.types.contains(variable)) {
val typeInfo = semanticTable.types.get(expression).orElse(findTypeIgnoringPosition(expression))
val spec = typeInfo.map(_.actual).getOrElse(CTAny.invariant)
semanticTable = semanticTable.addTypeInfo(variable, spec)
}
}
private def findTypeIgnoringPosition(expr: Expression) =
semanticTable.types.iterator
.map { case (k, v) => (k.node, v) } // unwrap nodes to discard position
.collectFirst { case (`expr`, t) => t }
protected def newNodes(nodes: Iterable[String]): Seq[LogicalVariable] = {
val result = nodes.map(varFor).toSeq
result.foreach(newNode)
result
}
protected def newRelationships(relationships: Iterable[String]): Seq[LogicalVariable] = {
val result = relationships.map(varFor).toSeq
result.foreach(newRelationship)
result
}
protected def newVariables(variables: Iterable[String]): Seq[LogicalVariable] = {
val result = variables.map(varFor).toSeq
result.foreach(newVariable)
result
}
private val hasLabelsAndHasTypeNormalizer = new HasLabelsAndHasTypeNormalizer {
override def isNode(expr: Expression): Boolean = semanticTable.typeFor(expr).is(CTNode)
override def isRelationship(expr: Expression): Boolean = semanticTable.typeFor(expr).is(CTRelationship)
}
protected def expressionRewriter: Rewriter =
inSequence(hasLabelsAndHasTypeNormalizer, combineHasLabels, desugarMapProjection.instance)
/**
* Returns the finalized output of the builder.
*/
protected def build(readOnly: Boolean = true): T
// HELPERS
private def parseExpression(expression: String): Expression = {
(Parser.parseExpression(expression) match {
case f: FunctionInvocation if f.needsToBeResolved =>
ResolvedFunctionInvocation(resolver.functionSignature)(f).coerceArguments
case e => e
}).endoRewrite(expressionRewriter)
}
private def parseProjections(projections: String*): Map[LogicalVariable, Expression] = {
toVarMap(Parser.parseProjections(projections: _*)).view.mapValues {
case f: FunctionInvocation if f.needsToBeResolved =>
ResolvedFunctionInvocation(resolver.functionSignature)(f).coerceArguments
case e => e
}.toMap
}
private def parseAggregationProjections(projections: String*): Map[LogicalVariable, Expression] = {
toVarMap(Parser.parseAggregationProjections(projections: _*)).view.mapValues {
case f: FunctionInvocation if f.needsToBeResolved =>
ResolvedFunctionInvocation(resolver.functionSignature)(f).coerceArguments
case e => e
}.toMap
}
protected def appendAtCurrentIndent(operatorBuilder: OperatorBuilder): IMPL = {
if (tree == null) {
if (wholePlan) {
throw new IllegalStateException("Must call produceResult before adding other operators.")
} else {
tree = new Tree(operatorBuilder)
looseEnds += tree
}
} else {
val newTree = new Tree(operatorBuilder)
def appendAtIndent(): Unit = {
val parent = looseEnds(indent)
parent.left = Some(newTree)
looseEnds(indent) = newTree
}
indent - (looseEnds.size - 1) match {
case 1 => // new rhs
val parent = looseEnds.last
parent.right = Some(newTree)
looseEnds += newTree
case 0 => // append to lhs
appendAtIndent()
case -1 => // end of rhs
appendAtIndent()
looseEnds.remove(looseEnds.size - 1)
case _ =>
throw new IllegalStateException("out of bounds")
}
indent = 0
}
self
}
// AST construction
protected def varFor(name: String): Variable = Variable(name)(pos)
private def labelName(s: String): LabelName = LabelName(s)(pos)
private def relTypeName(s: String): RelTypeName = RelTypeName(s)(pos)
private def literalInt(value: Long): SignedDecimalIntegerLiteral =
SignedDecimalIntegerLiteral(value.toString)(pos)
private def literalFloat(value: Double): DecimalDoubleLiteral =
DecimalDoubleLiteral(value.toString)(pos)
def literalString(str: String): StringLiteral = StringLiteral(str)(pos.withInputLength(0))
def function(name: String, args: Expression*): FunctionInvocation =
FunctionInvocation(FunctionName(name)(pos), distinct = false, args.toIndexedSeq)(pos)
}
object AbstractLogicalPlanBuilder {
val pos: InputPosition = InputPosition.NONE
case class Predicate(entity: String, predicate: String) {
def asVariablePredicate: VariablePredicate =
VariablePredicate(Variable(entity)(pos), Parser.parseExpression(predicate))
}
case class TrailParameters(
min: Int,
max: UpperBound,
start: String,
end: String,
innerStart: String,
innerEnd: String,
groupNodes: Set[(String, String)],
groupRelationships: Set[(String, String)],
innerRelationships: Set[String],
previouslyBoundRelationships: Set[String],
previouslyBoundRelationshipGroups: Set[String],
reverseGroupVariableProjections: Boolean
)
def createPattern(
nodes: Seq[CreateNode] = Seq.empty,
relationships: Seq[CreateRelationship] = Seq.empty
): CreatePattern = {
CreatePattern(nodes ++ relationships)
}
def createNode(node: String, labels: String*): CreateNode =
CreateNode(varFor(node), labels.map(LabelName(_)(pos)).toSet, None)
def createNodeWithProperties(node: String, labels: Seq[String], properties: String): CreateNode =
CreateNode(varFor(node), labels.map(LabelName(_)(pos)).toSet, Some(Parser.parseExpression(properties)))
def createNodeWithProperties(node: String, labels: Seq[String], properties: MapExpression): CreateNode =
CreateNode(varFor(node), labels.map(LabelName(_)(pos)).toSet, Some(properties))
def createRelationship(
relationship: String,
left: String,
typ: String,
right: String,
direction: SemanticDirection = OUTGOING,
properties: Option[String] = None
): CreateRelationship = {
val props = properties.map(Parser.parseExpression)
if (props.exists(!_.isInstanceOf[MapExpression]))
throw new IllegalArgumentException("Property must be a Map Expression")
CreateRelationship(
varFor(relationship),
varFor(left),
RelTypeName(typ)(pos),
varFor(right),
direction,
props
)
}
def createRelationshipExpression(
relationship: String,
left: String,
typ: String,
right: String,
direction: SemanticDirection = OUTGOING,
properties: Option[MapExpression] = None
): CreateRelationship = {
CreateRelationship(
varFor(relationship),
varFor(left),
RelTypeName(typ)(pos),
varFor(right),
direction,
properties
)
}
def setNodeProperty(node: String, key: String, value: String): SetMutatingPattern =
SetNodePropertyPattern(varFor(node), PropertyKeyName(key)(InputPosition.NONE), Parser.parseExpression(value))
def setNodeProperties(node: String, items: (String, String)*): SetMutatingPattern =
SetNodePropertiesPattern(
varFor(node),
items.map(i => (PropertyKeyName(i._1)(InputPosition.NONE), Parser.parseExpression(i._2)))
)
def setNodePropertiesFromMap(node: String, map: String, removeOtherProps: Boolean = true): SetMutatingPattern =
SetNodePropertiesFromMapPattern(varFor(node), Parser.parseExpression(map), removeOtherProps)
def setRelationshipProperty(relationship: String, key: String, value: String): SetMutatingPattern =
SetRelationshipPropertyPattern(
varFor(relationship),
PropertyKeyName(key)(InputPosition.NONE),
Parser.parseExpression(value)
)
def setRelationshipProperties(rel: String, items: (String, String)*): SetMutatingPattern =
SetRelationshipPropertiesPattern(
varFor(rel),
items.map(i => (PropertyKeyName(i._1)(InputPosition.NONE), Parser.parseExpression(i._2)))
)
def setRelationshipPropertiesFromMap(
node: String,
map: String,
removeOtherProps: Boolean = true
): SetMutatingPattern =
SetRelationshipPropertiesFromMapPattern(varFor(node), Parser.parseExpression(map), removeOtherProps)
def setProperty(entity: String, key: String, value: String): SetMutatingPattern =
SetPropertyPattern(
Parser.parseExpression(entity),
PropertyKeyName(key)(InputPosition.NONE),
Parser.parseExpression(value)
)
def setDynamicProperty(entity: String, key: String, value: String): SetMutatingPattern =
SetDynamicPropertyPattern(varFor(entity), Parser.parseExpression(key), Parser.parseExpression(value))
def setProperties(entity: String, items: (String, String)*): SetMutatingPattern =
SetPropertiesPattern(
Parser.parseExpression(entity),
items.map(i => (PropertyKeyName(i._1)(InputPosition.NONE), Parser.parseExpression(i._2)))
)
def setPropertyFromMap(entity: String, map: String, removeOtherProps: Boolean = true): SetMutatingPattern =
SetPropertiesFromMapPattern(Parser.parseExpression(entity), Parser.parseExpression(map), removeOtherProps)
def setLabel(node: String, labels: String*): SetMutatingPattern =
SetLabelPattern(varFor(node), labels.map(l => LabelName(l)(InputPosition.NONE)), Seq.empty)
def setLabel(node: String, labels: Seq[String], labelExpressions: Seq[String]): SetMutatingPattern =
SetLabelPattern(
varFor(node),
labels.map(l => LabelName(l)(InputPosition.NONE)),
labelExpressions.map(e => Parser.parseExpression(e))
)
def setDynamicLabel(node: String, labels: String*): SetMutatingPattern =
SetLabelPattern(varFor(node), Seq.empty, labels.map(l => Parser.parseExpression(l)))
def removeLabel(node: String, labels: String*): RemoveLabelPattern =
RemoveLabelPattern(varFor(node), labels.map(l => LabelName(l)(InputPosition.NONE)), Seq.empty)
def removeLabel(node: String, labels: Seq[String], labelExpressions: Seq[String]): SetMutatingPattern =
RemoveLabelPattern(
varFor(node),
labels.map(l => LabelName(l)(InputPosition.NONE)),
labelExpressions.map(e => Parser.parseExpression(e))
)
def removeDynamicLabel(node: String, labels: String*): SetMutatingPattern =
RemoveLabelPattern(varFor(node), Seq.empty, labels.map(l => Parser.parseExpression(l)))
def delete(entity: String, forced: Boolean = false): org.neo4j.cypher.internal.ir.DeleteExpression =
org.neo4j.cypher.internal.ir.DeleteExpression(Parser.parseExpression(entity), forced)
def andsReorderable(predicateExpressionsOrStrings: AnyRef*): AndsReorderable = {
val predicates = predicateExpressionsOrStrings.map {
case s: String => Parser.parseExpression(s)
case e: Expression => e
case other => throw new IllegalArgumentException(
s"Expected Expression or String, got [${other.getClass.getSimpleName}] $other}"
)
}
AndsReorderable(ListSet.from(predicates))(pos)
}
def column(name: String, cachedProperties: String*): Column = {
Column(varFor(name), cachedProperties.map(cp => Parser.parseExpression(cp).asInstanceOf[ASTCachedProperty]).toSet)
}
}