Protocol for accessing and traversing RDF data structures.
This protocol is the RDF equivalent of Elixir's Enumerable protocol,
providing a minimal set of functions that enable a rich API in the
RDF.Data module.
Structure Types
Implementations declare one of three structure types:
:description- statements about a single subject:graph- statements in a single named graph:dataset- statements across multiple graphs
Fallback Pattern
Many callbacks can return {:error, __MODULE__} to signal that the default
algorithm in RDF.Data should be used. This allows implementations to
provide optimized versions when possible while falling back to generic
traversal-based implementations.
Summary
Functions
Adds statements to the data structure.
Deletes statements from the data structure.
Derives an empty structure of the desired type from a template.
Gets the description of a specific subject.
Counts unique subjects (descriptions).
Gets a specific graph from the data structure.
Counts graphs.
Returns the graph name when the data represents a single-graph structure.
Returns all graph names in the data structure.
Reduces the RDF data structure into an element.
Counts statements (triples/quads).
Returns the structure type of this implementation.
Returns the subject when the data represents a single-subject structure.
Returns all unique subjects in the data structure.
Types
@type derive_error() :: :no_subject
@type reducer() :: (RDF.Statement.t(), term() -> acc())
@type result() :: {:done, term()} | {:halted, term()} | {:suspended, term(), continuation()}
@type structure_type() :: :description | :graph | :dataset
@type t() :: RDF.Description.t() | RDF.Graph.t() | RDF.Dataset.t() | term()
Functions
@spec add(t(), RDF.Statement.t() | [RDF.Statement.t()]) :: {:ok, t()} | {:error, module()}
Adds statements to the data structure.
Enables efficient statement-based addition without double traversal,
important for operations like merge/2.
Returns {:ok, updated_data} on success.
Returns {:error, __MODULE__} if not supported (falls back to generic implementation).
Cross-type adaptation:
- Quads → Graph: Graph component is removed (becomes triple)
- Triples → Dataset: Go into default graph (nil)
Statements are already coerced when this callback is invoked.
@spec delete(t(), RDF.Statement.t() | [RDF.Statement.t()]) :: {:ok, t()} | {:error, module()}
Deletes statements from the data structure.
Enables efficient statement-based deletion without double traversal.
Returns {:ok, updated_data} on success.
Returns {:error, __MODULE__} if not supported (falls back to generic implementation).
Cross-type adaptation:
- Quads → Graph: Graph component is removed (becomes triple)
- Triples → Dataset: Go into default graph (nil)
Statements are already coerced when this callback is invoked.
@spec derive(t(), structure_type(), keyword()) :: {:ok, t()} | {:error, derive_error()}
Derives an empty structure of the desired type from a template.
This callback serves two purposes:
System preservation: Custom implementations can return their own corresponding structures, keeping operations within the same system of structures.
Metadata inheritance: When
preserve_metadata: true(default), relevant metadata from the template is copied to the new structure.
When is this used?
RDF.Data functions that need to create new structures call this callback to
determine the appropriate target type. Examples include:
RDF.Data.merge/2- merging data of different structure typesRDF.Data.map/2- transforming statements while preserving structure type
Options
:subject- Subject for description. When target is:description:- From Description template: uses template subject, option overrides if provided
- From Graph/Dataset template: required (no template subject available)
:preserve_metadata- Whether to preserve metadata from template (default: true)- Graph → Graph: name, prefixes, base_iri
- Dataset → Dataset: name
Error
Return {:error, :no_subject} when target is :description but no :subject
option was provided and the template has no subject to inherit from (i.e.,
template is a graph or dataset).
@spec description(t(), RDF.Resource.coercible()) :: {:ok, t()} | :error
Gets the description of a specific subject.
Returns {:ok, description} if the subject exists, :error otherwise.
Behavior by structure type:
:description: Returns the Description when its subject matches:graph: Returns the Description for the given subject:dataset: Aggregates descriptions for the subject across all graphs
@spec description_count(t()) :: {:ok, non_neg_integer()} | {:error, module()}
Counts unique subjects (descriptions).
It should return {:ok, count} if you can count the number of subjects
in data in a faster way than fully traversing it.
Otherwise, it should return {:error, __MODULE__} and a default algorithm
built on top of reduce/3 that runs in linear time will be used.
@spec graph(t(), RDF.IRI.coercible() | nil) :: {:ok, t()} | :error
Gets a specific graph from the data structure.
Returns {:ok, graph} if the graph exists, :error otherwise.
Behavior by structure type:
:description: Returns the description as unnamed graph whengraph_nameisnil:graph: Returns the graph when its name matches:dataset: Returns the graph for the given name
@spec graph_count(t()) :: {:ok, non_neg_integer()} | {:error, module()}
Counts graphs.
It should return {:ok, count} if you can count the number of graphs
in data in a faster way than fully traversing it.
Otherwise, it should return {:error, __MODULE__} and a default algorithm
built on top of reduce/3 that runs in linear time will be used.
@spec graph_name(t()) :: RDF.Resource.t() | nil
Returns the graph name when the data represents a single-graph structure.
Behavior by structure type:
:description: Returnsnil(no graph context):graph: Returns the name of the graph (may benilfor unnamed graphs):dataset: Returnsnil(multiple graphs)
Returns all graph names in the data structure.
It should return {:ok, graph_names} if you can collect all graph names
in data in a faster way than fully traversing it.
Otherwise, it should return {:error, __MODULE__} and a default algorithm
built on top of reduce/3 that runs in linear time will be used.
Reduces the RDF data structure into an element.
This is the fundamental operation for traversing RDF data.
@spec statement_count(t()) :: {:ok, non_neg_integer()} | {:error, module()}
Counts statements (triples/quads).
It should return {:ok, count} if you can count the number of statements
in data in a faster way than fully traversing it.
Otherwise, it should return {:error, __MODULE__} and a default algorithm
built on top of reduce/3 that runs in linear time will be used.
@spec structure_type(t()) :: structure_type()
Returns the structure type of this implementation.
Must return one of:
:description- Single subject structure:graph- Multiple subjects, single graph:dataset- Multiple graphs
@spec subject(t()) :: RDF.Resource.t() | nil
Returns the subject when the data represents a single-subject structure.
Behavior by structure type:
:description: Returns the subject of the Description:graph: Returnsnil(multiple subjects):dataset: Returnsnil(multiple subjects)
@spec subjects(t()) :: {:ok, [RDF.Resource.t()]} | {:error, module()}
Returns all unique subjects in the data structure.
It should return {:ok, subjects} if you can collect all subjects
in data in a faster way than fully traversing it.
Otherwise, it should return {:error, __MODULE__} and a default algorithm
built on top of reduce/3 that runs in linear time will be used.