Current section
Files
Jump to
Current section
Files
lib/ontogen/model/store/adapter.ex
defmodule Ontogen.Store.Adapter do
@moduledoc """
A behaviour for SPARQL triple stores hosting an `Ontogen.Repository`.
"""
alias Ontogen.Store.SPARQL.Operation
alias Ontogen.InvalidStoreEndpointError
@type type :: module
@type t :: struct
@type endpoint_url :: String.t() | nil
@type graph_name :: RDF.IRI.t() | nil
@type result :: SPARQL.Query.Result.t() | RDF.Data.t() | nil
@callback determine_query_endpoint(t()) :: {:ok, endpoint_url()} | {:error, any}
@callback determine_update_endpoint(t()) :: {:ok, endpoint_url()} | {:error, any}
@callback determine_graph_store_endpoint(t()) :: {:ok, endpoint_url()} | {:error, any}
@callback dataset_endpoint_segment(t()) :: {:ok, binary()} | {:error, any}
@callback handle_sparql(Operation.t(), t(), graph_name(), keyword) ::
{:ok, result()} | :ok | {:error, any}
defmacro __using__(adapter_spec) do
{dataset_endpoint_segment_template, adapter_spec} =
Keyword.pop(adapter_spec, :dataset_endpoint_segment_template)
{query_endpoint_path, adapter_spec} = Keyword.pop(adapter_spec, :query_endpoint_path)
{update_endpoint_path, adapter_spec} = Keyword.pop(adapter_spec, :update_endpoint_path)
{graph_store_endpoint_path, _adapter_spec} =
Keyword.pop(adapter_spec, :graph_store_endpoint_path)
quote do
@behaviour Ontogen.Store.Adapter
use Ontogen.Bog.Referencable
alias Ontogen.NS.{Og, OgA}
alias Ontogen.Store.GenSPARQL
alias Ontogen.Store.SPARQL.Operation
if unquote(query_endpoint_path) do
@impl true
def determine_query_endpoint(%__MODULE__{} = adapter) do
Ontogen.Store.endpoint_base_with_path(adapter, unquote(query_endpoint_path))
end
defoverridable determine_query_endpoint: 1
end
if unquote(update_endpoint_path) do
@impl true
def determine_update_endpoint(%__MODULE__{} = adapter) do
Ontogen.Store.endpoint_base_with_path(adapter, unquote(update_endpoint_path))
end
defoverridable determine_update_endpoint: 1
end
if unquote(graph_store_endpoint_path) do
@impl true
def determine_graph_store_endpoint(%__MODULE__{} = adapter) do
Ontogen.Store.endpoint_base_with_path(adapter, unquote(graph_store_endpoint_path))
end
defoverridable determine_graph_store_endpoint: 1
end
@impl true
if unquote(dataset_endpoint_segment_template) do
@dataset_endpoint_segment_template unquote(dataset_endpoint_segment_template)
|> YuriTemplate.parse()
|> (case do
{:ok, template} -> template
{:error, error} -> raise error
end)
@dataset_endpoint_segment_template_parameters YuriTemplate.parameters(
@dataset_endpoint_segment_template
)
def dataset_endpoint_segment(%__MODULE__{} = adapter) do
adapter_vars =
adapter
|> Map.from_struct()
|> Keyword.new()
@dataset_endpoint_segment_template_parameters
|> Enum.reject(fn param -> adapter_vars[param] end)
|> case do
[] ->
YuriTemplate.expand(@dataset_endpoint_segment_template, adapter_vars)
missing ->
{:error,
InvalidStoreEndpointError.exception(
"missing dataset template params #{inspect(missing)} on store #{inspect(adapter)}"
)}
end
end
else
def dataset_endpoint_segment(%__MODULE__{} = adapter) do
if :dataset in Map.keys(__MODULE__.__struct__()) do
if dataset = Map.get(adapter, :dataset) do
{:ok, dataset}
else
{:error,
InvalidStoreEndpointError.exception(
"missing :dataset value for store #{inspect(adapter)}"
)}
end
else
{:ok, ""}
end
end
end
defoverridable dataset_endpoint_segment: 1
@impl true
def handle_sparql(
%Operation{} = operation,
%_adapter_type{} = adapter,
graph_name,
opts \\ []
) do
GenSPARQL.handle(operation, adapter, graph_name, opts)
end
defoverridable handle_sparql: 3, handle_sparql: 4
end
end
@doc """
Returns the store adapter name for the given store adapter module.
## Example
iex> Ontogen.Store.Adapter.type_name(Ontogen.Store.Adapters.Fuseki)
"Fuseki"
iex> Ontogen.Store.Adapter.type_name(Ontogen.Store.Adapters.Oxigraph)
"Oxigraph"
iex> Ontogen.Store.Adapter.type_name(Ontogen.Repository)
** (RuntimeError) Invalid Ontogen.Store.Adapter type: Ontogen.Repository
iex> Ontogen.Store.Adapter.type_name(NonExisting)
** (RuntimeError) Invalid Ontogen.Store.Adapter type: NonExisting
"""
@spec type(type()) :: binary
def type_name(type) do
if type?(type) do
case Module.split(type) do
["Ontogen", "Store", "Adapters", name] -> name
_ -> raise "Invalid Ontogen.Store.Adapter type name schema: #{inspect(type)}"
end
else
raise "Invalid Ontogen.Store.Adapter type: #{inspect(type)}"
end
end
@doc """
Returns the `Ontogen.Store.Adapter` module for the given string.
## Example
iex> Ontogen.Store.Adapter.type("Oxigraph")
Ontogen.Store.Adapters.Oxigraph
iex> Ontogen.Store.Adapter.type("Commit")
nil
iex> Ontogen.Store.Adapter.type("NonExisting")
nil
"""
@spec type(binary) :: type() | nil
def type(string) when is_binary(string) do
module = Module.concat(Ontogen.Store.Adapters, string)
if type?(module) do
module
end
end
@doc """
Checks if the given `module` is a `Ontogen.Store.Adapter` module.
"""
@spec type?(module) :: boolean
def type?(module) do
Code.ensure_loaded?(module) and function_exported?(module, :handle_sparql, 4)
end
end