exec

package
v0.11.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 13, 2026 License: MIT Imports: 30 Imported by: 0

Documentation

Overview

Package exec implements the Volcano-style executor for the Cypher query engine. It defines the Operator interface, the Row/RowSlab data model, and the pipeline driver Drain.

Data model

A Row is a slice of expr.Value. A RowSlab is a bounded, pooled container of pre-allocated rows used to eliminate per-row heap allocations in the hot path.

Concurrency

RowSlab is NOT safe for concurrent use. Each goroutine must obtain its own slab from NewRowSlab or from a sync.Pool managed by the caller. The exported SlabPool provides a ready-to-use pool with default capacity.

Index

Examples

Constants

View Source
const (
	VLEDirForward = 0
	VLEDirReverse = 1
)

VLEDirForward and VLEDirReverse are the direction-marker values stored at the third slot of each hop triple in the VarLengthExpand flat path list (stride VLEHopStride). A reverse marker tells the relationship hydrator to swap the storage endpoints when resolving the edge's per-instance type and properties, and tells the path renderer to emit `<-[…]-` (rmp #1685).

View Source
const DefaultChunkCapacity = 4096

DefaultChunkCapacity is the default per-column row capacity of a Chunk. It matches DefaultSlabCapacity so a Chunk aligns with the pipeline's row-batch boundary; the capacity is a pre-sizing hint (appends beyond it grow the backing), not a hard bound.

View Source
const DefaultMaxDistinct = 10_000_000

DefaultMaxDistinct is the default upper bound on distinct rows tracked by the Distinct operator.

View Source
const DefaultMaxEagerRows = 10_000_000

DefaultMaxEagerRows is the default upper bound on rows that Eager holds in memory. It matches the sibling pipeline-breaker caps (DefaultMaxSortRows, DefaultMaxDistinct).

View Source
const DefaultMaxGroups = 1_000_000

DefaultMaxGroups is the default upper bound on distinct groups that EagerAggregation will hold in memory.

View Source
const DefaultMaxSortRows = 10_000_000

DefaultMaxSortRows is the default upper bound on rows that Sort holds in memory.

View Source
const DefaultMorselSize = 1024

DefaultMorselSize is the number of NodeIDs processed per worker goroutine per scheduling quantum. Sized to fill roughly one or two cache lines of work before touching a channel.

View Source
const DefaultSlabCapacity = 4096

DefaultSlabCapacity is the default maximum number of rows a RowSlab holds before returning ErrSlabOverflow. It is sized to keep a typical pipeline batch within a few cache lines.

View Source
const MetricExpandIntersectEngaged = "cypher.expand_intersect.engaged"

MetricExpandIntersectEngaged counts how many times a fused cyclic expand was actually initialised for execution.

This is the white-box engagement counter, and it is not optional observability: SPIKE #2155 verified that the openCypher TCK contains NO directed cycle over three or more distinct node variables, so the 3897/3897 gate stays green whether this operator is correct, wrong, or never runs at all. A differential comparing the flag on against the flag off is likewise blind to an operator that silently declined to engage — both arms would simply run today's plan and agree. Only a counter distinguishes "identical because it is correct" from "identical because it never fired".

View Source
const VLEHopStride = 3

VLEHopStride is the per-hop element count of the flat alternating path list that VarLengthExpand emits. The list is

[srcNodeID, fwdPos0, dstNode0, dir0, fwdPos1, dstNode1, dir1, …]

so an N-hop path occupies 1 + VLEHopStride*N elements: the leading source node id, then one (forward edge position, destination node id, direction marker) triple per hop. The direction marker is VLEDirForward or VLEDirReverse. Readers in the cypher package (the path/relationship-list hydrators) and [appendExcludedFromValue] index the list with this stride; it is exported so those readers share the single source of truth (rmp #1685).

Before #1685 the stride was 2 ((edgePos, dst) pairs) and the edge position was synthetic for a reverse hop, which lost both the physical-edge identity and the traversal direction the hydrator needs for per-instance type and property reporting on multigraph parallel edges.

Variables

View Source
var ErrAggMemoryExceeded = errors.New("exec: aggregation memory cap exceeded")

ErrAggMemoryExceeded is returned by EagerAggregation.Next when the number of distinct groups exceeds the configured maxGroups limit.

View Source
var ErrConstraintAlreadyExists = errors.New("exec: constraint already exists")

ErrConstraintAlreadyExists is the sentinel returned (wrapped) when CREATE CONSTRAINT (without IF NOT EXISTS) names a constraint whose (kind, label, property) identity is already registered. It is the analogue of Neo4j's EquivalentSchemaRuleAlreadyExists / ConstraintAlreadyExists — a constraint- level fault surfaced instead of leaking the synthetic backing-index name.

View Source
var ErrConstraintNameConflict = errors.New("exec: constraint name already in use")

ErrConstraintNameConflict is the sentinel returned (wrapped) when CREATE CONSTRAINT requests a name already held by a DIFFERENT constraint (a distinct kind/label/property). Neo4j requires constraint names to be unique across the database; this is the analogue of its ConstraintWithNameAlreadyExists.

View Source
var ErrConstraintNotFound = errors.New("exec: constraint not found")

ErrConstraintNotFound is the sentinel returned (wrapped) when a DROP CONSTRAINT names a constraint that does not exist and IF EXISTS was not given. It is the fail-stop analogue of Neo4j's ConstraintDropFailed / "No such constraint": the drop reports a typed error rather than fail-silently claiming success.

View Source
var ErrConstraintViolation = errors.New("exec: constraint violation")

ErrConstraintViolation is the sentinel returned (wrapped) by CheckSetProperty when a write would violate a constraint.

View Source
var ErrDeleteNodeHasRelationships = errors.New("exec: cannot delete node with existing relationships; use DETACH DELETE")

ErrDeleteNodeHasRelationships is returned when DELETE is attempted on a node that still has one or more incident relationships. Use DETACH DELETE to remove the node together with its relationships.

View Source
var ErrDistinctMemoryExceeded = errors.New("exec: distinct memory cap exceeded")

ErrDistinctMemoryExceeded is returned by Distinct.Next when the number of distinct rows seen exceeds the configured maxDistinct limit.

View Source
var ErrEagerMemoryExceeded = errors.New("exec: eager memory cap exceeded")

ErrEagerMemoryExceeded is returned by Eager.Init when the drained child produces more than maxRows rows.

View Source
var ErrHashJoinMemoryExceeded = errors.New("exec: hash join memory cap exceeded")

ErrHashJoinMemoryExceeded is returned by the HashJoin build phase when the estimated retained size of the build table exceeds the configured byte budget (#1841). The build table is otherwise unbounded, so without a budget a large build side could exhaust memory before any drain-level guard fires.

View Source
var ErrIndexTypeMismatch = errors.New("exec: index type mismatch")

ErrIndexTypeMismatch is returned by NodeByIndexSeek.Init when the seek value's Kind is incompatible with the index's key type.

View Source
var ErrNestedPropertyValue = errors.New("exec: InvalidPropertyType: a nested list or map is not a valid property value")

ErrNestedPropertyValue is returned when a property value is a nested collection — a list containing a list or a map. openCypher restricts a property value to a primitive or a flat list of primitives; a nested collection is InvalidPropertyType. Unlike a non-literal expression (which is deferred to a runtime PropsEvalFn), this sentinel is a hard, fail-stop error: the CREATE/MERGE literal builders re-raise it rather than silently skipping the property, so an invalid value is never stored (the storage layer cannot serialise a nested PropList) and never silently dropped (audit 2026-07-13 security F3).

View Source
var ErrProjectionRowTooLarge = errors.New("exec: projection row memory cap exceeded")

ErrProjectionRowTooLarge is returned by Project.Next when the estimated size of a single assembled output row exceeds the configured per-row byte budget. It bounds the transient peak of one row's construction (e.g. a RETURN that projects several large list columns) to the ceiling plus one column, independent of the column count, so a query cannot OOM the process by compounding many big columns into one row (#1852).

View Source
var ErrPropertyValueIsNull = errors.New("exec: property value is null (skip)")

ErrPropertyValueIsNull is the sentinel returned by [parsePropValue] when the value is the literal "null". By openCypher semantics, assigning null to a property removes it (or never sets it for a fresh node), so callers that catch this sentinel must skip the property entirely rather than surface a parse error.

View Source
var ErrSchemaMismatch = errors.New("exec: union schema mismatch: column counts differ")

ErrSchemaMismatch is returned when the left and right operands of a UNION produce rows with different column counts.

View Source
var ErrSeekSetOverBudget = errors.New("exec: index seek set exceeds its posting budget")

ErrSeekSetOverBudget is returned by NodeByIndexSeekSet.Init when the merged posting count exceeds the budget the operator was built with. It is a planning signal, not a failure: the caller answers the query by scanning instead.

View Source
var ErrSlabOverflow = errors.New("exec: row slab overflow")

ErrSlabOverflow is returned by RowSlab.Alloc when the slab has reached its capacity limit. Callers must flush or reset the slab before continuing.

View Source
var ErrSortMemoryExceeded = errors.New("exec: sort memory cap exceeded")

ErrSortMemoryExceeded is returned when Sort collects more than maxRows rows.

View Source
var ErrUnwindNilChild = errors.New("exec: NewUnwind requires non-nil child Operator")

ErrUnwindNilChild is returned by NewUnwind when child is nil.

View Source
var ErrUnwindNilListFn = errors.New("exec: NewUnwind requires non-nil listFn")

ErrUnwindNilListFn is returned by NewUnwind when listFn is nil.

View Source
var ErrVarLenCapExceeded = errors.New("exec: variable-length expand safety cap exceeded")

ErrVarLenCapExceeded is returned when a VarLengthExpand exceeds its configured maximum edge traversal count — either the per-input-row cap or the aggregate per-query cap (see [defaultMaxEdgesTraversed] and [defaultMaxTotalEdgesTraversed]).

Functions

func BitSet added in v0.9.0

func BitSet(bitmap []uint64, i int) bool

BitSet reports whether bit i of a packed validity bitmap is set (LSB-first, the Arrow convention). It is the canonical test for a raw bitmap returned by the vectorized column accessors when allValid is false.

func EmitsExactly added in v0.11.0

func EmitsExactly(op Operator, cols []string) bool

EmitsExactly reports whether op is statically known to emit exactly cols, in that order — both the column NAMES and the row ARITY.

The arity half is what makes it safe to act on. A caller holding only a variable-to-index schema can tell where a column SITS but not how WIDE the row is, so `MATCH (a)-[r]->(b:P) RETURN a, r` looks like an identity — both columns are already at their own index — while the row that actually arrives carries a third column that a passthrough projection must still narrow away. Project.Columns reports the real output arity, which closes that gap.

The walk descends through operators that re-emit their input row UNCHANGED in width and column order (see rowShapePreserving), so `RETURN n LIMIT 3` — Limit over Project — is recognised as readily as a bare `RETURN n`. Any operator that neither declares its columns nor preserves the row shape stops the descent and yields false, so a shape-changing operator can never be walked through by omission.

func EnforceUniqueOnLabelRemove added in v0.11.0

func EnforceUniqueOnLabelRemove(
	reg *ConstraintRegistry, mutator GraphMutator, nodeKey, label string,
)

EnforceUniqueOnLabelRemove gives back what detaching label frees. It MUST run before the label is actually detached — it reads both the membership and the property values, and after the write neither is available. See EnforceUniqueOnLabelSet for the placement rationale.

func EnforceUniqueOnLabelSet added in v0.11.0

func EnforceUniqueOnLabelSet(
	reg *ConstraintRegistry, mutator GraphMutator, mgr *index.Manager, nodeKey, label string,
) error

EnforceUniqueOnLabelSet and EnforceUniqueOnLabelRemove are the LABEL half of the choke point (rmp #2358): the mutator adapter calls them from inside its own SetNodeLabel / RemoveNodeLabel, so an operator that writes a label cannot skip enforcement by forgetting to call anything.

Why enforcement moved here, and what it is worth

GoGraph used to enforce UNIQUE at each write SITE. Nothing in that design forced a NEW site to enforce anything: an operator that wrote a label and forgot to reserve compiled, passed every existing test, and silently admitted duplicates. That happened twice in one sprint — the SetLabels operator (rmp #2352), then both MERGE action paths — which is why the placement, not the semantics, was the defect.

Both reference engines enforce at the single write surface every operator must traverse. Memgraph's VertexAccessor::AddLabel does both halves itself (src/storage/v2/vertex_accessor.cpp:232, commit 343f7fe); Neo4j's Operations.nodeAddLabel validates through checkConstraintsAndAddLabelToNode BEFORE txState().nodeDoAddLabel, and nodeRemoveLabel is symmetric (community/kernel/.../newapi/Operations.java:791-850, commit eccd584). GoGraph already used this shape for its own NOT NULL enforcement — the touch lives in the adapters — so UNIQUE was the outlier.

The zero-constraint path stays free

ConstraintRegistry.HasAnyUnique is a lock-free atomic load and it is the gate here, so a schema with no UNIQUE constraint pays one nil check and one atomic load per label write and nothing else: no registry lock, no graph read, no allocation. That matters — [ConstraintRegistry.uniqueActive] records this registry's lock at 57 % of ALL lock delay at sixteen writers on a schema with no constraints at all.

The caller passes ITSELF as mutator, which is what makes the transaction-visible reads ([labelsInTx], [txVisibleNodeReader]) resolve through the writing transaction rather than the raw graph.

func EnforceUniqueOnPropertyDelete added in v0.11.0

func EnforceUniqueOnPropertyDelete(
	reg *ConstraintRegistry, mutator GraphMutator, nodeKey, key string,
)

EnforceUniqueOnPropertyDelete gives back what removing a property frees, so a later legitimate write of the same value is not refused by a phantom.

It MUST run before the property is removed: it reads the value it is releasing. SET n.k = null is a removal and reaches here through the same door.

func EnforceUniqueOnPropertySet added in v0.11.0

func EnforceUniqueOnPropertySet(
	reg *ConstraintRegistry, mutator GraphMutator, mgr *index.Manager,
	nodeKey, key string, value lpg.PropertyValue,
) error

EnforceUniqueOnPropertySet is the PROPERTY half of the choke point (rmp #2358): the mutator adapter calls it from inside its own SetNodeProperty, so an operator that writes a property cannot skip enforcement.

The release must precede the check, and it is not an optimisation

The node's OWN old value is released first, even when it equals the new one. Without that, an idempotent self-set — SET n.k = n.k, and every statement that rewrites a value unchanged — would be rejected as a duplicate of ITSELF. Releasing first cannot mask a real cross-node duplicate, because a UNIQUE constraint guarantees at most one holder of a value: whatever is released was this node's.

Which labels

[labelsInTx], never the raw label set: a UNIQUE constraint attaches to a label, so deciding what to reserve starts by asking which labels the node carries, and asking the raw graph would include another in-flight transaction's uncommitted label writes. That is the defect rmp #2355 fixed at each site individually, and having ONE site is what stops the next one from drifting.

The old value is likewise read through the transaction's view rather than the raw store — for the same reason, and because a value this transaction has already written is the one it must give back.

Callers gate on nothing: the zero-constraint check is here, and it is a lock-free atomic load.

func ExpandIntoSeekCount added in v0.11.0

func ExpandIntoSeekCount() uint64

ExpandIntoSeekCount reports how many times an expand-into hop has narrowed its FORWARD cursor to a bound destination's run since process start. It is a diagnostic seam for tests that must prove the seek actually FIRED — an EXPLAIN line proves the plan, this proves the access path — and for operational observability. Process-global and monotonic; callers snapshot it before and after a query rather than resetting it.

func ExpandIntoSeekReverseCount added in v0.11.0

func ExpandIntoSeekReverseCount() uint64

ExpandIntoSeekReverseCount reports the same for the REVERSE cursor, which a DirIn or DirBoth closing hop narrows.

It is counted separately because the two are independently defeatable and a result comparison cannot tell them apart: dropping the reverse narrowing makes the operator fall back to walking the whole in-edge range, which is SLOWER but returns exactly the same rows in the same order. Without its own counter that regression is invisible to every differential test — verified by injecting it.

func MergeActionEvalKey added in v0.8.0

func MergeActionEvalKey(targetVar, key string) string

MergeActionEvalKey composes the map key under which a MERGE ON CREATE / ON MATCH property-set action's per-row RHS evaluator is registered and looked up. targetVar is the entity variable the action writes (a node, a bound endpoint, or a relationship variable) and key is the property key. The NUL separator cannot appear in a Cypher identifier, so the composed key is unambiguous across distinct (variable, property) pairs. The physical builder ([cypher] package) and the operators here must agree on this encoding, so it is defined once and exported.

func NewUniqueBackingIndex added in v0.2.0

func NewUniqueBackingIndex() index.Subscriber

NewUniqueBackingIndex returns a fresh unbound hash-index subscriber. It is kept for callers (tests, recovery) that do not have a live graph and therefore cannot build a bound index. The engine's write path always supplies a bound subscriber via CreateConstraintOp.WithBackingIndex so the index self-maintains from the change fan-out.

func PropMapContainsNullLiteral

func PropMapContainsNullLiteral(s string) bool

PropMapContainsNullLiteral reports whether the property-map source string contains any value that is the literal `null`. It is used by MERGE to surface the openCypher `MergeReadOwnWrites` error when a merge predicate contains a null property value — such a merge can never match its own write because null comparisons are always tri-valued false, so the engine rejects the pattern outright. Used at MERGE plan-build time; CREATE silently drops null-valued properties so it does NOT use this check.

The argument may be either a single map literal "{k: v, …}" or a larger surface form (e.g. a full pattern string "(a)-[r:T {k: null}]->(b)") in which case every embedded balanced "{...}" segment is scanned. The check splits each map at top-level commas, isolates each value substring, and reports true if any value (case-insensitively) equals the bare token `null`. Variable refs and expressions are ignored — they parse to non-literal forms.

func RenderPlan added in v0.11.0

func RenderPlan(op Operator) string

RenderPlan renders the physical plan rooted at op as an indented tree, in the same shape the logical-plan renderer uses so the two read alike:

ProduceResults
└─ EagerAggregation
   └─ HashJoin [build=a, probe=b]
      ├─ NodeByLabelScan [a:P]
      └─ NodeByLabelScan [b:P]

A profiled plan appends each operator's emitted rows and self time.

func RenderPlanNode added in v0.11.0

func RenderPlanNode(n *PlanNode) string

RenderPlanNode renders an already-captured tree, so a caller that kept a PlanNode (for example from a profiling run whose operators are closed) can still print it. It does not modify n.

When any node in the tree carries measurements, the ones that do not are labelled "(not measured)" rather than left bare. That distinction is load bearing: a bare node in a profiled plan would read as an operator that cost nothing, when in fact it was never instrumented.

Such nodes USED to exist: instrumentation is applied at one point, the value the recursive builder returns, and a composite lowering emits several operators for a single logical node, of which only the outermost passed through it. rmp #2237 closed that by instrumenting each composite site, and TestProfile_EveryOperatorIsMeasured holds it closed. The label is kept because naming an unmeasured node honestly is still preferable to hiding it or inventing a zero, and a future composite lowering could reopen the gap.

func UniqueIndexName added in v0.2.0

func UniqueIndexName(label, prop string) string

UniqueIndexName returns the deterministic synthetic name of the hash index that backs a UNIQUE constraint on (label, prop). It is exported so the engine can re-create the same backing index when re-registering a constraint recovered from disk, keeping the name in lockstep with the one the CreateConstraint operator uses.

func WithCyphermorphism

func WithCyphermorphism(relCols []int) expandOption

WithCyphermorphism returns an option that enables cyphermorphism enforcement. relCols lists the column indices in each input row that hold existing edge IDs (as expr.IntegerValue). When Expand is about to emit a row with a new edgeID, it rejects the row if edgeID equals any value already present in those columns.

Types

type AdjacencySource added in v0.11.0

type AdjacencySource func() (fwd, rev CSRAdjacency, edgeTypeFilter map[uint64]string)

AdjacencySource yields the adjacency a traversal expands over, RESOLVED AT THE MOMENT IT IS CALLED rather than when the plan was built (rmp #2317).

Why a source and not a pair

A relationship traversal used to receive two prebuilt CSRs, materialised while the operator tree was being assembled — before any row executed. That froze the topology to an instant chosen before the statement began, and it is why a later clause of a statement could not observe an earlier edge CREATE or edge DELETE while it observed every node write: the node side reads live stores, the edge side read a frozen array.

Both reference engines resolve relationships at execution time against the transaction's own view — Memgraph's Expand::ExpandCursor::InitEdges goes through vertex.OutEdges with a storage::View, and Neo4j's query context resolves relationships per row rather than from a plan-time structure.

The type filter is part of the source, not a separate config field, because it is KEYED to the adjacency it was built against. Resolving one without the other would apply a filter built for one topology to a different one.

It is called from Expand.Init, which runs once per outer row under Apply, so the traversal follows the writes its own statement has made.

func StaticAdjacency added in v0.11.0

func StaticAdjacency(fwd, rev CSRAdjacency, edgeTypeFilter map[uint64]string) AdjacencySource

StaticAdjacency is an AdjacencySource over a fixed pair, for callers that genuinely hold one — an offline traversal, or a test that builds its own CSR. A production query plan must NOT use it: the pair it closes over is exactly the plan-build materialisation this type exists to remove.

type AggInputFactory added in v0.10.0

type AggInputFactory func(ids []graph.NodeID) (Operator, error)

AggInputFactory builds an independent sub-plan that emits the PRE-AGGREGATION rows for exactly the node IDs in ids: one row per node in scan order, laid out as [groupKey0..groupKey{nKeys-1}, aggArg0..aggArg{nAggs-1}] — the same layout EagerAggregation's pre-projection installs, evaluated by the same closures, so each worker's per-node key/argument values are byte-identical to serial. Each call must return a fresh operator sharing NO mutable state with any other call's (the planner rebuilds the pre-projection over a per-worker walker and a per-worker buildOpts copy). The ids slice is owned by the caller and is read-only for the operator's lifetime. The returned operator is driven Init → Next* → Close by exactly one worker goroutine.

type AggReducerKind added in v0.10.0

type AggReducerKind uint8

AggReducerKind identifies the deterministic, byte-identical-to-serial combine a ParallelAggregateScan reducer applies to one aggregate column. Only these four kinds have an admitted parallel combine; the planner emits no other kind.

const (
	// ReduceCountStar counts every row (count(*)). The combine is int64 addition.
	ReduceCountStar AggReducerKind = iota
	// ReduceCount counts non-NULL argument values (count(v)). The combine is int64
	// addition; the NULL-skip is a per-value predicate independent of partition.
	ReduceCount
	// ReduceMin keeps the minimum argument value under [expr.Compare], ties broken
	// by lowest global scan index (position-carrying). NULLs are skipped.
	ReduceMin
	// ReduceMax keeps the maximum argument value under [expr.Compare], ties broken
	// by lowest global scan index (position-carrying). NULLs are skipped.
	ReduceMax
)

type AllNodesCountScan added in v0.10.0

type AllNodesCountScan struct {
	// contains filtered or unexported fields
}

AllNodesCountScan is a Volcano leaf operator that computes a group-by-less count over a bare full-node scan by reading the graph's live-node count directly. It emits exactly one row with a single expr.IntegerValue column carrying that count.

AllNodesCountScan is NOT safe for concurrent use.

func NewAllNodesCountScan added in v0.10.0

func NewAllNodesCountScan(g nodeWalker) *AllNodesCountScan

NewAllNodesCountScan creates an AllNodesCountScan over g.

func (*AllNodesCountScan) Close added in v0.10.0

func (op *AllNodesCountScan) Close() error

Close releases resources held by the operator. AllNodesCountScan holds none, so Close is a no-op and is safe to call whether or not Next was ever called.

func (*AllNodesCountScan) Init added in v0.10.0

func (op *AllNodesCountScan) Init(ctx context.Context) error

Init reads the live-node count once. It prefers the O(1) direct counter when g supports it and otherwise falls back to a single WalkNodeIDs count pass — both yield the same tombstone-excluded live count.

func (*AllNodesCountScan) Next added in v0.10.0

func (op *AllNodesCountScan) Next(out *Row) (bool, error)

Next emits the single count row on its first call and reports end-of-stream thereafter.

type AllNodesScan

type AllNodesScan struct {
	// contains filtered or unexported fields
}

AllNodesScan is a Volcano leaf operator that produces one Row per node in the graph. Each Row has a single column: an expr.IntegerValue holding the node's graph.NodeID cast to int64.

AllNodesScan is NOT safe for concurrent use.

func NewAllNodesScan

func NewAllNodesScan(g nodeWalker) *AllNodesScan

NewAllNodesScan creates an AllNodesScan over g.

func (*AllNodesScan) Close

func (op *AllNodesScan) Close() error

Close releases resources. The collected nodeIDs slice is retained (but its length zeroed) to allow reuse if Init is called again.

func (*AllNodesScan) FillChunk added in v0.9.0

func (op *AllNodesScan) FillChunk(dst *Chunk, maxRows int) (int, error)

FillChunk appends up to maxRows more NodeIDs, as unboxed int64, into column 0 of dst and returns the number appended (0 at end-of-stream). It is the column-major counterpart of AllNodesScan.Next: the SAME nodeIDs in the SAME order (advancing the shared op.pos cursor), but written to a typed column with no per-row heap box. Only one of Next/FillChunk drives a given query — the result sink picks the columnar drain or the row drain once — so sharing op.pos between them is sound. It honours context cancellation. It implements ChunkProducer.

func (*AllNodesScan) Init

func (op *AllNodesScan) Init(ctx context.Context) error

Init collects all NodeIDs from the graph into an internal slice. The collection itself honours ctx cancellation every 4096 nodes.

func (*AllNodesScan) NewOutputChunk added in v0.9.0

func (op *AllNodesScan) NewOutputChunk(capacity int) *Chunk

NewOutputChunk returns a Chunk with a single static integer column that AllNodesScan fills with unboxed NodeIDs. It implements ChunkProducer (#1704 P3): a columnar-aware parent drains the scan column-major, avoiding the per-row expr.Value box AllNodesScan.Next pays.

func (*AllNodesScan) Next

func (op *AllNodesScan) Next(out *Row) (bool, error)

Next writes the next NodeID into out and returns (true, nil), or returns (false, nil) at end-of-stream. ctx.Err() is checked on every call.

type AllShortestPaths

type AllShortestPaths struct {
	// contains filtered or unexported fields
}

AllShortestPaths is a Volcano pipeline operator that, for each input row, finds all paths of minimum length from srcCol to dstCol and emits one output row per path, each carrying the flat alternating path list (stride VLEHopStride).

AllShortestPaths is NOT safe for concurrent use.

func NewAllShortestPaths

func NewAllShortestPaths(input Operator, src AdjacencySource, dir Direction, srcCol, dstCol int) *AllShortestPaths

NewAllShortestPaths creates an AllShortestPaths operator. Like NewShortestPath it starts with no type filter and minHops == 1; configure via AllShortestPaths.WithTypeFilter and AllShortestPaths.WithHopBounds.

func (*AllShortestPaths) Close

func (op *AllShortestPaths) Close() error

Close closes the input operator.

func (*AllShortestPaths) Init

func (op *AllShortestPaths) Init(ctx context.Context) error

Init initialises the operator.

func (*AllShortestPaths) Next

func (op *AllShortestPaths) Next(out *Row) (bool, error)

Next emits one row per shortest path per input row. With no path: one Null-path row (OPTIONAL MATCH) or no row (MATCH).

func (*AllShortestPaths) PlanChildren added in v0.11.0

func (op *AllShortestPaths) PlanChildren() []Operator

PlanChildren reports the operator whose rows it searches paths from.

func (*AllShortestPaths) WithHopBounds added in v0.6.0

func (op *AllShortestPaths) WithHopBounds(minHops, maxHops int) *AllShortestPaths

WithHopBounds sets the accepted path-length window. It returns op for chaining.

func (*AllShortestPaths) WithOptional added in v0.6.0

func (op *AllShortestPaths) WithOptional(optional bool) *AllShortestPaths

WithOptional selects the OPTIONAL MATCH no-path behaviour (emit a single Null-path row) when optional is true; the default (false) drops the row. It returns op for chaining.

func (*AllShortestPaths) WithPathPredicate added in v0.6.0

func (op *AllShortestPaths) WithPathPredicate(pred func(Row) (bool, error)) *AllShortestPaths

WithPathPredicate fuses a whole-path predicate onto the operator (#1786). The operator then returns ALL shortest paths that SATISFY pred (an exhaustive search at the minimum satisfying length), instead of all unconstrained shortest paths. pred is called with each candidate's full output row. It returns op for chaining.

func (*AllShortestPaths) WithTypeFilter added in v0.6.0

func (op *AllShortestPaths) WithTypeFilter(edgeType string) *AllShortestPaths

WithTypeFilter restricts traversal to edges whose forward position is present in filter. It returns op for chaining.

func (*AllShortestPaths) WithWorkBudget added in v0.7.0

func (op *AllShortestPaths) WithWorkBudget(maxPerRow, maxTotal int) *AllShortestPaths

WithWorkBudget overrides the exhaustive path-predicate search's per-input-row and aggregate per-query edge-traversal caps (see ShortestPath.WithWorkBudget). A non-positive value leaves the corresponding default in place. It returns op for chaining and is primarily a testing seam; production uses the defaults.

type AntiSemiApply

type AntiSemiApply struct {
	// contains filtered or unexported fields
}

AntiSemiApply emits each outer row for which the inner sub-plan produces zero rows.

AntiSemiApply is NOT safe for concurrent use.

func NewAntiSemiApply

func NewAntiSemiApply(outer, inner Operator, arg *Argument) *AntiSemiApply

NewAntiSemiApply creates an AntiSemiApply operator.

  • outer is the driving (left) plan.
  • inner is the correlated (right) sub-plan whose leaf is arg.
  • arg is the Argument node seeded with each outer row before inner Init.

func (*AntiSemiApply) Close

func (op *AntiSemiApply) Close() error

Close closes the outer plan.

func (*AntiSemiApply) Init

func (op *AntiSemiApply) Init(ctx context.Context) error

Init initialises the outer plan.

func (*AntiSemiApply) Next

func (op *AntiSemiApply) Next(out *Row) (bool, error)

Next advances to the next outer row for which the inner plan has zero results.

func (*AntiSemiApply) PlanChildren added in v0.11.0

func (op *AntiSemiApply) PlanChildren() []Operator

PlanChildren reports the outer input that drives this operator, then the inner sub-plan it re-drives for each outer row.

type Apply

type Apply struct {
	// contains filtered or unexported fields
}

Apply is a Volcano pipeline operator that performs a dependent (correlated) join: for each outer row, it re-runs the inner plan seeded with that row and emits one output row per inner result.

Apply is NOT safe for concurrent use.

func NewApply

func NewApply(outer, inner Operator, arg *Argument) *Apply

NewApply creates an Apply operator.

  • outer is the left (driving) plan.
  • inner is the right (sub) plan; its leaf must be the provided arg.
  • arg is the Argument node at the root of inner; Apply seeds it before each inner Init call.

Apply takes ownership of both plans. The caller must not use outer or inner directly after calling NewApply.

func (*Apply) Close

func (op *Apply) Close() error

Close releases resources and closes both the outer and inner plans.

func (*Apply) Init

func (op *Apply) Init(ctx context.Context) error

Init initialises both the outer plan and stores ctx for subsequent Next calls. The inner plan is initialised lazily on the first outer row.

func (*Apply) Next

func (op *Apply) Next(out *Row) (bool, error)

Next advances the Apply operator:

  • If there is no current inner row available, it pulls the next outer row, seeds and re-inits the inner plan, then pulls from the inner plan.
  • Returns each combined (outer || inner) row.
  • Returns (false, nil) when the outer plan is exhausted.

func (*Apply) PlanChildren added in v0.11.0

func (op *Apply) PlanChildren() []Operator

PlanChildren reports the outer input that drives this operator, then the inner sub-plan it re-drives for each outer row.

type Argument

type Argument struct {
	// contains filtered or unexported fields
}

Argument is a Volcano leaf operator that emits the single outer Row injected by an Apply driver. It emits exactly one row per Init/Next cycle.

Argument is NOT safe for concurrent use.

func NewArgument

func NewArgument() *Argument

NewArgument creates an Argument operator with no initial row. The Apply driver must call [SetOuterRow] before the first Init/Next cycle.

func (*Argument) Close

func (op *Argument) Close() error

Close is a no-op; Argument holds no resources beyond the outer row reference.

func (*Argument) Init

func (op *Argument) Init(ctx context.Context) error

Init resets the emitted flag so that the next Next call returns the outer row. It does not require a child operator.

func (*Argument) Next

func (op *Argument) Next(out *Row) (bool, error)

Next emits the outer row exactly once per Init call. Subsequent calls return (false, nil) until Init is called again.

func (*Argument) SetOuterRow

func (op *Argument) SetOuterRow(row Row)

SetOuterRow injects the current outer row. It must be called by the Apply driver before each Init/Next cycle for the inner plan.

type CSRAdjacency added in v0.11.0

type CSRAdjacency interface {
	// VerticesSlice returns the CSR offsets array (length MaxNodeID+1).
	VerticesSlice() []uint64
	// EdgesSlice returns the flat neighbour array.
	EdgesSlice() []graph.NodeID
	// HandlesSlice returns the per-slot stable edge handles parallel to
	// EdgesSlice, or nil when the snapshot carries no handles (a
	// non-multigraph never built via AddEdgeH). The forward and reverse
	// CSRs of the same graph carry the SAME handle for a given logical
	// edge, which is what lets the reverse traversal recover per-instance
	// edge identity across parallel edges (rmp #1634).
	HandlesSlice() []uint64
}

CSRAdjacency is the minimal interface required from a CSR snapshot. csr.CSR[W] satisfies this interface for any W.

It is exported so a query planner in another package can supply an AdjacencySource that resolves one at execution time (rmp #2317).

type Chunk added in v0.9.0

type Chunk struct {
	// contains filtered or unexported fields
}

Chunk is a column-major (struct-of-arrays) execution batch, the foundation for late materialization in the Cypher executor (rmp #1704, DuckDB DataChunk style). The row-at-a-time model (Row = []expr.Value) boxes every scalar into the expr.Value interface once per cell; a Chunk instead keeps scalar columns in contiguous, unboxed, typed backing slices ([]int64/[]float64/[]string/ []bool) so that later phases can scan them without interface dispatch and box only at the sink (Chunk.BoxCell/Chunk.BoxRow).

This type is purely additive: as of its introduction NO operator is wired to it. It lands the layout, its API, and its allocation discipline behind the existing Operator interface so later phases can migrate operators onto it without changing observable behaviour.

Design (validated with the columnar-db-expert against DuckDB / Apache Arrow)

  • Struct-of-arrays: a single logical row count (length) and capacity live at the Chunk level, never per column, so columns cannot silently drift.
  • Discriminated-fields column (a tagged union): each [column] holds every possible typed backing as a separate field and a storage tag selecting the live one. Only the live backing is allocated; the rest stay nil (GC leaves). This avoids both interface{} at the Chunk boundary (which a generic Column[T] behind a non-generic holder would reintroduce) and unsafe reinterpretation. The scalar type set is closed by the Cypher language, so a tagged union is the idiomatic fit.
  • Validity: a packed bitmap ([]uint64, 1 bit per row, bit set = valid / non-null, LSB-first) per column, matching the Arrow columnar spec. A sentinel value is impossible (0 is a valid int, NaN a valid float, "" a valid string); a []bool mask wastes 8x the memory. An allValid fast path (an explicit flag, not an overloaded nil) skips the bitmap entirely while a column has seen no NULL; the bitmap is materialized lazily on the first NULL and, under pooling, its backing is retained across Chunk.Reset.
  • String columns use []string for this phase. An offsets+bytes arena (Arrow-style) is the eventual memory win but is deferred: the strings the engine produces already exist as Go strings, so []string costs only a 16-byte header copy and no new byte allocation, which is enough to remove the per-cell interface box that #1704 targets. The cell accessor Chunk.String returns a string (a view), so a later arena swap behind it is non-breaking; Chunk.StringColumn returning []string is Phase-1 only.
  • Box-at-sink: the validity bitmap is authoritative for every column kind (including boxed); a NULL cell boxes to expr.Null regardless of the backing slot's value.

Deferred, deliberately (foundational decisions recorded for later phases)

  • Selection vectors: accessors index rows PHYSICALLY (logical index == physical index). Late-materialization filtering via a selection vector (DuckDB SelectionVector) is a named later phase; callers must not assume the physical-index contract is permanent.
  • Dynamic type promotion: a column's storage is fixed at construction from its declared expr.Kind. Scalar kinds map to typed backings; every other kind (List/Map/Node/Relationship/Path/temporals) maps to a boxed []expr.Value backing, which also serves genuinely heterogeneous columns. A typed column rejects a value of a different kind (programmer error); promote-a-typed-column-to-boxed-on-conflict is deferred. Declare heterogeneous columns with a non-scalar kind so they are boxed from the start.

Concurrency

A Chunk is NOT safe for concurrent use. Each pipeline stage owns its own instance, typically obtained from a ChunkPool.

func NewChunk added in v0.9.0

func NewChunk(capacity int, kinds ...expr.Kind) *Chunk

NewChunk creates an empty Chunk with one column per kind in kinds, each pre-sized to capacity rows. A capacity < 1 defaults to DefaultChunkCapacity. Scalar kinds (Integer/Float/String/Bool) get a typed backing; every other kind gets a boxed []expr.Value backing. The returned Chunk has length 0.

func NewDynamicChunk added in v0.9.0

func NewDynamicChunk(capacity, ncols int) *Chunk

NewDynamicChunk creates an empty Chunk with ncols dynamic columns and a row capacity of capacity. A capacity < 1 defaults to DefaultChunkCapacity.

Unlike NewChunk the constructor allocates no backing, because it cannot: a dynamic column's type is not known until its first value arrives. The backing is instead allocated — once, pre-sized to capacity — by the Put that commits the column to a type (see [Chunk.commitDynamic]), so a dynamic column pays the same single sized allocation a statically declared one does.

A dynamic column has no declared kind and no backing at construction: the first Chunk.PutInt64/Chunk.PutFloat64/Chunk.PutString/Chunk.PutBool COMMITS it to the matching typed backing, and a later value of a conflicting scalar kind — or any non-scalar value via Chunk.PutValue — PROMOTES it to a boxed []expr.Value backing (re-boxing the values already stored). This is the dynamic type promotion the Chunk type documentation defers for statically constructed columns; it is what the Cypher late-materialisation projection needs, because a property column's kind is not known until the values are read (openCypher permits a property to carry different types across nodes). A column whose first appended value is NULL commits to boxed (a null-first column stays boxed — a correct, minor missed optimisation). Box-at-sink (Chunk.BoxCell) is unaffected: a typed cell boxes to the identical expr.Value a boxed cell would, and a NULL boxes to expr.Null.

func (*Chunk) AppendBool added in v0.9.0

func (c *Chunk) AppendBool(j int, v bool)

AppendBool appends v to bool column j, marking it non-null. It panics if column j is not a bool column.

func (*Chunk) AppendFloat64 added in v0.9.0

func (c *Chunk) AppendFloat64(j int, v float64)

AppendFloat64 appends v to float column j, marking it non-null. It panics if column j is not a float column.

func (*Chunk) AppendInt64 added in v0.9.0

func (c *Chunk) AppendInt64(j int, v int64)

AppendInt64 appends v to integer column j, marking it non-null. It panics if column j is not an integer column.

func (*Chunk) AppendNull added in v0.9.0

func (c *Chunk) AppendNull(j int)

AppendNull appends a NULL to column j. A placeholder zero value is written to the live backing to keep row indices aligned; the validity bitmap records the NULL, and it is the bitmap — never the placeholder — that box-at-sink honours.

It escalates through [growTo] for the same reason the push helpers do: a column fed mostly NULLs — an OPTIONAL MATCH that misses, a projection over a sparse property — reaches the batch through this path and nothing else, and without the escalation it would walk the doubling series up from [dynamicCommitFloor] that growTo exists to avoid.

func (*Chunk) AppendRowFrom added in v0.9.0

func (c *Chunk) AppendRowFrom(src *Chunk, srcRow int)

AppendRowFrom appends the whole logical row srcRow of src to c, one cell per column, WITHOUT boxing a scalar: each source cell is copied into c's matching column through the typed append primitives, and a NULL source cell appends a NULL. It is the row-compaction primitive ColumnarFilter uses to copy a passing row from its source batch into its output batch. c and src MUST have the same column count and matching per-column storage (the filter's output chunk is built from the source producer's schema), otherwise the typed append panics on a kind mismatch. It panics on an out-of-range srcRow.

func (*Chunk) AppendString added in v0.9.0

func (c *Chunk) AppendString(j int, v string)

AppendString appends v to string column j, marking it non-null. It panics if column j is not a string column.

func (*Chunk) AppendValue added in v0.9.0

func (c *Chunk) AppendValue(j int, v expr.Value)

AppendValue appends a boxed expr.Value to column j at the sink boundary, routing it to the column's typed backing. A nil interface or expr.Null appends a NULL. For a typed (scalar) column the value's concrete type must match the column's storage, otherwise AppendValue panics (dynamic promotion is deferred; see the type doc). A boxed column accepts any value.

func (*Chunk) Bool added in v0.9.0

func (c *Chunk) Bool(j, row int) (value, valid bool)

Bool returns the value and validity of row in bool column j. See Chunk.Int64 for the NULL and panic contract.

func (*Chunk) BoolColumn added in v0.9.0

func (c *Chunk) BoolColumn(j int) (data []bool, valid []uint64, allValid bool)

BoolColumn returns the backing of bool column j. It panics on a kind mismatch.

func (*Chunk) BoxCell added in v0.9.0

func (c *Chunk) BoxCell(j, row int) expr.Value

BoxCell boxes cell (j, row) back to an expr.Value. A NULL cell (validity 0) boxes to expr.Null regardless of the backing slot's value. This, and Chunk.BoxRow, are the only places boxing happens once operators are wired. It panics on an out-of-range row.

func (*Chunk) BoxRow added in v0.9.0

func (c *Chunk) BoxRow(row int, dst Row) Row

BoxRow boxes the whole logical row at index row into dst, one expr.Value per column, and returns dst (reused when it has capacity, else freshly allocated). It is the row-materialization primitive later phases use to emit a Row at the sink. It panics if the Chunk is ragged (columns of unequal length) or row is out of range.

func (*Chunk) Cap added in v0.9.0

func (c *Chunk) Cap() int

Cap returns the per-column row capacity hint the Chunk was constructed with.

func (*Chunk) ColKind added in v0.9.0

func (c *Chunk) ColKind(j int) expr.Kind

ColKind returns the declared logical expr.Kind of column j.

func (*Chunk) CopyCellTo added in v0.9.0

func (c *Chunk) CopyCellTo(srcCol, srcRow int, dst *Chunk, dstCol int)

CopyCellTo copies cell (srcCol, srcRow) of c into column dstCol of dst WITHOUT boxing a plain scalar: a typed cell is appended through dst's matching dynamic Chunk.PutInt64/Chunk.PutFloat64/Chunk.PutString/Chunk.PutBool, a boxed cell through Chunk.PutValue (which re-routes a plain scalar back to a typed backing and keeps every other kind boxed), and a NULL cell through Chunk.PutNull. It reads nothing but the already-materialised value the source chunk holds — no graph access — so it is the scalar-column passthrough primitive (rmp #2045): a projection over an already-columnar child copies a materialised value from the child's chunk instead of re-boxing it row-at-a-time or re-reading the graph by NodeID (which the box-at-sink isolation contract forbids). dst columns must be dynamic or match the source kind — the Chunk.PutValue/Put* family promotes a dynamic column on a kind conflict rather than panicking, so a heterogeneous passthrough column stays byte-identical to the row-at-a-time path. It panics on an out-of-range srcRow.

func (*Chunk) Float64 added in v0.9.0

func (c *Chunk) Float64(j, row int) (value float64, valid bool)

Float64 returns the value and validity of row in float column j. See Chunk.Int64 for the NULL and panic contract.

func (*Chunk) Float64Column added in v0.9.0

func (c *Chunk) Float64Column(j int) (data []float64, valid []uint64, allValid bool)

Float64Column returns the backing of float column j. It panics on a kind mismatch.

func (*Chunk) Int64 added in v0.9.0

func (c *Chunk) Int64(j, row int) (value int64, valid bool)

Int64 returns the value and validity of row in integer column j. When the cell is NULL the returned value is the (meaningless) placeholder and valid is false; callers must check valid. It panics on a kind mismatch or an out-of-range row.

func (*Chunk) Int64Column added in v0.9.0

func (c *Chunk) Int64Column(j int) (data []int64, valid []uint64, allValid bool)

Int64Column returns the backing of integer column j. It panics on a kind mismatch.

func (*Chunk) IsBoolColumn added in v0.9.0

func (c *Chunk) IsBoolColumn(j int) bool

IsBoolColumn reports whether column j's live backing is an unboxed []bool. See Chunk.IsFloat64Column.

func (*Chunk) IsFloat64Column added in v0.9.0

func (c *Chunk) IsFloat64Column(j int) bool

IsFloat64Column reports whether column j's live backing is an unboxed []float64. Together with Chunk.IsInt64Column/Chunk.IsStringColumn/ Chunk.IsBoolColumn it lets a consumer (e.g. the columnar EagerAggregation grouping-key path, #2049) read a scalar cell unboxed via the typed accessor for that kind and fall back to Chunk.BoxCell for a boxed or promoted column.

func (*Chunk) IsInt64Column added in v0.9.0

func (c *Chunk) IsInt64Column(j int) bool

IsInt64Column reports whether column j's live backing is an unboxed int64 slice — the storage the scan emits for a raw NodeID column. The columnar projection's chunk-input fast path uses it to confirm a source column really holds raw NodeIDs before reading them via Chunk.Int64, falling back to the boxed row path otherwise so a non-int64 column never triggers a kind-mismatch panic on the read path.

func (*Chunk) IsNull added in v0.9.0

func (c *Chunk) IsNull(j, row int) bool

IsNull reports whether row of column j is NULL. It panics on an out-of-range row.

func (*Chunk) IsStringColumn added in v0.9.0

func (c *Chunk) IsStringColumn(j int) bool

IsStringColumn reports whether column j's live backing is an unboxed []string. See Chunk.IsFloat64Column.

func (*Chunk) IsValid added in v0.9.0

func (c *Chunk) IsValid(j, row int) bool

IsValid reports whether row of column j is non-null. It panics on an out-of-range row.

func (*Chunk) Len added in v0.9.0

func (c *Chunk) Len() int

Len returns the logical row count. For a well-formed (rectangular) Chunk every column shares this length; it is the length of the first column, or 0 when the Chunk has no columns.

func (*Chunk) NumCols added in v0.9.0

func (c *Chunk) NumCols() int

NumCols returns the number of columns in the Chunk.

func (*Chunk) PutBool added in v0.9.0

func (c *Chunk) PutBool(j int, v bool)

PutBool appends v as a bool to column j, committing or promoting its backing as described in NewDynamicChunk.

func (*Chunk) PutFloat64 added in v0.9.0

func (c *Chunk) PutFloat64(j int, v float64)

PutFloat64 appends v as a float to column j, committing or promoting its backing as described in NewDynamicChunk.

func (*Chunk) PutInt64 added in v0.9.0

func (c *Chunk) PutInt64(j int, v int64)

PutInt64 appends v as an integer to column j, committing or promoting its backing as described in NewDynamicChunk.

func (*Chunk) PutNull added in v0.9.0

func (c *Chunk) PutNull(j int)

PutNull appends a NULL to column j. On a dynamic column the first value being NULL commits the column to a boxed backing (a null-first column stays boxed); on an already-committed column it records a NULL in the live backing exactly like Chunk.AppendNull. The validity bitmap — never the placeholder — is what box-at-sink honours.

func (*Chunk) PutString added in v0.9.0

func (c *Chunk) PutString(j int, v string)

PutString appends v as a string to column j, committing or promoting its backing as described in NewDynamicChunk.

func (*Chunk) PutValue added in v0.9.0

func (c *Chunk) PutValue(j int, v expr.Value)

PutValue appends a boxed expr.Value to column j, routing a plain scalar (Integer/Float/String/Bool) to the typed fast paths — so a fallback that produces an already-boxed scalar keeps the column typed — and boxing every other kind (temporal / point / list / map / node / …) into the boxed backing, promoting the column if necessary. A nil interface or expr.Null appends a NULL. This is the sink-boundary entry point the columnar projection uses for values it must keep boxed for byte-identity with the row-at-a-time path.

func (*Chunk) Reset added in v0.9.0

func (c *Chunk) Reset()

Reset clears every column for reuse while retaining the backing allocations. It resets the length to 0 and, per column: leaves fixed-width backings (int64/float64/bool) untouched (they hold no pointers, so stale values are never traced by the GC and are invisible past length 0); nils the used slots of string and boxed backings (their headers hold pointers and would otherwise pin memory); zeroes the validity bitmap words while keeping the bitmap backing; and restores the allValid fast path.

func (*Chunk) RowByteEstimate added in v0.9.0

func (c *Chunk) RowByteEstimate(row int, overhead int64, estimateBoxed func(expr.Value) int64) int64

RowByteEstimate returns a coarse, allocation-free byte estimate for the whole logical row at index row, for a columnar sink's byte-budget accounting: per column it charges overhead for a NULL cell or a fixed-width scalar (int64/float64/bool), overhead plus the byte length for a string cell, and estimateBoxed applied to the stored expr.Value for a boxed cell. It boxes nothing. Summed this way it equals what a per-value estimator (overhead-based, string-length-aware) summed over the row's boxed values would yield, so a columnar drain's byte budget trips at the same point the row-oriented drain does. It panics on an out-of-range row.

func (*Chunk) SetBool added in v0.9.0

func (c *Chunk) SetBool(j, row int, v bool)

SetBool overwrites row of bool column j with v, marking it non-null. See Chunk.SetInt64 for the row and panic contract.

func (*Chunk) SetFloat64 added in v0.9.0

func (c *Chunk) SetFloat64(j, row int, v float64)

SetFloat64 overwrites row of float column j with v, marking it non-null. See Chunk.SetInt64 for the row and panic contract.

func (*Chunk) SetInt64 added in v0.9.0

func (c *Chunk) SetInt64(j, row int, v int64)

SetInt64 overwrites row of integer column j with v, marking it non-null. row must be an already-appended row (0 <= row < column length). It panics on a kind mismatch or an out-of-range row.

func (*Chunk) SetNull added in v0.9.0

func (c *Chunk) SetNull(j, row int)

SetNull marks row of column j as NULL, releasing any reference the slot held (string header or boxed value) so the batch does not pin it. See Chunk.SetInt64 for the row and panic contract.

func (*Chunk) SetString added in v0.9.0

func (c *Chunk) SetString(j, row int, v string)

SetString overwrites row of string column j with v, marking it non-null. See Chunk.SetInt64 for the row and panic contract.

func (*Chunk) String added in v0.9.0

func (c *Chunk) String(j, row int) (value string, valid bool)

String returns the value and validity of row in string column j. The returned string is a view; the arena swap planned for a later phase keeps this signature. See Chunk.Int64 for the NULL and panic contract.

func (*Chunk) StringColumn added in v0.9.0

func (c *Chunk) StringColumn(j int) (data []string, valid []uint64, allValid bool)

StringColumn returns the backing of string column j. It panics on a kind mismatch.

This []string shape is Phase-1 only: once the string bytes arena lands it will no longer exist. Consumers that must survive that change should use the cell accessor Chunk.String (which keeps its signature).

type ChunkColumnFiller added in v0.9.0

type ChunkColumnFiller func(src *Chunk, srcRow int, dst *Chunk, dstCol int) error

ChunkColumnFiller extracts one projected column value from a COLUMNAR input row — source row srcRow of src — and appends it to column dstCol of dst, WITHOUT boxing the input node id (the whole point of the chunk-input path: it reads the raw int64 NodeID from src via Chunk.Int64 instead of type-asserting a boxed expr.Value). Like ColumnFiller it MUST append EXACTLY one value to dstCol so the chunk stays rectangular, and falls back — byte-identically — to the row-at-a-time evaluation for any source row it cannot take the unboxed fast path on. It is built by the engine and paired 1:1 with the operator's ColumnFiller fallbacks; the two are equivalent by construction. It is used only when the child is a NodeIDColumnProducer (#1704 P3).

type ChunkPool added in v0.9.0

type ChunkPool struct {
	// contains filtered or unexported fields
}

ChunkPool is a sync.Pool-backed pool of Chunk instances with a fixed schema (column kinds) and capacity. Operators that process a high volume of batches should obtain chunks from a shared pool to reduce GC pressure.

ChunkPool is safe for concurrent use; the Chunk instances it vends are not.

func NewChunkPool added in v0.9.0

func NewChunkPool(capacity int, kinds ...expr.Kind) *ChunkPool

NewChunkPool creates a ChunkPool that vends Chunks with the given capacity and column kinds. The kinds slice is copied, so the caller may reuse it.

func (*ChunkPool) Get added in v0.9.0

func (cp *ChunkPool) Get() *Chunk

Get retrieves a Chunk from the pool, or allocates a new one. A pooled Chunk was Chunk.Reset before being returned, so it is empty.

func (*ChunkPool) Put added in v0.9.0

func (cp *ChunkPool) Put(c *Chunk)

Put resets c and returns it to the pool.

type ChunkPredicate added in v0.9.0

type ChunkPredicate func(src *Chunk, row int) (keep, decided bool)

ChunkPredicate decides, WITHOUT boxing, whether source row `row` of `src` passes the filter predicate. It returns (keep, decided): decided=true means keep is authoritative (the predicate was fully evaluated over the unboxed columns); decided=false means the predicate shape cannot be decided unboxed for this row, and the caller must fall back to the boxed row predicate for a byte-identical result. A ChunkPredicate is built by the engine (the cypher package), which owns the graph and the openCypher comparison semantics, and is handed to NewColumnarFilter. It never boxes a scalar; a nil ChunkPredicate means "always fall back" (every row uses the boxed predicate).

type ChunkProducer added in v0.9.0

type ChunkProducer interface {
	Operator
	// NewOutputChunk returns a Chunk sized for this operator's output columns.
	NewOutputChunk(capacity int) *Chunk
	// FillChunk appends up to maxRows more output rows into dst (column-major) and
	// returns the number of complete rows appended (0 at end-of-stream). dst must
	// have been obtained from NewOutputChunk on the same operator.
	FillChunk(dst *Chunk, maxRows int) (int, error)
}

ChunkProducer is implemented by a terminal operator that can emit its output column-major into a Chunk, letting a columnar-aware sink box values only at the API boundary rather than once per cell during the drain (rmp #1704). It is an optional capability discovered by type assertion; an operator that does not implement it is drained row-at-a-time via Operator.Next exactly as before.

func NewColumnarExpand added in v0.10.0

func NewColumnarExpand(exp *Expand) (ChunkProducer, bool)

NewColumnarExpand presents exp as a ChunkProducer when exp's child is itself a ChunkProducer (so [Expand.fillChunk] can pull it column-major and the chunk chain stays unbroken). It returns (wrapper, true) on success or (nil, false) when the child is row-mode, in which case the caller keeps the plain row-mode exp. The returned wrapper also implements NodeIDColumnProducer.

type ColumnFiller added in v0.9.0

type ColumnFiller func(row Row, dst *Chunk, col int) error

ColumnFiller extracts one projected column value for the current input row and appends it to column col of dst, WITHOUT boxing a plain scalar into an expr.Value — the whole point of the columnar projection path. It is built by the engine (the cypher package), which owns the graph and the property-value classification, and handed to NewColumnarProject.

A ColumnFiller MUST append EXACTLY one value to dst column col — typed via Chunk.PutInt64/Chunk.PutFloat64/Chunk.PutString/Chunk.PutBool, boxed via Chunk.PutValue, or NULL via Chunk.PutNull — so the chunk stays rectangular. A filler that cannot take the unboxed fast path for the current row (a cell that is not a resolvable node, or a value that must retain special decoding such as a temporal) falls back to the row-at-a-time evaluation and appends the resulting boxed value via Chunk.PutValue, keeping the result byte-identical to the row-at-a-time path.

type ColumnarFilter added in v0.9.0

type ColumnarFilter struct {
	Filter // boxed fallback: promoted Next/Close, predFn, ctx
	// contains filtered or unexported fields
}

ColumnarFilter applies a predicate to a columnar input and compacts the passing rows into a column-major output chunk.

The Filter is embedded BY VALUE, not by pointer: a ColumnarFilter is one heap allocation, exactly like the plain Filter it replaces, so building one for a predicate whose parent turns out to consume it row-at-a-time (e.g. an aggregation) costs no extra allocation over the plain Filter. The columnar-only state (scratch batch, cursor) stays zero until the columnar FillChunk path first runs, so the boxed Next path never pays for it either.

ColumnarFilter is NOT safe for concurrent use.

func NewColumnarFilter added in v0.9.0

func NewColumnarFilter(child ChunkProducer, predFn FilterFn, pred ChunkPredicate) *ColumnarFilter

NewColumnarFilter creates a ColumnarFilter over a ChunkProducer child. predFn is the row-at-a-time predicate (the Operator.Next fallback, identical to NewFilter); pred is the parallel unboxed fast path (nil to always fall back). The two must be equivalent for every row pred decides — see ChunkPredicate.

func (*ColumnarFilter) FillChunk added in v0.9.0

func (op *ColumnarFilter) FillChunk(dst *Chunk, maxRows int) (int, error)

FillChunk appends up to maxRows PASSING rows into dst (column-major) and returns the number appended, 0 at end-of-stream. It implements ChunkProducer.

It pulls the child in FULL batches into an owned scratch chunk and evaluates the predicate over each source row: the unboxed ChunkPredicate fast path when it can decide, otherwise a one-row box through the SAME boxed predicate the Next path uses (byte-identical). Passing rows are compacted into dst via Chunk.AppendRowFrom with no per-row box.

The scratch cursor (scratchPos) PERSISTS across calls: a single child pull can yield more survivors than the remaining dst capacity, so filling stops mid-batch and resumes here on the next call rather than re-pulling (which would drop or duplicate rows). Consequently a short return (n < maxRows) means the CHILD is exhausted — never merely that one internal pull was selective — which the drain relies on to detect end-of-stream (#1704 P3).

func (*ColumnarFilter) Init added in v0.9.0

func (op *ColumnarFilter) Init(ctx context.Context) error

Init initialises the embedded Filter (and, through it, the child) and resets the columnar cursor. The scratch batch is allocated lazily on the first ColumnarFilter.FillChunk call, so a ColumnarFilter driven only through Next (a non-columnar parent) allocates nothing beyond the plain Filter.

func (*ColumnarFilter) NewOutputChunk added in v0.9.0

func (op *ColumnarFilter) NewOutputChunk(capacity int) *Chunk

NewOutputChunk returns a Chunk shaped like the child's output: ColumnarFilter is a row-preserving passthrough (it drops rows, never changes the column layout), so its output schema equals its child's. It implements ChunkProducer.

type ColumnarHashJoin added in v0.10.0

type ColumnarHashJoin struct {
	// contains filtered or unexported fields
}

ColumnarHashJoin is the ChunkProducer equi-join operator described in the file comment. It implements both the row-mode Operator contract (its Next is the byte-identical fallback for a non-columnar parent) and the column-major ChunkProducer contract (FillChunk, preferred by a columnar-aware sink).

ColumnarHashJoin is NOT safe for concurrent use.

func NewColumnarHashJoin added in v0.10.0

func NewColumnarHashJoin(build, probe Operator, buildFn, probeFn KeyFn, buildOnLeft bool) (*ColumnarHashJoin, bool)

NewColumnarHashJoin returns a ColumnarHashJoin when BOTH build and probe are [NodeIDColumnProducer]s, or (nil, false) otherwise — in which case the caller keeps the row-mode HashJoin (design §6.2: the columnar operator is wired only when every child qualifies, so existing plans are unchanged).

The NodeIDColumnProducer requirement (stronger than plain ChunkProducer) is load-bearing for the allocation win: because every hash-join arm is a bare node scan, both children carry each bound node's raw int64 NodeID unboxed, so the join's output columns are all raw NodeIDs too and ColumnarHashJoin can itself be a NodeIDColumnProducer. That lets a ColumnarProject above the join read a node property (a.k / b.k) unboxed straight from the output chunk, keeping the chunk chain unbroken to the sink (design §0). Gating on plain ChunkProducer instead would break that chain for node-property projection: the projection falls back to row-input Next and re-boxes every output node cell under fan-out — a measured allocation REGRESSION (#2065 pattern). A child that is a ChunkProducer but not a NodeIDColumnProducer therefore keeps the row-mode HashJoin.

  • build is drained fully, column-major, into the hash table.
  • probe is streamed column-major against the table.
  • buildFn / probeFn extract the join key from a build / probe row.
  • buildOnLeft selects the output column order (build||probe vs probe||build), matching the Apply the planner replaces exactly as HashJoin does.

ColumnarHashJoin takes ownership of both plans; callers must not use them afterwards.

func (*ColumnarHashJoin) Close added in v0.10.0

func (op *ColumnarHashJoin) Close() error

Close releases the hash table and closes both child plans.

func (*ColumnarHashJoin) FillChunk added in v0.10.0

func (op *ColumnarHashJoin) FillChunk(dst *Chunk, maxRows int) (int, error)

FillChunk appends up to maxRows matched output rows into dst column-major and returns the number appended (0 at end-of-stream). Matched build columns are copied from the retained build buffer and probe columns from the current probe batch, both unboxed via Chunk.CopyCellTo. It implements ChunkProducer.

func (*ColumnarHashJoin) Init added in v0.10.0

func (op *ColumnarHashJoin) Init(ctx context.Context) error

Init initialises both child plans and resets join state.

func (*ColumnarHashJoin) NewOutputChunk added in v0.10.0

func (op *ColumnarHashJoin) NewOutputChunk(capacity int) *Chunk

NewOutputChunk returns a Chunk sized for the join output: one column per output column, typed by the children's output schema (build||probe or probe||build per buildOnLeft) and pre-sized to capacity. Because both children are NodeIDColumnProducers, every output column has a known scalar kind, so a typed chunk — rather than a dynamic one that reallocates its backing as it fills — copies each cell unboxed via Chunk.CopyCellTo and boxes byte-identically at the sink. It implements ChunkProducer.

func (*ColumnarHashJoin) Next added in v0.10.0

func (op *ColumnarHashJoin) Next(out *Row) (bool, error)

Next advances the join in row mode, boxing the matched (build||probe or probe||build) columns into a reused output buffer. It is the byte-identical fallback HashJoin.Next provides for a non-columnar parent.

func (*ColumnarHashJoin) PlanChildren added in v0.11.0

func (op *ColumnarHashJoin) PlanChildren() []Operator

PlanChildren reports the build side first, then the probe side, as HashJoin.PlanChildren does.

ColumnarHashJoin declares its own rather than inheriting: it embeds nothing, holding both sides as ChunkProducer. Rendering it under its own name is how the columnar tier becomes visible in a plan (rmp #2222 AC 1) — the row-mode HashJoin and this one are different operators with different costs, and a reader must be able to tell which ran.

func (*ColumnarHashJoin) PlanDetail added in v0.11.0

func (op *ColumnarHashJoin) PlanDetail() string

PlanDetail reports the build side of the columnar join, as HashJoin.PlanDetail does for the row-mode one.

func (*ColumnarHashJoin) WithByteBudget added in v0.10.0

func (op *ColumnarHashJoin) WithByteBudget(maxBytes int64, estimateRow func(Row) int64) *ColumnarHashJoin

WithByteBudget bounds the estimated retained size of the build buffer by maxBytes, returning ErrHashJoinMemoryExceeded when exceeded (#1841). It mirrors HashJoin.WithByteBudget exactly (same estimator, same sentinel), so the columnar and row-mode joins trip at the identical threshold. A non-positive maxBytes or nil estimateRow leaves it unbounded. Returns op for chaining and must be called before Init.

type ColumnarLimit added in v0.11.0

type ColumnarLimit struct {
	Limit // boxed fallback: promoted Next/Close/Init, n, emitted, ctx
	// contains filtered or unexported fields
}

ColumnarLimit is a Limit that additionally implements ChunkProducer, keeping the chunk chain unbroken through a LIMIT.

The Limit is embedded BY VALUE, so a ColumnarLimit is one heap allocation exactly like the plain Limit it replaces, and a ColumnarLimit whose parent turns out to consume it row-at-a-time costs nothing extra: the promoted Limit.Next runs unchanged, and `emitted` is the SAME field either way, so the two paths cannot disagree about how many rows have been emitted.

ColumnarLimit is NOT safe for concurrent use.

func NewColumnarLimit added in v0.11.0

func NewColumnarLimit(lim *Limit) (*ColumnarLimit, bool)

NewColumnarLimit returns a ColumnarLimit over lim when lim's child is a ChunkProducer, and (nil, false) otherwise — in which case the caller keeps the plain Limit, whose behaviour is identical.

lim must not have been initialised yet: the returned operator takes over its state.

func (*ColumnarLimit) FillChunk added in v0.11.0

func (op *ColumnarLimit) FillChunk(dst *Chunk, maxRows int) (int, error)

FillChunk appends up to maxRows rows into dst (column-major) and returns the number appended, 0 once the limit is reached or the child is exhausted. It implements ChunkProducer.

The clamp is the whole operator: at most `n - emitted` further rows may ever be emitted, so the child is asked for no more than that. Because a consumer treats a short fill (n < maxRows) as end-of-stream, clamping below the requested maxRows also signals exhaustion at exactly the right moment — the limit is reached — and a subsequent call returns 0 regardless.

Rows appended here count towards the SAME `emitted` counter Limit.Next uses, so a plan that drains partly column-major and partly row-at-a-time still emits exactly n rows in total.

func (*ColumnarLimit) NewOutputChunk added in v0.11.0

func (op *ColumnarLimit) NewOutputChunk(capacity int) *Chunk

NewOutputChunk returns a Chunk shaped like the child's output: LIMIT truncates the row stream and never changes the column layout, so its output schema equals its child's. It implements ChunkProducer.

type ColumnarProject added in v0.9.0

type ColumnarProject struct {
	*Project
	// contains filtered or unexported fields
}

ColumnarProject applies a list of scalar-property projections column-major.

ColumnarProject is NOT safe for concurrent use.

func NewColumnarProject added in v0.9.0

func NewColumnarProject(child Operator, items []ProjectionItem, fillers []ColumnFiller) (*ColumnarProject, error)

NewColumnarProject creates a ColumnarProject. items are the row-at-a-time projection items (the Operator.Next fallback, identical to NewProject); fillers are the parallel columnar extractors, one per item in the same order. len(fillers) must equal len(items).

func (*ColumnarProject) FillChunk added in v0.9.0

func (op *ColumnarProject) FillChunk(dst *Chunk, maxRows int) (int, error)

FillChunk pulls up to maxRows input rows from the child and appends each as one column-major row into dst via the ColumnFiller extractors, returning the number of complete rows appended (0 at end-of-stream). dst is the caller-owned sink chunk, filled incrementally across calls; box-at-sink happens later at the API boundary. It honours context cancellation before pulling each row. It implements ChunkProducer.

On a filler error the partially-appended row is left in dst but is not counted in the returned n; the caller (the result-materialisation drain) records the error and serves no rows, so the ragged tail is never observed.

func (*ColumnarProject) NewOutputChunk added in v0.9.0

func (op *ColumnarProject) NewOutputChunk(capacity int) *Chunk

NewOutputChunk returns a Chunk sized for this operator's output: one dynamic column per projection item, so each column's kind is decided by the values the fillers produce (a property column's kind is not known until the values are read). It implements ChunkProducer.

func (*ColumnarProject) WithChunkInput added in v0.9.0

func (op *ColumnarProject) WithChunkInput(chunkFillers []ChunkColumnFiller) error

WithChunkInput switches this ColumnarProject to consume its child column-major (#1704 P3): the child must be a NodeIDColumnProducer so chunkFillers can read raw int64 NodeID columns unboxed, and len(chunkFillers) must equal the number of projection items. It returns an error otherwise, leaving the operator on the row-input path. Call before Init. Passing this way — rather than a constructor variant — keeps the P2 NewColumnarProject signature and its callers untouched; the chunk-input path is strictly additive and opt-in.

func (*ColumnarProject) WithScalarChunkInput added in v0.9.0

func (op *ColumnarProject) WithScalarChunkInput(chunkFillers []ChunkColumnFiller) error

WithScalarChunkInput switches this ColumnarProject to consume its child column-major over ALREADY-MATERIALISED scalar columns (#2045): the child must be a ChunkProducer, and each chunk filler copies a source-chunk cell into the output WITHOUT any graph read — the value is already materialised in the child's chunk (a prior columnar projection produced it under the query's visibility barrier). len(chunkFillers) must equal the number of projection items. It returns an error otherwise, leaving the operator on the row-input path.

Unlike ColumnarProject.WithChunkInput it does NOT require a NodeIDColumnProducer: the scalar-passthrough fillers never read the live graph by NodeID, so the box-at-sink isolation contract that forbids a deferred entity-by-id read (rmp #1704 P4/P5) does not apply — every value copied here was captured at query time. Call before Init.

type ConstraintInfo added in v0.2.0

type ConstraintInfo struct {
	// Label is the constrained node label.
	Label string
	// Property is the constrained property key.
	Property string
	// Name is the user-defined constraint name (may be empty for a constraint
	// registered without one).
	Name string
	// KindUnique is true for a UNIQUE constraint, false for NOT NULL.
	KindUnique bool
}

ConstraintInfo is a structured description of one registered constraint, used to persist the constraint set durably and to re-register it on recovery. KindUnique distinguishes UNIQUE (true) from NOT NULL (false).

type ConstraintKind

type ConstraintKind uint8

ConstraintKind distinguishes UNIQUE from NOT_NULL constraints.

const (
	// ConstraintUnique requires that at most one node with a given label has a
	// particular value for the constrained property.
	ConstraintUnique ConstraintKind = iota
	// ConstraintNotNull requires that every node with a given label has a
	// non-null value for the constrained property.
	ConstraintNotNull
)

type ConstraintRegistry

type ConstraintRegistry struct {
	// contains filtered or unexported fields
}

ConstraintRegistry is a thread-safe registry of active constraints. It stores unique and not-null constraints keyed by "label.prop".

ConstraintRegistry is safe for concurrent use.

func NewConstraintRegistry

func NewConstraintRegistry() *ConstraintRegistry

NewConstraintRegistry creates an empty ConstraintRegistry.

func (*ConstraintRegistry) CheckSetProperty

func (r *ConstraintRegistry) CheckSetProperty(ct *ConstraintTxn, labels []string, prop string, value lpg.PropertyValue, mgr *index.Manager) error

CheckSetProperty validates that setting prop = value on a node with the given labels does not violate any registered constraint. mgr is used for unique-constraint index lookups (hash index Cardinality check) as a secondary source; the primary source is the registry's own value set.

The primary value-set is authoritative: when it is present (non-nil) and does not contain value, the secondary hash-index check is skipped. The value-set is seeded from the live graph at constraint creation and kept current by RecordPropertySet / ReleasePropertyValue / ReseedFromGraph, so an absent primary "not present" signal means the value is genuinely free. The secondary check is consulted only when the primary set has not been initialised (nil), covering the narrow window before SeedUniqueValues is called.

Returns *ConstraintViolationError (which wraps ErrConstraintViolation) on the first violation found; nil when all constraints pass.

It CHECKS ONLY, which is not enough for a concurrent writer

This is a read: it takes the read lock, consults the value-set, and returns. Reserving the value happens later and separately, in [RecordPropertySet], which takes the WRITE lock — so between the two, another writer can run the same check and reach the same conclusion. Two writers then both insert and the UNIQUE constraint is violated with both statements reporting success.

That is unreachable while the engine serialises whole write statements on the exclusive visibility barrier, which is why this shape survived. It is NOT reachable-but-rare: with the concurrent write path enabled it was measured at 14 of 15 runs (rmp #2321). A writer that can overlap another must therefore call ConstraintRegistry.ReserveSetProperty, which does both halves under one lock.

This entry point remains for callers that genuinely only want to ASK — pre-validation and diagnostics — and for the existing test surface.

ct carries the caller's own uncommitted releases, so asking about a value the asking transaction has itself given up answers "free"; nil means "no transaction". See ConstraintTxn.

func (*ConstraintRegistry) CommitTxn added in v0.11.0

func (r *ConstraintRegistry) CommitTxn(ct *ConstraintTxn)

CommitTxn applies ct's pending releases to the shared value-sets and clears it.

It must be called exactly once per transaction that committed, from inside the same window that publishes the transaction's writes, so the value a committed release frees becomes available at the instant the graph stops holding it.

A rolled-back transaction calls ConstraintTxn.Reset instead — there is nothing to undo, because a deferred release never touched anything shared.

Safe for concurrent use.

func (*ConstraintRegistry) Constraints added in v0.2.0

func (r *ConstraintRegistry) Constraints() []ConstraintInfo

Constraints returns a structured snapshot of every registered constraint, in deterministic order (UNIQUE before NOT NULL, then by label, property, name). It is used to persist the constraint set into a snapshot and to compare the recovered set against the live one.

Constraints is safe for concurrent use.

func (*ConstraintRegistry) Count added in v0.3.0

func (r *ConstraintRegistry) Count() int

Count returns the number of registered constraints (UNIQUE + NOT NULL). It is a cheap, allocation-free alternative to len(Constraints()) that the engine uses to mirror the count onto the graph for the checkpointer (#1464).

Count is safe for concurrent use.

func (*ConstraintRegistry) HasAnyNotNull added in v0.6.0

func (r *ConstraintRegistry) HasAnyNotNull() bool

HasAnyNotNull reports whether at least one NOT NULL (property-existence) constraint is registered, WITHOUT taking the registry lock.

It is the cheap gate the engine consults on every statement before doing any touched-node existence-constraint work: when it returns false the commit-time check is a no-op and the per-transaction touched-node recording is skipped entirely.

It used to take the read lock to measure `len(notNull)`, which put one lock acquisition per statement on the write path — cheap while writers were serialised and no longer free once rmp #2306 let them overlap, for the same reason [ConstraintRegistry.uniqueActive] gives: the acquisition IS the cost. It now reads the counter, which is sound for the same reason: a registration needs the schema barrier held exclusively, and an ordinary write holds it shared for its whole bracket.

HasAnyNotNull is safe for concurrent use.

func (*ConstraintRegistry) HasAnyUnique added in v0.11.0

func (r *ConstraintRegistry) HasAnyUnique() bool

HasAnyUnique reports whether any UNIQUE constraint is registered, WITHOUT taking the registry lock.

It is the write path's gate: with no UNIQUE constraint there is nothing to check and nothing to reserve, so the three value-set methods return before touching mu. See [ConstraintRegistry.uniqueActive] for the measurement that motivated it and for why the lock-free read is sound.

func (*ConstraintRegistry) HasNotNull

func (r *ConstraintRegistry) HasNotNull(label, prop string) bool

HasNotNull reports whether a not-null constraint exists for (label, prop).

func (*ConstraintRegistry) HasUnique added in v0.2.0

func (r *ConstraintRegistry) HasUnique(label, prop string) bool

HasUnique reports whether a unique constraint exists for (label, prop).

func (*ConstraintRegistry) ListConstraintRows

func (r *ConstraintRegistry) ListConstraintRows() [][]expr.Value

ListConstraintRows returns a [][]expr.Value where each inner slice has four elements: [name, type, label, property]. The name column carries the constraint's declared (or auto-generated) name — the same name DROP CONSTRAINT resolves by — falling back to the canonical "label.prop" key only for a constraint registered without a name (the legacy anonymous path). type is "UNIQUE" or "NOT_NULL". Rows are returned in deterministic order (name, type, label, property).

ListConstraintRows is safe for concurrent use.

func (*ConstraintRegistry) NameInUse added in v0.8.0

func (r *ConstraintRegistry) NameInUse(name string) (kind ConstraintKind, label, prop string, found bool)

NameInUse reports the identity of the constraint currently holding name, or found=false when no constraint carries it. It is the CREATE-time lookup used to reject a name already used by a different constraint. NameInUse is safe for concurrent use and deterministic (see [ResolveByName]).

func (*ConstraintRegistry) NotNullProperties added in v0.6.0

func (r *ConstraintRegistry) NotNullProperties(label string) []string

NotNullProperties returns the property keys for which a NOT NULL constraint is registered on label, or nil when label carries no existence constraint. It is the per-label lookup the commit-time existence check uses to test only the constrained properties of a touched node's labels.

The lookup is O(1) via the notNullByLabel index and allocates nothing on this hot path: it returns the registry's own copy-on-write slice, which RegisterNotNull / UnregisterNotNull never mutate in place. The caller must treat the result as READ-ONLY (it is shared and must not be appended to or modified). Most labels carry zero or one existence constraint, so the common return is nil or a one-element slice. NotNullProperties is safe for concurrent use.

func (*ConstraintRegistry) RecordPropertySet

func (r *ConstraintRegistry) RecordPropertySet(labels []string, prop string, value lpg.PropertyValue)

RecordPropertySet records that a property value has been successfully written to a node with the given labels. This keeps the unique value sets up-to-date so that subsequent CheckSetProperty calls detect violations. It is a no-op when no unique constraint exists for (label, prop). # DO NOT CALL THIS FROM A WRITE PATH (rmp #2357/#2358)

Its only legitimate caller is [releaseConstraintValue], where it IS the journaled inverse of a release: a rolled-back release must put the value back.

Every write path must reserve through [reserveConstraintValue] and nothing else. Eleven write sites used to call this immediately AFTER their reserve, and every one of those calls was dead weight: the body below is the SAME set insert as ConstraintRegistry.ReserveSetProperty's phase 2, over the same (labels, prop, value), and a set insert is idempotent — so the value was already present. What it was not free of is r.mu: each call took this registry's WRITE lock a second time per constrained property write, and [nodeStateReader] records that this lock measured 57 % of ALL lock delay at sixteen writers on a schema with no constraints at all. Verified site by site before the calls were removed, including the three whose reserve sits more than a dozen lines above it.

A new write path that "records" what it has already reserved therefore buys nothing and pays a lock acquisition. Reserve, and stop.

func (*ConstraintRegistry) RegisterNotNull

func (r *ConstraintRegistry) RegisterNotNull(label, prop string)

RegisterNotNull adds a not-null constraint for (label, prop).

func (*ConstraintRegistry) RegisterUnique

func (r *ConstraintRegistry) RegisterUnique(label, prop, indexName string)

RegisterUnique adds a unique constraint for (label, prop) backed by indexName in the index.Manager.

func (*ConstraintRegistry) ReleasePropertyValue added in v0.3.0

func (r *ConstraintRegistry) ReleasePropertyValue(ct *ConstraintTxn, labels []string, prop string, value lpg.PropertyValue)

ReleasePropertyValue removes a value from the unique value-set so it is no longer treated as "in use" by the constraint. It must be called whenever a previously recorded value is no longer present in the graph: on node deletion, on REMOVE of a constrained property, and when SET replaces an existing constrained value with a new one (releasing the old value).

ReleasePropertyValue is a no-op when the value was not present in the value-set, or when no unique constraint exists for (label, prop). It is safe for concurrent use.

It is DEFERRED when a transaction owns it (rmp #2366)

With ct non-nil the release is recorded as that transaction's own pending contribution and the shared value-set is left alone until ConstraintRegistry.CommitTxn. That is what makes a rollback free: it drops a private mark instead of writing a re-reservation into shared state that a peer's COMMITTED release may already have changed. ConstraintTxn carries the interleaving this closes.

With ct nil there is no transaction and therefore nothing to roll back, so the release applies immediately — the correct reading of a change that is committed the instant it is made.

func (*ConstraintRegistry) ReseedFromGraph added in v0.3.0

func (r *ConstraintRegistry) ReseedFromGraph(scanFn func(label, prop string) []lpg.PropertyValue)

ReseedFromGraph clears every UNIQUE value-set and rebuilds it from the provided (label, prop) → values mapping. It is called after an in-memory transaction undo (rollback) so the registry reflects the restored graph state rather than the rolled-back writes.

The caller supplies a scanFn that, given a label and property key, returns the property values currently carried by all live nodes with that label. ReseedFromGraph acquires the registry write lock only once per constraint, so the scan itself must NOT hold the registry lock.

func (*ConstraintRegistry) ReserveSetProperty added in v0.11.0

func (r *ConstraintRegistry) ReserveSetProperty(ct *ConstraintTxn, labels []string, prop string, value lpg.PropertyValue, mgr *index.Manager) error

ReserveSetProperty validates that setting prop = value on a node with the given labels violates no registered constraint AND, in the same critical section, records the value as in use — so no concurrent writer can pass the same check.

It is the enforcement entry point for every write path. On success the value is already reserved and the caller's later ConstraintRegistry.RecordPropertySet is a harmless idempotent no-op. On failure NOTHING is reserved, for any label.

Why atomic, and the prior art

Splitting the test from the insert is a check-then-act race, and it is the defect ConstraintRegistry.CheckSetProperty documents above. PostgreSQL closes the same hole by holding the leaf page's buffer lock ACROSS the uniqueness check and the insertion: `_bt_doinsert` calls `_bt_check_unique` with the buffer locked and does not release it before inserting, and `_bt_stepright` states the reason in as many words — "We must write-lock the target page before releasing write lock on current page; else someone else's _bt_check_unique scan could fail to see our insertion" (postgres/postgres, master @ 36f7330, 2026-08-03, src/backend/access/nbtree/nbtinsert.c:1033). Memgraph validates unique constraints under the constraint structure's own lock for the same reason (memgraph/memgraph, branch master, src/storage/v2/constraints/unique_constraints.cpp).

Why a duplicate is a VIOLATION here and not a retriable conflict

PostgreSQL distinguishes two cases: a duplicate committed by a finished transaction is a constraint violation, while a duplicate inserted by a STILL-RUNNING transaction makes the inserter release its lock, wait for that transaction (`XactLockTableWait`, or `SpeculativeInsertionWait` for `INSERT … ON CONFLICT`) and start over — so the outcome depends on whether the peer commits or aborts.

GoGraph cannot make that distinction here, and the reason is structural rather than an oversight: PostgreSQL's index entry carries the inserting transaction id, while this value-set is a set of strings with no owner and the registry has no transaction-liveness oracle to consult even if it had one. So every duplicate is reported as what openCypher calls it — a constraint violation, a client error — which is always correct for a committed duplicate and conservative for an in-flight one (it rejects a statement that a retry might have satisfied, rather than admitting a statement that breaks the invariant). Refining the in-flight case to a retriable [mvcc.ErrSerializationConflict], which is what Memgraph reports, needs the value-set to carry its reserver's transaction id; that arrives with the transaction threading in rmp #2320.

Intra-statement duplicates are now caught too

A statement that writes the same constrained value to two nodes — `SET a.email = 'x', b.email = 'x'` under a UNIQUE constraint on email — used to pass, because every check ran before any record. Reserving at check time rejects the second write. That is a fix, not a side effect: such a statement leaves the graph violating a declared invariant. ct carries the reservations this transaction has released but not committed, so a value it has itself given up is not refused as its own duplicate; see ConstraintTxn. It may be nil, which means "no transaction" — a caller with nothing to roll back.

func (*ConstraintRegistry) ResolveByName added in v0.4.0

func (r *ConstraintRegistry) ResolveByName(name string) (kind ConstraintKind, label, prop string, found bool)

ResolveByName resolves a user-defined constraint name to its (kind, label, property) identity, so a DROP CONSTRAINT <name> can locate the constraint to remove. It searches the UNIQUE names first, then the NOT NULL names, and returns found=false when no constraint carries that name.

Only constraints registered WITH a name (via [SetConstraintName], which the CREATE CONSTRAINT executor calls) are resolvable; an anonymous constraint registered through the legacy [RegisterUnique] / [RegisterNotNull] path has no name to match.

ResolveByName is safe for concurrent use. It is deterministic: it scans the UNIQUE names then the NOT NULL names, each in sorted key order, and returns the first match, so a lookup never depends on Go map iteration order. Once constraint names are enforced unique at CREATE time (ErrConstraintNameConflict) at most one constraint can match, so the sorted scan is belt-and-braces.

func (*ConstraintRegistry) SeedUniqueValues added in v0.2.0

func (r *ConstraintRegistry) SeedUniqueValues(label, prop string, values []lpg.PropertyValue) error

SeedUniqueValues populates the value-set of an already-registered UNIQUE constraint on (label, prop) from the property values of the nodes that currently carry the label. It is the post-creation seed that makes a constraint added to a non-empty dataset functional: without it the value-set starts empty and pre-existing duplicates (or duplicates of a pre-existing value) are accepted on the next write.

It also enforces the at-creation invariant (Neo4j semantics, audit gap H2): if two of the supplied values are equal it returns a *ConstraintViolationError wrapping ErrConstraintViolation and seeds nothing, so the caller can reject CREATE CONSTRAINT over already-duplicated data. Null values (the zero PropertyValue) are ignored by a UNIQUE constraint and are skipped.

SeedUniqueValues is a no-op (and returns nil) when no UNIQUE constraint is registered for (label, prop).

func (*ConstraintRegistry) SeedUniqueValuesIgnoringDuplicates added in v0.2.0

func (r *ConstraintRegistry) SeedUniqueValuesIgnoringDuplicates(label, prop string, values []lpg.PropertyValue)

SeedUniqueValuesIgnoringDuplicates seeds the value-set of an already-registered UNIQUE constraint on (label, prop) from values WITHOUT rejecting pre-existing duplicates. It is the recovery seed: recovery must always succeed so the store is serviceable, and a duplicate that predates the constraint is a historical artefact the live enforcement path still rejects on the next write. Null values are skipped. No-op when no UNIQUE constraint is registered for (label, prop).

func (*ConstraintRegistry) SetConstraintName added in v0.2.0

func (r *ConstraintRegistry) SetConstraintName(kindUnique bool, label, prop, name string)

SetConstraintName records the user-defined name of the constraint of the given kind on (label, prop), so the constraint round-trips durably with the name the client declared. kindUnique selects UNIQUE (true) vs NOT NULL. A later [UnregisterUnique] / [UnregisterNotNull] clears the matching name.

func (*ConstraintRegistry) UniqueIndexName

func (r *ConstraintRegistry) UniqueIndexName(label, prop string) (string, bool)

UniqueIndexName returns the backing index name for a unique constraint on (label, prop), or ("", false) if none exists.

func (*ConstraintRegistry) UniqueProperties added in v0.11.0

func (r *ConstraintRegistry) UniqueProperties(label string) []string

UniqueProperties returns the property keys for which a UNIQUE constraint is registered on label, or nil when label carries no uniqueness constraint. It is the per-label lookup the LABEL-set path uses to discover which of a node's properties attaching that label brings under a uniqueness constraint (rmp #2352) — the inverse of the (label, prop) lookup every property-set caller makes, which already knows both halves of the key.

The lookup is O(1) via the uniqueByLabel index and allocates nothing: it returns the registry's own copy-on-write slice, which RegisterUnique / UnregisterUnique never mutate in place. The caller must treat the result as READ-ONLY (it is shared and must not be appended to or modified). Most labels carry zero or one uniqueness constraint, so the common return is nil or a one-element slice.

Callers on the write path MUST gate this behind [HasAnyUnique], which is a lock-free atomic load: UniqueProperties itself takes the registry read lock, and putting that on every label write of an unconstrained schema is precisely the cost [ConstraintRegistry.uniqueActive] exists to avoid.

UniqueProperties is safe for concurrent use.

func (*ConstraintRegistry) UnregisterNotNull

func (r *ConstraintRegistry) UnregisterNotNull(label, prop string)

UnregisterNotNull removes the not-null constraint for (label, prop). No-op if absent.

func (*ConstraintRegistry) UnregisterUnique

func (r *ConstraintRegistry) UnregisterUnique(label, prop string)

UnregisterUnique removes the unique constraint for (label, prop). No-op if absent.

type ConstraintTxn added in v0.11.0

type ConstraintTxn struct {
	// contains filtered or unexported fields
}

ConstraintTxn is one transaction's UNCOMMITTED contribution to the UNIQUE value-sets: the values it has RELEASED and not yet committed.

Why a release is deferred and a reservation is not (rmp #2366)

The two directions are not symmetric, and treating them as if they were is the defect. A RESERVATION is taken eagerly into the shared value-set, and that is correct: it must block every peer immediately, and rolling it back — deleting a value no committed peer can have taken, because it was reserved throughout — cannot disturb anyone. A RELEASE is different. Applied eagerly it hands the value to any peer that asks, and if the releasing transaction then rolls back the value has two holders; so the old code applied it eagerly and journaled the RE-RESERVE as the rollback inverse, which put the value back into SHARED state judged against the rolling-back transaction's OWN view:

T1  REMOVE b:Person          releases 'old' (eagerly, shared)
T2  SET b.email = 'new'      releases 'old', reserves 'new'
T2  ROLLBACK                 replays: releases 'new', RE-RESERVES 'old'
T1  COMMIT                   the label removal stands — no live :Person holds 'old'
    CREATE (y:Person {email:'old'})  -> REFUSED, for ever

Both transactions behaved correctly in isolation; the merged state is wrong because a rollback wrote to state a peer's COMMITTED release had already changed. Deferring the release removes the write: a rollback drops a private mark and touches nothing shared, so there is nothing to order against a peer's commit.

The transaction still sees its own release — that is what the mark is for, and it is what lets one transaction free a value and take it again on another node.

The zero value is ready to use and allocates nothing until the first release, so a statement under a schema with no UNIQUE constraint costs nothing at all.

Not safe for concurrent use: it belongs to one transaction, which is driven by one goroutine at a time, exactly like the undo log it travels beside.

func (*ConstraintTxn) Reset added in v0.11.0

func (t *ConstraintTxn) Reset()

Reset drops every mark, which is what a ROLLBACK does. Nothing shared was touched, so there is nothing to undo.

The inline slots are zeroed rather than merely counted out, so a released value's string does not stay reachable through a recycled adapter; the spill map is kept for reuse, exactly as the undo log keeps its slice.

type ConstraintViolationError

type ConstraintViolationError struct {
	// Label is the node label the constraint is defined on.
	Label string
	// Property is the constrained property key.
	Property string
	// Kind describes the type of constraint: "UNIQUE" or "NOT NULL".
	Kind string
	// Detail is an optional human-readable explanation.
	Detail string
}

ConstraintViolationError carries structured context about which constraint was violated.

func (*ConstraintViolationError) Error

func (e *ConstraintViolationError) Error() string

Error implements the error interface.

func (*ConstraintViolationError) Unwrap

func (e *ConstraintViolationError) Unwrap() error

Unwrap chains to ErrConstraintViolation so callers can use errors.Is.

type CorrelatedApply

type CorrelatedApply struct {
	// contains filtered or unexported fields
}

CorrelatedApply is a Volcano pipeline operator that performs a dependent (correlated) join with the convention that the inner pipeline begins with an Argument leaf re-emitting the outer row. The inner row is forwarded verbatim as the operator's output; no concatenation with the outer row is performed (the outer columns are already present in the inner row).

CorrelatedApply is NOT safe for concurrent use.

func NewCorrelatedApply

func NewCorrelatedApply(outer, inner Operator, arg *Argument) *CorrelatedApply

NewCorrelatedApply creates a CorrelatedApply operator.

  • outer is the left (driving) plan.
  • inner is the right (sub) plan whose leftmost leaf is the provided arg.
  • arg is the Argument node at the inner leaf; CorrelatedApply seeds it before each inner Init call so that the inner pipeline observes the current outer row.

CorrelatedApply takes ownership of both plans. The caller must not use outer or inner directly after calling NewCorrelatedApply.

func (*CorrelatedApply) Close

func (op *CorrelatedApply) Close() error

Close releases resources and closes both the outer and inner plans.

func (*CorrelatedApply) Init

func (op *CorrelatedApply) Init(ctx context.Context) error

Init initialises the outer plan and stores ctx for subsequent Next calls. The inner plan is initialised lazily on the first outer row.

func (*CorrelatedApply) Next

func (op *CorrelatedApply) Next(out *Row) (bool, error)

Next advances the CorrelatedApply operator. The inner row is forwarded verbatim; CorrelatedApply does not concatenate the outer row because the inner row already carries the outer columns (the inner pipeline's leaf Argument re-emitted them).

func (*CorrelatedApply) PlanChildren added in v0.11.0

func (op *CorrelatedApply) PlanChildren() []Operator

PlanChildren reports the outer input that drives this operator, then the inner sub-plan it re-drives for each outer row.

type CountBuffer added in v0.10.0

type CountBuffer struct {
	// contains filtered or unexported fields
}

CountBuffer collects relationship count-store deltas and dirty markings produced by write operators during a single write transaction. It is the structural twin of IndexBuffer (design docs/count-store-design.md §3.1).

Call EnqueueDelta / MarkDirty for every count-affecting graph mutation. At the transaction boundary:

  • Commit: applies the accumulated deltas then the dirty markings to the count store, then resets.
  • Rollback: discards everything without touching the store — no undo log is needed because the store is a pure function of graph state and nothing was applied yet.

Both backing slices grow lazily on first use, so a transaction that touches no count cell (for example a bare CREATE (:N) over an edgeless graph) allocates nothing here. CountBuffer is NOT safe for concurrent use.

func (*CountBuffer) Commit added in v0.10.0

func (b *CountBuffer) Commit(cs *count.Store)

Commit applies all buffered deltas and then all buffered dirty markings to cs, then resets the buffer. Deltas precede dirty markings so a family the deltas just updated exactly is still marked non-exact when the same commit also trips the budget on it. A nil cs is safe: the buffer is discarded without panicking.

Commit must run inside the write visibility barrier (visMu.Lock, after the WAL fsync succeeds) so the count update becomes visible atomically with the graph writes it describes — the durable-then-visible seam the secondary-index fan-out uses.

What that requirement is, and what it is NOT (rmp #2303, MVCC B1)

It is a VISIBILITY requirement, not an ORDERING one, and the distinction is what lets rmp #2304 remove the barrier without redesigning this. A reader must not observe a count that disagrees with the graph it can see; that is atomicity between two structures and it needs whatever mechanism replaces the barrier to publish both together.

It does NOT rest on the barrier imposing a total order across committers. The count store's own ordering basis is COMMUTATIVITY: count.Store.Apply is an additive delta, and count.Store.MarkDirty is a monotone set insert, so any interleaving of two transactions' buffers reaches the same state a serial schedule would.

That was not free, and it was not true when the claim was first made: Apply deleted a cell at zero-or-below, which discarded a transiently-negative cell and lost the decrement that produced it, making the aggregate order-sensitive. It now deletes at exactly zero. See graph/index/count/commutative_test.go for the measurement and the differential.

The intra-transaction order — every delta, THEN every dirty marking — is a property of this buffer and survives concurrency unchanged, because one buffer belongs to one transaction.

func (*CountBuffer) EnqueueDelta added in v0.10.0

func (b *CountBuffer) EnqueueDelta(d count.Delta)

EnqueueDelta appends a cell increment to the buffer.

func (*CountBuffer) Len added in v0.10.0

func (b *CountBuffer) Len() int

Len returns the number of deltas plus dirty markings currently buffered.

func (*CountBuffer) MarkDirty added in v0.10.0

func (b *CountBuffer) MarkDirty(m count.DirtyMark)

MarkDirty appends an X-scoped dirty marking to the buffer.

func (*CountBuffer) NumDeltas added in v0.10.0

func (b *CountBuffer) NumDeltas() int

NumDeltas returns the number of buffered cell increments (excluding dirty markings). The commit fan-out reads it before CountBuffer.Commit resets the buffer, to attribute a "deltas applied" observability count to the commit (task #2087). It is zero for a transaction that touched no count cell, so the bare-CREATE write path emits nothing.

func (*CountBuffer) Rollback added in v0.10.0

func (b *CountBuffer) Rollback()

Rollback discards all buffered deltas and dirty markings without applying them.

type CreateConstraintOp

type CreateConstraintOp struct {
	// contains filtered or unexported fields
}

CreateConstraintOp is a Volcano DDL operator that registers a constraint.

CreateConstraintOp is NOT safe for concurrent use.

func NewCreateConstraintOp

func NewCreateConstraintOp(
	name, label, prop string,
	kind ConstraintKind,
	ifNotExists bool,
	mgr *index.Manager,
	reg *ConstraintRegistry,
	onSchemaChange func(),
) *CreateConstraintOp

NewCreateConstraintOp creates a CreateConstraintOp. onSchemaChange, when non-nil, is invoked exactly once after the operator successfully registers a new constraint — i.e. NOT when the IF NOT EXISTS branch silently absorbs an already-registered constraint. The Engine wires e.ClearPlanCache as onSchemaChange so cached plans are invalidated after a real schema mutation.

func (*CreateConstraintOp) Close

func (op *CreateConstraintOp) Close() error

Close implements Operator.

func (*CreateConstraintOp) Init

func (op *CreateConstraintOp) Init(ctx context.Context) error

Init implements Operator.

func (*CreateConstraintOp) Next

func (op *CreateConstraintOp) Next(_ *Row) (bool, error)

Next implements Operator. Performs the CREATE CONSTRAINT side effect on the first call, then signals end-of-stream.

func (*CreateConstraintOp) WithBackingIndex added in v0.3.0

func (op *CreateConstraintOp) WithBackingIndex(sub index.Subscriber) *CreateConstraintOp

WithBackingIndex supplies a pre-built index subscriber to use as the UNIQUE constraint's backing hash index instead of creating a fresh unbound index. The engine passes a bound index (built and backfilled from the live graph) so the index self-maintains from the change fan-out at commit time. Returns op for chaining.

type CreateIndexOp

type CreateIndexOp struct {
	// contains filtered or unexported fields
}

CreateIndexOp is a Volcano DDL operator that registers a new secondary index.

CreateIndexOp is NOT safe for concurrent use.

func NewCreateIndexOp

func NewCreateIndexOp(
	name string,
	kind IndexKindExec,
	ifNotExists bool,
	mgr *index.Manager,
	onSchemaChange func(),
) *CreateIndexOp

NewCreateIndexOp creates a CreateIndexOp. onSchemaChange, when non-nil, is invoked exactly once after the operator successfully creates a new index in mgr — i.e. NOT when the IF NOT EXISTS branch silently absorbs a duplicate. The Engine wires e.ClearPlanCache as onSchemaChange so cached plans are invalidated after a real schema mutation.

func (*CreateIndexOp) Close

func (op *CreateIndexOp) Close() error

Close implements Operator.

func (*CreateIndexOp) Init

func (op *CreateIndexOp) Init(ctx context.Context) error

Init implements Operator.

func (*CreateIndexOp) Next

func (op *CreateIndexOp) Next(_ *Row) (bool, error)

Next implements Operator. It performs the CREATE INDEX side effect on the first call, then signals end-of-stream. Returns (false, nil) immediately on subsequent calls.

type CreateNode

type CreateNode struct {
	// contains filtered or unexported fields
}

CreateNode creates a new graph node per input row, sets its labels and properties, and appends the new NodeID as a new column.

CreateNode is NOT safe for concurrent use.

func NewCreateNode

func NewCreateNode(
	nodeVar string,
	labels []string,
	properties string,
	child Operator,
	mutator GraphMutator,
) (*CreateNode, error)

NewCreateNode creates a CreateNode operator.

nodeVar is the variable name bound to the new node (may be empty if the node is not referenced downstream). labels is the ordered list of labels to attach. properties is the opaque literal property-map string (e.g. `{name: "Alice"}`) produced by the IR translator; it is parsed once during construction. mutator is the graph write surface.

func (*CreateNode) Close

func (op *CreateNode) Close() error

Close closes the child operator.

func (*CreateNode) Init

func (op *CreateNode) Init(ctx context.Context) error

Init initialises the operator and its child.

The first CreateNode.Init in the process also seeds [globalNodeCounter] past the largest synthetic key currently interned in op.mutator, so that node keys generated in this process cannot collide with keys persisted by an earlier process and replayed during WAL / snapshot recovery. The seed is gated by [globalNodeCounterSeededOnce] so the scan runs at most once per process regardless of how many CreateNode operators are created.

func (*CreateNode) Next

func (op *CreateNode) Next(out *Row) (bool, error)

Next pulls one row from the child, creates a node, and appends the NodeID column. Returns (true, nil) when a row was produced, (false, nil) at end-of-stream, (false, err) on error.

func (*CreateNode) PlanChildren added in v0.11.0

func (op *CreateNode) PlanChildren() []Operator

PlanChildren reports the input whose rows it creates a node for.

func (*CreateNode) WithConstraints

func (op *CreateNode) WithConstraints(reg *ConstraintRegistry, mgr *index.Manager) *CreateNode

WithConstraints attaches a ConstraintRegistry and index.Manager to the operator for pre-write enforcement. Both must be non-nil. Returns op for chaining.

func (*CreateNode) WithParams

func (op *CreateNode) WithParams(params map[string]expr.Value) (*CreateNode, error)

WithParams attaches query parameters for $name substitution in property expressions. Re-parses the property map with the supplied params. Returns op for chaining.

func (*CreateNode) WithPropsEvalFn

func (op *CreateNode) WithPropsEvalFn(fn PropsEvalFn) *CreateNode

WithPropsEvalFn attaches a per-row property evaluator. When fn is non-nil it is called on every Next invocation and its results are merged with the statically parsed props (literal values). Dynamic results take precedence over same-keyed literal values, allowing the property map to contain a mix of literals and expression-valued entries.

type CreateRelationship

type CreateRelationship struct {
	// contains filtered or unexported fields
}

CreateRelationship creates a new directed edge per input row between two already-bound nodes.

CreateRelationship is NOT safe for concurrent use.

func NewCreateRelationship

func NewCreateRelationship(
	startVar, endVar, relVar, relType, properties string,
	schema map[string]int,
	child Operator,
	mutator GraphMutator,
) (*CreateRelationship, error)

NewCreateRelationship creates a CreateRelationship operator.

startVar and endVar are the variable names (column indices are looked up in schema) of the source and destination nodes. relVar is the variable name bound to the new relationship (may be empty). relType is the relationship type label. properties is the opaque literal property-map string. schema maps currently bound variable names to their column indices.

func (*CreateRelationship) Close

func (op *CreateRelationship) Close() error

Close closes the child operator.

func (*CreateRelationship) Init

func (op *CreateRelationship) Init(ctx context.Context) error

Init initialises the operator and its child.

func (*CreateRelationship) Next

func (op *CreateRelationship) Next(out *Row) (bool, error)

Next pulls one row from the child, resolves the endpoint NodeIDs, creates the edge, and appends an optional RelationshipValue column.

func (*CreateRelationship) PlanChildren added in v0.11.0

func (op *CreateRelationship) PlanChildren() []Operator

PlanChildren reports the input whose rows it creates a relationship for.

func (*CreateRelationship) WithParams

func (op *CreateRelationship) WithParams(params map[string]expr.Value) (*CreateRelationship, error)

WithParams re-parses the property map with the supplied query parameters for $name substitution. Returns op for chaining.

func (*CreateRelationship) WithPropsEvalFn

func (op *CreateRelationship) WithPropsEvalFn(fn PropsEvalFn) *CreateRelationship

WithPropsEvalFn attaches a per-row property evaluator. See CreateNode.WithPropsEvalFn.

type DeleteNode

type DeleteNode struct {
	// contains filtered or unexported fields
}

DeleteNode deletes an already-bound node (labels + properties stripped) from the graph, provided it has no incident relationships.

DeleteNode is NOT safe for concurrent use.

func NewDeleteNode

func NewDeleteNode(
	nodeVar string,
	schema map[string]int,
	child Operator,
	mutator GraphMutator,
) *DeleteNode

NewDeleteNode creates a DeleteNode operator.

func (*DeleteNode) Close

func (op *DeleteNode) Close() error

Close closes the child operator.

func (*DeleteNode) Init

func (op *DeleteNode) Init(ctx context.Context) error

Init initialises the operator and its child.

func (*DeleteNode) Next

func (op *DeleteNode) Next(out *Row) (bool, error)

Next pulls one row from the child and deletes the bound node.

func (*DeleteNode) PlanChildren added in v0.11.0

func (op *DeleteNode) PlanChildren() []Operator

PlanChildren reports the input whose rows name the nodes it deletes.

func (*DeleteNode) WithConstraintRegistry added in v0.3.0

func (op *DeleteNode) WithConstraintRegistry(reg *ConstraintRegistry) *DeleteNode

WithConstraintRegistry attaches a ConstraintRegistry so DeleteNode releases unique-constraint value reservations when a node is deleted. Returns op for chaining.

func (*DeleteNode) WithRelEndpoints

func (op *DeleteNode) WithRelEndpoints(fn RelEndpointFn) *DeleteNode

WithRelEndpoints attaches a per-row lookup that returns the (srcID, dstID) endpoints of the edge identified by the bare-variable target. When set AND the schema-direct slot holds an IntegerValue (the in-pipeline edge-id encoding emitted by Expand), the operator dispatches to the edge-removal path instead of treating the integer as a NodeID.

func (*DeleteNode) WithTargetEvalFn

func (op *DeleteNode) WithTargetEvalFn(fn TargetEvalFn) *DeleteNode

WithTargetEvalFn attaches a per-row evaluator for non-variable DELETE targets (subscripts, property access, …). When set, the operator resolves the target value via the evaluator instead of the schema lookup keyed by nodeVar.

type DeleteRelationship

type DeleteRelationship struct {
	// contains filtered or unexported fields
}

DeleteRelationship removes a directed edge per input row.

DeleteRelationship is NOT safe for concurrent use.

func NewDeleteRelationship

func NewDeleteRelationship(
	relVar string,
	schema map[string]int,
	child Operator,
	mutator GraphMutator,
) *DeleteRelationship

NewDeleteRelationship creates a DeleteRelationship operator.

func (*DeleteRelationship) Close

func (op *DeleteRelationship) Close() error

Close closes the child operator.

func (*DeleteRelationship) Init

func (op *DeleteRelationship) Init(ctx context.Context) error

Init initialises the operator and its child.

func (*DeleteRelationship) Next

func (op *DeleteRelationship) Next(out *Row) (bool, error)

Next pulls one row from the child and removes the bound relationship.

func (*DeleteRelationship) PlanChildren added in v0.11.0

func (op *DeleteRelationship) PlanChildren() []Operator

PlanChildren reports the input whose rows name the relationships it deletes.

func (*DeleteRelationship) WithRelCols added in v0.9.0

func (op *DeleteRelationship) WithRelCols(rc RelCols) *DeleteRelationship

WithRelCols records the row columns that hold the bound relationship's endpoint NodeIDs and its forward-CSR edge position, so Next resolves the bound parallel instance's stable handle and removes the EXACT instance in a multigraph (rmp #2018) rather than the first-match endpoint slot. When unset (or when the position resolves no handle), Next falls back to the endpoint removal. Must be called before the first Next. Returns op for chaining.

type DetachDelete

type DetachDelete struct {
	// contains filtered or unexported fields
}

DetachDelete removes all incident edges from a node and then strips the node's labels and properties.

DetachDelete is NOT safe for concurrent use.

func NewDetachDelete

func NewDetachDelete(
	nodeVar string,
	schema map[string]int,
	child Operator,
	mutator GraphMutator,
) *DetachDelete

NewDetachDelete creates a DetachDelete operator.

func (*DetachDelete) Close

func (op *DetachDelete) Close() error

Close closes the child operator.

func (*DetachDelete) Init

func (op *DetachDelete) Init(ctx context.Context) error

Init initialises the operator and its child.

func (*DetachDelete) Next

func (op *DetachDelete) Next(out *Row) (bool, error)

Next pulls one row from the child, removes all incident edges of the bound node, then strips the node's labels and properties.

func (*DetachDelete) PlanChildren added in v0.11.0

func (op *DetachDelete) PlanChildren() []Operator

PlanChildren reports the input whose rows name the nodes it detaches and deletes.

func (*DetachDelete) WithConstraintRegistry added in v0.3.0

func (op *DetachDelete) WithConstraintRegistry(reg *ConstraintRegistry) *DetachDelete

WithConstraintRegistry attaches a ConstraintRegistry so DetachDelete releases unique-constraint value reservations when a node is deleted. Returns op for chaining.

func (*DetachDelete) WithTargetEvalFn

func (op *DetachDelete) WithTargetEvalFn(fn TargetEvalFn) *DetachDelete

WithTargetEvalFn attaches a per-row evaluator for non-variable DETACH DELETE targets (subscripts, property access, …).

type Direction

type Direction uint8

Direction controls which edges Expand follows.

const (
	// DirOut follows only out-edges of the source node.
	DirOut Direction = iota + 1
	// DirIn follows only in-edges (reverse edges).
	DirIn
	// DirBoth follows both out-edges and in-edges.
	DirBoth
)

type Distinct

type Distinct struct {
	// contains filtered or unexported fields
}

Distinct is a streaming Volcano operator that emits each unique row exactly once. It maintains a hash set of seen rows; hash collisions are resolved by full equality checks.

Distinct is NOT safe for concurrent use.

func NewDistinct

func NewDistinct(child Operator, maxDistinct int) *Distinct

NewDistinct creates a Distinct operator.

  • child: the upstream operator.
  • maxDistinct: upper bound on distinct rows; pass 0 to use DefaultMaxDistinct.

func (*Distinct) Close

func (op *Distinct) Close() error

Close closes the child operator and releases internal state.

func (*Distinct) Init

func (op *Distinct) Init(ctx context.Context) error

Init initialises the operator and resets the deduplication state.

func (*Distinct) Next

func (op *Distinct) Next(out *Row) (bool, error)

Next pulls rows from the child and emits the first occurrence of each unique row. Duplicate rows are silently discarded. Returns ErrDistinctMemoryExceeded if more than maxDistinct distinct rows are encountered.

func (*Distinct) PlanChildren added in v0.11.0

func (op *Distinct) PlanChildren() []Operator

PlanChildren reports the input it deduplicates.

func (*Distinct) WithByteBudget added in v0.7.0

func (op *Distinct) WithByteBudget(maxBytes int64, estimateRow func(Row) int64) *Distinct

WithByteBudget bounds the estimated retained size of the stored distinct rows by maxBytes. It complements the maxDistinct count cap so a few large-valued distinct rows cannot exceed the engine's result-byte budget before the count cap fires (#1841). A non-positive maxBytes or nil estimateRow leaves the byte dimension disabled. Returns op for chaining and must be called before Init.

type DropConstraintOp

type DropConstraintOp struct {
	// contains filtered or unexported fields
}

DropConstraintOp is a Volcano DDL operator that deregisters a constraint.

DropConstraintOp is NOT safe for concurrent use.

func NewDropConstraintOp

func NewDropConstraintOp(
	name, label, prop string,
	kind ConstraintKind,
	ifExists bool,
	mgr *index.Manager,
	reg *ConstraintRegistry,
	onSchemaChange func(),
) *DropConstraintOp

NewDropConstraintOp creates a DropConstraintOp. onSchemaChange, when non-nil, is invoked exactly once after the operator successfully removes a constraint — i.e. NOT when the IF EXISTS branch silently absorbs an absent-constraint condition. The Engine wires e.ClearPlanCache as onSchemaChange so cached plans are invalidated after a real schema mutation.

func (*DropConstraintOp) Close

func (op *DropConstraintOp) Close() error

Close implements Operator.

func (*DropConstraintOp) Init

func (op *DropConstraintOp) Init(ctx context.Context) error

Init implements Operator.

func (*DropConstraintOp) Next

func (op *DropConstraintOp) Next(_ *Row) (bool, error)

Next implements Operator. Performs the DROP CONSTRAINT side effect on the first call, then signals end-of-stream.

type DropIndexOp

type DropIndexOp struct {
	// contains filtered or unexported fields
}

DropIndexOp is a Volcano DDL operator that deregisters a secondary index.

DropIndexOp is NOT safe for concurrent use.

func NewDropIndexOp

func NewDropIndexOp(
	name string,
	ifExists bool,
	mgr *index.Manager,
	onSchemaChange func(),
) *DropIndexOp

NewDropIndexOp creates a DropIndexOp. onSchemaChange, when non-nil, is invoked exactly once after the operator successfully drops the index — i.e. NOT when the IF EXISTS branch silently absorbs a missing-index error. The Engine wires e.ClearPlanCache as onSchemaChange so cached plans are invalidated after a real schema mutation.

func (*DropIndexOp) Close

func (op *DropIndexOp) Close() error

Close implements Operator.

func (*DropIndexOp) Init

func (op *DropIndexOp) Init(ctx context.Context) error

Init implements Operator.

func (*DropIndexOp) Next

func (op *DropIndexOp) Next(_ *Row) (bool, error)

Next implements Operator. It performs the DROP INDEX side effect on the first call, then signals end-of-stream.

type Eager

type Eager struct {
	// contains filtered or unexported fields
}

Eager is a pipeline-breaking barrier. Init drains every row from the child into an internal buffer; Next then re-emits the buffered rows in insertion order.

Eager is NOT safe for concurrent use: it holds unsynchronised buffer and cursor state mutated across Init/Next/Close, so a single goroutine must drive one operator tree, like every other Operator.

func NewEager

func NewEager(child Operator, maxRows int) *Eager

NewEager wraps child in an Eager barrier.

  • child: the upstream operator to drain.
  • maxRows: upper bound on rows held in memory; pass 0 to use DefaultMaxEagerRows.

func (*Eager) Close

func (op *Eager) Close() error

Close releases the buffer and closes the child.

func (*Eager) Init

func (op *Eager) Init(ctx context.Context) error

Init initialises the operator AND drains every child row into the buffer so the downstream pipeline can apply LIMIT / SKIP / DISTINCT without starving any write-side child of its driving rows.

The drain is bounded by maxRows (ErrEagerMemoryExceeded when exceeded) and honours context cancellation: a cancelled or expired ctx aborts the drain promptly rather than buffering the entire child first.

func (*Eager) Next

func (op *Eager) Next(out *Row) (bool, error)

Next emits the next buffered row.

func (*Eager) PlanChildren added in v0.11.0

func (op *Eager) PlanChildren() []Operator

PlanChildren reports the input it fully materialises before emitting.

func (*Eager) WithByteBudget added in v0.7.0

func (op *Eager) WithByteBudget(maxBytes int64, estimateRow func(Row) int64) *Eager

WithByteBudget bounds the estimated retained size of the buffered rows by maxBytes. It complements the maxRows count cap so a few large-valued rows cannot exceed the engine's result-byte budget before the count cap fires (#1841). A non-positive maxBytes or nil estimateRow leaves the byte dimension disabled. Returns op for chaining and must be called before Init.

type EagerAggregation

type EagerAggregation struct {
	// contains filtered or unexported fields
}

EagerAggregation is a blocking (pipeline-breaking) Volcano operator that groups rows from its child by the specified key columns and applies per-group aggregators. It emits one output row per group once the child is exhausted.

EagerAggregation is NOT safe for concurrent use.

func NewEagerAggregation

func NewEagerAggregation(
	child Operator,
	keyCols []int,
	aggFactories []funcs.AggregatorFactory,
	maxGroups int,
) (*EagerAggregation, error)

NewEagerAggregation creates an EagerAggregation operator.

  • child: the upstream operator to consume.
  • keyCols: column indices whose values define the group key. An empty slice computes a single global aggregate.
  • aggFactories: one AggregatorFactory per aggregate expression. Must not be empty.
  • maxGroups: upper bound on distinct groups; pass 0 to use DefaultMaxGroups.

func (*EagerAggregation) Close

func (op *EagerAggregation) Close() error

Close closes the child operator and releases internal state.

func (*EagerAggregation) Init

func (op *EagerAggregation) Init(ctx context.Context) error

Init initialises the operator. The blocking consume phase is deferred to the first Next call.

func (*EagerAggregation) Next

func (op *EagerAggregation) Next(out *Row) (bool, error)

Next emits the next aggregated row. On the first call it consumes all rows from the child (pipeline breaker) and builds the group table. Subsequent calls iterate through the completed groups.

func (*EagerAggregation) PlanChildren added in v0.11.0

func (op *EagerAggregation) PlanChildren() []Operator

PlanChildren reports the input it groups and aggregates.

func (*EagerAggregation) WithByteBudget added in v0.7.0

func (op *EagerAggregation) WithByteBudget(maxBytes int64, estimateRow func(Row) int64) *EagerAggregation

WithByteBudget bounds the estimated retained size of the group keys by maxBytes. It complements the maxGroups count cap so a few large-valued group keys cannot exceed the engine's result-byte budget before the count cap fires (#1841). A non-positive maxBytes or nil estimateRow leaves the byte dimension disabled. Returns op for chaining and must be called before Init.

func (*EagerAggregation) WithChunkInput added in v0.9.0

func (op *EagerAggregation) WithChunkInput() error

WithChunkInput switches the consume phase to pull the child column-major so the scalar grouping keys are hashed and compared UNBOXED, boxing a key only on new-group creation (#2049), and each aggregate argument column is scatter-accumulated UNBOXED by its kernel. The child must be a ChunkProducer; the grouping keys must occupy chunk columns 0..len(keyCols)-1 and the aggregate arguments the columns after them (the layout the aggregation pre-projection installs), which is the same layout the row-input consume assumes. It returns an error otherwise, leaving the operator on the byte-identical row-input path. Call before Init.

len(keyCols) == 0 is supported: a group-key-free (global) aggregate forms exactly one group, because a zero-column key hashes to the same constant and compares equal for every row, and the empty-input neutral row is synthesised by the consume phase itself (#2185).

The path is reversible and self-checking per batch: a grouping-key column that is not an unboxed scalar backing (a promoted/boxed or non-scalar column) is read via Chunk.BoxCell and hashed/compared through the same boxed equivalence as the row path, so a heterogeneous or non-scalar key stays byte-identical.

type EffectCountingSuppressor added in v0.11.0

type EffectCountingSuppressor interface {
	// SuppressEffectCounting stops (true) or resumes (false) write-effect counting.
	// Calls do not nest: a paired true/false around one teardown span is the contract.
	SuppressEffectCounting(on bool)
}

EffectCountingSuppressor is implemented by a write adapter whose write-effect counting can be paused for a span of internal teardown (#2212).

It exists because deleting a node is implemented as: strip its incident edges, strip its labels, strip its properties, then tombstone it. Those strips go through the same RemoveNodeLabel / DelNodeProperty calls a user's REMOVE does, but they are NOT user-visible side effects — openCypher declares `DELETE n` and `DETACH DELETE n` as `-nodes 1` and nothing else (cypher/tck/features/clauses/delete/Delete1.feature scenarios [1] and [2]); a deleted node's labels and properties vanish WITH the node rather than being separately removed. Counting them would report effects the spec says did not happen.

It is a separate optional interface rather than a GraphMutator method so the many test stubs implementing GraphMutator need no change; a mutator that does not implement it simply cannot suppress, which is safe because only the counting adapters do.

type Expand

type Expand struct {
	// contains filtered or unexported fields
}

Expand is a Volcano pipeline operator that, for each input row, expands one hop along the graph's CSR adjacency.

Expand is NOT safe for concurrent use.

func NewExpand

func NewExpand(input Operator, src AdjacencySource, cfg ExpandConfig) *Expand

NewExpand creates an Expand operator.

src yields the forward and reverse adjacency (the reverse is required for DirIn/DirBoth and ignored for DirOut) together with the type filter keyed to them. It is consulted in Expand.Init, not here.

func NewExpandWithOptions

func NewExpandWithOptions(input Operator, src AdjacencySource, cfg ExpandConfig, options ...expandOption) *Expand

NewExpandWithOptions creates an Expand operator with optional extra configuration applied via functional options (e.g. WithCyphermorphism).

All ExpandConfig fields behave identically to NewExpand. Options are applied after base construction and augment the operator's behaviour without altering its public type.

func (*Expand) Close

func (op *Expand) Close() error

Close releases resources and closes the child operator.

func (*Expand) Init

func (op *Expand) Init(ctx context.Context) error

Init initialises the operator and its child.

func (*Expand) Next

func (op *Expand) Next(out *Row) (bool, error)

Next emits the next (srcID, edgeID, dstID) triplet appended to the current input row. It pulls a new input row whenever the current source's adjacency is exhausted.

func (*Expand) PlanChildren added in v0.11.0

func (op *Expand) PlanChildren() []Operator

PlanChildren reports the operator whose rows it expands from.

func (*Expand) PlanDetail added in v0.11.0

func (op *Expand) PlanDetail() string

PlanDetail reports whether this hop's destination is already bound and, when it is, which access path it takes to reach it (#2149). This is exactly the kind of physical decision the type name cannot carry: a bound-destination hop is still an *Expand, but it either SEEKS the destination's contiguous run in the destination-ordered CSR — O(log d + r) — or walks the whole neighbour run and filters, which is Θ(d). The two differ by an asymptotic factor on the shape behind triangles, cycle closing and mutual-relationship detection, so a plan that did not distinguish them would hide the change this operator exists to make.

"ExpandInto" is the name openCypher implementations conventionally give this access path; it appears in the DETAIL rather than as the operator name because a rendered name is the concrete Go type and must stay incapable of disagreeing with the operator that runs (rmp #2222). An ordinary hop returns "" and renders as "Expand" alone.

func (*Expand) WithExpandInto added in v0.11.0

func (op *Expand) WithExpandInto(col int) *Expand

WithExpandInto binds this hop's destination to an already-bound input column, so the operator emits only edges landing on that node instead of one row per neighbour (#2206). col < 0 disables it. Returns op for chaining; call before Init.

func (*Expand) WithExpandIntoSeek added in v0.11.0

func (op *Expand) WithExpandIntoSeek(enabled bool) *Expand

WithExpandIntoSeek enables or disables the O(log d) SEEK for an expand-into hop (#2149). It has no effect unless Expand.WithExpandInto has bound a column.

Disabling it keeps the expand-into FILTER and returns the operator to walking the whole neighbour run, which is the pre-#2149 behaviour and the "off" arm of the differential test that proves the two agree row for row. Returns op for chaining; call before Init.

type ExpandConfig

type ExpandConfig struct {
	// MultiplicityFn returns the Cypher CREATE-call multiplicity recorded
	// for the directed edge (srcID, dstID). When the returned count is N >
	// 1, the operator emits the corresponding output row N times in a row,
	// reflecting the openCypher rule that `MATCH ()-[r]->()` enumerates
	// each CREATE call separately even when the underlying simple-graph
	// storage collapsed them to one entry (Merge5 [21]). A nil fn (or
	// returning 0 / 1) disables the multiplicity emit and behaves like a
	// plain single-row Expand.
	MultiplicityFn func(srcID, dstID uint64) int64
	// EdgeType, when non-empty, restricts emitted edges to those whose
	// positional index is present in the [AdjacencySource]'s filter with this
	// type label.
	EdgeType string
	// RelCols lists the input-row columns holding edge IDs already traversed
	// by sibling Expand operators in the same MATCH pattern. Each emitted
	// edge must NOT match any of these columns (openCypher 9 §3.2.2
	// relationship-isomorphism / cyphermorphism). Empty disables the
	// check.
	RelCols []int
	// InputCol is the column index in each input row that holds the source
	// NodeID (as expr.IntegerValue).  Defaults to 0.
	InputCol int
	// Direction to follow. Defaults to DirOut when zero.
	Direction Direction
}

ExpandConfig carries the optional configuration for NewExpand.

The relationship-type FILTER is deliberately not here: it is keyed to the absolute edge positions of one particular adjacency, so it is supplied by the AdjacencySource that yields that adjacency and cannot drift from it (rmp #2317). It used to be a field, resolved at plan-build time alongside a pair that is now resolved at execution time — two lifetimes for two halves of one answer.

type ExpandIntersect added in v0.11.0

type ExpandIntersect struct {
	// contains filtered or unexported fields
}

ExpandIntersect fuses a cycle's open middle hop and its closing seek into one operator driven by a sorted-set intersection.

func NewExpandIntersect added in v0.11.0

func NewExpandIntersect(input Operator, src IntersectAdjacencySource, cfg *ExpandIntersectConfig) *ExpandIntersect

NewExpandIntersect creates a fused cyclic expand over the forward and reverse CSRs. rev is required: the candidate set is N_in(a).

cfg is taken by pointer because it is 88 bytes and this is a plan-build call, so there is nothing to gain from copying it. A nil cfg is treated as the zero value, which is an untyped fusion reading b from column 0 and a from column 0 — valid but not useful, so callers always pass one.

func (*ExpandIntersect) Close added in v0.11.0

func (op *ExpandIntersect) Close() error

Close releases the child and drops the snapshots.

func (*ExpandIntersect) Init added in v0.11.0

func (op *ExpandIntersect) Init(ctx context.Context) error

Init initialises the child, snapshots both CSR directions, and RESETS every cursor so the operator can be re-executed.

Resetting is not housekeeping — omitting it was a real defect, caught by the OPTIONAL MATCH case in cypher/cyclic_intersect_diff_test.go. Under a correlated Apply, Init runs once per OUTER ROW, not once per query. Without the reset the first outer row ran the operator to exhaustion, left done=true, and every subsequent outer row silently produced nothing — so an OPTIONAL MATCH over a cyclic pattern returned a null row for every input except at most the first. It failed silently and returned WRONG RESULTS rather than an error, which is why the reset is stated here explicitly rather than left implicit.

func (*ExpandIntersect) Next added in v0.11.0

func (op *ExpandIntersect) Next(out *Row) (bool, error)

Next advances by one row.

func (*ExpandIntersect) PlanChildren added in v0.11.0

func (op *ExpandIntersect) PlanChildren() []Operator

PlanChildren reports the input whose rows drive the intersection.

func (*ExpandIntersect) PlanDetail added in v0.11.0

func (op *ExpandIntersect) PlanDetail() string

PlanDetail names the two legs' types, which is the part of the physical decision the operator's own name cannot carry.

type ExpandIntersectConfig added in v0.11.0

type ExpandIntersectConfig struct {
	// The two type filters are NOT here (rmp #2317). They map forward edge
	// positions to the accepted type set for the middle (b→c) and closing (c→a)
	// legs, so they are keyed to one particular adjacency and travel with it; see
	// [IntersectAdjacencySource].
	//
	// MidEdgeType and EndEdgeType, when non-empty, restrict each leg to edges
	// present in the corresponding filter.
	MidEdgeType string
	EndEdgeType string
	// RelCols lists input-row columns holding edge IDs already traversed by
	// sibling relationship patterns in the same MATCH clause. Both emitted edges
	// must avoid all of them (relationship isomorphism). Empty disables the check.
	RelCols []int
	// MidCol is the input-row column holding b, the middle hop's source.
	MidCol int
	// EndCol is the input-row column holding a, the node the cycle closes on.
	EndCol int
}

ExpandIntersectConfig configures a fused cyclic expand.

type ExprValueEvalFn added in v0.9.0

type ExprValueEvalFn func(row Row) (expr.Value, error)

ExprValueEvalFn is a per-row evaluator for a whole map-valued RHS expression on a `SET x = <expr>` / `SET x += <expr>` operator. It returns the evaluated value; the operator dispatches on its runtime kind. See SetAllProperties.WithExprEvalFn.

type Filter

type Filter struct {
	// contains filtered or unexported fields
}

Filter is a Volcano pipeline operator that applies a FilterFn predicate to each row produced by its child operator. It emits only rows for which the predicate returns expr.BoolValue(true).

Null and false both suppress the row (three-valued logic).

Filter is NOT safe for concurrent use.

func NewFilter

func NewFilter(child Operator, predFn FilterFn) *Filter

NewFilter creates a Filter operator that wraps child and applies predFn to every row.

func (*Filter) Close

func (op *Filter) Close() error

Close releases resources and closes the child operator.

func (*Filter) Init

func (op *Filter) Init(ctx context.Context) error

Init initialises the operator and its child.

func (*Filter) Next

func (op *Filter) Next(out *Row) (bool, error)

Next advances to the next row that passes the predicate. It pulls rows from the child until one satisfies the predicate, end-of-stream, or an error.

func (*Filter) PlanChildren added in v0.11.0

func (op *Filter) PlanChildren() []Operator

PlanChildren reports the input it filters.

type FilterFn

type FilterFn func(row Row) (expr.Value, error)

FilterFn is a predicate over a Row. It must return (BoolValue(true), nil) to accept the row. Any other non-error return value (including NULL and BoolValue(false)) causes the row to be dropped. An error halts the pipeline.

type Float64RangeIndex added in v0.6.0

type Float64RangeIndex struct {
	// contains filtered or unexported fields
}

Float64RangeIndex adapts btree.Index[float64] to the [rangeLookup] interface for the UNIFIED numeric range seek (#1652). It backs a CREATE INDEX (btree) companion that indexes BOTH integer- and float-valued nodes under one float64 total order (openCypher orders integers and floats in a single numeric order), so a numeric range bound is a SUPERSET-complete probe over every numeric node — never a non-superset the way an int64-only index would be (it would silently drop the float-valued matches).

Both an integer and a float bound are coerced to float64: a Cypher IntegerValue bound and a FloatValue bound address the same numeric key space. The float64 widening of a large int64 bound can lose precision and make the probe over-return at the boundary, but the engine ALWAYS retains the original AST predicate as a residual Filter on top of the range scan, so any false positive is removed and the result is identical to a label scan+filter (cypher-expert-consultant, #1652). Nil / non-numeric bounds are treated as ±∞ so an unbounded side returns the whole numeric population.

NaN is never a bound here (the extractor only admits finite numeric literals/parameters) and is never indexed (projectNumericPropValue excludes it), so a NaN node can neither be a key nor be returned: the btree's total order places NaN below every real value and Range with a non-NaN lower bound never returns it, and the residual Filter is the final backstop.

func NewFloat64RangeIndex added in v0.6.0

func NewFloat64RangeIndex(idx interface {
	Range(lo, hi float64) *roaring64.Bitmap
}) *Float64RangeIndex

NewFloat64RangeIndex constructs a Float64RangeIndex.

func (*Float64RangeIndex) RangeBitmap added in v0.6.0

func (r *Float64RangeIndex) RangeBitmap(lo, hi expr.Value) *roaring64.Bitmap

RangeBitmap implements [rangeLookup]. A nil or NULL bound, or one that is neither an integer nor a float, is treated as the corresponding numeric infinity so an unbounded (or undecodable) side spans the whole numeric range — the residual Filter then enforces the exact predicate.

type Foreach added in v0.9.0

type Foreach struct {
	// contains filtered or unexported fields
}

Foreach runs a correlated body sub-plan per outer row for its writes, then passes the outer row through unchanged.

Foreach is NOT safe for concurrent use.

func NewForeach added in v0.9.0

func NewForeach(outer, inner Operator, arg *Argument) *Foreach

NewForeach creates a Foreach operator.

  • outer is the driving (left) plan.
  • inner is the body sub-plan whose leftmost leaf is the provided arg (Argument → Unwind(list) → updating operators).
  • arg is the Argument leaf; Foreach seeds it with the current outer row before each body Init so the body observes that row and the loop element.

Foreach takes ownership of both plans.

func (*Foreach) Close added in v0.9.0

func (op *Foreach) Close() error

Close closes both the outer and body plans.

func (*Foreach) Init added in v0.9.0

func (op *Foreach) Init(ctx context.Context) error

Init initialises the outer plan and stores ctx. The body plan is initialised per outer row inside Next.

func (*Foreach) Next added in v0.9.0

func (op *Foreach) Next(out *Row) (bool, error)

Next pulls the next outer row, drives the body sub-plan over the list for its side-effects, and emits the outer row unchanged. Returns (false, nil) once the outer plan is exhausted.

func (*Foreach) PlanChildren added in v0.11.0

func (op *Foreach) PlanChildren() []Operator

PlanChildren reports the outer input that drives this operator, then the inner sub-plan it re-drives for each outer row.

type GlobalAggregateAdapter

type GlobalAggregateAdapter struct {
	// contains filtered or unexported fields
}

GlobalAggregateAdapter wraps an EagerAggregation operator that has no group-by keys and ensures the output stream contains exactly one row even when the child is empty.

GlobalAggregateAdapter is NOT safe for concurrent use.

func NewGlobalAggregateAdapter

func NewGlobalAggregateAdapter(child Operator, aggFactories []funcs.AggregatorFactory) *GlobalAggregateAdapter

NewGlobalAggregateAdapter returns a GlobalAggregateAdapter that wraps child. aggFactories must contain one factory per aggregate column, in the same order as the columns produced by child. The factories are invoked exactly once, on empty input, to synthesise the neutral row.

Note: the adapter does not validate that child is a group-by-less EagerAggregation; callers are responsible for that. Wrapping a grouped aggregation has no effect because grouped aggregations always emit either zero (no input) or N rows (one per group), and the empty-input case for a grouped aggregation is correct as is.

func (*GlobalAggregateAdapter) Close

func (op *GlobalAggregateAdapter) Close() error

Close releases the adapter's child.

func (*GlobalAggregateAdapter) Init

Init initialises the adapter and its child.

func (*GlobalAggregateAdapter) Next

func (op *GlobalAggregateAdapter) Next(out *Row) (bool, error)

Next forwards child rows unchanged. When the child reports end-of-stream without ever producing a row, Next emits exactly one synthetic row built from the neutral results of each aggregate factory before signalling exhaustion itself.

func (*GlobalAggregateAdapter) PlanChildren added in v0.11.0

func (op *GlobalAggregateAdapter) PlanChildren() []Operator

PlanChildren reports the aggregation it adapts to a single global group.

type GraphMutator

type GraphMutator interface {
	// AddNode interns n and returns its stable NodeID. Returns the
	// error from the underlying graph implementation (currently only
	// [adjlist.ErrShardFull] is reachable, and only when the
	// underlying [adjlist.Config.MaxShardCapacity] is set).
	AddNode(n string) (graph.NodeID, error)

	// AddEdge inserts a directed edge (src→dst) with weight w, interning
	// endpoints as needed. Returns the stable NodeIDs of src and dst and
	// any error from the underlying graph implementation.
	AddEdge(src, dst string, w float64) (srcID, dstID graph.NodeID, err error)

	// AddEdgeH is [GraphMutator.AddEdge] with a stable per-edge handle
	// allocated by the underlying graph and stamped onto the adjacency
	// slot. The returned handle keys the *ByHandle metadata setters below
	// so a parallel CREATE's type and properties are resolvable on the
	// read path by an identity that survives sibling-edge deletion. Used
	// by CreateRelationship and by MergeRelationship's create path (rmp
	// #1683) so a MERGE-built parallel edge of a distinct type keeps its
	// own per-instance identity instead of collapsing onto the per-pair
	// label union. The handle is always non-zero on success.
	AddEdgeH(src, dst string, w float64) (srcID, dstID graph.NodeID, handle uint64, err error)

	// RemoveEdge removes the directed edge from src to dst (no-op if absent).
	// In a multigraph it removes the FIRST src→dst adjacency slot regardless of
	// which parallel instance is intended; callers that hold a bound instance's
	// stable handle must use RemoveEdgeByHandle for instance precision.
	RemoveEdge(src, dst string)

	// RemoveEdgeByHandle removes the single parallel edge instance identified by
	// the stable handle on the (src, dst) pair — its adjacency slot AND its
	// per-handle label/property metadata — leaving sibling instances intact.
	// Used by DELETE of a specifically-bound relationship so a parallel edge of
	// a distinct type/property is retired EXACTLY, not by first-match (rmp
	// #2018). handle must be non-zero; a caller with no resolvable handle (simple
	// graph, or a post-projection binding that lost the edge position) uses
	// RemoveEdge instead. No-op when no slot carries handle.
	RemoveEdgeByHandle(src, dst string, handle uint64)

	// SetNodeLabel attaches label to n (inserting n if absent). Returns
	// any error from the underlying [adjlist.AdjList.AddNode] (see
	// [GraphMutator.AddNode]).
	SetNodeLabel(n, label string) error

	// RemoveNodeLabel detaches label from n (no-op if absent).
	RemoveNodeLabel(n, label string)

	// RemoveNode tombstones n in the underlying graph so subsequent reads
	// (AllNodesScan, count(*), Order) treat the node as absent. Callers
	// should strip labels/properties/incident edges before invoking
	// RemoveNode so the tombstone reflects the fully-deleted state.
	RemoveNode(n string)

	// IsTombstoned reports whether the NodeID has been tombstoned. Used by
	// AllNodesScan to skip phantom entries the Mapper still indexes.
	IsTombstoned(id graph.NodeID) bool

	// SetNodeProperty sets the named property on n. Returns any error
	// from the underlying [adjlist.AdjList.AddNode] (see
	// [GraphMutator.AddNode]).
	SetNodeProperty(n, key string, value lpg.PropertyValue) error

	// DelNodeProperty removes the named property from n (no-op if absent).
	DelNodeProperty(n, key string)

	// NodeProperties returns a snapshot of all properties currently on n.
	NodeProperties(n string) map[string]lpg.PropertyValue

	// NodeLabels returns a snapshot of all labels currently on n in
	// unspecified order.
	NodeLabels(n string) []string

	// HasEdge reports whether a directed edge from src to dst is present.
	HasEdge(src, dst string) bool

	// SetEdgeLabel attaches label to the directed edge (src, dst).
	SetEdgeLabel(src, dst, label string)

	// SetEdgeProperty sets the named property on the directed edge (src, dst).
	// Returns any error from the underlying graph (e.g. schema violation).
	SetEdgeProperty(src, dst, key string, value lpg.PropertyValue) error

	// DelEdgeProperty removes the named property from the directed edge
	// (src, dst) (no-op if absent).
	DelEdgeProperty(src, dst, key string)

	// EdgeProperties returns a snapshot of every property currently set on
	// the directed edge (src, dst). Returns an empty map when the edge has
	// no properties or does not exist.
	EdgeProperties(src, dst string) map[string]lpg.PropertyValue

	// EdgeLabels returns a snapshot of every label currently attached to
	// the directed edge (src, dst). Returns an empty slice when the edge
	// has no labels or does not exist. Used by DELETE r to capture the
	// relationship type before tombstoning the edge so the row's
	// post-delete RelationshipValue keeps `RETURN type(r)` working.
	EdgeLabels(src, dst string) []string

	// IncEdgeCreateCount bumps the Cypher CREATE-call multiplicity
	// counter for the directed edge (src, dst) by one and returns the
	// new (1-based) value. The counter records how many CREATE
	// statements have targeted the same endpoint pair regardless of
	// whether the underlying storage already had an entry — MERGE
	// consults it to emit multiplicity rows when an existing edge
	// satisfies the merge pattern (Merge5 [3]). The returned index is
	// the per-instance idx callers pass to the *At family of
	// metadata-write helpers.
	IncEdgeCreateCount(src, dst string) int64
	// EdgeCreateCount returns the current CREATE-call multiplicity
	// counter for the directed edge (src, dst), or 0 when no CREATE
	// has been recorded.
	EdgeCreateCount(src, dst string) int64
	// DecEdgeCreateCount decrements the CREATE-call multiplicity
	// counter (floor 0). Called by DELETE so subsequent MERGEs see
	// the correct multiplicity.
	DecEdgeCreateCount(src, dst string)
	// SetEdgeLabelAt attaches `label` to the directed edge instance
	// (src, dst) at the supplied 1-based CREATE index. Used by
	// CreateRelationship so parallel CREATEs of the same endpoint
	// pair retain their distinct labels (Match2 [6] / Match7 [29]).
	SetEdgeLabelAt(src, dst string, idx int64, label string)
	// EdgeLabelsAt returns the labels recorded at instance `idx` of
	// the directed edge (src, dst), or nil when the instance has no
	// per-CREATE labels.
	EdgeLabelsAt(src, dst string, idx int64) []string
	// SetEdgePropertyAt records `key`=`value` on the directed edge
	// instance (src, dst) at the supplied 1-based CREATE index. Returns
	// any error returned by the installed SchemaValidator.
	SetEdgePropertyAt(src, dst string, idx int64, key string, value lpg.PropertyValue) error
	// EdgePropertiesAt returns the property map recorded at instance
	// `idx` of the directed edge (src, dst), or nil when no
	// per-CREATE map was captured.
	EdgePropertiesAt(src, dst string, idx int64) map[string]lpg.PropertyValue
	// RemoveEdgeInstance drops every per-CREATE label and property
	// associated with (src, dst) at `idx`. Used by DELETE to discard
	// a specific logical edge while leaving sibling instances
	// untouched.
	RemoveEdgeInstance(src, dst string, idx int64)

	// SetEdgeLabelByHandle attaches `label` to the edge identified by the
	// stable `handle` on the (src, dst) pair (see [GraphMutator.AddEdgeH]).
	// The handle-keyed analogue of SetEdgeLabelAt; the read path resolves a
	// parallel CREATE's type by this identity instead of a positional CSR
	// index. No-op when handle is 0.
	SetEdgeLabelByHandle(src, dst string, handle uint64, label string)
	// EdgeLabelsByHandle returns the labels recorded for the edge
	// identified by `handle` on the (src, dst) pair, or nil when none.
	EdgeLabelsByHandle(src, dst string, handle uint64) []string
	// SetEdgePropertyByHandle records `key`=`value` on the edge identified
	// by the stable `handle` on the (src, dst) pair. No-op when handle is 0.
	// Returns any error returned by the installed SchemaValidator.
	SetEdgePropertyByHandle(src, dst string, handle uint64, key string, value lpg.PropertyValue) error
	// DelEdgePropertyByHandle removes exactly `key` from the property bag of
	// the edge identified by the stable `handle` on the (src, dst) pair,
	// leaving sibling handles untouched. No-op when handle is 0 or the handle
	// never carried the key. The single-key removal analogue of
	// RemoveEdgeInstanceByHandle (which drops ALL of a handle's metadata); used
	// by REMOVE r.x / SET r.x = null on one parallel relationship instance.
	DelEdgePropertyByHandle(src, dst string, handle uint64, key string)
	// EdgePropertiesByHandle returns the property map recorded for the edge
	// identified by `handle` on the (src, dst) pair, or nil when none.
	EdgePropertiesByHandle(src, dst string, handle uint64) map[string]lpg.PropertyValue
	// RemoveEdgeInstanceByHandle drops every per-handle label and property
	// associated with (src, dst) at `handle`. Used by DELETE to discard a
	// specific logical edge while leaving sibling handles untouched.
	RemoveEdgeInstanceByHandle(src, dst string, handle uint64)

	// FirstEdgeHandle returns the stable per-edge handle stamped on the FIRST
	// adjacency slot from src to dst, and whether such a handled slot exists.
	// It is the by-PAIR handle resolver: MERGE binds a single logical
	// (src, dst) edge rather than a specific parallel instance, so the
	// ON MATCH / ON CREATE action path uses it to mirror its per-pair property
	// writes onto the matched edge's by-handle store. Returns 0 / false when
	// either endpoint is unknown, no src→dst edge exists, or the matched slot
	// carries the 0 "no handle" sentinel (simple-graph / pre-handle storage) —
	// in which case the caller mutates the per-pair store only and never a
	// by-handle instance.
	FirstEdgeHandle(src, dst string) (uint64, bool)

	// OutNeighbours returns the outgoing neighbour node keys of n as a
	// snapshot slice. Callers must not mutate the returned slice.
	OutNeighbours(n string) []string

	// InNeighbours returns the incoming neighbour node keys of n as a
	// snapshot slice. This requires a full graph walk for directed adjacency
	// lists that do not maintain a reverse index. Callers must not mutate the
	// returned slice.
	InNeighbours(n string) []string

	// RemoveAllEdgesFrom removes all edges incident from n in O(degree) time
	// and returns the outgoing neighbour keys that were removed. For undirected
	// graphs the mirror entries are also removed. The per-pair edge state
	// (labels, properties, handle metadata, CREATE counters) is cleared for
	// every removed pair, exactly as a sequence of RemoveEdge calls would do.
	//
	// Callers that also need to roll back undo-log entries or emit WAL records
	// must capture the neighbours via OutNeighbours before calling this method;
	// RemoveAllEdgesFrom does not emit WAL records on its own.
	RemoveAllEdgesFrom(n string)

	// OutDegree returns the number of outgoing edges from n.
	OutDegree(n string) int

	// ResolveNodeID translates a user-facing node key to its internal NodeID,
	// returning ok=false when the node has not been interned yet.
	ResolveNodeID(n string) (graph.NodeID, bool)

	// ResolveNodeLabel translates an internal NodeID back to the user-facing
	// node key, returning ok=false when id is unknown.
	ResolveNodeLabel(id graph.NodeID) (string, bool)

	// WalkNodeIDs calls fn for every node currently interned in the graph.
	WalkNodeIDs(fn func(graph.NodeID) bool)
}

GraphMutator is the write surface exposed to Cypher write operators.

All methods accept the user-facing node key (string) used by the lpg.Graph[string,float64] instantiation. The graph is responsible for interning the value and returning the stable internal NodeID where applicable.

GraphMutator is NOT safe for concurrent use from multiple goroutines; each physical operator tree owns exactly one instance.

type HashJoin added in v0.3.1

type HashJoin struct {
	// contains filtered or unexported fields
}

HashJoin is a Volcano pipeline operator that performs an order-insensitive equi-join between two independent (uncorrelated) plans. It replaces a nested-loop Apply + equi-join Filter when the planner has proven the substitution is result-identical and order-safe.

HashJoin is NOT safe for concurrent use.

func NewHashJoin added in v0.3.1

func NewHashJoin(build, probe Operator, buildFn, probeFn KeyFn, buildOnLeft bool) *HashJoin

NewHashJoin creates a HashJoin.

  • build is the plan whose rows are fully materialised into the hash table.
  • probe is the plan streamed against the table.
  • buildFn / probeFn extract the join key from a build / probe row.
  • buildOnLeft selects the output column order (see HashJoin.buildOnLeft).

HashJoin takes ownership of both plans; callers must not use them afterwards.

func (*HashJoin) Close added in v0.3.1

func (op *HashJoin) Close() error

Close releases the hash table and closes both child plans.

func (*HashJoin) Init added in v0.3.1

func (op *HashJoin) Init(ctx context.Context) error

Init initialises both child plans and resets join state.

func (*HashJoin) Next added in v0.3.1

func (op *HashJoin) Next(out *Row) (bool, error)

Next advances the hash join.

func (*HashJoin) PlanChildren added in v0.11.0

func (op *HashJoin) PlanChildren() []Operator

PlanChildren reports the build side first, then the probe side: the build input is materialised into the hash table before a single probe row is read, which is the order that explains the join's cost.

func (*HashJoin) PlanDetail added in v0.11.0

func (op *HashJoin) PlanDetail() string

PlanDetail reports which side of the join was materialised into the hash table. The build side is the operator's cost centre and the reason one input order beats the other, and it is not visible from the operator name.

func (*HashJoin) WithByteBudget added in v0.7.0

func (op *HashJoin) WithByteBudget(maxBytes int64, estimateRow func(Row) int64) *HashJoin

WithByteBudget bounds the estimated retained size of the build table by maxBytes, returning ErrHashJoinMemoryExceeded when exceeded. The build table has no count cap, so this is the operator's memory bound (#1841). A non-positive maxBytes or nil estimateRow leaves it unbounded (prior behaviour). Returns op for chaining and must be called before Init.

type HashLookup added in v0.11.0

type HashLookup interface {
	// LookupAppend appends the NodeIDs matching the seek value to dst in
	// ascending order and returns the extended slice, draining the index's
	// posting list under its read lock without materialising a bitmap.
	// Returns ErrIndexTypeMismatch when the value kind is incompatible.
	LookupAppend(value expr.Value, dst []uint64) ([]uint64, error)
}

HashLookup is the minimal interface that NodeByIndexSeek requires. hash.Index[V] satisfies it for every supported V.

Exported so the engine's build path can name it when it chooses between the guarded and unguarded seek forms in one place (rmp #2423).

type IndexBuffer

type IndexBuffer struct {
	// contains filtered or unexported fields
}

IndexBuffer collects index.Change events produced by write operators during a single write transaction.

Call Enqueue for every graph mutation. At the transaction boundary:

  • Commit: fans changes to index.Manager.ApplyBatch then resets.
  • Rollback: discards changes without touching indexes.

IndexBuffer is NOT safe for concurrent use.

func (*IndexBuffer) Commit

func (b *IndexBuffer) Commit(mgr *index.Manager)

Commit applies all buffered changes to mgr via ApplyBatch, then resets the buffer. A nil mgr is safe: changes are discarded without panicking.

func (*IndexBuffer) Enqueue

func (b *IndexBuffer) Enqueue(c index.Change)

Enqueue appends c to the buffer.

func (*IndexBuffer) Len

func (b *IndexBuffer) Len() int

Len returns the number of changes currently buffered.

func (*IndexBuffer) Rollback

func (b *IndexBuffer) Rollback()

Rollback discards all buffered changes without applying them.

type IndexKindExec

type IndexKindExec uint8

IndexKindExec distinguishes hash vs. btree in the exec layer.

const (
	// ExecIndexHash creates a hash.Index[string].
	ExecIndexHash IndexKindExec = iota
	// ExecIndexBTree creates a btree.Index[string].
	ExecIndexBTree
)

type IndexNestedLoopJoin added in v0.11.0

type IndexNestedLoopJoin struct {
	// contains filtered or unexported fields
}

IndexNestedLoopJoin joins an outer arm against an indexed population by seeking the index once per outer row. It emits outer||inner rows in the nested loop's exact sequence.

IndexNestedLoopJoin is NOT safe for concurrent use.

func NewIndexNestedLoopJoin added in v0.11.0

func NewIndexNestedLoopJoin(outer, inner Operator, idx NumericPointLookup, outerKeyFn, innerKeyFn KeyFn) *IndexNestedLoopJoin

NewIndexNestedLoopJoin creates an IndexNestedLoopJoin.

  • outer is the probe arm; its rows drive the join and set the output's major order.
  • inner is the plain inner arm, used only for the non-numeric-key fallback.
  • idx is the numeric btree companion covering the inner arm's (label, property).
  • outerKeyFn / innerKeyFn evaluate the join key against an outer row and an inner-only row respectively.

func (*IndexNestedLoopJoin) Close added in v0.11.0

func (op *IndexNestedLoopJoin) Close() error

Close closes both arms. The inner arm is closed only if it was ever Init'd.

func (*IndexNestedLoopJoin) Init added in v0.11.0

func (op *IndexNestedLoopJoin) Init(ctx context.Context) error

Init initialises the outer arm. The inner arm is Init'd lazily, on the first row that needs the fallback: a query whose keys are all numeric never touches it, so it never pays the scan's set-up.

func (*IndexNestedLoopJoin) Next added in v0.11.0

func (op *IndexNestedLoopJoin) Next(out *Row) (bool, error)

Next emits the next outer||inner row.

func (*IndexNestedLoopJoin) PlanChildren added in v0.11.0

func (op *IndexNestedLoopJoin) PlanChildren() []Operator

PlanChildren reports the outer input that drives the seek, then the inner arm.

The order is the OPPOSITE of the hash joins' above, and for the same reason theirs is build-first: it is the order that explains the cost. This join materialises nothing — the outer arm drives it and each outer row is answered by an index seek, so the outer side is what the row count multiplies.

The inner arm is reported even though most queries never execute it: it serves only the fallback path (an integer key too large for float64 to represent exactly), and rendering it is what makes that path visible in a plan rather than a surprise in a profile.

func (*IndexNestedLoopJoin) WithProvenNumericCoverage added in v0.11.0

func (op *IndexNestedLoopJoin) WithProvenNumericCoverage(proven bool) *IndexNestedLoopJoin

WithProvenNumericCoverage records that every node the inner scan produces is known to carry a numeric value for the join property. The operator then answers a non-numeric key with no rows instead of scanning for them. It returns op for chaining.

Only a caller that has actually PROVED this may set it — the planner does, by comparing the numeric index's entry count against the scan's row count. Setting it without the proof would drop rows.

type IndexRangePart added in v0.11.0

type IndexRangePart struct {
	Index rangeLookup
	Lo    RangeBound
	Hi    RangeBound
}

IndexRangePart is one indexed conjunct's contribution to a composed intersection: the index to probe and the bounds to probe it over (#2134).

type Int64HashIndex

type Int64HashIndex struct {
	// contains filtered or unexported fields
}

Int64HashIndex adapts hash.Index[int64] to the HashLookup interface. It accepts only expr.IntegerValue seek keys.

func NewInt64HashIndex

func NewInt64HashIndex(idx interface {
	LookupAppend(value int64, dst []uint64) []uint64
}) *Int64HashIndex

NewInt64HashIndex constructs an Int64HashIndex.

func (*Int64HashIndex) LookupAppend added in v0.6.0

func (h *Int64HashIndex) LookupAppend(value expr.Value, dst []uint64) ([]uint64, error)

LookupAppend implements HashLookup.

type Int64RangeIndex

type Int64RangeIndex struct {
	// contains filtered or unexported fields
}

Int64RangeIndex adapts btree.Index[int64] to the [rangeLookup] interface. Nil bounds are treated as ±∞ using math.MinInt64 / math.MaxInt64.

func NewInt64RangeIndex

func NewInt64RangeIndex(idx interface {
	Range(lo, hi int64) *roaring64.Bitmap
}) *Int64RangeIndex

NewInt64RangeIndex constructs an Int64RangeIndex.

func (*Int64RangeIndex) RangeBitmap

func (r *Int64RangeIndex) RangeBitmap(lo, hi expr.Value) *roaring64.Bitmap

RangeBitmap implements [rangeLookup].

type IntersectAdjacencySource added in v0.11.0

type IntersectAdjacencySource func() (fwd, rev CSRAdjacency, midFilter, endFilter map[uint64]string)

IntersectAdjacencySource is AdjacencySource for the fused cyclic expand, which filters TWO legs and therefore needs two type filters keyed to the one adjacency it resolves.

func StaticIntersectAdjacency added in v0.11.0

func StaticIntersectAdjacency(fwd, rev CSRAdjacency, midFilter, endFilter map[uint64]string) IntersectAdjacencySource

StaticIntersectAdjacency is StaticAdjacency for an IntersectAdjacencySource, and carries the same warning: a production plan must not use it.

type KeyFn added in v0.3.1

type KeyFn func(row Row) (expr.Value, error)

KeyFn extracts the join-key value from a row. It returns the evaluated key (which may be expr.Null) or an error that halts the pipeline.

type LPGLabelSource

type LPGLabelSource struct {
	// contains filtered or unexported fields
}

LPGLabelSource is a concrete [labelResolver] built from an lpg label registry and a label index. It is the standard adapter for tests and production use.

func NewLPGLabelSource

func NewLPGLabelSource(reg *label.Index, lookupFn func(string) (uint32, bool)) *LPGLabelSource

NewLPGLabelSource constructs a LPGLabelSource. lookupFn should wrap lpg.LabelRegistry.Lookup, casting LabelID to uint32.

func (*LPGLabelSource) ResolveLabelBitmap

func (s *LPGLabelSource) ResolveLabelBitmap(name string) *roaring64.Bitmap

ResolveLabelBitmap implements [labelResolver].

type LabelCountScan added in v0.9.0

type LabelCountScan struct {
	// contains filtered or unexported fields
}

LabelCountScan is a Volcano leaf operator that computes a group-by-less count over a bare single-label node scan by reading the label's live-node count directly. It emits exactly one row with a single expr.IntegerValue column carrying that count.

LabelCountScan is NOT safe for concurrent use.

func NewLabelCountScan added in v0.9.0

func NewLabelCountScan(label string, src labelResolver) *LabelCountScan

NewLabelCountScan creates a LabelCountScan that counts the nodes carrying label via src.

func (*LabelCountScan) Close added in v0.9.0

func (op *LabelCountScan) Close() error

Close releases resources held by the operator. LabelCountScan holds none, so Close is a no-op and is safe to call whether or not Next was ever called.

func (*LabelCountScan) Init added in v0.9.0

func (op *LabelCountScan) Init(ctx context.Context) error

Init reads the label's live-node count once. It prefers the zero-alloc direct count when src supports it and otherwise falls back to the cardinality of the resolved bitmap — both yield the same value.

func (*LabelCountScan) Next added in v0.9.0

func (op *LabelCountScan) Next(out *Row) (bool, error)

Next emits the single count row on its first call and reports end-of-stream thereafter.

type LabelIntersectResolver added in v0.11.0

type LabelIntersectResolver interface {
	ResolveLabelsBitmap(names []string) *roaring64.Bitmap
}

LabelIntersectResolver resolves a CONJUNCTION of label names to the bitmap of NodeIDs carrying EVERY one of them — the set-at-a-time answer to a multi-label node pattern (#2133).

The names arrive in the order the caller wants them intersected, which is load-bearing rather than cosmetic: label.Index.Intersect clones the FIRST label's bitmap, so passing the smallest label first is measured 6.0× faster and 7.5× lighter than passing the largest (docs/design-bitmap-intersection.md §4). The planner sorts by exact ascending cardinality; this interface preserves that order rather than re-deriving it.

type Limit

type Limit struct {
	// contains filtered or unexported fields
}

Limit is a Volcano pipeline operator that forwards at most n rows from its child operator and then signals end-of-stream.

Limit is NOT safe for concurrent use.

func NewLimit

func NewLimit(child Operator, n int64) (*Limit, error)

NewLimit creates a Limit operator that passes at most n rows from child. n must be ≥ 0; a limit of 0 emits no rows.

func (*Limit) Close

func (op *Limit) Close() error

Close releases resources and closes the child operator.

func (*Limit) Init

func (op *Limit) Init(ctx context.Context) error

Init initialises the operator and resets the emission counter.

func (*Limit) Next

func (op *Limit) Next(out *Row) (bool, error)

Next forwards the next row from the child, unless the limit has been reached, in which case it returns (false, nil) immediately.

func (*Limit) PlanChildren added in v0.11.0

func (op *Limit) PlanChildren() []Operator

PlanChildren reports the input it truncates.

type MapEvalFn added in v0.9.0

type MapEvalFn func(row Row) (entries []PropEntry, nullKeys []string, err error)

MapEvalFn is a per-row evaluator for a whole property map used by a `SET x = {…}` / `SET x += {…}` operator. Unlike PropsEvalFn it also reports the keys whose value evaluated to null (nullKeys): SET-map semantics REMOVE such keys from the target (openCypher: `SET n += {k: null}` deletes k), whereas PropsEvalFn omits null entries because a null on CREATE is a no-op. entries carries the non-null (key, value) pairs; nullKeys the keys to delete.

type Merge

type Merge struct {
	// contains filtered or unexported fields
}

Merge implements MERGE semantics: match-or-create a pattern.

Merge is NOT safe for concurrent use.

func NewMerge

func NewMerge(
	nodeVar string,
	labels []string,
	properties string,
	onCreateStrs, onMatchStrs []string,
	searchFn MergeSearchFn,
	schema map[string]int,
	child Operator,
	mutator GraphMutator,
) (*Merge, error)

NewMerge creates a Merge operator.

nodeVar is the variable bound to the merged node. labels and properties are the node-pattern components used when creating a new node. onCreateStrs and onMatchStrs are opaque SET-item strings from the IR translator. searchFn executes the read side of the match. schema maps variable names to column indices. mutator is the graph write surface.

func (*Merge) Close

func (op *Merge) Close() error

Close closes the child operator.

func (*Merge) Init

func (op *Merge) Init(ctx context.Context) error

Init initialises the operator: executes the search plan, then dispatches to the ON MATCH or ON CREATE branch depending on whether the search returned any rows.

The first Merge.Init (or CreateNode.Init) in the process also seeds [globalNodeCounter] past the largest synthetic key already interned in op.mutator, so that the keys minted by [Merge.freshNodeKey] in this process cannot collide with __cx_merge_<hex> keys persisted by an earlier process and replayed during WAL / snapshot recovery. Without this a one-process-per-command consumer mints __cx_merge_1 on every command and the second MERGE silently overwrites the first node. The seed is gated by [globalNodeCounterSeededOnce] so the O(N) scan runs at most once per process regardless of how many CreateNode / Merge operators are built.

func (*Merge) Next

func (op *Merge) Next(out *Row) (bool, error)

Next emits one row: either a matched row (ON MATCH) or the created row (ON CREATE), each emitted exactly once.

func (*Merge) PlanChildren added in v0.11.0

func (op *Merge) PlanChildren() []Operator

PlanChildren reports the input whose rows drive the merge.

func (*Merge) WithActionEvals added in v0.8.0

func (op *Merge) WithActionEvals(onCreate, onMatch map[string]ValueEvalFn) *Merge

WithActionEvals attaches per-row RHS evaluators for ON CREATE / ON MATCH property-set items whose right-hand side is a non-literal expression (keyed by MergeActionEvalKey). Without these, a self-referential assignment such as `ON MATCH SET n.num = n.num + 1` fails to parse as a literal and is silently dropped (#1965). Returns op for chaining.

func (*Merge) WithConstraints

func (op *Merge) WithConstraints(reg *ConstraintRegistry, mgr *index.Manager) *Merge

WithConstraints attaches a ConstraintRegistry and index.Manager for pre-write enforcement in ON CREATE and ON MATCH actions. Both must be non-nil. Returns op for chaining.

func (*Merge) WithLabelSource added in v0.11.0

func (op *Merge) WithLabelSource(src MergeLabelSource) *Merge

WithLabelSource attaches the label posting-list source that narrows the row-aware merge search to the nodes carrying a pattern label, instead of every interned node (#2217). It is the access path that makes the UNWIND-MERGE bulk-ingest idiom scale with the label's population rather than with the size of the whole graph.

src may be nil, in which case the search keeps the full walk. Returns op for chaining.

func (*Merge) WithParams

func (op *Merge) WithParams(params map[string]expr.Value) (*Merge, error)

WithParams re-parses the property map with the supplied query parameters for $name substitution. Returns op for chaining.

func (*Merge) WithPropsEvalFn

func (op *Merge) WithPropsEvalFn(fn PropsEvalFn) *Merge

WithPropsEvalFn attaches a per-row property evaluator. When fn is non-nil the operator re-evaluates the MERGE node-pattern property map against each driving row and uses the merged (literal ∪ dynamic) property set both as the search predicate and as the ON CREATE node-property writes. Required for MERGE patterns whose inline property map contains variable references such as `MERGE (p:Person {login: prop.login})` after an UNWIND.

Returns op for chaining.

func (*Merge) WithSetAllActions added in v0.9.0

func (op *Merge) WithSetAllActions(onCreate, onMatch []MergeSetAllAction) *Merge

WithSetAllActions attaches whole-entity ON CREATE / ON MATCH SET actions (`SET n = <expr>` / `SET n += <expr>`), which the per-property action path cannot represent. Each is evaluated per row and applied via [applyWholeEntityValueToNode] (#2031). Returns op for chaining.

type MergeLabelSource added in v0.11.0

type MergeLabelSource interface {
	// ResolveLabelBitmap returns the NodeIDs carrying name, or an empty bitmap
	// when the label is unknown. An empty bitmap is authoritative: no node can
	// carry a label that was never interned, so the MERGE correctly finds no
	// match and fires ON CREATE.
	ResolveLabelBitmap(name string) *roaring64.Bitmap
}

MergeLabelSource narrows the MERGE match phase to the nodes that carry a pattern label. lpg's label index satisfies it, and its bitmap reflects uncommitted writes made by the enclosing transaction — verified for a same-transaction CREATE and for a label added by SET — so driving from it cannot miss a node the caller has just written and thereby create a duplicate.

type MergePattern added in v0.7.0

type MergePattern struct {
	// contains filtered or unexported fields
}

MergePattern matches-or-creates a chain of one or more relationship hops in which at least one node is not already bound. See the package doc above for the full algorithm.

func NewMergePattern added in v0.7.0

func NewMergePattern(child Operator, mutator GraphMutator) *MergePattern

NewMergePattern creates an empty MergePattern; call AddBoundNode/ AddFreshNode once per chain position (in order) and AddHop once per relationship (len(hops) == positions added - 1), then WithActions.

func (*MergePattern) AddBoundNode added in v0.7.0

func (op *MergePattern) AddBoundNode(varName string, col int) *MergePattern

AddBoundNode appends a chain position whose value is already bound by an earlier clause, read from input-row column col.

func (*MergePattern) AddFreshNode added in v0.7.0

func (op *MergePattern) AddFreshNode(varName string, labels []string, propsRaw string, outCol int) *MergePattern

AddFreshNode appends a chain position this MERGE clause introduces fresh. outCol is the output-row column that receives the matched-or-created NodeID as an expr.IntegerValue; pass -1 if the variable is never referenced downstream (still searched/created, just not projected).

func (*MergePattern) AddHop added in v0.7.0

func (op *MergePattern) AddHop(relVar string, relCol int, relType, relPropsRaw string, undirected, reversed bool) *MergePattern

AddHop appends the relationship connecting the two most-recently-added chain positions. relCol is the output-row column for a named relationship variable, or -1 for an anonymous one. reversed is true for an incoming `<-` pattern, where the edge is stored from the next chain position back to the current one.

func (*MergePattern) Close added in v0.7.0

func (op *MergePattern) Close() error

Close closes the child operator.

func (*MergePattern) Init added in v0.7.0

func (op *MergePattern) Init(ctx context.Context) error

Init initialises the operator and its child. The first MergePattern.Init (or CreateNode.Init / Merge.Init) in the process also seeds [globalNodeCounter], exactly as those operators do, so fresh-node keys minted here cannot collide with __cx_merge_<hex> keys replayed from an earlier process during WAL / snapshot recovery.

func (*MergePattern) MarkHopRefsPatternNode added in v0.9.0

func (op *MergePattern) MarkHopRefsPatternNode() *MergePattern

MarkHopRefsPatternNode flags the most-recently-added hop as having an inline relationship property map that references an earlier same-pattern node, so its properties are evaluated per binding rather than via the once-per-row precomputation (#2024). Returns op for chaining.

func (*MergePattern) Next added in v0.7.0

func (op *MergePattern) Next(out *Row) (bool, error)

Next drives one search-or-create cycle per input row, buffering multiple matches (Merge5-style multiplicity, generalised: a joint pattern can have more than one satisfying binding) so each is emitted as its own row. Mirrors Merge.Next's buffering shape, including firing exactly once against an empty driving row when MergePattern is the leading clause (no preceding MATCH/WITH at all).

func (*MergePattern) PlanChildren added in v0.11.0

func (op *MergePattern) PlanChildren() []Operator

PlanChildren reports the input whose rows drive the pattern merge.

func (*MergePattern) WithActionEvals added in v0.8.0

func (op *MergePattern) WithActionEvals(onCreate, onMatch map[string]ValueEvalFn) *MergePattern

WithActionEvals attaches per-row RHS evaluators for ON CREATE / ON MATCH property-set items whose right-hand side is a non-literal expression (keyed by MergeActionEvalKey on the target variable and property key). Without these, an expression such as `ON MATCH SET n.num = n.num + 1` fails to parse as a literal and is dropped (#1965). Returns op for chaining.

func (*MergePattern) WithActions added in v0.7.0

func (op *MergePattern) WithActions(onCreateStrs, onMatchStrs []string) (*MergePattern, error)

WithActions parses and attaches ON CREATE / ON MATCH SET items (the same opaque-string representation Merge uses). Each item may target any variable in the chain (a fresh node, a bound node, or a hop's relationship variable) — dispatched at apply time by the parsed action's target name.

func (*MergePattern) WithConstraints added in v0.7.0

func (op *MergePattern) WithConstraints(reg *ConstraintRegistry, mgr *index.Manager) *MergePattern

WithConstraints attaches a ConstraintRegistry and index.Manager for pre-write enforcement of every fresh node's created properties and of every ON CREATE / ON MATCH node property action — mirroring Merge.WithConstraints and CreateNode.WithConstraints. Both must be non-nil. Returns op for chaining.

func (*MergePattern) WithHopPropsEvalFn added in v0.9.0

func (op *MergePattern) WithHopPropsEvalFn(fn PropsEvalFn) *MergePattern

WithHopPropsEvalFn attaches a per-row property evaluator to the most-recently-added hop (mirroring MergePattern.WithNodePropsEvalFn). Used when that hop's relationship property map contains a non-literal expression (a variable reference, property access, or arithmetic — e.g. `(a)-[:R {kind: row.pk}]->(b)`) that [parsePropLiteral] cannot resolve at plan-construction time. The dynamic entries are merged with the map's literal entries at both search and create time (see [MergePattern.effectiveHopProps]), taking precedence on key collision. Without this, a hop's row-driven relationship property would be silently dropped from both the search predicate and the created edge — the same defect class this operator exists to eliminate, previously rejected at build time on the compound-pattern path.

func (*MergePattern) WithLabelSource added in v0.11.0

func (op *MergePattern) WithLabelSource(src MergeLabelSource) *MergePattern

WithLabelSource attaches the label posting-list source that narrows the anchor-node search to the nodes carrying a pattern label, instead of every interned node (#2217). src may be nil, which keeps the full walk. Returns op for chaining.

func (*MergePattern) WithNodePropsEvalFn added in v0.7.0

func (op *MergePattern) WithNodePropsEvalFn(fn PropsEvalFn) *MergePattern

WithNodePropsEvalFn attaches a per-row property evaluator to the most-recently-added fresh node (mirroring [CreateNode.WithPropsEvalFn| CreateNode.WithPropsEvalFn]). Used when that node's property map contains non-literal expressions (parameters, variable references) that [parsePropLiteral] cannot resolve at plan-construction time; the dynamic entries are merged with the map's literal entries at both search and create time (see [mergeProps]), taking precedence on key collision. Without this, a node position's parameterised properties would be silently dropped from both the search predicate and the created node — the same defect class this operator exists to eliminate.

func (*MergePattern) WithParams added in v0.7.0

func (op *MergePattern) WithParams(params map[string]expr.Value) (*MergePattern, error)

WithParams attaches query parameters for $name substitution in every node's and every hop's inline property map, re-parsing each non-empty raw map with parameter references resolved to concrete literal values. Mirrors CreateNode.WithParams: resolving parameters once here, at build time, is cheaper than a per-row PropsEvalFn and is correct because parameter values are constant for the whole query execution. Must be called after every AddBoundNode/AddFreshNode/AddHop call it should cover.

func (*MergePattern) WithSetAllActions added in v0.9.0

func (op *MergePattern) WithSetAllActions(onCreate, onMatch []MergeSetAllAction) *MergePattern

WithSetAllActions attaches whole-entity ON CREATE / ON MATCH SET actions (`SET n = <expr>` / `SET n += <expr>`) on a chain node variable, which the per-property action path cannot represent. Each is evaluated per row and applied via [applyWholeEntityValueToNode] (#2031). Returns op for chaining.

type MergeRelAction

type MergeRelAction struct {
	// contains filtered or unexported fields
}

MergeRelAction is a pre-parsed `SET <relVar>.<key> = <value>` item, or a whole-entity REPLACE sentinel (#1687).

Three shapes:

  • Single-property write: key != "", value is the literal string.
  • Entity-copy: key == "", value == "<sourceVar>". When replace is true the edge's properties absent from the source entity are cleared first.
  • Replace-map sentinel: key == "", value == "", replace == true. retainKeys lists the RHS map keys; the edge's properties absent from retainKeys are cleared. The sentinel is immediately followed by the per-key write actions for the map, so the clear precedes the writes. An empty (non-nil) retainKeys clears every property (`SET r = {}`).

func MergeRelActionFromKV

func MergeRelActionFromKV(key, value string) MergeRelAction

MergeRelActionFromKV constructs a MergeRelationship ON CREATE / ON MATCH action from a (key, value) pair. value is the opaque literal string as it appears in the source query (e.g. `'foo'` or `42`).

func MergeRelActionReplaceFromKV added in v0.6.0

func MergeRelActionReplaceFromKV(key, value string, replace bool, retainKeys []string) MergeRelAction

MergeRelActionReplaceFromKV constructs a MergeRelationship ON CREATE / ON MATCH action carrying the whole-entity REPLACE marker (#1687). When replace is true the action is either a replace-map sentinel (key == "", value == "", retainKeys lists the RHS keys to keep) or an entity-copy replace (key == "", value == "<sourceVar>", retainKeys nil → retain the source's live keys). retainKeys is copied defensively so the caller may reuse its slice. When replace is false this is equivalent to MergeRelActionFromKV.

type MergeRelationship

type MergeRelationship struct {
	// contains filtered or unexported fields
}

MergeRelationship matches-or-creates a single-hop directed relationship between two already-bound endpoint columns. ON CREATE / ON MATCH actions targeting the relationship variable are applied to the matched-or-created edge.

MergeRelationship is NOT safe for concurrent use.

func NewMergeRelationship

func NewMergeRelationship(child Operator, srcCol, dstCol int, relType string, mutator GraphMutator) *MergeRelationship

NewMergeRelationship constructs a MergeRelationship operator.

  • child is the upstream plan providing rows with the bound endpoints.
  • srcCol / dstCol are the column indices that hold the src / dst NodeID.
  • relType is the relationship type label (single label only).
  • mutator is the graph write surface.

func (*MergeRelationship) Close

func (op *MergeRelationship) Close() error

Close closes the child operator.

func (*MergeRelationship) Init

func (op *MergeRelationship) Init(ctx context.Context) error

Init initialises the operator and its child.

func (*MergeRelationship) Next

func (op *MergeRelationship) Next(out *Row) (bool, error)

Next emits the next input row, ensuring that the (src)-[:relType]->(dst) edge exists in the graph (either pre-existing or newly created). When an existing edge has CREATE-multiplicity N > 1 the operator emits N rows for the same upstream tuple (Merge5 [3]).

func (*MergeRelationship) PlanChildren added in v0.11.0

func (op *MergeRelationship) PlanChildren() []Operator

PlanChildren reports the input whose rows drive the relationship merge.

func (*MergeRelationship) WithActionEvals added in v0.8.0

func (op *MergeRelationship) WithActionEvals(onCreate, onMatch map[string]ValueEvalFn) *MergeRelationship

WithActionEvals attaches per-row RHS evaluators for ON CREATE / ON MATCH property-set items whose right-hand side is a non-literal expression (keyed by MergeActionEvalKey on the relationship variable and property key). Without these, `ON MATCH SET r.n = r.n + 1` fails to parse as a literal and, on this fast path, surfaced a parse error instead of incrementing the edge property (#1965). Returns op for chaining.

func (*MergeRelationship) WithOnCreate

func (op *MergeRelationship) WithOnCreate(relVar string, actions []MergeRelAction) *MergeRelationship

WithOnCreate registers ON CREATE SET actions to apply when the edge is newly created. Each action is `<relVar>.<key> = <value>`; the caller has already verified that every action targets the relationship variable bound by this operator.

func (*MergeRelationship) WithOnMatch

func (op *MergeRelationship) WithOnMatch(relVar string, actions []MergeRelAction) *MergeRelationship

WithOnMatch registers ON MATCH SET actions to apply when the edge already exists.

func (*MergeRelationship) WithParams added in v0.9.0

func (op *MergeRelationship) WithParams(params map[string]expr.Value) (*MergeRelationship, error)

WithParams attaches query parameters for $name substitution in the inline relationship property map, re-parsing the raw map with parameter references resolved to concrete literal values. Mirrors CreateNode.WithParams: resolving parameters once here, at build time, is cheaper than a per-row evaluator and is correct because parameter values are constant for the whole query execution. Without it a parameterised inline property such as `MERGE (a)-[r:T {kind: $pk}]->(b)` is silently dropped, since the literal-only parser skips $param references (they are deferred to a resolver). Returns op for chaining.

func (*MergeRelationship) WithRelColumn

func (op *MergeRelationship) WithRelColumn(relCol int) *MergeRelationship

WithRelColumn registers the output-row column index that will carry the matched / created edge ID. When set (relCol >= 0) MergeRelationship extends the row with an IntegerValue(edgeID) at the column so downstream operators (RETURN r, count(r), …) see the bound relationship.

func (*MergeRelationship) WithRelProperties

func (op *MergeRelationship) WithRelProperties(propsRaw string) *MergeRelationship

WithRelProperties registers an inline relationship property predicate (e.g. `{name: 'r2'}` from `MERGE (a)-[r:T {name: 'r2'}]->(b)`). When set, the operator filters the existing-edge search by the predicate AND writes the listed properties when a new edge is created. Pass an empty string to clear.

func (*MergeRelationship) WithRelPropsEvalFn added in v0.9.0

func (op *MergeRelationship) WithRelPropsEvalFn(fn PropsEvalFn) *MergeRelationship

WithRelPropsEvalFn attaches a per-row evaluator for the inline relationship property map when it contains a non-literal value (a variable reference, property access, or arithmetic expression — e.g. `MERGE (a)-[r:T {kind: row.pk}]->(b)`). The merged (literal ∪ dynamic) property set drives both the existing-edge search predicate and the created edge's properties, exactly as the node Merge path does via [mergeProps]. Without it the literal-only parser drops the non-literal entry, so the property is neither searched on nor written — the created edge stores null (fail-silent Consistency defect). Pass nil to clear. Returns op for chaining.

func (*MergeRelationship) WithSchema

func (op *MergeRelationship) WithSchema(schema map[string]int) *MergeRelationship

WithSchema attaches the upstream variable-to-column mapping so entity-copy actions (`SET r = a`) can resolve the source variable from the row at write time.

func (*MergeRelationship) WithUndirected

func (op *MergeRelationship) WithUndirected(u bool) *MergeRelationship

WithUndirected toggles the undirected-search behaviour. When true, the match phase probes both (src, dst) and (dst, src) directions before falling through to the edge-create path, matching the openCypher semantics of `MERGE (a)-[r:T]-(b)` (Merge5 [13]).

type MergeSearchFn

type MergeSearchFn func(ctx context.Context) ([]Row, error)

MergeSearchFn is a function that executes the search sub-plan for the MERGE pattern and returns the matching rows. An empty slice means no match.

func NewMergeSearchFnFromPattern

func NewMergeSearchFnFromPattern(
	labels []string,
	propertiesRaw string,
	params map[string]expr.Value,
	mutator GraphMutator,
	labelSrc MergeLabelSource,
) (MergeSearchFn, error)

NewMergeSearchFnFromPattern returns a MergeSearchFn that finds every node in mutator whose label set contains every label in labels and whose property bag is equal to every (key, value) parsed from propertiesRaw.

labels is the slice of pattern labels (may be empty when the pattern is e.g. `(n {key: v})`). propertiesRaw is the opaque literal-map string surfaced by the IR (e.g. `{name: "Alice", age: 30}`); it may be empty. params binds `$name` references in propertiesRaw to query parameters; when empty the parser ignores parameter substitution.

The function returned by NewMergeSearchFnFromPattern enumerates candidate nodes, resolves the label and property bag, and admits the node iff every label and every property matches. When the pattern carries at least one label and labelSrc is non-nil the candidates come from the smallest matching label's posting list, so cost tracks that label's population rather than the whole graph; otherwise every interned node is examined. See [walkMergeCandidates].

labelSrc may be nil, in which case the search falls back to the full walk.

type MergeSetAllAction added in v0.9.0

type MergeSetAllAction struct {
	Eval      ExprValueEvalFn
	TargetVar string
	IsReplace bool
}

MergeSetAllAction is a whole-entity ON CREATE / ON MATCH SET action (`SET n = <expr>` / `SET n += <expr>`) evaluated per row. TargetVar names the node variable the action writes to; IsReplace selects `=` (true, replace all) vs `+=` (false, merge) semantics; Eval evaluates the right-hand side against the merged/created row.

type NodeByIndexRangeScan

type NodeByIndexRangeScan struct {
	// contains filtered or unexported fields
}

NodeByIndexRangeScan is a Volcano leaf operator that scans a B+tree index over a half-open, closed, or open interval. Each Row has a single column: expr.IntegerValue(nodeID).

NodeByIndexRangeScan is NOT safe for concurrent use.

func NewNodeByIndexIntersectionScan added in v0.11.0

func NewNodeByIndexIntersectionScan(idx rangeLookup, lo, hi RangeBound, parts []IndexRangePart) *NodeByIndexRangeScan

NewNodeByIndexIntersectionScan composes SEVERAL single-property indexes into one access path by intersecting their range bitmaps (#2134).

This is what lets `WHERE n.a > 1 AND n.b < 9` be answered from two ordinary single-property indexes — the answer Memgraph needs a dedicated COMPOSITE index type for. No new index type and no new statistic are involved: RangeBitmap already returns a Roaring bitmap, so the conjunction is a set operation.

Superset discipline (design §8)

Unlike the label intersection, each part is only a SUPERSET of its conjunct's true matches — the operator emits the inclusive [lo, hi] interval and cannot enforce an open bound (see the type doc, #F-EXEC1). Intersection PRESERVES that: if Bᵃ ⊇ Aᵃ and Bᵇ ⊇ Aᵇ then Bᵃ ∩ Bᵇ ⊇ Aᵃ ∩ Aᵇ. So the composed scan is a sound superset — and the caller's residual Filter remains MANDATORY, exactly as it is for a single range scan.

parts must hold at least one entry beyond the primary; the primary's own bounds are given by lo/hi. Parts are ANDed in the order supplied, which the planner orders by ascending exact cardinality so the cheapest bitmap is materialised first.

func NewNodeByIndexRangeScan

func NewNodeByIndexRangeScan(idx rangeLookup, lo, hi RangeBound) *NodeByIndexRangeScan

NewNodeByIndexRangeScan creates a NodeByIndexRangeScan.

It carries NO label restriction; a rewrite that replaced a labelled scan leaf must use NodeByIndexRangeScan.RestrictToLabel. See the labelBM field.

func (*NodeByIndexRangeScan) Close

func (op *NodeByIndexRangeScan) Close() error

Close releases resources.

func (*NodeByIndexRangeScan) Init

func (op *NodeByIndexRangeScan) Init(ctx context.Context) error

Init performs the range lookup — intersecting the additional indexed conjuncts when this is a composed scan (#2134) — and initialises the bitmap iterator.

func (*NodeByIndexRangeScan) Next

func (op *NodeByIndexRangeScan) Next(out *Row) (bool, error)

Next emits the next NodeID in the inclusive [lo, hi] superset. Returns (false, nil) at end-of-stream. Exact open/closed enforcement is the caller's residual-filter responsibility (see the type doc); this operator emits every NodeID the index's range bitmap contains.

func (*NodeByIndexRangeScan) PlanDetail added in v0.11.0

func (op *NodeByIndexRangeScan) PlanDetail() string

PlanDetail reports the bounds of the range this scan walks, so a reader can see whether the seek is a point lookup, a half-open range, or effectively a full index walk.

func (*NodeByIndexRangeScan) RestrictToLabel added in v0.11.0

func (op *NodeByIndexRangeScan) RestrictToLabel(resolve func() *roaring64.Bitmap) *NodeByIndexRangeScan

RestrictToLabel makes this scan intersect its candidates with the label bitmap resolve returns, and returns op so a builder can chain it onto a constructor.

resolve is called once per Init, so it observes the reading transaction's own snapshot rather than a bitmap captured at plan time. A nil resolve is a no-op, which keeps the unlabelled shape expressible without a second constructor.

type NodeByIndexSeek

type NodeByIndexSeek struct {
	// contains filtered or unexported fields
}

NodeByIndexSeek is a Volcano leaf operator that performs an equality lookup on a property hash index. Each Row has a single column: expr.IntegerValue(nodeID).

NodeByIndexSeek is NOT safe for concurrent use.

func NewNodeByIndexSeek

func NewNodeByIndexSeek(idx HashLookup, seekValue expr.Value) *NodeByIndexSeek

NewNodeByIndexSeek creates a NodeByIndexSeek that looks up seekValue in idx.

It carries NO residual predicate, so it is correct only where the index's candidates need no further qualification. A rewrite that dropped a label must use NewNodeByIndexSeekAdmitting; see [NodeByIndexSeek.admit].

func NewNodeByIndexSeekAdmitting added in v0.11.0

func NewNodeByIndexSeekAdmitting(idx HashLookup, seekValue expr.Value, admit func(nodeID uint64) bool) *NodeByIndexSeek

NewNodeByIndexSeekAdmitting is NewNodeByIndexSeek with a residual predicate applied to every candidate the index returns — the label check a rewrite over a labelled scan leaf owes (rmp #2423).

admit must be non-nil; a caller with nothing to verify uses NewNodeByIndexSeek so the distinction stays visible at the call site rather than hiding behind a nil.

func (*NodeByIndexSeek) Close

func (op *NodeByIndexSeek) Close() error

Close releases resources.

func (*NodeByIndexSeek) Init

func (op *NodeByIndexSeek) Init(ctx context.Context) error

Init performs the index lookup, draining the matching NodeIDs into the operator's reused buffer. The dominant singleton/small posting list fits the inline idbuf, so a seek allocates nothing after the buffer is established.

func (*NodeByIndexSeek) Next

func (op *NodeByIndexSeek) Next(out *Row) (bool, error)

Next emits the next matching NodeID. Returns (false, nil) at end-of-stream.

func (*NodeByIndexSeek) PlanDetail added in v0.11.0

func (op *NodeByIndexSeek) PlanDetail() string

PlanDetail reports the value this seek looks up, which is the whole point of the access path: an index seek is only as selective as its key.

type NodeByIndexSeekSet added in v0.11.0

type NodeByIndexSeekSet struct {
	// contains filtered or unexported fields
}

NodeByIndexSeekSet is a Volcano leaf operator that performs an equality lookup on a property hash index for each of several keys, emitting each matching NodeID exactly once. Each Row has a single column: expr.IntegerValue(nodeID).

func NewNodeByIndexSeekSet added in v0.11.0

func NewNodeByIndexSeekSet(idx HashLookup, keys []expr.Value, budget uint64) *NodeByIndexSeekSet

NewNodeByIndexSeekSet creates an operator that looks up every key in idx.

Duplicate keys in keys are harmless — they are probed once. A budget of 0 disables the over-budget check; any other value caps the merged posting count, above which Init reports ErrSeekSetOverBudget.

func (*NodeByIndexSeekSet) Admitting added in v0.11.0

func (op *NodeByIndexSeekSet) Admitting(admit func(nodeID uint64) bool) *NodeByIndexSeekSet

Admitting installs the residual predicate every candidate must pass, and returns op so a builder can chain it onto the constructor. A nil admit is a no-op. See the admit field (rmp #2423).

func (*NodeByIndexSeekSet) Close added in v0.11.0

func (op *NodeByIndexSeekSet) Close() error

Close releases the operator. The merged id run is retained for reuse across Init calls, matching NodeByIndexSeek.

func (*NodeByIndexSeekSet) Init added in v0.11.0

func (op *NodeByIndexSeekSet) Init(ctx context.Context) error

Init probes the index once per distinct key and merges the results into one ascending, duplicate-free run.

A key whose type the index cannot serve is SKIPPED rather than failing the query. That is a correctness requirement, not leniency: openCypher equality across type groups is FALSE, so a key that cannot be in this index matches nothing, and contributing nothing is the right answer. Failing instead would turn `WHERE n.name IN ['a', 7]` into an error where the specification asks for the rows matching 'a'.

func (*NodeByIndexSeekSet) Next added in v0.11.0

func (op *NodeByIndexSeekSet) Next(out *Row) (bool, error)

Next emits the next matching NodeID. Returns (false, nil) at end-of-stream.

type NodeByLabelScan

type NodeByLabelScan struct {
	// contains filtered or unexported fields
}

NodeByLabelScan is a Volcano leaf operator that emits one Row per NodeID carrying the named label. Each Row has a single column: expr.IntegerValue(nodeID).

NodeByLabelScan is NOT safe for concurrent use.

func NewNodeByLabelIntersectionScan added in v0.11.0

func NewNodeByLabelIntersectionScan(labels []string, src LabelIntersectResolver) *NodeByLabelScan

NewNodeByLabelIntersectionScan creates a scan over the INTERSECTION of labels — the set-at-a-time answer to `MATCH (n:A:B)` (#2133).

Only the bitmap Init resolves differs from the single-label form: Next, the columnar FillChunk path, the exact rowCountHint and Close all operate on whatever bitmap the scan holds, so the conjunction inherits the zero-alloc contract and the columnar fast path unchanged.

labels must be ordered as the caller wants them intersected (smallest first — see LabelIntersectResolver) and must contain at least two entries; a single-label conjunction is the plain scan and should use NewNodeByLabelScan.

func NewNodeByLabelScan

func NewNodeByLabelScan(labelName string, src labelResolver) *NodeByLabelScan

NewNodeByLabelScan creates a NodeByLabelScan for the given label.

func (*NodeByLabelScan) Close

func (op *NodeByLabelScan) Close() error

Close releases resources held by the operator.

func (*NodeByLabelScan) FillChunk added in v0.9.0

func (op *NodeByLabelScan) FillChunk(dst *Chunk, maxRows int) (int, error)

FillChunk appends up to maxRows more matching NodeIDs, as unboxed int64, into column 0 of dst and returns the number appended (0 at end-of-stream). It is the column-major counterpart of NodeByLabelScan.Next: the SAME bitmap iterator in the SAME order, but written to a typed column with no per-row heap box. Only one of Next/FillChunk drives a given query, so sharing op.iter/op.count between them is sound. It honours context cancellation. It implements ChunkProducer.

func (*NodeByLabelScan) Init

func (op *NodeByLabelScan) Init(ctx context.Context) error

Init resolves the label (or, for the conjunction form, the intersection of the labels) to a bitmap and initialises the iterator.

func (*NodeByLabelScan) NewOutputChunk added in v0.9.0

func (op *NodeByLabelScan) NewOutputChunk(capacity int) *Chunk

NewOutputChunk returns a Chunk with a single static integer column that NodeByLabelScan fills with unboxed NodeIDs. It implements ChunkProducer (#1704 P3): a columnar-aware parent drains the scan column-major, avoiding the per-row expr.Value box NodeByLabelScan.Next pays.

func (*NodeByLabelScan) Next

func (op *NodeByLabelScan) Next(out *Row) (bool, error)

Next emits the next matching NodeID. Returns (false, nil) at end-of-stream.

func (*NodeByLabelScan) PlanDetail added in v0.11.0

func (op *NodeByLabelScan) PlanDetail() string

PlanDetail reports the label this scan iterates, or — for the multi-label conjunction form (#2133) — the intersected labels in the order they are ANDed, which is the order the planner chose by ascending cardinality and is therefore part of the physical decision a reader needs to see.

type NodeIDColumnProducer added in v0.9.0

type NodeIDColumnProducer interface {
	ChunkProducer
	// contains filtered or unexported methods
}

NodeIDColumnProducer is a ChunkProducer whose output chunk carries, at every column that corresponds to a bound node variable, that node's raw int64 NodeID (unboxed) — the "scan row shape". The columnar projection's chunk-input fast path (#1704 P3) reads NodeIDs directly from such a producer via Chunk.Int64. AllNodesScan, NodeByLabelScan, and ColumnarFilter (a passthrough over a scan) implement it; ColumnarProject deliberately does NOT — it emits projected values, not raw NodeIDs, so a projection over a projection takes the boxed row path. The marker method is unexported, so the interface is sealed to this package: a consumer in another package can assert against it but cannot forge a non-scan-shaped producer into it.

type NumericPointLookup added in v0.11.0

type NumericPointLookup interface {
	LookupAppend(value float64, dst []uint64) []uint64
}

NumericPointLookup is the minimal capability this operator needs from the numeric btree companion: an allocation-free ascending point lookup.

It is deliberately narrower than [rangeLookup], whose RangeBitmap allocates a roaring bitmap per call. This operator performs one lookup per OUTER ROW, so a per-call allocation would be charged B times; LookupAppend reuses the caller's buffer instead. btree.Index[float64] satisfies it directly.

type Operator

type Operator interface {
	// Init initialises the operator and its children. ctx is stored for later
	// use in Next; implementations must not begin producing rows in Init.
	Init(ctx context.Context) error

	// Next advances the operator by one row, writing the result into out.
	// It returns (true, nil) if a row was written, (false, nil) at end-of-stream,
	// or (false, err) on error. After returning (false, _), Next must not be
	// called again.
	//
	// Implementations check ctx.Done() on every call. Long-running loops check
	// ctx.Done() every 4096 iterations.
	Next(out *Row) (bool, error)

	// Close releases all resources held by this operator (open file handles,
	// memory, goroutines). It must be called exactly once by the pipeline
	// driver, even when Next returned an error.
	Close() error
}

Operator is the core abstraction of the Volcano iterator model. Every node in a physical query plan implements this interface.

Lifecycle

  1. [Init] is called exactly once before the first call to [Next].
  2. [Next] is called repeatedly until it returns (false, nil) or an error.
  3. [Close] is called exactly once, regardless of whether [Next] returned an error. Implementations must release all resources in [Close].

Cancellation

Every [Next] implementation must check ctx.Done() at the top of the call. For long-running inner loops that produce more than 4096 tuples without returning, check ctx.Done() every 4096 iterations.

Concurrency

An Operator instance is NOT safe for concurrent use. Each goroutine in a parallel pipeline segment owns its own operator tree.

type OptionalApply

type OptionalApply struct {
	// contains filtered or unexported fields
}

OptionalApply is the left-outer variant of CorrelatedApply. For every outer row, the inner pipeline is driven exactly like CorrelatedApply; when the inner pipeline produces zero rows for a given outer row, OptionalApply emits a single NULL-extended row whose width equals the configured padded width, holding the outer columns followed by NULL placeholders for the inner-introduced columns.

OptionalApply is NOT safe for concurrent use.

func NewOptionalApply

func NewOptionalApply(outer, inner Operator, arg *Argument, paddedWidth int) *OptionalApply

NewOptionalApply creates an OptionalApply operator.

  • outer is the left (driving) plan.
  • inner is the right (sub) plan whose leftmost leaf is the provided arg.
  • arg is the Argument node at the inner leaf; OptionalApply seeds it before each inner Init call.
  • paddedWidth is the total width of an output row, i.e. outerWidth plus the number of columns the inner pipeline introduces. When the inner pipeline emits zero rows for an outer, OptionalApply emits a row of this width whose first outerWidth columns are the outer row and whose trailing columns are expr.Null.

OptionalApply takes ownership of both plans.

func (*OptionalApply) Close

func (op *OptionalApply) Close() error

Close releases resources and closes both the outer and inner plans.

func (*OptionalApply) Init

func (op *OptionalApply) Init(ctx context.Context) error

Init initialises both the outer plan and stores ctx for subsequent Next calls.

func (*OptionalApply) Next

func (op *OptionalApply) Next(out *Row) (bool, error)

Next emits the next output row. The semantics are:

  • For each outer row, drain the inner pipeline.
  • If the inner pipeline emits ≥1 row, those rows are forwarded verbatim.
  • If the inner pipeline emits 0 rows, a single NULL-extended row is emitted consisting of the outer columns followed by expr.Null for every inner column the pipeline would have introduced.

func (*OptionalApply) PlanChildren added in v0.11.0

func (op *OptionalApply) PlanChildren() []Operator

PlanChildren reports the outer input that drives this operator, then the inner sub-plan it re-drives for each outer row.

type OptionalExpand

type OptionalExpand struct {
	// contains filtered or unexported fields
}

OptionalExpand is a Volcano pipeline operator that performs a single-hop expansion and emits a NULL-extended row when no edges match for an input node.

OptionalExpand is NOT safe for concurrent use.

func NewOptionalExpand

func NewOptionalExpand(input Operator, src AdjacencySource, cfg ExpandConfig) *OptionalExpand

NewOptionalExpand creates an OptionalExpand operator.

  • input is the upstream operator supplying node IDs.
  • src yields the forward and reverse adjacency at execution time; see AdjacencySource.
  • cfg is the Expand configuration (direction, inputCol).

The NULL-extension row uses the same column layout as Expand: inputRow... || srcID || Null(edgeID) || Null(dstID).

func (*OptionalExpand) Close

func (op *OptionalExpand) Close() error

Close closes the input and child operators.

func (*OptionalExpand) Init

func (op *OptionalExpand) Init(ctx context.Context) error

Init initialises the operator.

func (*OptionalExpand) Next

func (op *OptionalExpand) Next(out *Row) (bool, error)

Next emits the next row. For each input row:

  • It feeds the row into the inner Expand one hop at a time.
  • If Expand emits ≥1 row, those rows are forwarded as-is.
  • If Expand emits 0 rows for an input node, a NULL-extended row is emitted.

func (*OptionalExpand) PlanChildren added in v0.11.0

func (op *OptionalExpand) PlanChildren() []Operator

PlanChildren reports the upstream input first, then the Expand it wraps.

OptionalExpand holds both its upstream input and the Expand it re-drives one row at a time (through singleArg). Both are real operators in the executed pipeline, so both are reported; input comes first because it drives.

type ParallelAggregateScan added in v0.10.0

type ParallelAggregateScan struct {
	// contains filtered or unexported fields
}

ParallelAggregateScan is a Volcano leaf operator that partitions a full-node scan into contiguous morsels, accumulates per-worker count/min/max partials over each morsel's pre-aggregation rows, and combines them into the final aggregation result with a deterministic, byte-identical-to-serial combine.

ParallelAggregateScan is NOT safe for concurrent use.

func NewParallelAggregateScan added in v0.10.0

func NewParallelAggregateScan(g nodeWalker, factory AggInputFactory, nKeys int, reducers []AggReducerKind, morselSize int, gov *ParallelGovernor) *ParallelAggregateScan

NewParallelAggregateScan creates a ParallelAggregateScan over g. nKeys is the number of leading grouping-key columns in each pre-aggregation row (0 ⇒ a global aggregate); reducers gives the combine for each aggregate column (in order, at row columns nKeys+i). factory rebuilds the per-worker pre-aggregation sub-plan. morselSize controls the chunk size per worker (0 ⇒ DefaultMorselSize); gov is the engine-shared worker-budget governor (nil ⇒ unbounded GOMAXPROCS).

func (*ParallelAggregateScan) Close added in v0.10.0

func (op *ParallelAggregateScan) Close() error

Close cancels any still-running workers and joins them. It is idempotent and safe whether or not Next was ever called.

func (*ParallelAggregateScan) Init added in v0.10.0

Init collects all live node IDs on the calling goroutine (the ONLY phase that touches graph state), partitions them into contiguous morsels tagged with their base offset, and launches the workers. Each worker accumulates private count/min/max partials over the morsels it dequeues. The join and combine are deferred to the first Next so every worker is joined on the Next goroutine, inside the engine's visibility barrier.

func (*ParallelAggregateScan) Next added in v0.10.0

func (op *ParallelAggregateScan) Next(out *Row) (bool, error)

Next emits the aggregation result. The first call joins every worker (wg.Wait), surfaces the first worker error, combines the per-worker partials, and builds the output rows; subsequent calls stream them.

func (*ParallelAggregateScan) WithByteBudget added in v0.10.0

func (op *ParallelAggregateScan) WithByteBudget(maxBytes int64, estimateRow func(Row) int64) *ParallelAggregateScan

WithByteBudget bounds the estimated retained size of the grouping keys by maxBytes (per worker and on the merged map), the group-by analogue of the serial EagerAggregation.WithByteBudget (#1841). A non-positive maxBytes or a nil estimateRow leaves the byte dimension disabled. Returns op for chaining; call before Init.

func (*ParallelAggregateScan) WithGroupCap added in v0.10.0

func (op *ParallelAggregateScan) WithGroupCap(maxGroups int) *ParallelAggregateScan

WithGroupCap sets the maximum distinct group count (per worker and on the merged map); 0 keeps DefaultMaxGroups. Returns op for chaining; call before Init.

type ParallelCountScan added in v0.6.0

type ParallelCountScan struct {
	// contains filtered or unexported fields
}

ParallelCountScan is a Volcano leaf operator that computes a group-by-less count over a full node scan using per-worker partial counters combined once at the end. It emits exactly one row with a single expr.IntegerValue column carrying the total live-node count.

ParallelCountScan is NOT safe for concurrent use.

func NewParallelCountScan added in v0.6.0

func NewParallelCountScan(g nodeWalker, morselSize int, gov *ParallelGovernor) *ParallelCountScan

NewParallelCountScan creates a ParallelCountScan over g. morselSize controls the chunk size per worker; pass 0 to use DefaultMorselSize. gov is the engine-shared adaptive worker-budget governor (nil = unbounded GOMAXPROCS).

func (*ParallelCountScan) Close added in v0.6.0

func (op *ParallelCountScan) Close() error

Close cancels any still-running workers and joins them. It is idempotent and safe whether or not Next was ever called: wg.Wait returns immediately once the workers have drained, and cancel unblocks a worker stalled on ctx.

func (*ParallelCountScan) Init added in v0.6.0

func (op *ParallelCountScan) Init(ctx context.Context) error

Init collects all node IDs, partitions them into morsels, and launches worker goroutines that each accumulate a private count. The combine is deferred to the first Next call so every worker is joined on the Next goroutine, inside the engine's visibility barrier.

func (*ParallelCountScan) Next added in v0.6.0

func (op *ParallelCountScan) Next(out *Row) (bool, error)

Next emits the single aggregated row on its first call. It joins every worker synchronously (wg.Wait) on the calling goroutine, then sums the per-worker partials. Subsequent calls report end-of-stream.

type ParallelGovernor added in v0.6.0

type ParallelGovernor struct {
	// contains filtered or unexported fields
}

ParallelGovernor bounds intra-query parallelism across the concurrently executing queries that share one Engine. A morsel-parallel leaf registers on Init via ParallelGovernor.Enter (which returns its worker budget) and deregisters on Close via ParallelGovernor.Leave.

The budget is GOMAXPROCS divided by the number of parallel leaves currently in flight (including the caller), clamped to [1, morsels]:

  • a single parallel query in flight gets the full GOMAXPROCS workers — no change from the prior unconditional behaviour, so single-query latency is unaffected;
  • N concurrent parallel queries each get ~GOMAXPROCS/N workers, so the aggregate worker count stays near GOMAXPROCS and the scheduler/cache thrash of N×GOMAXPROCS goroutines is avoided.

The inflight count is sampled once per Enter and races at the edges (an early query may briefly see a low count and grab a large budget); this is a deliberate approximation — it need only prevent the sustained N×GOMAXPROCS explosion, not divide the machine exactly. Worker count never affects results, only timing, so the governor has no ACID or openCypher-TCK impact.

A nil *ParallelGovernor is valid and means "unbounded" — every leaf gets the full GOMAXPROCS budget and no count is tracked. This preserves the prior behaviour for any caller that constructs an operator without a governor (the public BuildPlan path and the operator unit tests).

ParallelGovernor is safe for concurrent use by any number of goroutines.

func (*ParallelGovernor) Enter added in v0.6.0

func (g *ParallelGovernor) Enter(morsels int) int

Enter registers one parallel leaf and returns its worker budget, an integer in [1, morsels]. It MUST be paired with exactly one ParallelGovernor.Leave (deferred in the operator's Close). On a nil governor it returns the full GOMAXPROCS budget (clamped to morsels) without tracking anything.

morsels is the number of work units the leaf has to distribute; the budget is never larger than that (more workers than morsels would leave workers idle).

func (*ParallelGovernor) Leave added in v0.6.0

func (g *ParallelGovernor) Leave()

Leave deregisters a parallel leaf previously registered via ParallelGovernor.Enter. It is nil-safe and idempotency is the caller's responsibility (call it exactly once per successful Enter).

type ParallelScanProject added in v0.6.0

type ParallelScanProject struct {
	// contains filtered or unexported fields
}

ParallelScanProject is a Volcano leaf operator that partitions a full-node scan into morsels, runs an independent fused scan→filter→project sub-plan per morsel on up to GOMAXPROCS workers, and emits the concatenated per-worker result rows. The output schema is the projection's output schema (set by the factory's Project), so this operator's rows are ready for the engine's final column passthrough.

ParallelScanProject is NOT safe for concurrent use.

func NewParallelScanProject added in v0.6.0

func NewParallelScanProject(g nodeWalker, factory SubplanFactory, morselSize int, gov *ParallelGovernor) *ParallelScanProject

NewParallelScanProject creates a ParallelScanProject over g whose per-worker fused sub-plans are built by factory. morselSize controls the chunk size per worker; pass 0 to use DefaultMorselSize.

func (*ParallelScanProject) Close added in v0.6.0

func (op *ParallelScanProject) Close() error

Close cancels any still-running workers and joins them. It is idempotent and safe whether or not Next was ever called: wg.Wait returns immediately once the workers have drained, and cancel unblocks a worker stalled on ctx or inside a sub-plan's Next.

func (*ParallelScanProject) Init added in v0.6.0

func (op *ParallelScanProject) Init(ctx context.Context) error

Init collects all node IDs, partitions them into morsels, builds one independent sub-plan per worker on the calling goroutine, and launches the workers. Each worker drives its sub-plan over the morsels it dequeues and accumulates deep-copied result rows into its private buffer. The join and combine are deferred to the first Next call so every worker is joined on the Next goroutine, inside the engine's visibility barrier.

func (*ParallelScanProject) Next added in v0.6.0

func (op *ParallelScanProject) Next(out *Row) (bool, error)

Next streams the concatenated per-worker result rows. The first call joins every worker synchronously (wg.Wait) on the calling goroutine, surfaces the first worker error if any, then concatenates the per-worker buffers. Subsequent calls advance the cursor through the concatenation.

func (*ParallelScanProject) WithResultBudget added in v0.7.0

func (op *ParallelScanProject) WithResultBudget(maxRows, maxBytes int64, estimateRow func(Row) int64) *ParallelScanProject

WithResultBudget threads the engine's per-query result-memory budget into the operator so the morsel workers stop accumulating once the fleet-wide total exceeds maxRows or maxBytes, bounding peak memory on the parallel path (#1830). maxRows/maxBytes of 0 leave that dimension unbounded (the engine convention); estimateRow is the engine's coarse per-row byte estimate and may be nil to disable byte-budget enforcement. It returns op for chaining and must be called before Init. When neither bound is set the operator behaves exactly as before (full materialisation), so the result multiset is unchanged under budget.

type PlanChildren added in v0.11.0

type PlanChildren interface {
	PlanChildren() []Operator
}

PlanChildren is implemented by every operator that draws rows from other operators. An operator that does not implement it is rendered as a leaf.

It exists because an operator's inputs are held in UNEXPORTED fields, which reflection cannot read: `reflect.Value.Interface` panics on an unexported field, so the plan tree cannot be recovered by inspection alone. The method is therefore the structural contract, and a source-level completeness gate (TestPlanChildren_EveryOperatorWithInputsImplementsIt) fails the build if an operator that holds an input forgets it — otherwise a rendered plan would silently truncate at that node, which is the class of defect this whole surface exists to remove.

Return the inputs in EXECUTION order, which for an asymmetric operator is the order that explains the cost: a join returns its build side before its probe side, an apply its outer before its inner.

type PlanDetail added in v0.11.0

type PlanDetail interface {
	PlanDetail() string
}

PlanDetail is implemented by operators that took a physical decision worth showing next to their name — the label they scan, the index they seek, the tier they engaged. It is optional: an operator without it renders as its name alone.

Keep the string short and factual; it is appended in square brackets after the operator name.

type PlanNode added in v0.11.0

type PlanNode struct {
	// Name is the concrete operator type name, e.g. "HashJoin".
	Name string
	// Detail is the operator's own [PlanDetail], empty when it has none.
	Detail string
	// Children are the operator's inputs, in execution order.
	Children []PlanNode

	// Rows is the number of rows this operator emitted, and Time the wall-clock
	// time attributed to its own Next calls. Both are zero unless the plan was
	// captured by a profiling run ([Profiler]); Profiled records which.
	Rows     int64
	Time     time.Duration
	Profiled bool

	// DbHits is the number of logical storage record accesses attributed to this
	// operator — the measure that distinguishes a selective seek from a scan that
	// filtered afterwards, since both can emit the same few rows while touching
	// wildly different amounts of storage (rmp #2238).
	//
	// It is counted only where it can be counted EXACTLY: at the operators that
	// read records from storage, one hit per record read (see [StorageRecordScan]).
	// An operator that only transforms rows its children produced reports 0,
	// because it accessed no storage. That convention is the one the in-tree
	// db-hits work established (T910/T913) and it is a documented DIVERGENCE from
	// Neo4j, which additionally charges a hit per property read; see docs/cypher.md.
	DbHits int64
}

PlanNode is one operator in a rendered physical plan.

Name is the operator's CONCRETE Go type name, taken from the value itself, so it cannot disagree with the operator that runs. That is the property the plan surface turns on: a HashJoin substituted for a nested loop is named HashJoin because it IS a *HashJoin, not because a second reconstruction of the planner's decisions happened to agree (rmp #2222).

func PlanTree added in v0.11.0

func PlanTree(op Operator) PlanNode

PlanTree builds the physical plan tree rooted at op.

It follows PlanChildren for structure and reads each node's name from its concrete type. When op is a profiling wrapper the wrapper is transparent: the node carries the wrapped operator's name with the wrapper's measurements.

type ProcedureCallOp

type ProcedureCallOp struct {
	// contains filtered or unexported fields
}

ProcedureCallOp invokes a registered procedure and emits its result rows.

ProcedureCallOp is NOT safe for concurrent use.

func NewProcedureCallOp

func NewProcedureCallOp(
	namespace []string,
	name string,
	argExprs []func(Row) (expr.Value, error),
	yieldVars []string,
	child Operator,
	reg *procs.Registry,
) *ProcedureCallOp

NewProcedureCallOp creates a ProcedureCallOp.

namespace and name identify the procedure. argExprs evaluate procedure arguments against the current driver row. yieldVars names the output columns. child is the driving subplan; pass nil for a standalone CALL. reg is the procedure registry used for lookup at runtime.

func (*ProcedureCallOp) Close

func (op *ProcedureCallOp) Close() error

Close releases resources and closes the child operator.

func (*ProcedureCallOp) Init

func (op *ProcedureCallOp) Init(ctx context.Context) error

Init resets internal state and initialises the child if present.

func (*ProcedureCallOp) Next

func (op *ProcedureCallOp) Next(out *Row) (bool, error)

Next advances the operator by one row.

It draws driving rows from the child (or a synthetic empty row when child is nil), invokes the procedure for each, buffers all result rows, and emits them one at a time.

Void procedure semantics. A procedure declared with no output columns (len(yieldVars) == 0) is treated as a side-effect-only step. When invoked in-query (op.child != nil) it must NOT consume the driver row; instead each driver row is emitted unchanged once the impl has run, preserving the upstream variable bindings for any downstream RETURN. Standalone CALL (op.child == nil) emits nothing.

func (*ProcedureCallOp) PlanChildren added in v0.11.0

func (op *ProcedureCallOp) PlanChildren() []Operator

PlanChildren reports the input whose rows drive the procedure call.

type Profiler added in v0.11.0

type Profiler struct {
	// contains filtered or unexported fields
}

Profiler captures per-operator measurements for one query execution.

Cost when off

A Profiler is opt-in and there is exactly one place that installs it: the recursive plan builder wraps each operator it returns, and only when a Profiler is present. With none, no wrapper is built and no operator executes any instrumentation — the normal path runs byte-identical code to a build that never had profiling. That is the acceptance condition (rmp #2222 AC 3) and the reason the counters live in a wrapper rather than in the operators: an `if p != nil` inside 55 Next implementations would be a cost on every row of every query forever.

Why the wrapper must be transparent

The builder wraps on the way OUT of its recursion, so a parent is constructed with its child ALREADY wrapped and runs its capability type-assertions against the wrapper. A wrapper that hid ChunkProducer would make the parent build a row-mode operator instead of a columnar one, so profiling would change the very plan it exists to observe. Wrap therefore returns a variant matching what the wrapped operator exposes:

All variants forward rowCountHint, which is likewise asserted on children (to bound allocation) and whose contract already covers "no bound known". TestProfile_PlanShapeIsIdenticalProfiledOrNot is the gate on that transparency: it compares the rendered tree with and without a Profiler.

Why this is not cypher/explain.ProfiledOperator

The cypher/explain package already had a ProfiledOperator recording rows and elapsed time (plus a DbHits counter), written before any engine surface existed to wire it to. It could not be the wiring, for a reason that is structural rather than stylistic: transparency requires the wrapper to re-implement NodeIDColumnProducer, whose identifying method nodeIDColumnProducer() is UNEXPORTED to this package. A wrapper declared anywhere else cannot satisfy that interface, so wrapping from cypher/explain would strip the marker and silently downgrade a columnar plan to row mode.

The measurements therefore live here; cypher/explain keeps its DbHits counter and report formatter, and folding db-hits into this Profiler is tracked separately (rmp #2238) since it needs a counter threaded through the storage accessors rather than a wrapper.

Concurrency

A Profiler is NOT safe for concurrent use and must not be shared between queries. One query's pipeline is driven by one goroutine, so the wrappers need no synchronisation. An operator that fans work out internally (the parallel tier) is measured as one node: the wrapper times the calls the driving goroutine makes, which is the honest attribution — it cannot see inside.

func NewProfiler added in v0.11.0

func NewProfiler() *Profiler

NewProfiler returns a Profiler ready to instrument one query build.

func (*Profiler) Wrap added in v0.11.0

func (p *Profiler) Wrap(op Operator) Operator

Wrap returns op instrumented to record the rows it emits and the time its own Next/FillChunk calls take, preserving every capability op exposes. It returns op unchanged when op is nil or already wrapped, so a double-wrap cannot double-count.

type Project

type Project struct {
	// contains filtered or unexported fields
}

Project is a Volcano pipeline operator that applies a list of ProjectionItem expressions to each input row, producing an output row with one column per item.

Project is NOT safe for concurrent use.

func NewProject

func NewProject(child Operator, items []ProjectionItem) (*Project, error)

NewProject creates a Project operator. items defines the output schema; each item's Eval function is applied to each input row. An empty items slice is legal (e.g. `WITH *` over a pattern that binds no variables); the resulting operator forwards an empty Row for every input row.

func (*Project) Close

func (op *Project) Close() error

Close releases resources and closes the child operator.

func (*Project) Columns

func (op *Project) Columns() []string

Columns returns the ordered list of output column aliases.

func (*Project) Init

func (op *Project) Init(ctx context.Context) error

Init initialises the operator and its child.

func (*Project) Next

func (op *Project) Next(out *Row) (bool, error)

Next evaluates each projection item against the next input row and writes the result into out. Returns (true, nil) when a projected row is available, (false, nil) at end-of-stream, or (false, err) on evaluation or child error.

func (*Project) PlanChildren added in v0.11.0

func (op *Project) PlanChildren() []Operator

PlanChildren reports the input it projects.

func (*Project) WithRowByteBudget added in v0.7.0

func (op *Project) WithRowByteBudget(maxRowBytes int64, estimateValue func(expr.Value) int64) *Project

WithRowByteBudget bounds the estimated size of a single assembled output row by maxRowBytes, using estimateValue for the per-column estimate. It is enforced INCREMENTALLY inside Next — after each column is evaluated, before the next — so a projection of several large columns (e.g. RETURN range(1,N), range(1,N), …) is rejected before the whole row is materialised, bounding the transient peak to maxRowBytes plus one column regardless of the column count. It complements the drain's aggregate per-result byte budget, which is a retention guard on the SUM of already-built rows and therefore fires only after Next has assembled the row; this per-row guard moves the same accounting earlier and makes it per-column so construction cannot OOM (#1852). A non-positive maxRowBytes or nil estimateValue leaves the guard disabled (behaviour-preserving). Returns op for chaining; call before Init.

type ProjectionItem

type ProjectionItem struct {
	// Eval evaluates the item expression against the current input row and
	// returns the projected value.
	Eval func(Row) (expr.Value, error)
	// Alias is the output column name (e.g. "n", "count(n)", "x").
	Alias string
}

ProjectionItem describes a single column in a projection. Eval is evaluated against the input row; Alias names the resulting output column.

type PropEntry

type PropEntry struct {
	Value lpg.PropertyValue
	Key   string
}

PropEntry is an exported key/value pair for use by external plan builders (api.go) that construct dynamic property evaluators. It mirrors propLiteral but carries exported fields so the physical builder can return values from a PropsEvalFn without requiring propLiteral to be exported.

type PropsEvalFn

type PropsEvalFn func(row Row) ([]PropEntry, error)

PropsEvalFn is a per-row property evaluator closure. It receives the current row and returns a slice of (key, value) pairs produced by evaluating the property-map AST expressions against the row's bound variables. Any entry whose evaluation yields Null is omitted (openCypher: assigning null to a property is a no-op on a fresh node).

It returns a non-nil error to fail-stop the statement — a runtime evaluation error, or a value that is not a valid property (a map or nested collection, InvalidPropertyType) — rather than silently dropping the entry (audit 2026-07-13 security F3 / fail-stop mandate).

The closure is constructed once by the physical plan builder and captures the schema, function registry, and query parameters.

type QueryCounters added in v0.11.0

type QueryCounters struct {
	NodesCreated         int64
	NodesDeleted         int64
	RelationshipsCreated int64
	RelationshipsDeleted int64
	PropertiesSet        int64
	PropertiesRemoved    int64
	LabelsAdded          int64
	LabelsRemoved        int64
	IndexesAdded         int64
	IndexesRemoved       int64
	ConstraintsAdded     int64
	ConstraintsRemoved   int64
}

QueryCounters holds the write effects one Cypher statement actually applied.

Semantics

Every counter records an effect that was ACTUALLY APPLIED, incremented by the write adapter at the point it already discriminates a real change from a no-op:

  • a re-intern of an existing node is not a creation, but re-creating a TOMBSTONED key is (the adapter's existing rule);
  • REMOVE of an absent property, and removal of an absent label, count nothing;
  • a MERGE that matched counts nothing, because it never reaches a create.

A statement that fails or is rolled back must report nothing: the counters live on the per-statement adapter, so an aborted statement's instance is simply discarded.

Concurrency

QueryCounters is NOT safe for concurrent use, and deliberately holds plain integers rather than atomics. The reason is OWNERSHIP, not serialisation: each physical operator tree owns exactly one write adapter and the adapter owns its counters inline, so concurrent statements never share a QueryCounters and there is nothing to synchronise. Adding atomics would cost the write path for nothing.

This note used to give the reason as "the Cypher write path is single-writer", which stopped being true when rmp #2306 retired the writer serialisers. The safety survived because it never rested on that; pinned by cypher.TestConcurrentCreate_PerStatementCountersAreNotShared.

func (*QueryCounters) Add added in v0.11.0

func (c *QueryCounters) Add(other *QueryCounters)

Add folds other into c. It is used to accumulate an explicit transaction's effects across the statements it contains. A nil receiver or argument is a no-op.

func (*QueryCounters) ContainsUpdates added in v0.11.0

func (c *QueryCounters) ContainsUpdates() bool

ContainsUpdates reports whether the statement changed anything at all — the value Bolt's contains-updates carries and the driver surfaces as ResultSummary.Counters().ContainsUpdates().

It is derived rather than tracked so it cannot disagree with the counters: any non-zero effect makes it true.

type RangeBound

type RangeBound struct {
	// Value is the bound's expr.Value.  Nil means unbounded (use the
	// minimum or maximum representable value for the index type).
	Value expr.Value
	// Include records the caller's intended inclusivity (≤ / ≥ vs < / >). It is
	// metadata only: NodeByIndexRangeScan always emits the inclusive [lo, hi]
	// superset and relies on a residual predicate Filter for exact open/closed
	// semantics (see the NodeByIndexRangeScan type doc, #F-EXEC1).
	Include bool
}

RangeBound carries one endpoint of a range predicate.

type Record

type Record map[string]interface{}

Record is a single result row, accessed by column name. The underlying map is owned by the ResultSet; callers must copy values they need to retain beyond the next ResultSet.Next call.

type RelCols

type RelCols struct {
	SrcCol  int
	DstCol  int
	EdgeCol int
}

RelCols carries the raw column indices that the Expand operator places for a relationship variable. SetProperty and RemoveProperty use it to reconstruct the (src, dst) endpoint keys when the bound entity is a relationship rather than a node.

SrcCol and DstCol hold the endpoint NodeIDs as IntegerValue. EdgeCol holds the forward-CSR edge-position counter (= schema[entityVar]); it is used to resolve the bound parallel instance's stable handle via [GraphMutator.EdgeHandleAtPosition] so a relationship SET/REMOVE maintains the per-instance by-handle property store (#1686). EdgeCol may be 0 only when it genuinely is column 0; callers distinguish "no handle" by the resolver returning 0, not by the column index.

type RelEndpointFn

type RelEndpointFn func(row Row) (uint64, uint64, bool)

RelEndpointFn returns the (srcID, dstID) endpoints for an edge that the schema-direct path is about to delete. Used when the bare-variable target carries an IntegerValue edge id (the in-pipeline encoding emitted by Expand) so DeleteNode can dispatch to the edge-removal branch without misinterpreting the id as a node id.

type RemoveLabels

type RemoveLabels struct {
	// contains filtered or unexported fields
}

RemoveLabels removes one or more labels from an already-bound node per input row.

Detaching a label takes the node OUT of every UNIQUE constraint declared on that label, so the operator releases the node's reservation for each constrained property before it writes — without that, the value stays reserved for ever and a later legitimate write of it is refused by a phantom. See cypher/exec/label_constraints.go. Enforcement is inert unless a UNIQUE constraint is registered.

RemoveLabels is NOT safe for concurrent use.

func NewRemoveLabels

func NewRemoveLabels(
	nodeVar string,
	labels []string,
	schema map[string]int,
	child Operator,
	mutator GraphMutator,
) *RemoveLabels

NewRemoveLabels creates a RemoveLabels operator.

func (*RemoveLabels) Close

func (op *RemoveLabels) Close() error

Close closes the child operator.

func (*RemoveLabels) Init

func (op *RemoveLabels) Init(ctx context.Context) error

Init initialises the operator and its child.

func (*RemoveLabels) Next

func (op *RemoveLabels) Next(out *Row) (bool, error)

Next pulls one row from the child and removes the specified labels.

func (*RemoveLabels) PlanChildren added in v0.11.0

func (op *RemoveLabels) PlanChildren() []Operator

PlanChildren reports the input whose rows name the nodes it relabels.

func (*RemoveLabels) WithConstraintRegistry added in v0.11.0

func (op *RemoveLabels) WithConstraintRegistry(reg *ConstraintRegistry) *RemoveLabels

WithConstraintRegistry attaches a ConstraintRegistry so RemoveLabels releases the unique-constraint reservations the detached labels free. Returns op for chaining. No index.Manager is needed: releasing consults only the registry's own value-set, never the backing hash index.

type RemoveProperty

type RemoveProperty struct {
	// contains filtered or unexported fields
}

RemoveProperty removes a single named property from an already-bound node or relationship per input row. For relationships, call WithRelCols to supply the endpoint column indices.

RemoveProperty is NOT safe for concurrent use.

func NewRemoveProperty

func NewRemoveProperty(
	entityVar, propertyKey string,
	schema map[string]int,
	child Operator,
	mutator GraphMutator,
) *RemoveProperty

NewRemoveProperty creates a RemoveProperty operator.

func (*RemoveProperty) Close

func (op *RemoveProperty) Close() error

Close closes the child operator.

func (*RemoveProperty) Init

func (op *RemoveProperty) Init(ctx context.Context) error

Init initialises the operator and its child.

func (*RemoveProperty) Next

func (op *RemoveProperty) Next(out *Row) (bool, error)

Next pulls one row from the child and removes the specified property.

func (*RemoveProperty) PlanChildren added in v0.11.0

func (op *RemoveProperty) PlanChildren() []Operator

PlanChildren reports the input whose rows name the entities it strips.

func (*RemoveProperty) WithConstraintRegistry added in v0.3.0

func (op *RemoveProperty) WithConstraintRegistry(reg *ConstraintRegistry) *RemoveProperty

WithConstraintRegistry attaches a ConstraintRegistry so RemoveProperty releases unique-constraint value reservations when a node property is removed. Returns op for chaining.

func (*RemoveProperty) WithRelCols

func (op *RemoveProperty) WithRelCols(rc RelCols) *RemoveProperty

WithRelCols marks entityVar as a relationship variable and records the row columns that hold the src and dst NodeIDs. Must be called before the first Next invocation. Returns op for chaining.

type Result

type Result interface {
	// Next advances to the next result row. It returns true if a row is
	// available, false at end-of-stream or on error. After Next returns false,
	// callers should check Err.
	Next() bool

	// Record returns the current row as a [Record] (column name → value).
	// Record must not be called before the first successful Next or after Next
	// returns false.
	Record() Record

	// Err returns the first error encountered during iteration, or nil.
	Err() error

	// Columns returns the ordered list of column names for this result set.
	// The slice is stable across calls and is not modified after construction.
	Columns() []string

	// Close releases all resources held by this result set, including the
	// underlying operator tree. It must be called exactly once.
	Close() error
}

Result is a forward-only, streaming iterator over query result rows.

Lifecycle

  1. Call [Next] in a loop until it returns false.
  2. After the loop, check [Err] for any error that terminated iteration.
  3. Call [Close] exactly once to release resources.

Concurrency

Result implementations are NOT safe for concurrent use.

type ResultSet

type ResultSet struct {
	// contains filtered or unexported fields
}

ResultSet is the concrete implementation of Result returned by Run.

ResultSet is NOT safe for concurrent use.

func Run

func Run(ctx context.Context, plan Operator, cols []string) *ResultSet

Run initialises plan, stores the column names, and returns a ResultSet ready for iteration. The caller drives iteration via ResultSet.Next and must call ResultSet.Close when done.

Run does not pull any rows; all work happens lazily in ResultSet.Next. The Record map is allocated lazily on the first ResultSet.Record / ResultSet.TakeRecord call (#1499 follow-up) and then reused across every subsequent Next call. A consumer that drains rows positionally via ResultSet.Row — the materialisation path and the Bolt PULL path — never triggers the allocation at all, which keeps small-result queries off the heap.

func (*ResultSet) Close

func (rs *ResultSet) Close() error

Close releases all resources held by the ResultSet, including the underlying operator tree. It must be called exactly once. Calling Close after a previous Close is a no-op.

func (*ResultSet) ColumnarProducer added in v0.9.0

func (rs *ResultSet) ColumnarProducer() (ChunkProducer, bool)

ColumnarProducer reports whether this ResultSet's plan can produce its output column-major, returning the ChunkProducer when it can. A columnar-aware sink prefers the columnar drain when this returns true; otherwise it drains row-at-a-time via ResultSet.Next.

func (*ResultSet) Columns

func (rs *ResultSet) Columns() []string

Columns returns the ordered list of column names. The slice is never nil and is stable for the lifetime of the ResultSet.

func (*ResultSet) Err

func (rs *ResultSet) Err() error

Err returns the first error encountered during iteration, or nil.

func (*ResultSet) Next

func (rs *ResultSet) Next() bool

Next advances to the next result row. It returns true when a row is available (accessible via Record), and false at end-of-stream or on error.

func (*ResultSet) PlanChildren added in v0.11.0

func (op *ResultSet) PlanChildren() []Operator

PlanChildren reports the plan this result set drains. ResultSet is the pipeline root the driver pulls from.

func (*ResultSet) Record

func (rs *ResultSet) Record() Record

Record returns the current row as a map. Must only be called after a successful Next.

The returned map is owned by the ResultSet and reused by the next Next call; callers that need to retain a row beyond the next Next must copy it (or use ResultSet.TakeRecord). The map is built lazily on the first Record call for the current row, so a caller that consumes rows positionally via ResultSet.Row never pays for it.

func (*ResultSet) Row added in v0.3.1

func (rs *ResultSet) Row() Row

Row returns the current row as a positional slice of values whose indices correspond to ResultSet.Columns. The slice is owned by the operator tree and is reused on the next ResultSet.Next call; callers that retain values beyond the next Next must copy them. This is the allocation-free accessor: unlike ResultSet.Record it never builds a map. Must only be called after a successful Next.

func (*ResultSet) RowCountHint added in v0.6.0

func (rs *ResultSet) RowCountHint() (n int, ok bool)

RowCountHint reports a best-effort upper bound on the number of rows this ResultSet will yield, valid after Run (which calls Init). It returns ok=false when the plan exposes no sound upper bound. Callers use it purely to presize buffers; the value is never an exact count and must not be used to decide how many rows exist. See [rowCountHinter].

func (*ResultSet) TakeRecord

func (rs *ResultSet) TakeRecord() Record

TakeRecord returns the current row and transfers ownership of its backing map to the caller, installing a fresh map for subsequent Next calls. Unlike ResultSet.Record — whose result is reused on the next Next — the map returned here is safe to retain. The materialisation path uses this to drain rows under the transaction-visibility barrier without the extra per-row copy that re-hashing every column into a new map would cost. Must only be called after a successful Next.

type RollUpApply

type RollUpApply struct {
	// contains filtered or unexported fields
}

RollUpApply is a Volcano pipeline operator that performs pattern-comprehension execution: for each outer row, it drains the entire inner sub-plan into a expr.ListValue and emits (outerRow... || listValue) as a single output row.

RollUpApply is NOT safe for concurrent use.

func NewRollUpApply

func NewRollUpApply(outer, inner Operator, arg *Argument, listEval func(Row) (expr.Value, error)) *RollUpApply

NewRollUpApply creates a RollUpApply operator with the default per-list element budget (funcs.DefaultMaxCollectItems).

  • outer is the driving (left) plan.
  • inner is the correlated (right) sub-plan whose leaf is arg.
  • arg is the Argument node seeded with each outer row before inner Init.
  • listEval, when non-nil, is called for each inner row to extract the value to collect into the list. When nil, the first column of each inner row is collected.

func NewRollUpApplyN added in v0.2.0

func NewRollUpApplyN(outer, inner Operator, arg *Argument, listEval func(Row) (expr.Value, error), maxItems int) *RollUpApply

NewRollUpApplyN is NewRollUpApply with an explicit per-list element budget. maxItems uses the EngineOptions.MaxCollectItems encoding so the cap is consistent with the buffering aggregators (#1294): 0 selects funcs.DefaultMaxCollectItems, a negative value disables the cap entirely, and a positive value is used verbatim. The encoding is resolved once here so the drain loop compares against a single non-negative ceiling.

func (*RollUpApply) Close

func (op *RollUpApply) Close() error

Close closes both the outer and inner plans.

func (*RollUpApply) Init

func (op *RollUpApply) Init(ctx context.Context) error

Init initialises the outer plan.

func (*RollUpApply) Next

func (op *RollUpApply) Next(out *Row) (bool, error)

Next emits one output row per outer row. For each outer row, the entire inner plan is drained into a ListValue appended as a new column.

func (*RollUpApply) PlanChildren added in v0.11.0

func (op *RollUpApply) PlanChildren() []Operator

PlanChildren reports the outer input that drives this operator, then the inner sub-plan it re-drives for each outer row.

type Row

type Row []expr.Value

Row is a single tuple in the pipeline: a slice of expr.Value whose positions correspond to the operator's output schema. The slice is owned by the RowSlab that allocated it; callers must not retain it beyond the slab's lifetime.

func Drain

func Drain(ctx context.Context, op Operator) ([]Row, error)

Drain initialises op, pulls every row from the pipeline, and returns the collected rows as a []Row. Close is always called before Drain returns, regardless of whether an error occurred.

Cancellation: Drain honours ctx.Done() via the per-Next check inside each operator. If ctx is cancelled, Drain returns the partial result set and the context error.

The returned rows are independent copies: each element is a snapshot of the Row written by the operator at that iteration. Callers own the returned slice.

Example

ExampleDrain assembles the equivalent of `UNWIND [10, 20, 30] AS x RETURN x LIMIT 2` as a hand-built operator tree and drains it. SingleRow seeds one empty row, Unwind expands the literal list, and Limit caps the output.

package main

import (
	"context"
	"fmt"

	"github.com/FlavioCFOliveira/GoGraph/cypher/exec"
	"github.com/FlavioCFOliveira/GoGraph/cypher/expr"
)

func main() {
	// SingleRow emits exactly one empty row to drive the pipeline.
	src := exec.NewSingleRowOperator()

	// Unwind expands a fixed list; listFn ignores the (empty) input row.
	unwind, err := exec.NewUnwind(src, func(exec.Row) (expr.ListValue, error) {
		return expr.ListValue{
			expr.IntegerValue(10),
			expr.IntegerValue(20),
			expr.IntegerValue(30),
		}, nil
	})
	if err != nil {
		fmt.Println("NewUnwind:", err)
		return
	}

	// Limit passes at most two rows downstream.
	limit, err := exec.NewLimit(unwind, 2)
	if err != nil {
		fmt.Println("NewLimit:", err)
		return
	}

	// Drain runs the pipeline and always closes it before returning.
	rows, err := exec.Drain(context.Background(), limit)
	if err != nil {
		fmt.Println("Drain:", err)
		return
	}

	fmt.Println("rows:", len(rows))
	for _, r := range rows {
		fmt.Println("x =", r[0])
	}
}
Output:
rows: 2
x = 10
x = 20

type RowSlab

type RowSlab struct {
	// contains filtered or unexported fields
}

RowSlab is a bounded arena of pre-allocated rows. It eliminates per-row heap allocations by backing all rows in a single flat slice. Each call to [Alloc] hands out a sub-slice at zero GC cost after the initial backing allocation.

RowSlab is NOT safe for concurrent use; each pipeline stage owns its own instance, typically obtained from SlabPool.

Lifecycle

  1. Obtain a slab from SlabPool.Get (or call NewRowSlab).
  2. Call [Alloc] for each row needed in the current batch.
  3. When the batch is fully processed, call [Reset] and return the slab to SlabPool.Put.

[Reset] zeroes the column values in every allocated row so that no expr.Value reference is retained past the batch boundary (preventing GC nepotism between batches).

func NewRowSlab

func NewRowSlab(width, capacity int) *RowSlab

NewRowSlab creates a RowSlab with the given column count and row capacity. width is the number of expr.Value slots pre-allocated per row; pass 0 for variable-width rows (callers supply their own slice to [AllocRaw]). capacity must be ≥ 1; DefaultSlabCapacity is a reasonable default.

func (*RowSlab) Alloc

func (s *RowSlab) Alloc() (Row, error)

Alloc returns the next available pre-allocated row in the slab. It returns ErrSlabOverflow if the slab is exhausted. The row width matches the width passed to NewRowSlab; for variable-width slabs (width=0) use [AllocRaw].

func (*RowSlab) AllocRaw

func (s *RowSlab) AllocRaw() (int, error)

AllocRaw returns the next row slot for variable-width slabs (width=0). The caller is responsible for setting the returned row to a correctly-sized slice before use. For fixed-width slabs, use [Alloc].

func (*RowSlab) Cap

func (s *RowSlab) Cap() int

Cap returns the maximum number of rows this slab can hold.

func (*RowSlab) GetRow

func (s *RowSlab) GetRow(idx int) Row

GetRow returns the row at index idx. Panics if idx is out of bounds.

func (*RowSlab) Len

func (s *RowSlab) Len() int

Len returns the number of rows currently allocated.

func (*RowSlab) Reset

func (s *RowSlab) Reset()

Reset resets the slab for reuse. It zeroes every value slot in each allocated row to release references held by the GC, then resets the allocation counter to zero. The backing memory is retained.

func (*RowSlab) SetRow

func (s *RowSlab) SetRow(idx int, r Row)

SetRow stores row r at index idx. Panics if idx is out of bounds. Used with variable-width slabs after [AllocRaw].

type SemiApply

type SemiApply struct {
	// contains filtered or unexported fields
}

SemiApply emits each outer row for which the inner sub-plan produces at least one row. The inner plan is closed after the first match (short-circuit).

SemiApply is NOT safe for concurrent use.

func NewSemiApply

func NewSemiApply(outer, inner Operator, arg *Argument) *SemiApply

NewSemiApply creates a SemiApply operator.

  • outer is the driving (left) plan.
  • inner is the correlated (right) sub-plan whose leaf is arg.
  • arg is the Argument node seeded with each outer row before inner Init.

func (*SemiApply) Close

func (op *SemiApply) Close() error

Close closes the outer plan. The inner plan is already closed per-row inside Next; a redundant close here is safe because Close on a closed operator must be a no-op per the Operator contract.

func (*SemiApply) Init

func (op *SemiApply) Init(ctx context.Context) error

Init initialises the outer plan.

func (*SemiApply) Next

func (op *SemiApply) Next(out *Row) (bool, error)

Next advances to the next outer row for which the inner plan has ≥1 result.

func (*SemiApply) PlanChildren added in v0.11.0

func (op *SemiApply) PlanChildren() []Operator

PlanChildren reports the outer input that drives this operator, then the inner sub-plan it re-drives for each outer row.

type SetAllProperties

type SetAllProperties struct {
	// contains filtered or unexported fields
}

SetAllProperties replaces or merges every property on an already-bound node or relationship per input row.

SetAllProperties is NOT safe for concurrent use.

func NewSetAllPropertiesFromEntity

func NewSetAllPropertiesFromEntity(
	entityVar, sourceVar string,
	isReplace bool,
	schema map[string]int,
	child Operator,
	mutator GraphMutator,
) *SetAllProperties

NewSetAllPropertiesFromEntity creates a SetAllProperties operator copying every property from sourceVar (a bound node or relationship) to entityVar. isReplace selects `=` (true) vs `+=` (false) semantics.

func NewSetAllPropertiesFromExpr added in v0.9.0

func NewSetAllPropertiesFromExpr(
	entityVar string,
	isReplace bool,
	schema map[string]int,
	child Operator,
	mutator GraphMutator,
) *SetAllProperties

NewSetAllPropertiesFromExpr creates a SetAllProperties operator whose source is an arbitrary map-valued expression evaluated per row (installed via SetAllProperties.WithExprEvalFn). It backs the `SET n = <expr>` / `SET n += <expr>` forms whose right-hand side is neither a `{…}` literal, a bound entity variable, nor a `$param` — e.g. `SET n = properties(m)`, a map projection, or coalesce. The evaluated value is dispatched on its runtime kind at apply time (see [SetAllProperties.applyExprValue]).

func NewSetAllPropertiesFromMap

func NewSetAllPropertiesFromMap(
	entityVar, mapLiteral string,
	isReplace bool,
	schema map[string]int,
	child Operator,
	mutator GraphMutator,
) (*SetAllProperties, error)

NewSetAllPropertiesFromMap creates a SetAllProperties operator writing every key/value pair from mapLiteral to entityVar. mapLiteral is the opaque literal-map string (e.g. `{a: 1, b: "x"}`) produced by the AST printer.

func NewSetAllPropertiesFromParam

func NewSetAllPropertiesFromParam(
	entityVar, paramName string,
	isReplace bool,
	schema map[string]int,
	child Operator,
	mutator GraphMutator,
) *SetAllProperties

NewSetAllPropertiesFromParam creates a SetAllProperties operator writing every key/value pair from the named query parameter to entityVar. The parameter must resolve to a MapValue at exec time; non-map values are treated as a no-op.

func (*SetAllProperties) Close

func (op *SetAllProperties) Close() error

Close closes the child operator.

func (*SetAllProperties) Init

func (op *SetAllProperties) Init(ctx context.Context) error

Init initialises the operator and its child.

func (*SetAllProperties) Next

func (op *SetAllProperties) Next(out *Row) (bool, error)

Next pulls one row from the child and applies the configured whole-entity mutation. The row is forwarded unchanged so downstream operators (e.g. ProduceResults) can read the affected entity.

func (*SetAllProperties) PlanChildren added in v0.11.0

func (op *SetAllProperties) PlanChildren() []Operator

PlanChildren reports the input whose rows name the entities it overwrites.

func (*SetAllProperties) WithConstraints

func (op *SetAllProperties) WithConstraints(reg *ConstraintRegistry, mgr *index.Manager) *SetAllProperties

WithConstraints attaches a ConstraintRegistry and index.Manager for pre-write enforcement. Both must be non-nil. Returns op for chaining.

func (*SetAllProperties) WithExprEvalFn added in v0.9.0

func (op *SetAllProperties) WithExprEvalFn(fn ExprValueEvalFn) *SetAllProperties

WithExprEvalFn attaches a per-row evaluator for a whole map-valued RHS expression (see ExprValueEvalFn). The evaluated value is dispatched on its runtime kind at apply time: a map writes its entries (null-valued keys removed), a node/relationship copies its properties, a null clears the target for `=` and is a no-op for `+=`, and any other kind raises a TypeError. Pass nil to clear. Returns op for chaining.

func (*SetAllProperties) WithMapEvalFn added in v0.9.0

func (op *SetAllProperties) WithMapEvalFn(fn MapEvalFn) *SetAllProperties

WithMapEvalFn attaches a per-row evaluator for the source map when it holds a non-literal value (a variable reference, property access, or arithmetic — e.g. `SET n += {x: row.y}`). The evaluator's output supersedes the static parsedMap/nullKeys for each row, so the map's row-driven values are written (and null-valued keys removed) exactly as the literal map is. Without it the literal-only parser drops the non-literal entry and the target keeps null or stale data — the same fail-silent defect the CREATE/MERGE property evaluators exist to prevent. Pass nil to clear. Returns op for chaining.

func (*SetAllProperties) WithParams

func (op *SetAllProperties) WithParams(params map[string]expr.Value) (*SetAllProperties, error)

WithParams attaches query parameters for $name substitution in the literal map and for parameter-sourced operators. Returns op for chaining.

func (*SetAllProperties) WithRelCols

func (op *SetAllProperties) WithRelCols(rc RelCols) *SetAllProperties

WithRelCols marks entityVar as a relationship variable and records the row columns that hold the src and dst NodeIDs. Must be called before the first Next invocation. Returns op for chaining.

func (*SetAllProperties) WithSourceRelCols

func (op *SetAllProperties) WithSourceRelCols(rc RelCols) *SetAllProperties

WithSourceRelCols marks sourceVar as a relationship variable and records the row columns that hold its src and dst NodeIDs. Must be called before the first Next invocation when SourceVar is a relationship. Returns op for chaining.

type SetLabels

type SetLabels struct {
	// contains filtered or unexported fields
}

SetLabels adds one or more labels to an already-bound node per input row.

Attaching a label puts the node under every UNIQUE constraint declared on that label, so the operator reserves the node's current value for each constrained property before it writes — see cypher/exec/label_constraints.go. Enforcement is inert unless a UNIQUE constraint is registered.

SetLabels is NOT safe for concurrent use.

func NewSetLabels

func NewSetLabels(
	nodeVar string,
	labels []string,
	schema map[string]int,
	child Operator,
	mutator GraphMutator,
) *SetLabels

NewSetLabels creates a SetLabels operator.

func (*SetLabels) Close

func (op *SetLabels) Close() error

Close closes the child operator.

func (*SetLabels) Init

func (op *SetLabels) Init(ctx context.Context) error

Init initialises the operator and its child.

func (*SetLabels) Next

func (op *SetLabels) Next(out *Row) (bool, error)

Next pulls one row from the child and adds the specified labels.

func (*SetLabels) PlanChildren added in v0.11.0

func (op *SetLabels) PlanChildren() []Operator

PlanChildren reports the input whose rows name the nodes it labels.

func (*SetLabels) WithConstraints added in v0.11.0

func (op *SetLabels) WithConstraints(reg *ConstraintRegistry, mgr *index.Manager) *SetLabels

WithConstraints attaches the constraint registry and index manager so SetLabels enforces the UNIQUE constraints the added labels bring into play. Returns op for chaining.

type SetProperty

type SetProperty struct {
	// contains filtered or unexported fields
}

SetProperty sets or replaces properties on an already-bound node or relationship per input row. The entity is identified by entityVar. For nodes, the column value must be an IntegerValue-encoded NodeID or a NodeValue. For relationships, call WithRelCols to supply the endpoint column indices.

SetProperty is NOT safe for concurrent use.

func NewSetProperty

func NewSetProperty(
	entityVar, propertyKey, valueExpr string,
	schema map[string]int,
	child Operator,
	mutator GraphMutator,
) (*SetProperty, error)

NewSetProperty creates a SetProperty operator.

entityVar is the variable name of the target node or relationship. propertyKey is the property key for single-property mode; pass empty for whole-entity mode. valueExpr is the opaque literal string from the IR. schema maps variable names to column indices. mutator is the graph write surface.

func (*SetProperty) Close

func (op *SetProperty) Close() error

Close closes the child operator.

func (*SetProperty) Init

func (op *SetProperty) Init(ctx context.Context) error

Init initialises the operator and its child.

func (*SetProperty) Next

func (op *SetProperty) Next(out *Row) (bool, error)

Next pulls one row from the child and applies the property mutation.

func (*SetProperty) PlanChildren added in v0.11.0

func (op *SetProperty) PlanChildren() []Operator

PlanChildren reports the input whose rows name the entities it updates.

func (*SetProperty) WithConstraints

func (op *SetProperty) WithConstraints(reg *ConstraintRegistry, mgr *index.Manager) *SetProperty

WithConstraints attaches a ConstraintRegistry and index.Manager for pre-write enforcement. Both must be non-nil. Returns op for chaining.

func (*SetProperty) WithParams

func (op *SetProperty) WithParams(params map[string]expr.Value) *SetProperty

WithParams attaches query parameters for $name substitution in value expressions. Returns op for chaining.

func (*SetProperty) WithRelCols

func (op *SetProperty) WithRelCols(rc RelCols) *SetProperty

WithRelCols marks entityVar as a relationship variable and records the row columns that hold the src and dst NodeIDs. Must be called before the first Next invocation. Returns op for chaining.

func (*SetProperty) WithValueEvalFn

func (op *SetProperty) WithValueEvalFn(fn ValueEvalFn) *SetProperty

WithValueEvalFn attaches a per-row evaluator for the SET RHS expression. The closure is invoked whenever the operator needs to compute the new property value; it takes priority over the literal-string parser path for single-property assignments.

type ShortestPath

type ShortestPath struct {
	// contains filtered or unexported fields
}

ShortestPath is a Volcano pipeline operator that, for each input row, finds a single shortest path from srcCol to dstCol using BFS and emits one output row containing the flat alternating path list (or Null if unreachable).

ShortestPath is NOT safe for concurrent use.

func NewShortestPath

func NewShortestPath(input Operator, src AdjacencySource, dir Direction, srcCol, dstCol int) *ShortestPath

NewShortestPath creates a ShortestPath operator.

  • input is the upstream operator supplying (srcID, dstID) pairs.
  • fwd is the forward CSR adjacency.
  • rev is the reverse CSR (required for DirIn/DirBoth).
  • dir is the traversal direction.
  • srcCol / dstCol are the column indices in each input row for source and destination node IDs.

The returned operator has no type filter and no hop bounds; use ShortestPath.WithTypeFilter and ShortestPath.WithHopBounds to configure them.

func (*ShortestPath) Close

func (op *ShortestPath) Close() error

Close closes the input operator.

func (*ShortestPath) Init

func (op *ShortestPath) Init(ctx context.Context) error

Init initialises the operator.

func (*ShortestPath) Next

func (op *ShortestPath) Next(out *Row) (bool, error)

Next emits one row per input row, containing the shortest path. When no path exists the row is emitted with a Null path (OPTIONAL MATCH) or dropped (MATCH) depending on the operator's optional flag.

func (*ShortestPath) PlanChildren added in v0.11.0

func (op *ShortestPath) PlanChildren() []Operator

PlanChildren reports the operator whose rows it searches paths from.

func (*ShortestPath) WithHopBounds added in v0.6.0

func (op *ShortestPath) WithHopBounds(minHops, maxHops int) *ShortestPath

WithHopBounds sets the accepted path-length window. minHops is the minimum length (a zero-length src==dst path is reported only when minHops == 0); maxHops caps the BFS depth ([shortestNoMaxHops] == 0 means unbounded). It returns op for chaining.

func (*ShortestPath) WithOptional added in v0.6.0

func (op *ShortestPath) WithOptional(optional bool) *ShortestPath

WithOptional selects the OPTIONAL MATCH no-path behaviour (emit a Null-path row) when optional is true; the default (false) drops the row when no path exists, the MATCH behaviour. It returns op for chaining.

func (*ShortestPath) WithPathPredicate added in v0.6.0

func (op *ShortestPath) WithPathPredicate(pred func(Row) (bool, error)) *ShortestPath

WithPathPredicate fuses a whole-path predicate onto the operator (#1786). The operator then returns the shortest path that SATISFIES pred (an exhaustive search), instead of the unconstrained shortest path. pred is called with the candidate's full output row. It returns op for chaining.

func (*ShortestPath) WithTypeFilter added in v0.6.0

func (op *ShortestPath) WithTypeFilter(edgeType string) *ShortestPath

WithTypeFilter restricts traversal to edges whose forward position is present in filter. edgeType is the non-empty "a filter was requested" gate (typically the pattern's first declared relationship type). It returns op for chaining.

Callers configure the operator before Init, as the planner does. Changing the filter afterwards nonetheless invalidates the reverse-position admit bitset derived from it, so this clears the once-only build flag rather than leaving a bitset that describes a filter no longer in force.

func (*ShortestPath) WithWorkBudget added in v0.7.0

func (op *ShortestPath) WithWorkBudget(maxPerRow, maxTotal int) *ShortestPath

WithWorkBudget overrides the exhaustive path-predicate search's per-input-row and aggregate per-query edge-traversal caps (see [ShortestPath.maxEdgesTraversed]). A non-positive value leaves the corresponding default in place. It returns op for chaining and is primarily a testing seam; production uses the defaults.

type SingleRow

type SingleRow struct {
	// contains filtered or unexported fields
}

SingleRow emits one empty row then signals exhaustion.

SingleRow is NOT safe for concurrent use.

func NewSingleRowOperator

func NewSingleRowOperator() *SingleRow

NewSingleRowOperator returns a SingleRow operator.

func (*SingleRow) Close

func (op *SingleRow) Close() error

Close is a no-op; SingleRow holds no resources.

func (*SingleRow) Init

func (op *SingleRow) Init(ctx context.Context) error

Init resets the operator state.

func (*SingleRow) Next

func (op *SingleRow) Next(out *Row) (bool, error)

Next emits one empty row on the first call and returns false on all subsequent calls.

type Skip

type Skip struct {
	// contains filtered or unexported fields
}

Skip is a Volcano pipeline operator that discards the first n rows from its child operator and then forwards all remaining rows.

Skip is NOT safe for concurrent use.

func NewSkip

func NewSkip(child Operator, n int64) (*Skip, error)

NewSkip creates a Skip operator that discards the first n rows from child. n must be ≥ 0; a skip of 0 is a no-op pass-through.

func (*Skip) Close

func (op *Skip) Close() error

Close releases resources and closes the child operator.

func (*Skip) Init

func (op *Skip) Init(ctx context.Context) error

Init initialises the operator and resets the skip counter.

func (*Skip) Next

func (op *Skip) Next(out *Row) (bool, error)

Next discards rows until n have been skipped, then forwards subsequent rows.

func (*Skip) PlanChildren added in v0.11.0

func (op *Skip) PlanChildren() []Operator

PlanChildren reports the input whose leading rows it discards.

type SlabPool

type SlabPool struct {
	// contains filtered or unexported fields
}

SlabPool is a sync.Pool-backed pool of RowSlab instances with a fixed column width and capacity. Operators that process a high volume of rows should obtain slabs from a shared pool to reduce GC pressure.

SlabPool is safe for concurrent use.

func NewSlabPool

func NewSlabPool(width, capacity int) *SlabPool

NewSlabPool creates a SlabPool that vends RowSlabs with the given column width and row capacity.

func (*SlabPool) Get

func (sp *SlabPool) Get() *RowSlab

Get retrieves a reset RowSlab from the pool, or allocates a new one.

func (*SlabPool) Put

func (sp *SlabPool) Put(s *RowSlab)

Put resets s and returns it to the pool.

type Sort

type Sort struct {
	// contains filtered or unexported fields
}

Sort is a blocking Volcano operator that collects all rows from its child, sorts them by the specified SortKey sequence, and emits them in order.

Sort is NOT safe for concurrent use.

func NewSort

func NewSort(child Operator, keys []SortKey, maxRows int) (*Sort, error)

NewSort creates a Sort operator.

  • child: the upstream operator to consume.
  • keys: ORDER BY specification. Must not be empty.
  • maxRows: upper bound on rows held in memory; pass 0 to use DefaultMaxSortRows.

func (*Sort) Close

func (op *Sort) Close() error

Close closes the child operator and releases internal storage.

func (*Sort) Init

func (op *Sort) Init(ctx context.Context) error

Init initialises the operator. The blocking collect+sort phase is deferred to the first Next call.

func (*Sort) Next

func (op *Sort) Next(out *Row) (bool, error)

Next emits the next sorted row. On the first call it collects and sorts all rows from the child (pipeline breaker). Subsequent calls step through the sorted slice.

func (*Sort) PlanChildren added in v0.11.0

func (op *Sort) PlanChildren() []Operator

PlanChildren reports the input it orders.

func (*Sort) WithByteBudget added in v0.7.0

func (op *Sort) WithByteBudget(maxBytes int64, estimateRow func(Row) int64) *Sort

WithByteBudget bounds the estimated retained size of the buffered rows by maxBytes, using estimateRow for the per-row estimate. It complements the maxRows count cap so a few large-valued rows cannot exceed the engine's result-byte budget before the count cap fires (#1841). A non-positive maxBytes or nil estimateRow leaves the byte dimension disabled. Returns op for chaining and must be called before Init.

type SortKey

type SortKey struct {
	// Eval is an optional expression evaluator. When non-nil the sort key
	// value is obtained by calling Eval(row) rather than reading row[ColIdx].
	// This supports ORDER BY expressions that are not direct projection
	// output columns (e.g. ORDER BY n.age after RETURN n).
	Eval func(Row) (expr.Value, error)
	// ColIdx is the zero-based index of the column within each Row.
	// Ignored when Eval is non-nil.
	ColIdx int
	// Ascending controls the sort direction. true = ASC, false = DESC.
	Ascending bool
}

SortKey describes a single ORDER BY column.

type StaticRows added in v0.9.0

type StaticRows struct {
	// contains filtered or unexported fields
}

StaticRows emits a fixed slice of rows, one per Next call, in slice order.

StaticRows is NOT safe for concurrent use.

func NewStaticRows added in v0.9.0

func NewStaticRows(rows []Row) *StaticRows

NewStaticRows creates a StaticRows operator over rows. The slice is retained by reference and never modified; a nil or empty slice yields an operator that emits no rows.

func (*StaticRows) Close added in v0.9.0

func (op *StaticRows) Close() error

Close releases the row reference. It is idempotent and always returns nil.

func (*StaticRows) Init added in v0.9.0

func (op *StaticRows) Init(ctx context.Context) error

Init stores ctx and rewinds the cursor to the first row.

func (*StaticRows) Next added in v0.9.0

func (op *StaticRows) Next(out *Row) (bool, error)

Next emits the next buffered row, or (false, nil) once every row has been emitted. It honours context cancellation on every call.

type StorageRecordScan added in v0.11.0

type StorageRecordScan interface {
	// contains filtered or unexported methods
}

StorageRecordScan marks an operator that READS RECORDS FROM STORAGE, one record per row it emits, so its logical storage-access count (its db-hits) equals its emitted row count (rmp #2238).

Why a marker rather than a counter

Db-hits exist to distinguish a selective seek from a scan that filtered afterwards: both can emit the same handful of rows while touching wildly different amounts of storage. That distinction lives entirely in the LEAVES — which records were read — and every operator above them consumes rows its children already produced, touching no storage of its own.

For a leaf, "records read" and "rows emitted" are the same number by construction:

  • a label or all-nodes scan yields one node record per emitted row;
  • an index seek, seek-set or range scan yields one node record per posting-list entry it emits;
  • an expand yields one relationship record per emitted neighbour.

So the count is available at the operator boundary, where the profiling wrapper already sits, and needs no counter threaded through any accessor. That is not a shortcut but the point: with nothing threaded, a non-PROFILE Run executes no counting CODE AT ALL — there is not even a nil check to skip on the hot path.

What this deliberately does not count

PROPERTY READS. Neo4j charges a db-hit per property access, so its numbers for a filter-heavy plan are larger than GoGraph's. Counting them here would mean threading a counter into the property accessors — precisely the hot path the paragraph above protects — and would make every ordinary query pay for a diagnostic. The divergence is documented in docs/cypher.md rather than papered over with an estimate, because a db-hits figure that silently blends measured leaf reads with guessed property reads would be less useful than one whose meaning is exact.

An operator that does not implement this interface reports 0 db-hits, which is the honest answer for a pure row transformer.

type StringHashIndex

type StringHashIndex struct {
	// contains filtered or unexported fields
}

StringHashIndex adapts hash.Index[string] to the HashLookup interface. It accepts only expr.StringValue seek keys; other kinds return ErrIndexTypeMismatch.

func NewStringHashIndex

func NewStringHashIndex(idx interface {
	LookupAppend(value string, dst []uint64) []uint64
}) *StringHashIndex

NewStringHashIndex constructs a StringHashIndex.

func (*StringHashIndex) LookupAppend added in v0.6.0

func (h *StringHashIndex) LookupAppend(value expr.Value, dst []uint64) ([]uint64, error)

LookupAppend implements HashLookup.

type StringRangeIndex

type StringRangeIndex struct {
	// contains filtered or unexported fields
}

StringRangeIndex adapts btree.Index[string] to the [rangeLookup] interface. An unbounded lower bound is "" (the true minimum of the string order); an unbounded (or non-string) upper bound routes to the index's open-ended RangeFrom scan rather than a fixed sentinel — no fixed key is a true maximum for a variable-length string, so a sentinel cap would silently drop any key sorting above it (#F-CY1).

func NewStringRangeIndex

func NewStringRangeIndex(idx interface {
	Range(lo, hi string) *roaring64.Bitmap
	RangeFrom(lo string) *roaring64.Bitmap
}) *StringRangeIndex

NewStringRangeIndex constructs a StringRangeIndex.

func (*StringRangeIndex) RangeBitmap

func (r *StringRangeIndex) RangeBitmap(lo, hi expr.Value) *roaring64.Bitmap

RangeBitmap implements [rangeLookup]. An unbounded-above range (nil/NULL or a non-string upper bound) scans open-ended via RangeFrom so the bitmap is a genuine superset of every key >= lo (#F-CY1); a bounded upper uses the inclusive [lo, hi] Range.

type SubplanFactory added in v0.6.0

type SubplanFactory func(morsel []graph.NodeID) (Operator, error)

SubplanFactory builds an independent physical sub-plan that scans exactly the NodeIDs in morsel and applies the fused Filter/Projection over them. Each call must return a fresh operator tree that shares NO mutable state with any other call's tree (the planner achieves this with a per-worker schema map and a per-worker buildOpts copy whose lazily-written fields are zeroed). The morsel slice is owned by the caller (ParallelScanProject) and is read-only for the lifetime of the returned operator; the factory must not retain or mutate it beyond feeding it to the morsel scan leaf.

The returned operator is driven Init → Next* → Close by exactly one worker goroutine. It must honour the standard Operator lifecycle.

type TargetEvalFn

type TargetEvalFn func(row Row) (expr.Value, error)

TargetEvalFn evaluates a DELETE / DETACH DELETE target expression against the current input row and returns the resolved value. The exec operator inspects the value: NodeValue / IntegerValue selects the node by ID; RelationshipValue selects the relationship; null is a row-passthrough no-op (matches openCypher 9 §3.5.8).

type Top

type Top struct {
	// contains filtered or unexported fields
}

Top is a blocking Volcano operator that emits the N smallest rows (per the given sort keys) from its child, using a bounded heap for O(M log N) memory and time.

Top is NOT safe for concurrent use.

func NewTop

func NewTop(child Operator, keys []SortKey, n int) (*Top, error)

NewTop creates a Top operator.

  • child: the upstream operator to consume.
  • keys: ORDER BY specification. Must not be empty.
  • n: number of rows to return. Must be ≥ 0; n == 0 yields an empty result while still draining the child (ORDER BY … LIMIT 0, see #1801).

func (*Top) Close

func (op *Top) Close() error

Close closes the child operator and releases internal storage.

func (*Top) Init

func (op *Top) Init(ctx context.Context) error

Init initialises the operator. The blocking consume phase is deferred to the first Next call.

func (*Top) Next

func (op *Top) Next(out *Row) (bool, error)

Next emits the next top-N row in sorted order. On the first call it consumes all rows from the child and finalises the heap.

func (*Top) PlanChildren added in v0.11.0

func (op *Top) PlanChildren() []Operator

PlanChildren reports the input it orders and truncates.

type Union

type Union struct {
	// contains filtered or unexported fields
}

Union emits the set-union of left and right: all rows from both sides with duplicates removed. It is implemented as UnionAll wrapped in a Distinct operator.

Schema mismatch detection is inherited from UnionAll.

Union is NOT safe for concurrent use.

func NewUnion

func NewUnion(left, right Operator, maxDistinct int) *Union

NewUnion creates a Union operator that deduplicates the concatenation of left and right.

  • maxDistinct: upper bound on distinct rows; pass 0 to use DefaultMaxDistinct.

func (*Union) Close

func (op *Union) Close() error

Close releases all resources.

func (*Union) Init

func (op *Union) Init(ctx context.Context) error

Init initialises the operator.

func (*Union) Next

func (op *Union) Next(out *Row) (bool, error)

Next emits the next unique row from the union.

func (*Union) PlanChildren added in v0.11.0

func (op *Union) PlanChildren() []Operator

PlanChildren reports the Distinct it wraps.

Union is a Distinct over a UnionAll; reporting the Distinct keeps the rendered tree faithful to what actually executes rather than flattening two operators into one.

type UnionAll

type UnionAll struct {
	// contains filtered or unexported fields
}

UnionAll is a Volcano operator that concatenates the output of left and right children without deduplication. It validates that both sides produce rows of the same width (column count).

UnionAll is NOT safe for concurrent use.

func NewUnionAll

func NewUnionAll(left, right Operator) *UnionAll

NewUnionAll creates a UnionAll operator that concatenates left then right.

func (*UnionAll) Close

func (op *UnionAll) Close() error

Close closes both child operators. Both are always attempted; if both fail the errors are joined.

func (*UnionAll) Init

func (op *UnionAll) Init(ctx context.Context) error

Init initialises both child operators.

func (*UnionAll) Next

func (op *UnionAll) Next(out *Row) (bool, error)

Next emits rows from left until exhausted, then emits rows from right. Returns ErrSchemaMismatch if the first right-side row has a different column count than the first left-side row.

func (*UnionAll) PlanChildren added in v0.11.0

func (op *UnionAll) PlanChildren() []Operator

PlanChildren reports the two inputs whose rows it concatenates.

type Unwind

type Unwind struct {
	// contains filtered or unexported fields
}

Unwind is a Volcano pipeline operator that implements the UNWIND clause. For each input row it evaluates a list expression and emits one output row per list element, appending the element value as a new column.

Unwind is NOT safe for concurrent use.

func NewUnwind

func NewUnwind(child Operator, listFn UnwindListFn) (*Unwind, error)

NewUnwind creates an Unwind operator.

child provides the context rows. listFn is evaluated once per input row and must return the list to expand. The caller is responsible for appending the element column to the output row; Unwind handles that internally.

Both child and listFn are required: a nil argument returns the typed sentinel ErrUnwindNilChild or ErrUnwindNilListFn respectively, so callers can distinguish the cause via errors.Is. NewUnwind never panics.

func (*Unwind) Close

func (op *Unwind) Close() error

Close releases resources and closes the child operator.

Close is idempotent within a single pipeline lifecycle: calling it more than once between two Init invocations returns nil from the second and later calls and does NOT propagate to op.child.Close again. The idempotency guard is reset by Init, so an Init→Close→Init→Close sequence still closes the child twice — once per cycle, as expected.

func (*Unwind) Init

func (op *Unwind) Init(ctx context.Context) error

Init initialises the operator and its child. It clears all per-iteration state (curRow, curList, listIdx) and resets the idempotency guard (closed) so that Init is the exact dual of Close, allowing an operator instance to be safely re-Init'd after a previous Close.

func (*Unwind) Next

func (op *Unwind) Next(out *Row) (bool, error)

Next advances to the next element. It pulls a new input row from the child whenever the current list is exhausted, then emits one row per element.

Returns (true, nil) when an output row was written to out, (false, nil) at end-of-stream, (false, err) on error.

func (*Unwind) PlanChildren added in v0.11.0

func (op *Unwind) PlanChildren() []Operator

PlanChildren reports the input whose list column it expands.

type UnwindListFn

type UnwindListFn func(row Row) (expr.ListValue, error)

UnwindListFn evaluates the list expression for one input row. It returns a expr.ListValue when the expression evaluates to a list, or nil/empty when there is nothing to expand.

type ValueEvalFn

type ValueEvalFn func(row Row) (value lpg.PropertyValue, isNull bool, hasValue bool, err error)

ValueEvalFn evaluates a SET RHS expression against the current input row and returns the resulting property value plus a flag distinguishing the "no value produced" case from the "explicit null" case. The exec operator uses null/no-value semantics to either delete (null) or no-op (no value).

type VarLengthConfig

type VarLengthConfig struct {
	// EdgeType, when non-empty, restricts expansion to edges of this type.
	EdgeType string
	// ExcludedRelCols lists column indices in the input row holding edge
	// identifiers (IntegerValue or RelationshipValue) that must not be
	// traversed inside this VLE step. Implements the openCypher
	// no-repeated-relationships rule across distinct rel patterns within
	// the same MATCH (e.g. `MATCH ()-[r:EDGE]-() MATCH (n)-[*0..1]-()-[r]
	// -()-[*0..1]-(m)` — the two variable-length steps must not reuse the
	// edge bound to `r`). The visited bitset is pre-populated with each
	// listed column's edge position at BFS seed time.
	ExcludedRelCols []int
	// InputCol is the column index in each input row that holds the source
	// NodeID. Defaults to 0.
	InputCol int
	// MinHops is the minimum path length (inclusive). Must be ≥ 0.
	MinHops int
	// MaxHops is the maximum path length (inclusive). Must be ≥ MinHops.
	// Use math.MaxInt for unbounded (not recommended without a safety cap).
	MaxHops int
	// MaxEdgesTraversed is the safety cap on edge traversals per input row.
	// Defaults to [defaultMaxEdgesTraversed] (1,000,000) when 0.
	MaxEdgesTraversed int
	// MaxTotalEdgesTraversed is the aggregate safety cap on edge traversals
	// across all input rows for the whole query — it is NOT reset per row, so it
	// bounds the M × (per-row cost) multiplication that the per-row cap alone
	// cannot (#1478). Defaults to [defaultMaxTotalEdgesTraversed] (100,000,000)
	// when 0.
	MaxTotalEdgesTraversed int
	// Direction to follow. Defaults to DirOut when zero.
	Direction Direction
}

VarLengthConfig carries configuration for NewVarLengthExpand.

type VarLengthExpand

type VarLengthExpand struct {
	// contains filtered or unexported fields
}

VarLengthExpand is a Volcano pipeline operator that performs bounded BFS variable-length expansion.

VarLengthExpand is NOT safe for concurrent use.

func NewVarLengthExpand

func NewVarLengthExpand(input Operator, src AdjacencySource, cfg *VarLengthConfig) *VarLengthExpand

NewVarLengthExpand creates a VarLengthExpand operator. cfg is read-only and taken by pointer to avoid copying the configuration struct on this hot path.

func (*VarLengthExpand) Close

func (op *VarLengthExpand) Close() error

Close releases resources.

func (*VarLengthExpand) Init

func (op *VarLengthExpand) Init(ctx context.Context) error

Init initialises the operator.

func (*VarLengthExpand) Next

func (op *VarLengthExpand) Next(out *Row) (bool, error)

Next emits the next (inputRow... || pathEdgesAsListValue || dstNodeID) row. The path is encoded as a expr.ListValue of edge positions (IntegerValues), followed by the destination node ID as an IntegerValue.

func (*VarLengthExpand) PlanChildren added in v0.11.0

func (op *VarLengthExpand) PlanChildren() []Operator

PlanChildren reports the operator whose rows it expands from.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL