diff --git a/lib/rdf/query/builder.ex b/lib/rdf/query/builder.ex index bad28ed..73a2ad5 100644 --- a/lib/rdf/query/builder.ex +++ b/lib/rdf/query/builder.ex @@ -144,4 +144,35 @@ defmodule RDF.Query.Builder do 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() + 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 diff --git a/test/unit/query/builder_test.exs b/test/unit/query/builder_test.exs index 5bab0dc..be62249 100644 --- a/test/unit/query/builder_test.exs +++ b/test/unit/query/builder_test.exs @@ -150,4 +150,40 @@ defmodule RDF.Query.BuilderTest do bgp [{EX.s, EX.p, EX.o}] end end + + + describe "path/2" do + test "element count == 3" do + assert Builder.path([EX.s, EX.p, EX.o]) == bgp [{EX.s, EX.p, EX.o}] + assert Builder.path([:s?, :p?, :o?]) == bgp [{:s, :p, :o}] + end + + test "element count > 3" do + assert Builder.path([EX.s, EX.p1, EX.p2, EX.o]) == + bgp [ + {EX.s, EX.p1, RDF.bnode("0")}, + {RDF.bnode("0"), EX.p2, EX.o}, + ] + + assert Builder.path([:s?, :p1?, :p2?, :o?]) == + bgp [ + {:s, :p1, RDF.bnode("0")}, + {RDF.bnode("0"), :p2, :o}, + ] + end + + test "element count < 3" do + assert {:error, %RDF.Query.InvalidError{}} = Builder.path([EX.s, EX.p]) + assert {:error, %RDF.Query.InvalidError{}} = Builder.path([EX.s]) + assert {:error, %RDF.Query.InvalidError{}} = Builder.path([]) + end + + test "with_elements: true" do + assert Builder.path([EX.s, EX.p1, EX.p2, :o?], with_elements: true) == + bgp [ + {EX.s, EX.p1, :el0}, + {:el0, EX.p2, :o}, + ] + end + end end