RDF.Data.Source protocol (RDF.ex v3.0.0)

Copy Markdown View Source

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

acc()

@type acc() :: {:cont, term()} | {:halt, term()} | {:suspend, term()}

continuation()

@type continuation() :: (acc() -> result())

derive_error()

@type derive_error() :: :no_subject

reducer()

@type reducer() :: (RDF.Statement.t(), term() -> acc())

result()

@type result() ::
  {:done, term()} | {:halted, term()} | {:suspended, term(), continuation()}

structure_type()

@type structure_type() :: :description | :graph | :dataset

t()

@type t() :: RDF.Description.t() | RDF.Graph.t() | RDF.Dataset.t() | term()

Functions

add(data, statements)

@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.

delete(data, statements)

@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.

derive(template_data, target_type, opts \\ [])

@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:

  1. System preservation: Custom implementations can return their own corresponding structures, keeping operations within the same system of structures.

  2. 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:

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).

description(data, subject)

@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

description_count(data)

@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.

graph(data, graph_name)

@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 when graph_name is nil
  • :graph: Returns the graph when its name matches
  • :dataset: Returns the graph for the given name

graph_count(data)

@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.

graph_name(data)

@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: Returns nil (no graph context)
  • :graph: Returns the name of the graph (may be nil for unnamed graphs)
  • :dataset: Returns nil (multiple graphs)

graph_names(data)

@spec graph_names(t()) :: {:ok, [RDF.IRI.t() | nil]} | {:error, module()}

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.

reduce(data, acc, fun)

@spec reduce(t(), acc(), reducer()) :: result()

Reduces the RDF data structure into an element.

This is the fundamental operation for traversing RDF data.

statement_count(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.

structure_type(data)

@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

subject(data)

@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: Returns nil (multiple subjects)
  • :dataset: Returns nil (multiple subjects)

subjects(data)

@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.