Batch mutate/1 tier-3 write entry point#48
Open
david-w-t wants to merge 7 commits into
Open
Conversation
The last open follow-up of the write-path transaction-layering seam.
mutate([Mutation]) applies a list of add_relationship/retire_node/
unretire_node mutations atomically in one graphdb_mgr:transaction/1,
composing tier-1 primitives. Opaque bare-reason contract ({ok,[ok,...]}
| {error,Reason}) for drop-in compatibility; indexed errors considered
and rejected (contract + mnesia deadlock-restart footgun). One
behaviour-preserving extraction: graphdb_instance:add_relationship_in_txn/9.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
…ired, X}
Read-your-writes rollback aborts with {endpoint_retired, X} (the reason
graphdb_instance:validate_arc_endpoints_in_txn actually emits), not the
shorthand 'retired'. Also drop test-7's unobservable 'no rel-id allocated'
sub-assertion in favour of the contract (error reason + no rows written).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
Closes the final-review coverage gap: the 6- and 7-element add_relationship mutation forms were unexercised through mutate/1. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EWukKCbrN8GybaScJGU2kF
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
graphdb_mgr:mutate/1— the tier-3 batch write entry point sketchedby the write-path transaction-layering seam (the last open follow-up of that
family). It applies an ordered list of
add_relationship/retire_node/unretire_nodemutations atomically in onegraphdb_mgr:transaction/1:all commit or none do.
mutate/1is a plain exported function (not agen_server:call) — liketransaction/1, it owns the transaction in the caller's process. Threephases:
no DB, no allocation. Malformed term →
{error, {bad_mutation, M}};permanent-tier retire/unretire →
{error, permanent_node_immutable}.attr nrefs once via
graphdb_attr:seeded_nrefs/0, allocate one rel-idpair per
add_relationshipviarel_id_server:get_id_pair/0.mutation to a tier-1 in-transaction primitive.
The load-bearing invariant: gen_server calls live only in phase 2; the
phase-3 fold calls only the
_in_txnprimitives and module-localset_retired_/3, never a gen_server call inside the Mnesia activity.Contract (opaque, bare-reason)
{ok, [ok, …]}— one native value per mutation, list order.{error, Reason}(first aborting mutation), whole batchrolled back. No index — keeps
mutate/1drop-in compatible with theerror handling callers already write for the solo operations.
mutate([]) -> {ok, []}(no transaction opened).The one refactor
To make
add_relationshipcomposable inside the batch transaction, itsin-transaction body was extracted verbatim into a new exported tier-1
primitive
graphdb_instance:add_relationship_in_txn/9(the "add, don'trewrap" pattern from PRs #44/#45).
do_add_relationship/7now allocates therel-id pair up-front and delegates — behaviour-identical (the existing
114-case instance suite is the proof).
Scope
Batch covers the three write ops that are fully implemented today.
create_*/delete_node/update_node_avpsand symboliccross-mutation back-references are deferred (no tier-1 write primitives, or
not-implemented) — see the design doc.
Testing
Full suite green — 465 CT + 105 EUnit = 570 tests, zero failures, zero
compile warnings.
graphdb_mgr_SUITEgains a 10-casemutategroup:empty batch, single add_relationship, single retire/unretire, mixed
all-succeed, atomic rollback, read-your-writes rollback
(
{endpoint_retired, X}), malformed term, permanent-tier guard, plusexplicit-template and per-direction-AVP forms.
Docs
TASKS.md(IMPLEMENTED),apps/graphdb/CLAUDE.md(both worker blurbs),docs/Architecture.md(one tier-3 sentence). Designdocs/designs/batch-mutate-design.md; plandocs/superpowers/plans/2026-06-24-batch-mutate.md.🤖 Generated with Claude Code