lib/sparql/algebra/expression/join.ex

defmodule SPARQL.Algebra.Join do
  defstruct [:expr1, :expr2]

  alias __MODULE__
  alias SPARQL.Algebra.Expression
  alias SPARQL.Query.Result.SolutionMapping

  def result_set(results1, results2) do
    # TODO: optimization: if variables are disjoint, build cross-product directly, without checking compatibility for every pair (Assuming the variables of all solutions are the same)
    joined_results =
      Enum.reduce(results1, [], fn result1, joined_results ->
        Enum.reduce(results2, joined_results, fn result2, joined_results ->
          if SolutionMapping.compatible?(result1, result2) do
            [SolutionMapping.merge(result1, result2) | joined_results]
          else
            joined_results
          end
        end)
      end)
    joined_variables =
      results1.variables
      |> MapSet.new()
      |> MapSet.union(MapSet.new(results2.variables))
      |> MapSet.to_list()
    SPARQL.Query.Result.new(joined_results, joined_variables)
  end

  defimpl Expression do
    def evaluate(join, data, execution) do
      Join.result_set(
        Expression.evaluate(join.expr1, data, execution),
        Expression.evaluate(join.expr2, data, execution))
    end

# TODO: remove this conditional which is currently needed to work with the unfinished algebra expression
    def variables(%Join{expr1: expr} = join) when expr in [nil, :"$undefined"],
      do: %Join{join | expr1: SPARQL.Algebra.BGP.zero()} |> variables()
    def variables(%Join{expr2: expr} = join) when expr in [nil, :"$undefined"],
        do: %Join{join | expr2: SPARQL.Algebra.BGP.zero()} |> variables()
# END-TODO
    def variables(join) do
      Expression.variables(join.expr1) ++ Expression.variables(join.expr2)
    end
  end
end