Clarity.Graph (Clarity v0.4.0)

View Source

Manages the graph structure.

Summary

Functions

Adds an edge between two vertices.

Gets all unique vertex types present in the graph.

Gets the shortest path between the root and the vertex. Returns false if no path exists.

Clears all vertices and edges from the graph.

Deletes Graph

Gets edge information for a given edge ID.

Gets all edges IDs.

Creates a filtered subgraph using a composable filter. Returns a new Clarity.Graph instance with the filtered vertices and edges.

Gets the shortest path between two vertices. Returns false if no path exists.

Gets the current update count for the graph.

Looks up a vertex struct by its ID.

Transfers ownership of all ETS tables to another process.

Gets the total in-degree for a vertex across all edge types.

Gets the in-degree for a vertex for a specific edge type.

Gets incoming edges for a vertex.

Gets all vertices that are direct sources of incoming edges to a vertex.

Loads a persisted graph from disk.

Gets the direct children of a vertex in the tree graph, grouped by edge label.

Creates a new graph.

Gets the total out-degree for a vertex across all edge types.

Gets the out-degree for a vertex for a specific edge type.

Gets outgoing edges for a vertex.

Gets all vertices that are direct targets of outgoing edges from a vertex.

Persists a graph to disk.

Purges a vertex and all vertices that were caused by it.

Gets the total number of vertices.

Gets all vertices matching the query.

Types

error()

@type error() :: :subgraphs_are_readonly | :not_owner | :file.posix()

query()

@type query() ::
  {:and, query(), query()}
  | {:or, query(), query()}
  | {:not, query()}
  | {:==, query_subject(), term()}
  | {:!=, query_subject(), term()}
  | {:in, query_subject(), [term()]}
  | boolean()

query_subject()

@type query_subject() :: :vertex_type | :vertex_id | {:field, atom()}

result()

@type result() :: :ok | {:error, error()}

result(inner)

@type result(inner) :: {:ok, inner} | {:error, error()}

t()

@opaque t()

The Graph structure.

It is opaque and should be manipulated only via the provided functions.

Functions

add_edge(graph, from_vertex, to_vertex, label)

@spec add_edge(t(), Clarity.Vertex.t(), Clarity.Vertex.t(), :digraph.label()) ::
  result()

Adds an edge between two vertices.

add_vertex(graph, vertex, caused_by)

@spec add_vertex(t(), Clarity.Vertex.t(), Clarity.Vertex.t()) :: result()

Adds a vertex.

available_vertex_types(graph)

@spec available_vertex_types(t()) :: [module()]

Gets all unique vertex types present in the graph.

Returns a list of modules representing the types of vertices in the graph.

clear(graph)

@spec clear(t()) :: result()

Clears all vertices and edges from the graph.

Resets graphs to empty state with root vertex.

delete(graph)

@spec delete(t()) :: result()

Deletes Graph

edge(graph, edge_id)

@spec edge(t(), :digraph.edge()) ::
  {:digraph.edge(), Clarity.Vertex.t() | nil, Clarity.Vertex.t() | nil,
   :digraph.label()}
  | false

Gets edge information for a given edge ID.

edges(graph)

@spec edges(t()) :: [:digraph.edge()]

Gets all edges IDs.

filter(graph, filters)

Creates a filtered subgraph using a composable filter. Returns a new Clarity.Graph instance with the filtered vertices and edges.

Graph Memory Management

Creating a subgraph will create multiple :digraph instances and :ets tables. While the main graph is managed by Clarity, any subgraphs created via this function must be explicitly deleted using delete/1 when no longer needed to free up memory.

Examples

# Single filter
subgraph = Graph.filter(graph, Filter.within_steps(vertex, 2, 1))

# Multiple composed filters
filter = Filter.all([
  Filter.within_steps(vertex, 2, 1),
  Filter.reachable_from([root_vertex])
])
subgraph = Graph.filter(graph, filter)

get_short_path(graph, from_vertex, to_vertex)

@spec get_short_path(t(), Clarity.Vertex.t(), Clarity.Vertex.t()) ::
  [Clarity.Vertex.t()] | false

Gets the shortest path between two vertices. Returns false if no path exists.

get_update_count(graph)

@spec get_update_count(t()) :: pos_integer()

Gets the current update count for the graph.

The count remains stable when no mutations occur and increases monotonically when the graph is modified. Use this for change detection to invalidate cached subgraphs.

Do not rely on specific count values or increment amounts as the internal update mechanism may change.

Example

count1 = Graph.get_update_count(graph)
# ... operations that might modify graph ...
count2 = Graph.get_update_count(graph)

if count2 > count1, do: # graph changed

get_vertex(graph, vertex_id)

@spec get_vertex(t(), String.t()) :: Clarity.Vertex.t() | nil

Looks up a vertex struct by its ID.

handover(graph, pid)

@spec handover(t(), pid()) :: result(t())

Transfers ownership of all ETS tables to another process.

Used to hand over a loaded graph from the Cache process to the Server process. Returns an updated graph struct with the new owner.

in_degree(graph, vertex)

@spec in_degree(t(), Clarity.Vertex.t()) :: non_neg_integer()

Gets the total in-degree for a vertex across all edge types.

in_degree(graph, vertex, label)

@spec in_degree(t(), Clarity.Vertex.t(), :digraph.label()) :: non_neg_integer()

Gets the in-degree for a vertex for a specific edge type.

in_edges(graph, vertex)

@spec in_edges(t(), Clarity.Vertex.t()) :: [:digraph.edge()]

Gets incoming edges for a vertex.

in_neighbors(graph, vertex)

@spec in_neighbors(t(), Clarity.Vertex.t()) :: [Clarity.Vertex.t()]

Gets all vertices that are direct sources of incoming edges to a vertex.

load(path)

@spec load(Path.t()) :: result(t())

Loads a persisted graph from disk.

Security Warning

Only load trusted graphs. ETS tables can contain arbitrary terms including atoms and functions that may crash the VM if malicious.

Returns {:error, posix} on file errors (e.g., :enoent, :eacces).

new()

@spec new() :: t()

Creates a new graph.

out_degree(graph, vertex)

@spec out_degree(t(), Clarity.Vertex.t()) :: non_neg_integer()

Gets the total out-degree for a vertex across all edge types.

out_degree(graph, vertex, label)

@spec out_degree(t(), Clarity.Vertex.t(), :digraph.label()) :: non_neg_integer()

Gets the out-degree for a vertex for a specific edge type.

out_edges(graph, vertex)

@spec out_edges(t(), Clarity.Vertex.t()) :: [:digraph.edge()]

Gets outgoing edges for a vertex.

out_neighbors(graph, vertex)

@spec out_neighbors(t(), Clarity.Vertex.t()) :: [Clarity.Vertex.t()]

Gets all vertices that are direct targets of outgoing edges from a vertex.

pack_digraph(vtab, etab, ntab, cyclic)

@spec pack_digraph(:ets.tid(), :ets.tid(), :ets.tid(), boolean()) :: :digraph.graph()

persist(graph, path)

@spec persist(t(), Path.t()) :: result()

Persists a graph to disk.

The graph must not be a subgraph.

Returns {:error, posix} on file errors (e.g., :enoent, :eacces, :enospc).

purge(graph, vertex)

@spec purge(t(), Clarity.Vertex.t()) :: result([Clarity.Vertex.t()])

Purges a vertex and all vertices that were caused by it.

unpack_digraph(digraph)

@spec unpack_digraph(:digraph.graph()) ::
  {:ets.tid(), :ets.tid(), :ets.tid(), boolean()}

vertex_count(graph)

@spec vertex_count(t()) :: non_neg_integer()

Gets the total number of vertices.

vertices(graph, query \\ true)

@spec vertices(t(), query()) :: [Clarity.Vertex.t()]

Gets all vertices matching the query.

Query Syntax

Queries support complex boolean expressions using operations, operators, and fields.

Operations

  • {:and, query1, query2} - Both queries must match
  • {:or, query1, query2} - Either query must match
  • {:not, query} - Query must not match

Operators

  • {:==, field, value} - Field equals value
  • {:!=, field, value} - Field does not equal value
  • {:in, field, values} - Field is in list of values

Fields

  • :vertex_type - The module of the vertex (e.g., Application, Module)
  • :vertex_id - The unique ID string of the vertex
  • {:field, :field_name} - A field within the vertex struct (e.g., {:field, :app}, {:field, :module})

Examples

# All Application vertices
Graph.vertices(graph, {:==, :vertex_type, Application})

# Application OR Root vertices
Graph.vertices(graph, {:or,
  {:==, :vertex_type, Application},
  {:==, :vertex_type, Root}
})

# Same as above, using :in
Graph.vertices(graph, {:in, :vertex_type, [Application, Root]})

# Complex: Root/Application OR (Module with specific ID)
Graph.vertices(graph, {:or,
  {:in, :vertex_type, [Root, Application]},
  {:and, {:==, :vertex_type, Module}, {:==, :vertex_id, "module:Foo"}}
})

# All vertices except Root
Graph.vertices(graph, {:not, {:==, :vertex_type, Root}})

# Query by field value
Graph.vertices(graph, {:and,
  {:==, :vertex_type, Application},
  {:==, {:field, :app}, :my_app}
})

# All vertices (default)
Graph.vertices(graph, true)
Graph.vertices(graph)