cypher

package
v0.3.2 Latest Latest
Warning

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

Go to latest
Published: Jun 15, 2026 License: MIT Imports: 33 Imported by: 0

Documentation

Overview

Package cypher provides the public query engine API for the GoGraph Cypher executor.

Usage

g := lpg.New[string, float64](adjlist.Config{})
// ... populate graph ...

engine := cypher.NewEngine(g)
result, err := engine.Run(ctx, "MATCH (n) RETURN n", nil)
if err != nil { ... }
defer result.Close()
for result.Next() {
    rec := result.Record()
    _ = rec
}

Plan cache

Engine caches parsed and translated logical plans together with the semantic-analysis verdict in a bounded LRU keyed by the query string. The cached entry is a *planCacheEntry; the physical build step runs per Engine.Run call so that per-call executor state is fresh. Semantically invalid queries are also cached (with the typed error) so that repeated runs of the same bad query short-circuit without re-parsing.

The default capacity is DefaultPlanCacheCapacity (1024 entries). Configure a different bound via EngineOptions.PlanCacheCapacity and the NewEngineWithOptions constructor. Eviction is least-recently-used and emits the cypher.plan_cache.evictions counter on the global metrics surface; hits and misses are reported under cypher.plan_cache.hits and cypher.plan_cache.misses.

Concurrency

Engine is safe for concurrent use. Each Run call creates an independent physical operator tree. The plan cache itself serialises its structural updates on a single sync.Mutex; the cached *planCacheEntry is immutable once published, so callers operate on the returned pointer without further synchronisation.

Write queries serialise on a single-writer lock: the backing txn.Store's writer mutex when the engine is WAL-backed (NewEngineWithStore), or the engine's own writer mutex when it is store-less (NewEngine). Autocommit Engine.RunInTx holds that lock for one statement; an explicit transaction (Engine.BeginTx) holds it from BEGIN until COMMIT/ROLLBACK, so concurrent writers block until it finishes (write-write isolation). Reads (Engine.Run) never take the writer lock. The lock order is writer-lock (outermost) → the graph visibility barrier (visMu, inside lpg.Graph.ApplyAtomically); both wirings share it, so no deadlock is possible across them.

Transactions

Engine.RunInTx is autocommit: each call is its own all-or-nothing, durable-then-visible transaction. Engine.BeginTx opens an explicit, multi-statement transaction (ExplicitTx) whose statements commit or roll back together — the engine substrate for the Bolt BEGIN/RUN/COMMIT/ROLLBACK protocol. Both apply writes eagerly to the in-memory graph and roll back via the in-memory undo log on error; a concurrent reader can therefore observe an open transaction's not-yet-committed writes (read-uncommitted for readers). See ExplicitTx (exectx.go) for the full transaction and isolation contract.

Index

Examples

Constants

View Source
const DefaultMaxResultBytes int64 = 1 << 30 // 1 GiB

DefaultMaxResultBytes is the default upper bound on the aggregate estimated encoded size of the rows a single Engine.Run or Engine.RunInTx call materialises when EngineOptions.MaxResultBytes is left at its zero value. It is a coarse budget against the worst case the row cap alone cannot catch — a result that stays under DefaultMaxResultRows yet carries enough bytes per row to exhaust memory inside the visibility barrier. The default (1 GiB) is set high enough that ordinary queries, the openCypher TCK, and all examples stay well below it; callers that genuinely need an unbounded result size must opt out explicitly with MaxResultBytesUnlimited.

View Source
const DefaultMaxResultRows int64 = 10_000_000

DefaultMaxResultRows is the default upper bound on the number of rows a single Engine.Run or Engine.RunInTx call materialises when EngineOptions.MaxResultRows is left at its zero value. It bounds the worst case — an unintentional whole-graph scan or Cartesian product — so the engine never materialises an unbounded number of rows into memory inside the visibility barrier. It matches the sibling pipeline-breaker caps (exec.DefaultMaxSortRows, exec.DefaultMaxDistinct) and is set high enough that ordinary queries, the openCypher TCK, and all examples stay well below it; callers that genuinely need an unbounded result must opt out explicitly with MaxResultRowsUnlimited.

View Source
const DefaultPlanCacheCapacity = 1024

DefaultPlanCacheCapacity is the default upper bound on the number of entries held by an Engine's plan cache. Chosen so that a typical OLTP workload — the same hundreds of queries reissued by connection pools and ORMs — stays entirely in-cache without unbounded growth under high query-text churn (parameter-baked queries, ad-hoc analytics, fuzzed input).

Configure a different capacity via EngineOptions.PlanCacheCapacity; pass 0 to use the default, or a positive integer to override. A negative value is rejected at constructor time as a configuration error.

View Source
const MaxCollectItemsUnlimited = -1

MaxCollectItemsUnlimited is the explicit opt-out sentinel for EngineOptions.MaxCollectItems: set the field to this value to disable the per-group element budget entirely and allow an unbounded buffering aggregator (collect / percentile). It is distinct from the zero value, which selects funcs.DefaultMaxCollectItems. Use it only when memory is bounded by another means, because an unbounded `collect(n)` over a whole-graph scan then materialises every value into one list under the graph's visibility barrier.

View Source
const MaxResultBytesUnlimited int64 = -1

MaxResultBytesUnlimited is the explicit opt-out sentinel for EngineOptions.MaxResultBytes: set the field to this value to disable the aggregate-byte budget entirely. It is distinct from the zero value, which selects DefaultMaxResultBytes. Use it only when memory is bounded by another means, because an unbounded wide-row result then materialises every byte under the graph's visibility barrier.

View Source
const MaxResultRowsUnlimited int64 = -1

MaxResultRowsUnlimited is the explicit opt-out sentinel for EngineOptions.MaxResultRows: set the field to this value to disable the row cap entirely and allow an unbounded result. It is distinct from the zero value, which selects DefaultMaxResultRows. Use it only when the caller can bound memory by another means (e.g. streaming the result and closing it promptly), because an unbounded MATCH then materialises every row under the graph's visibility barrier.

Variables

View Source
var ErrInternalPanic = errors.New("cypher: internal panic")

ErrInternalPanic wraps a recoverable panic that occurred while planning or executing a query on behalf of a single caller. The engine's query entrypoints (Engine.Run, Engine.RunInTx, Engine.RunAny, Engine.RunInTxAny) install a recover boundary so that such a panic — an index-out-of-range on a malformed plan, a nil dereference, a future bug — is converted into this error and returned to the caller instead of unwinding past the engine and crashing the embedding process. Callers may match it with errors.Is.

The returned error deliberately carries only the panic value, never a stack trace: the full trace (via runtime/debug.Stack) is logged to the default slog handler so internal details are not leaked to the caller. This is defence-in-depth against recoverable panics; a Go fatal runtime error (an uncatchable stack overflow) cannot be intercepted here and is instead prevented upstream by the parser's length/nesting guards.

View Source
var ErrResultBytesExceeded = errors.New("cypher: result byte budget exceeded")

ErrResultBytesExceeded is returned by Result.Err when the cumulative estimated encoded size of the materialised rows exceeds EngineOptions.MaxResultBytes. It complements ErrResultRowsExceeded: the row cap bounds the *number* of rows, but a handful of rows carrying very large values (a node with megabyte-scale string properties) can dwarf a high row count, so the byte budget bounds that residual case. Like the row cap it is a permanent error tripped inside the visibility barrier during materialisation, before the surplus reaches the caller.

View Source
var ErrResultRowsExceeded = errors.New("cypher: result row limit exceeded")

ErrResultRowsExceeded is returned by Result.Next and Result.Err when the number of materialised rows exceeds EngineOptions.MaxResultRows. It is a permanent error: once set, subsequent Next calls return false.

View Source
var ErrTxFinished = errors.New("cypher: explicit transaction already finished")

ErrTxFinished is returned by ExplicitTx.Exec, ExplicitTx.Commit, and ExplicitTx.Rollback when the transaction has already been committed or rolled back. The handle holds no resources after it finishes — the writer serialisation is released and any WAL transaction is closed — so a stale call is rejected rather than acting on a released transaction. Matchable with errors.Is.

View Source
var ErrTxPoisoned = errors.New("cypher: transaction poisoned by a prior failed Exec statement — call Rollback")

ErrTxPoisoned is returned by ExplicitTx.Commit when a prior ExplicitTx.Exec call returned an ErrStatementPipeline error. A poisoned transaction cannot be committed — its partial writes must be unwound by calling ExplicitTx.Rollback instead. Matchable with errors.Is.

View Source
var ErrUndoFailed = errors.New("cypher: in-memory transaction undo failed; graph may be inconsistent until reopen")

ErrUndoFailed is returned when the in-memory transaction-undo replay itself fails — an inverse operation panicked while rolling back a write query's eager mutations. It is the in-memory analogue of txn.ErrCommittedNotApplied: it signals that the graph may be left in a state that neither fully contains nor fully excludes the failed transaction, so the inconsistency is surfaced to the caller (and counted via metrics) rather than silently ignored. A WAL-backed store reconciles to the durable state on the next reopen; the in-memory engine has no such backstop, so the caller must treat the graph as suspect. Callers may match it with errors.Is.

View Source
var ErrUnsupportedParamType = errors.New("cypher: unsupported parameter type")

ErrUnsupportedParamType is the sentinel wrapped by BindParams when a parameter value's Go type cannot be converted to an expr.Value (for example, a Bolt Point or temporal Struct sent as a raw parameter). It is a CLIENT fault — the request carried a value the engine cannot bind — so a front-end can classify it via errors.Is and map it to the appropriate client-error status (the Bolt server maps it to Neo.ClientError.Statement.TypeError). The wrapped message names only the offending Go type, which is the caller's own input and discloses nothing about internal server state.

Functions

func BindParams

func BindParams(params map[string]any) (map[string]expr.Value, error)

BindParams converts a map[string]any to map[string]expr.Value using the following type mapping:

  • nil → expr.Null
  • bool → expr.BoolValue
  • int, int8, int16, int32, int64 → expr.IntegerValue
  • uint, uint8, uint16, uint32, uint64 → expr.IntegerValue (truncated to int64)
  • float32, float64 → expr.FloatValue
  • string → expr.StringValue
  • []any → expr.ListValue (recursively converted)
  • map[string]any → expr.MapValue (recursively converted)
  • expr.Value → passed through unchanged

Returns an error wrapping ErrUnsupportedParamType for any value whose Go type is not in the list above.

Example

ExampleBindParams converts a map of Go values into the engine's internal parameter representation. Engine.RunAny calls this for you; BindParams is exported for callers that bind once and run a query repeatedly.

package main

import (
	"fmt"

	"github.com/FlavioCFOliveira/GoGraph/cypher"
)

func main() {
	bound, err := cypher.BindParams(map[string]any{
		"name": "acme",
		"size": int64(42),
	})
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	_, hasName := bound["name"]
	_, hasSize := bound["size"]
	fmt.Printf("bound=%d name=%v size=%v\n", len(bound), hasName, hasSize)
}
Output:
bound=2 name=true size=true

func BuildPlan

func BuildPlan(
	plan ir.LogicalPlan,
	walker nodeWalkerIface,
	labelSrc labelResolverIface,
	reg expr.FunctionRegistry,
	params map[string]expr.Value,
) (op exec.Operator, cols []string, err error)

BuildPlan converts an IR ir.LogicalPlan tree into a physical exec.Operator tree together with the ordered output column names.

walker provides node enumeration; labelSrc provides label-filtered scans; reg provides the built-in function registry; params are the query parameters.

Sprint 25 support matrix:

func BuildPlanWithMutator

func BuildPlanWithMutator(
	plan ir.LogicalPlan,
	walker nodeWalkerIface,
	labelSrc labelResolverIface,
	reg expr.FunctionRegistry,
	params map[string]expr.Value,
	mutator exec.GraphMutator,
) (op exec.Operator, cols []string, err error)

BuildPlanWithMutator converts an IR ir.LogicalPlan tree into a physical exec.Operator tree, supporting both read and write IR operators. The mutator provides the write surface for CREATE, SET, REMOVE, DELETE, and MERGE operators.

For read-only plans the behaviour is identical to BuildPlan; the mutator is only invoked when a write IR node is encountered.

func QueryHasWritingClause

func QueryHasWritingClause(query string) bool

QueryHasWritingClause reports whether the query string contains any writing keyword (CREATE, MERGE, SET, REMOVE, DELETE, DETACH) outside a DDL prefix, i.e. whether it must be routed through Engine.RunInTx rather than Engine.Run. This is a textual heuristic: it avoids triggering the plan-cache machinery on a second pass, which would otherwise double-count hits and misses in concurrency tests.

External front-ends that classify queries as read vs write (for example, to serialise writers or pick a read replica) should call this rather than re-deriving the keyword set, so the classification stays in lockstep with Engine.RunAny.

The heuristic is intentionally permissive — false positives (writing keywords inside string literals or backtick identifiers) merely cause a read-only query to be routed through RunInTx, which executes identical semantics with the same correctness guarantees, only with the cost of opening and committing a write transaction.

Types

type ConstraintDef added in v0.2.0

type ConstraintDef struct {
	// Unique is true for a UNIQUE constraint, false for a NOT NULL constraint.
	Unique bool
	// Label is the constrained node label.
	Label string
	// Property is the constrained property key.
	Property string
	// Name is the user-defined constraint name.
	Name string
}

ConstraintDef is a durable constraint definition handed to the engine on open so it can re-register a constraint recovered from disk. It mirrors store/recovery.ConstraintRecord without coupling callers to the recovery package's wire types; ConstraintDefsFromRecovery converts a recovery result into this form.

func ConstraintDefsFromRecovery added in v0.2.0

func ConstraintDefsFromRecovery(recovered []recovery.ConstraintRecord) []ConstraintDef

ConstraintDefsFromRecovery converts the durable constraint set surfaced by store/recovery.Open into the ConstraintDef slice the engine constructor accepts via EngineOptions.RecoveredConstraints. Pass it the store/recovery.Result.Constraints field. The recovery package's wire kind (txn.ConstraintKind: 0 = UNIQUE, 1 = NOT NULL) is mapped to the boolean ConstraintDef.Unique.

type Engine

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

Engine is the public query engine. It binds a graph, a function registry, and a plan cache, and exposes a single Run method for query execution.

Engine is safe for concurrent use. A single Engine may serve any number of concurrent Engine.Run readers together with concurrent Engine.RunInTx writers: each call builds its own operator tree, the plan cache is internally synchronised, and both the physical-plan build and execution run under the graph's visibility barrier (lpg.Graph.View for reads, lpg.Graph.ApplyAtomically for writes). A writer that grows the node space can therefore never tear a concurrent reader's plan build, and readers never observe a partially-applied write transaction (#1077, audit gap F3).

Write queries remain subject to the underlying store's single-writer constraint: when the Engine is backed by a txn.Store, concurrent Engine.RunInTx calls serialise on the store's writer mutex.

func NewEngine

func NewEngine(g *lpg.Graph[string, float64]) *Engine

NewEngine creates an Engine backed by g. The default built-in function registry (funcs.DefaultRegistry) and the default plan cache capacity (DefaultPlanCacheCapacity) are used. Use NewEngineWithOptions when a non-default function registry or plan cache capacity is required.

If g has no index.Manager attached yet, NewEngine installs a new empty one so that DDL statements (CREATE INDEX / DROP INDEX) work out of the box.

Example

ExampleNewEngine shows the minimal setup: build an empty labelled property graph and bind it to an Engine ready to run queries.

package main

import (
	"context"
	"fmt"

	"github.com/FlavioCFOliveira/GoGraph/cypher"
	"github.com/FlavioCFOliveira/GoGraph/graph/adjlist"
	"github.com/FlavioCFOliveira/GoGraph/graph/lpg"
)

func main() {
	g := lpg.New[string, float64](adjlist.Config{})
	eng := cypher.NewEngine(g)

	// A fresh engine over an empty graph runs queries that return no rows.
	res, err := eng.Run(context.Background(), "MATCH (n) RETURN n", nil)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	defer res.Close()

	var rows int
	for res.Next() {
		rows++
	}
	fmt.Println("rows:", rows)
}
Output:
rows: 0

func NewEngineWithOptions

func NewEngineWithOptions(g *lpg.Graph[string, float64], opts EngineOptions) *Engine

NewEngineWithOptions creates an Engine backed by g with explicit options. Zero-valued fields are filled with their documented defaults. When opts.Store is non-nil, the Engine is bound to that WAL-enabled txn.Store in addition to g.

If g has no index.Manager attached yet, a new empty one is installed.

func NewEngineWithRegistry

func NewEngineWithRegistry(g *lpg.Graph[string, float64], reg expr.FunctionRegistry) *Engine

NewEngineWithRegistry creates an Engine backed by g using a custom function registry and the default plan cache capacity.

If g has no index.Manager attached yet, a new empty one is installed.

func NewEngineWithStore

func NewEngineWithStore(store *txn.Store[string, float64]) *Engine

NewEngineWithStore creates an Engine backed by a WAL-enabled txn.Store using the default plan cache capacity.

All write queries routed through Engine.RunInTx use a single txn.Tx for atomicity and WAL durability: mutations are applied eagerly to the in-memory graph (so reads within the same transaction see the writes) and the WAL is fsynced on Result.Close when no pipeline error occurred.

The underlying graph is taken from store.Graph(). If the graph has no index.Manager attached yet, a new empty one is installed.

func NewEngineWithStoreAndConstraints added in v0.2.0

func NewEngineWithStoreAndConstraints(store *txn.Store[string, float64], recovered []recovery.ConstraintRecord) *Engine

NewEngineWithStoreAndConstraints creates a WAL-backed Engine that also re-registers the schema constraints recovered from disk. It is the recommended constructor for opening a persisted store: pass the store/recovery.Result.Constraints surfaced by the open that produced store, so a constraint declared before a crash is enforced again (audit gap H1). Using the plain NewEngineWithStore on a recovered store leaves the constraint registry empty and duplicates would be silently accepted.

recovered is converted via ConstraintDefsFromRecovery; pass nil (or use NewEngineWithStore) when there are no recovered constraints.

func NewEngineWithStoreAndSchema added in v0.3.0

func NewEngineWithStoreAndSchema(store *txn.Store[string, float64], constraints []recovery.ConstraintRecord, indexes []recovery.IndexRecord) *Engine

NewEngineWithStoreAndSchema creates a WAL-backed Engine that re-registers both the schema constraints and the secondary index definitions recovered from disk. It is the recommended constructor when opening a persisted store that may contain user-created indexes (task #1343). Pass the store/recovery.Result fields directly:

cypher.NewEngineWithStoreAndSchema(store, res.Constraints, res.Indexes)

Using NewEngineWithStoreAndConstraints on a store that also has indexes leaves the index manager populated only from the WAL-replayed index change events (not from the durable index definitions), so the planner's NodeByIndexSeek rewrites may serve an empty index immediately after restart. This constructor closes that gap by calling [registerRecoveredIndexes].

func (*Engine) BeginTx added in v0.2.0

func (e *Engine) BeginTx(ctx context.Context) (*ExplicitTx, error)

BeginTx opens an explicit, multi-statement transaction bound to ctx and acquires the engine's writer serialisation: the store's single-writer mutex on a WAL-backed engine, or the engine writer mutex on a store-less engine. The caller MUST finish the returned handle with exactly one ExplicitTx.Commit or ExplicitTx.Rollback; until then the writer serialisation is held and concurrent writers block (write-write Isolation).

ctx bounds every statement executed through the handle. Pass the connection context (optionally narrowed with a transaction timeout) so that a cancelled connection, a server shutdown, or an elapsed timeout interrupts an in-flight statement and guarantees the writer serialisation cannot be held forever.

If ctx is already cancelled or its deadline has elapsed, BeginTx returns promptly without acquiring any lock, with an error wrapping the context error (matchable via errors.Is against context.Canceled / context.DeadlineExceeded).

See exectx.go for the full transaction and concurrency contract, including the read-committed isolation scope: concurrent readers block while this transaction is open and observe only the committed state once it ends (task #1412).

func (*Engine) ClearPlanCache

func (e *Engine) ClearPlanCache()

ClearPlanCache drops every cached plan and increments the cypher.plan_cache.invalidations counter exactly once. It is the operator-facing invalidation hook installed on every DDL operator (CREATE/DROP INDEX, CREATE/DROP CONSTRAINT) — successful schema mutations call it so that subsequent queries re-plan against the new index / constraint topology rather than reusing stale plans built before the schema changed.

ClearPlanCache is also safe to invoke directly as a user-facing manual reset (e.g. from operational tooling after an out-of-band index swap on the underlying graph).

ClearPlanCache is idempotent and safe for concurrent use; each call emits exactly one invalidations counter increment regardless of the cache's prior size.

func (*Engine) ConstraintSpecsForSnapshot added in v0.2.0

func (e *Engine) ConstraintSpecsForSnapshot() []snapshot.ConstraintSpec

ConstraintSpecsForSnapshot converts the engine's current constraint set into the store/snapshot.ConstraintSpec slice that store/snapshot.WriteSnapshotFullWithConstraints (and its mapper-codec variant) persists into a snapshot's constraints.bin component. A checkpointer calls e.ConstraintSpecsForSnapshot() and hands the result to the writer so a checkpoint + WAL truncate does not lose constraints.

func (*Engine) Constraints added in v0.2.0

func (e *Engine) Constraints() []ConstraintDef

Constraints returns a structured snapshot of every schema constraint currently registered on the engine, in deterministic order (UNIQUE before NOT NULL, then by label, property, name). It is the source a checkpointer passes to store/snapshot.WriteSnapshotFullWithConstraints (via [ConstraintSpecsForSnapshot]) so the constraint set survives a checkpoint that truncates the WAL prefix which first declared a constraint (audit gap H1, the checkpoint-survival half).

Constraints is safe for concurrent use.

func (*Engine) Explain

func (e *Engine) Explain(query string, params map[string]expr.Value) (s string, err error)

Explain returns a textual representation of the physical plan that would be chosen to execute query with the given params. The plan reflects current index availability: a hash index on the relevant (label, property) pair causes the relevant Selection+LabelScan subtree to appear as NodeByIndexSeek. No rows are produced; the graph is not modified.

The format mirrors ir.Explain but annotates Selection→LabelScan pairs that would be rewritten to index seeks at execution time.

Example

ExampleEngine_Explain returns the logical plan for a query as text without executing it or touching the graph.

package main

import (
	"fmt"

	"github.com/FlavioCFOliveira/GoGraph/cypher"
	"github.com/FlavioCFOliveira/GoGraph/graph/adjlist"
	"github.com/FlavioCFOliveira/GoGraph/graph/lpg"
)

func main() {
	g := lpg.New[string, float64](adjlist.Config{})
	eng := cypher.NewEngine(g)

	plan, err := eng.Explain("MATCH (n:Person) RETURN n", nil)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Print(plan)
}
Output:
ProduceResults
└─ Projection
   └─ NodeByLabelScan

func (*Engine) ListIndexes added in v0.3.0

func (e *Engine) ListIndexes() []string

ListIndexes returns the names of every secondary index currently registered on the engine, in unspecified order. The list reflects the live in-memory state: it includes every index created via CREATE INDEX and excludes any dropped via DROP INDEX. It is safe for concurrent use.

This is a thin wrapper over index.Manager.ListIndexes: the test suite and embedders that need to inspect the engine's schema without importing the graph/index package can use this method directly.

func (*Engine) Procs

func (e *Engine) Procs() *procs.Registry

Procs returns the engine's procedure registry so callers can register custom procedures alongside the built-in db.* set. The returned *procs.Registry is the live, owning registry — mutations are observed immediately by every subsequent CALL <ns>.<name>() in any query parsed by this engine.

Returned registry is non-nil. Safe for concurrent use; see procs.Registry for the concurrency contract.

func (*Engine) ResultRowCap added in v0.2.0

func (e *Engine) ResultRowCap() int64

ResultRowCap reports the effective per-query result-row cap this Engine enforces, after EngineOptions.MaxResultRows has been resolved by the constructor:

  • A positive value is the active cap. A single Engine.Run or Engine.RunInTx call materialising more than this many rows trips ErrResultRowsExceeded during the in-barrier drain, before the surplus rows are ever handed to the caller.
  • Zero means the cap is disabled (the engine was built with MaxResultRowsUnlimited). Such an engine offers no upper bound on the rows a single query materialises, so an embedder exposing it to untrusted callers — for example behind the Bolt server — should bound memory by another means.

The accessor lets an embedder that receives a pre-built Engine observe its memory-safety posture without reaching into unexported state; the Bolt server uses it to warn when handed an uncapped engine.

func (*Engine) Run

func (e *Engine) Run(ctx context.Context, query string, params map[string]expr.Value) (res *Result, err error)

Run parses, analyses, plans, and executes query, returning a materialised Result. The query is built and drained inside the read visibility barrier (Graph.View) so it observes a consistent, partial-transaction-free snapshot. DDL statements take a dedicated fast path. Parameters are bound from params and type-checked against the plan before execution.

If ctx is already cancelled or its deadline has elapsed when Run is called, it returns promptly — before any parse, plan, or execution work — with an error wrapping the context error (matchable via errors.Is against context.Canceled / context.DeadlineExceeded).

A recoverable panic raised while planning or executing the query is intercepted and returned as an error wrapping ErrInternalPanic; it never unwinds past this method to crash the embedding process.

Example

ExampleEngine_Run runs a read query against a populated graph and reads a scalar aggregate from the streaming result. Result must always be closed.

package main

import (
	"context"
	"fmt"

	"github.com/FlavioCFOliveira/GoGraph/cypher"
	"github.com/FlavioCFOliveira/GoGraph/graph/adjlist"
	"github.com/FlavioCFOliveira/GoGraph/graph/lpg"
)

func main() {
	g := lpg.New[string, float64](adjlist.Config{})
	for _, key := range []string{"a", "b", "c"} {
		if err := g.AddNode(key); err != nil {
			fmt.Println("error:", err)
			return
		}
		if err := g.SetNodeLabel(key, "Person"); err != nil {
			fmt.Println("error:", err)
			return
		}
	}

	eng := cypher.NewEngine(g)
	res, err := eng.Run(context.Background(), "MATCH (n:Person) RETURN count(n) AS people", nil)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	defer res.Close()

	for res.Next() {
		rec := res.Record()
		fmt.Println("people:", rec["people"])
	}
}
Output:
people: 3

func (*Engine) RunAny

func (e *Engine) RunAny(ctx context.Context, query string, params map[string]any) (*Result, error)

RunAny executes query with params expressed as map[string]any, automatically converting Go native types to expr.Value. See BindParams for the supported conversions.

RunAny auto-detects whether the query contains writing clauses (CREATE, MERGE, SET, REMOVE, DELETE, DETACH DELETE) and routes through Engine.RunInTx when so, or Engine.Run otherwise. Callers that need an explicit choice should invoke Engine.Run / Engine.RunInTx directly.

Example

ExampleEngine_RunAny passes query parameters as a plain map[string]any, which the engine binds automatically. This is the convenient entry point for callers that do not want to import the internal value types.

package main

import (
	"context"
	"fmt"

	"github.com/FlavioCFOliveira/GoGraph/cypher"
	"github.com/FlavioCFOliveira/GoGraph/graph/adjlist"
	"github.com/FlavioCFOliveira/GoGraph/graph/lpg"
)

func main() {
	g := lpg.New[string, float64](adjlist.Config{})
	eng := cypher.NewEngine(g)

	if _, err := drainTxAny(eng,
		`CREATE (:Account {owner: "alice"}), (:Account {owner: "bob"})`); err != nil {
		fmt.Println("seed error:", err)
		return
	}

	// $owner is supplied as a Go string in the params map.
	res, err := eng.RunAny(context.Background(),
		`MATCH (a:Account) WHERE a.owner = $owner RETURN a.owner AS owner`,
		map[string]any{"owner": "bob"},
	)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	defer res.Close()
	for res.Next() {
		fmt.Println("owner:", res.Record()["owner"])
	}
}

// drainTxAny runs a write query in a transaction, draining and closing the
// result. It is a tiny helper shared by the parameter example above.
func drainTxAny(eng *cypher.Engine, query string) (int, error) {
	res, err := eng.RunInTxAny(context.Background(), query, nil)
	if err != nil {
		return 0, err
	}
	defer res.Close()
	var rows int
	for res.Next() {
		rows++
	}
	return rows, res.Err()
}
Output:
owner: "bob"

func (*Engine) RunInTx

func (e *Engine) RunInTx(ctx context.Context, query string, params map[string]expr.Value) (res *Result, err error)

RunInTx executes a write query against the engine's graph and returns a streaming Result. Unlike [Run], RunInTx inspects the IR plan for write operators; when any write operator is present it builds a mutator adapter so that write operators can modify the graph.

For the current in-memory implementation there is no external transaction manager (lpg.Graph does not support rollback). "Commit on success, rollback on error" means: the pipeline runs to completion with mutations applied eagerly; if any operator returns an error the pipeline is drained no further (standard Volcano error propagation) and the partial mutations remain in the graph. This matches the single-writer, in-memory contract documented in CLAUDE.md.

RunInTx is safe for concurrent use (each call creates an independent operator tree), subject to the single-writer constraint on write queries.

If ctx is already cancelled or its deadline has elapsed when RunInTx is called, it returns promptly — before any parse, plan, or txn.Store.Begin work — with an error wrapping the context error (matchable via errors.Is against context.Canceled / context.DeadlineExceeded).

Example

ExampleEngine_RunInTx executes a CREATE inside a transaction (atomic and, for WAL-backed engines, durable) and then reads the data back in a second query.

package main

import (
	"context"
	"fmt"

	"github.com/FlavioCFOliveira/GoGraph/cypher"
	"github.com/FlavioCFOliveira/GoGraph/graph/adjlist"
	"github.com/FlavioCFOliveira/GoGraph/graph/lpg"
)

func main() {
	g := lpg.New[string, float64](adjlist.Config{})
	eng := cypher.NewEngine(g)

	// Write: CREATE two labelled nodes atomically.
	write, err := eng.RunInTx(context.Background(),
		`CREATE (:Account {owner: "alice"}), (:Account {owner: "bob"})`, nil)
	if err != nil {
		fmt.Println("write error:", err)
		return
	}
	for write.Next() { //nolint:revive // a write query streams no result rows; drain then close
	}
	if err := write.Err(); err != nil {
		fmt.Println("write error:", err)
		return
	}
	write.Close()

	// Read-back: the committed nodes are visible to a subsequent query.
	read, err := eng.Run(context.Background(), "MATCH (a:Account) RETURN count(a) AS accounts", nil)
	if err != nil {
		fmt.Println("read error:", err)
		return
	}
	defer read.Close()
	for read.Next() {
		fmt.Println("accounts:", read.Record()["accounts"])
	}
}
Output:
accounts: 2

func (*Engine) RunInTxAny

func (e *Engine) RunInTxAny(ctx context.Context, query string, params map[string]any) (*Result, error)

RunInTxAny executes a write query with params expressed as map[string]any, automatically converting Go native types to expr.Value. See BindParams.

type EngineOptions

type EngineOptions struct {
	// Registry, when non-nil, overrides the default built-in function
	// registry used to resolve scalar function calls.
	Registry expr.FunctionRegistry

	// Store, when non-nil, binds the Engine to a WAL-enabled
	// [txn.Store]. The Engine's graph is taken from store.Graph()
	// when both Store and Graph fields are set; the explicit Graph
	// is then ignored. Run queries through [Engine.RunInTx] for
	// atomicity and WAL durability.
	Store *txn.Store[string, float64]

	// PlanCacheCapacity bounds the number of cached plans. Zero
	// selects [DefaultPlanCacheCapacity]; positive values override
	// it. A negative value is treated as misconfiguration and is
	// clamped to the default by the constructor.
	PlanCacheCapacity int

	// MaxResultRows limits the number of rows a single [Engine.Run] or
	// [Engine.RunInTx] call may materialise. If a query produces more rows than
	// the limit, the [Result] iterator returns [ErrResultRowsExceeded] from
	// [Result.Next] when the limit is hit, and [Result.Err] reports the same
	// error.
	//
	// The value is interpreted as follows:
	//
	//   - Zero (the default) selects [DefaultMaxResultRows], a finite cap that
	//     prevents an unintentional whole-graph scan or Cartesian-product query
	//     from materialising an unbounded number of rows — and holding the
	//     graph's visibility barrier — until memory is exhausted.
	//   - A positive value overrides the default. Set it to a value appropriate
	//     for the operational environment (e.g. 1_000_000 for a shared
	//     multi-tenant server).
	//   - [MaxResultRowsUnlimited] (-1) disables the cap entirely; use it only
	//     when memory is bounded by another means.
	MaxResultRows int64

	// MaxResultBytes is a coarse aggregate-BYTE budget on a single [Engine.Run]
	// or [Engine.RunInTx] result, complementing [MaxResultRows]. The row cap
	// bounds the number of rows; a small number of rows carrying very large
	// values (a node with megabyte-scale string properties) can still consume
	// large memory inside the visibility barrier under that cap. When the
	// cumulative *estimated* encoded size of the materialised rows exceeds this
	// budget, [Result.Err] reports [ErrResultBytesExceeded].
	//
	// The estimate is intentionally coarse and cheap (O(columns) per row, no
	// allocation, no serialisation): a fixed per-value overhead plus the lengths
	// of string/[]byte payloads and the element counts of lists/maps. It is a
	// guard against pathological memory use, not an exact accounting of heap
	// bytes.
	//
	// The value is interpreted as follows:
	//
	//   - Zero (the default) selects [DefaultMaxResultBytes], a finite budget.
	//   - A positive value overrides the default (a byte count).
	//   - [MaxResultBytesUnlimited] (-1) disables the budget entirely; use it
	//     only when memory is bounded by another means.
	MaxResultBytes int64

	// MaxCollectItems bounds the number of values a single buffering aggregator
	// — collect(), collect(DISTINCT …), percentileCont(), percentileDisc() —
	// retains in one group. A grouping-key-free aggregate such as
	// `RETURN collect(n)` forms exactly one group, so the group-count cap never
	// fires; without this per-aggregator budget, `MATCH (n) RETURN collect(n)`
	// would build an unbounded list inside the graph's visibility barrier.
	//
	// The value is interpreted as follows:
	//
	//   - Zero (the default) selects [funcs.DefaultMaxCollectItems], a finite cap
	//     that prevents an unbounded collect/percentile buffer from exhausting
	//     memory and holding the visibility barrier.
	//   - A positive value overrides the default.
	//   - [MaxCollectItemsUnlimited] (-1) disables the cap entirely; use it only
	//     when memory is bounded by another means.
	//
	// When the budget is exceeded the aggregator returns
	// [funcs.ErrCollectItemsExceeded], which the executor surfaces through
	// [Result.Err] (the aggregation buffers during materialisation inside the
	// barrier, so the cap trips before the whole list is built).
	MaxCollectItems int

	// RecoveredConstraints, when non-empty, are the durable schema constraints
	// recovered from disk (the [store/recovery.Result.Constraints] of the open
	// that produced Store/Graph). The constructor re-registers each one in the
	// engine's constraint registry and re-seeds every UNIQUE value-set by
	// scanning the recovered graph, so a constraint declared before a crash is
	// enforced again after recovery — without this, the registry is rebuilt
	// empty on every open and duplicates are silently accepted (audit gap H1).
	// A caller recovering a WAL-backed store from disk MUST pass these (or use
	// [NewEngineWithStoreAndConstraints]); a store-less in-memory engine leaves
	// the field nil.
	RecoveredConstraints []ConstraintDef
	// RecoveredIndexes, when non-empty, are the durable index definitions
	// recovered from the WAL (the [store/recovery.Result.Indexes] of the open
	// that produced Store/Graph). The constructor re-registers and re-backfills
	// each one in the index.Manager so a user-created index survives a crash and
	// a restart — without this, every CREATE INDEX is silently absent after
	// recovery and the planner would serve 0-row results for indexed queries
	// (audit gap: CREATE INDEX not durable). A caller recovering a WAL-backed
	// store from disk MUST pass these (or use [NewEngineWithStoreAndSchema]);
	// a store-less in-memory engine leaves the field nil.
	RecoveredIndexes []IndexDef

	// DisableHashJoin turns OFF the disconnected-equi-join hash-join physical
	// optimisation (#1506). When false (the default) the planner may replace a
	// nested-loop Cartesian product over two disconnected pattern parts joined
	// by an equality predicate (`MATCH (a:A),(b:B) WHERE a.x = b.y …`) with an
	// order-insensitive hash join, under the structural and order-safety guards
	// in hash_join_plan.go. Setting it true forces the legacy nested-loop plan;
	// it exists for the differential test that proves both plans return an
	// identical result multiset, and as an operational escape hatch.
	DisableHashJoin bool

	// DisableRangeIndexSeek turns OFF the range-predicate B+tree index seek
	// (#1505). When false (the default) the planner may replace a
	// NodeByLabelScan+Filter for a range predicate (`n.p > x`, `n.p >= x`,
	// `n.p < x`, `n.p <= x`, or a two-sided AND) on a property backed by a
	// bound string btree index with a NodeByIndexRangeScan, under the
	// comparability + selectivity guards in range_seek_plan.go. Setting it true
	// forces the legacy scan+filter plan; it exists for the differential test
	// that proves both plans return an identical result multiset, and as an
	// operational escape hatch.
	DisableRangeIndexSeek bool
}

EngineOptions configures an Engine. The zero value is valid: it selects the default function registry (funcs.DefaultRegistry), no WAL-backed store, and the default plan cache capacity (DefaultPlanCacheCapacity). Use NewEngineWithOptions to construct an Engine from this struct.

EngineOptions is a plain configuration value read once by the constructor. It is safe for concurrent read use once constructed; do not mutate it after passing it to NewEngineWithOptions (the constructor copies the fields it needs, but the shared Registry and Store it references carry their own concurrency contracts).

type ErrStatementPipeline added in v0.3.0

type ErrStatementPipeline struct{ Err error }

ErrStatementPipeline wraps a runtime pipeline error from ExplicitTx.Exec. It signals that the query was compiled and ran to completion inside the visibility barrier but the execution pipeline failed (e.g. a constraint violation, a type error mid-pipeline, a validation error). The partial in-memory writes remain in the transaction's accumulated undo log; the caller (or the Bolt server layer) may decide whether to roll the whole transaction back.

Callers that need to distinguish pipeline errors from compile-time or build errors use errors.As to unwrap this type; the wrapped error is the original pipeline error (matchable via errors.Is against sentinel errors such as exec.ErrConstraintViolation).

func (*ErrStatementPipeline) Error added in v0.3.0

func (e *ErrStatementPipeline) Error() string

Error implements the error interface.

func (*ErrStatementPipeline) Unwrap added in v0.3.0

func (e *ErrStatementPipeline) Unwrap() error

Unwrap returns the underlying pipeline error so errors.Is and errors.As traversal works correctly.

type ExplicitTx added in v0.2.0

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

ExplicitTx is an open engine-level transaction spanning one or more statements. Obtain one from Engine.BeginTx; execute statements with ExplicitTx.Exec / ExplicitTx.ExecAny; finish with exactly one call to ExplicitTx.Commit or ExplicitTx.Rollback.

See the package file exectx.go for the full transaction, durability, and concurrency contract. In brief: writes accumulate and become durable together on Commit (WAL-backed) or unwind together on Rollback; the handle holds both the engine's writer serialisation and the graph's transaction-visibility write lock (visMu) for its whole lifetime — write-write Isolation for writers, and read-committed Isolation for concurrent readers (which block until the transaction ends); it is NOT safe for concurrent use by multiple goroutines.

func (*ExplicitTx) Commit added in v0.2.0

func (tx *ExplicitTx) Commit() (err error)

Commit makes the whole transaction durable and visible, then releases the writer serialisation. On a WAL-backed engine the WAL is fsynced exactly ONCE for every statement's accumulated writes (durable-then-visible, #1281) and the secondary-index buffer is committed; on a store-less engine the writes are already visible and Commit simply finalises the index buffer. The accumulated undo log is discarded. After Commit the handle is finished.

Commit runs the finalisation inside the visibility barrier so that, on a WAL-backed engine, the fsync happens-before the index commit and no concurrent reader can observe a committed-but-not-durable state. If the WAL fsync fails, the transaction is rolled back instead (in-memory undo replayed, index and WAL rolled back) and the fsync error is returned wrapping it: a transaction whose durability could not be guaranteed is reported as failed, never acknowledged.

Commit returns ErrTxFinished if the transaction was already committed or rolled back, and ErrTxPoisoned if a prior ExplicitTx.Exec call returned an ErrStatementPipeline error (call ExplicitTx.Rollback instead).

func (*ExplicitTx) Exec added in v0.2.0

func (tx *ExplicitTx) Exec(query string, params map[string]expr.Value) (res *Result, err error)

Exec runs one statement inside the open transaction and returns a materialised Result. The statement's writes are applied eagerly and accumulate in the transaction; they are NOT made durable or finalised here — that happens once, at ExplicitTx.Commit. Closing the returned Result releases only its own iterator state; it never commits or rolls the transaction back.

A DDL statement (CREATE/DROP INDEX or CONSTRAINT) is rejected: schema changes are not transactional in this engine and must be issued outside an explicit transaction (autocommit). A read-only statement is permitted and simply observes the transaction's current state.

A statement that raises a runtime error is returned directly as the error return of Exec. The per-statement writes remain in the accumulated undo log, so the caller (the Bolt session) can roll the whole transaction back via ExplicitTx.Rollback after inspecting the error. A statement that panics is converted to an error wrapping ErrInternalPanic; the in-memory writes of the whole transaction are rolled back inside the visibility barrier, the writer serialisation is released, and the handle is marked finished (a subsequent Rollback is then a no-op).

Exec returns ErrTxFinished if the transaction has already been committed or rolled back, or if ctx (the BeginTx context) is already done.

func (*ExplicitTx) ExecAny added in v0.2.0

func (tx *ExplicitTx) ExecAny(query string, params map[string]any) (*Result, error)

ExecAny is the ExplicitTx.Exec variant taking params as map[string]any, converting Go native values to expr.Value via BindParams.

func (*ExplicitTx) Rollback added in v0.2.0

func (tx *ExplicitTx) Rollback() (err error)

Rollback unwinds the whole transaction: it replays the accumulated in-memory undo log in reverse inside the visibility barrier (restoring the graph to its pre-transaction state), rolls back the secondary-index buffer, rolls back the WAL transaction (WAL-backed only, so a fresh recovery observes none of the writes), and releases the writer serialisation. After Rollback the handle is finished.

Rollback is best-effort and total: it always releases the writer serialisation and finishes the handle, even if an inverse operation fails. It returns ErrUndoFailed (wrapped) when the in-memory undo replay itself failed — the graph may then be inconsistent until reopen, which a WAL-backed engine reconciles to the durable state and a store-less engine cannot. It returns ErrTxFinished if the transaction was already committed or rolled back.

type IndexDef added in v0.3.0

type IndexDef struct {
	// Hash is true for a hash index, false for a btree index.
	Hash bool
	// Name is the user-defined index name.
	Name string
	// Label is the indexed node label.
	Label string
	// Property is the indexed property key.
	Property string
}

IndexDef is a durable index definition handed to the engine on open so it can re-register an index recovered from disk. It mirrors store/recovery.IndexRecord without coupling callers to the recovery package's wire types; IndexDefsFromRecovery converts a recovery result.

func IndexDefsFromRecovery added in v0.3.0

func IndexDefsFromRecovery(recovered []recovery.IndexRecord) []IndexDef

IndexDefsFromRecovery converts store/recovery.Result.Indexes into the engine's IndexDef slice so callers need not import the recovery package when constructing a NewEngineWithOptions with EngineOptions.RecoveredIndexes.

type Notification added in v0.3.1

type Notification struct {
	// Code is the stable machine-readable notification code, e.g.
	// "Neo.ClientNotification.Statement.CartesianProductWarning".
	Code string
	// Title is the short human-readable summary.
	Title string
	// Description is the full human-readable explanation, including any
	// query-specific detail (such as the offending variable names).
	Description string
	// Severity is the advisory severity, e.g. "INFORMATION".
	Severity string
	// Category classifies the notification, e.g. "PERFORMANCE".
	Category string
}

Notification is an out-of-band, advisory message attached to a query result. Notifications are NOT result rows and never affect the rows a query returns: they surface performance or correctness advisories (for example, a query that builds a Cartesian product between disconnected patterns) so a caller — an embedder of Engine or a Bolt driver via the SUCCESS metadata "notifications" field — can warn the user without changing query semantics.

The shape mirrors Neo4j's client notifications so a Bolt driver receives the same code/title/description it expects (#1483).

type Result

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

Result is a forward-only streaming result set returned by Engine.Run / Engine.RunInTx. It wraps exec.ResultSet and exposes the same iterator contract.

Lifecycle contract

Every Result returned from a successful Run/RunInTx call MUST be closed by the caller via Result.Close, even if Result.Err is non-nil and even if the caller stops iterating before exhaustion. Close releases the physical operator tree, drains any goroutines spawned by parallel operators, commits or rolls back buffered index mutations for write queries, and (for WAL-backed engines) fsyncs the WAL or rolls the transaction back.

The typical pattern is:

res, err := engine.Run(ctx, query, params)
if err != nil {
    return err
}
defer res.Close()
for res.Next() {
    rec := res.Record()
    // ... consume rec ...
}
return res.Err()

Safety net

Result installs a runtime.SetFinalizer that detects callers who forget to Close. When the garbage collector reclaims an unclosed Result, the finalizer:

  1. Increments the metric "cypher.result.leaked" so operators see the incidence count in their monitoring; and
  2. Best-effort closes the underlying resources to limit damage on a long-running server.

The finalizer is a fail-stop diagnostic, NOT a substitute for an explicit Close. In particular, the finalizer runs at an unpredictable time after the leak (it depends on the GC schedule) and CANNOT report errors back to the caller. For a write Result from Engine.RunInTx the WAL transaction is already committed (fsynced) or rolled back under the barrier before RunInTx returns (#1281), so the store's single-writer mutex is released at that point — a leaked, unclosed Result leaks only the ResultSet, not the write lock. Callers that need predictable resource release MUST still call Close themselves.

Result is NOT safe for concurrent use.

func NewErrResult added in v0.3.0

func NewErrResult(err error) *Result

NewErrResult returns a zero-row Result whose Result.Err reports err immediately. It is intended for callers that need to represent a failed query as a Result rather than as an error return — for example, the Bolt server layer, which needs to store a per-statement error in a cursor so it surfaces at PULL time rather than at RUN time, preserving the Bolt v5 state-machine contract that a TX_STREAMING cursor is always drained before the session transitions back to TX_READY or FAILED.

Next always returns false and Err returns err.

func (*Result) Close

func (r *Result) Close() error

Close releases all resources held by the result set.

For a write Result created by Engine.RunInTx, the buffered index changes and the WAL transaction were already committed (durably, fsync first) or rolled back inside the write query's lpg.Graph.ApplyAtomically window (commitUnderBarrier, #1281). Close therefore only releases the underlying ResultSet for such a result — the durability and visibility decision is made and finalised before RunInTx returns, never deferred to Close. The commit/rollback branches below survive only as a fallback for a Result that reached Close without that in-barrier finalisation (e.g. one that was never materialised), preserving the historical contract for that path.

Close is idempotent: a second invocation returns nil without re-entering the underlying ResultSet. The finalizer safety net also relies on this idempotence — see the type-level documentation.

func (*Result) Columns

func (r *Result) Columns() []string

Columns returns the ordered list of output column names.

func (*Result) Err

func (r *Result) Err() error

Err returns the first error encountered during iteration, or nil.

When a bounded-resource guard truncated the result during materialisation, Err returns the guard's sentinel: ErrResultRowsExceeded when the row cap (EngineOptions.MaxResultRows) was hit, or ErrResultBytesExceeded when the aggregate-byte budget (EngineOptions.MaxResultBytes) was hit. Either is matchable with errors.Is. For an autocommit write statement a tripped guard is a failed statement: its eager mutations were rolled back atomically inside the barrier and nothing was made durable (#1338).

When the query was a write that failed and the subsequent in-memory undo replay ALSO failed (an inverse panicked, ErrUndoFailed), Err returns the pipeline error wrapped together with ErrUndoFailed so the caller learns both that the statement failed and that the rollback could not fully restore the graph; either is matchable with errors.Is.

When the in-barrier WAL fsync failed (#1281), Err returns that error. RunInTx already surfaces it directly and does not hand such a Result back, so this is a defensive backstop for any caller that nonetheless holds the Result: it reports that the write did not become durable (and was therefore rolled back).

func (*Result) IsClosed

func (r *Result) IsClosed() bool

IsClosed reports whether Close has been called on this Result.

func (*Result) Next

func (r *Result) Next() bool

Next advances to the next result row. Returns true when a row is available. If EngineOptions.MaxResultRows is set and the limit is reached, Next sets the result's error to ErrResultRowsExceeded and returns false.

func (*Result) Notifications added in v0.3.1

func (r *Result) Notifications() []Notification

Notifications returns the out-of-band advisories attached to this result by the planner — for example a Cartesian-product warning when the query builds a cross product between disconnected patterns (#1483). Notifications are NOT result rows and never affect iteration; a caller (or a Bolt driver via the SUCCESS "notifications" metadata) may surface them to warn the user. The returned slice is nil when the query produced no notifications.

func (*Result) Record

func (r *Result) Record() exec.Record

Record returns the current row as a map from column name to value. Must only be called after a successful [Next].

For a materialised result the map is built lazily from the column-oriented backing store into a single reused scratch map (#1499): the returned map is owned by the Result and is overwritten on the next Record call, mirroring the streaming exec.ResultSet.Record contract. Callers that need to retain a row must copy it. Use Result.RowAt/Result.ValueAt to read values positionally without materialising the map.

func (*Result) RowAt added in v0.3.1

func (r *Result) RowAt(i int) []expr.Value

RowAt returns the materialised row at index i as a positional slice of values whose indices correspond to Result.Columns. The returned slice aliases the Result's backing store and must not be mutated or retained beyond the Result's lifetime. It is only valid for a materialised result (every read query and every RunInTx result is materialised under the visibility barrier); it panics if i is out of range. This is the allocation-free row accessor: it never builds the per-row map that Result.Record returns.

func (*Result) ValueAt added in v0.3.1

func (r *Result) ValueAt(col int) expr.Value

ValueAt returns the value at column index col of the current materialised row. It must only be called after a successful [Next] on a materialised result and is the allocation-free positional accessor used by hot consumers (the Bolt PULL path) that read every column by index and discard the row.

Directories

Path Synopsis
Package ast defines the Abstract Syntax Tree (AST) for openCypher 9.
Package ast defines the Abstract Syntax Tree (AST) for openCypher 9.
Package exec implements the Volcano-style executor for the Cypher query engine.
Package exec implements the Volcano-style executor for the Cypher query engine.
Package explain renders Cypher execution plans as human-readable text (EXPLAIN mode) and instruments them with per-operator execution statistics (PROFILE mode).
Package explain renders Cypher execution plans as human-readable text (EXPLAIN mode) and instruments them with per-operator execution statistics (PROFILE mode).
Package expr defines the runtime value model for the Cypher executor.
Package expr defines the runtime value model for the Cypher executor.
Package funcs implements the built-in Cypher function registry.
Package funcs implements the built-in Cypher function registry.
ir
Package ir defines the logical plan intermediate representation (IR) for the Cypher query compiler.
Package ir defines the logical plan intermediate representation (IR) for the Cypher query compiler.
rewrite
Package rewrite provides a rule-based IR plan-rewrite framework for the GoGraph Cypher engine.
Package rewrite provides a rule-based IR plan-rewrite framework for the GoGraph Cypher engine.
Package parser translates the ANTLR4-generated Cypher parse tree into the typed AST defined in github.com/FlavioCFOliveira/GoGraph/cypher/ast.
Package parser translates the ANTLR4-generated Cypher parse tree into the typed AST defined in github.com/FlavioCFOliveira/GoGraph/cypher/ast.
gen
Package gen contains the ANTLR4-generated lexer and parser for openCypher 9.
Package gen contains the ANTLR4-generated lexer and parser for openCypher 9.
Package plan provides the cost-based planner for the Cypher executor.
Package plan provides the cost-based planner for the Cypher executor.
Package procs defines the procedure registry for the Cypher executor.
Package procs defines the procedure registry for the Cypher executor.
Package sema implements the scope-analysis pass for openCypher queries.
Package sema implements the scope-analysis pass for openCypher queries.
Package tck records the conformance evolution of the GoGraph Cypher engine against the openCypher Technology Compatibility Kit.
Package tck records the conformance evolution of the GoGraph Cypher engine against the openCypher Technology Compatibility Kit.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL