Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions TASKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ independent and broken into slices A–E below. `graphdb_mgr` owns the
generic low-level node/relationship CRUD; type-specific behaviour
delegates to the owning worker.

### Transaction-layering seam (slice A prerequisite) — IN DESIGN
### Transaction-layering seam (slice A prerequisite) — IMPLEMENTED

The decided convention for all write-path mutation: separate the Mnesia
transaction boundary from the CRUD logic, so operations compose into one
Expand All @@ -126,9 +126,20 @@ and `remove_relationship` adopt it as their first consumers.

Tracked follow-ups (not in the seam spec):

- **Retrofit existing write ops** (`create_instance`, `add_relationship`,
the membership `do_*` ops) onto the primitive/wrapper layering — uniform
convention, no behaviour change.
- **Retrofit existing write ops** — IMPLEMENTED. Full sweep: all 40
`mnesia:transaction` sites across the six workers + bootstrap now route
through `graphdb_mgr:transaction/1` (the single `{atomic,_}`/`{aborted,_}`
mapping point). Behaviour-preserving; existing tests unchanged, +2 new
instance CT cases (`characterization_not_found`/`reciprocal_not_found`
arms). Design `docs/designs/transaction-seam-retrofit-design.md`; plan
`docs/superpowers/plans/2026-06-20-transaction-seam-retrofit.md`.
- **Atomic `add_relationship`** — collapse its four separate transactions
(validate → resolve classes → resolve template → write) into one.
Blocked on `graphdb_class` exposing tier-1 in-transaction read
primitives: its reads (`default_template`, `get_template`,
`class_in_ancestry`) are `gen_server:call` today, which cannot run inside
an Mnesia transaction. Sequence with / before `mutate/1`, which wants
those primitives too.
- **Batch `mutate([Mutation])`** — the tier-3 entry point.

### Node deletion (slice A) — IMPLEMENTED
Expand Down
67 changes: 31 additions & 36 deletions apps/graphdb/src/graphdb_attr.erl
Original file line number Diff line number Diff line change
Expand Up @@ -494,12 +494,14 @@ find_attribute_by_name(ParentNref, Name) ->
F = fun() ->
Children = downward_children_by_arc(ParentNref, ?ARC_ATTR_CHILD,
taxonomy),
lists:search(fun(N) -> node_has_name(N, Name) end, Children)
case lists:search(fun(N) -> node_has_name(N, Name) end, Children) of
{value, #node{nref = Nref}} -> {ok, Nref};
false -> not_found
end
end,
case mnesia:transaction(F) of
{atomic, {value, #node{nref = Nref}}} -> {ok, Nref};
{atomic, false} -> not_found;
{aborted, Reason} -> throw({error, Reason})
case graphdb_mgr:transaction(F) of
{ok, Result} -> Result;
{error, Reason} -> throw({error, Reason})
end.


Expand Down Expand Up @@ -559,9 +561,9 @@ do_create_attribute(Name, ParentNref, ExtraAVPs) ->
ok = mnesia:write(relationships, P2C, write),
ok = mnesia:write(relationships, C2P, write)
end,
case mnesia:transaction(Txn) of
{atomic, ok} -> {ok, Nref};
{aborted, Reason} -> {error, Reason}
case graphdb_mgr:transaction(Txn) of
{ok, ok} -> {ok, Nref};
{error, _} = Err -> Err
end.


Expand Down Expand Up @@ -654,9 +656,9 @@ do_create_relationship_attribute_pair(FwdName, RevName, ExtraAVPs, ParentNref) -
ok = mnesia:write(relationships, RevP2C, write),
ok = mnesia:write(relationships, RevC2P, write)
end,
case mnesia:transaction(Txn) of
{atomic, ok} -> {ok, {FwdNref, RevNref}};
{aborted, Reason} -> {error, Reason}
case graphdb_mgr:transaction(Txn) of
{ok, ok} -> {ok, {FwdNref, RevNref}};
{error, _} = Err -> Err
end.


Expand All @@ -682,11 +684,11 @@ validate_parent(ParentNref) ->
%% {ok, #node{}} | {error, not_found | not_an_attribute | term()}
%%-----------------------------------------------------------------------------
do_get_attribute(Nref) ->
case mnesia:transaction(fun() -> mnesia:read(nodes, Nref) end) of
{atomic, [#node{kind = attribute} = Node]} -> {ok, Node};
{atomic, [_Other]} -> {error, not_an_attribute};
{atomic, []} -> {error, not_found};
{aborted, Reason} -> {error, Reason}
case graphdb_mgr:transaction(fun() -> mnesia:read(nodes, Nref) end) of
{ok, [#node{kind = attribute} = Node]} -> {ok, Node};
{ok, [_Other]} -> {error, not_an_attribute};
{ok, []} -> {error, not_found};
{error, Reason} -> {error, Reason}
end.


Expand All @@ -697,10 +699,7 @@ do_list_attributes() ->
F = fun() ->
mnesia:match_object(nodes, #node{_ = '_', kind = attribute}, read)
end,
case mnesia:transaction(F) of
{atomic, Nodes} -> {ok, Nodes};
{aborted, Reason} -> {error, Reason}
end.
graphdb_mgr:transaction(F).


%%-----------------------------------------------------------------------------
Expand All @@ -710,10 +709,7 @@ do_list_children(ParentNref) ->
F = fun() ->
downward_children_by_arc(ParentNref, ?ARC_ATTR_CHILD, taxonomy)
end,
case mnesia:transaction(F) of
{atomic, Nodes} -> {ok, Nodes};
{aborted, Reason} -> {error, Reason}
end.
graphdb_mgr:transaction(F).


%%-----------------------------------------------------------------------------
Expand Down Expand Up @@ -752,15 +748,15 @@ attr_type_avp(Kind, #state{attribute_type_nref = AtAttr})
%%-----------------------------------------------------------------------------
do_attribute_type_of(Nref, AtAttrNref) ->
F = fun() -> mnesia:read(nodes, Nref) end,
case mnesia:transaction(F) of
{atomic, [#node{kind = attribute, attribute_value_pairs = AVPs}]} ->
case graphdb_mgr:transaction(F) of
{ok, [#node{kind = attribute, attribute_value_pairs = AVPs}]} ->
case find_attribute_type_value(AtAttrNref, AVPs) of
{ok, Kind} -> {ok, Kind};
not_found -> {error, no_attribute_type}
end;
{atomic, [_Other]} -> {error, not_an_attribute};
{atomic, []} -> {error, not_found};
{aborted, Reason} -> {error, Reason}
{ok, [_Other]} -> {error, not_an_attribute};
{ok, []} -> {error, not_found};
{error, Reason} -> {error, Reason}
end.

find_attribute_type_value(_AtAttrNref, []) ->
Expand Down Expand Up @@ -796,10 +792,9 @@ retro_stamp_bootstrap_attribute_types(AtAttrNref) ->
fun(N) -> stamp_attribute_type_if_missing(N, AtAttrNref) end,
Attrs)
end,
case mnesia:transaction(Txn) of
{atomic, ok} -> ok;
{atomic, _Other} -> ok;
{aborted, Reason} -> throw({error, Reason})
case graphdb_mgr:transaction(Txn) of
{ok, _} -> ok;
{error, Reason} -> throw({error, Reason})
end.

stamp_attribute_type_if_missing(#node{nref = Nref,
Expand Down Expand Up @@ -880,7 +875,7 @@ ensure_template_avp_marker(RelAvpAttrNref) ->
throw({error, {template_avp_node_missing, ?ARC_TEMPLATE}})
end
end,
case mnesia:transaction(Txn) of
{atomic, ok} -> ok;
{aborted, Reason} -> throw({error, Reason})
case graphdb_mgr:transaction(Txn) of
{ok, ok} -> ok;
{error, Reason} -> throw({error, Reason})
end.
4 changes: 2 additions & 2 deletions apps/graphdb/src/graphdb_bootstrap.erl
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@ validate_no_unresolved_labels(Nodes, Rels) ->
write_nodes(Nodes) ->
lists:foreach(fun(NodeTerm) ->
Record = term_to_node(NodeTerm),
{atomic, ok} = mnesia:transaction(fun() ->
{ok, ok} = graphdb_mgr:transaction(fun() ->
ok = mnesia:write(nodes, Record, write)
end)
end, Nodes),
Expand Down Expand Up @@ -543,7 +543,7 @@ term_to_node({node, Nref, Kind, {NameAttrNref, NameValue}, ExtraAVPs}) ->
write_relationships(Rels) ->
lists:foreach(fun(RelTerm) ->
{Row1, Row2} = expand_relationship(RelTerm),
{atomic, ok} = mnesia:transaction(fun() ->
{ok, ok} = graphdb_mgr:transaction(fun() ->
ok = mnesia:write(relationships, Row1, write),
ok = mnesia:write(relationships, Row2, write)
end)
Expand Down
54 changes: 24 additions & 30 deletions apps/graphdb/src/graphdb_class.erl
Original file line number Diff line number Diff line change
Expand Up @@ -496,10 +496,10 @@ do_create_class(Name, ParentClassNref, AVPs, InstAttr) ->
ok = mnesia:write(relationships, TaxC2P, write),
[ ok = mnesia:write(T, R, write) || {T, R} <- TemplateRows ]
end,
case mnesia:transaction(Txn) of
case graphdb_mgr:transaction(Txn) of
%% Txn value is [] (abstract) or [ok,ok,ok] (template rows)
{atomic, _Writes} -> {ok, ClassNref};
{aborted, Reason} -> {error, Reason}
{ok, _Writes} -> {ok, ClassNref};
{error, _} = Err -> Err
end;
{error, _} = Err ->
Err
Expand Down Expand Up @@ -621,10 +621,10 @@ do_write_superclass(ClassNref, AdditionalParentNref) ->
ok
end
end,
case mnesia:transaction(Txn) of
{atomic, ok} -> ok;
{atomic, already_exists} -> ok;
{aborted, Reason} -> {error, Reason}
case graphdb_mgr:transaction(Txn) of
{ok, ok} -> ok;
{ok, already_exists} -> ok;
{error, _} = Err -> Err
end.


Expand Down Expand Up @@ -679,9 +679,9 @@ do_write_template(ClassNref, Name) ->
ok = mnesia:write(relationships, P2C, write),
ok = mnesia:write(relationships, C2P, write)
end,
case mnesia:transaction(Txn) of
{atomic, ok} -> {ok, TemplateNref};
{aborted, Reason} -> {error, Reason}
case graphdb_mgr:transaction(Txn) of
{ok, ok} -> {ok, TemplateNref};
{error, _} = Err -> Err
end.


Expand All @@ -701,10 +701,10 @@ do_find_template_by_name(ClassNref, Name) ->
(_) -> false
end, Children)
end,
case mnesia:transaction(F) of
{atomic, {value, #node{nref = Nref}}} -> {ok, Nref};
{atomic, false} -> not_found;
{aborted, _} -> not_found
case graphdb_mgr:transaction(F) of
{ok, {value, #node{nref = Nref}}} -> {ok, Nref};
{ok, false} -> not_found;
{error, _} -> not_found
end.

template_has_name(#node{attribute_value_pairs = AVPs}, Name) ->
Expand Down Expand Up @@ -735,10 +735,7 @@ do_templates_for_class(ClassNref) ->
composition),
[N || N <- Children, N#node.kind =:= template]
end,
case mnesia:transaction(F) of
{atomic, Nodes} -> {ok, Nodes};
{aborted, Reason} -> {error, Reason}
end.
graphdb_mgr:transaction(F).


%%-----------------------------------------------------------------------------
Expand Down Expand Up @@ -829,11 +826,11 @@ do_add_qc(ClassNref, AttrNref) ->
{error, not_found}
end
end,
case mnesia:transaction(Txn) of
{atomic, ok} -> ok;
{atomic, already_exists} -> ok;
{atomic, {error, _} = E} -> E;
{aborted, Reason} -> {error, Reason}
case graphdb_mgr:transaction(Txn) of
{ok, ok} -> ok;
{ok, already_exists} -> ok;
{ok, {error, _} = E} -> E;
{error, Reason} -> {error, Reason}
end.


Expand Down Expand Up @@ -866,9 +863,9 @@ do_bind_qc_value(ClassNref, AttrNref, Value) ->
[] -> mnesia:abort(not_found)
end
end,
case mnesia:transaction(F) of
{atomic, ok} -> ok;
{aborted, Reason} -> {error, Reason}
case graphdb_mgr:transaction(F) of
{ok, ok} -> ok;
{error, _} = Err -> Err
end.

%%-----------------------------------------------------------------------------
Expand Down Expand Up @@ -908,10 +905,7 @@ do_subclasses(ClassNref) ->
taxonomy),
[N || N <- Children, N#node.kind =:= class]
end,
case mnesia:transaction(F) of
{atomic, Nodes} -> {ok, Nodes};
{aborted, Reason} -> {error, Reason}
end.
graphdb_mgr:transaction(F).


%%-----------------------------------------------------------------------------
Expand Down
Loading
Loading