180 lines
4.7 KiB
Elixir
180 lines
4.7 KiB
Elixir
defmodule RDF.Query.Builder do
|
|
@moduledoc false
|
|
|
|
alias RDF.Query.BGP
|
|
alias RDF.{IRI, BlankNode, Literal, Namespace, PropertyMap}
|
|
import RDF.Utils.Guards
|
|
import RDF.Utils
|
|
|
|
def bgp(query, opts \\ []) do
|
|
property_map = if context = Keyword.get(opts, :context), do: PropertyMap.new(context)
|
|
|
|
with {:ok, triple_patterns} <- triple_patterns(query, property_map) do
|
|
{:ok, %BGP{triple_patterns: triple_patterns}}
|
|
end
|
|
end
|
|
|
|
def bgp!(query, opts \\ []) do
|
|
case bgp(query, opts) do
|
|
{:ok, bgp} -> bgp
|
|
{:error, error} -> raise error
|
|
end
|
|
end
|
|
|
|
defp triple_patterns(query, property_map) when is_list(query) or is_map(query) do
|
|
flat_map_while_ok(query, fn triple ->
|
|
with {:ok, triple_pattern} <- triple_patterns(triple, property_map) do
|
|
{:ok, List.wrap(triple_pattern)}
|
|
end
|
|
end)
|
|
end
|
|
|
|
defp triple_patterns({subject, predicate, objects}, property_map) do
|
|
with {:ok, subject_pattern} <- subject_pattern(subject) do
|
|
do_triple_patterns(subject_pattern, {predicate, objects}, property_map)
|
|
end
|
|
end
|
|
|
|
defp triple_patterns({subject, predications}, property_map) when is_map(predications) do
|
|
triple_patterns({subject, Map.to_list(predications)}, property_map)
|
|
end
|
|
|
|
defp triple_patterns({subject, predications}, property_map) do
|
|
with {:ok, subject_pattern} <- subject_pattern(subject) do
|
|
predications
|
|
|> List.wrap()
|
|
|> flat_map_while_ok(&do_triple_patterns(subject_pattern, &1, property_map))
|
|
end
|
|
end
|
|
|
|
defp do_triple_patterns(subject_pattern, {predicate, objects}, property_map) do
|
|
with {:ok, predicate_pattern} <- predicate_pattern(predicate, property_map) do
|
|
objects
|
|
|> List.wrap()
|
|
|> map_while_ok(fn object ->
|
|
with {:ok, object_pattern} <- object_pattern(object) do
|
|
{:ok, {subject_pattern, predicate_pattern, object_pattern}}
|
|
end
|
|
end)
|
|
end
|
|
end
|
|
|
|
defp subject_pattern(subject) do
|
|
value = variable(subject) || resource(subject)
|
|
|
|
if value do
|
|
{:ok, value}
|
|
else
|
|
{:error,
|
|
%RDF.Query.InvalidError{
|
|
message: "Invalid subject term in BGP triple pattern: #{inspect(subject)}"
|
|
}}
|
|
end
|
|
end
|
|
|
|
defp predicate_pattern(predicate, property_map) do
|
|
value = variable(predicate) || resource(predicate) || property(predicate, property_map)
|
|
|
|
if value do
|
|
{:ok, value}
|
|
else
|
|
{:error,
|
|
%RDF.Query.InvalidError{
|
|
message: "Invalid predicate term in BGP triple pattern: #{inspect(predicate)}"
|
|
}}
|
|
end
|
|
end
|
|
|
|
defp object_pattern(object) do
|
|
value = variable(object) || resource(object) || literal(object)
|
|
|
|
if value do
|
|
{:ok, value}
|
|
else
|
|
{:error,
|
|
%RDF.Query.InvalidError{
|
|
message: "Invalid object term in BGP triple pattern: #{inspect(object)}"
|
|
}}
|
|
end
|
|
end
|
|
|
|
defp variable(var) when is_atom(var) do
|
|
var_string = to_string(var)
|
|
|
|
if String.ends_with?(var_string, "?") do
|
|
var_string
|
|
|> String.slice(0..-2)
|
|
|> String.to_atom()
|
|
end
|
|
end
|
|
|
|
defp variable(_), do: nil
|
|
|
|
defp resource(%IRI{} = iri), do: iri
|
|
defp resource(%URI{} = uri), do: IRI.new(uri)
|
|
defp resource(%BlankNode{} = bnode), do: bnode
|
|
|
|
defp resource(var) when is_ordinary_atom(var) do
|
|
case to_string(var) do
|
|
"_" <> bnode ->
|
|
BlankNode.new(bnode)
|
|
|
|
_ ->
|
|
case Namespace.resolve_term(var) do
|
|
{:ok, iri} -> iri
|
|
_ -> nil
|
|
end
|
|
end
|
|
end
|
|
|
|
defp resource(_), do: nil
|
|
|
|
defp property(:a, _), do: RDF.type()
|
|
|
|
defp property(term, property_map) when is_atom(term) and not is_nil(property_map) do
|
|
PropertyMap.iri(property_map, term)
|
|
end
|
|
|
|
defp property(_, _), do: nil
|
|
|
|
defp literal(%Literal{} = literal), do: literal
|
|
defp literal(value), do: Literal.coerce(value)
|
|
|
|
def path(query, opts \\ [])
|
|
|
|
def path(query, _) when is_list(query) and length(query) < 3 do
|
|
{:error,
|
|
%RDF.Query.InvalidError{
|
|
message: "Invalid path expression: must have at least three elements"
|
|
}}
|
|
end
|
|
|
|
def path([subject | rest], opts) do
|
|
path_pattern(subject, rest, [], 0, Keyword.get(opts, :with_elements, false))
|
|
|> bgp(opts)
|
|
end
|
|
|
|
def path!(query, opts \\ []) do
|
|
case path(query, opts) do
|
|
{:ok, bgp} -> bgp
|
|
{:error, error} -> raise error
|
|
end
|
|
end
|
|
|
|
defp path_pattern(subject, [predicate, object], triple_patterns, _, _) do
|
|
[{subject, predicate, object} | triple_patterns]
|
|
|> Enum.reverse()
|
|
end
|
|
|
|
defp path_pattern(subject, [predicate | rest], triple_patterns, count, with_elements) do
|
|
object = if with_elements, do: :"el#{count}?", else: RDF.bnode(count)
|
|
|
|
path_pattern(
|
|
object,
|
|
rest,
|
|
[{subject, predicate, object} | triple_patterns],
|
|
count + 1,
|
|
with_elements
|
|
)
|
|
end
|
|
end
|