All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.neo4j.cypher.internal.runtime.slotted.SlottedPipeMapper.scala Maven / Gradle / Ivy

There is a newer version: 5.26.0
Show newest version
/*
 * 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.runtime.slotted

import org.neo4j.cypher.internal
import org.neo4j.cypher.internal.ast.semantics.TokenTable
import org.neo4j.cypher.internal.expressions.Equals
import org.neo4j.cypher.internal.expressions.LogicalVariable
import org.neo4j.cypher.internal.expressions.SemanticDirection
import org.neo4j.cypher.internal.expressions.SignedDecimalIntegerLiteral
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.RemoveLabelPattern
import org.neo4j.cypher.internal.ir.SetDynamicPropertyPattern
import org.neo4j.cypher.internal.ir.SetLabelPattern
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.SimpleMutatingPattern
import org.neo4j.cypher.internal.ir.VarPatternLength
import org.neo4j.cypher.internal.logical.plans
import org.neo4j.cypher.internal.logical.plans.Aggregation
import org.neo4j.cypher.internal.logical.plans.AllNodesScan
import org.neo4j.cypher.internal.logical.plans.AntiConditionalApply
import org.neo4j.cypher.internal.logical.plans.Apply
import org.neo4j.cypher.internal.logical.plans.Argument
import org.neo4j.cypher.internal.logical.plans.AssertSameRelationship
import org.neo4j.cypher.internal.logical.plans.BFSPruningVarExpand
import org.neo4j.cypher.internal.logical.plans.CartesianProduct
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.DirectedRelationshipIndexContainsScan
import org.neo4j.cypher.internal.logical.plans.DirectedRelationshipIndexEndsWithScan
import org.neo4j.cypher.internal.logical.plans.DirectedRelationshipIndexScan
import org.neo4j.cypher.internal.logical.plans.DirectedRelationshipIndexSeek
import org.neo4j.cypher.internal.logical.plans.DirectedRelationshipTypeScan
import org.neo4j.cypher.internal.logical.plans.DirectedRelationshipUniqueIndexSeek
import org.neo4j.cypher.internal.logical.plans.DirectedUnionRelationshipTypesScan
import org.neo4j.cypher.internal.logical.plans.Distinct
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.FindShortestPaths
import org.neo4j.cypher.internal.logical.plans.FindShortestPaths.AllowSameNode
import org.neo4j.cypher.internal.logical.plans.FindShortestPaths.DisallowSameNode
import org.neo4j.cypher.internal.logical.plans.Foreach
import org.neo4j.cypher.internal.logical.plans.ForeachApply
import org.neo4j.cypher.internal.logical.plans.IndexOrderNone
import org.neo4j.cypher.internal.logical.plans.InjectCompilationError
import org.neo4j.cypher.internal.logical.plans.IntersectionNodeByLabelsScan
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.Merge
import org.neo4j.cypher.internal.logical.plans.MultiNodeIndexSeek
import org.neo4j.cypher.internal.logical.plans.NodeByLabelScan
import org.neo4j.cypher.internal.logical.plans.NodeHashJoin
import org.neo4j.cypher.internal.logical.plans.NodeIndexContainsScan
import org.neo4j.cypher.internal.logical.plans.NodeIndexEndsWithScan
import org.neo4j.cypher.internal.logical.plans.NodeIndexScan
import org.neo4j.cypher.internal.logical.plans.NodeIndexSeek
import org.neo4j.cypher.internal.logical.plans.NodeUniqueIndexSeek
import org.neo4j.cypher.internal.logical.plans.NonFuseable
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.PartitionedDirectedRelationshipIndexScan
import org.neo4j.cypher.internal.logical.plans.PartitionedDirectedRelationshipIndexSeek
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.PartitionedNodeIndexScan
import org.neo4j.cypher.internal.logical.plans.PartitionedNodeIndexSeek
import org.neo4j.cypher.internal.logical.plans.PartitionedSubtractionNodeByLabelsScan
import org.neo4j.cypher.internal.logical.plans.PartitionedUndirectedAllRelationshipsScan
import org.neo4j.cypher.internal.logical.plans.PartitionedUndirectedRelationshipIndexScan
import org.neo4j.cypher.internal.logical.plans.PartitionedUndirectedRelationshipIndexSeek
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.Prober
import org.neo4j.cypher.internal.logical.plans.ProduceResult
import org.neo4j.cypher.internal.logical.plans.Projection
import org.neo4j.cypher.internal.logical.plans.RemoveLabels
import org.neo4j.cypher.internal.logical.plans.RepeatTrail
import org.neo4j.cypher.internal.logical.plans.RepeatWalk
import org.neo4j.cypher.internal.logical.plans.RollUpApply
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.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.Skip
import org.neo4j.cypher.internal.logical.plans.Sort
import org.neo4j.cypher.internal.logical.plans.StatefulShortestPath
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.TransactionApply
import org.neo4j.cypher.internal.logical.plans.TransactionConcurrency
import org.neo4j.cypher.internal.logical.plans.TransactionForeach
import org.neo4j.cypher.internal.logical.plans.UndirectedAllRelationshipsScan
import org.neo4j.cypher.internal.logical.plans.UndirectedRelationshipIndexContainsScan
import org.neo4j.cypher.internal.logical.plans.UndirectedRelationshipIndexEndsWithScan
import org.neo4j.cypher.internal.logical.plans.UndirectedRelationshipIndexScan
import org.neo4j.cypher.internal.logical.plans.UndirectedRelationshipIndexSeek
import org.neo4j.cypher.internal.logical.plans.UndirectedRelationshipTypeScan
import org.neo4j.cypher.internal.logical.plans.UndirectedRelationshipUniqueIndexSeek
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.macros.AssertMacros.checkOnlyWhenAssertionsAreEnabled
import org.neo4j.cypher.internal.physicalplanning.LongSlot
import org.neo4j.cypher.internal.physicalplanning.PhysicalPlan
import org.neo4j.cypher.internal.physicalplanning.RefSlot
import org.neo4j.cypher.internal.physicalplanning.Slot
import org.neo4j.cypher.internal.physicalplanning.SlotAllocation
import org.neo4j.cypher.internal.physicalplanning.SlotAllocation.LOAD_CSV_METADATA_KEY
import org.neo4j.cypher.internal.physicalplanning.SlotConfiguration
import org.neo4j.cypher.internal.physicalplanning.SlotConfiguration.ApplyPlanSlotKey
import org.neo4j.cypher.internal.physicalplanning.SlotConfiguration.CachedPropertySlotKey
import org.neo4j.cypher.internal.physicalplanning.SlotConfiguration.DuplicatedSlotKey
import org.neo4j.cypher.internal.physicalplanning.SlotConfiguration.MetaDataSlotKey
import org.neo4j.cypher.internal.physicalplanning.SlotConfiguration.OuterNestedApplyPlanSlotKey
import org.neo4j.cypher.internal.physicalplanning.SlotConfiguration.SlotWithKeyAndAliases
import org.neo4j.cypher.internal.physicalplanning.SlotConfiguration.VariableSlotKey
import org.neo4j.cypher.internal.physicalplanning.SlotConfiguration.isRefSlotAndNotAlias
import org.neo4j.cypher.internal.physicalplanning.SlotConfigurationUtils
import org.neo4j.cypher.internal.physicalplanning.SlotConfigurationUtils.finalizeSlotConfiguration
import org.neo4j.cypher.internal.physicalplanning.SlottedIndexedProperty
import org.neo4j.cypher.internal.physicalplanning.ast.NodeFromSlot
import org.neo4j.cypher.internal.physicalplanning.ast.NullCheckVariable
import org.neo4j.cypher.internal.physicalplanning.ast.RelationshipFromSlot
import org.neo4j.cypher.internal.runtime.CypherRow
import org.neo4j.cypher.internal.runtime.QueryIndexRegistrator
import org.neo4j.cypher.internal.runtime.ReadableRow
import org.neo4j.cypher.internal.runtime.interpreted.GroupingExpression
import org.neo4j.cypher.internal.runtime.interpreted.commands
import org.neo4j.cypher.internal.runtime.interpreted.commands.CommandNFA
import org.neo4j.cypher.internal.runtime.interpreted.commands.convert.ExpressionConverters
import org.neo4j.cypher.internal.runtime.interpreted.commands.expressions.AggregationExpression
import org.neo4j.cypher.internal.runtime.interpreted.commands.expressions.DeleteOperation
import org.neo4j.cypher.internal.runtime.interpreted.commands.expressions.Expression
import org.neo4j.cypher.internal.runtime.interpreted.commands.expressions.SideEffect
import org.neo4j.cypher.internal.runtime.interpreted.commands.predicates.Predicate
import org.neo4j.cypher.internal.runtime.interpreted.pipes.EagerAggregationPipe
import org.neo4j.cypher.internal.runtime.interpreted.pipes.EmptyResultPipe
import org.neo4j.cypher.internal.runtime.interpreted.pipes.IndexSeekModeFactory
import org.neo4j.cypher.internal.runtime.interpreted.pipes.LazyLabel
import org.neo4j.cypher.internal.runtime.interpreted.pipes.LazyPropertyKey
import org.neo4j.cypher.internal.runtime.interpreted.pipes.LazyType
import org.neo4j.cypher.internal.runtime.interpreted.pipes.MergePipe
import org.neo4j.cypher.internal.runtime.interpreted.pipes.OrderedAggregationPipe
import org.neo4j.cypher.internal.runtime.interpreted.pipes.PartialSortPipe
import org.neo4j.cypher.internal.runtime.interpreted.pipes.PartialTop1Pipe
import org.neo4j.cypher.internal.runtime.interpreted.pipes.PartialTopNPipe
import org.neo4j.cypher.internal.runtime.interpreted.pipes.Pipe
import org.neo4j.cypher.internal.runtime.interpreted.pipes.PipeMapper
import org.neo4j.cypher.internal.runtime.interpreted.pipes.ProduceResultsPipe
import org.neo4j.cypher.internal.runtime.interpreted.pipes.ProjectionPipe
import org.neo4j.cypher.internal.runtime.interpreted.pipes.QueryState
import org.neo4j.cypher.internal.runtime.interpreted.pipes.RelationshipTypes
import org.neo4j.cypher.internal.runtime.interpreted.pipes.SetDynamicPropertyOperation
import org.neo4j.cypher.internal.runtime.interpreted.pipes.SetPipe
import org.neo4j.cypher.internal.runtime.interpreted.pipes.SetPropertiesOperation
import org.neo4j.cypher.internal.runtime.interpreted.pipes.SetPropertyFromMapOperation
import org.neo4j.cypher.internal.runtime.interpreted.pipes.SetPropertyOperation
import org.neo4j.cypher.internal.runtime.interpreted.pipes.Top1Pipe
import org.neo4j.cypher.internal.runtime.interpreted.pipes.Top1WithTiesPipe
import org.neo4j.cypher.internal.runtime.interpreted.pipes.TopNPipe
import org.neo4j.cypher.internal.runtime.interpreted.pipes.TraversalPredicates
import org.neo4j.cypher.internal.runtime.slotted
import org.neo4j.cypher.internal.runtime.slotted.SlottedPipeMapper.DistinctAllPrimitive
import org.neo4j.cypher.internal.runtime.slotted.SlottedPipeMapper.DistinctWithReferences
import org.neo4j.cypher.internal.runtime.slotted.SlottedPipeMapper.computeSlotMappings
import org.neo4j.cypher.internal.runtime.slotted.SlottedPipeMapper.computeSlotsDifference
import org.neo4j.cypher.internal.runtime.slotted.SlottedPipeMapper.createProjectionForVariable
import org.neo4j.cypher.internal.runtime.slotted.SlottedPipeMapper.createProjectionsForResult
import org.neo4j.cypher.internal.runtime.slotted.SlottedPipeMapper.findDistinctPhysicalOp
import org.neo4j.cypher.internal.runtime.slotted.SlottedPipeMapper.partitionGroupingExpressions
import org.neo4j.cypher.internal.runtime.slotted.SlottedPipeMapper.translateColumnOrder
import org.neo4j.cypher.internal.runtime.slotted.aggregation.SlottedGroupingAggTable
import org.neo4j.cypher.internal.runtime.slotted.aggregation.SlottedNonGroupingAggTable
import org.neo4j.cypher.internal.runtime.slotted.aggregation.SlottedOrderedGroupingAggTable
import org.neo4j.cypher.internal.runtime.slotted.aggregation.SlottedOrderedNonGroupingAggTable
import org.neo4j.cypher.internal.runtime.slotted.aggregation.SlottedPrimitiveGroupingAggTable
import org.neo4j.cypher.internal.runtime.slotted.expressions.CreateSlottedNode
import org.neo4j.cypher.internal.runtime.slotted.expressions.CreateSlottedRelationship
import org.neo4j.cypher.internal.runtime.slotted.expressions.SlottedRemoveLabelsOperation
import org.neo4j.cypher.internal.runtime.slotted.pipes.AllNodesScanSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.AllOrderedDistinctSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.AllOrderedDistinctSlottedPrimitivePipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.AntiConditionalApplySlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.ApplySlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.ArgumentSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.AssertSameRelationshipSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.BFSPruningVarLengthExpandSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.CartesianProductSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.ConcurrentTransactionApplySlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.ConcurrentTransactionForeachSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.ConditionalApplySlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.CreateNodeSlottedCommand
import org.neo4j.cypher.internal.runtime.slotted.pipes.CreateRelationshipSlottedCommand
import org.neo4j.cypher.internal.runtime.slotted.pipes.CreateSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.DirectedAllRelationshipsScanSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.DirectedRelationshipIndexContainsScanSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.DirectedRelationshipIndexEndsWithScanSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.DirectedRelationshipIndexScanSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.DirectedRelationshipIndexSeekSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.DirectedRelationshipTypeScanSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.DirectedUnionRelationshipTypesScanSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.DistinctSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.DistinctSlottedPrimitivePipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.DistinctSlottedSinglePrimitivePipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.EagerSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.ExpandAllSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.ExpandIntoSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.ForeachSlottedApplyPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.ForeachSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.GroupSlot
import org.neo4j.cypher.internal.runtime.slotted.pipes.IntersectionNodesByLabelsScanSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.LoadCSVSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.LockingMergeSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.NodeHashJoinSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.NodeHashJoinSlottedPipe.KeyOffsets
import org.neo4j.cypher.internal.runtime.slotted.pipes.NodeHashJoinSlottedPipe.SlotMapping
import org.neo4j.cypher.internal.runtime.slotted.pipes.NodeHashJoinSlottedSingleNodePipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.NodeIndexContainsScanSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.NodeIndexEndsWithScanSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.NodeIndexScanSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.NodeIndexSeekSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.NodesByLabelScanSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.OptionalExpandAllSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.OptionalExpandIntoSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.OptionalSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.OrderedDistinctSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.OrderedDistinctSlottedPrimitivePipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.OrderedDistinctSlottedSinglePrimitivePipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.OrderedUnionSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.RepeatSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.RepeatSlottedPipe.TrailModeConstraint
import org.neo4j.cypher.internal.runtime.slotted.pipes.RepeatSlottedPipe.WalkModeConstraint
import org.neo4j.cypher.internal.runtime.slotted.pipes.RollUpApplySlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.SelectOrSemiApplySlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.ShortestPathSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.SlottedSetLabelsOperation
import org.neo4j.cypher.internal.runtime.slotted.pipes.SlottedSetNodePropertiesOperation
import org.neo4j.cypher.internal.runtime.slotted.pipes.SlottedSetNodePropertyFromMapOperation
import org.neo4j.cypher.internal.runtime.slotted.pipes.SlottedSetNodePropertyOperation
import org.neo4j.cypher.internal.runtime.slotted.pipes.SlottedSetRelationshipPropertiesOperation
import org.neo4j.cypher.internal.runtime.slotted.pipes.SlottedSetRelationshipPropertyFromMapOperation
import org.neo4j.cypher.internal.runtime.slotted.pipes.SlottedSetRelationshipPropertyOperation
import org.neo4j.cypher.internal.runtime.slotted.pipes.SortSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.StatefulShortestPathSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.SubtractionNodesByLabelsScanSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.TransactionApplySlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.TransactionForeachSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.UndirectedAllRelationshipsScanSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.UndirectedRelationshipIndexContainsScanSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.UndirectedRelationshipIndexEndsWithScanSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.UndirectedRelationshipIndexScanSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.UndirectedRelationshipIndexSeekSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.UndirectedRelationshipTypeScanSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.UndirectedUnionRelationshipTypesScanSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.UnionNodesByLabelsScanSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.UnionSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.UnwindSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.ValueHashJoinSlottedPipe
import org.neo4j.cypher.internal.runtime.slotted.pipes.VarLengthExpandSlottedPipe
import org.neo4j.cypher.internal.util.attribution.Id
import org.neo4j.cypher.internal.util.symbols.CTNode
import org.neo4j.cypher.internal.util.symbols.CTRelationship
import org.neo4j.exceptions.CantCompileQueryException
import org.neo4j.exceptions.InternalException
import org.neo4j.exceptions.ShortestPathCommonEndNodesForbiddenException.shortestPathCommonEndNodes
import org.neo4j.internal.kernel.api.helpers.traversal.SlotOrName
import org.neo4j.kernel.api.StatementConstants
import org.neo4j.values.storable.Values.NO_VALUE

import scala.annotation.nowarn
import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer

class SlottedPipeMapper(
  fallback: PipeMapper,
  expressionConverters: ExpressionConverters,
  physicalPlan: PhysicalPlan,
  readOnly: Boolean,
  indexRegistrator: QueryIndexRegistrator
)(implicit semanticTable: TokenTable)
    extends PipeMapper {

  override def onLeaf(plan: LogicalPlan): Pipe = {

    val id = plan.id
    val convertExpressions = (e: internal.expressions.Expression) => expressionConverters.toCommandExpression(id, e)
    val slots = physicalPlan.slotConfigurations(id)
    val argumentSize = physicalPlan.argumentSizes(id)
    finalizeSlotConfiguration(slots)

    val pipe = plan match {
      case AllNodesScan(column, _) =>
        AllNodesScanSlottedPipe(column.name, slots)(id)

      // Note: this plan shouldn't really be used here, but having it mapped here helps
      //      fallback and makes testing easier
      case PartitionedAllNodesScan(column, _) =>
        AllNodesScanSlottedPipe(column.name, slots)(id)

      case NodeIndexScan(column, label, properties, _, indexOrder, indexType, _) =>
        NodeIndexScanSlottedPipe(
          column.name,
          label,
          properties.map(SlottedIndexedProperty(column, _, slots)),
          indexRegistrator.registerQueryIndex(indexType, label, properties),
          indexOrder,
          slots
        )(id)

      case PartitionedNodeIndexScan(column, label, properties, _, indexType) =>
        NodeIndexScanSlottedPipe(
          column.name,
          label,
          properties.map(SlottedIndexedProperty(column, _, slots)),
          indexRegistrator.registerQueryIndex(indexType, label, properties),
          IndexOrderNone,
          slots
        )(id)

      case NodeIndexContainsScan(column, label, property, valueExpr, _, indexOrder, indexType) =>
        NodeIndexContainsScanSlottedPipe(
          column.name,
          label,
          SlottedIndexedProperty(column, property, slots),
          indexRegistrator.registerQueryIndex(indexType, label, property),
          convertExpressions(valueExpr),
          slots,
          indexOrder
        )(id)

      case NodeIndexEndsWithScan(column, label, property, valueExpr, _, indexOrder, indexType) =>
        NodeIndexEndsWithScanSlottedPipe(
          column.name,
          label,
          SlottedIndexedProperty(column, property, slots),
          indexRegistrator.registerQueryIndex(indexType, label, property),
          convertExpressions(valueExpr),
          slots,
          indexOrder
        )(id)

      case NodeIndexSeek(column, label, properties, valueExpr, _, indexOrder, indexType, _) =>
        val indexSeekMode = IndexSeekModeFactory(unique = false, readOnly = readOnly).fromQueryExpression(valueExpr)
        NodeIndexSeekSlottedPipe(
          column.name,
          label,
          properties.map(SlottedIndexedProperty(column, _, slots)).toIndexedSeq,
          indexRegistrator.registerQueryIndex(indexType, label, properties),
          valueExpr.map(convertExpressions),
          indexSeekMode,
          indexOrder,
          slots
        )(id)

      case PartitionedNodeIndexSeek(column, label, properties, valueExpr, _, indexType) =>
        val indexSeekMode = IndexSeekModeFactory(unique = false, readOnly = readOnly).fromQueryExpression(valueExpr)
        NodeIndexSeekSlottedPipe(
          column.name,
          label,
          properties.map(SlottedIndexedProperty(column, _, slots)).toIndexedSeq,
          indexRegistrator.registerQueryIndex(indexType, label, properties),
          valueExpr.map(convertExpressions),
          indexSeekMode,
          IndexOrderNone,
          slots
        )(id)

      case NodeUniqueIndexSeek(column, label, properties, valueExpr, _, indexOrder, indexType, _) =>
        val indexSeekMode = IndexSeekModeFactory(unique = true, readOnly = readOnly).fromQueryExpression(valueExpr)
        NodeIndexSeekSlottedPipe(
          column.name,
          label,
          properties.map(SlottedIndexedProperty(column, _, slots)).toIndexedSeq,
          indexRegistrator.registerQueryIndex(indexType, label, properties),
          valueExpr.map(convertExpressions),
          indexSeekMode,
          indexOrder,
          slots
        )(id = id)

      case NodeByLabelScan(column, label, _, indexOrder) =>
        indexRegistrator.registerLabelScan()
        NodesByLabelScanSlottedPipe(column.name, LazyLabel(label)(semanticTable), slots, indexOrder)(id)

      // Note: this plan shouldn't really be used here, but having it mapped here helps
      //      fallback and makes testing easier
      case PartitionedNodeByLabelScan(column, label, _) =>
        indexRegistrator.registerLabelScan()
        NodesByLabelScanSlottedPipe(column.name, LazyLabel(label)(semanticTable), slots, IndexOrderNone)(id)

      case UnionNodeByLabelsScan(column, labels, _, indexOrder) =>
        indexRegistrator.registerLabelScan()
        UnionNodesByLabelsScanSlottedPipe(
          slots.getLongOffsetFor(column),
          labels.map(label => LazyLabel(label)(semanticTable)),
          indexOrder
        )(id)

      case PartitionedUnionNodeByLabelsScan(column, labels, _) =>
        indexRegistrator.registerLabelScan()
        UnionNodesByLabelsScanSlottedPipe(
          slots.getLongOffsetFor(column),
          labels.map(label => LazyLabel(label)(semanticTable)),
          IndexOrderNone
        )(id)

      case IntersectionNodeByLabelsScan(column, labels, _, indexOrder) =>
        indexRegistrator.registerLabelScan()
        IntersectionNodesByLabelsScanSlottedPipe(
          slots.getLongOffsetFor(column),
          labels.map(label => LazyLabel(label)(semanticTable)),
          indexOrder
        )(id)

      case PartitionedIntersectionNodeByLabelsScan(column, labels, _) =>
        indexRegistrator.registerLabelScan()
        IntersectionNodesByLabelsScanSlottedPipe(
          slots.getLongOffsetFor(column),
          labels.map(label => LazyLabel(label)(semanticTable)),
          IndexOrderNone
        )(id)

      case SubtractionNodeByLabelsScan(column, positiveLabels, negativeLabels, _, indexOrder) =>
        indexRegistrator.registerLabelScan()
        SubtractionNodesByLabelsScanSlottedPipe(
          slots.getLongOffsetFor(column),
          positiveLabels.map(l => LazyLabel(l)(semanticTable)),
          negativeLabels.map(l => LazyLabel(l)(semanticTable)),
          indexOrder
        )(id)

      case PartitionedSubtractionNodeByLabelsScan(column, positiveLabels, negativeLabels, _) =>
        indexRegistrator.registerLabelScan()
        SubtractionNodesByLabelsScanSlottedPipe(
          slots.getLongOffsetFor(column),
          positiveLabels.map(l => LazyLabel(l)(semanticTable)),
          negativeLabels.map(l => LazyLabel(l)(semanticTable)),
          IndexOrderNone
        )(id)

      case DirectedRelationshipUniqueIndexSeek(
          column,
          leftNode,
          rightNode,
          typeToken,
          properties,
          valueExpr,
          _,
          indexOrder,
          indexType
        ) =>
        val indexSeekMode = IndexSeekModeFactory(unique = true, readOnly = readOnly).fromQueryExpression(valueExpr)
        DirectedRelationshipIndexSeekSlottedPipe(
          column.name,
          leftNode.name,
          rightNode.name,
          typeToken,
          properties.map(SlottedIndexedProperty(column, _, slots)).toIndexedSeq,
          indexRegistrator.registerQueryIndex(indexType, typeToken, properties),
          valueExpr.map(convertExpressions),
          indexSeekMode,
          indexOrder,
          slots
        )(id)

      case DirectedRelationshipIndexSeek(
          column,
          leftNode,
          rightNode,
          typeToken,
          properties,
          valueExpr,
          _,
          indexOrder,
          indexType,
          _
        ) =>
        val indexSeekMode = IndexSeekModeFactory(unique = false, readOnly = readOnly).fromQueryExpression(valueExpr)
        DirectedRelationshipIndexSeekSlottedPipe(
          column.name,
          leftNode.name,
          rightNode.name,
          typeToken,
          properties.map(SlottedIndexedProperty(column, _, slots)).toIndexedSeq,
          indexRegistrator.registerQueryIndex(indexType, typeToken, properties),
          valueExpr.map(convertExpressions),
          indexSeekMode,
          indexOrder,
          slots
        )(id)

      case PartitionedDirectedRelationshipIndexSeek(
          column,
          leftNode,
          rightNode,
          typeToken,
          properties,
          valueExpr,
          _,
          indexType
        ) =>
        val indexSeekMode = IndexSeekModeFactory(unique = false, readOnly = readOnly).fromQueryExpression(valueExpr)
        DirectedRelationshipIndexSeekSlottedPipe(
          column.name,
          leftNode.name,
          rightNode.name,
          typeToken,
          properties.map(SlottedIndexedProperty(column, _, slots)).toIndexedSeq,
          indexRegistrator.registerQueryIndex(indexType, typeToken, properties),
          valueExpr.map(convertExpressions),
          indexSeekMode,
          IndexOrderNone,
          slots
        )(id)

      case UndirectedRelationshipUniqueIndexSeek(
          column,
          leftNode,
          rightNode,
          typeToken,
          properties,
          valueExpr,
          _,
          indexOrder,
          indexType
        ) =>
        val indexSeekMode = IndexSeekModeFactory(unique = true, readOnly = readOnly).fromQueryExpression(valueExpr)
        UndirectedRelationshipIndexSeekSlottedPipe(
          column.name,
          leftNode.name,
          rightNode.name,
          typeToken,
          properties.map(SlottedIndexedProperty(column, _, slots)).toIndexedSeq,
          indexRegistrator.registerQueryIndex(indexType, typeToken, properties),
          valueExpr.map(convertExpressions),
          indexSeekMode,
          indexOrder,
          slots
        )(id)

      case UndirectedRelationshipIndexSeek(
          column,
          leftNode,
          rightNode,
          typeToken,
          properties,
          valueExpr,
          _,
          indexOrder,
          indexType,
          _
        ) =>
        val indexSeekMode = IndexSeekModeFactory(unique = false, readOnly = readOnly).fromQueryExpression(valueExpr)
        UndirectedRelationshipIndexSeekSlottedPipe(
          column.name,
          leftNode.name,
          rightNode.name,
          typeToken,
          properties.map(SlottedIndexedProperty(column, _, slots)).toIndexedSeq,
          indexRegistrator.registerQueryIndex(indexType, typeToken, properties),
          valueExpr.map(convertExpressions),
          indexSeekMode,
          indexOrder,
          slots
        )(id)

      case PartitionedUndirectedRelationshipIndexSeek(
          column,
          leftNode,
          rightNode,
          typeToken,
          properties,
          valueExpr,
          _,
          indexType
        ) =>
        val indexSeekMode = IndexSeekModeFactory(unique = false, readOnly = readOnly).fromQueryExpression(valueExpr)
        UndirectedRelationshipIndexSeekSlottedPipe(
          column.name,
          leftNode.name,
          rightNode.name,
          typeToken,
          properties.map(SlottedIndexedProperty(column, _, slots)).toIndexedSeq,
          indexRegistrator.registerQueryIndex(indexType, typeToken, properties),
          valueExpr.map(convertExpressions),
          indexSeekMode,
          IndexOrderNone,
          slots
        )(id)

      case DirectedRelationshipIndexScan(
          column,
          leftNode,
          rightNode,
          typeToken,
          properties,
          _,
          indexOrder,
          indexType,
          _
        ) =>
        DirectedRelationshipIndexScanSlottedPipe(
          column.name,
          leftNode.name,
          rightNode.name,
          typeToken,
          properties.map(SlottedIndexedProperty(column, _, slots)).toIndexedSeq,
          indexRegistrator.registerQueryIndex(indexType, typeToken, properties),
          indexOrder,
          slots
        )(id)

      case UndirectedRelationshipIndexScan(
          column,
          leftNode,
          rightNode,
          typeToken,
          properties,
          _,
          indexOrder,
          indexType,
          _
        ) =>
        UndirectedRelationshipIndexScanSlottedPipe(
          column.name,
          leftNode.name,
          rightNode.name,
          typeToken,
          properties.map(SlottedIndexedProperty(column, _, slots)).toIndexedSeq,
          indexRegistrator.registerQueryIndex(indexType, typeToken, properties),
          indexOrder,
          slots
        )(id)

      case PartitionedDirectedRelationshipIndexScan(
          column,
          leftNode,
          rightNode,
          typeToken,
          properties,
          _,
          indexType
        ) =>
        DirectedRelationshipIndexScanSlottedPipe(
          column.name,
          leftNode.name,
          rightNode.name,
          typeToken,
          properties.map(SlottedIndexedProperty(column, _, slots)).toIndexedSeq,
          indexRegistrator.registerQueryIndex(indexType, typeToken, properties),
          IndexOrderNone,
          slots
        )(id)

      case PartitionedUndirectedRelationshipIndexScan(
          column,
          leftNode,
          rightNode,
          typeToken,
          properties,
          _,
          indexType
        ) =>
        UndirectedRelationshipIndexScanSlottedPipe(
          column.name,
          leftNode.name,
          rightNode.name,
          typeToken,
          properties.map(SlottedIndexedProperty(column, _, slots)).toIndexedSeq,
          indexRegistrator.registerQueryIndex(indexType, typeToken, properties),
          IndexOrderNone,
          slots
        )(id)

      case DirectedAllRelationshipsScan(name, start, end, _) =>
        DirectedAllRelationshipsScanSlottedPipe(
          slots.getLongOffsetFor(name),
          slots.getLongOffsetFor(start),
          slots.getLongOffsetFor(end)
        )(id)

      case UndirectedAllRelationshipsScan(name, start, end, _) =>
        UndirectedAllRelationshipsScanSlottedPipe(
          slots.getLongOffsetFor(name),
          slots.getLongOffsetFor(start),
          slots.getLongOffsetFor(end)
        )(id)

      case PartitionedDirectedAllRelationshipsScan(name, start, end, _) =>
        DirectedAllRelationshipsScanSlottedPipe(
          slots.getLongOffsetFor(name),
          slots.getLongOffsetFor(start),
          slots.getLongOffsetFor(end)
        )(id)

      case PartitionedUndirectedAllRelationshipsScan(name, start, end, _) =>
        UndirectedAllRelationshipsScanSlottedPipe(
          slots.getLongOffsetFor(name),
          slots.getLongOffsetFor(start),
          slots.getLongOffsetFor(end)
        )(id)

      case DirectedRelationshipTypeScan(name, start, typ, end, _, indexOrder) =>
        indexRegistrator.registerTypeScan()
        DirectedRelationshipTypeScanSlottedPipe(
          slots.getLongOffsetFor(name),
          slots.getLongOffsetFor(start),
          LazyType(typ),
          slots.getLongOffsetFor(end),
          indexOrder
        )(id)

      case UndirectedRelationshipTypeScan(name, start, typ, end, _, indexOrder) =>
        indexRegistrator.registerTypeScan()
        UndirectedRelationshipTypeScanSlottedPipe(
          slots.getLongOffsetFor(name),
          slots.getLongOffsetFor(start),
          LazyType(typ),
          slots.getLongOffsetFor(end),
          indexOrder
        )(id)

      case PartitionedDirectedRelationshipTypeScan(name, start, typ, end, _) =>
        indexRegistrator.registerTypeScan()
        DirectedRelationshipTypeScanSlottedPipe(
          slots.getLongOffsetFor(name),
          slots.getLongOffsetFor(start),
          LazyType(typ),
          slots.getLongOffsetFor(end),
          IndexOrderNone
        )(id)

      case PartitionedUndirectedRelationshipTypeScan(name, start, typ, end, _) =>
        indexRegistrator.registerTypeScan()
        UndirectedRelationshipTypeScanSlottedPipe(
          slots.getLongOffsetFor(name),
          slots.getLongOffsetFor(start),
          LazyType(typ),
          slots.getLongOffsetFor(end),
          IndexOrderNone
        )(id)

      case DirectedUnionRelationshipTypesScan(name, start, types, end, _, indexOrder) =>
        indexRegistrator.registerTypeScan()
        DirectedUnionRelationshipTypesScanSlottedPipe(
          slots.getLongOffsetFor(name),
          slots.getLongOffsetFor(start),
          types.map(t => LazyType(t)(semanticTable)),
          slots.getLongOffsetFor(end),
          indexOrder
        )(id)

      case UndirectedUnionRelationshipTypesScan(name, start, types, end, _, indexOrder) =>
        indexRegistrator.registerTypeScan()
        UndirectedUnionRelationshipTypesScanSlottedPipe(
          slots.getLongOffsetFor(name),
          slots.getLongOffsetFor(start),
          types.map(t => LazyType(t)(semanticTable)),
          slots.getLongOffsetFor(end),
          indexOrder
        )(id)

      case PartitionedDirectedUnionRelationshipTypesScan(name, start, types, end, _) =>
        indexRegistrator.registerTypeScan()
        DirectedUnionRelationshipTypesScanSlottedPipe(
          slots.getLongOffsetFor(name),
          slots.getLongOffsetFor(start),
          types.map(t => LazyType(t)(semanticTable)),
          slots.getLongOffsetFor(end),
          IndexOrderNone
        )(id)

      case PartitionedUndirectedUnionRelationshipTypesScan(name, start, types, end, _) =>
        indexRegistrator.registerTypeScan()
        UndirectedUnionRelationshipTypesScanSlottedPipe(
          slots.getLongOffsetFor(name),
          slots.getLongOffsetFor(start),
          types.map(t => LazyType(t)(semanticTable)),
          slots.getLongOffsetFor(end),
          IndexOrderNone
        )(id)

      case DirectedRelationshipIndexContainsScan(
          name,
          startNode,
          endNode,
          typeToken,
          property,
          valueExpr,
          _,
          indexOrder,
          indexType
        ) =>
        DirectedRelationshipIndexContainsScanSlottedPipe(
          name.name,
          startNode.name,
          endNode.name,
          SlottedIndexedProperty(name, property, slots),
          indexRegistrator.registerQueryIndex(indexType, typeToken, property),
          convertExpressions(valueExpr),
          slots,
          indexOrder
        )(id)

      case UndirectedRelationshipIndexContainsScan(
          name,
          startNode,
          endNode,
          typeToken,
          property,
          valueExpr,
          _,
          indexOrder,
          indexType
        ) =>
        UndirectedRelationshipIndexContainsScanSlottedPipe(
          name.name,
          startNode.name,
          endNode.name,
          SlottedIndexedProperty(name, property, slots),
          indexRegistrator.registerQueryIndex(indexType, typeToken, property),
          convertExpressions(valueExpr),
          slots,
          indexOrder
        )(id)

      case DirectedRelationshipIndexEndsWithScan(
          name,
          startNode,
          endNode,
          typeToken,
          property,
          valueExpr,
          _,
          indexOrder,
          indexType
        ) =>
        DirectedRelationshipIndexEndsWithScanSlottedPipe(
          name.name,
          startNode.name,
          endNode.name,
          SlottedIndexedProperty(name, property, slots),
          indexRegistrator.registerQueryIndex(indexType, typeToken, property),
          convertExpressions(valueExpr),
          slots,
          indexOrder
        )(id)

      case UndirectedRelationshipIndexEndsWithScan(
          name,
          startNode,
          endNode,
          typeToken,
          property,
          valueExpr,
          _,
          indexOrder,
          indexType
        ) =>
        UndirectedRelationshipIndexEndsWithScanSlottedPipe(
          name.name,
          startNode.name,
          endNode.name,
          SlottedIndexedProperty(name, property, slots),
          indexRegistrator.registerQueryIndex(indexType, typeToken, property),
          convertExpressions(valueExpr),
          slots,
          indexOrder
        )(id)

      case _: Argument =>
        ArgumentSlottedPipe()(id)

      // Currently used for testing only
      case _: MultiNodeIndexSeek =>
        throw new CantCompileQueryException(s"Slotted runtime does not support $plan")

      case _ =>
        fallback.onLeaf(plan)
    }
    pipe.rowFactory = SlottedCypherRowFactory(slots, argumentSize)
    pipe
  }

  override def onOneChildPlan(plan: LogicalPlan, source: Pipe): Pipe = {

    val id = plan.id
    val convertExpressions = (e: internal.expressions.Expression) => expressionConverters.toCommandExpression(id, e)

    val slots = physicalPlan.slotConfigurations(id)
    // some operators will overwrite this value
    var argumentSize = physicalPlan.argumentSizes.getOrElse(id, SlotConfiguration.Size.zero)
    finalizeSlotConfiguration(slots)

    def compileEffects(sideEffect: SimpleMutatingPattern): Seq[SideEffect] = sideEffect match {

      case CreatePattern(commands) =>
        commands.map {
          case CreateNode(node, labels, properties) =>
            CreateSlottedNode(
              CreateNodeSlottedCommand(
                slots.getLongOffsetFor(node),
                labels.toSeq.map(l => LazyLabel(l)(semanticTable)),
                properties.map(convertExpressions)
              ),
              allowNullOrNaNProperty = true
            )
          case r: CreateRelationship =>
            CreateSlottedRelationship(
              CreateRelationshipSlottedCommand(
                slots.getLongOffsetFor(r.variable),
                SlotConfigurationUtils.makeGetPrimitiveNodeFromSlotFunctionFor(slots(r.startNode)),
                LazyType(r.relType.name),
                SlotConfigurationUtils.makeGetPrimitiveNodeFromSlotFunctionFor(slots(r.endNode)),
                r.properties.map(convertExpressions),
                r.variable.name,
                r.startNode.name,
                r.endNode.name
              ),
              allowNullOrNaNProperty = true
            )
        }
      case org.neo4j.cypher.internal.ir.DeleteExpression(expression, forced) =>
        Seq(DeleteOperation(convertExpressions(expression), forced))
      case SetLabelPattern(node, labelNames, dynamicLabels) =>
        Seq(SlottedSetLabelsOperation(
          slots(node),
          labelNames.map(l => LazyLabel(l)(semanticTable)),
          dynamicLabels.map(l => convertExpressions(l))
        ))
      case RemoveLabelPattern(node, labelNames, dynamicLabels) =>
        Seq(SlottedRemoveLabelsOperation(
          slots(node),
          labelNames.map(l => LazyLabel(l)(semanticTable)),
          dynamicLabels.map(l => convertExpressions(l))
        ))
      case SetNodePropertyPattern(node, propertyKey, value) =>
        val needsExclusiveLock = internal.expressions.Expression.hasPropertyReadDependency(node, value, propertyKey)
        Seq(SlottedSetNodePropertyOperation(
          slots(node),
          LazyPropertyKey(propertyKey),
          convertExpressions(value),
          needsExclusiveLock
        ))
      case SetNodePropertiesPattern(node, items) =>
        val needsExclusiveLock = items.exists {
          case (p, e) => internal.expressions.Expression.hasPropertyReadDependency(node, e, p)
        }
        val size = items.size
        val keys = new Array[LazyPropertyKey](size)
        val values = new Array[Expression](size)
        items.zipWithIndex.foreach {
          case ((k, e), i) =>
            keys(i) = LazyPropertyKey(k)
            values(i) = convertExpressions(e)
        }

        Seq(SlottedSetNodePropertiesOperation(slots(node), keys, values, needsExclusiveLock))
      case SetNodePropertiesFromMapPattern(node, map, removeOtherProps) =>
        val needsExclusiveLock = internal.expressions.Expression.mapExpressionHasPropertyReadDependency(node, map)
        Seq(SlottedSetNodePropertyFromMapOperation(
          slots(node),
          convertExpressions(map),
          removeOtherProps,
          needsExclusiveLock
        ))
      case SetRelationshipPropertyPattern(relationship, propertyKey, value) =>
        val needsExclusiveLock =
          internal.expressions.Expression.hasPropertyReadDependency(relationship, value, propertyKey)
        Seq(SlottedSetRelationshipPropertyOperation(
          slots(relationship),
          LazyPropertyKey(propertyKey),
          convertExpressions(value),
          needsExclusiveLock
        ))
      case SetRelationshipPropertiesPattern(rel, items) =>
        val needsExclusiveLock = items.exists {
          case (p, e) => internal.expressions.Expression.hasPropertyReadDependency(rel, e, p)
        }
        val size = items.size
        val keys = new Array[LazyPropertyKey](size)
        val values = new Array[Expression](size)
        items.zipWithIndex.foreach {
          case ((k, e), i) =>
            keys(i) = LazyPropertyKey(k)
            values(i) = convertExpressions(e)
        }

        Seq(SlottedSetRelationshipPropertiesOperation(slots(rel), keys, values, needsExclusiveLock))
      case SetRelationshipPropertiesFromMapPattern(relationship, map, removeOtherProps) =>
        val needsExclusiveLock =
          internal.expressions.Expression.mapExpressionHasPropertyReadDependency(relationship, map)
        Seq(SlottedSetRelationshipPropertyFromMapOperation(
          slots(relationship),
          convertExpressions(map),
          removeOtherProps,
          needsExclusiveLock
        ))
      case SetPropertyPattern(entityExpression, propertyKeyName, expression) =>
        Seq(SetPropertyOperation(
          convertExpressions(entityExpression),
          LazyPropertyKey(propertyKeyName),
          convertExpressions(expression)
        ))

      case SetDynamicPropertyPattern(entityExpression, propertyKey, expression) =>
        Seq(SetDynamicPropertyOperation(
          convertExpressions(entityExpression),
          convertExpressions(propertyKey),
          convertExpressions(expression)
        ))

      case SetPropertiesPattern(entity, items) =>
        val size = items.size
        val keys = new Array[LazyPropertyKey](size)
        val values = new Array[Expression](size)
        items.zipWithIndex.foreach {
          case ((k, e), i) =>
            keys(i) = LazyPropertyKey(k)
            values(i) = convertExpressions(e)
        }

        Seq(SetPropertiesOperation(convertExpressions(entity), keys, values))
      case SetPropertiesFromMapPattern(entityExpression, expression, removeOtherProps) =>
        Seq(SetPropertyFromMapOperation(
          convertExpressions(entityExpression),
          convertExpressions(expression),
          removeOtherProps
        ))

      case other => throw new IllegalStateException(s"Cannot merge with $other")
    }

    val pipe = plan match {
      case ProduceResult(_, columns) =>
        val columnExpressions = columns.map(c =>
          c.variable -> c.cachedProperties.map(e =>
            LazyPropertyKey(e.propertyKey)(semanticTable) -> convertExpressions(e)
          )
        )
        val runtimeColumns = createProjectionsForResult(columnExpressions, slots)
        ProduceResultsPipe(source, runtimeColumns.toArray)(id)

      case Expand(_, from, dir, types, to, relName, ExpandAll) =>
        val fromSlot = slots(from)
        val relOffset = slots.getLongOffsetFor(relName)
        val toOffset = slots.getLongOffsetFor(to)
        ExpandAllSlottedPipe(source, fromSlot, relOffset, toOffset, dir, RelationshipTypes(types.toArray), slots)(id)

      case Expand(_, from, dir, types, to, relName, ExpandInto) =>
        val fromSlot = slots(from)
        val relOffset = slots.getLongOffsetFor(relName)
        val toSlot = slots(to)
        ExpandIntoSlottedPipe(source, fromSlot, relOffset, toSlot, dir, RelationshipTypes(types.toArray), slots)(id)

      case OptionalExpand(_, fromName, dir, types, toName, relName, ExpandAll, predicate) =>
        val fromSlot = slots(fromName)
        val relOffset = slots.getLongOffsetFor(relName)
        val toOffset = slots.getLongOffsetFor(toName)
        OptionalExpandAllSlottedPipe(
          source,
          fromSlot,
          relOffset,
          toOffset,
          dir,
          RelationshipTypes(types.toArray),
          slots,
          predicate.map(convertExpressions)
        )(id)

      case OptionalExpand(_, fromName, dir, types, toName, relName, ExpandInto, predicate) =>
        val fromSlot = slots(fromName)
        val relOffset = slots.getLongOffsetFor(relName)
        val toSlot = slots(toName)

        OptionalExpandIntoSlottedPipe(
          source,
          fromSlot,
          relOffset,
          toSlot,
          dir,
          RelationshipTypes(types.toArray),
          slots,
          predicate.map(convertExpressions)
        )(id)

      case VarExpand(
          sourcePlan,
          fromName,
          dir,
          projectedDir,
          types,
          toName,
          relName,
          VarPatternLength(min, max),
          expansionMode,
          nodePredicates,
          relationshipPredicates
        ) =>
        val shouldExpandAll = expansionMode match {
          case ExpandAll  => true
          case ExpandInto => false
        }
        val fromSlot = slots(fromName)
        val relOffset = slots.getReferenceOffsetFor(relName)
        val toSlot = slots(toName)

        // The node/relationship predicates are evaluated on the source pipeline, not the produced one
        val sourceSlots = physicalPlan.slotConfigurations(sourcePlan.id)

        val predicates = TraversalPredicates.create(
          nodePredicates,
          relationshipPredicates,
          expressionConverters.toCommandExpression(id, _)
        )

        argumentSize = SlotConfiguration.Size(sourceSlots.numberOfLongs, sourceSlots.numberOfReferences)
        VarLengthExpandSlottedPipe(
          source,
          fromSlot,
          relOffset,
          toSlot,
          dir,
          projectedDir,
          RelationshipTypes(types.toArray),
          min,
          max,
          shouldExpandAll,
          slots,
          predicates,
          argumentSize = argumentSize
        )(id)

      case BFSPruningVarExpand(
          _,
          from,
          dir,
          types,
          to,
          includeStartNode,
          max,
          depthName,
          mode,
          nodePredicates,
          relationshipPredicates
        ) =>
        val fromSlot = slots(from)
        val toSlot = slots(to)
        val depthOffset = depthName.map(slots.getReferenceOffsetFor)

        // The node/relationship predicates are evaluated on the source pipeline, not the produced one
        val sourceSlots = physicalPlan.slotConfigurations(source.id)

        val predicates = TraversalPredicates.create(
          nodePredicates,
          relationshipPredicates,
          expressionConverters.toCommandExpression(id, _)
        )

        argumentSize = SlotConfiguration.Size(sourceSlots.numberOfLongs, sourceSlots.numberOfReferences)
        BFSPruningVarLengthExpandSlottedPipe(
          source,
          fromSlot,
          toSlot,
          depthOffset,
          RelationshipTypes(types.toArray),
          dir,
          includeStartNode,
          max,
          slots,
          mode,
          predicates
        )(id = id)

      case FindShortestPaths(
          _,
          shortestPathPattern,
          perStepNodePredicates,
          perStepRelPredicates,
          pathPredicates,
          withFallBack,
          sameNodeMode
        ) =>
        val rel = shortestPathPattern.expr.element match {
          case internal.expressions.RelationshipChain(_, relationshipPattern, _) =>
            relationshipPattern
          case _ =>
            throw new IllegalStateException("This should be caught during semantic checking")
        }

        val single = shortestPathPattern.expr.single

        val patternRelationship = shortestPathPattern.rel

        val (sourceNodeName, targetNodeName) = patternRelationship.boundaryNodes

        if (sameNodeMode == DisallowSameNode && sourceNodeName == targetNodeName) {
          throw shortestPathCommonEndNodes()
        }

        val pathName = shortestPathPattern.maybePathVar.get // Should always be given anonymous name
        val relsName = rel.variable.get.name // Should always be given anonymous name

        val sourceSlot = slots(sourceNodeName)
        val targetSlot = slots(targetNodeName)
        val pathOffset = slots.getReferenceOffsetFor(pathName)
        val relsOffset = slots.getReferenceOffsetFor(relsName)

        val (allowZeroLength, maxDepth) = rel.length match {
          case Some(Some(internal.expressions.Range(lower, max))) =>
            (lower.exists(_.value == 0L), max.map(_.value.toInt))
          case None => (false, Some(1)) // non-varlength case
          case _    => (false, None)
        }

        if (!allowZeroLength && sameNodeMode == AllowSameNode && rel.direction == SemanticDirection.BOTH) {
          throw new IllegalArgumentException("We don't allow -[*1..]- for AllowSameNode")
        }

        val predicates = TraversalPredicates.create(
          perStepNodePredicates,
          perStepRelPredicates,
          expressionConverters.toCommandExpression(id, _)
        )

        val pathCommandPredicates =
          pathPredicates.map(expressionConverters.toCommandExpression(id, _)).map(_.rewriteAsPredicate(identity))

        ShortestPathSlottedPipe(
          source,
          sourceSlot,
          targetSlot,
          pathOffset,
          relsOffset,
          RelationshipTypes(patternRelationship.types.toArray),
          patternRelationship.dir,
          predicates,
          pathCommandPredicates,
          returnOneShortestPathOnly = single,
          sameNodeMode = sameNodeMode,
          allowZeroLength = allowZeroLength,
          maxDepth = maxDepth,
          needOnlyOnePath = single && !withFallBack,
          slots = slots
        )(id)

      case StatefulShortestPath(
          _,
          sourceNode,
          targetNode,
          nfa,
          mode,
          nonInlinedPreFilters,
          nodeVariableGroupings,
          relationshipVariableGroupings,
          singletonNodeVariables,
          singletonRelationshipVariables,
          selector,
          _,
          reverseGroupVariableProjections,
          bounds,
          matchMode
        ) =>
        val groupMap = (nodeVariableGroupings ++ relationshipVariableGroupings)
          .map(grouping => grouping.singleton.name -> slots(grouping.group.name))
          .toMap

        val singletonMap = (singletonNodeVariables ++ singletonRelationshipVariables)
          .map(mapping => mapping.nfaExprVar.name -> mapping.rowVar.name)
          .toMap

        def getSlot(variable: LogicalVariable): SlotOrName = {
          groupMap.get(variable.name) match {
            case Some(slot) =>
              SlotOrName.Slotted(slot.offset, isGroup = true)
            case None =>
              val rowVar = singletonMap.getOrElse(variable.name, variable.name)
              slots.get(rowVar).map { slot =>
                SlotOrName.Slotted(slot.offset, isGroup = false)
              }.getOrElse(SlotOrName.None)
          }
        }

        val commandNFA = CommandNFA.fromLogicalNFA(
          nfa,
          expressionConverters.toCommandPredicate(id, _),
          getSlot
        )

        val commandPreFilters: Option[Predicate] =
          nonInlinedPreFilters.map(expressionConverters.toCommandExpression(id, _)).map(_.rewriteAsPredicate(identity))

        val intoTargetName = mode match {
          case ExpandInto => Some(slots(targetNode))
          case ExpandAll  => None
        }

        StatefulShortestPathSlottedPipe(
          source,
          slots(sourceNode),
          intoTargetName,
          commandNFA,
          bounds,
          commandPreFilters,
          selector,
          groupMap.values.map(_.offset).toList,
          slots,
          reverseGroupVariableProjections,
          matchMode
        )(id = id)

      case Optional(inner, symbols) =>
        val nullableSlots = computeSlotsDifference(inner.availableSymbols, symbols, slots)
        OptionalSlottedPipe(source, nullableSlots)(id)

      case Projection(_, expressions) =>
        val toProject = expressions collect {
          case (k, e) if isRefSlotAndNotAlias(slots, k) => k -> e
        }
        ProjectionPipe(source, expressionConverters.toCommandProjection(id, toProject))(id)

      case Create(_, commands) =>
        CreateSlottedPipe(
          source,
          commands.map {
            case n: CreateNode =>
              CreateNodeSlottedCommand(
                slots.getLongOffsetFor(n.variable),
                n.labels.toSeq.map(l => LazyLabel(l)(semanticTable)),
                n.properties.map(convertExpressions)
              )

            case r: CreateRelationship =>
              CreateRelationshipSlottedCommand(
                slots.getLongOffsetFor(r.variable),
                SlotConfigurationUtils.makeGetPrimitiveNodeFromSlotFunctionFor(slots(r.startNode)),
                LazyType(r.relType.name),
                SlotConfigurationUtils.makeGetPrimitiveNodeFromSlotFunctionFor(slots(r.endNode)),
                r.properties.map(convertExpressions),
                r.variable.name,
                r.startNode.name,
                r.endNode.name
              )
          }.toIndexedSeq
        )(id)

      case Merge(_, createNodes, createRelationships, onMatch, onCreate, nodesToLock) =>
        val creates = createNodes.map {
          case CreateNode(node, labels, properties) =>
            CreateSlottedNode(
              CreateNodeSlottedCommand(
                slots.getLongOffsetFor(node),
                labels.toSeq.map(l => LazyLabel(l)(semanticTable)),
                properties.map(convertExpressions)
              ),
              allowNullOrNaNProperty = false
            )
        } ++ createRelationships.map {
          (r: CreateRelationship) =>
            CreateSlottedRelationship(
              CreateRelationshipSlottedCommand(
                slots.getLongOffsetFor(r.variable),
                SlotConfigurationUtils.makeGetPrimitiveNodeFromSlotFunctionFor(slots(r.startNode)),
                LazyType(r.relType.name),
                SlotConfigurationUtils.makeGetPrimitiveNodeFromSlotFunctionFor(slots(r.endNode)),
                r.properties.map(convertExpressions),
                r.variable.name,
                r.startNode.name,
                r.endNode.name
              ),
              allowNullOrNaNProperty = false
            )
        }

        if (nodesToLock.isEmpty) new MergePipe(
          source,
          (creates ++ onCreate.flatMap(compileEffects)).toArray,
          onMatch.flatMap(compileEffects).toArray
        )(id = id)
        else new LockingMergeSlottedPipe(
          source,
          (creates ++ onCreate.flatMap(compileEffects)).toArray,
          onMatch.flatMap(compileEffects).toArray,
          nodesToLock.map(n => slots(n)).toArray
        )(id = id)

      case Foreach(_, variable, expression, mutations) =>
        val innerVariableSlot =
          slots.get(variable).getOrElse(throw new InternalException(s"Foreach variable '$variable' has no slot"))
        ForeachSlottedPipe(
          source,
          innerVariableSlot,
          convertExpressions(expression),
          mutations.flatMap(compileEffects).toArray
        )(id = id)

      case SetNodeProperty(_, name, propertyKey, expression) =>
        val needsExclusiveLock =
          internal.expressions.Expression.hasPropertyReadDependency(name, expression, propertyKey)
        SetPipe(
          source,
          SlottedSetNodePropertyOperation(
            slots(name),
            LazyPropertyKey(propertyKey),
            convertExpressions(expression),
            needsExclusiveLock
          )
        )(id = id)

      case SetNodeProperties(_, name, items) =>
        val needsExclusiveLock = items.exists {
          case (p, e) => internal.expressions.Expression.hasPropertyReadDependency(name, e, p)
        }
        val size = items.size
        val keys = new Array[LazyPropertyKey](size)
        val values = new Array[Expression](size)
        items.zipWithIndex.foreach {
          case ((k, e), i) =>
            keys(i) = LazyPropertyKey(k)
            values(i) = convertExpressions(e)
        }
        SetPipe(source, SlottedSetNodePropertiesOperation(slots(name), keys, values, needsExclusiveLock))(id = id)

      case SetNodePropertiesFromMap(_, name, expression, removeOtherProps) =>
        val needsExclusiveLock =
          internal.expressions.Expression.mapExpressionHasPropertyReadDependency(name, expression)
        SetPipe(
          source,
          SlottedSetNodePropertyFromMapOperation(
            slots(name),
            convertExpressions(expression),
            removeOtherProps,
            needsExclusiveLock
          )
        )(id = id)

      case SetRelationshipProperty(_, name, propertyKey, expression) =>
        val needsExclusiveLock =
          internal.expressions.Expression.hasPropertyReadDependency(name, expression, propertyKey)
        SetPipe(
          source,
          SlottedSetRelationshipPropertyOperation(
            slots(name),
            LazyPropertyKey(propertyKey),
            convertExpressions(expression),
            needsExclusiveLock
          )
        )(id = id)

      case SetRelationshipProperties(_, name, items) =>
        val needsExclusiveLock = items.exists {
          case (p, e) => internal.expressions.Expression.hasPropertyReadDependency(name, e, p)
        }
        val size = items.size
        val keys = new Array[LazyPropertyKey](size)
        val values = new Array[Expression](size)
        items.zipWithIndex.foreach {
          case ((k, e), i) =>
            keys(i) = LazyPropertyKey(k)
            values(i) = convertExpressions(e)
        }
        SetPipe(source, SlottedSetRelationshipPropertiesOperation(slots(name), keys, values, needsExclusiveLock))(id =
          id
        )

      case SetRelationshipPropertiesFromMap(_, name, expression, removeOtherProps) =>
        val needsExclusiveLock =
          internal.expressions.Expression.mapExpressionHasPropertyReadDependency(name, expression)
        SetPipe(
          source,
          SlottedSetRelationshipPropertyFromMapOperation(
            slots(name),
            convertExpressions(expression),
            removeOtherProps,
            needsExclusiveLock
          )
        )(id = id)

      case EmptyResult(_) =>
        EmptyResultPipe(source)(id)

      case UnwindCollection(_, name, expression) =>
        val offset = slots.getReferenceOffsetFor(name)
        UnwindSlottedPipe(source, convertExpressions(expression), offset, slots)(id)

      // Note: this plan shouldn't really be used here, but having it mapped here helps
      //      fallback and makes testing easier
      case PartitionedUnwindCollection(_, name, expression) =>
        val offset = slots.getReferenceOffsetFor(name)
        UnwindSlottedPipe(source, convertExpressions(expression), offset, slots)(id)

      case Aggregation(_, groupingExpressions, aggregationExpression) =>
        val aggregation = aggregationExpression.map {
          case (key, expression) =>
            slots.getReferenceOffsetFor(key) -> convertExpressions(expression).asInstanceOf[AggregationExpression]
        }

        val keys = groupingExpressions.keys.toArray

        val longSlotGroupingValues = keys.collect {
          case key if slots(key).isLongSlot =>
            groupingExpressions(key) match {
              case NodeFromSlot(offset, _)                               => offset
              case RelationshipFromSlot(offset, _)                       => offset
              case NullCheckVariable(_, NodeFromSlot(offset, _))         => offset
              case NullCheckVariable(_, RelationshipFromSlot(offset, _)) => offset
              case x => throw new InternalException(
                  s"Cannot build slotted aggregation pipe. Unexpected grouping expression: $x"
                )
            }
        }

        val longSlotGroupingKeys: Array[Int] = keys.collect {
          case x if slots(x).isLongSlot => slots(x).offset
        }

        // Choose the right kind of aggregation table factory based on what grouping columns we have
        val tableFactory =
          if (groupingExpressions.isEmpty) {
            SlottedNonGroupingAggTable.Factory(slots, aggregation, physicalPlan.argumentSizes(plan.id))
          } else if (
            longSlotGroupingValues.length == groupingExpressions.size &&
            longSlotGroupingValues.length == longSlotGroupingKeys.length
          ) {
            // If we are able to use primitive for all incoming and outgoing grouping columns, we can use the more effective
            // Primitive table that leverages that the fact that grouping can be done a single array of longs
            SlottedPrimitiveGroupingAggTable.Factory(
              slots,
              longSlotGroupingValues,
              longSlotGroupingKeys,
              aggregation,
              physicalPlan.argumentSizes(plan.id)
            )
          } else {
            SlottedGroupingAggTable.Factory(
              slots,
              expressionConverters.toGroupingExpression(id, groupingExpressions, Seq.empty),
              aggregation,
              physicalPlan.argumentSizes(plan.id)
            )
          }

        EagerAggregationPipe(source, tableFactory)(id)

      case OrderedAggregation(_, groupingExpressions, aggregationExpression, orderToLeverage) =>
        val aggregation = aggregationExpression.map {
          case (key, expression) =>
            slots.getReferenceOffsetFor(key) -> convertExpressions(expression).asInstanceOf[AggregationExpression]
        }

        val (orderedGroupingColumns, unorderedGroupingColumns) =
          partitionGroupingExpressions(expressionConverters, groupingExpressions, orderToLeverage, id)

        val tableFactory =
          if (unorderedGroupingColumns.isEmpty) {
            SlottedOrderedNonGroupingAggTable.Factory(
              slots,
              orderedGroupingColumns,
              aggregation,
              physicalPlan.argumentSizes(plan.id)
            )
          } else {
            SlottedOrderedGroupingAggTable.Factory(
              slots,
              orderedGroupingColumns,
              unorderedGroupingColumns,
              aggregation,
              physicalPlan.argumentSizes(plan.id)
            )
          }
        OrderedAggregationPipe(source, tableFactory)(id = id)

      case Distinct(_, groupingExpressions) =>
        chooseDistinctPipe(groupingExpressions, Seq.empty, slots, source, id)

      case OrderedDistinct(_, groupingExpressions, orderToLeverage) =>
        chooseDistinctPipe(groupingExpressions, orderToLeverage, slots, source, id)

      case Top(_, sortItems, _) if sortItems.isEmpty => source

      case Top(_, sortItems, SignedDecimalIntegerLiteral("1")) =>
        Top1Pipe(source, SlottedExecutionContextOrdering.asComparator(sortItems.map(translateColumnOrder(slots, _))))(
          id = id
        )

      case Top1WithTies(_, sortItems) =>
        Top1WithTiesPipe(
          source,
          SlottedExecutionContextOrdering.asComparator(sortItems.map(translateColumnOrder(slots, _)))
        )(id = id)

      case Top(_, sortItems, limit) =>
        TopNPipe(
          source,
          convertExpressions(limit),
          SlottedExecutionContextOrdering.asComparator(sortItems.map(translateColumnOrder(slots, _)))
        )(id = id)

      case PartialTop(_, _, stillToSortSuffix, _, _) if stillToSortSuffix.isEmpty => source

      case PartialTop(_, alreadySortedPrefix, stillToSortSuffix, SignedDecimalIntegerLiteral("1"), _) =>
        PartialTop1Pipe(
          source,
          SlottedExecutionContextOrdering.asComparator(alreadySortedPrefix.map(translateColumnOrder(slots, _)).toList),
          SlottedExecutionContextOrdering.asComparator(stillToSortSuffix.map(translateColumnOrder(slots, _)).toList)
        )(id = id)

      case PartialTop(_, alreadySortedPrefix, stillToSortSuffix, limit, skipSortingPrefixLength) =>
        PartialTopNPipe(
          source,
          convertExpressions(limit),
          skipSortingPrefixLength.map(convertExpressions),
          SlottedExecutionContextOrdering.asComparator(alreadySortedPrefix.map(translateColumnOrder(slots, _)).toList),
          SlottedExecutionContextOrdering.asComparator(stillToSortSuffix.map(translateColumnOrder(slots, _)).toList)
        )(id = id)

      // Pipes that do not themselves read/write slots should be fine to use the fallback (non-slot aware pipes)
      case _: Selection |
        _: Limit |
        _: ExhaustiveLimit |
        _: ErrorPlan |
        _: Skip |
        _: NonFuseable |
        _: InjectCompilationError |
        _: Prober =>
        fallback.onOneChildPlan(plan, source)

      case Sort(_, sortItems) =>
        SortSlottedPipe(
          source,
          SlottedExecutionContextOrdering.asComparator(sortItems.map(translateColumnOrder(slots, _)))
        )(id = id)

      case PartialSort(_, alreadySortedPrefix, stillToSortSuffix, skipSortingPrefixLength) =>
        PartialSortPipe(
          source,
          SlottedExecutionContextOrdering.asComparator(alreadySortedPrefix.map(translateColumnOrder(slots, _))),
          SlottedExecutionContextOrdering.asComparator(stillToSortSuffix.map(translateColumnOrder(slots, _))),
          skipSortingPrefixLength.map(convertExpressions)
        )(id = id)

      case Eager(_, _) =>
        EagerSlottedPipe(source, slots)(id)

      case _: DeleteNode |
        _: DeleteRelationship |
        _: DeletePath |
        _: DeleteExpression |
        _: DetachDeleteNode |
        _: DetachDeletePath |
        _: DetachDeleteExpression =>
        fallback.onOneChildPlan(plan, source)

      case _: SetLabels |
        _: SetProperty |
        _: SetDynamicProperty |
        _: SetProperties |
        _: SetPropertiesFromMap |
        _: RemoveLabels =>
        fallback.onOneChildPlan(plan, source)

      case LoadCSV(_, url, variableName, format, fieldTerminator, legacyCsvQuoteEscaping, bufferSize) =>
        val lineVariableOffset = slots.getReferenceOffsetFor(variableName)
        val metaDataOffset = slots.getMetaDataOffsetFor(LOAD_CSV_METADATA_KEY)
        LoadCSVSlottedPipe(
          source,
          format,
          convertExpressions(url),
          lineVariableOffset,
          metaDataOffset,
          fieldTerminator,
          legacyCsvQuoteEscaping,
          bufferSize
        )(id)

      case _ =>
        fallback.onOneChildPlan(plan, source)
    }
    pipe.rowFactory = SlottedCypherRowFactory(slots, argumentSize)
    pipe
  }

  override def onTwoChildPlan(plan: LogicalPlan, lhs: Pipe, rhs: Pipe): Pipe = {

    val slotConfigs = physicalPlan.slotConfigurations
    val id = plan.id
    val convertExpressions = (e: internal.expressions.Expression) => expressionConverters.toCommandExpression(id, e)
    val slots = slotConfigs(id)
    // some plans (e.g. Apply) have no argument size attribute set
    val argumentSize = physicalPlan.argumentSizes.getOrElse(id, SlotConfiguration.Size.zero)
    finalizeSlotConfiguration(slots)

    val pipe = plan match {
      case Apply(_, _) =>
        ApplySlottedPipe(lhs, rhs)(id)

      case RollUpApply(_, rhsPlan, collectionName, identifierToCollect) =>
        val rhsSlots = slotConfigs(rhsPlan.id)
        val identifierToCollectExpression = createProjectionForVariable(rhsSlots, identifierToCollect)
        val collectionRefSlotOffset = slots.getReferenceOffsetFor(collectionName)
        RollUpApplySlottedPipe(lhs, rhs, collectionRefSlotOffset, identifierToCollectExpression, slots)(id = id)

      case _: CartesianProduct =>
        val lhsPlan = plan.lhs.get
        val lhsSlots = slotConfigs(lhsPlan.id)

        // Verify the assumption that the only shared slots we have are arguments which are identical on both lhs and rhs.
        // This assumption enables us to use array copy within CartesianProductSlottedPipe.
        checkOnlyWhenAssertionsAreEnabled(verifyOnlyArgumentsAreSharedSlots(plan, physicalPlan))

        CartesianProductSlottedPipe(lhs, rhs, lhsSlots.numberOfLongs, lhsSlots.numberOfReferences, slots, argumentSize)(
          id
        )

      case joinPlan: NodeHashJoin =>
        val nodes = joinPlan.nodes.toArray // Make sure that leftNodes and rightNodes have the same order

        val lhsSlots = slotConfigs(joinPlan.left.id)
        val rhsSlots = slotConfigs(joinPlan.right.id)

        val leftNodes = KeyOffsets.create(lhsSlots, nodes)
        val rightNodes = KeyOffsets.create(rhsSlots, nodes)

        // Verify the assumption that the argument slots are the same on both sides
        checkOnlyWhenAssertionsAreEnabled(verifyArgumentsAreTheSameOnBothSides(plan, physicalPlan))
        val rhsSlotMappings = computeSlotMappings(rhsSlots, argumentSize, slots)

        if (leftNodes.isSingle) {
          NodeHashJoinSlottedSingleNodePipe(leftNodes.asSingle, rightNodes.asSingle, lhs, rhs, slots, rhsSlotMappings)(
            id
          )
        } else {
          NodeHashJoinSlottedPipe(leftNodes, rightNodes, lhs, rhs, slots, rhsSlotMappings)(id)
        }

      case ValueHashJoin(lhsPlan, rhsPlan, Equals(lhsAstExp, rhsAstExp)) =>
        val lhsCmdExp = convertExpressions(lhsAstExp)
        val rhsCmdExp = convertExpressions(rhsAstExp)
        val rhsSlots = slotConfigs(rhsPlan.id)

        // Verify the assumption that the only shared slots we have are arguments which are identical on both lhs and rhs.
        // This assumption enables us to use array copy within ValueHashJoin.
        checkOnlyWhenAssertionsAreEnabled(verifyArgumentsAreTheSameOnBothSides(plan, physicalPlan))
        val rhsSlotMappings = computeSlotMappings(rhsSlots, argumentSize, slots)

        ValueHashJoinSlottedPipe(lhsCmdExp, rhsCmdExp, lhs, rhs, slots, rhsSlotMappings)(id)

      case ConditionalApply(left, right, items) =>
        val (longIds, refIds) = items.partition(idName =>
          slots.get(idName) match {
            case Some(_: LongSlot) => true
            case Some(_: RefSlot)  => false
            case _                 => throw new InternalException("We expect only an existing LongSlot or RefSlot here")
          }
        )
        val longOffsets = longIds.map(e => slots.getLongOffsetFor(e))
        val refOffsets = refIds.map(e => slots.getReferenceOffsetFor(e))
        val nullableSlots = computeSlotsDifference(right.availableSymbols, left.availableSymbols, slots)
        ConditionalApplySlottedPipe(lhs, rhs, longOffsets.toArray, refOffsets.toArray, slots, nullableSlots)(id)

      case AntiConditionalApply(left, right, items) =>
        val (longIds, refIds) = items.partition(idName =>
          slots.get(idName) match {
            case Some(_: LongSlot) => true
            case Some(_: RefSlot)  => false
            case _                 => throw new InternalException("We expect only an existing LongSlot or RefSlot here")
          }
        )
        val longOffsets = longIds.map(e => slots.getLongOffsetFor(e))
        val refOffsets = refIds.map(e => slots.getReferenceOffsetFor(e))
        val nullableSlots = computeSlotsDifference(right.availableSymbols, left.availableSymbols, slots)
        AntiConditionalApplySlottedPipe(lhs, rhs, longOffsets.toArray, refOffsets.toArray, slots, nullableSlots)(id)

      case ForeachApply(_, _, variable, expression) =>
        val innerVariableSlot =
          slots.get(variable).getOrElse(throw new InternalException(s"Foreach variable '$variable' has no slot"))
        ForeachSlottedApplyPipe(lhs, rhs, innerVariableSlot, convertExpressions(expression))(id)

      case TransactionForeach(_, _, batchSize, TransactionConcurrency.Serial, onErrorBehaviour, maybeReportAs) =>
        TransactionForeachSlottedPipe(
          lhs,
          rhs,
          expressionConverters.toCommandExpression(id, batchSize),
          onErrorBehaviour,
          maybeReportAs.map(slots.apply)
        )(id = id)

      case TransactionApply(
          lhsPlan,
          rhsPlan,
          batchSize,
          TransactionConcurrency.Serial,
          onErrorBehaviour,
          maybeReportAs
        ) =>
        TransactionApplySlottedPipe(
          lhs,
          rhs,
          expressionConverters.toCommandExpression(id, batchSize),
          onErrorBehaviour,
          (rhsPlan.availableSymbols.map(_.name) -- lhsPlan.availableSymbols.map(_.name)).map(slots.apply),
          maybeReportAs.map(slots.apply),
          argumentSize
        )(id = id)

      case TransactionForeach(
          _,
          _,
          batchSize,
          TransactionConcurrency.Concurrent(maybeConcurrency),
          onErrorBehaviour,
          maybeReportAs
        ) =>
        ConcurrentTransactionForeachSlottedPipe(
          lhs,
          rhs,
          expressionConverters.toCommandExpression(id, batchSize),
          maybeConcurrency.map(expressionConverters.toCommandExpression(id, _)),
          onErrorBehaviour,
          maybeReportAs.map(slots.apply)
        )(id = id)

      case TransactionApply(
          lhsPlan,
          rhsPlan,
          batchSize,
          TransactionConcurrency.Concurrent(maybeConcurrency),
          onErrorBehaviour,
          maybeReportAs
        ) =>
        ConcurrentTransactionApplySlottedPipe(
          lhs,
          rhs,
          expressionConverters.toCommandExpression(id, batchSize),
          maybeConcurrency.map(expressionConverters.toCommandExpression(id, _)),
          onErrorBehaviour,
          (rhsPlan.availableSymbols.map(_.name) -- lhsPlan.availableSymbols.map(_.name)).map(slots.apply),
          maybeReportAs.map(slots.apply),
          argumentSize
        )(id = id)

      case SelectOrSemiApply(_, _, expression) =>
        SelectOrSemiApplySlottedPipe(
          lhs,
          rhs,
          expressionConverters.toCommandExpression(id, expression),
          negated = false,
          slots
        )(id)

      case SelectOrAntiSemiApply(_, _, expression) =>
        SelectOrSemiApplySlottedPipe(
          lhs,
          rhs,
          expressionConverters.toCommandExpression(id, expression),
          negated = true,
          slots
        )(id)

      case Union(_, _) =>
        val lhsSlots = slotConfigs(lhs.id)
        val rhsSlots = slotConfigs(rhs.id)
        UnionSlottedPipe(
          lhs,
          rhs,
          slots,
          SlottedPipeMapper.computeUnionRowMapping(lhsSlots, slots),
          SlottedPipeMapper.computeUnionRowMapping(rhsSlots, slots)
        )(id = id)

      case OrderedUnion(_, _, sortedColumns) =>
        val lhsSlots = slotConfigs(lhs.id)
        val rhsSlots = slotConfigs(rhs.id)
        OrderedUnionSlottedPipe(
          lhs,
          rhs,
          slots,
          SlottedPipeMapper.computeUnionRowMapping(lhsSlots, slots),
          SlottedPipeMapper.computeUnionRowMapping(rhsSlots, slots),
          SlottedExecutionContextOrdering.asComparator(sortedColumns.map(translateColumnOrder(slots, _)))
        )(id = id)

      case AssertSameRelationship(relationship, _, _) =>
        AssertSameRelationshipSlottedPipe(lhs, rhs, relationship.name, slots(relationship))(id = id)

      case RepeatTrail(
          _,
          _,
          repetition,
          start,
          end,
          innerStart,
          innerEnd,
          groupNodes,
          groupRelationships,
          innerRelationships,
          previouslyBoundRelationships,
          previouslyBoundRelationshipGroups,
          reverseGroupVariableProjections
        ) =>
        val rhsSlots = slotConfigs(rhs.id)
        val lhsSlots = slotConfigs(lhs.id)
        RepeatSlottedPipe(
          lhs,
          rhs,
          repetition,
          slots(start),
          slots.getLongOffsetFor(end),
          rhsSlots.getLongOffsetFor(innerStart),
          rhsSlots(innerEnd),
          groupNodes.map(n => GroupSlot(rhsSlots(n.singleton), slots(n.group))).toArray,
          groupRelationships.map(r => GroupSlot(rhsSlots(r.singleton), slots(r.group))).toArray,
          TrailModeConstraint(
            rhsSlots.getMetaDataOffsetFor(SlotAllocation.TRAIL_STATE_METADATA_KEY, id),
            innerRelationships.map(r => rhsSlots(r)).toArray,
            previouslyBoundRelationships.map(r => lhsSlots(r)).toArray,
            previouslyBoundRelationshipGroups.map(r => lhsSlots(r)).toArray
          ),
          slots,
          rhsSlots,
          argumentSize,
          reverseGroupVariableProjections
        )(id = id)

      case RepeatWalk(
          _,
          _,
          repetition,
          start,
          end,
          innerStart,
          innerEnd,
          groupNodes,
          groupRelationships,
          reverseGroupVariableProjections
        ) =>
        val rhsSlots = slotConfigs(rhs.id)
        RepeatSlottedPipe(
          lhs,
          rhs,
          repetition,
          slots(start),
          slots.getLongOffsetFor(end),
          rhsSlots.getLongOffsetFor(innerStart),
          rhsSlots(innerEnd),
          groupNodes.map(n => GroupSlot(rhsSlots(n.singleton), slots(n.group))).toArray,
          groupRelationships.map(r => GroupSlot(rhsSlots(r.singleton), slots(r.group))).toArray,
          WalkModeConstraint,
          slots,
          rhsSlots,
          argumentSize,
          reverseGroupVariableProjections
        )(id = id)

      case _ =>
        fallback.onTwoChildPlan(plan, lhs, rhs)
    }
    pipe.rowFactory = SlottedCypherRowFactory(slots, argumentSize)
    pipe
  }

  private def chooseDistinctPipe(
    groupingExpressions: Map[LogicalVariable, internal.expressions.Expression],
    orderToLeverage: Seq[internal.expressions.Expression],
    slots: SlotConfiguration,
    source: Pipe,
    id: Id
  ): Pipe = {
    val convertExpressions = (e: internal.expressions.Expression) => expressionConverters.toCommandExpression(id, e)

    val runtimeProjections: Map[Slot, commands.expressions.Expression] = groupingExpressions.map {
      case (key, expression) =>
        slots(key) -> convertExpressions(expression)
    }

    val physicalDistinctOp = findDistinctPhysicalOp(groupingExpressions, orderToLeverage)

    physicalDistinctOp match {
      case DistinctAllPrimitive(offsets, orderedOffsets) if offsets.size == 1 && orderedOffsets.isEmpty =>
        val (toSlot, runtimeExpression) = runtimeProjections.head
        DistinctSlottedSinglePrimitivePipe(source, slots, toSlot, offsets.head, runtimeExpression)(id)

      case DistinctAllPrimitive(offsets, orderedOffsets) if offsets.size == 1 && orderedOffsets == offsets =>
        val (toSlot, runtimeExpression) = runtimeProjections.head
        OrderedDistinctSlottedSinglePrimitivePipe(source, slots, toSlot, offsets.head, runtimeExpression)(id)

      case DistinctAllPrimitive(offsets, orderedOffsets) =>
        if (orderToLeverage.isEmpty) {
          DistinctSlottedPrimitivePipe(
            source,
            slots,
            offsets.sorted.toArray,
            expressionConverters.toGroupingExpression(id, groupingExpressions, orderToLeverage)
          )(id)
        } else if (orderedOffsets == offsets) {
          AllOrderedDistinctSlottedPrimitivePipe(
            source,
            slots,
            offsets.sorted.toArray,
            expressionConverters.toGroupingExpression(id, groupingExpressions, orderToLeverage)
          )(id)
        } else {
          OrderedDistinctSlottedPrimitivePipe(
            source,
            slots,
            orderedOffsets.sorted.toArray,
            offsets.filterNot(orderedOffsets.contains(_)).sorted.toArray,
            expressionConverters.toGroupingExpression(id, groupingExpressions, orderToLeverage)
          )(id)
        }

      case DistinctWithReferences =>
        if (orderToLeverage.isEmpty) {
          DistinctSlottedPipe(
            source,
            slots,
            expressionConverters.toGroupingExpression(id, groupingExpressions, orderToLeverage)
          )(id)
        } else if (groupingExpressions.values.forall(orderToLeverage.contains)) {
          AllOrderedDistinctSlottedPipe(
            source,
            slots,
            expressionConverters.toGroupingExpression(id, groupingExpressions, orderToLeverage)
          )(id)
        } else {
          val (ordered, unordered) =
            partitionGroupingExpressions(expressionConverters, groupingExpressions, orderToLeverage, id)
          OrderedDistinctSlottedPipe(source, slots, ordered, unordered)(id)
        }
    }
  }

  // Verifies the assumption that all shared slots are arguments with slot offsets within the first argument size number of slots
  // and the number of shared slots are identical to the argument size.
  private def verifyOnlyArgumentsAreSharedSlots(plan: LogicalPlan, physicalPlan: PhysicalPlan): Boolean = {
    val argumentSize = physicalPlan.argumentSizes(plan.id)
    val lhsPlan = plan.lhs.get
    val rhsPlan = plan.rhs.get
    val lhsSlots = physicalPlan.slotConfigurations(lhsPlan.id)
    val rhsSlots = physicalPlan.slotConfigurations(rhsPlan.id)
    val sharedSlots =
      rhsSlots.filterSlots({
        case (VariableSlotKey(k), _) => lhsSlots.get(k).isDefined
        case (CachedPropertySlotKey(k), slot) =>
          slot.offset < argumentSize.nReferences && lhsSlots.hasCachedPropertySlot(k)
        case (DuplicatedSlotKey(k, offset), _) =>
          lhsSlots.hasDuplicateSlot(k, offset)
        case (key: MetaDataSlotKey, slot) => slot.offset < argumentSize.nReferences && lhsSlots.hasMetaDataSlot(key)
        case (key: ApplyPlanSlotKey, _)   => throw new InternalException(s"Unexpected slot key $key")
        case (key: OuterNestedApplyPlanSlotKey, _) => throw new InternalException(s"Unexpected slot key $key")
      })

    val (sharedLongSlots, sharedRefSlots) = sharedSlots.partition(_.isLongSlot)

    @nowarn("msg=return statement")
    def checkSharedSlots(slots: Seq[Slot], expectedSlots: Int): Boolean = {
      val sorted = slots.sortBy(_.offset)
      var prevOffset = -1
      for (slot <- sorted) {
        if (
          slot.offset == prevOffset || // if we have aliases for the same slot, we will get it again
          slot.offset == prevOffset + 1
        ) { // otherwise we expect the next shared slot to sit at the next offset
          prevOffset = slot.offset
        } else {
          return false
        }
      }
      prevOffset + 1 == expectedSlots
    }

    val longSlotsOk = checkSharedSlots(sharedLongSlots.toSeq, argumentSize.nLongs)
    val refSlotsOk = checkSharedSlots(sharedRefSlots.toSeq, argumentSize.nReferences)

    if (!longSlotsOk || !refSlotsOk) {
      val longSlotsMessage =
        if (longSlotsOk) "" else s"#long arguments=${argumentSize.nLongs} shared long slots: $sharedLongSlots "
      val refSlotsMessage =
        if (refSlotsOk) "" else s"#ref arguments=${argumentSize.nReferences} shared ref slots: $sharedRefSlots "
      throw new InternalException(
        s"Unexpected slot configuration. Shared slots not only within argument size: $longSlotsMessage$refSlotsMessage"
      )
    }

    true
  }

  private def verifyArgumentsAreTheSameOnBothSides(plan: LogicalPlan, physicalPlan: PhysicalPlan): Boolean = {
    val argumentSize = physicalPlan.argumentSizes(plan.id)
    val lhsPlan = plan.lhs.get
    val rhsPlan = plan.rhs.get
    val lhsSlots = physicalPlan.slotConfigurations(lhsPlan.id)
    val rhsSlots = physicalPlan.slotConfigurations(rhsPlan.id)

    val lhsArgLongSlots = mutable.ArrayBuffer.empty[(String, Slot)]
    val lhsArgRefSlots = mutable.ArrayBuffer.empty[(String, Slot)]
    val rhsArgLongSlots = mutable.ArrayBuffer.empty[(String, Slot)]
    val rhsArgRefSlots = mutable.ArrayBuffer.empty[(String, Slot)]
    lhsSlots.foreachSlotAndAliases({
      case SlotWithKeyAndAliases(VariableSlotKey(key), slot, aliases) =>
        if (slot.isLongSlot && slot.offset < argumentSize.nLongs) {
          lhsArgLongSlots += (key -> slot)
          aliases.foreach(alias => lhsArgLongSlots += (alias -> slot))
        } else if (!slot.isLongSlot && slot.offset < argumentSize.nReferences) {
          lhsArgRefSlots += (key -> slot)
          aliases.foreach(alias => lhsArgRefSlots += (alias -> slot))
        }
      case SlotWithKeyAndAliases(CachedPropertySlotKey(key), slot, _) =>
        if (slot.offset < argumentSize.nReferences) {
          lhsArgRefSlots += (key.asCanonicalStringVal -> slot)
        }
      case SlotWithKeyAndAliases(key: MetaDataSlotKey, slot, _) =>
        if (slot.offset < argumentSize.nReferences) {
          lhsArgRefSlots += (key.toString -> slot)
        }
      case SlotWithKeyAndAliases(key: ApplyPlanSlotKey, _, _) =>
        throw new InternalException(s"Unexpected slot key $key")
      case SlotWithKeyAndAliases(key: OuterNestedApplyPlanSlotKey, _, _) =>
        throw new InternalException(s"Unexpected slot key $key")
      case SlotWithKeyAndAliases(key: DuplicatedSlotKey, slot, _) =>
        if (slot.isLongSlot && slot.offset < argumentSize.nLongs) {
          lhsArgLongSlots += (key.toString -> slot)
        } else if (!slot.isLongSlot && slot.offset < argumentSize.nReferences) {
          lhsArgRefSlots += (key.toString -> slot)
        }
    })
    rhsSlots.foreachSlotAndAliases({
      case SlotWithKeyAndAliases(VariableSlotKey(key), slot, aliases) =>
        if (slot.isLongSlot && slot.offset < argumentSize.nLongs) {
          rhsArgLongSlots += (key -> slot)
          aliases.foreach(alias => rhsArgLongSlots += (alias -> slot))
        } else if (!slot.isLongSlot && slot.offset < argumentSize.nReferences) {
          rhsArgRefSlots += (key -> slot)
          aliases.foreach(alias => rhsArgRefSlots += (alias -> slot))
        }
      case SlotWithKeyAndAliases(CachedPropertySlotKey(key), slot, _) =>
        if (slot.offset < argumentSize.nReferences) {
          rhsArgRefSlots += (key.asCanonicalStringVal -> slot)
        }
      case SlotWithKeyAndAliases(key: MetaDataSlotKey, slot, _) =>
        if (slot.offset < argumentSize.nReferences) {
          rhsArgRefSlots += (key.toString -> slot)
        }
      case SlotWithKeyAndAliases(key: ApplyPlanSlotKey, _, _) =>
        throw new InternalException(s"Unexpected slot key $key")
      case SlotWithKeyAndAliases(key: OuterNestedApplyPlanSlotKey, _, _) =>
        throw new InternalException(s"Unexpected slot key $key")
      case SlotWithKeyAndAliases(key: DuplicatedSlotKey, slot, _) =>
        if (slot.isLongSlot && slot.offset < argumentSize.nLongs) {
          lhsArgLongSlots += (key.toString -> slot)
        } else if (!slot.isLongSlot && slot.offset < argumentSize.nReferences) {
          lhsArgRefSlots += (key.toString -> slot)
        }
    })

    def sameSlotsInOrder(a: ArrayBuffer[(String, Slot)], b: ArrayBuffer[(String, Slot)]): Boolean =
      a.sortBy(_._1).zip(b.sortBy(_._1)) forall {
        case ((k1, slot1), (k2, slot2)) =>
          k1 == k2 && slot1.offset == slot2.offset && slot1.isTypeCompatibleWith(slot2)
      }

    val longSlotsOk = lhsArgLongSlots.size == rhsArgLongSlots.size && sameSlotsInOrder(lhsArgLongSlots, rhsArgLongSlots)
    val refSlotsOk = lhsArgRefSlots.size == rhsArgRefSlots.size && sameSlotsInOrder(lhsArgRefSlots, rhsArgRefSlots)

    if (!longSlotsOk || !refSlotsOk) {
      val longSlotsMessage =
        if (longSlotsOk) "" else s"#long arguments=${argumentSize.nLongs} lhs: $lhsArgLongSlots rhs: $rhsArgLongSlots "
      val refSlotsMessage =
        if (refSlotsOk) "" else s"#ref arguments=${argumentSize.nReferences} lhs: $lhsArgRefSlots rhs: $rhsArgRefSlots "
      throw new InternalException(
        s"Unexpected slot configuration. Arguments differ between lhs and rhs: $longSlotsMessage$refSlotsMessage"
      )
    }
    true
  }
}

object SlottedPipeMapper {

  case class SlotMappings(
    slotMapping: Array[SlotMapping],
    cachedPropertyMappings: Array[(Int, Int)]
  )

  /** Computes slot mappings from one slot configuration to another
   *
   * Given the values:
   *                  0,    1, 2, 3, 4, 5, 6      0, 1,  2,  3,  4, 5
   *  fromSlots = [arg1, arg2, a, b, c]       [arg3, k, p1,  m, p2, d]
   *  toSlots =   [arg1, arg2, a, c, d, e, b] [arg3, k,  m, p1, p2]
   *  argumentSize.nLongs = 2
   *  argumentSize.nReferences = 1
   *
   * it produces the output:
   * SlotMapping:
   *  SlotMapping(fromOffset=2, toOffset=2, fromIsLongSlot=true, toIsLongSlot=true)
   *  SlotMapping(fromOffset=3, toOffset=6, fromIsLongSlot=true, toIsLongSlot=true)
   *  SlotMapping(fromOffset=4, toOffset=3, fromIsLongSlot=true, toIsLongSlot=true)
   *  SlotMapping(fromOffset=1, toOffset=1, fromIsLongSlot=false, toIsLongSlot=false)
   *  SlotMapping(fromOffset=3, toOffset=2, fromIsLongSlot=false, toIsLongSlot=false)
   *  SlotMapping(fromOffset=5, toOffset=4, fromIsLongSlot=false, toIsLongSlot=true)
   *
   * CachedPropertyMapping:
   *  (2 -> 3)
   *  (4 -> 4)
   *
   * */
  def computeSlotMappings(
    fromSlots: SlotConfiguration,
    argumentSize: SlotConfiguration.Size,
    toSlots: SlotConfiguration
  ): SlotMappings = {
    val slotMappings = collection.mutable.ArrayBuffer.newBuilder[SlotMapping]
    val cachedPropertyMappings = collection.mutable.ArrayBuffer.newBuilder[(Int, Int)]

    fromSlots.foreachSlotAndAliasesOrdered({
      case SlotWithKeyAndAliases(VariableSlotKey(key), fromSlot, _)
        if (fromSlot.isLongSlot && fromSlot.offset >= argumentSize.nLongs) || (!fromSlot.isLongSlot && fromSlot.offset >= argumentSize.nReferences) =>
        toSlots.get(key).foreach { toSlot =>
          slotMappings += SlotMapping(fromSlot.offset, toSlot.offset, fromSlot.isLongSlot, toSlot.isLongSlot)
        }
      case SlotWithKeyAndAliases(_: VariableSlotKey, _, _)             => // do nothing, part of arguments
      case SlotWithKeyAndAliases(_: ApplyPlanSlotKey, _, _)            => // do nothing, part of arguments
      case SlotWithKeyAndAliases(_: OuterNestedApplyPlanSlotKey, _, _) => // do nothing, part of arguments
      case SlotWithKeyAndAliases(_: DuplicatedSlotKey, _, _)           => // do nothing
      case SlotWithKeyAndAliases(CachedPropertySlotKey(cnp), _, _) =>
        val offset = fromSlots.getCachedPropertyOffsetFor(cnp)
        if (offset >= argumentSize.nReferences) {
          cachedPropertyMappings += offset -> toSlots.getCachedPropertyOffsetFor(cnp)
        }
      case SlotWithKeyAndAliases(key: MetaDataSlotKey, _, _) =>
        val fromOffset = fromSlots.getMetaDataOffsetFor(key)
        if (fromOffset >= argumentSize.nReferences) {
          val toOffset = toSlots.getMetaDataOffsetFor(key)
          slotMappings += SlotMapping(fromOffset, toOffset, fromIsLongSlot = false, toIsLongSlot = false)
        }

    })

    SlotMappings(slotMappings.result().toArray, cachedPropertyMappings.result().toArray)
  }

  /**
   * We use these objects to figure out:
   * a) can we use the primitive distinct pipe?
   * b) if we can, what offsets are interesting
   */
  sealed trait DistinctPhysicalOp {
    def addExpression(e: internal.expressions.Expression, ordered: Boolean): DistinctPhysicalOp
  }

  case class DistinctAllPrimitive(offsets: Seq[Int], orderedOffsets: Seq[Int]) extends DistinctPhysicalOp {

    override def addExpression(e: internal.expressions.Expression, ordered: Boolean): DistinctPhysicalOp = e match {
      case v: NodeFromSlot =>
        val oo = if (ordered) orderedOffsets :+ v.offset else orderedOffsets
        DistinctAllPrimitive(offsets :+ v.offset, oo)
      case v: RelationshipFromSlot =>
        val oo = if (ordered) orderedOffsets :+ v.offset else orderedOffsets
        DistinctAllPrimitive(offsets :+ v.offset, oo)
      case _ =>
        DistinctWithReferences
    }
  }

  case object DistinctWithReferences extends DistinctPhysicalOp {

    override def addExpression(e: internal.expressions.Expression, ordered: Boolean): DistinctPhysicalOp =
      DistinctWithReferences
  }

  def findDistinctPhysicalOp(
    groupingExpressions: Map[LogicalVariable, internal.expressions.Expression],
    orderToLeverage: Seq[internal.expressions.Expression]
  ): DistinctPhysicalOp = {
    groupingExpressions.foldLeft[DistinctPhysicalOp](DistinctAllPrimitive(Seq.empty, Seq.empty)) {
      case (acc: DistinctPhysicalOp, (_, expression)) =>
        acc.addExpression(expression, orderToLeverage.contains(expression))
    }
  }

  def createProjectionsForResult(
    columns: Seq[(LogicalVariable, Set[(LazyPropertyKey, Expression)])],
    slots: SlotConfiguration
  ): Seq[Expression] = {
    columns.map {
      case (v, cps) => createProjectionForVariable(slots, v, cps)._2
    }
  }

  private def createProjectionForVariable(
    slots: SlotConfiguration,
    variable: LogicalVariable,
    cachedProperties: Set[(LazyPropertyKey, Expression)] = Set.empty
  ): (String, Expression) = {
    val identifier = variable.name
    val slot = slots.get(identifier).getOrElse(
      throw new InternalException(s"Did not find `$identifier` in the slot configuration")
    )
    identifier -> SlottedPipeMapper.projectSlotExpression(slot, cachedProperties)
  }

  private def projectSlotExpression(
    slot: Slot,
    cachedProperties: Set[(LazyPropertyKey, Expression)] = Set.empty
  ): commands.expressions.Expression = {
    def createNodeFromSlot(offset: Int) = {
      if (cachedProperties.nonEmpty) {
        slotted.expressions.ValuePopulatingNodeFromSlot(offset, cachedProperties.toArray)
      } else {
        slotted.expressions.NodeFromSlot(offset)
      }
    }
    def createRelationshipFromSlot(offset: Int) = {
      if (cachedProperties.nonEmpty) {
        slotted.expressions.ValuePopulatingRelationshipFromSlot(offset, cachedProperties.toArray)
      } else {
        slotted.expressions.RelationshipFromSlot(offset)
      }
    }

    slot match {
      case LongSlot(offset, false, CTNode) => createNodeFromSlot(offset)
      case LongSlot(offset, true, CTNode) =>
        slotted.expressions.NullCheck(offset, createNodeFromSlot(offset))
      case LongSlot(offset, false, CTRelationship) =>
        createRelationshipFromSlot(offset)
      case LongSlot(offset, true, CTRelationship) =>
        slotted.expressions.NullCheck(offset, createRelationshipFromSlot(offset))

      case RefSlot(offset, _, _) =>
        slotted.expressions.ReferenceFromSlot(offset)

      case _ =>
        throw new InternalException(s"Do not know how to project $slot")
    }
  }

  /**
   * A mapping from one input slot configuration to the output slot configuration that dictates what to copy in a Union.
   */
  sealed trait UnionSlotMapping
  case class CopyLongSlot(sourceOffset: Int, targetOffset: Int) extends UnionSlotMapping
  case class CopyRefSlot(sourceOffset: Int, targetOffset: Int) extends UnionSlotMapping
  case class CopyCachedProperty(sourceOffset: Int, targetOffset: Int) extends UnionSlotMapping
  case class ProjectLongToRefSlot(sourceSlot: LongSlot, targetOffset: Int) extends UnionSlotMapping

  /**
   * A [[UnionSlotMapping]] is a function that actually performs the copying.
   */
  trait RowMapping extends {
    def mapRows(incoming: ReadableRow, outgoing: CypherRow, state: QueryState): Unit
  }

  /**
   * compute mapping from incoming to outgoing pipeline, the slot order may differ
   * between the output and the input (lhs and rhs) and it may be the case that
   * we have a reference slot in the output but a long slot on one of the inputs,
   * e.g. MATCH (n) RETURN n UNION RETURN 42 AS n
   */
  def computeUnionSlotMappings(in: SlotConfiguration, out: SlotConfiguration): Iterable[UnionSlotMapping] = {
    in.mapSlotsDoNotSkipAliases {
      case (VariableSlotKey(k), inLongSlot: LongSlot) =>
        out.get(k).map {
          case l: LongSlot => CopyLongSlot(inLongSlot.offset, l.offset)
          case r: RefSlot  => ProjectLongToRefSlot(inLongSlot, r.offset)
        }
      case (VariableSlotKey(k), inSlot: RefSlot) =>
        // This means out must be a ref slot as well, if it exists, otherwise slot allocation was wrong
        out.get(k).map {
          case l: LongSlot => throw new IllegalStateException(s"Expected Union output slot to be a refslot but was: $l")
          case r: RefSlot  => CopyRefSlot(inSlot.offset, r.offset)
        }
      case (CachedPropertySlotKey(cachedProp), inRefSlot) =>
        out.getCachedPropertySlot(cachedProp).map {
          outRefSlot => CopyCachedProperty(inRefSlot.offset, outRefSlot.offset)
        }
      case (key: MetaDataSlotKey, inRefSlot) =>
        out.getMetaDataSlot(key).map {
          outRefSlot => CopyRefSlot(inRefSlot.offset, outRefSlot.offset)
        }
      case (ApplyPlanSlotKey(id), slot) =>
        out.getArgumentSlot(id).map {
          outArgumentSlot => CopyLongSlot(slot.offset, outArgumentSlot.offset)
        }
      case (OuterNestedApplyPlanSlotKey(id), slot) =>
        out.getNestedArgumentSlot(id).map {
          outArgumentSlot => CopyLongSlot(slot.offset, outArgumentSlot.offset)
        }
      case (DuplicatedSlotKey(_, _), _) => None
    }.flatten
  }

  /**
   * Compute the [[RowMapping]] from [[UnionSlotMapping]]s, which can be then applied to Rows at runtime.
   */
  def computeUnionRowMapping(
    in: SlotConfiguration,
    out: SlotConfiguration,
    longsToNull: Array[Int] = Array.empty,
    refsToNull: Array[Int] = Array.empty
  ): RowMapping = {
    val mappings = computeUnionSlotMappings(in, out)

    // Collect all 4 types of mappings
    case class ProjectExpressionToRefSlot(expression: Expression, targetOffset: Int)
    val copyLongSlots = mappings.collect { case c: CopyLongSlot => c }.toArray.sortBy(_.targetOffset)
    val copyRefSlots = mappings.collect { case c: CopyRefSlot => c }.toArray.sortBy(_.targetOffset)
    val copyCachedProperties = mappings.collect { case c: CopyCachedProperty => c }.toArray.sortBy(_.targetOffset)
    val projectExpressionToRefSlots = mappings.collect {
      case c: ProjectLongToRefSlot =>
        // Pre-compute projection expression
        val projectionExpression = projectSlotExpression(c.sourceSlot)
        ProjectExpressionToRefSlot(projectionExpression, c.targetOffset)
    }.toArray.sortBy(_.targetOffset)

    // Apply all transformations
    (in, out, state) => {
      var i = 0
      while (i < copyLongSlots.length) {
        val x = copyLongSlots(i)
        out.setLongAt(x.targetOffset, in.getLongAt(x.sourceOffset))
        i += 1
      }
      i = 0
      while (i < copyRefSlots.length) {
        val x = copyRefSlots(i)
        out.setRefAt(x.targetOffset, in.getRefAt(x.sourceOffset))
        i += 1
      }
      i = 0
      while (i < copyCachedProperties.length) {
        val x = copyCachedProperties(i)
        out.setCachedPropertyAt(x.targetOffset, in.getCachedPropertyAt(x.sourceOffset))
        i += 1
      }
      i = 0
      while (i < projectExpressionToRefSlots.length) {
        val x = projectExpressionToRefSlots(i)
        out.setRefAt(x.targetOffset, x.expression(in, state))
        i += 1
      }
      i = 0
      while (i < longsToNull.length) {
        out.setLongAt(longsToNull(i), StatementConstants.NO_SUCH_ENTITY)
        i += 1
      }
      i = 0
      while (i < refsToNull.length) {
        out.setRefAt(refsToNull(i), NO_VALUE)
        i += 1
      }
    }
  }

  /**
   * Translate a [[plans.ColumnOrder]] into a [[slotted.ColumnOrder]] where every row is expected to have the same slot configuration
   */
  def translateColumnOrder(slots: SlotConfiguration, s: plans.ColumnOrder): slotted.ColumnOrder = s match {
    case plans.Ascending(name) =>
      slots.get(name) match {
        case Some(slot) => slotted.Ascending(slot)
        case None       => throw new InternalException(s"Did not find `$name` in the pipeline information")
      }
    case plans.Descending(name) =>
      slots.get(name) match {
        case Some(slot) => slotted.Descending(slot)
        case None       => throw new InternalException(s"Did not find `$name` in the pipeline information")
      }
  }

  /**
   * Translate a [[plans.ColumnOrder]] into a [[slotted.ColumnOrder2]] where rows have different slot configurations depending
   * on which downstream they originate from (lhs or rhs).
   */
  def translateColumnOrder2(
    lhsSlots: SlotConfiguration,
    rhsSlots: SlotConfiguration,
    s: plans.ColumnOrder
  ): slotted.ColumnOrder2 = s match {
    case plans.Ascending(name) =>
      val lhsSlot = lhsSlots.get(name) match {
        case Some(slot) => slot
        case None       => throw new InternalException(s"Did not find `$name` in the pipeline information")
      }
      val rhsSlot = rhsSlots.get(name) match {
        case Some(slot) => slot
        case None       => throw new InternalException(s"Did not find `$name` in the pipeline information")
      }
      Ascending2(lhsSlot, rhsSlot)

    case plans.Descending(name) =>
      val lhsSlot = lhsSlots.get(name) match {
        case Some(slot) => slot
        case None       => throw new InternalException(s"Did not find `$name` in the pipeline information")
      }
      val rhsSlot = rhsSlots.get(name) match {
        case Some(slot) => slot
        case None       => throw new InternalException(s"Did not find `$name` in the pipeline information")
      }
      Descending2(lhsSlot, rhsSlot)
  }

  def partitionGroupingExpressions(
    expressionConverters: ExpressionConverters,
    groupingExpressions: Map[LogicalVariable, internal.expressions.Expression],
    orderToLeverage: Seq[internal.expressions.Expression],
    id: Id
  ): (GroupingExpression, GroupingExpression) = {
    val (orderedGroupingExpressions, unorderedGroupingExpressions) = groupingExpressions.partition { case (_, v) =>
      orderToLeverage.contains(v)
    }
    val orderedGroupingColumns =
      expressionConverters.toGroupingExpression(id, orderedGroupingExpressions, orderToLeverage)
    val unorderedGroupingColumns =
      expressionConverters.toGroupingExpression(id, unorderedGroupingExpressions, orderToLeverage)
    (orderedGroupingColumns, unorderedGroupingColumns)
  }

  def computeSlotsDifference(
    left: Set[LogicalVariable],
    right: Set[LogicalVariable],
    slotConfiguration: SlotConfiguration
  ): Array[Slot] = {
    val leftSlots = left.map(slotConfiguration(_))
    val rightSlots = right.map(slotConfiguration(_))
    (leftSlots -- rightSlots).toArray
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy