Transactions and sessions¶
KGLite has three mutation styles. Choose by the failure and concurrency contract you need:
Surface |
Use when |
Failure behavior |
|---|---|---|
|
one-owner, simple direct work |
a failed Cypher statement restores its earlier writes; earlier successful statements remain |
|
several operations must commit or roll back together |
isolated copy-on-write transaction with OCC at commit |
|
threads/tasks share one live graph |
reads use snapshots; writes serialize and atomically swap on success |
Explicit transactions¶
import kglite
graph = kglite.KnowledgeGraph()
with graph.begin(timeout_ms=30_000) as tx:
tx.cypher("CREATE (:Person {id: 1, name: 'Alice'})")
tx.cypher("CREATE (:Person {id: 2, name: 'Bob'})")
# auto-commit on clean exit; auto-rollback on exception
read_tx = graph.begin_read() # O(1) immutable snapshot
rows = read_tx.cypher("MATCH (p:Person) RETURN p.name ORDER BY p.name")
read_tx.commit() # no-op; releases the snapshot
begin() is O(1). It captures an Arc snapshot and creates a
backend-specific working fork only on the first mutation. Memory/mapped modes
clone then; disk mode shares immutable bases and copies mutation overlays.
Outside readers continue to see the pre-commit snapshot.
commit() checks the graph version. If another writer committed after
begin(), the commit raises a typed kglite.KgError; the application decides
whether to retry. rollback() discards the working fork. A transaction cannot
be reused after commit or rollback.
Transaction deadlines and query deadlines raise
kglite.CypherTimeoutError. Read-only transactions reject mutations. Nested
transactions/savepoints, last-writer-wins, and multi-graph atomic commits are
not supported.
Statement rollback¶
Each Cypher statement restores its changes if execution fails, including a late work-budget refusal after a write. This applies to direct graph calls, sessions, and transactions. Catching a statement error inside a transaction preserves its previous successful statements; committing afterward does not publish the failed statement’s writes. Result retention limits do not abort or undo successful writes. Signal-based cancellation is exposed separately by each binding.
import kglite
graph = kglite.KnowledgeGraph()
graph.cypher("CREATE (:N {id: 1, v: 1})")
try:
graph.cypher(
"MATCH (n:N) SET n.v=2 WITH n "
"UNWIND range(1,n.v*10) AS i RETURN i",
max_work_units=5,
)
except kglite.CypherExecutionError as exc:
assert exc.code == "CypherExecution"
assert "20 work units" in str(exc)
else:
raise AssertionError("expected the dependent range to exceed the work budget")
assert graph.cypher("MATCH (n:N) RETURN n.v").scalar() == 1
The range depends on the updated value, so this example exercises an error after
SET executes. An explicit transaction is needed to roll back multiple
successful statements as a group, not to make one statement rollback-safe.
Captured embedding services¶
freeze(), session(), begin(), and begin_read() capture the embedding
model registered on the source graph when the handle is created. Session
snapshots and cursors inherit that binding. Replacing or unbinding the source
model affects newly created handles only. This captures the model object, not a
deep copy of its mutable weights or callback state, so mutations inside that
same object remain visible. Model bindings are runtime services and are not
stored in .kgl files; register them again after loading.
The captured model powers text_score() in read queries and mutation
expressions, including supported nested CALL subqueries, UNION arms,
EXISTS predicates and FOREACH bodies. Only a mutation whose prepared query
can invoke that model uses an isolated working copy; other mutations use the
direct serialized path. Its callback may read the same Session’s committed
snapshot. A synchronous same-thread write re-entering that Session from the
callback raises kglite.ArgumentError with code InvalidArgument before
waiting for the writer lock. A callback error rolls back the current statement
while leaving the handle usable. Python exposes no value-codec registration on
these handles; native callers pass codecs per execution, and configured
protocol servers forward their own runtime service.
Storage and protocol bindings¶
Transactions and sessions use the same core implementation for memory, mapped, and disk storage. Rust protocol servers consume the native session/transaction surface directly; they do not depend on Python or the GIL. Bolt maps KGLite’s typed error codes to Neo4j status codes and manages one transaction per Bolt session. The C ABI currently exposes atomic mutation batches rather than an explicit begin/commit handle.
See also¶
Concurrency — ownership, snapshots, and shared sessions.
Error handling — typed exceptions and stable codes.
Durable apps — WAL-backed in-memory persistence.
Rust session abstraction — binding-level execution contract.
Captured query defaults and ownership¶
freeze(), session(), begin() and begin_read() capture the source graph’s
query timeout, work budget and row cap. Session snapshots/cursors inherit that
captured policy. Changing the original graph’s defaults affects newly created
handles, not existing snapshots or transactions. Embedding services follow the
separate capture contract above; they are not part of these three query defaults.
Option |
Omitted or |
Explicit zero |
|---|---|---|
Query |
captured default, then180000ms |
no per-query deadline |
Query |
captured budget, then engine backstop |
literal zero budget |
Query |
captured row cap, then no cap |
retain no result rows |
|
no transaction lifetime deadline |
no transaction lifetime deadline |
An explicit per-call value overrides the captured value. A positive transaction
lifetime still bounds every query; per-query zero cannot bypass it. Clear a graph
default with its setter before deriving a handle when no inherited row/work limit
is wanted; passing None to a query means inherit.
Closing a persisted graph ends its write-back authority. Its retained data stays
mutable as a detached graph, but an earlier transaction with writes cannot commit
into that ended owner. Read-only snapshots and rollback remain available. This is
separate from optimistic data-version conflicts. A with-block around open() is
not a multi-statement transaction; see Durable embedded apps.
Fluent views of a CDC-owning graph and Session cursors sharing change-data
capture (CDC) are read-only for captured mutations. Their descendants inherit
that restriction. Use cursor.copy()
for an independent graph and change stream, or write through the original owner.