Packages

macula

0.36.5
7.1.0 7.0.0 6.0.0 5.2.2 5.2.1 5.2.0 5.1.0 5.0.0 4.8.0 4.7.1 4.7.0 4.6.0 4.5.0 4.4.10 4.4.9 4.4.8 4.4.7 4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 4.4.0 4.3.1 4.3.0 4.2.9 4.2.8 4.2.7 4.2.6 4.2.5 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.1 4.1.0 4.0.0 3.16.0 3.15.3 3.15.2 3.15.1 3.14.0 3.13.0 3.12.1 3.12.0 3.11.1 3.11.0 3.10.3 3.10.2 3.10.1 3.9.0 3.8.0 3.7.0 3.5.0 3.4.0 3.3.0 3.2.0 3.1.0 3.0.0 2.1.1 2.1.0 2.0.0 1.5.2 1.5.1 1.4.30 1.4.29 1.4.28 1.4.27 1.4.26 1.4.25 1.4.24 1.4.23 1.4.22 1.4.21 1.4.20 1.4.19 1.4.18 1.4.17 1.4.16 1.4.15 1.4.14 1.4.13 1.4.11 1.4.10 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.1 1.3.0 1.2.0 1.1.0 1.0.10 1.0.9 1.0.8 1.0.7 1.0.6 1.0.5 1.0.4 1.0.3 1.0.2 1.0.1 1.0.0 0.48.6 0.48.5 0.48.4 0.48.3 0.48.2 0.48.1 0.48.0 0.47.1 0.47.0 0.46.3 0.46.1 0.46.0 0.45.3 0.45.2 0.45.1 0.45.0 0.44.2 0.44.1 0.44.0 0.43.3 0.43.2 0.43.1 0.43.0 0.42.9 0.42.8 0.42.7 0.42.6 0.42.5 0.42.4 0.42.3 0.42.2 0.42.1 0.42.0 0.41.1 0.41.0 0.40.1 0.40.0 0.39.9 0.39.8 0.39.7 0.39.6 0.39.5 0.39.4 0.39.3 0.39.2 0.39.1 0.39.0 0.38.8 0.38.7 0.38.6 0.38.5 0.38.4 0.38.3 0.38.2 0.38.1 0.38.0 0.37.7 0.37.6 0.37.5 0.37.4 0.37.3 0.37.2 0.37.1 0.37.0 0.36.6 0.36.5 0.36.4 0.36.3 0.36.2 0.36.1 0.36.0 0.35.4 0.35.3 0.35.2 0.35.1 0.35.0 0.34.1 0.34.0 0.33.1 0.33.0 0.32.5 0.32.4 0.32.3 0.32.2 0.32.1 0.32.0 0.31.9 0.31.8 0.31.7 0.31.6 0.31.5 0.31.4 0.31.3 0.31.2 0.31.1 0.31.0 0.30.10 0.30.9 0.30.8 0.30.7 0.30.6 0.30.5 0.30.4 0.30.3 0.30.2 0.30.1 0.30.0 0.29.0 0.28.3 0.28.2 0.28.1 0.28.0 0.27.1 0.27.0 0.26.1 0.26.0 0.25.6 0.25.5 0.25.4 0.25.3 0.25.2 0.25.1 0.25.0 0.24.6 0.24.5 0.24.4 0.24.3 0.24.2 0.24.1 0.24.0 0.23.3 0.23.2 0.23.1 0.23.0 0.22.12 0.22.11 0.22.10 0.22.9 0.22.8 0.22.7 0.22.6 0.22.5 0.22.4 0.22.3 0.22.2 0.22.1 0.22.0 0.21.7 0.21.6 0.21.5 0.21.4 0.21.2 0.21.1 0.21.0 0.20.25 0.20.24 0.20.23 0.20.22 0.20.21 0.20.20 0.20.19 0.20.18 0.20.17 0.20.16 0.20.15 0.20.14 0.20.13 0.20.12 0.20.11 0.20.10 0.20.9 0.20.8 0.20.7 0.20.6 0.20.5 0.20.3 0.20.2 0.20.1 0.20.0 0.19.2 0.19.1 0.19.0 0.18.1 0.18.0 0.17.4 0.17.3 0.17.2 0.17.1 0.17.0 0.16.6 0.16.5 0.16.4 0.16.3 0.16.2 0.16.1 0.16.0 0.15.1 0.15.0 0.14.3 0.14.2 0.14.1 0.14.0 0.12.6 0.12.5 0.12.3 0.11.3 0.10.2 0.10.1 0.10.0 0.9.2 0.9.1 0.9.0 0.8.25 0.8.24 0.8.23 0.8.22 0.8.21 0.8.20 0.8.19 0.8.18 0.8.17 0.8.16 0.8.15 0.8.14 0.8.13 0.8.12 0.8.11 0.8.10 0.8.9 0.8.8 0.8.7 0.8.6 0.8.5 0.8.4 0.8.3 0.8.2 0.8.1 0.8.0 0.7.30 0.7.29 0.7.28 0.7.27 0.7.26 0.7.25 0.7.24 0.7.23 0.7.22 0.7.21 0.7.20 0.7.19 0.7.18 0.7.17 0.7.16 0.7.15 0.7.14 0.7.13 0.7.12 0.7.11 0.7.10 0.7.9 0.7.8 0.7.7 0.7.6 0.7.5 0.7.4 0.7.3 0.7.2 0.7.1 0.7.0 0.6.7 0.6.6 0.6.5 0.6.4 0.6.3 0.6.2 0.6.1 0.6.0 0.5.0 0.4.4 0.4.3 0.4.2 0.4.1 0.4.0 0.3.4 0.3.3 0.3.2 0.3.1

Macula HTTP/3 Mesh SDK — connect, subscribe, publish, call, advertise

Current section

Files

Jump to
macula src macula_mri_graph.erl
Raw

src/macula_mri_graph.erl

%%%-------------------------------------------------------------------
%%% @doc MRI Graph Behaviour
%%%
%%% Defines the interface for MRI relationship (graph) storage.
%%% Implementations must provide relationship CRUD and traversal.
%%%
%%% Relationships are triples: {Subject, Predicate, Object}
%%% Both forward (subject→object) and reverse (object→subject) queries
%%% should be efficient.
%%%
%%% Built-in predicates include:
%%% - Spatial: located_at, contains, adjacent_to
%%% - Organizational: member_of, has_member, manages, managed_by, owns, owned_by
%%% - Technical: depends_on, depended_on_by, provides, provided_by, connects_to
%%% - Data: trained_on, derived_from, source_of, version_of
%%% - Taxonomic: instance_of, has_instance, subclass_of, superclass_of
%%%
%%% Custom predicates use the format: {custom, Binary} where Binary is the predicate name
%%%
%%% @end
%%%-------------------------------------------------------------------
-module(macula_mri_graph).
%% Type exports
-export_type([predicate/0, direction/0, relationship/0]).
-type predicate() :: atom() | {custom, binary()}.
-type direction() :: forward | reverse.
-type relationship() :: #{
subject := macula_mri:mri(),
predicate := predicate(),
object := macula_mri:mri(),
metadata => map()
}.
%% Behaviour definition
%% Create a relationship between two MRIs
-callback create_relationship(
Subject :: macula_mri:mri(),
Predicate :: predicate(),
Object :: macula_mri:mri()
) -> ok | {error, term()}.
%% Create a relationship with metadata
-callback create_relationship(
Subject :: macula_mri:mri(),
Predicate :: predicate(),
Object :: macula_mri:mri(),
Metadata :: map()
) -> ok | {error, term()}.
%% Delete a relationship
-callback delete_relationship(
Subject :: macula_mri:mri(),
Predicate :: predicate(),
Object :: macula_mri:mri()
) -> ok | {error, term()}.
%% Query: what does Subject relate to via Predicate?
-callback related_to(
Subject :: macula_mri:mri(),
Predicate :: predicate()
) -> [macula_mri:mri()].
%% Query: what relates to Object via Predicate?
-callback related_from(
Object :: macula_mri:mri(),
Predicate :: predicate()
) -> [macula_mri:mri()].
%% Query: all relationships from a subject
-callback all_related(Subject :: macula_mri:mri()) ->
[{predicate(), macula_mri:mri()}].
%% Transitive traversal (follow relationships recursively)
-callback traverse_transitive(
Start :: macula_mri:mri(),
Predicate :: predicate(),
Direction :: direction()
) -> [macula_mri:mri()].
%% Get relationship with metadata
-callback get_relationship(
Subject :: macula_mri:mri(),
Predicate :: predicate(),
Object :: macula_mri:mri()
) -> {ok, relationship()} | {error, not_found | term()}.
%% Optional callbacks for taxonomy operations
-callback instances_of(Class :: macula_mri:mri()) -> [macula_mri:mri()].
-callback instances_of_transitive(Class :: macula_mri:mri()) -> [macula_mri:mri()].
-callback classes_of(Instance :: macula_mri:mri()) -> [macula_mri:mri()].
-callback subclasses(Class :: macula_mri:mri()) -> [macula_mri:mri()].
-callback superclasses(Class :: macula_mri:mri()) -> [macula_mri:mri()].
-optional_callbacks([
instances_of/1,
instances_of_transitive/1,
classes_of/1,
subclasses/1,
superclasses/1
]).
%% API for working with configured adapter
-export([adapter/0, set_adapter/1]).
-export([create_relationship/3, create_relationship/4, delete_relationship/3]).
-export([related_to/2, related_from/2, all_related/1]).
-export([traverse_transitive/3, get_relationship/3]).
-export([instances_of/1, instances_of_transitive/1, classes_of/1]).
-export([subclasses/1, superclasses/1]).
%% Predicate utilities
-export([is_builtin_predicate/1, inverse_predicate/1]).
-define(ADAPTER_KEY, {?MODULE, adapter}).
-define(DEFAULT_ADAPTER, macula_mri_ets).
%%===================================================================
%% Adapter Management
%%===================================================================
%% @doc Get the configured graph adapter module.
-spec adapter() -> module().
adapter() ->
case persistent_term:get(?ADAPTER_KEY, undefined) of
undefined -> ?DEFAULT_ADAPTER;
Adapter -> Adapter
end.
%% @doc Set the graph adapter module.
-spec set_adapter(module()) -> ok.
set_adapter(Module) when is_atom(Module) ->
persistent_term:put(?ADAPTER_KEY, Module),
ok.
%%===================================================================
%% Delegating API
%%===================================================================
%% @doc Create a relationship between two MRIs.
-spec create_relationship(macula_mri:mri(), predicate(), macula_mri:mri()) ->
ok | {error, term()}.
create_relationship(Subject, Predicate, Object) ->
(adapter()):create_relationship(Subject, Predicate, Object).
%% @doc Create a relationship with metadata.
-spec create_relationship(macula_mri:mri(), predicate(), macula_mri:mri(), map()) ->
ok | {error, term()}.
create_relationship(Subject, Predicate, Object, Metadata) ->
(adapter()):create_relationship(Subject, Predicate, Object, Metadata).
%% @doc Delete a relationship.
-spec delete_relationship(macula_mri:mri(), predicate(), macula_mri:mri()) ->
ok | {error, term()}.
delete_relationship(Subject, Predicate, Object) ->
(adapter()):delete_relationship(Subject, Predicate, Object).
%% @doc Query what Subject relates to via Predicate.
-spec related_to(macula_mri:mri(), predicate()) -> [macula_mri:mri()].
related_to(Subject, Predicate) ->
(adapter()):related_to(Subject, Predicate).
%% @doc Query what relates to Object via Predicate.
-spec related_from(macula_mri:mri(), predicate()) -> [macula_mri:mri()].
related_from(Object, Predicate) ->
(adapter()):related_from(Object, Predicate).
%% @doc Get all relationships from a subject.
-spec all_related(macula_mri:mri()) -> [{predicate(), macula_mri:mri()}].
all_related(Subject) ->
(adapter()):all_related(Subject).
%% @doc Traverse relationships transitively.
-spec traverse_transitive(macula_mri:mri(), predicate(), direction()) ->
[macula_mri:mri()].
traverse_transitive(Start, Predicate, Direction) ->
(adapter()):traverse_transitive(Start, Predicate, Direction).
%% @doc Get a specific relationship with its metadata.
-spec get_relationship(macula_mri:mri(), predicate(), macula_mri:mri()) ->
{ok, relationship()} | {error, not_found | term()}.
get_relationship(Subject, Predicate, Object) ->
(adapter()):get_relationship(Subject, Predicate, Object).
%%===================================================================
%% Taxonomy Operations
%%===================================================================
%% @doc Get direct instances of a class.
-spec instances_of(macula_mri:mri()) -> [macula_mri:mri()].
instances_of(Class) ->
Adapter = adapter(),
case erlang:function_exported(Adapter, instances_of, 1) of
true -> Adapter:instances_of(Class);
false -> related_from(Class, instance_of)
end.
%% @doc Get all instances of a class including subclasses (transitive).
-spec instances_of_transitive(macula_mri:mri()) -> [macula_mri:mri()].
instances_of_transitive(Class) ->
Adapter = adapter(),
case erlang:function_exported(Adapter, instances_of_transitive, 1) of
true ->
Adapter:instances_of_transitive(Class);
false ->
%% Default implementation: collect instances from class and all subclasses
DirectInstances = instances_of(Class),
SubclassInstances = lists:flatmap(
fun(Subclass) -> instances_of_transitive(Subclass) end,
subclasses(Class)
),
lists:usort(DirectInstances ++ SubclassInstances)
end.
%% @doc Get the classes an instance belongs to.
-spec classes_of(macula_mri:mri()) -> [macula_mri:mri()].
classes_of(Instance) ->
Adapter = adapter(),
case erlang:function_exported(Adapter, classes_of, 1) of
true -> Adapter:classes_of(Instance);
false -> related_to(Instance, instance_of)
end.
%% @doc Get direct subclasses of a class.
-spec subclasses(macula_mri:mri()) -> [macula_mri:mri()].
subclasses(Class) ->
Adapter = adapter(),
case erlang:function_exported(Adapter, subclasses, 1) of
true -> Adapter:subclasses(Class);
false -> related_from(Class, subclass_of)
end.
%% @doc Get direct superclasses of a class.
-spec superclasses(macula_mri:mri()) -> [macula_mri:mri()].
superclasses(Class) ->
Adapter = adapter(),
case erlang:function_exported(Adapter, superclasses, 1) of
true -> Adapter:superclasses(Class);
false -> related_to(Class, subclass_of)
end.
%%===================================================================
%% Predicate Utilities
%%===================================================================
%% @doc Check if a predicate is a built-in predicate.
-spec is_builtin_predicate(predicate()) -> boolean().
is_builtin_predicate(located_at) -> true;
is_builtin_predicate(contains) -> true;
is_builtin_predicate(adjacent_to) -> true;
is_builtin_predicate(member_of) -> true;
is_builtin_predicate(has_member) -> true;
is_builtin_predicate(manages) -> true;
is_builtin_predicate(managed_by) -> true;
is_builtin_predicate(owns) -> true;
is_builtin_predicate(owned_by) -> true;
is_builtin_predicate(depends_on) -> true;
is_builtin_predicate(depended_on_by) -> true;
is_builtin_predicate(provides) -> true;
is_builtin_predicate(provided_by) -> true;
is_builtin_predicate(consumes) -> true;
is_builtin_predicate(consumed_by) -> true;
is_builtin_predicate(connects_to) -> true;
is_builtin_predicate(trained_on) -> true;
is_builtin_predicate(used_to_train) -> true;
is_builtin_predicate(derived_from) -> true;
is_builtin_predicate(source_of) -> true;
is_builtin_predicate(version_of) -> true;
is_builtin_predicate(instance_of) -> true;
is_builtin_predicate(has_instance) -> true;
is_builtin_predicate(subclass_of) -> true;
is_builtin_predicate(superclass_of) -> true;
is_builtin_predicate(categorized_as) -> true;
is_builtin_predicate(categorizes) -> true;
is_builtin_predicate({custom, _}) -> false;
is_builtin_predicate(_) -> false.
%% @doc Get the inverse of a predicate (for bidirectional queries).
-spec inverse_predicate(predicate()) -> predicate() | undefined.
inverse_predicate(located_at) -> contains;
inverse_predicate(contains) -> located_at;
inverse_predicate(adjacent_to) -> adjacent_to; %% Symmetric
inverse_predicate(member_of) -> has_member;
inverse_predicate(has_member) -> member_of;
inverse_predicate(manages) -> managed_by;
inverse_predicate(managed_by) -> manages;
inverse_predicate(owns) -> owned_by;
inverse_predicate(owned_by) -> owns;
inverse_predicate(depends_on) -> depended_on_by;
inverse_predicate(depended_on_by) -> depends_on;
inverse_predicate(provides) -> provided_by;
inverse_predicate(provided_by) -> provides;
inverse_predicate(consumes) -> consumed_by;
inverse_predicate(consumed_by) -> consumes;
inverse_predicate(connects_to) -> connects_to; %% Symmetric
inverse_predicate(trained_on) -> used_to_train;
inverse_predicate(used_to_train) -> trained_on;
inverse_predicate(derived_from) -> source_of;
inverse_predicate(source_of) -> derived_from;
inverse_predicate(version_of) -> undefined; %% Not symmetric
inverse_predicate(instance_of) -> has_instance;
inverse_predicate(has_instance) -> instance_of;
inverse_predicate(subclass_of) -> superclass_of;
inverse_predicate(superclass_of) -> subclass_of;
inverse_predicate(categorized_as) -> categorizes;
inverse_predicate(categorizes) -> categorized_as;
inverse_predicate({custom, _}) -> undefined;
inverse_predicate(_) -> undefined.