Skip to main content

Module apoc_trigger

Module apoc_trigger 

Source
Expand description

Implementation of the apoc.trigger.* procedure family.

Triggers are Cypher snippets that fire on commit boundaries. Each trigger receives the transaction’s diff as a set of well-known parameters that mirror Neo4j APOC’s surface:

  • $createdNodes / $createdRelationships — newly-created elements (id wasn’t in the store pre-tx)
  • $deletedNodes / $deletedRelationships — deleted elements, captured pre-tx so the trigger sees their final shape before deletion
  • $assignedNodeProperties / $assignedRelationshipProperties — property changes on existing elements, list of {key, old, new, node|relationship}
  • $removedNodeProperties / $removedRelationshipProperties — property removals, list of {key, old, node|relationship}
  • $assignedLabels / $removedLabels — label changes on existing nodes, list of {label, node}

All four phases work — before (pre-commit; errors abort the tx; trigger writes commit atomically with the user’s writes), after (post-commit, sync; recurses through the cluster commit path so trigger writes replicate), afterAsync (post-commit, spawned in a background tokio task so it doesn’t block the response), and rollback (fires when the commit fails, including before-trigger aborts).

§Procedure surface

Five procedures: install / drop / list / start / stop. Install/drop emit GraphCommand::InstallTrigger / DropTrigger so the cluster commit machinery replicates the registry across every peer (Raft log + routing-mode DDL fan-out). Firing is leader/coordinator-only — exactly one peer per logical commit fires.

Recursion safety has two layers: the thread-local SUPPRESSING flag catches the sync-call path, and the from_trigger: bool parameter on commit_buffered_commands catches the async-recursive cluster commit path. Either alone is sufficient; both together close every observed self-firing scenario.

§Multi-db

databaseName is accepted on every procedure but ignored — Mesh is single-db today. Callers can pass any string.

Structs§

LabelChange
One label-change record (assignment or removal). element is always a Value::Node since labels are node-scope.
PropertyChange
One property-change record (assignment or removal) emitted per (element, key) tuple touched by the tx. The element field is a Value::Node or Value::Edge depending on scope so the trigger body can inspect the wider shape.
TriggerDiff
Per-tx diff handed to a trigger as $created* / $deleted* / $assigned* / $removed* params. Computed in meshdb-rpc (where GraphCommand lives) and passed in to the firing helpers. into_param_map shapes the lists into Neo4j APOC’s expected param surface.
TriggerRegistry
In-memory cache + persistent backing of the trigger registry. Mutating operations (install / drop) write through to storage; reads served from the cache. The Arc<dyn StorageEngine> is held internally so the procedure call surface only needs the registry handle — not a separate store reference — to mutate the persistent state.
TriggerSpec
Persisted trigger spec. Roundtrips through serde_json for storage in the trigger_meta column family. The name field duplicates the storage key — kept on the value too so list / show output is self-describing.

Functions§

drop_call
apoc.trigger.drop(databaseName, name). Same write-path shape as install_call — the drop rides through the cluster commit so every peer’s storage drops the entry. The yielded removed column is always true in V2 because the actual apply happens on the leader/applier path; the install/drop procedure itself only buffers the command.
fire_after_triggers
Fire every registered after-phase trigger. Backwards-compat shim for callers that haven’t migrated to fire_phase_triggers yet.
fire_before_triggers
Fire every registered before-phase trigger against the pre-commit diff. Returns Ok if every trigger ran cleanly, Err with the offending message if one failed — propagated to abort the originator’s commit. Trigger writes are buffered into the supplied writer so the caller can merge them into the prepared command batch.
fire_phase_triggers
Fire every registered trigger of phase against the given diff. Errors are logged and swallowed — the original tx has already committed (for after / afterAsync / rollback) or already aborted (for rollback), so a failing trigger can’t undo anything. Used as the workhorse for the three non-blocking phases; before has its own entry point with abort semantics.
install_call
apoc.trigger.install(databaseName, name, statement, selector, config). databaseName is accepted but ignored — Mesh is single-database today. selector and config are inspected for a phase (defaults to “after”) and params (defaults to empty); other keys are ignored for forward-compat.
is_suppressed
Whether the calling thread is currently inside a trigger invocation. The commit hook calls this before firing.
list_call
apoc.trigger.list().
now_ms
Helper: wall-clock millis since the UNIX epoch. Used to stamp installed_at_ms on a fresh trigger spec.
start_call
apoc.trigger.start(databaseName, name) — un-pause a previously stopped trigger. Looks up the existing spec through the registry, sets paused = false, and re-emits the spec via the writer so the cluster commit replicates the unpause. Errors if the trigger doesn’t exist.
stop_call
apoc.trigger.stop(databaseName, name) — pause a registered trigger. Mirrors start_call; sets paused = true.
with_suppression
Run body with the suppression flag set. The flag resets to the prior value on drop — even on panic — so a panicking trigger doesn’t leave the calling thread permanently suppressed.