defmodule SPARQL.Algebra.Construct do defstruct [:template, :query] alias SPARQL.Algebra.Expression alias RDF.BlankNode def result(%SPARQL.Query.Result{results: results}, template, generator) do template_bnodes = template_bnodes(template) Enum.reduce results, RDF.Graph.new, fn result, graph -> template_for_solution = template_bnodes |> create_solution_bnodes(generator) |> set_template_solution_bnodes(template) RDF.Graph.add(graph, solve_patterns(template_for_solution, result, generator)) end end defp solve_patterns(template, solutions, generator) do template |> Stream.map(&(solve_pattern(&1, solutions, generator))) |> Enum.filter(&RDF.Triple.valid?/1) end defp solve_pattern({variable, predicate, object}, solutions, generator) when is_binary(variable) do if subject = solutions[variable] do {replace_solved_bnode(subject, solutions, generator), predicate, object} |> solve_pattern(solutions, generator) end end defp solve_pattern({subject, variable, object}, solutions, generator) when is_binary(variable) do if predicate = solutions[variable] do {subject, replace_solved_bnode(predicate, solutions, generator), object} |> solve_pattern(solutions, generator) end end defp solve_pattern({subject, predicate, variable}, solutions, generator) when is_binary(variable) do if object = solutions[variable] do {subject, predicate, replace_solved_bnode(object, solutions, generator)} |> solve_pattern(solutions, generator) end end defp solve_pattern(pattern, _, _), do: pattern defp template_bnodes(template) do Enum.reduce template, MapSet.new, fn pattern, bnodes -> MapSet.union(bnodes, pattern_bnodes(pattern)) end end defp pattern_bnodes(pattern, acc \\ MapSet.new) defp pattern_bnodes({%BlankNode{} = bnode, p, o}, acc), do: pattern_bnodes({nil, p, o}, MapSet.put(acc, bnode)) defp pattern_bnodes({s, %BlankNode{} = bnode, o}, acc), do: pattern_bnodes({s, nil, o}, MapSet.put(acc, bnode)) defp pattern_bnodes({s, p, %BlankNode{} = bnode}, acc), do: pattern_bnodes({s, p, nil}, MapSet.put(acc, bnode)) defp pattern_bnodes(_, acc), do: acc defp create_solution_bnodes(bnodes, generator) do Enum.reduce bnodes, %{}, fn bnode, solution_bnodes -> Map.put(solution_bnodes, bnode, BlankNode.Generator.generate(generator)) end end defp set_template_solution_bnodes(bnodes, template) when map_size(bnodes) == 0, do: template defp set_template_solution_bnodes(bnodes, template) do Enum.map template, &(set_solution_bnodes(&1, bnodes)) end defp set_solution_bnodes({s, p, o}, bnodes) do {set_solution_bnode(s, bnodes), set_solution_bnode(p, bnodes), set_solution_bnode(o, bnodes)} end defp set_solution_bnode(%BlankNode{} = bnode, solution_bnodes), do: solution_bnodes[bnode] defp set_solution_bnode(node, _), do: node defp replace_solved_bnode(%BlankNode{} = bnode, %{__id__: solution_id}, generator) do BlankNode.Generator.generate_for(generator, {:construct, bnode}) end defp replace_solved_bnode(node, _, _), do: node defimpl Expression do def evaluate(construct, data, execution) do Expression.evaluate(construct.query, data, execution) |> SPARQL.Algebra.Construct.result(construct.template, execution.bnode_generator) end def variables(construct) do Expression.variables(construct.query) end end end